mirror of
https://github.com/gfx-rs/wgpu.git
synced 2026-04-22 03:02:01 -04:00
Fixes #1745: Support out-of-order module scope declarations in WGSL Fixes #1044: Forbid local variable shadowing in WGSL Fixes #2076: [wgsl-in] no error for duplicated type definition Fixes #2071: Global item does not support 'const' Fixes #2105: [wgsl-in] Type aliases for a vecN<T> doesn't work when constructing vec from a single argument Fixes #1775: Referencing a function without a return type yields an unknown identifier error. Fixes #2089: Error span reported on the declaration of a variable instead of its use Fixes #1996: [wgsl-in] Confusing error: "expected unsigned/signed integer literal, found '1'" Separate parsing from lowering by generating an AST, which desugars as much as possible down to something like Naga IR. The AST is then used to resolve identifiers while lowering to Naga IR. Co-authored-by: Teodor Tanasoaia <28601907+teoxoy@users.noreply.github.com> Co-authored-by: Jim Blandy <jimb@red-bean.com>
1758 lines
40 KiB
Rust
1758 lines
40 KiB
Rust
/*!
|
|
Tests for the WGSL front end.
|
|
*/
|
|
#![cfg(feature = "wgsl-in")]
|
|
|
|
fn check(input: &str, snapshot: &str) {
|
|
let output = naga::front::wgsl::parse_str(input)
|
|
.expect_err("expected parser error")
|
|
.emit_to_string(input);
|
|
if output != snapshot {
|
|
for diff in diff::lines(&output, snapshot) {
|
|
match diff {
|
|
diff::Result::Left(l) => println!("-{}", l),
|
|
diff::Result::Both(l, _) => println!(" {}", l),
|
|
diff::Result::Right(r) => println!("+{}", r),
|
|
}
|
|
}
|
|
panic!("Error snapshot failed");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn reserved_identifier_prefix() {
|
|
check(
|
|
"var __bad;",
|
|
r###"error: Identifier starts with a reserved prefix: '__bad'
|
|
┌─ wgsl:1:5
|
|
│
|
|
1 │ var __bad;
|
|
│ ^^^^^ invalid identifier
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn function_without_identifier() {
|
|
check(
|
|
"fn () {}",
|
|
r###"error: expected identifier, found '('
|
|
┌─ wgsl:1:4
|
|
│
|
|
1 │ fn () {}
|
|
│ ^ expected identifier
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_integer() {
|
|
check(
|
|
"fn foo([location(1.)] x: i32) {}",
|
|
r###"error: expected identifier, found '['
|
|
┌─ wgsl:1:8
|
|
│
|
|
1 │ fn foo([location(1.)] x: i32) {}
|
|
│ ^ expected identifier
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_float() {
|
|
check(
|
|
"const scale: f32 = 1.1.;",
|
|
r###"error: expected identifier, found ';'
|
|
┌─ wgsl:1:24
|
|
│
|
|
1 │ const scale: f32 = 1.1.;
|
|
│ ^ expected identifier
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_texture_sample_type() {
|
|
check(
|
|
"const x: texture_2d<bool>;",
|
|
r###"error: texture sample type must be one of f32, i32 or u32, but found bool
|
|
┌─ wgsl:1:21
|
|
│
|
|
1 │ const x: texture_2d<bool>;
|
|
│ ^^^^ must be one of f32, i32 or u32
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_identifier() {
|
|
check(
|
|
r###"
|
|
fn f(x: f32) -> f32 {
|
|
return x * schmoo;
|
|
}
|
|
"###,
|
|
r###"error: no definition in scope for identifier: 'schmoo'
|
|
┌─ wgsl:3:30
|
|
│
|
|
3 │ return x * schmoo;
|
|
│ ^^^^^^ unknown identifier
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn negative_index() {
|
|
check(
|
|
r#"
|
|
fn main() -> f32 {
|
|
let a = array<f32, 3>(0., 1., 2.);
|
|
return a[-1];
|
|
}
|
|
"#,
|
|
r#"error: expected unsigned integer constant expression, found `-1`
|
|
┌─ wgsl:4:26
|
|
│
|
|
4 │ return a[-1];
|
|
│ ^^ expected unsigned integer
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn bad_texture() {
|
|
check(
|
|
r#"
|
|
@group(0) @binding(0) var sampler1 : sampler;
|
|
|
|
@fragment
|
|
fn main() -> @location(0) vec4<f32> {
|
|
let a = 3;
|
|
return textureSample(a, sampler1, vec2<f32>(0.0));
|
|
}
|
|
"#,
|
|
r#"error: expected an image, but found 'a' which is not an image
|
|
┌─ wgsl:7:38
|
|
│
|
|
7 │ return textureSample(a, sampler1, vec2<f32>(0.0));
|
|
│ ^ not an image
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn bad_type_cast() {
|
|
check(
|
|
r#"
|
|
fn x() -> i32 {
|
|
return i32(vec2<f32>(0.0));
|
|
}
|
|
"#,
|
|
r#"error: cannot cast a vec2<f32> to a i32
|
|
┌─ wgsl:3:28
|
|
│
|
|
3 │ return i32(vec2<f32>(0.0));
|
|
│ ^^^^^^^^^^^^^^ cannot cast a vec2<f32> to a i32
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn type_not_constructible() {
|
|
check(
|
|
r#"
|
|
fn x() {
|
|
_ = atomic<i32>(0);
|
|
}
|
|
"#,
|
|
r#"error: type `atomic` is not constructible
|
|
┌─ wgsl:3:21
|
|
│
|
|
3 │ _ = atomic<i32>(0);
|
|
│ ^^^^^^ type is not constructible
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn type_not_inferrable() {
|
|
check(
|
|
r#"
|
|
fn x() {
|
|
_ = vec2();
|
|
}
|
|
"#,
|
|
r#"error: type can't be inferred
|
|
┌─ wgsl:3:21
|
|
│
|
|
3 │ _ = vec2();
|
|
│ ^^^^ type can't be inferred
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unexpected_constructor_parameters() {
|
|
check(
|
|
r#"
|
|
fn x() {
|
|
_ = i32(0, 1);
|
|
}
|
|
"#,
|
|
r#"error: unexpected components
|
|
┌─ wgsl:3:28
|
|
│
|
|
3 │ _ = i32(0, 1);
|
|
│ ^ unexpected components
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn constructor_parameter_type_mismatch() {
|
|
check(
|
|
r#"
|
|
fn x() {
|
|
_ = mat2x2<f32>(array(0, 1), vec2(2, 3));
|
|
}
|
|
"#,
|
|
r#"error: invalid type for constructor component at index [0]
|
|
┌─ wgsl:3:33
|
|
│
|
|
3 │ _ = mat2x2<f32>(array(0, 1), vec2(2, 3));
|
|
│ ^^^^^^^^^^^ invalid component type
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn bad_texture_sample_type() {
|
|
check(
|
|
r#"
|
|
@group(0) @binding(0) var sampler1 : sampler;
|
|
@group(0) @binding(1) var texture : texture_2d<bool>;
|
|
|
|
@fragment
|
|
fn main() -> @location(0) vec4<f32> {
|
|
return textureSample(texture, sampler1, vec2<f32>(0.0));
|
|
}
|
|
"#,
|
|
r#"error: texture sample type must be one of f32, i32 or u32, but found bool
|
|
┌─ wgsl:3:60
|
|
│
|
|
3 │ @group(0) @binding(1) var texture : texture_2d<bool>;
|
|
│ ^^^^ must be one of f32, i32 or u32
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn bad_for_initializer() {
|
|
check(
|
|
r#"
|
|
fn x() {
|
|
for ({};;) {}
|
|
}
|
|
"#,
|
|
r#"error: for(;;) initializer is not an assignment or a function call: '{}'
|
|
┌─ wgsl:3:22
|
|
│
|
|
3 │ for ({};;) {}
|
|
│ ^^ not an assignment or function call
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_storage_class() {
|
|
check(
|
|
r#"
|
|
@group(0) @binding(0) var<bad> texture: texture_2d<f32>;
|
|
"#,
|
|
r#"error: unknown address space: 'bad'
|
|
┌─ wgsl:2:39
|
|
│
|
|
2 │ @group(0) @binding(0) var<bad> texture: texture_2d<f32>;
|
|
│ ^^^ unknown address space
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_attribute() {
|
|
check(
|
|
r#"
|
|
@a
|
|
fn x() {}
|
|
"#,
|
|
r#"error: unknown attribute: 'a'
|
|
┌─ wgsl:2:14
|
|
│
|
|
2 │ @a
|
|
│ ^ unknown attribute
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_built_in() {
|
|
check(
|
|
r#"
|
|
fn x(@builtin(unknown_built_in) y: u32) {}
|
|
"#,
|
|
r#"error: unknown builtin: 'unknown_built_in'
|
|
┌─ wgsl:2:27
|
|
│
|
|
2 │ fn x(@builtin(unknown_built_in) y: u32) {}
|
|
│ ^^^^^^^^^^^^^^^^ unknown builtin
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_access() {
|
|
check(
|
|
r#"
|
|
var<storage,unknown_access> x: array<u32>;
|
|
"#,
|
|
r#"error: unknown access: 'unknown_access'
|
|
┌─ wgsl:2:25
|
|
│
|
|
2 │ var<storage,unknown_access> x: array<u32>;
|
|
│ ^^^^^^^^^^^^^^ unknown access
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_ident() {
|
|
check(
|
|
r#"
|
|
fn main() {
|
|
let a = b;
|
|
}
|
|
"#,
|
|
r#"error: no definition in scope for identifier: 'b'
|
|
┌─ wgsl:3:25
|
|
│
|
|
3 │ let a = b;
|
|
│ ^ unknown identifier
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_scalar_type() {
|
|
check(
|
|
r#"
|
|
const a: vec2<something>;
|
|
"#,
|
|
r#"error: unknown scalar type: 'something'
|
|
┌─ wgsl:2:27
|
|
│
|
|
2 │ const a: vec2<something>;
|
|
│ ^^^^^^^^^ unknown scalar type
|
|
│
|
|
= note: Valid scalar types are f32, f64, i32, u32, bool
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_type() {
|
|
check(
|
|
r#"
|
|
const a: Vec = 10;
|
|
"#,
|
|
r#"error: unknown type: 'Vec'
|
|
┌─ wgsl:2:22
|
|
│
|
|
2 │ const a: Vec = 10;
|
|
│ ^^^ unknown type
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_storage_format() {
|
|
check(
|
|
r#"
|
|
const storage1: texture_storage_1d<rgba>;
|
|
"#,
|
|
r#"error: unknown storage format: 'rgba'
|
|
┌─ wgsl:2:48
|
|
│
|
|
2 │ const storage1: texture_storage_1d<rgba>;
|
|
│ ^^^^ unknown storage format
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_conservative_depth() {
|
|
check(
|
|
r#"
|
|
@early_depth_test(abc) fn main() {}
|
|
"#,
|
|
r#"error: unknown conservative depth: 'abc'
|
|
┌─ wgsl:2:31
|
|
│
|
|
2 │ @early_depth_test(abc) fn main() {}
|
|
│ ^^^ unknown conservative depth
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn struct_member_size_too_low() {
|
|
check(
|
|
r#"
|
|
struct Bar {
|
|
@size(0) data: array<f32>
|
|
}
|
|
"#,
|
|
r#"error: struct member size must be at least 4
|
|
┌─ wgsl:3:23
|
|
│
|
|
3 │ @size(0) data: array<f32>
|
|
│ ^ must be at least 4
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn struct_member_align_too_low() {
|
|
check(
|
|
r#"
|
|
struct Bar {
|
|
@align(8) data: vec3<f32>
|
|
}
|
|
"#,
|
|
r#"error: struct member alignment must be at least 16
|
|
┌─ wgsl:3:24
|
|
│
|
|
3 │ @align(8) data: vec3<f32>
|
|
│ ^ must be at least 16
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn struct_member_non_po2_align() {
|
|
check(
|
|
r#"
|
|
struct Bar {
|
|
@align(7) data: array<f32>
|
|
}
|
|
"#,
|
|
r#"error: struct member alignment must be a power of 2
|
|
┌─ wgsl:3:24
|
|
│
|
|
3 │ @align(7) data: array<f32>
|
|
│ ^ must be a power of 2
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn inconsistent_binding() {
|
|
check(
|
|
r#"
|
|
fn foo(@builtin(vertex_index) @location(0) x: u32) {}
|
|
"#,
|
|
r#"error: input/output binding is not consistent
|
|
┌─ wgsl:2:16
|
|
│
|
|
2 │ fn foo(@builtin(vertex_index) @location(0) x: u32) {}
|
|
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ input/output binding is not consistent
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_local_function() {
|
|
check(
|
|
r#"
|
|
fn x() {
|
|
for (a();;) {}
|
|
}
|
|
"#,
|
|
r#"error: no definition in scope for identifier: 'a'
|
|
┌─ wgsl:3:22
|
|
│
|
|
3 │ for (a();;) {}
|
|
│ ^ unknown identifier
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn let_type_mismatch() {
|
|
check(
|
|
r#"
|
|
const x: i32 = 1.0;
|
|
"#,
|
|
r#"error: the type of `x` is expected to be `i32`, but got `f32`
|
|
┌─ wgsl:2:19
|
|
│
|
|
2 │ const x: i32 = 1.0;
|
|
│ ^ definition of `x`
|
|
|
|
"#,
|
|
);
|
|
|
|
check(
|
|
r#"
|
|
fn foo() {
|
|
let x: f32 = true;
|
|
}
|
|
"#,
|
|
r#"error: the type of `x` is expected to be `f32`, but got `bool`
|
|
┌─ wgsl:3:21
|
|
│
|
|
3 │ let x: f32 = true;
|
|
│ ^ definition of `x`
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn var_type_mismatch() {
|
|
check(
|
|
r#"
|
|
fn foo() {
|
|
var x: f32 = 1u;
|
|
}
|
|
"#,
|
|
r#"error: the type of `x` is expected to be `f32`, but got `u32`
|
|
┌─ wgsl:3:21
|
|
│
|
|
3 │ var x: f32 = 1u;
|
|
│ ^ definition of `x`
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn local_var_missing_type() {
|
|
check(
|
|
r#"
|
|
fn foo() {
|
|
var x;
|
|
}
|
|
"#,
|
|
r#"error: variable `x` needs a type
|
|
┌─ wgsl:3:21
|
|
│
|
|
3 │ var x;
|
|
│ ^ definition of `x`
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn postfix_pointers() {
|
|
check(
|
|
r#"
|
|
fn main() {
|
|
var v: vec4<f32> = vec4<f32>(1.0, 1.0, 1.0, 1.0);
|
|
let pv = &v;
|
|
let a = *pv[3]; // Problematic line
|
|
}
|
|
"#,
|
|
r#"error: the value indexed by a `[]` subscripting expression must not be a pointer
|
|
┌─ wgsl:5:26
|
|
│
|
|
5 │ let a = *pv[3]; // Problematic line
|
|
│ ^^ expression is a pointer
|
|
|
|
"#,
|
|
);
|
|
|
|
check(
|
|
r#"
|
|
struct S { m: i32 };
|
|
fn main() {
|
|
var s: S = S(42);
|
|
let ps = &s;
|
|
let a = *ps.m; // Problematic line
|
|
}
|
|
"#,
|
|
r#"error: the value accessed by a `.member` expression must not be a pointer
|
|
┌─ wgsl:6:26
|
|
│
|
|
6 │ let a = *ps.m; // Problematic line
|
|
│ ^^ expression is a pointer
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn reserved_keyword() {
|
|
// global var
|
|
check(
|
|
r#"
|
|
var bool: bool = true;
|
|
"#,
|
|
r###"error: name `bool` is a reserved keyword
|
|
┌─ wgsl:2:17
|
|
│
|
|
2 │ var bool: bool = true;
|
|
│ ^^^^ definition of `bool`
|
|
|
|
"###,
|
|
);
|
|
|
|
// global constant
|
|
check(
|
|
r#"
|
|
const break: bool = true;
|
|
fn foo() {
|
|
var foo = break;
|
|
}
|
|
"#,
|
|
r###"error: name `break` is a reserved keyword
|
|
┌─ wgsl:2:19
|
|
│
|
|
2 │ const break: bool = true;
|
|
│ ^^^^^ definition of `break`
|
|
|
|
"###,
|
|
);
|
|
|
|
// local let
|
|
check(
|
|
r#"
|
|
fn foo() {
|
|
let atomic: f32 = 1.0;
|
|
}
|
|
"#,
|
|
r###"error: name `atomic` is a reserved keyword
|
|
┌─ wgsl:3:21
|
|
│
|
|
3 │ let atomic: f32 = 1.0;
|
|
│ ^^^^^^ definition of `atomic`
|
|
|
|
"###,
|
|
);
|
|
|
|
// local var
|
|
check(
|
|
r#"
|
|
fn foo() {
|
|
var sampler: f32 = 1.0;
|
|
}
|
|
"#,
|
|
r###"error: name `sampler` is a reserved keyword
|
|
┌─ wgsl:3:21
|
|
│
|
|
3 │ var sampler: f32 = 1.0;
|
|
│ ^^^^^^^ definition of `sampler`
|
|
|
|
"###,
|
|
);
|
|
|
|
// fn name
|
|
check(
|
|
r#"
|
|
fn break() {}
|
|
"#,
|
|
r###"error: name `break` is a reserved keyword
|
|
┌─ wgsl:2:16
|
|
│
|
|
2 │ fn break() {}
|
|
│ ^^^^^ definition of `break`
|
|
|
|
"###,
|
|
);
|
|
|
|
// struct
|
|
check(
|
|
r#"
|
|
struct array {}
|
|
"#,
|
|
r###"error: name `array` is a reserved keyword
|
|
┌─ wgsl:2:20
|
|
│
|
|
2 │ struct array {}
|
|
│ ^^^^^ definition of `array`
|
|
|
|
"###,
|
|
);
|
|
|
|
// struct member
|
|
check(
|
|
r#"
|
|
struct Foo { sampler: f32 }
|
|
"#,
|
|
r###"error: name `sampler` is a reserved keyword
|
|
┌─ wgsl:2:26
|
|
│
|
|
2 │ struct Foo { sampler: f32 }
|
|
│ ^^^^^^^ definition of `sampler`
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn module_scope_identifier_redefinition() {
|
|
// const
|
|
check(
|
|
r#"
|
|
const foo: bool = true;
|
|
const foo: bool = true;
|
|
"#,
|
|
r###"error: redefinition of `foo`
|
|
┌─ wgsl:2:19
|
|
│
|
|
2 │ const foo: bool = true;
|
|
│ ^^^ previous definition of `foo`
|
|
3 │ const foo: bool = true;
|
|
│ ^^^ redefinition of `foo`
|
|
|
|
"###,
|
|
);
|
|
// var
|
|
check(
|
|
r#"
|
|
var foo: bool = true;
|
|
var foo: bool = true;
|
|
"#,
|
|
r###"error: redefinition of `foo`
|
|
┌─ wgsl:2:17
|
|
│
|
|
2 │ var foo: bool = true;
|
|
│ ^^^ previous definition of `foo`
|
|
3 │ var foo: bool = true;
|
|
│ ^^^ redefinition of `foo`
|
|
|
|
"###,
|
|
);
|
|
|
|
// let and var
|
|
check(
|
|
r#"
|
|
var foo: bool = true;
|
|
const foo: bool = true;
|
|
"#,
|
|
r###"error: redefinition of `foo`
|
|
┌─ wgsl:2:17
|
|
│
|
|
2 │ var foo: bool = true;
|
|
│ ^^^ previous definition of `foo`
|
|
3 │ const foo: bool = true;
|
|
│ ^^^ redefinition of `foo`
|
|
|
|
"###,
|
|
);
|
|
|
|
// function
|
|
check(
|
|
r#"fn foo() {}
|
|
fn bar() {}
|
|
fn foo() {}"#,
|
|
r###"error: redefinition of `foo`
|
|
┌─ wgsl:1:4
|
|
│
|
|
1 │ fn foo() {}
|
|
│ ^^^ previous definition of `foo`
|
|
2 │ fn bar() {}
|
|
3 │ fn foo() {}
|
|
│ ^^^ redefinition of `foo`
|
|
|
|
"###,
|
|
);
|
|
|
|
// let and function
|
|
check(
|
|
r#"
|
|
const foo: bool = true;
|
|
fn foo() {}
|
|
"#,
|
|
r###"error: redefinition of `foo`
|
|
┌─ wgsl:2:19
|
|
│
|
|
2 │ const foo: bool = true;
|
|
│ ^^^ previous definition of `foo`
|
|
3 │ fn foo() {}
|
|
│ ^^^ redefinition of `foo`
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn matrix_with_bad_type() {
|
|
check(
|
|
r#"
|
|
fn main() {
|
|
let m = mat2x2<i32>();
|
|
}
|
|
"#,
|
|
r#"error: matrix scalar type must be floating-point, but found `i32`
|
|
┌─ wgsl:3:32
|
|
│
|
|
3 │ let m = mat2x2<i32>();
|
|
│ ^^^ must be floating-point (e.g. `f32`)
|
|
|
|
"#,
|
|
);
|
|
|
|
check(
|
|
r#"
|
|
fn main() {
|
|
let m: mat3x3<i32>;
|
|
}
|
|
"#,
|
|
r#"error: matrix scalar type must be floating-point, but found `i32`
|
|
┌─ wgsl:3:31
|
|
│
|
|
3 │ let m: mat3x3<i32>;
|
|
│ ^^^ must be floating-point (e.g. `f32`)
|
|
|
|
"#,
|
|
);
|
|
}
|
|
|
|
/// Check the result of validating a WGSL program against a pattern.
|
|
///
|
|
/// Unless you are generating code programmatically, the
|
|
/// `check_validation_error` macro will probably be more convenient to
|
|
/// use.
|
|
macro_rules! check_one_validation {
|
|
( $source:expr, $pattern:pat $( if $guard:expr )? ) => {
|
|
let source = $source;
|
|
let error = validation_error($source);
|
|
if ! matches!(&error, $pattern $( if $guard )? ) {
|
|
eprintln!("validation error does not match pattern:\n\
|
|
source code: {}\n\
|
|
\n\
|
|
actual result:\n\
|
|
{:#?}\n\
|
|
\n\
|
|
expected match for pattern:\n\
|
|
{}",
|
|
&source,
|
|
error,
|
|
stringify!($pattern));
|
|
$( eprintln!("if {}", stringify!($guard)); )?
|
|
panic!("validation error does not match pattern");
|
|
}
|
|
}
|
|
}
|
|
|
|
macro_rules! check_validation {
|
|
// We want to support an optional guard expression after the pattern, so
|
|
// that we can check values we can't match against, like strings.
|
|
// Unfortunately, we can't simply include `$( if $guard:expr )?` in the
|
|
// pattern, because Rust treats `?` as a repetition operator, and its count
|
|
// (0 or 1) will not necessarily match `$source`.
|
|
( $( $source:literal ),* : $pattern:pat ) => {
|
|
$(
|
|
check_one_validation!($source, $pattern);
|
|
)*
|
|
};
|
|
( $( $source:literal ),* : $pattern:pat if $guard:expr ) => {
|
|
$(
|
|
check_one_validation!($source, $pattern if $guard);
|
|
)*
|
|
}
|
|
}
|
|
|
|
fn validation_error(source: &str) -> Result<naga::valid::ModuleInfo, naga::valid::ValidationError> {
|
|
let module = match naga::front::wgsl::parse_str(source) {
|
|
Ok(module) => module,
|
|
Err(err) => {
|
|
eprintln!("WGSL parse failed:");
|
|
panic!("{}", err.emit_to_string(source));
|
|
}
|
|
};
|
|
naga::valid::Validator::new(
|
|
naga::valid::ValidationFlags::all(),
|
|
naga::valid::Capabilities::empty(),
|
|
)
|
|
.validate(&module)
|
|
.map_err(|e| e.into_inner()) // TODO: Add tests for spans, too?
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_arrays() {
|
|
check_validation! {
|
|
"type Bad = array<array<f32>, 4>;",
|
|
"type Bad = array<sampler, 4>;",
|
|
"type Bad = array<texture_2d<f32>, 4>;":
|
|
Err(naga::valid::ValidationError::Type {
|
|
source: naga::valid::TypeError::InvalidArrayBaseType(_),
|
|
..
|
|
})
|
|
}
|
|
|
|
check_validation! {
|
|
"type Bad = array<f32, true>;",
|
|
r#"
|
|
const length: f32 = 2.718;
|
|
type Bad = array<f32, length>;
|
|
"#:
|
|
Err(naga::valid::ValidationError::Type {
|
|
source: naga::valid::TypeError::InvalidArraySizeConstant(_),
|
|
..
|
|
})
|
|
}
|
|
|
|
check_validation! {
|
|
"type Bad = array<f32, 0>;",
|
|
"type Bad = array<f32, -1>;":
|
|
Err(naga::valid::ValidationError::Type {
|
|
source: naga::valid::TypeError::NonPositiveArrayLength(_),
|
|
..
|
|
})
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_structs() {
|
|
check_validation! {
|
|
"struct Bad { data: sampler }",
|
|
"struct Bad { data: texture_2d<f32> }":
|
|
Err(naga::valid::ValidationError::Type {
|
|
source: naga::valid::TypeError::InvalidData(_),
|
|
..
|
|
})
|
|
}
|
|
|
|
check_validation! {
|
|
"struct Bad { data: array<f32>, other: f32, }":
|
|
Err(naga::valid::ValidationError::Type {
|
|
source: naga::valid::TypeError::InvalidDynamicArray(_, _),
|
|
..
|
|
})
|
|
}
|
|
|
|
check_validation! {
|
|
"struct Empty {}":
|
|
Err(naga::valid::ValidationError::Type {
|
|
source: naga::valid::TypeError::EmptyStruct,
|
|
..
|
|
})
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_functions() {
|
|
check_validation! {
|
|
"fn unacceptable_unsized(arg: array<f32>) { }",
|
|
"
|
|
struct Unsized { data: array<f32> }
|
|
fn unacceptable_unsized(arg: Unsized) { }
|
|
":
|
|
Err(naga::valid::ValidationError::Function {
|
|
name: function_name,
|
|
source: naga::valid::FunctionError::InvalidArgumentType {
|
|
index: 0,
|
|
name: argument_name,
|
|
},
|
|
..
|
|
})
|
|
if function_name == "unacceptable_unsized" && argument_name == "arg"
|
|
}
|
|
|
|
// Pointer's address space cannot hold unsized data.
|
|
check_validation! {
|
|
"fn unacceptable_unsized(arg: ptr<workgroup, array<f32>>) { }",
|
|
"
|
|
struct Unsized { data: array<f32> }
|
|
fn unacceptable_unsized(arg: ptr<workgroup, Unsized>) { }
|
|
":
|
|
Err(naga::valid::ValidationError::Type {
|
|
source: naga::valid::TypeError::InvalidPointerToUnsized {
|
|
base: _,
|
|
space: naga::AddressSpace::WorkGroup { .. },
|
|
},
|
|
..
|
|
})
|
|
}
|
|
|
|
// Pointers of these storage classes cannot be passed as arguments.
|
|
check_validation! {
|
|
"fn unacceptable_ptr_space(arg: ptr<storage, array<f32>>) { }":
|
|
Err(naga::valid::ValidationError::Function {
|
|
name: function_name,
|
|
source: naga::valid::FunctionError::InvalidArgumentPointerSpace {
|
|
index: 0,
|
|
name: argument_name,
|
|
space: naga::AddressSpace::Storage { .. },
|
|
},
|
|
..
|
|
})
|
|
if function_name == "unacceptable_ptr_space" && argument_name == "arg"
|
|
}
|
|
|
|
check_validation! {
|
|
"fn unacceptable_ptr_space(arg: ptr<uniform, f32>) { }":
|
|
Err(naga::valid::ValidationError::Function {
|
|
name: function_name,
|
|
source: naga::valid::FunctionError::InvalidArgumentPointerSpace {
|
|
index: 0,
|
|
name: argument_name,
|
|
space: naga::AddressSpace::Uniform,
|
|
},
|
|
..
|
|
})
|
|
if function_name == "unacceptable_ptr_space" && argument_name == "arg"
|
|
}
|
|
|
|
check_validation! {
|
|
"
|
|
struct AFloat {
|
|
said_float: f32
|
|
};
|
|
@group(0) @binding(0)
|
|
var<storage> float: AFloat;
|
|
|
|
fn return_pointer() -> ptr<storage, f32> {
|
|
return &float.said_float;
|
|
}
|
|
":
|
|
Err(naga::valid::ValidationError::Function {
|
|
name: function_name,
|
|
source: naga::valid::FunctionError::NonConstructibleReturnType,
|
|
..
|
|
})
|
|
if function_name == "return_pointer"
|
|
}
|
|
|
|
check_validation! {
|
|
"
|
|
@group(0) @binding(0)
|
|
var<storage> atom: atomic<u32>;
|
|
|
|
fn return_atomic() -> atomic<u32> {
|
|
return atom;
|
|
}
|
|
":
|
|
Err(naga::valid::ValidationError::Function {
|
|
name: function_name,
|
|
source: naga::valid::FunctionError::NonConstructibleReturnType,
|
|
..
|
|
})
|
|
if function_name == "return_atomic"
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn pointer_type_equivalence() {
|
|
check_validation! {
|
|
r#"
|
|
fn f(pv: ptr<function, vec2<f32>>, pf: ptr<function, f32>) { }
|
|
|
|
fn g() {
|
|
var m: mat2x2<f32>;
|
|
let pv: ptr<function, vec2<f32>> = &m.x;
|
|
let pf: ptr<function, f32> = &m.x.x;
|
|
|
|
f(pv, pf);
|
|
}
|
|
"#:
|
|
Ok(_)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn missing_bindings() {
|
|
check_validation! {
|
|
"
|
|
@vertex
|
|
fn vertex(_input: vec4<f32>) -> @location(0) vec4<f32> {
|
|
return _input;
|
|
}
|
|
":
|
|
Err(naga::valid::ValidationError::EntryPoint {
|
|
stage: naga::ShaderStage::Vertex,
|
|
source: naga::valid::EntryPointError::Argument(
|
|
0,
|
|
naga::valid::VaryingError::MissingBinding,
|
|
),
|
|
..
|
|
})
|
|
}
|
|
|
|
check_validation! {
|
|
"
|
|
@vertex
|
|
fn vertex(@location(0) _input: vec4<f32>, more_input: f32) -> @location(0) vec4<f32> {
|
|
return _input + more_input;
|
|
}
|
|
":
|
|
Err(naga::valid::ValidationError::EntryPoint {
|
|
stage: naga::ShaderStage::Vertex,
|
|
source: naga::valid::EntryPointError::Argument(
|
|
1,
|
|
naga::valid::VaryingError::MissingBinding,
|
|
),
|
|
..
|
|
})
|
|
}
|
|
|
|
check_validation! {
|
|
"
|
|
@vertex
|
|
fn vertex(@location(0) _input: vec4<f32>) -> vec4<f32> {
|
|
return _input;
|
|
}
|
|
":
|
|
Err(naga::valid::ValidationError::EntryPoint {
|
|
stage: naga::ShaderStage::Vertex,
|
|
source: naga::valid::EntryPointError::Result(
|
|
naga::valid::VaryingError::MissingBinding,
|
|
),
|
|
..
|
|
})
|
|
}
|
|
|
|
check_validation! {
|
|
"
|
|
struct VertexIn {
|
|
@location(0) pos: vec4<f32>,
|
|
uv: vec2<f32>
|
|
}
|
|
|
|
@vertex
|
|
fn vertex(_input: VertexIn) -> @location(0) vec4<f32> {
|
|
return _input.pos;
|
|
}
|
|
":
|
|
Err(naga::valid::ValidationError::EntryPoint {
|
|
stage: naga::ShaderStage::Vertex,
|
|
source: naga::valid::EntryPointError::Argument(
|
|
0,
|
|
naga::valid::VaryingError::MemberMissingBinding(1),
|
|
),
|
|
..
|
|
})
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_access() {
|
|
check_validation! {
|
|
"
|
|
fn array_by_value(a: array<i32, 5>, i: i32) -> i32 {
|
|
return a[i];
|
|
}
|
|
",
|
|
"
|
|
fn matrix_by_value(m: mat4x4<f32>, i: i32) -> vec4<f32> {
|
|
return m[i];
|
|
}
|
|
":
|
|
Err(naga::valid::ValidationError::Function {
|
|
source: naga::valid::FunctionError::Expression {
|
|
source: naga::valid::ExpressionError::IndexMustBeConstant(_),
|
|
..
|
|
},
|
|
..
|
|
})
|
|
}
|
|
|
|
check_validation! {
|
|
r#"
|
|
fn main() -> f32 {
|
|
let a = array<f32, 3>(0., 1., 2.);
|
|
return a[3];
|
|
}
|
|
"#:
|
|
Err(naga::valid::ValidationError::Function {
|
|
source: naga::valid::FunctionError::Expression {
|
|
source: naga::valid::ExpressionError::IndexOutOfBounds(_, _),
|
|
..
|
|
},
|
|
..
|
|
})
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn valid_access() {
|
|
check_validation! {
|
|
"
|
|
fn vector_by_value(v: vec4<i32>, i: i32) -> i32 {
|
|
return v[i];
|
|
}
|
|
",
|
|
"
|
|
fn matrix_dynamic(m: mat4x4<f32>, i: i32, j: i32) -> f32 {
|
|
var temp: mat4x4<f32> = m;
|
|
// Dynamically indexing the column vector applies
|
|
// `Access` to a `ValuePointer`.
|
|
return temp[i][j];
|
|
}
|
|
",
|
|
"
|
|
fn main() {
|
|
var v: vec4<f32> = vec4<f32>(1.0, 1.0, 1.0, 1.0);
|
|
let pv = &v;
|
|
let a = (*pv)[3];
|
|
}
|
|
":
|
|
Ok(_)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_local_vars() {
|
|
check_validation! {
|
|
"
|
|
struct Unsized { data: array<f32> }
|
|
fn local_ptr_dynamic_array(okay: ptr<storage, Unsized>) {
|
|
var not_okay: ptr<storage, array<f32>> = &(*okay).data;
|
|
}
|
|
":
|
|
Err(naga::valid::ValidationError::Function {
|
|
source: naga::valid::FunctionError::LocalVariable {
|
|
name: local_var_name,
|
|
source: naga::valid::LocalVariableError::InvalidType(_),
|
|
..
|
|
},
|
|
..
|
|
})
|
|
if local_var_name == "not_okay"
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn dead_code() {
|
|
check_validation! {
|
|
"
|
|
fn dead_code_after_if(condition: bool) -> i32 {
|
|
if (condition) {
|
|
return 1;
|
|
} else {
|
|
return 2;
|
|
}
|
|
return 3;
|
|
}
|
|
":
|
|
Ok(_)
|
|
}
|
|
check_validation! {
|
|
"
|
|
fn dead_code_after_block() -> i32 {
|
|
{
|
|
return 1;
|
|
}
|
|
return 2;
|
|
}
|
|
":
|
|
Err(naga::valid::ValidationError::Function {
|
|
source: naga::valid::FunctionError::InstructionsAfterReturn,
|
|
..
|
|
})
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_runtime_sized_arrays() {
|
|
// You can't have structs whose last member is an unsized struct. An unsized
|
|
// array may only appear as the last member of a struct used directly as a
|
|
// variable's store type.
|
|
check_validation! {
|
|
"
|
|
struct Unsized {
|
|
arr: array<f32>
|
|
}
|
|
|
|
struct Outer {
|
|
legit: i32,
|
|
_unsized: Unsized
|
|
}
|
|
|
|
@group(0) @binding(0) var<storage> outer: Outer;
|
|
|
|
fn fetch(i: i32) -> f32 {
|
|
return outer._unsized.arr[i];
|
|
}
|
|
":
|
|
Err(naga::valid::ValidationError::Type {
|
|
name: struct_name,
|
|
source: naga::valid::TypeError::InvalidDynamicArray(member_name, _),
|
|
..
|
|
})
|
|
if struct_name == "Outer" && member_name == "_unsized"
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn select() {
|
|
check_validation! {
|
|
"
|
|
fn select_pointers(which: bool) -> i32 {
|
|
var x: i32 = 1;
|
|
var y: i32 = 2;
|
|
let p = select(&x, &y, which);
|
|
return *p;
|
|
}
|
|
",
|
|
"
|
|
fn select_arrays(which: bool) -> i32 {
|
|
var x: array<i32, 4>;
|
|
var y: array<i32, 4>;
|
|
let s = select(x, y, which);
|
|
return s[0];
|
|
}
|
|
",
|
|
"
|
|
struct S { member: i32 }
|
|
fn select_structs(which: bool) -> S {
|
|
var x: S = S(1);
|
|
var y: S = S(2);
|
|
let s = select(x, y, which);
|
|
return s;
|
|
}
|
|
":
|
|
Err(
|
|
naga::valid::ValidationError::Function {
|
|
name,
|
|
source: naga::valid::FunctionError::Expression {
|
|
source: naga::valid::ExpressionError::InvalidSelectTypes,
|
|
..
|
|
},
|
|
..
|
|
},
|
|
)
|
|
if name.starts_with("select_")
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn missing_default_case() {
|
|
check_validation! {
|
|
"
|
|
fn test_missing_default_case() {
|
|
switch(0) {
|
|
case 0: {}
|
|
}
|
|
}
|
|
":
|
|
Err(
|
|
naga::valid::ValidationError::Function {
|
|
source: naga::valid::FunctionError::MissingDefaultCase,
|
|
..
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn wrong_access_mode() {
|
|
// The assignments to `global.i` should be forbidden, because they are in
|
|
// variables whose access mode is `read`, not `read_write`.
|
|
check_validation! {
|
|
"
|
|
struct Globals {
|
|
i: i32
|
|
}
|
|
|
|
@group(0) @binding(0)
|
|
var<storage> globals: Globals;
|
|
|
|
fn store(v: i32) {
|
|
globals.i = v;
|
|
}
|
|
",
|
|
"
|
|
struct Globals {
|
|
i: i32
|
|
}
|
|
|
|
@group(0) @binding(0)
|
|
var<uniform> globals: Globals;
|
|
|
|
fn store(v: i32) {
|
|
globals.i = v;
|
|
}
|
|
":
|
|
Err(
|
|
naga::valid::ValidationError::Function {
|
|
name,
|
|
source: naga::valid::FunctionError::InvalidStorePointer(_),
|
|
..
|
|
},
|
|
)
|
|
if name == "store"
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn io_shareable_types() {
|
|
for numeric in "i32 u32 f32".split_whitespace() {
|
|
let types = format!(
|
|
"{} vec2<{}> vec3<{}> vec4<{}>",
|
|
numeric, numeric, numeric, numeric
|
|
);
|
|
for ty in types.split_whitespace() {
|
|
check_one_validation! {
|
|
&format!("@vertex
|
|
fn f(@location(0) arg: {}) -> @builtin(position) vec4<f32>
|
|
{{ return vec4<f32>(0.0); }}",
|
|
ty),
|
|
Ok(_module)
|
|
}
|
|
}
|
|
}
|
|
|
|
for ty in "bool
|
|
vec2<bool> vec3<bool> vec4<bool>
|
|
array<f32,4>
|
|
mat2x2<f32>
|
|
ptr<function,f32>"
|
|
.split_whitespace()
|
|
{
|
|
check_one_validation! {
|
|
&format!("@vertex
|
|
fn f(@location(0) arg: {}) -> @builtin(position) vec4<f32>
|
|
{{ return vec4<f32>(0.0); }}",
|
|
ty),
|
|
Err(
|
|
naga::valid::ValidationError::EntryPoint {
|
|
stage: naga::ShaderStage::Vertex,
|
|
name,
|
|
source: naga::valid::EntryPointError::Argument(
|
|
0,
|
|
naga::valid::VaryingError::NotIOShareableType(
|
|
_,
|
|
),
|
|
),
|
|
},
|
|
)
|
|
if name == "f"
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn host_shareable_types() {
|
|
// Host-shareable, constructible types.
|
|
let types = "i32 u32 f32
|
|
vec2<i32> vec3<u32> vec4<f32>
|
|
mat4x4<f32>
|
|
array<mat4x4<f32>,4>
|
|
AStruct";
|
|
for ty in types.split_whitespace() {
|
|
check_one_validation! {
|
|
&format!("struct AStruct {{ member: array<mat4x4<f32>, 8> }};
|
|
@group(0) @binding(0) var<uniform> ubuf: {};
|
|
@group(0) @binding(1) var<storage> sbuf: {};",
|
|
ty, ty),
|
|
Ok(_module)
|
|
}
|
|
}
|
|
|
|
// Host-shareable but not constructible types.
|
|
let types = "atomic<i32> atomic<u32>
|
|
array<atomic<u32>,4>
|
|
array<u32>
|
|
AStruct";
|
|
for ty in types.split_whitespace() {
|
|
check_one_validation! {
|
|
&format!("struct AStruct {{ member: array<atomic<u32>, 8> }};
|
|
@group(0) @binding(1) var<storage> sbuf: {};",
|
|
ty),
|
|
Ok(_module)
|
|
}
|
|
}
|
|
|
|
// Types that are neither host-shareable nor constructible.
|
|
for ty in "bool ptr<storage,i32>".split_whitespace() {
|
|
check_one_validation! {
|
|
&format!("@group(0) @binding(0) var<storage> sbuf: {};", ty),
|
|
Err(
|
|
naga::valid::ValidationError::GlobalVariable {
|
|
name,
|
|
handle: _,
|
|
source: naga::valid::GlobalVariableError::MissingTypeFlags { .. },
|
|
},
|
|
)
|
|
if name == "sbuf"
|
|
}
|
|
|
|
check_one_validation! {
|
|
&format!("@group(0) @binding(0) var<uniform> ubuf: {};", ty),
|
|
Err(naga::valid::ValidationError::GlobalVariable {
|
|
name,
|
|
handle: _,
|
|
source: naga::valid::GlobalVariableError::MissingTypeFlags { .. },
|
|
},
|
|
)
|
|
if name == "ubuf"
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn misplaced_break_if() {
|
|
check(
|
|
"
|
|
fn test_misplaced_break_if() {
|
|
loop {
|
|
break if true;
|
|
}
|
|
}
|
|
",
|
|
r###"error: A break if is only allowed in a continuing block
|
|
┌─ wgsl:4:17
|
|
│
|
|
4 │ break if true;
|
|
│ ^^^^^^^^ not in a continuing block
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn break_if_bad_condition() {
|
|
check_validation! {
|
|
"
|
|
fn test_break_if_bad_condition() {
|
|
loop {
|
|
continuing {
|
|
break if 1;
|
|
}
|
|
}
|
|
}
|
|
":
|
|
Err(
|
|
naga::valid::ValidationError::Function {
|
|
source: naga::valid::FunctionError::InvalidIfType(_),
|
|
..
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn swizzle_assignment() {
|
|
check(
|
|
"
|
|
fn f() {
|
|
var v = vec2(0);
|
|
v.xy = vec2(1);
|
|
}
|
|
",
|
|
r###"error: invalid left-hand side of assignment
|
|
┌─ wgsl:4:13
|
|
│
|
|
4 │ v.xy = vec2(1);
|
|
│ ^^^^ cannot assign to this expression
|
|
│
|
|
= note: WGSL does not support assignments to swizzles
|
|
= note: consider assigning each component individually
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn binary_statement() {
|
|
check(
|
|
"
|
|
fn f() {
|
|
3 + 5;
|
|
}
|
|
",
|
|
r###"error: expected assignment or increment/decrement, found ';'
|
|
┌─ wgsl:3:18
|
|
│
|
|
3 │ 3 + 5;
|
|
│ ^ expected assignment or increment/decrement
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn assign_to_expr() {
|
|
check(
|
|
"
|
|
fn f() {
|
|
3 + 5 = 10;
|
|
}
|
|
",
|
|
r###"error: invalid left-hand side of assignment
|
|
┌─ wgsl:3:13
|
|
│
|
|
3 │ 3 + 5 = 10;
|
|
│ ^^^^^ cannot assign to this expression
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn assign_to_let() {
|
|
check(
|
|
"
|
|
fn f() {
|
|
let a = 10;
|
|
a = 20;
|
|
}
|
|
",
|
|
r###"error: invalid left-hand side of assignment
|
|
┌─ wgsl:4:10
|
|
│
|
|
4 │ a = 20;
|
|
│ ^ cannot assign to this expression
|
|
│
|
|
= note: 'a' is an immutable binding
|
|
= note: consider declaring it with `var` instead of `let`
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn recursive_function() {
|
|
check(
|
|
"
|
|
fn f() {
|
|
f();
|
|
}
|
|
",
|
|
r###"error: declaration of `f` is recursive
|
|
┌─ wgsl:2:12
|
|
│
|
|
2 │ fn f() {
|
|
│ ^
|
|
3 │ f();
|
|
│ ^ uses itself here
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cyclic_function() {
|
|
check(
|
|
"
|
|
fn f() {
|
|
g();
|
|
}
|
|
fn g() {
|
|
f();
|
|
}
|
|
",
|
|
r###"error: declaration of `f` is cyclic
|
|
┌─ wgsl:2:12
|
|
│
|
|
2 │ fn f() {
|
|
│ ^
|
|
3 │ g();
|
|
│ ^ uses `g`
|
|
4 │ }
|
|
5 │ fn g() {
|
|
│ ^
|
|
6 │ f();
|
|
│ ^ ending the cycle
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn switch_signed_unsigned_mismatch() {
|
|
check(
|
|
"
|
|
fn x(y: u32) {
|
|
switch y {
|
|
case 1: {}
|
|
}
|
|
}
|
|
",
|
|
r###"error: invalid switch value
|
|
┌─ wgsl:4:16
|
|
│
|
|
4 │ case 1: {}
|
|
│ ^ expected unsigned integer
|
|
│
|
|
= note: suffix the integer with a `u`: '1u'
|
|
|
|
"###,
|
|
);
|
|
|
|
check(
|
|
"
|
|
fn x(y: i32) {
|
|
switch y {
|
|
case 1u: {}
|
|
}
|
|
}
|
|
",
|
|
r###"error: invalid switch value
|
|
┌─ wgsl:4:16
|
|
│
|
|
4 │ case 1u: {}
|
|
│ ^^ expected signed integer
|
|
│
|
|
= note: remove the `u` suffix: '1'
|
|
|
|
"###,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn function_returns_void() {
|
|
check(
|
|
"
|
|
fn x() {
|
|
let a = vec2<f32>(1, 2u);
|
|
}
|
|
|
|
fn b() {
|
|
let a = x();
|
|
}
|
|
",
|
|
r###"error: function does not return any value
|
|
┌─ wgsl:7:18
|
|
│
|
|
7 │ let a = x();
|
|
│ ^
|
|
│
|
|
= note: perhaps you meant to call the function in a separate statement?
|
|
|
|
"###,
|
|
)
|
|
}
|