blob: d0be3181c142ee682c841bef21d9831c52175c71 [file] [edit]
#!/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()