Repository: gfx-rs/wgpu (2026-04-03T09:27:24Z)

Issue:
naga does not validate `must_use` for global declarations
```
[fail] webgpu:shader,validation,parse,must_use:declaration:test="var" (44ms). Log:
  - EXPECTATION FAILED: Expected validation error
      at (elided: below max severity)
  - VALIDATION FAILED: Missing expected compilationInfo 'error' message.


    ---- shader ----
    @must_use @group(0) @binding(0)
        var<storage> x : array<u32>;
        at ShaderValidationTest.expectCompileResult (file:///Users/Andy/Development/wgpu2/cts/out/webgpu/shader/validation/shader_validation_test.js:74:19)
        at RunCaseSpecific.fn (file:///Users/Andy/Development/wgpu2/cts/out/webgpu/shader/validation/parse/must_use.spec.js:60:5)
```


Discussion:
I noticed that the [spec](https://gpuweb.github.io/gpuweb/wgsl/#must-use-attr) seems to describe `@must_use` only in the context of functions, so maybe there is something that needs to be clarified here.
Hi, according to the WGSL spec, `@must_use` is restricted to functions with a return type, so applying it to a `var` is invalid. I'd like to work on fixing this validation if that's okay — could you assign this to me?
@dnsn021: Assigned! 🫡 



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8ece886a084..050305c898e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -90,6 +90,7 @@ Bottom level categories:
 - Naga no longer allows derivative operations on `f16`. WGSL does not currently allow this, although [it may be added in the future](https://github.com/gpuweb/gpuweb/issues/5482). By @andyleiserson in [#9154](https://github.com/gfx-rs/wgpu/pull/9154).
 - Disallow direct access to atomic variables in WGSL front-end (e.g. `let x = myAtomic;`). By @ecoricemon in [#9262](https://github.com/gfx-rs/wgpu/pull/9262).
 - Fixed handling of unterminated block comments. By @BKDaugherty in [#9356](https://github.com/gfx-rs/wgpu/pull/9356).
+- Enforce that `@must_use` appear only on function declarations. By @dnsn021 in [#9367](https://github.com/gfx-rs/wgpu/pull/9367).
 
 #### dx12
 
diff --git a/cts_runner/fail.lst b/cts_runner/fail.lst
index a46978c7bca..fea9e119e41 100644
--- a/cts_runner/fail.lst
+++ b/cts_runner/fail.lst
@@ -125,7 +125,6 @@ webgpu:shader,validation,parse,blankspace:* // 95%, null in comments, https://gi
 webgpu:shader,validation,parse,comments:* // 93%, unterminated block comments, https://github.com/gfx-rs/wgpu/issues/8877
 webgpu:shader,validation,parse,diagnostic:* // 50%, https://github.com/gfx-rs/wgpu/issues/6458
 webgpu:shader,validation,parse,literal:* // 96%, https://github.com/gfx-rs/wgpu/issues/7046
-webgpu:shader,validation,parse,must_use:* // 97%, https://github.com/gfx-rs/wgpu/issues/8876
 webgpu:shader,validation,parse,shadow_builtins:* // 83%, function param shadowing parser issue; determinant const-eval; texture_external capability missing
 webgpu:shader,validation,shader_io,align:* // 98%, @align(2^31) accepted but exceeds i32::MAX per WGSL spec
 webgpu:shader,validation,shader_io,id:* // 97%, @id on const
diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs
index 538215c1e44..59d2268333f 100644
--- a/naga/src/front/wgsl/error.rs
+++ b/naga/src/front/wgsl/error.rs
@@ -362,6 +362,7 @@ pub(crate) enum Error<'a> {
     FunctionReturnsVoid(Span),
     FunctionMustUseUnused(Span),
     FunctionMustUseReturnsVoid(Span, Span),
+    FunctionMustUseOnNonFunction(Span),
     InvalidWorkGroupUniformLoad(Span),
     Internal(&'static str),
     ExpectedConstExprConcreteIntegerScalar(Span),
@@ -1077,6 +1078,13 @@ impl<'a> Error<'a> {
                     "declare a return type or remove the attribute".into(),
                 ],
             },
+            Error::FunctionMustUseOnNonFunction(attr) => ParseError {
+                message: "attribute `@must_use` is only valid on function declarations".into(),
+                labels: vec![(attr, "".into())],
+                notes: vec![
+                    "place `@must_use` on a function declaration with a return type".into(),
+                ],
+            },
             Error::InvalidWorkGroupUniformLoad(span) => ParseError {
                 message: "incorrect type passed to workgroupUniformLoad".into(),
                 labels: vec![(span, "".into())],
diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs
index 45c5514ae63..5f503ef2207 100644
--- a/naga/src/front/wgsl/parse/mod.rs
+++ b/naga/src/front/wgsl/parse/mod.rs
@@ -2203,6 +2203,12 @@ impl Parser {
             }
         };
 
+        if let Some(must_use_span) = must_use.value {
+            if !matches!(kind.as_ref(), Some(ast::GlobalDeclKind::Fn(_))) {
+                return Err(Box::new(Error::FunctionMustUseOnNonFunction(must_use_span)));
+            }
+        }
+
         if let Some(kind) = kind {
             out.decls.append(
                 ast::GlobalDecl { kind, dependencies },


Test patch:
diff --git a/cts_runner/test.lst b/cts_runner/test.lst
index 834473651fe..031ced0b639 100644
--- a/cts_runner/test.lst
+++ b/cts_runner/test.lst
@@ -450,6 +450,7 @@ webgpu:shader,validation,parse,blankspace:null_characters:contains_null=true;pla
 webgpu:shader,validation,parse,comments:unterminated_block_comment:terminated=false
 webgpu:shader,validation,parse,enable:*
 webgpu:shader,validation,parse,identifiers:*
+webgpu:shader,validation,parse,must_use:*
 webgpu:shader,validation,parse,requires:*
 webgpu:shader,validation,parse,semicolon:*
 webgpu:shader,validation,parse,shadow_builtins:function_param:*
diff --git a/naga/tests/naga/wgsl_errors.rs b/naga/tests/naga/wgsl_errors.rs
index ec98311f34e..97bed72b7ad 100644
--- a/naga/tests/naga/wgsl_errors.rs
+++ b/naga/tests/naga/wgsl_errors.rs
@@ -3158,6 +3158,27 @@ struct S {
     )
 }
 
+#[test]
+fn global_var_must_use() {
+    check(
+        r#"
+@must_use
+@group(0)
+@binding(0)
+var<storage> x : array<u32>;
+"#,
+        r#"error: attribute `@must_use` is only valid on function declarations
+  ┌─ wgsl:2:2
+  │
+2 │ @must_use
+  │  ^^^^^^^^
+  │
+  = note: place `@must_use` on a function declaration with a return type
+
+"#,
+    )
+}
+
 #[test]
 fn function_param_redefinition_as_param() {
     check(