Import Tint changes from Dawn

Changes:
  - be4c9f48aaef9f2144a654a847b76a6a0050ec5b Convert @group to an expression by dan sinclair <dsinclair@chromium.org>
  - f9b831c39a1f436fe7b5a09a458b6704d2aa69ed Convert @binding to an expression by dan sinclair <dsinclair@chromium.org>
  - cbe8b36256603aae5b0709fdde0cda2b75586b8c tint: spir-v reader: add support for GLSLstd450Determinant by Antonio Maiorano <amaiorano@google.com>
GitOrigin-RevId: be4c9f48aaef9f2144a654a847b76a6a0050ec5b
Change-Id: Id17cb4748974b969ee113e269f98a068098f9bf1
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/100701
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/ast/binding_attribute.cc b/src/tint/ast/binding_attribute.cc
index 8180f99..405fe98 100644
--- a/src/tint/ast/binding_attribute.cc
+++ b/src/tint/ast/binding_attribute.cc
@@ -22,7 +22,10 @@
 
 namespace tint::ast {
 
-BindingAttribute::BindingAttribute(ProgramID pid, NodeID nid, const Source& src, uint32_t val)
+BindingAttribute::BindingAttribute(ProgramID pid,
+                                   NodeID nid,
+                                   const Source& src,
+                                   const ast::Expression* val)
     : Base(pid, nid, src), value(val) {}
 
 BindingAttribute::~BindingAttribute() = default;
@@ -34,7 +37,8 @@
 const BindingAttribute* BindingAttribute::Clone(CloneContext* ctx) const {
     // Clone arguments outside of create() call to have deterministic ordering
     auto src = ctx->Clone(source);
-    return ctx->dst->create<BindingAttribute>(src, value);
+    auto* value_ = ctx->Clone(value);
+    return ctx->dst->create<BindingAttribute>(src, value_);
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/binding_attribute.h b/src/tint/ast/binding_attribute.h
index b5379b8..39ca2b6 100644
--- a/src/tint/ast/binding_attribute.h
+++ b/src/tint/ast/binding_attribute.h
@@ -18,6 +18,7 @@
 #include <string>
 
 #include "src/tint/ast/attribute.h"
+#include "src/tint/ast/expression.h"
 
 namespace tint::ast {
 
@@ -28,8 +29,8 @@
     /// @param pid the identifier of the program that owns this node
     /// @param nid the unique node identifier
     /// @param src the source of this node
-    /// @param value the binding value
-    BindingAttribute(ProgramID pid, NodeID nid, const Source& src, uint32_t value);
+    /// @param value the binding value expression
+    BindingAttribute(ProgramID pid, NodeID nid, const Source& src, const ast::Expression* value);
     ~BindingAttribute() override;
 
     /// @returns the WGSL name for the attribute
@@ -41,8 +42,8 @@
     /// @return the newly cloned node
     const BindingAttribute* Clone(CloneContext* ctx) const override;
 
-    /// the binding value
-    const uint32_t value;
+    /// the binding value expression
+    const ast::Expression* const value;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/binding_attribute_test.cc b/src/tint/ast/binding_attribute_test.cc
index 8000933..ec8538c 100644
--- a/src/tint/ast/binding_attribute_test.cc
+++ b/src/tint/ast/binding_attribute_test.cc
@@ -17,11 +17,12 @@
 namespace tint::ast {
 namespace {
 
+using namespace tint::number_suffixes;  // NOLINT
 using BindingAttributeTest = TestHelper;
 
 TEST_F(BindingAttributeTest, Creation) {
-    auto* d = create<BindingAttribute>(2u);
-    EXPECT_EQ(2u, d->value);
+    auto* d = Binding(2_a);
+    EXPECT_TRUE(d->value->Is<IntLiteralExpression>());
 }
 
 }  // namespace
diff --git a/src/tint/ast/builtin_texture_helper_test.cc b/src/tint/ast/builtin_texture_helper_test.cc
index 882c0bb..37d4d65 100644
--- a/src/tint/ast/builtin_texture_helper_test.cc
+++ b/src/tint/ast/builtin_texture_helper_test.cc
@@ -142,8 +142,8 @@
 
 const ast::Variable* TextureOverloadCase::BuildTextureVariable(ProgramBuilder* b) const {
     utils::Vector attrs{
-        b->create<ast::GroupAttribute>(0u),
-        b->create<ast::BindingAttribute>(0u),
+        b->Group(0_u),
+        b->Binding(0_a),
     };
     switch (texture_kind) {
         case ast::builtin::test::TextureKind::kRegular:
@@ -175,10 +175,7 @@
 }
 
 const ast::Variable* TextureOverloadCase::BuildSamplerVariable(ProgramBuilder* b) const {
-    utils::Vector attrs = {
-        b->create<ast::GroupAttribute>(0u),
-        b->create<ast::BindingAttribute>(1u),
-    };
+    utils::Vector attrs = {b->Group(0_a), b->Binding(1_a)};
     return b->GlobalVar("sampler", b->ty.sampler(sampler_kind), attrs);
 }
 
diff --git a/src/tint/ast/group_attribute.cc b/src/tint/ast/group_attribute.cc
index 9d01111..9f44003 100644
--- a/src/tint/ast/group_attribute.cc
+++ b/src/tint/ast/group_attribute.cc
@@ -22,7 +22,10 @@
 
 namespace tint::ast {
 
-GroupAttribute::GroupAttribute(ProgramID pid, NodeID nid, const Source& src, uint32_t val)
+GroupAttribute::GroupAttribute(ProgramID pid,
+                               NodeID nid,
+                               const Source& src,
+                               const ast::Expression* val)
     : Base(pid, nid, src), value(val) {}
 
 GroupAttribute::~GroupAttribute() = default;
@@ -34,7 +37,8 @@
 const GroupAttribute* GroupAttribute::Clone(CloneContext* ctx) const {
     // Clone arguments outside of create() call to have deterministic ordering
     auto src = ctx->Clone(source);
-    return ctx->dst->create<GroupAttribute>(src, value);
+    auto* value_ = ctx->Clone(value);
+    return ctx->dst->create<GroupAttribute>(src, value_);
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/group_attribute.h b/src/tint/ast/group_attribute.h
index 66d6ccb..5110489 100644
--- a/src/tint/ast/group_attribute.h
+++ b/src/tint/ast/group_attribute.h
@@ -18,6 +18,7 @@
 #include <string>
 
 #include "src/tint/ast/attribute.h"
+#include "src/tint/ast/expression.h"
 
 namespace tint::ast {
 
@@ -28,8 +29,8 @@
     /// @param pid the identifier of the program that owns this node
     /// @param nid the unique node identifier
     /// @param src the source of this node
-    /// @param value the group value
-    GroupAttribute(ProgramID pid, NodeID nid, const Source& src, uint32_t value);
+    /// @param value the group value expression
+    GroupAttribute(ProgramID pid, NodeID nid, const Source& src, const ast::Expression* value);
     ~GroupAttribute() override;
 
     /// @returns the WGSL name for the attribute
@@ -41,8 +42,8 @@
     /// @return the newly cloned node
     const GroupAttribute* Clone(CloneContext* ctx) const override;
 
-    /// The group value
-    const uint32_t value;
+    /// The group value expression
+    const ast::Expression* const value;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/group_attribute_test.cc b/src/tint/ast/group_attribute_test.cc
index 4a6dd1d..616ea37 100644
--- a/src/tint/ast/group_attribute_test.cc
+++ b/src/tint/ast/group_attribute_test.cc
@@ -17,11 +17,12 @@
 namespace tint::ast {
 namespace {
 
+using namespace tint::number_suffixes;  // NOLINT
 using GroupAttributeTest = TestHelper;
 
 TEST_F(GroupAttributeTest, Creation) {
-    auto* d = create<GroupAttribute>(2u);
-    EXPECT_EQ(2u, d->value);
+    auto* d = Group(2_a);
+    EXPECT_TRUE(d->value->Is<IntLiteralExpression>());
 }
 
 }  // namespace
diff --git a/src/tint/ast/variable_test.cc b/src/tint/ast/variable_test.cc
index 14fb766..e67336d 100644
--- a/src/tint/ast/variable_test.cc
+++ b/src/tint/ast/variable_test.cc
@@ -106,7 +106,7 @@
 }
 
 TEST_F(VariableTest, HasBindingPoint_BothProvided) {
-    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, Binding(2), Group(1));
+    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, Binding(2_a), Group(1_a));
     EXPECT_TRUE(var->HasBindingPoint());
 }
 
@@ -116,12 +116,12 @@
 }
 
 TEST_F(VariableTest, HasBindingPoint_MissingGroupAttribute) {
-    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, Binding(2));
+    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, Binding(2_a));
     EXPECT_FALSE(var->HasBindingPoint());
 }
 
 TEST_F(VariableTest, HasBindingPoint_MissingBindingAttribute) {
-    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, Group(1));
+    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, Group(1_a));
     EXPECT_FALSE(var->HasBindingPoint());
 }
 
diff --git a/src/tint/inspector/test_inspector_builder.cc b/src/tint/inspector/test_inspector_builder.cc
index 3d67876..342167c 100644
--- a/src/tint/inspector/test_inspector_builder.cc
+++ b/src/tint/inspector/test_inspector_builder.cc
@@ -126,7 +126,7 @@
                                         const ast::Type* type,
                                         uint32_t group,
                                         uint32_t binding) {
-    GlobalVar(name, type, ast::StorageClass::kUniform, Binding(binding), Group(group));
+    GlobalVar(name, type, ast::StorageClass::kUniform, Binding(AInt(binding)), Group(AInt(group)));
 }
 
 void InspectorBuilder::AddWorkgroupStorage(const std::string& name, const ast::Type* type) {
@@ -138,7 +138,8 @@
                                         ast::Access access,
                                         uint32_t group,
                                         uint32_t binding) {
-    GlobalVar(name, type, ast::StorageClass::kStorage, access, Binding(binding), Group(group));
+    GlobalVar(name, type, ast::StorageClass::kStorage, access, Binding(AInt(binding)),
+              Group(AInt(group)));
 }
 
 void InspectorBuilder::MakeStructVariableReferenceBodyFunction(
@@ -170,20 +171,20 @@
 }
 
 void InspectorBuilder::AddSampler(const std::string& name, uint32_t group, uint32_t binding) {
-    GlobalVar(name, sampler_type(), Binding(binding), Group(group));
+    GlobalVar(name, sampler_type(), Binding(AInt(binding)), Group(AInt(group)));
 }
 
 void InspectorBuilder::AddComparisonSampler(const std::string& name,
                                             uint32_t group,
                                             uint32_t binding) {
-    GlobalVar(name, comparison_sampler_type(), Binding(binding), Group(group));
+    GlobalVar(name, comparison_sampler_type(), Binding(AInt(binding)), Group(AInt(group)));
 }
 
 void InspectorBuilder::AddResource(const std::string& name,
                                    const ast::Type* type,
                                    uint32_t group,
                                    uint32_t binding) {
-    GlobalVar(name, type, Binding(binding), Group(group));
+    GlobalVar(name, type, Binding(AInt(binding)), Group(AInt(group)));
 }
 
 void InspectorBuilder::AddGlobalVariable(const std::string& name, const ast::Type* type) {
@@ -285,7 +286,7 @@
                                          const ast::Type* type,
                                          uint32_t group,
                                          uint32_t binding) {
-    GlobalVar(name, type, Binding(binding), Group(group));
+    GlobalVar(name, type, Binding(AInt(binding)), Group(AInt(group)));
 }
 
 const ast::Function* InspectorBuilder::MakeStorageTextureBodyFunction(
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index c797aab..033577b 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -2290,15 +2290,37 @@
     }
 
     /// Creates the ast::GroupAttribute
-    /// @param value group attribute index
+    /// @param value group attribute index expresion
     /// @returns the group attribute pointer
-    const ast::GroupAttribute* Group(uint32_t value) { return create<ast::GroupAttribute>(value); }
+    template <typename EXPR>
+    const ast::GroupAttribute* Group(EXPR&& value) {
+        return create<ast::GroupAttribute>(Expr(std::forward<EXPR>(value)));
+    }
+
+    /// Creates the ast::GroupAttribute
+    /// @param source the source
+    /// @param value group attribute index expression
+    /// @returns the group attribute pointer
+    template <typename EXPR>
+    const ast::GroupAttribute* Group(const Source& source, EXPR&& value) {
+        return create<ast::GroupAttribute>(source, Expr(std::forward<EXPR>(value)));
+    }
 
     /// Creates the ast::BindingAttribute
-    /// @param value the binding index
+    /// @param value the binding index expression
     /// @returns the binding deocration pointer
-    const ast::BindingAttribute* Binding(uint32_t value) {
-        return create<ast::BindingAttribute>(value);
+    template <typename EXPR>
+    const ast::BindingAttribute* Binding(EXPR&& value) {
+        return create<ast::BindingAttribute>(Expr(std::forward<EXPR>(value)));
+    }
+
+    /// Creates the ast::BindingAttribute
+    /// @param source the source
+    /// @param value the binding index expression
+    /// @returns the binding deocration pointer
+    template <typename EXPR>
+    const ast::BindingAttribute* Binding(const Source& source, EXPR&& value) {
+        return create<ast::BindingAttribute>(source, Expr(std::forward<EXPR>(value)));
     }
 
     /// Creates an ast::Function and registers it with the ast::Module.
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
index a57984b..fcf149b 100644
--- a/src/tint/reader/spirv/function.cc
+++ b/src/tint/reader/spirv/function.cc
@@ -315,6 +315,8 @@
             return "cross";
         case GLSLstd450Degrees:
             return "degrees";
+        case GLSLstd450Determinant:
+            return "determinant";
         case GLSLstd450Distance:
             return "distance";
         case GLSLstd450Exp:
@@ -413,7 +415,6 @@
         case GLSLstd450Acosh:
         case GLSLstd450Atanh:
 
-        case GLSLstd450Determinant:
         case GLSLstd450MatrixInverse:
 
         case GLSLstd450Modf:
@@ -3960,6 +3961,12 @@
         // Some GLSLstd450 builtins have scalar forms not supported by WGSL.
         // Emulate them.
         switch (ext_opcode) {
+            case GLSLstd450Determinant: {
+                auto m = MakeOperand(inst, 2);
+                TINT_ASSERT(Reader, m.type->Is<Matrix>());
+                return {ty_.F32(), builder_.Call(Source{}, "determinant", m.expr)};
+            }
+
             case GLSLstd450Normalize:
                 // WGSL does not have scalar form of the normalize builtin.
                 // The answer would be 1 anyway, so return that directly.
diff --git a/src/tint/reader/spirv/function_glsl_std_450_test.cc b/src/tint/reader/spirv/function_glsl_std_450_test.cc
index 4836a68..f97f3e9 100644
--- a/src/tint/reader/spirv/function_glsl_std_450_test.cc
+++ b/src/tint/reader/spirv/function_glsl_std_450_test.cc
@@ -52,6 +52,9 @@
   OpName %v3f2 "v3f2"
   OpName %v4f1 "v4f1"
   OpName %v4f2 "v4f2"
+  OpName %m2x2f1 "m2x2f1"
+  OpName %m3x3f1 "m3x3f1"
+  OpName %m4x4f1 "m4x4f1"
 
   %void = OpTypeVoid
   %voidfn = OpTypeFunction %void
@@ -75,6 +78,9 @@
   %v2float = OpTypeVector %float 2
   %v3float = OpTypeVector %float 3
   %v4float = OpTypeVector %float 4
+  %mat2v2float = OpTypeMatrix %v2float 2
+  %mat3v3float = OpTypeMatrix %v3float 3
+  %mat4v4float = OpTypeMatrix %v4float 4
 
   %v2uint_10_20 = OpConstantComposite %v2uint %uint_10 %uint_20
   %v2uint_20_10 = OpConstantComposite %v2uint %uint_20 %uint_10
@@ -91,6 +97,10 @@
 
   %v4float_50_50_50_50 = OpConstantComposite %v4float %float_50 %float_50 %float_50 %float_50
 
+  %mat2v2float_50_60 = OpConstantComposite %mat2v2float %v2float_50_60 %v2float_50_60
+  %mat3v3float_50_60_70 = OpConstantComposite %mat2v2float %v3float_50_60_70 %v3float_50_60_70 %v3float_50_60_70
+  %mat4v4float_50_50_50_50 = OpConstantComposite %mat2v2float %v4float_50_50_50_50 %v4float_50_50_50_50 %v4float_50_50_50_50 %v4float_50_50_50_50
+
   %100 = OpFunction %void None %voidfn
   %entry = OpLabel
 
@@ -123,6 +133,10 @@
 
   %v4f1 = OpCopyObject %v4float %v4float_50_50_50_50
   %v4f2 = OpCopyObject %v4float %v4f1
+
+  %m2x2f1 = OpCopyObject %mat2v2float %mat2v2float_50_60
+  %m3x3f1 = OpCopyObject %mat3v3float %mat3v3float_50_60_70
+  %m4x4f1 = OpCopyObject %mat4v4float %mat4v4float_50_50_50_50
 )";
 }
 
@@ -1122,5 +1136,27 @@
     EXPECT_THAT(body, HasSubstr(expected)) << body;
 }
 
+using GlslStd450_Determinant = SpvParserTestBase<::testing::TestWithParam<std::string>>;
+TEST_P(GlslStd450_Determinant, Test) {
+    const auto assembly = Preamble() + R"(
+     %1 = OpExtInst %float %glsl Determinant %)" +
+                          GetParam() + R"(
+     OpReturn
+     OpFunctionEnd
+  )";
+    auto p = parser(test::Assemble(assembly));
+    ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+    auto fe = p->function_emitter(100);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    auto ast_body = fe.ast_body();
+    const auto body = test::ToString(p->program(), ast_body);
+    std::string expected = "let x_1 : f32 = determinant(" + GetParam() + ");";
+
+    EXPECT_THAT(body, HasSubstr(expected)) << body;
+}
+INSTANTIATE_TEST_SUITE_P(Test,
+                         GlslStd450_Determinant,
+                         ::testing::Values("m2x2f1", "m3x3f1", "m4x4f1"));
+
 }  // namespace
 }  // namespace tint::reader::spirv
