Repository: gfx-rs/wgpu (language: rust)

Issue:
Naga Does Not Validate Against Structs with Duplicate Names
```rust
struct A {
    a: f32,
    a: f32,
}

struct B {
    b: f32,
    b: u32,
}

fn use_a(a: A) -> f32 {
    return a.a;
}

fn use_b(b: B) -> f32 {
    return b.b;
}
```

Is accepted by naga without complaint. It seems to use the first member for type checking as if you swap the member order on `B` it throws a type error in `use_b`. The member does make it into the IR, so this is a failure of the validator (and maybe also wgsl in?)

```
Module {
    types: {
        [0]: Type {
            name: None,
            inner: Scalar(
                Scalar {
                    kind: Float,
                    width: 4,
                },
            ),
        },
        [1]: Type {
            name: Some(
                "A",
            ),
            inner: Struct {
                members: [
                    StructMember {
                        name: Some(
                            "a",
                        ),
                        ty: [0],
                        binding: None,
                        offset: 0,
                    },
                    StructMember {
                        name: Some(
                            "a",
                        ),
                        ty: [0],
                        binding: None,
                        offset: 4,
                    },
                ],
                span: 8,
            },
        },
        [2]: Type {
            name: None,
            inner: Scalar(
                Scalar {
                    kind: Uint,
                    width: 4,
                },
            ),
        },
        [3]: Type {
            name: Some(
                "B",
            ),
            inner: Struct {
                members: [
                    StructMember {
                        name: Some(
                            "b",
                        ),
                        ty: [0],
                        binding: None,
                        offset: 0,
                    },
                    StructMember {
                        name: Some(
                            "b",
                        ),
                        ty: [2],
                        binding: None,
                        offset: 4,
                    },
                ],
                span: 8,
            },
        },
    },
    special_types: SpecialTypes {
        ray_desc: None,
        ray_intersection: None,
        predeclared_types: {},
    },
    constants: {},
    overrides: {},
    global_variables: {},
    global_expressions: {},
    functions: {
        [0]: Function {
            name: Some(
                "use_a",
            ),
            arguments: [
                FunctionArgument {
                    name: Some(
                        "a",
                    ),
                    ty: [1],
                    binding: None,
                },
            ],
            result: Some(
                FunctionResult {
                    ty: [0],
                    binding: None,
                },
            ),
            local_variables: {},
            expressions: {
                [0]: FunctionArgument(
                    0,
                ),
                [1]: AccessIndex {
                    base: [0],
                    index: 0,
                },
            },
            named_expressions: {
                [0]: "a",
            },
            body: Block {
                body: [
                    Emit(
                        [1..2],
                    ),
                    Return {
                        value: Some(
                            [1],
                        ),
                    },
                ],
                span_info: [
                    Span {
                        start: 122,
                        end: 125,
                    },
                    Span {
                        start: 115,
                        end: 126,
                    },
                ],
            },
            diagnostic_filter_leaf: None,
        },
        [1]: Function {
            name: Some(
                "use_b",
            ),
            arguments: [
                FunctionArgument {
                    name: Some(
                        "b",
                    ),
                    ty: [3],
                    binding: None,
                },
            ],
            result: Some(
                FunctionResult {
                    ty: [0],
                    binding: None,
                },
            ),
            local_variables: {},
            expressions: {
                [0]: FunctionArgument(
                    0,
                ),
                [1]: AccessIndex {
                    base: [0],
                    index: 0,
                },
            },
            named_expressions: {
                [0]: "b",
            },
            body: Block {
                body: [
                    Emit(
                        [1..2],
                    ),
                    Return {
                        value: Some(
                            [1],
                        ),
                    },
                ],
                span_info: [
                    Span {
                        start: 169,
                        end: 172,
                    },
                    Span {
                        start: 162,
                        end: 173,
                    },
                ],
            },
            diagnostic_filter_leaf: None,
        },
    },
    entry_points: [],
    diagnostic_filters: {},
    diagnostic_filter_leaf: None,
}

ModuleInfo {
    type_flags: [
        TypeFlags(
            DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE,
        ),
        TypeFlags(
            DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE,
        ),
        TypeFlags(
            DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE,
        ),
        TypeFlags(
            DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE,
        ),
    ],
    functions: [
        FunctionInfo {
            flags: ValidationFlags(
                EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS,
            ),
            available_stages: ShaderStages(
                VERTEX | FRAGMENT | COMPUTE,
            ),
            uniformity: Uniformity {
                non_uniform_result: Some(
                    [0],
                ),
                requirements: UniformityRequirements(
                    0x0,
                ),
            },
            may_kill: false,
            sampling_set: {},
            global_uses: [],
            expressions: [
                ExpressionInfo {
                    uniformity: Uniformity {
                        non_uniform_result: Some(
                            [0],
                        ),
                        requirements: UniformityRequirements(
                            0x0,
                        ),
                    },
                    ref_count: 1,
                    assignable_global: None,
                    ty: Handle(
                        [1],
                    ),
                },
                ExpressionInfo {
                    uniformity: Uniformity {
                        non_uniform_result: Some(
                            [0],
                        ),
                        requirements: UniformityRequirements(
                            0x0,
                        ),
                    },
                    ref_count: 1,
                    assignable_global: None,
                    ty: Handle(
                        [0],
                    ),
                },
            ],
            sampling: {},
            dual_source_blending: false,
            diagnostic_filter_leaf: None,
        },
        FunctionInfo {
            flags: ValidationFlags(
                EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS,
            ),
            available_stages: ShaderStages(
                VERTEX | FRAGMENT | COMPUTE,
            ),
            uniformity: Uniformity {
                non_uniform_result: Some(
                    [0],
                ),
                requirements: UniformityRequirements(
                    0x0,
                ),
            },
            may_kill: false,
            sampling_set: {},
            global_uses: [],
            expressions: [
                ExpressionInfo {
                    uniformity: Uniformity {
                        non_uniform_result: Some(
                            [0],
                        ),
                        requirements: UniformityRequirements(
                            0x0,
                        ),
                    },
                    ref_count: 1,
                    assignable_global: None,
                    ty: Handle(
                        [3],
                    ),
                },
                ExpressionInfo {
                    uniformity: Uniformity {
                        non_uniform_result: Some(
                            [0],
                        ),
                        requirements: UniformityRequirements(
                            0x0,
                        ),
                    },
                    ref_count: 1,
                    assignable_global: None,
                    ty: Handle(
                        [0],
                    ),
                },
            ],
            sampling: {},
            dual_source_blending: false,
            diagnostic_filter_leaf: None,
        },
    ],
    entry_points: [],
    const_expression_types: [],
}
```


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index de6a9cc352..ccd2f98404 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -83,6 +83,7 @@ By @brodycj in [#6924](https://github.com/gfx-rs/wgpu/pull/6924).
 
 - Fix some instances of functions which have a return type but don't return a value being incorrectly validated. By @jamienicol in [#7013](https://github.com/gfx-rs/wgpu/pull/7013).
 - Allow abstract expressions to be used in WGSL function return statements. By @jamienicol in [#7035](https://github.com/gfx-rs/wgpu/pull/7035).
+- Error if structs have two fields with the same name. By @SparkyPotato in [#7088](https://github.com/gfx-rs/wgpu/pull/7088).
 
 #### General
 
diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs
index 498abd526a..23b2984e75 100644
--- a/naga/src/front/wgsl/parse/mod.rs
+++ b/naga/src/front/wgsl/parse/mod.rs
@@ -12,7 +12,7 @@ use crate::front::wgsl::parse::lexer::{Lexer, Token};
 use crate::front::wgsl::parse::number::Number;
 use crate::front::wgsl::Scalar;
 use crate::front::SymbolTable;
-use crate::{Arena, FastIndexSet, Handle, ShaderStage, Span};
+use crate::{Arena, FastHashSet, FastIndexSet, Handle, ShaderStage, Span};
 
 pub mod ast;
 pub mod conv;
@@ -1171,6 +1171,7 @@ impl Parser {
         ctx: &mut ExpressionContext<'a, '_, '_>,
     ) -> Result<Vec<ast::StructMember<'a>>, Error<'a>> {
         let mut members = Vec::new();
+        let mut member_names = FastHashSet::default();
 
         lexer.expect(Token::Paren('{'))?;
         let mut ready = true;
@@ -1217,6 +1218,17 @@ impl Parser {
                 size: size.value,
                 align: align.value,
             });
+
+            if !member_names.insert(name.name) {
+                return Err(Error::Redefinition {
+                    previous: members
+                        .iter()
+                        .find(|x| x.name.name == name.name)
+                        .map(|x| x.name.span)
+                        .unwrap(),
+                    current: name.span,
+                });
+            }
         }
 
         Ok(members)


Test patch:
diff --git a/naga/tests/wgsl_errors.rs b/naga/tests/wgsl_errors.rs
index 5abd61cd8f..a110a37fe0 100644
--- a/naga/tests/wgsl_errors.rs
+++ b/naga/tests/wgsl_errors.rs
@@ -2092,6 +2092,27 @@ fn function_param_redefinition_as_local() {
     )
 }
 
+#[test]
+fn struct_member_redefinition() {
+    check(
+        "
+        struct A {
+            a: f32,
+            a: f32,
+        }
+    ",
+        r###"error: redefinition of `a`
+  ┌─ wgsl:3:13
+  │
+3 │             a: f32,
+  │             ^ previous definition of `a`
+4 │             a: f32,
+  │             ^ redefinition of `a`
+
+"###,
+    )
+}
+
 #[test]
 fn function_must_return_value() {
     check_validation!(