export const description = `Validation tests for @must_use`; import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { keysOf } from '../../../../common/util/data_tables.js'; import { ShaderValidationTest } from '../shader_validation_test.js'; export const g = makeTestGroup(ShaderValidationTest); const kMustUseDeclarations = { var: { code: `@must_use @group(0) @binding(0) var x : array;`, valid: false, }, function_no_return: { code: `@must_use fn foo() { }`, valid: false, }, function_scalar_return: { code: `@must_use fn foo() -> u32 { return 0; }`, valid: true, }, function_struct_return: { code: `struct S { x : u32 } @must_use fn foo() -> S { return S(); }`, valid: true, }, function_var: { code: `fn foo() { @must_use var x = 0; }`, valid: false, }, function_call: { code: `fn bar() -> u32 { return 0; } fn foo() { @must_use bar(); }`, valid: false, }, function_parameter: { code: `fn foo(@must_use param : u32) -> u32 { return param; }`, valid: false, }, empty_parameter: { code: `@must_use() fn foo() -> u32 { return 0; }`, valid: false, }, parameter: { code: `@must_use(0) fn foo() -> u32 { return 0; }`, valid: false, }, duplicate: { code: `@must_use @must_use fn foo() -> u32 { return 0; }`, valid: false, }, }; g.test('declaration') .desc(`Validate attribute can only be applied to a function declaration with a return type`) .params(u => u.combine('test', keysOf(kMustUseDeclarations))) .fn(t => { const test = kMustUseDeclarations[t.params.test]; t.expectCompileResult(test.valid, test.code); }); const kMustUseCalls = { no_call: ``, // Never calling a @must_use function should pass phony: `_ = bar();`, let: `let tmp = bar();`, local_var: `var tmp = bar();`, private_var: `private_var = bar();`, storage_var: `storage_var = bar();`, pointer: ` var a : f32; let p = &a; (*p) = bar();`, vector_elem: ` var a : vec3; a.x = bar();`, matrix_elem: ` var a : mat3x2; a[0][0] = bar();`, condition: `if bar() == 0 { }`, param: `baz(bar());`, return: `return bar();`, statement: `bar();`, // should fail if bar is @must_use }; g.test('call') .desc(`Validate that a call to must_use function cannot be the whole function call statement`) .params(u => u // .combine('use', ['@must_use', ''] as const) .combine('call', keysOf(kMustUseCalls)) ) .fn(t => { const test = kMustUseCalls[t.params.call]; const code = ` @group(0) @binding(0) var storage_var : f32; var private_var : f32; fn baz(param : f32) { } ${t.params.use} fn bar() -> f32 { return 0; } fn foo() ${t.params.call === 'return' ? '-> f32' : ''} { ${test} }`; const should_pass = t.params.call !== 'statement' || t.params.use === ''; t.expectCompileResult(should_pass, code); }); g.test('ignore_result_of_non_must_use_that_returns_call_of_must_use') .desc( `Test that ignoring the result of a non-@must_use function that returns the result of a @must_use function succeeds` ) .fn(t => { const wgsl = ` @must_use fn f() -> f32 { return 0; } fn g() -> f32 { return f(); } fn main() { g(); // Ignore result } `; t.expectCompileResult(true, wgsl); });