diff --git a/src/tint/reader/spirv/parser_impl.cc b/src/tint/reader/spirv/parser_impl.cc
index f9117a5..85caaf9 100644
--- a/src/tint/reader/spirv/parser_impl.cc
+++ b/src/tint/reader/spirv/parser_impl.cc
@@ -1690,13 +1690,13 @@
                 return Fail() << "malformed DescriptorSet decoration on ID " << id
                               << ": has no operand";
             }
-            decorations->Push(create<ast::GroupAttribute>(Source{}, deco[1]));
+            decorations->Push(builder_.Group(Source{}, AInt(deco[1])));
         }
         if (deco[0] == SpvDecorationBinding) {
             if (deco.size() == 1) {
                 return Fail() << "malformed Binding decoration on ID " << id << ": has no operand";
             }
-            decorations->Push(create<ast::BindingAttribute>(Source{}, deco[1]));
+            decorations->Push(builder_.Binding(Source{}, AInt(deco[1])));
         }
     }
 
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index bf95b4e..aee4b99 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -3456,7 +3456,9 @@
             }
             match(Token::Type::kComma);
 
-            return create<ast::BindingAttribute>(t.source(), val.value);
+            return create<ast::BindingAttribute>(
+                t.source(), create<ast::IntLiteralExpression>(
+                                val.value, ast::IntLiteralExpression::Suffix::kNone));
         });
     }
 
@@ -3491,7 +3493,9 @@
             }
             match(Token::Type::kComma);
 
-            return create<ast::GroupAttribute>(t.source(), val.value);
+            return create<ast::GroupAttribute>(
+                t.source(), create<ast::IntLiteralExpression>(
+                                val.value, ast::IntLiteralExpression::Suffix::kNone));
         });
     }
 
diff --git a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
index ef77891..e48cc85 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
@@ -364,7 +364,11 @@
     ASSERT_TRUE(var_attr->Is<ast::BindingAttribute>());
 
     auto* binding = var_attr->As<ast::BindingAttribute>();
-    EXPECT_EQ(binding->value, 4u);
+    ASSERT_TRUE(binding->value->Is<ast::IntLiteralExpression>());
+
+    auto* expr = binding->value->As<ast::IntLiteralExpression>();
+    EXPECT_EQ(expr->value, 4);
+    EXPECT_EQ(expr->suffix, ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, Attribute_Binding_TrailingComma) {
@@ -379,7 +383,11 @@
     ASSERT_TRUE(var_attr->Is<ast::BindingAttribute>());
 
     auto* binding = var_attr->As<ast::BindingAttribute>();
-    EXPECT_EQ(binding->value, 4u);
+    ASSERT_TRUE(binding->value->Is<ast::IntLiteralExpression>());
+
+    auto* expr = binding->value->As<ast::IntLiteralExpression>();
+    EXPECT_EQ(expr->value, 4);
+    EXPECT_EQ(expr->suffix, ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, Attribute_Binding_MissingLeftParen) {
@@ -434,7 +442,11 @@
     ASSERT_TRUE(var_attr->Is<ast::GroupAttribute>());
 
     auto* group = var_attr->As<ast::GroupAttribute>();
-    EXPECT_EQ(group->value, 4u);
+    ASSERT_TRUE(group->value->Is<ast::IntLiteralExpression>());
+
+    auto* expr = group->value->As<ast::IntLiteralExpression>();
+    EXPECT_EQ(expr->value, 4);
+    EXPECT_EQ(expr->suffix, ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, Attribute_group_TrailingComma) {
@@ -449,7 +461,11 @@
     ASSERT_TRUE(var_attr->Is<ast::GroupAttribute>());
 
     auto* group = var_attr->As<ast::GroupAttribute>();
-    EXPECT_EQ(group->value, 4u);
+    ASSERT_TRUE(group->value->Is<ast::IntLiteralExpression>());
+
+    auto* expr = group->value->As<ast::IntLiteralExpression>();
+    EXPECT_EQ(expr->value, 4);
+    EXPECT_EQ(expr->suffix, ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, Attribute_Group_MissingLeftParen) {
diff --git a/src/tint/resolver/assignment_validation_test.cc b/src/tint/resolver/assignment_validation_test.cc
index 8985e5d..36ce0cb 100644
--- a/src/tint/resolver/assignment_validation_test.cc
+++ b/src/tint/resolver/assignment_validation_test.cc
@@ -33,7 +33,7 @@
                                  Member("m", ty.i32()),
                              });
     GlobalVar(Source{{12, 34}}, "a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              Binding(0), Group(0));
+              Binding(0_a), Group(0_a));
 
     WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("a", "m"), 1_i));
 
@@ -234,8 +234,8 @@
                                   ast::Access::kWrite);
     };
 
-    GlobalVar("a", make_type(), Binding(0), Group(0));
-    GlobalVar("b", make_type(), Binding(1), Group(0));
+    GlobalVar("a", make_type(), Binding(0_a), Group(0_a));
+    GlobalVar("b", make_type(), Binding(1_a), Group(0_a));
 
     WrapInFunction(Assign(Source{{56, 78}}, "a", "b"));
 
@@ -252,7 +252,7 @@
                                  Member("a", ty.atomic(ty.i32())),
                              });
     GlobalVar(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              Binding(0), Group(0));
+              Binding(0_a), Group(0_a));
 
     WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("v", "a"), MemberAccessor("v", "a")));
 
@@ -269,7 +269,7 @@
                                  Member("a", ty.array(ty.f32())),
                              });
     GlobalVar(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              Binding(0), Group(0));
+              Binding(0_a), Group(0_a));
 
     WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("v", "a"), MemberAccessor("v", "a")));
 
@@ -288,7 +288,7 @@
     auto* s = Structure("S", utils::Vector{
                                  Member("arr", ty.array<i32>()),
                              });
-    GlobalVar("s", ty.Of(s), ast::StorageClass::kStorage, Group(0), Binding(0));
+    GlobalVar("s", ty.Of(s), ast::StorageClass::kStorage, Group(0_a), Binding(0_a));
 
     WrapInFunction(Assign(Phony(), Expr(Source{{12, 34}}, "s")));
 
@@ -310,7 +310,7 @@
     auto* s = Structure("S", utils::Vector{
                                  Member("arr", ty.array<i32>()),
                              });
-    GlobalVar("s", ty.Of(s), ast::StorageClass::kStorage, Group(0), Binding(0));
+    GlobalVar("s", ty.Of(s), ast::StorageClass::kStorage, Group(0_a), Binding(0_a));
 
     WrapInFunction(Assign(Phony(), MemberAccessor(Source{{12, 34}}, "s", "arr")));
 
@@ -360,11 +360,11 @@
     auto* U = Structure("U", utils::Vector{
                                  Member("i", ty.i32()),
                              });
