| #!/usr/bin/env python3 | |
| # Copyright 2026 The Dawn & Tint Authors | |
| # | |
| # Redistribution and use in source and binary forms, with or without | |
| # modification, are permitted provided that the following conditions are met: | |
| # | |
| # 1. Redistributions of source code must retain the above copyright notice, this | |
| # list of conditions and the following disclaimer. | |
| # | |
| # 2. Redistributions in binary form must reproduce the above copyright notice, | |
| # this list of conditions and the following disclaimer in the documentation | |
| # and/or other materials provided with the distribution. | |
| # | |
| # 3. Neither the name of the copyright holder nor the names of its | |
| # contributors may be used to endorse or promote products derived from | |
| # this software without specific prior written permission. | |
| # | |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| """Unit tests for sem.py and resolver.py.""" | |
| import unittest | |
| import parser | |
| import resolver | |
| class ResolverTest(unittest.TestCase): | |
| def test_resolver(self): | |
| success = "" | |
| cases: list[tuple[str, str]] = [ | |
| ("type X", success), | |
| ("enum E {}", success), | |
| ("enum E {A B C}", success), | |
| ("type X", success), | |
| ('@display("Y") type X', success), | |
| ( | |
| """ | |
| type x | |
| match y: x""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| enum e {a b c} | |
| match y: e.c | e.a | e.b""", | |
| success, | |
| ), | |
| ("fn f()", success), | |
| ("implicit(T) fn f()", success), | |
| ( | |
| """ | |
| type f32 | |
| fn f<T>(T) -> f32""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| implicit(N: num) fn f()""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| enum e { a b c } | |
| implicit(N: e) fn f()""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| implicit(T) fn f(T) -> f32""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| implicit(N: num) fn f<T: f32>()""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| implicit(T: f32) fn f(T: f32) -> f32""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| type P<T> | |
| match m: f32 | |
| implicit(T: m) fn f(P<T>) -> T""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| enum e { a } | |
| match m: e.a | |
| fn f(m)""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| enum e { a b } | |
| type T<E: e> | |
| match m: e.a | |
| fn f(T<m>)""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| enum e { a } | |
| type T<E: e> | |
| match m : e.a | |
| fn f(T<m>)""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| enum e { a } | |
| type T<E: e> | |
| match a : e.a | |
| fn f(T<a>)""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type T<E: num> | |
| implicit(E: num) fn f(T<E>)""", | |
| success, | |
| ), | |
| ("implicit(T) fn f(T)", success), | |
| ( | |
| """ | |
| enum e { a b } | |
| implicit(E: e) fn f()""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| enum e { a b } | |
| match m: e.a | e.b | |
| implicit(E: m) fn f()""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| type T<x> | |
| fn f(T< T<f32> >)""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type a | |
| type b | |
| type c | |
| match S: a | b | c | |
| type V<N: num, T> | |
| implicit(N: num, T: S, U: S) fn f<I: V<N, T> >(V<N, U>) -> I""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| op -(f32)""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| type T<x> | |
| op +(T<f32>, T<f32>)""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| ctor f32(f32)""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| type T<x> | |
| ctor f32(T<f32>)""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| type i32 | |
| conv f32(i32)""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| type T<x> | |
| conv f32(T<f32>)""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| @must_use fn f() -> f32""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| @member_function fn f(f32)""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| type P<T> | |
| match m: f32 | |
| fn f(m)""", | |
| success, | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| type P<T> | |
| match m: f32 | |
| fn f(P<m>)""", | |
| success, | |
| ), | |
| ( | |
| "enum E {A A}", | |
| "file.txt:1:11 duplicate enum entry 'A'", | |
| ), | |
| ( | |
| "type X type X", | |
| """file.txt:1:13 'X' already declared | |
| First declared here: file.txt:1:6""", | |
| ), | |
| ( | |
| "@meow type X", | |
| "file.txt:1:2 unknown attribute", | |
| ), | |
| ( | |
| '@display("Y", "Z") type X', | |
| "file.txt:1:2 expected a single value for 'display' attribute", | |
| ), | |
| ( | |
| """ | |
| enum e { a } | |
| enum e { b }""", | |
| """file.txt:2:6 'e' already declared | |
| First declared here: file.txt:1:6""", | |
| ), | |
| ( | |
| """ | |
| type X | |
| match X : X""", | |
| """file.txt:2:7 'X' already declared | |
| First declared here: file.txt:1:6""", | |
| ), | |
| ( | |
| """type T<X> | |
| match M : T""", | |
| "file.txt:2:11 'T' requires 1 template arguments, but 0 were provided", | |
| ), | |
| ( | |
| "match x: y", | |
| "file.txt:1:10 cannot resolve 'y'", | |
| ), | |
| ( | |
| """ | |
| type a | |
| match x: a | b""", | |
| "file.txt:2:14 cannot resolve 'b'", | |
| ), | |
| ( | |
| """ | |
| type a | |
| type b | |
| match x: a | b | a""", | |
| """file.txt:3:18 duplicate option 'a' in matcher | |
| First declared here: file.txt:3:10""", | |
| ), | |
| ( | |
| """ | |
| enum e { a c } | |
| match x: e.a | e.b | e.c""", | |
| "file.txt:2:18 enum 'e' does not contain 'b'", | |
| ), | |
| ( | |
| """ | |
| enum e { a } | |
| match x: e.a | |
| match x: e.a""", | |
| """file.txt:3:7 'x' already declared | |
| First declared here: file.txt:2:7""", | |
| ), | |
| ( | |
| """ | |
| type t | |
| match x: t | |
| match y: x""", | |
| "file.txt:3:10 'x' resolves to type matcher 'x' but type is expected", | |
| ), | |
| ( | |
| "fn f(u)", | |
| "file.txt:1:6 cannot resolve 'u'", | |
| ), | |
| ( | |
| "fn f() -> u", | |
| "file.txt:1:11 cannot resolve 'u'", | |
| ), | |
| ( | |
| "implicit(T: u) fn f()", | |
| "file.txt:1:13 cannot resolve 'u'", | |
| ), | |
| ( | |
| """ | |
| enum e { a } | |
| fn f() -> e""", | |
| "file.txt:2:11 cannot use 'e' as return type. Must be a type or template type", | |
| ), | |
| ( | |
| """ | |
| type T<x> | |
| fn f(T<u>)""", | |
| "file.txt:2:8 cannot resolve 'u'", | |
| ), | |
| ( | |
| """ | |
| type x | |
| implicit(T) fn f(T<x>)""", | |
| "file.txt:2:18 'T' template parameters do not accept template arguments", | |
| ), | |
| ( | |
| """ | |
| type A<N: num> | |
| type B | |
| fn f(A<B>)""", | |
| "file.txt:3:8 cannot use type 'B' as template number", | |
| ), | |
| ( | |
| """ | |
| type T | |
| type P<N: num> | |
| match m: T | |
| fn f(P<m>)""", | |
| "file.txt:4:8 cannot use type matcher 'm' as template number", | |
| ), | |
| ( | |
| """ | |
| type P<N: num> | |
| enum E { b } | |
| fn f(P<E>)""", | |
| "file.txt:3:8 cannot use enum 'E' as template number", | |
| ), | |
| ( | |
| """ | |
| type P<N: num> | |
| enum E { a b } | |
| match m: E.a | E.b | |
| fn f(P<m>)""", | |
| "file.txt:4:8 cannot use enum matcher 'm' as template number", | |
| ), | |
| ( | |
| """ | |
| type P<N: num> | |
| enum E { a b } | |
| match m: E.a | E.b | |
| implicit(M: m) fn f(P<M>)""", | |
| "file.txt:4:23 cannot use template enum 'E' as template number", | |
| ), | |
| ( | |
| """ | |
| type i | |
| enum e { a } | |
| op << (i) -> e""", | |
| "file.txt:3:14 cannot use 'e' as return type. Must be a type or template type", | |
| ), | |
| ( | |
| """ | |
| type T<x> | |
| op << (T<u>)""", | |
| "file.txt:2:10 cannot resolve 'u'", | |
| ), | |
| ( | |
| "op << ()", | |
| "file.txt:1:4 operators must have either 1 or 2 parameters", | |
| ), | |
| ( | |
| """ | |
| type i | |
| op << (i, i, i)""", | |
| "file.txt:2:4 operators must have either 1 or 2 parameters", | |
| ), | |
| ( | |
| """ | |
| type x | |
| implicit(T) op << (T<x>)""", | |
| "file.txt:2:20 'T' template parameters do not accept template arguments", | |
| ), | |
| ( | |
| """ | |
| type A<N: num> | |
| type B | |
| op << (A<B>)""", | |
| "file.txt:3:10 cannot use type 'B' as template number", | |
| ), | |
| ( | |
| """ | |
| type A<N> | |
| enum E { b } | |
| match M: E.b | |
| op << (A<M>)""", | |
| "file.txt:4:10 cannot use enum matcher 'M' as template type", | |
| ), | |
| ( | |
| """ | |
| type T | |
| type P<N: num> | |
| match m: T | |
| op << (P<m>)""", | |
| "file.txt:4:10 cannot use type matcher 'm' as template number", | |
| ), | |
| ( | |
| """ | |
| type P<N: num> | |
| enum E { b } | |
| op << (P<E>)""", | |
| "file.txt:3:10 cannot use enum 'E' as template number", | |
| ), | |
| ( | |
| """ | |
| type P<N: num> | |
| enum E { a b } | |
| match m: E.a | E.b | |
| op << (P<m>)""", | |
| "file.txt:4:10 cannot use enum matcher 'm' as template number", | |
| ), | |
| ( | |
| """ | |
| type P<N: num> | |
| enum E { a b } | |
| match m: E.a | E.b | |
| implicit(M: m) op << (P<M>)""", | |
| "file.txt:4:25 cannot use template enum 'E' as template number", | |
| ), | |
| ( | |
| """ | |
| type i | |
| enum e { a } | |
| ctor F(i) -> e""", | |
| "file.txt:3:14 cannot use 'e' as return type. Must be a type or template type", | |
| ), | |
| ( | |
| """ | |
| type T<x> | |
| ctor F(T<u>)""", | |
| "file.txt:2:10 cannot resolve 'u'", | |
| ), | |
| ( | |
| """ | |
| type x | |
| implicit(T) ctor F(T<x>)""", | |
| "file.txt:2:20 'T' template parameters do not accept template arguments", | |
| ), | |
| ( | |
| """ | |
| type A<N: num> | |
| type B | |
| ctor F(A<B>)""", | |
| "file.txt:3:10 cannot use type 'B' as template number", | |
| ), | |
| ( | |
| """ | |
| type A<N> | |
| enum E { b } | |
| match M: E.b | |
| ctor F(A<M>)""", | |
| "file.txt:4:10 cannot use enum matcher 'M' as template type", | |
| ), | |
| ( | |
| """ | |
| type T | |
| type P<N: num> | |
| match m: T | |
| ctor F(P<m>)""", | |
| "file.txt:4:10 cannot use type matcher 'm' as template number", | |
| ), | |
| ( | |
| """ | |
| type P<N: num> | |
| enum E { b } | |
| ctor F(P<E>)""", | |
| "file.txt:3:10 cannot use enum 'E' as template number", | |
| ), | |
| ( | |
| """ | |
| type P<N: num> | |
| enum E { a b } | |
| match m: E.a | E.b | |
| ctor F(P<m>)""", | |
| "file.txt:4:10 cannot use enum matcher 'm' as template number", | |
| ), | |
| ( | |
| """ | |
| type P<N: num> | |
| enum E { a b } | |
| match m: E.a | E.b | |
| implicit(M: m) ctor F(P<M>)""", | |
| "file.txt:4:25 cannot use template enum 'E' as template number", | |
| ), | |
| ( | |
| "conv F()", | |
| "file.txt:1:6 conversions must have a single parameter", | |
| ), | |
| ( | |
| """ | |
| type i | |
| conv F(i, i, i)""", | |
| "file.txt:2:6 conversions must have a single parameter", | |
| ), | |
| ( | |
| """ | |
| type i | |
| enum e { a } | |
| conv F(i) -> e""", | |
| "file.txt:3:14 cannot use 'e' as return type. Must be a type or template type", | |
| ), | |
| ( | |
| """ | |
| type T<x> | |
| conv F(T<u>)""", | |
| "file.txt:2:10 cannot resolve 'u'", | |
| ), | |
| ( | |
| """ | |
| type x | |
| implicit(T) conv F(T<x>)""", | |
| "file.txt:2:20 'T' template parameters do not accept template arguments", | |
| ), | |
| ( | |
| """ | |
| type A<N: num> | |
| type B | |
| conv F(A<B>)""", | |
| "file.txt:3:10 cannot use type 'B' as template number", | |
| ), | |
| ( | |
| """ | |
| type A<N> | |
| enum E { b } | |
| match M: E.b | |
| conv F(A<M>)""", | |
| "file.txt:4:10 cannot use enum matcher 'M' as template type", | |
| ), | |
| ( | |
| """ | |
| type T | |
| type P<N: num> | |
| match m: T | |
| conv F(P<m>)""", | |
| "file.txt:4:10 cannot use type matcher 'm' as template number", | |
| ), | |
| ( | |
| """ | |
| type P<N: num> | |
| enum E { b } | |
| conv F(P<E>)""", | |
| "file.txt:3:10 cannot use enum 'E' as template number", | |
| ), | |
| ( | |
| """ | |
| type P<N: num> | |
| enum E { a b } | |
| match m: E.a | E.b | |
| conv F(P<m>)""", | |
| "file.txt:4:10 cannot use enum matcher 'm' as template number", | |
| ), | |
| ( | |
| """ | |
| type P<N: num> | |
| enum E { a b } | |
| match m: E.a | E.b | |
| implicit(M: m) conv F(P<M>)""", | |
| "file.txt:4:25 cannot use template enum 'E' as template number", | |
| ), | |
| ( | |
| "@must_use fn f()", | |
| "file.txt:1:2 @must_use can only be used on a function with a return type", | |
| ), | |
| ( | |
| """ | |
| type f32 | |
| @member_function(0) fn f(f32)""", | |
| "file.txt:2:2 unexpected value for member_function attribute", | |
| ), | |
| ( | |
| "@member_function fn f()", | |
| "file.txt:1:2 @member_function can only be used on a function with at least one parameter", | |
| ), | |
| ( | |
| "implicit(T) fn f<T>()", | |
| """file.txt:1:18 'T' already declared | |
| First declared here: file.txt:1:10""", | |
| ), | |
| ( | |
| """ | |
| type bool | |
| fn f(@const bool)""", | |
| "file.txt:2:13 @const parameters must be named", | |
| ), | |
| ] | |
| for src, expect_err in cases: | |
| with self.subTest(src=src): | |
| parsed = parser.parse(src.strip(), "file.txt", {}) | |
| if expect_err == "": | |
| resolved = resolver.resolve(parsed) | |
| self.assertIsNotNone(resolved) | |
| else: | |
| with self.assertRaises(resolver.ResolveError) as ctx: | |
| resolver.resolve(parsed) | |
| self.assertEqual( | |
| str(ctx.exception).strip(), expect_err.strip()) | |
| def test_resolve_real_def_files(self): | |
| for rel_path in ( | |
| "src/tint/lang/core/core.def", | |
| "src/tint/lang/wgsl/wgsl.def", | |
| "src/tint/lang/glsl/glsl.def", | |
| "src/tint/lang/hlsl/hlsl.def", | |
| "src/tint/lang/msl/msl.def", | |
| "src/tint/lang/spirv/spirv.def", | |
| ): | |
| with self.subTest(rel_path=rel_path): | |
| full_path = parser.DAWN_ROOT / rel_path | |
| source = full_path.read_text(encoding="utf-8") | |
| parsed = parser.parse(source, rel_path) | |
| resolved = resolver.resolve(parsed) | |
| self.assertGreater(len(resolved.types), 0) | |
| self.assertGreater(len(resolved.builtins), 0) | |
| self.assertGreater(resolved.max_templates, 0) | |
| if __name__ == "__main__": | |
| unittest.main() |