[tint][sem] Move variable attributes to separate structs

Similar to MemberAttributes, add GlobalVariableAttributes and
ParameterAttributes.

This avoids confusion over Parameter::Index() which is the index of the
parameter in the function, and the @index value.

Change-Id: If045ab5d70ef60580f51c5d701d892828253d844
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/160080
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/cmd/common/generate_external_texture_bindings.cc b/src/tint/cmd/common/generate_external_texture_bindings.cc
index 7002faa..fdb7704 100644
--- a/src/tint/cmd/common/generate_external_texture_bindings.cc
+++ b/src/tint/cmd/common/generate_external_texture_bindings.cc
@@ -48,7 +48,7 @@
     std::vector<tint::BindingPoint> ext_tex_bps;
     for (auto* var : program.AST().GlobalVariables()) {
         if (auto* sem_var = program.Sem().Get(var)->As<sem::GlobalVariable>()) {
-            if (auto bp = sem_var->BindingPoint()) {
+            if (auto bp = sem_var->Attributes().binding_point) {
                 auto& n = group_to_next_binding_number[bp->group];
                 n = std::max(n, bp->binding + 1);
 
diff --git a/src/tint/fuzzers/tint_common_fuzzer.cc b/src/tint/fuzzers/tint_common_fuzzer.cc
index 8f36a89..e6ae17b 100644
--- a/src/tint/fuzzers/tint_common_fuzzer.cc
+++ b/src/tint/fuzzers/tint_common_fuzzer.cc
@@ -284,7 +284,7 @@
         std::vector<BindingPoint> ext_tex_bps;
         for (auto* var : program.AST().GlobalVariables()) {
             if (auto* sem_var = program.Sem().Get(var)->As<sem::GlobalVariable>()) {
-                if (auto bp = sem_var->BindingPoint()) {
+                if (auto bp = sem_var->Attributes().binding_point) {
                     auto& n = group_to_next_binding_number[bp->group];
                     n = std::max(n, bp->binding + 1);
 
diff --git a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
index b0c7219..738844e 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
@@ -1929,7 +1929,7 @@
         TINT_ICE() << "storage variable must be of struct type";
         return;
     }
-    auto bp = *sem->As<sem::GlobalVariable>()->BindingPoint();
+    auto bp = *sem->As<sem::GlobalVariable>()->Attributes().binding_point;
     {
         auto out = Line();
         out << "layout(binding = " << bp.binding << ", std140";
@@ -1948,7 +1948,7 @@
         TINT_ICE() << "storage variable must be of struct type";
         return;
     }
-    auto bp = *sem->As<sem::GlobalVariable>()->BindingPoint();
+    auto bp = *sem->As<sem::GlobalVariable>()->Attributes().binding_point;
     Line() << "layout(binding = " << bp.binding << ", std430) buffer "
            << UniqueIdentifier(StructName(str) + "_ssbo") << " {";
     EmitStructMembers(current_buffer_, str);
@@ -2080,7 +2080,7 @@
     }
 
     auto out = Line();
-    EmitAttributes(out, var, decl->attributes);
+    EmitAttributes(out, var);
     EmitInterpolationQualifiers(out, decl->attributes);
 
     auto name = decl->name->symbol.Name();
@@ -2130,23 +2130,17 @@
     }
 }
 
-void ASTPrinter::EmitAttributes(StringStream& out,
-                                const sem::GlobalVariable* var,
-                                VectorRef<const ast::Attribute*> attributes) {
-    if (attributes.IsEmpty()) {
-        return;
-    }
+void ASTPrinter::EmitAttributes(StringStream& out, const sem::GlobalVariable* var) {
+    auto& attrs = var->Attributes();
 
     bool first = true;
-    for (auto* attr : attributes) {
-        if (attr->As<ast::LocationAttribute>()) {
-            out << (first ? "layout(" : ", ");
-            out << "location = " << std::to_string(var->Location().value());
-            first = false;
-        }
-        if (attr->As<ast::IndexAttribute>()) {
-            out << ", index = " << std::to_string(var->Index().value());
-        }
+    if (attrs.location.has_value()) {
+        out << (first ? "layout(" : ", ");
+        out << "location = " << std::to_string(attrs.location.value());
+        first = false;
+    }
+    if (attrs.index.has_value()) {
+        out << ", index = " << std::to_string(attrs.index.value());
     }
     if (!first) {
         out << ") ";
diff --git a/src/tint/lang/glsl/writer/ast_printer/ast_printer.h b/src/tint/lang/glsl/writer/ast_printer/ast_printer.h
index 519bdf8..f5cc633 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer.h
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer.h
@@ -317,10 +317,7 @@
     /// Handles emitting attributes
     /// @param out the output of the expression stream
     /// @param var the global variable semantics
-    /// @param attrs the attributes
-    void EmitAttributes(StringStream& out,
-                        const sem::GlobalVariable* var,
-                        VectorRef<const ast::Attribute*> attrs);
+    void EmitAttributes(StringStream& out, const sem::GlobalVariable* var);
     /// Handles emitting the entry point function
     /// @param func the entry point
     void EmitEntryPointFunction(const ast::Function* func);
diff --git a/src/tint/lang/glsl/writer/ast_raise/combine_samplers.cc b/src/tint/lang/glsl/writer/ast_raise/combine_samplers.cc
index b41e960..b056310 100644
--- a/src/tint/lang/glsl/writer/ast_raise/combine_samplers.cc
+++ b/src/tint/lang/glsl/writer/ast_raise/combine_samplers.cc
@@ -118,10 +118,10 @@
                                               std::string name) {
         SamplerTexturePair bp_pair;
         bp_pair.texture_binding_point =
-            texture_var ? *texture_var->As<sem::GlobalVariable>()->BindingPoint()
+            texture_var ? *texture_var->As<sem::GlobalVariable>()->Attributes().binding_point
                         : binding_info->placeholder_binding_point;
         bp_pair.sampler_binding_point =
-            sampler_var ? *sampler_var->As<sem::GlobalVariable>()->BindingPoint()
+            sampler_var ? *sampler_var->As<sem::GlobalVariable>()->Attributes().binding_point
                         : binding_info->placeholder_binding_point;
         auto it = binding_info->binding_map.find(bp_pair);
         if (it != binding_info->binding_map.end()) {
@@ -232,7 +232,7 @@
             if (tint::IsAnyOf<core::type::Texture, core::type::Sampler>(type) &&
                 !type->Is<core::type::StorageTexture>()) {
                 ctx.Remove(ctx.src->AST().GlobalDeclarations(), global);
-            } else if (auto binding_point = global_sem->BindingPoint()) {
+            } else if (auto binding_point = global_sem->Attributes().binding_point) {
                 if (binding_point->group == 0 && binding_point->binding == 0) {
                     auto* attribute =
                         ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision);
diff --git a/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc b/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc
index 67eb338..bc40549 100644
--- a/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc
+++ b/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc
@@ -366,7 +366,7 @@
                 auto* global_sem = sem.Get<sem::GlobalVariable>(var);
 
                 // The original binding point
-                BindingPoint binding_point = *global_sem->BindingPoint();
+                BindingPoint binding_point = *global_sem->Attributes().binding_point;
 
                 if (binding_point == cfg->ubo_binding) {
                     // This ubo_binding struct already exists.
@@ -423,7 +423,7 @@
     /// @returns binding of the global variable.
     BindingPoint GetAndRecordGlobalBinding(const sem::GlobalVariable* global,
                                            TextureBuiltinsFromUniformOptions::Field field) {
-        auto binding = global->BindingPoint().value();
+        auto binding = global->Attributes().binding_point.value();
         auto iter = bindpoint_to_data.find(binding);
         if (iter == bindpoint_to_data.end()) {
             // First visit, recording the binding.
diff --git a/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
index b23c19f..6ee9b5a 100644
--- a/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
@@ -3331,7 +3331,7 @@
 }
 
 bool ASTPrinter::EmitUniformVariable(const ast::Var* var, const sem::Variable* sem) {
-    auto binding_point = *sem->As<sem::GlobalVariable>()->BindingPoint();
+    auto binding_point = *sem->As<sem::GlobalVariable>()->Attributes().binding_point;
     auto* type = sem->Type()->UnwrapRef();
     auto name = var->name->symbol.Name();
     Line() << "cbuffer cbuffer_" << name << RegisterAndSpace('b', binding_point) << " {";
@@ -3360,7 +3360,7 @@
 
     auto* global_sem = sem->As<sem::GlobalVariable>();
     out << RegisterAndSpace(sem->Access() == core::Access::kRead ? 't' : 'u',
-                            *global_sem->BindingPoint())
+                            *global_sem->Attributes().binding_point)
         << ";";
 
     return true;
@@ -3389,7 +3389,7 @@
     }
 
     if (register_space) {
-        auto bp = sem->As<sem::GlobalVariable>()->BindingPoint();
+        auto bp = sem->As<sem::GlobalVariable>()->Attributes().binding_point;
         out << " : register(" << register_space << bp->binding;
         // Omit the space if it's 0, as it's the default.
         // SM 5.0 doesn't support spaces, so we don't emit them if group is 0 for better
diff --git a/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.cc b/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.cc
index 31590d9..22a8ff3 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.cc
@@ -165,7 +165,7 @@
 
                 for (auto* global : src.AST().GlobalVariables()) {
                     auto* global_sem = src.Sem().Get<sem::GlobalVariable>(global);
-                    if (auto bp = global_sem->BindingPoint()) {
+                    if (auto bp = global_sem->Attributes().binding_point) {
                         if (bp->group >= group) {
                             group = bp->group + 1;
                         }
diff --git a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
index aed7504..6ef2020 100644
--- a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
@@ -1970,10 +1970,9 @@
             return kInvalidBindingIndex;
         }
         auto* param_sem = builder_.Sem().Get<sem::Parameter>(param);
-        auto bp = param_sem->BindingPoint();
+        auto bp = param_sem->Attributes().binding_point;
         if (TINT_UNLIKELY(bp->group != 0)) {
-            TINT_ICE() << "encountered non-zero resource group index (use "
-                          "BindingRemapper to fix)";
+            TINT_ICE() << "encountered non-zero resource group index (use BindingRemapper to fix)";
             return kInvalidBindingIndex;
         }
         return bp->binding;
diff --git a/src/tint/lang/msl/writer/writer_bench.cc b/src/tint/lang/msl/writer/writer_bench.cc
index 2705e93..0af1f7b 100644
--- a/src/tint/lang/msl/writer/writer_bench.cc
+++ b/src/tint/lang/msl/writer/writer_bench.cc
@@ -65,7 +65,7 @@
     uint32_t next_binding_point = 0;
     for (auto* var : program.AST().GlobalVariables()) {
         if (auto* var_sem = program.Sem().Get(var)->As<sem::GlobalVariable>()) {
-            if (auto bp = var_sem->BindingPoint()) {
+            if (auto bp = var_sem->Attributes().binding_point) {
                 gen_options.binding_remapper_options.binding_points[*bp] = BindingPoint{
                     0,                     // group
                     next_binding_point++,  // binding
diff --git a/src/tint/lang/spirv/writer/ast_printer/builder.cc b/src/tint/lang/spirv/writer/ast_printer/builder.cc
index 734c380..a75d703 100644
--- a/src/tint/lang/spirv/writer/ast_printer/builder.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/builder.cc
@@ -806,13 +806,13 @@
             [&](const ast::LocationAttribute*) {
                 module_.PushAnnot(spv::Op::OpDecorate,
                                   {Operand(var_id), U32Operand(SpvDecorationLocation),
-                                   Operand(sem->Location().value())});
+                                   Operand(sem->Attributes().location.value())});
                 return true;
             },
             [&](const ast::IndexAttribute*) {
                 module_.PushAnnot(spv::Op::OpDecorate,
                                   {Operand(var_id), U32Operand(SpvDecorationIndex),
-                                   Operand(sem->Index().value())});
+                                   Operand(sem->Attributes().index.value())});
                 return true;
             },
             [&](const ast::InterpolateAttribute* interpolate) {
@@ -837,14 +837,14 @@
                 return true;
             },
             [&](const ast::BindingAttribute*) {
-                auto bp = sem->BindingPoint();
+                auto bp = sem->Attributes().binding_point;
                 module_.PushAnnot(
                     spv::Op::OpDecorate,
                     {Operand(var_id), U32Operand(SpvDecorationBinding), Operand(bp->binding)});
                 return true;
             },
             [&](const ast::GroupAttribute*) {
-                auto bp = sem->BindingPoint();
+                auto bp = sem->Attributes().binding_point;
                 module_.PushAnnot(
                     spv::Op::OpDecorate,
                     {Operand(var_id), U32Operand(SpvDecorationDescriptorSet), Operand(bp->group)});
diff --git a/src/tint/lang/spirv/writer/helpers/generate_bindings.cc b/src/tint/lang/spirv/writer/helpers/generate_bindings.cc
index 21ef88b..076a594 100644
--- a/src/tint/lang/spirv/writer/helpers/generate_bindings.cc
+++ b/src/tint/lang/spirv/writer/helpers/generate_bindings.cc
@@ -55,7 +55,7 @@
     Vector<tint::BindingPoint, 4> ext_tex_bps;
     for (auto* var : program.AST().GlobalVariables()) {
         if (auto* sem_var = program.Sem().Get(var)->As<sem::GlobalVariable>()) {
-            if (auto bp = sem_var->BindingPoint()) {
+            if (auto bp = sem_var->Attributes().binding_point) {
                 if (auto val = group_to_next_binding_number.Find(bp->group)) {
                     *val = std::max(*val, bp->binding + 1);
                 } else {
diff --git a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc
index 2f617c1..d98068d 100644
--- a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc
+++ b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc
@@ -100,7 +100,7 @@
 
         IterateArrayLengthOnStorageVar(
             [&](const CallExpression*, const sem::VariableUser*, const sem::GlobalVariable* var) {
-                if (auto binding = var->BindingPoint()) {
+                if (auto binding = var->Attributes().binding_point) {
                     auto idx_itr = cfg->bindpoint_to_size_index.find(*binding);
                     if (idx_itr == cfg->bindpoint_to_size_index.end()) {
                         return;
@@ -138,7 +138,7 @@
         IterateArrayLengthOnStorageVar([&](const CallExpression* call_expr,
                                            const sem::VariableUser* storage_buffer_sem,
                                            const sem::GlobalVariable* var) {
-            auto binding = var->BindingPoint();
+            auto binding = var->Attributes().binding_point;
             if (!binding) {
                 return;
             }
diff --git a/src/tint/lang/wgsl/ast/transform/binding_remapper.cc b/src/tint/lang/wgsl/ast/transform/binding_remapper.cc
index 3ff5e0a..b2ec92a 100644
--- a/src/tint/lang/wgsl/ast/transform/binding_remapper.cc
+++ b/src/tint/lang/wgsl/ast/transform/binding_remapper.cc
@@ -89,7 +89,7 @@
             auto* func = src.Sem().Get(func_ast);
             std::unordered_map<BindingPoint, int> binding_point_counts;
             for (auto* global : func->TransitivelyReferencedGlobals()) {
-                if (auto from = global->BindingPoint()) {
+                if (auto from = global->Attributes().binding_point) {
                     auto bp_it = remappings->binding_points.find(*from);
                     if (bp_it != remappings->binding_points.end()) {
                         // Remapped
@@ -113,7 +113,7 @@
             auto* global_sem = src.Sem().Get<sem::GlobalVariable>(var);
 
             // The original binding point
-            BindingPoint from = *global_sem->BindingPoint();
+            BindingPoint from = *global_sem->Attributes().binding_point;
 
             // The binding point after remapping
             BindingPoint bp = from;
diff --git a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
index 4643176..45c270c 100644
--- a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
+++ b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
@@ -428,7 +428,8 @@
         }
 
         auto name = param->Declaration()->name->symbol.Name();
-        auto* input_expr = AddInput(name, param->Type(), param->Location(), std::move(attributes));
+        auto* input_expr =
+            AddInput(name, param->Type(), param->Attributes().location, std::move(attributes));
         inner_call_parameters.Push(input_expr);
     }
 
diff --git a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.cc b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.cc
index c463dbc..0a5f85e 100644
--- a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.cc
+++ b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.cc
@@ -131,7 +131,7 @@
             // The binding points for the newly introduced bindings must have been provided to this
             // transform. We fetch the new binding points by providing the original texture_external
             // binding points into the passed map.
-            BindingPoint bp = *sem_var->BindingPoint();
+            BindingPoint bp = *sem_var->Attributes().binding_point;
 
             BindingsMap::const_iterator it = new_binding_points->bindings_map.find(bp);
             if (it == new_binding_points->bindings_map.end()) {
diff --git a/src/tint/lang/wgsl/ast/transform/robustness.cc b/src/tint/lang/wgsl/ast/transform/robustness.cc
index c984b29..0bc1575 100644
--- a/src/tint/lang/wgsl/ast/transform/robustness.cc
+++ b/src/tint/lang/wgsl/ast/transform/robustness.cc
@@ -699,11 +699,11 @@
         if (globalVariable == nullptr) {
             return false;
         }
-        if (!globalVariable->BindingPoint().has_value()) {
+        auto binding_point = globalVariable->Attributes().binding_point;
+        if (!binding_point.has_value()) {
             return false;
         }
-        BindingPoint bindingPoint = *globalVariable->BindingPoint();
-        return cfg.bindings_ignored.find(bindingPoint) != cfg.bindings_ignored.cend();
+        return cfg.bindings_ignored.find(*binding_point) != cfg.bindings_ignored.cend();
     }
 
     /// @returns true if expr is an IndexAccessorExpression whose object is a runtime-sized array.
diff --git a/src/tint/lang/wgsl/ast/transform/single_entry_point.cc b/src/tint/lang/wgsl/ast/transform/single_entry_point.cc
index daae6f8..3311ce4 100644
--- a/src/tint/lang/wgsl/ast/transform/single_entry_point.cc
+++ b/src/tint/lang/wgsl/ast/transform/single_entry_point.cc
@@ -105,7 +105,7 @@
                         // so that its allocated ID so that it won't be affected by other
                         // stripped away overrides
                         auto* global = sem.Get(override);
-                        const auto* id = b.Id(global->OverrideId());
+                        const auto* id = b.Id(global->Attributes().override_id.value());
                         ctx.InsertFront(override->attributes, id);
                     }
                     b.AST().AddGlobalVariable(ctx.Clone(override));
diff --git a/src/tint/lang/wgsl/ast/transform/substitute_override.cc b/src/tint/lang/wgsl/ast/transform/substitute_override.cc
index 75dd9df..a36bc65 100644
--- a/src/tint/lang/wgsl/ast/transform/substitute_override.cc
+++ b/src/tint/lang/wgsl/ast/transform/substitute_override.cc
@@ -87,7 +87,7 @@
         Type ty = w->type ? ctx.Clone(w->type) : Type{};
 
         // No replacement provided, just clone the override node as a const.
-        auto iter = data->map.find(sem->OverrideId());
+        auto iter = data->map.find(sem->Attributes().override_id.value());
         if (iter == data->map.end()) {
             if (!w->initializer) {
                 b.Diagnostics().add_error(
diff --git a/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc b/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
index 145d053..0ebf6fa 100644
--- a/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
+++ b/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
@@ -796,11 +796,11 @@
             auto* sem = src.Sem().Get<sem::Parameter>(param);
             info.type = sem->Type();
 
-            if (TINT_UNLIKELY(!sem->Location().has_value())) {
+            if (TINT_UNLIKELY(!sem->Attributes().location.has_value())) {
                 TINT_ICE() << "Location missing value";
                 return;
             }
-            location_info[sem->Location().value()] = info;
+            location_info[sem->Attributes().location.value()] = info;
         } else {
             auto* builtin_attr = GetAttribute<BuiltinAttribute>(param->attributes);
             if (TINT_UNLIKELY(!builtin_attr)) {
diff --git a/src/tint/lang/wgsl/helpers/flatten_bindings_test.cc b/src/tint/lang/wgsl/helpers/flatten_bindings_test.cc
index b395664..003ef72 100644
--- a/src/tint/lang/wgsl/helpers/flatten_bindings_test.cc
+++ b/src/tint/lang/wgsl/helpers/flatten_bindings_test.cc
@@ -81,18 +81,18 @@
 
     auto* sem = flattened->Sem().Get<sem::GlobalVariable>(vars[0]);
     ASSERT_NE(sem, nullptr);
-    EXPECT_EQ(sem->BindingPoint()->group, 0u);
-    EXPECT_EQ(sem->BindingPoint()->binding, 0u);
+    EXPECT_EQ(sem->Attributes().binding_point->group, 0u);
+    EXPECT_EQ(sem->Attributes().binding_point->binding, 0u);
 
     sem = flattened->Sem().Get<sem::GlobalVariable>(vars[1]);
     ASSERT_NE(sem, nullptr);
-    EXPECT_EQ(sem->BindingPoint()->group, 0u);
-    EXPECT_EQ(sem->BindingPoint()->binding, 1u);
+    EXPECT_EQ(sem->Attributes().binding_point->group, 0u);
+    EXPECT_EQ(sem->Attributes().binding_point->binding, 1u);
 
     sem = flattened->Sem().Get<sem::GlobalVariable>(vars[2]);
     ASSERT_NE(sem, nullptr);
-    EXPECT_EQ(sem->BindingPoint()->group, 0u);
-    EXPECT_EQ(sem->BindingPoint()->binding, 2u);
+    EXPECT_EQ(sem->Attributes().binding_point->group, 0u);
+    EXPECT_EQ(sem->Attributes().binding_point->binding, 2u);
 }
 
 TEST_F(FlattenBindingsTest, NotFlat_MultipleNamespaces) {
@@ -144,20 +144,20 @@
     for (size_t i = 0; i < num_buffers; ++i) {
         auto* sem = flattened->Sem().Get<sem::GlobalVariable>(vars[i]);
         ASSERT_NE(sem, nullptr);
-        EXPECT_EQ(sem->BindingPoint()->group, 0u);
-        EXPECT_EQ(sem->BindingPoint()->binding, i);
+        EXPECT_EQ(sem->Attributes().binding_point->group, 0u);
+        EXPECT_EQ(sem->Attributes().binding_point->binding, i);
     }
     for (size_t i = 0; i < num_samplers; ++i) {
         auto* sem = flattened->Sem().Get<sem::GlobalVariable>(vars[i + num_buffers]);
         ASSERT_NE(sem, nullptr);
-        EXPECT_EQ(sem->BindingPoint()->group, 0u);
-        EXPECT_EQ(sem->BindingPoint()->binding, i);
+        EXPECT_EQ(sem->Attributes().binding_point->group, 0u);
+        EXPECT_EQ(sem->Attributes().binding_point->binding, i);
     }
     for (size_t i = 0; i < num_textures; ++i) {
         auto* sem = flattened->Sem().Get<sem::GlobalVariable>(vars[i + num_buffers + num_samplers]);
         ASSERT_NE(sem, nullptr);
-        EXPECT_EQ(sem->BindingPoint()->group, 0u);
-        EXPECT_EQ(sem->BindingPoint()->binding, i);
+        EXPECT_EQ(sem->Attributes().binding_point->group, 0u);
+        EXPECT_EQ(sem->Attributes().binding_point->binding, i);
     }
 }
 
diff --git a/src/tint/lang/wgsl/inspector/inspector.cc b/src/tint/lang/wgsl/inspector/inspector.cc
index 278773a..7f49f66 100644
--- a/src/tint/lang/wgsl/inspector/inspector.cc
+++ b/src/tint/lang/wgsl/inspector/inspector.cc
@@ -177,7 +177,7 @@
     for (auto* param : sem->Parameters()) {
         AddEntryPointInOutVariables(param->Declaration()->name->symbol.Name(),
                                     param->Declaration()->name->symbol.Name(), param->Type(),
-                                    param->Declaration()->attributes, param->Location(),
+                                    param->Declaration()->attributes, param->Attributes().location,
                                     entry_point.input_variables);
 
         entry_point.input_position_used |= ContainsBuiltin(
@@ -212,10 +212,10 @@
         auto name = decl->name->symbol.Name();
 
         auto* global = var->As<sem::GlobalVariable>();
-        if (global && global->Declaration()->Is<ast::Override>()) {
+        if (auto override_id = global->Attributes().override_id) {
             Override override;
             override.name = name;
-            override.id = global->OverrideId();
+            override.id = override_id.value();
             auto* type = var->Type();
             TINT_ASSERT(type->Is<core::type::Scalar>());
             if (type->is_bool_scalar_or_vector()) {
@@ -279,7 +279,7 @@
         // WGSL, so the resolver should catch it. Thus here the inspector just
         // assumes all definitions of the override id are the same, so only needs
         // to find the first reference to override id.
-        OverrideId override_id = global->OverrideId();
+        auto override_id = global->Attributes().override_id.value();
         if (result.find(override_id) != result.end()) {
             continue;
         }
@@ -311,9 +311,9 @@
     std::map<std::string, OverrideId> result;
     for (auto* var : program_.AST().GlobalVariables()) {
         auto* global = program_.Sem().Get<sem::GlobalVariable>(var);
-        if (global && global->Declaration()->Is<ast::Override>()) {
+        if (auto override_id = global->Attributes().override_id) {
             auto name = var->name->symbol.Name();
-            result[name] = global->OverrideId();
+            result[name] = override_id.value();
         }
     }
     return result;
@@ -527,8 +527,9 @@
         auto* texture = pair.first->As<sem::GlobalVariable>();
         auto* sampler = pair.second ? pair.second->As<sem::GlobalVariable>() : nullptr;
         SamplerTexturePair new_pair;
-        new_pair.sampler_binding_point = sampler ? *sampler->BindingPoint() : placeholder;
-        new_pair.texture_binding_point = *texture->BindingPoint();
+        new_pair.sampler_binding_point =
+            sampler ? *sampler->Attributes().binding_point : placeholder;
+        new_pair.texture_binding_point = *texture->Attributes().binding_point;
         new_pairs.push_back(new_pair);
     }
     return new_pairs;
@@ -818,18 +819,18 @@
         auto* t = c->args[static_cast<size_t>(texture_index)];
         auto* s = c->args[static_cast<size_t>(sampler_index)];
 
-        GetOriginatingResources(std::array<const ast::Expression*, 2>{t, s},
-                                [&](std::array<const sem::GlobalVariable*, 2> globals) {
-                                    auto texture_binding_point = *globals[0]->BindingPoint();
-                                    auto sampler_binding_point = *globals[1]->BindingPoint();
+        GetOriginatingResources(
+            std::array<const ast::Expression*, 2>{t, s},
+            [&](std::array<const sem::GlobalVariable*, 2> globals) {
+                auto texture_binding_point = *globals[0]->Attributes().binding_point;
+                auto sampler_binding_point = *globals[1]->Attributes().binding_point;
 
-                                    for (auto* entry_point : entry_points) {
-                                        const auto& ep_name =
-                                            entry_point->Declaration()->name->symbol.Name();
-                                        (*sampler_targets_)[ep_name].Add(
-                                            {sampler_binding_point, texture_binding_point});
-                                    }
-                                });
+                for (auto* entry_point : entry_points) {
+                    const auto& ep_name = entry_point->Declaration()->name->symbol.Name();
+                    (*sampler_targets_)[ep_name].Add(
+                        {sampler_binding_point, texture_binding_point});
+                }
+            });
     }
 }
 
diff --git a/src/tint/lang/wgsl/inspector/inspector_test.cc b/src/tint/lang/wgsl/inspector/inspector_test.cc
index 052c464..0eb5c8e 100644
--- a/src/tint/lang/wgsl/inspector/inspector_test.cc
+++ b/src/tint/lang/wgsl/inspector/inspector_test.cc
@@ -1879,15 +1879,15 @@
 
     ASSERT_TRUE(result.count("a"));
     ASSERT_TRUE(program_->Sem().Get(a));
-    EXPECT_EQ(result["a"], program_->Sem().Get(a)->OverrideId());
+    EXPECT_EQ(result["a"], program_->Sem().Get(a)->Attributes().override_id);
 
     ASSERT_TRUE(result.count("b"));
     ASSERT_TRUE(program_->Sem().Get(b));
-    EXPECT_EQ(result["b"], program_->Sem().Get(b)->OverrideId());
+    EXPECT_EQ(result["b"], program_->Sem().Get(b)->Attributes().override_id);
 
     ASSERT_TRUE(result.count("c"));
     ASSERT_TRUE(program_->Sem().Get(c));
-    EXPECT_EQ(result["c"], program_->Sem().Get(c)->OverrideId());
+    EXPECT_EQ(result["c"], program_->Sem().Get(c)->Attributes().override_id);
 }
 
 TEST_F(InspectorGetResourceBindingsTest, Empty) {
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
index a0c10a8..5e99225 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
@@ -456,12 +456,8 @@
                         }
                     });
 
-                if (param_sem->Location().has_value()) {
-                    param->SetLocation(param_sem->Location().value(), interpolation);
-                }
-                if (param_sem->BindingPoint().has_value()) {
-                    param->SetBindingPoint(param_sem->BindingPoint()->group,
-                                           param_sem->BindingPoint()->binding);
+                if (param_sem->Attributes().location.has_value()) {
+                    param->SetLocation(param_sem->Attributes().location.value(), interpolation);
                 }
             }
 
@@ -1324,8 +1320,8 @@
                 current_block_->Append(val);
 
                 if (auto* gv = sem->As<sem::GlobalVariable>(); gv && var->HasBindingPoint()) {
-                    val->SetBindingPoint(gv->BindingPoint().value().group,
-                                         gv->BindingPoint().value().binding);
+                    val->SetBindingPoint(gv->Attributes().binding_point->group,
+                                         gv->Attributes().binding_point->binding);
                 }
 
                 // Store the declaration so we can get the instruction to store too
diff --git a/src/tint/lang/wgsl/resolver/override_test.cc b/src/tint/lang/wgsl/resolver/override_test.cc
index 6bd7223..3077e79 100644
--- a/src/tint/lang/wgsl/resolver/override_test.cc
+++ b/src/tint/lang/wgsl/resolver/override_test.cc
@@ -46,7 +46,7 @@
         ASSERT_NE(sem, nullptr);
         EXPECT_EQ(sem->Declaration(), var);
         EXPECT_TRUE(sem->Declaration()->Is<ast::Override>());
-        EXPECT_EQ(sem->OverrideId().value, id);
+        EXPECT_EQ(sem->Attributes().override_id->value, id);
         EXPECT_FALSE(sem->ConstantValue());
     }
 };
diff --git a/src/tint/lang/wgsl/resolver/resolver.cc b/src/tint/lang/wgsl/resolver/resolver.cc
index c3d2f53..d5a0e93 100644
--- a/src/tint/lang/wgsl/resolver/resolver.cc
+++ b/src/tint/lang/wgsl/resolver/resolver.cc
@@ -389,7 +389,7 @@
                 }
 
                 auto o = OverrideId{static_cast<decltype(OverrideId::value)>(value)};
-                sem->SetOverrideId(o);
+                sem->Attributes().override_id = o;
 
                 // Track the constant IDs that are specified in the shader.
                 override_ids_.Add(o, sem);
@@ -626,7 +626,7 @@
                     if (!value) {
                         return kErrored;
                     }
-                    global->SetLocation(value.Get());
+                    global->Attributes().location = value.Get();
                     return kSuccess;
                 },
                 [&](const ast::IndexAttribute* attr) {
@@ -637,7 +637,7 @@
                     if (!value) {
                         return kErrored;
                     }
-                    global->SetIndex(value.Get());
+                    global->Attributes().index = value.Get();
                     return kSuccess;
                 },
                 [&](const ast::BuiltinAttribute* attr) {
@@ -675,7 +675,7 @@
         }
 
         if (group && binding) {
-            global->SetBindingPoint(BindingPoint{group.value(), binding.value()});
+            global->Attributes().binding_point = BindingPoint{group.value(), binding.value()};
         }
 
     } else {
@@ -720,7 +720,7 @@
                     if (TINT_UNLIKELY(!value)) {
                         return false;
                     }
-                    sem->SetLocation(value.Get());
+                    sem->Attributes().location = value.Get();
                     return true;
                 },
                 [&](const ast::BuiltinAttribute* attr) -> bool { return BuiltinAttribute(attr); },
@@ -766,7 +766,7 @@
             }
         }
         if (group && binding) {
-            sem->SetBindingPoint(BindingPoint{group.value(), binding.value()});
+            sem->Attributes().binding_point = BindingPoint{group.value(), binding.value()};
         }
     } else {
         for (auto* attribute : param->attributes) {
@@ -862,8 +862,8 @@
         auto* sem = sem_.Get(override);
 
         OverrideId id;
-        if (ast::HasAttribute<ast::IdAttribute>(override->attributes)) {
-            id = sem->OverrideId();
+        if (auto sem_id = sem->Attributes().override_id) {
+            id = *sem_id;
         } else {
             // No ID was specified, so allocate the next available ID.
             while (!ids_exhausted && override_ids_.Contains(next_id)) {
@@ -879,7 +879,7 @@
             increment_next_id();
         }
 
-        const_cast<sem::GlobalVariable*>(sem)->SetOverrideId(id);
+        const_cast<sem::GlobalVariable*>(sem)->Attributes().override_id = id;
     }
     return true;
 }
diff --git a/src/tint/lang/wgsl/resolver/resolver_test.cc b/src/tint/lang/wgsl/resolver/resolver_test.cc
index 9b3c171..f78bdc3 100644
--- a/src/tint/lang/wgsl/resolver/resolver_test.cc
+++ b/src/tint/lang/wgsl/resolver/resolver_test.cc
@@ -896,9 +896,9 @@
     auto* func_sem = Sem().Get(func);
     ASSERT_NE(func_sem, nullptr);
     EXPECT_EQ(func_sem->Parameters().Length(), 3u);
-    EXPECT_EQ(3u, func_sem->Parameters()[0]->Location());
-    EXPECT_FALSE(func_sem->Parameters()[1]->Location().has_value());
-    EXPECT_EQ(1u, func_sem->Parameters()[2]->Location());
+    EXPECT_EQ(3u, func_sem->Parameters()[0]->Attributes().location);
+    EXPECT_FALSE(func_sem->Parameters()[1]->Attributes().location.has_value());
+    EXPECT_EQ(1u, func_sem->Parameters()[2]->Attributes().location);
 }
 
 TEST_F(ResolverTest, Function_GlobalVariable_Location) {
@@ -910,7 +910,7 @@
 
     auto* sem = Sem().Get<sem::GlobalVariable>(var);
     ASSERT_NE(sem, nullptr);
-    EXPECT_EQ(3u, sem->Location());
+    EXPECT_EQ(3u, sem->Attributes().location);
 }
 
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
@@ -1931,8 +1931,10 @@
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
-    EXPECT_EQ(Sem().Get<sem::GlobalVariable>(s1)->BindingPoint(), (BindingPoint{1u, 2u}));
-    EXPECT_EQ(Sem().Get<sem::GlobalVariable>(s2)->BindingPoint(), (BindingPoint{3u, 4u}));
+    EXPECT_EQ(Sem().Get<sem::GlobalVariable>(s1)->Attributes().binding_point,
+              (BindingPoint{1u, 2u}));
+    EXPECT_EQ(Sem().Get<sem::GlobalVariable>(s2)->Attributes().binding_point,
+              (BindingPoint{3u, 4u}));
 }
 
 TEST_F(ResolverTest, Function_EntryPoints_StageAttribute) {
diff --git a/src/tint/lang/wgsl/resolver/validator.cc b/src/tint/lang/wgsl/resolver/validator.cc
index 929a508..7c8e2c9 100644
--- a/src/tint/lang/wgsl/resolver/validator.cc
+++ b/src/tint/lang/wgsl/resolver/validator.cc
@@ -767,17 +767,14 @@
         return false;
     }
 
-    for (auto* attr : decl->attributes) {
-        if (attr->Is<ast::IdAttribute>()) {
-            auto id = v->OverrideId();
-            if (auto var = override_ids.Find(id); var && *var != v) {
-                AddError("@id values must be unique", attr->source);
-                AddNote(
-                    "a override with an ID of " + std::to_string(id.value) +
+    if (auto id = v->Attributes().override_id) {
+        if (auto var = override_ids.Find(*id); var && *var != v) {
+            auto* attr = ast::GetAttribute<ast::IdAttribute>(v->Declaration()->attributes);
+            AddError("@id values must be unique", attr->source);
+            AddNote("a override with an ID of " + std::to_string(id->value) +
                         " was previously declared here:",
                     ast::GetAttribute<ast::IdAttribute>((*var)->Declaration()->attributes)->source);
-                return false;
-            }
+            return false;
         }
     }
 
@@ -1335,7 +1332,8 @@
         auto* param_decl = param->Declaration();
         if (!validate_entry_point_attributes(param_decl->attributes, param->Type(),
                                              param_decl->source, ParamOrRetType::kParameter,
-                                             param->Location(), std::nullopt)) {
+                                             param->Attributes().location,
+                                             param->Attributes().index)) {
             return false;
         }
     }
@@ -1389,7 +1387,7 @@
         if (!var_decl) {
             continue;
         }
-        auto bp = global->BindingPoint();
+        auto bp = global->Attributes().binding_point;
         if (!bp) {
             continue;
         }
diff --git a/src/tint/lang/wgsl/sem/function.cc b/src/tint/lang/wgsl/sem/function.cc
index 4d1d3c9..9a56376 100644
--- a/src/tint/lang/wgsl/sem/function.cc
+++ b/src/tint/lang/wgsl/sem/function.cc
@@ -81,7 +81,7 @@
             continue;
         }
 
-        if (auto bp = global->BindingPoint()) {
+        if (auto bp = global->Attributes().binding_point) {
             ret.push_back({global, *bp});
         }
     }
@@ -96,7 +96,7 @@
             continue;
         }
 
-        if (auto bp = global->BindingPoint()) {
+        if (auto bp = global->Attributes().binding_point) {
             ret.push_back({global, *bp});
         }
     }
@@ -140,7 +140,7 @@
     for (auto* global : TransitivelyReferencedGlobals()) {
         auto* unwrapped_type = global->Type()->UnwrapRef();
         if (unwrapped_type->TypeInfo().Is(type)) {
-            if (auto bp = global->BindingPoint()) {
+            if (auto bp = global->Attributes().binding_point) {
                 ret.push_back({global, *bp});
             }
         }
@@ -168,7 +168,7 @@
             continue;
         }
 
-        if (auto bp = global->BindingPoint()) {
+        if (auto bp = global->Attributes().binding_point) {
             ret.push_back({global, *bp});
         }
     }
@@ -193,7 +193,7 @@
             continue;
         }
 
-        if (auto bp = global->BindingPoint()) {
+        if (auto bp = global->Attributes().binding_point) {
             ret.push_back({global, *bp});
         }
     }
diff --git a/src/tint/lang/wgsl/sem/variable.h b/src/tint/lang/wgsl/sem/variable.h
index bca5d1b..3ee24f3 100644
--- a/src/tint/lang/wgsl/sem/variable.h
+++ b/src/tint/lang/wgsl/sem/variable.h
@@ -151,6 +151,22 @@
     const CastableBase* shadows_ = nullptr;
 };
 
+/// Attributes that can be applied to global variables
+struct GlobalVariableAttributes {
+    /// the pipeline constant ID associated with the variable
+    std::optional<tint::OverrideId> override_id;
+    /// the resource binding point for the variable, if set.
+    std::optional<tint::BindingPoint> binding_point;
+    /// The `location` attribute value for the variable, if set
+    /// @note a GlobalVariable generally doesn't have a `location` in WGSL, as it isn't allowed by
+    /// the spec. The location maybe attached by transforms such as CanonicalizeEntryPointIO.
+    std::optional<uint32_t> location;
+    /// The `index` attribute value for the variable, if set
+    /// @note a GlobalVariable generally doesn't have a `index` in WGSL, as it isn't allowed by
+    /// the spec. The location maybe attached by transforms such as CanonicalizeEntryPointIO.
+    std::optional<uint32_t> index;
+};
+
 /// GlobalVariable is a module-scope variable
 class GlobalVariable final : public Castable<GlobalVariable, Variable> {
   public:
@@ -161,34 +177,6 @@
     /// Destructor
     ~GlobalVariable() override;
 
-    /// @param binding_point the resource binding point for the parameter
-    void SetBindingPoint(std::optional<tint::BindingPoint> binding_point) {
-        binding_point_ = binding_point;
-    }
-
-    /// @returns the resource binding point for the variable
-    std::optional<tint::BindingPoint> BindingPoint() const { return binding_point_; }
-
-    /// @param id the constant identifier to assign to this variable
-    void SetOverrideId(OverrideId id) { override_id_ = id; }
-
-    /// @returns the pipeline constant ID associated with the variable
-    tint::OverrideId OverrideId() const { return override_id_; }
-
-    /// @param location the location value for the parameter, if set
-    /// @note a GlobalVariable generally doesn't have a `location` in WGSL, as it isn't allowed by
-    /// the spec. The location maybe attached by transforms such as CanonicalizeEntryPointIO.
-    void SetLocation(std::optional<uint32_t> location) { location_ = location; }
-
-    /// @returns the location value for the parameter, if set
-    std::optional<uint32_t> Location() const { return location_; }
-
-    /// @param index the index value for the parameter, if set
-    void SetIndex(std::optional<uint32_t> index) { index_ = index; }
-
-    /// @returns the index value for the parameter, if set
-    std::optional<uint32_t> Index() const { return index_; }
-
     /// Records that this variable (transitively) references the given override variable.
     /// @param var the module-scope override variable
     void AddTransitivelyReferencedOverride(const GlobalVariable* var);
@@ -198,12 +186,30 @@
         return transitively_referenced_overrides_;
     }
 
+    /// @return the mutable attributes for the variable
+    GlobalVariableAttributes& Attributes() { return attributes_; }
+
+    /// @return the immutable attributes for the variable
+    const GlobalVariableAttributes& Attributes() const { return attributes_; }
+
   private:
     std::optional<tint::BindingPoint> binding_point_;
     tint::OverrideId override_id_;
-    std::optional<uint32_t> location_;
-    std::optional<uint32_t> index_;
     UniqueVector<const GlobalVariable*, 4> transitively_referenced_overrides_;
+    GlobalVariableAttributes attributes_;
+};
+
+/// Attributes that can be applied to parameters
+struct ParameterAttributes {
+    /// the resource binding point for the variable, if set.
+    /// @note a Parameter generally doesn't have a `group` or `binding` attribute in WGSL, as it
+    /// isn't allowed by the spec. The binding point maybe attached by transforms such as
+    /// CanonicalizeEntryPointIO.
+    std::optional<tint::BindingPoint> binding_point;
+    /// The `location` attribute value for the variable, if set
+    std::optional<uint32_t> location;
+    /// The `index` attribute value for the variable, if set
+    std::optional<uint32_t> index;
 };
 
 /// Parameter is a function parameter
@@ -227,9 +233,6 @@
         return static_cast<const ast::Parameter*>(Variable::Declaration());
     }
 
-    /// @param index the index value for the parameter, if set
-    void SetIndex(uint32_t index) { index_ = index; }
-
     /// @return the index of the parameter in the function
     uint32_t Index() const { return index_; }
 
@@ -252,27 +255,18 @@
     /// @returns the Type, Function or Variable that this local variable shadows
     const CastableBase* Shadows() const { return shadows_; }
 
-    /// @param binding_point the resource binding point for the parameter
-    void SetBindingPoint(std::optional<tint::BindingPoint> binding_point) {
-        binding_point_ = binding_point;
-    }
+    /// @return the mutable attributes for the parameter
+    ParameterAttributes& Attributes() { return attributes_; }
 
-    /// @returns the resource binding point for the parameter
-    std::optional<tint::BindingPoint> BindingPoint() const { return binding_point_; }
-
-    /// @param location the location value for the parameter, if set
-    void SetLocation(std::optional<uint32_t> location) { location_ = location; }
-
-    /// @returns the location value for the parameter, if set
-    std::optional<uint32_t> Location() const { return location_; }
+    /// @return the immutable attributes for the parameter
+    const ParameterAttributes& Attributes() const { return attributes_; }
 
   private:
-    uint32_t index_ = 0;
+    const uint32_t index_ = 0;
     core::ParameterUsage usage_ = core::ParameterUsage::kNone;
     CallTarget const* owner_ = nullptr;
     const CastableBase* shadows_ = nullptr;
-    std::optional<tint::BindingPoint> binding_point_;
-    std::optional<uint32_t> location_;
+    ParameterAttributes attributes_;
 };
 
 /// VariableUser holds the semantic information for an identifier expression