-    GlobalVar("tex", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(0),
-              Binding(0));
-    GlobalVar("smp", ty.sampler(ast::SamplerKind::kSampler), Group(0), Binding(1));
-    GlobalVar("u", ty.Of(U), ast::StorageClass::kUniform, Group(0), Binding(2));
-    GlobalVar("s", ty.Of(S), ast::StorageClass::kStorage, Group(0), Binding(3));
+    GlobalVar("tex", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(0_a),
+              Binding(0_a));
+    GlobalVar("smp", ty.sampler(ast::SamplerKind::kSampler), Group(0_a), Binding(1_a));
+    GlobalVar("u", ty.Of(U), ast::StorageClass::kUniform, Group(0_a), Binding(2_a));
+    GlobalVar("s", ty.Of(S), ast::StorageClass::kStorage, Group(0_a), Binding(3_a));
     GlobalVar("wg", ty.array<f32, 10>(), ast::StorageClass::kWorkgroup);
 
     WrapInFunction(Assign(Phony(), 1_i),                                    //
diff --git a/src/tint/resolver/atomics_test.cc b/src/tint/resolver/atomics_test.cc
index 405bc27..a22977e 100644
--- a/src/tint/resolver/atomics_test.cc
+++ b/src/tint/resolver/atomics_test.cc
@@ -22,6 +22,8 @@
 namespace tint::resolver {
 namespace {
 
+using namespace tint::number_suffixes;  // NOLINT
+
 struct ResolverAtomicTest : public resolver::TestHelper, public testing::Test {};
 
 TEST_F(ResolverAtomicTest, GlobalWorkgroupI32) {
@@ -47,7 +49,7 @@
 TEST_F(ResolverAtomicTest, GlobalStorageStruct) {
     auto* s = Structure("s", utils::Vector{Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
     auto* g = GlobalVar("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-                        Binding(0), Group(0));
+                        Binding(0_a), Group(0_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
diff --git a/src/tint/resolver/atomics_validation_test.cc b/src/tint/resolver/atomics_validation_test.cc
index eb4f43e..aa4c1c2 100644
--- a/src/tint/resolver/atomics_validation_test.cc
+++ b/src/tint/resolver/atomics_validation_test.cc
@@ -34,15 +34,15 @@
 
 TEST_F(ResolverAtomicValidationTest, StorageClass_Storage) {
     GlobalVar("g", ty.atomic(Source{{12, 34}}, ty.i32()), ast::StorageClass::kStorage,
-              ast::Access::kReadWrite, Group(0), Binding(0));
+              ast::Access::kReadWrite, Group(0_a), Binding(0_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(ResolverAtomicValidationTest, StorageClass_Storage_Struct) {
     auto* s = Structure("s", utils::Vector{Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
-    GlobalVar("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(0),
-              Binding(0));
+    GlobalVar("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(0_a),
+              Binding(0_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -210,7 +210,7 @@
 TEST_F(ResolverAtomicValidationTest, Struct_AccessMode_Read) {
     auto* s = Structure("s", utils::Vector{Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
     GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              Group(0), Binding(0));
+              Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -222,7 +222,7 @@
 TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_Struct) {
     auto* s = Structure("s", utils::Vector{Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
     GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              Group(0), Binding(0));
+              Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -240,7 +240,7 @@
         Structure("Inner", utils::Vector{Member("m", ty.atomic(Source{{12, 34}}, ty.i32()))});
     auto* Outer = Structure("Outer", utils::Vector{Member("m", ty.Of(Inner))});
     GlobalVar(Source{{56, 78}}, "g", ty.Of(Outer), ast::StorageClass::kStorage, ast::Access::kRead,
-              Group(0), Binding(0));
+              Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -258,7 +258,7 @@
         Structure("Inner", utils::Vector{Member(Source{{12, 34}}, "m", ty.atomic(ty.i32()))});
     auto* Outer = Structure("Outer", utils::Vector{Member("m", ty.Of(Inner))});
     GlobalVar(Source{{56, 78}}, "g", ty.Of(Outer), ast::StorageClass::kStorage, ast::Access::kRead,
-              Group(0), Binding(0));
+              Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -300,7 +300,7 @@
     auto* s1 = Structure("S1", utils::Vector{Member("x", ty.Of(s2))});
     auto* s0 = Structure("S0", utils::Vector{Member("x", ty.Of(s1))});
     GlobalVar(Source{{56, 78}}, "g", ty.Of(s0), ast::StorageClass::kStorage, ast::Access::kRead,
-              Group(0), Binding(0));
+              Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc
index 70d4612..fd06200 100644
--- a/src/tint/resolver/attribute_validation_test.cc
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -91,11 +91,11 @@
         case AttributeKind::kAlign:
             return {builder.MemberAlign(source, 4_u)};
         case AttributeKind::kBinding:
-            return {builder.create<ast::BindingAttribute>(source, 1u)};
+            return {builder.Binding(source, 1_a)};
         case AttributeKind::kBuiltin:
             return {builder.Builtin(source, ast::BuiltinValue::kPosition)};
         case AttributeKind::kGroup:
-            return {builder.create<ast::GroupAttribute>(source, 1u)};
+            return {builder.Group(source, 1_a)};
         case AttributeKind::kId:
             return {builder.create<ast::IdAttribute>(source, 0u)};
         case AttributeKind::kInterpolate:
@@ -116,8 +116,7 @@
         case AttributeKind::kWorkgroup:
             return {builder.create<ast::WorkgroupAttribute>(source, builder.Expr(1_i))};
         case AttributeKind::kBindingAndGroup:
-            return {builder.create<ast::BindingAttribute>(source, 1_u),
-                    builder.create<ast::GroupAttribute>(source, 1_u)};
+            return {builder.Binding(source, 1_a), builder.Group(source, 1_a)};
     }
     return {};
 }
@@ -509,8 +508,8 @@
 TEST_F(EntryPointParameterAttributeTest, DuplicateInternalAttribute) {
     auto* s = Param("s", ty.sampler(ast::SamplerKind::kSampler),
                     utils::Vector{
-                        create<ast::BindingAttribute>(0u),
-                        create<ast::GroupAttribute>(0u),
+                        Binding(0_a),
+                        Group(0_a),
                         Disable(ast::DisabledValidation::kBindingPointCollision),
                         Disable(ast::DisabledValidation::kEntryPointParameter),
                     });
@@ -734,9 +733,8 @@
                                          TestParams{AttributeKind::kBindingAndGroup, true}));
 
 TEST_F(VariableAttributeTest, DuplicateAttribute) {
-    GlobalVar("a", ty.sampler(ast::SamplerKind::kSampler),
-              create<ast::BindingAttribute>(Source{{12, 34}}, 2u), create<ast::GroupAttribute>(2u),
-              create<ast::BindingAttribute>(Source{{56, 78}}, 3u));
+    GlobalVar("a", ty.sampler(ast::SamplerKind::kSampler), Binding(Source{{12, 34}}, 2_a),
+              Group(2_a), Binding(Source{{56, 78}}, 3_a));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -745,10 +743,7 @@
 }
 
 TEST_F(VariableAttributeTest, LocalVariable) {
-    auto* v = Var("a", ty.f32(),
-                  utils::Vector{
-                      create<ast::BindingAttribute>(Source{{12, 34}}, 2u),
-                  });
+    auto* v = Var("a", ty.f32(), utils::Vector{Binding(Source{{12, 34}}, 2_a)});
 
     WrapInFunction(v);
 
@@ -1015,7 +1010,7 @@
 }
 
 TEST_F(ResourceAttributeTest, BindingPairMissingBinding) {
-    GlobalVar(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler), Group(1));
+    GlobalVar(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler), Group(1_a));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1023,7 +1018,7 @@
 }
 
 TEST_F(ResourceAttributeTest, BindingPairMissingGroup) {
-    GlobalVar(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler), Binding(1));
+    GlobalVar(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler), Binding(1_a));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1032,9 +1027,9 @@
 
 TEST_F(ResourceAttributeTest, BindingPointUsedTwiceByEntryPoint) {
     GlobalVar(Source{{12, 34}}, "A", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-              Binding(1), Group(2));
+              Binding(1_a), Group(2_a));
     GlobalVar(Source{{56, 78}}, "B", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-              Binding(1), Group(2));
+              Binding(1_a), Group(2_a));
 
     Func("F", utils::Empty, ty.void_(),
          utils::Vector{
@@ -1054,9 +1049,9 @@
 
 TEST_F(ResourceAttributeTest, BindingPointUsedTwiceByDifferentEntryPoints) {
     GlobalVar(Source{{12, 34}}, "A", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-              Binding(1), Group(2));
+              Binding(1_a), Group(2_a));
     GlobalVar(Source{{56, 78}}, "B", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-              Binding(1), Group(2));
+              Binding(1_a), Group(2_a));
 
     Func("F_A", utils::Empty, ty.void_(),
          utils::Vector{
@@ -1077,7 +1072,8 @@
 }
 
 TEST_F(ResourceAttributeTest, BindingPointOnNonResource) {
-    GlobalVar(Source{{12, 34}}, "G", ty.f32(), ast::StorageClass::kPrivate, Binding(1), Group(2));
+    GlobalVar(Source{{12, 34}}, "G", ty.f32(), ast::StorageClass::kPrivate, Binding(1_a),
+              Group(2_a));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
diff --git a/src/tint/resolver/builtin_test.cc b/src/tint/resolver/builtin_test.cc
index e98d9b5..b1d1e35 100644
--- a/src/tint/resolver/builtin_test.cc
+++ b/src/tint/resolver/builtin_test.cc
@@ -212,8 +212,8 @@
 TEST_F(ResolverBuiltinArrayTest, ArrayLength_Vector) {
     auto* ary = ty.array<i32>();
     auto* str = Structure("S", utils::Vector{Member("x", ary)});
-    GlobalVar("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
-              Group(0));
+    GlobalVar("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     auto* call = Call("arrayLength", AddressOf(MemberAccessor("a", "x")));
     WrapInFunction(call);
@@ -2143,7 +2143,7 @@
 
     void add_call_param(std::string name, const ast::Type* type, ExpressionList* call_params) {
         if (type->IsAnyOf<ast::Texture, ast::Sampler>()) {
-            GlobalVar(name, type, Binding(0), Group(0));
+            GlobalVar(name, type, Binding(0_a), Group(0_a));
 
         } else {
             GlobalVar(name, type, ast::StorageClass::kPrivate);
diff --git a/src/tint/resolver/compound_assignment_validation_test.cc b/src/tint/resolver/compound_assignment_validation_test.cc
index 9bd5c3a..9112fa0 100644
--- a/src/tint/resolver/compound_assignment_validation_test.cc
+++ b/src/tint/resolver/compound_assignment_validation_test.cc
@@ -234,7 +234,7 @@
     //   a += 1i;
     // }
     GlobalVar(Source{{12, 34}}, "a", ty.i32(), ast::StorageClass::kStorage, ast::Access::kRead,
-              Group(0), Binding(0));
+              Group(0_a), Binding(0_a));
     WrapInFunction(CompoundAssign(Source{{56, 78}}, "a", 1_i, ast::BinaryOp::kAdd));
 
     EXPECT_FALSE(r()->Resolve());
diff --git a/src/tint/resolver/host_shareable_validation_test.cc b/src/tint/resolver/host_shareable_validation_test.cc
index b9831cc..bba156b 100644
--- a/src/tint/resolver/host_shareable_validation_test.cc
+++ b/src/tint/resolver/host_shareable_validation_test.cc
@@ -21,13 +21,15 @@
 namespace tint::resolver {
 namespace {
 
+using namespace tint::number_suffixes;  // NOLINT
+
 using ResolverHostShareableValidationTest = ResolverTest;
 
 TEST_F(ResolverHostShareableValidationTest, BoolMember) {
     auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.bool_())});
 
     GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              Binding(0), Group(0));
+              Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -42,7 +44,7 @@
     auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.vec3<bool>())});
 
     GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              Binding(0), Group(0));
+              Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -58,7 +60,7 @@
     auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.Of(a1))});
     auto* a2 = Alias("a2", ty.Of(s));
     GlobalVar(Source{{56, 78}}, "g", ty.Of(a2), ast::StorageClass::kStorage, ast::Access::kRead,
-              Binding(0), Group(0));
+              Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -77,7 +79,7 @@
     auto* s = Structure("S", utils::Vector{Member(Source{{7, 8}}, "m", ty.Of(i3))});
 
     GlobalVar(Source{{9, 10}}, "g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              Binding(0), Group(0));
+              Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -116,7 +118,7 @@
     auto* s = Structure("S", utils::Vector{Member(Source{{7, 8}}, "m", ty.Of(i3))});
 
     GlobalVar(Source{{9, 10}}, "g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              Binding(0), Group(0));
+              Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
diff --git a/src/tint/resolver/increment_decrement_validation_test.cc b/src/tint/resolver/increment_decrement_validation_test.cc
index 87d7baf..7368b6d 100644
--- a/src/tint/resolver/increment_decrement_validation_test.cc
+++ b/src/tint/resolver/increment_decrement_validation_test.cc
@@ -194,7 +194,7 @@
     //   a++;
     // }
     GlobalVar(Source{{12, 34}}, "a", ty.i32(), ast::StorageClass::kStorage, ast::Access::kRead,
-              Group(0), Binding(0));
+              Group(0_a), Binding(0_a));
     WrapInFunction(Increment(Source{{56, 78}}, "a"));
 
     EXPECT_FALSE(r()->Resolve());
diff --git a/src/tint/resolver/ptr_ref_test.cc b/src/tint/resolver/ptr_ref_test.cc
index ba5be62..129263a 100644
--- a/src/tint/resolver/ptr_ref_test.cc
+++ b/src/tint/resolver/ptr_ref_test.cc
@@ -21,6 +21,8 @@
 namespace tint::resolver {
 namespace {
 
+using namespace tint::number_suffixes;  // NOLINT
+
 struct ResolverPtrRefTest : public resolver::TestHelper, public testing::Test {};
 
 TEST_F(ResolverPtrRefTest, AddressOf) {
@@ -61,8 +63,10 @@
     auto* function = Var("f", ty.i32());
     auto* private_ = GlobalVar("p", ty.i32(), ast::StorageClass::kPrivate);
     auto* workgroup = GlobalVar("w", ty.i32(), ast::StorageClass::kWorkgroup);
-    auto* uniform = GlobalVar("ub", ty.Of(buf), ast::StorageClass::kUniform, Binding(0), Group(0));
-    auto* storage = GlobalVar("sb", ty.Of(buf), ast::StorageClass::kStorage, Binding(1), Group(0));
+    auto* uniform =
+        GlobalVar("ub", ty.Of(buf), ast::StorageClass::kUniform, Binding(0_a), Group(0_a));
+    auto* storage =
+        GlobalVar("sb", ty.Of(buf), ast::StorageClass::kStorage, Binding(1_a), Group(0_a));
 
     auto* function_ptr =
         Let("f_ptr", ty.pointer(ty.i32(), ast::StorageClass::kFunction), AddressOf(function));
diff --git a/src/tint/resolver/ptr_ref_validation_test.cc b/src/tint/resolver/ptr_ref_validation_test.cc
index 0f6f6e5..f850f2a 100644
--- a/src/tint/resolver/ptr_ref_validation_test.cc
+++ b/src/tint/resolver/ptr_ref_validation_test.cc
@@ -54,7 +54,8 @@
 TEST_F(ResolverPtrRefValidationTest, AddressOfHandle) {
     // @group(0) @binding(0) var t: texture_3d<f32>;
     // &t
-    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()), Group(0), Binding(0));
+    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()), Group(0_a),
+              Binding(0_a));
     auto* expr = AddressOf(Expr(Source{{12, 34}}, "t"));
     WrapInFunction(expr);
 
@@ -93,7 +94,8 @@
 TEST_F(ResolverPtrRefValidationTest, IndirectOfAddressOfHandle) {
     // @group(0) @binding(0) var t: texture_3d<f32>;
     // *&t
-    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()), Group(0), Binding(0));
+    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()), Group(0_a),
+              Binding(0_a));
     auto* expr = Deref(AddressOf(Expr(Source{{12, 34}}, "t")));
     WrapInFunction(expr);
 
@@ -142,7 +144,7 @@
     auto* inner = Structure("Inner", utils::Vector{Member("arr", ty.array<i32, 4>())});
     auto* buf = Structure("S", utils::Vector{Member("inner", ty.Of(inner))});
     auto* storage = GlobalVar("s", ty.Of(buf), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-                              Binding(0), Group(0));
+                              Binding(0_a), Group(0_a));
 
     auto* expr = IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 2_i);
     auto* ptr =
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 90ec254..527c921 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -582,15 +582,33 @@
             uint32_t binding = 0;
             {
                 auto* attr = ast::GetAttribute<ast::BindingAttribute>(var->attributes);
-                // TODO(dsinclair): Materialize when binding attribute is an expression
-                binding = attr->value;
+                auto* materialize = Materialize(Expression(attr->value));
+                if (!materialize) {
+                    return nullptr;
+                }
+                auto* c = materialize->ConstantValue();
+                if (!c) {
+                    // TODO(crbug.com/tint/1633): Add error message about invalid materialization
+                    // when binding can be an expression.
+                    return nullptr;
+                }
+                binding = c->As<uint32_t>();
             }
 
             uint32_t group = 0;
             {
                 auto* attr = ast::GetAttribute<ast::GroupAttribute>(var->attributes);
-                // TODO(dsinclair): Materialize when group attribute is an expression
-                group = attr->value;
+                auto* materialize = Materialize(Expression(attr->value));
+                if (!materialize) {
+                    return nullptr;
+                }
+                auto* c = materialize->ConstantValue();
+                if (!c) {
+                    // TODO(crbug.com/tint/1633): Add error message about invalid materialization
+                    // when binding can be an expression.
+                    return nullptr;
+                }
+                group = c->As<uint32_t>();
             }
             binding_point = {group, binding};
         }
@@ -646,13 +664,31 @@
     if (param->HasBindingPoint()) {
         {
             auto* attr = ast::GetAttribute<ast::BindingAttribute>(param->attributes);
-            // TODO(dsinclair): Materialize the binding information
-            binding_point.binding = attr->value;
+            auto* materialize = Materialize(Expression(attr->value));
+            if (!materialize) {
+                return nullptr;
+            }
+            auto* c = materialize->ConstantValue();
+            if (!c) {
+                // TODO(crbug.com/tint/1633): Add error message about invalid materialization when
+                // binding can be an expression.
+                return nullptr;
+            }
+            binding_point.binding = c->As<uint32_t>();
         }
         {
             auto* attr = ast::GetAttribute<ast::GroupAttribute>(param->attributes);
-            // TODO(dsinclair): Materialize the group information
-            binding_point.group = attr->value;
+            auto* materialize = Materialize(Expression(attr->value));
+            if (!materialize) {
+                return nullptr;
+            }
+            auto* c = materialize->ConstantValue();
+            if (!c) {
+                // TODO(crbug.com/tint/1633): Add error message about invalid materialization when
+                // binding can be an expression.
+                return nullptr;
+            }
+            binding_point.group = c->As<uint32_t>();
         }
     }
 
diff --git a/src/tint/resolver/resolver_test.cc b/src/tint/resolver/resolver_test.cc
index d4f0ef9..595973d 100644
--- a/src/tint/resolver/resolver_test.cc
+++ b/src/tint/resolver/resolver_test.cc
@@ -777,7 +777,7 @@
     auto* s = Structure("S", utils::Vector{Member("m", ty.u32())});
 
     auto* sb_var = GlobalVar("sb_var", ty.Of(s), ast::StorageClass::kStorage,
-                             ast::Access::kReadWrite, Binding(0), Group(0));
+                             ast::Access::kReadWrite, Binding(0_a), Group(0_a));
     auto* wg_var = GlobalVar("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
     auto* priv_var = GlobalVar("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -806,7 +806,7 @@
     auto* s = Structure("S", utils::Vector{Member("m", ty.u32())});
 
     auto* sb_var = GlobalVar("sb_var", ty.Of(s), ast::StorageClass::kStorage,
-                             ast::Access::kReadWrite, Binding(0), Group(0));
+                             ast::Access::kReadWrite, Binding(0_a), Group(0_a));
     auto* wg_var = GlobalVar("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
     auto* priv_var = GlobalVar("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -1727,7 +1727,7 @@
 
 TEST_F(ResolverTest, StorageClass_SetForSampler) {
     auto* t = ty.sampler(ast::SamplerKind::kSampler);
-    auto* var = GlobalVar("var", t, Binding(0), Group(0));
+    auto* var = GlobalVar("var", t, Binding(0_a), Group(0_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1736,7 +1736,7 @@
 
 TEST_F(ResolverTest, StorageClass_SetForTexture) {
     auto* t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-    auto* var = GlobalVar("var", t, Binding(0), Group(0));
+    auto* var = GlobalVar("var", t, Binding(0_a), Group(0_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1757,8 +1757,8 @@
     // struct S { x : i32 };
     // var<storage> g : S;
     auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.i32())});
-    auto* var = GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage, Binding(0),
-                          Group(0));
+    auto* var = GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
+                          Binding(0_a), Group(0_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1768,8 +1768,8 @@
 TEST_F(ResolverTest, BindingPoint_SetForResources) {
     // @group(1) @binding(2) var s1 : sampler;
     // @group(3) @binding(4) var s2 : sampler;
-    auto* s1 = GlobalVar(Sym(), ty.sampler(ast::SamplerKind::kSampler), Group(1), Binding(2));
-    auto* s2 = GlobalVar(Sym(), ty.sampler(ast::SamplerKind::kSampler), Group(3), Binding(4));
+    auto* s1 = GlobalVar(Sym(), ty.sampler(ast::SamplerKind::kSampler), Group(1_a), Binding(2_a));
+    auto* s2 = GlobalVar(Sym(), ty.sampler(ast::SamplerKind::kSampler), Group(3_a), Binding(4_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1972,8 +1972,9 @@
 }
 
 TEST_F(ResolverTest, TextureSampler_TextureSample) {
-    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(1), Binding(1));
-    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(1), Binding(2));
+    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(1_a),
+              Binding(1_a));
+    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(1_a), Binding(2_a));
 
     auto* call = CallStmt(Call("textureSample", "t", "s", vec2<f32>(1_f, 2_f)));
     const ast::Function* f = Func("test_function", utils::Empty, ty.void_(), utils::Vector{call},
@@ -1989,8 +1990,9 @@
 }
 
 TEST_F(ResolverTest, TextureSampler_TextureSampleInFunction) {
-    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(1), Binding(1));
-    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(1), Binding(2));
+    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(1_a),
+              Binding(1_a));
+    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(1_a), Binding(2_a));
 
     auto* inner_call = CallStmt(Call("textureSample", "t", "s", vec2<f32>(1_f, 2_f)));
     const ast::Function* inner_func =
@@ -2014,8 +2016,9 @@
 }
 
 TEST_F(ResolverTest, TextureSampler_TextureSampleFunctionDiamondSameVariables) {
-    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(1), Binding(1));
-    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(1), Binding(2));
+    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(1_a),
+              Binding(1_a));
+    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(1_a), Binding(2_a));
 
     auto* inner_call_1 = CallStmt(Call("textureSample", "t", "s", vec2<f32>(1_f, 2_f)));
     const ast::Function* inner_func_1 =
@@ -2048,9 +2051,11 @@
 }
 
 TEST_F(ResolverTest, TextureSampler_TextureSampleFunctionDiamondDifferentVariables) {
-    GlobalVar("t1", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(1), Binding(1));
-    GlobalVar("t2", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(1), Binding(2));
-    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(1), Binding(3));
+    GlobalVar("t1", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(1_a),
+              Binding(1_a));
+    GlobalVar("t2", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(1_a),
+              Binding(2_a));
+    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(1_a), Binding(3_a));
 
     auto* inner_call_1 = CallStmt(Call("textureSample", "t1", "s", vec2<f32>(1_f, 2_f)));
     const ast::Function* inner_func_1 =
@@ -2085,7 +2090,8 @@
 }
 
 TEST_F(ResolverTest, TextureSampler_TextureDimensions) {
-    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(1), Binding(2));
+    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(1_a),
+              Binding(2_a));
 
     auto* call = Call("textureDimensions", "t");
     const ast::Function* f = WrapInFunction(call);
diff --git a/src/tint/resolver/side_effects_test.cc b/src/tint/resolver/side_effects_test.cc
index 9a7bd9f..17604a9 100644
--- a/src/tint/resolver/side_effects_test.cc
+++ b/src/tint/resolver/side_effects_test.cc
@@ -174,26 +174,27 @@
     GlobalVar("vb", ty.vec3<bool>(), ast::StorageClass::kPrivate);
     GlobalVar("m", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
     GlobalVar("arr", ty.array<f32, 10>(), ast::StorageClass::kPrivate);
-    GlobalVar("storage_arr", ty.array<f32>(), ast::StorageClass::kStorage, Group(0),
-              Binding(next_binding++));
+    GlobalVar("storage_arr", ty.array<f32>(), ast::StorageClass::kStorage, Group(0_a),
+              Binding(AInt(next_binding++)));
     GlobalVar("a", ty.atomic(ty.i32()), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              Group(0), Binding(next_binding++));
+              Group(0_a), Binding(AInt(next_binding++)));
     if (c.pipeline_stage != ast::PipelineStage::kCompute) {
-        GlobalVar("t2d", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(0),
-                  Binding(next_binding++));
-        GlobalVar("tdepth2d", ty.depth_texture(ast::TextureDimension::k2d), Group(0),
-                  Binding(next_binding++));
+        GlobalVar("t2d", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(0_a),
+                  Binding(AInt(next_binding++)));
+        GlobalVar("tdepth2d", ty.depth_texture(ast::TextureDimension::k2d), Group(0_a),
+                  Binding(AInt(next_binding++)));
         GlobalVar("t2d_arr", ty.sampled_texture(ast::TextureDimension::k2dArray, ty.f32()),
-                  Group(0), Binding(next_binding++));
+                  Group(0_a), Binding(AInt(next_binding++)));
         GlobalVar("t2d_multi", ty.multisampled_texture(ast::TextureDimension::k2d, ty.f32()),
-                  Group(0), Binding(next_binding++));
+                  Group(0_a), Binding(AInt(next_binding++)));
         GlobalVar("tstorage2d",
                   ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Float,
                                      ast::Access::kWrite),
-                  Group(0), Binding(next_binding++));
-        GlobalVar("s2d", ty.sampler(ast::SamplerKind::kSampler), Group(0), Binding(next_binding++));
-        GlobalVar("scomp", ty.sampler(ast::SamplerKind::kComparisonSampler), Group(0),
-                  Binding(next_binding++));
+                  Group(0_a), Binding(AInt(next_binding++)));
+        GlobalVar("s2d", ty.sampler(ast::SamplerKind::kSampler), Group(0_a),
+                  Binding(AInt(next_binding++)));
+        GlobalVar("scomp", ty.sampler(ast::SamplerKind::kComparisonSampler), Group(0_a),
+                  Binding(AInt(next_binding++)));
     }
 
     utils::Vector<const ast::Statement*, 4> stmts;
diff --git a/src/tint/resolver/source_variable_test.cc b/src/tint/resolver/source_variable_test.cc
index ada4fc2..85d0c31 100644
--- a/src/tint/resolver/source_variable_test.cc
+++ b/src/tint/resolver/source_variable_test.cc
@@ -48,7 +48,7 @@
 }
 
 TEST_F(ResolverSourceVariableTest, GlobalStorageVar) {
-    auto* a = GlobalVar("a", ty.f32(), ast::StorageClass::kStorage, Group(0), Binding(0));
+    auto* a = GlobalVar("a", ty.f32(), ast::StorageClass::kStorage, Group(0_a), Binding(0_a));
     auto* expr = Expr(a);
     WrapInFunction(expr);
 
@@ -59,7 +59,7 @@
 }
 
 TEST_F(ResolverSourceVariableTest, GlobalUniformVar) {
-    auto* a = GlobalVar("a", ty.f32(), ast::StorageClass::kUniform, Group(0), Binding(0));
+    auto* a = GlobalVar("a", ty.f32(), ast::StorageClass::kUniform, Group(0_a), Binding(0_a));
     auto* expr = Expr(a);
     WrapInFunction(expr);
 
@@ -71,7 +71,7 @@
 
 TEST_F(ResolverSourceVariableTest, GlobalTextureVar) {
     auto* a = GlobalVar("a", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-                        ast::StorageClass::kNone, Group(0), Binding(0));
+                        ast::StorageClass::kNone, Group(0_a), Binding(0_a));
     auto* expr = Expr(a);
     WrapInFunction(Call("textureDimensions", expr));
 
diff --git a/src/tint/resolver/storage_class_layout_validation_test.cc b/src/tint/resolver/storage_class_layout_validation_test.cc
index 51b8ecc..8f1b2c1 100644
--- a/src/tint/resolver/storage_class_layout_validation_test.cc
+++ b/src/tint/resolver/storage_class_layout_validation_test.cc
@@ -39,8 +39,8 @@
                   Member(Source{{34, 56}}, "b", ty.f32(), utils::Vector{MemberAlign(1_u)}),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage, Group(0),
-              Binding(0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage, Group(0_a),
+              Binding(0_a));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -69,8 +69,8 @@
                   Member(Source{{34, 56}}, "b", ty.f32(), utils::Vector{MemberAlign(4_u)}),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage, Group(0),
-              Binding(0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage, Group(0_a),
+              Binding(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -100,8 +100,8 @@
                   Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
-              Binding(0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0_a),
+              Binding(0_a));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -145,8 +145,8 @@
                          utils::Vector{MemberAlign(16_u)}),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
-              Binding(0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0_a),
+              Binding(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -170,8 +170,8 @@
                   Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
-              Binding(0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0_a),
+              Binding(0_a));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -204,8 +204,8 @@
                          utils::Vector{MemberAlign(16_u)}),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
