Import Tint changes from Dawn

Changes:
  - 158c85dab456c06b5d99d4c509841ad697b399fd Add new relational rules to WGSL grammar. by dan sinclair <dsinclair@chromium.org>
  - 97e0c71eb6a3804e94936835faa5cf5ae91706dc Add new shift expression grammar rule. by dan sinclair <dsinclair@chromium.org>
  - 9d27ab70439e3c0fb971fc89865e984a2369a28c Update parser comments. by dan sinclair <dsinclair@chromium.org>
  - d0ccb1aae63ae89338a94e317436863c825cd130 tint/fuzzers: Add a fuzzer that tests concurrency by Ben Clayton <bclayton@google.com>
  - 2788becd0995b6a2cdea9c81f5f8e7192d40b4b7 Add `element_count_expression` to WGSL parser by dan sinclair <dsinclair@chromium.org>
  - 5d7de871b40058018df33b8d5513f66ea4cd7c2d tint/writer/msl: Remove TODO and old logic by Ben Clayton <bclayton@google.com>
  - 58794ae1189c675f4c7eb5d2021edb583c3f4f53 tint::ProgramBuilder: Simplify variable constructors by Ben Clayton <bclayton@google.com>
GitOrigin-RevId: 158c85dab456c06b5d99d4c509841ad697b399fd
Change-Id: I569ff387e005229c7ed542787f89753c93418abc
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/99860
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 8e9a576..9b2fd35 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -1364,6 +1364,7 @@
       "reader/wgsl/parser_impl_continuing_stmt_test.cc",
       "reader/wgsl/parser_impl_core_lhs_expression_test.cc",
       "reader/wgsl/parser_impl_depth_texture_test.cc",
+      "reader/wgsl/parser_impl_element_count_expression_test.cc",
       "reader/wgsl/parser_impl_enable_directive_test.cc",
       "reader/wgsl/parser_impl_equality_expression_test.cc",
       "reader/wgsl/parser_impl_error_msg_test.cc",
@@ -1385,6 +1386,7 @@
       "reader/wgsl/parser_impl_logical_and_expression_test.cc",
       "reader/wgsl/parser_impl_logical_or_expression_test.cc",
       "reader/wgsl/parser_impl_loop_stmt_test.cc",
+      "reader/wgsl/parser_impl_math_expression_test.cc",
       "reader/wgsl/parser_impl_multiplicative_expression_test.cc",
       "reader/wgsl/parser_impl_param_list_test.cc",
       "reader/wgsl/parser_impl_paren_expression_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 8612975..288735a 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -959,6 +959,7 @@
       reader/wgsl/parser_impl_continuing_stmt_test.cc
       reader/wgsl/parser_impl_core_lhs_expression_test.cc
       reader/wgsl/parser_impl_depth_texture_test.cc
+      reader/wgsl/parser_impl_element_count_expression_test.cc
       reader/wgsl/parser_impl_enable_directive_test.cc
       reader/wgsl/parser_impl_external_texture_test.cc
       reader/wgsl/parser_impl_equality_expression_test.cc
@@ -980,6 +981,7 @@
       reader/wgsl/parser_impl_logical_and_expression_test.cc
       reader/wgsl/parser_impl_logical_or_expression_test.cc
       reader/wgsl/parser_impl_loop_stmt_test.cc
+      reader/wgsl/parser_impl_math_expression_test.cc
       reader/wgsl/parser_impl_multiplicative_expression_test.cc
       reader/wgsl/parser_impl_param_list_test.cc
       reader/wgsl/parser_impl_paren_expression_test.cc
diff --git a/src/tint/ast/override_test.cc b/src/tint/ast/override_test.cc
index 9e7af83..f037601 100644
--- a/src/tint/ast/override_test.cc
+++ b/src/tint/ast/override_test.cc
@@ -22,12 +22,12 @@
 using OverrideTest = TestHelper;
 
 TEST_F(OverrideTest, Identifier_NoId) {
-    auto* o = Override("o", nullptr, Expr(f32(1.0)));
+    auto* o = Override("o", Expr(f32(1.0)));
     EXPECT_EQ(std::string("o"), o->Identifier(Symbols()));
 }
 
 TEST_F(OverrideTest, Identifier_WithId) {
-    auto* o = Override("o", nullptr, Expr(f32(1.0)), utils::Vector{Id(4u)});
+    auto* o = Override("o", Expr(f32(1.0)), Id(4u));
     EXPECT_EQ(std::string("4"), o->Identifier(Symbols()));
 }
 
diff --git a/src/tint/ast/variable_decl_statement_test.cc b/src/tint/ast/variable_decl_statement_test.cc
index 2cd4d4d..5867ae9 100644
--- a/src/tint/ast/variable_decl_statement_test.cc
+++ b/src/tint/ast/variable_decl_statement_test.cc
@@ -23,14 +23,14 @@
 using VariableDeclStatementTest = TestHelper;
 
 TEST_F(VariableDeclStatementTest, Creation) {
-    auto* var = Var("a", ty.f32(), StorageClass::kNone);
+    auto* var = Var("a", ty.f32());
 
     auto* stmt = create<VariableDeclStatement>(var);
     EXPECT_EQ(stmt->variable, var);
 }
 
 TEST_F(VariableDeclStatementTest, Creation_WithSource) {
-    auto* var = Var("a", ty.f32(), StorageClass::kNone);
+    auto* var = Var("a", ty.f32());
 
     auto* stmt = create<VariableDeclStatement>(Source{Source::Location{20, 2}}, var);
     auto src = stmt->source;
@@ -39,7 +39,7 @@
 }
 
 TEST_F(VariableDeclStatementTest, IsVariableDecl) {
-    auto* var = Var("a", ty.f32(), StorageClass::kNone);
+    auto* var = Var("a", ty.f32());
 
     auto* stmt = create<VariableDeclStatement>(var);
     EXPECT_TRUE(stmt->Is<VariableDeclStatement>());
@@ -59,7 +59,7 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<VariableDeclStatement>(b2.Var("a", b2.ty.f32(), StorageClass::kNone));
+            b1.create<VariableDeclStatement>(b2.Var("a", b2.ty.f32()));
         },
         "internal compiler error");
 }
diff --git a/src/tint/ast/variable_test.cc b/src/tint/ast/variable_test.cc
index b226994..12f528b 100644
--- a/src/tint/ast/variable_test.cc
+++ b/src/tint/ast/variable_test.cc
@@ -38,7 +38,7 @@
 
 TEST_F(VariableTest, CreationWithSource) {
     auto* v = Var(Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 5}}}, "i",
-                  ty.f32(), StorageClass::kPrivate, nullptr, utils::Empty);
+                  ty.f32(), StorageClass::kPrivate, utils::Empty);
 
     EXPECT_EQ(v->symbol, Symbol(1, ID()));
     EXPECT_EQ(v->declared_storage_class, StorageClass::kPrivate);
@@ -51,7 +51,7 @@
 
 TEST_F(VariableTest, CreationEmpty) {
     auto* v = Var(Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 7}}}, "a_var",
-                  ty.i32(), StorageClass::kWorkgroup, nullptr, utils::Empty);
+                  ty.i32(), StorageClass::kWorkgroup, utils::Empty);
 
     EXPECT_EQ(v->symbol, Symbol(1, ID()));
     EXPECT_EQ(v->declared_storage_class, StorageClass::kWorkgroup);
@@ -66,7 +66,7 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.Var("", b.ty.i32(), StorageClass::kNone);
+            b.Var("", b.ty.i32());
         },
         "internal compiler error");
 }
@@ -76,7 +76,7 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.Var(b2.Sym("x"), b1.ty.f32(), StorageClass::kNone);
+            b1.Var(b2.Sym("x"), b1.ty.f32());
         },
         "internal compiler error");
 }
@@ -86,18 +86,14 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.Var("x", b1.ty.f32(), StorageClass::kNone, b2.Expr(1.2_f));
+            b1.Var("x", b1.ty.f32(), b2.Expr(1.2_f));
         },
         "internal compiler error");
 }
 
 TEST_F(VariableTest, WithAttributes) {
-    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
-                    utils::Vector{
-                        create<LocationAttribute>(1u),
-                        create<BuiltinAttribute>(BuiltinValue::kPosition),
-                        create<IdAttribute>(1200u),
-                    });
+    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, Location(1u),
+                    Builtin(BuiltinValue::kPosition), Id(1200u));
 
     auto& attributes = var->attributes;
     EXPECT_TRUE(ast::HasAttribute<ast::LocationAttribute>(attributes));
@@ -110,11 +106,7 @@
 }
 
 TEST_F(VariableTest, BindingPoint) {
-    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
-                    utils::Vector{
-                        create<BindingAttribute>(2u),
-                        create<GroupAttribute>(1u),
-                    });
+    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, Binding(2), Group(1));
     EXPECT_TRUE(var->BindingPoint());
     ASSERT_NE(var->BindingPoint().binding, nullptr);
     ASSERT_NE(var->BindingPoint().group, nullptr);
@@ -123,17 +115,14 @@
 }
 
 TEST_F(VariableTest, BindingPointAttributes) {
-    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr, utils::Empty);
+    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, utils::Empty);
     EXPECT_FALSE(var->BindingPoint());
     EXPECT_EQ(var->BindingPoint().group, nullptr);
     EXPECT_EQ(var->BindingPoint().binding, nullptr);
 }
 
 TEST_F(VariableTest, BindingPointMissingGroupAttribute) {
-    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
-                    utils::Vector{
-                        create<BindingAttribute>(2u),
-                    });
+    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, Binding(2));
     EXPECT_FALSE(var->BindingPoint());
     ASSERT_NE(var->BindingPoint().binding, nullptr);
     EXPECT_EQ(var->BindingPoint().binding->value, 2u);
@@ -141,8 +130,7 @@
 }
 
 TEST_F(VariableTest, BindingPointMissingBindingAttribute) {
-    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
-                    utils::Vector{create<GroupAttribute>(1u)});
+    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, Group(1));
     EXPECT_FALSE(var->BindingPoint());
     ASSERT_NE(var->BindingPoint().group, nullptr);
     EXPECT_EQ(var->BindingPoint().group->value, 1u);
diff --git a/src/tint/fuzzers/BUILD.gn b/src/tint/fuzzers/BUILD.gn
index 48d5e66..25e475e 100644
--- a/src/tint/fuzzers/BUILD.gn
+++ b/src/tint/fuzzers/BUILD.gn
@@ -165,6 +165,15 @@
       seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
     }
 
+    fuzzer_test("tint_concurrency_fuzzer") {
+      sources = [ "tint_concurrency_fuzzer.cc" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
+      dict = "dictionary.txt"
+      libfuzzer_options = tint_fuzzer_common_libfuzzer_options
+      seed_corpus = fuzzer_corpus_wgsl_dir
+      seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ]
+    }
+
     fuzzer_test("tint_first_index_offset_fuzzer") {
       sources = [ "tint_first_index_offset_fuzzer.cc" ]
       deps = [ ":tint_fuzzer_common_with_init_src" ]
diff --git a/src/tint/fuzzers/CMakeLists.txt b/src/tint/fuzzers/CMakeLists.txt
index 55c9963..7be1237 100644
--- a/src/tint/fuzzers/CMakeLists.txt
+++ b/src/tint/fuzzers/CMakeLists.txt
@@ -45,6 +45,7 @@
 if (${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_SPV_WRITER})
   add_tint_fuzzer(tint_all_transforms_fuzzer)
   add_tint_fuzzer(tint_binding_remapper_fuzzer)
+  add_tint_fuzzer(tint_concurrency_fuzzer)
   add_tint_fuzzer(tint_first_index_offset_fuzzer)
   add_tint_fuzzer(tint_renamer_fuzzer)
   add_tint_fuzzer(tint_robustness_fuzzer)
diff --git a/src/tint/fuzzers/tint_concurrency_fuzzer.cc b/src/tint/fuzzers/tint_concurrency_fuzzer.cc
new file mode 100644
index 0000000..945cc0e
--- /dev/null
+++ b/src/tint/fuzzers/tint_concurrency_fuzzer.cc
@@ -0,0 +1,127 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <unordered_set>
+
+#include <thread>
+
+#include "src/tint/inspector/inspector.h"
+#include "src/tint/reader/wgsl/parser.h"
+#include "src/tint/utils/hash.h"
+#include "src/tint/writer/flatten_bindings.h"
+#include "src/tint/writer/glsl/generator.h"
+#include "src/tint/writer/hlsl/generator.h"
+#include "src/tint/writer/msl/generator.h"
+#include "src/tint/writer/spirv/generator.h"
+#include "src/tint/writer/wgsl/generator.h"
+
+static constexpr size_t kNumThreads = 32;
+
+[[noreturn]] void TintInternalCompilerErrorReporter(const tint::diag::List& diagnostics) {
+    auto printer = tint::diag::Printer::create(stderr, true);
+    tint::diag::Formatter{}.format(diagnostics, printer.get());
+    __builtin_trap();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
+
+    std::string str(reinterpret_cast<const char*>(data), size);
+    auto file = std::make_unique<tint::Source::File>("test.wgsl", str);
+    auto program = tint::reader::wgsl::Parse(file.get());
+    if (!program.IsValid()) {
+        return 0;
+    }
+
+    tint::inspector::Inspector inspector(&program);
+    auto entry_points = inspector.GetEntryPoints();
+    std::string entry_point = entry_points.empty() ? "" : entry_points.front().name;
+
+    std::array<std::thread, kNumThreads> threads;
+
+    for (size_t thread_idx = 0; thread_idx < kNumThreads; thread_idx++) {
+        auto thread = std::thread([&program, thread_idx, entry_point] {
+            enum class Writer {
+#if TINT_BUILD_GLSL_WRITER
+                kGLSL,
+#endif
+#if TINT_BUILD_HLSL_WRITER
+                kHLSL,
+#endif
+#if TINT_BUILD_MSL_WRITER
+                kMSL,
+#endif
+#if TINT_BUILD_SPV_WRITER
+                kSPIRV,
+#endif
+#if TINT_BUILD_WGSL_WRITER
+                kWGSL,
+#endif
+                kCount
+            };
+            switch (static_cast<Writer>(thread_idx % static_cast<size_t>(Writer::kCount))) {
+#if TINT_BUILD_WGSL_WRITER
+                case Writer::kWGSL: {
+                    tint::writer::wgsl::Generate(&program, {});
+                    break;
+                }
+#endif  // TINT_BUILD_WGSL_WRITER
+
+#if TINT_BUILD_SPV_WRITER
+                case Writer::kSPIRV: {
+                    tint::writer::spirv::Generate(&program, {});
+                    break;
+                }
+#endif  // TINT_BUILD_SPV_WRITER
+
+#if TINT_BUILD_HLSL_WRITER
+                case Writer::kHLSL: {
+                    tint::writer::hlsl::Generate(&program, {});
+                    break;
+                }
+#endif  // TINT_BUILD_HLSL_WRITER
+
+#if TINT_BUILD_GLSL_WRITER
+                case Writer::kGLSL: {
+                    tint::writer::glsl::Generate(&program, {}, entry_point);
+                    break;
+                }
+#endif  // TINT_BUILD_GLSL_WRITER
+
+#if TINT_BUILD_MSL_WRITER
+                case Writer::kMSL: {
+                    // Remap resource numbers to a flat namespace.
+                    if (auto flattened = tint::writer::FlattenBindings(&program)) {
+                        tint::writer::msl::Generate(&flattened.value(), {});
+                    }
+                    break;
+                }
+#endif  // TINT_BUILD_MSL_WRITER
+
+                case Writer::kCount:
+                    break;
+            }
+        });
+        threads[thread_idx] = std::move(thread);
+    }
+
+    for (auto& thread : threads) {
+        thread.join();
+    }
+
+    return 0;
+}
diff --git a/src/tint/inspector/inspector_test.cc b/src/tint/inspector/inspector_test.cc
index cb46833..1d04465 100644
--- a/src/tint/inspector/inspector_test.cc
+++ b/src/tint/inspector/inspector_test.cc
@@ -647,7 +647,7 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, OverrideUnreferenced) {
-    Override("foo", ty.f32(), nullptr);
+    Override("foo", ty.f32());
     MakeEmptyBodyFunction("ep_func", utils::Vector{
                                          Stage(ast::PipelineStage::kCompute),
                                          WorkgroupSize(1_i),
@@ -662,7 +662,7 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, OverrideReferencedByEntryPoint) {
-    Override("foo", ty.f32(), nullptr);
+    Override("foo", ty.f32());
     MakePlainGlobalReferenceBodyFunction("ep_func", "foo", ty.f32(),
                                          utils::Vector{
                                              Stage(ast::PipelineStage::kCompute),
@@ -679,7 +679,7 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, OverrideReferencedByCallee) {
-    Override("foo", ty.f32(), nullptr);
+    Override("foo", ty.f32());
     MakePlainGlobalReferenceBodyFunction("callee_func", "foo", ty.f32(), utils::Empty);
     MakeCallerBodyFunction("ep_func", utils::Vector{std::string("callee_func")},
                            utils::Vector{
@@ -697,14 +697,8 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, OverrideSomeReferenced) {
-    Override("foo", ty.f32(), nullptr,
-             utils::Vector{
-                 Id(1),
-             });
-    Override("bar", ty.f32(), nullptr,
-             utils::Vector{
-                 Id(2),
-             });
+    Override("foo", ty.f32(), Id(1));
+    Override("bar", ty.f32(), Id(2));
     MakePlainGlobalReferenceBodyFunction("callee_func", "foo", ty.f32(), utils::Empty);
     MakeCallerBodyFunction("ep_func", utils::Vector{std::string("callee_func")},
                            utils::Vector{
@@ -723,10 +717,10 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, OverrideTypes) {
-    Override("bool_var", ty.bool_(), nullptr);
-    Override("float_var", ty.f32(), nullptr);
-    Override("u32_var", ty.u32(), nullptr);
-    Override("i32_var", ty.i32(), nullptr);
+    Override("bool_var", ty.bool_());
+    Override("float_var", ty.f32());
+    Override("u32_var", ty.u32());
+    Override("i32_var", ty.i32());
 
     MakePlainGlobalReferenceBodyFunction("bool_func", "bool_var", ty.bool_(), utils::Empty);
     MakePlainGlobalReferenceBodyFunction("float_func", "float_var", ty.f32(), utils::Empty);
@@ -775,7 +769,7 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, OverrideUninitialized) {
-    Override("foo", ty.f32(), nullptr);
+    Override("foo", ty.f32());
     MakePlainGlobalReferenceBodyFunction("ep_func", "foo", ty.f32(),
                                          utils::Vector{
                                              Stage(ast::PipelineStage::kCompute),
@@ -794,11 +788,8 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, OverrideNumericIDSpecified) {
-    Override("foo_no_id", ty.f32(), nullptr);
-    Override("foo_id", ty.f32(), nullptr,
-             utils::Vector{
-                 Id(1234),
-             });
+    Override("foo_no_id", ty.f32());
+    Override("foo_id", ty.f32(), Id(1234));
 
     MakePlainGlobalReferenceBodyFunction("no_id_func", "foo_no_id", ty.f32(), utils::Empty);
     MakePlainGlobalReferenceBodyFunction("id_func", "foo_id", ty.f32(), utils::Empty);
@@ -1234,18 +1225,9 @@
             InterpolationType::kFlat, InterpolationSampling::kNone}));
 
 TEST_F(InspectorGetOverrideDefaultValuesTest, Bool) {
-    Override("foo", ty.bool_(), nullptr,
-             utils::Vector{
-                 Id(1),
-             });
-    Override("bar", ty.bool_(), Expr(true),
-             utils::Vector{
-                 Id(20),
-             });
-    Override("baz", ty.bool_(), Expr(false),
-             utils::Vector{
-                 Id(300),
-             });
+    Override("foo", ty.bool_(), Id(1));
+    Override("bar", ty.bool_(), Expr(true), Id(20));
+    Override("baz", ty.bool_(), Expr(false), Id(300));
 
     Inspector& inspector = Build();
 
@@ -1265,14 +1247,8 @@
 }
 
 TEST_F(InspectorGetOverrideDefaultValuesTest, U32) {
-    Override("foo", ty.u32(), nullptr,
-             utils::Vector{
-                 Id(1),
-             });
-    Override("bar", ty.u32(), Expr(42_u),
-             utils::Vector{
-                 Id(20),
-             });
+    Override("foo", ty.u32(), Id(1));
+    Override("bar", ty.u32(), Expr(42_u), Id(20));
 
     Inspector& inspector = Build();
 
@@ -1288,18 +1264,9 @@
 }
 
 TEST_F(InspectorGetOverrideDefaultValuesTest, I32) {
-    Override("foo", ty.i32(), nullptr,
-             utils::Vector{
-                 Id(1),
-             });
-    Override("bar", ty.i32(), Expr(-42_i),
-             utils::Vector{
-                 Id(20),
-             });
-    Override("baz", ty.i32(), Expr(42_i),
-             utils::Vector{
-                 Id(300),
-             });
+    Override("foo", ty.i32(), Id(1));
+    Override("bar", ty.i32(), Expr(-42_i), Id(20));
+    Override("baz", ty.i32(), Expr(42_i), Id(300));
 
     Inspector& inspector = Build();
 
@@ -1319,22 +1286,10 @@
 }
 
 TEST_F(InspectorGetOverrideDefaultValuesTest, Float) {
-    Override("foo", ty.f32(), nullptr,
-             utils::Vector{
-                 Id(1),
-             });
-    Override("bar", ty.f32(), Expr(0_f),
-             utils::Vector{
-                 Id(20),
-             });
-    Override("baz", ty.f32(), Expr(-10_f),
-             utils::Vector{
-                 Id(300),
-             });
-    Override("x", ty.f32(), Expr(15_f),
-             utils::Vector{
-                 Id(4000),
-             });
+    Override("foo", ty.f32(), Id(1));
+    Override("bar", ty.f32(), Expr(0_f), Id(20));
+    Override("baz", ty.f32(), Expr(-10_f), Id(300));
+    Override("x", ty.f32(), Expr(15_f), Id(4000));
 
     Inspector& inspector = Build();
 
@@ -1358,21 +1313,12 @@
 }
 
 TEST_F(InspectorGetConstantNameToIdMapTest, WithAndWithoutIds) {
-    Override("v1", ty.f32(), nullptr,
-             utils::Vector{
-                 Id(1),
-             });
-    Override("v20", ty.f32(), nullptr,
-             utils::Vector{
-                 Id(20),
-             });
-    Override("v300", ty.f32(), nullptr,
-             utils::Vector{
-                 Id(300),
-             });
-    auto* a = Override("a", ty.f32(), nullptr);
-    auto* b = Override("b", ty.f32(), nullptr);
-    auto* c = Override("c", ty.f32(), nullptr);
+    Override("v1", ty.f32(), Id(1));
+    Override("v20", ty.f32(), Id(20));
+    Override("v300", ty.f32(), Id(300));
+    auto* a = Override("a", ty.f32());
+    auto* b = Override("b", ty.f32());
+    auto* c = Override("c", ty.f32());
 
     Inspector& inspector = Build();
 
@@ -1416,9 +1362,9 @@
     AddStorageBuffer("rosb_var", ty.i32(), ast::Access::kRead, 1, 1);
     Func("ep_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("ub", nullptr, Expr("ub_var"))),
-             Decl(Let("sb", nullptr, Expr("sb_var"))),
-             Decl(Let("rosb", nullptr, Expr("rosb_var"))),
+             Decl(Let("ub", Expr("ub_var"))),
+             Decl(Let("sb", Expr("sb_var"))),
+             Decl(Let("rosb", Expr("rosb_var"))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kCompute),
@@ -1474,7 +1420,7 @@
     AddUniformBuffer("ub_var", ty.vec3<f32>(), 0, 0);
     Func("ep_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("ub", nullptr, Expr("ub_var"))),
+             Decl(Let("ub", Expr("ub_var"))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kCompute),
@@ -1493,7 +1439,7 @@
     AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0);
     Func("ep_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("ub", nullptr, Expr("ub_var"))),
+             Decl(Let("ub", Expr("ub_var"))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kCompute),
diff --git a/src/tint/inspector/test_inspector_builder.cc b/src/tint/inspector/test_inspector_builder.cc
index 5b89d7c..3d67876 100644
--- a/src/tint/inspector/test_inspector_builder.cc
+++ b/src/tint/inspector/test_inspector_builder.cc
@@ -126,11 +126,7 @@
                                         const ast::Type* type,
                                         uint32_t group,
                                         uint32_t binding) {
-    GlobalVar(name, type, ast::StorageClass::kUniform,
-              utils::Vector{
-                  create<ast::BindingAttribute>(binding),
-                  create<ast::GroupAttribute>(group),
-              });
+    GlobalVar(name, type, ast::StorageClass::kUniform, Binding(binding), Group(group));
 }
 
 void InspectorBuilder::AddWorkgroupStorage(const std::string& name, const ast::Type* type) {
@@ -142,11 +138,7 @@
                                         ast::Access access,
                                         uint32_t group,
                                         uint32_t binding) {
-    GlobalVar(name, type, ast::StorageClass::kStorage, access,
-              utils::Vector{
-                  create<ast::BindingAttribute>(binding),
-                  create<ast::GroupAttribute>(group),
-              });
+    GlobalVar(name, type, ast::StorageClass::kStorage, access, Binding(binding), Group(group));
 }
 
 void InspectorBuilder::MakeStructVariableReferenceBodyFunction(
@@ -178,32 +170,20 @@
 }
 
 void InspectorBuilder::AddSampler(const std::string& name, uint32_t group, uint32_t binding) {
-    GlobalVar(name, sampler_type(),
-              utils::Vector{
-                  create<ast::BindingAttribute>(binding),
-                  create<ast::GroupAttribute>(group),
-              });
+    GlobalVar(name, sampler_type(), Binding(binding), Group(group));
 }
 
 void InspectorBuilder::AddComparisonSampler(const std::string& name,
                                             uint32_t group,
                                             uint32_t binding) {
-    GlobalVar(name, comparison_sampler_type(),
-              utils::Vector{
-                  create<ast::BindingAttribute>(binding),
-                  create<ast::GroupAttribute>(group),
-              });
+    GlobalVar(name, comparison_sampler_type(), Binding(binding), Group(group));
 }
 
 void InspectorBuilder::AddResource(const std::string& name,
                                    const ast::Type* type,
                                    uint32_t group,
                                    uint32_t binding) {
-    GlobalVar(name, type,
-              utils::Vector{
-                  create<ast::BindingAttribute>(binding),
-                  create<ast::GroupAttribute>(group),
-              });
+    GlobalVar(name, type, Binding(binding), Group(group));
 }
 
 void InspectorBuilder::AddGlobalVariable(const std::string& name, const ast::Type* type) {
@@ -305,11 +285,7 @@
                                          const ast::Type* type,
                                          uint32_t group,
                                          uint32_t binding) {
-    GlobalVar(name, type,
-              utils::Vector{
-                  create<ast::BindingAttribute>(binding),
-                  create<ast::GroupAttribute>(group),
-              });
+    GlobalVar(name, type, Binding(binding), Group(group));
 }
 
 const ast::Function* InspectorBuilder::MakeStorageTextureBodyFunction(
diff --git a/src/tint/program_builder.cc b/src/tint/program_builder.cc
index 1507cd3..c6cff0f 100644
--- a/src/tint/program_builder.cc
+++ b/src/tint/program_builder.cc
@@ -26,7 +26,10 @@
 
 namespace tint {
 
-ProgramBuilder::VarOptionals::~VarOptionals() = default;
+ProgramBuilder::VarOptions::~VarOptions() = default;
+ProgramBuilder::LetOptions::~LetOptions() = default;
+ProgramBuilder::ConstOptions::~ConstOptions() = default;
+ProgramBuilder::OverrideOptions::~OverrideOptions() = default;
 
 ProgramBuilder::ProgramBuilder()
     : id_(ProgramID::New()),
@@ -118,7 +121,7 @@
 
 const ast::Statement* ProgramBuilder::WrapInStatement(const ast::Expression* expr) {
     // Create a temporary variable of inferred type from expr.
-    return Decl(Let(symbols_.New(), nullptr, expr));
+    return Decl(Let(symbols_.New(), expr));
 }
 
 const ast::VariableDeclStatement* ProgramBuilder::WrapInStatement(const ast::Variable* v) {
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index 25766e6..42e428a 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -162,32 +162,98 @@
     using DisableIfVectorLike = traits::EnableIf<
         !detail::IsVectorLike<traits::Decay<traits::NthTypeOf<0, TYPES..., void>>>::value>;
 
-    /// VarOptionals is a helper for accepting a number of optional, extra
-    /// arguments for Var() and GlobalVar().
-    struct VarOptionals {
+    /// VarOptions is a helper for accepting an arbitrary number of order independent options for
+    /// constructing an ast::Var.
+    struct VarOptions {
         template <typename... ARGS>
-        explicit VarOptionals(ARGS&&... args) {
-            Apply(std::forward<ARGS>(args)...);
+        explicit VarOptions(ARGS&&... args) {
+            (Set(std::forward<ARGS>(args)), ...);
         }
-        ~VarOptionals();
+        ~VarOptions();
 
+        const ast::Type* type = nullptr;
         ast::StorageClass storage = ast::StorageClass::kNone;
         ast::Access access = ast::Access::kUndefined;
         const ast::Expression* constructor = nullptr;
         utils::Vector<const ast::Attribute*, 4> attributes;
 
       private:
+        void Set(const ast::Type* t) { type = t; }
         void Set(ast::StorageClass sc) { storage = sc; }
         void Set(ast::Access ac) { access = ac; }
         void Set(const ast::Expression* c) { constructor = c; }
         void Set(utils::VectorRef<const ast::Attribute*> l) { attributes = std::move(l); }
+        void Set(const ast::Attribute* a) { attributes.Push(a); }
+    };
 
-        template <typename FIRST, typename... ARGS>
-        void Apply(FIRST&& first, ARGS&&... args) {
-            Set(std::forward<FIRST>(first));
-            Apply(std::forward<ARGS>(args)...);
+    /// LetOptions is a helper for accepting an arbitrary number of order independent options for
+    /// constructing an ast::Let.
+    struct LetOptions {
+        template <typename... ARGS>
+        explicit LetOptions(ARGS&&... args) {
+            static constexpr bool has_init =
+                (traits::IsTypeOrDerived<std::remove_pointer_t<std::remove_reference_t<ARGS>>,
+                                         ast::Expression> ||
+                 ...);
+            static_assert(has_init, "Let() must be constructed with an initializer expression");
+            (Set(std::forward<ARGS>(args)), ...);
         }
-        void Apply() {}
+        ~LetOptions();
+
+        const ast::Type* type = nullptr;
+        const ast::Expression* constructor = nullptr;
+        utils::Vector<const ast::Attribute*, 4> attributes;
+
+      private:
+        void Set(const ast::Type* t) { type = t; }
+        void Set(const ast::Expression* c) { constructor = c; }
+        void Set(utils::VectorRef<const ast::Attribute*> l) { attributes = std::move(l); }
+        void Set(const ast::Attribute* a) { attributes.Push(a); }
+    };
+
+    /// ConstOptions is a helper for accepting an arbitrary number of order independent options for
+    /// constructing an ast::Const.
+    struct ConstOptions {
+        template <typename... ARGS>
+        explicit ConstOptions(ARGS&&... args) {
+            static constexpr bool has_init =
+                (traits::IsTypeOrDerived<std::remove_pointer_t<std::remove_reference_t<ARGS>>,
+                                         ast::Expression> ||
+                 ...);
+            static_assert(has_init, "Const() must be constructed with an initializer expression");
+            (Set(std::forward<ARGS>(args)), ...);
+        }
+        ~ConstOptions();
+
+        const ast::Type* type = nullptr;
+        const ast::Expression* constructor = nullptr;
+        utils::Vector<const ast::Attribute*, 4> attributes;
+
+      private:
+        void Set(const ast::Type* t) { type = t; }
+        void Set(const ast::Expression* c) { constructor = c; }
+        void Set(utils::VectorRef<const ast::Attribute*> l) { attributes = std::move(l); }
+        void Set(const ast::Attribute* a) { attributes.Push(a); }
+    };
+
+    /// OverrideOptions is a helper for accepting an arbitrary number of order independent options
+    /// for constructing an ast::Override.
+    struct OverrideOptions {
+        template <typename... ARGS>
+        explicit OverrideOptions(ARGS&&... args) {
+            (Set(std::forward<ARGS>(args)), ...);
+        }
+        ~OverrideOptions();
+
+        const ast::Type* type = nullptr;
+        const ast::Expression* constructor = nullptr;
+        utils::Vector<const ast::Attribute*, 4> attributes;
+
+      private:
+        void Set(const ast::Type* t) { type = t; }
+        void Set(const ast::Expression* c) { constructor = c; }
+        void Set(utils::VectorRef<const ast::Attribute*> l) { attributes = std::move(l); }
+        void Set(const ast::Attribute* a) { attributes.Push(a); }
     };
 
   public:
@@ -1582,102 +1648,101 @@
     }
 
     /// @param name the variable name
-    /// @param type the variable type
-    /// @param optional the optional variable settings.
+    /// @param options the extra options passed to the ast::Var constructor
     /// Can be any of the following, in any order:
+    ///   * ast::Type*          - specifies the variable type
     ///   * ast::StorageClass   - specifies the variable storage class
     ///   * ast::Access         - specifies the variable's access control
     ///   * ast::Expression*    - specifies the variable's initializer expression
-    ///   * ast::AttributeList - specifies the variable's attributes
-    /// Note that repeated arguments of the same type will use the last argument's
-    /// value.
+    ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
+    /// Note that non-repeatable arguments of the same type will use the last argument's value.
     /// @returns a `ast::Var` with the given name, type and additional
     /// options
-    template <typename NAME, typename... OPTIONAL>
-    const ast::Var* Var(NAME&& name, const ast::Type* type, OPTIONAL&&... optional) {
-        VarOptionals opts(std::forward<OPTIONAL>(optional)...);
-        return create<ast::Var>(Sym(std::forward<NAME>(name)), type, opts.storage, opts.access,
+    template <typename NAME, typename... OPTIONS, typename = DisableIfSource<NAME>>
+    const ast::Var* Var(NAME&& name, OPTIONS&&... options) {
+        VarOptions opts(std::forward<OPTIONS>(options)...);
+        return create<ast::Var>(Sym(std::forward<NAME>(name)), opts.type, opts.storage, opts.access,
                                 opts.constructor, std::move(opts.attributes));
     }
 
     /// @param source the variable source
     /// @param name the variable name
-    /// @param type the variable type
-    /// @param optional the optional variable settings.
+    /// @param options the extra options passed to the ast::Var constructor
     /// Can be any of the following, in any order:
+    ///   * ast::Type*          - specifies the variable type
     ///   * ast::StorageClass   - specifies the variable storage class
     ///   * ast::Access         - specifies the variable's access control
     ///   * ast::Expression*    - specifies the variable's initializer expression
-    ///   * ast::AttributeList - specifies the variable's attributes
-    /// Note that repeated arguments of the same type will use the last argument's
-    /// value.
+    ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
+    /// Note that non-repeatable arguments of the same type will use the last argument's value.
     /// @returns a `ast::Var` with the given name, storage and type
-    template <typename NAME, typename... OPTIONAL>
-    const ast::Var* Var(const Source& source,
-                        NAME&& name,
-                        const ast::Type* type,
-                        OPTIONAL&&... optional) {
-        VarOptionals opts(std::forward<OPTIONAL>(optional)...);
-        return create<ast::Var>(source, Sym(std::forward<NAME>(name)), type, opts.storage,
+    template <typename NAME, typename... OPTIONS>
+    const ast::Var* Var(const Source& source, NAME&& name, OPTIONS&&... options) {
+        VarOptions opts(std::forward<OPTIONS>(options)...);
+        return create<ast::Var>(source, Sym(std::forward<NAME>(name)), opts.type, opts.storage,
                                 opts.access, opts.constructor, std::move(opts.attributes));
     }
 
     /// @param name the variable name
-    /// @param type the variable type
-    /// @param constructor constructor expression
-    /// @param attributes optional variable attributes
-    /// @returns an `ast::Const` with the given name and type
-    template <typename NAME>
-    const ast::Const* Const(NAME&& name,
-                            const ast::Type* type,
-                            const ast::Expression* constructor,
-                            utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
-        return create<ast::Const>(Sym(std::forward<NAME>(name)), type, constructor, attributes);
+    /// @param options the extra options passed to the ast::Var constructor
+    /// Can be any of the following, in any order:
+    ///   * ast::Expression*    - specifies the variable's initializer expression (required)
+    ///   * ast::Type*          - specifies the variable type
+    ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
+    /// Note that non-repeatable arguments of the same type will use the last argument's value.
+    /// @returns an `ast::Const` with the given name, type and additional options
+    template <typename NAME, typename... OPTIONS, typename = DisableIfSource<NAME>>
+    const ast::Const* Const(NAME&& name, OPTIONS&&... options) {
+        ConstOptions opts(std::forward<OPTIONS>(options)...);
+        return create<ast::Const>(Sym(std::forward<NAME>(name)), opts.type, opts.constructor,
+                                  std::move(opts.attributes));
     }
 
     /// @param source the variable source
     /// @param name the variable name
-    /// @param type the variable type
-    /// @param constructor constructor expression
-    /// @param attributes optional variable attributes
-    /// @returns an `ast::Const` with the given name and type
-    template <typename NAME>
-    const ast::Const* Const(const Source& source,
-                            NAME&& name,
-                            const ast::Type* type,
-                            const ast::Expression* constructor,
-                            utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
-        return create<ast::Const>(source, Sym(std::forward<NAME>(name)), type, constructor,
-                                  attributes);
+    /// @param options the extra options passed to the ast::Var constructor
+    /// Can be any of the following, in any order:
+    ///   * ast::Expression*    - specifies the variable's initializer expression (required)
+    ///   * ast::Type*          - specifies the variable type
+    ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
+    /// Note that non-repeatable arguments of the same type will use the last argument's value.
+    /// @returns an `ast::Const` with the given name, type and additional options
+    template <typename NAME, typename... OPTIONS>
+    const ast::Const* Const(const Source& source, NAME&& name, OPTIONS&&... options) {
+        ConstOptions opts(std::forward<OPTIONS>(options)...);
+        return create<ast::Const>(source, Sym(std::forward<NAME>(name)), opts.type,
+                                  opts.constructor, std::move(opts.attributes));
     }
 
     /// @param name the variable name
-    /// @param type the variable type
-    /// @param constructor constructor expression
-    /// @param attributes optional variable attributes
-    /// @returns an `ast::Let` with the given name and type
-    template <typename NAME>
-    const ast::Let* Let(NAME&& name,
-                        const ast::Type* type,
-                        const ast::Expression* constructor,
-                        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
-        return create<ast::Let>(Sym(std::forward<NAME>(name)), type, constructor, attributes);
+    /// @param options the extra options passed to the ast::Var constructor
+    /// Can be any of the following, in any order:
+    ///   * ast::Expression*    - specifies the variable's initializer expression (required)
+    ///   * ast::Type*          - specifies the variable type
+    ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
+    /// Note that non-repeatable arguments of the same type will use the last argument's value.
+    /// @returns an `ast::Let` with the given name, type and additional options
+    template <typename NAME, typename... OPTIONS, typename = DisableIfSource<NAME>>
+    const ast::Let* Let(NAME&& name, OPTIONS&&... options) {
+        LetOptions opts(std::forward<OPTIONS>(options)...);
+        return create<ast::Let>(Sym(std::forward<NAME>(name)), opts.type, opts.constructor,
+                                std::move(opts.attributes));
     }
 
     /// @param source the variable source
     /// @param name the variable name
-    /// @param type the variable type
-    /// @param constructor constructor expression
-    /// @param attributes optional variable attributes
-    /// @returns an `ast::Let` with the given name and type
-    template <typename NAME>
-    const ast::Let* Let(const Source& source,
-                        NAME&& name,
-                        const ast::Type* type,
-                        const ast::Expression* constructor,
-                        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
-        return create<ast::Let>(source, Sym(std::forward<NAME>(name)), type, constructor,
-                                attributes);
+    /// @param options the extra options passed to the ast::Var constructor
+    /// Can be any of the following, in any order:
+    ///   * ast::Expression*    - specifies the variable's initializer expression (required)
+    ///   * ast::Type*          - specifies the variable type
+    ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
+    /// Note that non-repeatable arguments of the same type will use the last argument's value.
+    /// @returns an `ast::Let` with the given name, type and additional options
+    template <typename NAME, typename... OPTIONS>
+    const ast::Let* Let(const Source& source, NAME&& name, OPTIONS&&... options) {
+        LetOptions opts(std::forward<OPTIONS>(options)...);
+        return create<ast::Let>(source, Sym(std::forward<NAME>(name)), opts.type, opts.constructor,
+                                std::move(opts.attributes));
     }
 
     /// @param name the parameter name
@@ -1705,122 +1770,110 @@
     }
 
     /// @param name the variable name
-    /// @param type the variable type
-    /// @param optional the optional variable settings.
+    /// @param options the extra options passed to the ast::Var constructor
     /// Can be any of the following, in any order:
+    ///   * ast::Type*          - specifies the variable type
     ///   * ast::StorageClass   - specifies the variable storage class
     ///   * ast::Access         - specifies the variable's access control
     ///   * ast::Expression*    - specifies the variable's initializer expression
-    ///   * ast::AttributeList - specifies the variable's attributes
-    /// Note that repeated arguments of the same type will use the last argument's
-    /// value.
+    ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
+    /// Note that non-repeatable arguments of the same type will use the last argument's value.
     /// @returns a new `ast::Var`, which is automatically registered as a global variable with the
     /// ast::Module.
-    template <typename NAME, typename... OPTIONAL, typename = DisableIfSource<NAME>>
-    const ast::Var* GlobalVar(NAME&& name, const ast::Type* type, OPTIONAL&&... optional) {
-        auto* var = Var(std::forward<NAME>(name), type, std::forward<OPTIONAL>(optional)...);
-        AST().AddGlobalVariable(var);
-        return var;
+    template <typename NAME, typename... OPTIONS, typename = DisableIfSource<NAME>>
+    const ast::Var* GlobalVar(NAME&& name, OPTIONS&&... options) {
+        auto* variable = Var(std::forward<NAME>(name), std::forward<OPTIONS>(options)...);
+        AST().AddGlobalVariable(variable);
+        return variable;
     }
 
     /// @param source the variable source
     /// @param name the variable name
-    /// @param type the variable type
-    /// @param optional the optional variable settings.
+    /// @param options the extra options passed to the ast::Var constructor
     /// Can be any of the following, in any order:
+    ///   * ast::Type*          - specifies the variable type
     ///   * ast::StorageClass   - specifies the variable storage class
     ///   * ast::Access         - specifies the variable's access control
     ///   * ast::Expression*    - specifies the variable's initializer expression
-    ///   * ast::AttributeList - specifies the variable's attributes
-    /// Note that repeated arguments of the same type will use the last argument's
-    /// value.
+    ///   * ast::Attribute*    - specifies the variable's attributes (repeatable, or vector)
+    /// Note that non-repeatable arguments of the same type will use the last argument's value.
     /// @returns a new `ast::Var`, which is automatically registered as a global variable with the
     /// ast::Module.
-    template <typename NAME, typename... OPTIONAL>
-    const ast::Var* GlobalVar(const Source& source,
-                              NAME&& name,
-                              const ast::Type* type,
-                              OPTIONAL&&... optional) {
-        auto* var =
-            Var(source, std::forward<NAME>(name), type, std::forward<OPTIONAL>(optional)...);
-        AST().AddGlobalVariable(var);
-        return var;
+    template <typename NAME, typename... OPTIONS>
+    const ast::Var* GlobalVar(const Source& source, NAME&& name, OPTIONS&&... options) {
+        auto* variable = Var(source, std::forward<NAME>(name), std::forward<OPTIONS>(options)...);
+        AST().AddGlobalVariable(variable);
+        return variable;
     }
 
     /// @param name the variable name
-    /// @param type the variable type
-    /// @param constructor constructor expression
-    /// @param attributes optional variable attributes
-    /// @returns an `ast::Const` constructed by calling Const() with the arguments of `args`, which
-    /// is automatically registered as a global variable with the ast::Module.
-    template <typename NAME>
-    const ast::Const* GlobalConst(
-        NAME&& name,
-        const ast::Type* type,
-        const ast::Expression* constructor,
-        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
-        auto* var = Const(std::forward<NAME>(name), type, constructor, std::move(attributes));
-        AST().AddGlobalVariable(var);
-        return var;
+    /// @param options the extra options passed to the ast::Const constructor
+    /// Can be any of the following, in any order:
+    ///   * ast::Expression*    - specifies the variable's initializer expression (required)
+    ///   * ast::Type*          - specifies the variable type
+    ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
+    /// Note that non-repeatable arguments of the same type will use the last argument's value.
+    /// @returns an `ast::Const` with the given name, type and additional options, which is
+    /// automatically registered as a global variable with the ast::Module.
+    template <typename NAME, typename... OPTIONS, typename = DisableIfSource<NAME>>
+    const ast::Const* GlobalConst(NAME&& name, OPTIONS&&... options) {
+        auto* variable = Const(std::forward<NAME>(name), std::forward<OPTIONS>(options)...);
+        AST().AddGlobalVariable(variable);
+        return variable;
     }
 
     /// @param source the variable source
     /// @param name the variable name
-    /// @param type the variable type
-    /// @param constructor constructor expression
-    /// @param attributes optional variable attributes
-    /// @returns a const `ast::Const` constructed by calling Var() with the
-    /// arguments of `args`, which is automatically registered as a global
-    /// variable with the ast::Module.
-    template <typename NAME>
-    const ast::Const* GlobalConst(
-        const Source& source,
-        NAME&& name,
-        const ast::Type* type,
-        const ast::Expression* constructor,
-        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
-        auto* var =
-            Const(source, std::forward<NAME>(name), type, constructor, std::move(attributes));
-        AST().AddGlobalVariable(var);
-        return var;
+    /// @param options the extra options passed to the ast::Const constructor
+    /// Can be any of the following, in any order:
+    ///   * ast::Expression*    - specifies the variable's initializer expression (required)
+    ///   * ast::Type*          - specifies the variable type
+    ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
+    /// Note that non-repeatable arguments of the same type will use the last argument's value.
+    /// @returns an `ast::Const` with the given name, type and additional options, which is
+    /// automatically registered as a global variable with the ast::Module.
+    template <typename NAME, typename... OPTIONS>
+    const ast::Const* GlobalConst(const Source& source, NAME&& name, OPTIONS&&... options) {
+        auto* variable = Const(source, std::forward<NAME>(name), std::forward<OPTIONS>(options)...);
+        AST().AddGlobalVariable(variable);
+        return variable;
     }
 
     /// @param name the variable name
-    /// @param type the variable type
-    /// @param constructor optional constructor expression
-    /// @param attributes optional variable attributes
-    /// @returns an `ast::Override` which is automatically registered as a global variable with the
-    /// ast::Module.
-    template <typename NAME>
-    const ast::Override* Override(
-        NAME&& name,
-        const ast::Type* type,
-        const ast::Expression* constructor,
-        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
-        auto* var = create<ast::Override>(source_, Sym(std::forward<NAME>(name)), type, constructor,
-                                          std::move(attributes));
-        AST().AddGlobalVariable(var);
-        return var;
+    /// @param options the extra options passed to the ast::Override constructor
+    /// Can be any of the following, in any order:
+    ///   * ast::Expression*    - specifies the variable's initializer expression (required)
+    ///   * ast::Type*          - specifies the variable type
+    ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
+    /// Note that non-repeatable arguments of the same type will use the last argument's value.
+    /// @returns an `ast::Override` with the given name, type and additional options, which is
+    /// automatically registered as a global variable with the ast::Module.
+    template <typename NAME, typename... OPTIONS, typename = DisableIfSource<NAME>>
+    const ast::Override* Override(NAME&& name, OPTIONS&&... options) {
+        OverrideOptions opts(std::forward<OPTIONS>(options)...);
+        auto* variable = create<ast::Override>(Sym(std::forward<NAME>(name)), opts.type,
+                                               opts.constructor, std::move(opts.attributes));
+        AST().AddGlobalVariable(variable);
+        return variable;
     }
 
     /// @param source the variable source
     /// @param name the variable name
-    /// @param type the variable type
-    /// @param constructor constructor expression
-    /// @param attributes optional variable attributes
-    /// @returns an `ast::Override` constructed with the arguments of `args`, which is automatically
-    /// registered as a global variable with the ast::Module.
-    template <typename NAME>
-    const ast::Override* Override(
-        const Source& source,
-        NAME&& name,
-        const ast::Type* type,
-        const ast::Expression* constructor,
-        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
-        auto* var = create<ast::Override>(source, Sym(std::forward<NAME>(name)), type, constructor,
-                                          std::move(attributes));
-        AST().AddGlobalVariable(var);
-        return var;
+    /// @param options the extra options passed to the ast::Override constructor
+    /// Can be any of the following, in any order:
+    ///   * ast::Expression*    - specifies the variable's initializer expression (required)
+    ///   * ast::Type*          - specifies the variable type
+    ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
+    /// Note that non-repeatable arguments of the same type will use the last argument's value.
+    /// @returns an `ast::Override` with the given name, type and additional options, which is
+    /// automatically registered as a global variable with the ast::Module.
+    template <typename NAME, typename... OPTIONS>
+    const ast::Override* Override(const Source& source, NAME&& name, OPTIONS&&... options) {
+        OverrideOptions opts(std::forward<OPTIONS>(options)...);
+        auto* variable = create<ast::Override>(source, Sym(std::forward<NAME>(name)), opts.type,
+                                               opts.constructor, std::move(opts.attributes));
+        AST().AddGlobalVariable(variable);
+        return variable;
     }
 
     /// @param source the source information
@@ -2246,15 +2299,6 @@
         return create<ast::BindingAttribute>(value);
     }
 
-    /// Convenience function to create both a ast::GroupAttribute and
-    /// ast::BindingAttribute
-    /// @param group the group index
-    /// @param binding the binding index
-    /// @returns a attribute list with both the group and binding attributes
-    utils::Vector<const ast::Attribute*, 2> GroupAndBinding(uint32_t group, uint32_t binding) {
-        return {Group(group), Binding(binding)};
-    }
-
     /// Creates an ast::Function and registers it with the ast::Module.
     /// @param source the source information
     /// @param name the function name
diff --git a/src/tint/reader/wgsl/lexer_test.cc b/src/tint/reader/wgsl/lexer_test.cc
index dfd1b85..3268eaf 100644
--- a/src/tint/reader/wgsl/lexer_test.cc
+++ b/src/tint/reader/wgsl/lexer_test.cc
@@ -1028,7 +1028,6 @@
                                          TokenData{"%", Token::Type::kMod},
                                          TokenData{"!=", Token::Type::kNotEqual},
                                          TokenData{"-", Token::Type::kMinus},
-                                         TokenData{"--", Token::Type::kMinusMinus},
                                          TokenData{".", Token::Type::kPeriod},
                                          TokenData{"+", Token::Type::kPlus},
                                          TokenData{"++", Token::Type::kPlusPlus},
@@ -1088,6 +1087,7 @@
                          SplittablePunctuationTest,
                          testing::Values(TokenData{"&&", Token::Type::kAndAnd},
                                          TokenData{">=", Token::Type::kGreaterThanEqual},
+                                         TokenData{"--", Token::Type::kMinusMinus},
                                          TokenData{">>", Token::Type::kShiftRight}));
 
 using KeywordTest = testing::TestWithParam<TokenData>;
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index e4e6812..cf684d3 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -1066,7 +1066,7 @@
 //   | VEC3 LESS_THAN type_decl GREATER_THAN
 //   | VEC4 LESS_THAN type_decl GREATER_THAN
 //   | PTR LESS_THAN storage_class, type_decl (COMMA access_mode)? GREATER_THAN
-//   | array_attribute_list* ARRAY LESS_THAN type_decl COMMA INT_LITERAL GREATER_THAN
+//   | array_attribute_list* ARRAY LESS_THAN type_decl COMMA element_count_expression GREATER_THAN
 //   | array_attribute_list* ARRAY LESS_THAN type_decl GREATER_THAN
 //   | MAT2x2 LESS_THAN type_decl GREATER_THAN
 //   | MAT2x3 LESS_THAN type_decl GREATER_THAN
@@ -1244,10 +1244,11 @@
             return TypeAndSize{type.value, nullptr};
         }
 
-        auto size = primary_expression();
+        auto size = element_count_expression();
         if (size.errored) {
             return Failure::kErrored;
-        } else if (!size.matched) {
+        }
+        if (!size.matched) {
             return add_error(peek(), "expected array size expression");
         }
 
@@ -1532,7 +1533,7 @@
 }
 
 // param
-//   : attribute_list* ident_with_type_decl
+//   : attribute_list* ident COLON type_decl
 Expect<ast::Parameter*> ParserImpl::expect_param() {
     auto attrs = attribute_list();
 
@@ -2562,20 +2563,6 @@
     return Failure::kErrored;
 }
 
-// singular_expression
-//   : primary_expression postfix_expr
-Maybe<const ast::Expression*> ParserImpl::singular_expression() {
-    auto prefix = primary_expression();
-    if (prefix.errored) {
-        return Failure::kErrored;
-    }
-    if (!prefix.matched) {
-        return Failure::kNoMatch;
-    }
-
-    return postfix_expression(prefix.value);
-}
-
 // argument_expression_list
 //   : PAREN_LEFT ((expression COMMA)* expression COMMA?)? PAREN_RIGHT
 Expect<ParserImpl::ExpressionList> ParserImpl::expect_argument_expression_list(
@@ -2657,6 +2644,284 @@
     return Failure::kErrored;
 }
 
+// multiplicative_operator
+//   : FORWARD_SLASH
+//   | MODULO
+//   | STAR
+Maybe<ast::BinaryOp> ParserImpl::multiplicative_operator() {
+    if (match(Token::Type::kForwardSlash)) {
+        return ast::BinaryOp::kDivide;
+    }
+    if (match(Token::Type::kMod)) {
+        return ast::BinaryOp::kModulo;
+    }
+    if (match(Token::Type::kStar)) {
+        return ast::BinaryOp::kMultiply;
+    }
+
+    return Failure::kNoMatch;
+}
+
+// multiplicative_expression.post.unary_expression
+//   : (multiplicative_operator unary_expression)*
+Expect<const ast::Expression*> ParserImpl::expect_multiplicative_expression_post_unary_expression(
+    const ast::Expression* lhs) {
+    while (continue_parsing()) {
+        auto& t = peek();
+
+        auto op = multiplicative_operator();
+        if (op.errored) {
+            return Failure::kErrored;
+        }
+        if (!op.matched) {
+            return lhs;
+        }
+
+        auto rhs = unary_expression();
+        if (rhs.errored) {
+            return Failure::kErrored;
+        }
+        if (!rhs.matched) {
+            return add_error(peek(), std::string("unable to parse right side of ") +
+                                         std::string(t.to_name()) + " expression");
+        }
+
+        lhs = create<ast::BinaryExpression>(t.source(), op.value, lhs, rhs.value);
+    }
+    return Failure::kErrored;
+}
+
+// additive_operator
+//   : MINUS
+//   | PLUS
+//
+// Note, this also splits a `--` token. This is currently safe as the only way to get into
+// here is through additive expression and rules for where `--` are allowed are very restrictive.
+Maybe<ast::BinaryOp> ParserImpl::additive_operator() {
+    if (match(Token::Type::kPlus)) {
+        return ast::BinaryOp::kAdd;
+    }
+
+    auto& t = peek();
+    if (t.Is(Token::Type::kMinusMinus)) {
+        next();
+        split_token(Token::Type::kMinus, Token::Type::kMinus);
+    } else if (t.Is(Token::Type::kMinus)) {
+        next();
+    } else {
+        return Failure::kNoMatch;
+    }
+
+    return ast::BinaryOp::kSubtract;
+}
+
+// additive_expression.pos.unary_expression
+//   : (additive_operator unary_expression expect_multiplicative_expression.post.unary_expression)*
+//
+// This is `( additive_operator unary_expression ( multiplicative_operator unary_expression )* )*`
+// split apart.
+Expect<const ast::Expression*> ParserImpl::expect_additive_expression_post_unary_expression(
+    const ast::Expression* lhs) {
+    while (continue_parsing()) {
+        auto& t = peek();
+
+        auto op = additive_operator();
+        if (op.errored) {
+            return Failure::kErrored;
+        }
+        if (!op.matched) {
+            return lhs;
+        }
+
+        auto unary = unary_expression();
+        if (unary.errored) {
+            return Failure::kErrored;
+        }
+        if (!unary.matched) {
+            return add_error(peek(), std::string("unable to parse right side of ") +
+                                         std::string(t.to_name()) + " expression");
+        }
+
+        // The multiplicative binds tigher, so pass the unary into that and build that expression
+        // before creating the additve expression.
+        auto rhs = expect_multiplicative_expression_post_unary_expression(unary.value);
+        if (rhs.errored) {
+            return Failure::kErrored;
+        }
+
+        lhs = create<ast::BinaryExpression>(t.source(), op.value, lhs, rhs.value);
+    }
+    return Failure::kErrored;
+}
+
+// math_expression.post.unary_expression
+//   : multiplicative_expression.post.unary_expression additive_expression.post.unary_expression
+//
+// This is `( multiplicative_operator unary_expression )* ( additive_operator unary_expression (
+// multiplicative_operator unary_expression )* )*` split apart.
+Expect<const ast::Expression*> ParserImpl::expect_math_expression_post_unary_expression(
+    const ast::Expression* lhs) {
+    auto rhs = expect_multiplicative_expression_post_unary_expression(lhs);
+    if (rhs.errored) {
+        return Failure::kErrored;
+    }
+
+    return expect_additive_expression_post_unary_expression(rhs.value);
+}
+
+// element_count_expression
+//   : unary_expression math_expression.post.unary_expression
+//   | unary_expression bitwise_expression.post.unary_expression
+//
+// Note, this moves the `( multiplicative_operator unary_expression )* ( additive_operator
+// unary_expression ( multiplicative_operator unary_expression )* )*` expression for the first
+// branch out into helper methods.
+Maybe<const ast::Expression*> ParserImpl::element_count_expression() {
+    auto lhs = unary_expression();
+    if (lhs.errored) {
+        return Failure::kErrored;
+    }
+    if (!lhs.matched) {
+        return Failure::kNoMatch;
+    }
+
+    auto bitwise = bitwise_expression_post_unary_expression(lhs.value);
+    if (bitwise.errored) {
+        return Failure::kErrored;
+    }
+    if (bitwise.matched) {
+        return bitwise.value;
+    }
+
+    auto math = expect_math_expression_post_unary_expression(lhs.value);
+    if (math.errored) {
+        return Failure::kErrored;
+    }
+    return math.value;
+}
+
+// shift_expression
+//   : unary_expression shift_expression.post.unary_expression
+Maybe<const ast::Expression*> ParserImpl::maybe_shift_expression() {
+    auto lhs = unary_expression();
+    if (lhs.errored) {
+        return Failure::kErrored;
+    }
+    if (!lhs.matched) {
+        return Failure::kNoMatch;
+    }
+    return expect_shift_expression_post_unary_expression(lhs.value);
+}
+
+// shift_expression.post.unary_expression
+//   : multiplicative_expression.post.unary_expression?
+//   | SHIFT_LEFT unary_expression
+//   | SHIFT_RIGHT unary_expression
+//
+// Note, add the `multiplicative_expression.post.unary_expression` is added here to make
+// implementation simpler.
+Expect<const ast::Expression*> ParserImpl::expect_shift_expression_post_unary_expression(
+    const ast::Expression* lhs) {
+    auto& t = peek();
+    if (match(Token::Type::kShiftLeft) || match(Token::Type::kShiftRight)) {
+        std::string name;
+        ast::BinaryOp op = ast::BinaryOp::kNone;
+        if (t.Is(Token::Type::kShiftLeft)) {
+            op = ast::BinaryOp::kShiftLeft;
+            name = "<<";
+        } else if (t.Is(Token::Type::kShiftRight)) {
+            op = ast::BinaryOp::kShiftRight;
+            name = ">>";
+        }
+
+        auto rhs = unary_expression();
+        if (rhs.errored) {
+            return Failure::kErrored;
+        }
+        if (!rhs.matched) {
+            return add_error(t,
+                             std::string("unable to parse right side of ") + name + " expression");
+        }
+        return create<ast::BinaryExpression>(t.source(), op, lhs, rhs.value);
+    }
+
+    return expect_multiplicative_expression_post_unary_expression(lhs);
+}
+
+// relational_expression
+//   : unary_expression relational_expression.post.unary_expression
+Maybe<const ast::Expression*> ParserImpl::maybe_relational_expression() {
+    auto lhs = unary_expression();
+    if (lhs.errored) {
+        return Failure::kErrored;
+    }
+    if (!lhs.matched) {
+        return Failure::kNoMatch;
+    }
+    return expect_relational_expression_post_unary_expression(lhs.value);
+}
+
+// relational_expression.post.unary_expression
+//   : shift_expression.post.unary_expression
+//   | shift_expression.post.unary_expression EQUAL_EQUAL shift_expression
+//   | shift_expression.post.unary_expression GREATER_THAN shift_expression
+//   | shift_expression.post.unary_expression GREATER_THAN_EQUAL shift_expression
+//   | shift_expression.post.unary_expression LESS_THAN shift_expression
+//   | shift_expression.post.unary_expression LESS_THAN_EQUAL shift_expression
+//   | shift_expression.post.unary_expression NOT_EQUAL shift_expression
+//
+// Note, a `shift_expression` element was added to simplify many of the right sides
+Expect<const ast::Expression*> ParserImpl::expect_relational_expression_post_unary_expression(
+    const ast::Expression* lhs) {
+    auto lhs_result = expect_shift_expression_post_unary_expression(lhs);
+    if (lhs_result.errored) {
+        return Failure::kErrored;
+    }
+    lhs = lhs_result.value;
+
+    auto& t = peek();
+    if (match(Token::Type::kEqualEqual) || match(Token::Type::kGreaterThan) ||
+        match(Token::Type::kGreaterThanEqual) || match(Token::Type::kLessThan) ||
+        match(Token::Type::kLessThanEqual) || match(Token::Type::kNotEqual)) {
+        ast::BinaryOp op = ast::BinaryOp::kNone;
+        if (t.Is(Token::Type::kLessThan)) {
+            op = ast::BinaryOp::kLessThan;
+        } else if (t.Is(Token::Type::kGreaterThan)) {
+            op = ast::BinaryOp::kGreaterThan;
+        } else if (t.Is(Token::Type::kLessThanEqual)) {
+            op = ast::BinaryOp::kLessThanEqual;
+        } else if (t.Is(Token::Type::kGreaterThanEqual)) {
+            op = ast::BinaryOp::kGreaterThanEqual;
+        }
+
+        auto& next = peek();
+        auto rhs = maybe_shift_expression();
+        if (rhs.errored) {
+            return Failure::kErrored;
+        }
+        if (!rhs.matched) {
+            return add_error(next, std::string("unable to parse right side of ") +
+                                       std::string(t.to_name()) + " expression");
+        }
+        lhs = create<ast::BinaryExpression>(t.source(), op, lhs, rhs.value);
+    }
+    return lhs;
+}
+
+// singular_expression
+//   : primary_expression postfix_expr
+Maybe<const ast::Expression*> ParserImpl::singular_expression() {
+    auto prefix = primary_expression();
+    if (prefix.errored) {
+        return Failure::kErrored;
+    }
+    if (!prefix.matched) {
+        return Failure::kNoMatch;
+    }
+
+    return postfix_expression(prefix.value);
+}
+
 // unary_expression
 //   : singular_expression
 //   | MINUS unary_expression
@@ -2664,6 +2929,8 @@
 //   | TILDE unary_expression
 //   | STAR unary_expression
 //   | AND unary_expression
+//
+// The `primary_expression postfix_expression ?` is moved out into a `singular_expression`
 Maybe<const ast::Expression*> ParserImpl::unary_expression() {
     auto& t = peek();
 
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h
index 1bde5a4..d6d3ccc 100644
--- a/src/tint/reader/wgsl/parser_impl.h
+++ b/src/tint/reader/wgsl/parser_impl.h
@@ -646,6 +646,46 @@
     /// @returns the parsed expression or nullptr
     Maybe<const ast::Expression*> bitwise_expression_post_unary_expression(
         const ast::Expression* lhs);
+    /// Parse the `multiplicative_operator` grammar element
+    /// @returns the parsed operator if successful
+    Maybe<ast::BinaryOp> multiplicative_operator();
+    /// Parses multiplicative elements
+    /// @param lhs the left side of the expression
+    /// @returns the parsed expression or `lhs` if no match
+    Expect<const ast::Expression*> expect_multiplicative_expression_post_unary_expression(
+        const ast::Expression* lhs);
+    /// Parses additive elements
+    /// @param lhs the left side of the expression
+    /// @returns the parsed expression or `lhs` if no match
+    Expect<const ast::Expression*> expect_additive_expression_post_unary_expression(
+        const ast::Expression* lhs);
+    /// Parses math elements
+    /// @param lhs the left side of the expression
+    /// @returns the parsed expression or `lhs` if no match
+    Expect<const ast::Expression*> expect_math_expression_post_unary_expression(
+        const ast::Expression* lhs);
+    /// Parses an `element_count_expression` grammar element
+    /// @returns the parsed expression or nullptr
+    Maybe<const ast::Expression*> element_count_expression();
+    /// Parses a `unary_expression shift.post.unary_expression`
+    /// @returns the parsed expression or nullptr
+    Maybe<const ast::Expression*> maybe_shift_expression();
+    /// Parses a `shift_expression.post.unary_expression` grammar element
+    /// @param lhs the left side of the expression
+    /// @returns the parsed expression or `lhs` if no match
+    Expect<const ast::Expression*> expect_shift_expression_post_unary_expression(
+        const ast::Expression* lhs);
+    /// Parses a `unary_expression relational_expression.post.unary_expression`
+    /// @returns the parsed expression or nullptr
+    Maybe<const ast::Expression*> maybe_relational_expression();
+    /// Parses a `relational_expression.post.unary_expression` grammar element
+    /// @param lhs the left side of the expression
+    /// @returns the parsed expression or `lhs` if no match
+    Expect<const ast::Expression*> expect_relational_expression_post_unary_expression(
+        const ast::Expression* lhs);
+    /// Parse the `additive_operator` grammar element
+    /// @returns the parsed operator if successful
+    Maybe<ast::BinaryOp> additive_operator();
     /// Parses the recursive part of the `and_expression`, erroring on parse
     /// failure.
     /// @param lhs the left side of the expression
diff --git a/src/tint/reader/wgsl/parser_impl_additive_expression_test.cc b/src/tint/reader/wgsl/parser_impl_additive_expression_test.cc
index 4573b84..3259e27 100644
--- a/src/tint/reader/wgsl/parser_impl_additive_expression_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_additive_expression_test.cc
@@ -17,7 +17,7 @@
 namespace tint::reader::wgsl {
 namespace {
 
-TEST_F(ParserImplTest, AdditiveExpression_Parses_Plus) {
+TEST_F(ParserImplTest, AdditiveExpression_Orig_Parses_Plus) {
     auto p = parser("a + true");
     auto e = p->additive_expression();
     EXPECT_TRUE(e.matched);
@@ -42,7 +42,7 @@
     ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
 }
 
-TEST_F(ParserImplTest, AdditiveExpression_Parses_Minus) {
+TEST_F(ParserImplTest, AdditiveExpression_Orig_Parses_Minus) {
     auto p = parser("a - true");
     auto e = p->additive_expression();
     EXPECT_TRUE(e.matched);
@@ -62,7 +62,7 @@
     ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
 }
 
-TEST_F(ParserImplTest, AdditiveExpression_InvalidLHS) {
+TEST_F(ParserImplTest, AdditiveExpression_Orig_InvalidLHS) {
     auto p = parser("if (a) {} + true");
     auto e = p->additive_expression();
     EXPECT_FALSE(e.matched);
@@ -71,7 +71,7 @@
     EXPECT_EQ(e.value, nullptr);
 }
 
-TEST_F(ParserImplTest, AdditiveExpression_InvalidRHS) {
+TEST_F(ParserImplTest, AdditiveExpression_Orig_InvalidRHS) {
     auto p = parser("true + if (a) {}");
     auto e = p->additive_expression();
     EXPECT_FALSE(e.matched);
@@ -81,7 +81,7 @@
     EXPECT_EQ(p->error(), "1:8: unable to parse right side of + expression");
 }
 
-TEST_F(ParserImplTest, AdditiveExpression_NoOr_ReturnsLHS) {
+TEST_F(ParserImplTest, AdditiveExpression_Orig_NoOr_ReturnsLHS) {
     auto p = parser("a true");
     auto e = p->additive_expression();
     EXPECT_TRUE(e.matched);
@@ -91,5 +91,189 @@
     ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
+TEST_F(ParserImplTest, AdditiveExpression_Parses_Plus) {
+    auto p = parser("a + b");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_additive_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    EXPECT_EQ(e->source.range.begin.line, 1u);
+    EXPECT_EQ(e->source.range.begin.column, 3u);
+    EXPECT_EQ(e->source.range.end.line, 1u);
+    EXPECT_EQ(e->source.range.end.column, 4u);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kAdd, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, AdditiveExpression_Parses_Minus) {
+    auto p = parser("a - b");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_additive_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kSubtract, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, AdditiveExpression_Parses_MinusMinus) {
+    auto p = parser("a--b");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_additive_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kSubtract, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::UnaryOpExpression>());
+    auto* unary = rel->rhs->As<ast::UnaryOpExpression>();
+    EXPECT_EQ(ast::UnaryOp::kNegation, unary->op);
+
+    ASSERT_TRUE(unary->expr->Is<ast::IdentifierExpression>());
+    ident = unary->expr->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, AdditiveExpression_Parses_MultipleOps) {
+    auto p = parser("a - b + c - d");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_additive_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    // lhs: ((a - b) + c
+    // op: -
+    // rhs: d
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kSubtract, rel->op);
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("d"));
+
+    ASSERT_TRUE(rel->lhs->Is<ast::BinaryExpression>());
+    // lhs: a - b
+    // op: +
+    // rhs: c
+    rel = rel->lhs->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kAdd, rel->op);
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("c"));
+
+    ASSERT_TRUE(rel->lhs->Is<ast::BinaryExpression>());
+    // lhs: a
+    // op: -
+    // rhs: b
+    rel = rel->lhs->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kSubtract, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, AdditiveExpression_Parses_MultipleOps_MixedMultiplication) {
+    auto p = parser("a - b * c - d");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_additive_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    // lhs: a - (b * c)
+    // op: -
+    // rhs: d
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kSubtract, rel->op);
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("d"));
+
+    ASSERT_TRUE(rel->lhs->Is<ast::BinaryExpression>());
+    // lhs: a
+    // op: -
+    // rhs: b * c
+    rel = rel->lhs->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kSubtract, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::BinaryExpression>());
+    // lhs: b
+    // op: *
+    // rhs: c
+    rel = rel->rhs->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("c"));
+}
+
+TEST_F(ParserImplTest, AdditiveExpression_InvalidRHS) {
+    auto p = parser("a + if (a) {}");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_additive_expression_post_unary_expression(lhs.value);
+    EXPECT_TRUE(e.errored);
+    EXPECT_EQ(e.value, nullptr);
+    EXPECT_TRUE(p->has_error());
+    EXPECT_EQ(p->error(), "1:5: unable to parse right side of + expression");
+}
+
+TEST_F(ParserImplTest, AdditiveExpression_NoMatch_ReturnsLHS) {
+    auto p = parser("a true");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_additive_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+    EXPECT_EQ(lhs.value, e.value);
+}
+
 }  // namespace
 }  // namespace tint::reader::wgsl
diff --git a/src/tint/reader/wgsl/parser_impl_element_count_expression_test.cc b/src/tint/reader/wgsl/parser_impl_element_count_expression_test.cc
new file mode 100644
index 0000000..0dd287d
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_element_count_expression_test.cc
@@ -0,0 +1,81 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint::reader::wgsl {
+namespace {
+
+TEST_F(ParserImplTest, ElementCountExpression_Math) {
+    auto p = parser("a * b");
+    auto e = p->element_count_expression();
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, ElementCountExpression_Bitwise) {
+    auto p = parser("a | true");
+    auto e = p->element_count_expression();
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kOr, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+    ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, ElementCountExpression_NoMatch) {
+    auto p = parser("if (a) { }");
+    auto e = p->element_count_expression();
+    EXPECT_FALSE(e.matched);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, ElementCountExpression_InvalidRHS) {
+    auto p = parser("a * if");
+    auto e = p->element_count_expression();
+    EXPECT_FALSE(e.matched);
+    EXPECT_TRUE(e.errored);
+    EXPECT_TRUE(p->has_error());
+    ASSERT_EQ(e.value, nullptr);
+    EXPECT_EQ("1:5: unable to parse right side of * expression", p->error());
+}
+
+}  // namespace
+}  // namespace tint::reader::wgsl
diff --git a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
index 85e888d..ac2161c 100644
--- a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
@@ -937,9 +937,9 @@
 
 TEST_F(ParserImplErrorTest, GlobalDeclVarArrayInvalidSize) {
     EXPECT("var i : array<u32, !>;",
-           R"(test.wgsl:1:20 error: expected array size expression
+           R"(test.wgsl:1:21 error: unable to parse right side of ! expression
 var i : array<u32, !>;
-                   ^
+                    ^
 )");
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_math_expression_test.cc b/src/tint/reader/wgsl/parser_impl_math_expression_test.cc
new file mode 100644
index 0000000..27b5870
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_math_expression_test.cc
@@ -0,0 +1,153 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint::reader::wgsl {
+namespace {
+
+TEST_F(ParserImplTest, MathExpression_Parses_Multiplicative) {
+    auto p = parser("a * b");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_math_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, MathExpression_Parses_Mixed_MultiplicativeStart) {
+    auto p = parser("a * b + c");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_math_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    // lhs: a * b
+    // op: +
+    // rhs: c
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kAdd, rel->op);
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("c"));
+
+    ASSERT_TRUE(rel->lhs->Is<ast::BinaryExpression>());
+    // lhs: a
+    // op: *
+    // rhs: b
+    rel = rel->lhs->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, MathExpression_Parses_Additive) {
+    auto p = parser("a + b");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_math_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kAdd, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, MathExpression_Parses_Mixed_AdditiveStart) {
+    auto p = parser("a + b * c");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_math_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    // lhs: a
+    // op: +
+    // rhs: b * c
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kAdd, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::BinaryExpression>());
+    // lhs: b
+    // op: *
+    // rhs: c
+    rel = rel->rhs->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("c"));
+}
+
+TEST_F(ParserImplTest, MathExpression_NoMatch_ReturnLHS) {
+    auto p = parser("a if");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_math_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+    EXPECT_EQ(lhs.value, e.value);
+}
+
+TEST_F(ParserImplTest, MathExpression_InvalidRHS) {
+    auto p = parser("a * if");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_math_expression_post_unary_expression(lhs.value);
+    EXPECT_TRUE(e.errored);
+    EXPECT_TRUE(p->has_error());
+    ASSERT_EQ(e.value, nullptr);
+    EXPECT_EQ("1:5: unable to parse right side of * expression", p->error());
+}
+
+}  // namespace
+}  // namespace tint::reader::wgsl
diff --git a/src/tint/reader/wgsl/parser_impl_multiplicative_expression_test.cc b/src/tint/reader/wgsl/parser_impl_multiplicative_expression_test.cc
index 28ac568..618a0c0 100644
--- a/src/tint/reader/wgsl/parser_impl_multiplicative_expression_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_multiplicative_expression_test.cc
@@ -17,7 +17,7 @@
 namespace tint::reader::wgsl {
 namespace {
 
-TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Multiply) {
+TEST_F(ParserImplTest, MultiplicativeExpression_Orig_Parses_Multiply) {
     auto p = parser("a * true");
     auto e = p->multiplicative_expression();
     EXPECT_TRUE(e.matched);
@@ -42,7 +42,7 @@
     ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
 }
 
-TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Divide) {
+TEST_F(ParserImplTest, MultiplicativeExpression_Orig_Parses_Divide) {
     auto p = parser("a / true");
     auto e = p->multiplicative_expression();
     EXPECT_TRUE(e.matched);
@@ -62,7 +62,7 @@
     ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
 }
 
-TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Modulo) {
+TEST_F(ParserImplTest, MultiplicativeExpression_Orig_Parses_Modulo) {
     auto p = parser("a % true");
     auto e = p->multiplicative_expression();
     EXPECT_TRUE(e.matched);
@@ -82,7 +82,7 @@
     ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
 }
 
-TEST_F(ParserImplTest, MultiplicativeExpression_InvalidLHS) {
+TEST_F(ParserImplTest, MultiplicativeExpression_Orig_InvalidLHS) {
     auto p = parser("if (a) {} * true");
     auto e = p->multiplicative_expression();
     EXPECT_FALSE(e.matched);
@@ -91,7 +91,7 @@
     EXPECT_EQ(e.value, nullptr);
 }
 
-TEST_F(ParserImplTest, MultiplicativeExpression_InvalidRHS) {
+TEST_F(ParserImplTest, MultiplicativeExpression_Orig_InvalidRHS) {
     auto p = parser("true * if (a) {}");
     auto e = p->multiplicative_expression();
     EXPECT_FALSE(e.matched);
@@ -101,7 +101,7 @@
     EXPECT_EQ(p->error(), "1:8: unable to parse right side of * expression");
 }
 
-TEST_F(ParserImplTest, MultiplicativeExpression_NoOr_ReturnsLHS) {
+TEST_F(ParserImplTest, MultiplicativeExpression_Orig_NoOr_ReturnsLHS) {
     auto p = parser("a true");
     auto e = p->multiplicative_expression();
     EXPECT_TRUE(e.matched);
@@ -111,5 +111,170 @@
     ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
+TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Multiply) {
+    auto p = parser("a * b");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_multiplicative_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Multiply_UnaryIndirect) {
+    auto p = parser("a **b");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_multiplicative_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::UnaryOpExpression>());
+    auto* unary = rel->rhs->As<ast::UnaryOpExpression>();
+    EXPECT_EQ(ast::UnaryOp::kIndirection, unary->op);
+
+    ASSERT_TRUE(unary->expr->Is<ast::IdentifierExpression>());
+    ident = unary->expr->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Divide) {
+    auto p = parser("a / b");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_multiplicative_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kDivide, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Modulo) {
+    auto p = parser("a % b");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_multiplicative_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kModulo, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Grouping) {
+    auto p = parser("a * b / c % d * e");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_multiplicative_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    // lhs: ((a * b) / c) % d
+    // op: *
+    // rhs: e
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op);
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("e"));
+
+    ASSERT_TRUE(rel->lhs->Is<ast::BinaryExpression>());
+    // lhs: (a * b) / c
+    // op: %
+    // rhs: d
+    rel = rel->lhs->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kModulo, rel->op);
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("d"));
+
+    ASSERT_TRUE(rel->lhs->Is<ast::BinaryExpression>());
+    // lhs: a * b
+    // op: /
+    // rhs: c
+    rel = rel->lhs->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kDivide, rel->op);
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("c"));
+
+    ASSERT_TRUE(rel->lhs->Is<ast::BinaryExpression>());
+    // lhs: a
+    // op: *
+    // rhs: b
+    rel = rel->lhs->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, MultiplicativeExpression_InvalidRHS) {
+    auto p = parser("a * if (a) {}");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_multiplicative_expression_post_unary_expression(lhs.value);
+    EXPECT_TRUE(e.errored);
+    EXPECT_EQ(e.value, nullptr);
+    ASSERT_TRUE(p->has_error());
+    EXPECT_EQ(p->error(), "1:5: unable to parse right side of * expression");
+}
+
+TEST_F(ParserImplTest, MultiplicativeExpression_NoMatch_ReturnsLHS) {
+    auto p = parser("a + b");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_multiplicative_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+    EXPECT_EQ(lhs.value, e.value);
+}
+
 }  // namespace
 }  // namespace tint::reader::wgsl
diff --git a/src/tint/reader/wgsl/parser_impl_relational_expression_test.cc b/src/tint/reader/wgsl/parser_impl_relational_expression_test.cc
index 7ad161f..1c47f87 100644
--- a/src/tint/reader/wgsl/parser_impl_relational_expression_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_relational_expression_test.cc
@@ -17,7 +17,7 @@
 namespace tint::reader::wgsl {
 namespace {
 
-TEST_F(ParserImplTest, RelationalExpression_Parses_LessThan) {
+TEST_F(ParserImplTest, RelationalExpression_Orig_Parses_LessThan) {
     auto p = parser("a < true");
     auto e = p->relational_expression();
     EXPECT_TRUE(e.matched);
@@ -42,7 +42,7 @@
     ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
 }
 
-TEST_F(ParserImplTest, RelationalExpression_Parses_GreaterThan) {
+TEST_F(ParserImplTest, RelationalExpression_Orig_Parses_GreaterThan) {
     auto p = parser("a > true");
     auto e = p->relational_expression();
     EXPECT_TRUE(e.matched);
@@ -67,7 +67,7 @@
     ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
 }
 
-TEST_F(ParserImplTest, RelationalExpression_Parses_LessThanEqual) {
+TEST_F(ParserImplTest, RelationalExpression_Orig_Parses_LessThanEqual) {
     auto p = parser("a <= true");
     auto e = p->relational_expression();
     EXPECT_TRUE(e.matched);
@@ -92,7 +92,7 @@
     ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
 }
 
-TEST_F(ParserImplTest, RelationalExpression_Parses_GreaterThanEqual) {
+TEST_F(ParserImplTest, RelationalExpression_Orig_Parses_GreaterThanEqual) {
     auto p = parser("a >= true");
     auto e = p->relational_expression();
     EXPECT_TRUE(e.matched);
@@ -117,7 +117,7 @@
     ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
 }
 
-TEST_F(ParserImplTest, RelationalExpression_InvalidLHS) {
+TEST_F(ParserImplTest, RelationalExpression_Orig_InvalidLHS) {
     auto p = parser("if (a) {} < true");
     auto e = p->relational_expression();
     EXPECT_FALSE(e.matched);
@@ -126,7 +126,7 @@
     EXPECT_EQ(e.value, nullptr);
 }
 
-TEST_F(ParserImplTest, RelationalExpression_InvalidRHS) {
+TEST_F(ParserImplTest, RelationalExpression_Orig_InvalidRHS) {
     auto p = parser("true < if (a) {}");
     auto e = p->relational_expression();
     ASSERT_TRUE(p->has_error());
@@ -134,7 +134,7 @@
     EXPECT_EQ(p->error(), "1:8: unable to parse right side of < expression");
 }
 
-TEST_F(ParserImplTest, RelationalExpression_NoOr_ReturnsLHS) {
+TEST_F(ParserImplTest, RelationalExpression_Orig_NoOr_ReturnsLHS) {
     auto p = parser("a true");
     auto e = p->relational_expression();
     EXPECT_TRUE(e.matched);
@@ -144,5 +144,163 @@
     ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
+TEST_F(ParserImplTest, RelationalExpression_PostUnary_Parses_LessThan) {
+    auto p = parser("a < true");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_relational_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    EXPECT_EQ(e->source.range.begin.line, 1u);
+    EXPECT_EQ(e->source.range.begin.column, 3u);
+    EXPECT_EQ(e->source.range.end.line, 1u);
+    EXPECT_EQ(e->source.range.end.column, 4u);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kLessThan, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+    ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, RelationalExpression_PostUnary_Parses_GreaterThan) {
+    auto p = parser("a > true");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_relational_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    EXPECT_EQ(e->source.range.begin.line, 1u);
+    EXPECT_EQ(e->source.range.begin.column, 3u);
+    EXPECT_EQ(e->source.range.end.line, 1u);
+    EXPECT_EQ(e->source.range.end.column, 4u);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kGreaterThan, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+    ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, RelationalExpression_PostUnary_Parses_LessThanEqual) {
+    auto p = parser("a <= true");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_relational_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    EXPECT_EQ(e->source.range.begin.line, 1u);
+    EXPECT_EQ(e->source.range.begin.column, 3u);
+    EXPECT_EQ(e->source.range.end.line, 1u);
+    EXPECT_EQ(e->source.range.end.column, 5u);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kLessThanEqual, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+    ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, RelationalExpression_PostUnary_Parses_GreaterThanEqual) {
+    auto p = parser("a >= true");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_relational_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    EXPECT_EQ(e->source.range.begin.line, 1u);
+    EXPECT_EQ(e->source.range.begin.column, 3u);
+    EXPECT_EQ(e->source.range.end.line, 1u);
+    EXPECT_EQ(e->source.range.end.column, 5u);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kGreaterThanEqual, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+    ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, RelationalExpression_PostUnary_InvalidRHS) {
+    auto p = parser("true < if (a) {}");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_relational_expression_post_unary_expression(lhs.value);
+    ASSERT_TRUE(p->has_error());
+    EXPECT_EQ(e.value, nullptr);
+    EXPECT_EQ(p->error(), "1:8: unable to parse right side of < expression");
+}
+
+TEST_F(ParserImplTest, RelationalExpression_PostUnary_NoMatch_ReturnsLHS) {
+    auto p = parser("a true");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_relational_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+    EXPECT_EQ(lhs.value, e.value);
+}
+
+TEST_F(ParserImplTest, RelationalExpression_Matches) {
+    auto p = parser("a >= true");
+    auto e = p->maybe_relational_expression();
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kGreaterThanEqual, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Register("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+    ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, RelationalExpression_InvalidLHS) {
+    auto p = parser("if (a) {}< 3");
+    auto e = p->maybe_relational_expression();
+    ASSERT_FALSE(e.matched);
+    EXPECT_FALSE(e.errored);
+    ASSERT_FALSE(p->has_error()) << p->error();
+    EXPECT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, RelationalExpression_InvalidRHS) {
+    auto p = parser("true < if (a) {}");
+    auto e = p->maybe_relational_expression();
+    ASSERT_FALSE(e.matched);
+    EXPECT_TRUE(e.errored);
+    ASSERT_TRUE(p->has_error());
+    EXPECT_EQ(e.value, nullptr);
+    EXPECT_EQ(p->error(), "1:8: unable to parse right side of < expression");
+}
+
 }  // namespace
 }  // namespace tint::reader::wgsl
diff --git a/src/tint/reader/wgsl/parser_impl_shift_expression_test.cc b/src/tint/reader/wgsl/parser_impl_shift_expression_test.cc
index 83c1255..76337cd 100644
--- a/src/tint/reader/wgsl/parser_impl_shift_expression_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_shift_expression_test.cc
@@ -17,7 +17,7 @@
 namespace tint::reader::wgsl {
 namespace {
 
-TEST_F(ParserImplTest, ShiftExpression_Parses_ShiftLeft) {
+TEST_F(ParserImplTest, ShiftExpression_Orig_Parses_ShiftLeft) {
     auto p = parser("a << true");
     auto e = p->shift_expression();
     EXPECT_TRUE(e.matched);
@@ -42,7 +42,7 @@
     ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
 }
 
-TEST_F(ParserImplTest, ShiftExpression_Parses_ShiftRight) {
+TEST_F(ParserImplTest, ShiftExpression_Orig_Parses_ShiftRight) {
     auto p = parser("a >> true");
     auto e = p->shift_expression();
     EXPECT_TRUE(e.matched);
@@ -67,7 +67,7 @@
     ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
 }
 
-TEST_F(ParserImplTest, ShiftExpression_InvalidSpaceLeft) {
+TEST_F(ParserImplTest, ShiftExpression_Orig_InvalidSpaceLeft) {
     auto p = parser("a < < true");
     auto e = p->shift_expression();
     EXPECT_TRUE(e.matched);
@@ -76,7 +76,7 @@
     EXPECT_FALSE(e.value->Is<ast::BinaryExpression>());
 }
 
-TEST_F(ParserImplTest, ShiftExpression_InvalidSpaceRight) {
+TEST_F(ParserImplTest, ShiftExpression_Orig_InvalidSpaceRight) {
     auto p = parser("a > > true");
     auto e = p->shift_expression();
     EXPECT_TRUE(e.matched);
@@ -85,7 +85,7 @@
     EXPECT_FALSE(e.value->Is<ast::BinaryExpression>());
 }
 
-TEST_F(ParserImplTest, ShiftExpression_InvalidLHS) {
+TEST_F(ParserImplTest, ShiftExpression_Orig_InvalidLHS) {
     auto p = parser("if (a) {} << true");
     auto e = p->shift_expression();
     EXPECT_FALSE(e.matched);
@@ -94,7 +94,7 @@
     EXPECT_EQ(e.value, nullptr);
 }
 
-TEST_F(ParserImplTest, ShiftExpression_InvalidRHS) {
+TEST_F(ParserImplTest, ShiftExpression_Orig_InvalidRHS) {
     auto p = parser("true << if (a) {}");
     auto e = p->shift_expression();
     EXPECT_FALSE(e.matched);
@@ -104,7 +104,7 @@
     EXPECT_EQ(p->error(), "1:9: unable to parse right side of << expression");
 }
 
-TEST_F(ParserImplTest, ShiftExpression_NoOr_ReturnsLHS) {
+TEST_F(ParserImplTest, ShiftExpression_Orig_NoOr_ReturnsLHS) {
     auto p = parser("a true");
     auto e = p->shift_expression();
     EXPECT_TRUE(e.matched);
@@ -114,5 +114,154 @@
     ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
+TEST_F(ParserImplTest, ShiftExpression_PostUnary_Parses_ShiftLeft) {
+    auto p = parser("a << true");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_shift_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    EXPECT_EQ(e->source.range.begin.line, 1u);
+    EXPECT_EQ(e->source.range.begin.column, 3u);
+    EXPECT_EQ(e->source.range.end.line, 1u);
+    EXPECT_EQ(e->source.range.end.column, 5u);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kShiftLeft, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+    ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, ShiftExpression_PostUnary_Parses_ShiftRight) {
+    auto p = parser("a >> true");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_shift_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    EXPECT_EQ(e->source.range.begin.line, 1u);
+    EXPECT_EQ(e->source.range.begin.column, 3u);
+    EXPECT_EQ(e->source.range.end.line, 1u);
+    EXPECT_EQ(e->source.range.end.column, 5u);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kShiftRight, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+    ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, ShiftExpression_PostUnary_Parses_Multiplicative) {
+    auto p = parser("a * b");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_shift_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::IdentifierExpression>());
+    ident = rel->rhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
+}
+
+TEST_F(ParserImplTest, ShiftExpression_PostUnary_InvalidSpaceLeft) {
+    auto p = parser("a < < true");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_shift_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    ASSERT_NE(e.value, nullptr);
+    EXPECT_FALSE(e.value->Is<ast::BinaryExpression>());
+}
+
+TEST_F(ParserImplTest, ShiftExpression_PostUnary_InvalidSpaceRight) {
+    auto p = parser("a > > true");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_shift_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    ASSERT_NE(e.value, nullptr);
+    EXPECT_FALSE(e.value->Is<ast::BinaryExpression>());
+}
+
+TEST_F(ParserImplTest, ShiftExpression_PostUnary_InvalidRHS) {
+    auto p = parser("a << if (a) {}");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_shift_expression_post_unary_expression(lhs.value);
+    EXPECT_TRUE(e.errored);
+    EXPECT_TRUE(p->has_error());
+    EXPECT_EQ(e.value, nullptr);
+    EXPECT_EQ(p->error(), "1:3: unable to parse right side of << expression");
+}
+
+TEST_F(ParserImplTest, ShiftExpression_PostUnary_NoOr_ReturnsLHS) {
+    auto p = parser("a true");
+    auto lhs = p->unary_expression();
+    auto e = p->expect_shift_expression_post_unary_expression(lhs.value);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+    ASSERT_EQ(lhs.value, e.value);
+}
+
+TEST_F(ParserImplTest, ShiftExpression_Parses) {
+    auto p = parser("a << true");
+    auto e = p->maybe_shift_expression();
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+
+    ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+    auto* rel = e->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kShiftLeft, rel->op);
+
+    ASSERT_TRUE(rel->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = rel->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
+
+    ASSERT_TRUE(rel->rhs->Is<ast::BoolLiteralExpression>());
+    ASSERT_TRUE(rel->rhs->As<ast::BoolLiteralExpression>()->value);
+}
+
+TEST_F(ParserImplTest, ShiftExpression_Invalid_Unary) {
+    auto p = parser("if >> true");
+    auto e = p->maybe_shift_expression();
+    EXPECT_FALSE(e.matched);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_EQ(e.value, nullptr);
+}
+
+TEST_F(ParserImplTest, ShiftExpression_Inavlid_ShiftExpressionPostUnary) {
+    auto p = parser("a * if (a) {}");
+    auto e = p->maybe_shift_expression();
+    EXPECT_FALSE(e.matched);
+    EXPECT_TRUE(e.errored);
+    EXPECT_TRUE(p->has_error());
+    ASSERT_EQ(e.value, nullptr);
+
+    EXPECT_EQ(p->error(), "1:5: unable to parse right side of * expression");
+}
+
 }  // namespace
 }  // namespace tint::reader::wgsl
diff --git a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
index 1cf66c0..57c1b72 100644
--- a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
@@ -469,6 +469,33 @@
     EXPECT_EQ(p->builder().Symbols().NameFor(count_expr->symbol), "size");
 }
 
+TEST_F(ParserImplTest, TypeDecl_Array_ExpressionSize) {
+    auto p = parser("array<f32, size + 2>");
+    auto t = p->type_decl();
+    EXPECT_TRUE(t.matched);
+    EXPECT_FALSE(t.errored);
+    ASSERT_NE(t.value, nullptr) << p->error();
+    ASSERT_FALSE(p->has_error());
+    ASSERT_TRUE(t.value->Is<ast::Array>());
+
+    auto* a = t.value->As<ast::Array>();
+    ASSERT_FALSE(a->IsRuntimeArray());
+    ASSERT_TRUE(a->type->Is<ast::F32>());
+    EXPECT_EQ(a->attributes.Length(), 0u);
+
+    ASSERT_TRUE(a->count->Is<ast::BinaryExpression>());
+    auto* count_expr = a->count->As<ast::BinaryExpression>();
+    EXPECT_EQ(ast::BinaryOp::kAdd, count_expr->op);
+
+    ASSERT_TRUE(count_expr->lhs->Is<ast::IdentifierExpression>());
+    auto* ident = count_expr->lhs->As<ast::IdentifierExpression>();
+    EXPECT_EQ(p->builder().Symbols().NameFor(ident->symbol), "size");
+
+    ASSERT_TRUE(count_expr->rhs->Is<ast::IntLiteralExpression>());
+    auto* val = count_expr->rhs->As<ast::IntLiteralExpression>();
+    EXPECT_EQ(2, static_cast<int32_t>(val->value));
+}
+
 TEST_F(ParserImplTest, TypeDecl_Array_Runtime) {
     auto p = parser("array<u32>");
     auto t = p->type_decl();
@@ -524,7 +551,7 @@
     EXPECT_FALSE(t.matched);
     ASSERT_EQ(t.value, nullptr);
     ASSERT_TRUE(p->has_error());
-    ASSERT_EQ(p->error(), "1:12: expected array size expression");
+    ASSERT_EQ(p->error(), "1:13: unable to parse right side of ! expression");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_MissingSize) {
diff --git a/src/tint/reader/wgsl/token.h b/src/tint/reader/wgsl/token.h
index d8b93c6..4cf9aad 100644
--- a/src/tint/reader/wgsl/token.h
+++ b/src/tint/reader/wgsl/token.h
@@ -380,7 +380,7 @@
     /// @returns true if the token can be split during parse into component tokens
     bool IsSplittable() const {
         return Is(Token::Type::kShiftRight) || Is(Token::Type::kGreaterThanEqual) ||
-               Is(Token::Type::kAndAnd);
+               Is(Token::Type::kAndAnd) || Is(Token::Type::kMinusMinus);
     }
 
     /// @returns the source information for this token
diff --git a/src/tint/resolver/assignment_validation_test.cc b/src/tint/resolver/assignment_validation_test.cc
index d6a7e5c..8985e5d 100644
--- a/src/tint/resolver/assignment_validation_test.cc
+++ b/src/tint/resolver/assignment_validation_test.cc
@@ -33,10 +33,7 @@
                                  Member("m", ty.i32()),
                              });
     GlobalVar(Source{{12, 34}}, "a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              Binding(0), Group(0));
 
     WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("a", "m"), 1_i));
 
@@ -52,7 +49,7 @@
     //  a = 2.3;
     // }
 
-    auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
+    auto* var = Var("a", ty.i32(), Expr(2_i));
 
     auto* assign = Assign(Source{{12, 34}}, "a", 2.3_f);
     WrapInFunction(var, assign);
@@ -70,7 +67,7 @@
     //   a = b;
     // }
 
-    GlobalConst("len", nullptr, Expr(4_u));
+    GlobalConst("len", Expr(4_u));
 
     auto* a = Var("a", ty.array(ty.f32(), 4_u));
     auto* b = Var("b", ty.array(ty.f32(), "len"));
@@ -89,7 +86,7 @@
     //   a = b;
     // }
 
-    GlobalConst("len", nullptr, Expr(5_u));
+    GlobalConst("len", Expr(5_u));
 
     auto* a = Var("a", ty.array(ty.f32(), 4_u));
     auto* b = Var("b", ty.array(ty.f32(), "len"));
@@ -107,7 +104,7 @@
     //  var a : i32 = 2i;
     //  a = 2i
     // }
-    auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
+    auto* var = Var("a", ty.i32(), Expr(2_i));
     WrapInFunction(var, Assign("a", 2_i));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -119,7 +116,7 @@
     //  a = 2.3;
     // }
 
-    auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
+    auto* var = Var("a", ty.i32(), Expr(2_i));
     WrapInFunction(var, Assign(Source{{12, 34}}, "a", 2.3_f));
 
     ASSERT_FALSE(r()->Resolve());
@@ -135,7 +132,7 @@
     //  }
     // }
 
-    auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
+    auto* var = Var("a", ty.i32(), Expr(2_i));
     auto* inner_block = Block(Decl(var), Assign(Source{{12, 34}}, "a", 2.3_f));
     auto* outer_block = Block(inner_block);
     WrapInFunction(outer_block);
@@ -149,7 +146,7 @@
     // var my_var : i32 = 2i;
     // 1 = my_var;
 
-    WrapInFunction(Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(2_i)),  //
+    WrapInFunction(Var("my_var", ty.i32(), Expr(2_i)),  //
                    Assign(Expr(Source{{12, 34}}, 1_i), "my_var"));
 
     EXPECT_FALSE(r()->Resolve());
@@ -160,8 +157,8 @@
     // var a : i32 = 1i;
     // a = 2i;
     // a = 3;
-    WrapInFunction(Var("a", ty.i32(), ast::StorageClass::kNone, Expr(1_i)),  //
-                   Assign("a", 2_i),                                         //
+    WrapInFunction(Var("a", ty.i32(), Expr(1_i)),  //
+                   Assign("a", 2_i),               //
                    Assign("a", 3_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -173,8 +170,8 @@
     // a = 2u;
     // a = 3;
     auto* myint = Alias("myint", ty.u32());
-    WrapInFunction(Var("a", ty.Of(myint), ast::StorageClass::kNone, Expr(1_u)),  //
-                   Assign("a", 2_u),                                             //
+    WrapInFunction(Var("a", ty.Of(myint), Expr(1_u)),  //
+                   Assign("a", 2_u),                   //
                    Assign("a", 3_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -184,8 +181,8 @@
     // var a : i32 = 2i;
     // var b : i32 = 3i;
     // a = b;
-    WrapInFunction(Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i)),  //
-                   Var("b", ty.i32(), ast::StorageClass::kNone, Expr(3_i)),  //
+    WrapInFunction(Var("a", ty.i32(), Expr(2_i)),  //
+                   Var("b", ty.i32(), Expr(3_i)),  //
                    Assign("a", "b"));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -237,16 +234,8 @@
                                   ast::Access::kWrite);
     };
 
-    GlobalVar("a", make_type(), ast::StorageClass::kNone,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
-    GlobalVar("b", make_type(), ast::StorageClass::kNone,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("a", make_type(), Binding(0), Group(0));
+    GlobalVar("b", make_type(), Binding(1), Group(0));
 
     WrapInFunction(Assign(Source{{56, 78}}, "a", "b"));
 
@@ -263,10 +252,7 @@
                                  Member("a", ty.atomic(ty.i32())),
                              });
     GlobalVar(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              Binding(0), Group(0));
 
     WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("v", "a"), MemberAccessor("v", "a")));
 
@@ -283,10 +269,7 @@
                                  Member("a", ty.array(ty.f32())),
                              });
     GlobalVar(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              Binding(0), Group(0));
 
     WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("v", "a"), MemberAccessor("v", "a")));
 
@@ -305,7 +288,7 @@
     auto* s = Structure("S", utils::Vector{
                                  Member("arr", ty.array<i32>()),
                              });
-    GlobalVar("s", ty.Of(s), ast::StorageClass::kStorage, GroupAndBinding(0, 0));
+    GlobalVar("s", ty.Of(s), ast::StorageClass::kStorage, Group(0), Binding(0));
 
     WrapInFunction(Assign(Phony(), Expr(Source{{12, 34}}, "s")));
 
@@ -327,7 +310,7 @@
     auto* s = Structure("S", utils::Vector{
                                  Member("arr", ty.array<i32>()),
                              });
-    GlobalVar("s", ty.Of(s), ast::StorageClass::kStorage, GroupAndBinding(0, 0));
+    GlobalVar("s", ty.Of(s), ast::StorageClass::kStorage, Group(0), Binding(0));
 
     WrapInFunction(Assign(Phony(), MemberAccessor(Source{{12, 34}}, "s", "arr")));
 
@@ -377,11 +360,11 @@
     auto* U = Structure("U", utils::Vector{
                                  Member("i", ty.i32()),
                              });
-    GlobalVar("tex", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-              GroupAndBinding(0, 0));
-    GlobalVar("smp", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(0, 1));
-    GlobalVar("u", ty.Of(U), ast::StorageClass::kUniform, GroupAndBinding(0, 2));
-    GlobalVar("s", ty.Of(S), ast::StorageClass::kStorage, GroupAndBinding(0, 3));
+    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("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 d7b3824..405bc27 100644
--- a/src/tint/resolver/atomics_test.cc
+++ b/src/tint/resolver/atomics_test.cc
@@ -47,10 +47,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,
-                        utils::Vector{
-                            create<ast::BindingAttribute>(0u),
-                            create<ast::GroupAttribute>(0u),
-                        });
+                        Binding(0), Group(0));
 
     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 0961397..eb4f43e 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, GroupAndBinding(0, 0));
+              ast::Access::kReadWrite, Group(0), Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+    GlobalVar("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(0),
+              Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+              Group(0), Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+              Group(0), Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+              Group(0), Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+              Group(0), Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+              Group(0), Binding(0));
 
     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 ed0227e..4f6545c 100644
--- a/src/tint/resolver/attribute_validation_test.cc
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -701,10 +701,10 @@
     auto& params = GetParam();
 
     if (IsBindingAttribute(params.kind)) {
-        GlobalVar("a", ty.sampler(ast::SamplerKind::kSampler), ast::StorageClass::kNone, nullptr,
+        GlobalVar("a", ty.sampler(ast::SamplerKind::kSampler),
                   createAttributes(Source{{12, 34}}, *this, params.kind));
     } else {
-        GlobalVar("a", ty.f32(), ast::StorageClass::kPrivate, nullptr,
+        GlobalVar("a", ty.f32(), ast::StorageClass::kPrivate,
                   createAttributes(Source{{12, 34}}, *this, params.kind));
     }
 
@@ -736,11 +736,8 @@
 
 TEST_F(VariableAttributeTest, DuplicateAttribute) {
     GlobalVar("a", ty.sampler(ast::SamplerKind::kSampler),
-              utils::Vector{
-                  create<ast::BindingAttribute>(Source{{12, 34}}, 2u),
-                  create<ast::GroupAttribute>(2u),
-                  create<ast::BindingAttribute>(Source{{56, 78}}, 3u),
-              });
+              create<ast::BindingAttribute>(Source{{12, 34}}, 2u), create<ast::GroupAttribute>(2u),
+              create<ast::BindingAttribute>(Source{{56, 78}}, 3u));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1003,8 +1000,7 @@
 }
 
 TEST_F(ResourceAttributeTest, TextureMissingBinding) {
-    GlobalVar(Source{{12, 34}}, "G", ty.depth_texture(ast::TextureDimension::k2d),
-              ast::StorageClass::kNone);
+    GlobalVar(Source{{12, 34}}, "G", ty.depth_texture(ast::TextureDimension::k2d));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1012,8 +1008,7 @@
 }
 
 TEST_F(ResourceAttributeTest, SamplerMissingBinding) {
-    GlobalVar(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
-              ast::StorageClass::kNone);
+    GlobalVar(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1021,11 +1016,7 @@
 }
 
 TEST_F(ResourceAttributeTest, BindingPairMissingBinding) {
-    GlobalVar(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
-              ast::StorageClass::kNone,
-              utils::Vector{
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler), Group(1));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1033,11 +1024,7 @@
 }
 
 TEST_F(ResourceAttributeTest, BindingPairMissingGroup) {
-    GlobalVar(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
-              ast::StorageClass::kNone,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-              });
+    GlobalVar(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler), Binding(1));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1046,24 +1033,14 @@
 
 TEST_F(ResourceAttributeTest, BindingPointUsedTwiceByEntryPoint) {
     GlobalVar(Source{{12, 34}}, "A", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-              ast::StorageClass::kNone,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+              Binding(1), Group(2));
     GlobalVar(Source{{56, 78}}, "B", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-              ast::StorageClass::kNone,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+              Binding(1), Group(2));
 
     Func("F", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("a", ty.vec4<f32>(), ast::StorageClass::kNone,
-                      Call("textureLoad", "A", vec2<i32>(1_i, 2_i), 0_i))),
-             Decl(Var("b", ty.vec4<f32>(), ast::StorageClass::kNone,
-                      Call("textureLoad", "B", vec2<i32>(1_i, 2_i), 0_i))),
+             Decl(Var("a", ty.vec4<f32>(), Call("textureLoad", "A", vec2<i32>(1_i, 2_i), 0_i))),
+             Decl(Var("b", ty.vec4<f32>(), Call("textureLoad", "B", vec2<i32>(1_i, 2_i), 0_i))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -1078,30 +1055,20 @@
 
 TEST_F(ResourceAttributeTest, BindingPointUsedTwiceByDifferentEntryPoints) {
     GlobalVar(Source{{12, 34}}, "A", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-              ast::StorageClass::kNone,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+              Binding(1), Group(2));
     GlobalVar(Source{{56, 78}}, "B", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-              ast::StorageClass::kNone,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+              Binding(1), Group(2));
 
     Func("F_A", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("a", ty.vec4<f32>(), ast::StorageClass::kNone,
-                      Call("textureLoad", "A", vec2<i32>(1_i, 2_i), 0_i))),
+             Decl(Var("a", ty.vec4<f32>(), Call("textureLoad", "A", vec2<i32>(1_i, 2_i), 0_i))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
     Func("F_B", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("b", ty.vec4<f32>(), ast::StorageClass::kNone,
-                      Call("textureLoad", "B", vec2<i32>(1_i, 2_i), 0_i))),
+             Decl(Var("b", ty.vec4<f32>(), Call("textureLoad", "B", vec2<i32>(1_i, 2_i), 0_i))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -1111,11 +1078,7 @@
 }
 
 TEST_F(ResourceAttributeTest, BindingPointOnNonResource) {
-    GlobalVar(Source{{12, 34}}, "G", ty.f32(), ast::StorageClass::kPrivate,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar(Source{{12, 34}}, "G", ty.f32(), ast::StorageClass::kPrivate, Binding(1), Group(2));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
diff --git a/src/tint/resolver/bitcast_validation_test.cc b/src/tint/resolver/bitcast_validation_test.cc
index 78223fc..d341808 100644
--- a/src/tint/resolver/bitcast_validation_test.cc
+++ b/src/tint/resolver/bitcast_validation_test.cc
@@ -113,7 +113,7 @@
     auto dst = std::get<1>(GetParam());
 
     auto* cast = Bitcast(dst.ast(*this), Expr(Source{{12, 34}}, "src"));
-    WrapInFunction(Let("src", nullptr, src.expr(*this, 0)), cast);
+    WrapInFunction(Let("src", src.expr(*this, 0)), cast);
 
     auto expected =
         "12:34 error: '" + src.sem(*this)->FriendlyName(Symbols()) + "' cannot be bitcast";
diff --git a/src/tint/resolver/builtin_test.cc b/src/tint/resolver/builtin_test.cc
index 6046a73..e98d9b5 100644
--- a/src/tint/resolver/builtin_test.cc
+++ b/src/tint/resolver/builtin_test.cc
@@ -212,11 +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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
+              Group(0));
 
     auto* call = Call("arrayLength", AddressOf(MemberAccessor("a", "x")));
     WrapInFunction(call);
@@ -2146,11 +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,
-                      utils::Vector{
-                          create<ast::BindingAttribute>(0u),
-                          create<ast::GroupAttribute>(0u),
-                      });
+            GlobalVar(name, type, Binding(0), Group(0));
 
         } else {
             GlobalVar(name, type, ast::StorageClass::kPrivate);
diff --git a/src/tint/resolver/builtin_validation_test.cc b/src/tint/resolver/builtin_validation_test.cc
index 83603c3..63f3ccb 100644
--- a/src/tint/resolver/builtin_validation_test.cc
+++ b/src/tint/resolver/builtin_validation_test.cc
@@ -305,7 +305,7 @@
     overload.BuildSamplerVariable(this);
 
     // Build the module-scope const 'G' with the offset value
-    GlobalConst("G", nullptr, expr({}, *this));
+    GlobalConst("G", expr({}, *this));
 
     auto args = overload.args(this);
     auto*& arg_to_replace = (param.position == Position::kFirst) ? args.Front() : args.Back();
diff --git a/src/tint/resolver/call_validation_test.cc b/src/tint/resolver/call_validation_test.cc
index 4243093..3fa71b8 100644
--- a/src/tint/resolver/call_validation_test.cc
+++ b/src/tint/resolver/call_validation_test.cc
@@ -250,7 +250,7 @@
          ty.void_(), utils::Empty);
     auto* v = Var("v", ty.i32());
     auto* p = Let("p", ty.pointer(ty.i32(), ast::StorageClass::kFunction), AddressOf(v));
-    auto* c = Var("c", ty.i32(), ast::StorageClass::kNone, Call("x", Expr(Source{{12, 34}}, p)));
+    auto* c = Var("c", ty.i32(), Call("x", Expr(Source{{12, 34}}, p)));
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(v),
@@ -281,7 +281,7 @@
          ty.void_(), utils::Empty);
     auto* v = GlobalVar("v", ty.i32(), ast::StorageClass::kPrivate);
     auto* p = Let("p", ty.pointer(ty.i32(), ast::StorageClass::kPrivate), AddressOf(v));
-    auto* c = Var("c", ty.i32(), ast::StorageClass::kNone, Call("foo", Expr(Source{{12, 34}}, p)));
+    auto* c = Var("c", ty.i32(), Call("foo", Expr(Source{{12, 34}}, p)));
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(p),
diff --git a/src/tint/resolver/compound_assignment_validation_test.cc b/src/tint/resolver/compound_assignment_validation_test.cc
index 5edcdaa..9bd5c3a 100644
--- a/src/tint/resolver/compound_assignment_validation_test.cc
+++ b/src/tint/resolver/compound_assignment_validation_test.cc
@@ -30,7 +30,7 @@
 TEST_F(ResolverCompoundAssignmentValidationTest, CompatibleTypes) {
     // var a : i32 = 2;
     // a += 2
-    auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
+    auto* var = Var("a", ty.i32(), Expr(2_i));
     WrapInFunction(var, CompoundAssign(Source{{12, 34}}, "a", 2_i, ast::BinaryOp::kAdd));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -41,7 +41,7 @@
     // var a : myint = 2;
     // a += 2
     auto* myint = Alias("myint", ty.i32());
-    auto* var = Var("a", ty.Of(myint), ast::StorageClass::kNone, Expr(2_i));
+    auto* var = Var("a", ty.Of(myint), Expr(2_i));
     WrapInFunction(var, CompoundAssign(Source{{12, 34}}, "a", 2_i, ast::BinaryOp::kAdd));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -66,7 +66,7 @@
     //   a += 2.3;
     // }
 
-    auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
+    auto* var = Var("a", ty.i32(), Expr(2_i));
 
     auto* assign = CompoundAssign(Source{{12, 34}}, "a", 2.3_f, ast::BinaryOp::kAdd);
     WrapInFunction(var, assign);
@@ -83,7 +83,7 @@
     //   a |= 2.0;
     // }
 
-    auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(1_f));
+    auto* var = Var("a", ty.f32(), Expr(1_f));
 
     auto* assign = CompoundAssign(Source{{12, 34}}, "a", 2_f, ast::BinaryOp::kOr);
     WrapInFunction(var, assign);
@@ -100,7 +100,7 @@
     //   a += 1.0;
     // }
 
-    auto* var = Var("a", ty.vec4<f32>(), ast::StorageClass::kNone);
+    auto* var = Var("a", ty.vec4<f32>());
 
     auto* assign = CompoundAssign(Source{{12, 34}}, "a", 1_f, ast::BinaryOp::kAdd);
     WrapInFunction(var, assign);
@@ -114,7 +114,7 @@
     //   a += vec4<f32>();
     // }
 
-    auto* var = Var("a", ty.f32(), ast::StorageClass::kNone);
+    auto* var = Var("a", ty.f32());
 
     auto* assign = CompoundAssign(Source{{12, 34}}, "a", vec4<f32>(), ast::BinaryOp::kAdd);
     WrapInFunction(var, assign);
@@ -130,7 +130,7 @@
     //   a *= 2.0;
     // }
 
-    auto* var = Var("a", ty.mat4x4<f32>(), ast::StorageClass::kNone);
+    auto* var = Var("a", ty.mat4x4<f32>());
 
     auto* assign = CompoundAssign(Source{{12, 34}}, "a", 2_f, ast::BinaryOp::kMultiply);
     WrapInFunction(var, assign);
@@ -144,7 +144,7 @@
     //   a *= mat4x4();
     // }
 
-    auto* var = Var("a", ty.f32(), ast::StorageClass::kNone);
+    auto* var = Var("a", ty.f32());
 
     auto* assign = CompoundAssign(Source{{12, 34}}, "a", mat4x4<f32>(), ast::BinaryOp::kMultiply);
     WrapInFunction(var, assign);
@@ -160,7 +160,7 @@
     //   a *= mat4x4();
     // }
 
-    auto* var = Var("a", ty.vec4<f32>(), ast::StorageClass::kNone);
+    auto* var = Var("a", ty.vec4<f32>());
 
     auto* assign = CompoundAssign(Source{{12, 34}}, "a", mat4x4<f32>(), ast::BinaryOp::kMultiply);
     WrapInFunction(var, assign);
@@ -174,7 +174,7 @@
     //   a *= mat4x2();
     // }
 
-    auto* var = Var("a", ty.vec4<f32>(), ast::StorageClass::kNone);
+    auto* var = Var("a", ty.vec4<f32>());
 
     auto* assign = CompoundAssign(Source{{12, 34}}, "a", mat4x2<f32>(), ast::BinaryOp::kMultiply);
     WrapInFunction(var, assign);
@@ -192,7 +192,7 @@
     //   a *= mat2x4();
     // }
 
-    auto* var = Var("a", ty.vec4<f32>(), ast::StorageClass::kNone);
+    auto* var = Var("a", ty.vec4<f32>());
 
     auto* assign = CompoundAssign(Source{{12, 34}}, "a", mat2x4<f32>(), ast::BinaryOp::kMultiply);
     WrapInFunction(var, assign);
@@ -208,7 +208,7 @@
     //   a *= vec4();
     // }
 
-    auto* var = Var("a", ty.mat4x4<f32>(), ast::StorageClass::kNone);
+    auto* var = Var("a", ty.mat4x4<f32>());
 
     auto* assign = CompoundAssign(Source{{12, 34}}, "a", vec4<f32>(), ast::BinaryOp::kMultiply);
     WrapInFunction(var, assign);
@@ -234,7 +234,7 @@
     //   a += 1i;
     // }
     GlobalVar(Source{{12, 34}}, "a", ty.i32(), ast::StorageClass::kStorage, ast::Access::kRead,
-              GroupAndBinding(0, 0));
+              Group(0), Binding(0));
     WrapInFunction(CompoundAssign(Source{{56, 78}}, "a", 1_i, ast::BinaryOp::kAdd));
 
     EXPECT_FALSE(r()->Resolve());
@@ -245,7 +245,7 @@
 TEST_F(ResolverCompoundAssignmentValidationTest, LhsConstant) {
     // let a = 1i;
     // a += 1i;
-    auto* a = Let(Source{{12, 34}}, "a", nullptr, Expr(1_i));
+    auto* a = Let(Source{{12, 34}}, "a", Expr(1_i));
     WrapInFunction(a, CompoundAssign(Expr(Source{{56, 78}}, "a"), 1_i, ast::BinaryOp::kAdd));
 
     EXPECT_FALSE(r()->Resolve());
diff --git a/src/tint/resolver/const_eval_test.cc b/src/tint/resolver/const_eval_test.cc
index 1509b5d..d515ac4 100644
--- a/src/tint/resolver/const_eval_test.cc
+++ b/src/tint/resolver/const_eval_test.cc
@@ -3019,7 +3019,7 @@
         [&](auto&& values) {
             using T = decltype(values.expect);
             auto* expr = create<ast::UnaryOpExpression>(op, Expr(values.input));
-            GlobalConst("C", nullptr, expr);
+            GlobalConst("C", expr);
 
             EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -3117,7 +3117,7 @@
 // number.
 TEST_F(ResolverConstEvalTest, UnaryNegateLowestAbstract) {
     // const break_me = -(-9223372036854775808);
-    auto* c = GlobalConst("break_me", nullptr, Negation(Negation(Expr(9223372036854775808_a))));
+    auto* c = GlobalConst("break_me", Negation(Negation(Expr(9223372036854775808_a))));
     (void)c;
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     auto* sem = Sem().Get(c);
@@ -3171,7 +3171,7 @@
             }
 
             auto* expr = create<ast::BinaryExpression>(op, Expr(lhs), Expr(rhs));
-            GlobalConst("C", nullptr, expr);
+            GlobalConst("C", expr);
 
             EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -3274,34 +3274,34 @@
                                               OpSubFloatCases<f16>()))));
 
 TEST_F(ResolverConstEvalTest, BinaryAbstractAddOverflow_AInt) {
-    GlobalConst("c", nullptr, Add(Source{{1, 1}}, Expr(AInt::Highest()), 1_a));
+    GlobalConst("c", Add(Source{{1, 1}}, Expr(AInt::Highest()), 1_a));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               "1:1 error: '-9223372036854775808' cannot be represented as 'abstract-int'");
 }
 
 TEST_F(ResolverConstEvalTest, BinaryAbstractAddUnderflow_AInt) {
-    GlobalConst("c", nullptr, Add(Source{{1, 1}}, Expr(AInt::Lowest()), -1_a));
+    GlobalConst("c", Add(Source{{1, 1}}, Expr(AInt::Lowest()), -1_a));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               "1:1 error: '9223372036854775807' cannot be represented as 'abstract-int'");
 }
 
 TEST_F(ResolverConstEvalTest, BinaryAbstractAddOverflow_AFloat) {
-    GlobalConst("c", nullptr, Add(Source{{1, 1}}, Expr(AFloat::Highest()), AFloat::Highest()));
+    GlobalConst("c", Add(Source{{1, 1}}, Expr(AFloat::Highest()), AFloat::Highest()));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "1:1 error: 'inf' cannot be represented as 'abstract-float'");
 }
 
 TEST_F(ResolverConstEvalTest, BinaryAbstractAddUnderflow_AFloat) {
-    GlobalConst("c", nullptr, Add(Source{{1, 1}}, Expr(AFloat::Lowest()), AFloat::Lowest()));
+    GlobalConst("c", Add(Source{{1, 1}}, Expr(AFloat::Lowest()), AFloat::Lowest()));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "1:1 error: '-inf' cannot be represented as 'abstract-float'");
 }
 
 TEST_F(ResolverConstEvalTest, BinaryAbstractMixed_ScalarScalar) {
-    auto* a = Const("a", nullptr, Expr(1_a));    // AInt
-    auto* b = Const("b", nullptr, Expr(2.3_a));  // AFloat
+    auto* a = Const("a", Expr(1_a));    // AInt
+    auto* b = Const("b", Expr(2.3_a));  // AFloat
     auto* c = Add(Expr("a"), Expr("b"));
     WrapInFunction(a, b, c);
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -3313,8 +3313,8 @@
 }
 
 TEST_F(ResolverConstEvalTest, BinaryAbstractMixed_ScalarVector) {
-    auto* a = Const("a", nullptr, Expr(1_a));                                   // AInt
-    auto* b = Const("b", nullptr, Construct(ty.vec(nullptr, 3), Expr(2.3_a)));  // AFloat
+    auto* a = Const("a", Expr(1_a));                                   // AInt
+    auto* b = Const("b", Construct(ty.vec(nullptr, 3), Expr(2.3_a)));  // AFloat
     auto* c = Add(Expr("a"), Expr("b"));
     WrapInFunction(a, b, c);
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -3372,7 +3372,7 @@
             using T = std::decay_t<decltype(result)>;
             auto* expr = Call(sem::str(builtin), std::move(args));
 
-            GlobalConst("C", nullptr, expr);
+            GlobalConst("C", expr);
 
             EXPECT_TRUE(r()->Resolve()) << r()->error();
 
diff --git a/src/tint/resolver/dependency_graph_test.cc b/src/tint/resolver/dependency_graph_test.cc
index 7a8110b..224d342 100644
--- a/src/tint/resolver/dependency_graph_test.cc
+++ b/src/tint/resolver/dependency_graph_test.cc
@@ -970,7 +970,7 @@
          utils::Vector{Return(Expr(Source{{1, 10}}, "Z"))});
     Alias(Source{{2, 1}}, "A", ty.type_name(Source{{2, 10}}, "S"));
     Structure(Source{{3, 1}}, "S", utils::Vector{Member("a", ty.type_name(Source{{3, 10}}, "A"))});
-    GlobalVar(Source{{4, 1}}, "Z", nullptr, Expr(Source{{4, 10}}, "L"));
+    GlobalVar(Source{{4, 1}}, "Z", Expr(Source{{4, 10}}, "L"));
     Alias(Source{{5, 1}}, "R", ty.type_name(Source{{5, 10}}, "A"));
     GlobalConst(Source{{6, 1}}, "L", ty.type_name(Source{{5, 5}}, "S"), Expr(Source{{5, 10}}, "Z"));
 
@@ -1092,9 +1092,9 @@
     // Although all enable directives in a valid WGSL program must go before any other global
     // declaration, a transform may produce such a AST tree that has some declarations before enable
     // nodes. DependencyGraph should deal with these cases.
-    auto* var_1 = GlobalVar("SYMBOL1", ty.i32(), nullptr);
+    auto* var_1 = GlobalVar("SYMBOL1", ty.i32());
     auto* enable_1 = Enable(ast::Extension::kF16);
-    auto* var_2 = GlobalVar("SYMBOL2", ty.f32(), nullptr);
+    auto* var_2 = GlobalVar("SYMBOL2", ty.f32());
     auto* enable_2 = Enable(ast::Extension::kF16);
 
     EXPECT_THAT(AST().GlobalDeclarations(), ElementsAre(var_1, enable_1, var_2, enable_2));
@@ -1298,10 +1298,10 @@
 
 TEST_F(ResolverDependencyGraphTraversalTest, InferredType) {
     // Check that the nullptr of the var / const / let type doesn't make things explode
-    GlobalVar("a", nullptr, Expr(1_i));
-    GlobalConst("b", nullptr, Expr(1_i));
-    WrapInFunction(Var("c", nullptr, Expr(1_i)),  //
-                   Let("d", nullptr, Expr(1_i)));
+    GlobalVar("a", Expr(1_i));
+    GlobalConst("b", Expr(1_i));
+    WrapInFunction(Var("c", Expr(1_i)),  //
+                   Let("d", Expr(1_i)));
     Build();
 }
 
diff --git a/src/tint/resolver/evaluation_stage_test.cc b/src/tint/resolver/evaluation_stage_test.cc
index 4b603bb..4504782 100644
--- a/src/tint/resolver/evaluation_stage_test.cc
+++ b/src/tint/resolver/evaluation_stage_test.cc
@@ -51,7 +51,7 @@
 TEST_F(ResolverEvaluationStageTest, Vector_Ctor_Const_Const) {
     // const f = 1.f;
     // vec2<f32>(f, f);
-    auto* f = Const("f", nullptr, Expr(1_f));
+    auto* f = Const("f", Expr(1_f));
     auto* expr = vec2<f32>(f, f);
     WrapInFunction(f, expr);
 
@@ -63,7 +63,7 @@
 TEST_F(ResolverEvaluationStageTest, Vector_Ctor_Runtime_Runtime) {
     // var f = 1.f;
     // vec2<f32>(f, f);
-    auto* f = Var("f", nullptr, Expr(1_f));
+    auto* f = Var("f", Expr(1_f));
     auto* expr = vec2<f32>(f, f);
     WrapInFunction(f, expr);
 
@@ -75,7 +75,7 @@
 TEST_F(ResolverEvaluationStageTest, Vector_Conv_Const) {
     // const f = 1.f;
     // vec2<u32>(vec2<f32>(f));
-    auto* f = Const("f", nullptr, Expr(1_f));
+    auto* f = Const("f", Expr(1_f));
     auto* expr = vec2<u32>(vec2<f32>(f));
     WrapInFunction(f, expr);
 
@@ -87,7 +87,7 @@
 TEST_F(ResolverEvaluationStageTest, Vector_Conv_Runtime) {
     // var f = 1.f;
     // vec2<u32>(vec2<f32>(f));
-    auto* f = Var("f", nullptr, Expr(1_f));
+    auto* f = Var("f", Expr(1_f));
     auto* expr = vec2<u32>(vec2<f32>(f));
     WrapInFunction(f, expr);
 
@@ -115,7 +115,7 @@
 TEST_F(ResolverEvaluationStageTest, Array_Ctor_Const_Const) {
     // const f = 1.f;
     // array<f32, 2>(f, f);
-    auto* f = Const("f", nullptr, Expr(1_f));
+    auto* f = Const("f", Expr(1_f));
     auto* expr = Construct(ty.array<f32, 2>(), f, f);
     WrapInFunction(f, expr);
 
@@ -128,8 +128,8 @@
     // const f1 = 1.f;
     // override f2 = 2.f;
     // array<f32, 2>(f1, f2);
-    auto* f1 = Const("f1", nullptr, Expr(1_f));
-    auto* f2 = Override("f2", nullptr, Expr(2_f));
+    auto* f1 = Const("f1", Expr(1_f));
+    auto* f2 = Override("f2", Expr(2_f));
     auto* expr = Construct(ty.array<f32, 2>(), f1, f2);
     WrapInFunction(f1, expr);
 
@@ -143,8 +143,8 @@
     // override f1 = 1.f;
     // var f2 = 2.f;
     // array<f32, 2>(f1, f2);
-    auto* f1 = Override("f1", nullptr, Expr(1_f));
-    auto* f2 = Var("f2", nullptr, Expr(2_f));
+    auto* f1 = Override("f1", Expr(1_f));
+    auto* f2 = Var("f2", Expr(2_f));
     auto* expr = Construct(ty.array<f32, 2>(), f1, f2);
     WrapInFunction(f2, expr);
 
@@ -158,8 +158,8 @@
     // const f1 = 1.f;
     // var f2 = 2.f;
     // array<f32, 2>(f1, f2);
-    auto* f1 = Const("f1", nullptr, Expr(1_f));
-    auto* f2 = Var("f2", nullptr, Expr(2_f));
+    auto* f1 = Const("f1", Expr(1_f));
+    auto* f2 = Var("f2", Expr(2_f));
     auto* expr = Construct(ty.array<f32, 2>(), f1, f2);
     WrapInFunction(f1, f2, expr);
 
@@ -172,7 +172,7 @@
 TEST_F(ResolverEvaluationStageTest, Array_Ctor_Runtime_Runtime) {
     // var f = 1.f;
     // array<f32, 2>(f, f);
-    auto* f = Var("f", nullptr, Expr(1_f));
+    auto* f = Var("f", Expr(1_f));
     auto* expr = Construct(ty.array<f32, 2>(), f, f);
     WrapInFunction(f, expr);
 
@@ -185,8 +185,8 @@
     // const vec = vec4<f32>();
     // const idx = 1_i;
     // vec[idx]
-    auto* vec = Const("vec", nullptr, vec4<f32>());
-    auto* idx = Const("idx", nullptr, Expr(1_i));
+    auto* vec = Const("vec", vec4<f32>());
+    auto* idx = Const("idx", Expr(1_i));
     auto* expr = IndexAccessor(vec, idx);
     WrapInFunction(vec, idx, expr);
 
@@ -200,8 +200,8 @@
     // var vec = vec4<f32>();
     // const idx = 1_i;
     // vec[idx]
-    auto* vec = Var("vec", nullptr, vec4<f32>());
-    auto* idx = Const("idx", nullptr, Expr(1_i));
+    auto* vec = Var("vec", vec4<f32>());
+    auto* idx = Const("idx", Expr(1_i));
     auto* expr = IndexAccessor(vec, idx);
     WrapInFunction(vec, idx, expr);
 
@@ -215,8 +215,8 @@
     // const vec = vec4<f32>();
     // override idx = 1_i;
     // vec[idx]
-    auto* vec = Const("vec", nullptr, vec4<f32>());
-    auto* idx = Override("idx", nullptr, Expr(1_i));
+    auto* vec = Const("vec", vec4<f32>());
+    auto* idx = Override("idx", Expr(1_i));
     auto* expr = IndexAccessor(vec, idx);
     WrapInFunction(vec, expr);
 
@@ -230,8 +230,8 @@
     // const vec = vec4<f32>();
     // let idx = 1_i;
     // vec[idx]
-    auto* vec = Const("vec", nullptr, vec4<f32>());
-    auto* idx = Let("idx", nullptr, Expr(1_i));
+    auto* vec = Const("vec", vec4<f32>());
+    auto* idx = Let("idx", Expr(1_i));
     auto* expr = IndexAccessor(vec, idx);
     WrapInFunction(vec, idx, expr);
 
@@ -244,7 +244,7 @@
 TEST_F(ResolverEvaluationStageTest, Swizzle_Const) {
     // const vec = S();
     // vec.m
-    auto* vec = Const("vec", nullptr, vec4<f32>());
+    auto* vec = Const("vec", vec4<f32>());
     auto* expr = MemberAccessor(vec, "xz");
     WrapInFunction(vec, expr);
 
@@ -256,7 +256,7 @@
 TEST_F(ResolverEvaluationStageTest, Swizzle_Runtime) {
     // var vec = S();
     // vec.m
-    auto* vec = Var("vec", nullptr, vec4<f32>());
+    auto* vec = Var("vec", vec4<f32>());
     auto* expr = MemberAccessor(vec, "rg");
     WrapInFunction(vec, expr);
 
@@ -270,7 +270,7 @@
     // const str = S();
     // str.m
     Structure("S", utils::Vector{Member("m", ty.i32())});
-    auto* str = Const("str", nullptr, Construct(ty.type_name("S")));
+    auto* str = Const("str", Construct(ty.type_name("S")));
     auto* expr = MemberAccessor(str, "m");
     WrapInFunction(str, expr);
 
@@ -284,7 +284,7 @@
     // var str = S();
     // str.m
     Structure("S", utils::Vector{Member("m", ty.i32())});
-    auto* str = Var("str", nullptr, Construct(ty.type_name("S")));
+    auto* str = Var("str", Construct(ty.type_name("S")));
     auto* expr = MemberAccessor(str, "m");
     WrapInFunction(str, expr);
 
diff --git a/src/tint/resolver/function_validation_test.cc b/src/tint/resolver/function_validation_test.cc
index 3c4a6ac..f699504 100644
--- a/src/tint/resolver/function_validation_test.cc
+++ b/src/tint/resolver/function_validation_test.cc
@@ -51,7 +51,7 @@
     // }
     Func("func", utils::Vector{Param(Source{{12, 34}}, "common_name", ty.f32())}, ty.void_(),
          utils::Vector{
-             Decl(Let(Source{{56, 78}}, "common_name", nullptr, Expr(1_i))),
+             Decl(Let(Source{{56, 78}}, "common_name", Expr(1_i))),
          });
 
     EXPECT_FALSE(r()->Resolve());
@@ -67,7 +67,7 @@
     // }
     Func("func", utils::Vector{Param(Source{{12, 34}}, "common_name", ty.f32())}, ty.void_(),
          utils::Vector{
-             Block(Decl(Let(Source{{56, 78}}, "common_name", nullptr, Expr(1_i)))),
+             Block(Decl(Let(Source{{56, 78}}, "common_name", Expr(1_i)))),
          });
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -388,7 +388,7 @@
          utils::Vector{
              Return(1_i),
          });
-    GlobalVar("x", nullptr, Call(Source{{12, 34}}, "F"), ast::StorageClass::kPrivate);
+    GlobalVar("x", Call(Source{{12, 34}}, "F"), ast::StorageClass::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), R"(12:34 error: functions cannot be called at module-scope)");
diff --git a/src/tint/resolver/host_shareable_validation_test.cc b/src/tint/resolver/host_shareable_validation_test.cc
index d67a108..b9831cc 100644
--- a/src/tint/resolver/host_shareable_validation_test.cc
+++ b/src/tint/resolver/host_shareable_validation_test.cc
@@ -27,10 +27,7 @@
     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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -45,10 +42,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -64,10 +58,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -86,10 +77,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -128,10 +116,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              Binding(0), Group(0));
 
     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 a7e636f..87d7baf 100644
--- a/src/tint/resolver/increment_decrement_validation_test.cc
+++ b/src/tint/resolver/increment_decrement_validation_test.cc
@@ -27,7 +27,7 @@
 TEST_F(ResolverIncrementDecrementValidationTest, Increment_Signed) {
     // var a : i32 = 2;
     // a++;
-    auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
+    auto* var = Var("a", ty.i32(), Expr(2_i));
     WrapInFunction(var, Increment(Source{{12, 34}}, "a"));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -36,7 +36,7 @@
 TEST_F(ResolverIncrementDecrementValidationTest, Decrement_Signed) {
     // var a : i32 = 2;
     // a--;
-    auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
+    auto* var = Var("a", ty.i32(), Expr(2_i));
     WrapInFunction(var, Decrement(Source{{12, 34}}, "a"));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -45,7 +45,7 @@
 TEST_F(ResolverIncrementDecrementValidationTest, Increment_Unsigned) {
     // var a : u32 = 2u;
     // a++;
-    auto* var = Var("a", ty.u32(), ast::StorageClass::kNone, Expr(2_u));
+    auto* var = Var("a", ty.u32(), Expr(2_u));
     WrapInFunction(var, Increment(Source{{12, 34}}, "a"));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -54,7 +54,7 @@
 TEST_F(ResolverIncrementDecrementValidationTest, Decrement_Unsigned) {
     // var a : u32 = 2u;
     // a--;
-    auto* var = Var("a", ty.u32(), ast::StorageClass::kNone, Expr(2_u));
+    auto* var = Var("a", ty.u32(), Expr(2_u));
     WrapInFunction(var, Decrement(Source{{12, 34}}, "a"));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -74,7 +74,7 @@
 TEST_F(ResolverIncrementDecrementValidationTest, ThroughArray) {
     // var a : array<i32, 4_u>;
     // a[1i]++;
-    auto* var_a = Var("a", ty.array(ty.i32(), 4_u), ast::StorageClass::kNone);
+    auto* var_a = Var("a", ty.array(ty.i32(), 4_u));
     WrapInFunction(var_a, Increment(Source{{12, 34}}, IndexAccessor("a", 1_i)));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -83,7 +83,7 @@
 TEST_F(ResolverIncrementDecrementValidationTest, ThroughVector_Index) {
     // var a : vec4<i32>;
     // a[1i]++;
-    auto* var_a = Var("a", ty.vec4(ty.i32()), ast::StorageClass::kNone);
+    auto* var_a = Var("a", ty.vec4(ty.i32()));
     WrapInFunction(var_a, Increment(Source{{12, 34}}, IndexAccessor("a", 1_i)));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -92,7 +92,7 @@
 TEST_F(ResolverIncrementDecrementValidationTest, ThroughVector_Member) {
     // var a : vec4<i32>;
     // a.y++;
-    auto* var_a = Var("a", ty.vec4(ty.i32()), ast::StorageClass::kNone);
+    auto* var_a = Var("a", ty.vec4(ty.i32()));
     WrapInFunction(var_a, Increment(Source{{12, 34}}, MemberAccessor("a", "y")));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -101,7 +101,7 @@
 TEST_F(ResolverIncrementDecrementValidationTest, Float) {
     // var a : f32 = 2.0;
     // a++;
-    auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2_f));
+    auto* var = Var("a", ty.f32(), Expr(2_f));
     auto* inc = Increment(Expr(Source{{12, 34}}, "a"));
     WrapInFunction(var, inc);
 
@@ -114,7 +114,7 @@
 TEST_F(ResolverIncrementDecrementValidationTest, Vector) {
     // var a : vec4<f32>;
     // a++;
-    auto* var = Var("a", ty.vec4<i32>(), ast::StorageClass::kNone);
+    auto* var = Var("a", ty.vec4<i32>());
     auto* inc = Increment(Expr(Source{{12, 34}}, "a"));
     WrapInFunction(var, inc);
 
@@ -147,7 +147,7 @@
 TEST_F(ResolverIncrementDecrementValidationTest, Constant) {
     // let a = 1;
     // a++;
-    auto* a = Let(Source{{12, 34}}, "a", nullptr, Expr(1_i));
+    auto* a = Let(Source{{12, 34}}, "a", Expr(1_i));
     WrapInFunction(a, Increment(Expr(Source{{56, 78}}, "a")));
 
     EXPECT_FALSE(r()->Resolve());
@@ -194,7 +194,7 @@
     //   a++;
     // }
     GlobalVar(Source{{12, 34}}, "a", ty.i32(), ast::StorageClass::kStorage, ast::Access::kRead,
-              GroupAndBinding(0, 0));
+              Group(0), Binding(0));
     WrapInFunction(Increment(Source{{56, 78}}, "a"));
 
     EXPECT_FALSE(r()->Resolve());
@@ -213,7 +213,7 @@
     // for (a++; ; ) {
     //   break;
     // }
-    auto* a = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
+    auto* a = Var("a", ty.i32(), Expr(2_i));
     auto* loop = For(Increment(Source{{56, 78}}, "a"), nullptr, nullptr, Block(Break()));
     WrapInFunction(a, loop);
 
@@ -225,7 +225,7 @@
     // for (; ; a++) {
     //   break;
     // }
-    auto* a = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
+    auto* a = Var("a", ty.i32(), Expr(2_i));
     auto* loop = For(nullptr, nullptr, Increment(Source{{56, 78}}, "a"), Block(Break()));
     WrapInFunction(a, loop);
 
diff --git a/src/tint/resolver/inferred_type_test.cc b/src/tint/resolver/inferred_type_test.cc
index 4051d82..8ce611e 100644
--- a/src/tint/resolver/inferred_type_test.cc
+++ b/src/tint/resolver/inferred_type_test.cc
@@ -82,7 +82,7 @@
 
     // const a = <type constructor>;
     auto* ctor_expr = params.create_value(*this, 0);
-    auto* a = GlobalConst("a", nullptr, ctor_expr);
+    auto* a = GlobalConst("a", ctor_expr);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     EXPECT_EQ(TypeOf(a), expected_type);
@@ -95,7 +95,7 @@
 
     // var a = <type constructor>;
     auto* ctor_expr = params.create_value(*this, 0);
-    auto* var = GlobalVar("a", nullptr, ast::StorageClass::kPrivate, ctor_expr);
+    auto* var = GlobalVar("a", ast::StorageClass::kPrivate, ctor_expr);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
@@ -108,7 +108,7 @@
 
     // let a = <type constructor>;
     auto* ctor_expr = params.create_value(*this, 0);
-    auto* var = Let("a", nullptr, ctor_expr);
+    auto* var = Let("a", ctor_expr);
     WrapInFunction(var);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -122,7 +122,7 @@
 
     // var a = <type constructor>;
     auto* ctor_expr = params.create_value(*this, 0);
-    auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
+    auto* var = Var("a", ast::StorageClass::kFunction, ctor_expr);
     WrapInFunction(var);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -136,7 +136,7 @@
     auto* expected_type = create<sem::Array>(create<sem::U32>(), 10u, 4u, 4u * 10u, 4u, 4u);
 
     auto* ctor_expr = Construct(type);
-    auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
+    auto* var = Var("a", ast::StorageClass::kFunction, ctor_expr);
     WrapInFunction(var);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -155,7 +155,7 @@
 
     auto* ctor_expr = Construct(ty.Of(str));
 
-    auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
+    auto* var = Var("a", ast::StorageClass::kFunction, ctor_expr);
     WrapInFunction(var);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index 26f109f..01ff0d5 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -328,7 +328,7 @@
             WrapInFunction(Decl(Let("a", target_ty(), abstract_expr)));
             break;
         case Method::kAssign:
-            WrapInFunction(Decl(Var("a", target_ty(), nullptr)), Assign("a", abstract_expr));
+            WrapInFunction(Decl(Var("a", target_ty())), Assign("a", abstract_expr));
             break;
         case Method::kPhonyAssign:
             WrapInFunction(Assign(Phony(), abstract_expr));
@@ -381,7 +381,7 @@
                                Stage(ast::PipelineStage::kCompute)});
             break;
         case Method::kRuntimeIndex:
-            auto* runtime_index = Var("runtime_index", nullptr, Expr(1_i));
+            auto* runtime_index = Var("runtime_index", Expr(1_i));
             WrapInFunction(runtime_index, IndexAccessor(abstract_expr, runtime_index));
             break;
     }
@@ -872,10 +872,10 @@
     };
     switch (method) {
         case Method::kVar:
-            WrapInFunction(Decl(Var("a", nullptr, abstract_expr())));
+            WrapInFunction(Decl(Var("a", abstract_expr())));
             break;
         case Method::kLet:
-            WrapInFunction(Decl(Let("a", nullptr, abstract_expr())));
+            WrapInFunction(Decl(Let("a", abstract_expr())));
             break;
         case Method::kBuiltinArg:
             WrapInFunction(CallStmt(Call("min", abstract_expr(), abstract_expr())));
@@ -904,7 +904,7 @@
             WrapInFunction(IndexAccessor("arr", abstract_expr()));
             break;
         case Method::kRuntimeIndex:
-            auto* runtime_index = Var("runtime_index", nullptr, Expr(1_i));
+            auto* runtime_index = Var("runtime_index", Expr(1_i));
             WrapInFunction(runtime_index, IndexAccessor(abstract_expr(), runtime_index));
             break;
     }
diff --git a/src/tint/resolver/override_test.cc b/src/tint/resolver/override_test.cc
index 2615d73..08fec58 100644
--- a/src/tint/resolver/override_test.cc
+++ b/src/tint/resolver/override_test.cc
@@ -50,7 +50,7 @@
 }
 
 TEST_F(ResolverOverrideTest, WithId) {
-    auto* a = Override("a", ty.f32(), Expr(1_f), utils::Vector{Id(7u)});
+    auto* a = Override("a", ty.f32(), Expr(1_f), Id(7u));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -69,10 +69,10 @@
     std::vector<ast::Variable*> variables;
     auto* a = Override("a", ty.f32(), Expr(1_f));
     auto* b = Override("b", ty.f32(), Expr(1_f));
-    auto* c = Override("c", ty.f32(), Expr(1_f), utils::Vector{Id(2u)});
-    auto* d = Override("d", ty.f32(), Expr(1_f), utils::Vector{Id(4u)});
+    auto* c = Override("c", ty.f32(), Expr(1_f), Id(2u));
+    auto* d = Override("d", ty.f32(), Expr(1_f), Id(4u));
     auto* e = Override("e", ty.f32(), Expr(1_f));
-    auto* f = Override("f", ty.f32(), Expr(1_f), utils::Vector{Id(1u)});
+    auto* f = Override("f", ty.f32(), Expr(1_f), Id(1u));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -86,8 +86,8 @@
 }
 
 TEST_F(ResolverOverrideTest, DuplicateIds) {
-    Override("a", ty.f32(), Expr(1_f), utils::Vector{Id(Source{{12, 34}}, 7u)});
-    Override("b", ty.f32(), Expr(1_f), utils::Vector{Id(Source{{56, 78}}, 7u)});
+    Override("a", ty.f32(), Expr(1_f), Id(Source{{12, 34}}, 7u));
+    Override("b", ty.f32(), Expr(1_f), Id(Source{{56, 78}}, 7u));
 
     EXPECT_FALSE(r()->Resolve());
 
@@ -96,7 +96,7 @@
 }
 
 TEST_F(ResolverOverrideTest, IdTooLarge) {
-    Override("a", ty.f32(), Expr(1_f), utils::Vector{Id(Source{{12, 34}}, 65536u)});
+    Override("a", ty.f32(), Expr(1_f), Id(Source{{12, 34}}, 65536u));
 
     EXPECT_FALSE(r()->Resolve());
 
@@ -106,7 +106,7 @@
 TEST_F(ResolverOverrideTest, F16_TemporallyBan) {
     Enable(ast::Extension::kF16);
 
-    Override(Source{{12, 34}}, "a", ty.f16(), Expr(1_h), utils::Vector{Id(1u)});
+    Override(Source{{12, 34}}, "a", ty.f16(), Expr(1_h), Id(1u));
 
     EXPECT_FALSE(r()->Resolve());
 
diff --git a/src/tint/resolver/ptr_ref_test.cc b/src/tint/resolver/ptr_ref_test.cc
index 0a757b4..ba5be62 100644
--- a/src/tint/resolver/ptr_ref_test.cc
+++ b/src/tint/resolver/ptr_ref_test.cc
@@ -27,7 +27,7 @@
     // var v : i32;
     // &v
 
-    auto* v = Var("v", ty.i32(), ast::StorageClass::kNone);
+    auto* v = Var("v", ty.i32());
     auto* expr = AddressOf(v);
 
     WrapInFunction(v, expr);
@@ -43,7 +43,7 @@
     // var v : i32;
     // *(&v)
 
-    auto* v = Var("v", ty.i32(), ast::StorageClass::kNone);
+    auto* v = Var("v", ty.i32());
     auto* expr = Deref(AddressOf(v));
 
     WrapInFunction(v, expr);
@@ -61,16 +61,8 @@
     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,
-                              utils::Vector{
-                                  create<ast::BindingAttribute>(0u),
-                                  create<ast::GroupAttribute>(0u),
-                              });
-    auto* storage = GlobalVar("sb", ty.Of(buf), ast::StorageClass::kStorage,
-                              utils::Vector{
-                                  create<ast::BindingAttribute>(1u),
-                                  create<ast::GroupAttribute>(0u),
-                              });
+    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* 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 5de5007..0f6f6e5 100644
--- a/src/tint/resolver/ptr_ref_validation_test.cc
+++ b/src/tint/resolver/ptr_ref_validation_test.cc
@@ -54,8 +54,7 @@
 TEST_F(ResolverPtrRefValidationTest, AddressOfHandle) {
     // @group(0) @binding(0) var t: texture_3d<f32>;
     // &t
-    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()),
-              GroupAndBinding(0u, 0u));
+    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()), Group(0), Binding(0));
     auto* expr = AddressOf(Expr(Source{{12, 34}}, "t"));
     WrapInFunction(expr);
 
@@ -94,8 +93,7 @@
 TEST_F(ResolverPtrRefValidationTest, IndirectOfAddressOfHandle) {
     // @group(0) @binding(0) var t: texture_3d<f32>;
     // *&t
-    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()),
-              GroupAndBinding(0u, 0u));
+    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()), Group(0), Binding(0));
     auto* expr = Deref(AddressOf(Expr(Source{{12, 34}}, "t")));
     WrapInFunction(expr);
 
@@ -144,10 +142,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,
-                              utils::Vector{
-                                  create<ast::BindingAttribute>(0u),
-                                  create<ast::GroupAttribute>(0u),
-                              });
+                              Binding(0), Group(0));
 
     auto* expr = IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 2_i);
     auto* ptr =
diff --git a/src/tint/resolver/resolver_test.cc b/src/tint/resolver/resolver_test.cc
index 35ebcf5..d4f0ef9 100644
--- a/src/tint/resolver/resolver_test.cc
+++ b/src/tint/resolver/resolver_test.cc
@@ -288,7 +288,7 @@
 }
 
 TEST_F(ResolverTest, Stmt_VariableDecl) {
-    auto* var = Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
+    auto* var = Var("my_var", ty.i32(), Expr(2_i));
     auto* init = var->constructor;
 
     auto* decl = Decl(var);
@@ -302,7 +302,7 @@
 
 TEST_F(ResolverTest, Stmt_VariableDecl_Alias) {
     auto* my_int = Alias("MyInt", ty.i32());
-    auto* var = Var("my_var", ty.Of(my_int), ast::StorageClass::kNone, Expr(2_i));
+    auto* var = Var("my_var", ty.Of(my_int), Expr(2_i));
     auto* init = var->constructor;
 
     auto* decl = Decl(var);
@@ -336,24 +336,24 @@
     // }
 
     // Declare i32 "foo" inside a block
-    auto* foo_i32 = Var("foo", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
+    auto* foo_i32 = Var("foo", ty.i32(), Expr(2_i));
     auto* foo_i32_init = foo_i32->constructor;
     auto* foo_i32_decl = Decl(foo_i32);
 
     // Reference "foo" inside the block
-    auto* bar_i32 = Var("bar", ty.i32(), ast::StorageClass::kNone, Expr("foo"));
+    auto* bar_i32 = Var("bar", ty.i32(), Expr("foo"));
     auto* bar_i32_init = bar_i32->constructor;
     auto* bar_i32_decl = Decl(bar_i32);
 
     auto* inner = Block(foo_i32_decl, bar_i32_decl);
 
     // Declare f32 "foo" at function scope
-    auto* foo_f32 = Var("foo", ty.f32(), ast::StorageClass::kNone, Expr(2_f));
+    auto* foo_f32 = Var("foo", ty.f32(), Expr(2_f));
     auto* foo_f32_init = foo_f32->constructor;
     auto* foo_f32_decl = Decl(foo_f32);
 
     // Reference "foo" at function scope
-    auto* bar_f32 = Var("bar", ty.f32(), ast::StorageClass::kNone, Expr("foo"));
+    auto* bar_f32 = Var("bar", ty.f32(), Expr("foo"));
     auto* bar_f32_init = bar_f32->constructor;
     auto* bar_f32_decl = Decl(bar_f32);
 
@@ -390,7 +390,7 @@
     // }
 
     // Declare i32 "foo" inside a function
-    auto* fn_i32 = Var("foo", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
+    auto* fn_i32 = Var("foo", ty.i32(), Expr(2_i));
     auto* fn_i32_init = fn_i32->constructor;
     auto* fn_i32_decl = Decl(fn_i32);
     Func("func_i32", utils::Empty, ty.void_(), utils::Vector{fn_i32_decl});
@@ -401,7 +401,7 @@
     AST().AddGlobalVariable(mod_f32);
 
     // Reference "foo" in another function
-    auto* fn_f32 = Var("bar", ty.f32(), ast::StorageClass::kNone, Expr("foo"));
+    auto* fn_f32 = Var("bar", ty.f32(), Expr("foo"));
     auto* fn_f32_init = fn_f32->constructor;
     auto* fn_f32_decl = Decl(fn_f32);
     Func("func_f32", utils::Empty, ty.void_(), utils::Vector{fn_f32_decl});
@@ -451,7 +451,7 @@
 TEST_F(ResolverTest, ArraySize_UnsignedConst) {
     // const size = 10u;
     // var<private> a : array<f32, size>;
-    GlobalConst("size", nullptr, Expr(10_u));
+    GlobalConst("size", Expr(10_u));
     auto* a = GlobalVar("a", ty.array(ty.f32(), Expr("size")), ast::StorageClass::kPrivate);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -466,7 +466,7 @@
 TEST_F(ResolverTest, ArraySize_SignedConst) {
     // const size = 0;
     // var<private> a : array<f32, size>;
-    GlobalConst("size", nullptr, Expr(10_i));
+    GlobalConst("size", Expr(10_i));
     auto* a = GlobalVar("a", ty.array(ty.f32(), Expr("size")), ast::StorageClass::kPrivate);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -633,7 +633,7 @@
 TEST_F(ResolverTest, Expr_Identifier_FunctionVariable_Const) {
     auto* my_var_a = Expr("my_var");
     auto* var = Let("my_var", ty.f32(), Construct(ty.f32()));
-    auto* decl = Decl(Var("b", ty.f32(), ast::StorageClass::kNone, my_var_a));
+    auto* decl = Decl(Var("b", ty.f32(), my_var_a));
 
     Func("my_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -776,12 +776,8 @@
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
     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,
-                  utils::Vector{
-                      create<ast::BindingAttribute>(0u),
-                      create<ast::GroupAttribute>(0u),
-                  });
+    auto* sb_var = GlobalVar("sb_var", ty.Of(s), ast::StorageClass::kStorage,
+                             ast::Access::kReadWrite, Binding(0), Group(0));
     auto* wg_var = GlobalVar("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
     auto* priv_var = GlobalVar("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -809,12 +805,8 @@
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) {
     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,
-                  utils::Vector{
-                      create<ast::BindingAttribute>(0u),
-                      create<ast::GroupAttribute>(0u),
-                  });
+    auto* sb_var = GlobalVar("sb_var", ty.Of(s), ast::StorageClass::kStorage,
+                             ast::Access::kReadWrite, Binding(0), Group(0));
     auto* wg_var = GlobalVar("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
     auto* priv_var = GlobalVar("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -1009,9 +1001,9 @@
     // @id(2) override depth = 2i;
     // @compute @workgroup_size(width, height, depth)
     // fn main() {}
-    auto* width = Override("width", ty.i32(), Expr(16_i), utils::Vector{Id(0)});
-    auto* height = Override("height", ty.i32(), Expr(8_i), utils::Vector{Id(1)});
-    auto* depth = Override("depth", ty.i32(), Expr(2_i), utils::Vector{Id(2)});
+    auto* width = Override("width", ty.i32(), Expr(16_i), Id(0));
+    auto* height = Override("height", ty.i32(), Expr(8_i), Id(1));
+    auto* depth = Override("depth", ty.i32(), Expr(2_i), Id(2));
     auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
                       utils::Vector{
                           Stage(ast::PipelineStage::kCompute),
@@ -1037,9 +1029,9 @@
     // @id(2) override depth : i32;
     // @compute @workgroup_size(width, height, depth)
     // fn main() {}
-    auto* width = Override("width", ty.i32(), nullptr, utils::Vector{Id(0)});
-    auto* height = Override("height", ty.i32(), nullptr, utils::Vector{Id(1)});
-    auto* depth = Override("depth", ty.i32(), nullptr, utils::Vector{Id(2)});
+    auto* width = Override("width", ty.i32(), Id(0));
+    auto* height = Override("height", ty.i32(), Id(1));
+    auto* depth = Override("depth", ty.i32(), Id(2));
     auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
                       utils::Vector{
                           Stage(ast::PipelineStage::kCompute),
@@ -1064,7 +1056,7 @@
     // const depth = 3i;
     // @compute @workgroup_size(8, height, depth)
     // fn main() {}
-    auto* height = Override("height", ty.i32(), Expr(2_i), utils::Vector{Id(0)});
+    auto* height = Override("height", ty.i32(), Expr(2_i), Id(0));
     GlobalConst("depth", ty.i32(), Expr(3_i));
     auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
                       utils::Vector{
@@ -1735,11 +1727,7 @@
 
 TEST_F(ResolverTest, StorageClass_SetForSampler) {
     auto* t = ty.sampler(ast::SamplerKind::kSampler);
-    auto* var = GlobalVar("var", t,
-                          utils::Vector{
-                              create<ast::BindingAttribute>(0u),
-                              create<ast::GroupAttribute>(0u),
-                          });
+    auto* var = GlobalVar("var", t, Binding(0), Group(0));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1748,11 +1736,7 @@
 
 TEST_F(ResolverTest, StorageClass_SetForTexture) {
     auto* t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-    auto* var = GlobalVar("var", t,
-                          utils::Vector{
-                              create<ast::BindingAttribute>(0u),
-                              create<ast::GroupAttribute>(0u),
-                          });
+    auto* var = GlobalVar("var", t, Binding(0), Group(0));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1773,11 +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,
-                          utils::Vector{
-                              create<ast::BindingAttribute>(0u),
-                              create<ast::GroupAttribute>(0u),
-                          });
+    auto* var = GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage, Binding(0),
+                          Group(0));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1787,12 +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),
-        utils::Vector{create<ast::GroupAttribute>(1u), create<ast::BindingAttribute>(2u)});
-    auto* s2 = GlobalVar(
-        Sym(), ty.sampler(ast::SamplerKind::kSampler),
-        utils::Vector{create<ast::GroupAttribute>(3u), create<ast::BindingAttribute>(4u)});
+    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));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1995,8 +1972,8 @@
 }
 
 TEST_F(ResolverTest, TextureSampler_TextureSample) {
-    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), GroupAndBinding(1, 1));
-    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
+    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));
 
     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},
@@ -2012,8 +1989,8 @@
 }
 
 TEST_F(ResolverTest, TextureSampler_TextureSampleInFunction) {
-    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), GroupAndBinding(1, 1));
-    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
+    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));
 
     auto* inner_call = CallStmt(Call("textureSample", "t", "s", vec2<f32>(1_f, 2_f)));
     const ast::Function* inner_func =
@@ -2037,8 +2014,8 @@
 }
 
 TEST_F(ResolverTest, TextureSampler_TextureSampleFunctionDiamondSameVariables) {
-    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), GroupAndBinding(1, 1));
-    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
+    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));
 
     auto* inner_call_1 = CallStmt(Call("textureSample", "t", "s", vec2<f32>(1_f, 2_f)));
     const ast::Function* inner_func_1 =
@@ -2071,11 +2048,9 @@
 }
 
 TEST_F(ResolverTest, TextureSampler_TextureSampleFunctionDiamondDifferentVariables) {
-    GlobalVar("t1", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-              GroupAndBinding(1, 1));
-    GlobalVar("t2", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-              GroupAndBinding(1, 2));
-    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 3));
+    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));
 
     auto* inner_call_1 = CallStmt(Call("textureSample", "t1", "s", vec2<f32>(1_f, 2_f)));
     const ast::Function* inner_func_1 =
@@ -2110,7 +2085,7 @@
 }
 
 TEST_F(ResolverTest, TextureSampler_TextureDimensions) {
-    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), GroupAndBinding(1, 2));
+    GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(1), Binding(2));
 
     auto* call = Call("textureDimensions", "t");
     const ast::Function* f = WrapInFunction(call);
@@ -2153,7 +2128,7 @@
     for (size_t i = 0; i < kMaxExpressionDepth; ++i) {
         chain = Add(chain ? chain : Expr("b"), Expr("b"));
     }
-    auto* a = Let("a", nullptr, chain);
+    auto* a = Let("a", chain);
     WrapInFunction(b, a);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -2165,7 +2140,7 @@
     for (size_t i = 0; i < kMaxExpressionDepth + 1; ++i) {
         chain = Add(chain ? chain : Expr("b"), Expr("b"));
     }
-    auto* a = Let("a", nullptr, chain);
+    auto* a = Let("a", chain);
     WrapInFunction(b, a);
 
     EXPECT_FALSE(r()->Resolve());
diff --git a/src/tint/resolver/side_effects_test.cc b/src/tint/resolver/side_effects_test.cc
index d0cb32c..9a7bd9f 100644
--- a/src/tint/resolver/side_effects_test.cc
+++ b/src/tint/resolver/side_effects_test.cc
@@ -174,32 +174,31 @@
     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,
-              GroupAndBinding(0, next_binding++));
+    GlobalVar("storage_arr", ty.array<f32>(), ast::StorageClass::kStorage, Group(0),
+              Binding(next_binding++));
     GlobalVar("a", ty.atomic(ty.i32()), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              GroupAndBinding(0, next_binding++));
+              Group(0), Binding(next_binding++));
     if (c.pipeline_stage != ast::PipelineStage::kCompute) {
-        GlobalVar("t2d", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
-                  GroupAndBinding(0, next_binding++));
-        GlobalVar("tdepth2d", ty.depth_texture(ast::TextureDimension::k2d),
-                  GroupAndBinding(0, next_binding++));
+        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_arr", ty.sampled_texture(ast::TextureDimension::k2dArray, ty.f32()),
-                  GroupAndBinding(0, next_binding++));
+                  Group(0), Binding(next_binding++));
         GlobalVar("t2d_multi", ty.multisampled_texture(ast::TextureDimension::k2d, ty.f32()),
-                  GroupAndBinding(0, next_binding++));
+                  Group(0), Binding(next_binding++));
         GlobalVar("tstorage2d",
                   ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Float,
                                      ast::Access::kWrite),
-                  GroupAndBinding(0, next_binding++));
-        GlobalVar("s2d", ty.sampler(ast::SamplerKind::kSampler),
-                  GroupAndBinding(0, next_binding++));
-        GlobalVar("scomp", ty.sampler(ast::SamplerKind::kComparisonSampler),
-                  GroupAndBinding(0, next_binding++));
+                  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++));
     }
 
     utils::Vector<const ast::Statement*, 4> stmts;
-    stmts.Push(Decl(Let("pstorage_arr", nullptr, AddressOf("storage_arr"))));
-    stmts.Push(Decl(Let("pa", nullptr, AddressOf("a"))));
+    stmts.Push(Decl(Let("pstorage_arr", AddressOf("storage_arr"))));
+    stmts.Push(Decl(Let("pa", AddressOf("a"))));
 
     utils::Vector<const ast::Expression*, 5> args;
     for (auto& a : c.args) {
diff --git a/src/tint/resolver/source_variable_test.cc b/src/tint/resolver/source_variable_test.cc
index f80c980..ada4fc2 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, GroupAndBinding(0, 0));
+    auto* a = GlobalVar("a", ty.f32(), ast::StorageClass::kStorage, Group(0), Binding(0));
     auto* expr = Expr(a);
     WrapInFunction(expr);
 
@@ -59,7 +59,7 @@
 }
 
 TEST_F(ResolverSourceVariableTest, GlobalUniformVar) {
-    auto* a = GlobalVar("a", ty.f32(), ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+    auto* a = GlobalVar("a", ty.f32(), ast::StorageClass::kUniform, Group(0), Binding(0));
     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, GroupAndBinding(0, 0));
+                        ast::StorageClass::kNone, Group(0), Binding(0));
     auto* expr = Expr(a);
     WrapInFunction(Call("textureDimensions", expr));
 
@@ -104,7 +104,7 @@
 }
 
 TEST_F(ResolverSourceVariableTest, FunctionVar) {
-    auto* a = Var("a", ty.f32(), ast::StorageClass::kNone);
+    auto* a = Var("a", ty.f32());
     auto* expr = Expr(a);
     WrapInFunction(a, expr);
 
@@ -143,7 +143,7 @@
     // }
     auto* param = Param("a", ty.pointer(ty.f32(), ast::StorageClass::kFunction));
     auto* expr_param = Expr(param);
-    auto* let = Let("b", nullptr, expr_param);
+    auto* let = Let("b", expr_param);
     auto* expr_let = Expr("b");
     Func("foo", utils::Vector{param}, ty.void_(),
          utils::Vector{WrapInStatement(let), WrapInStatement(expr_let)});
@@ -160,9 +160,9 @@
     //   var a : f32;
     //   var b = a;
     // }
-    auto* a = Var("a", ty.f32(), ast::StorageClass::kNone);
+    auto* a = Var("a", ty.f32());
     auto* expr_a = Expr(a);
-    auto* b = Var("b", ty.f32(), ast::StorageClass::kNone, expr_a);
+    auto* b = Var("b", ty.f32(), expr_a);
     auto* expr_b = Expr(b);
     WrapInFunction(a, b, expr_b);
 
@@ -179,7 +179,7 @@
     //   var a : f32;
     //   let b = a;
     // }
-    auto* a = Var("a", ty.f32(), ast::StorageClass::kNone);
+    auto* a = Var("a", ty.f32());
     auto* expr_a = Expr(a);
     auto* b = Let("b", ty.f32(), expr_a);
     auto* expr_b = Expr(b);
@@ -235,10 +235,10 @@
     auto* address_of_1 = AddressOf(a);
     auto* deref_1 = Deref(address_of_1);
     auto* address_of_2 = AddressOf(deref_1);
-    auto* a_ptr1 = Let("a_ptr1", nullptr, address_of_2);
+    auto* a_ptr1 = Let("a_ptr1", address_of_2);
     auto* deref_2 = Deref(a_ptr1);
     auto* address_of_3 = AddressOf(deref_2);
-    auto* a_ptr2 = Let("a_ptr2", nullptr, address_of_3);
+    auto* a_ptr2 = Let("a_ptr2", address_of_3);
     WrapInFunction(a_ptr1, a_ptr2);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -270,7 +270,7 @@
 }
 
 TEST_F(ResolverSourceVariableTest, BinaryExpression) {
-    auto* a = Var("a", ty.f32(), ast::StorageClass::kNone);
+    auto* a = Var("a", ty.f32());
     auto* expr = Add(a, Expr(1_f));
     WrapInFunction(a, expr);
 
@@ -280,7 +280,7 @@
 }
 
 TEST_F(ResolverSourceVariableTest, UnaryExpression) {
-    auto* a = Var("a", ty.f32(), ast::StorageClass::kNone);
+    auto* a = Var("a", ty.f32());
     auto* expr = create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr(a));
     WrapInFunction(a, expr);
 
diff --git a/src/tint/resolver/storage_class_layout_validation_test.cc b/src/tint/resolver/storage_class_layout_validation_test.cc
index c55aece..9c16ec4 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)}),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage, Group(0),
+              Binding(0));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -69,8 +69,8 @@
                   Member(Source{{34, 56}}, "b", ty.f32(), utils::Vector{MemberAlign(4)}),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage, Group(0),
+              Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
+              Binding(0));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -145,8 +145,8 @@
                          utils::Vector{MemberAlign(16)}),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
+              Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
+              Binding(0));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -204,8 +204,8 @@
                          utils::Vector{MemberAlign(16)}),
               });
 
-    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
+              Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{22, 24}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
+              Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{22, 24}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
+              Binding(0));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -336,8 +336,8 @@
                   Member(Source{{78, 90}}, "scalar", ty.i32(), utils::Vector{MemberAlign(16)}),
               });
 
-    GlobalVar(Source{{22, 34}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{22, 34}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
+              Binding(0));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -358,7 +358,7 @@
                                          });
 
     GlobalVar(Source{{78, 90}}, "a", ty.type_name("ScalarPackedAtEndOfVec3"),
-              ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+              ast::StorageClass::kUniform, Group(0), Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
+              Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
+              Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
+              Binding(0));
 
     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, GroupAndBinding(0, 0));
+              ast::StorageClass::kUniform, Group(0), Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
+              Binding(0));
 
     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,
-              GroupAndBinding(0, 0));
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform, Group(0),
+              Binding(0));
 
     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 e1b3dab..ab9c049 100644
--- a/src/tint/resolver/storage_class_validation_test.cc
+++ b/src/tint/resolver/storage_class_validation_test.cc
@@ -29,7 +29,7 @@
 
 TEST_F(ResolverStorageClassValidationTest, GlobalVariableNoStorageClass_Fail) {
     // var g : f32;
-    GlobalVar(Source{{12, 34}}, "g", ty.f32(), ast::StorageClass::kNone);
+    GlobalVar(Source{{12, 34}}, "g", ty.f32());
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -87,11 +87,7 @@
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferBool) {
     // var<storage> g : bool;
-    GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kStorage,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kStorage, Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -105,11 +101,7 @@
     // 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kStorage, Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -125,11 +117,7 @@
     // var<storage> g : f16;
     Enable(ast::Extension::kF16);
 
-    GlobalVar("g", ty.f16(Source{{56, 78}}), ast::StorageClass::kStorage,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("g", ty.f16(Source{{56, 78}}), ast::StorageClass::kStorage, Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -144,11 +132,8 @@
     Enable(ast::Extension::kF16);
 
     auto* a = Alias("a", ty.f16());
-    GlobalVar("g", ty.type_name(Source{{56, 78}}, a->name), ast::StorageClass::kStorage,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("g", ty.type_name(Source{{56, 78}}, a->name), ast::StorageClass::kStorage, Binding(0),
+              Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -161,10 +146,7 @@
     // var<storage> g : vec4<f16>;
     Enable(ast::Extension::kF16);
     GlobalVar("g", ty.vec(Source{{56, 78}}, ty.Of<f16>(), 4u), ast::StorageClass::kStorage,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -180,11 +162,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("g", a, ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -198,11 +176,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::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -219,11 +193,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("g", ty.Of(a2), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
+              Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -234,11 +205,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              ast::StorageClass::kStorage, Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -250,22 +217,15 @@
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferIntScalar) {
     // var<storage> g : i32;
-    GlobalVar(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kStorage,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kStorage, Binding(0), Group(0));
 
     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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kStorage, Binding(0),
+              Group(0));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -274,11 +234,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
+              Group(0));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -297,7 +254,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,
-              GroupAndBinding(0, 0));
+              Group(0), Binding(0));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -305,7 +262,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,
-              GroupAndBinding(0, 0));
+              Group(0), Binding(0));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -313,7 +270,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,
-              GroupAndBinding(0, 0));
+              Group(0), Binding(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -326,10 +283,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              Binding(0), Group(0));
 
     ASSERT_TRUE(r()->Resolve());
 }
@@ -342,10 +296,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              Binding(0), Group(0));
 
     ASSERT_TRUE(r()->Resolve());
 }
@@ -356,11 +307,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{56, 78}}, "svar", ty.Of(s), ast::StorageClass::kUniform, Binding(0),
+              Group(0));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -371,11 +319,7 @@
 
 TEST_F(ResolverStorageClassValidationTest, UniformBufferBool) {
     // var<uniform> g : bool;
-    GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kUniform,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kUniform, Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -389,11 +333,7 @@
     // type a = bool;
     // var<uniform> g : a;
     auto* a = Alias("a", ty.bool_());
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kUniform,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kUniform, Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -409,11 +349,7 @@
     // var<uniform> g : f16;
     Enable(ast::Extension::kF16);
 
-    GlobalVar("g", ty.f16(Source{{56, 78}}), ast::StorageClass::kUniform,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("g", ty.f16(Source{{56, 78}}), ast::StorageClass::kUniform, Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -428,11 +364,8 @@
     Enable(ast::Extension::kF16);
 
     auto* a = Alias("a", ty.f16());
-    GlobalVar("g", ty.type_name(Source{{56, 78}}, a->name), ast::StorageClass::kUniform,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("g", ty.type_name(Source{{56, 78}}, a->name), ast::StorageClass::kUniform, Binding(0),
+              Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -445,10 +378,7 @@
     // var<uniform> g : vec4<f16>;
     Enable(ast::Extension::kF16);
     GlobalVar("g", ty.vec(Source{{56, 78}}, ty.Of<f16>(), 4u), ast::StorageClass::kUniform,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -466,11 +396,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("g", a, ast::StorageClass::kUniform, Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -484,11 +410,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("g", ty.Of(s), ast::StorageClass::kUniform, Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -504,11 +426,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("g", ty.Of(a1), ast::StorageClass::kUniform, Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -519,11 +437,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              ast::StorageClass::kUniform, Binding(0), Group(0));
 
     ASSERT_FALSE(r()->Resolve());
 
@@ -535,22 +449,15 @@
 
 TEST_F(ResolverStorageClassValidationTest, UniformBufferIntScalar) {
     // var<uniform> g : i32;
-    GlobalVar(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kUniform,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kUniform, Binding(0), Group(0));
 
     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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kUniform, Binding(0),
+              Group(0));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -562,11 +469,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform, Binding(0), Group(0));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -575,11 +478,7 @@
     // 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kUniform, Binding(0), Group(0));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -590,11 +489,7 @@
     // 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{56, 78}}, "g", ty.Of(a1), ast::StorageClass::kUniform, Binding(0), Group(0));
 
     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 d58c906..af7f976 100644
--- a/src/tint/resolver/struct_storage_class_use_test.cc
+++ b/src/tint/resolver/struct_storage_class_use_test.cc
@@ -159,16 +159,8 @@
 
 TEST_F(ResolverStorageClassUseTest, StructMultipleStorageClassUses) {
     auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
-    GlobalVar("x", ty.Of(s), ast::StorageClass::kUniform,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
-    GlobalVar("y", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    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));
     WrapInFunction(Var("g", ty.Of(s)));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/tint/resolver/type_constructor_validation_test.cc b/src/tint/resolver/type_constructor_validation_test.cc
index 33c15f3..6d771f2 100644
--- a/src/tint/resolver/type_constructor_validation_test.cc
+++ b/src/tint/resolver/type_constructor_validation_test.cc
@@ -59,8 +59,8 @@
 TEST_F(ResolverTypeConstructorValidationTest, InferTypeTest_Simple) {
     // var a = 1i;
     // var b = a;
-    auto* a = Var("a", nullptr, ast::StorageClass::kNone, Expr(1_i));
-    auto* b = Var("b", nullptr, ast::StorageClass::kNone, Expr("a"));
+    auto* a = Var("a", Expr(1_i));
+    auto* b = Var("b", Expr("a"));
     auto* a_ident = Expr("a");
     auto* b_ident = Expr("b");
 
@@ -87,7 +87,7 @@
 
     auto* constructor_expr = params.create_rhs_ast_value(*this, 0);
 
-    auto* a = Var("a", nullptr, ast::StorageClass::kNone, constructor_expr);
+    auto* a = Var("a", constructor_expr);
     // Self-assign 'a' to force the expression to be resolved so we can test its
     // type below
     auto* a_ident = Expr("a");
@@ -141,7 +141,7 @@
     auto* arith_rhs_expr = params.create_rhs_ast_value(*this, 3);
     auto* constructor_expr = Mul(arith_lhs_expr, arith_rhs_expr);
 
-    auto* a = Var("a", nullptr, constructor_expr);
+    auto* a = Var("a", constructor_expr);
     // Self-assign 'a' to force the expression to be resolved so we can test its
     // type below
     auto* a_ident = Expr("a");
@@ -189,7 +189,7 @@
     Func("foo", utils::Empty, params.create_rhs_ast_type(*this),
          utils::Vector{Return(Construct(params.create_rhs_ast_type(*this)))}, {});
 
-    auto* a = Var("a", nullptr, Call("foo"));
+    auto* a = Var("a", Call("foo"));
     // Self-assign 'a' to force the expression to be resolved so we can test its
     // type below
     auto* a_ident = Expr("a");
@@ -355,7 +355,7 @@
 
     auto* arg = Construct(rhs_type, rhs_value_expr);
     auto* tc = Construct(lhs_type2, arg);
-    auto* a = Var("a", lhs_type1, ast::StorageClass::kNone, tc);
+    auto* a = Var("a", lhs_type1, tc);
 
     // Self-assign 'a' to force the expression to be resolved so we can test its
     // type below
@@ -448,8 +448,7 @@
 
     Enable(ast::Extension::kF16);
 
-    auto* a = Var("a", lhs_type1, ast::StorageClass::kNone,
-                  Construct(lhs_type2, Construct(rhs_type, rhs_value_expr)));
+    auto* a = Var("a", lhs_type1, Construct(lhs_type2, Construct(rhs_type, rhs_value_expr)));
 
     // Self-assign 'a' to force the expression to be resolved so we can test its
     // type below
@@ -464,8 +463,7 @@
                                           testing::ValuesIn(all_types)));
 
 TEST_F(ResolverTypeConstructorValidationTest, ConversionConstructorInvalid_TooManyInitializers) {
-    auto* a = Var("a", ty.f32(), ast::StorageClass::kNone,
-                  Construct(Source{{12, 34}}, ty.f32(), Expr(1_f), Expr(2_f)));
+    auto* a = Var("a", ty.f32(), Construct(Source{{12, 34}}, ty.f32(), Expr(1_f), Expr(2_f)));
     WrapInFunction(a);
 
     ASSERT_FALSE(r()->Resolve());
@@ -473,8 +471,8 @@
 }
 
 TEST_F(ResolverTypeConstructorValidationTest, ConversionConstructorInvalid_InvalidInitializer) {
-    auto* a = Var("a", ty.f32(), ast::StorageClass::kNone,
-                  Construct(Source{{12, 34}}, ty.f32(), Construct(ty.array<f32, 4>())));
+    auto* a =
+        Var("a", ty.f32(), Construct(Source{{12, 34}}, ty.f32(), Construct(ty.array<f32, 4>())));
     WrapInFunction(a);
 
     ASSERT_FALSE(r()->Resolve());
@@ -600,7 +598,7 @@
 TEST_F(ResolverTypeConstructorValidationTest, InferredArray_AIAIAI) {
     // const c = array(0, 10, 20);
     auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 0_a, 10_a, 20_a);
-    WrapInFunction(Decl(Const("C", nullptr, tc)));
+    WrapInFunction(Decl(Const("C", tc)));
 
     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 d7c2e64..2575002 100644
--- a/src/tint/resolver/type_validation_test.cc
+++ b/src/tint/resolver/type_validation_test.cc
@@ -60,7 +60,7 @@
     // var a :i32;
     // a = 2;
     // }
-    auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, nullptr);
+    auto* var = Var("a", ty.i32());
     auto* lhs = Expr("a");
     auto* rhs = Expr(2_i);
 
@@ -75,10 +75,7 @@
 
 TEST_F(ResolverTypeValidationTest, GlobalOverrideNoConstructor_Pass) {
     // @id(0) override a :i32;
-    Override(Source{{12, 34}}, "a", ty.i32(), nullptr,
-             utils::Vector{
-                 Id(0),
-             });
+    Override(Source{{12, 34}}, "a", ty.i32(), Id(0));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -116,7 +113,7 @@
 
     Func("my_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2_f))),
+             Decl(Var("a", ty.f32(), Expr(2_f))),
          });
 
     GlobalVar("a", ty.f32(), ast::StorageClass::kPrivate, Expr(2.1_f));
@@ -129,12 +126,12 @@
     // if (true) { var a : f32 = 2.0; }
     // var a : f32 = 3.14;
     // }
-    auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2_f));
+    auto* var = Var("a", ty.f32(), Expr(2_f));
 
     auto* cond = Expr(true);
     auto* body = Block(Decl(var));
 
-    auto* var_a_float = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(3.1_f));
+    auto* var_a_float = Var("a", ty.f32(), Expr(3.1_f));
 
     auto* outer_body = Block(If(cond, body), Decl(Source{{12, 34}}, var_a_float));
 
@@ -148,10 +145,10 @@
     //  { var a : f32; }
     //  var a : f32;
     // }
-    auto* var_inner = Var("a", ty.f32(), ast::StorageClass::kNone);
+    auto* var_inner = Var("a", ty.f32());
     auto* inner = Block(Decl(Source{{12, 34}}, var_inner));
 
-    auto* var_outer = Var("a", ty.f32(), ast::StorageClass::kNone);
+    auto* var_outer = Var("a", ty.f32());
     auto* outer_body = Block(inner, Decl(var_outer));
 
     WrapInFunction(outer_body);
@@ -162,9 +159,9 @@
 TEST_F(ResolverTypeValidationTest, RedeclaredIdentifierDifferentFunctions_Pass) {
     // func0 { var a : f32 = 2.0; return; }
     // func1 { var a : f32 = 3.0; return; }
-    auto* var0 = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2_f));
+    auto* var0 = Var("a", ty.f32(), Expr(2_f));
 
-    auto* var1 = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(1_f));
+    auto* var1 = Var("a", ty.f32(), Expr(1_f));
 
     Func("func0", utils::Empty, ty.void_(),
          utils::Vector{
@@ -202,7 +199,7 @@
 TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedConst_Pass) {
     // const size = 4u;
     // var<private> a : array<f32, size>;
-    GlobalConst("size", nullptr, Expr(4_u));
+    GlobalConst("size", Expr(4_u));
     GlobalVar("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")), ast::StorageClass::kPrivate);
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -210,7 +207,7 @@
 TEST_F(ResolverTypeValidationTest, ArraySize_SignedConst_Pass) {
     // const size = 4i;
     // var<private> a : array<f32, size>;
-    GlobalConst("size", nullptr, Expr(4_i));
+    GlobalConst("size", Expr(4_i));
     GlobalVar("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")), ast::StorageClass::kPrivate);
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -246,7 +243,7 @@
 TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedConst_Zero) {
     // const size = 0u;
     // var<private> a : array<f32, size>;
-    GlobalConst("size", nullptr, Expr(0_u));
+    GlobalConst("size", Expr(0_u));
     GlobalVar("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")), ast::StorageClass::kPrivate);
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: array size (0) must be greater than 0");
@@ -255,7 +252,7 @@
 TEST_F(ResolverTypeValidationTest, ArraySize_SignedConst_Zero) {
     // const size = 0i;
     // var<private> a : array<f32, size>;
-    GlobalConst("size", nullptr, Expr(0_i));
+    GlobalConst("size", Expr(0_i));
     GlobalVar("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")), ast::StorageClass::kPrivate);
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: array size (0) must be greater than 0");
@@ -264,7 +261,7 @@
 TEST_F(ResolverTypeValidationTest, ArraySize_SignedConst_Negative) {
     // const size = -10i;
     // var<private> a : array<f32, size>;
-    GlobalConst("size", nullptr, Expr(-10_i));
+    GlobalConst("size", Expr(-10_i));
     GlobalVar("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")), ast::StorageClass::kPrivate);
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: array size (-10) must be greater than 0");
@@ -292,7 +289,7 @@
 TEST_F(ResolverTypeValidationTest, ArraySize_FloatConst) {
     // const size = 10.0;
     // var<private> a : array<f32, size>;
-    GlobalConst("size", nullptr, Expr(10_f));
+    GlobalConst("size", Expr(10_f));
     GlobalVar("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")), ast::StorageClass::kPrivate);
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -303,7 +300,7 @@
 TEST_F(ResolverTypeValidationTest, ArraySize_IVecConst) {
     // const size = vec2<i32>(100, 100);
     // var<private> a : array<f32, size>;
-    GlobalConst("size", nullptr, Construct(ty.vec2<i32>(), 100_i, 100_i));
+    GlobalConst("size", Construct(ty.vec2<i32>(), 100_i, 100_i));
     GlobalVar("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")), ast::StorageClass::kPrivate);
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -331,7 +328,7 @@
 TEST_F(ResolverTypeValidationTest, ArraySize_Overridable) {
     // override size = 10i;
     // var<private> a : array<f32, size>;
-    Override("size", nullptr, Expr(10_i));
+    Override("size", Expr(10_i));
     GlobalVar("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")), ast::StorageClass::kPrivate);
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -354,7 +351,7 @@
     //   const size = 10;
     //   var a : array<f32, size>;
     // }
-    auto* size = Const("size", nullptr, Expr(10_i));
+    auto* size = Const("size", Expr(10_i));
     auto* a = Var("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")));
     WrapInFunction(size, a);
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -365,7 +362,7 @@
     //   let size = 10;
     //   var a : array<f32, size>;
     // }
-    auto* size = Let("size", nullptr, Expr(10_i));
+    auto* size = Let("size", Expr(10_i));
     auto* a = Var("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")));
     WrapInFunction(size, a);
     EXPECT_FALSE(r()->Resolve());
@@ -384,7 +381,7 @@
     /// @vertex
     // fn func() { var a : array<i32>; }
 
-    auto* var = Var(Source{{12, 34}}, "a", ty.array<i32>(), ast::StorageClass::kNone);
+    auto* var = Var(Source{{12, 34}}, "a", ty.array<i32>());
 
     Func("func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -763,11 +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()),
-              ast::StorageClass::kNone, nullptr,
-              utils::Vector{
-                  GroupAndBinding(0, 0),
-              });
+    GlobalVar(Source{{12, 34}}, "a", ty.sampled_texture(params.dim, ty.i32()), Group(0),
+              Binding(0));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -784,11 +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()),
-              ast::StorageClass::kNone, nullptr,
-              utils::Vector{
-                  GroupAndBinding(0, 0),
-              });
+    GlobalVar("a", ty.multisampled_texture(Source{{12, 34}}, params.dim, ty.i32()), Group(0),
+              Binding(0));
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -841,10 +832,7 @@
     GlobalVar(
         "a",
         ty.sampled_texture(Source{{12, 34}}, ast::TextureDimension::k2d, params.type_func(*this)),
-        ast::StorageClass::kNone, nullptr,
-        utils::Vector{
-            GroupAndBinding(0, 0),
-        });
+        Group(0), Binding(0));
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -863,10 +851,7 @@
     GlobalVar("a",
               ty.multisampled_texture(Source{{12, 34}}, ast::TextureDimension::k2d,
                                       params.type_func(*this)),
-              ast::StorageClass::kNone, nullptr,
-              utils::Vector{
-                  GroupAndBinding(0, 0),
-              });
+              Group(0), Binding(0));
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -905,10 +890,7 @@
     auto* st = ty.storage_texture(Source{{12, 34}}, params.dim, ast::TexelFormat::kR32Uint,
                                   ast::Access::kWrite);
 
-    GlobalVar("a", st, ast::StorageClass::kNone,
-              utils::Vector{
-                  GroupAndBinding(0, 0),
-              });
+    GlobalVar("a", st, Group(0), Binding(0));
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -958,29 +940,17 @@
 
     auto* st_a = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d, params.format,
                                     ast::Access::kWrite);
-    GlobalVar("a", st_a, ast::StorageClass::kNone,
-              utils::Vector{
-                  GroupAndBinding(0, 0),
-              });
+    GlobalVar("a", st_a, Group(0), Binding(0));
 
     auto* st_b = ty.storage_texture(ast::TextureDimension::k2d, params.format, ast::Access::kWrite);
-    GlobalVar("b", st_b, ast::StorageClass::kNone,
-              utils::Vector{
-                  GroupAndBinding(0, 1),
-              });
+    GlobalVar("b", st_b, Group(0), Binding(1));
 
     auto* st_c =
         ty.storage_texture(ast::TextureDimension::k2dArray, params.format, ast::Access::kWrite);
-    GlobalVar("c", st_c, ast::StorageClass::kNone,
-              utils::Vector{
-                  GroupAndBinding(0, 2),
-              });
+    GlobalVar("c", st_c, Group(0), Binding(2));
 
     auto* st_d = ty.storage_texture(ast::TextureDimension::k3d, params.format, ast::Access::kWrite);
-    GlobalVar("d", st_d, ast::StorageClass::kNone,
-              utils::Vector{
-                  GroupAndBinding(0, 3),
-              });
+    GlobalVar("d", st_d, Group(0), Binding(3));
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -1004,10 +974,7 @@
     auto* st = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
                                   ast::TexelFormat::kR32Uint, ast::Access::kUndefined);
 
-    GlobalVar("a", st, ast::StorageClass::kNone,
-              utils::Vector{
-                  GroupAndBinding(0, 0),
-              });
+    GlobalVar("a", st, Group(0), Binding(0));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: storage texture missing access control");
@@ -1020,10 +987,7 @@
     auto* st = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
                                   ast::TexelFormat::kR32Uint, ast::Access::kReadWrite);
 
-    GlobalVar("a", st, ast::StorageClass::kNone, nullptr,
-              utils::Vector{
-                  GroupAndBinding(0, 0),
-              });
+    GlobalVar("a", st, Group(0), Binding(0));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1037,10 +1001,7 @@
     auto* st = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
                                   ast::TexelFormat::kR32Uint, ast::Access::kRead);
 
-    GlobalVar("a", st, ast::StorageClass::kNone, nullptr,
-              utils::Vector{
-                  GroupAndBinding(0, 0),
-              });
+    GlobalVar("a", st, Group(0), Binding(0));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1054,10 +1015,7 @@
     auto* st = ty.storage_texture(ast::TextureDimension::k1d, ast::TexelFormat::kR32Uint,
                                   ast::Access::kWrite);
 
-    GlobalVar("a", st, ast::StorageClass::kNone, nullptr,
-              utils::Vector{
-                  GroupAndBinding(0, 0),
-              });
+    GlobalVar("a", st, Group(0), Binding(0));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
diff --git a/src/tint/resolver/uniformity_test.cc b/src/tint/resolver/uniformity_test.cc
index bbf7a4b..e2e2796 100644
--- a/src/tint/resolver/uniformity_test.cc
+++ b/src/tint/resolver/uniformity_test.cc
@@ -5289,7 +5289,7 @@
     for (int i = 1; i < 255; i++) {
         rhs_init = b.Add(rhs_init, b.Deref("p" + std::to_string(i)));
     }
-    foo_body.Push(b.Decl(b.Let("rhs", nullptr, rhs_init)));
+    foo_body.Push(b.Decl(b.Let("rhs", rhs_init)));
     for (int i = 0; i < 255; i++) {
         params.Push(
             b.Param("p" + std::to_string(i), ty.pointer(ty.i32(), ast::StorageClass::kFunction)));
@@ -6526,7 +6526,7 @@
     std::string v_last = "v0";
     for (int i = 1; i < 100000; i++) {
         auto v = "v" + std::to_string(i);
-        foo_body.Push(b.Decl(b.Var(v, nullptr, b.Expr(v_last))));
+        foo_body.Push(b.Decl(b.Var(v, b.Expr(v_last))));
         v_last = v;
     }
     foo_body.Push(b.If(b.Equal(v_last, 0_i), b.Block(b.CallStmt(b.Call("workgroupBarrier")))));
diff --git a/src/tint/resolver/validation_test.cc b/src/tint/resolver/validation_test.cc
index 52d4c87..623ff1c 100644
--- a/src/tint/resolver/validation_test.cc
+++ b/src/tint/resolver/validation_test.cc
@@ -242,7 +242,7 @@
     //   if (true) { var a : f32 = 2.0; }
     //   a = 3.14;
     // }
-    auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2_f));
+    auto* var = Var("a", ty.f32(), Expr(2_f));
 
     auto* cond = Expr(true);
     auto* body = Block(Decl(var));
@@ -264,7 +264,7 @@
     //   var a : f32 = 2.0;
     //   if (true) { a = 3.14; }
     // }
-    auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2_f));
+    auto* var = Var("a", ty.f32(), Expr(2_f));
 
     auto* lhs = Expr(Source{{12, 34}}, "a");
     auto* rhs = Expr(3.14_f);
@@ -284,7 +284,7 @@
     //  { var a : f32 = 2.0; }
     //  { a = 3.14; }
     // }
-    auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2_f));
+    auto* var = Var("a", ty.f32(), Expr(2_f));
     auto* first_body = Block(Decl(var));
 
     auto* lhs = Expr(Source{{12, 34}}, "a");
@@ -329,11 +329,7 @@
 
 TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) {
     auto* t = ty.sampler(ast::SamplerKind::kSampler);
-    GlobalVar(Source{{12, 34}}, "var", t, ast::StorageClass::kHandle,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{12, 34}}, "var", t, ast::StorageClass::kHandle, Binding(0), Group(0));
 
     EXPECT_FALSE(r()->Resolve());
 
@@ -343,11 +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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar(Source{{12, 34}}, "var", t, ast::StorageClass::kHandle, Binding(0), Group(0));
 
     EXPECT_FALSE(r()->Resolve()) << r()->error();
 
@@ -472,7 +464,7 @@
     // }
 
     auto error_loc = Source{{12, 34}};
-    auto* body = Block(Continue(), Decl(error_loc, Var("z", ty.i32(), ast::StorageClass::kNone)));
+    auto* body = Block(Continue(), Decl(error_loc, Var("z", ty.i32())));
     auto* continuing = Block(Assign(Expr("z"), 2_i));
     auto* loop_stmt = Loop(body, continuing);
     WrapInFunction(loop_stmt);
@@ -497,9 +489,8 @@
     //     }
     // }
 
-    auto* body =
-        Block(If(false, Block(Break())),  //
-              Decl(Var("z", ty.i32(), ast::StorageClass::kNone)), Block(Block(Block(Continue()))));
+    auto* body = Block(If(false, Block(Break())),  //
+                       Decl(Var("z", ty.i32())), Block(Block(Block(Continue()))));
     auto* continuing = Block(Assign(Expr("z"), 2_i));
     auto* loop_stmt = Loop(body, continuing);
     WrapInFunction(loop_stmt);
@@ -521,8 +512,8 @@
     auto cont_loc = Source{{12, 34}};
     auto decl_loc = Source{{56, 78}};
     auto ref_loc = Source{{90, 12}};
-    auto* body = Block(If(Expr(true), Block(Continue(cont_loc))),
-                       Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
+    auto* body =
+        Block(If(Expr(true), Block(Continue(cont_loc))), Decl(Var(decl_loc, "z", ty.i32())));
     auto* continuing = Block(Assign(Expr(ref_loc, "z"), 2_i));
     auto* loop_stmt = Loop(body, continuing);
     WrapInFunction(loop_stmt);
@@ -551,8 +542,8 @@
     auto cont_loc = Source{{12, 34}};
     auto decl_loc = Source{{56, 78}};
     auto ref_loc = Source{{90, 12}};
-    auto* body = Block(If(Expr(true), Block(Continue(cont_loc))),
-                       Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
+    auto* body =
+        Block(If(Expr(true), Block(Continue(cont_loc))), Decl(Var(decl_loc, "z", ty.i32())));
 
     auto* continuing = Block(If(Expr(true), Block(Assign(Expr(ref_loc, "z"), 2_i))));
     auto* loop_stmt = Loop(body, continuing);
@@ -582,8 +573,8 @@
     auto cont_loc = Source{{12, 34}};
     auto decl_loc = Source{{56, 78}};
     auto ref_loc = Source{{90, 12}};
-    auto* body = Block(If(Expr(true), Block(Continue(cont_loc))),
-                       Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
+    auto* body =
+        Block(If(Expr(true), Block(Continue(cont_loc))), Decl(Var(decl_loc, "z", ty.i32())));
     auto* compare =
         create<ast::BinaryExpression>(ast::BinaryOp::kLessThan, Expr(ref_loc, "z"), Expr(2_i));
     auto* continuing = Block(If(compare, Block()));
@@ -614,8 +605,8 @@
     auto cont_loc = Source{{12, 34}};
     auto decl_loc = Source{{56, 78}};
     auto ref_loc = Source{{90, 12}};
-    auto* body = Block(If(Expr(true), Block(Continue(cont_loc))),
-                       Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
+    auto* body =
+        Block(If(Expr(true), Block(Continue(cont_loc))), Decl(Var(decl_loc, "z", ty.i32())));
 
     auto* continuing = Block(Loop(Block(Assign(Expr(ref_loc, "z"), 2_i))));
     auto* loop_stmt = Loop(body, continuing);
@@ -644,8 +635,8 @@
     auto* inner_loop = Loop(Block(    //
         If(true, Block(Continue())),  //
         Break()));
-    auto* body = Block(inner_loop,                                          //
-                       Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),  //
+    auto* body = Block(inner_loop,                //
+                       Decl(Var("z", ty.i32())),  //
                        Break());
     auto* continuing = Block(Assign("z", 2_i));
     auto* loop_stmt = Loop(body, continuing);
@@ -672,8 +663,8 @@
 
     auto* inner_loop = Loop(Block(If(true, Block(Continue())),  //
                                   Break()));
-    auto* body = Block(inner_loop,                                          //
-                       Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),  //
+    auto* body = Block(inner_loop,                //
+                       Decl(Var("z", ty.i32())),  //
                        Break());
     auto* continuing = Block(If(Expr(true), Block(Assign("z", 2_i))));
     auto* loop_stmt = Loop(body, continuing);
@@ -700,8 +691,8 @@
 
     auto* inner_loop = Loop(Block(If(true, Block(Continue())),  //
                                   Break()));
-    auto* body = Block(inner_loop,                                          //
-                       Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),  //
+    auto* body = Block(inner_loop,                //
+                       Decl(Var("z", ty.i32())),  //
                        Break());
     auto* continuing = Block(Loop(Block(Assign("z", 2_i),  //
                                         Break())));
@@ -722,9 +713,8 @@
     // }
 
     auto error_loc = Source{{12, 34}};
-    auto* body =
-        Block(Decl(Var("z", ty.i32(), ast::StorageClass::kNone)), If(true, Block(Continue())),  //
-              Break());
+    auto* body = Block(Decl(Var("z", ty.i32())), If(true, Block(Continue())),  //
+                       Break());
     auto* continuing = Block(Assign(Expr(error_loc, "z"), 2_i));
     auto* loop_stmt = Loop(body, continuing);
     WrapInFunction(loop_stmt);
diff --git a/src/tint/resolver/variable_test.cc b/src/tint/resolver/variable_test.cc
index cc4bd65..02c37d5 100644
--- a/src/tint/resolver/variable_test.cc
+++ b/src/tint/resolver/variable_test.cc
@@ -46,13 +46,13 @@
     auto* S = Structure("S", utils::Vector{Member("i", ty.i32())});
     auto* A = Alias("A", ty.Of(S));
 
-    auto* i = Var("i", ty.i32(), ast::StorageClass::kNone);
-    auto* u = Var("u", ty.u32(), ast::StorageClass::kNone);
-    auto* f = Var("f", ty.f32(), ast::StorageClass::kNone);
-    auto* h = Var("h", ty.f16(), ast::StorageClass::kNone);
-    auto* b = Var("b", ty.bool_(), ast::StorageClass::kNone);
-    auto* s = Var("s", ty.Of(S), ast::StorageClass::kNone);
-    auto* a = Var("a", ty.Of(A), ast::StorageClass::kNone);
+    auto* i = Var("i", ty.i32());
+    auto* u = Var("u", ty.u32());
+    auto* f = Var("f", ty.f32());
+    auto* h = Var("h", ty.f16());
+    auto* b = Var("b", ty.bool_());
+    auto* s = Var("s", ty.Of(S));
+    auto* a = Var("a", ty.Of(A));
 
     Func("F", utils::Empty, ty.void_(),
          utils::Vector{
@@ -119,13 +119,13 @@
     auto* s_c = Construct(ty.Of(S), Expr(1_i));
     auto* a_c = Construct(ty.Of(A), Expr(1_i));
 
-    auto* i = Var("i", ty.i32(), ast::StorageClass::kNone, i_c);
-    auto* u = Var("u", ty.u32(), ast::StorageClass::kNone, u_c);
-    auto* f = Var("f", ty.f32(), ast::StorageClass::kNone, f_c);
-    auto* h = Var("h", ty.f16(), ast::StorageClass::kNone, h_c);
-    auto* b = Var("b", ty.bool_(), ast::StorageClass::kNone, b_c);
-    auto* s = Var("s", ty.Of(S), ast::StorageClass::kNone, s_c);
-    auto* a = Var("a", ty.Of(A), ast::StorageClass::kNone, a_c);
+    auto* i = Var("i", ty.i32(), i_c);
+    auto* u = Var("u", ty.u32(), u_c);
+    auto* f = Var("f", ty.f32(), f_c);
+    auto* h = Var("h", ty.f16(), h_c);
+    auto* b = Var("b", ty.bool_(), b_c);
+    auto* s = Var("s", ty.Of(S), s_c);
+    auto* a = Var("a", ty.Of(A), a_c);
 
     Func("F", utils::Empty, ty.void_(),
          utils::Vector{
@@ -181,7 +181,7 @@
     // }
 
     auto* t = Alias("a", ty.i32());
-    auto* v = Var("a", nullptr, Expr(false));
+    auto* v = Var("a", Expr(false));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(v)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -202,7 +202,7 @@
     // }
 
     auto* t = Structure("a", utils::Vector{Member("m", ty.i32())});
-    auto* v = Var("a", nullptr, Expr(false));
+    auto* v = Var("a", Expr(false));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(v)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -218,7 +218,7 @@
     //   var a = true;
     // }
 
-    auto* v = Var("a", nullptr, Expr(false));
+    auto* v = Var("a", Expr(false));
     auto* f = Func("a", utils::Empty, ty.void_(), utils::Vector{Decl(v)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -239,7 +239,7 @@
     // }
 
     auto* g = GlobalVar("a", ty.i32(), ast::StorageClass::kPrivate);
-    auto* v = Var("a", nullptr, Expr("a"));
+    auto* v = Var("a", Expr("a"));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(v)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -262,7 +262,7 @@
     // }
 
     auto* g = GlobalConst("a", ty.i32(), Expr(1_i));
-    auto* v = Var("a", nullptr, Expr("a"));
+    auto* v = Var("a", Expr("a"));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(v)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -286,7 +286,7 @@
     // }
 
     auto* x = Var("a", ty.i32(), Expr(1_i));
-    auto* y = Var("a", nullptr, Expr("a"));
+    auto* y = Var("a", Expr("a"));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(x), Block(Decl(y))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -312,7 +312,7 @@
     // }
 
     auto* c = Const("a", ty.i32(), Expr(1_i));
-    auto* v = Var("a", nullptr, Expr("a"));
+    auto* v = Var("a", Expr("a"));
     Func("X", utils::Empty, ty.void_(), utils::Vector{Decl(c), Block(Decl(v))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -338,7 +338,7 @@
     // }
 
     auto* l = Let("a", ty.i32(), Expr(1_i));
-    auto* v = Var("a", nullptr, Expr("a"));
+    auto* v = Var("a", Expr("a"));
     Func("X", utils::Empty, ty.void_(), utils::Vector{Decl(l), Block(Decl(v))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -363,7 +363,7 @@
     // }
 
     auto* p = Param("a", ty.i32());
-    auto* v = Var("a", nullptr, Expr("a"));
+    auto* v = Var("a", Expr("a"));
     Func("X", utils::Vector{p}, ty.void_(), utils::Vector{Block(Decl(v))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -401,7 +401,7 @@
 
     auto* S = Structure("S", utils::Vector{Member("i", ty.i32())});
     auto* A = Alias("A", ty.Of(S));
-    auto* v = Var("v", ty.i32(), ast::StorageClass::kNone);
+    auto* v = Var("v", ty.i32());
 
     auto* i_c = Expr(1_i);
     auto* u_c = Expr(1_u);
@@ -471,13 +471,10 @@
     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,
-                              utils::Vector{
-                                  create<ast::BindingAttribute>(0u),
-                                  create<ast::GroupAttribute>(0u),
-                              });
+                              Binding(0), Group(0));
 
     auto* expr = IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4_i);
-    auto* ptr = Let("p", nullptr, AddressOf(expr));
+    auto* ptr = Let("p", AddressOf(expr));
 
     WrapInFunction(ptr);
 
@@ -498,7 +495,7 @@
     // }
 
     auto* t = Alias("a", ty.i32());
-    auto* l = Let("a", nullptr, Expr(false));
+    auto* l = Let("a", Expr(false));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(l)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -519,7 +516,7 @@
     // }
 
     auto* t = Structure("a", utils::Vector{Member("m", ty.i32())});
-    auto* l = Let("a", nullptr, Expr(false));
+    auto* l = Let("a", Expr(false));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(l)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -535,7 +532,7 @@
     //   let a = false;
     // }
 
-    auto* l = Let("a", nullptr, Expr(false));
+    auto* l = Let("a", Expr(false));
     auto* fb = Func("a", utils::Empty, ty.void_(), utils::Vector{Decl(l)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -556,7 +553,7 @@
     // }
 
     auto* g = GlobalVar("a", ty.i32(), ast::StorageClass::kPrivate);
-    auto* l = Let("a", nullptr, Expr("a"));
+    auto* l = Let("a", Expr("a"));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(l)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -579,7 +576,7 @@
     // }
 
     auto* g = GlobalConst("a", ty.i32(), Expr(1_i));
-    auto* l = Let("a", nullptr, Expr("a"));
+    auto* l = Let("a", Expr("a"));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(l)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -603,7 +600,7 @@
     // }
 
     auto* v = Var("a", ty.i32(), Expr(1_i));
-    auto* l = Let("a", nullptr, Expr("a"));
+    auto* l = Let("a", Expr("a"));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(v), Block(Decl(l))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -629,7 +626,7 @@
     // }
 
     auto* x = Const("a", ty.i32(), Expr(1_i));
-    auto* y = Let("a", nullptr, Expr("a"));
+    auto* y = Let("a", Expr("a"));
     Func("X", utils::Empty, ty.void_(), utils::Vector{Decl(x), Block(Decl(y))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -655,7 +652,7 @@
     // }
 
     auto* x = Let("a", ty.i32(), Expr(1_i));
-    auto* y = Let("a", nullptr, Expr("a"));
+    auto* y = Let("a", Expr("a"));
     Func("X", utils::Empty, ty.void_(), utils::Vector{Decl(x), Block(Decl(y))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -680,7 +677,7 @@
     // }
 
     auto* p = Param("a", ty.i32());
-    auto* l = Let("a", nullptr, Expr("a"));
+    auto* l = Let("a", Expr("a"));
     Func("X", utils::Vector{p}, ty.void_(), utils::Vector{Block(Decl(l))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -708,7 +705,7 @@
     // }
 
     auto* t = Alias("a", ty.i32());
-    auto* c = Const("a", nullptr, Expr(false));
+    auto* c = Const("a", Expr(false));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(c)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -729,7 +726,7 @@
     // }
 
     auto* t = Structure("a", utils::Vector{Member("m", ty.i32())});
-    auto* c = Const("a", nullptr, Expr(false));
+    auto* c = Const("a", Expr(false));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(c)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -745,7 +742,7 @@
     //   const a = false;
     // }
 
-    auto* c = Const("a", nullptr, Expr(false));
+    auto* c = Const("a", Expr(false));
     auto* fb = Func("a", utils::Empty, ty.void_(), utils::Vector{Decl(c)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -766,7 +763,7 @@
     // }
 
     auto* g = GlobalVar("a", ty.i32(), ast::StorageClass::kPrivate);
-    auto* c = Const("a", nullptr, Expr(1_i));
+    auto* c = Const("a", Expr(1_i));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(c)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -785,7 +782,7 @@
     // }
 
     auto* g = GlobalConst("a", ty.i32(), Expr(1_i));
-    auto* c = Const("a", nullptr, Expr("a"));
+    auto* c = Const("a", Expr("a"));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(c)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -809,7 +806,7 @@
     // }
 
     auto* v = Var("a", ty.i32(), Expr(1_i));
-    auto* c = Const("a", nullptr, Expr(1_i));
+    auto* c = Const("a", Expr(1_i));
     Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(v), Block(Decl(c))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -831,7 +828,7 @@
     // }
 
     auto* x = Const("a", ty.i32(), Expr(1_i));
-    auto* y = Const("a", nullptr, Expr("a"));
+    auto* y = Const("a", Expr("a"));
     Func("X", utils::Empty, ty.void_(), utils::Vector{Decl(x), Block(Decl(y))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -857,7 +854,7 @@
     // }
 
     auto* l = Let("a", ty.i32(), Expr(1_i));
-    auto* c = Const("a", nullptr, Expr(1_i));
+    auto* c = Const("a", Expr(1_i));
     Func("X", utils::Empty, ty.void_(), utils::Vector{Decl(l), Block(Decl(c))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -878,7 +875,7 @@
     // }
 
     auto* p = Param("a", ty.i32());
-    auto* c = Const("a", nullptr, Expr(1_i));
+    auto* c = Const("a", Expr(1_i));
     Func("X", utils::Vector{p}, ty.void_(), utils::Vector{Block(Decl(c))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -938,23 +935,22 @@
 TEST_F(ResolverVariableTest, LocalConst_ImplicitType_Decls) {
     Structure("S", utils::Vector{Member("m", ty.u32())});
 
-    auto* c_i32 = Const("a", nullptr, Expr(0_i));
-    auto* c_u32 = Const("b", nullptr, Expr(0_u));
-    auto* c_f32 = Const("c", nullptr, Expr(0_f));
-    auto* c_ai = Const("d", nullptr, Expr(0_a));
-    auto* c_af = Const("e", nullptr, Expr(0._a));
-    auto* c_vi32 = Const("f", nullptr, vec3<i32>());
-    auto* c_vu32 = Const("g", nullptr, vec3<u32>());
-    auto* c_vf32 = Const("h", nullptr, vec3<f32>());
-    auto* c_vai = Const("i", nullptr, Construct(ty.vec(nullptr, 3), Expr(0_a)));
-    auto* c_vaf = Const("j", nullptr, Construct(ty.vec(nullptr, 3), Expr(0._a)));
-    auto* c_mf32 = Const("k", nullptr, mat3x3<f32>());
-    auto* c_maf32 = Const("l", nullptr,
-                          Construct(ty.mat(nullptr, 3, 3),  //
-                                    Construct(ty.vec(nullptr, 3), Expr(0._a)),
-                                    Construct(ty.vec(nullptr, 3), Expr(0._a)),
-                                    Construct(ty.vec(nullptr, 3), Expr(0._a))));
-    auto* c_s = Const("m", nullptr, Construct(ty.type_name("S")));
+    auto* c_i32 = Const("a", Expr(0_i));
+    auto* c_u32 = Const("b", Expr(0_u));
+    auto* c_f32 = Const("c", Expr(0_f));
+    auto* c_ai = Const("d", Expr(0_a));
+    auto* c_af = Const("e", Expr(0._a));
+    auto* c_vi32 = Const("f", vec3<i32>());
+    auto* c_vu32 = Const("g", vec3<u32>());
+    auto* c_vf32 = Const("h", vec3<f32>());
+    auto* c_vai = Const("i", Construct(ty.vec(nullptr, 3), Expr(0_a)));
+    auto* c_vaf = Const("j", Construct(ty.vec(nullptr, 3), Expr(0._a)));
+    auto* c_mf32 = Const("k", mat3x3<f32>());
+    auto* c_maf32 = Const("l", Construct(ty.mat(nullptr, 3, 3),  //
+                                         Construct(ty.vec(nullptr, 3), Expr(0._a)),
+                                         Construct(ty.vec(nullptr, 3), Expr(0._a)),
+                                         Construct(ty.vec(nullptr, 3), Expr(0._a))));
+    auto* c_s = Const("m", Construct(ty.type_name("S")));
 
     WrapInFunction(c_i32, c_u32, c_f32, c_ai, c_af, c_vi32, c_vu32, c_vf32, c_vai, c_vaf, c_mf32,
                    c_maf32, c_s);
@@ -1005,9 +1001,9 @@
 }
 
 TEST_F(ResolverVariableTest, LocalConst_PropagateConstValue) {
-    auto* a = Const("a", nullptr, Expr(42_i));
-    auto* b = Const("b", nullptr, Expr("a"));
-    auto* c = Const("c", nullptr, Expr("b"));
+    auto* a = Const("a", Expr(42_i));
+    auto* b = Const("b", Expr("a"));
+    auto* c = Const("c", Expr("b"));
 
     WrapInFunction(a, b, c);
 
@@ -1020,7 +1016,7 @@
 
 // Enable when we have @const operators implemented
 TEST_F(ResolverVariableTest, DISABLED_LocalConst_ConstEval) {
-    auto* c = Const("c", nullptr, Div(Mul(Add(1_i, 2_i), 3_i), 2_i));
+    auto* c = Const("c", Div(Mul(Add(1_i, 2_i), 3_i), 2_i));
 
     WrapInFunction(c);
 
@@ -1040,21 +1036,10 @@
     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,
-                              utils::Vector{
-                                  create<ast::BindingAttribute>(0u),
-                                  create<ast::GroupAttribute>(0u),
-                              });
-    auto* storage = GlobalVar("sb", ty.Of(buf), ast::StorageClass::kStorage,
-                              utils::Vector{
-                                  create<ast::BindingAttribute>(1u),
-                                  create<ast::GroupAttribute>(0u),
-                              });
-    auto* handle = GlobalVar("h", ty.depth_texture(ast::TextureDimension::k2d),
-                             utils::Vector{
-                                 create<ast::BindingAttribute>(2u),
-                                 create<ast::GroupAttribute>(0u),
-                             });
+    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* handle =
+        GlobalVar("h", ty.depth_texture(ast::TextureDimension::k2d), Binding(2), Group(0));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1075,12 +1060,8 @@
     // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
 
     auto* buf = Structure("S", utils::Vector{Member("m", ty.i32())});
-    auto* storage =
-        GlobalVar("sb", ty.Of(buf), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-                  utils::Vector{
-                      create<ast::BindingAttribute>(1u),
-                      create<ast::GroupAttribute>(0u),
-                  });
+    auto* storage = GlobalVar("sb", ty.Of(buf), ast::StorageClass::kStorage,
+                              ast::Access::kReadWrite, Binding(1), Group(0));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1129,22 +1110,21 @@
 }
 
 TEST_F(ResolverVariableTest, GlobalConst_ImplicitType_Decls) {
-    auto* c_i32 = GlobalConst("a", nullptr, Expr(0_i));
-    auto* c_u32 = GlobalConst("b", nullptr, Expr(0_u));
-    auto* c_f32 = GlobalConst("c", nullptr, Expr(0_f));
-    auto* c_ai = GlobalConst("d", nullptr, Expr(0_a));
-    auto* c_af = GlobalConst("e", nullptr, Expr(0._a));
-    auto* c_vi32 = GlobalConst("f", nullptr, vec3<i32>());
-    auto* c_vu32 = GlobalConst("g", nullptr, vec3<u32>());
-    auto* c_vf32 = GlobalConst("h", nullptr, vec3<f32>());
-    auto* c_vai = GlobalConst("i", nullptr, Construct(ty.vec(nullptr, 3), Expr(0_a)));
-    auto* c_vaf = GlobalConst("j", nullptr, Construct(ty.vec(nullptr, 3), Expr(0._a)));
-    auto* c_mf32 = GlobalConst("k", nullptr, mat3x3<f32>());
-    auto* c_maf32 = GlobalConst("l", nullptr,
-                                Construct(ty.mat(nullptr, 3, 3),  //
-                                          Construct(ty.vec(nullptr, 3), Expr(0._a)),
-                                          Construct(ty.vec(nullptr, 3), Expr(0._a)),
-                                          Construct(ty.vec(nullptr, 3), Expr(0._a))));
+    auto* c_i32 = GlobalConst("a", Expr(0_i));
+    auto* c_u32 = GlobalConst("b", Expr(0_u));
+    auto* c_f32 = GlobalConst("c", Expr(0_f));
+    auto* c_ai = GlobalConst("d", Expr(0_a));
+    auto* c_af = GlobalConst("e", Expr(0._a));
+    auto* c_vi32 = GlobalConst("f", vec3<i32>());
+    auto* c_vu32 = GlobalConst("g", vec3<u32>());
+    auto* c_vf32 = GlobalConst("h", vec3<f32>());
+    auto* c_vai = GlobalConst("i", Construct(ty.vec(nullptr, 3), Expr(0_a)));
+    auto* c_vaf = GlobalConst("j", Construct(ty.vec(nullptr, 3), Expr(0._a)));
+    auto* c_mf32 = GlobalConst("k", mat3x3<f32>());
+    auto* c_maf32 = GlobalConst("l", Construct(ty.mat(nullptr, 3, 3),  //
+                                               Construct(ty.vec(nullptr, 3), Expr(0._a)),
+                                               Construct(ty.vec(nullptr, 3), Expr(0._a)),
+                                               Construct(ty.vec(nullptr, 3), Expr(0._a))));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1189,9 +1169,9 @@
 }
 
 TEST_F(ResolverVariableTest, GlobalConst_PropagateConstValue) {
-    GlobalConst("b", nullptr, Expr("a"));
-    auto* c = GlobalConst("c", nullptr, Expr("b"));
-    GlobalConst("a", nullptr, Expr(42_i));
+    GlobalConst("b", Expr("a"));
+    auto* c = GlobalConst("c", Expr("b"));
+    GlobalConst("a", Expr(42_i));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1202,7 +1182,7 @@
 
 // Enable when we have @const operators implemented
 TEST_F(ResolverVariableTest, DISABLED_GlobalConst_ConstEval) {
-    auto* c = GlobalConst("c", nullptr, Div(Mul(Add(1_i, 2_i), 3_i), 2_i));
+    auto* c = GlobalConst("c", Div(Mul(Add(1_i, 2_i), 3_i), 2_i));
 
     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 97b177e..0fb50a5 100644
--- a/src/tint/resolver/variable_validation_test.cc
+++ b/src/tint/resolver/variable_validation_test.cc
@@ -26,7 +26,7 @@
 
 TEST_F(ResolverVariableValidationTest, VarNoInitializerNoType) {
     // var a;
-    WrapInFunction(Var(Source{{12, 34}}, "a", nullptr));
+    WrapInFunction(Var(Source{{12, 34}}, "a"));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: var declaration requires a type or initializer");
@@ -34,7 +34,7 @@
 
 TEST_F(ResolverVariableValidationTest, GlobalVarNoInitializerNoType) {
     // var a;
-    GlobalVar(Source{{12, 34}}, "a", nullptr);
+    GlobalVar(Source{{12, 34}}, "a");
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: var declaration requires a type or initializer");
@@ -43,7 +43,7 @@
 TEST_F(ResolverVariableValidationTest, VarInitializerNoReturnValueBuiltin) {
     // fn f() { var a = storageBarrier(); }
     auto* NoReturnValueBuiltin = Call(Source{{12, 34}}, "storageBarrier");
-    WrapInFunction(Var("a", nullptr, ast::StorageClass::kNone, NoReturnValueBuiltin));
+    WrapInFunction(Var("a", NoReturnValueBuiltin));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: builtin 'storageBarrier' does not return a value");
@@ -52,7 +52,7 @@
 TEST_F(ResolverVariableValidationTest, GlobalVarInitializerNoReturnValueBuiltin) {
     // var a = storageBarrier();
     auto* NoReturnValueBuiltin = Call(Source{{12, 34}}, "storageBarrier");
-    GlobalVar("a", nullptr, ast::StorageClass::kNone, NoReturnValueBuiltin);
+    GlobalVar("a", NoReturnValueBuiltin);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: builtin 'storageBarrier' does not return a value");
@@ -61,7 +61,7 @@
 TEST_F(ResolverVariableValidationTest, GlobalVarUsedAtModuleScope) {
     // var<private> a : i32;
     // var<private> b : i32 = a;
-    GlobalVar(Source{{12, 34}}, "a", ty.i32(), ast::StorageClass::kPrivate, nullptr);
+    GlobalVar(Source{{12, 34}}, "a", ty.i32(), ast::StorageClass::kPrivate);
     GlobalVar("b", ty.i32(), ast::StorageClass::kPrivate, Expr(Source{{56, 78}}, "a"));
 
     EXPECT_FALSE(r()->Resolve());
@@ -71,7 +71,7 @@
 
 TEST_F(ResolverVariableValidationTest, OverrideNoInitializerNoType) {
     // override a;
-    Override(Source{{12, 34}}, "a", nullptr, nullptr);
+    Override(Source{{12, 34}}, "a");
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: override declaration requires a type or initializer");
@@ -84,9 +84,9 @@
     // override bang : i32;
     constexpr size_t kLimit = std::numeric_limits<decltype(OverrideId::value)>::max();
     for (size_t i = 0; i <= kLimit; i++) {
-        Override("o" + std::to_string(i), ty.i32(), nullptr);
+        Override("o" + std::to_string(i), ty.i32());
     }
-    Override(Source{{12, 34}}, "bang", ty.i32(), nullptr);
+    Override(Source{{12, 34}}, "bang", ty.i32());
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: number of 'override' variables exceeded limit of 65535");
@@ -98,14 +98,11 @@
     // ...
     // @id(N) override oN : i32;
     constexpr size_t kLimit = std::numeric_limits<decltype(OverrideId::value)>::max();
-    Override("reserved", ty.i32(), nullptr,
-             utils::Vector{
-                 Id(kLimit),
-             });
+    Override("reserved", ty.i32(), Id(kLimit));
     for (size_t i = 0; i < kLimit; i++) {
-        Override("o" + std::to_string(i), ty.i32(), nullptr);
+        Override("o" + std::to_string(i), ty.i32());
     }
-    Override(Source{{12, 34}}, "bang", ty.i32(), nullptr);
+    Override(Source{{12, 34}}, "bang", ty.i32());
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: number of 'override' variables exceeded limit of 65535");
@@ -114,7 +111,7 @@
 TEST_F(ResolverVariableValidationTest, VarTypeNotConstructible) {
     // var i : i32;
     // var p : pointer<function, i32> = &v;
-    auto* i = Var("i", ty.i32(), ast::StorageClass::kNone);
+    auto* i = Var("i", ty.i32());
     auto* p = Var("a", ty.pointer<i32>(Source{{56, 78}}, ast::StorageClass::kFunction),
                   ast::StorageClass::kNone, AddressOf(Source{{12, 34}}, "i"));
     WrapInFunction(i, p);
@@ -126,9 +123,9 @@
 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()),
-                         GroupAndBinding(0, 0));
-    auto* t2 = Let(Source{{56, 78}}, "t2", nullptr, Expr(t1));
+    auto* t1 = GlobalVar("t1", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Group(0),
+                         Binding(0));
+    auto* t2 = Let(Source{{56, 78}}, "t2", Expr(t1));
     WrapInFunction(t2);
 
     EXPECT_FALSE(r()->Resolve());
@@ -137,7 +134,7 @@
 
 TEST_F(ResolverVariableValidationTest, OverrideExplicitTypeNotScalar) {
     // override o : vec3<f32>;
-    Override(Source{{56, 78}}, "o", ty.vec3<f32>(), nullptr);
+    Override(Source{{56, 78}}, "o", ty.vec3<f32>());
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "56:78 error: vec3<f32> cannot be used as the type of a 'override'");
@@ -145,7 +142,7 @@
 
 TEST_F(ResolverVariableValidationTest, OverrideInferedTypeNotScalar) {
     // override o = vec3(1.0f);
-    Override(Source{{56, 78}}, "o", nullptr, vec3<f32>(1.0_f));
+    Override(Source{{56, 78}}, "o", vec3<f32>(1.0_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "56:78 error: vec3<f32> cannot be used as the type of a 'override'");
@@ -171,7 +168,7 @@
 
 TEST_F(ResolverVariableValidationTest, VarConstructorWrongType) {
     // var v : i32 = 2u
-    WrapInFunction(Var(Source{{3, 3}}, "v", ty.i32(), ast::StorageClass::kNone, Expr(2_u)));
+    WrapInFunction(Var(Source{{3, 3}}, "v", ty.i32(), Expr(2_u)));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -198,7 +195,7 @@
 
 TEST_F(ResolverVariableValidationTest, VarConstructorWrongTypeViaAlias) {
     auto* a = Alias("I32", ty.i32());
-    WrapInFunction(Var(Source{{3, 3}}, "v", ty.Of(a), ast::StorageClass::kNone, Expr(2_u)));
+    WrapInFunction(Var(Source{{3, 3}}, "v", ty.Of(a), Expr(2_u)));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -210,7 +207,7 @@
     // let b : ptr<function,f32> = a;
     const auto priv = ast::StorageClass::kFunction;
     auto* var_a = Var("a", ty.f32(), priv);
-    auto* var_b = Let(Source{{12, 34}}, "b", ty.pointer<f32>(priv), Expr("a"), {});
+    auto* var_b = Let(Source{{12, 34}}, "b", ty.pointer<f32>(priv), Expr("a"));
     WrapInFunction(var_a, var_b);
 
     ASSERT_FALSE(r()->Resolve());
@@ -241,7 +238,7 @@
 
     GlobalVar("v", ty.f32(), ast::StorageClass::kPrivate, Expr(2.1_f));
 
-    WrapInFunction(Var(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kNone, Expr(2_f)));
+    WrapInFunction(Var(Source{{12, 34}}, "v", ty.f32(), Expr(2_f)));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -251,8 +248,8 @@
     //  var v : f32;
     //  { var v : f32; }
     // }
-    auto* var_outer = Var("v", ty.f32(), ast::StorageClass::kNone);
-    auto* var_inner = Var(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kNone);
+    auto* var_outer = Var("v", ty.f32());
+    auto* var_inner = Var(Source{{12, 34}}, "v", ty.f32());
     auto* inner = Block(Decl(var_inner));
     auto* outer_body = Block(Decl(var_outer), inner);
 
@@ -266,9 +263,9 @@
     //   var v : f32 = 3.14;
     //   if (true) { var v : f32 = 2.0; }
     // }
-    auto* var_a_float = Var("v", ty.f32(), ast::StorageClass::kNone, Expr(3.1_f));
+    auto* var_a_float = Var("v", ty.f32(), Expr(3.1_f));
 
-    auto* var = Var(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kNone, Expr(2_f));
+    auto* var = Var(Source{{12, 34}}, "v", ty.f32(), Expr(2_f));
 
     auto* cond = Expr(true);
     auto* body = Block(Decl(var));
@@ -297,11 +294,7 @@
     auto* buf = Structure("S", utils::Vector{
                                    Member("inner", ty.Of(inner)),
                                });
-    auto* storage = GlobalVar("s", ty.Of(buf), ast::StorageClass::kStorage,
-                              utils::Vector{
-                                  create<ast::BindingAttribute>(0u),
-                                  create<ast::GroupAttribute>(0u),
-                              });
+    auto* storage = GlobalVar("s", ty.Of(buf), ast::StorageClass::kStorage, Binding(0), Group(0));
 
     auto* expr = IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 2_i);
     auto* ptr =
@@ -355,8 +348,8 @@
     // fn foo() {
     //   var v = s;
     // }
-    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(0, 0));
-    auto* v = Var(Source{{12, 34}}, "v", nullptr, Expr("s"));
+    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(0), Binding(0));
+    auto* v = Var(Source{{12, 34}}, "v", Expr("s"));
     WrapInFunction(v);
 
     EXPECT_FALSE(r()->Resolve());
@@ -424,8 +417,8 @@
 }
 
 TEST_F(ResolverVariableValidationTest, ConstInitWithVar) {
-    auto* v = Var("v", nullptr, Expr(1_i));
-    auto* c = Const("c", nullptr, Expr(Source{{12, 34}}, v));
+    auto* v = Var("v", Expr(1_i));
+    auto* c = Const("c", Expr(Source{{12, 34}}, v));
     WrapInFunction(v, c);
 
     EXPECT_FALSE(r()->Resolve());
@@ -433,8 +426,8 @@
 }
 
 TEST_F(ResolverVariableValidationTest, ConstInitWithOverride) {
-    auto* o = Override("v", nullptr, Expr(1_i));
-    auto* c = Const("c", nullptr, Expr(Source{{12, 34}}, o));
+    auto* o = Override("v", Expr(1_i));
+    auto* c = Const("c", Expr(Source{{12, 34}}, o));
     WrapInFunction(c);
 
     EXPECT_FALSE(r()->Resolve());
@@ -442,8 +435,8 @@
 }
 
 TEST_F(ResolverVariableValidationTest, ConstInitWithLet) {
-    auto* l = Let("v", nullptr, Expr(1_i));
-    auto* c = Const("c", nullptr, Expr(Source{{12, 34}}, l));
+    auto* l = Let("v", Expr(1_i));
+    auto* c = Const("c", Expr(Source{{12, 34}}, l));
     WrapInFunction(l, c);
 
     EXPECT_FALSE(r()->Resolve());
diff --git a/src/tint/transform/array_length_from_uniform.cc b/src/tint/transform/array_length_from_uniform.cc
index cdb75c8..36ddcdc 100644
--- a/src/tint/transform/array_length_from_uniform.cc
+++ b/src/tint/transform/array_length_from_uniform.cc
@@ -148,7 +148,7 @@
                 });
             buffer_size_ubo = ctx.dst->GlobalVar(
                 ctx.dst->Sym(), ctx.dst->ty.Of(buffer_size_struct), ast::StorageClass::kUniform,
-                ctx.dst->GroupAndBinding(cfg->ubo_binding.group, cfg->ubo_binding.binding));
+                ctx.dst->Group(cfg->ubo_binding.group), ctx.dst->Binding(cfg->ubo_binding.binding));
         }
         return buffer_size_ubo;
     };
diff --git a/src/tint/transform/builtin_polyfill.cc b/src/tint/transform/builtin_polyfill.cc
index 3ca7035..03b39f7 100644
--- a/src/tint/transform/builtin_polyfill.cc
+++ b/src/tint/transform/builtin_polyfill.cc
@@ -154,45 +154,44 @@
         auto V = [&](uint32_t value) -> const ast::Expression* {
             return ScalarOrVector(width, u32(value));
         };
-        b.Func(
-            name,
-            utils::Vector{
-                b.Param("v", T(ty)),
-            },
-            T(ty),
-            utils::Vector{
-                // var x = U(v);
-                b.Decl(b.Var("x", nullptr, b.Construct(U(), b.Expr("v")))),
-                // let b16 = select(0, 16, x <= 0x0000ffff);
-                b.Decl(b.Let("b16", nullptr,
-                             b.Call("select", V(0), V(16), b.LessThanEqual("x", V(0x0000ffff))))),
-                // x = x << b16;
-                b.Assign("x", b.Shl("x", "b16")),
-                // let b8  = select(0, 8,  x <= 0x00ffffff);
-                b.Decl(b.Let("b8", nullptr,
-                             b.Call("select", V(0), V(8), b.LessThanEqual("x", V(0x00ffffff))))),
-                // x = x << b8;
-                b.Assign("x", b.Shl("x", "b8")),
-                // let b4  = select(0, 4,  x <= 0x0fffffff);
-                b.Decl(b.Let("b4", nullptr,
-                             b.Call("select", V(0), V(4), b.LessThanEqual("x", V(0x0fffffff))))),
-                // x = x << b4;
-                b.Assign("x", b.Shl("x", "b4")),
-                // let b2  = select(0, 2,  x <= 0x3fffffff);
-                b.Decl(b.Let("b2", nullptr,
-                             b.Call("select", V(0), V(2), b.LessThanEqual("x", V(0x3fffffff))))),
-                // x = x << b2;
-                b.Assign("x", b.Shl("x", "b2")),
-                // let b1  = select(0, 1,  x <= 0x7fffffff);
-                b.Decl(b.Let("b1", nullptr,
-                             b.Call("select", V(0), V(1), b.LessThanEqual("x", V(0x7fffffff))))),
-                // let is_zero  = select(0, 1, x == 0);
-                b.Decl(b.Let("is_zero", nullptr, b.Call("select", V(0), V(1), b.Equal("x", V(0))))),
-                // return R((b16 | b8 | b4 | b2 | b1) + zero);
-                b.Return(b.Construct(
-                    T(ty),
-                    b.Add(b.Or(b.Or(b.Or(b.Or("b16", "b8"), "b4"), "b2"), "b1"), "is_zero"))),
-            });
+        b.Func(name,
+               utils::Vector{
+                   b.Param("v", T(ty)),
+               },
+               T(ty),
+               utils::Vector{
+                   // var x = U(v);
+                   b.Decl(b.Var("x", b.Construct(U(), b.Expr("v")))),
+                   // let b16 = select(0, 16, x <= 0x0000ffff);
+                   b.Decl(b.Let(
+                       "b16", b.Call("select", V(0), V(16), b.LessThanEqual("x", V(0x0000ffff))))),
+                   // x = x << b16;
+                   b.Assign("x", b.Shl("x", "b16")),
+                   // let b8  = select(0, 8,  x <= 0x00ffffff);
+                   b.Decl(b.Let("b8",
+                                b.Call("select", V(0), V(8), b.LessThanEqual("x", V(0x00ffffff))))),
+                   // x = x << b8;
+                   b.Assign("x", b.Shl("x", "b8")),
+                   // let b4  = select(0, 4,  x <= 0x0fffffff);
+                   b.Decl(b.Let("b4",
+                                b.Call("select", V(0), V(4), b.LessThanEqual("x", V(0x0fffffff))))),
+                   // x = x << b4;
+                   b.Assign("x", b.Shl("x", "b4")),
+                   // let b2  = select(0, 2,  x <= 0x3fffffff);
+                   b.Decl(b.Let("b2",
+                                b.Call("select", V(0), V(2), b.LessThanEqual("x", V(0x3fffffff))))),
+                   // x = x << b2;
+                   b.Assign("x", b.Shl("x", "b2")),
+                   // let b1  = select(0, 1,  x <= 0x7fffffff);
+                   b.Decl(b.Let("b1",
+                                b.Call("select", V(0), V(1), b.LessThanEqual("x", V(0x7fffffff))))),
+                   // let is_zero  = select(0, 1, x == 0);
+                   b.Decl(b.Let("is_zero", b.Call("select", V(0), V(1), b.Equal("x", V(0))))),
+                   // return R((b16 | b8 | b4 | b2 | b1) + zero);
+                   b.Return(b.Construct(
+                       T(ty),
+                       b.Add(b.Or(b.Or(b.Or(b.Or("b16", "b8"), "b4"), "b2"), "b1"), "is_zero"))),
+               });
         return name;
     }
 
@@ -227,32 +226,27 @@
             T(ty),
             utils::Vector{
                 // var x = U(v);
-                b.Decl(b.Var("x", nullptr, b.Construct(U(), b.Expr("v")))),
+                b.Decl(b.Var("x", b.Construct(U(), b.Expr("v")))),
                 // let b16 = select(16, 0, bool(x & 0x0000ffff));
-                b.Decl(b.Let("b16", nullptr,
-                             b.Call("select", V(16), V(0), B(b.And("x", V(0x0000ffff)))))),
+                b.Decl(b.Let("b16", b.Call("select", V(16), V(0), B(b.And("x", V(0x0000ffff)))))),
                 // x = x >> b16;
                 b.Assign("x", b.Shr("x", "b16")),
                 // let b8  = select(8,  0, bool(x & 0x000000ff));
-                b.Decl(b.Let("b8", nullptr,
-                             b.Call("select", V(8), V(0), B(b.And("x", V(0x000000ff)))))),
+                b.Decl(b.Let("b8", b.Call("select", V(8), V(0), B(b.And("x", V(0x000000ff)))))),
                 // x = x >> b8;
                 b.Assign("x", b.Shr("x", "b8")),
                 // let b4  = select(4,  0, bool(x & 0x0000000f));
-                b.Decl(b.Let("b4", nullptr,
-                             b.Call("select", V(4), V(0), B(b.And("x", V(0x0000000f)))))),
+                b.Decl(b.Let("b4", b.Call("select", V(4), V(0), B(b.And("x", V(0x0000000f)))))),
                 // x = x >> b4;
                 b.Assign("x", b.Shr("x", "b4")),
                 // let b2  = select(2,  0, bool(x & 0x00000003));
-                b.Decl(b.Let("b2", nullptr,
-                             b.Call("select", V(2), V(0), B(b.And("x", V(0x00000003)))))),
+                b.Decl(b.Let("b2", b.Call("select", V(2), V(0), B(b.And("x", V(0x00000003)))))),
                 // x = x >> b2;
                 b.Assign("x", b.Shr("x", "b2")),
                 // let b1  = select(1,  0, bool(x & 0x00000001));
-                b.Decl(b.Let("b1", nullptr,
-                             b.Call("select", V(1), V(0), B(b.And("x", V(0x00000001)))))),
+                b.Decl(b.Let("b1", b.Call("select", V(1), V(0), B(b.And("x", V(0x00000001)))))),
                 // let is_zero  = select(0, 1, x == 0);
-                b.Decl(b.Let("is_zero", nullptr, b.Call("select", V(0), V(1), b.Equal("x", V(0))))),
+                b.Decl(b.Let("is_zero", b.Call("select", V(0), V(1), b.Equal("x", V(0))))),
                 // return R((b16 | b8 | b4 | b2 | b1) + zero);
                 b.Return(b.Construct(
                     T(ty),
@@ -278,14 +272,14 @@
         };
 
         utils::Vector<const ast::Statement*, 8> body{
-            b.Decl(b.Let("s", nullptr, b.Call("min", "offset", u32(W)))),
-            b.Decl(b.Let("e", nullptr, b.Call("min", u32(W), b.Add("s", "count")))),
+            b.Decl(b.Let("s", b.Call("min", "offset", u32(W)))),
+            b.Decl(b.Let("e", b.Call("min", u32(W), b.Add("s", "count")))),
         };
 
         switch (polyfill.extract_bits) {
             case Level::kFull:
-                body.Push(b.Decl(b.Let("shl", nullptr, b.Sub(u32(W), "e"))));
-                body.Push(b.Decl(b.Let("shr", nullptr, b.Add("shl", "s"))));
+                body.Push(b.Decl(b.Let("shl", b.Sub(u32(W), "e"))));
+                body.Push(b.Decl(b.Let("shr", b.Add("shl", "s"))));
                 body.Push(
                     b.Return(b.Shr(b.Shl("v", vecN_u32(b.Expr("shl"))), vecN_u32(b.Expr("shr")))));
                 break;
@@ -353,33 +347,27 @@
             utils::Vector{
                 // var x = v;                          (unsigned)
                 // var x = select(U(v), ~U(v), v < 0); (signed)
-                b.Decl(b.Var("x", nullptr, x)),
+                b.Decl(b.Var("x", x)),
                 // let b16 = select(0, 16, bool(x & 0xffff0000));
-                b.Decl(b.Let("b16", nullptr,
-                             b.Call("select", V(0), V(16), B(b.And("x", V(0xffff0000)))))),
+                b.Decl(b.Let("b16", b.Call("select", V(0), V(16), B(b.And("x", V(0xffff0000)))))),
                 // x = x >> b16;
                 b.Assign("x", b.Shr("x", "b16")),
                 // let b8  = select(0, 8,  bool(x & 0x0000ff00));
-                b.Decl(b.Let("b8", nullptr,
-                             b.Call("select", V(0), V(8), B(b.And("x", V(0x0000ff00)))))),
+                b.Decl(b.Let("b8", b.Call("select", V(0), V(8), B(b.And("x", V(0x0000ff00)))))),
                 // x = x >> b8;
                 b.Assign("x", b.Shr("x", "b8")),
                 // let b4  = select(0, 4,  bool(x & 0x000000f0));
-                b.Decl(b.Let("b4", nullptr,
-                             b.Call("select", V(0), V(4), B(b.And("x", V(0x000000f0)))))),
+                b.Decl(b.Let("b4", b.Call("select", V(0), V(4), B(b.And("x", V(0x000000f0)))))),
                 // x = x >> b4;
                 b.Assign("x", b.Shr("x", "b4")),
                 // let b2  = select(0, 2,  bool(x & 0x0000000c));
-                b.Decl(b.Let("b2", nullptr,
-                             b.Call("select", V(0), V(2), B(b.And("x", V(0x0000000c)))))),
+                b.Decl(b.Let("b2", b.Call("select", V(0), V(2), B(b.And("x", V(0x0000000c)))))),
                 // x = x >> b2;
                 b.Assign("x", b.Shr("x", "b2")),
                 // let b1  = select(0, 1,  bool(x & 0x00000002));
-                b.Decl(b.Let("b1", nullptr,
-                             b.Call("select", V(0), V(1), B(b.And("x", V(0x00000002)))))),
+                b.Decl(b.Let("b1", b.Call("select", V(0), V(1), B(b.And("x", V(0x00000002)))))),
                 // let is_zero  = select(0, 0xffffffff, x == 0);
-                b.Decl(b.Let("is_zero", nullptr,
-                             b.Call("select", V(0), V(0xffffffff), b.Equal("x", V(0))))),
+                b.Decl(b.Let("is_zero", b.Call("select", V(0), V(0xffffffff), b.Equal("x", V(0))))),
                 // return R(b16 | b8 | b4 | b2 | b1 | zero);
                 b.Return(b.Construct(
                     T(ty), b.Or(b.Or(b.Or(b.Or(b.Or("b16", "b8"), "b4"), "b2"), "b1"), "is_zero"))),
@@ -418,33 +406,27 @@
             T(ty),
             utils::Vector{
                 // var x = U(v);
-                b.Decl(b.Var("x", nullptr, b.Construct(U(), b.Expr("v")))),
+                b.Decl(b.Var("x", b.Construct(U(), b.Expr("v")))),
                 // let b16 = select(16, 0, bool(x & 0x0000ffff));
-                b.Decl(b.Let("b16", nullptr,
-                             b.Call("select", V(16), V(0), B(b.And("x", V(0x0000ffff)))))),
+                b.Decl(b.Let("b16", b.Call("select", V(16), V(0), B(b.And("x", V(0x0000ffff)))))),
                 // x = x >> b16;
                 b.Assign("x", b.Shr("x", "b16")),
                 // let b8  = select(8,  0, bool(x & 0x000000ff));
-                b.Decl(b.Let("b8", nullptr,
-                             b.Call("select", V(8), V(0), B(b.And("x", V(0x000000ff)))))),
+                b.Decl(b.Let("b8", b.Call("select", V(8), V(0), B(b.And("x", V(0x000000ff)))))),
                 // x = x >> b8;
                 b.Assign("x", b.Shr("x", "b8")),
                 // let b4  = select(4,  0, bool(x & 0x0000000f));
-                b.Decl(b.Let("b4", nullptr,
-                             b.Call("select", V(4), V(0), B(b.And("x", V(0x0000000f)))))),
+                b.Decl(b.Let("b4", b.Call("select", V(4), V(0), B(b.And("x", V(0x0000000f)))))),
                 // x = x >> b4;
                 b.Assign("x", b.Shr("x", "b4")),
                 // let b2  = select(2,  0, bool(x & 0x00000003));
-                b.Decl(b.Let("b2", nullptr,
-                             b.Call("select", V(2), V(0), B(b.And("x", V(0x00000003)))))),
+                b.Decl(b.Let("b2", b.Call("select", V(2), V(0), B(b.And("x", V(0x00000003)))))),
                 // x = x >> b2;
                 b.Assign("x", b.Shr("x", "b2")),
                 // let b1  = select(1,  0, bool(x & 0x00000001));
-                b.Decl(b.Let("b1", nullptr,
-                             b.Call("select", V(1), V(0), B(b.And("x", V(0x00000001)))))),
+                b.Decl(b.Let("b1", b.Call("select", V(1), V(0), B(b.And("x", V(0x00000001)))))),
                 // let is_zero  = select(0, 0xffffffff, x == 0);
-                b.Decl(b.Let("is_zero", nullptr,
-                             b.Call("select", V(0), V(0xffffffff), b.Equal("x", V(0))))),
+                b.Decl(b.Let("is_zero", b.Call("select", V(0), V(0xffffffff), b.Equal("x", V(0))))),
                 // return R(b16 | b8 | b4 | b2 | b1 | is_zero);
                 b.Return(b.Construct(
                     T(ty), b.Or(b.Or(b.Or(b.Or(b.Or("b16", "b8"), "b4"), "b2"), "b1"), "is_zero"))),
@@ -479,16 +461,15 @@
         };
 
         utils::Vector<const ast::Statement*, 8> body = {
-            b.Decl(b.Let("s", nullptr, b.Call("min", "offset", u32(W)))),
-            b.Decl(b.Let("e", nullptr, b.Call("min", u32(W), b.Add("s", "count")))),
+            b.Decl(b.Let("s", b.Call("min", "offset", u32(W)))),
+            b.Decl(b.Let("e", b.Call("min", u32(W), b.Add("s", "count")))),
         };
 
         switch (polyfill.insert_bits) {
             case Level::kFull:
                 // let mask = ((1 << s) - 1) ^ ((1 << e) - 1)
-                body.Push(
-                    b.Decl(b.Let("mask", nullptr,
-                                 b.Xor(b.Sub(b.Shl(1_u, "s"), 1_u), b.Sub(b.Shl(1_u, "e"), 1_u)))));
+                body.Push(b.Decl(b.Let(
+                    "mask", b.Xor(b.Sub(b.Shl(1_u, "s"), 1_u), b.Sub(b.Shl(1_u, "e"), 1_u)))));
                 // return ((n << s) & mask) | (v & ~mask)
                 body.Push(b.Return(b.Or(b.And(b.Shl("n", U("s")), V("mask")),
                                         b.And("v", V(b.Complement("mask"))))));
diff --git a/src/tint/transform/calculate_array_length.cc b/src/tint/transform/calculate_array_length.cc
index 6c15e73..8edcbf5 100644
--- a/src/tint/transform/calculate_array_length.cc
+++ b/src/tint/transform/calculate_array_length.cc
@@ -169,9 +169,8 @@
 
                             // Construct the variable that'll hold the result of
                             // RWByteAddressBuffer.GetDimensions()
-                            auto* buffer_size_result = ctx.dst->Decl(
-                                ctx.dst->Var(ctx.dst->Sym(), ctx.dst->ty.u32(),
-                                             ast::StorageClass::kNone, ctx.dst->Expr(0_u)));
+                            auto* buffer_size_result = ctx.dst->Decl(ctx.dst->Var(
+                                ctx.dst->Sym(), ctx.dst->ty.u32(), ctx.dst->Expr(0_u)));
 
                             // Call storage_buffer.GetDimensions(&buffer_size_result)
                             auto* call_get_dims = ctx.dst->CallStmt(ctx.dst->Call(
diff --git a/src/tint/transform/canonicalize_entry_point_io.cc b/src/tint/transform/canonicalize_entry_point_io.cc
index 7670f09..31feb86 100644
--- a/src/tint/transform/canonicalize_entry_point_io.cc
+++ b/src/tint/transform/canonicalize_entry_point_io.cc
@@ -544,8 +544,7 @@
             wrapper_body.Push(ctx.dst->CallStmt(call_inner));
         } else {
             // Capture the result of calling the original function.
-            auto* inner_result =
-                ctx.dst->Let(ctx.dst->Symbols().New("inner_result"), nullptr, call_inner);
+            auto* inner_result = ctx.dst->Let(ctx.dst->Symbols().New("inner_result"), call_inner);
             wrapper_body.Push(ctx.dst->Decl(inner_result));
 
             // Process the original return type to determine the outputs that the
diff --git a/src/tint/transform/combine_samplers.cc b/src/tint/transform/combine_samplers.cc
index ca9005b..13e6de4 100644
--- a/src/tint/transform/combine_samplers.cc
+++ b/src/tint/transform/combine_samplers.cc
@@ -79,7 +79,7 @@
     /// 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->GroupAndBinding(0, 0);
+        utils::Vector<const ast::Attribute*, 3> attributes{ctx.dst->Group(0), ctx.dst->Binding(0)};
         attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision));
         return attributes;
     }
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index 99fe94e..767a2a3 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -469,7 +469,7 @@
                     // }
                     auto load = LoadFunc(buf_ty, arr_ty->ElemType()->UnwrapRef(), var_user);
                     auto* arr = b.Var(b.Symbols().New("arr"), CreateASTTypeFor(ctx, arr_ty));
-                    auto* i = b.Var(b.Symbols().New("i"), nullptr, b.Expr(0_u));
+                    auto* i = b.Var(b.Symbols().New("i"), b.Expr(0_u));
                     auto* for_init = b.Decl(i);
                     auto* for_cond = b.create<ast::BinaryExpression>(
                         ast::BinaryOp::kLessThan, b.Expr(i), b.Expr(u32(arr_ty->Count())));
@@ -557,10 +557,10 @@
                             //   }
                             //   return arr;
                             // }
-                            auto* array = b.Var(b.Symbols().New("array"), nullptr, b.Expr("value"));
+                            auto* array = b.Var(b.Symbols().New("array"), b.Expr("value"));
                             auto store =
                                 StoreFunc(buf_ty, arr_ty->ElemType()->UnwrapRef(), var_user);
-                            auto* i = b.Var(b.Symbols().New("i"), nullptr, b.Expr(0_u));
+                            auto* i = b.Var(b.Symbols().New("i"), b.Expr(0_u));
                             auto* for_init = b.Decl(i);
                             auto* for_cond = b.create<ast::BinaryExpression>(
                                 ast::BinaryOp::kLessThan, b.Expr(i), b.Expr(u32(arr_ty->Count())));
diff --git a/src/tint/transform/decompose_strided_array_test.cc b/src/tint/transform/decompose_strided_array_test.cc
index 4b0a335..2de42fa 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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0), b.Binding(0));
     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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0), b.Binding(0));
     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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, b.Group(0), b.Binding(0));
     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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, b.Group(0), b.Binding(0));
     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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
+                b.Binding(0));
     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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
+                b.Binding(0));
     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,14 +450,14 @@
     // }
     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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
+                b.Binding(0));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
-               b.Decl(b.Let("a", nullptr, b.AddressOf(b.MemberAccessor("s", "a")))),
-               b.Decl(b.Let("b", nullptr, b.AddressOf(b.Deref(b.AddressOf(b.Deref("a")))))),
-               b.Decl(b.Let("c", nullptr, b.Deref("b"))),
-               b.Decl(b.Let("d", nullptr, b.IndexAccessor(b.Deref("b"), 1_i))),
+               b.Decl(b.Let("a", b.AddressOf(b.MemberAccessor("s", "a")))),
+               b.Decl(b.Let("b", b.AddressOf(b.Deref(b.AddressOf(b.Deref("a")))))),
+               b.Decl(b.Let("c", b.Deref("b"))),
+               b.Decl(b.Let("d", b.IndexAccessor(b.Deref("b"), 1_i))),
                b.Assign(b.Deref("b"), b.Construct(b.ty.array<f32, 4u>(32), 1_f, 2_f, 3_f, 4_f)),
                b.Assign(b.IndexAccessor(b.Deref("b"), 1_i), 5_f),
            },
@@ -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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
+                b.Binding(0));
     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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
+                b.Binding(0));
     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 afb06e8..a947f6d 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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0), b.Binding(0));
     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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0), b.Binding(0));
     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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.Group(0), b.Binding(0));
     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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
+                b.Binding(0));
     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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
+                b.Binding(0));
     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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
+                b.Binding(0));
     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.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
+                b.Binding(0));
     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,15 +466,15 @@
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
                           }),
              });
-    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-                b.GroupAndBinding(0, 0));
+    b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite, b.Group(0),
+                b.Binding(0));
     b.Func("f", utils::Empty, b.ty.void_(),
            utils::Vector{
-               b.Decl(b.Let("a", nullptr, b.AddressOf(b.MemberAccessor("s", "m")))),
-               b.Decl(b.Let("b", nullptr, b.AddressOf(b.Deref(b.AddressOf(b.Deref("a")))))),
-               b.Decl(b.Let("x", nullptr, b.Deref("b"))),
-               b.Decl(b.Let("y", nullptr, b.IndexAccessor(b.Deref("b"), 1_i))),
-               b.Decl(b.Let("z", nullptr, b.IndexAccessor("x", 1_i))),
+               b.Decl(b.Let("a", b.AddressOf(b.MemberAccessor("s", "m")))),
+               b.Decl(b.Let("b", b.AddressOf(b.Deref(b.AddressOf(b.Deref("a")))))),
+               b.Decl(b.Let("x", b.Deref("b"))),
+               b.Decl(b.Let("y", b.IndexAccessor(b.Deref("b"), 1_i))),
+               b.Decl(b.Let("z", b.IndexAccessor("x", 1_i))),
                b.Assign(b.Deref("b"), b.mat2x2<f32>(b.vec2<f32>(1_f, 2_f), b.vec2<f32>(3_f, 4_f))),
                b.Assign(b.IndexAccessor(b.Deref("b"), 1_i), b.vec2<f32>(5_f, 6_f)),
            },
diff --git a/src/tint/transform/expand_compound_assignment.cc b/src/tint/transform/expand_compound_assignment.cc
index e5e953c..85b9bf6 100644
--- a/src/tint/transform/expand_compound_assignment.cc
+++ b/src/tint/transform/expand_compound_assignment.cc
@@ -84,7 +84,7 @@
         auto hoist_pointer_to = [&](const ast::Expression* expr) {
             auto name = b.Sym();
             auto* ptr = b.AddressOf(ctx.Clone(expr));
-            auto* decl = b.Decl(b.Let(name, nullptr, ptr));
+            auto* decl = b.Decl(b.Let(name, ptr));
             hoist_to_decl_before.InsertBefore(ctx.src->Sem().Get(stmt), decl);
             return name;
         };
@@ -92,7 +92,7 @@
         // Helper function to hoist `expr` to a let declaration.
         auto hoist_expr_to_let = [&](const ast::Expression* expr) {
             auto name = b.Sym();
-            auto* decl = b.Decl(b.Let(name, nullptr, ctx.Clone(expr)));
+            auto* decl = b.Decl(b.Let(name, ctx.Clone(expr)));
             hoist_to_decl_before.InsertBefore(ctx.src->Sem().Get(stmt), decl);
             return name;
         };
diff --git a/src/tint/transform/first_index_offset.cc b/src/tint/transform/first_index_offset.cc
index a966f7f..ee80e95 100644
--- a/src/tint/transform/first_index_offset.cc
+++ b/src/tint/transform/first_index_offset.cc
@@ -122,7 +122,6 @@
         // Create a global to hold the uniform buffer
         Symbol buffer_name = ctx.dst->Sym();
         ctx.dst->GlobalVar(buffer_name, ctx.dst->ty.Of(struct_), ast::StorageClass::kUniform,
-                           nullptr,
                            utils::Vector{
                                ctx.dst->create<ast::BindingAttribute>(ub_binding),
                                ctx.dst->create<ast::GroupAttribute>(ub_group),
diff --git a/src/tint/transform/localize_struct_array_assignment.cc b/src/tint/transform/localize_struct_array_assignment.cc
index d43ddae..8286845 100644
--- a/src/tint/transform/localize_struct_array_assignment.cc
+++ b/src/tint/transform/localize_struct_array_assignment.cc
@@ -158,8 +158,7 @@
                 // Store the address of the member access into a let as we need to read
                 // the value twice e.g. let tint_symbol = &(s.a1);
                 auto mem_access_ptr = b.Sym();
-                s.insert_before_stmts.Push(
-                    b.Decl(b.Let(mem_access_ptr, nullptr, b.AddressOf(mem_access))));
+                s.insert_before_stmts.Push(b.Decl(b.Let(mem_access_ptr, b.AddressOf(mem_access))));
 
                 // Disable further transforms when cloning
                 TINT_SCOPED_ASSIGNMENT(s.process_nested_nodes, false);
@@ -167,8 +166,7 @@
                 // Copy entire array out of struct into local temp var
                 // e.g. var tint_symbol_1 = *(tint_symbol);
                 auto tmp_var = b.Sym();
-                s.insert_before_stmts.Push(
-                    b.Decl(b.Var(tmp_var, nullptr, b.Deref(mem_access_ptr))));
+                s.insert_before_stmts.Push(b.Decl(b.Var(tmp_var, b.Deref(mem_access_ptr))));
 
                 // Replace input index_access with a clone of itself, but with its
                 // .object replaced by the new temp var. This is returned from this
diff --git a/src/tint/transform/multiplanar_external_texture.cc b/src/tint/transform/multiplanar_external_texture.cc
index f68a165..d3ac05f 100644
--- a/src/tint/transform/multiplanar_external_texture.cc
+++ b/src/tint/transform/multiplanar_external_texture.cc
@@ -132,11 +132,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.GroupAndBinding(bps.plane_1.group, bps.plane_1.binding));
+                        b.Group(bps.plane_1.group), b.Binding(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.GroupAndBinding(bps.params.group, bps.params.binding));
+                        ast::StorageClass::kUniform, b.Group(bps.params.group),
+                        b.Binding(bps.params.binding));
 
             // Replace the original texture_external binding with a texture_2d<f32>
             // binding.
@@ -276,24 +276,22 @@
             b.ty.vec3<f32>(),
             utils::Vector{
                 // let cond = abs(v) < vec3(params.D);
-                b.Decl(b.Let(
-                    "cond", nullptr,
-                    b.LessThan(b.Call("abs", "v"), b.vec3<f32>(b.MemberAccessor("params", "D"))))),
+                b.Decl(b.Let("cond", b.LessThan(b.Call("abs", "v"),
+                                                b.vec3<f32>(b.MemberAccessor("params", "D"))))),
                 // let t = sign(v) * ((params.C * abs(v)) + params.F);
-                b.Decl(b.Let("t", nullptr,
+                b.Decl(b.Let("t",
                              b.Mul(b.Call("sign", "v"),
                                    b.Add(b.Mul(b.MemberAccessor("params", "C"), b.Call("abs", "v")),
                                          b.MemberAccessor("params", "F"))))),
                 // let f = (sign(v) * pow(((params.A * abs(v)) + params.B),
                 // vec3(params.G))) + params.E;
-                b.Decl(b.Let("f", nullptr,
-                             b.Mul(b.Call("sign", "v"),
-                                   b.Add(b.Call("pow",
-                                                b.Add(b.Mul(b.MemberAccessor("params", "A"),
-                                                            b.Call("abs", "v")),
-                                                      b.MemberAccessor("params", "B")),
-                                                b.vec3<f32>(b.MemberAccessor("params", "G"))),
-                                         b.MemberAccessor("params", "E"))))),
+                b.Decl(b.Let("f", b.Mul(b.Call("sign", "v"),
+                                        b.Add(b.Call("pow",
+                                                     b.Add(b.Mul(b.MemberAccessor("params", "A"),
+                                                                 b.Call("abs", "v")),
+                                                           b.MemberAccessor("params", "B")),
+                                                     b.vec3<f32>(b.MemberAccessor("params", "G"))),
+                                              b.MemberAccessor("params", "E"))))),
                 // return select(f, t, cond);
                 b.Return(b.Call("select", "f", "t", "cond")),
             });
diff --git a/src/tint/transform/num_workgroups_from_uniform.cc b/src/tint/transform/num_workgroups_from_uniform.cc
index b9476f2..293c6c4 100644
--- a/src/tint/transform/num_workgroups_from_uniform.cc
+++ b/src/tint/transform/num_workgroups_from_uniform.cc
@@ -148,7 +148,7 @@
 
             num_workgroups_ubo = ctx.dst->GlobalVar(
                 ctx.dst->Sym(), ctx.dst->ty.Of(num_workgroups_struct), ast::StorageClass::kUniform,
-                ctx.dst->GroupAndBinding(group, binding));
+                ctx.dst->Group(group), ctx.dst->Binding(binding));
         }
         return num_workgroups_ubo;
     };
diff --git a/src/tint/transform/promote_side_effects_to_decl.cc b/src/tint/transform/promote_side_effects_to_decl.cc
index 46e8f46..d1e1cf1 100644
--- a/src/tint/transform/promote_side_effects_to_decl.cc
+++ b/src/tint/transform/promote_side_effects_to_decl.cc
@@ -427,7 +427,7 @@
         auto clone_maybe_hoisted = [&](const ast::Expression* e) -> const ast::Expression* {
             if (to_hoist.count(e)) {
                 auto name = b.Symbols().New();
-                auto* v = b.Let(name, nullptr, ctx.Clone(e));
+                auto* v = b.Let(name, ctx.Clone(e));
                 auto* decl = b.Decl(v);
                 curr_stmts->Push(decl);
                 return b.Expr(name);
@@ -476,7 +476,7 @@
                 // let r = temp;
 
                 auto name = b.Sym();
-                curr_stmts->Push(b.Decl(b.Var(name, nullptr, decompose(bin_expr->lhs))));
+                curr_stmts->Push(b.Decl(b.Var(name, decompose(bin_expr->lhs))));
 
                 const ast::Expression* if_cond = nullptr;
                 if (bin_expr->IsLogicalOr()) {
diff --git a/src/tint/transform/simplify_pointers.cc b/src/tint/transform/simplify_pointers.cc
index 4190312..396a31c 100644
--- a/src/tint/transform/simplify_pointers.cc
+++ b/src/tint/transform/simplify_pointers.cc
@@ -181,8 +181,7 @@
                         // Create a new variable
                         auto saved_name = ctx.dst->Symbols().New(
                             ctx.src->Symbols().NameFor(var->Declaration()->symbol) + "_save");
-                        auto* decl =
-                            ctx.dst->Decl(ctx.dst->Let(saved_name, nullptr, ctx.Clone(idx_expr)));
+                        auto* decl = ctx.dst->Decl(ctx.dst->Let(saved_name, ctx.Clone(idx_expr)));
                         saved.emplace_back(decl);
                         // Record the substitution of `idx_expr` to the saved variable
                         // with the symbol `saved_name`. This will be used by the
diff --git a/src/tint/transform/spirv_atomic.cc b/src/tint/transform/spirv_atomic.cc
index bece2d6..a6cfd5e 100644
--- a/src/tint/transform/spirv_atomic.cc
+++ b/src/tint/transform/spirv_atomic.cc
@@ -79,7 +79,7 @@
                         auto* block = call->Stmt()->Block()->Declaration();
                         auto old_value = b.Symbols().New("old_value");
                         auto old_value_decl = b.Decl(b.Let(
-                            old_value, nullptr,
+                            old_value,
                             b.MemberAccessor(b.Call(sem::str(stub->builtin), std::move(out_args)),
                                              b.Expr("old_value"))));
                         ctx.InsertBefore(block->statements, call->Stmt()->Declaration(),
diff --git a/src/tint/transform/unwind_discard_functions.cc b/src/tint/transform/unwind_discard_functions.cc
index 8c62f04..f94f549 100644
--- a/src/tint/transform/unwind_discard_functions.cc
+++ b/src/tint/transform/unwind_discard_functions.cc
@@ -141,7 +141,7 @@
 
         auto ip = utils::GetInsertionPoint(ctx, stmt);
         auto var_name = b.Sym();
-        auto* decl = b.Decl(b.Var(var_name, nullptr, ctx.Clone(expr)));
+        auto* decl = b.Decl(b.Var(var_name, ctx.Clone(expr)));
         ctx.InsertBefore(ip.first->Declaration()->statements, ip.second, decl);
 
         ctx.InsertBefore(ip.first->Declaration()->statements, ip.second, IfDiscardReturn(stmt));
diff --git a/src/tint/transform/utils/get_insertion_point_test.cc b/src/tint/transform/utils/get_insertion_point_test.cc
index 72fedd0..d910858 100644
--- a/src/tint/transform/utils/get_insertion_point_test.cc
+++ b/src/tint/transform/utils/get_insertion_point_test.cc
@@ -33,7 +33,7 @@
     // }
     ProgramBuilder b;
     auto* expr = b.Expr(1_i);
-    auto* var = b.Decl(b.Var("a", nullptr, expr));
+    auto* var = b.Decl(b.Var("a", expr));
     auto* block = b.Block(var);
     b.Func("f", tint::utils::Empty, b.ty.void_(), tint::utils::Vector{block});
 
@@ -55,7 +55,7 @@
     // }
     ProgramBuilder b;
     auto* expr = b.Expr(1_i);
-    auto* var = b.Decl(b.Var("a", nullptr, expr));
+    auto* var = b.Decl(b.Var("a", expr));
     auto* fl = b.For(var, b.Expr(true), nullptr, b.Block());
     auto* func_block = b.Block(fl);
     b.Func("f", tint::utils::Empty, b.ty.void_(), tint::utils::Vector{func_block});
@@ -77,7 +77,7 @@
     // }
     ProgramBuilder b;
     auto* expr = b.Expr(1_i);
-    auto* var = b.Decl(b.Var("a", nullptr, expr));
+    auto* var = b.Decl(b.Var("a", expr));
     auto* s = b.For({}, b.Expr(true), var, b.Block());
     b.Func("f", tint::utils::Empty, b.ty.void_(), tint::utils::Vector{s});
 
diff --git a/src/tint/transform/utils/hoist_to_decl_before.cc b/src/tint/transform/utils/hoist_to_decl_before.cc
index 9133267..85bd2b2 100644
--- a/src/tint/transform/utils/hoist_to_decl_before.cc
+++ b/src/tint/transform/utils/hoist_to_decl_before.cc
@@ -199,8 +199,8 @@
         auto name = b.Symbols().New(decl_name);
 
         // Construct the let/var that holds the hoisted expr
-        auto* v = as_let ? static_cast<const ast::Variable*>(b.Let(name, nullptr, ctx.Clone(expr)))
-                         : static_cast<const ast::Variable*>(b.Var(name, nullptr, ctx.Clone(expr)));
+        auto* v = as_let ? static_cast<const ast::Variable*>(b.Let(name, ctx.Clone(expr)))
+                         : static_cast<const ast::Variable*>(b.Var(name, ctx.Clone(expr)));
         auto* decl = b.Decl(v);
 
         if (!InsertBefore(before_expr->Stmt(), decl)) {
diff --git a/src/tint/transform/utils/hoist_to_decl_before_test.cc b/src/tint/transform/utils/hoist_to_decl_before_test.cc
index 6d4533d..f0d3b36 100644
--- a/src/tint/transform/utils/hoist_to_decl_before_test.cc
+++ b/src/tint/transform/utils/hoist_to_decl_before_test.cc
@@ -35,7 +35,7 @@
     // }
     ProgramBuilder b;
     auto* expr = b.Expr(1_i);
-    auto* var = b.Decl(b.Var("a", nullptr, expr));
+    auto* var = b.Decl(b.Var("a", expr));
     b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var});
 
     Program original(std::move(b));
@@ -67,7 +67,7 @@
     // }
     ProgramBuilder b;
     auto* expr = b.Expr(1_i);
-    auto* s = b.For(b.Decl(b.Var("a", nullptr, expr)), b.Expr(true), nullptr, b.Block());
+    auto* s = b.For(b.Decl(b.Var("a", expr)), b.Expr(true), nullptr, b.Block());
     b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{s});
 
     Program original(std::move(b));
@@ -141,7 +141,7 @@
     // }
     ProgramBuilder b;
     auto* expr = b.Expr(1_i);
-    auto* s = b.For(nullptr, b.Expr(true), b.Decl(b.Var("a", nullptr, expr)), b.Block());
+    auto* s = b.For(nullptr, b.Expr(true), b.Decl(b.Var("a", expr)), b.Block());
     b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{s});
 
     Program original(std::move(b));
@@ -269,7 +269,7 @@
     ProgramBuilder b;
     auto* var1 = b.Decl(b.Var("a", b.ty.array<i32, 10>()));
     auto* expr = b.IndexAccessor("a", 0_i);
-    auto* var2 = b.Decl(b.Var("b", nullptr, expr));
+    auto* var2 = b.Decl(b.Var("b", expr));
     b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var1, var2});
 
     Program original(std::move(b));
@@ -304,7 +304,7 @@
 
     auto* var1 = b.Decl(b.Var("a", b.ty.array(b.ty.array<i32, 10>(), 10_i)));
     auto* expr = b.IndexAccessor(b.IndexAccessor("a", 0_i), 0_i);
-    auto* var2 = b.Decl(b.Var("b", nullptr, expr));
+    auto* var2 = b.Decl(b.Var("b", expr));
     b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var1, var2});
 
     Program original(std::move(b));
@@ -377,7 +377,7 @@
     // }
     ProgramBuilder b;
     auto* expr = b.Expr(1_i);
-    auto* s = b.For(nullptr, b.Expr(true), b.Decl(b.Var("a", nullptr, expr)), b.Block());
+    auto* s = b.For(nullptr, b.Expr(true), b.Decl(b.Var("a", expr)), b.Block());
     b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{s});
 
     Program original(std::move(b));
@@ -462,7 +462,7 @@
     // }
     ProgramBuilder b;
     b.Func("foo", utils::Empty, b.ty.void_(), utils::Empty);
-    auto* var = b.Decl(b.Var("a", nullptr, b.Expr(1_i)));
+    auto* var = b.Decl(b.Var("a", b.Expr(1_i)));
     b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var});
 
     Program original(std::move(b));
@@ -500,7 +500,7 @@
     // }
     ProgramBuilder b;
     b.Func("foo", utils::Empty, b.ty.void_(), utils::Empty);
-    auto* var = b.Decl(b.Var("a", nullptr, b.Expr(1_i)));
+    auto* var = b.Decl(b.Var("a", b.Expr(1_i)));
     auto* s = b.For(var, b.Expr(true), nullptr, b.Block());
     b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{s});
 
@@ -541,7 +541,7 @@
     // }
     ProgramBuilder b;
     b.Func("foo", utils::Empty, b.ty.void_(), utils::Empty);
-    auto* var = b.Decl(b.Var("a", nullptr, b.Expr(1_i)));
+    auto* var = b.Decl(b.Var("a", b.Expr(1_i)));
     auto* cont = b.CompoundAssign("a", b.Expr(1_i), ast::BinaryOp::kAdd);
     auto* s = b.For(nullptr, b.Expr(true), cont, b.Block());
     b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var, s});
diff --git a/src/tint/transform/vertex_pulling.cc b/src/tint/transform/vertex_pulling.cc
index 6eb2bbf..346bb16 100644
--- a/src/tint/transform/vertex_pulling.cc
+++ b/src/tint/transform/vertex_pulling.cc
@@ -260,11 +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,
-                               utils::Vector{
-                                   ctx.dst->create<ast::BindingAttribute>(i),
-                                   ctx.dst->create<ast::GroupAttribute>(cfg.pulling_group),
-                               });
+                               ast::StorageClass::kStorage, ast::Access::kRead, ctx.dst->Binding(i),
+                               ctx.dst->Group(cfg.pulling_group));
         }
     }
 
@@ -303,7 +300,7 @@
             }
 
             // let pulling_offset_n = <attribute_offset>
-            stmts.Push(ctx.dst->Decl(ctx.dst->Let(buffer_array_base, nullptr, attribute_offset)));
+            stmts.Push(ctx.dst->Decl(ctx.dst->Let(buffer_array_base, attribute_offset)));
 
             for (const VertexAttributeDescriptor& attribute_desc : buffer_layout.attributes) {
                 auto it = location_info.find(attribute_desc.shader_location);
diff --git a/src/tint/writer/flatten_bindings_test.cc b/src/tint/writer/flatten_bindings_test.cc
index be5fb42..830e720 100644
--- a/src/tint/writer/flatten_bindings_test.cc
+++ b/src/tint/writer/flatten_bindings_test.cc
@@ -40,9 +40,9 @@
 
 TEST_F(FlattenBindingsTest, AlreadyFlat) {
     ProgramBuilder b;
-    b.GlobalVar("a", b.ty.i32(), ast::StorageClass::kUniform, b.GroupAndBinding(0, 0));
-    b.GlobalVar("b", b.ty.i32(), ast::StorageClass::kUniform, b.GroupAndBinding(0, 1));
-    b.GlobalVar("c", b.ty.i32(), ast::StorageClass::kUniform, b.GroupAndBinding(0, 2));
+    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));
 
     resolver::Resolver resolver(&b);
 
@@ -55,9 +55,9 @@
 
 TEST_F(FlattenBindingsTest, NotFlat_SingleNamespace) {
     ProgramBuilder b;
-    b.GlobalVar("a", b.ty.i32(), ast::StorageClass::kUniform, b.GroupAndBinding(0, 0));
-    b.GlobalVar("b", b.ty.i32(), ast::StorageClass::kUniform, b.GroupAndBinding(1, 1));
-    b.GlobalVar("c", b.ty.i32(), ast::StorageClass::kUniform, b.GroupAndBinding(2, 2));
+    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.WrapInFunction(b.Expr("a"), b.Expr("b"), b.Expr("c"));
 
     resolver::Resolver resolver(&b);
@@ -81,30 +81,30 @@
     ProgramBuilder b;
 
     const size_t num_buffers = 3;
-    b.GlobalVar("buffer1", b.ty.i32(), ast::StorageClass::kUniform, b.GroupAndBinding(0, 0));
-    b.GlobalVar("buffer2", b.ty.i32(), ast::StorageClass::kStorage, b.GroupAndBinding(1, 1));
-    b.GlobalVar("buffer3", b.ty.i32(), ast::StorageClass::kStorage, ast::Access::kRead,
-                b.GroupAndBinding(2, 2));
+    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));
 
     const size_t num_samplers = 2;
-    b.GlobalVar("sampler1", b.ty.sampler(ast::SamplerKind::kSampler), b.GroupAndBinding(3, 3));
-    b.GlobalVar("sampler2", b.ty.sampler(ast::SamplerKind::kComparisonSampler),
-                b.GroupAndBinding(4, 4));
+    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));
 
     const size_t num_textures = 6;
     b.GlobalVar("texture1", b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()),
-                b.GroupAndBinding(5, 5));
+                b.Group(5), b.Binding(5));
     b.GlobalVar("texture2", b.ty.multisampled_texture(ast::TextureDimension::k2d, b.ty.f32()),
-                b.GroupAndBinding(6, 6));
+                b.Group(6), b.Binding(6));
     b.GlobalVar("texture3",
                 b.ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Float,
                                      ast::Access::kWrite),
-                b.GroupAndBinding(7, 7));
-    b.GlobalVar("texture4", b.ty.depth_texture(ast::TextureDimension::k2d),
-                b.GroupAndBinding(8, 8));
-    b.GlobalVar("texture5", b.ty.depth_multisampled_texture(ast::TextureDimension::k2d),
-                b.GroupAndBinding(9, 9));
-    b.GlobalVar("texture6", b.ty.external_texture(), b.GroupAndBinding(10, 10));
+                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.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 ee3fc47..8c7b8d4 100644
--- a/src/tint/writer/generate_external_texture_bindings_test.cc
+++ b/src/tint/writer/generate_external_texture_bindings_test.cc
@@ -36,7 +36,7 @@
 
 TEST_F(GenerateExternalTextureBindingsTest, One) {
     ProgramBuilder b;
-    b.GlobalVar("v0", b.ty.external_texture(), b.GroupAndBinding(0, 0));
+    b.GlobalVar("v0", b.ty.external_texture(), b.Group(0), b.Binding(0));
 
     tint::Program program(std::move(b));
     ASSERT_TRUE(program.IsValid());
@@ -52,8 +52,8 @@
 
 TEST_F(GenerateExternalTextureBindingsTest, Two_SameGroup) {
     ProgramBuilder b;
-    b.GlobalVar("v0", b.ty.external_texture(), b.GroupAndBinding(0, 0));
-    b.GlobalVar("v1", b.ty.external_texture(), b.GroupAndBinding(0, 1));
+    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));
 
     tint::Program program(std::move(b));
     ASSERT_TRUE(program.IsValid());
@@ -75,8 +75,8 @@
 
 TEST_F(GenerateExternalTextureBindingsTest, Two_DifferentGroup) {
     ProgramBuilder b;
-    b.GlobalVar("v0", b.ty.external_texture(), b.GroupAndBinding(0, 0));
-    b.GlobalVar("v1", b.ty.external_texture(), b.GroupAndBinding(1, 0));
+    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));
 
     tint::Program program(std::move(b));
     ASSERT_TRUE(program.IsValid());
@@ -98,11 +98,11 @@
 
 TEST_F(GenerateExternalTextureBindingsTest, Two_WithOtherBindingsInSameGroup) {
     ProgramBuilder b;
-    b.GlobalVar("v0", b.ty.i32(), b.GroupAndBinding(0, 0), kUniform);
-    b.GlobalVar("v1", b.ty.external_texture(), b.GroupAndBinding(0, 1));
-    b.GlobalVar("v2", b.ty.i32(), b.GroupAndBinding(0, 2), kUniform);
-    b.GlobalVar("v3", b.ty.external_texture(), b.GroupAndBinding(0, 3));
-    b.GlobalVar("v4", b.ty.i32(), b.GroupAndBinding(0, 4), kUniform);
+    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);
 
     tint::Program program(std::move(b));
     ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
diff --git a/src/tint/writer/glsl/generator_impl_binary_test.cc b/src/tint/writer/glsl/generator_impl_binary_test.cc
index 2a4994a..9113ae6 100644
--- a/src/tint/writer/glsl/generator_impl_binary_test.cc
+++ b/src/tint/writer/glsl/generator_impl_binary_test.cc
@@ -760,7 +760,7 @@
     GlobalVar("d", ty.bool_(), ast::StorageClass::kPrivate);
 
     auto* var =
-        Var("a", ty.bool_(), ast::StorageClass::kNone,
+        Var("a", ty.bool_(),
             create<ast::BinaryExpression>(
                 ast::BinaryOp::kLogicalOr,
                 create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("b"), Expr("c")),
diff --git a/src/tint/writer/glsl/generator_impl_constructor_test.cc b/src/tint/writer/glsl/generator_impl_constructor_test.cc
index 5b65c2b..b987a31 100644
--- a/src/tint/writer/glsl/generator_impl_constructor_test.cc
+++ b/src/tint/writer/glsl/generator_impl_constructor_test.cc
@@ -181,7 +181,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec_SingleScalar_F32_Var) {
-    auto* var = Var("v", nullptr, Expr(2_f));
+    auto* var = Var("v", Expr(2_f));
     auto* cast = vec3<f32>(var);
     WrapInFunction(var, cast);
 
@@ -195,7 +195,7 @@
 TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec_SingleScalar_F16_Var) {
     Enable(ast::Extension::kF16);
 
-    auto* var = Var("v", nullptr, Expr(2_h));
+    auto* var = Var("v", Expr(2_h));
     auto* cast = vec3<f16>(var);
     WrapInFunction(var, cast);
 
diff --git a/src/tint/writer/glsl/generator_impl_function_test.cc b/src/tint/writer/glsl/generator_impl_function_test.cc
index cc01e5d..90824ba 100644
--- a/src/tint/writer/glsl/generator_impl_function_test.cc
+++ b/src/tint/writer/glsl/generator_impl_function_test.cc
@@ -351,11 +351,7 @@
 
 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,
-                          utils::Vector{
-                              create<ast::BindingAttribute>(0u),
-                              create<ast::GroupAttribute>(1u),
-                          });
+    auto* ubo = GlobalVar("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform, Binding(0), Group(1));
 
     Func("sub_func",
          utils::Vector{
@@ -366,7 +362,7 @@
              Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")),
          });
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
+    auto* var = Var("v", ty.f32(), Call("sub_func", 1_f));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -405,14 +401,9 @@
 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("uniforms", ty.Of(s), ast::StorageClass::kUniform, Binding(0), Group(1));
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                    MemberAccessor(MemberAccessor("uniforms", "coord"), "x"));
+    auto* var = Var("v", ty.f32(), MemberAccessor(MemberAccessor("uniforms", "coord"), "x"));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -450,13 +441,10 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
+              Group(1));
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("coord", "b"));
+    auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b"));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -500,13 +488,10 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
+              Group(1));
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("coord", "b"));
+    auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b"));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -551,11 +536,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
+              Group(1));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -599,11 +581,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
+              Group(1));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -643,18 +622,14 @@
 
 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kUniform, Binding(0), Group(1));
 
     Func("sub_func", utils::Vector{Param("param", ty.f32())}, ty.f32(),
          utils::Vector{
              Return(MemberAccessor("coord", "x")),
          });
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
+    auto* var = Var("v", ty.f32(), Call("sub_func", 1_f));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -692,18 +667,15 @@
 
 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
+              Group(1));
 
     Func("sub_func", utils::Vector{Param("param", ty.f32())}, ty.f32(),
          utils::Vector{
              Return(MemberAccessor("coord", "x")),
          });
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
+    auto* var = Var("v", ty.f32(), Call("sub_func", 1_f));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -831,9 +803,9 @@
 
 TEST_F(GlslGeneratorImplTest_Function,
        Emit_Attribute_EntryPoint_Compute_WithWorkgroup_OverridableConst) {
-    Override("width", ty.i32(), Construct(ty.i32(), 2_i), utils::Vector{Id(7u)});
-    Override("height", ty.i32(), Construct(ty.i32(), 3_i), utils::Vector{Id(8u)});
-    Override("depth", ty.i32(), Construct(ty.i32(), 4_i), utils::Vector{Id(9u)});
+    Override("width", ty.i32(), Construct(ty.i32(), 2_i), Id(7u));
+    Override("height", ty.i32(), Construct(ty.i32(), 3_i), Id(8u));
+    Override("depth", ty.i32(), Construct(ty.i32(), 4_i), Id(9u));
     Func("main", utils::Empty, ty.void_(), {},
          utils::Vector{
              Stage(ast::PipelineStage::kCompute),
@@ -921,14 +893,11 @@
 
     auto* s = Structure("Data", utils::Vector{Member("d", ty.f32())});
 
-    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
+              Group(0));
 
     {
-        auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
+        auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
 
         Func("a", utils::Empty, ty.void_(),
              utils::Vector{
@@ -942,7 +911,7 @@
     }
 
     {
-        auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
+        auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
 
         Func("b", utils::Empty, ty.void_(),
              utils::Vector{
diff --git a/src/tint/writer/glsl/generator_impl_loop_test.cc b/src/tint/writer/glsl/generator_impl_loop_test.cc
index 9da50f0..1271463 100644
--- a/src/tint/writer/glsl/generator_impl_loop_test.cc
+++ b/src/tint/writer/glsl/generator_impl_loop_test.cc
@@ -188,7 +188,7 @@
 
     auto* multi_stmt =
         create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
-    auto* f = For(Decl(Var("b", nullptr, multi_stmt)), nullptr, nullptr, Block(Return()));
+    auto* f = For(Decl(Var("b", multi_stmt)), nullptr, nullptr, Block(Return()));
     WrapInFunction(f);
 
     GeneratorImpl& gen = Build();
@@ -348,9 +348,8 @@
     auto* multi_stmt_c =
         create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
 
-    auto* f =
-        For(Decl(Var("i", nullptr, multi_stmt_a)), multi_stmt_b, Assign("i", multi_stmt_c),  //
-            Block(Return()));
+    auto* f = For(Decl(Var("i", multi_stmt_a)), multi_stmt_b, Assign("i", multi_stmt_c),  //
+                  Block(Return()));
     WrapInFunction(f);
 
     GeneratorImpl& gen = Build();
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 ba6dc43..1736ab0 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.GroupAndBinding(1u, 0u));
+                    b.Group(1), b.Binding(0));
     }
 
     void SetupFunction(utils::VectorRef<const ast::Statement*> statements) {
@@ -115,7 +115,7 @@
     GlobalVar("str", ty.Of(s), ast::StorageClass::kPrivate);
 
     auto* expr = MemberAccessor("str", "mem");
-    WrapInFunction(Var("expr", ty.f32(), ast::StorageClass::kNone, expr));
+    WrapInFunction(Var("expr", ty.f32(), expr));
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -168,7 +168,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone, MemberAccessor("data", "b"))),
+        Decl(Var("x", MemberAccessor("data", "b"))),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -219,8 +219,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("value", p.member_type(ty), ast::StorageClass::kNone,
-                 Construct(p.member_type(ty)))),
+        Decl(Var("value", p.member_type(ty), Construct(p.member_type(ty)))),
         Assign(MemberAccessor("data", "b"), Expr("value")),
     });
 
@@ -313,8 +312,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
-                 IndexAccessor(IndexAccessor(MemberAccessor("data", "a"), 2_i), 1_i))),
+        Decl(Var("x", IndexAccessor(IndexAccessor(MemberAccessor("data", "a"), 2_i), 1_i))),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -359,8 +357,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
-                 IndexAccessor(MemberAccessor("data", "a"), 2_i))),
+        Decl(Var("x", IndexAccessor(MemberAccessor("data", "a"), 2_i))),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -405,11 +402,10 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("a", nullptr, Expr(2_i))),
-        Decl(Var("b", nullptr, Expr(4_i))),
-        Decl(Var("c", nullptr, Expr(3_i))),
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
-                 IndexAccessor(MemberAccessor("data", "a"), Sub(Add("a", "b"), "c")))),
+        Decl(Var("a", Expr(2_i))),
+        Decl(Var("b", Expr(4_i))),
+        Decl(Var("c", Expr(3_i))),
+        Decl(Var("x", IndexAccessor(MemberAccessor("data", "a"), Sub(Add("a", "b"), "c")))),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -509,8 +505,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
-                 MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"))),
+        Decl(Var("x", MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"))),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -566,7 +561,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
+        Decl(Var("x",
                  MemberAccessor(
                      MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), "xy"))),
     });
@@ -625,7 +620,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
+        Decl(Var("x",
                  MemberAccessor(
                      MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), "g"))),
     });
@@ -683,7 +678,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
+        Decl(Var("x",
                  IndexAccessor(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"),
                                1_i))),
     });
@@ -835,8 +830,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_MemberAccessor, Swizzle_xyz) {
-    auto* var =
-        Var("my_vec", ty.vec4<f32>(), ast::StorageClass::kNone, vec4<f32>(1_f, 2_f, 3_f, 4_f));
+    auto* var = Var("my_vec", ty.vec4<f32>(), vec4<f32>(1_f, 2_f, 3_f, 4_f));
     auto* expr = MemberAccessor("my_vec", "xyz");
     WrapInFunction(var, expr);
 
@@ -846,8 +840,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_MemberAccessor, Swizzle_gbr) {
-    auto* var =
-        Var("my_vec", ty.vec4<f32>(), ast::StorageClass::kNone, vec4<f32>(1_f, 2_f, 3_f, 4_f));
+    auto* var = Var("my_vec", ty.vec4<f32>(), vec4<f32>(1_f, 2_f, 3_f, 4_f));
     auto* expr = MemberAccessor("my_vec", "gbr");
     WrapInFunction(var, expr);
 
diff --git a/src/tint/writer/glsl/generator_impl_module_constant_test.cc b/src/tint/writer/glsl/generator_impl_module_constant_test.cc
index 44cbb53..e607efc 100644
--- a/src/tint/writer/glsl/generator_impl_module_constant_test.cc
+++ b/src/tint/writer/glsl/generator_impl_module_constant_test.cc
@@ -33,10 +33,10 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_AInt) {
-    auto* var = GlobalConst("G", nullptr, Expr(1_a));
+    auto* var = GlobalConst("G", Expr(1_a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -53,10 +53,10 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_AFloat) {
-    auto* var = GlobalConst("G", nullptr, Expr(1._a));
+    auto* var = GlobalConst("G", Expr(1._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -73,10 +73,10 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_i32) {
-    auto* var = GlobalConst("G", nullptr, Expr(1_i));
+    auto* var = GlobalConst("G", Expr(1_i));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -93,10 +93,10 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_u32) {
-    auto* var = GlobalConst("G", nullptr, Expr(1_u));
+    auto* var = GlobalConst("G", Expr(1_u));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -113,10 +113,10 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_f32) {
-    auto* var = GlobalConst("G", nullptr, Expr(1_f));
+    auto* var = GlobalConst("G", Expr(1_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -135,10 +135,10 @@
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = GlobalConst("G", nullptr, Expr(1_h));
+    auto* var = GlobalConst("G", Expr(1_h));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -156,10 +156,10 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_AInt) {
-    auto* var = GlobalConst("G", nullptr, Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    auto* var = GlobalConst("G", Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -176,10 +176,10 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_AFloat) {
-    auto* var = GlobalConst("G", nullptr, Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    auto* var = GlobalConst("G", Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -196,10 +196,10 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_f32) {
-    auto* var = GlobalConst("G", nullptr, vec3<f32>(1_f, 2_f, 3_f));
+    auto* var = GlobalConst("G", vec3<f32>(1_f, 2_f, 3_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -218,10 +218,10 @@
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = GlobalConst("G", nullptr, vec3<f16>(1_h, 2_h, 3_h));
+    auto* var = GlobalConst("G", vec3<f16>(1_h, 2_h, 3_h));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -239,11 +239,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_mat2x3_AFloat) {
-    auto* var = GlobalConst("G", nullptr,
-                            Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    auto* var =
+        GlobalConst("G", Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -260,10 +260,10 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_mat2x3_f32) {
-    auto* var = GlobalConst("G", nullptr, mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
+    auto* var = GlobalConst("G", mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -282,10 +282,10 @@
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_mat2x3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = GlobalConst("G", nullptr, mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
+    auto* var = GlobalConst("G", mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -303,10 +303,10 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_arr_f32) {
-    auto* var = GlobalConst("G", nullptr, Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
+    auto* var = GlobalConst("G", Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -323,14 +323,13 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_arr_vec2_bool) {
-    auto* var = GlobalConst("G", nullptr,
-                            Construct(ty.array(ty.vec2<bool>(), 3_u),  //
-                                      vec2<bool>(true, false),         //
-                                      vec2<bool>(false, true),         //
-                                      vec2<bool>(true, true)));
+    auto* var = GlobalConst("G", Construct(ty.array(ty.vec2<bool>(), 3_u),  //
+                                           vec2<bool>(true, false),         //
+                                           vec2<bool>(false, true),         //
+                                           vec2<bool>(true, true)));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("l", nullptr, Expr(var))),
+             Decl(Let("l", Expr(var))),
          });
 
     GeneratorImpl& gen = Build();
@@ -347,10 +346,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_Override) {
-    auto* var = Override("pos", ty.f32(), Expr(3_f),
-                         utils::Vector{
-                             Id(23),
-                         });
+    auto* var = Override("pos", ty.f32(), Expr(3_f), Id(23));
 
     GeneratorImpl& gen = Build();
 
@@ -363,10 +359,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_Override_NoConstructor) {
-    auto* var = Override("pos", ty.f32(), nullptr,
-                         utils::Vector{
-                             Id(23),
-                         });
+    auto* var = Override("pos", ty.f32(), Id(23));
 
     GeneratorImpl& gen = Build();
 
@@ -379,10 +372,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_Override_NoId) {
-    auto* a = Override("a", ty.f32(), Expr(3_f),
-                       utils::Vector{
-                           Id(0),
-                       });
+    auto* a = Override("a", ty.f32(), Expr(3_f), Id(0));
     auto* b = Override("b", ty.f32(), Expr(2_f));
 
     GeneratorImpl& gen = Build();
diff --git a/src/tint/writer/glsl/generator_impl_sanitizer_test.cc b/src/tint/writer/glsl/generator_impl_sanitizer_test.cc
index c2a4ff2..c801b47 100644
--- a/src/tint/writer/glsl/generator_impl_sanitizer_test.cc
+++ b/src/tint/writer/glsl/generator_impl_sanitizer_test.cc
@@ -26,16 +26,11 @@
 
 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                      Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
+             Decl(Var("len", ty.u32(), Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -69,16 +64,11 @@
                                          Member(0, "z", ty.f32()),
                                          Member(4, "a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                      Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
+             Decl(Var("len", ty.u32(), Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -111,20 +101,16 @@
 
 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
 
-    auto* p = Let("p", nullptr, AddressOf("b"));
-    auto* p2 = Let("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
+    auto* p = Let("p", AddressOf("b"));
+    auto* p2 = Let("p2", AddressOf(MemberAccessor(Deref(p), "a")));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(p),
              Decl(p2),
-             Decl(Var("len", ty.u32(), ast::StorageClass::kNone, Call("arrayLength", p2))),
+             Decl(Var("len", ty.u32(), Call("arrayLength", p2))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -159,7 +145,7 @@
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("idx", nullptr, Expr(3_i))),
+             Decl(Var("idx", Expr(3_i))),
              Decl(Var("pos", ty.i32(), IndexAccessor(array_init, "idx"))),
          },
          utils::Vector{
@@ -196,7 +182,7 @@
                                });
     auto* struct_init = Construct(ty.Of(str), 1_i, vec3<f32>(2_f, 3_f, 4_f), 4_i);
     auto* struct_access = MemberAccessor(struct_init, "b");
-    auto* pos = Var("pos", ty.vec3<f32>(), ast::StorageClass::kNone, struct_access);
+    auto* pos = Var("pos", ty.vec3<f32>(), struct_access);
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -239,7 +225,7 @@
     // let x : i32 = *p;
     auto* v = Var("v", ty.i32());
     auto* p = Let("p", ty.pointer<i32>(ast::StorageClass::kFunction), AddressOf(v));
-    auto* x = Var("x", ty.i32(), ast::StorageClass::kNone, Deref(p));
+    auto* x = Var("x", ty.i32(), Deref(p));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -285,7 +271,7 @@
                    AddressOf(IndexAccessor(Deref(ap), 3_i)));
     auto* vp = Let("vp", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction),
                    AddressOf(IndexAccessor(Deref(mp), 2_i)));
-    auto* v = Var("v", ty.vec4<f32>(), ast::StorageClass::kNone, Deref(vp));
+    auto* v = Var("v", ty.vec4<f32>(), Deref(vp));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
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 0323d74..842bf35 100644
--- a/src/tint/writer/glsl/generator_impl_storage_buffer_test.cc
+++ b/src/tint/writer/glsl/generator_impl_storage_buffer_test.cc
@@ -35,11 +35,8 @@
                        ctx->Member("dewey", ctx->ty.f32(), utils::Vector{ctx->MemberAlign(256)}),
                        ctx->Member("louie", ctx->ty.f32(), utils::Vector{ctx->MemberAlign(256)}),
                    });
-    ctx->GlobalVar("nephews", ctx->ty.Of(nephews), ast::StorageClass::kStorage,
-                   utils::Vector{
-                       ctx->create<ast::BindingAttribute>(0u),
-                       ctx->create<ast::GroupAttribute>(0u),
-                   });
+    ctx->GlobalVar("nephews", ctx->ty.Of(nephews), ast::StorageClass::kStorage, ctx->Binding(0),
+                   ctx->Group(0));
 }
 
 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 aa2afab..23da975 100644
--- a/src/tint/writer/glsl/generator_impl_type_test.cc
+++ b/src/tint/writer/glsl/generator_impl_type_test.cc
@@ -312,11 +312,7 @@
 
     auto* t = ty.depth_texture(params.dim);
 
-    GlobalVar("tex", t,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("tex", t, Binding(1), Group(2));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -344,11 +340,7 @@
 TEST_F(GlslDepthMultisampledTexturesTest, Emit) {
     auto* t = ty.depth_multisampled_texture(ast::TextureDimension::k2d);
 
-    GlobalVar("tex", t,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("tex", t, Binding(1), Group(2));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -392,11 +384,7 @@
     }
     auto* t = ty.sampled_texture(params.dim, datatype);
 
-    GlobalVar("tex", t,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("tex", t, Binding(1), Group(2));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -531,11 +519,7 @@
 
     auto* t = ty.storage_texture(params.dim, params.imgfmt, ast::Access::kWrite);
 
-    GlobalVar("tex", t,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("tex", t, Binding(1), Group(2));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
diff --git a/src/tint/writer/glsl/generator_impl_unary_op_test.cc b/src/tint/writer/glsl/generator_impl_unary_op_test.cc
index 22012c8..009d173 100644
--- a/src/tint/writer/glsl/generator_impl_unary_op_test.cc
+++ b/src/tint/writer/glsl/generator_impl_unary_op_test.cc
@@ -45,8 +45,7 @@
 
 TEST_F(GlslUnaryOpTest, Indirection) {
     GlobalVar("G", ty.f32(), ast::StorageClass::kPrivate);
-    auto* p =
-        Let("expr", nullptr, create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
+    auto* p = Let("expr", create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
     auto* op = create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection, Expr("expr"));
     WrapInFunction(p, op);
 
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 4ea5779..a2e5ee8 100644
--- a/src/tint/writer/glsl/generator_impl_uniform_buffer_test.cc
+++ b/src/tint/writer/glsl/generator_impl_uniform_buffer_test.cc
@@ -24,7 +24,7 @@
 
 TEST_F(GlslGeneratorImplTest_UniformBuffer, Simple) {
     auto* simple = Structure("Simple", utils::Vector{Member("member", ty.f32())});
-    GlobalVar("simple", ty.Of(simple), ast::StorageClass::kUniform, GroupAndBinding(0, 0));
+    GlobalVar("simple", ty.Of(simple), ast::StorageClass::kUniform, Group(0), Binding(0));
 
     GeneratorImpl& gen = Build();
 
@@ -44,7 +44,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, GroupAndBinding(0, 0));
+    GlobalVar("simple", ty.Of(simple), ast::StorageClass::kUniform, Group(0), Binding(0));
 
     GeneratorImpl& gen = Build(Version(Version::Standard::kDesktop, 4, 4));
 
diff --git a/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc b/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc
index 1ad1c55..6cac463 100644
--- a/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc
+++ b/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc
@@ -65,11 +65,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_AInt) {
-    auto* C = Const("C", nullptr, Expr(1_a));
+    auto* C = Const("C", Expr(1_a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -86,11 +86,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_AFloat) {
-    auto* C = Const("C", nullptr, Expr(1._a));
+    auto* C = Const("C", Expr(1._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -107,11 +107,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_i32) {
-    auto* C = Const("C", nullptr, Expr(1_i));
+    auto* C = Const("C", Expr(1_i));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -128,11 +128,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_u32) {
-    auto* C = Const("C", nullptr, Expr(1_u));
+    auto* C = Const("C", Expr(1_u));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -149,11 +149,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_f32) {
-    auto* C = Const("C", nullptr, Expr(1_f));
+    auto* C = Const("C", Expr(1_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -172,11 +172,11 @@
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* C = Const("C", nullptr, Expr(1_h));
+    auto* C = Const("C", Expr(1_h));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -194,11 +194,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_AInt) {
-    auto* C = Const("C", nullptr, Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    auto* C = Const("C", Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -215,11 +215,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_AFloat) {
-    auto* C = Const("C", nullptr, Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    auto* C = Const("C", Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -236,11 +236,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_f32) {
-    auto* C = Const("C", nullptr, vec3<f32>(1_f, 2_f, 3_f));
+    auto* C = Const("C", vec3<f32>(1_f, 2_f, 3_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -259,11 +259,11 @@
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* C = Const("C", nullptr, vec3<f16>(1_h, 2_h, 3_h));
+    auto* C = Const("C", vec3<f16>(1_h, 2_h, 3_h));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -281,12 +281,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_AFloat) {
-    auto* C =
-        Const("C", nullptr, Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    auto* C = Const("C", Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -303,11 +302,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_f32) {
-    auto* C = Const("C", nullptr, mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
+    auto* C = Const("C", mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -326,11 +325,11 @@
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* C = Const("C", nullptr, mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
+    auto* C = Const("C", mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -348,11 +347,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_f32) {
-    auto* C = Const("C", nullptr, Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
+    auto* C = Const("C", Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -369,15 +368,14 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_vec2_bool) {
-    auto* C = Const("C", nullptr,
-                    Construct(ty.array(ty.vec2<bool>(), 3_u),  //
-                              vec2<bool>(true, false),         //
-                              vec2<bool>(false, true),         //
-                              vec2<bool>(true, true)));
+    auto* C = Const("C", Construct(ty.array(ty.vec2<bool>(), 3_u),  //
+                                   vec2<bool>(true, false),         //
+                                   vec2<bool>(false, true),         //
+                                   vec2<bool>(true, true)));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -421,7 +419,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroVec_f32) {
-    auto* var = Var("a", ty.vec3<f32>(), ast::StorageClass::kNone, vec3<f32>());
+    auto* var = Var("a", ty.vec3<f32>(), vec3<f32>());
 
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
@@ -436,7 +434,7 @@
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroVec_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = Var("a", ty.vec3<f16>(), ast::StorageClass::kNone, vec3<f16>());
+    auto* var = Var("a", ty.vec3<f16>(), vec3<f16>());
 
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
@@ -449,7 +447,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroMat_f32) {
-    auto* var = Var("a", ty.mat2x3<f32>(), ast::StorageClass::kNone, mat2x3<f32>());
+    auto* var = Var("a", ty.mat2x3<f32>(), mat2x3<f32>());
 
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
@@ -465,7 +463,7 @@
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroMat_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = Var("a", ty.mat2x3<f16>(), ast::StorageClass::kNone, mat2x3<f16>());
+    auto* var = Var("a", ty.mat2x3<f16>(), mat2x3<f16>());
 
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
diff --git a/src/tint/writer/hlsl/generator_impl_binary_test.cc b/src/tint/writer/hlsl/generator_impl_binary_test.cc
index d5f70fb..aa54144 100644
--- a/src/tint/writer/hlsl/generator_impl_binary_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_binary_test.cc
@@ -678,7 +678,7 @@
     Func("fn", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(Var("a", ty.i32())),
-             Decl(Let("r", nullptr, Op("a", 0_i))),
+             Decl(Let("r", Op("a", 0_i))),
          });
 
     GeneratorImpl& gen = Build();
@@ -696,7 +696,7 @@
     Func("fn", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(Var("a", ty.u32())),
-             Decl(Let("r", nullptr, Op("a", 0_u))),
+             Decl(Let("r", Op("a", 0_u))),
          });
 
     GeneratorImpl& gen = Build();
@@ -713,8 +713,8 @@
 TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_vec_by_vec_i32) {
     Func("fn", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("a", nullptr, vec4<i32>(100_i, 100_i, 100_i, 100_i))),
-             Decl(Let("r", nullptr, Op("a", vec4<i32>(50_i, 0_i, 25_i, 0_i)))),
+             Decl(Var("a", vec4<i32>(100_i, 100_i, 100_i, 100_i))),
+             Decl(Let("r", Op("a", vec4<i32>(50_i, 0_i, 25_i, 0_i)))),
          });
 
     GeneratorImpl& gen = Build();
@@ -731,8 +731,8 @@
 TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_vec_by_scalar_i32) {
     Func("fn", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("a", nullptr, vec4<i32>(100_i, 100_i, 100_i, 100_i))),
-             Decl(Let("r", nullptr, Op("a", 0_i))),
+             Decl(Var("a", vec4<i32>(100_i, 100_i, 100_i, 100_i))),
+             Decl(Let("r", Op("a", 0_i))),
          });
 
     GeneratorImpl& gen = Build();
@@ -750,7 +750,7 @@
     Func("fn", utils::Vector{Param("b", ty.i32())}, ty.void_(),
          utils::Vector{
              Decl(Var("a", ty.i32())),
-             Decl(Let("r", nullptr, Op("a", "b"))),
+             Decl(Let("r", Op("a", "b"))),
          });
 
     GeneratorImpl& gen = Build();
@@ -768,7 +768,7 @@
     Func("fn", utils::Vector{Param("b", ty.u32())}, ty.void_(),
          utils::Vector{
              Decl(Var("a", ty.u32())),
-             Decl(Let("r", nullptr, Op("a", "b"))),
+             Decl(Let("r", Op("a", "b"))),
          });
 
     GeneratorImpl& gen = Build();
@@ -786,7 +786,7 @@
     Func("fn", utils::Vector{Param("b", ty.vec3<i32>())}, ty.void_(),
          utils::Vector{
              Decl(Var("a", ty.vec3<i32>())),
-             Decl(Let("r", nullptr, Op("a", "b"))),
+             Decl(Let("r", Op("a", "b"))),
          });
 
     GeneratorImpl& gen = Build();
@@ -804,7 +804,7 @@
     Func("fn", utils::Vector{Param("b", ty.i32())}, ty.void_(),
          utils::Vector{
              Decl(Var("a", ty.vec3<i32>())),
-             Decl(Let("r", nullptr, Op("a", "b"))),
+             Decl(Let("r", Op("a", "b"))),
          });
 
     GeneratorImpl& gen = Build();
@@ -827,7 +827,7 @@
     Func("fn", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(Var("a", ty.i32())),
-             Decl(Let("r", nullptr, Op("a", Call("zero")))),
+             Decl(Let("r", Op("a", Call("zero")))),
          });
 
     GeneratorImpl& gen = Build();
@@ -858,7 +858,7 @@
     Func("fn", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(Var("a", ty.u32())),
-             Decl(Let("r", nullptr, Op("a", Call("zero")))),
+             Decl(Let("r", Op("a", Call("zero")))),
          });
 
     GeneratorImpl& gen = Build();
@@ -889,7 +889,7 @@
     Func("fn", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(Var("a", ty.vec3<i32>())),
-             Decl(Let("r", nullptr, Op("a", Call("zero")))),
+             Decl(Let("r", Op("a", Call("zero")))),
          });
 
     GeneratorImpl& gen = Build();
@@ -920,7 +920,7 @@
     Func("fn", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(Var("a", ty.vec3<i32>())),
-             Decl(Let("r", nullptr, Op("a", Call("zero")))),
+             Decl(Let("r", Op("a", Call("zero")))),
          });
 
     GeneratorImpl& gen = Build();
diff --git a/src/tint/writer/hlsl/generator_impl_constructor_test.cc b/src/tint/writer/hlsl/generator_impl_constructor_test.cc
index bb708db..e5157d9 100644
--- a/src/tint/writer/hlsl/generator_impl_constructor_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_constructor_test.cc
@@ -183,7 +183,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec_SingleScalar_F32_Var) {
-    auto* var = Var("v", nullptr, Expr(2_f));
+    auto* var = Var("v", Expr(2_f));
     auto* cast = vec3<f32>(var);
     WrapInFunction(var, cast);
 
@@ -197,7 +197,7 @@
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec_SingleScalar_F16_Var) {
     Enable(ast::Extension::kF16);
 
-    auto* var = Var("v", nullptr, Expr(2_h));
+    auto* var = Var("v", Expr(2_h));
     auto* cast = vec3<f16>(var);
     WrapInFunction(var, cast);
 
@@ -218,7 +218,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec_SingleScalar_Bool_Var) {
-    auto* var = Var("v", nullptr, Expr(true));
+    auto* var = Var("v", Expr(true));
     auto* cast = vec3<bool>(var);
     WrapInFunction(var, cast);
 
diff --git a/src/tint/writer/hlsl/generator_impl_function_test.cc b/src/tint/writer/hlsl/generator_impl_function_test.cc
index 7e1ce60..5bff35e 100644
--- a/src/tint/writer/hlsl/generator_impl_function_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_function_test.cc
@@ -360,11 +360,7 @@
 
 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,
-                          utils::Vector{
-                              create<ast::BindingAttribute>(0u),
-                              create<ast::GroupAttribute>(1u),
-                          });
+    auto* ubo = GlobalVar("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform, Binding(0), Group(1));
 
     Func("sub_func",
          utils::Vector{
@@ -375,7 +371,7 @@
              Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")),
          });
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
+    auto* var = Var("v", ty.f32(), Call("sub_func", 1_f));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -407,14 +403,9 @@
 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("uniforms", ty.Of(s), ast::StorageClass::kUniform, Binding(0), Group(1));
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
-                    MemberAccessor(MemberAccessor("uniforms", "coord"), "x"));
+    auto* var = Var("v", ty.f32(), MemberAccessor(MemberAccessor("uniforms", "coord"), "x"));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -445,13 +436,10 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
+              Group(1));
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("coord", "b"));
+    auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b"));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -481,13 +469,10 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
+              Group(1));
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("coord", "b"));
+    auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b"));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -517,11 +502,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
+              Group(1));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -551,11 +533,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
+              Group(1));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -581,11 +560,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kUniform, Binding(0), Group(1));
 
     Func("sub_func",
          utils::Vector{
@@ -596,7 +571,7 @@
              Return(MemberAccessor("coord", "x")),
          });
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
+    auto* var = Var("v", ty.f32(), Call("sub_func", 1_f));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -627,11 +602,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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(1u),
-              });
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
+              Group(1));
 
     Func("sub_func",
          utils::Vector{
@@ -642,7 +614,7 @@
              Return(MemberAccessor("coord", "x")),
          });
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
+    auto* var = Var("v", ty.f32(), Call("sub_func", 1_f));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -741,9 +713,9 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Attribute_EntryPoint_Compute_WithWorkgroup_OverridableConst) {
-    Override("width", ty.i32(), Construct(ty.i32(), 2_i), utils::Vector{Id(7u)});
-    Override("height", ty.i32(), Construct(ty.i32(), 3_i), utils::Vector{Id(8u)});
-    Override("depth", ty.i32(), Construct(ty.i32(), 4_i), utils::Vector{Id(9u)});
+    Override("width", ty.i32(), Construct(ty.i32(), 2_i), Id(7u));
+    Override("height", ty.i32(), Construct(ty.i32(), 3_i), Id(8u));
+    Override("depth", ty.i32(), Construct(ty.i32(), 4_i), Id(9u));
     Func("main", utils::Empty, ty.void_(), utils::Empty,
          utils::Vector{
              Stage(ast::PipelineStage::kCompute),
@@ -873,14 +845,11 @@
 
     auto* s = Structure("Data", utils::Vector{Member("d", ty.f32())});
 
-    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
+              Group(0));
 
     {
-        auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
+        auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
 
         Func("a", utils::Empty, ty.void_(),
              utils::Vector{
@@ -894,7 +863,7 @@
     }
 
     {
-        auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
+        auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
 
         Func("b", utils::Empty, ty.void_(),
              utils::Vector{
diff --git a/src/tint/writer/hlsl/generator_impl_loop_test.cc b/src/tint/writer/hlsl/generator_impl_loop_test.cc
index 9f288f6..77f0dd4 100644
--- a/src/tint/writer/hlsl/generator_impl_loop_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_loop_test.cc
@@ -187,7 +187,7 @@
 
     auto* multi_stmt =
         create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
-    auto* f = For(Decl(Var("b", nullptr, multi_stmt)), nullptr, nullptr, Block(Return()));
+    auto* f = For(Decl(Var("b", multi_stmt)), nullptr, nullptr, Block(Return()));
     WrapInFunction(f);
 
     GeneratorImpl& gen = Build();
@@ -341,8 +341,8 @@
     auto* multi_stmt_c =
         create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
 
-    auto* f = For(Decl(Var("i", nullptr, multi_stmt_a)), multi_stmt_b, Assign("i", multi_stmt_c),
-                  Block(Return()));
+    auto* f =
+        For(Decl(Var("i", multi_stmt_a)), multi_stmt_b, Assign("i", multi_stmt_c), Block(Return()));
     WrapInFunction(f);
 
     GeneratorImpl& gen = Build();
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 a0068b9..39dd86c 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.GroupAndBinding(1, 0));
+                    b.Group(1), b.Binding(0));
     }
 
     void SetupFunction(utils::VectorRef<const ast::Statement*> statements) {
@@ -114,7 +114,7 @@
     GlobalVar("str", ty.Of(s), ast::StorageClass::kPrivate);
 
     auto* expr = MemberAccessor("str", "mem");
-    WrapInFunction(Var("expr", ty.f32(), ast::StorageClass::kNone, expr));
+    WrapInFunction(Var("expr", ty.f32(), expr));
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -162,7 +162,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone, MemberAccessor("data", "b"))),
+        Decl(Var("x", MemberAccessor("data", "b"))),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -233,8 +233,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("value", p.member_type(ty), ast::StorageClass::kNone,
-                 Construct(p.member_type(ty)))),
+        Decl(Var("value", p.member_type(ty), Construct(p.member_type(ty)))),
         Assign(MemberAccessor("data", "b"), Expr("value")),
     });
 
@@ -354,8 +353,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
-                 IndexAccessor(IndexAccessor(MemberAccessor("data", "a"), 2_i), 1_i))),
+        Decl(Var("x", IndexAccessor(IndexAccessor(MemberAccessor("data", "a"), 2_i), 1_i))),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -386,8 +384,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
-                 IndexAccessor(MemberAccessor("data", "a"), 2_i))),
+        Decl(Var("x", IndexAccessor(MemberAccessor("data", "a"), 2_i))),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -418,11 +415,10 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("a", nullptr, Expr(2_i))),
-        Decl(Var("b", nullptr, Expr(4_i))),
-        Decl(Var("c", nullptr, Expr(3_i))),
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
-                 IndexAccessor(MemberAccessor("data", "a"), Sub(Add("a", "b"), "c")))),
+        Decl(Var("a", Expr(2_i))),
+        Decl(Var("b", Expr(4_i))),
+        Decl(Var("c", Expr(3_i))),
+        Decl(Var("x", IndexAccessor(MemberAccessor("data", "a"), Sub(Add("a", "b"), "c")))),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -494,8 +490,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
-                 MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"))),
+        Decl(Var("x", MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"))),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -534,7 +529,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
+        Decl(Var("x",
                  MemberAccessor(
                      MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), "xy"))),
     });
@@ -576,7 +571,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
+        Decl(Var("x",
                  MemberAccessor(
                      MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), "g"))),
     });
@@ -617,7 +612,7 @@
     });
 
     SetupFunction(utils::Vector{
-        Decl(Var("x", nullptr, ast::StorageClass::kNone,
+        Decl(Var("x",
                  IndexAccessor(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"),
                                1_i))),
     });
@@ -718,8 +713,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor, Swizzle_xyz) {
-    auto* var =
-        Var("my_vec", ty.vec4<f32>(), ast::StorageClass::kNone, vec4<f32>(1_f, 2_f, 3_f, 4_f));
+    auto* var = Var("my_vec", ty.vec4<f32>(), vec4<f32>(1_f, 2_f, 3_f, 4_f));
     auto* expr = MemberAccessor("my_vec", "xyz");
     WrapInFunction(var, expr);
 
@@ -729,8 +723,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor, Swizzle_gbr) {
-    auto* var =
-        Var("my_vec", ty.vec4<f32>(), ast::StorageClass::kNone, vec4<f32>(1_f, 2_f, 3_f, 4_f));
+    auto* var = Var("my_vec", ty.vec4<f32>(), vec4<f32>(1_f, 2_f, 3_f, 4_f));
     auto* expr = MemberAccessor("my_vec", "gbr");
     WrapInFunction(var, expr);
 
diff --git a/src/tint/writer/hlsl/generator_impl_module_constant_test.cc b/src/tint/writer/hlsl/generator_impl_module_constant_test.cc
index 7296d13..49cf831 100644
--- a/src/tint/writer/hlsl/generator_impl_module_constant_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_module_constant_test.cc
@@ -23,8 +23,8 @@
 using HlslGeneratorImplTest_ModuleConstant = TestHelper;
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_AInt) {
-    auto* var = GlobalConst("G", nullptr, Expr(1_a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Expr(1_a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -37,8 +37,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_AFloat) {
-    auto* var = GlobalConst("G", nullptr, Expr(1._a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Expr(1._a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -51,8 +51,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_i32) {
-    auto* var = GlobalConst("G", nullptr, Expr(1_i));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Expr(1_i));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -65,8 +65,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_u32) {
-    auto* var = GlobalConst("G", nullptr, Expr(1_u));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Expr(1_u));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -79,8 +79,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_f32) {
-    auto* var = GlobalConst("G", nullptr, Expr(1_f));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Expr(1_f));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -95,8 +95,8 @@
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = GlobalConst("G", nullptr, Expr(1_h));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Expr(1_h));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -109,8 +109,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_AInt) {
-    auto* var = GlobalConst("G", nullptr, Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -123,8 +123,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_AFloat) {
-    auto* var = GlobalConst("G", nullptr, Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -137,8 +137,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_f32) {
-    auto* var = GlobalConst("G", nullptr, vec3<f32>(1_f, 2_f, 3_f));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", vec3<f32>(1_f, 2_f, 3_f));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -153,8 +153,8 @@
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = GlobalConst("G", nullptr, vec3<f16>(1_h, 2_h, 3_h));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", vec3<f16>(1_h, 2_h, 3_h));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -167,9 +167,9 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_mat2x3_AFloat) {
-    auto* var = GlobalConst("G", nullptr,
-                            Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var =
+        GlobalConst("G", Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -182,8 +182,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_mat2x3_f32) {
-    auto* var = GlobalConst("G", nullptr, mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -198,8 +198,8 @@
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_mat2x3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = GlobalConst("G", nullptr, mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -212,8 +212,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_arr_f32) {
-    auto* var = GlobalConst("G", nullptr, Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -226,12 +226,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_arr_vec2_bool) {
-    auto* var = GlobalConst("G", nullptr,
-                            Construct(ty.array(ty.vec2<bool>(), 3_u),  //
-                                      vec2<bool>(true, false),         //
-                                      vec2<bool>(false, true),         //
-                                      vec2<bool>(true, true)));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Construct(ty.array(ty.vec2<bool>(), 3_u),  //
+                                           vec2<bool>(true, false),         //
+                                           vec2<bool>(false, true),         //
+                                           vec2<bool>(true, true)));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -244,7 +243,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_Override) {
-    auto* var = Override("pos", ty.f32(), Expr(3_f), utils::Vector{Id(23)});
+    auto* var = Override("pos", ty.f32(), Expr(3_f), Id(23));
 
     GeneratorImpl& gen = Build();
 
@@ -257,7 +256,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_Override_NoConstructor) {
-    auto* var = Override("pos", ty.f32(), nullptr, utils::Vector{Id(23)});
+    auto* var = Override("pos", ty.f32(), Id(23));
 
     GeneratorImpl& gen = Build();
 
@@ -270,7 +269,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_Override_NoId) {
-    auto* a = Override("a", ty.f32(), Expr(3_f), utils::Vector{Id(0)});
+    auto* a = Override("a", ty.f32(), Expr(3_f), Id(0));
     auto* b = Override("b", ty.f32(), Expr(2_f));
 
     GeneratorImpl& gen = Build();
diff --git a/src/tint/writer/hlsl/generator_impl_sanitizer_test.cc b/src/tint/writer/hlsl/generator_impl_sanitizer_test.cc
index 3d0785a..62a236e 100644
--- a/src/tint/writer/hlsl/generator_impl_sanitizer_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_sanitizer_test.cc
@@ -26,16 +26,11 @@
 
 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                      Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
+             Decl(Var("len", ty.u32(), Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -64,16 +59,11 @@
                                          Member(0, "z", ty.f32()),
                                          Member(4, "a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                      Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
+             Decl(Var("len", ty.u32(), Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -100,20 +90,16 @@
 
 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
 
-    auto* p = Let("p", nullptr, AddressOf("b"));
-    auto* p2 = Let("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
+    auto* p = Let("p", AddressOf("b"));
+    auto* p2 = Let("p2", AddressOf(MemberAccessor(Deref(p), "a")));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(p),
              Decl(p2),
-             Decl(Var("len", ty.u32(), ast::StorageClass::kNone, Call("arrayLength", p2))),
+             Decl(Var("len", ty.u32(), Call("arrayLength", p2))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -140,20 +126,12 @@
 
 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
-    GlobalVar("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(2u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    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));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
+             Decl(Var("len", ty.u32(),
                       Add(Call("arrayLength", AddressOf(MemberAccessor("b", "a"))),
                           Call("arrayLength", AddressOf(MemberAccessor("c", "a")))))),
          },
@@ -191,7 +169,7 @@
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("idx", nullptr, Expr(3_i))),
+             Decl(Var("idx", Expr(3_i))),
              Decl(Var("pos", ty.i32(), IndexAccessor(array_init, "idx"))),
          },
          utils::Vector{
@@ -221,7 +199,7 @@
                                });
     auto* struct_init = Construct(ty.Of(str), 1_i, vec3<f32>(2_f, 3_f, 4_f), 4_i);
     auto* struct_access = MemberAccessor(struct_init, "b");
-    auto* pos = Var("pos", ty.vec3<f32>(), ast::StorageClass::kNone, struct_access);
+    auto* pos = Var("pos", ty.vec3<f32>(), struct_access);
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -257,7 +235,7 @@
     // let x : i32 = *p;
     auto* v = Var("v", ty.i32());
     auto* p = Let("p", ty.pointer<i32>(ast::StorageClass::kFunction), AddressOf(v));
-    auto* x = Var("x", ty.i32(), ast::StorageClass::kNone, Deref(p));
+    auto* x = Var("x", ty.i32(), Deref(p));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -296,7 +274,7 @@
                    AddressOf(IndexAccessor(Deref(ap), 3_i)));
     auto* vp = Let("vp", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction),
                    AddressOf(IndexAccessor(Deref(mp), 2_i)));
-    auto* v = Var("v", ty.vec4<f32>(), ast::StorageClass::kNone, Deref(vp));
+    auto* v = Var("v", ty.vec4<f32>(), Deref(vp));
 
     Func("main", 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 4e443d5..e89c500 100644
--- a/src/tint/writer/hlsl/generator_impl_type_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_type_test.cc
@@ -177,11 +177,8 @@
                                  Member("a", ty.i32()),
                                  Member("b", ty.f32()),
                              });
-    GlobalVar("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
+              Group(0));
 
     GeneratorImpl& gen = Build();
 
@@ -311,11 +308,7 @@
 
     auto* t = ty.depth_texture(params.dim);
 
-    GlobalVar("tex", t,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("tex", t, Binding(1), Group(2));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -346,11 +339,7 @@
 TEST_F(HlslDepthMultisampledTexturesTest, Emit) {
     auto* t = ty.depth_multisampled_texture(ast::TextureDimension::k2d);
 
-    GlobalVar("tex", t,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("tex", t, Binding(1), Group(2));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -394,11 +383,7 @@
     }
     auto* t = ty.sampled_texture(params.dim, datatype);
 
-    GlobalVar("tex", t,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("tex", t, Binding(1), Group(2));
 
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -536,7 +521,8 @@
 
     GlobalVar("tex", t,
               utils::Vector{
-                  GroupAndBinding(2, 1),
+                  Group(2),
+                  Binding(1),
               });
 
     Func("main", utils::Empty, ty.void_(),
diff --git a/src/tint/writer/hlsl/generator_impl_unary_op_test.cc b/src/tint/writer/hlsl/generator_impl_unary_op_test.cc
index 74320ea..d311061 100644
--- a/src/tint/writer/hlsl/generator_impl_unary_op_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_unary_op_test.cc
@@ -45,8 +45,7 @@
 
 TEST_F(HlslUnaryOpTest, Indirection) {
     GlobalVar("G", ty.f32(), ast::StorageClass::kPrivate);
-    auto* p =
-        Let("expr", nullptr, create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
+    auto* p = Let("expr", create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
     auto* op = create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection, Expr("expr"));
     WrapInFunction(p, op);
 
diff --git a/src/tint/writer/hlsl/generator_impl_variable_decl_statement_test.cc b/src/tint/writer/hlsl/generator_impl_variable_decl_statement_test.cc
index db8fb77..4e4643f 100644
--- a/src/tint/writer/hlsl/generator_impl_variable_decl_statement_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_variable_decl_statement_test.cc
@@ -65,11 +65,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_AInt) {
-    auto* C = Const("C", nullptr, Expr(1_a));
+    auto* C = Const("C", Expr(1_a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -83,11 +83,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_AFloat) {
-    auto* C = Const("C", nullptr, Expr(1._a));
+    auto* C = Const("C", Expr(1._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -101,11 +101,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_i32) {
-    auto* C = Const("C", nullptr, Expr(1_i));
+    auto* C = Const("C", Expr(1_i));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -119,11 +119,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_u32) {
-    auto* C = Const("C", nullptr, Expr(1_u));
+    auto* C = Const("C", Expr(1_u));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -137,11 +137,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_f32) {
-    auto* C = Const("C", nullptr, Expr(1_f));
+    auto* C = Const("C", Expr(1_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -157,11 +157,11 @@
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* C = Const("C", nullptr, Expr(1_h));
+    auto* C = Const("C", Expr(1_h));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -175,11 +175,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_AInt) {
-    auto* C = Const("C", nullptr, Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    auto* C = Const("C", Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -193,11 +193,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_AFloat) {
-    auto* C = Const("C", nullptr, Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    auto* C = Const("C", Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -211,11 +211,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_f32) {
-    auto* C = Const("C", nullptr, vec3<f32>(1_f, 2_f, 3_f));
+    auto* C = Const("C", vec3<f32>(1_f, 2_f, 3_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -231,11 +231,11 @@
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* C = Const("C", nullptr, vec3<f16>(1_h, 2_h, 3_h));
+    auto* C = Const("C", vec3<f16>(1_h, 2_h, 3_h));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -249,12 +249,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_AFloat) {
-    auto* C =
-        Const("C", nullptr, Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    auto* C = Const("C", Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -268,11 +267,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_f32) {
-    auto* C = Const("C", nullptr, mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
+    auto* C = Const("C", mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -288,11 +287,11 @@
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* C = Const("C", nullptr, mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
+    auto* C = Const("C", mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -306,11 +305,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_f32) {
-    auto* C = Const("C", nullptr, Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
+    auto* C = Const("C", Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -324,15 +323,14 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_vec2_bool) {
-    auto* C = Const("C", nullptr,
-                    Construct(ty.array(ty.vec2<bool>(), 3_u),  //
-                              vec2<bool>(true, false),         //
-                              vec2<bool>(false, true),         //
-                              vec2<bool>(true, true)));
+    auto* C = Const("C", Construct(ty.array(ty.vec2<bool>(), 3_u),  //
+                                   vec2<bool>(true, false),         //
+                                   vec2<bool>(false, true),         //
+                                   vec2<bool>(true, true)));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -372,7 +370,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroVec_F32) {
-    auto* var = Var("a", ty.vec3<f32>(), ast::StorageClass::kNone, vec3<f32>());
+    auto* var = Var("a", ty.vec3<f32>(), vec3<f32>());
 
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
@@ -387,7 +385,7 @@
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroVec_F16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = Var("a", ty.vec3<f16>(), ast::StorageClass::kNone, vec3<f16>());
+    auto* var = Var("a", ty.vec3<f16>(), vec3<f16>());
 
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
@@ -400,7 +398,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroMat_F32) {
-    auto* var = Var("a", ty.mat2x3<f32>(), ast::StorageClass::kNone, mat2x3<f32>());
+    auto* var = Var("a", ty.mat2x3<f32>(), mat2x3<f32>());
 
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
@@ -416,7 +414,7 @@
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroMat_F16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = Var("a", ty.mat2x3<f16>(), ast::StorageClass::kNone, mat2x3<f16>());
+    auto* var = Var("a", ty.mat2x3<f16>(), mat2x3<f16>());
 
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 602ec71..cccf622 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -1764,14 +1764,8 @@
 
 bool GeneratorImpl::EmitExpression(std::ostream& out, const ast::Expression* expr) {
     if (auto* sem = builder_.Sem().Get(expr)) {
-        if (auto* user = sem->As<sem::VariableUser>();
-            !user || !user->Variable()->Declaration()->Is<ast::Let>()) {
-            // Disable constant inlining if the constant expression is from a 'let' declaration.
-            // TODO(crbug.com/tint/1580): Once 'const' is implemented, 'let' will no longer resolve
-            // to a shader-creation time constant value, and this can be removed.
-            if (auto constant = sem->ConstantValue()) {
-                return EmitConstant(out, constant);
-            }
+        if (auto constant = sem->ConstantValue()) {
+            return EmitConstant(out, constant);
         }
     }
     return Switch(
diff --git a/src/tint/writer/msl/generator_impl_array_accessor_test.cc b/src/tint/writer/msl/generator_impl_array_accessor_test.cc
index bfa76dc..e058ff5 100644
--- a/src/tint/writer/msl/generator_impl_array_accessor_test.cc
+++ b/src/tint/writer/msl/generator_impl_array_accessor_test.cc
@@ -36,7 +36,7 @@
 TEST_F(MslGeneratorImplTest, IndexAccessor_OfDref) {
     GlobalVar("ary", ty.array<i32, 10>(), ast::StorageClass::kPrivate);
 
-    auto* p = Let("p", nullptr, AddressOf("ary"));
+    auto* p = Let("p", AddressOf("ary"));
     auto* expr = IndexAccessor(Deref("p"), 5_i);
     WrapInFunction(p, expr);
 
diff --git a/src/tint/writer/msl/generator_impl_binary_test.cc b/src/tint/writer/msl/generator_impl_binary_test.cc
index cfc8c7e..e08c420 100644
--- a/src/tint/writer/msl/generator_impl_binary_test.cc
+++ b/src/tint/writer/msl/generator_impl_binary_test.cc
@@ -202,8 +202,8 @@
 }
 
 TEST_F(MslBinaryTest, BoolAnd) {
-    auto* left = Var("left", nullptr, Expr(true));
-    auto* right = Var("right", nullptr, Expr(false));
+    auto* left = Var("left", Expr(true));
+    auto* right = Var("right", Expr(false));
     auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kAnd, Expr(left), Expr(right));
     WrapInFunction(left, right, expr);
 
@@ -215,8 +215,8 @@
 }
 
 TEST_F(MslBinaryTest, BoolOr) {
-    auto* left = Var("left", nullptr, Expr(true));
-    auto* right = Var("right", nullptr, Expr(false));
+    auto* left = Var("left", Expr(true));
+    auto* right = Var("right", Expr(false));
     auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kOr, Expr(left), Expr(right));
     WrapInFunction(left, right, expr);
 
diff --git a/src/tint/writer/msl/generator_impl_constructor_test.cc b/src/tint/writer/msl/generator_impl_constructor_test.cc
index b0bbbea..4626e67 100644
--- a/src/tint/writer/msl/generator_impl_constructor_test.cc
+++ b/src/tint/writer/msl/generator_impl_constructor_test.cc
@@ -181,7 +181,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Vec_SingleScalar_F32_Var) {
-    auto* var = Var("v", nullptr, Expr(2_f));
+    auto* var = Var("v", Expr(2_f));
     auto* cast = vec3<f32>(var);
     WrapInFunction(var, cast);
 
@@ -195,7 +195,7 @@
 TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Vec_SingleScalar_F16_Var) {
     Enable(ast::Extension::kF16);
 
-    auto* var = Var("v", nullptr, Expr(2_h));
+    auto* var = Var("v", Expr(2_h));
     auto* cast = vec3<f16>(var);
     WrapInFunction(var, cast);
 
diff --git a/src/tint/writer/msl/generator_impl_function_test.cc b/src/tint/writer/msl/generator_impl_function_test.cc
index 0f891a2..56761a7 100644
--- a/src/tint/writer/msl/generator_impl_function_test.cc
+++ b/src/tint/writer/msl/generator_impl_function_test.cc
@@ -342,10 +342,10 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              GroupAndBinding(0, 0));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(0),
+              Binding(0));
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("coord", "b"));
+    auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b"));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -381,10 +381,10 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              GroupAndBinding(0, 0));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Group(0),
+              Binding(0));
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("coord", "b"));
+    auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b"));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -416,7 +416,7 @@
 
 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, GroupAndBinding(0, 0));
+    auto* ubo = GlobalVar("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform, Group(0), Binding(0));
 
     Func("sub_func",
          utils::Vector{
@@ -427,7 +427,7 @@
              Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")),
          });
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
+    auto* var = Var("v", ty.f32(), Call("sub_func", 1_f));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -466,8 +466,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              GroupAndBinding(0, 0));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(0),
+              Binding(0));
 
     Func("sub_func",
          utils::Vector{
@@ -478,7 +478,7 @@
              Return(MemberAccessor("coord", "b")),
          });
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
+    auto* var = Var("v", ty.f32(), Call("sub_func", 1_f));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -518,8 +518,8 @@
                                     Member("b", ty.f32()),
                                 });
 
-    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              GroupAndBinding(0, 0));
+    GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Group(0),
+              Binding(0));
 
     Func("sub_func",
          utils::Vector{
@@ -530,7 +530,7 @@
              Return(MemberAccessor("coord", "b")),
          });
 
-    auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
+    auto* var = Var("v", ty.f32(), Call("sub_func", 1_f));
 
     Func("frag_main", utils::Empty, ty.void_(),
          utils::Vector{
@@ -656,11 +656,11 @@
 
     auto* s = Structure("Data", utils::Vector{Member("d", ty.f32())});
 
-    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              GroupAndBinding(0, 0));
+    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Group(0),
+              Binding(0));
 
     {
-        auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
+        auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
 
         Func("a", utils::Empty, ty.void_(),
              utils::Vector{
@@ -674,7 +674,7 @@
     }
 
     {
-        auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
+        auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
 
         Func("b", utils::Empty, ty.void_(),
              utils::Vector{
diff --git a/src/tint/writer/msl/generator_impl_module_constant_test.cc b/src/tint/writer/msl/generator_impl_module_constant_test.cc
index 054a0f4..fd7b5d6 100644
--- a/src/tint/writer/msl/generator_impl_module_constant_test.cc
+++ b/src/tint/writer/msl/generator_impl_module_constant_test.cc
@@ -23,8 +23,8 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_AInt) {
-    auto* var = GlobalConst("G", nullptr, Expr(1_a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Expr(1_a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -41,8 +41,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_AFloat) {
-    auto* var = GlobalConst("G", nullptr, Expr(1._a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Expr(1._a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -59,8 +59,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_i32) {
-    auto* var = GlobalConst("G", nullptr, Expr(1_i));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Expr(1_i));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -77,8 +77,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_u32) {
-    auto* var = GlobalConst("G", nullptr, Expr(1_u));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Expr(1_u));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -95,8 +95,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_f32) {
-    auto* var = GlobalConst("G", nullptr, Expr(1_f));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Expr(1_f));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -115,8 +115,8 @@
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = GlobalConst("G", nullptr, Expr(1_h));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Expr(1_h));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -133,8 +133,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_vec3_AInt) {
-    auto* var = GlobalConst("G", nullptr, Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -151,8 +151,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_vec3_AFloat) {
-    auto* var = GlobalConst("G", nullptr, Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -169,8 +169,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_vec3_f32) {
-    auto* var = GlobalConst("G", nullptr, vec3<f32>(1_f, 2_f, 3_f));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", vec3<f32>(1_f, 2_f, 3_f));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -189,8 +189,8 @@
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_vec3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = GlobalConst("G", nullptr, vec3<f16>(1_h, 2_h, 3_h));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", vec3<f16>(1_h, 2_h, 3_h));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -207,9 +207,9 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_mat2x3_AFloat) {
-    auto* var = GlobalConst("G", nullptr,
-                            Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var =
+        GlobalConst("G", Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -226,8 +226,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_mat2x3_f32) {
-    auto* var = GlobalConst("G", nullptr, mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -246,8 +246,8 @@
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_mat2x3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = GlobalConst("G", nullptr, mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -264,8 +264,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_arr_f32) {
-    auto* var = GlobalConst("G", nullptr, Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -295,12 +295,11 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_arr_vec2_bool) {
-    auto* var = GlobalConst("G", nullptr,
-                            Construct(ty.array(ty.vec2<bool>(), 3_u),  //
-                                      vec2<bool>(true, false),         //
-                                      vec2<bool>(false, true),         //
-                                      vec2<bool>(true, true)));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
+    auto* var = GlobalConst("G", Construct(ty.array(ty.vec2<bool>(), 3_u),  //
+                                           vec2<bool>(true, false),         //
+                                           vec2<bool>(false, true),         //
+                                           vec2<bool>(true, true)));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -330,10 +329,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_Override) {
-    auto* var = Override("pos", ty.f32(), Expr(3_f),
-                         utils::Vector{
-                             Id(23),
-                         });
+    auto* var = Override("pos", ty.f32(), Expr(3_f), Id(23));
 
     GeneratorImpl& gen = Build();
 
@@ -342,11 +338,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_Override_NoId) {
-    auto* var_a = Override("a", ty.f32(), nullptr,
-                           utils::Vector{
-                               Id(0),
-                           });
-    auto* var_b = Override("b", ty.f32(), nullptr);
+    auto* var_a = Override("a", ty.f32(), Id(0));
+    auto* var_b = Override("b", ty.f32());
 
     GeneratorImpl& gen = Build();
 
diff --git a/src/tint/writer/msl/generator_impl_sanitizer_test.cc b/src/tint/writer/msl/generator_impl_sanitizer_test.cc
index 62ada06..472822b 100644
--- a/src/tint/writer/msl/generator_impl_sanitizer_test.cc
+++ b/src/tint/writer/msl/generator_impl_sanitizer_test.cc
@@ -27,16 +27,11 @@
 
 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                      Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
+             Decl(Var("len", ty.u32(), Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -85,16 +80,11 @@
                                          Member(0, "z", ty.f32()),
                                          Member(4, "a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
-                      Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
+             Decl(Var("len", ty.u32(), Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -142,20 +132,16 @@
 
 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
 
-    auto* p = Let("p", nullptr, AddressOf("b"));
-    auto* p2 = Let("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
+    auto* p = Let("p", AddressOf("b"));
+    auto* p2 = Let("p2", AddressOf(MemberAccessor(Deref(p), "a")));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(p),
              Decl(p2),
-             Decl(Var("len", ty.u32(), ast::StorageClass::kNone, Call("arrayLength", p2))),
+             Decl(Var("len", ty.u32(), Call("arrayLength", p2))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -202,20 +188,12 @@
 
 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(0u),
-              });
-    GlobalVar("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(2u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    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));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
+             Decl(Var("len", ty.u32(),
                       Add(Call("arrayLength", AddressOf(MemberAccessor("b", "a"))),
                           Call("arrayLength", AddressOf(MemberAccessor("c", "a")))))),
          },
@@ -267,20 +245,12 @@
 
 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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(0u),
-              });
-    GlobalVar("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(2u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    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));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
+             Decl(Var("len", ty.u32(),
                       Add(Call("arrayLength", AddressOf(MemberAccessor("b", "a"))),
                           Call("arrayLength", AddressOf(MemberAccessor("c", "a")))))),
          },
diff --git a/src/tint/writer/msl/generator_impl_test.cc b/src/tint/writer/msl/generator_impl_test.cc
index 63c56b0..9b3d0bc 100644
--- a/src/tint/writer/msl/generator_impl_test.cc
+++ b/src/tint/writer/msl/generator_impl_test.cc
@@ -155,7 +155,7 @@
 
 TEST_F(MslGeneratorImplTest, WorkgroupMatrix) {
     GlobalVar("m", ty.mat2x2<f32>(), ast::StorageClass::kWorkgroup);
-    Func("comp_main", utils::Empty, ty.void_(), utils::Vector{Decl(Let("x", nullptr, Expr("m")))},
+    Func("comp_main", utils::Empty, ty.void_(), utils::Vector{Decl(Let("x", Expr("m")))},
          utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
@@ -195,7 +195,7 @@
 
 TEST_F(MslGeneratorImplTest, WorkgroupMatrixInArray) {
     GlobalVar("m", ty.array(ty.mat2x2<f32>(), 4_i), ast::StorageClass::kWorkgroup);
-    Func("comp_main", utils::Empty, ty.void_(), utils::Vector{Decl(Let("x", nullptr, Expr("m")))},
+    Func("comp_main", utils::Empty, ty.void_(), utils::Vector{Decl(Let("x", Expr("m")))},
          utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
@@ -256,7 +256,7 @@
                         Member("s", ty.type_name("S1")),
                     });
     GlobalVar("s", ty.type_name("S2"), ast::StorageClass::kWorkgroup);
-    Func("comp_main", utils::Empty, ty.void_(), utils::Vector{Decl(Let("x", nullptr, Expr("s")))},
+    Func("comp_main", utils::Empty, ty.void_(), utils::Vector{Decl(Let("x", Expr("s")))},
          utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
@@ -316,9 +316,9 @@
     GlobalVar("m9", ty.mat4x4<f32>(), ast::StorageClass::kWorkgroup);
     Func("main1", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("a1", nullptr, Expr("m1"))),
-             Decl(Let("a2", nullptr, Expr("m2"))),
-             Decl(Let("a3", nullptr, Expr("m3"))),
+             Decl(Let("a1", Expr("m1"))),
+             Decl(Let("a2", Expr("m2"))),
+             Decl(Let("a3", Expr("m3"))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kCompute),
@@ -326,9 +326,9 @@
          });
     Func("main2", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("a1", nullptr, Expr("m4"))),
-             Decl(Let("a2", nullptr, Expr("m5"))),
-             Decl(Let("a3", nullptr, Expr("m6"))),
+             Decl(Let("a1", Expr("m4"))),
+             Decl(Let("a2", Expr("m5"))),
+             Decl(Let("a3", Expr("m6"))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kCompute),
@@ -336,9 +336,9 @@
          });
     Func("main3", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("a1", nullptr, Expr("m7"))),
-             Decl(Let("a2", nullptr, Expr("m8"))),
-             Decl(Let("a3", nullptr, Expr("m9"))),
+             Decl(Let("a1", Expr("m7"))),
+             Decl(Let("a2", Expr("m8"))),
+             Decl(Let("a3", Expr("m9"))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kCompute),
diff --git a/src/tint/writer/msl/generator_impl_type_test.cc b/src/tint/writer/msl/generator_impl_type_test.cc
index a3e85a5..72f9fa5 100644
--- a/src/tint/writer/msl/generator_impl_type_test.cc
+++ b/src/tint/writer/msl/generator_impl_type_test.cc
@@ -282,11 +282,7 @@
                            Member("z", ty.f32()),
                        });
 
-    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
 
     GeneratorImpl& gen = Build();
 
@@ -391,11 +387,7 @@
                                  Member("e", ty.f32()),
                              });
 
-    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
 
     GeneratorImpl& gen = Build();
 
@@ -485,11 +477,7 @@
                                  Member("f", array_z),
                              });
 
-    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
 
     GeneratorImpl& gen = Build();
 
@@ -571,11 +559,7 @@
                                  Member("c", ty.i32()),
                              });
 
-    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
 
     GeneratorImpl& gen = Build();
 
@@ -635,11 +619,7 @@
                  Member("tint_pad_21", ty.f32()),
              });
 
-    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
 
     GeneratorImpl& gen = Build();
 
@@ -696,11 +676,7 @@
                                  Member("b", ty.f32()),
                              });
 
-    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
 
     GeneratorImpl& gen = Build();
 
@@ -863,11 +839,7 @@
     auto params = GetParam();
 
     auto* s = ty.storage_texture(params.dim, ast::TexelFormat::kR32Float, ast::Access::kWrite);
-    GlobalVar("test_var", s,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("test_var", s, Binding(0), Group(0));
 
     GeneratorImpl& gen = Build();
 
diff --git a/src/tint/writer/msl/generator_impl_unary_op_test.cc b/src/tint/writer/msl/generator_impl_unary_op_test.cc
index 21d6357..5f86bb5 100644
--- a/src/tint/writer/msl/generator_impl_unary_op_test.cc
+++ b/src/tint/writer/msl/generator_impl_unary_op_test.cc
@@ -45,8 +45,7 @@
 
 TEST_F(MslUnaryOpTest, Indirection) {
     GlobalVar("G", ty.f32(), ast::StorageClass::kPrivate);
-    auto* p =
-        Let("expr", nullptr, create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
+    auto* p = Let("expr", create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
     auto* op = create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection, Expr("expr"));
     WrapInFunction(p, op);
 
diff --git a/src/tint/writer/msl/generator_impl_variable_decl_statement_test.cc b/src/tint/writer/msl/generator_impl_variable_decl_statement_test.cc
index 726e634..3152f6c 100644
--- a/src/tint/writer/msl/generator_impl_variable_decl_statement_test.cc
+++ b/src/tint/writer/msl/generator_impl_variable_decl_statement_test.cc
@@ -26,7 +26,7 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement) {
-    auto* var = Var("a", ty.f32(), ast::StorageClass::kNone);
+    auto* var = Var("a", ty.f32());
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
 
@@ -65,8 +65,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_AInt) {
-    auto* C = Const("C", nullptr, Expr(1_a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", Expr(1_a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -83,8 +83,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_AFloat) {
-    auto* C = Const("C", nullptr, Expr(1._a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", Expr(1._a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -101,8 +101,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_i32) {
-    auto* C = Const("C", nullptr, Expr(1_i));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", Expr(1_i));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -119,8 +119,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_u32) {
-    auto* C = Const("C", nullptr, Expr(1_u));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", Expr(1_u));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -137,8 +137,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_f32) {
-    auto* C = Const("C", nullptr, Expr(1_f));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", Expr(1_f));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -157,8 +157,8 @@
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* C = Const("C", nullptr, Expr(1_h));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", Expr(1_h));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -175,8 +175,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_AInt) {
-    auto* C = Const("C", nullptr, Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -193,8 +193,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_AFloat) {
-    auto* C = Const("C", nullptr, Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -211,8 +211,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_f32) {
-    auto* C = Const("C", nullptr, vec3<f32>(1_f, 2_f, 3_f));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", vec3<f32>(1_f, 2_f, 3_f));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -231,8 +231,8 @@
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* C = Const("C", nullptr, vec3<f16>(1_h, 2_h, 3_h));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", vec3<f16>(1_h, 2_h, 3_h));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -249,9 +249,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_mat2x3_AFloat) {
-    auto* C =
-        Const("C", nullptr, Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -268,8 +267,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_mat2x3_f32) {
-    auto* C = Const("C", nullptr, mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -288,8 +287,8 @@
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_mat2x3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* C = Const("C", nullptr, mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -306,8 +305,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_arr_f32) {
-    auto* C = Const("C", nullptr, Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -337,12 +336,11 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_arr_vec2_bool) {
-    auto* C = Const("C", nullptr,
-                    Construct(ty.array(ty.vec2<bool>(), 3_u),  //
-                              vec2<bool>(true, false),         //
-                              vec2<bool>(false, true),         //
-                              vec2<bool>(true, true)));
-    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    auto* C = Const("C", Construct(ty.array(ty.vec2<bool>(), 3_u),  //
+                                   vec2<bool>(true, false),         //
+                                   vec2<bool>(false, true),         //
+                                   vec2<bool>(true, true)));
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -372,7 +370,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Array) {
-    auto* var = Var("a", ty.array<f32, 5>(), ast::StorageClass::kNone);
+    auto* var = Var("a", ty.array<f32, 5>());
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
 
@@ -390,7 +388,7 @@
                                  Member("b", ty.f32()),
                              });
 
-    auto* var = Var("a", ty.Of(s), ast::StorageClass::kNone);
+    auto* var = Var("a", ty.Of(s));
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
 
@@ -462,7 +460,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Initializer_ZeroVec_f32) {
-    auto* var = Var("a", ty.vec3<f32>(), ast::StorageClass::kNone, vec3<f32>());
+    auto* var = Var("a", ty.vec3<f32>(), vec3<f32>());
 
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
@@ -477,7 +475,7 @@
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Initializer_ZeroVec_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = Var("a", ty.vec3<f16>(), ast::StorageClass::kNone, vec3<f16>());
+    auto* var = Var("a", ty.vec3<f16>(), vec3<f16>());
 
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
@@ -490,7 +488,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Initializer_ZeroMat_f32) {
-    auto* var = Var("a", ty.mat2x3<f32>(), ast::StorageClass::kNone, mat2x3<f32>());
+    auto* var = Var("a", ty.mat2x3<f32>(), mat2x3<f32>());
 
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
@@ -506,7 +504,7 @@
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Initializer_ZeroMat_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* var = Var("a", ty.mat2x3<f16>(), ast::StorageClass::kNone, mat2x3<f16>());
+    auto* var = Var("a", ty.mat2x3<f16>(), mat2x3<f16>());
 
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
diff --git a/src/tint/writer/spirv/builder_accessor_expression_test.cc b/src/tint/writer/spirv/builder_accessor_expression_test.cc
index 0c0dc3f..ae755bb 100644
--- a/src/tint/writer/spirv/builder_accessor_expression_test.cc
+++ b/src/tint/writer/spirv/builder_accessor_expression_test.cc
@@ -26,8 +26,8 @@
     // let ary = vec3<i32>(1, 2, 3);
     // var x = ary[1i];
 
-    auto* ary = Let("ary", nullptr, vec3<i32>(1_i, 2_i, 3_i));
-    auto* x = Var("x", nullptr, IndexAccessor(ary, 1_i));
+    auto* ary = Let("ary", vec3<i32>(1_i, 2_i, 3_i));
+    auto* x = Var("x", IndexAccessor(ary, 1_i));
     WrapInFunction(ary, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -60,8 +60,8 @@
     // const ary = vec3<i32>(1, 2, 3);
     // var x = ary[1i];
 
-    auto* ary = Const("ary", nullptr, vec3<i32>(1_i, 2_i, 3_i));
-    auto* x = Var("x", nullptr, IndexAccessor(ary, 1_i));
+    auto* ary = Const("ary", vec3<i32>(1_i, 2_i, 3_i));
+    auto* x = Var("x", IndexAccessor(ary, 1_i));
     WrapInFunction(ary, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -90,7 +90,7 @@
     // var x = ary[1i];
 
     auto* ary = Var("ary", ty.vec3<u32>());
-    auto* x = Var("x", nullptr, IndexAccessor(ary, 1_i));
+    auto* x = Var("x", IndexAccessor(ary, 1_i));
     WrapInFunction(ary, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -127,7 +127,7 @@
 
     auto* ary = Var("ary", ty.vec3<f32>());
     auto* idx = Var("idx", ty.i32());
-    auto* x = Var("x", nullptr, IndexAccessor(ary, idx));
+    auto* x = Var("x", IndexAccessor(ary, idx));
     WrapInFunction(ary, idx, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -164,8 +164,8 @@
     // let ary : vec3<i32>(1, 2, 3);
     // var x = ary[1i + 1i];
 
-    auto* ary = Let("ary", nullptr, vec3<i32>(1_i, 2_i, 3_i));
-    auto* x = Var("x", nullptr, IndexAccessor(ary, Add(1_i, 1_i)));
+    auto* ary = Let("ary", vec3<i32>(1_i, 2_i, 3_i));
+    auto* x = Var("x", IndexAccessor(ary, Add(1_i, 1_i)));
     WrapInFunction(ary, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -199,7 +199,7 @@
     // var x = ary[1i + 1i];
 
     auto* ary = Var("ary", ty.vec3<f32>());
-    auto* x = Var("x", nullptr, IndexAccessor(ary, Add(1_i, 1_i)));
+    auto* x = Var("x", IndexAccessor(ary, Add(1_i, 1_i)));
     WrapInFunction(ary, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -235,8 +235,8 @@
     // var x = ary[one + 2i];
 
     auto* ary = Var("ary", ty.vec3<f32>());
-    auto* one = Var("one", nullptr, Expr(1_i));
-    auto* x = Var("x", nullptr, IndexAccessor(ary, Add(one, 2_i)));
+    auto* one = Var("one", Expr(1_i));
+    auto* x = Var("x", IndexAccessor(ary, Add(one, 2_i)));
     WrapInFunction(ary, one, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -277,10 +277,9 @@
     // let ary = array<vec3<f32>, 2u>(vec3<f32>(1.0f, 2.0f, 3.0f), vec3<f32>(4.0f, 5.0f, 6.0f));
     // var x = ary[1i][2i];
 
-    auto* ary =
-        Let("ary", nullptr,
-            array(ty.vec3<f32>(), 2_u, vec3<f32>(1._f, 2._f, 3._f), vec3<f32>(4._f, 5._f, 6._f)));
-    auto* x = Var("x", nullptr, IndexAccessor(IndexAccessor(ary, 1_i), 2_i));
+    auto* ary = Let("ary", array(ty.vec3<f32>(), 2_u, vec3<f32>(1._f, 2._f, 3._f),
+                                 vec3<f32>(4._f, 5._f, 6._f)));
+    auto* x = Var("x", IndexAccessor(IndexAccessor(ary, 1_i), 2_i));
     WrapInFunction(ary, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -325,10 +324,9 @@
     // const ary = array<vec3<f32>, 2u>(vec3<f32>(1.0f, 2.0f, 3.0f), vec3<f32>(4.0f, 5.0f, 6.0f));
     // var x = ary[1i][2i];
 
-    auto* ary =
-        Const("ary", nullptr,
-              array(ty.vec3<f32>(), 2_u, vec3<f32>(1._f, 2._f, 3._f), vec3<f32>(4._f, 5._f, 6._f)));
-    auto* x = Var("x", nullptr, IndexAccessor(IndexAccessor(ary, 1_i), 2_i));
+    auto* ary = Const("ary", array(ty.vec3<f32>(), 2_u, vec3<f32>(1._f, 2._f, 3._f),
+                                   vec3<f32>(4._f, 5._f, 6._f)));
+    auto* x = Var("x", IndexAccessor(IndexAccessor(ary, 1_i), 2_i));
     WrapInFunction(ary, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -357,7 +355,7 @@
     // var x = ary[1i][2i];
 
     auto* ary = Var("ary", ty.array(ty.vec3<f32>(), 4_u));
-    auto* x = Var("x", nullptr, IndexAccessor(IndexAccessor(ary, 1_i), 2_i));
+    auto* x = Var("x", IndexAccessor(IndexAccessor(ary, 1_i), 2_i));
     WrapInFunction(ary, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -398,8 +396,8 @@
     // var x = ary[one][2i];
 
     auto* ary = Var("ary", ty.array(ty.vec3<f32>(), 4_u));
-    auto* one = Var("one", nullptr, Expr(3_i));
-    auto* x = Var("x", nullptr, IndexAccessor(IndexAccessor(ary, one), 2_i));
+    auto* one = Var("one", Expr(3_i));
+    auto* x = Var("x", IndexAccessor(IndexAccessor(ary, one), 2_i));
     WrapInFunction(ary, one, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -442,10 +440,9 @@
     // let ary = array<vec3<f32>, 2u>(vec3<f32>(1.0f, 2.0f, 3.0f), vec3<f32>(4.0f, 5.0f, 6.0f));
     // var x = a[1i].xy;
 
-    auto* ary =
-        Let("ary", nullptr,
-            array(ty.vec3<f32>(), 2_u, vec3<f32>(1._f, 2._f, 3._f), vec3<f32>(4._f, 5._f, 6._f)));
-    auto* x = Var("x", nullptr, MemberAccessor(IndexAccessor("ary", 1_i), "xy"));
+    auto* ary = Let("ary", array(ty.vec3<f32>(), 2_u, vec3<f32>(1._f, 2._f, 3._f),
+                                 vec3<f32>(4._f, 5._f, 6._f)));
+    auto* x = Var("x", MemberAccessor(IndexAccessor("ary", 1_i), "xy"));
     WrapInFunction(ary, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -491,7 +488,7 @@
     // var x = ary[1i].xy;
 
     auto* ary = Var("ary", ty.array(ty.vec3<f32>(), 4_u));
-    auto* x = Var("x", nullptr, MemberAccessor(IndexAccessor("ary", 1_i), "xy"));
+    auto* x = Var("x", MemberAccessor(IndexAccessor("ary", 1_i), "xy"));
     WrapInFunction(ary, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -533,8 +530,8 @@
     // var x = ary[one].xy;
 
     auto* ary = Var("ary", ty.array(ty.vec3<f32>(), 4_u));
-    auto* one = Var("one", nullptr, Expr(1_i));
-    auto* x = Var("x", nullptr, MemberAccessor(IndexAccessor("ary", one), "xy"));
+    auto* one = Var("one", Expr(1_i));
+    auto* x = Var("x", MemberAccessor(IndexAccessor("ary", one), "xy"));
     WrapInFunction(ary, one, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -585,7 +582,7 @@
     auto* pos = Let("pos", ty.array(ty.vec2<f32>(), 3_u),
                     Construct(ty.array(ty.vec2<f32>(), 3_u), vec2<f32>(0_f, 0.5_f),
                               vec2<f32>(-0.5_f, -0.5_f), vec2<f32>(0.5_f, -0.5_f)));
-    auto* x = Var("x", nullptr, IndexAccessor(IndexAccessor(pos, 1_u), 0_u));
+    auto* x = Var("x", IndexAccessor(IndexAccessor(pos, 1_u), 0_u));
     WrapInFunction(pos, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -632,7 +629,7 @@
     auto* pos = Const("pos", ty.array(ty.vec2<f32>(), 3_u),
                       Construct(ty.array(ty.vec2<f32>(), 3_u), vec2<f32>(0_f, 0.5_f),
                                 vec2<f32>(-0.5_f, -0.5_f), vec2<f32>(0.5_f, -0.5_f)));
-    auto* x = Var("x", nullptr, IndexAccessor(IndexAccessor(pos, 1_u), 0_u));
+    auto* x = Var("x", IndexAccessor(IndexAccessor(pos, 1_u), 0_u));
     WrapInFunction(pos, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -661,7 +658,7 @@
     // var x = pos[1u][2u];
 
     auto* pos = Var("pos", ty.array(ty.vec2<f32>(), 3_u));
-    auto* x = Var("x", nullptr, IndexAccessor(IndexAccessor(pos, 1_u), 2_u));
+    auto* x = Var("x", IndexAccessor(IndexAccessor(pos, 1_u), 2_u));
     WrapInFunction(pos, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -701,8 +698,8 @@
     // var x = pos[one][2u];
 
     auto* pos = Var("pos", ty.array(ty.vec2<f32>(), 3_u));
-    auto* one = Var("one", nullptr, Expr(2_u));
-    auto* x = Var("x", nullptr, IndexAccessor(IndexAccessor(pos, "one"), 2_u));
+    auto* one = Var("one", Expr(2_u));
+    auto* x = Var("x", IndexAccessor(IndexAccessor(pos, "one"), 2_u));
     WrapInFunction(pos, one, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -746,7 +743,7 @@
     auto* a = Let("a", ty.mat2x2<f32>(),
                   Construct(ty.mat2x2<f32>(), Construct(ty.vec2<f32>(), 1_f, 2_f),
                             Construct(ty.vec2<f32>(), 3_f, 4_f)));
-    auto* x = Var("x", nullptr, IndexAccessor("a", 1_i));
+    auto* x = Var("x", IndexAccessor("a", 1_i));
     WrapInFunction(a, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -788,7 +785,7 @@
     auto* a = Const("a", ty.mat2x2<f32>(),
                     Construct(ty.mat2x2<f32>(), Construct(ty.vec2<f32>(), 1_f, 2_f),
                               Construct(ty.vec2<f32>(), 3_f, 4_f)));
-    auto* x = Var("x", nullptr, IndexAccessor("a", 1_i));
+    auto* x = Var("x", IndexAccessor("a", 1_i));
     WrapInFunction(a, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -820,7 +817,7 @@
     // var x = a[1i]
 
     auto* a = Var("a", ty.mat2x2<f32>());
-    auto* x = Var("x", nullptr, IndexAccessor("a", 1_i));
+    auto* x = Var("x", IndexAccessor("a", 1_i));
     WrapInFunction(a, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -858,7 +855,7 @@
 
     auto* a = Var("a", ty.mat2x2<f32>());
     auto* idx = Var("idx", ty.i32());
-    auto* x = Var("x", nullptr, IndexAccessor("a", idx));
+    auto* x = Var("x", IndexAccessor("a", idx));
     WrapInFunction(a, idx, x);
 
     spirv::Builder& b = SanitizeAndBuild();
diff --git a/src/tint/writer/spirv/builder_block_test.cc b/src/tint/writer/spirv/builder_block_test.cc
index 85a2371..76f7556 100644
--- a/src/tint/writer/spirv/builder_block_test.cc
+++ b/src/tint/writer/spirv/builder_block_test.cc
@@ -25,8 +25,8 @@
 TEST_F(BuilderTest, Block) {
     // Note, this test uses shadow variables which aren't allowed in WGSL but
     // serves to prove the block code is pushing new scopes as needed.
-    auto* inner = Block(Decl(Var("var", ty.f32(), ast::StorageClass::kNone)), Assign("var", 2_f));
-    auto* outer = Block(Decl(Var("var", ty.f32(), ast::StorageClass::kNone)), Assign("var", 1_f),
+    auto* inner = Block(Decl(Var("var", ty.f32())), Assign("var", 2_f));
+    auto* outer = Block(Decl(Var("var", ty.f32())), Assign("var", 1_f),
                         inner, Assign("var", 3_f));
 
     WrapInFunction(outer);
diff --git a/src/tint/writer/spirv/builder_builtin_test.cc b/src/tint/writer/spirv/builder_builtin_test.cc
index 52254d2..f4b6096 100644
--- a/src/tint/writer/spirv/builder_builtin_test.cc
+++ b/src/tint/writer/spirv/builder_builtin_test.cc
@@ -43,17 +43,8 @@
     auto* s = ty.sampler(ast::SamplerKind::kComparisonSampler);
     auto* t = ty.depth_texture(ast::TextureDimension::k2d);
 
-    auto* tex = GlobalVar("texture", t,
-                          utils::Vector{
-                              create<ast::BindingAttribute>(0u),
-                              create<ast::GroupAttribute>(0u),
-                          });
-
-    auto* sampler = GlobalVar("sampler", s,
-                              utils::Vector{
-                                  create<ast::BindingAttribute>(1u),
-                                  create<ast::GroupAttribute>(0u),
-                              });
+    auto* tex = GlobalVar("texture", t, Binding(0), Group(0));
+    auto* sampler = GlobalVar("sampler", s, Binding(1), Group(0));
 
     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);
@@ -286,11 +277,7 @@
     auto* s = Structure("my_struct", utils::Vector{
                                          Member("a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
     auto* expr = Call("arrayLength", AddressOf(MemberAccessor("b", "a")));
 
     Func("a_func", utils::Empty, ty.void_(),
@@ -333,11 +320,7 @@
                                          Member("z", ty.f32()),
                                          Member(4, "a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
     auto* expr = Call("arrayLength", AddressOf(MemberAccessor("b", "a")));
 
     Func("a_func", utils::Empty, ty.void_(),
@@ -379,14 +362,10 @@
     auto* s = Structure("my_struct", utils::Vector{
                                          Member("a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
 
-    auto* p = Let("p", nullptr, AddressOf("b"));
-    auto* p2 = Let("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
+    auto* p = Let("p", AddressOf("b"));
+    auto* p2 = Let("p2", AddressOf(MemberAccessor(Deref(p), "a")));
     auto* expr = Call("arrayLength", p2);
 
     Func("a_func", utils::Empty, ty.void_(),
@@ -441,15 +420,11 @@
     auto* s = Structure("my_struct", utils::Vector{
                                          Member("a", ty.array<f32>(4)),
                                      });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(1), Group(2));
 
-    auto* p = Let("p", nullptr, AddressOf(Deref(AddressOf("b"))));
-    auto* p2 = Let("p2", nullptr, AddressOf(Deref(p)));
-    auto* p3 = Let("p3", nullptr, AddressOf(MemberAccessor(Deref(p2), "a")));
+    auto* p = Let("p", AddressOf(Deref(AddressOf("b"))));
+    auto* p2 = Let("p2", AddressOf(Deref(p)));
+    auto* p3 = Let("p3", AddressOf(MemberAccessor(Deref(p2), "a")));
     auto* expr = Call("arrayLength", AddressOf(Deref(p3)));
 
     Func("a_func", utils::Empty, ty.void_(),
@@ -499,7 +474,7 @@
 TEST_P(Builtin_Builder_SingleParam_Float_Test, Call_Scalar_f32) {
     auto param = GetParam();
     // Use a variable to prevent the function being evaluated as constant.
-    auto* scalar = Var("a", nullptr, Expr(1_f));
+    auto* scalar = Var("a", Expr(1_f));
     auto* expr = Call(param.name, scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -540,7 +515,7 @@
 
     auto param = GetParam();
     // Use a variable to prevent the function being evaluated as constant.
-    auto* scalar = Var("a", nullptr, Expr(1_h));
+    auto* scalar = Var("a", Expr(1_h));
     auto* expr = Call(param.name, scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -580,7 +555,7 @@
     auto param = GetParam();
 
     // Use a variable to prevent the function being evaluated as constant.
-    auto* vec = Var("a", nullptr, vec2<f32>(1_f, 1_f));
+    auto* vec = Var("a", vec2<f32>(1_f, 1_f));
     auto* expr = Call(param.name, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -624,7 +599,7 @@
     auto param = GetParam();
 
     // Use a variable to prevent the function being evaluated as constant.
-    auto* vec = Var("a", nullptr, vec2<f16>(1_h, 1_h));
+    auto* vec = Var("a", vec2<f16>(1_h, 1_h));
     auto* expr = Call(param.name, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -690,7 +665,7 @@
                                          BuiltinData{"trunc", "Trunc"}));
 
 TEST_F(BuiltinBuilderTest, Call_Length_Scalar_f32) {
-    auto* scalar = Var("a", nullptr, Expr(1_f));
+    auto* scalar = Var("a", Expr(1_f));
     auto* expr = Call("length", scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -727,7 +702,7 @@
 TEST_F(BuiltinBuilderTest, Call_Length_Scalar_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* scalar = Var("a", nullptr, Expr(1_h));
+    auto* scalar = Var("a", Expr(1_h));
     auto* expr = Call("length", scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -762,7 +737,7 @@
 }
 
 TEST_F(BuiltinBuilderTest, Call_Length_Vector_f32) {
-    auto* vec = Var("a", nullptr, vec2<f32>(1_f, 1_f));
+    auto* vec = Var("a", vec2<f32>(1_f, 1_f));
     auto* expr = Call("length", vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -801,7 +776,7 @@
 TEST_F(BuiltinBuilderTest, Call_Length_Vector_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* vec = Var("a", nullptr, vec2<f16>(1_h, 1_h));
+    auto* vec = Var("a", vec2<f16>(1_h, 1_h));
     auto* expr = Call("length", vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -838,7 +813,7 @@
 }
 
 TEST_F(BuiltinBuilderTest, Call_Normalize_f32) {
-    auto* vec = Var("a", nullptr, vec2<f32>(1_f, 1_f));
+    auto* vec = Var("a", vec2<f32>(1_f, 1_f));
     auto* expr = Call("normalize", vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -877,7 +852,7 @@
 TEST_F(BuiltinBuilderTest, Call_Normalize_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* vec = Var("a", nullptr, vec2<f16>(1_h, 1_h));
+    auto* vec = Var("a", vec2<f16>(1_h, 1_h));
     auto* expr = Call("normalize", vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -916,7 +891,7 @@
 using Builtin_Builder_DualParam_Float_Test = BuiltinBuilderTestWithParam<BuiltinData>;
 TEST_P(Builtin_Builder_DualParam_Float_Test, Call_Scalar_f32) {
     auto param = GetParam();
-    auto* scalar = Var("scalar", nullptr, Expr(1_f));
+    auto* scalar = Var("scalar", Expr(1_f));
     auto* expr = Call(param.name, scalar, scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -957,7 +932,7 @@
     Enable(ast::Extension::kF16);
 
     auto param = GetParam();
-    auto* scalar = Var("scalar", nullptr, Expr(1_h));
+    auto* scalar = Var("scalar", Expr(1_h));
     auto* expr = Call(param.name, scalar, scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -996,7 +971,7 @@
 
 TEST_P(Builtin_Builder_DualParam_Float_Test, Call_Vector_f32) {
     auto param = GetParam();
-    auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 1_f));
+    auto* vec = Var("vec", vec2<f32>(1_f, 1_f));
     auto* expr = Call(param.name, vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1039,7 +1014,7 @@
     Enable(ast::Extension::kF16);
 
     auto param = GetParam();
-    auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 1_h));
+    auto* vec = Var("vec", vec2<f16>(1_h, 1_h));
     auto* expr = Call(param.name, vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1087,7 +1062,7 @@
                                          BuiltinData{"step", "Step"}));
 
 TEST_F(BuiltinBuilderTest, Call_Reflect_Vector_f32) {
-    auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 1_f));
+    auto* vec = Var("vec", vec2<f32>(1_f, 1_f));
     auto* expr = Call("reflect", vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1127,7 +1102,7 @@
 TEST_F(BuiltinBuilderTest, Call_Reflect_Vector_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 1_h));
+    auto* vec = Var("vec", vec2<f16>(1_h, 1_h));
     auto* expr = Call("reflect", vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1165,7 +1140,7 @@
 }
 
 TEST_F(BuiltinBuilderTest, Call_Distance_Scalar_f32) {
-    auto* scalar = Var("scalar", nullptr, Expr(1_f));
+    auto* scalar = Var("scalar", Expr(1_f));
     auto* expr = Call("distance", scalar, scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1203,7 +1178,7 @@
 TEST_F(BuiltinBuilderTest, Call_Distance_Scalar_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* scalar = Var("scalar", nullptr, Expr(1_h));
+    auto* scalar = Var("scalar", Expr(1_h));
     auto* expr = Call("distance", scalar, scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1239,7 +1214,7 @@
 }
 
 TEST_F(BuiltinBuilderTest, Call_Distance_Vector_f32) {
-    auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 1_f));
+    auto* vec = Var("vec", vec2<f32>(1_f, 1_f));
     auto* expr = Call("distance", vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1279,7 +1254,7 @@
 TEST_F(BuiltinBuilderTest, Call_Distance_Vector_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 1_h));
+    auto* vec = Var("vec", vec2<f16>(1_h, 1_h));
     auto* expr = Call("distance", vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1317,7 +1292,7 @@
 }
 
 TEST_F(BuiltinBuilderTest, Call_Cross_f32) {
-    auto* vec = Var("vec", nullptr, vec3<f32>(1_f, 1_f, 1_f));
+    auto* vec = Var("vec", vec3<f32>(1_f, 1_f, 1_f));
     auto* expr = Call("cross", vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1357,7 +1332,7 @@
 TEST_F(BuiltinBuilderTest, Call_Cross_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* vec = Var("vec", nullptr, vec3<f16>(1_h, 1_h, 1_h));
+    auto* vec = Var("vec", vec3<f16>(1_h, 1_h, 1_h));
     auto* expr = Call("cross", vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1397,7 +1372,7 @@
 using Builtin_Builder_ThreeParam_Float_Test = BuiltinBuilderTestWithParam<BuiltinData>;
 TEST_P(Builtin_Builder_ThreeParam_Float_Test, Call_Scalar_f32) {
     auto param = GetParam();
-    auto* scalar = Var("scalar", nullptr, Expr(1_f));
+    auto* scalar = Var("scalar", Expr(1_f));
     auto* expr = Call(param.name, scalar, scalar, scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1439,7 +1414,7 @@
     Enable(ast::Extension::kF16);
 
     auto param = GetParam();
-    auto* scalar = Var("scalar", nullptr, Expr(1_h));
+    auto* scalar = Var("scalar", Expr(1_h));
     auto* expr = Call(param.name, scalar, scalar, scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1479,7 +1454,7 @@
 
 TEST_P(Builtin_Builder_ThreeParam_Float_Test, Call_Vector_f32) {
     auto param = GetParam();
-    auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 1_f));
+    auto* vec = Var("vec", vec2<f32>(1_f, 1_f));
     auto* expr = Call(param.name, vec, vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1523,7 +1498,7 @@
     Enable(ast::Extension::kF16);
 
     auto param = GetParam();
-    auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 1_h));
+    auto* vec = Var("vec", vec2<f16>(1_h, 1_h));
     auto* expr = Call(param.name, vec, vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1572,7 +1547,7 @@
                                          BuiltinData{"smoothstep", "SmoothStep"}));
 
 TEST_F(BuiltinBuilderTest, Call_FaceForward_Vector_f32) {
-    auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 1_f));
+    auto* vec = Var("vec", vec2<f32>(1_f, 1_f));
     auto* expr = Call("faceForward", vec, vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1613,7 +1588,7 @@
 TEST_F(BuiltinBuilderTest, Call_FaceForward_Vector_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 1_h));
+    auto* vec = Var("vec", vec2<f16>(1_h, 1_h));
     auto* expr = Call("faceForward", vec, vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -1652,7 +1627,7 @@
 }
 
 TEST_F(BuiltinBuilderTest, Call_Modf_f32) {
-    auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 2_f));
+    auto* vec = Var("vec", vec2<f32>(1_f, 2_f));
     auto* expr = Call("modf", vec);
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -1706,7 +1681,7 @@
 TEST_F(BuiltinBuilderTest, Call_Modf_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 2_h));
+    auto* vec = Var("vec", vec2<f16>(1_h, 2_h));
     auto* expr = Call("modf", vec);
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -1762,7 +1737,7 @@
 }
 
 TEST_F(BuiltinBuilderTest, Call_Frexp_f32) {
-    auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 2_f));
+    auto* vec = Var("vec", vec2<f32>(1_f, 2_f));
     auto* expr = Call("frexp", vec);
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -1818,7 +1793,7 @@
 TEST_F(BuiltinBuilderTest, Call_Frexp_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 2_h));
+    auto* vec = Var("vec", vec2<f16>(1_h, 2_h));
     auto* expr = Call("frexp", vec);
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -2010,7 +1985,7 @@
 using Builtin_Builder_SingleParam_Sint_Test = BuiltinBuilderTestWithParam<BuiltinData>;
 TEST_P(Builtin_Builder_SingleParam_Sint_Test, Call_Scalar) {
     auto param = GetParam();
-    auto* scalar = Var("scalar", nullptr, Expr(1_i));
+    auto* scalar = Var("scalar", Expr(1_i));
     auto* expr = Call(param.name, scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -2048,7 +2023,7 @@
 
 TEST_P(Builtin_Builder_SingleParam_Sint_Test, Call_Vector) {
     auto param = GetParam();
-    auto* vec = Var("vec", nullptr, vec2<i32>(1_i, 1_i));
+    auto* vec = Var("vec", vec2<i32>(1_i, 1_i));
     auto* expr = Call(param.name, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -2092,7 +2067,7 @@
 // Calling abs() on an unsigned integer scalar / vector is a no-op.
 using Builtin_Builder_Abs_Uint_Test = BuiltinBuilderTest;
 TEST_F(Builtin_Builder_Abs_Uint_Test, Call_Scalar) {
-    auto* scalar = Var("scalar", nullptr, Expr(1_u));
+    auto* scalar = Var("scalar", Expr(1_u));
     auto* expr = Call("abs", scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -2125,7 +2100,7 @@
 }
 
 TEST_F(Builtin_Builder_Abs_Uint_Test, Call_Vector) {
-    auto* scalar = Var("scalar", nullptr, vec2<u32>(1_u, 1_u));
+    auto* scalar = Var("scalar", vec2<u32>(1_u, 1_u));
     auto* expr = Call("abs", scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -2162,7 +2137,7 @@
 using Builtin_Builder_DualParam_SInt_Test = BuiltinBuilderTestWithParam<BuiltinData>;
 TEST_P(Builtin_Builder_DualParam_SInt_Test, Call_Scalar) {
     auto param = GetParam();
-    auto* scalar = Var("scalar", nullptr, Expr(1_i));
+    auto* scalar = Var("scalar", Expr(1_i));
     auto* expr = Call(param.name, scalar, scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -2201,7 +2176,7 @@
 
 TEST_P(Builtin_Builder_DualParam_SInt_Test, Call_Vector) {
     auto param = GetParam();
-    auto* vec = Var("vec", nullptr, vec2<i32>(1_i, 1_i));
+    auto* vec = Var("vec", vec2<i32>(1_i, 1_i));
     auto* expr = Call(param.name, vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -2246,7 +2221,7 @@
 using Builtin_Builder_DualParam_UInt_Test = BuiltinBuilderTestWithParam<BuiltinData>;
 TEST_P(Builtin_Builder_DualParam_UInt_Test, Call_Scalar) {
     auto param = GetParam();
-    auto* scalar = Var("scalar", nullptr, Expr(1_u));
+    auto* scalar = Var("scalar", Expr(1_u));
     auto* expr = Call(param.name, scalar, scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -2285,7 +2260,7 @@
 
 TEST_P(Builtin_Builder_DualParam_UInt_Test, Call_Vector) {
     auto param = GetParam();
-    auto* vec = Var("vec", nullptr, vec2<u32>(1_u, 1_u));
+    auto* vec = Var("vec", vec2<u32>(1_u, 1_u));
     auto* expr = Call(param.name, vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -2330,7 +2305,7 @@
 using Builtin_Builder_ThreeParam_Sint_Test = BuiltinBuilderTestWithParam<BuiltinData>;
 TEST_P(Builtin_Builder_ThreeParam_Sint_Test, Call_Scalar) {
     auto param = GetParam();
-    auto* scalar = Var("scalar", nullptr, Expr(1_i));
+    auto* scalar = Var("scalar", Expr(1_i));
     auto* expr = Call(param.name, scalar, scalar, scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -2370,7 +2345,7 @@
 
 TEST_P(Builtin_Builder_ThreeParam_Sint_Test, Call_Vector) {
     auto param = GetParam();
-    auto* vec = Var("vec", nullptr, vec2<i32>(1_i, 1_i));
+    auto* vec = Var("vec", vec2<i32>(1_i, 1_i));
     auto* expr = Call(param.name, vec, vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -2416,7 +2391,7 @@
 using Builtin_Builder_ThreeParam_Uint_Test = BuiltinBuilderTestWithParam<BuiltinData>;
 TEST_P(Builtin_Builder_ThreeParam_Uint_Test, Call_Scalar) {
     auto param = GetParam();
-    auto* scalar = Var("scalar", nullptr, Expr(1_u));
+    auto* scalar = Var("scalar", Expr(1_u));
     auto* expr = Call(param.name, scalar, scalar, scalar);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -2456,7 +2431,7 @@
 
 TEST_P(Builtin_Builder_ThreeParam_Uint_Test, Call_Vector) {
     auto param = GetParam();
-    auto* vec = Var("vec", nullptr, vec2<u32>(1_u, 1_u));
+    auto* vec = Var("vec", vec2<u32>(1_u, 1_u));
     auto* expr = Call(param.name, vec, vec, vec);
     auto* func = Func("a_func", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -3244,11 +3219,8 @@
                                  Member("u", ty.atomic<u32>()),
                                  Member("i", ty.atomic<i32>()),
                              });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1),
+              Group(2));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -3310,16 +3282,13 @@
                                  Member("u", ty.atomic<u32>()),
                                  Member("i", ty.atomic<i32>()),
                              });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1),
+              Group(2));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("u", nullptr, Expr(1_u))),
-             Decl(Var("i", nullptr, Expr(2_i))),
+             Decl(Var("u", Expr(1_u))),
+             Decl(Var("i", Expr(2_i))),
              CallStmt(Call("atomicStore", AddressOf(MemberAccessor("b", "u")), "u")),
              CallStmt(Call("atomicStore", AddressOf(MemberAccessor("b", "i")), "i")),
          },
@@ -3384,15 +3353,12 @@
     auto* s = Structure("S", utils::Vector{
                                  Member("v", ty.atomic<i32>()),
                              });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1),
+              Group(2));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("v", nullptr, Expr(10_i))),
+             Decl(Var("v", Expr(10_i))),
              Decl(Let("x", ty.i32(),
                       Call(GetParam().name, AddressOf(MemberAccessor("b", "v")), "v"))),
          },
@@ -3459,15 +3425,12 @@
     auto* s = Structure("S", utils::Vector{
                                  Member("v", ty.atomic<u32>()),
                              });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1),
+              Group(2));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("v", nullptr, Expr(10_u))),
+             Decl(Var("v", Expr(10_u))),
              Decl(Let("x", ty.u32(),
                       Call(GetParam().name, AddressOf(MemberAccessor("b", "v")), "v"))),
          },
@@ -3536,16 +3499,13 @@
                                  Member("u", ty.atomic<u32>()),
                                  Member("i", ty.atomic<i32>()),
                              });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1),
+              Group(2));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Var("u", nullptr, Expr(10_u))),
-             Decl(Var("i", nullptr, Expr(10_i))),
+             Decl(Var("u", Expr(10_u))),
+             Decl(Var("i", Expr(10_i))),
              Decl(Let("r", ty.u32(),
                       Call("atomicExchange", AddressOf(MemberAccessor("b", "u")), "u"))),
              Decl(Let("s", ty.i32(),
@@ -3614,20 +3574,15 @@
                                  Member("u", ty.atomic<u32>()),
                                  Member("i", ty.atomic<i32>()),
                              });
-    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(1),
+              Group(2));
 
     Func("a_func", utils::Empty, ty.void_(),
          utils::Vector{
-             Decl(Let("u", nullptr,
-                      Call("atomicCompareExchangeWeak", AddressOf(MemberAccessor("b", "u")), 10_u,
-                           20_u))),
-             Decl(Let("i", nullptr,
-                      Call("atomicCompareExchangeWeak", AddressOf(MemberAccessor("b", "i")), 10_i,
-                           20_i))),
+             Decl(Let("u", Call("atomicCompareExchangeWeak", AddressOf(MemberAccessor("b", "u")),
+                                10_u, 20_u))),
+             Decl(Let("i", Call("atomicCompareExchangeWeak", AddressOf(MemberAccessor("b", "i")),
+                                10_i, 20_i))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
diff --git a/src/tint/writer/spirv/builder_constructor_expression_test.cc b/src/tint/writer/spirv/builder_constructor_expression_test.cc
index 3ae4f65..9c9bd74 100644
--- a/src/tint/writer/spirv/builder_constructor_expression_test.cc
+++ b/src/tint/writer/spirv/builder_constructor_expression_test.cc
@@ -122,7 +122,7 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, Vector_Bitcast_Params) {
-    auto* var = Var("v", nullptr, vec3<f32>(1_f, 2_f, 3_f));
+    auto* var = Var("v", vec3<f32>(1_f, 2_f, 3_f));
     auto* cast = Bitcast(ty.vec3<u32>(), var);
     WrapInFunction(var, cast);
 
@@ -247,7 +247,7 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_Bool_Var) {
-    auto* var = Var("v", nullptr, Expr(true));
+    auto* var = Var("v", Expr(true));
     auto* cast = vec2<bool>(var);
     WrapInFunction(var, cast);
 
@@ -1892,7 +1892,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_F32_With_F32) {
     auto* ctor = Construct<f32>(2_f);
     GlobalConst("g", ty.f32(), ctor);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -1915,7 +1915,7 @@
 
     auto* ctor = Construct<f16>(2_h);
     GlobalConst("g", ty.f16(), ctor);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -1972,7 +1972,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_U32_With_F32) {
     auto* ctor = Construct<u32>(1.5_f);
     GlobalConst("g", ty.u32(), ctor);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -1995,7 +1995,7 @@
 
     auto* ctor = Construct<u32>(1.5_h);
     GlobalConst("g", ty.u32(), ctor);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2052,7 +2052,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec2_With_F32) {
     auto* cast = vec2<f32>(2_f);
     GlobalConst("g", ty.vec2<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2077,7 +2077,7 @@
 
     auto* cast = vec2<f16>(2_h);
     GlobalConst("g", ty.vec2<f16>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2134,7 +2134,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec2_F32_With_Vec2) {
     auto* cast = vec2<f32>(vec2<f32>(2_f, 2_f));
     GlobalConst("g", ty.vec2<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2159,7 +2159,7 @@
 
     auto* cast = vec2<f16>(vec2<f16>(2_h, 2_h));
     GlobalConst("g", ty.vec2<f16>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2224,7 +2224,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec3_F32_With_Vec3) {
     auto* cast = vec3<f32>(vec3<f32>(2_f, 2_f, 2_f));
     GlobalConst("g", ty.vec3<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2249,7 +2249,7 @@
 
     auto* cast = vec3<f16>(vec3<f16>(2_h, 2_h, 2_h));
     GlobalConst("g", ty.vec3<f16>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2314,7 +2314,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec4_F32_With_Vec4) {
     auto* cast = vec4<f32>(vec4<f32>(2_f, 2_f, 2_f, 2_f));
     GlobalConst("g", ty.vec4<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2339,7 +2339,7 @@
 
     auto* cast = vec4<f16>(vec4<f16>(2_h, 2_h, 2_h, 2_h));
     GlobalConst("g", ty.vec4<f16>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2404,7 +2404,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec3_With_F32) {
     auto* cast = vec3<f32>(2_f);
     GlobalConst("g", ty.vec3<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2429,7 +2429,7 @@
 
     auto* cast = vec3<f16>(2_h);
     GlobalConst("g", ty.vec3<f16>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2486,7 +2486,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec3_With_F32_Vec2) {
     auto* cast = vec3<f32>(2_f, vec2<f32>(2_f, 2_f));
     GlobalConst("g", ty.vec3<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2511,7 +2511,7 @@
 
     auto* cast = vec3<f16>(2_h, vec2<f16>(2_h, 2_h));
     GlobalConst("g", ty.vec3<f16>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2568,7 +2568,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec3_With_Vec2_F32) {
     auto* cast = vec3<f32>(vec2<f32>(2_f, 2_f), 2_f);
     GlobalConst("g", ty.vec3<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2593,7 +2593,7 @@
 
     auto* cast = vec3<f16>(vec2<f16>(2_h, 2_h), 2_h);
     GlobalConst("g", ty.vec3<f16>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2650,7 +2650,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec4_With_F32) {
     auto* cast = vec4<f32>(2_f);
     GlobalConst("g", ty.vec4<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2675,7 +2675,7 @@
 
     auto* cast = vec4<f16>(2_h);
     GlobalConst("g", ty.vec4<f16>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2732,7 +2732,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec4_With_F32_F32_Vec2) {
     auto* cast = vec4<f32>(2_f, 2_f, vec2<f32>(2_f, 2_f));
     GlobalConst("g", ty.vec4<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2757,7 +2757,7 @@
 
     auto* cast = vec4<f16>(2_h, 2_h, vec2<f16>(2_h, 2_h));
     GlobalConst("g", ty.vec4<f16>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2814,7 +2814,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec4_With_F32_Vec2_F32) {
     auto* cast = vec4<f32>(2_f, vec2<f32>(2_f, 2_f), 2_f);
     GlobalConst("g", ty.vec4<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2839,7 +2839,7 @@
 
     auto* cast = vec4<f16>(2_h, vec2<f16>(2_h, 2_h), 2_h);
     GlobalConst("g", ty.vec4<f16>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2896,7 +2896,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec4_With_Vec2_F32_F32) {
     auto* cast = vec4<f32>(vec2<f32>(2_f, 2_f), 2_f, 2_f);
     GlobalConst("g", ty.vec4<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2921,7 +2921,7 @@
 
     auto* cast = vec4<f16>(vec2<f16>(2_h, 2_h), 2_h, 2_h);
     GlobalConst("g", ty.vec4<f16>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -2978,7 +2978,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec4_F32_With_Vec2_Vec2) {
     auto* cast = vec4<f32>(vec2<f32>(2_f, 2_f), vec2<f32>(2_f, 2_f));
     GlobalConst("g", ty.vec4<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -3003,7 +3003,7 @@
 
     auto* cast = vec4<f16>(vec2<f16>(2_h, 2_h), vec2<f16>(2_h, 2_h));
     GlobalConst("g", ty.vec4<f16>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -3060,7 +3060,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec4_With_F32_Vec3) {
     auto* cast = vec4<f32>(2_f, vec3<f32>(2_f, 2_f, 2_f));
     GlobalConst("g", ty.vec4<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -3117,7 +3117,7 @@
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_Vec4_With_Vec3_F32) {
     auto* cast = vec4<f32>(vec3<f32>(2_f, 2_f, 2_f), 2_f);
     GlobalConst("g", ty.vec4<f32>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -3142,7 +3142,7 @@
 
     auto* cast = vec4<f16>(vec3<f16>(2_h, 2_h, 2_h), 2_h);
     GlobalConst("g", ty.vec4<f16>(), cast);
-    WrapInFunction(Decl(Var("l", nullptr, Expr("g"))));
+    WrapInFunction(Decl(Var("l", Expr("g"))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -4572,8 +4572,8 @@
     // }
     // let y = vec3<f32>(1.0, 2.0, 3.0); // Reuses the ID 'x'
 
-    WrapInFunction(If(true, Block(Decl(Let("x", nullptr, vec3<f32>(1_f, 2_f, 3_f))))),
-                   Decl(Let("y", nullptr, vec3<f32>(1_f, 2_f, 3_f))));
+    WrapInFunction(If(true, Block(Decl(Let("x", vec3<f32>(1_f, 2_f, 3_f))))),
+                   Decl(Let("y", vec3<f32>(1_f, 2_f, 3_f))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
@@ -4616,9 +4616,9 @@
     // }
     // let y = vec3<f32>(one, 2.0, 3.0); // Mustn't reuse the ID 'x'
 
-    WrapInFunction(Decl(Var("one", nullptr, Expr(1_f))),
-                   If(true, Block(Decl(Let("x", nullptr, vec3<f32>("one", 2_f, 3_f))))),
-                   Decl(Let("y", nullptr, vec3<f32>("one", 2_f, 3_f))));
+    WrapInFunction(Decl(Var("one", Expr(1_f))),
+                   If(true, Block(Decl(Let("x", vec3<f32>("one", 2_f, 3_f))))),
+                   Decl(Let("y", vec3<f32>("one", 2_f, 3_f))));
 
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
diff --git a/src/tint/writer/spirv/builder_entry_point_test.cc b/src/tint/writer/spirv/builder_entry_point_test.cc
index ea36e35..424e89b 100644
--- a/src/tint/writer/spirv/builder_entry_point_test.cc
+++ b/src/tint/writer/spirv/builder_entry_point_test.cc
@@ -51,7 +51,7 @@
                            Location(1u),
                        });
     auto* mul = Mul(Expr(MemberAccessor("coord", "x")), Expr("loc1"));
-    auto* col = Var("col", ty.f32(), ast::StorageClass::kNone, mul);
+    auto* col = Var("col", ty.f32(), mul);
     Func("frag_main", utils::Vector{coord, loc1}, ty.void_(), utils::Vector{WrapInStatement(col)},
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
diff --git a/src/tint/writer/spirv/builder_function_attribute_test.cc b/src/tint/writer/spirv/builder_function_attribute_test.cc
index 449b75d..554c028 100644
--- a/src/tint/writer/spirv/builder_function_attribute_test.cc
+++ b/src/tint/writer/spirv/builder_function_attribute_test.cc
@@ -150,9 +150,9 @@
 }
 
 TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_OverridableConst) {
-    Override("width", ty.i32(), Construct(ty.i32(), 2_i), utils::Vector{Id(7u)});
-    Override("height", ty.i32(), Construct(ty.i32(), 3_i), utils::Vector{Id(8u)});
-    Override("depth", ty.i32(), Construct(ty.i32(), 4_i), utils::Vector{Id(9u)});
+    Override("width", ty.i32(), Construct(ty.i32(), 2_i), Id(7u));
+    Override("height", ty.i32(), Construct(ty.i32(), 3_i), Id(8u));
+    Override("depth", ty.i32(), Construct(ty.i32(), 4_i), Id(9u));
     auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
                       utils::Vector{
                           WorkgroupSize("width", "height", "depth"),
@@ -180,7 +180,7 @@
 }
 
 TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_LiteralAndConst) {
-    Override("height", ty.i32(), Construct(ty.i32(), 2_i), utils::Vector{Id(7u)});
+    Override("height", ty.i32(), Construct(ty.i32(), 2_i), Id(7u));
     GlobalConst("depth", ty.i32(), Construct(ty.i32(), 3_i));
     auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
                       utils::Vector{
diff --git a/src/tint/writer/spirv/builder_function_test.cc b/src/tint/writer/spirv/builder_function_test.cc
index 7cbd442..d1c0e9f 100644
--- a/src/tint/writer/spirv/builder_function_test.cc
+++ b/src/tint/writer/spirv/builder_function_test.cc
@@ -198,14 +198,11 @@
 
     auto* s = Structure("Data", utils::Vector{Member("d", ty.f32())});
 
-    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
+              Group(0));
 
     {
-        auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
+        auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
 
         Func("a", utils::Empty, ty.void_(),
              utils::Vector{
@@ -216,7 +213,7 @@
     }
 
     {
-        auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
+        auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
 
         Func("b", utils::Empty, ty.void_(),
              utils::Vector{
diff --git a/src/tint/writer/spirv/builder_function_variable_test.cc b/src/tint/writer/spirv/builder_function_variable_test.cc
index 4045f4c..40bc85d 100644
--- a/src/tint/writer/spirv/builder_function_variable_test.cc
+++ b/src/tint/writer/spirv/builder_function_variable_test.cc
@@ -73,10 +73,10 @@
 }
 
 TEST_F(BuilderTest, FunctionVar_WithNonConstantConstructor) {
-    auto* a = Let("a", nullptr, Expr(3_f));
+    auto* a = Let("a", Expr(3_f));
     auto* init = vec2<f32>(1_f, Add(Expr("a"), 3_f));
 
-    auto* v = Var("var", ty.vec2<f32>(), ast::StorageClass::kNone, init);
+    auto* v = Var("var", ty.vec2<f32>(), init);
     WrapInFunction(a, v);
 
     spirv::Builder& b = Build();
@@ -109,9 +109,9 @@
     // var v : f32 = 1.0;
     // var v2 : f32 = v; // Should generate the load and store automatically.
 
-    auto* v = Var("v", ty.f32(), ast::StorageClass::kNone, Expr(1_f));
+    auto* v = Var("v", ty.f32(), Expr(1_f));
 
-    auto* v2 = Var("v2", ty.f32(), ast::StorageClass::kNone, Expr("v"));
+    auto* v2 = Var("v2", ty.f32(), Expr("v"));
     WrapInFunction(v, v2);
 
     spirv::Builder& b = Build();
@@ -144,9 +144,9 @@
     // var v : f32 = 1.0;
     // let v2 : f32 = v; // Should generate the load
 
-    auto* v = Var("v", ty.f32(), ast::StorageClass::kNone, Expr(1_f));
+    auto* v = Var("v", ty.f32(), Expr(1_f));
 
-    auto* v2 = Var("v2", ty.f32(), ast::StorageClass::kNone, Expr("v"));
+    auto* v2 = Var("v2", ty.f32(), Expr("v"));
     WrapInFunction(v, v2);
 
     spirv::Builder& b = Build();
@@ -181,7 +181,7 @@
 
     auto* v = Const("v", ty.f32(), Expr(1_f));
 
-    auto* v2 = Var("v2", ty.f32(), ast::StorageClass::kNone, Expr("v"));
+    auto* v2 = Var("v2", ty.f32(), Expr("v"));
     WrapInFunction(v, v2);
 
     spirv::Builder& b = Build();
diff --git a/src/tint/writer/spirv/builder_global_variable_test.cc b/src/tint/writer/spirv/builder_global_variable_test.cc
index e14a017..bfecdf1 100644
--- a/src/tint/writer/spirv/builder_global_variable_test.cc
+++ b/src/tint/writer/spirv/builder_global_variable_test.cc
@@ -65,8 +65,8 @@
     // const c = 42;
     // var v = c;
 
-    auto* c = GlobalConst("c", nullptr, Expr(42_a));
-    GlobalVar("v", nullptr, ast::StorageClass::kPrivate, Expr(c));
+    auto* c = GlobalConst("c", Expr(42_a));
+    GlobalVar("v", ast::StorageClass::kPrivate, Expr(c));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -90,8 +90,8 @@
     // const c = vec3<f32>(1f, 2f, 3f);
     // var v = c;
 
-    auto* c = GlobalConst("c", nullptr, vec3<f32>(1_f, 2_f, 3_f));
-    GlobalVar("v", nullptr, ast::StorageClass::kPrivate, Expr(c));
+    auto* c = GlobalConst("c", vec3<f32>(1_f, 2_f, 3_f));
+    GlobalVar("v", ast::StorageClass::kPrivate, Expr(c));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -120,8 +120,8 @@
     // var v = c;
     Enable(ast::Extension::kF16);
 
-    auto* c = GlobalConst("c", nullptr, vec3<f16>(1_h, 2_h, 3_h));
-    GlobalVar("v", nullptr, ast::StorageClass::kPrivate, Expr(c));
+    auto* c = GlobalConst("c", vec3<f16>(1_h, 2_h, 3_h));
+    GlobalVar("v", ast::StorageClass::kPrivate, Expr(c));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -149,8 +149,8 @@
     // const c = vec3(1, 2, 3);
     // var v = c;
 
-    auto* c = GlobalConst("c", nullptr, Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
-    GlobalVar("v", nullptr, ast::StorageClass::kPrivate, Expr(c));
+    auto* c = GlobalConst("c", Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    GlobalVar("v", ast::StorageClass::kPrivate, Expr(c));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -178,8 +178,8 @@
     // const c = vec3(1.0, 2.0, 3.0);
     // var v = c;
 
-    auto* c = GlobalConst("c", nullptr, Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
-    GlobalVar("v", nullptr, ast::StorageClass::kPrivate, Expr(c));
+    auto* c = GlobalConst("c", Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    GlobalVar("v", ast::StorageClass::kPrivate, Expr(c));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -207,8 +207,8 @@
     // const c = vec3<f32>(vec2<f32>(1f, 2f), 3f));
     // var v = c;
 
-    auto* c = GlobalConst("c", nullptr, vec3<f32>(vec2<f32>(1_f, 2_f), 3_f));
-    GlobalVar("v", nullptr, ast::StorageClass::kPrivate, Expr(c));
+    auto* c = GlobalConst("c", vec3<f32>(vec2<f32>(1_f, 2_f), 3_f));
+    GlobalVar("v", ast::StorageClass::kPrivate, Expr(c));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -233,12 +233,7 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_WithBindingAndGroup) {
-    auto* v =
-        GlobalVar("var", ty.sampler(ast::SamplerKind::kSampler), ast::StorageClass::kNone, nullptr,
-                  utils::Vector{
-                      create<ast::BindingAttribute>(2u),
-                      create<ast::GroupAttribute>(3u),
-                  });
+    auto* v = GlobalVar("var", ty.sampler(ast::SamplerKind::kSampler), Binding(2), Group(3));
 
     spirv::Builder& b = Build();
 
@@ -255,10 +250,7 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_Override_Bool) {
-    auto* v = Override("var", ty.bool_(), Expr(true),
-                       utils::Vector{
-                           Id(1200),
-                       });
+    auto* v = Override("var", ty.bool_(), Expr(true), Id(1200));
 
     spirv::Builder& b = Build();
 
@@ -273,10 +265,7 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_Override_Bool_ZeroValue) {
-    auto* v = Override("var", ty.bool_(), Construct<bool>(),
-                       utils::Vector{
-                           Id(1200),
-                       });
+    auto* v = Override("var", ty.bool_(), Construct<bool>(), Id(1200));
 
     spirv::Builder& b = Build();
 
@@ -291,10 +280,7 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_Override_Bool_NoConstructor) {
-    auto* v = Override("var", ty.bool_(), nullptr,
-                       utils::Vector{
-                           Id(1200),
-                       });
+    auto* v = Override("var", ty.bool_(), Id(1200));
 
     spirv::Builder& b = Build();
 
@@ -309,10 +295,7 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_Override_Scalar) {
-    auto* v = Override("var", ty.f32(), Expr(2_f),
-                       utils::Vector{
-                           Id(0),
-                       });
+    auto* v = Override("var", ty.f32(), Expr(2_f), Id(0));
 
     spirv::Builder& b = Build();
 
@@ -327,10 +310,7 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_Override_Scalar_ZeroValue) {
-    auto* v = Override("var", ty.f32(), Construct<f32>(),
-                       utils::Vector{
-                           Id(0),
-                       });
+    auto* v = Override("var", ty.f32(), Construct<f32>(), Id(0));
 
     spirv::Builder& b = Build();
 
@@ -345,10 +325,7 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_Override_Scalar_F32_NoConstructor) {
-    auto* v = Override("var", ty.f32(), nullptr,
-                       utils::Vector{
-                           Id(0),
-                       });
+    auto* v = Override("var", ty.f32(), Id(0));
 
     spirv::Builder& b = Build();
 
@@ -363,10 +340,7 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_Override_Scalar_I32_NoConstructor) {
-    auto* v = Override("var", ty.i32(), nullptr,
-                       utils::Vector{
-                           Id(0),
-                       });
+    auto* v = Override("var", ty.i32(), Id(0));
 
     spirv::Builder& b = Build();
 
@@ -381,10 +355,7 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_Override_Scalar_U32_NoConstructor) {
-    auto* v = Override("var", ty.u32(), nullptr,
-                       utils::Vector{
-                           Id(0),
-                       });
+    auto* v = Override("var", ty.u32(), Id(0));
 
     spirv::Builder& b = Build();
 
@@ -399,10 +370,7 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_Override_NoId) {
-    auto* var_a = Override("a", ty.bool_(), Expr(true),
-                           utils::Vector{
-                               Id(0),
-                           });
+    auto* var_a = Override("a", ty.bool_(), Expr(true), Id(0));
     auto* var_b = Override("b", ty.bool_(), Expr(false));
 
     spirv::Builder& b = Build();
@@ -479,11 +447,7 @@
                                  Member("b", ty.i32()),
                              });
 
-    GlobalVar("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -520,11 +484,7 @@
 
     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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("b", ty.Of(B), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -559,11 +519,7 @@
 
     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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("b", ty.Of(B), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0), Group(0));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -597,16 +553,9 @@
     // 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,
-              utils::Vector{
-                  create<ast::GroupAttribute>(0u),
-                  create<ast::BindingAttribute>(0u),
-              });
-    GlobalVar("c", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::GroupAttribute>(1u),
-                  create<ast::BindingAttribute>(0u),
-              });
+    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));
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -643,11 +592,7 @@
     auto* type = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
                                     ast::Access::kWrite);
 
-    auto* var_a = GlobalVar("a", type,
-                            utils::Vector{
-                                create<ast::BindingAttribute>(0u),
-                                create<ast::GroupAttribute>(0u),
-                            });
+    auto* var_a = GlobalVar("a", type, Binding(0), Group(0));
 
     spirv::Builder& b = Build();
 
@@ -674,19 +619,11 @@
 
     auto* type_a = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
                                       ast::Access::kReadWrite);
-    auto* var_a = GlobalVar("a", type_a, ast::StorageClass::kNone,
-                            utils::Vector{
-                                create<ast::BindingAttribute>(0u),
-                                create<ast::GroupAttribute>(0u),
-                            });
+    auto* var_a = GlobalVar("a", type_a, Binding(0), Group(0));
 
     auto* type_b = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
                                       ast::Access::kWrite);
-    auto* var_b = GlobalVar("b", type_b, ast::StorageClass::kNone,
-                            utils::Vector{
-                                create<ast::BindingAttribute>(1u),
-                                create<ast::GroupAttribute>(0u),
-                            });
+    auto* var_b = GlobalVar("b", type_b, Binding(1), Group(0));
 
     spirv::Builder& b = Build();
 
diff --git a/src/tint/writer/spirv/builder_loop_test.cc b/src/tint/writer/spirv/builder_loop_test.cc
index ae9e408..7e7f327 100644
--- a/src/tint/writer/spirv/builder_loop_test.cc
+++ b/src/tint/writer/spirv/builder_loop_test.cc
@@ -304,7 +304,7 @@
     //   }
     // }
 
-    auto* cond_var = Decl(Var("cond", nullptr, Expr(true)));
+    auto* cond_var = Decl(Var("cond", Expr(true)));
     auto* if_stmt = If(Expr("cond"), Block(Break()));
     auto* continuing = Block(cond_var, if_stmt);
     auto* loop = Loop(Block(), continuing);
@@ -342,7 +342,7 @@
     //     if (cond) {} else { break; }
     //   }
     // }
-    auto* cond_var = Decl(Var("cond", nullptr, Expr(true)));
+    auto* cond_var = Decl(Var("cond", Expr(true)));
     auto* if_stmt = If(Expr("cond"), Block(), Else(Block(Break())));
     auto* continuing = Block(cond_var, if_stmt);
     auto* loop = Loop(Block(), continuing);
diff --git a/src/tint/writer/spirv/builder_type_test.cc b/src/tint/writer/spirv/builder_type_test.cc
index 58fc174..c6f044e 100644
--- a/src/tint/writer/spirv/builder_type_test.cc
+++ b/src/tint/writer/spirv/builder_type_test.cc
@@ -28,11 +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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
+              Group(0));
 
     spirv::Builder& b = Build();
 
@@ -48,11 +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,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
+              Group(0));
 
     spirv::Builder& b = Build();
 
@@ -842,11 +836,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k1d, ast::TexelFormat::kR32Float,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("test_var", s, Binding(0), Group(0));
 
     spirv::Builder& b = Build();
 
@@ -861,11 +851,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Float,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("test_var", s, Binding(0), Group(0));
 
     spirv::Builder& b = Build();
 
@@ -880,11 +866,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k2dArray, ast::TexelFormat::kR32Float,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("test_var", s, Binding(0), Group(0));
 
     spirv::Builder& b = Build();
 
@@ -899,11 +881,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k3d, ast::TexelFormat::kR32Float,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("test_var", s, Binding(0), Group(0));
 
     spirv::Builder& b = Build();
 
@@ -918,11 +896,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Float,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("test_var", s, Binding(0), Group(0));
 
     spirv::Builder& b = Build();
 
@@ -937,11 +911,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Sint,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("test_var", s, Binding(0), Group(0));
 
     spirv::Builder& b = Build();
 
@@ -956,11 +926,7 @@
     auto* s = ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint,
                                  ast::Access::kWrite);
 
-    GlobalVar("test_var", s,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("test_var", s, Binding(0), Group(0));
 
     spirv::Builder& b = Build();
 
diff --git a/src/tint/writer/wgsl/generator_impl_array_accessor_test.cc b/src/tint/writer/wgsl/generator_impl_array_accessor_test.cc
index d5f7f9f..0b28ae9 100644
--- a/src/tint/writer/wgsl/generator_impl_array_accessor_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_array_accessor_test.cc
@@ -36,7 +36,7 @@
 TEST_F(WgslGeneratorImplTest, IndexAccessor_OfDref) {
     GlobalVar("ary", ty.array<i32, 10>(), ast::StorageClass::kPrivate);
 
-    auto* p = Let("p", nullptr, AddressOf("ary"));
+    auto* p = Let("p", AddressOf("ary"));
     auto* expr = IndexAccessor(Deref("p"), 5_i);
     WrapInFunction(p, expr);
 
diff --git a/src/tint/writer/wgsl/generator_impl_function_test.cc b/src/tint/writer/wgsl/generator_impl_function_test.cc
index 9e9c3a3..09e1e8b 100644
--- a/src/tint/writer/wgsl/generator_impl_function_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_function_test.cc
@@ -179,14 +179,11 @@
                                     Member("d", ty.f32()),
                                 });
 
-    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              utils::Vector{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+    GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite, Binding(0),
+              Group(0));
 
     {
-        auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
+        auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
 
         Func("a", utils::Empty, ty.void_(),
              utils::Vector{
@@ -200,7 +197,7 @@
     }
 
     {
-        auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
+        auto* var = Var("v", ty.f32(), MemberAccessor("data", "d"));
 
         Func("b", utils::Empty, ty.void_(),
              utils::Vector{
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 04f520a..fc424f6 100644
--- a/src/tint/writer/wgsl/generator_impl_global_decl_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_global_decl_test.cc
@@ -105,11 +105,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_Global_Sampler) {
-    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler),
-              utils::Vector{
-                  create<ast::GroupAttribute>(0u),
-                  create<ast::BindingAttribute>(0u),
-              });
+    GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), Group(0), Binding(0));
 
     GeneratorImpl& gen = Build();
 
@@ -121,11 +117,7 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_Global_Texture) {
     auto* st = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-    GlobalVar("t", st,
-              utils::Vector{
-                  create<ast::GroupAttribute>(0u),
-                  create<ast::BindingAttribute>(0u),
-              });
+    GlobalVar("t", st, Group(0), Binding(0));
 
     GeneratorImpl& gen = Build();
 
@@ -137,7 +129,7 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_GlobalConst) {
     GlobalConst("explicit", ty.f32(), Expr(1_f));
-    GlobalConst("inferred", nullptr, Expr(1_f));
+    GlobalConst("inferred", Expr(1_f));
 
     GeneratorImpl& gen = Build();
 
@@ -151,11 +143,8 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_OverridableConstants) {
-    Override("a", ty.f32(), nullptr);
-    Override("b", ty.f32(), nullptr,
-             utils::Vector{
-                 Id(7u),
-             });
+    Override("a", ty.f32());
+    Override("b", ty.f32(), Id(7u));
 
     GeneratorImpl& gen = Build();
 
diff --git a/src/tint/writer/wgsl/generator_impl_member_accessor_test.cc b/src/tint/writer/wgsl/generator_impl_member_accessor_test.cc
index 7386cd0..849f018 100644
--- a/src/tint/writer/wgsl/generator_impl_member_accessor_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_member_accessor_test.cc
@@ -37,7 +37,7 @@
     auto* s = Structure("Data", utils::Vector{Member("mem", ty.f32())});
     GlobalVar("str", ty.Of(s), ast::StorageClass::kPrivate);
 
-    auto* p = Let("p", nullptr, AddressOf("str"));
+    auto* p = Let("p", AddressOf("str"));
     auto* expr = MemberAccessor(Deref("p"), "mem");
     WrapInFunction(p, expr);
 
diff --git a/src/tint/writer/wgsl/generator_impl_type_test.cc b/src/tint/writer/wgsl/generator_impl_type_test.cc
index a86c2a0..73fc186 100644
--- a/src/tint/writer/wgsl/generator_impl_type_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_type_test.cc
@@ -462,11 +462,7 @@
     auto param = GetParam();
 
     auto* t = ty.storage_texture(param.dim, param.fmt, param.access);
-    GlobalVar("g", t,
-              utils::Vector{
-                  create<ast::BindingAttribute>(1u),
-                  create<ast::GroupAttribute>(2u),
-              });
+    GlobalVar("g", t, Binding(1), Group(2));
 
     GeneratorImpl& gen = Build();
 
diff --git a/src/tint/writer/wgsl/generator_impl_unary_op_test.cc b/src/tint/writer/wgsl/generator_impl_unary_op_test.cc
index b741dc8..dadcede 100644
--- a/src/tint/writer/wgsl/generator_impl_unary_op_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_unary_op_test.cc
@@ -45,8 +45,7 @@
 
 TEST_F(WgslUnaryOpTest, Indirection) {
     GlobalVar("G", ty.f32(), ast::StorageClass::kPrivate);
-    auto* p =
-        Let("expr", nullptr, create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
+    auto* p = Let("expr", create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, Expr("G")));
     auto* op = create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection, Expr("expr"));
     WrapInFunction(p, op);
 
diff --git a/src/tint/writer/wgsl/generator_impl_variable_decl_statement_test.cc b/src/tint/writer/wgsl/generator_impl_variable_decl_statement_test.cc
index 1d5e4d4..38c98e9 100644
--- a/src/tint/writer/wgsl/generator_impl_variable_decl_statement_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_variable_decl_statement_test.cc
@@ -37,7 +37,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_InferredType) {
-    auto* var = Var("a", nullptr, ast::StorageClass::kNone, Expr(123_i));
+    auto* var = Var("a", Expr(123_i));
 
     auto* stmt = Decl(var);
     WrapInFunction(stmt);
@@ -51,11 +51,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_AInt) {
-    auto* C = Const("C", nullptr, Expr(1_a));
+    auto* C = Const("C", Expr(1_a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -70,11 +70,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_AFloat) {
-    auto* C = Const("C", nullptr, Expr(1._a));
+    auto* C = Const("C", Expr(1._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -89,11 +89,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_i32) {
-    auto* C = Const("C", nullptr, Expr(1_i));
+    auto* C = Const("C", Expr(1_i));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -108,11 +108,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_u32) {
-    auto* C = Const("C", nullptr, Expr(1_u));
+    auto* C = Const("C", Expr(1_u));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -127,11 +127,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_f32) {
-    auto* C = Const("C", nullptr, Expr(1_f));
+    auto* C = Const("C", Expr(1_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -148,11 +148,11 @@
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* C = Const("C", nullptr, Expr(1_h));
+    auto* C = Const("C", Expr(1_h));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -169,11 +169,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_AInt) {
-    auto* C = Const("C", nullptr, Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    auto* C = Const("C", Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -188,11 +188,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_AFloat) {
-    auto* C = Const("C", nullptr, Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    auto* C = Const("C", Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -207,11 +207,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_f32) {
-    auto* C = Const("C", nullptr, vec3<f32>(1_f, 2_f, 3_f));
+    auto* C = Const("C", vec3<f32>(1_f, 2_f, 3_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -228,11 +228,11 @@
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* C = Const("C", nullptr, vec3<f16>(1_h, 2_h, 3_h));
+    auto* C = Const("C", vec3<f16>(1_h, 2_h, 3_h));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -249,12 +249,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_mat2x3_AFloat) {
-    auto* C =
-        Const("C", nullptr, Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    auto* C = Const("C", Construct(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -269,11 +268,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_mat2x3_f32) {
-    auto* C = Const("C", nullptr, mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
+    auto* C = Const("C", mat2x3<f32>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -290,11 +289,11 @@
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_mat2x3_f16) {
     Enable(ast::Extension::kF16);
 
-    auto* C = Const("C", nullptr, mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
+    auto* C = Const("C", mat2x3<f16>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -311,11 +310,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_arr_f32) {
-    auto* C = Const("C", nullptr, Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
+    auto* C = Const("C", Construct(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     GeneratorImpl& gen = Build();
@@ -330,15 +329,14 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_arr_vec2_bool) {
-    auto* C = Const("C", nullptr,
-                    Construct(ty.array(ty.vec2<bool>(), 3_u),  //
-                              vec2<bool>(true, false),         //
-                              vec2<bool>(false, true),         //
-                              vec2<bool>(true, true)));
+    auto* C = Const("C", Construct(ty.array(ty.vec2<bool>(), 3_u),  //
+                                   vec2<bool>(true, false),         //
+                                   vec2<bool>(false, true),         //
+                                   vec2<bool>(true, true)));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
-             Decl(Let("l", nullptr, Expr(C))),
+             Decl(Let("l", Expr(C))),
          });
 
     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 78e518e..521d3b6 100644
--- a/src/tint/writer/wgsl/generator_impl_variable_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_variable_test.cc
@@ -43,11 +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,
-                        utils::Vector{
-                            create<ast::BindingAttribute>(0u),
-                            create<ast::GroupAttribute>(0u),
-                        });
+    auto* v = GlobalVar("a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead, Binding(0),
+                        Group(0));
 
     GeneratorImpl& gen = Build();
 
@@ -59,10 +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,
-                        utils::Vector{
-                            create<ast::BindingAttribute>(0u),
-                            create<ast::GroupAttribute>(0u),
-                        });
+                        Binding(0), Group(0));
 
     GeneratorImpl& gen = Build();
 
@@ -72,12 +66,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitVariable_Decorated) {
-    auto* v =
-        GlobalVar("a", ty.sampler(ast::SamplerKind::kSampler), ast::StorageClass::kNone, nullptr,
-                  utils::Vector{
-                      create<ast::GroupAttribute>(1u),
-                      create<ast::BindingAttribute>(2u),
-                  });
+    auto* v = GlobalVar("a", ty.sampler(ast::SamplerKind::kSampler), Group(1), Binding(2));
 
     GeneratorImpl& gen = Build();
 
@@ -108,7 +97,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitVariable_Let_Inferred) {
-    auto* v = Let("a", nullptr, Expr(1_f));
+    auto* v = Let("a", Expr(1_f));
     WrapInFunction(v);
 
     GeneratorImpl& gen = Build();
@@ -130,7 +119,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitVariable_Const_Inferred) {
-    auto* v = Const("a", nullptr, Expr(1_f));
+    auto* v = Const("a", Expr(1_f));
     WrapInFunction(v);
 
     GeneratorImpl& gen = Build();