From 7349dc5cff49cf22c82eb912813e47f30cd7a757 Mon Sep 17 00:00:00 2001 From: Julius Ikkala Date: Sat, 28 Jun 2025 05:39:24 +0300 Subject: Minimal optional constraints (#7422) * Parse optional witness syntax * Allow failing optional constraint * Make `is` work with optional constraint * Allow using optional constraint in checked if statements * Fix tests * Make it work with structs * Fix MSVC build error * Disallow using `as` with optional constraints * Update test to match split is/as errors * Add tests * Fix uninitialized variables in tests * Add tests of incorrect uses & fix related bugs * Mention optional constraints in docs * format code * Fix type unification with NoneWitness * Fix formatting --------- Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com> Co-authored-by: Nathan V. Morrical --- .../generics/where-optional-1.slang | 11 +++ .../generics/where-optional-2.slang | 63 ++++++++++++++ .../generics/where-optional-3.slang | 95 ++++++++++++++++++++++ .../generics/where-optional-4.slang | 15 ++++ .../generics/where-optional-5.slang | 17 ++++ .../language-feature/interface-as-rhs-error.slang | 4 +- 6 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 tests/language-feature/generics/where-optional-1.slang create mode 100644 tests/language-feature/generics/where-optional-2.slang create mode 100644 tests/language-feature/generics/where-optional-3.slang create mode 100644 tests/language-feature/generics/where-optional-4.slang create mode 100644 tests/language-feature/generics/where-optional-5.slang (limited to 'tests/language-feature') diff --git a/tests/language-feature/generics/where-optional-1.slang b/tests/language-feature/generics/where-optional-1.slang new file mode 100644 index 000000000..da4bdaacb --- /dev/null +++ b/tests/language-feature/generics/where-optional-1.slang @@ -0,0 +1,11 @@ +//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): +interface IThing +{ + void thing(); +} + +void f(T t) where optional T: IThing +{ + // Unchecked optional constraint is an error. + t.thing(); // CHECK: error 30403 +} diff --git a/tests/language-feature/generics/where-optional-2.slang b/tests/language-feature/generics/where-optional-2.slang new file mode 100644 index 000000000..67679bd8d --- /dev/null +++ b/tests/language-feature/generics/where-optional-2.slang @@ -0,0 +1,63 @@ +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK): -shaderobj +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK): -vk -shaderobj +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK): -cpu -shaderobj + +//TEST_INPUT:ubuffer(data=[0 0 0 0 0 0 0 0], stride=4):out,name=outputBuffer +RWStructuredBuffer outputBuffer; + +interface IThing +{ + [mutating] + int thing(int index); +} + +struct MyThing: IThing +{ + int val; + + [mutating] + int thing(int index) + { + val++; + outputBuffer[index] = val; + return val; + } +} + +struct NotMyThing +{ + int val; +} + +void f(inout T t, int index) where optional T: IThing +{ + if (T is IThing) + { + outputBuffer[index+1] = 2 * t.thing(index); + } + else + { + outputBuffer[index] = 0; + outputBuffer[index+1] = 0; + } +} + +[numthreads(1, 1, 1)] +void computeMain(int3 dispatchThreadID: SV_DispatchThreadID) +{ + MyThing mt = MyThing(0); + NotMyThing nt = NotMyThing(1); + + // CHECK: 1 + // CHECK-NEXT: 2 + f(mt, 0); + // CHECK-NEXT: 0 + // CHECK-NEXT: 0 + f(nt, 2); + // CHECK: 2 + // CHECK-NEXT: 4 + f(mt, 4); + // CHECK-NEXT: 0 + // CHECK-NEXT: 0 + f(nt, 6); +} diff --git a/tests/language-feature/generics/where-optional-3.slang b/tests/language-feature/generics/where-optional-3.slang new file mode 100644 index 000000000..f8b3a4907 --- /dev/null +++ b/tests/language-feature/generics/where-optional-3.slang @@ -0,0 +1,95 @@ +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK): -shaderobj +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK): -vk -shaderobj +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK): -cpu -shaderobj + +//TEST_INPUT:ubuffer(data=[0 0 0 0 0 0 0 0], stride=4):out,name=outputBuffer +RWStructuredBuffer outputBuffer; + +interface IReleaseable +{ + [mutating] + void release(); +} + +struct Container + where optional K : IReleaseable + where optional V : IReleaseable +{ + K k[2]; + V v[2]; + + [mutating] + void erase(int index) + { + if (K is IReleaseable) + k[index].release(); + if (V is IReleaseable) + v[index].release(); + } +} + +struct HeavyEntry: IReleaseable +{ + int index; + int value; + + [mutating] + void release() + { + outputBuffer[index] = value; + } +}; + +struct LightEntry +{ + int value; +}; + +[numthreads(1, 1, 1)] +void computeMain(int3 dispatchThreadID: SV_DispatchThreadID) +{ + { // Neither is IReleaseable + var c = Container(); + c.k[0] = LightEntry(1); + c.k[1] = LightEntry(2); + c.v[0] = LightEntry(3); + c.v[1] = LightEntry(4); + c.erase(0); + c.erase(1); + } + { // K is IReleaseable + var c = Container(); + c.k[0] = HeavyEntry(0,1); + c.k[1] = HeavyEntry(1,2); + c.v[0] = LightEntry(3); + c.v[1] = LightEntry(4); + // CHECK: 1 + c.erase(0); + // CHECK-NEXT: 2 + c.erase(1); + } + { // V is IReleaseable + var c = Container(); + c.k[0] = LightEntry(1); + c.k[1] = LightEntry(2); + c.v[0] = HeavyEntry(2,3); + c.v[1] = HeavyEntry(3,4); + // CHECK-NEXT: 3 + c.erase(0); + // CHECK-NEXT: 4 + c.erase(1); + } + { // K and V are IReleaseable + var c = Container(); + c.k[0] = HeavyEntry(4,5); + c.k[1] = HeavyEntry(6,7); + c.v[0] = HeavyEntry(5,6); + c.v[1] = HeavyEntry(7,8); + // CHECK-NEXT: 5 + // CHECK-NEXT: 6 + c.erase(0); + // CHECK-NEXT: 7 + // CHECK-NEXT: 8 + c.erase(1); + } +} diff --git a/tests/language-feature/generics/where-optional-4.slang b/tests/language-feature/generics/where-optional-4.slang new file mode 100644 index 000000000..6d72186d9 --- /dev/null +++ b/tests/language-feature/generics/where-optional-4.slang @@ -0,0 +1,15 @@ +//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): +interface IThing +{ + void thing(); +} + +void g(T t) where T: IThing +{ +} + +void f(T t) where optional T: IThing +{ + // Error: cannot upgrade optional to non-optional witness in unchecked context. + g(t); // CHECK: error 38029 +} diff --git a/tests/language-feature/generics/where-optional-5.slang b/tests/language-feature/generics/where-optional-5.slang new file mode 100644 index 000000000..3ce8041ad --- /dev/null +++ b/tests/language-feature/generics/where-optional-5.slang @@ -0,0 +1,17 @@ +//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): +interface IThing +{ + void thing(); +} + +void f(T t, U u) + where optional T: IThing + where optional U: IThing +{ + // Error: cannot upgrade optional to non-optional witness in unchecked context. + if (U is IThing) + { + // U being IThing doesn't justify using T as such! + t.thing(); // CHECK: error 30403 + } +} diff --git a/tests/language-feature/interface-as-rhs-error.slang b/tests/language-feature/interface-as-rhs-error.slang index 9ad71afde..7293b5134 100644 --- a/tests/language-feature/interface-as-rhs-error.slang +++ b/tests/language-feature/interface-as-rhs-error.slang @@ -21,13 +21,13 @@ struct AnotherType // These should produce errors - interface types as RHS bool testIsOperatorWithInterface() { - //CHECK: ([[# @LINE+1]]): error 30301: 'is' and 'as' operators do not support interface types as the right-hand side + //CHECK: ([[# @LINE+1]]): error 30301: cannot use 'is' operator with an interface type as the right-hand side return (T is IMyInterface); } void testAsOperatorWithInterface(T value) { - //CHECK: ([[# @LINE+1]]): error 30301: 'is' and 'as' operators do not support interface types as the right-hand side + //CHECK: ([[# @LINE+1]]): error 30302: cannot use 'as' operator with an interface type as the right-hand side let result = value as IMyInterface; } -- cgit v1.2.3