-              Binding(0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0_a),
+              Binding(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -236,8 +236,8 @@
                   Member(Source{{78, 90}}, "scalar", ty.i32()),
               });
 
-    GlobalVar(Source{{22, 24}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
-              Binding(0));
+    GlobalVar(Source{{22, 24}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0_a),
+              Binding(0_a));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -288,8 +288,8 @@
                   Member(Source{{78, 90}}, "scalar", ty.i32()),
               });
 
-    GlobalVar(Source{{22, 24}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
-              Binding(0));
+    GlobalVar(Source{{22, 24}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0_a),
+              Binding(0_a));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -336,8 +336,8 @@
                   Member(Source{{78, 90}}, "scalar", ty.i32(), utils::Vector{MemberAlign(16_u)}),
               });
 
-    GlobalVar(Source{{22, 34}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
-              Binding(0));
+    GlobalVar(Source{{22, 34}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0_a),
+              Binding(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -358,7 +358,7 @@
                                          });
 
     GlobalVar(Source{{78, 90}}, "a", ty.type_name("ScalarPackedAtEndOfVec3"),
-              ast::StorageClass::kUniform, Group(0), Binding(0));
+              ast::StorageClass::kUniform, Group(0_a), Binding(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -383,8 +383,8 @@
                   Member("scalar", ty.i32()),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
-              Binding(0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0_a),
+              Binding(0_a));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -417,8 +417,8 @@
                   Member("scalar", ty.i32()),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
-              Binding(0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0_a),
+              Binding(0_a));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -460,8 +460,8 @@
                   Member("scalar", ty.i32()),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
-              Binding(0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0_a),
+              Binding(0_a));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -479,7 +479,7 @@
     // @group(0) @binding(0)
     // var<uniform> a : array<f32, 4u>;
     GlobalVar(Source{{78, 90}}, "a", ty.array(Source{{34, 56}}, ty.f32(), 4_u),
-              ast::StorageClass::kUniform, Group(0), Binding(0));
+              ast::StorageClass::kUniform, Group(0_a), Binding(0_a));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -500,8 +500,8 @@
                   Member("inner", ty.array(Source{{34, 56}}, ty.array(ty.f32(), 4_u), 4_u)),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
-              Binding(0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0_a),
+              Binding(0_a));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -533,8 +533,8 @@
                   Member("scalar", ty.i32()),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
-              Binding(0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0_a),
+              Binding(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
diff --git a/src/tint/resolver/storage_class_validation_test.cc b/src/tint/resolver/storage_class_validation_test.cc
index ab9c049..8c1a253 100644
--- a/src/tint/resolver/storage_class_validation_test.cc
+++ b/src/tint/resolver/storage_class_validation_test.cc
@@ -87,7 +87,8 @@
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferBool) {
     // var<storage> g : bool;
-    GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kStorage, Binding(0), Group(0));
+    GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kStorage, Binding(0_a),
+              Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -101,7 +102,8 @@
     // type a = bool;
     // var<storage, read> g : a;
     auto* a = Alias("a", ty.bool_());
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kStorage, Binding(0), Group(0));
+    GlobalVar(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kStorage, Binding(0_a),
+              Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -117,7 +119,7 @@
     // var<storage> g : f16;
     Enable(ast::Extension::kF16);
 
-    GlobalVar("g", ty.f16(Source{{56, 78}}), ast::StorageClass::kStorage, Binding(0), Group(0));
+    GlobalVar("g", ty.f16(Source{{56, 78}}), ast::StorageClass::kStorage, Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -132,8 +134,8 @@
     Enable(ast::Extension::kF16);
 
     auto* a = Alias("a", ty.f16());
-    GlobalVar("g", ty.type_name(Source{{56, 78}}, a->name), ast::StorageClass::kStorage, Binding(0),
-              Group(0));
+    GlobalVar("g", ty.type_name(Source{{56, 78}}, a->name), ast::StorageClass::kStorage,
+              Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -146,7 +148,7 @@
     // var<storage> g : vec4<f16>;
     Enable(ast::Extension::kF16);
     GlobalVar("g", ty.vec(Source{{56, 78}}, ty.Of<f16>(), 4u), ast::StorageClass::kStorage,
-              Binding(0), Group(0));
+              Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -162,7 +164,7 @@
 
     auto* s = Structure("S", utils::Vector{Member("a", ty.f16(Source{{56, 78}}))});
     auto* a = ty.array(ty.Of(s), 3_u);
-    GlobalVar("g", a, ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
+    GlobalVar("g", a, ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -176,7 +178,8 @@
     Enable(ast::Extension::kF16);
 
     auto* s = Structure("S", utils::Vector{Member("x", ty.f16(Source{{12, 34}}))});
-    GlobalVar("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
+    GlobalVar("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -193,8 +196,8 @@
     auto* s = Structure("S", utils::Vector{Member("x", ty.f16(Source{{12, 34}}))});
     auto* a1 = Alias("a1", ty.Of(s));
     auto* a2 = Alias("a2", ty.Of(a1));
-    GlobalVar("g", ty.Of(a2), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
-              Group(0));
+    GlobalVar("g", ty.Of(a2), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -205,7 +208,7 @@
 TEST_F(ResolverStorageClassValidationTest, StorageBufferPointer) {
     // var<storage> g : ptr<private, f32>;
     GlobalVar(Source{{56, 78}}, "g", ty.pointer(ty.f32(), ast::StorageClass::kPrivate),
-              ast::StorageClass::kStorage, Binding(0), Group(0));
+              ast::StorageClass::kStorage, Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -217,15 +220,16 @@
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferIntScalar) {
     // var<storage> g : i32;
-    GlobalVar(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kStorage, Binding(0), Group(0));
+    GlobalVar(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kStorage, Binding(0_a),
+              Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferVectorF32) {
     // var<storage> g : vec4<f32>;
-    GlobalVar(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kStorage, Binding(0),
-              Group(0));
+    GlobalVar(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kStorage, Binding(0_a),
+              Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -234,8 +238,8 @@
     // var<storage, read> g : array<S, 3u>;
     auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
     auto* a = ty.array(ty.Of(s), 3_u);
-    GlobalVar(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
-              Group(0));
+    GlobalVar(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage, ast::Access::kRead,
+              Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -254,7 +258,7 @@
 TEST_F(ResolverStorageClassValidationTest, Storage_ReadAccessMode) {
     // @group(0) @binding(0) var<storage, read> a : i32;
     GlobalVar(Source{{56, 78}}, "a", ty.i32(), ast::StorageClass::kStorage, ast::Access::kRead,
-              Group(0), Binding(0));
+              Group(0_a), Binding(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -262,7 +266,7 @@
 TEST_F(ResolverStorageClassValidationTest, Storage_ReadWriteAccessMode) {
     // @group(0) @binding(0) var<storage, read_write> a : i32;
     GlobalVar(Source{{56, 78}}, "a", ty.i32(), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              Group(0), Binding(0));
+              Group(0_a), Binding(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -270,7 +274,7 @@
 TEST_F(ResolverStorageClassValidationTest, Storage_WriteAccessMode) {
     // @group(0) @binding(0) var<storage, read_write> a : i32;
     GlobalVar(Source{{56, 78}}, "a", ty.i32(), ast::StorageClass::kStorage, ast::Access::kWrite,
-              Group(0), Binding(0));
+              Group(0_a), Binding(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -283,7 +287,7 @@
     // var<storage, read> g : S;
     auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.i32())});
     GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              Binding(0), Group(0));
+              Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve());
 }
@@ -296,7 +300,7 @@
     auto* a1 = Alias("a1", ty.Of(s));
     auto* a2 = Alias("a2", ty.Of(a1));
     GlobalVar(Source{{56, 78}}, "g", ty.Of(a2), ast::StorageClass::kStorage, ast::Access::kRead,
-              Binding(0), Group(0));
+              Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve());
 }
@@ -307,8 +311,8 @@
 
     auto* s = Structure(Source{{12, 34}}, "S", utils::Vector{Member("m", ty.array<i32>())});
 
-    GlobalVar(Source{{56, 78}}, "svar", ty.Of(s), ast::StorageClass::kUniform, Binding(0),
-              Group(0));
+    GlobalVar(Source{{56, 78}}, "svar", ty.Of(s), ast::StorageClass::kUniform, Binding(0_a),
+              Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -319,7 +323,8 @@
 
 TEST_F(ResolverStorageClassValidationTest, UniformBufferBool) {
     // var<uniform> g : bool;
-    GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kUniform, Binding(0), Group(0));
+    GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kUniform, Binding(0_a),
+              Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -333,7 +338,8 @@
     // type a = bool;
     // var<uniform> g : a;
     auto* a = Alias("a", ty.bool_());
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kUniform, Binding(0), Group(0));
+    GlobalVar(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kUniform, Binding(0_a),
+              Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -349,7 +355,7 @@
     // var<uniform> g : f16;
     Enable(ast::Extension::kF16);
 
-    GlobalVar("g", ty.f16(Source{{56, 78}}), ast::StorageClass::kUniform, Binding(0), Group(0));
+    GlobalVar("g", ty.f16(Source{{56, 78}}), ast::StorageClass::kUniform, Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -364,8 +370,8 @@
     Enable(ast::Extension::kF16);
 
     auto* a = Alias("a", ty.f16());
-    GlobalVar("g", ty.type_name(Source{{56, 78}}, a->name), ast::StorageClass::kUniform, Binding(0),
-              Group(0));
+    GlobalVar("g", ty.type_name(Source{{56, 78}}, a->name), ast::StorageClass::kUniform,
+              Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -378,7 +384,7 @@
     // var<uniform> g : vec4<f16>;
     Enable(ast::Extension::kF16);
     GlobalVar("g", ty.vec(Source{{56, 78}}, ty.Of<f16>(), 4u), ast::StorageClass::kUniform,
-              Binding(0), Group(0));
+              Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -396,7 +402,7 @@
     auto* s = Structure(
         "S", utils::Vector{Member("a", ty.f16(Source{{56, 78}}), utils::Vector{MemberSize(16)})});
     auto* a = ty.array(ty.Of(s), 3_u);
-    GlobalVar("g", a, ast::StorageClass::kUniform, Binding(0), Group(0));
+    GlobalVar("g", a, ast::StorageClass::kUniform, Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -410,7 +416,7 @@
     Enable(ast::Extension::kF16);
 
     auto* s = Structure("S", utils::Vector{Member("x", ty.f16(Source{{12, 34}}))});
-    GlobalVar("g", ty.Of(s), ast::StorageClass::kUniform, Binding(0), Group(0));
+    GlobalVar("g", ty.Of(s), ast::StorageClass::kUniform, Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -426,7 +432,7 @@
 
     auto* s = Structure("S", utils::Vector{Member("x", ty.f16(Source{{12, 34}}))});
     auto* a1 = Alias("a1", ty.Of(s));
-    GlobalVar("g", ty.Of(a1), ast::StorageClass::kUniform, Binding(0), Group(0));
+    GlobalVar("g", ty.Of(a1), ast::StorageClass::kUniform, Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -437,7 +443,7 @@
 TEST_F(ResolverStorageClassValidationTest, UniformBufferPointer) {
     // var<uniform> g : ptr<private, f32>;
     GlobalVar(Source{{56, 78}}, "g", ty.pointer(ty.f32(), ast::StorageClass::kPrivate),
-              ast::StorageClass::kUniform, Binding(0), Group(0));
+              ast::StorageClass::kUniform, Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -449,15 +455,16 @@
 
 TEST_F(ResolverStorageClassValidationTest, UniformBufferIntScalar) {
     // var<uniform> g : i32;
-    GlobalVar(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kUniform, Binding(0), Group(0));
+    GlobalVar(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kUniform, Binding(0_a),
+              Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(ResolverStorageClassValidationTest, UniformBufferVectorF32) {
     // var<uniform> g : vec4<f32>;
-    GlobalVar(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kUniform, Binding(0),
-              Group(0));
+    GlobalVar(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kUniform, Binding(0_a),
+              Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -469,7 +476,7 @@
     // var<uniform> g : array<S, 3u>;
     auto* s = Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{MemberSize(16)})});
     auto* a = ty.array(ty.Of(s), 3_u);
-    GlobalVar(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform, Binding(0), Group(0));
+    GlobalVar(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -478,7 +485,8 @@
     // struct S { x : i32 };
     // var<uniform> g :  S;
     auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.i32())});
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kUniform, Binding(0), Group(0));
+    GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kUniform, Binding(0_a),
+              Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -489,7 +497,8 @@
     // var<uniform> g : a1;
     auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.i32())});
     auto* a1 = Alias("a1", ty.Of(s));
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(a1), ast::StorageClass::kUniform, Binding(0), Group(0));
+    GlobalVar(Source{{56, 78}}, "g", ty.Of(a1), ast::StorageClass::kUniform, Binding(0_a),
+              Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
diff --git a/src/tint/resolver/struct_storage_class_use_test.cc b/src/tint/resolver/struct_storage_class_use_test.cc
index af7f976..4d709e3 100644
--- a/src/tint/resolver/struct_storage_class_use_test.cc
+++ b/src/tint/resolver/struct_storage_class_use_test.cc
@@ -159,8 +159,9 @@
 
 TEST_F(ResolverStorageClassUseTest, StructMultipleStorageClassUses) {
     auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
-    GlobalVar("x", ty.Of(s), ast::StorageClass::kUniform, Binding(0), Group(0));
-    GlobalVar("y", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(0));
+    GlobalVar("x", ty.Of(s), ast::StorageClass::kUniform, Binding(0_a), Group(0_a));
+    GlobalVar("y", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(0_a));
     WrapInFunction(Var("g", ty.Of(s)));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/tint/resolver/type_validation_test.cc b/src/tint/resolver/type_validation_test.cc
index 2575002..39ce5e3 100644
--- a/src/tint/resolver/type_validation_test.cc
+++ b/src/tint/resolver/type_validation_test.cc
@@ -760,8 +760,8 @@
 using SampledTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
 TEST_P(SampledTextureDimensionTest, All) {
     auto& params = GetParam();
-    GlobalVar(Source{{12, 34}}, "a", ty.sampled_texture(params.dim, ty.i32()), Group(0),
-              Binding(0));
+    GlobalVar(Source{{12, 34}}, "a", ty.sampled_texture(params.dim, ty.i32()), Group(0_a),
+              Binding(0_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -778,8 +778,8 @@
 using MultisampledTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
 TEST_P(MultisampledTextureDimensionTest, All) {
     auto& params = GetParam();
-    GlobalVar("a", ty.multisampled_texture(Source{{12, 34}}, params.dim, ty.i32()), Group(0),
-              Binding(0));
+    GlobalVar("a", ty.multisampled_texture(Source{{12, 34}}, params.dim, ty.i32()), Group(0_a),
+              Binding(0_a));
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -832,7 +832,7 @@
     GlobalVar(
         "a",
         ty.sampled_texture(Source{{12, 34}}, ast::TextureDimension::k2d, params.type_func(*this)),
-        Group(0), Binding(0));
+        Group(0_a), Binding(0_a));
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -851,7 +851,7 @@
     GlobalVar("a",
               ty.multisampled_texture(Source{{12, 34}}, ast::TextureDimension::k2d,
                                       params.type_func(*this)),
-              Group(0), Binding(0));
+              Group(0_a), Binding(0_a));
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -890,7 +890,7 @@
     auto* st = ty.storage_texture(Source{{12, 34}}, params.dim, ast::TexelFormat::kR32Uint,
                                   ast::Access::kWrite);
 
-    GlobalVar("a", st, Group(0), Binding(0));
+    GlobalVar("a", st, Group(0_a), Binding(0_a));
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -940,17 +940,17 @@
 
     auto* st_a = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d, params.format,
                                     ast::Access::kWrite);
-    GlobalVar("a", st_a, Group(0), Binding(0));
+    GlobalVar("a", st_a, Group(0_a), Binding(0_a));
 
     auto* st_b = ty.storage_texture(ast::TextureDimension::k2d, params.format, ast::Access::kWrite);
-    GlobalVar("b", st_b, Group(0), Binding(1));
+    GlobalVar("b", st_b, Group(0_a), Binding(1_a));
 
     auto* st_c =
         ty.storage_texture(ast::TextureDimension::k2dArray, params.format, ast::Access::kWrite);
-    GlobalVar("c", st_c, Group(0), Binding(2));
+    GlobalVar("c", st_c, Group(0_a), Binding(2_a));
 
     auto* st_d = ty.storage_texture(ast::TextureDimension::k3d, params.format, ast::Access::kWrite);
-    GlobalVar("d", st_d, Group(0), Binding(3));
+    GlobalVar("d", st_d, Group(0_a), Binding(3_a));
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -974,7 +974,7 @@
     auto* st = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
                                   ast::TexelFormat::kR32Uint, ast::Access::kUndefined);
 
-    GlobalVar("a", st, Group(0), Binding(0));
+    GlobalVar("a", st, Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: storage texture missing access control");
@@ -987,7 +987,7 @@
     auto* st = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
                                   ast::TexelFormat::kR32Uint, ast::Access::kReadWrite);
 
-    GlobalVar("a", st, Group(0), Binding(0));
+    GlobalVar("a", st, Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1001,7 +1001,7 @@
     auto* st = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
                                   ast::TexelFormat::kR32Uint, ast::Access::kRead);
 
-    GlobalVar("a", st, Group(0), Binding(0));
+    GlobalVar("a", st, Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1015,7 +1015,7 @@
     auto* st = ty.storage_texture(ast::TextureDimension::k1d, ast::TexelFormat::kR32Uint,
                                   ast::Access::kWrite);
 
-    GlobalVar("a", st, Group(0), Binding(0));
+    GlobalVar("a", st, Group(0_a), Binding(0_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
diff --git a/src/tint/resolver/validation_test.cc b/src/tint/resolver/validation_test.cc
index cbd05c1..6054cac 100644
--- a/src/tint/resolver/validation_test.cc
+++ b/src/tint/resolver/validation_test.cc
@@ -329,7 +329,7 @@
 
 TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) {
     auto* t = ty.sampler(ast::SamplerKind::kSampler);
-    GlobalVar(Source{{12, 34}}, "var", t, ast::StorageClass::kHandle, Binding(0), Group(0));
+    GlobalVar(Source{{12, 34}}, "var", t, ast::StorageClass::kHandle, Binding(0_a), Group(0_a));
 
     EXPECT_FALSE(r()->Resolve());
 
@@ -339,7 +339,7 @@
 
 TEST_F(ResolverValidationTest, StorageClass_TextureExplicitStorageClass) {
     auto* t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-    GlobalVar(Source{{12, 34}}, "var", t, ast::StorageClass::kHandle, Binding(0), Group(0));
+    GlobalVar(Source{{12, 34}}, "var", t, ast::StorageClass::kHandle, Binding(0_a), Group(0_a));
 
     EXPECT_FALSE(r()->Resolve()) << r()->error();
 
diff --git a/src/tint/resolver/variable_test.cc b/src/tint/resolver/variable_test.cc
index 02c37d5..52d240c 100644
--- a/src/tint/resolver/variable_test.cc
+++ b/src/tint/resolver/variable_test.cc
@@ -18,11 +18,11 @@
 
 #include "gmock/gmock.h"
 
-using namespace tint::number_suffixes;  // NOLINT
-
 namespace tint::resolver {
 namespace {
 
+using namespace tint::number_suffixes;  // NOLINT
+
 struct ResolverVariableTest : public resolver::TestHelper, public testing::Test {};
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -471,7 +471,7 @@
     auto* inner = Structure("Inner", utils::Vector{Member("arr", ty.array<i32, 4>())});
     auto* buf = Structure("S", utils::Vector{Member("inner", ty.Of(inner))});
     auto* storage = GlobalVar("s", ty.Of(buf), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-                              Binding(0), Group(0));
+                              Binding(0_a), Group(0_a));
 
     auto* expr = IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4_i);
     auto* ptr = Let("p", AddressOf(expr));
@@ -1036,10 +1036,12 @@
     auto* buf = Structure("S", utils::Vector{Member("m", ty.i32())});
     auto* private_ = GlobalVar("p", ty.i32(), ast::StorageClass::kPrivate);
     auto* workgroup = GlobalVar("w", ty.i32(), ast::StorageClass::kWorkgroup);
-    auto* uniform = GlobalVar("ub", ty.Of(buf), ast::StorageClass::kUniform, Binding(0), Group(0));
-    auto* storage = GlobalVar("sb", ty.Of(buf), ast::StorageClass::kStorage, Binding(1), Group(0));
+    auto* uniform =
+        GlobalVar("ub", ty.Of(buf), ast::StorageClass::kUniform, Binding(0_a), Group(0_a));
+    auto* storage =
+        GlobalVar("sb", ty.Of(buf), ast::StorageClass::kStorage, Binding(1_a), Group(0_a));
     auto* handle =
-        GlobalVar("h", ty.depth_texture(ast::TextureDimension::k2d), Binding(2), Group(0));
+        GlobalVar("h", ty.depth_texture(ast::TextureDimension::k2d), Binding(2_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1061,7 +1063,7 @@
 
     auto* buf = Structure("S", utils::Vector{Member("m", ty.i32())});
     auto* storage = GlobalVar("sb", ty.Of(buf), ast::StorageClass::kStorage,
-                              ast::Access::kReadWrite, Binding(1), Group(0));
+                              ast::Access::kReadWrite, Binding(1_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
diff --git a/src/tint/resolver/variable_validation_test.cc b/src/tint/resolver/variable_validation_test.cc
index 0fb50a5..3416909 100644
--- a/src/tint/resolver/variable_validation_test.cc
+++ b/src/tint/resolver/variable_validation_test.cc
@@ -123,8 +123,8 @@
 TEST_F(ResolverVariableValidationTest, LetTypeNotConstructible) {
     // @group(0) @binding(0) var t1 : texture_2d<f32>;
     // let t2 : t1;
-    auto* t1 = GlobalVar("t1", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(0),
-                         Binding(0));
+    auto* t1 = GlobalVar("t1", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(0_a),
+                         Binding(0_a));
     auto* t2 = Let(Source{{56, 78}}, "t2", Expr(t1));
     WrapInFunction(t2);
 
@@ -294,7 +294,8 @@
     auto* buf = Structure("S", utils::Vector{
                                    Member("inner", ty.Of(inner)),
                                });
-    auto* storage = GlobalVar("s", ty.Of(buf), ast::StorageClass::kStorage, Binding(0), Group(0));
+    auto* storage =
+        GlobalVar("s", ty.Of(buf), ast::StorageClass::kStorage, Binding(0_a), Group(0_a));
 
     auto* expr = IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 2_i);
     auto* ptr =
@@ -348,7 +349,7 @@
     // fn foo() {
     //   var v = s;
     // }
-    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(0), Binding(0));
+    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(0_a), Binding(0_a));
     auto* v = Var(Source{{12, 34}}, "v", Expr("s"));
     WrapInFunction(v);
 
diff --git a/src/tint/transform/array_length_from_uniform.cc b/src/tint/transform/array_length_from_uniform.cc
index 36ddcdc..4f264c4 100644
--- a/src/tint/transform/array_length_from_uniform.cc
+++ b/src/tint/transform/array_length_from_uniform.cc
@@ -146,9 +146,10 @@
                                     ctx.dst->ty.array(ctx.dst->ty.vec4(ctx.dst->ty.u32()),
                                                       u32((max_buffer_size_index / 4) + 1))),
                 });
-            buffer_size_ubo = ctx.dst->GlobalVar(
-                ctx.dst->Sym(), ctx.dst->ty.Of(buffer_size_struct), ast::StorageClass::kUniform,
-                ctx.dst->Group(cfg->ubo_binding.group), ctx.dst->Binding(cfg->ubo_binding.binding));
+            buffer_size_ubo = ctx.dst->GlobalVar(ctx.dst->Sym(), ctx.dst->ty.Of(buffer_size_struct),
+                                                 ast::StorageClass::kUniform,
+                                                 ctx.dst->Group(AInt(cfg->ubo_binding.group)),
+                                                 ctx.dst->Binding(AInt(cfg->ubo_binding.binding)));
         }
         return buffer_size_ubo;
     };
diff --git a/src/tint/transform/binding_remapper.cc b/src/tint/transform/binding_remapper.cc
index eaf9725..487752e 100644
--- a/src/tint/transform/binding_remapper.cc
+++ b/src/tint/transform/binding_remapper.cc
@@ -106,8 +106,8 @@
             auto bp_it = remappings->binding_points.find(from);
             if (bp_it != remappings->binding_points.end()) {
                 BindingPoint to = bp_it->second;
-                auto* new_group = ctx.dst->create<ast::GroupAttribute>(to.group);
-                auto* new_binding = ctx.dst->create<ast::BindingAttribute>(to.binding);
+                auto* new_group = ctx.dst->Group(AInt(to.group));
+                auto* new_binding = ctx.dst->Binding(AInt(to.binding));
 
                 auto* old_group = ast::GetAttribute<ast::GroupAttribute>(var->attributes);
                 auto* old_binding = ast::GetAttribute<ast::BindingAttribute>(var->attributes);
diff --git a/src/tint/transform/combine_samplers.cc b/src/tint/transform/combine_samplers.cc
index 6290b26..97650ad 100644
--- a/src/tint/transform/combine_samplers.cc
+++ b/src/tint/transform/combine_samplers.cc
@@ -39,6 +39,8 @@
 
 namespace tint::transform {
 
+using namespace tint::number_suffixes;  // NOLINT
+
 CombineSamplers::BindingInfo::BindingInfo(const BindingMap& map,
                                           const sem::BindingPoint& placeholder)
     : binding_map(map), placeholder_binding_point(placeholder) {}
@@ -79,7 +81,8 @@
     /// Group 0 and binding 0 are used, with collisions disabled.
     /// @returns the newly-created attribute list
     auto Attributes() const {
-        utils::Vector<const ast::Attribute*, 3> attributes{ctx.dst->Group(0), ctx.dst->Binding(0)};
+        utils::Vector<const ast::Attribute*, 3> attributes{ctx.dst->Group(0_a),
+                                                           ctx.dst->Binding(0_a)};
         attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision));
         return attributes;
     }
diff --git a/src/tint/transform/decompose_strided_array_test.cc b/src/tint/transform/decompose_strided_array_test.cc
index 2de42fa..58908fb 100644
--- a/src/tint/transform/decompose_strided_array_test.cc
+++ b/src/tint/transform/decompose_strided_array_test.cc
@@ -158,7 +158,7 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure("S", utils::Vector{b.Member("a", b.ty.array<f32, 4u>(32))});
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0), b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0_a), b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Decl(b.Let("a", b.ty.array<f32, 4u>(32), b.MemberAccessor("s", "a"))),
@@ -206,7 +206,7 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure("S", utils::Vector{b.Member("a", b.ty.array(b.ty.vec4<f32>(), 4_u, 16))});
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0), b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0_a), b.Binding(0_a));
     b.Func(
         "f", utils::Empty, b.ty.void_(),
         utils::Vector{
@@ -252,7 +252,7 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure("S", utils::Vector{b.Member("a", b.ty.array<f32, 4u>(32))});
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, b.Group(0), b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, b.Group(0_a), b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Decl(b.Let("a", b.ty.array<f32, 4u>(32), b.MemberAccessor("s", "a"))),
@@ -300,7 +300,7 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure("S", utils::Vector{b.Member("a", b.ty.array<f32, 4u>(4))});
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, b.Group(0), b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, b.Group(0_a), b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Decl(b.Let("a", b.ty.array<f32, 4u>(4), b.MemberAccessor("s", "a"))),
@@ -344,8 +344,8 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure("S", utils::Vector{b.Member("a", b.ty.array<f32, 4u>(32))});
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
-                b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0_a),
+                b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Assign(b.MemberAccessor("s", "a"), b.Construct(b.ty.array<f32, 4u>(32))),
@@ -398,8 +398,8 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure("S", utils::Vector{b.Member("a", b.ty.array<f32, 4u>(4))});
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
-                b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0_a),
+                b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Assign(b.MemberAccessor("s", "a"), b.Construct(b.ty.array<f32, 4u>(4))),
@@ -450,8 +450,8 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure("S", utils::Vector{b.Member("a", b.ty.array<f32, 4u>(32))});
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
-                b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0_a),
+                b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Decl(b.Let("a", b.AddressOf(b.MemberAccessor("s", "a")))),
@@ -511,8 +511,8 @@
     ProgramBuilder b;
     b.Alias("ARR", b.ty.array<f32, 4u>(32));
     auto* S = b.Structure("S", utils::Vector{b.Member("a", b.ty.type_name("ARR"))});
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
-                b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0_a),
+                b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Decl(b.Let("a", b.ty.type_name("ARR"), b.MemberAccessor("s", "a"))),
@@ -581,8 +581,8 @@
                 b.ty.array(b.ty.type_name("ARR_A"), 3_u, 16),  //
                 4_u, 128));
     auto* S = b.Structure("S", utils::Vector{b.Member("a", b.ty.type_name("ARR_B"))});
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
-                b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0_a),
+                b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Decl(b.Let("a", b.ty.type_name("ARR_B"), b.MemberAccessor("s", "a"))),
diff --git a/src/tint/transform/decompose_strided_matrix_test.cc b/src/tint/transform/decompose_strided_matrix_test.cc
index a947f6d..c7a8f59 100644
--- a/src/tint/transform/decompose_strided_matrix_test.cc
+++ b/src/tint/transform/decompose_strided_matrix_test.cc
@@ -76,7 +76,7 @@
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
                           }),
              });
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0), b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0_a), b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Decl(b.Let("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
@@ -132,7 +132,7 @@
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
                           }),
              });
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0), b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0_a), b.Binding(0_a));
     b.Func(
         "f", utils::Empty, b.ty.void_(),
         utils::Vector{
@@ -185,7 +185,7 @@
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
                           }),
              });
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0), b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0_a), b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Decl(b.Let("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
@@ -238,8 +238,8 @@
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
                           }),
              });
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
-                b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0_a),
+                b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Decl(b.Let("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
@@ -295,8 +295,8 @@
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
                           }),
              });
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
-                b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0_a),
+                b.Binding(0_a));
     b.Func(
         "f", utils::Empty, b.ty.void_(),
         utils::Vector{
@@ -349,8 +349,8 @@
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
                           }),
              });
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
-                b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0_a),
+                b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Assign(b.MemberAccessor("s", "m"),
@@ -407,8 +407,8 @@
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
                           }),
              });
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
-                b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0_a),
+                b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Assign(b.IndexAccessor(b.MemberAccessor("s", "m"), 1_i), b.vec2<f32>(1_f, 2_f)),
@@ -466,8 +466,8 @@
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
                           }),
              });
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
-                b.Binding(0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0_a),
+                b.Binding(0_a));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
                b.Decl(b.Let("a", b.AddressOf(b.MemberAccessor("s", "m")))),
diff --git a/src/tint/transform/first_index_offset.cc b/src/tint/transform/first_index_offset.cc
index 34b481e..918fd5b 100644
--- a/src/tint/transform/first_index_offset.cc
+++ b/src/tint/transform/first_index_offset.cc
@@ -123,8 +123,8 @@
         Symbol buffer_name = ctx.dst->Sym();
         ctx.dst->GlobalVar(buffer_name, ctx.dst->ty.Of(struct_), ast::StorageClass::kUniform,
                            utils::Vector{
-                               ctx.dst->create<ast::BindingAttribute>(ub_binding),
-                               ctx.dst->create<ast::GroupAttribute>(ub_group),
+                               ctx.dst->Binding(AInt(ub_binding)),
+                               ctx.dst->Group(AInt(ub_group)),
                            });
 
         // Fix up all references to the builtins with the offsets
diff --git a/src/tint/transform/multiplanar_external_texture.cc b/src/tint/transform/multiplanar_external_texture.cc
index 2fb4248..74ce22e 100644
--- a/src/tint/transform/multiplanar_external_texture.cc
+++ b/src/tint/transform/multiplanar_external_texture.cc
@@ -131,11 +131,11 @@
             syms.plane_0 = ctx.Clone(global->symbol);
             syms.plane_1 = b.Symbols().New("ext_tex_plane_1");
             b.GlobalVar(syms.plane_1, b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()),
-                        b.Group(bps.plane_1.group), b.Binding(bps.plane_1.binding));
+                        b.Group(AInt(bps.plane_1.group)), b.Binding(AInt(bps.plane_1.binding)));
             syms.params = b.Symbols().New("ext_tex_params");
             b.GlobalVar(syms.params, b.ty.type_name("ExternalTextureParams"),
-                        ast::StorageClass::kUniform, b.Group(bps.params.group),
-                        b.Binding(bps.params.binding));
+                        ast::StorageClass::kUniform, b.Group(AInt(bps.params.group)),
+                        b.Binding(AInt(bps.params.binding)));
 
             // Replace the original texture_external binding with a texture_2d<f32>
             // binding.
diff --git a/src/tint/transform/num_workgroups_from_uniform.cc b/src/tint/transform/num_workgroups_from_uniform.cc
index 5772424..b96a2ee 100644
--- a/src/tint/transform/num_workgroups_from_uniform.cc
+++ b/src/tint/transform/num_workgroups_from_uniform.cc
@@ -150,7 +150,7 @@
 
             num_workgroups_ubo = ctx.dst->GlobalVar(
                 ctx.dst->Sym(), ctx.dst->ty.Of(num_workgroups_struct), ast::StorageClass::kUniform,
-                ctx.dst->Group(group), ctx.dst->Binding(binding));
+                ctx.dst->Group(AInt(group)), ctx.dst->Binding(AInt(binding)));
         }
         return num_workgroups_ubo;
     };
diff --git a/src/tint/transform/vertex_pulling.cc b/src/tint/transform/vertex_pulling.cc
index 346bb16..40d8d30 100644
--- a/src/tint/transform/vertex_pulling.cc
+++ b/src/tint/transform/vertex_pulling.cc
@@ -260,8 +260,8 @@
         for (uint32_t i = 0; i < cfg.vertex_state.size(); ++i) {
             // The decorated variable with struct type
             ctx.dst->GlobalVar(GetVertexBufferName(i), ctx.dst->ty.Of(struct_type),
-                               ast::StorageClass::kStorage, ast::Access::kRead, ctx.dst->Binding(i),
-                               ctx.dst->Group(cfg.pulling_group));
+                               ast::StorageClass::kStorage, ast::Access::kRead,
+                               ctx.dst->Binding(AInt(i)), ctx.dst->Group(AInt(cfg.pulling_group)));
         }
     }
 
diff --git a/src/tint/writer/flatten_bindings_test.cc b/src/tint/writer/flatten_bindings_test.cc
index 7137218..64c775f 100644
--- a/src/tint/writer/flatten_bindings_test.cc
+++ b/src/tint/writer/flatten_bindings_test.cc
@@ -23,6 +23,8 @@
 namespace tint::writer {
 namespace {
 
+using namespace tint::number_suffixes;  // NOLINT
+
 class FlattenBindingsTest : public ::testing::Test {};
 
 TEST_F(FlattenBindingsTest, NoBindings) {
@@ -36,9 +38,9 @@
 
 TEST_F(FlattenBindingsTest, AlreadyFlat) {
     ProgramBuilder b;
-    b.GlobalVar("a", b.ty.i32(), ast::StorageClass::kUniform, b.Group(0), b.Binding(0));
-    b.GlobalVar("b", b.ty.i32(), ast::StorageClass::kUniform, b.Group(0), b.Binding(1));
-    b.GlobalVar("c", b.ty.i32(), ast::StorageClass::kUniform, b.Group(0), b.Binding(2));
+    b.GlobalVar("a", b.ty.i32(), ast::StorageClass::kUniform, b.Group(0_a), b.Binding(0_a));
+    b.GlobalVar("b", b.ty.i32(), ast::StorageClass::kUniform, b.Group(0_a), b.Binding(1_a));
+    b.GlobalVar("c", b.ty.i32(), ast::StorageClass::kUniform, b.Group(0_a), b.Binding(2_a));
 
     Program program(std::move(b));
     ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
@@ -49,9 +51,9 @@
 
 TEST_F(FlattenBindingsTest, NotFlat_SingleNamespace) {
     ProgramBuilder b;
-    b.GlobalVar("a", b.ty.i32(), ast::StorageClass::kUniform, b.Group(0), b.Binding(0));
-    b.GlobalVar("b", b.ty.i32(), ast::StorageClass::kUniform, b.Group(1), b.Binding(1));
-    b.GlobalVar("c", b.ty.i32(), ast::StorageClass::kUniform, b.Group(2), b.Binding(2));
+    b.GlobalVar("a", b.ty.i32(), ast::StorageClass::kUniform, b.Group(0_a), b.Binding(0_a));
+    b.GlobalVar("b", b.ty.i32(), ast::StorageClass::kUniform, b.Group(1_a), b.Binding(1_a));
+    b.GlobalVar("c", b.ty.i32(), ast::StorageClass::kUniform, b.Group(2_a), b.Binding(2_a));
     b.WrapInFunction(b.Expr("a"), b.Expr("b"), b.Expr("c"));
 
     Program program(std::move(b));
@@ -82,30 +84,30 @@
     ProgramBuilder b;
 
     const size_t num_buffers = 3;
-    b.GlobalVar("buffer1", b.ty.i32(), ast::StorageClass::kUniform, b.Group(0), b.Binding(0));
-    b.GlobalVar("buffer2", b.ty.i32(), ast::StorageClass::kStorage, b.Group(1), b.Binding(1));
-    b.GlobalVar("buffer3", b.ty.i32(), ast::StorageClass::kStorage, ast::Access::kRead, b.Group(2),
-                b.Binding(2));
+    b.GlobalVar("buffer1", b.ty.i32(), ast::StorageClass::kUniform, b.Group(0_a), b.Binding(0_a));
+    b.GlobalVar("buffer2", b.ty.i32(), ast::StorageClass::kStorage, b.Group(1_a), b.Binding(1_a));
+    b.GlobalVar("buffer3", b.ty.i32(), ast::StorageClass::kStorage, ast::Access::kRead,
+                b.Group(2_a), b.Binding(2_a));
 
     const size_t num_samplers = 2;
-    b.GlobalVar("sampler1", b.ty.sampler(ast::SamplerKind::kSampler), b.Group(3), b.Binding(3));
-    b.GlobalVar("sampler2", b.ty.sampler(ast::SamplerKind::kComparisonSampler), b.Group(4),
-                b.Binding(4));
+    b.GlobalVar("sampler1", b.ty.sampler(ast::SamplerKind::kSampler), b.Group(3_a), b.Binding(3_a));
+    b.GlobalVar("sampler2", b.ty.sampler(ast::SamplerKind::kComparisonSampler), b.Group(4_a),
+                b.Binding(4_a));
 
     const size_t num_textures = 6;
     b.GlobalVar("texture1", b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()),
-                b.Group(5), b.Binding(5));
+                b.Group(5_a), b.Binding(5_a));
     b.GlobalVar("texture2", b.ty.multisampled_texture(ast::TextureDimension::k2d, b.ty.f32()),
-                b.Group(6), b.Binding(6));
+                b.Group(6_a), b.Binding(6_a));
     b.GlobalVar("texture3",
                 b.ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Float,
                                      ast::Access::kWrite),
-                b.Group(7), b.Binding(7));
-    b.GlobalVar("texture4", b.ty.depth_texture(ast::TextureDimension::k2d), b.Group(8),
-                b.Binding(8));
-    b.GlobalVar("texture5", b.ty.depth_multisampled_texture(ast::TextureDimension::k2d), b.Group(9),
-                b.Binding(9));
-    b.GlobalVar("texture6", b.ty.external_texture(), b.Group(10), b.Binding(10));
+                b.Group(7_a), b.Binding(7_a));
+    b.GlobalVar("texture4", b.ty.depth_texture(ast::TextureDimension::k2d), b.Group(8_a),
+                b.Binding(8_a));
+    b.GlobalVar("texture5", b.ty.depth_multisampled_texture(ast::TextureDimension::k2d),
+                b.Group(9_a), b.Binding(9_a));
+    b.GlobalVar("texture6", b.ty.external_texture(), b.Group(10_a), b.Binding(10_a));
 
     b.WrapInFunction(b.Assign(b.Phony(), "buffer1"), b.Assign(b.Phony(), "buffer2"),
                      b.Assign(b.Phony(), "buffer3"), b.Assign(b.Phony(), "sampler1"),
diff --git a/src/tint/writer/generate_external_texture_bindings_test.cc b/src/tint/writer/generate_external_texture_bindings_test.cc
index 8c7b8d4..718ab43 100644
--- a/src/tint/writer/generate_external_texture_bindings_test.cc
+++ b/src/tint/writer/generate_external_texture_bindings_test.cc
@@ -21,6 +21,8 @@
 namespace tint::writer {
 namespace {
 
+using namespace tint::number_suffixes;  // NOLINT
+
 constexpr auto kUniform = ast::StorageClass::kUniform;
 
 class GenerateExternalTextureBindingsTest : public ::testing::Test {};
@@ -36,7 +38,7 @@
 
 TEST_F(GenerateExternalTextureBindingsTest, One) {
     ProgramBuilder b;
-    b.GlobalVar("v0", b.ty.external_texture(), b.Group(0), b.Binding(0));
+    b.GlobalVar("v0", b.ty.external_texture(), b.Group(0_a), b.Binding(0_a));
 
     tint::Program program(std::move(b));
     ASSERT_TRUE(program.IsValid());
@@ -52,8 +54,8 @@
 
 TEST_F(GenerateExternalTextureBindingsTest, Two_SameGroup) {
     ProgramBuilder b;
-    b.GlobalVar("v0", b.ty.external_texture(), b.Group(0), b.Binding(0));
-    b.GlobalVar("v1", b.ty.external_texture(), b.Group(0), b.Binding(1));
+    b.GlobalVar("v0", b.ty.external_texture(), b.Group(0_a), b.Binding(0_a));
+    b.GlobalVar("v1", b.ty.external_texture(), b.Group(0_a), b.Binding(1_a));
 
     tint::Program program(std::move(b));
     ASSERT_TRUE(program.IsValid());
@@ -75,8 +77,8 @@
 
 TEST_F(GenerateExternalTextureBindingsTest, Two_DifferentGroup) {
     ProgramBuilder b;
-    b.GlobalVar("v0", b.ty.external_texture(), b.Group(0), b.Binding(0));
-    b.GlobalVar("v1", b.ty.external_texture(), b.Group(1), b.Binding(0));
+    b.GlobalVar("v0", b.ty.external_texture(), b.Group(0_a), b.Binding(0_a));
+    b.GlobalVar("v1", b.ty.external_texture(), b.Group(1_a), b.Binding(0_a));
 
     tint::Program program(std::move(b));
     ASSERT_TRUE(program.IsValid());
@@ -98,11 +100,11 @@
 
 TEST_F(GenerateExternalTextureBindingsTest, Two_WithOtherBindingsInSameGroup) {
     ProgramBuilder b;
-    b.GlobalVar("v0", b.ty.i32(), b.Group(0), b.Binding(0), kUniform);
-    b.GlobalVar("v1", b.ty.external_texture(), b.Group(0), b.Binding(1));
-    b.GlobalVar("v2", b.ty.i32(), b.Group(0), b.Binding(2), kUniform);
-    b.GlobalVar("v3", b.ty.external_texture(), b.Group(0), b.Binding(3));
-    b.GlobalVar("v4", b.ty.i32(), b.Group(0), b.Binding(4), kUniform);
+    b.GlobalVar("v0", b.ty.i32(), b.Group(0_a), b.Binding(0_a), kUniform);
+    b.GlobalVar("v1", b.ty.external_texture(), b.Group(0_a), b.Binding(1_a));
+    b.GlobalVar("v2", b.ty.i32(), b.Group(0_a), b.Binding(2_a), kUniform);
+    b.GlobalVar("v3", b.ty.external_texture(), b.Group(0_a), b.Binding(3_a));
+    b.GlobalVar("v4", b.ty.i32(), b.Group(0_a), b.Binding(4_a), kUniform);
 
     tint::Program program(std::move(b));
     ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
diff --git a/src/tint/writer/glsl/generator_impl_function_test.cc b/src/tint/writer/glsl/generator_impl_function_test.cc
index 90824ba..24747a9 100644
--- a/src/tint/writer/glsl/generator_impl_function_test.cc
+++ b/src/tint/writer/glsl/generator_impl_function_test.cc
@@ -351,7 +351,8 @@
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_Uniform) {
     auto* ubo_ty = Structure("UBO", utils::Vector{Member("coord", ty.vec4<f32>())});
-    auto* ubo = GlobalVar("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform, Binding(0), Group(1));
+    auto* ubo =
+        GlobalVar("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform, Binding(0_a), Group(1_a));
 
     Func("sub_func",
          utils::Vector{
@@ -401,7 +402,7 @@
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_UniformStruct) {
     auto* s = Structure("Uniforms", utils::Vector{Member("coord", ty.vec4<f32>())});
 
-    GlobalVar("uniforms", ty.Of(s), ast::StorageClass::kUniform, Binding(0), Group(1));
+    GlobalVar("uniforms", ty.Of(s), ast::StorageClass::kUniform, Binding(0_a), Group(1_a));
 
     auto* var = Var("v", ty.f32(), MemberAccessor(MemberAccessor("uniforms", "coord"), "x"));
 
@@ -441,8 +442,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
-              Group(1));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0_a),
+              Group(1_a));
 
     auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b"));
 
@@ -488,8 +489,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
-              Group(1));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(1_a));
 
     auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b"));
 
@@ -536,8 +537,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
-              Group(1));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0_a),
+              Group(1_a));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -581,8 +582,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
-              Group(1));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0_a),
+              Group(1_a));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -622,7 +623,7 @@
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_Called_By_EntryPoint_With_Uniform) {
     auto* s = Structure("S", utils::Vector{Member("x", ty.f32())});
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kUniform, Binding(0), Group(1));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kUniform, Binding(0_a), Group(1_a));
 
     Func("sub_func", utils::Vector{Param("param", ty.f32())}, ty.f32(),
          utils::Vector{
@@ -667,8 +668,8 @@
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_Called_By_EntryPoint_With_StorageBuffer) {
     auto* s = Structure("S", utils::Vector{Member("x", ty.f32())});
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
-              Group(1));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0_a),
+              Group(1_a));
 
     Func("sub_func", utils::Vector{Param("param", ty.f32())}, ty.f32(),
          utils::Vector{
@@ -893,8 +894,8 @@
 
     auto* s = Structure("Data", utils::Vector{Member("d", ty.f32())});
 
-    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
-              Group(0));
+    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0_a),
+              Group(0_a));
 
     {
         auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
diff --git a/src/tint/writer/glsl/generator_impl_member_accessor_test.cc b/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
index 1736ab0..1602068 100644
--- a/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
+++ b/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
@@ -92,7 +92,7 @@
         auto* s = b.Structure("Data", members);
 
         b.GlobalVar("data", b.ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-                    b.Group(1), b.Binding(0));
+                    b.Group(1_a), b.Binding(0_a));
     }
 
     void SetupFunction(utils::VectorRef<const ast::Statement*> statements) {
diff --git a/src/tint/writer/glsl/generator_impl_sanitizer_test.cc b/src/tint/writer/glsl/generator_impl_sanitizer_test.cc
index c801b47..f1157f1 100644
--- a/src/tint/writer/glsl/generator_impl_sanitizer_test.cc
+++ b/src/tint/writer/glsl/generator_impl_sanitizer_test.cc
@@ -26,7 +26,8 @@
 
 TEST_F(GlslSanitizerTest, Call_ArrayLength) {
     auto* s = Structure("my_struct", utils::Vector{Member(0, "a", ty.array<f32>(4))});
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -64,7 +65,8 @@
                                          Member(0, "z", ty.f32()),
                                          Member(4, "a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -101,7 +103,8 @@
 
 TEST_F(GlslSanitizerTest, Call_ArrayLength_ViaLets) {
     auto* s = Structure("my_struct", utils::Vector{Member(0, "a", ty.array<f32>(4))});
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
 
     auto* p = Let("p", AddressOf("b"));
     auto* p2 = Let("p2", AddressOf(MemberAccessor(Deref(p), "a")));
diff --git a/src/tint/writer/glsl/generator_impl_storage_buffer_test.cc b/src/tint/writer/glsl/generator_impl_storage_buffer_test.cc
index 0264b02..8c60374 100644
--- a/src/tint/writer/glsl/generator_impl_storage_buffer_test.cc
+++ b/src/tint/writer/glsl/generator_impl_storage_buffer_test.cc
@@ -37,8 +37,8 @@
                        ctx->Member("dewey", ctx->ty.f32(), utils::Vector{ctx->MemberAlign(256_u)}),
                        ctx->Member("louie", ctx->ty.f32(), utils::Vector{ctx->MemberAlign(256_u)}),
                    });
-    ctx->GlobalVar("nephews", ctx->ty.Of(nephews), ast::StorageClass::kStorage, ctx->Binding(0),
-                   ctx->Group(0));
+    ctx->GlobalVar("nephews", ctx->ty.Of(nephews), ast::StorageClass::kStorage, ctx->Binding(0_a),
+                   ctx->Group(0_a));
 }
 
 TEST_F(GlslGeneratorImplTest_StorageBuffer, Align) {
diff --git a/src/tint/writer/glsl/generator_impl_type_test.cc b/src/tint/writer/glsl/generator_impl_type_test.cc
index 23da975..15c1398 100644
--- a/src/tint/writer/glsl/generator_impl_type_test.cc
+++ b/src/tint/writer/glsl/generator_impl_type_test.cc
@@ -312,7 +312,7 @@
 
     auto* t = ty.depth_texture(params.dim);
 
-    GlobalVar("tex", t, Binding(1), Group(2));
+    GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -340,7 +340,7 @@
 TEST_F(GlslDepthMultisampledTexturesTest, Emit) {
     auto* t = ty.depth_multisampled_texture(ast::TextureDimension::k2d);
 
-    GlobalVar("tex", t, Binding(1), Group(2));
+    GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -384,7 +384,7 @@
     }
     auto* t = ty.sampled_texture(params.dim, datatype);
 
-    GlobalVar("tex", t, Binding(1), Group(2));
+    GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -519,7 +519,7 @@
 
     auto* t = ty.storage_texture(params.dim, params.imgfmt, ast::Access::kWrite);
 
-    GlobalVar("tex", t, Binding(1), Group(2));
+    GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
diff --git a/src/tint/writer/glsl/generator_impl_uniform_buffer_test.cc b/src/tint/writer/glsl/generator_impl_uniform_buffer_test.cc
index a2e5ee8..555e237 100644
--- a/src/tint/writer/glsl/generator_impl_uniform_buffer_test.cc
+++ b/src/tint/writer/glsl/generator_impl_uniform_buffer_test.cc
@@ -20,11 +20,13 @@
 namespace tint::writer::glsl {
 namespace {
 
+using namespace tint::number_suffixes;  // NOLINT
+
 using GlslGeneratorImplTest_UniformBuffer = TestHelper;
 
 TEST_F(GlslGeneratorImplTest_UniformBuffer, Simple) {
     auto* simple = Structure("Simple", utils::Vector{Member("member", ty.f32())});
-    GlobalVar("simple", ty.Of(simple), ast::StorageClass::kUniform, Group(0), Binding(0));
+    GlobalVar("simple", ty.Of(simple), ast::StorageClass::kUniform, Group(0_a), Binding(0_a));
 
     GeneratorImpl& gen = Build();
 
@@ -44,7 +46,7 @@
 
 TEST_F(GlslGeneratorImplTest_UniformBuffer, Simple_Desktop) {
     auto* simple = Structure("Simple", utils::Vector{Member("member", ty.f32())});
-    GlobalVar("simple", ty.Of(simple), ast::StorageClass::kUniform, Group(0), Binding(0));
+    GlobalVar("simple", ty.Of(simple), ast::StorageClass::kUniform, Group(0_a), Binding(0_a));
 
     GeneratorImpl& gen = Build(Version(Version::Standard::kDesktop, 4, 4));
 
diff --git a/src/tint/writer/hlsl/generator_impl_function_test.cc b/src/tint/writer/hlsl/generator_impl_function_test.cc
index 5bff35e..c1124a5 100644
--- a/src/tint/writer/hlsl/generator_impl_function_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_function_test.cc
@@ -360,7 +360,8 @@
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_Uniform) {
     auto* ubo_ty = Structure("UBO", utils::Vector{Member("coord", ty.vec4<f32>())});
-    auto* ubo = GlobalVar("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform, Binding(0), Group(1));
+    auto* ubo =
+        GlobalVar("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform, Binding(0_a), Group(1_a));
 
     Func("sub_func",
          utils::Vector{
@@ -403,7 +404,7 @@
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_UniformStruct) {
     auto* s = Structure("Uniforms", utils::Vector{Member("coord", ty.vec4<f32>())});
 
-    GlobalVar("uniforms", ty.Of(s), ast::StorageClass::kUniform, Binding(0), Group(1));
+    GlobalVar("uniforms", ty.Of(s), ast::StorageClass::kUniform, Binding(0_a), Group(1_a));
 
     auto* var = Var("v", ty.f32(), MemberAccessor(MemberAccessor("uniforms", "coord"), "x"));
 
@@ -436,8 +437,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
-              Group(1));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0_a),
+              Group(1_a));
 
     auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b"));
 
@@ -469,8 +470,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
-              Group(1));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(1_a));
 
     auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b"));
 
@@ -502,8 +503,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
-              Group(1));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0_a),
+              Group(1_a));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -533,8 +534,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
-              Group(1));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0_a),
+              Group(1_a));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -560,7 +561,7 @@
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_Called_By_EntryPoint_With_Uniform) {
     auto* s = Structure("S", utils::Vector{Member("x", ty.f32())});
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kUniform, Binding(0), Group(1));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kUniform, Binding(0_a), Group(1_a));
 
     Func("sub_func",
          utils::Vector{
@@ -602,8 +603,8 @@
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_Called_By_EntryPoint_With_StorageBuffer) {
     auto* s = Structure("S", utils::Vector{Member("x", ty.f32())});
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
-              Group(1));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0_a),
+              Group(1_a));
 
     Func("sub_func",
          utils::Vector{
@@ -845,8 +846,8 @@
 
     auto* s = Structure("Data", utils::Vector{Member("d", ty.f32())});
 
-    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
-              Group(0));
+    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0_a),
+              Group(0_a));
 
     {
         auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
diff --git a/src/tint/writer/hlsl/generator_impl_member_accessor_test.cc b/src/tint/writer/hlsl/generator_impl_member_accessor_test.cc
index 39dd86c..9ca2d18 100644
--- a/src/tint/writer/hlsl/generator_impl_member_accessor_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_member_accessor_test.cc
@@ -91,7 +91,7 @@
         auto* s = b.Structure("Data", members);
 
         b.GlobalVar("data", b.ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-                    b.Group(1), b.Binding(0));
+                    b.Group(1_a), b.Binding(0_a));
     }
 
     void SetupFunction(utils::VectorRef<const ast::Statement*> statements) {
diff --git a/src/tint/writer/hlsl/generator_impl_sanitizer_test.cc b/src/tint/writer/hlsl/generator_impl_sanitizer_test.cc
index 62a236e..b9ebd87 100644
--- a/src/tint/writer/hlsl/generator_impl_sanitizer_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_sanitizer_test.cc
@@ -26,7 +26,8 @@
 
 TEST_F(HlslSanitizerTest, Call_ArrayLength) {
     auto* s = Structure("my_struct", utils::Vector{Member(0, "a", ty.array<f32>(4))});
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -59,7 +60,8 @@
                                          Member(0, "z", ty.f32()),
                                          Member(4, "a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -90,7 +92,8 @@
 
 TEST_F(HlslSanitizerTest, Call_ArrayLength_ViaLets) {
     auto* s = Structure("my_struct", utils::Vector{Member(0, "a", ty.array<f32>(4))});
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
 
     auto* p = Let("p", AddressOf("b"));
     auto* p2 = Let("p2", AddressOf(MemberAccessor(Deref(p), "a")));
@@ -126,8 +129,10 @@
 
 TEST_F(HlslSanitizerTest, Call_ArrayLength_ArrayLengthFromUniform) {
     auto* s = Structure("my_struct", utils::Vector{Member(0, "a", ty.array<f32>(4))});
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
-    GlobalVar("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(2), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
+    GlobalVar("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(2_a),
+              Group(2_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
diff --git a/src/tint/writer/hlsl/generator_impl_type_test.cc b/src/tint/writer/hlsl/generator_impl_type_test.cc
index e89c500..279eb3f 100644
--- a/src/tint/writer/hlsl/generator_impl_type_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_type_test.cc
@@ -177,8 +177,8 @@
                                  Member("a", ty.i32()),
                                  Member("b", ty.f32()),
                              });
-    GlobalVar("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
-              Group(0));
+    GlobalVar("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0_a),
+              Group(0_a));
 
     GeneratorImpl& gen = Build();
 
@@ -308,7 +308,7 @@
 
     auto* t = ty.depth_texture(params.dim);
 
-    GlobalVar("tex", t, Binding(1), Group(2));
+    GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -339,7 +339,7 @@
 TEST_F(HlslDepthMultisampledTexturesTest, Emit) {
     auto* t = ty.depth_multisampled_texture(ast::TextureDimension::k2d);
 
-    GlobalVar("tex", t, Binding(1), Group(2));
+    GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -383,7 +383,7 @@
     }
     auto* t = ty.sampled_texture(params.dim, datatype);
 
-    GlobalVar("tex", t, Binding(1), Group(2));
+    GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -521,8 +521,8 @@
 
     GlobalVar("tex", t,
               utils::Vector{
-                  Group(2),
-                  Binding(1),
+                  Group(2_a),
+                  Binding(1_a),
               });
 
     Func("main", utils::Empty, ty.void_(),
diff --git a/src/tint/writer/msl/generator_impl_function_test.cc b/src/tint/writer/msl/generator_impl_function_test.cc
index 56761a7..fd612b3 100644
--- a/src/tint/writer/msl/generator_impl_function_test.cc
+++ b/src/tint/writer/msl/generator_impl_function_test.cc
@@ -342,8 +342,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(0),
-              Binding(0));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(0_a),
+              Binding(0_a));
 
     auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b"));
 
@@ -381,8 +381,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Group(0),
-              Binding(0));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Group(0_a),
+              Binding(0_a));
 
     auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b"));
 
@@ -416,7 +416,8 @@
 
 TEST_F(MslGeneratorImplTest, Emit_Attribute_Called_By_EntryPoint_With_Uniform) {
     auto* ubo_ty = Structure("UBO", utils::Vector{Member("coord", ty.vec4<f32>())});
-    auto* ubo = GlobalVar("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform, Group(0), Binding(0));
+    auto* ubo =
+        GlobalVar("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform, Group(0_a), Binding(0_a));
 
     Func("sub_func",
          utils::Vector{
@@ -466,8 +467,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(0),
-              Binding(0));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(0_a),
+              Binding(0_a));
 
     Func("sub_func",
          utils::Vector{
@@ -518,8 +519,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Group(0),
-              Binding(0));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Group(0_a),
+              Binding(0_a));
 
     Func("sub_func",
          utils::Vector{
@@ -656,8 +657,8 @@
 
     auto* s = Structure("Data", utils::Vector{Member("d", ty.f32())});
 
-    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(0),
-              Binding(0));
+    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(0_a),
+              Binding(0_a));
 
     {
         auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
diff --git a/src/tint/writer/msl/generator_impl_sanitizer_test.cc b/src/tint/writer/msl/generator_impl_sanitizer_test.cc
index 472822b..f96a1c9 100644
--- a/src/tint/writer/msl/generator_impl_sanitizer_test.cc
+++ b/src/tint/writer/msl/generator_impl_sanitizer_test.cc
@@ -22,12 +22,14 @@
 namespace {
 
 using ::testing::HasSubstr;
+using namespace tint::number_suffixes;  // NOLINT
 
 using MslSanitizerTest = TestHelper;
 
 TEST_F(MslSanitizerTest, Call_ArrayLength) {
     auto* s = Structure("my_struct", utils::Vector{Member(0, "a", ty.array<f32>(4))});
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -80,7 +82,8 @@
                                          Member(0, "z", ty.f32()),
                                          Member(4, "a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -132,7 +135,8 @@
 
 TEST_F(MslSanitizerTest, Call_ArrayLength_ViaLets) {
     auto* s = Structure("my_struct", utils::Vector{Member(0, "a", ty.array<f32>(4))});
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
 
     auto* p = Let("p", AddressOf("b"));
     auto* p2 = Let("p2", AddressOf(MemberAccessor(Deref(p), "a")));
@@ -188,8 +192,10 @@
 
 TEST_F(MslSanitizerTest, Call_ArrayLength_ArrayLengthFromUniform) {
     auto* s = Structure("my_struct", utils::Vector{Member(0, "a", ty.array<f32>(4))});
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(0));
-    GlobalVar("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(2), Group(0));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(0_a));
+    GlobalVar("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(2_a),
+              Group(0_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -245,8 +251,10 @@
 
 TEST_F(MslSanitizerTest, Call_ArrayLength_ArrayLengthFromUniformMissingBinding) {
     auto* s = Structure("my_struct", utils::Vector{Member(0, "a", ty.array<f32>(4))});
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(0));
-    GlobalVar("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(2), Group(0));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(0_a));
+    GlobalVar("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(2_a),
+              Group(0_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
diff --git a/src/tint/writer/msl/generator_impl_type_test.cc b/src/tint/writer/msl/generator_impl_type_test.cc
index 531a45d..a6901b4 100644
--- a/src/tint/writer/msl/generator_impl_type_test.cc
+++ b/src/tint/writer/msl/generator_impl_type_test.cc
@@ -282,7 +282,8 @@
                  Member("z", ty.f32()),
              });
 
-    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
+    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     GeneratorImpl& gen = Build();
 
@@ -388,7 +389,8 @@
                                  Member("e", ty.f32()),
                              });
 
-    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
+    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     GeneratorImpl& gen = Build();
 
@@ -478,7 +480,8 @@
                                  Member("f", array_z),
                              });
 
-    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
+    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     GeneratorImpl& gen = Build();
 
@@ -560,7 +563,8 @@
                                  Member("c", ty.i32()),
                              });
 
-    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
+    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     GeneratorImpl& gen = Build();
 
@@ -620,7 +624,8 @@
                                  Member("tint_pad_21", ty.f32()),
                              });
 
-    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
+    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     GeneratorImpl& gen = Build();
 
@@ -677,7 +682,8 @@
                                  Member("b", ty.f32()),
                              });
 
-    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
+    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     GeneratorImpl& gen = Build();
 
@@ -840,7 +846,7 @@
     auto params = GetParam();
 
     auto* s = ty.storage_texture(params.dim, ast::TexelFormat::kR32Float, ast::Access::kWrite);
-    GlobalVar("test_var", s, Binding(0), Group(0));
+    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
 
     GeneratorImpl& gen = Build();
 
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index 933b562..55b8bc7 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -762,7 +762,12 @@
         return true;
     }
 
-    auto* sem = builder_.Sem().Get(v);
+    auto* sem = builder_.Sem().Get<sem::GlobalVariable>(v);
+    if (!sem) {
+        TINT_ICE(Writer, builder_.Diagnostics())
+            << "attempted to generate a global from a non-global variable";
+        return false;
+    }
     auto* type = sem->Type()->UnwrapRef();
 
     uint32_t init_id = 0;
@@ -893,15 +898,17 @@
                            {Operand(var_id), U32Operand(SpvDecorationInvariant)});
                 return true;
             },
-            [&](const ast::BindingAttribute* binding) {
+            [&](const ast::BindingAttribute*) {
+                auto bp = sem->BindingPoint();
                 push_annot(spv::Op::OpDecorate, {Operand(var_id), U32Operand(SpvDecorationBinding),
-                                                 Operand(binding->value)});
+                                                 Operand(bp.binding)});
                 return true;
             },
-            [&](const ast::GroupAttribute* group) {
-                push_annot(spv::Op::OpDecorate,
-                           {Operand(var_id), U32Operand(SpvDecorationDescriptorSet),
-                            Operand(group->value)});
+            [&](const ast::GroupAttribute*) {
+                auto bp = sem->BindingPoint();
+                push_annot(
+                    spv::Op::OpDecorate,
+                    {Operand(var_id), U32Operand(SpvDecorationDescriptorSet), Operand(bp.group)});
                 return true;
             },
             [&](const ast::IdAttribute*) {
diff --git a/src/tint/writer/spirv/builder_assign_test.cc b/src/tint/writer/spirv/builder_assign_test.cc
index be30dc7..963e680 100644
--- a/src/tint/writer/spirv/builder_assign_test.cc
+++ b/src/tint/writer/spirv/builder_assign_test.cc
@@ -186,7 +186,7 @@
     spirv::Builder& b = Build();
 
     b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
     ASSERT_FALSE(b.has_error()) << b.error();
 
     EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
@@ -195,16 +195,16 @@
     EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeStruct %4 %4
 %2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%5 = OpTypeInt 32 0
-%6 = OpConstant %5 1
-%7 = OpTypePointer Function %4
-%9 = OpConstant %4 4
+%5 = OpConstantNull %3
+%6 = OpTypeInt 32 0
+%7 = OpConstant %6 1
+%8 = OpTypePointer Function %4
+%10 = OpConstant %4 4
 )");
 
     EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%8 = OpAccessChain %7 %1 %6
-OpStore %8 %9
+              R"(%9 = OpAccessChain %8 %1 %7
+OpStore %9 %10
 )");
 }
 
diff --git a/src/tint/writer/spirv/builder_binary_expression_test.cc b/src/tint/writer/spirv/builder_binary_expression_test.cc
index 922473e..9c4ba08 100644
--- a/src/tint/writer/spirv/builder_binary_expression_test.cc
+++ b/src/tint/writer/spirv/builder_binary_expression_test.cc
@@ -703,7 +703,6 @@
 
 TEST_F(BuilderTest, Binary_Multiply_MatrixScalar_F32) {
     auto* var = Var("mat", ty.mat3x3<f32>());
-
     auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr("mat"), Expr(1_f));
 
     WrapInFunction(var, expr);
@@ -711,120 +710,7 @@
     spirv::Builder& b = Build();
 
     b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
-              R"(%5 = OpTypeFloat 32
-%4 = OpTypeVector %5 3
-%3 = OpTypeMatrix %4 3
-%2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%7 = OpConstant %5 1
-)");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%6 = OpLoad %3 %1
-%8 = OpMatrixTimesScalar %3 %6 %7
-)");
-}
-
-TEST_F(BuilderTest, Binary_Multiply_MatrixScalar_F16) {
-    Enable(ast::Extension::kF16);
-
-    auto* var = Var("mat", ty.mat3x3<f16>());
-
-    auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr("mat"), Expr(1_h));
-
-    WrapInFunction(var, expr);
-
-    spirv::Builder& b = Build();
-
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
-              R"(%5 = OpTypeFloat 16
-%4 = OpTypeVector %5 3
-%3 = OpTypeMatrix %4 3
-%2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%7 = OpConstant %5 0x1p+0
-)");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%6 = OpLoad %3 %1
-%8 = OpMatrixTimesScalar %3 %6 %7
-)");
-}
-
-TEST_F(BuilderTest, Binary_Multiply_ScalarMatrix_F32) {
-    auto* var = Var("mat", ty.mat3x3<f32>());
-
-    auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr(1_f), Expr("mat"));
-
-    WrapInFunction(var, expr);
-
-    spirv::Builder& b = Build();
-
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
-              R"(%5 = OpTypeFloat 32
-%4 = OpTypeVector %5 3
-%3 = OpTypeMatrix %4 3
-%2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%6 = OpConstant %5 1
-)");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%7 = OpLoad %3 %1
-%8 = OpMatrixTimesScalar %3 %7 %6
-)");
-}
-
-TEST_F(BuilderTest, Binary_Multiply_ScalarMatrix_F16) {
-    Enable(ast::Extension::kF16);
-
-    auto* var = Var("mat", ty.mat3x3<f16>());
-
-    auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr(1_h), Expr("mat"));
-
-    WrapInFunction(var, expr);
-
-    spirv::Builder& b = Build();
-
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
-              R"(%5 = OpTypeFloat 16
-%4 = OpTypeVector %5 3
-%3 = OpTypeMatrix %4 3
-%2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%6 = OpConstant %5 0x1p+0
-)");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%7 = OpLoad %3 %1
-%8 = OpMatrixTimesScalar %3 %7 %6
-)");
-}
-
-TEST_F(BuilderTest, Binary_Multiply_MatrixVector_F32) {
-    auto* var = Var("mat", ty.mat3x3<f32>());
-    auto* rhs = vec3<f32>(1_f, 1_f, 1_f);
-
-    auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr("mat"), rhs);
-
-    WrapInFunction(var, expr);
-
-    spirv::Builder& b = Build();
-
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
 
     EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()),
@@ -832,13 +718,122 @@
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
 %2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%7 = OpConstant %5 1
-%8 = OpConstantComposite %4 %7 %7 %7
+%6 = OpConstantNull %3
+%8 = OpConstant %5 1
 )");
     EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%6 = OpLoad %3 %1
-%9 = OpMatrixTimesVector %4 %6 %8
+              R"(%7 = OpLoad %3 %1
+%9 = OpMatrixTimesScalar %3 %7 %8
+)");
+}
+
+TEST_F(BuilderTest, Binary_Multiply_MatrixScalar_F16) {
+    Enable(ast::Extension::kF16);
+
+    auto* var = Var("mat", ty.mat3x3<f16>());
+    auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr("mat"), Expr(1_h));
+
+    WrapInFunction(var, expr);
+
+    spirv::Builder& b = Build();
+
+    b.push_function(Function{});
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
+    EXPECT_EQ(DumpInstructions(b.types()),
+              R"(%5 = OpTypeFloat 16
+%4 = OpTypeVector %5 3
+%3 = OpTypeMatrix %4 3
+%2 = OpTypePointer Function %3
+%6 = OpConstantNull %3
+%8 = OpConstant %5 0x1p+0
+)");
+    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+              R"(%7 = OpLoad %3 %1
+%9 = OpMatrixTimesScalar %3 %7 %8
+)");
+}
+
+TEST_F(BuilderTest, Binary_Multiply_ScalarMatrix_F32) {
+    auto* var = Var("mat", ty.mat3x3<f32>());
+    auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr(1_f), Expr("mat"));
+
+    WrapInFunction(var, expr);
+
+    spirv::Builder& b = Build();
+
+    b.push_function(Function{});
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
+    EXPECT_EQ(DumpInstructions(b.types()),
+              R"(%5 = OpTypeFloat 32
+%4 = OpTypeVector %5 3
+%3 = OpTypeMatrix %4 3
+%2 = OpTypePointer Function %3
+%6 = OpConstantNull %3
+%7 = OpConstant %5 1
+)");
+    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+              R"(%8 = OpLoad %3 %1
+%9 = OpMatrixTimesScalar %3 %8 %7
+)");
+}
+
+TEST_F(BuilderTest, Binary_Multiply_ScalarMatrix_F16) {
+    Enable(ast::Extension::kF16);
+
+    auto* var = Var("mat", ty.mat3x3<f16>());
+    auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr(1_h), Expr("mat"));
+
+    WrapInFunction(var, expr);
+
+    spirv::Builder& b = Build();
+
+    b.push_function(Function{});
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
+    EXPECT_EQ(DumpInstructions(b.types()),
+              R"(%5 = OpTypeFloat 16
+%4 = OpTypeVector %5 3
+%3 = OpTypeMatrix %4 3
+%2 = OpTypePointer Function %3
+%6 = OpConstantNull %3
+%7 = OpConstant %5 0x1p+0
+)");
+    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+              R"(%8 = OpLoad %3 %1
+%9 = OpMatrixTimesScalar %3 %8 %7
+)");
+}
+
+TEST_F(BuilderTest, Binary_Multiply_MatrixVector_F32) {
+    auto* var = Var("mat", ty.mat3x3<f32>());
+    auto* rhs = vec3<f32>(1_f, 1_f, 1_f);
+    auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr("mat"), rhs);
+
+    WrapInFunction(var, expr);
+
+    spirv::Builder& b = Build();
+
+    b.push_function(Function{});
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
+    EXPECT_EQ(DumpInstructions(b.types()),
+              R"(%5 = OpTypeFloat 32
+%4 = OpTypeVector %5 3
+%3 = OpTypeMatrix %4 3
+%2 = OpTypePointer Function %3
+%6 = OpConstantNull %3
+%8 = OpConstant %5 1
+%9 = OpConstantComposite %4 %8 %8 %8
+)");
+    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+              R"(%7 = OpLoad %3 %1
+%10 = OpMatrixTimesVector %4 %7 %9
 )");
 }
 
@@ -847,7 +842,6 @@
 
     auto* var = Var("mat", ty.mat3x3<f16>());
     auto* rhs = vec3<f16>(1_h, 1_h, 1_h);
-
     auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr("mat"), rhs);
 
     WrapInFunction(var, expr);
@@ -855,28 +849,27 @@
     spirv::Builder& b = Build();
 
     b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()),
               R"(%5 = OpTypeFloat 16
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
 %2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%7 = OpConstant %5 0x1p+0
-%8 = OpConstantComposite %4 %7 %7 %7
+%6 = OpConstantNull %3
+%8 = OpConstant %5 0x1p+0
+%9 = OpConstantComposite %4 %8 %8 %8
 )");
     EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%6 = OpLoad %3 %1
-%9 = OpMatrixTimesVector %4 %6 %8
+              R"(%7 = OpLoad %3 %1
+%10 = OpMatrixTimesVector %4 %7 %9
 )");
 }
 
 TEST_F(BuilderTest, Binary_Multiply_VectorMatrix_F32) {
     auto* var = Var("mat", ty.mat3x3<f32>());
     auto* lhs = vec3<f32>(1_f, 1_f, 1_f);
-
     auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, Expr("mat"));
 
     WrapInFunction(var, expr);
@@ -884,21 +877,21 @@
     spirv::Builder& b = Build();
 
     b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()),
               R"(%5 = OpTypeFloat 32
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
 %2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%6 = OpConstant %5 1
-%7 = OpConstantComposite %4 %6 %6 %6
+%6 = OpConstantNull %3
+%7 = OpConstant %5 1
+%8 = OpConstantComposite %4 %7 %7 %7
 )");
     EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%8 = OpLoad %3 %1
-%9 = OpVectorTimesMatrix %4 %7 %8
+              R"(%9 = OpLoad %3 %1
+%10 = OpVectorTimesMatrix %4 %8 %9
 )");
 }
 
@@ -915,27 +908,26 @@
     spirv::Builder& b = Build();
 
     b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()),
               R"(%5 = OpTypeFloat 16
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
 %2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
-%6 = OpConstant %5 0x1p+0
-%7 = OpConstantComposite %4 %6 %6 %6
+%6 = OpConstantNull %3
+%7 = OpConstant %5 0x1p+0
+%8 = OpConstantComposite %4 %7 %7 %7
 )");
     EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%8 = OpLoad %3 %1
-%9 = OpVectorTimesMatrix %4 %7 %8
+              R"(%9 = OpLoad %3 %1
+%10 = OpVectorTimesMatrix %4 %8 %9
 )");
 }
 
 TEST_F(BuilderTest, Binary_Multiply_MatrixMatrix_F32) {
     auto* var = Var("mat", ty.mat3x3<f32>());
-
     auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr("mat"), Expr("mat"));
 
     WrapInFunction(var, expr);
@@ -943,20 +935,20 @@
     spirv::Builder& b = Build();
 
     b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()),
               R"(%5 = OpTypeFloat 32
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
 %2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
+%6 = OpConstantNull %3
 )");
     EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%6 = OpLoad %3 %1
-%7 = OpLoad %3 %1
-%8 = OpMatrixTimesMatrix %3 %6 %7
+              R"(%7 = OpLoad %3 %1
+%8 = OpLoad %3 %1
+%9 = OpMatrixTimesMatrix %3 %7 %8
 )");
 }
 
@@ -964,7 +956,6 @@
     Enable(ast::Extension::kF16);
 
     auto* var = Var("mat", ty.mat3x3<f16>());
-
     auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr("mat"), Expr("mat"));
 
     WrapInFunction(var, expr);
@@ -972,28 +963,26 @@
     spirv::Builder& b = Build();
 
     b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()),
               R"(%5 = OpTypeFloat 16
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
 %2 = OpTypePointer Function %3
-%1 = OpVariable %2 Function
+%6 = OpConstantNull %3
 )");
     EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%6 = OpLoad %3 %1
-%7 = OpLoad %3 %1
-%8 = OpMatrixTimesMatrix %3 %6 %7
+              R"(%7 = OpLoad %3 %1
+%8 = OpLoad %3 %1
+%9 = OpMatrixTimesMatrix %3 %7 %8
 )");
 }
 
 TEST_F(BuilderTest, Binary_LogicalAnd) {
     auto* lhs = create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(1_i), Expr(2_i));
-
     auto* rhs = create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(3_i), Expr(4_i));
-
     auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, lhs, rhs);
 
     WrapInFunction(expr);
@@ -1028,7 +1017,6 @@
 TEST_F(BuilderTest, Binary_LogicalAnd_WithLoads) {
     auto* a_var = GlobalVar("a", ty.bool_(), ast::StorageClass::kPrivate, Expr(true));
     auto* b_var = GlobalVar("b", ty.bool_(), ast::StorageClass::kPrivate, Expr(false));
-
     auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"), Expr("b"));
 
     WrapInFunction(expr);
diff --git a/src/tint/writer/spirv/builder_builtin_test.cc b/src/tint/writer/spirv/builder_builtin_test.cc
index f4b6096..cfb85d9 100644
--- a/src/tint/writer/spirv/builder_builtin_test.cc
+++ b/src/tint/writer/spirv/builder_builtin_test.cc
@@ -43,8 +43,8 @@
     auto* s = ty.sampler(ast::SamplerKind::kComparisonSampler);
     auto* t = ty.depth_texture(ast::TextureDimension::k2d);
 
-    auto* tex = GlobalVar("texture", t, Binding(0), Group(0));
-    auto* sampler = GlobalVar("sampler", s, Binding(1), Group(0));
+    auto* tex = GlobalVar("texture", t, Binding(0_a), Group(0_a));
+    auto* sampler = GlobalVar("sampler", s, Binding(1_a), Group(0_a));
 
     auto* expr1 = Call("textureSampleCompare", "texture", "sampler", vec2<f32>(1_f, 2_f), 2_f);
     auto* expr2 = Call("textureSampleCompare", "texture", "sampler", vec2<f32>(1_f, 2_f), 2_f);
@@ -277,7 +277,8 @@
     auto* s = Structure("my_struct", utils::Vector{
                                          Member("a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
     auto* expr = Call("arrayLength", AddressOf(MemberAccessor("b", "a")));
 
     Func("a_func", utils::Empty, ty.void_(),
@@ -320,7 +321,8 @@
                                          Member("z", ty.f32()),
                                          Member(4, "a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
     auto* expr = Call("arrayLength", AddressOf(MemberAccessor("b", "a")));
 
     Func("a_func", utils::Empty, ty.void_(),
@@ -362,7 +364,8 @@
     auto* s = Structure("my_struct", utils::Vector{
                                          Member("a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
 
     auto* p = Let("p", AddressOf("b"));
     auto* p2 = Let("p2", AddressOf(MemberAccessor(Deref(p), "a")));
@@ -420,7 +423,8 @@
     auto* s = Structure("my_struct", utils::Vector{
                                          Member("a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1_a),
+              Group(2_a));
 
     auto* p = Let("p", AddressOf(Deref(AddressOf("b"))));
     auto* p2 = Let("p2", AddressOf(Deref(p)));
@@ -3219,8 +3223,8 @@
                                  Member("u", ty.atomic<u32>()),
                                  Member("i", ty.atomic<i32>()),
                              });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1),
-              Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1_a),
+              Group(2_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -3282,8 +3286,8 @@
                                  Member("u", ty.atomic<u32>()),
                                  Member("i", ty.atomic<i32>()),
                              });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1),
-              Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1_a),
+              Group(2_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -3353,8 +3357,8 @@
     auto* s = Structure("S", utils::Vector{
                                  Member("v", ty.atomic<i32>()),
                              });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1),
-              Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1_a),
+              Group(2_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -3425,8 +3429,8 @@
     auto* s = Structure("S", utils::Vector{
                                  Member("v", ty.atomic<u32>()),
                              });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1),
-              Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1_a),
+              Group(2_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -3499,8 +3503,8 @@
                                  Member("u", ty.atomic<u32>()),
                                  Member("i", ty.atomic<i32>()),
                              });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1),
-              Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1_a),
+              Group(2_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -3574,8 +3578,8 @@
                                  Member("u", ty.atomic<u32>()),
                                  Member("i", ty.atomic<i32>()),
                              });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1),
-              Group(2));
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1_a),
+              Group(2_a));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
diff --git a/src/tint/writer/spirv/builder_function_test.cc b/src/tint/writer/spirv/builder_function_test.cc
index d1c0e9f..449f937 100644
--- a/src/tint/writer/spirv/builder_function_test.cc
+++ b/src/tint/writer/spirv/builder_function_test.cc
@@ -198,8 +198,8 @@
 
     auto* s = Structure("Data", utils::Vector{Member("d", ty.f32())});
 
-    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
-              Group(0));
+    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0_a),
+              Group(0_a));
 
     {
         auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
diff --git a/src/tint/writer/spirv/builder_global_variable_test.cc b/src/tint/writer/spirv/builder_global_variable_test.cc
index bfecdf1..e4a6a48 100644
--- a/src/tint/writer/spirv/builder_global_variable_test.cc
+++ b/src/tint/writer/spirv/builder_global_variable_test.cc
@@ -233,7 +233,7 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_WithBindingAndGroup) {
-    auto* v = GlobalVar("var", ty.sampler(ast::SamplerKind::kSampler), Binding(2), Group(3));
+    auto* v = GlobalVar("var", ty.sampler(ast::SamplerKind::kSampler), Binding(2_a), Group(3_a));
 
     spirv::Builder& b = Build();
 
@@ -447,7 +447,8 @@
                                  Member("b", ty.i32()),
                              });
 
-    GlobalVar("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
+    GlobalVar("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -484,7 +485,8 @@
 
     auto* A = Structure("A", utils::Vector{Member("a", ty.i32())});
     auto* B = Alias("B", ty.Of(A));
-    GlobalVar("b", ty.Of(B), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
+    GlobalVar("b", ty.Of(B), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -519,7 +521,8 @@
 
     auto* A = Structure("A", utils::Vector{Member("a", ty.i32())});
     auto* B = Alias("B", ty.Of(A));
-    GlobalVar("b", ty.Of(B), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
+    GlobalVar("b", ty.Of(B), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -553,9 +556,10 @@
     // var<storage, read_write> c : A
 
     auto* A = Structure("A", utils::Vector{Member("a", ty.i32())});
-    GlobalVar("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead, Group(0), Binding(0));
-    GlobalVar("c", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(1),
-              Binding(0));
+    GlobalVar("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead, Group(0_a),
+              Binding(0_a));
+    GlobalVar("c", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(1_a),
+              Binding(0_a));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -592,7 +596,7 @@
     auto* type = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
                                     ast::Access::kWrite);
 
-    auto* var_a = GlobalVar("a", type, Binding(0), Group(0));
+    auto* var_a = GlobalVar("a", type, Binding(0_a), Group(0_a));
 
     spirv::Builder& b = Build();
 
@@ -619,11 +623,11 @@
 
     auto* type_a = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
                                       ast::Access::kReadWrite);
-    auto* var_a = GlobalVar("a", type_a, Binding(0), Group(0));
+    auto* var_a = GlobalVar("a", type_a, Binding(0_a), Group(0_a));
 
     auto* type_b = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
                                       ast::Access::kWrite);
-    auto* var_b = GlobalVar("b", type_b, Binding(1), Group(0));
+    auto* var_b = GlobalVar("b", type_b, Binding(1_a), Group(0_a));
 
     spirv::Builder& b = Build();
 
diff --git a/src/tint/writer/spirv/builder_type_test.cc b/src/tint/writer/spirv/builder_type_test.cc
index 3b8e0b1..9b61268 100644
--- a/src/tint/writer/spirv/builder_type_test.cc
+++ b/src/tint/writer/spirv/builder_type_test.cc
@@ -28,8 +28,8 @@
 TEST_F(BuilderTest_Type, GenerateRuntimeArray) {
     auto* ary = ty.array(ty.i32());
     auto* str = Structure("S", utils::Vector{Member("x", ary)});
-    GlobalVar("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
-              Group(0));
+    GlobalVar("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     spirv::Builder& b = Build();
 
@@ -45,8 +45,8 @@
 TEST_F(BuilderTest_Type, ReturnsGeneratedRuntimeArray) {
     auto* ary = ty.array(ty.i32());
     auto* str = Structure("S", utils::Vector{Member("x", ary)});
-    GlobalVar("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
-              Group(0));
+    GlobalVar("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0_a),
+              Group(0_a));
 
     spirv::Builder& b = Build();
 
@@ -836,7 +836,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k1d, ast::TexelFormat::kR32Float,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0), Group(0));
+    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
 
     spirv::Builder& b = Build();
 
@@ -851,7 +851,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Float,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0), Group(0));
+    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
 
     spirv::Builder& b = Build();
 
@@ -866,7 +866,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k2dArray, ast::TexelFormat::kR32Float,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0), Group(0));
+    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
 
     spirv::Builder& b = Build();
 
@@ -881,7 +881,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k3d, ast::TexelFormat::kR32Float,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0), Group(0));
+    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
 
     spirv::Builder& b = Build();
 
@@ -896,7 +896,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Float,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0), Group(0));
+    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
 
     spirv::Builder& b = Build();
 
@@ -911,7 +911,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Sint,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0), Group(0));
+    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
 
     spirv::Builder& b = Build();
 
@@ -926,7 +926,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0), Group(0));
+    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
 
     spirv::Builder& b = Build();
 
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index 9f9c4c6..755fe6b 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -740,11 +740,19 @@
                 return true;
             },
             [&](const ast::BindingAttribute* binding) {
-                out << "binding(" << binding->value << ")";
+                out << "binding(";
+                if (!EmitExpression(out, binding->value)) {
+                    return false;
+                }
+                out << ")";
                 return true;
             },
             [&](const ast::GroupAttribute* group) {
-                out << "group(" << group->value << ")";
+                out << "group(";
+                if (!EmitExpression(out, group->value)) {
+                    return false;
+                }
+                out << ")";
                 return true;
             },
             [&](const ast::LocationAttribute* location) {
diff --git a/src/tint/writer/wgsl/generator_impl_function_test.cc b/src/tint/writer/wgsl/generator_impl_function_test.cc
index 09e1e8b..3b80e69 100644
--- a/src/tint/writer/wgsl/generator_impl_function_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_function_test.cc
@@ -179,8 +179,8 @@
                                     Member("d", ty.f32()),
                                 });
 
-    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
-              Group(0));
+    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0_a),
+              Group(0_a));
 
     {
         auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
diff --git a/src/tint/writer/wgsl/generator_impl_global_decl_test.cc b/src/tint/writer/wgsl/generator_impl_global_decl_test.cc
index fc424f6..06a5e19 100644
--- a/src/tint/writer/wgsl/generator_impl_global_decl_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_global_decl_test.cc
@@ -105,7 +105,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_Global_Sampler) {
-    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(0), Binding(0));
+    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(0_a), Binding(0_a));
 
     GeneratorImpl& gen = Build();
 
@@ -117,7 +117,7 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_Global_Texture) {
     auto* st = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-    GlobalVar("t", st, Group(0), Binding(0));
+    GlobalVar("t", st, Group(0_a), Binding(0_a));
 
     GeneratorImpl& gen = Build();
 
diff --git a/src/tint/writer/wgsl/generator_impl_type_test.cc b/src/tint/writer/wgsl/generator_impl_type_test.cc
index cdba585..5390057 100644
--- a/src/tint/writer/wgsl/generator_impl_type_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_type_test.cc
@@ -462,7 +462,7 @@
     auto param = GetParam();
 
     auto* t = ty.storage_texture(param.dim, param.fmt, param.access);
-    GlobalVar("g", t, Binding(1), Group(2));
+    GlobalVar("g", t, Binding(1_a), Group(2_a));
 
     GeneratorImpl& gen = Build();
 
diff --git a/src/tint/writer/wgsl/generator_impl_variable_test.cc b/src/tint/writer/wgsl/generator_impl_variable_test.cc
index 521d3b6..f1ac216 100644
--- a/src/tint/writer/wgsl/generator_impl_variable_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_variable_test.cc
@@ -43,8 +43,8 @@
 
 TEST_F(WgslGeneratorImplTest, EmitVariable_Access_Read) {
     auto* s = Structure("S", utils::Vector{Member("a", ty.i32())});
-    auto* v = GlobalVar("a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
-                        Group(0));
+    auto* v = GlobalVar("a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+                        Binding(0_a), Group(0_a));
 
     GeneratorImpl& gen = Build();
 
@@ -56,7 +56,7 @@
 TEST_F(WgslGeneratorImplTest, EmitVariable_Access_ReadWrite) {
     auto* s = Structure("S", utils::Vector{Member("a", ty.i32())});
     auto* v = GlobalVar("a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-                        Binding(0), Group(0));
+                        Binding(0_a), Group(0_a));
 
     GeneratorImpl& gen = Build();
 
@@ -66,7 +66,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitVariable_Decorated) {
-    auto* v = GlobalVar("a", ty.sampler(ast::SamplerKind::kSampler), Group(1), Binding(2));
+    auto* v = GlobalVar("a", ty.sampler(ast::SamplerKind::kSampler), Group(1_a), Binding(2_a));
 
     GeneratorImpl& gen = Build();