Import Tint changes from Dawn

Changes:
  - 2de4deea1f4de5bae44be624e0ec88060e8aae60 tint: Fix implicit conversion of vector-scalar of abstrac... by Antonio Maiorano <amaiorano@google.com>
  - fa4a7291f2f6083b72a09cb4d944e68c109f8e2b tint: add [[nodiscard]] on utils::Result class by Antonio Maiorano <amaiorano@google.com>
  - cc937971c9ff16d905f59f9c23ae38f7bcb0c94f tint: natvis: add utils::Slice, utils::Vector, and utils:... by Antonio Maiorano <amaiorano@google.com>
  - 735e4b4fa1ef359f2db9308522f5715a3130debd Emit deprecation for fallthrough. by dan sinclair <dsinclair@chromium.org>
  - 4acf608cf2268dc894480bc8d125098e944e553c Include 'libfuzzer_exports.h' in fuzzers that are missing... by Ryan Harrison <rharrison@chromium.org>
  - 3abad2c1257df4d7499914241acf70d03fc2141e When emitting a MSL struct initializer emit the struct na... by dan sinclair <dsinclair@chromium.org>
  - a58d8c9facbf55291dfa90edaf21a7c67cd7cc6e tint: fix builtin calls and binary ops with abstract args... by Antonio Maiorano <amaiorano@google.com>
  - 90d5eb612895d143791aa3527832fcefa57a2b99 tint: Add RemovePhonies transform to spir-v backend by Antonio Maiorano <amaiorano@google.com>
  - b91d7e1b31be193bdeb3a240cd1ae9f09782b290 Spit `expect_variable_ident_decl` into helper and calling... by dan sinclair <dsinclair@chromium.org>
  - 7058a17bda91e64f3c46b8330466776ee585af91 tint: refactor ConstEval API to accept vector of constant... by Antonio Maiorano <amaiorano@google.com>
  - a8e9a6ef2bd6391bce2460d0b544847f4b70eae2 tint: Fix Resolver erroneously materializing matrices of ... by Antonio Maiorano <amaiorano@google.com>
  - 50bc60359ee103f8096e04aa3a6d09c28d481fe5 tint: Add unit test for HasSideEffects for builtins by Antonio Maiorano <amaiorano@google.com>
  - f7e2de797145279ca568f7eed514ba20584756be tint: make RemovePhonies remove call statements to builti... by Antonio Maiorano <amaiorano@google.com>
  - e62fbbc75cfd0e131dd2ed5979290b1dd4863590 Syncing WGSL grammar to Tint. by dan sinclair <dsinclair@chromium.org>
  - 6d5542d7036730f268bf1deb2d566ba815436caf Fix compilation error in delete_statement.cc by Corentin Wallez <cwallez@chromium.org>
  - 1884c964d7e64ff97f69c8e1240d1073dace1ad7 tint: Fix utils::Vector<T, 0>::Push segfaulting by Antonio Maiorano <amaiorano@google.com>
  - 953d610aa2244604a271be3bddd7c609337241df AST fuzzer: mutation that deletes statements by Shiyu Liu <jamessliu2020@gmail.com>
  - 20cddbf04ca6bbca806bd24ef201d1e714d88448 tint: Implement modf and frexp built-ins for f16 types by Zhaoming Jiang <zhaoming.jiang@intel.com>
  - 1cd4706f850edefc7f12c82b041621daf94b21fa Sync the `statement` grammar element. by dan sinclair <dsinclair@chromium.org>
  - 7d8307122e6cdc6c66b3da8e6d7eaa52a2cb54f1 Cleanup some strings; Reorder `attribute` rule. by dan sinclair <dsinclair@chromium.org>
  - 69a9415084dabdf140aa6efafe88deda784e2b66 Extract interpolation sample and type name parsing by dan sinclair <dsinclair@chromium.org>
  - 737fd8223ebab063da09281295ede8bad02db378 Add `expression` into the WGSL parser. by dan sinclair <dsinclair@chromium.org>
  - 7e9f571814c720b887f63870628c03e5e3102757 tint: const eval of binary add by Antonio Maiorano <amaiorano@google.com>
  - b8893b4c2f8a7f018fa55bc8cddeceb4c10d8e7c Sync the `global_decl` grammar rule by dan sinclair <dsinclair@chromium.org>
  - c2a052eaa4453eeb9c797b6f576143bdcf3b26a8 tint: Allow ConstEval functions to fail by Antonio Maiorano <amaiorano@google.com>
  - 50bddbffca5e50e82741d2b203f74a94c009a17c tint: Fix dxc on Linux by Antonio Maiorano <amaiorano@google.com>
  - 50940aeef1da1feccff97b94c9c800f3fafcaf36 tint: add CheckedAdd AFloat overload by Antonio Maiorano <amaiorano@google.com>
  - f3015a81cc35f06226545f4de59164531997b92f tint/reader: Parse static_assert by Ben Clayton <bclayton@google.com>
  - 7d7976d590fe95a0e500e235d3cb78c5997a2f53 tint: Add end-to-end tests for expressions using f16 types by Zhaoming Jiang <zhaoming.jiang@intel.com>
  - 7d04cedce3f197109d076cddbccf8db90d3a1f98 tint/resolver: Pre-allocate containers by Ben Clayton <bclayton@google.com>
  - 6d6692105303da31ac0df301f621ecae834863f7 tint/resolver: Reduce small-vector sizes by Ben Clayton <bclayton@google.com>
  - b4744ac3599ae1691e1f8be9f4feb162b6e5de21 tint/writer: Handle static_asserts by Ben Clayton <bclayton@google.com>
  - 759ac33cd177a74b4f82ace7ead6f02a70dd65ed tint/resolver: Optimize intrinsic lookups by Ben Clayton <bclayton@google.com>
  - 6f8d945dd6ce5d786530c1b2b8bdb2140616cbed Sync reserved token list to WGSL spec. by dan sinclair <dsinclair@chromium.org>
  - 02791e95f3ae8f0536d7b5067704ad3332387251 tint/resolver: Resolve static_assert by Ben Clayton <bclayton@google.com>
  - bfd1a81364f0718f806ab01b2eaa673e1b2cc642 tint/ast: Add StaticAssert node by Ben Clayton <bclayton@google.com>
  - ce6246502c4d4e43b158bd161859179ba606bf75 Add `global_directive` rule to the WGSL parser. by dan sinclair <dsinclair@chromium.org>
  - ab5fc1c4dcdc0f5ac69b463bf18d15112c2326ee Fixup utils::Vector merge conflicts. by dan sinclair <dsinclair@chromium.org>
  - 73778d3b0bd7cb49d3cc9c165c8f4bf7c3017b9d Add support for >>= and <<=. by dan sinclair <dsinclair@chromium.org>
  - 783b169bf47b9fe7a0af7e86381bb4c1b95b3128 tint/ast: Migrate to utils::Vector by Ben Clayton <bclayton@google.com>
  - 34d46731bbc4270086e585186a8cda9e5ed9e02f tint: Replace VectorRef with ConstVectorRef. by Ben Clayton <bclayton@google.com>
  - 4abf28e29b515cbdadba2c83949059bf94f879b5 tint: Add basic support for chromium_experimental_push_co... by dan sinclair <dsinclair@chromium.org>
  - 24d36b022747fddcd345eeecd0767ac68bcaf204 AST fuzzer: fix change unary operator mutation by Alastair F. Donaldson <alastair.donaldson@imperial.ac.uk>
GitOrigin-RevId: 2de4deea1f4de5bae44be624e0ec88060e8aae60
Change-Id: If080953641c8de4363964e89fb685210d6a9c57d
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/98063
Commit-Queue: Copybara Prod <copybara-worker-blackhole@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index bb017a1..cf3ebae 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -310,6 +310,8 @@
     "ast/stage_attribute.h",
     "ast/statement.cc",
     "ast/statement.h",
+    "ast/static_assert.cc",
+    "ast/static_assert.h",
     "ast/storage_class.cc",
     "ast/storage_class.h",
     "ast/storage_texture.cc",
@@ -1046,6 +1048,7 @@
       "ast/sampled_texture_test.cc",
       "ast/sampler_test.cc",
       "ast/stage_attribute_test.cc",
+      "ast/static_assert_test.cc",
       "ast/storage_class_test.cc",
       "ast/storage_texture_test.cc",
       "ast/stride_attribute_test.cc",
@@ -1110,6 +1113,7 @@
       "resolver/function_validation_test.cc",
       "resolver/host_shareable_validation_test.cc",
       "resolver/increment_decrement_validation_test.cc",
+      "resolver/inferred_type_test.cc",
       "resolver/intrinsic_table_test.cc",
       "resolver/is_host_shareable_test.cc",
       "resolver/is_storeable_test.cc",
@@ -1123,6 +1127,7 @@
       "resolver/resolver_test_helper.h",
       "resolver/side_effects_test.cc",
       "resolver/source_variable_test.cc",
+      "resolver/static_assert_test.cc",
       "resolver/storage_class_layout_validation_test.cc",
       "resolver/storage_class_validation_test.cc",
       "resolver/struct_layout_test.cc",
@@ -1317,6 +1322,7 @@
       "writer/spirv/builder_literal_test.cc",
       "writer/spirv/builder_loop_test.cc",
       "writer/spirv/builder_return_test.cc",
+      "writer/spirv/builder_static_assert_test.cc",
       "writer/spirv/builder_switch_test.cc",
       "writer/spirv/builder_test.cc",
       "writer/spirv/builder_type_test.cc",
@@ -1343,11 +1349,11 @@
       "reader/wgsl/parser_impl_and_expression_test.cc",
       "reader/wgsl/parser_impl_argument_expression_list_test.cc",
       "reader/wgsl/parser_impl_assignment_stmt_test.cc",
-      "reader/wgsl/parser_impl_body_stmt_test.cc",
       "reader/wgsl/parser_impl_break_stmt_test.cc",
       "reader/wgsl/parser_impl_bug_cases_test.cc",
       "reader/wgsl/parser_impl_call_stmt_test.cc",
       "reader/wgsl/parser_impl_case_body_test.cc",
+      "reader/wgsl/parser_impl_compound_stmt_test.cc",
       "reader/wgsl/parser_impl_const_literal_test.cc",
       "reader/wgsl/parser_impl_continue_stmt_test.cc",
       "reader/wgsl/parser_impl_continuing_stmt_test.cc",
@@ -1442,6 +1448,7 @@
       "writer/wgsl/generator_impl_loop_test.cc",
       "writer/wgsl/generator_impl_member_accessor_test.cc",
       "writer/wgsl/generator_impl_return_test.cc",
+      "writer/wgsl/generator_impl_static_assert_test.cc",
       "writer/wgsl/generator_impl_switch_test.cc",
       "writer/wgsl/generator_impl_test.cc",
       "writer/wgsl/generator_impl_type_test.cc",
@@ -1482,6 +1489,7 @@
       "writer/msl/generator_impl_module_constant_test.cc",
       "writer/msl/generator_impl_return_test.cc",
       "writer/msl/generator_impl_sanitizer_test.cc",
+      "writer/msl/generator_impl_static_assert_test.cc",
       "writer/msl/generator_impl_switch_test.cc",
       "writer/msl/generator_impl_test.cc",
       "writer/msl/generator_impl_type_test.cc",
@@ -1521,6 +1529,7 @@
       "writer/hlsl/generator_impl_module_constant_test.cc",
       "writer/hlsl/generator_impl_return_test.cc",
       "writer/hlsl/generator_impl_sanitizer_test.cc",
+      "writer/hlsl/generator_impl_static_assert_test.cc",
       "writer/hlsl/generator_impl_switch_test.cc",
       "writer/hlsl/generator_impl_test.cc",
       "writer/hlsl/generator_impl_type_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index b5c32d7..2d25330 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -180,6 +180,8 @@
   ast/stage_attribute.h
   ast/statement.cc
   ast/statement.h
+  ast/static_assert.cc
+  ast/static_assert.h
   ast/storage_class.cc
   ast/storage_class.h
   ast/storage_texture.cc
@@ -742,6 +744,7 @@
     ast/sampled_texture_test.cc
     ast/sampler_test.cc
     ast/stage_attribute_test.cc
+    ast/static_assert_test.cc
     ast/storage_class_test.cc
     ast/storage_texture_test.cc
     ast/stride_attribute_test.cc
@@ -806,6 +809,7 @@
     resolver/resolver_test_helper.h
     resolver/resolver_test.cc
     resolver/side_effects_test.cc
+    resolver/static_assert_test.cc
     resolver/source_variable_test.cc
     resolver/storage_class_layout_validation_test.cc
     resolver/storage_class_validation_test.cc
@@ -940,11 +944,11 @@
       reader/wgsl/parser_impl_and_expression_test.cc
       reader/wgsl/parser_impl_argument_expression_list_test.cc
       reader/wgsl/parser_impl_assignment_stmt_test.cc
-      reader/wgsl/parser_impl_body_stmt_test.cc
       reader/wgsl/parser_impl_break_stmt_test.cc
       reader/wgsl/parser_impl_bug_cases_test.cc
       reader/wgsl/parser_impl_call_stmt_test.cc
       reader/wgsl/parser_impl_case_body_test.cc
+      reader/wgsl/parser_impl_compound_stmt_test.cc
       reader/wgsl/parser_impl_const_literal_test.cc
       reader/wgsl/parser_impl_continue_stmt_test.cc
       reader/wgsl/parser_impl_continuing_stmt_test.cc
@@ -1035,6 +1039,7 @@
       writer/spirv/builder_literal_test.cc
       writer/spirv/builder_loop_test.cc
       writer/spirv/builder_return_test.cc
+      writer/spirv/builder_static_assert_test.cc
       writer/spirv/builder_switch_test.cc
       writer/spirv/builder_test.cc
       writer/spirv/builder_type_test.cc
@@ -1074,6 +1079,7 @@
       writer/wgsl/generator_impl_literal_test.cc
       writer/wgsl/generator_impl_member_accessor_test.cc
       writer/wgsl/generator_impl_return_test.cc
+      writer/wgsl/generator_impl_static_assert_test.cc
       writer/wgsl/generator_impl_switch_test.cc
       writer/wgsl/generator_impl_type_test.cc
       writer/wgsl/generator_impl_unary_op_test.cc
@@ -1156,6 +1162,7 @@
       writer/msl/generator_impl_module_constant_test.cc
       writer/msl/generator_impl_return_test.cc
       writer/msl/generator_impl_sanitizer_test.cc
+      writer/msl/generator_impl_static_assert_test.cc
       writer/msl/generator_impl_switch_test.cc
       writer/msl/generator_impl_test.cc
       writer/msl/generator_impl_type_test.cc
@@ -1227,6 +1234,7 @@
       writer/hlsl/generator_impl_module_constant_test.cc
       writer/hlsl/generator_impl_return_test.cc
       writer/hlsl/generator_impl_sanitizer_test.cc
+      writer/hlsl/generator_impl_static_assert_test.cc
       writer/hlsl/generator_impl_switch_test.cc
       writer/hlsl/generator_impl_test.cc
       writer/hlsl/generator_impl_type_test.cc
diff --git a/src/tint/ast/array.cc b/src/tint/ast/array.cc
index 988dfaf..6a898f4 100644
--- a/src/tint/ast/array.cc
+++ b/src/tint/ast/array.cc
@@ -15,6 +15,7 @@
 #include "src/tint/ast/array.h"
 
 #include <cmath>
+#include <utility>
 
 #include "src/tint/program_builder.h"
 
@@ -42,8 +43,8 @@
              const Source& src,
              const Type* subtype,
              const Expression* cnt,
-             AttributeList attrs)
-    : Base(pid, nid, src), type(subtype), count(cnt), attributes(attrs) {}
+             utils::VectorRef<const Attribute*> attrs)
+    : Base(pid, nid, src), type(subtype), count(cnt), attributes(std::move(attrs)) {}
 
 Array::Array(Array&&) = default;
 
@@ -73,7 +74,7 @@
     auto* ty = ctx->Clone(type);
     auto* cnt = ctx->Clone(count);
     auto attrs = ctx->Clone(attributes);
-    return ctx->dst->create<Array>(src, ty, cnt, attrs);
+    return ctx->dst->create<Array>(src, ty, cnt, std::move(attrs));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/array.h b/src/tint/ast/array.h
index 6ad1cbc..19e806e 100644
--- a/src/tint/ast/array.h
+++ b/src/tint/ast/array.h
@@ -43,7 +43,7 @@
           const Source& src,
           const Type* subtype,
           const Expression* count,
-          AttributeList attributes);
+          utils::VectorRef<const Attribute*> attributes);
     /// Move constructor
     Array(Array&&);
     ~Array() override;
@@ -69,7 +69,7 @@
     const Expression* const count;
 
     /// the array attributes
-    const AttributeList attributes;
+    const utils::Vector<const Attribute*, 1> attributes;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/array_test.cc b/src/tint/ast/array_test.cc
index f8d469b..e2ce77b 100644
--- a/src/tint/ast/array_test.cc
+++ b/src/tint/ast/array_test.cc
@@ -26,7 +26,7 @@
 TEST_F(AstArrayTest, CreateSizedArray) {
     auto* u32 = create<U32>();
     auto* count = Expr(3_u);
-    auto* arr = create<Array>(u32, count, AttributeList{});
+    auto* arr = create<Array>(u32, count, utils::Empty);
     EXPECT_EQ(arr->type, u32);
     EXPECT_EQ(arr->count, count);
     EXPECT_TRUE(arr->Is<Array>());
@@ -35,7 +35,7 @@
 
 TEST_F(AstArrayTest, CreateRuntimeArray) {
     auto* u32 = create<U32>();
-    auto* arr = create<Array>(u32, nullptr, AttributeList{});
+    auto* arr = create<Array>(u32, nullptr, utils::Empty);
     EXPECT_EQ(arr->type, u32);
     EXPECT_EQ(arr->count, nullptr);
     EXPECT_TRUE(arr->Is<Array>());
@@ -43,7 +43,7 @@
 }
 
 TEST_F(AstArrayTest, CreateInferredTypeArray) {
-    auto* arr = create<Array>(nullptr, nullptr, AttributeList{});
+    auto* arr = create<Array>(nullptr, nullptr, utils::Empty);
     EXPECT_EQ(arr->type, nullptr);
     EXPECT_EQ(arr->count, nullptr);
     EXPECT_TRUE(arr->Is<Array>());
@@ -52,35 +52,35 @@
 
 TEST_F(AstArrayTest, FriendlyName_RuntimeSized) {
     auto* i32 = create<I32>();
-    auto* arr = create<Array>(i32, nullptr, AttributeList{});
+    auto* arr = create<Array>(i32, nullptr, utils::Empty);
     EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32>");
 }
 
 TEST_F(AstArrayTest, FriendlyName_LiteralSized) {
     auto* i32 = create<I32>();
-    auto* arr = create<Array>(i32, Expr(5_u), AttributeList{});
+    auto* arr = create<Array>(i32, Expr(5_u), utils::Empty);
     EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, 5>");
 }
 
 TEST_F(AstArrayTest, FriendlyName_ConstantSized) {
     auto* i32 = create<I32>();
-    auto* arr = create<Array>(i32, Expr("size"), AttributeList{});
+    auto* arr = create<Array>(i32, Expr("size"), utils::Empty);
     EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, size>");
 }
 
 TEST_F(AstArrayTest, FriendlyName_WithStride) {
     auto* i32 = create<I32>();
-    auto* arr = create<Array>(i32, Expr(5_u), AttributeList{create<StrideAttribute>(32u)});
+    auto* arr = create<Array>(i32, Expr(5_u), utils::Vector{create<StrideAttribute>(32u)});
     EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(32) array<i32, 5>");
 }
 
 TEST_F(AstArrayTest, FriendlyName_InferredTypeAndCount) {
-    auto* arr = create<Array>(nullptr, nullptr, AttributeList{});
+    auto* arr = create<Array>(nullptr, nullptr, utils::Empty);
     EXPECT_EQ(arr->FriendlyName(Symbols()), "array");
 }
 
 TEST_F(AstArrayTest, FriendlyName_InferredTypeAndCount_WithStrize) {
-    auto* arr = create<Array>(nullptr, nullptr, AttributeList{create<StrideAttribute>(32u)});
+    auto* arr = create<Array>(nullptr, nullptr, utils::Vector{create<StrideAttribute>(32u)});
     EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(32) array");
 }
 
diff --git a/src/tint/ast/attribute.h b/src/tint/ast/attribute.h
index 03d80c6..1809782 100644
--- a/src/tint/ast/attribute.h
+++ b/src/tint/ast/attribute.h
@@ -38,13 +38,10 @@
     Attribute(ProgramID pid, NodeID nid, const Source& src) : Base(pid, nid, src) {}
 };
 
-/// A list of attributes
-using AttributeList = std::vector<const Attribute*>;
-
 /// @param attributes the list of attributes to search
 /// @returns true if `attributes` includes a attribute of type `T`
 template <typename... Ts>
-bool HasAttribute(const AttributeList& attributes) {
+bool HasAttribute(utils::VectorRef<const Attribute*> attributes) {
     for (auto* attr : attributes) {
         if (attr->IsAnyOf<Ts...>()) {
             return true;
@@ -56,7 +53,7 @@
 /// @param attributes the list of attributes to search
 /// @returns a pointer to `T` from `attributes` if found, otherwise nullptr.
 template <typename T>
-const T* GetAttribute(const AttributeList& attributes) {
+const T* GetAttribute(utils::VectorRef<const Attribute*> attributes) {
     for (auto* attr : attributes) {
         if (attr->Is<T>()) {
             return attr->As<T>();
diff --git a/src/tint/ast/block_statement.cc b/src/tint/ast/block_statement.cc
index 4b39121..430dbba 100644
--- a/src/tint/ast/block_statement.cc
+++ b/src/tint/ast/block_statement.cc
@@ -23,7 +23,7 @@
 BlockStatement::BlockStatement(ProgramID pid,
                                NodeID nid,
                                const Source& src,
-                               const StatementList& stmts)
+                               utils::VectorRef<const Statement*> stmts)
     : Base(pid, nid, src), statements(std::move(stmts)) {
     for (auto* stmt : statements) {
         TINT_ASSERT(AST, stmt);
@@ -39,7 +39,7 @@
     // Clone arguments outside of create() call to have deterministic ordering
     auto src = ctx->Clone(source);
     auto stmts = ctx->Clone(statements);
-    return ctx->dst->create<BlockStatement>(src, stmts);
+    return ctx->dst->create<BlockStatement>(src, std::move(stmts));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/block_statement.h b/src/tint/ast/block_statement.h
index c67ecd0..22d0a65 100644
--- a/src/tint/ast/block_statement.h
+++ b/src/tint/ast/block_statement.h
@@ -32,16 +32,16 @@
     BlockStatement(ProgramID pid,
                    NodeID nid,
                    const Source& source,
-                   const StatementList& statements);
+                   utils::VectorRef<const Statement*> statements);
     /// Move constructor
     BlockStatement(BlockStatement&&);
     ~BlockStatement() override;
 
     /// @returns true if the block has no statements
-    bool Empty() const { return statements.empty(); }
+    bool Empty() const { return statements.IsEmpty(); }
 
     /// @returns the last statement in the block or nullptr if block empty
-    const Statement* Last() const { return statements.empty() ? nullptr : statements.back(); }
+    const Statement* Last() const { return statements.IsEmpty() ? nullptr : statements.Back(); }
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
     /// `ctx`.
@@ -50,7 +50,7 @@
     const BlockStatement* Clone(CloneContext* ctx) const override;
 
     /// the statement list
-    const StatementList statements;
+    const utils::Vector<const Statement*, 8> statements;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/block_statement_test.cc b/src/tint/ast/block_statement_test.cc
index 4097b20..ec92c3f 100644
--- a/src/tint/ast/block_statement_test.cc
+++ b/src/tint/ast/block_statement_test.cc
@@ -26,21 +26,21 @@
     auto* d = create<DiscardStatement>();
     auto* ptr = d;
 
-    auto* b = create<BlockStatement>(StatementList{d});
+    auto* b = create<BlockStatement>(utils::Vector{d});
 
-    ASSERT_EQ(b->statements.size(), 1u);
+    ASSERT_EQ(b->statements.Length(), 1u);
     EXPECT_EQ(b->statements[0], ptr);
 }
 
 TEST_F(BlockStatementTest, Creation_WithSource) {
-    auto* b = create<BlockStatement>(Source{Source::Location{20, 2}}, ast::StatementList{});
+    auto* b = create<BlockStatement>(Source{Source::Location{20, 2}}, utils::Empty);
     auto src = b->source;
     EXPECT_EQ(src.range.begin.line, 20u);
     EXPECT_EQ(src.range.begin.column, 2u);
 }
 
 TEST_F(BlockStatementTest, IsBlock) {
-    auto* b = create<BlockStatement>(ast::StatementList{});
+    auto* b = create<BlockStatement>(utils::Empty);
     EXPECT_TRUE(b->Is<BlockStatement>());
 }
 
@@ -48,7 +48,7 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.create<BlockStatement>(ast::StatementList{nullptr});
+            b.create<BlockStatement>(utils::Vector<const ast::Statement*, 1>{nullptr});
         },
         "internal compiler error");
 }
@@ -58,7 +58,7 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<BlockStatement>(ast::StatementList{b2.create<DiscardStatement>()});
+            b1.create<BlockStatement>(utils::Vector{b2.create<DiscardStatement>()});
         },
         "internal compiler error");
 }
diff --git a/src/tint/ast/builtin_texture_helper_test.cc b/src/tint/ast/builtin_texture_helper_test.cc
index ad42267..882c0bb 100644
--- a/src/tint/ast/builtin_texture_helper_test.cc
+++ b/src/tint/ast/builtin_texture_helper_test.cc
@@ -29,7 +29,7 @@
                                          ast::TextureDimension dims,
                                          TextureDataType datatype,
                                          const char* f,
-                                         std::function<ExpressionList(ProgramBuilder*)> a)
+                                         std::function<Args(ProgramBuilder*)> a)
     : overload(o),
       description(desc),
       texture_kind(tk),
@@ -44,7 +44,7 @@
                                          ast::TextureDimension dims,
                                          TextureDataType datatype,
                                          const char* f,
-                                         std::function<ExpressionList(ProgramBuilder*)> a)
+                                         std::function<Args(ProgramBuilder*)> a)
     : overload(o),
       description(desc),
       texture_kind(tk),
@@ -59,7 +59,7 @@
                                          ast::TextureDimension dims,
                                          TextureDataType datatype,
                                          const char* f,
-                                         std::function<ExpressionList(ProgramBuilder*)> a)
+                                         std::function<Args(ProgramBuilder*)> a)
     : overload(o),
       description(d),
       texture_kind(TextureKind::kStorage),
@@ -141,7 +141,7 @@
 }
 
 const ast::Variable* TextureOverloadCase::BuildTextureVariable(ProgramBuilder* b) const {
-    AttributeList attrs = {
+    utils::Vector attrs{
         b->create<ast::GroupAttribute>(0u),
         b->create<ast::BindingAttribute>(0u),
     };
@@ -175,7 +175,7 @@
 }
 
 const ast::Variable* TextureOverloadCase::BuildSamplerVariable(ProgramBuilder* b) const {
-    AttributeList attrs = {
+    utils::Vector attrs = {
         b->create<ast::GroupAttribute>(0u),
         b->create<ast::BindingAttribute>(1u),
     };
diff --git a/src/tint/ast/builtin_texture_helper_test.h b/src/tint/ast/builtin_texture_helper_test.h
index 1616a86..bb61875 100644
--- a/src/tint/ast/builtin_texture_helper_test.h
+++ b/src/tint/ast/builtin_texture_helper_test.h
@@ -177,6 +177,9 @@
 
 /// Describes a texture builtin overload
 struct TextureOverloadCase {
+    /// Args is a list of ast::Expression used as arguments to the texture overload case.
+    using Args = utils::Vector<const ast::Expression*, 8>;
+
     /// Constructor for textureSample...() functions
     TextureOverloadCase(ValidTextureOverload,
                         const char*,
@@ -185,7 +188,7 @@
                         ast::TextureDimension,
                         TextureDataType,
                         const char*,
-                        std::function<ExpressionList(ProgramBuilder*)>);
+                        std::function<Args(ProgramBuilder*)>);
     /// Constructor for textureLoad() functions with non-storage textures
     TextureOverloadCase(ValidTextureOverload,
                         const char*,
@@ -193,7 +196,7 @@
                         ast::TextureDimension,
                         TextureDataType,
                         const char*,
-                        std::function<ExpressionList(ProgramBuilder*)>);
+                        std::function<Args(ProgramBuilder*)>);
     /// Constructor for textureLoad() with storage textures
     TextureOverloadCase(ValidTextureOverload,
                         const char*,
@@ -202,7 +205,7 @@
                         ast::TextureDimension,
                         TextureDataType,
                         const char*,
-                        std::function<ExpressionList(ProgramBuilder*)>);
+                        std::function<Args(ProgramBuilder*)>);
     /// Copy constructor
     TextureOverloadCase(const TextureOverloadCase&);
     /// Destructor
@@ -246,7 +249,7 @@
     /// Name of the function. e.g. `textureSample`, `textureSampleGrad`, etc
     const char* const function;
     /// A function that builds the AST arguments for the overload
-    std::function<ExpressionList(ProgramBuilder*)> const args;
+    std::function<Args(ProgramBuilder*)> const args;
 };
 
 std::ostream& operator<<(std::ostream& out, const TextureOverloadCase& data);
diff --git a/src/tint/ast/call_expression.cc b/src/tint/ast/call_expression.cc
index 03eecf5..5a8aa96 100644
--- a/src/tint/ast/call_expression.cc
+++ b/src/tint/ast/call_expression.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/ast/call_expression.h"
 
+#include <utility>
+
 #include "src/tint/program_builder.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::CallExpression);
@@ -37,8 +39,8 @@
                                NodeID nid,
                                const Source& src,
                                const IdentifierExpression* name,
-                               ExpressionList a)
-    : Base(pid, nid, src), target(ToTarget(name)), args(a) {
+                               utils::VectorRef<const Expression*> a)
+    : Base(pid, nid, src), target(ToTarget(name)), args(std::move(a)) {
     TINT_ASSERT(AST, name);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, name, program_id);
     for (auto* arg : args) {
@@ -51,8 +53,8 @@
                                NodeID nid,
                                const Source& src,
                                const Type* type,
-                               ExpressionList a)
-    : Base(pid, nid, src), target(ToTarget(type)), args(a) {
+                               utils::VectorRef<const Expression*> a)
+    : Base(pid, nid, src), target(ToTarget(type)), args(std::move(a)) {
     TINT_ASSERT(AST, type);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, type, program_id);
     for (auto* arg : args) {
@@ -69,8 +71,9 @@
     // Clone arguments outside of create() call to have deterministic ordering
     auto src = ctx->Clone(source);
     auto p = ctx->Clone(args);
-    return target.name ? ctx->dst->create<CallExpression>(src, ctx->Clone(target.name), p)
-                       : ctx->dst->create<CallExpression>(src, ctx->Clone(target.type), p);
+    return target.name
+               ? ctx->dst->create<CallExpression>(src, ctx->Clone(target.name), std::move(p))
+               : ctx->dst->create<CallExpression>(src, ctx->Clone(target.type), std::move(p));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/call_expression.h b/src/tint/ast/call_expression.h
index e020429..587a88f 100644
--- a/src/tint/ast/call_expression.h
+++ b/src/tint/ast/call_expression.h
@@ -42,7 +42,7 @@
                    NodeID nid,
                    const Source& source,
                    const IdentifierExpression* name,
-                   ExpressionList args);
+                   utils::VectorRef<const Expression*> args);
 
     /// Constructor
     /// @param pid the identifier of the program that owns this node
@@ -54,7 +54,7 @@
                    NodeID nid,
                    const Source& source,
                    const Type* type,
-                   ExpressionList args);
+                   utils::VectorRef<const Expression*> args);
 
     /// Move constructor
     CallExpression(CallExpression&&);
@@ -80,7 +80,7 @@
     const Target target;
 
     /// The arguments
-    const ExpressionList args;
+    const utils::Vector<const Expression*, 8> args;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/call_expression_test.cc b/src/tint/ast/call_expression_test.cc
index 5cb31cb..8b5b6a9 100644
--- a/src/tint/ast/call_expression_test.cc
+++ b/src/tint/ast/call_expression_test.cc
@@ -22,23 +22,24 @@
 
 TEST_F(CallExpressionTest, CreationIdentifier) {
     auto* func = Expr("func");
-    ExpressionList params;
-    params.push_back(Expr("param1"));
-    params.push_back(Expr("param2"));
+    utils::Vector params{
+        Expr("param1"),
+        Expr("param2"),
+    };
 
     auto* stmt = create<CallExpression>(func, params);
     EXPECT_EQ(stmt->target.name, func);
     EXPECT_EQ(stmt->target.type, nullptr);
 
     const auto& vec = stmt->args;
-    ASSERT_EQ(vec.size(), 2u);
+    ASSERT_EQ(vec.Length(), 2u);
     EXPECT_EQ(vec[0], params[0]);
     EXPECT_EQ(vec[1], params[1]);
 }
 
 TEST_F(CallExpressionTest, CreationIdentifier_WithSource) {
     auto* func = Expr("func");
-    auto* stmt = create<CallExpression>(Source{{20, 2}}, func, ExpressionList{});
+    auto* stmt = create<CallExpression>(Source{{20, 2}}, func, utils::Empty);
     EXPECT_EQ(stmt->target.name, func);
     EXPECT_EQ(stmt->target.type, nullptr);
 
@@ -49,23 +50,24 @@
 
 TEST_F(CallExpressionTest, CreationType) {
     auto* type = ty.f32();
-    ExpressionList params;
-    params.push_back(Expr("param1"));
-    params.push_back(Expr("param2"));
+    utils::Vector params{
+        Expr("param1"),
+        Expr("param2"),
+    };
 
     auto* stmt = create<CallExpression>(type, params);
     EXPECT_EQ(stmt->target.name, nullptr);
     EXPECT_EQ(stmt->target.type, type);
 
     const auto& vec = stmt->args;
-    ASSERT_EQ(vec.size(), 2u);
+    ASSERT_EQ(vec.Length(), 2u);
     EXPECT_EQ(vec[0], params[0]);
     EXPECT_EQ(vec[1], params[1]);
 }
 
 TEST_F(CallExpressionTest, CreationType_WithSource) {
     auto* type = ty.f32();
-    auto* stmt = create<CallExpression>(Source{{20, 2}}, type, ExpressionList{});
+    auto* stmt = create<CallExpression>(Source{{20, 2}}, type, utils::Empty);
     EXPECT_EQ(stmt->target.name, nullptr);
     EXPECT_EQ(stmt->target.type, type);
 
@@ -76,7 +78,7 @@
 
 TEST_F(CallExpressionTest, IsCall) {
     auto* func = Expr("func");
-    auto* stmt = create<CallExpression>(func, ExpressionList{});
+    auto* stmt = create<CallExpression>(func, utils::Empty);
     EXPECT_TRUE(stmt->Is<CallExpression>());
 }
 
@@ -84,7 +86,7 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.create<CallExpression>(static_cast<IdentifierExpression*>(nullptr), ExpressionList{});
+            b.create<CallExpression>(static_cast<IdentifierExpression*>(nullptr), utils::Empty);
         },
         "internal compiler error");
 }
@@ -93,7 +95,7 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.create<CallExpression>(static_cast<Type*>(nullptr), ExpressionList{});
+            b.create<CallExpression>(static_cast<Type*>(nullptr), utils::Empty);
         },
         "internal compiler error");
 }
@@ -102,11 +104,11 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            ExpressionList params;
-            params.push_back(b.Expr("param1"));
-            params.push_back(nullptr);
-            params.push_back(b.Expr("param2"));
-            b.create<CallExpression>(b.Expr("func"), params);
+            b.create<CallExpression>(b.Expr("func"), utils::Vector{
+                                                         b.Expr("param1"),
+                                                         nullptr,
+                                                         b.Expr("param2"),
+                                                     });
         },
         "internal compiler error");
 }
@@ -116,7 +118,7 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<CallExpression>(b2.Expr("func"), ExpressionList{});
+            b1.create<CallExpression>(b2.Expr("func"), utils::Empty);
         },
         "internal compiler error");
 }
@@ -126,7 +128,7 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<CallExpression>(b2.ty.f32(), ExpressionList{});
+            b1.create<CallExpression>(b2.ty.f32(), utils::Empty);
         },
         "internal compiler error");
 }
@@ -136,7 +138,7 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<CallExpression>(b1.Expr("func"), ExpressionList{b2.Expr("param1")});
+            b1.create<CallExpression>(b1.Expr("func"), utils::Vector{b2.Expr("param1")});
         },
         "internal compiler error");
 }
diff --git a/src/tint/ast/call_statement_test.cc b/src/tint/ast/call_statement_test.cc
index 3426597..84d2b41 100644
--- a/src/tint/ast/call_statement_test.cc
+++ b/src/tint/ast/call_statement_test.cc
@@ -23,7 +23,7 @@
 using CallStatementTest = TestHelper;
 
 TEST_F(CallStatementTest, Creation) {
-    auto* expr = create<CallExpression>(Expr("func"), ExpressionList{});
+    auto* expr = create<CallExpression>(Expr("func"), utils::Empty);
 
     auto* c = create<CallStatement>(expr);
     EXPECT_EQ(c->expr, expr);
@@ -48,7 +48,7 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<CallStatement>(b2.create<CallExpression>(b2.Expr("func"), ExpressionList{}));
+            b1.create<CallStatement>(b2.create<CallExpression>(b2.Expr("func"), utils::Empty));
         },
         "internal compiler error");
 }
diff --git a/src/tint/ast/case_statement.cc b/src/tint/ast/case_statement.cc
index 55be8e3..9f1c20e 100644
--- a/src/tint/ast/case_statement.cc
+++ b/src/tint/ast/case_statement.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/ast/case_statement.h"
 
+#include <utility>
+
 #include "src/tint/program_builder.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::CaseStatement);
@@ -23,9 +25,9 @@
 CaseStatement::CaseStatement(ProgramID pid,
                              NodeID nid,
                              const Source& src,
-                             CaseSelectorList s,
+                             utils::VectorRef<const IntLiteralExpression*> s,
                              const BlockStatement* b)
-    : Base(pid, nid, src), selectors(s), body(b) {
+    : Base(pid, nid, src), selectors(std::move(s)), body(b) {
     TINT_ASSERT(AST, body);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
     for (auto* selector : selectors) {
@@ -43,7 +45,7 @@
     auto src = ctx->Clone(source);
     auto sel = ctx->Clone(selectors);
     auto* b = ctx->Clone(body);
-    return ctx->dst->create<CaseStatement>(src, sel, b);
+    return ctx->dst->create<CaseStatement>(src, std::move(sel), b);
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/case_statement.h b/src/tint/ast/case_statement.h
index 613b721..47d2097 100644
--- a/src/tint/ast/case_statement.h
+++ b/src/tint/ast/case_statement.h
@@ -22,9 +22,6 @@
 
 namespace tint::ast {
 
-/// A list of case literals
-using CaseSelectorList = std::vector<const IntLiteralExpression*>;
-
 /// A case statement
 class CaseStatement final : public Castable<CaseStatement, Statement> {
   public:
@@ -37,14 +34,14 @@
     CaseStatement(ProgramID pid,
                   NodeID nid,
                   const Source& src,
-                  CaseSelectorList selectors,
+                  utils::VectorRef<const IntLiteralExpression*> selectors,
                   const BlockStatement* body);
     /// Move constructor
     CaseStatement(CaseStatement&&);
     ~CaseStatement() override;
 
     /// @returns true if this is a default statement
-    bool IsDefault() const { return selectors.empty(); }
+    bool IsDefault() const { return selectors.IsEmpty(); }
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
     /// `ctx`.
@@ -53,15 +50,12 @@
     const CaseStatement* Clone(CloneContext* ctx) const override;
 
     /// The case selectors, empty if none set
-    const CaseSelectorList selectors;
+    const utils::Vector<const IntLiteralExpression*, 4> selectors;
 
     /// The case body
     const BlockStatement* const body;
 };
 
-/// A list of case statements
-using CaseStatementList = std::vector<const CaseStatement*>;
-
 }  // namespace tint::ast
 
 #endif  // SRC_TINT_AST_CASE_STATEMENT_H_
diff --git a/src/tint/ast/case_statement_test.cc b/src/tint/ast/case_statement_test.cc
index 12fcbda..dc3c88a 100644
--- a/src/tint/ast/case_statement_test.cc
+++ b/src/tint/ast/case_statement_test.cc
@@ -27,40 +27,37 @@
 using CaseStatementTest = TestHelper;
 
 TEST_F(CaseStatementTest, Creation_i32) {
-    CaseSelectorList b;
     auto* selector = Expr(2_i);
-    b.push_back(selector);
+    utils::Vector b{selector};
 
     auto* discard = create<DiscardStatement>();
-    auto* body = create<BlockStatement>(StatementList{discard});
+    auto* body = create<BlockStatement>(utils::Vector{discard});
 
     auto* c = create<CaseStatement>(b, body);
-    ASSERT_EQ(c->selectors.size(), 1u);
+    ASSERT_EQ(c->selectors.Length(), 1u);
     EXPECT_EQ(c->selectors[0], selector);
-    ASSERT_EQ(c->body->statements.size(), 1u);
+    ASSERT_EQ(c->body->statements.Length(), 1u);
     EXPECT_EQ(c->body->statements[0], discard);
 }
 
 TEST_F(CaseStatementTest, Creation_u32) {
-    CaseSelectorList b;
     auto* selector = Expr(2_u);
-    b.push_back(selector);
+    utils::Vector b{selector};
 
     auto* discard = create<DiscardStatement>();
-    auto* body = create<BlockStatement>(StatementList{discard});
+    auto* body = create<BlockStatement>(utils::Vector{discard});
 
     auto* c = create<CaseStatement>(b, body);
-    ASSERT_EQ(c->selectors.size(), 1u);
+    ASSERT_EQ(c->selectors.Length(), 1u);
     EXPECT_EQ(c->selectors[0], selector);
-    ASSERT_EQ(c->body->statements.size(), 1u);
+    ASSERT_EQ(c->body->statements.Length(), 1u);
     EXPECT_EQ(c->body->statements[0], discard);
 }
 
 TEST_F(CaseStatementTest, Creation_WithSource) {
-    CaseSelectorList b;
-    b.push_back(Expr(2_i));
+    utils::Vector b{Expr(2_i)};
 
-    auto* body = create<BlockStatement>(StatementList{
+    auto* body = create<BlockStatement>(utils::Vector{
         create<DiscardStatement>(),
     });
     auto* c = create<CaseStatement>(Source{Source::Location{20, 2}}, b, body);
@@ -70,23 +67,21 @@
 }
 
 TEST_F(CaseStatementTest, IsDefault_WithoutSelectors) {
-    auto* body = create<BlockStatement>(StatementList{
+    auto* body = create<BlockStatement>(utils::Vector{
         create<DiscardStatement>(),
     });
-    auto* c = create<CaseStatement>(CaseSelectorList{}, body);
+    auto* c = create<CaseStatement>(utils::Empty, body);
     EXPECT_TRUE(c->IsDefault());
 }
 
 TEST_F(CaseStatementTest, IsDefault_WithSelectors) {
-    CaseSelectorList b;
-    b.push_back(Expr(2_i));
-
-    auto* c = create<CaseStatement>(b, create<BlockStatement>(StatementList{}));
+    utils::Vector b{Expr(2_i)};
+    auto* c = create<CaseStatement>(b, create<BlockStatement>(utils::Empty));
     EXPECT_FALSE(c->IsDefault());
 }
 
 TEST_F(CaseStatementTest, IsCase) {
-    auto* c = create<CaseStatement>(CaseSelectorList{}, create<BlockStatement>(StatementList{}));
+    auto* c = create<CaseStatement>(utils::Empty, create<BlockStatement>(utils::Empty));
     EXPECT_TRUE(c->Is<CaseStatement>());
 }
 
@@ -94,7 +89,7 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.create<CaseStatement>(CaseSelectorList{}, nullptr);
+            b.create<CaseStatement>(utils::Empty, nullptr);
         },
         "internal compiler error");
 }
@@ -103,8 +98,8 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.create<CaseStatement>(CaseSelectorList{nullptr},
-                                    b.create<BlockStatement>(StatementList{}));
+            b.create<CaseStatement>(utils::Vector<const ast::IntLiteralExpression*, 1>{nullptr},
+                                    b.create<BlockStatement>(utils::Empty));
         },
         "internal compiler error");
 }
@@ -114,8 +109,7 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<CaseStatement>(CaseSelectorList{},
-                                     b2.create<BlockStatement>(StatementList{}));
+            b1.create<CaseStatement>(utils::Empty, b2.create<BlockStatement>(utils::Empty));
         },
         "internal compiler error");
 }
@@ -125,8 +119,8 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<CaseStatement>(CaseSelectorList{b2.Expr(2_i)},
-                                     b1.create<BlockStatement>(StatementList{}));
+            b1.create<CaseStatement>(utils::Vector{b2.Expr(2_i)},
+                                     b1.create<BlockStatement>(utils::Empty));
         },
         "internal compiler error");
 }
diff --git a/src/tint/ast/const.cc b/src/tint/ast/const.cc
index 4dbbb86..fbc8469 100644
--- a/src/tint/ast/const.cc
+++ b/src/tint/ast/const.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/ast/const.h"
 
+#include <utility>
+
 #include "src/tint/program_builder.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::Const);
@@ -26,8 +28,8 @@
              const Symbol& sym,
              const ast::Type* ty,
              const Expression* ctor,
-             AttributeList attrs)
-    : Base(pid, nid, src, sym, ty, ctor, attrs) {
+             utils::VectorRef<const Attribute*> attrs)
+    : Base(pid, nid, src, sym, ty, ctor, std::move(attrs)) {
     TINT_ASSERT(AST, ctor != nullptr);
 }
 
@@ -45,7 +47,7 @@
     auto* ty = ctx->Clone(type);
     auto* ctor = ctx->Clone(constructor);
     auto attrs = ctx->Clone(attributes);
-    return ctx->dst->create<Const>(src, sym, ty, ctor, attrs);
+    return ctx->dst->create<Const>(src, sym, ty, ctor, std::move(attrs));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/const.h b/src/tint/ast/const.h
index 48cbab8..c37458f 100644
--- a/src/tint/ast/const.h
+++ b/src/tint/ast/const.h
@@ -46,7 +46,7 @@
           const Symbol& sym,
           const ast::Type* type,
           const Expression* constructor,
-          AttributeList attributes);
+          utils::VectorRef<const Attribute*> attributes);
 
     /// Move constructor
     Const(Const&&);
diff --git a/src/tint/ast/enable.h b/src/tint/ast/enable.h
index df8c5e9..4c27113 100644
--- a/src/tint/ast/enable.h
+++ b/src/tint/ast/enable.h
@@ -52,9 +52,6 @@
     const Extension extension;
 };
 
-/// A list of enables
-using EnableList = std::vector<const Enable*>;
-
 }  // namespace tint::ast
 
 #endif  // SRC_TINT_AST_ENABLE_H_
diff --git a/src/tint/ast/expression.h b/src/tint/ast/expression.h
index 2690b93..8e0718b 100644
--- a/src/tint/ast/expression.h
+++ b/src/tint/ast/expression.h
@@ -38,9 +38,6 @@
     Expression(Expression&&);
 };
 
-/// A list of expressions
-using ExpressionList = std::vector<const Expression*>;
-
 }  // namespace tint::ast
 
 #endif  // SRC_TINT_AST_EXPRESSION_H_
diff --git a/src/tint/ast/extension.cc b/src/tint/ast/extension.cc
index 4283df1..e57b248 100644
--- a/src/tint/ast/extension.cc
+++ b/src/tint/ast/extension.cc
@@ -37,6 +37,9 @@
     if (str == "chromium_disable_uniformity_analysis") {
         return Extension::kChromiumDisableUniformityAnalysis;
     }
+    if (str == "chromium_experimental_push_constant") {
+        return Extension::kChromiumExperimentalPushConstant;
+    }
     return Extension::kInvalid;
 }
 
@@ -50,6 +53,8 @@
             return out << "chromium_experimental_dp4a";
         case Extension::kChromiumDisableUniformityAnalysis:
             return out << "chromium_disable_uniformity_analysis";
+        case Extension::kChromiumExperimentalPushConstant:
+            return out << "chromium_experimental_push_constant";
     }
     return out << "<unknown>";
 }
diff --git a/src/tint/ast/extension.h b/src/tint/ast/extension.h
index 32bf507..ce3d48a 100644
--- a/src/tint/ast/extension.h
+++ b/src/tint/ast/extension.h
@@ -36,6 +36,7 @@
     kF16,
     kChromiumExperimentalDp4A,
     kChromiumDisableUniformityAnalysis,
+    kChromiumExperimentalPushConstant,
 };
 
 /// @param out the std::ostream to write to
diff --git a/src/tint/ast/extension_bench.cc b/src/tint/ast/extension_bench.cc
index 47787e3..8fc9d1c 100644
--- a/src/tint/ast/extension_bench.cc
+++ b/src/tint/ast/extension_bench.cc
@@ -52,6 +52,13 @@
         "chromiuE_disable_uniformity_analysis",
         "chromium_disable_uniTTormity_aPPalsis",
         "ddhromium_disabexxuniformity_analysis",
+        "c44romium_experimental_push_constant",
+        "chromium_experimental_pSSsVV_constant",
+        "chrom22Rm_experimental_pushRonstant",
+        "chromium_experimental_push_constant",
+        "chromium_exp9rimFntal_ush_constant",
+        "chrmium_experimental_push_constant",
+        "cOOromium_experiVeHtal_puh_conRRtant",
     };
     for (auto _ : state) {
         for (auto& str : kStrings) {
diff --git a/src/tint/ast/extension_test.cc b/src/tint/ast/extension_test.cc
index 8c75613..283088d 100644
--- a/src/tint/ast/extension_test.cc
+++ b/src/tint/ast/extension_test.cc
@@ -45,6 +45,7 @@
     {"f16", Extension::kF16},
     {"chromium_experimental_dp4a", Extension::kChromiumExperimentalDp4A},
     {"chromium_disable_uniformity_analysis", Extension::kChromiumDisableUniformityAnalysis},
+    {"chromium_experimental_push_constant", Extension::kChromiumExperimentalPushConstant},
 };
 
 static constexpr Case kInvalidCases[] = {
@@ -57,6 +58,9 @@
     {"chromiumppdisableqquniformity_aalysHHs", Extension::kInvalid},
     {"chromiu_disable_unifovmitc_analyi", Extension::kInvalid},
     {"chromium_diable_uGbformity_analysis", Extension::kInvalid},
+    {"chvomium_experimental_push_constiint", Extension::kInvalid},
+    {"chromiu8WWexperimental_push_constant", Extension::kInvalid},
+    {"chromium_experiMental_push_costanxx", Extension::kInvalid},
 };
 
 using ExtensionParseTest = testing::TestWithParam<Case>;
diff --git a/src/tint/ast/function.cc b/src/tint/ast/function.cc
index 49bfb9b..1cb0339 100644
--- a/src/tint/ast/function.cc
+++ b/src/tint/ast/function.cc
@@ -26,11 +26,11 @@
                    NodeID nid,
                    const Source& src,
                    Symbol sym,
-                   ParameterList parameters,
+                   utils::VectorRef<const Parameter*> parameters,
                    const Type* return_ty,
                    const BlockStatement* b,
-                   AttributeList attrs,
-                   AttributeList return_type_attrs)
+                   utils::VectorRef<const Attribute*> attrs,
+                   utils::VectorRef<const Attribute*> return_type_attrs)
     : Base(pid, nid, src),
       symbol(sym),
       params(std::move(parameters)),
diff --git a/src/tint/ast/function.h b/src/tint/ast/function.h
index ed7f5b2..61771bb 100644
--- a/src/tint/ast/function.h
+++ b/src/tint/ast/function.h
@@ -48,11 +48,11 @@
              NodeID nid,
              const Source& source,
              Symbol symbol,
-             ParameterList params,
+             utils::VectorRef<const Parameter*> params,
              const Type* return_type,
              const BlockStatement* body,
-             AttributeList attributes,
-             AttributeList return_type_attributes);
+             utils::VectorRef<const Attribute*> attributes,
+             utils::VectorRef<const Attribute*> return_type_attributes);
     /// Move constructor
     Function(Function&&);
 
@@ -74,7 +74,7 @@
     const Symbol symbol;
 
     /// The function params
-    const ParameterList params;
+    const utils::Vector<const Parameter*, 8> params;
 
     /// The function return type
     const Type* const return_type;
@@ -83,18 +83,18 @@
     const BlockStatement* const body;
 
     /// The attributes attached to this function
-    const AttributeList attributes;
+    const utils::Vector<const Attribute*, 2> attributes;
 
     /// The attributes attached to the function return type.
-    const AttributeList return_type_attributes;
+    const utils::Vector<const Attribute*, 2> return_type_attributes;
 };
 
 /// A list of functions
-class FunctionList : public std::vector<const Function*> {
+class FunctionList : public utils::Vector<const Function*, 8> {
   public:
     /// Appends f to the end of the list
     /// @param f the function to append to this list
-    void Add(const Function* f) { this->emplace_back(f); }
+    void Add(const Function* f) { this->Push(f); }
 
     /// Returns the function with the given name
     /// @param sym the function symbol to search for
diff --git a/src/tint/ast/function_test.cc b/src/tint/ast/function_test.cc
index f1de639..b95af26 100644
--- a/src/tint/ast/function_test.cc
+++ b/src/tint/ast/function_test.cc
@@ -26,20 +26,20 @@
 using FunctionTest = TestHelper;
 
 TEST_F(FunctionTest, Creation) {
-    ParameterList params{Param("var", ty.i32())};
+    utils::Vector params{Param("var", ty.i32())};
     auto* var = params[0];
 
-    auto* f = Func("func", params, ty.void_(), {});
+    auto* f = Func("func", params, ty.void_(), utils::Empty);
     EXPECT_EQ(f->symbol, Symbols().Get("func"));
-    ASSERT_EQ(f->params.size(), 1u);
+    ASSERT_EQ(f->params.Length(), 1u);
     EXPECT_TRUE(f->return_type->Is<ast::Void>());
     EXPECT_EQ(f->params[0], var);
 }
 
 TEST_F(FunctionTest, Creation_WithSource) {
-    ParameterList params{Param("var", ty.i32())};
+    utils::Vector params{Param("var", ty.i32())};
 
-    auto* f = Func(Source{Source::Location{20, 2}}, "func", params, ty.void_(), {});
+    auto* f = Func(Source{Source::Location{20, 2}}, "func", params, ty.void_(), utils::Empty);
     auto src = f->source;
     EXPECT_EQ(src.range.begin.line, 20u);
     EXPECT_EQ(src.range.begin.column, 2u);
@@ -49,7 +49,7 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.Func("", {}, b.ty.void_(), {});
+            b.Func("", utils::Empty, b.ty.void_(), utils::Empty);
         },
         "internal compiler error");
 }
@@ -58,20 +58,20 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.Func("f", {}, nullptr, {});
+            b.Func("f", utils::Empty, nullptr, utils::Empty);
         },
         "internal compiler error");
 }
 
 TEST_F(FunctionTest, Assert_Null_Param) {
+    using ParamList = utils::Vector<const ast::Parameter*, 2>;
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            ParameterList params;
-            params.push_back(b.Param("var", b.ty.i32()));
-            params.push_back(nullptr);
-
-            b.Func("f", params, b.ty.void_(), {});
+            ParamList params;
+            params.Push(b.Param("var", b.ty.i32()));
+            params.Push(nullptr);
+            b.Func("f", params, b.ty.void_(), utils::Empty);
         },
         "internal compiler error");
 }
@@ -81,7 +81,7 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.Func(b2.Sym("func"), {}, b1.ty.void_(), {});
+            b1.Func(b2.Sym("func"), utils::Empty, b1.ty.void_(), utils::Empty);
         },
         "internal compiler error");
 }
@@ -91,7 +91,11 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.Func("func", {b2.Param("var", b2.ty.i32())}, b1.ty.void_(), {});
+            b1.Func("func",
+                    utils::Vector{
+                        b2.Param("var", b2.ty.i32()),
+                    },
+                    b1.ty.void_(), utils::Empty);
         },
         "internal compiler error");
 }
@@ -101,8 +105,8 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.Func("func", {}, b1.ty.void_(), {},
-                    {
+            b1.Func("func", utils::Empty, b1.ty.void_(), utils::Empty,
+                    utils::Vector{
                         b2.WorkgroupSize(2_i, 4_i, 6_i),
                     });
         },
@@ -114,8 +118,8 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.Func("func", {}, b1.ty.void_(), {}, {},
-                    {
+            b1.Func("func", utils::Empty, b1.ty.void_(), utils::Empty, utils::Empty,
+                    utils::Vector{
                         b2.WorkgroupSize(2_i, 4_i, 6_i),
                     });
         },
@@ -125,7 +129,7 @@
 using FunctionListTest = TestHelper;
 
 TEST_F(FunctionListTest, FindSymbol) {
-    auto* func = Func("main", {}, ty.f32(), {});
+    auto* func = Func("main", utils::Empty, ty.f32(), utils::Empty);
     FunctionList list;
     list.Add(func);
     EXPECT_EQ(func, list.Find(Symbols().Register("main")));
@@ -137,12 +141,12 @@
 }
 
 TEST_F(FunctionListTest, FindSymbolStage) {
-    auto* fs = Func("main", {}, ty.f32(), {},
-                    {
+    auto* fs = Func("main", utils::Empty, ty.f32(), utils::Empty,
+                    utils::Vector{
                         Stage(PipelineStage::kFragment),
                     });
-    auto* vs = Func("main", {}, ty.f32(), {},
-                    {
+    auto* vs = Func("main", utils::Empty, ty.f32(), utils::Empty,
+                    utils::Vector{
                         Stage(PipelineStage::kVertex),
                     });
     FunctionList list;
@@ -154,8 +158,8 @@
 
 TEST_F(FunctionListTest, FindSymbolStageMissing) {
     FunctionList list;
-    list.Add(Func("main", {}, ty.f32(), {},
-                  {
+    list.Add(Func("main", utils::Empty, ty.f32(), utils::Empty,
+                  utils::Vector{
                       Stage(PipelineStage::kFragment),
                   }));
     EXPECT_EQ(nullptr, list.Find(Symbols().Register("main"), PipelineStage::kVertex));
@@ -163,8 +167,8 @@
 
 TEST_F(FunctionListTest, HasStage) {
     FunctionList list;
-    list.Add(Func("main", {}, ty.f32(), {},
-                  {
+    list.Add(Func("main", utils::Empty, ty.f32(), utils::Empty,
+                  utils::Vector{
                       Stage(PipelineStage::kFragment),
                   }));
     EXPECT_TRUE(list.HasStage(PipelineStage::kFragment));
diff --git a/src/tint/ast/let.cc b/src/tint/ast/let.cc
index b42e6e6..0248234 100644
--- a/src/tint/ast/let.cc
+++ b/src/tint/ast/let.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/ast/let.h"
 
+#include <utility>
+
 #include "src/tint/program_builder.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::Let);
@@ -26,8 +28,8 @@
          const Symbol& sym,
          const ast::Type* ty,
          const Expression* ctor,
-         AttributeList attrs)
-    : Base(pid, nid, src, sym, ty, ctor, attrs) {
+         utils::VectorRef<const Attribute*> attrs)
+    : Base(pid, nid, src, sym, ty, ctor, std::move(attrs)) {
     TINT_ASSERT(AST, ctor != nullptr);
 }
 
@@ -45,7 +47,7 @@
     auto* ty = ctx->Clone(type);
     auto* ctor = ctx->Clone(constructor);
     auto attrs = ctx->Clone(attributes);
-    return ctx->dst->create<Let>(src, sym, ty, ctor, attrs);
+    return ctx->dst->create<Let>(src, sym, ty, ctor, std::move(attrs));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/let.h b/src/tint/ast/let.h
index 0f71da2..fa0c8be 100644
--- a/src/tint/ast/let.h
+++ b/src/tint/ast/let.h
@@ -43,7 +43,7 @@
         const Symbol& sym,
         const ast::Type* type,
         const Expression* constructor,
-        AttributeList attributes);
+        utils::VectorRef<const Attribute*> attributes);
 
     /// Move constructor
     Let(Let&&);
diff --git a/src/tint/ast/loop_statement_test.cc b/src/tint/ast/loop_statement_test.cc
index c28665b..caa995d 100644
--- a/src/tint/ast/loop_statement_test.cc
+++ b/src/tint/ast/loop_statement_test.cc
@@ -31,9 +31,9 @@
     auto* continuing = Block(create<DiscardStatement>());
 
     auto* l = create<LoopStatement>(body, continuing);
-    ASSERT_EQ(l->body->statements.size(), 1u);
+    ASSERT_EQ(l->body->statements.Length(), 1u);
     EXPECT_EQ(l->body->statements[0], b);
-    ASSERT_EQ(l->continuing->statements.size(), 1u);
+    ASSERT_EQ(l->continuing->statements.Length(), 1u);
     EXPECT_EQ(l->continuing->statements[0], continuing->Last());
 }
 
diff --git a/src/tint/ast/module.cc b/src/tint/ast/module.cc
index 7cf9282..3faab6c 100644
--- a/src/tint/ast/module.cc
+++ b/src/tint/ast/module.cc
@@ -28,7 +28,7 @@
 Module::Module(ProgramID pid,
                NodeID nid,
                const Source& src,
-               std::vector<const ast::Node*> global_decls)
+               utils::VectorRef<const ast::Node*> global_decls)
     : Base(pid, nid, src), global_declarations_(std::move(global_decls)) {
     for (auto* decl : global_declarations_) {
         if (decl == nullptr) {
@@ -53,7 +53,7 @@
 void Module::AddGlobalDeclaration(const tint::ast::Node* decl) {
     diag::List diags;
     BinGlobalDeclaration(decl, diags);
-    global_declarations_.emplace_back(decl);
+    global_declarations_.Push(decl);
 }
 
 void Module::BinGlobalDeclaration(const tint::ast::Node* decl, diag::List& diags) {
@@ -61,19 +61,23 @@
         decl,  //
         [&](const ast::TypeDecl* type) {
             TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, type, program_id);
-            type_decls_.push_back(type);
+            type_decls_.Push(type);
         },
         [&](const Function* func) {
             TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, func, program_id);
-            functions_.push_back(func);
+            functions_.Push(func);
         },
         [&](const Variable* var) {
             TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, var, program_id);
-            global_variables_.push_back(var);
+            global_variables_.Push(var);
         },
         [&](const Enable* enable) {
             TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, enable, program_id);
-            enables_.push_back(enable);
+            enables_.Push(enable);
+        },
+        [&](const StaticAssert* assertion) {
+            TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, assertion, program_id);
+            static_asserts_.Push(assertion);
         },
         [&](Default) { TINT_ICE(AST, diags) << "Unknown global declaration type"; });
 }
@@ -81,29 +85,36 @@
 void Module::AddEnable(const ast::Enable* enable) {
     TINT_ASSERT(AST, enable);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, enable, program_id);
-    global_declarations_.push_back(enable);
-    enables_.push_back(enable);
+    global_declarations_.Push(enable);
+    enables_.Push(enable);
 }
 
 void Module::AddGlobalVariable(const ast::Variable* var) {
     TINT_ASSERT(AST, var);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, var, program_id);
-    global_variables_.push_back(var);
-    global_declarations_.push_back(var);
+    global_variables_.Push(var);
+    global_declarations_.Push(var);
+}
+
+void Module::AddStaticAssert(const StaticAssert* assertion) {
+    TINT_ASSERT(AST, assertion);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, assertion, program_id);
+    static_asserts_.Push(assertion);
+    global_declarations_.Push(assertion);
 }
 
 void Module::AddTypeDecl(const ast::TypeDecl* type) {
     TINT_ASSERT(AST, type);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, type, program_id);
-    type_decls_.push_back(type);
-    global_declarations_.push_back(type);
+    type_decls_.Push(type);
+    global_declarations_.Push(type);
 }
 
 void Module::AddFunction(const ast::Function* func) {
     TINT_ASSERT(AST, func);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, func, program_id);
-    functions_.push_back(func);
-    global_declarations_.push_back(func);
+    functions_.Push(func);
+    global_declarations_.Push(func);
 }
 
 const Module* Module::Clone(CloneContext* ctx) const {
@@ -117,10 +128,10 @@
 
     // During the clone, declarations may have been placed into the module.
     // Clear everything out, as we're about to re-bin the declarations.
-    type_decls_.clear();
-    functions_.clear();
-    global_variables_.clear();
-    enables_.clear();
+    type_decls_.Clear();
+    functions_.Clear();
+    global_variables_.Clear();
+    enables_.Clear();
 
     for (auto* decl : global_declarations_) {
         if (!decl) {
diff --git a/src/tint/ast/module.h b/src/tint/ast/module.h
index 27c62dc..2818cb8 100644
--- a/src/tint/ast/module.h
+++ b/src/tint/ast/module.h
@@ -16,11 +16,12 @@
 #define SRC_TINT_AST_MODULE_H_
 
 #include <string>
-#include <vector>
 
 #include "src/tint/ast/enable.h"
 #include "src/tint/ast/function.h"
+#include "src/tint/ast/static_assert.h"
 #include "src/tint/ast/type.h"
+#include "src/tint/utils/vector.h"
 
 namespace tint::ast {
 
@@ -42,19 +43,18 @@
     /// @param src the source of this node
     /// @param global_decls the list of global types, functions, and variables, in
     /// the order they were declared in the source program
-    Module(ProgramID pid, NodeID nid, const Source& src, std::vector<const Node*> global_decls);
+    Module(ProgramID pid,
+           NodeID nid,
+           const Source& src,
+           utils::VectorRef<const ast::Node*> global_decls);
 
     /// Destructor
     ~Module() override;
 
     /// @returns the declaration-ordered global declarations for the module
-    const std::vector<const Node*>& GlobalDeclarations() const { return global_declarations_; }
+    const auto& GlobalDeclarations() const { return global_declarations_; }
 
-    /// Add a enable directive to the Builder
-    /// @param ext the enable directive to add
-    void AddEnable(const Enable* ext);
-
-    /// Add a global variable to the Builder
+    /// Add a global variable to the module
     /// @param var the variable to add
     void AddGlobalVariable(const Variable* var);
 
@@ -69,33 +69,44 @@
         return false;
     }
 
-    /// Adds a global declaration to the Builder.
+    /// Adds a global declaration to the module.
     /// @param decl the declaration to add
     void AddGlobalDeclaration(const tint::ast::Node* decl);
 
     /// @returns the global variables for the module
-    const VariableList& GlobalVariables() const { return global_variables_; }
+    const auto& GlobalVariables() const { return global_variables_; }
 
     /// @returns the global variables for the module
-    VariableList& GlobalVariables() { return global_variables_; }
+    auto& GlobalVariables() { return global_variables_; }
 
     /// @returns the global variable declarations of kind 'T' for the module
     template <typename T, typename = traits::EnableIfIsType<T, ast::Variable>>
-    std::vector<const T*> Globals() const {
-        std::vector<const T*> out;
-        out.reserve(global_variables_.size());
+    auto Globals() const {
+        utils::Vector<const T*, 32> out;
+        out.Reserve(global_variables_.Length());
         for (auto* global : global_variables_) {
             if (auto* var = global->As<T>()) {
-                out.emplace_back(var);
+                out.Push(var);
             }
         }
         return out;
     }
 
-    /// @returns the extension set for the module
-    const EnableList& Enables() const { return enables_; }
+    /// Add a enable directive to the module
+    /// @param ext the enable directive to add
+    void AddEnable(const Enable* ext);
 
-    /// Adds a type declaration to the Builder.
+    /// @returns the extension set for the module
+    const auto& Enables() const { return enables_; }
+
+    /// Add a global static assertion to the module
+    /// @param assertion the static assert to add
+    void AddStaticAssert(const StaticAssert* assertion);
+
+    /// @returns the list of global static assertions
+    const auto& StaticAsserts() const { return static_asserts_; }
+
+    /// Adds a type declaration to the module
     /// @param decl the type declaration to add
     void AddTypeDecl(const TypeDecl* decl);
 
@@ -104,9 +115,9 @@
     const TypeDecl* LookupType(Symbol name) const;
 
     /// @returns the declared types in the module
-    const std::vector<const TypeDecl*>& TypeDecls() const { return type_decls_; }
+    const auto& TypeDecls() const { return type_decls_; }
 
-    /// Add a function to the Builder
+    /// Add a function to the module
     /// @param func the function to add
     void AddFunction(const Function* func);
 
@@ -131,11 +142,12 @@
     /// * #functions_
     void BinGlobalDeclaration(const tint::ast::Node* decl, diag::List& diags);
 
-    std::vector<const Node*> global_declarations_;
-    std::vector<const TypeDecl*> type_decls_;
+    utils::Vector<const Node*, 64> global_declarations_;
+    utils::Vector<const TypeDecl*, 16> type_decls_;
     FunctionList functions_;
-    VariableList global_variables_;
-    EnableList enables_;
+    utils::Vector<const Variable*, 32> global_variables_;
+    utils::Vector<const Enable*, 8> enables_;
+    utils::Vector<const StaticAssert*, 8> static_asserts_;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/module_test.cc b/src/tint/ast/module_test.cc
index 7096769..3093a16 100644
--- a/src/tint/ast/module_test.cc
+++ b/src/tint/ast/module_test.cc
@@ -22,7 +22,7 @@
 using ModuleTest = TestHelper;
 
 TEST_F(ModuleTest, Creation) {
-    EXPECT_EQ(Program(std::move(*this)).AST().Functions().size(), 0u);
+    EXPECT_EQ(Program(std::move(*this)).AST().Functions().Length(), 0u);
 }
 
 TEST_F(ModuleTest, LookupFunction) {
@@ -61,8 +61,8 @@
             ProgramBuilder b1;
             ProgramBuilder b2;
             b1.AST().AddFunction(b2.create<ast::Function>(b2.Symbols().Register("func"),
-                                                          ParameterList{}, b2.ty.f32(), b2.Block(),
-                                                          AttributeList{}, AttributeList{}));
+                                                          utils::Empty, b2.ty.f32(), b2.Block(),
+                                                          utils::Empty, utils::Empty));
         },
         "internal compiler error");
 }
@@ -116,7 +116,7 @@
     ctx.Clone();
 
     auto& decls = cloned.AST().GlobalDeclarations();
-    ASSERT_EQ(decls.size(), 6u);
+    ASSERT_EQ(decls.Length(), 6u);
     EXPECT_TRUE(decls[1]->Is<ast::Function>());
     EXPECT_TRUE(decls[3]->Is<ast::Alias>());
     EXPECT_TRUE(decls[5]->Is<ast::Variable>());
diff --git a/src/tint/ast/override.cc b/src/tint/ast/override.cc
index 2876920..3a0a3a9 100644
--- a/src/tint/ast/override.cc
+++ b/src/tint/ast/override.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/ast/override.h"
 
+#include <utility>
+
 #include "src/tint/program_builder.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::Override);
@@ -26,8 +28,8 @@
                    const Symbol& sym,
                    const ast::Type* ty,
                    const Expression* ctor,
-                   AttributeList attrs)
-    : Base(pid, nid, src, sym, ty, ctor, attrs) {}
+                   utils::VectorRef<const Attribute*> attrs)
+    : Base(pid, nid, src, sym, ty, ctor, std::move(attrs)) {}
 
 Override::Override(Override&&) = default;
 
@@ -43,7 +45,7 @@
     auto* ty = ctx->Clone(type);
     auto* ctor = ctx->Clone(constructor);
     auto attrs = ctx->Clone(attributes);
-    return ctx->dst->create<Override>(src, sym, ty, ctor, attrs);
+    return ctx->dst->create<Override>(src, sym, ty, ctor, std::move(attrs));
 }
 
 std::string Override::Identifier(const SymbolTable& symbols) const {
diff --git a/src/tint/ast/override.h b/src/tint/ast/override.h
index 00c36fc..7d01d13 100644
--- a/src/tint/ast/override.h
+++ b/src/tint/ast/override.h
@@ -46,7 +46,7 @@
              const Symbol& sym,
              const ast::Type* type,
              const Expression* constructor,
-             AttributeList attributes);
+             utils::VectorRef<const Attribute*> attributes);
 
     /// Move constructor
     Override(Override&&);
diff --git a/src/tint/ast/override_test.cc b/src/tint/ast/override_test.cc
index b4179e5..9e7af83 100644
--- a/src/tint/ast/override_test.cc
+++ b/src/tint/ast/override_test.cc
@@ -27,7 +27,7 @@
 }
 
 TEST_F(OverrideTest, Identifier_WithId) {
-    auto* o = Override("o", nullptr, Expr(f32(1.0)), {Id(4u)});
+    auto* o = Override("o", nullptr, Expr(f32(1.0)), utils::Vector{Id(4u)});
     EXPECT_EQ(std::string("4"), o->Identifier(Symbols()));
 }
 
diff --git a/src/tint/ast/parameter.cc b/src/tint/ast/parameter.cc
index bab2729..01ee3aa 100644
--- a/src/tint/ast/parameter.cc
+++ b/src/tint/ast/parameter.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/ast/parameter.h"
 
+#include <utility>
+
 #include "src/tint/program_builder.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::Parameter);
@@ -25,8 +27,8 @@
                      const Source& src,
                      const Symbol& sym,
                      const ast::Type* ty,
-                     AttributeList attrs)
-    : Base(pid, nid, src, sym, ty, nullptr, attrs) {}
+                     utils::VectorRef<const Attribute*> attrs)
+    : Base(pid, nid, src, sym, ty, nullptr, std::move(attrs)) {}
 
 Parameter::Parameter(Parameter&&) = default;
 
@@ -41,7 +43,7 @@
     auto sym = ctx->Clone(symbol);
     auto* ty = ctx->Clone(type);
     auto attrs = ctx->Clone(attributes);
-    return ctx->dst->create<Parameter>(src, sym, ty, attrs);
+    return ctx->dst->create<Parameter>(src, sym, ty, std::move(attrs));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/parameter.h b/src/tint/ast/parameter.h
index 4a9bb93..e9d9f62 100644
--- a/src/tint/ast/parameter.h
+++ b/src/tint/ast/parameter.h
@@ -45,7 +45,7 @@
               const Source& source,
               const Symbol& sym,
               const ast::Type* type,
-              AttributeList attributes);
+              utils::VectorRef<const Attribute*> attributes);
 
     /// Move constructor
     Parameter(Parameter&&);
@@ -63,9 +63,6 @@
     const Parameter* Clone(CloneContext* ctx) const override;
 };
 
-/// A list of parameters
-using ParameterList = std::vector<const Parameter*>;
-
 }  // namespace tint::ast
 
 #endif  // SRC_TINT_AST_PARAMETER_H_
diff --git a/src/tint/ast/statement.h b/src/tint/ast/statement.h
index e3a96d1..616e348 100644
--- a/src/tint/ast/statement.h
+++ b/src/tint/ast/statement.h
@@ -39,9 +39,6 @@
     Statement(Statement&&);
 };
 
-/// A list of statements
-using StatementList = std::vector<const Statement*>;
-
 }  // namespace tint::ast
 
 #endif  // SRC_TINT_AST_STATEMENT_H_
diff --git a/src/tint/ast/static_assert.cc b/src/tint/ast/static_assert.cc
new file mode 100644
index 0000000..0609194
--- /dev/null
+++ b/src/tint/ast/static_assert.cc
@@ -0,0 +1,40 @@
+// 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/ast/static_assert.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::StaticAssert);
+
+namespace tint::ast {
+
+StaticAssert::StaticAssert(ProgramID pid, NodeID nid, const Source& src, const Expression* cond)
+    : Base(pid, nid, src), condition(cond) {
+    TINT_ASSERT(AST, cond);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, cond, program_id);
+}
+
+StaticAssert::StaticAssert(StaticAssert&&) = default;
+
+StaticAssert::~StaticAssert() = default;
+
+const StaticAssert* StaticAssert::Clone(CloneContext* ctx) const {
+    // Clone arguments outside of create() call to have deterministic ordering
+    auto src = ctx->Clone(source);
+    auto* cond = ctx->Clone(condition);
+    return ctx->dst->create<StaticAssert>(src, cond);
+}
+
+}  // namespace tint::ast
diff --git a/src/tint/ast/static_assert.h b/src/tint/ast/static_assert.h
new file mode 100644
index 0000000..f42ad07
--- /dev/null
+++ b/src/tint/ast/static_assert.h
@@ -0,0 +1,50 @@
+// 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.
+
+#ifndef SRC_TINT_AST_STATIC_ASSERT_H_
+#define SRC_TINT_AST_STATIC_ASSERT_H_
+
+#include "src/tint/ast/statement.h"
+#include "src/tint/ast/variable.h"
+
+namespace tint::ast {
+
+/// A `static_assert` statement
+class StaticAssert final : public Castable<StaticAssert, Statement> {
+  public:
+    /// Constructor
+    /// @param pid the identifier of the program that owns this node
+    /// @param nid the unique node identifier
+    /// @param source the variable statement source
+    /// @param condition the assertion condition
+    StaticAssert(ProgramID pid, NodeID nid, const Source& source, const Expression* condition);
+
+    /// Move constructor
+    StaticAssert(StaticAssert&&);
+
+    /// Destructor
+    ~StaticAssert() override;
+
+    /// Clones this node and all transitive child nodes using the `CloneContext` `ctx`.
+    /// @param ctx the clone context
+    /// @return the newly cloned node
+    const StaticAssert* Clone(CloneContext* ctx) const override;
+
+    /// The assertion condition
+    const Expression* const condition;
+};
+
+}  // namespace tint::ast
+
+#endif  // SRC_TINT_AST_STATIC_ASSERT_H_
diff --git a/src/tint/ast/static_assert_test.cc b/src/tint/ast/static_assert_test.cc
new file mode 100644
index 0000000..48bee48
--- /dev/null
+++ b/src/tint/ast/static_assert_test.cc
@@ -0,0 +1,66 @@
+// 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/ast/static_assert.h"
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint::ast {
+namespace {
+
+using StaticAssertTest = TestHelper;
+
+TEST_F(StaticAssertTest, Creation) {
+    auto* cond = Expr(true);
+    auto* stmt = StaticAssert(cond);
+    EXPECT_EQ(stmt->condition, cond);
+}
+
+TEST_F(StaticAssertTest, Creation_WithSource) {
+    auto* cond = Expr(true);
+    auto* stmt = StaticAssert(Source{{20, 2}}, cond);
+    auto src = stmt->source;
+    EXPECT_EQ(src.range.begin.line, 20u);
+    EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(StaticAssertTest, IsStaticAssert) {
+    auto* cond = Expr(true);
+
+    auto* stmt = StaticAssert(cond);
+    EXPECT_TRUE(stmt->Is<ast::StaticAssert>());
+}
+
+TEST_F(StaticAssertTest, Assert_Null_Condition) {
+    EXPECT_FATAL_FAILURE(
+        {
+            ProgramBuilder b;
+            b.StaticAssert(nullptr);
+        },
+        "internal compiler error");
+}
+
+TEST_F(StaticAssertTest, Assert_DifferentProgramID_Condition) {
+    EXPECT_FATAL_FAILURE(
+        {
+            ProgramBuilder b1;
+            ProgramBuilder b2;
+            b1.StaticAssert(b2.Expr(i32(123)));
+        },
+        "internal compiler error");
+}
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/ast/storage_class.cc b/src/tint/ast/storage_class.cc
index 303c04e..450067b 100644
--- a/src/tint/ast/storage_class.cc
+++ b/src/tint/ast/storage_class.cc
@@ -43,6 +43,9 @@
     if (str == "storage") {
         return StorageClass::kStorage;
     }
+    if (str == "push_constant") {
+        return StorageClass::kPushConstant;
+    }
     return StorageClass::kInvalid;
 }
 
@@ -62,6 +65,8 @@
             return out << "uniform";
         case StorageClass::kStorage:
             return out << "storage";
+        case StorageClass::kPushConstant:
+            return out << "push_constant";
         case StorageClass::kHandle:
             return out << "handle";
         case StorageClass::kIn:
diff --git a/src/tint/ast/storage_class.h b/src/tint/ast/storage_class.h
index 4da3db0..43e41e1 100644
--- a/src/tint/ast/storage_class.h
+++ b/src/tint/ast/storage_class.h
@@ -36,6 +36,7 @@
     kWorkgroup,
     kUniform,
     kStorage,
+    kPushConstant,
     kHandle,  // Tint-internal enum entry - not parsed
     kIn,      // Tint-internal enum entry - not parsed
     kOut,     // Tint-internal enum entry - not parsed
@@ -55,7 +56,8 @@
 /// @param sc the StorageClass
 /// @see https://gpuweb.github.io/gpuweb/wgsl.html#host-shareable
 inline bool IsHostShareable(StorageClass sc) {
-    return sc == ast::StorageClass::kUniform || sc == ast::StorageClass::kStorage;
+    return sc == ast::StorageClass::kUniform || sc == ast::StorageClass::kStorage ||
+           sc == ast::StorageClass::kPushConstant;
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/storage_class.h.tmpl b/src/tint/ast/storage_class.h.tmpl
index d885c72..c21d6f6 100644
--- a/src/tint/ast/storage_class.h.tmpl
+++ b/src/tint/ast/storage_class.h.tmpl
@@ -28,7 +28,8 @@
 /// @param sc the StorageClass
 /// @see https://gpuweb.github.io/gpuweb/wgsl.html#host-shareable
 inline bool IsHostShareable(StorageClass sc) {
-    return sc == ast::StorageClass::kUniform || sc == ast::StorageClass::kStorage;
+    return sc == ast::StorageClass::kUniform || sc == ast::StorageClass::kStorage ||
+           sc == ast::StorageClass::kPushConstant;
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/storage_class_bench.cc b/src/tint/ast/storage_class_bench.cc
index d8e9ae9..00095c2 100644
--- a/src/tint/ast/storage_class_bench.cc
+++ b/src/tint/ast/storage_class_bench.cc
@@ -31,12 +31,48 @@
 
 void StorageClassParser(::benchmark::State& state) {
     std::array kStrings{
-        "fccnctin",   "ucti3",      "functVon",   "function", "1unction",  "unJtqqon",
-        "llun77tion", "ppqqivtHH",  "prcv",       "bivaGe",   "private",   "priviive",
-        "8WWivate",   "pxxvate",    "wXkgrggup",  "worXVup",  "3orkgroup", "workgroup",
-        "workgroEp",  "woTTPkroup", "ddorkroxxp", "u44iform", "unSSfoVVm", "RniR22m",
-        "uniform",    "uFfo9m",     "uniorm",     "VOORRHrm", "straye",    "llntrrr77ge",
-        "stor4g00",   "storage",    "trooe",      "zzrage",   "siioppa1",
+        "fccnctin",
+        "ucti3",
+        "functVon",
+        "function",
+        "1unction",
+        "unJtqqon",
+        "llun77tion",
+        "ppqqivtHH",
+        "prcv",
+        "bivaGe",
+        "private",
+        "priviive",
+        "8WWivate",
+        "pxxvate",
+        "wXkgrggup",
+        "worXVup",
+        "3orkgroup",
+        "workgroup",
+        "workgroEp",
+        "woTTPkroup",
+        "ddorkroxxp",
+        "u44iform",
+        "unSSfoVVm",
+        "RniR22m",
+        "uniform",
+        "uFfo9m",
+        "uniorm",
+        "VOORRHrm",
+        "straye",
+        "llntrrr77ge",
+        "stor4g00",
+        "storage",
+        "trooe",
+        "zzrage",
+        "siioppa1",
+        "puXXh_constant",
+        "pusII9_nn55nstant",
+        "YusHH_coaastSSrnt",
+        "push_constant",
+        "pushonkkHan",
+        "jush_consgRt",
+        "puh_cobsant",
     };
     for (auto _ : state) {
         for (auto& str : kStrings) {
diff --git a/src/tint/ast/storage_class_test.cc b/src/tint/ast/storage_class_test.cc
index 9688236..1d12d45 100644
--- a/src/tint/ast/storage_class_test.cc
+++ b/src/tint/ast/storage_class_test.cc
@@ -44,18 +44,19 @@
 static constexpr Case kValidCases[] = {
     {"function", StorageClass::kFunction},   {"private", StorageClass::kPrivate},
     {"workgroup", StorageClass::kWorkgroup}, {"uniform", StorageClass::kUniform},
-    {"storage", StorageClass::kStorage},
+    {"storage", StorageClass::kStorage},     {"push_constant", StorageClass::kPushConstant},
 };
 
 static constexpr Case kInvalidCases[] = {
-    {"fccnctin", StorageClass::kInvalid},    {"ucti3", StorageClass::kInvalid},
-    {"functVon", StorageClass::kInvalid},    {"priv1te", StorageClass::kInvalid},
-    {"pqiJate", StorageClass::kInvalid},     {"privat7ll", StorageClass::kInvalid},
-    {"workroppqHH", StorageClass::kInvalid}, {"workru", StorageClass::kInvalid},
-    {"wbkgGoup", StorageClass::kInvalid},    {"unifiivm", StorageClass::kInvalid},
-    {"8WWiform", StorageClass::kInvalid},    {"uxxform", StorageClass::kInvalid},
-    {"sXraggg", StorageClass::kInvalid},     {"traXe", StorageClass::kInvalid},
-    {"stor3ge", StorageClass::kInvalid},
+    {"fccnctin", StorageClass::kInvalid},       {"ucti3", StorageClass::kInvalid},
+    {"functVon", StorageClass::kInvalid},       {"priv1te", StorageClass::kInvalid},
+    {"pqiJate", StorageClass::kInvalid},        {"privat7ll", StorageClass::kInvalid},
+    {"workroppqHH", StorageClass::kInvalid},    {"workru", StorageClass::kInvalid},
+    {"wbkgGoup", StorageClass::kInvalid},       {"unifiivm", StorageClass::kInvalid},
+    {"8WWiform", StorageClass::kInvalid},       {"uxxform", StorageClass::kInvalid},
+    {"sXraggg", StorageClass::kInvalid},        {"traXe", StorageClass::kInvalid},
+    {"stor3ge", StorageClass::kInvalid},        {"push_constanE", StorageClass::kInvalid},
+    {"push_TTPnstant", StorageClass::kInvalid}, {"puxxdh_constan", StorageClass::kInvalid},
 };
 
 using StorageClassParseTest = testing::TestWithParam<Case>;
diff --git a/src/tint/ast/struct.cc b/src/tint/ast/struct.cc
index 326718a..c2d9002 100644
--- a/src/tint/ast/struct.cc
+++ b/src/tint/ast/struct.cc
@@ -26,8 +26,8 @@
                NodeID nid,
                const Source& src,
                Symbol n,
-               StructMemberList m,
-               AttributeList attrs)
+               utils::VectorRef<const ast::StructMember*> m,
+               utils::VectorRef<const ast::Attribute*> attrs)
     : Base(pid, nid, src, n), members(std::move(m)), attributes(std::move(attrs)) {
     for (auto* mem : members) {
         TINT_ASSERT(AST, mem);
@@ -49,7 +49,7 @@
     auto n = ctx->Clone(name);
     auto mem = ctx->Clone(members);
     auto attrs = ctx->Clone(attributes);
-    return ctx->dst->create<Struct>(src, n, mem, attrs);
+    return ctx->dst->create<Struct>(src, n, std::move(mem), std::move(attrs));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/struct.h b/src/tint/ast/struct.h
index 5d55b8c..3e3e5cf 100644
--- a/src/tint/ast/struct.h
+++ b/src/tint/ast/struct.h
@@ -21,6 +21,7 @@
 #include "src/tint/ast/attribute.h"
 #include "src/tint/ast/struct_member.h"
 #include "src/tint/ast/type_decl.h"
+#include "src/tint/utils/vector.h"
 
 namespace tint::ast {
 
@@ -38,8 +39,8 @@
            NodeID nid,
            const Source& src,
            Symbol name,
-           StructMemberList members,
-           AttributeList attributes);
+           utils::VectorRef<const ast::StructMember*> members,
+           utils::VectorRef<const ast::Attribute*> attributes);
     /// Move constructor
     Struct(Struct&&);
 
@@ -52,10 +53,10 @@
     const Struct* Clone(CloneContext* ctx) const override;
 
     /// The members
-    const StructMemberList members;
+    const utils::Vector<const ast::StructMember*, 8> members;
 
     /// The struct attributes
-    const AttributeList attributes;
+    const utils::Vector<const ast::Attribute*, 4> attributes;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/struct_member.cc b/src/tint/ast/struct_member.cc
index 72acd33..22de044 100644
--- a/src/tint/ast/struct_member.cc
+++ b/src/tint/ast/struct_member.cc
@@ -25,7 +25,7 @@
                            const Source& src,
                            const Symbol& sym,
                            const ast::Type* ty,
-                           AttributeList attrs)
+                           utils::VectorRef<const Attribute*> attrs)
     : Base(pid, nid, src), symbol(sym), type(ty), attributes(std::move(attrs)) {
     TINT_ASSERT(AST, type);
     TINT_ASSERT(AST, symbol.IsValid());
@@ -46,7 +46,7 @@
     auto sym = ctx->Clone(symbol);
     auto* ty = ctx->Clone(type);
     auto attrs = ctx->Clone(attributes);
-    return ctx->dst->create<StructMember>(src, sym, ty, attrs);
+    return ctx->dst->create<StructMember>(src, sym, ty, std::move(attrs));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/struct_member.h b/src/tint/ast/struct_member.h
index 39c8532..fc616d7 100644
--- a/src/tint/ast/struct_member.h
+++ b/src/tint/ast/struct_member.h
@@ -16,7 +16,6 @@
 #define SRC_TINT_AST_STRUCT_MEMBER_H_
 
 #include <utility>
-#include <vector>
 
 #include "src/tint/ast/attribute.h"
 
@@ -42,7 +41,7 @@
                  const Source& src,
                  const Symbol& sym,
                  const ast::Type* type,
-                 AttributeList attributes);
+                 utils::VectorRef<const Attribute*> attributes);
     /// Move constructor
     StructMember(StructMember&&);
 
@@ -61,12 +60,9 @@
     const ast::Type* const type;
 
     /// The attributes
-    const AttributeList attributes;
+    const utils::Vector<const Attribute*, 4> attributes;
 };
 
-/// A list of struct members
-using StructMemberList = std::vector<const StructMember*>;
-
 }  // namespace tint::ast
 
 #endif  // SRC_TINT_AST_STRUCT_MEMBER_H_
diff --git a/src/tint/ast/struct_member_test.cc b/src/tint/ast/struct_member_test.cc
index cec82ae..69772f4 100644
--- a/src/tint/ast/struct_member_test.cc
+++ b/src/tint/ast/struct_member_test.cc
@@ -21,10 +21,10 @@
 using StructMemberTest = TestHelper;
 
 TEST_F(StructMemberTest, Creation) {
-    auto* st = Member("a", ty.i32(), {MemberSize(4)});
+    auto* st = Member("a", ty.i32(), utils::Vector{MemberSize(4)});
     EXPECT_EQ(st->symbol, Symbol(1, ID()));
     EXPECT_TRUE(st->type->Is<ast::I32>());
-    EXPECT_EQ(st->attributes.size(), 1u);
+    EXPECT_EQ(st->attributes.Length(), 1u);
     EXPECT_TRUE(st->attributes[0]->Is<StructMemberSizeAttribute>());
     EXPECT_EQ(st->source.range.begin.line, 0u);
     EXPECT_EQ(st->source.range.begin.column, 0u);
@@ -37,7 +37,7 @@
                       ty.i32());
     EXPECT_EQ(st->symbol, Symbol(1, ID()));
     EXPECT_TRUE(st->type->Is<ast::I32>());
-    EXPECT_EQ(st->attributes.size(), 0u);
+    EXPECT_EQ(st->attributes.Length(), 0u);
     EXPECT_EQ(st->source.range.begin.line, 27u);
     EXPECT_EQ(st->source.range.begin.column, 4u);
     EXPECT_EQ(st->source.range.end.line, 27u);
@@ -66,7 +66,7 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.Member("a", b.ty.i32(), {b.MemberSize(4), nullptr});
+            b.Member("a", b.ty.i32(), utils::Vector{b.MemberSize(4), nullptr});
         },
         "internal compiler error");
 }
@@ -76,7 +76,7 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.Member(b2.Sym("a"), b1.ty.i32(), {b1.MemberSize(4)});
+            b1.Member(b2.Sym("a"), b1.ty.i32(), utils::Vector{b1.MemberSize(4)});
         },
         "internal compiler error");
 }
@@ -86,7 +86,7 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.Member("a", b1.ty.i32(), {b2.MemberSize(4)});
+            b1.Member("a", b1.ty.i32(), utils::Vector{b2.MemberSize(4)});
         },
         "internal compiler error");
 }
diff --git a/src/tint/ast/struct_test.cc b/src/tint/ast/struct_test.cc
index a94ceb3..53ec9c8 100644
--- a/src/tint/ast/struct_test.cc
+++ b/src/tint/ast/struct_test.cc
@@ -36,10 +36,10 @@
 
 TEST_F(AstStructTest, Creation) {
     auto name = Sym("s");
-    auto* s = create<Struct>(name, StructMemberList{Member("a", ty.i32())}, AttributeList{});
+    auto* s = create<Struct>(name, utils::Vector{Member("a", ty.i32())}, utils::Empty);
     EXPECT_EQ(s->name, name);
-    EXPECT_EQ(s->members.size(), 1u);
-    EXPECT_TRUE(s->attributes.empty());
+    EXPECT_EQ(s->members.Length(), 1u);
+    EXPECT_TRUE(s->attributes.IsEmpty());
     EXPECT_EQ(s->source.range.begin.line, 0u);
     EXPECT_EQ(s->source.range.begin.column, 0u);
     EXPECT_EQ(s->source.range.end.line, 0u);
@@ -48,13 +48,14 @@
 
 TEST_F(AstStructTest, Creation_WithAttributes) {
     auto name = Sym("s");
-    AttributeList attrs;
-    attrs.push_back(ASTNodes().Create<SpirvBlockAttribute>(ID(), AllocateNodeID()));
 
-    auto* s = create<Struct>(name, StructMemberList{Member("a", ty.i32())}, attrs);
+    auto* s = create<Struct>(name, utils::Vector{Member("a", ty.i32())},
+                             utils::Vector{
+                                 ASTNodes().Create<SpirvBlockAttribute>(ID(), AllocateNodeID()),
+                             });
     EXPECT_EQ(s->name, name);
-    EXPECT_EQ(s->members.size(), 1u);
-    ASSERT_EQ(s->attributes.size(), 1u);
+    EXPECT_EQ(s->members.Length(), 1u);
+    ASSERT_EQ(s->attributes.Length(), 1u);
     EXPECT_TRUE(s->attributes[0]->Is<SpirvBlockAttribute>());
     EXPECT_EQ(s->source.range.begin.line, 0u);
     EXPECT_EQ(s->source.range.begin.column, 0u);
@@ -66,11 +67,11 @@
     auto name = Sym("s");
     auto* s = create<Struct>(
         Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 8}}}, name,
-        StructMemberList{Member("a", ty.i32())},
-        AttributeList{ASTNodes().Create<SpirvBlockAttribute>(ID(), AllocateNodeID())});
+        utils::Vector{Member("a", ty.i32())},
+        utils::Vector{ASTNodes().Create<SpirvBlockAttribute>(ID(), AllocateNodeID())});
     EXPECT_EQ(s->name, name);
-    EXPECT_EQ(s->members.size(), 1u);
-    ASSERT_EQ(s->attributes.size(), 1u);
+    EXPECT_EQ(s->members.Length(), 1u);
+    ASSERT_EQ(s->attributes.Length(), 1u);
     EXPECT_TRUE(s->attributes[0]->Is<SpirvBlockAttribute>());
     EXPECT_EQ(s->source.range.begin.line, 27u);
     EXPECT_EQ(s->source.range.begin.column, 4u);
@@ -82,8 +83,8 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.create<Struct>(b.Sym("S"), StructMemberList{b.Member("a", b.ty.i32()), nullptr},
-                             AttributeList{});
+            b.create<Struct>(b.Sym("S"), utils::Vector{b.Member("a", b.ty.i32()), nullptr},
+                             utils::Empty);
         },
         "internal compiler error");
 }
@@ -92,8 +93,8 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.create<Struct>(b.Sym("S"), StructMemberList{b.Member("a", b.ty.i32())},
-                             AttributeList{nullptr});
+            b.create<Struct>(b.Sym("S"), utils::Vector{b.Member("a", b.ty.i32())},
+                             utils::Vector<const ast::Attribute*, 1>{nullptr});
         },
         "internal compiler error");
 }
@@ -103,8 +104,8 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<Struct>(b1.Sym("S"), StructMemberList{b2.Member("a", b2.ty.i32())},
-                              AttributeList{});
+            b1.create<Struct>(b1.Sym("S"), utils::Vector{b2.Member("a", b2.ty.i32())},
+                              utils::Empty);
         },
         "internal compiler error");
 }
@@ -114,8 +115,8 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<Struct>(b1.Sym("S"), StructMemberList{b1.Member("a", b1.ty.i32())},
-                              AttributeList{b2.ASTNodes().Create<SpirvBlockAttribute>(
+            b1.create<Struct>(b1.Sym("S"), utils::Vector{b1.Member("a", b1.ty.i32())},
+                              utils::Vector{b2.ASTNodes().Create<SpirvBlockAttribute>(
                                   b2.ID(), b2.AllocateNodeID())});
         },
         "internal compiler error");
diff --git a/src/tint/ast/switch_statement.cc b/src/tint/ast/switch_statement.cc
index 7abf0c0..1cdf8a4 100644
--- a/src/tint/ast/switch_statement.cc
+++ b/src/tint/ast/switch_statement.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/ast/switch_statement.h"
 
+#include <utility>
+
 #include "src/tint/program_builder.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::SwitchStatement);
@@ -24,8 +26,8 @@
                                  NodeID nid,
                                  const Source& src,
                                  const Expression* cond,
-                                 CaseStatementList b)
-    : Base(pid, nid, src), condition(cond), body(b) {
+                                 utils::VectorRef<const CaseStatement*> b)
+    : Base(pid, nid, src), condition(cond), body(std::move(b)) {
     TINT_ASSERT(AST, condition);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
     for (auto* stmt : body) {
@@ -43,7 +45,7 @@
     auto src = ctx->Clone(source);
     auto* cond = ctx->Clone(condition);
     auto b = ctx->Clone(body);
-    return ctx->dst->create<SwitchStatement>(src, cond, b);
+    return ctx->dst->create<SwitchStatement>(src, cond, std::move(b));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/switch_statement.h b/src/tint/ast/switch_statement.h
index c13ac88..82a9aa4 100644
--- a/src/tint/ast/switch_statement.h
+++ b/src/tint/ast/switch_statement.h
@@ -33,7 +33,7 @@
                     NodeID nid,
                     const Source& src,
                     const Expression* condition,
-                    CaseStatementList body);
+                    utils::VectorRef<const CaseStatement*> body);
     /// Move constructor
     SwitchStatement(SwitchStatement&&);
     ~SwitchStatement() override;
@@ -51,7 +51,7 @@
     const Expression* const condition;
 
     /// The Switch body
-    const CaseStatementList body;
+    const utils::Vector<const CaseStatement*, 4> body;
     SwitchStatement(const SwitchStatement&) = delete;
 };
 
diff --git a/src/tint/ast/switch_statement_test.cc b/src/tint/ast/switch_statement_test.cc
index 19d1cfb..0f66c61 100644
--- a/src/tint/ast/switch_statement_test.cc
+++ b/src/tint/ast/switch_statement_test.cc
@@ -25,54 +25,47 @@
 using SwitchStatementTest = TestHelper;
 
 TEST_F(SwitchStatementTest, Creation) {
-    CaseSelectorList lit;
-    lit.push_back(Expr(1_u));
-
+    auto* case_stmt = create<CaseStatement>(utils::Vector{Expr(1_u)}, Block());
     auto* ident = Expr("ident");
-    CaseStatementList body;
-    auto* case_stmt = create<CaseStatement>(lit, Block());
-    body.push_back(case_stmt);
+    utils::Vector body{case_stmt};
 
     auto* stmt = create<SwitchStatement>(ident, body);
     EXPECT_EQ(stmt->condition, ident);
-    ASSERT_EQ(stmt->body.size(), 1u);
+    ASSERT_EQ(stmt->body.Length(), 1u);
     EXPECT_EQ(stmt->body[0], case_stmt);
 }
 
 TEST_F(SwitchStatementTest, Creation_WithSource) {
     auto* ident = Expr("ident");
-
-    auto* stmt =
-        create<SwitchStatement>(Source{Source::Location{20, 2}}, ident, CaseStatementList());
+    auto* stmt = create<SwitchStatement>(Source{Source::Location{20, 2}}, ident, utils::Empty);
     auto src = stmt->source;
     EXPECT_EQ(src.range.begin.line, 20u);
     EXPECT_EQ(src.range.begin.column, 2u);
 }
 
 TEST_F(SwitchStatementTest, IsSwitch) {
-    CaseSelectorList lit;
-    lit.push_back(Expr(2_i));
-
+    utils::Vector lit{Expr(2_i)};
     auto* ident = Expr("ident");
-    CaseStatementList body;
-    body.push_back(create<CaseStatement>(lit, Block()));
+    utils::Vector body{create<CaseStatement>(lit, Block())};
 
     auto* stmt = create<SwitchStatement>(ident, body);
     EXPECT_TRUE(stmt->Is<SwitchStatement>());
 }
 
 TEST_F(SwitchStatementTest, Assert_Null_Condition) {
+    using CaseStatementList = utils::Vector<const ast::CaseStatement*, 2>;
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
             CaseStatementList cases;
-            cases.push_back(b.create<CaseStatement>(CaseSelectorList{b.Expr(1_i)}, b.Block()));
+            cases.Push(b.create<CaseStatement>(utils::Vector{b.Expr(1_i)}, b.Block()));
             b.create<SwitchStatement>(nullptr, cases);
         },
         "internal compiler error");
 }
 
 TEST_F(SwitchStatementTest, Assert_Null_CaseStatement) {
+    using CaseStatementList = utils::Vector<const ast::CaseStatement*, 2>;
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
@@ -86,9 +79,9 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<SwitchStatement>(b2.Expr(true), CaseStatementList{
+            b1.create<SwitchStatement>(b2.Expr(true), utils::Vector{
                                                           b1.create<CaseStatement>(
-                                                              CaseSelectorList{
+                                                              utils::Vector{
                                                                   b1.Expr(1_i),
                                                               },
                                                               b1.Block()),
@@ -102,9 +95,9 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<SwitchStatement>(b1.Expr(true), CaseStatementList{
+            b1.create<SwitchStatement>(b1.Expr(true), utils::Vector{
                                                           b2.create<CaseStatement>(
-                                                              CaseSelectorList{
+                                                              utils::Vector{
                                                                   b2.Expr(1_i),
                                                               },
                                                               b2.Block()),
diff --git a/src/tint/ast/traverse_expressions.h b/src/tint/ast/traverse_expressions.h
index 0974fd8..bcf0bfd 100644
--- a/src/tint/ast/traverse_expressions.h
+++ b/src/tint/ast/traverse_expressions.h
@@ -68,7 +68,7 @@
         size_t depth;
     };
 
-    utils::Vector<Pending, 64> to_visit{{root, 0}};
+    utils::Vector<Pending, 32> to_visit{{root, 0}};
 
     auto push_single = [&](const ast::Expression* expr, size_t depth) {
         to_visit.Push({expr, depth});
@@ -82,7 +82,7 @@
             to_visit.Push({right, depth});
         }
     };
-    auto push_list = [&](const std::vector<const ast::Expression*>& exprs, size_t depth) {
+    auto push_list = [&](utils::VectorRef<const ast::Expression*> exprs, size_t depth) {
         if (ORDER == TraverseOrder::LeftToRight) {
             for (auto* expr : utils::Reverse(exprs)) {
                 to_visit.Push({expr, depth});
diff --git a/src/tint/ast/traverse_expressions_test.cc b/src/tint/ast/traverse_expressions_test.cc
index e79decb..a5a6dfd 100644
--- a/src/tint/ast/traverse_expressions_test.cc
+++ b/src/tint/ast/traverse_expressions_test.cc
@@ -97,19 +97,19 @@
     auto* b2 = Bitcast<i32>(b1);
     auto* root = Bitcast<i32>(b2);
     {
-        std::vector<const ast::Expression*> l2r;
+        utils::Vector<const ast::Expression*, 8> l2r;
         TraverseExpressions<TraverseOrder::LeftToRight>(root, Diagnostics(),
                                                         [&](const ast::Expression* expr) {
-                                                            l2r.push_back(expr);
+                                                            l2r.Push(expr);
                                                             return ast::TraverseAction::Descend;
                                                         });
         EXPECT_THAT(l2r, ElementsAre(root, b2, b1, b0, e));
     }
     {
-        std::vector<const ast::Expression*> r2l;
+        utils::Vector<const ast::Expression*, 8> r2l;
         TraverseExpressions<TraverseOrder::RightToLeft>(root, Diagnostics(),
                                                         [&](const ast::Expression* expr) {
-                                                            r2l.push_back(expr);
+                                                            r2l.Push(expr);
                                                             return ast::TraverseAction::Descend;
                                                         });
         EXPECT_THAT(r2l, ElementsAre(root, b2, b1, b0, e));
@@ -117,23 +117,23 @@
 }
 
 TEST_F(TraverseExpressionsTest, DescendCallExpression) {
-    std::vector<const ast::Expression*> e = {Expr(1_i), Expr(1_i), Expr(1_i), Expr(1_i)};
-    std::vector<const ast::Expression*> c = {Call("a", e[0], e[1]), Call("b", e[2], e[3])};
+    utils::Vector e{Expr(1_i), Expr(1_i), Expr(1_i), Expr(1_i)};
+    utils::Vector c{Call("a", e[0], e[1]), Call("b", e[2], e[3])};
     auto* root = Call("c", c[0], c[1]);
     {
-        std::vector<const ast::Expression*> l2r;
+        utils::Vector<const ast::Expression*, 8> l2r;
         TraverseExpressions<TraverseOrder::LeftToRight>(root, Diagnostics(),
                                                         [&](const ast::Expression* expr) {
-                                                            l2r.push_back(expr);
+                                                            l2r.Push(expr);
                                                             return ast::TraverseAction::Descend;
                                                         });
         EXPECT_THAT(l2r, ElementsAre(root, c[0], e[0], e[1], c[1], e[2], e[3]));
     }
     {
-        std::vector<const ast::Expression*> r2l;
+        utils::Vector<const ast::Expression*, 8> r2l;
         TraverseExpressions<TraverseOrder::RightToLeft>(root, Diagnostics(),
                                                         [&](const ast::Expression* expr) {
-                                                            r2l.push_back(expr);
+                                                            r2l.Push(expr);
                                                             return ast::TraverseAction::Descend;
                                                         });
         EXPECT_THAT(r2l, ElementsAre(root, c[1], e[3], e[2], c[0], e[1], e[0]));
diff --git a/src/tint/ast/var.cc b/src/tint/ast/var.cc
index 763949f..474cff2 100644
--- a/src/tint/ast/var.cc
+++ b/src/tint/ast/var.cc
@@ -28,8 +28,8 @@
          StorageClass storage_class,
          Access access,
          const Expression* ctor,
-         AttributeList attrs)
-    : Base(pid, nid, src, sym, ty, ctor, attrs),
+         utils::VectorRef<const Attribute*> attrs)
+    : Base(pid, nid, src, sym, ty, ctor, std::move(attrs)),
       declared_storage_class(storage_class),
       declared_access(access) {}
 
@@ -48,7 +48,7 @@
     auto* ctor = ctx->Clone(constructor);
     auto attrs = ctx->Clone(attributes);
     return ctx->dst->create<Var>(src, sym, ty, declared_storage_class, declared_access, ctor,
-                                 attrs);
+                                 std::move(attrs));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/var.h b/src/tint/ast/var.h
index cd12ff4..908a9b3 100644
--- a/src/tint/ast/var.h
+++ b/src/tint/ast/var.h
@@ -59,7 +59,7 @@
         StorageClass declared_storage_class,
         Access declared_access,
         const Expression* constructor,
-        AttributeList attributes);
+        utils::VectorRef<const Attribute*> attributes);
 
     /// Move constructor
     Var(Var&&);
diff --git a/src/tint/ast/variable.cc b/src/tint/ast/variable.cc
index b16b2aa..ec87e54 100644
--- a/src/tint/ast/variable.cc
+++ b/src/tint/ast/variable.cc
@@ -26,7 +26,7 @@
                    const Symbol& sym,
                    const ast::Type* ty,
                    const Expression* ctor,
-                   AttributeList attrs)
+                   utils::VectorRef<const Attribute*> attrs)
     : Base(pid, nid, src), symbol(sym), type(ty), constructor(ctor), attributes(std::move(attrs)) {
     TINT_ASSERT(AST, symbol.IsValid());
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, symbol, program_id);
diff --git a/src/tint/ast/variable.h b/src/tint/ast/variable.h
index 3031318..1f5d77a 100644
--- a/src/tint/ast/variable.h
+++ b/src/tint/ast/variable.h
@@ -67,7 +67,7 @@
              const Symbol& sym,
              const ast::Type* type,
              const Expression* constructor,
-             AttributeList attributes);
+             utils::VectorRef<const Attribute*> attributes);
 
     /// Move constructor
     Variable(Variable&&);
@@ -95,12 +95,9 @@
     const Expression* const constructor;
 
     /// The attributes attached to this variable
-    const AttributeList attributes;
+    const utils::Vector<const Attribute*, 2> attributes;
 };
 
-/// A list of variables
-using VariableList = std::vector<const Variable*>;
-
 }  // namespace tint::ast
 
 #endif  // SRC_TINT_AST_VARIABLE_H_
diff --git a/src/tint/ast/variable_test.cc b/src/tint/ast/variable_test.cc
index a54e869..b226994 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, AttributeList{});
+                  ty.f32(), StorageClass::kPrivate, nullptr, 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, AttributeList{});
+                  ty.i32(), StorageClass::kWorkgroup, nullptr, utils::Empty);
 
     EXPECT_EQ(v->symbol, Symbol(1, ID()));
     EXPECT_EQ(v->declared_storage_class, StorageClass::kWorkgroup);
@@ -93,7 +93,7 @@
 
 TEST_F(VariableTest, WithAttributes) {
     auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
-                    AttributeList{
+                    utils::Vector{
                         create<LocationAttribute>(1u),
                         create<BuiltinAttribute>(BuiltinValue::kPosition),
                         create<IdAttribute>(1200u),
@@ -111,7 +111,7 @@
 
 TEST_F(VariableTest, BindingPoint) {
     auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
-                    AttributeList{
+                    utils::Vector{
                         create<BindingAttribute>(2u),
                         create<GroupAttribute>(1u),
                     });
@@ -123,7 +123,7 @@
 }
 
 TEST_F(VariableTest, BindingPointAttributes) {
-    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr, AttributeList{});
+    auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr, utils::Empty);
     EXPECT_FALSE(var->BindingPoint());
     EXPECT_EQ(var->BindingPoint().group, nullptr);
     EXPECT_EQ(var->BindingPoint().binding, nullptr);
@@ -131,7 +131,7 @@
 
 TEST_F(VariableTest, BindingPointMissingGroupAttribute) {
     auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
-                    AttributeList{
+                    utils::Vector{
                         create<BindingAttribute>(2u),
                     });
     EXPECT_FALSE(var->BindingPoint());
@@ -142,7 +142,7 @@
 
 TEST_F(VariableTest, BindingPointMissingBindingAttribute) {
     auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
-                    AttributeList{create<GroupAttribute>(1u)});
+                    utils::Vector{create<GroupAttribute>(1u)});
     EXPECT_FALSE(var->BindingPoint());
     ASSERT_NE(var->BindingPoint().group, nullptr);
     EXPECT_EQ(var->BindingPoint().group->value, 1u);
diff --git a/src/tint/clone_context.cc b/src/tint/clone_context.cc
index 513c710..1225294 100644
--- a/src/tint/clone_context.cc
+++ b/src/tint/clone_context.cc
@@ -62,7 +62,7 @@
 
 ast::FunctionList CloneContext::Clone(const ast::FunctionList& v) {
     ast::FunctionList out;
-    out.reserve(v.size());
+    out.Reserve(v.Length());
     for (const ast::Function* el : v) {
         out.Add(Clone(el));
     }
diff --git a/src/tint/clone_context.h b/src/tint/clone_context.h
index e8e197f..e7e2d52 100644
--- a/src/tint/clone_context.h
+++ b/src/tint/clone_context.h
@@ -28,6 +28,7 @@
 #include "src/tint/program_id.h"
 #include "src/tint/symbol.h"
 #include "src/tint/traits.h"
+#include "src/tint/utils/vector.h"
 
 // Forward declarations
 namespace tint {
@@ -163,12 +164,12 @@
     ///
     /// @param v the vector to clone
     /// @return the cloned vector
-    template <typename T, typename A>
-    std::vector<T> Clone(const std::vector<T, A>& v) {
-        std::vector<T> out;
+    template <typename T, size_t N>
+    utils::Vector<T, N> Clone(const utils::Vector<T, N>& v) {
+        utils::Vector<T, N> out;
         out.reserve(v.size());
         for (auto& el : v) {
-            out.emplace_back(Clone(el));
+            out.Push(Clone(el));
         }
         return out;
     }
@@ -181,9 +182,9 @@
     ///
     /// @param v the vector to clone
     /// @return the cloned vector
-    template <typename T, typename A>
-    std::vector<T*, A> Clone(const std::vector<T*, A>& v) {
-        std::vector<T*, A> out;
+    template <typename T, size_t N>
+    utils::Vector<T*, N> Clone(const utils::Vector<T*, N>& v) {
+        utils::Vector<T*, N> out;
         Clone(out, v);
         return out;
     }
@@ -196,39 +197,39 @@
     ///
     /// @param from the vector to clone
     /// @param to the cloned result
-    template <typename T, typename A>
-    void Clone(std::vector<T*, A>& to, const std::vector<T*, A>& from) {
-        to.reserve(from.size());
+    template <typename T, size_t N>
+    void Clone(utils::Vector<T*, N>& to, const utils::Vector<T*, N>& from) {
+        to.Reserve(from.Length());
 
         auto list_transform_it = list_transforms_.find(&from);
         if (list_transform_it != list_transforms_.end()) {
             const auto& transforms = list_transform_it->second;
             for (auto* o : transforms.insert_front_) {
-                to.emplace_back(CheckedCast<T>(o));
+                to.Push(CheckedCast<T>(o));
             }
             for (auto& el : from) {
                 auto insert_before_it = transforms.insert_before_.find(el);
                 if (insert_before_it != transforms.insert_before_.end()) {
                     for (auto insert : insert_before_it->second) {
-                        to.emplace_back(CheckedCast<T>(insert));
+                        to.Push(CheckedCast<T>(insert));
                     }
                 }
                 if (transforms.remove_.count(el) == 0) {
-                    to.emplace_back(Clone(el));
+                    to.Push(Clone(el));
                 }
                 auto insert_after_it = transforms.insert_after_.find(el);
                 if (insert_after_it != transforms.insert_after_.end()) {
                     for (auto insert : insert_after_it->second) {
-                        to.emplace_back(CheckedCast<T>(insert));
+                        to.Push(CheckedCast<T>(insert));
                     }
                 }
             }
             for (auto* o : transforms.insert_back_) {
-                to.emplace_back(CheckedCast<T>(o));
+                to.Push(CheckedCast<T>(o));
             }
         } else {
             for (auto& el : from) {
-                to.emplace_back(Clone(el));
+                to.Push(Clone(el));
 
                 // Clone(el) may have inserted after
                 list_transform_it = list_transforms_.find(&from);
@@ -238,7 +239,7 @@
                     auto insert_after_it = transforms.insert_after_.find(el);
                     if (insert_after_it != transforms.insert_after_.end()) {
                         for (auto insert : insert_after_it->second) {
-                            to.emplace_back(CheckedCast<T>(insert));
+                            to.Push(CheckedCast<T>(insert));
                         }
                     }
                 }
@@ -250,7 +251,7 @@
                 const auto& transforms = list_transform_it->second;
 
                 for (auto* o : transforms.insert_back_) {
-                    to.emplace_back(CheckedCast<T>(o));
+                    to.Push(CheckedCast<T>(o));
                 }
             }
         }
@@ -318,7 +319,7 @@
         CloneableTransform transform;
         transform.typeinfo = &TypeInfo::Of<T>();
         transform.function = [=](const Cloneable* in) { return replacer(in->As<T>()); };
-        transforms_.emplace_back(std::move(transform));
+        transforms_.Push(std::move(transform));
         return *this;
     }
 
@@ -386,8 +387,8 @@
     /// @param object a pointer to the object in #src that will be omitted from
     /// the cloned vector.
     /// @returns this CloneContext so calls can be chained
-    template <typename T, typename A, typename OBJECT>
-    CloneContext& Remove(const std::vector<T, A>& vector, OBJECT* object) {
+    template <typename T, size_t N, typename OBJECT>
+    CloneContext& Remove(const utils::Vector<T, N>& vector, OBJECT* object) {
         TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, object);
         if (std::find(vector.begin(), vector.end(), object) == vector.end()) {
             TINT_ICE(Clone, Diagnostics())
@@ -404,12 +405,12 @@
     /// @param object a pointer to the object in #dst that will be inserted at the
     /// front of the vector
     /// @returns this CloneContext so calls can be chained
-    template <typename T, typename A, typename OBJECT>
-    CloneContext& InsertFront(const std::vector<T, A>& vector, OBJECT* object) {
+    template <typename T, size_t N, typename OBJECT>
+    CloneContext& InsertFront(const utils::Vector<T, N>& vector, OBJECT* object) {
         TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object);
         auto& transforms = list_transforms_[&vector];
         auto& list = transforms.insert_front_;
-        list.emplace_back(object);
+        list.Push(object);
         return *this;
     }
 
@@ -418,12 +419,12 @@
     /// @param object a pointer to the object in #dst that will be inserted at the
     /// end of the vector
     /// @returns this CloneContext so calls can be chained
-    template <typename T, typename A, typename OBJECT>
-    CloneContext& InsertBack(const std::vector<T, A>& vector, OBJECT* object) {
+    template <typename T, size_t N, typename OBJECT>
+    CloneContext& InsertBack(const utils::Vector<T, N>& vector, OBJECT* object) {
         TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object);
         auto& transforms = list_transforms_[&vector];
         auto& list = transforms.insert_back_;
-        list.emplace_back(object);
+        list.Push(object);
         return *this;
     }
 
@@ -433,8 +434,8 @@
     /// @param object a pointer to the object in #dst that will be inserted before
     /// any occurrence of the clone of `before`
     /// @returns this CloneContext so calls can be chained
-    template <typename T, typename A, typename BEFORE, typename OBJECT>
-    CloneContext& InsertBefore(const std::vector<T, A>& vector,
+    template <typename T, size_t N, typename BEFORE, typename OBJECT>
+    CloneContext& InsertBefore(const utils::Vector<T, N>& vector,
                                const BEFORE* before,
                                const OBJECT* object) {
         TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, before);
@@ -447,7 +448,7 @@
 
         auto& transforms = list_transforms_[&vector];
         auto& list = transforms.insert_before_[before];
-        list.emplace_back(object);
+        list.Push(object);
         return *this;
     }
 
@@ -457,8 +458,8 @@
     /// @param object a pointer to the object in #dst that will be inserted after
     /// any occurrence of the clone of `after`
     /// @returns this CloneContext so calls can be chained
-    template <typename T, typename A, typename AFTER, typename OBJECT>
-    CloneContext& InsertAfter(const std::vector<T, A>& vector,
+    template <typename T, size_t N, typename AFTER, typename OBJECT>
+    CloneContext& InsertAfter(const utils::Vector<T, N>& vector,
                               const AFTER* after,
                               const OBJECT* object) {
         TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, after);
@@ -471,7 +472,7 @@
 
         auto& transforms = list_transforms_[&vector];
         auto& list = transforms.insert_after_[after];
-        list.emplace_back(object);
+        list.Push(object);
         return *this;
     }
 
@@ -530,7 +531,7 @@
     diag::List& Diagnostics() const;
 
     /// A vector of const Cloneable*
-    using CloneableList = std::vector<const Cloneable*>;
+    using CloneableList = utils::Vector<const Cloneable*, 4>;
 
     /// Transformations to be applied to a list (vector)
     struct ListTransforms {
@@ -551,12 +552,12 @@
         CloneableList insert_back_;
 
         /// A map of object in #src to the list of cloned objects in #dst.
-        /// Clone(const std::vector<T*>& v) will use this to insert the map-value
+        /// Clone(const utils::Vector<T*>& v) will use this to insert the map-value
         /// list into the target vector before cloning and inserting the map-key.
         std::unordered_map<const Cloneable*, CloneableList> insert_before_;
 
         /// A map of object in #src to the list of cloned objects in #dst.
-        /// Clone(const std::vector<T*>& v) will use this to insert the map-value
+        /// Clone(const utils::Vector<T*>& v) will use this to insert the map-value
         /// list into the target vector after cloning and inserting the map-key.
         std::unordered_map<const Cloneable*, CloneableList> insert_after_;
     };
@@ -569,9 +570,9 @@
     std::unordered_map<Symbol, Symbol> cloned_symbols_;
 
     /// Cloneable transform functions registered with ReplaceAll()
-    std::vector<CloneableTransform> transforms_;
+    utils::Vector<CloneableTransform, 8> transforms_;
 
-    /// Map of std::vector pointer to transforms for that list
+    /// Map of utils::Vector pointer to transforms for that list
     std::unordered_map<const void*, ListTransforms> list_transforms_;
 
     /// Symbol transform registered with ReplaceAll()
diff --git a/src/tint/clone_context_test.cc b/src/tint/clone_context_test.cc
index 46cc720..c895151 100644
--- a/src/tint/clone_context_test.cc
+++ b/src/tint/clone_context_test.cc
@@ -37,12 +37,13 @@
          const Node* node_b = nullptr,
          const Node* node_c = nullptr)
         : allocator(alloc), name(n), a(node_a), b(node_b), c(node_c) {}
+    Node(Node&&) = delete;
     Allocator* const allocator;
     Symbol name;
     const Node* a = nullptr;
     const Node* b = nullptr;
     const Node* c = nullptr;
-    std::vector<const Node*> vec;
+    utils::Vector<const Node*, 8> vec;
 
     Node* Clone(CloneContext* ctx) const override {
         auto* out = allocator->Create<Node>(ctx->Clone(name));
@@ -387,7 +388,7 @@
                             .Remove(original_root->vec, original_root->vec[1])
                             .Clone(original_root);
 
-    EXPECT_EQ(cloned_root->vec.size(), 2u);
+    EXPECT_EQ(cloned_root->vec.Length(), 2u);
 
     EXPECT_NE(cloned_root->vec[0], cloned_root->a);
     EXPECT_NE(cloned_root->vec[1], cloned_root->c);
@@ -416,7 +417,7 @@
                             .InsertFront(original_root->vec, insertion)
                             .Clone(original_root);
 
-    EXPECT_EQ(cloned_root->vec.size(), 4u);
+    EXPECT_EQ(cloned_root->vec.Length(), 4u);
 
     EXPECT_NE(cloned_root->vec[0], cloned_root->a);
     EXPECT_NE(cloned_root->vec[1], cloned_root->b);
@@ -434,7 +435,7 @@
 
     ProgramBuilder builder;
     auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-    original_root->vec = {};
+    original_root->vec.Clear();
     Program original(std::move(builder));
 
     ProgramBuilder cloned;
@@ -444,7 +445,7 @@
                             .InsertFront(original_root->vec, insertion)
                             .Clone(original_root);
 
-    EXPECT_EQ(cloned_root->vec.size(), 1u);
+    EXPECT_EQ(cloned_root->vec.Length(), 1u);
 
     EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
     EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion"));
@@ -469,7 +470,7 @@
                             .InsertBack(original_root->vec, insertion)
                             .Clone(original_root);
 
-    EXPECT_EQ(cloned_root->vec.size(), 4u);
+    EXPECT_EQ(cloned_root->vec.Length(), 4u);
 
     EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
     EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
@@ -483,7 +484,7 @@
 
     ProgramBuilder builder;
     auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-    original_root->vec = {};
+    original_root->vec.Clear();
     Program original(std::move(builder));
 
     ProgramBuilder cloned;
@@ -493,7 +494,7 @@
                             .InsertBack(original_root->vec, insertion)
                             .Clone(original_root);
 
-    EXPECT_EQ(cloned_root->vec.size(), 1u);
+    EXPECT_EQ(cloned_root->vec.Length(), 1u);
 
     EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
     EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion"));
@@ -504,7 +505,7 @@
 
     ProgramBuilder builder;
     auto* original_root = a.Create<Node>(builder.Symbols().Register("root"));
-    original_root->vec = {};
+    original_root->vec.Clear();
     Program original(std::move(builder));
 
     ProgramBuilder cloned;
@@ -516,7 +517,7 @@
                             .InsertFront(original_root->vec, insertion_front)
                             .Clone(original_root);
 
-    EXPECT_EQ(cloned_root->vec.size(), 2u);
+    EXPECT_EQ(cloned_root->vec.Length(), 2u);
 
     EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
     EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion_front"));
@@ -542,7 +543,7 @@
                             .InsertBefore(original_root->vec, original_root->vec[1], insertion)
                             .Clone(original_root);
 
-    EXPECT_EQ(cloned_root->vec.size(), 4u);
+    EXPECT_EQ(cloned_root->vec.Length(), 4u);
 
     EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
     EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
@@ -570,7 +571,7 @@
                             .InsertAfter(original_root->vec, original_root->vec[1], insertion)
                             .Clone(original_root);
 
-    EXPECT_EQ(cloned_root->vec.size(), 4u);
+    EXPECT_EQ(cloned_root->vec.Length(), 4u);
 
     EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
     EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
@@ -602,7 +603,7 @@
 
     auto* cloned_root = ctx.Clone(original_root);
 
-    EXPECT_EQ(cloned_root->vec.size(), 4u);
+    EXPECT_EQ(cloned_root->vec.Length(), 4u);
 
     EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
     EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
@@ -634,7 +635,7 @@
 
     auto* cloned_root = ctx.Clone(original_root);
 
-    EXPECT_EQ(cloned_root->vec.size(), 4u);
+    EXPECT_EQ(cloned_root->vec.Length(), 4u);
 
     EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
     EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
@@ -666,7 +667,7 @@
             .Remove(original_root->vec, original_root->vec[1])
             .Clone(original_root);
 
-    EXPECT_EQ(cloned_root->vec.size(), 4u);
+    EXPECT_EQ(cloned_root->vec.Length(), 4u);
 
     EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root"));
     EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a"));
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc
index c38e6f9..6f12348 100644
--- a/src/tint/cmd/main.cc
+++ b/src/tint/cmd/main.cc
@@ -32,6 +32,7 @@
 #include "spirv-tools/libspirv.hpp"
 #endif  // TINT_BUILD_SPV_READER
 
+#include "src/tint/ast/module.h"
 #include "src/tint/utils/io/command.h"
 #include "src/tint/utils/string.h"
 #include "src/tint/utils/transform.h"
@@ -820,7 +821,18 @@
                 tint::utils::Command::LookPath(options.dxc_path.empty() ? "dxc" : options.dxc_path);
             if (dxc.Found()) {
                 dxc_found = true;
-                dxc_res = tint::val::HlslUsingDXC(dxc.Path(), result.hlsl, result.entry_points);
+
+                auto enable_list = program->AST().Enables();
+                bool dxc_require_16bit_types = false;
+                for (auto enable : enable_list) {
+                    if (enable->extension == tint::ast::Extension::kF16) {
+                        dxc_require_16bit_types = true;
+                        break;
+                    }
+                }
+
+                dxc_res = tint::val::HlslUsingDXC(dxc.Path(), result.hlsl, result.entry_points,
+                                                  dxc_require_16bit_types);
             } else if (must_validate_dxc) {
                 // DXC was explicitly requested. Error if it could not be found.
                 dxc_res.failed = true;
diff --git a/src/tint/fuzzers/fuzzer_init.cc b/src/tint/fuzzers/fuzzer_init.cc
index 3d3f692..66f701a 100644
--- a/src/tint/fuzzers/fuzzer_init.cc
+++ b/src/tint/fuzzers/fuzzer_init.cc
@@ -12,8 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/fuzzers/fuzzer_init.h"
+#include <cstddef>
+
 #include "src/tint/fuzzers/cli.h"
+#include "src/tint/fuzzers/fuzzer_init.h"
+#include "testing/libfuzzer/libfuzzer_exports.h"
 
 namespace tint::fuzzers {
 
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn
index 69ef3fe..58a36ea 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn
+++ b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn
@@ -46,6 +46,8 @@
       "expression_size.cc",
       "expression_size.h",
       "fuzzer.cc",
+      "jump_tracker.cc",
+      "jump_tracker.h",
       "mutation.cc",
       "mutation.h",
       "mutation_finder.cc",
@@ -54,6 +56,8 @@
       "mutation_finders/change_binary_operators.h",
       "mutation_finders/change_unary_operators.cc",
       "mutation_finders/change_unary_operators.h",
+      "mutation_finders/delete_statements.cc",
+      "mutation_finders/delete_statements.h",
       "mutation_finders/replace_identifiers.cc",
       "mutation_finders/replace_identifiers.h",
       "mutation_finders/wrap_unary_operators.cc",
@@ -62,6 +66,8 @@
       "mutations/change_binary_operator.h",
       "mutations/change_unary_operator.cc",
       "mutations/change_unary_operator.h",
+      "mutations/delete_statement.cc",
+      "mutations/delete_statement.h",
       "mutations/replace_identifier.cc",
       "mutations/replace_identifier.h",
       "mutations/wrap_unary_operator.cc",
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt
index 5bd8c50..0d1f5a2 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt
+++ b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt
@@ -39,14 +39,17 @@
         ../random_generator.h
         ../random_generator_engine.h
         expression_size.h
+        jump_tracker.h
         mutation.h
         mutation_finder.h
         mutation_finders/change_binary_operators.h
         mutation_finders/change_unary_operators.h
+        mutation_finders/delete_statements.h
         mutation_finders/replace_identifiers.h
         mutation_finders/wrap_unary_operators.h
         mutations/change_binary_operator.h
         mutations/change_unary_operator.h
+        mutations/delete_statement.h
         mutations/replace_identifier.h
         mutations/wrap_unary_operator.h
         mutator.h
@@ -61,14 +64,17 @@
         ../random_generator.cc
         ../random_generator_engine.cc
         expression_size.cc
+        jump_tracker.cc
         mutation.cc
         mutation_finder.cc
         mutation_finders/change_binary_operators.cc
         mutation_finders/change_unary_operators.cc
+        mutation_finders/delete_statements.cc
         mutation_finders/replace_identifiers.cc
         mutation_finders/wrap_unary_operators.cc
         mutations/change_binary_operator.cc
         mutations/change_unary_operator.cc
+        mutations/delete_statement.cc
         mutations/replace_identifier.cc
         mutations/wrap_unary_operator.cc
         mutator.cc
@@ -107,8 +113,10 @@
 if (${TINT_BUILD_TESTS})
     set(TEST_SOURCES
             expression_size_test.cc
+            jump_tracker_test.cc
             mutations/change_binary_operator_test.cc
             mutations/change_unary_operator_test.cc
+            mutations/delete_statement_test.cc
             mutations/replace_identifier_test.cc
             mutations/wrap_unary_operator_test.cc)
 
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc
index 20601df..383135d 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc
@@ -21,9 +21,9 @@
 #include "src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h"
 #include "src/tint/fuzzers/tint_common_fuzzer.h"
 #include "src/tint/fuzzers/transform_builder.h"
-
 #include "src/tint/reader/wgsl/parser.h"
 #include "src/tint/writer/wgsl/generator.h"
+#include "testing/libfuzzer/libfuzzer_exports.h"
 
 namespace tint::fuzzers::ast_fuzzer {
 namespace {
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.cc b/src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.cc
new file mode 100644
index 0000000..66bbaf7
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.cc
@@ -0,0 +1,83 @@
+// 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/fuzzers/tint_ast_fuzzer/jump_tracker.h"
+
+#include <cassert>
+#include <unordered_set>
+
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/for_loop_statement.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/while_statement.h"
+#include "src/tint/sem/statement.h"
+
+namespace tint::fuzzers::ast_fuzzer {
+
+JumpTracker::JumpTracker(const Program& program) {
+    // Consider every AST node, looking for break, return and discard statements.
+    for (auto* node : program.ASTNodes().Objects()) {
+        auto* stmt = node->As<ast::Statement>();
+        if (stmt == nullptr) {
+            continue;
+        }
+        if (stmt->As<ast::BreakStatement>()) {
+            // This break statement either exits a loop or a switch statement.
+            // Walk up the AST until either a loop or switch statement is found. In the former case,
+            // it is the innermost loop containing the break statement, and thus all the nodes
+            // encountered along the way are nodes that contain a break from the innermost loop.
+
+            // This records the statements encountered when walking up the AST from the break
+            // statement to the innermost enclosing loop or switch statement.
+            std::unordered_set<const ast::Statement*> candidate_statements;
+            for (const ast::Statement* current = stmt;;
+                 current =
+                     As<sem::Statement>(program.Sem().Get(current))->Parent()->Declaration()) {
+                if (current->Is<ast::ForLoopStatement>() || current->Is<ast::LoopStatement>() ||
+                    current->Is<ast::WhileStatement>()) {
+                    // A loop has been encountered, thus all that statements recorded until this
+                    // point contain a break from their innermost loop.
+                    for (auto* candidate : candidate_statements) {
+                        contains_break_for_innermost_loop_.insert(candidate);
+                    }
+                    break;
+                }
+                if (current->Is<ast::SwitchStatement>()) {
+                    // A switch statement has been encountered, so the break does not correspond to
+                    // a loop break.
+                    break;
+                }
+                candidate_statements.insert(current);
+            }
+        } else if (stmt->As<ast::ReturnStatement>() || stmt->As<ast::DiscardStatement>()) {
+            // Walk up the AST from the return or discard statement, recording that every node
+            // encountered along the way contains a return/discard.
+            auto& target_set = stmt->As<ast::ReturnStatement>() ? contains_return_
+                                                                : contains_intraprocedural_discard_;
+            const ast::Statement* current = stmt;
+            while (true) {
+                target_set.insert(current);
+                auto* parent = program.Sem().Get(current)->Parent();
+                if (parent == nullptr) {
+                    break;
+                }
+                current = parent->Declaration();
+            }
+        }
+    }
+}
+}  // namespace tint::fuzzers::ast_fuzzer
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.h b/src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.h
new file mode 100644
index 0000000..614ceb5
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.h
@@ -0,0 +1,68 @@
+// 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.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_JUMP_TRACKER_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_JUMP_TRACKER_H_
+
+#include <unordered_set>
+
+#include "src/tint/ast/statement.h"
+#include "src/tint/program.h"
+
+namespace tint::fuzzers::ast_fuzzer {
+
+/// This class computes information on which statements contain loop breaks, returns and discards.
+/// It could be extended to handle other jumps, such as switch breaks and loop continues, should
+/// such information prove useful.
+class JumpTracker {
+  public:
+    /// Initializes jump tracking information for the given program.
+    /// @param program - the program for which jumps will be tracked;
+    ///     must remain in scope as long as this instance exists.
+    explicit JumpTracker(const Program& program);
+
+    /// Indicates whether a statement contains a break statement for the innermost loop (if any).
+    /// @param statement - the statement of interest.
+    /// @return true if and only if the statement is, or contains, a break for the innermost
+    ///     enclosing loop.
+    bool ContainsBreakForInnermostLoop(const ast::Statement& statement) const {
+        return contains_break_for_innermost_loop_.count(&statement) > 0;
+    }
+
+    /// Indicates whether a statement contains a return statement.
+    /// @param statement - the statement of interest.
+    /// @return true if and only if the statement is, or contains, a return statement.
+    bool ContainsReturn(const ast::Statement& statement) const {
+        return contains_return_.count(&statement) > 0;
+    }
+
+    /// Indicates whether a statement contains a discard statement.
+    /// @param statement - the statement of interest.
+    /// @return true if and only if the statement is, or contains, a discard statement. This is
+    ///    determined in an intraprocedural fashion: the answer will be "false" if no discard occurs
+    ///    inside the statement, even if the statement calls a function that may lead to a discard
+    ///    being performed.
+    bool ContainsIntraproceduralDiscard(const ast::Statement& statement) const {
+        return contains_intraprocedural_discard_.count(&statement) > 0;
+    }
+
+  private:
+    std::unordered_set<const ast::Statement*> contains_break_for_innermost_loop_;
+    std::unordered_set<const ast::Statement*> contains_return_;
+    std::unordered_set<const ast::Statement*> contains_intraprocedural_discard_;
+};
+
+}  // namespace tint::fuzzers::ast_fuzzer
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_JUMP_TRACKER_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/jump_tracker_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/jump_tracker_test.cc
new file mode 100644
index 0000000..f83e639
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/jump_tracker_test.cc
@@ -0,0 +1,332 @@
+// 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/fuzzers/tint_ast_fuzzer/jump_tracker.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "src/tint/ast/block_statement.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/for_loop_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/module.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/while_statement.h"
+#include "src/tint/program.h"
+#include "src/tint/reader/wgsl/parser.h"
+
+namespace tint::fuzzers::ast_fuzzer {
+namespace {
+
+TEST(JumpTrackerTest, Breaks) {
+    std::string content = R"(
+fn main() {
+  var x : u32;
+  for (var i : i32 = 0; i < 100; i++) {
+    if (i == 40) {
+      {
+        break;
+      }
+    }
+    for (var j : i32 = 0; j < 10; j++) {
+      loop {
+        if (i > j) {
+          break;
+        }
+        continuing {
+          i++;
+          j-=2;
+        }
+      }
+      switch (j) {
+        case 0: {
+          if (i == j) {
+            break;
+          }
+          i = i + 1;
+          continue;
+        }
+        default: {
+          break;
+        }
+      }
+    }
+  }
+}
+  )";
+    Source::File file("test.wgsl", content);
+    auto program = reader::wgsl::Parse(&file);
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+    JumpTracker jump_tracker(program);
+
+    const auto* outer_loop_body =
+        program.AST().Functions()[0]->body->statements[1]->As<ast::ForLoopStatement>()->body;
+    const auto* first_if = outer_loop_body->statements[0]->As<ast::IfStatement>();
+    const auto* first_if_body = first_if->body;
+    const auto* block_in_first_if = first_if_body->statements[0]->As<ast::BlockStatement>();
+    const auto* break_in_first_if = block_in_first_if->statements[0]->As<ast::BreakStatement>();
+
+    const auto* innermost_loop_body = outer_loop_body->statements[1]
+                                          ->As<ast::ForLoopStatement>()
+                                          ->body->statements[0]
+                                          ->As<ast::LoopStatement>()
+                                          ->body;
+    const auto* innermost_loop_if = innermost_loop_body->statements[0]->As<ast::IfStatement>();
+    const auto* innermost_loop_if_body = innermost_loop_if->body;
+    const auto* break_in_innermost_loop =
+        innermost_loop_if_body->statements[0]->As<ast::BreakStatement>();
+
+    std::unordered_set<const ast::Statement*> containing_loop_break = {
+        outer_loop_body,        first_if,
+        first_if_body,          block_in_first_if,
+        break_in_first_if,      innermost_loop_body,
+        innermost_loop_if,      innermost_loop_if_body,
+        break_in_innermost_loop};
+
+    for (auto* node : program.ASTNodes().Objects()) {
+        auto* stmt = node->As<ast::Statement>();
+        if (stmt == nullptr) {
+            continue;
+        }
+        if (containing_loop_break.count(stmt) > 0) {
+            ASSERT_TRUE(jump_tracker.ContainsBreakForInnermostLoop(*stmt));
+        } else {
+            ASSERT_FALSE(jump_tracker.ContainsBreakForInnermostLoop(*stmt));
+        }
+    }
+}
+
+TEST(JumpTrackerTest, Returns) {
+    std::string content = R"(
+fn main() {
+  var x : u32;
+  for (var i : i32 = 0; i < 100; i++) {
+    if (i == 40) {
+      {
+        return;
+      }
+    }
+    for (var j : i32 = 0; j < 10; j++) {
+      loop {
+        if (i > j) {
+          return;
+        }
+        continuing {
+          i++;
+          j-=2;
+        }
+      }
+      switch (j) {
+        case 0: {
+          if (i == j) {
+            break;
+          }
+          i = i + 1;
+          continue;
+        }
+        default: {
+          return;
+        }
+      }
+    }
+  }
+}
+  )";
+    Source::File file("test.wgsl", content);
+    auto program = reader::wgsl::Parse(&file);
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+    JumpTracker jump_tracker(program);
+
+    const auto* function_body = program.AST().Functions()[0]->body;
+    const auto* outer_loop = function_body->statements[1]->As<ast::ForLoopStatement>();
+    const auto* outer_loop_body = outer_loop->body;
+    const auto* first_if = outer_loop_body->statements[0]->As<ast::IfStatement>();
+    const auto* first_if_body = first_if->body;
+    const auto* block_in_first_if = first_if_body->statements[0]->As<ast::BlockStatement>();
+    const auto* return_in_first_if = block_in_first_if->statements[0]->As<ast::ReturnStatement>();
+    const auto* inner_for_loop = outer_loop_body->statements[1]->As<ast::ForLoopStatement>();
+    const auto* inner_for_loop_body = inner_for_loop->body;
+    const auto* innermost_loop = inner_for_loop_body->statements[0]->As<ast::LoopStatement>();
+    const auto* innermost_loop_body = innermost_loop->body;
+    const auto* innermost_loop_if = innermost_loop_body->statements[0]->As<ast::IfStatement>();
+    const auto* innermost_loop_if_body = innermost_loop_if->body;
+    const auto* return_in_innermost_loop =
+        innermost_loop_if_body->statements[0]->As<ast::ReturnStatement>();
+    const auto* switch_statement = inner_for_loop_body->statements[1]->As<ast::SwitchStatement>();
+    const auto* default_statement = switch_statement->body[1];
+    const auto* default_statement_body = default_statement->body;
+    const auto* return_in_default_statement =
+        default_statement_body->statements[0]->As<ast::ReturnStatement>();
+
+    std::unordered_set<const ast::Statement*> containing_return = {
+        function_body,          outer_loop,
+        outer_loop_body,        first_if,
+        first_if_body,          block_in_first_if,
+        return_in_first_if,     inner_for_loop,
+        inner_for_loop_body,    innermost_loop,
+        innermost_loop_body,    innermost_loop_if,
+        innermost_loop_if_body, return_in_innermost_loop,
+        switch_statement,       default_statement,
+        default_statement_body, return_in_default_statement};
+
+    for (auto* node : program.ASTNodes().Objects()) {
+        auto* stmt = node->As<ast::Statement>();
+        if (stmt == nullptr) {
+            continue;
+        }
+        if (containing_return.count(stmt) > 0) {
+            ASSERT_TRUE(jump_tracker.ContainsReturn(*stmt));
+        } else {
+            ASSERT_FALSE(jump_tracker.ContainsReturn(*stmt));
+        }
+    }
+}
+
+TEST(JumpTrackerTest, Discards) {
+    std::string content = R"(
+fn main() {
+  var x : u32;
+  for (var i : i32 = 0; i < 100; i++) {
+    if (i == 40) {
+      {
+        discard;
+      }
+    }
+    for (var j : i32 = 0; j < 10; j++) {
+      loop {
+        if (i > j) {
+          discard;
+        }
+        continuing {
+          i++;
+          j-=2;
+        }
+      }
+      switch (j) {
+        case 0: {
+          if (i == j) {
+            break;
+          }
+          i = i + 1;
+          continue;
+        }
+        default: {
+          discard;
+        }
+      }
+    }
+  }
+}
+  )";
+    Source::File file("test.wgsl", content);
+    auto program = reader::wgsl::Parse(&file);
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+    JumpTracker jump_tracker(program);
+
+    const auto* function_body = program.AST().Functions()[0]->body;
+    const auto* outer_loop = function_body->statements[1]->As<ast::ForLoopStatement>();
+    const auto* outer_loop_body = outer_loop->body;
+    const auto* first_if = outer_loop_body->statements[0]->As<ast::IfStatement>();
+    const auto* first_if_body = first_if->body;
+    const auto* block_in_first_if = first_if_body->statements[0]->As<ast::BlockStatement>();
+    const auto* discard_in_first_if = block_in_first_if->statements[0]->As<ast::DiscardStatement>();
+    const auto* inner_for_loop = outer_loop_body->statements[1]->As<ast::ForLoopStatement>();
+    const auto* inner_for_loop_body = inner_for_loop->body;
+    const auto* innermost_loop = inner_for_loop_body->statements[0]->As<ast::LoopStatement>();
+    const auto* innermost_loop_body = innermost_loop->body;
+    const auto* innermost_loop_if = innermost_loop_body->statements[0]->As<ast::IfStatement>();
+    const auto* innermost_loop_if_body = innermost_loop_if->body;
+    const auto* discard_in_innermost_loop =
+        innermost_loop_if_body->statements[0]->As<ast::DiscardStatement>();
+    const auto* switch_statement = inner_for_loop_body->statements[1]->As<ast::SwitchStatement>();
+    const auto* default_statement = switch_statement->body[1];
+    const auto* default_statement_body = default_statement->body;
+    const auto* discard_in_default_statement =
+        default_statement_body->statements[0]->As<ast::DiscardStatement>();
+
+    std::unordered_set<const ast::Statement*> containing_discard = {
+        function_body,          outer_loop,
+        outer_loop_body,        first_if,
+        first_if_body,          block_in_first_if,
+        discard_in_first_if,    inner_for_loop,
+        inner_for_loop_body,    innermost_loop,
+        innermost_loop_body,    innermost_loop_if,
+        innermost_loop_if_body, discard_in_innermost_loop,
+        switch_statement,       default_statement,
+        default_statement_body, discard_in_default_statement};
+
+    for (auto* node : program.ASTNodes().Objects()) {
+        auto* stmt = node->As<ast::Statement>();
+        if (stmt == nullptr) {
+            continue;
+        }
+        if (containing_discard.count(stmt) > 0) {
+            ASSERT_TRUE(jump_tracker.ContainsIntraproceduralDiscard(*stmt));
+        } else {
+            ASSERT_FALSE(jump_tracker.ContainsIntraproceduralDiscard(*stmt));
+        }
+    }
+}
+
+TEST(JumpTrackerTest, WhileLoop) {
+    std::string content = R"(
+fn main() {
+  var x : u32;
+  x = 0;
+  while (x < 100) {
+    if (x > 50) {
+      break;
+    }
+    x = x + 1;
+  }
+}
+  )";
+    Source::File file("test.wgsl", content);
+    auto program = reader::wgsl::Parse(&file);
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+    JumpTracker jump_tracker(program);
+
+    const auto* while_loop_body =
+        program.AST().Functions()[0]->body->statements[2]->As<ast::WhileStatement>()->body;
+    const auto* if_statement = while_loop_body->statements[0]->As<ast::IfStatement>();
+    const auto* if_statement_body = if_statement->body;
+    const auto* break_in_if = if_statement_body->statements[0]->As<ast::BreakStatement>();
+
+    std::unordered_set<const ast::Statement*> containing_loop_break = {
+        while_loop_body, if_statement, if_statement_body, break_in_if};
+
+    for (auto* node : program.ASTNodes().Objects()) {
+        auto* stmt = node->As<ast::Statement>();
+        if (stmt == nullptr) {
+            continue;
+        }
+        if (containing_loop_break.count(stmt) > 0) {
+            ASSERT_TRUE(jump_tracker.ContainsBreakForInnermostLoop(*stmt));
+        } else {
+            ASSERT_FALSE(jump_tracker.ContainsBreakForInnermostLoop(*stmt));
+        }
+    }
+}
+
+}  // namespace
+}  // namespace tint::fuzzers::ast_fuzzer
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc
index 6f60fc3..62c970b 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc
@@ -18,6 +18,7 @@
 
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h"
 
@@ -33,6 +34,8 @@
             return std::make_unique<MutationReplaceIdentifier>(message.replace_identifier());
         case protobufs::Mutation::kChangeBinaryOperator:
             return std::make_unique<MutationChangeBinaryOperator>(message.change_binary_operator());
+        case protobufs::Mutation::kDeleteStatement:
+            return std::make_unique<MutationDeleteStatement>(message.delete_statement());
         case protobufs::Mutation::kWrapUnaryOperator:
             return std::make_unique<MutationWrapUnaryOperator>(message.wrap_unary_operator());
         case protobufs::Mutation::MUTATION_NOT_SET:
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.cc
index fb3fb96..c8dff2c 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.cc
@@ -50,6 +50,12 @@
             continue;
         }
 
+        // Only complement and negation operators can be swapped.
+        if (!(unary_expr->op == ast::UnaryOp::kComplement ||
+              unary_expr->op == ast::UnaryOp::kNegation)) {
+            continue;
+        }
+
         result.push_back(std::make_unique<MutationChangeUnaryOperator>(
             node_id_map->GetId(unary_expr),
             MutationChangeUnaryOperator::ToggleOperator(unary_expr->op)));
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.cc
new file mode 100644
index 0000000..f1d7e4d
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.cc
@@ -0,0 +1,68 @@
+// Copyright 2021 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/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.h"
+
+#include <memory>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/util.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
+
+namespace tint::fuzzers::ast_fuzzer {
+
+MutationList MutationFinderDeleteStatements::FindMutations(const tint::Program& program,
+                                                           NodeIdMap* node_id_map,
+                                                           ProbabilityContext* /*unused*/) const {
+    MutationList result;
+
+    JumpTracker jump_tracker(program);
+
+    // Consider every statement node in the AST.
+    for (auto* node : program.ASTNodes().Objects()) {
+        auto* statement_node = tint::As<ast::Statement>(node);
+
+        if (!statement_node) {
+            continue;
+        }
+
+        const auto* statement_sem_node =
+            tint::As<sem::Statement>(program.Sem().Get(statement_node));
+
+        // Semantic information for the node is required in order to delete it.
+        if (!statement_sem_node) {
+            continue;
+        }
+
+        // Check that this kind of statement can be deleted.
+        if (!MutationDeleteStatement::CanBeDeleted(*statement_node, program, jump_tracker)) {
+            continue;
+        }
+
+        result.push_back(
+            std::make_unique<MutationDeleteStatement>(node_id_map->GetId(statement_node)));
+    }
+
+    return result;
+}
+
+uint32_t MutationFinderDeleteStatements::GetChanceOfApplyingMutation(
+    ProbabilityContext* probability_context) const {
+    return probability_context->GetChanceOfDeletingStatements();
+}
+
+}  // namespace tint::fuzzers::ast_fuzzer
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.h
new file mode 100644
index 0000000..97a4cbf
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.h
@@ -0,0 +1,36 @@
+// Copyright 2021 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.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_DELETE_STATEMENTS_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_DELETE_STATEMENTS_H_
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h"
+
+namespace tint::fuzzers::ast_fuzzer {
+
+/// Looks for opportunities to apply
+/// `MutationFinderDeleteStatements`.
+///
+/// Considers statements for deletion if their deletion would not make the module invalid.
+class MutationFinderDeleteStatements : public MutationFinder {
+  public:
+    MutationList FindMutations(const tint::Program& program,
+                               NodeIdMap* node_id_map,
+                               ProbabilityContext* probability_context) const override;
+    uint32_t GetChanceOfApplyingMutation(ProbabilityContext* probability_context) const override;
+};
+
+}  // namespace tint::fuzzers::ast_fuzzer
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_DELETE_STATEMENTS_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.cc
new file mode 100644
index 0000000..05435d0
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.cc
@@ -0,0 +1,208 @@
+// Copyright 2021 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/fuzzers/tint_ast_fuzzer/mutations/delete_statement.h"
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/block_statement.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/for_loop_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/util.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/for_loop_statement.h"
+#include "src/tint/sem/if_statement.h"
+#include "src/tint/sem/loop_statement.h"
+#include "src/tint/sem/statement.h"
+
+namespace tint::fuzzers::ast_fuzzer {
+
+MutationDeleteStatement::MutationDeleteStatement(protobufs::MutationDeleteStatement message)
+    : message_(std::move(message)) {}
+
+MutationDeleteStatement::MutationDeleteStatement(uint32_t statement_id) {
+    message_.set_statement_id(statement_id);
+}
+
+bool MutationDeleteStatement::IsApplicable(const tint::Program& program,
+                                           const NodeIdMap& node_id_map) const {
+    auto* statement_node = tint::As<ast::Statement>(node_id_map.GetNode(message_.statement_id()));
+
+    if (!statement_node) {
+        // The statement id is invalid or does not refer to a statement.
+        return false;
+    }
+
+    const auto* statement_sem_node = tint::As<sem::Statement>(program.Sem().Get(statement_node));
+
+    if (!statement_sem_node) {
+        // Semantic information for the statement is not available. This
+        // information is required in order to perform the deletion.
+        return false;
+    }
+
+    // Check whether it is OK to delete this statement.
+    if (!CanBeDeleted(*statement_node, program, JumpTracker(program))) {
+        return false;
+    }
+
+    return true;
+}
+
+void MutationDeleteStatement::Apply(const NodeIdMap& node_id_map,
+                                    tint::CloneContext* clone_context,
+                                    NodeIdMap* /* unused */) const {
+    const auto* statement_node =
+        tint::As<ast::Statement>(node_id_map.GetNode(message_.statement_id()));
+    const auto* statement_sem_node =
+        tint::As<sem::Statement>(clone_context->src->Sem().Get(statement_node));
+    const auto* sem_parent = statement_sem_node->Parent();
+
+    if (tint::Is<sem::IfStatement>(sem_parent) &&
+        tint::As<ast::IfStatement>(sem_parent->Declaration())->else_statement == statement_node) {
+        // Remove the "else" part of an if statement.
+        clone_context->Replace(statement_node, static_cast<const ast::Statement*>(nullptr));
+    } else if (tint::Is<sem::ForLoopStatement>(sem_parent) &&
+               tint::As<ast::ForLoopStatement>(sem_parent->Declaration())->initializer ==
+                   statement_node) {
+        // Remove the initializer of a for loop.
+        clone_context->Replace(statement_node, static_cast<const ast::Statement*>(nullptr));
+    } else if (tint::Is<sem::ForLoopStatement>(sem_parent) &&
+               tint::As<ast::ForLoopStatement>(sem_parent->Declaration())->continuing ==
+                   statement_node) {
+        // Remove the "continuing" statement of a for loop.
+        clone_context->Replace(statement_node, static_cast<const ast::Statement*>(nullptr));
+    } else if (tint::Is<sem::LoopContinuingBlockStatement>(statement_sem_node)) {
+        // Remove the "continuing" block of a loop.
+        clone_context->Replace(statement_node, static_cast<const ast::Statement*>(nullptr));
+    } else if (tint::Is<ast::CaseStatement>(statement_node)) {
+        // Remove a case statement from its enclosing switch statement.
+        const auto& case_statement_list =
+            &sem_parent->Declaration()->As<ast::SwitchStatement>()->body;
+        assert(std::find(case_statement_list->begin(), case_statement_list->end(),
+                         statement_node) != case_statement_list->end() &&
+               "Statement not found.");
+        clone_context->Remove(*case_statement_list, statement_node);
+    } else if (tint::Is<ast::BlockStatement>(statement_node)) {
+        // Remove a block statement from the block that encloses it. A special case is required for
+        // this, since a sem::Block has itself as its associated sem::Block, so it is necessary to
+        // look at the parent to get the enclosing block.
+        const auto& statement_list =
+            sem_parent->Declaration()->As<ast::BlockStatement>()->statements;
+        assert(std::find(statement_list.begin(), statement_list.end(), statement_node) !=
+                   statement_list.end() &&
+               "Statement not found.");
+        clone_context->Remove(statement_list, statement_node);
+    } else {
+        // Remove a non-block statement from the block that encloses it.
+        const auto& statement_list =
+            statement_sem_node->Block()->Declaration()->As<ast::BlockStatement>()->statements;
+        assert(std::find(statement_list.begin(), statement_list.end(), statement_node) !=
+                   statement_list.end() &&
+               "Statement not found.");
+        clone_context->Remove(statement_list, statement_node);
+    }
+}
+
+protobufs::Mutation MutationDeleteStatement::ToMessage() const {
+    protobufs::Mutation mutation;
+    *mutation.mutable_delete_statement() = message_;
+    return mutation;
+}
+
+bool MutationDeleteStatement::CanBeDeleted(const ast::Statement& statement_node,
+                                           const Program& program,
+                                           const JumpTracker& jump_tracker) {
+    if (statement_node.Is<ast::VariableDeclStatement>()) {
+        // This is conservative. It would be possible to delete variable declarations if they are
+        // not used. Further analysis could allow that.
+        return false;
+    }
+
+    if (jump_tracker.ContainsReturn(statement_node) ||
+        jump_tracker.ContainsIntraproceduralDiscard(statement_node)) {
+        // This is conservative. It would be possible to delete a return/discard statement as long
+        // as there is still a return/discard on every control flow path.
+        return false;
+    }
+
+    if (jump_tracker.ContainsBreakForInnermostLoop(statement_node)) {
+        // This is conservative. Disallowing the removal of breaks ensures that loops cannot become
+        // statically infinite. However, a loop might in practice have multiple breaks, some of
+        // which can be removed.
+        return false;
+    }
+
+    if (auto* case_statement = statement_node.As<ast::CaseStatement>()) {
+        // It is not OK to delete the final case statement in a switch statement if the penultimate
+        // case statement falls through to the final case statement.
+        auto* switch_statement =
+            program.Sem().Get(case_statement)->Parent()->Declaration()->As<ast::SwitchStatement>();
+
+        if (switch_statement->body.Length() > 1 &&
+            switch_statement->body[switch_statement->body.Length() - 1] == case_statement) {
+            // There are at least two case statements, and this is the final case statement.
+            auto& penultimate_case_statement_body_statements =
+                switch_statement->body[switch_statement->body.Length() - 2]->body->statements;
+            if (penultimate_case_statement_body_statements.Length() > 0 &&
+                penultimate_case_statement_body_statements
+                    [penultimate_case_statement_body_statements.Length() - 1]
+                        ->Is<ast::FallthroughStatement>()) {
+                // The penultimate case statement falls through to the final case statement, thus
+                // the final case statement cannot be removed.
+                return false;
+            }
+        }
+    }
+
+    auto* parent_sem = program.Sem().Get(&statement_node)->Parent();
+    if (parent_sem == nullptr) {
+        // Semantic information for the parent node is required.
+        return false;
+    }
+
+    auto* parent_stmt = parent_sem->Declaration();
+
+    // It does not make sense to delete the entire body of a loop or if statement.
+    if (auto* for_loop = parent_stmt->As<ast::ForLoopStatement>()) {
+        if (for_loop->body == &statement_node) {
+            return false;
+        }
+    }
+    if (auto* loop = parent_stmt->As<ast::LoopStatement>()) {
+        if (loop->body == &statement_node) {
+            return false;
+        }
+    }
+    if (auto* while_loop = parent_stmt->As<ast::WhileStatement>()) {
+        if (while_loop->body == &statement_node) {
+            return false;
+        }
+    }
+    if (auto* if_statement = parent_stmt->As<ast::IfStatement>()) {
+        if (if_statement->body == &statement_node) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+}  // namespace tint::fuzzers::ast_fuzzer
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.h b/src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.h
new file mode 100644
index 0000000..c7c0192
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.h
@@ -0,0 +1,75 @@
+// Copyright 2021 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.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_DELETE_STATEMENT_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_DELETE_STATEMENT_H_
+
+#include "src/tint/ast/statement.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h"
+
+namespace tint::fuzzers::ast_fuzzer {
+
+/// @see MutationDeleteStatement::Apply
+class MutationDeleteStatement : public Mutation {
+  public:
+    /// @brief Constructs an instance of this mutation from a protobuf message.
+    /// @param message - protobuf message
+    explicit MutationDeleteStatement(protobufs::MutationDeleteStatement message);
+
+    /// @brief Constructor.
+    /// @param statement_id - the id of the statement to delete.
+    explicit MutationDeleteStatement(uint32_t statement_id);
+
+    /// @copybrief Mutation::IsApplicable
+    ///
+    /// The mutation is applicable iff:
+    /// - `statement_id` corresponds to a statement in the AST.
+    /// - `statement_id` does not refer to a variable declaration, since the declared variables will
+    ///   be inaccessible if the statement is deleted.
+    /// - `statement_id` is not a return statement, since removing return statements arbitrarily can
+    ///   make the program invalid.
+    /// - `statement_id` is not a break statement, since removing break statements can lead to
+    ///   syntactically infinite loops.
+    ///
+    /// @copydetails Mutation::IsApplicable
+    bool IsApplicable(const tint::Program& program, const NodeIdMap& node_id_map) const override;
+
+    /// @copybrief Mutation::Apply
+    ///
+    /// Delete the statement referenced by `statement_id`.
+    ///
+    /// @copydetails Mutation::Apply
+    void Apply(const NodeIdMap& node_id_map,
+               tint::CloneContext* clone_context,
+               NodeIdMap* new_node_id_map) const override;
+
+    protobufs::Mutation ToMessage() const override;
+
+    /// Return whether the given statement is suitable for deletion.
+    /// @param statement_node - the statement to be considered for deletion.
+    /// @param program - the program containing  the statement.
+    /// @param jump_tracker - information about jump statements for the program.
+    /// @return true if and only if it is OK to delete the statement.
+    static bool CanBeDeleted(const ast::Statement& statement_node,
+                             const Program& program,
+                             const JumpTracker& jump_tracker);
+
+  private:
+    protobufs::MutationDeleteStatement message_;
+};
+
+}  // namespace tint::fuzzers::ast_fuzzer
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_DELETE_STATEMENT_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement_test.cc
new file mode 100644
index 0000000..925ee80
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement_test.cc
@@ -0,0 +1,750 @@
+// Copyright 2021 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/fuzzers/tint_ast_fuzzer/mutations/delete_statement.h"
+
+#include <functional>
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/block_statement.h"
+#include "src/tint/ast/case_statement.h"
+#include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/for_loop_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutator.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/probability_context.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/reader/wgsl/parser.h"
+#include "src/tint/writer/wgsl/generator.h"
+
+namespace tint::fuzzers::ast_fuzzer {
+namespace {
+
+void CheckStatementDeletionWorks(
+    const std::string& original,
+    const std::string& expected,
+    const std::function<const ast::Statement*(const Program&)>& statement_finder) {
+    Source::File original_file("original.wgsl", original);
+    auto program = reader::wgsl::Parse(&original_file);
+
+    Source::File expected_file("expected.wgsl", expected);
+    auto expected_program = reader::wgsl::Parse(&expected_file);
+
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(expected_program.IsValid()) << expected_program.Diagnostics().str();
+
+    NodeIdMap node_id_map(program);
+    const auto* statement = statement_finder(program);
+    ASSERT_NE(statement, nullptr);
+    auto statement_id = node_id_map.GetId(statement);
+    ASSERT_NE(statement_id, 0);
+    ASSERT_TRUE(MaybeApplyMutation(program, MutationDeleteStatement(statement_id), node_id_map,
+                                   &program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    writer::wgsl::Options options;
+    auto transformed_result = writer::wgsl::Generate(&program, options);
+    auto expected_result = writer::wgsl::Generate(&expected_program, options);
+    ASSERT_TRUE(transformed_result.success) << transformed_result.error;
+    ASSERT_TRUE(expected_result.success) << expected_result.error;
+    ASSERT_EQ(expected_result.wgsl, transformed_result.wgsl);
+}
+
+void CheckStatementDeletionNotAllowed(
+    const std::string& original,
+    const std::function<const ast::Statement*(const Program&)>& statement_finder) {
+    Source::File original_file("original.wgsl", original);
+    auto program = reader::wgsl::Parse(&original_file);
+
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+    NodeIdMap node_id_map(program);
+    const auto* statement = statement_finder(program);
+    ASSERT_NE(statement, nullptr);
+    auto statement_id = node_id_map.GetId(statement);
+    ASSERT_NE(statement_id, 0);
+    ASSERT_FALSE(MaybeApplyMutation(program, MutationDeleteStatement(statement_id), node_id_map,
+                                    &program, &node_id_map, nullptr));
+}
+
+TEST(DeleteStatementTest, DeleteAssignStatement) {
+    auto original = R"(
+    fn main() {
+      {
+        var a : i32 = 5;
+        a = 6;
+      }
+    })";
+    auto expected = R"(fn main() {
+  {
+    var a : i32 = 5;
+  }
+}
+)";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[0]
+            ->As<ast::BlockStatement>()
+            ->statements[1]
+            ->As<ast::AssignmentStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteForStatement) {
+    auto original =
+        R"(
+    fn main() {
+      for (var i : i32 = 0; i < 10; i++) {
+      }
+    }
+  )";
+    auto expected = "fn main() { }";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[0]->As<ast::ForLoopStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteIfStatement) {
+    auto original =
+        R"(
+    fn main() {
+      if (true) { } else { }
+    }
+  )";
+    auto expected = "fn main() { }";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[0]->As<ast::IfStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteBlockStatement) {
+    auto original = "fn main() { { } }";
+    auto expected = "fn main() { }";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[0]->As<ast::BlockStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteSwitchStatement) {
+    auto original = R"(
+fn main() {
+  switch(1) {
+    case 0, 1: {
+    }
+    default: {
+      fallthrough;
+    }
+    case 2: {
+    }
+  }
+})";
+    auto expected = R"(fn main() { })";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[0]->As<ast::SwitchStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteCaseStatement) {
+    auto original = R"(
+fn main() {
+  switch(1) {
+    case 0, 1: {
+    }
+    default: {
+      fallthrough;
+    }
+    case 2: {
+    }
+  }
+})";
+    auto expected = R"(
+fn main() {
+  switch(1) {
+    default: {
+      fallthrough;
+    }
+    case 2: {
+    }
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[0]
+            ->As<ast::SwitchStatement>()
+            ->body[0]
+            ->As<ast::CaseStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteFallthroughStatement) {
+    auto original = R"(
+fn main() {
+  switch(1) {
+    case 0, 1: {
+    }
+    default: {
+      fallthrough;
+    }
+    case 2: {
+    }
+  }
+})";
+    auto expected = R"(
+fn main() {
+  switch(1) {
+    case 0, 1: {
+    }
+    default: {
+    }
+    case 2: {
+    }
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[0]
+            ->As<ast::SwitchStatement>()
+            ->body[1]
+            ->As<ast::CaseStatement>()
+            ->body->statements[0]
+            ->As<ast::FallthroughStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteElse) {
+    auto original = R"(
+fn main() {
+  if (true) {
+  } else {
+  }
+})";
+    auto expected = R"(
+fn main() {
+  if (true) {
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[0]
+            ->As<ast::IfStatement>()
+            ->else_statement;
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteCall) {
+    auto original = R"(
+fn main() {
+  sin(1.0);
+})";
+    auto expected = R"(
+fn main() {
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[0]->As<ast::CallStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteCompoundAssign) {
+    auto original = R"(
+fn main() {
+  var x : i32 = 0;
+  x += 2;;
+})";
+    auto expected = R"(
+fn main() {
+  var x : i32 = 0;
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[1]
+            ->As<ast::CompoundAssignmentStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteLoop) {
+    auto original = R"(
+fn main() {
+  var x : i32 = 0;
+  loop {
+    if (x > 100) {
+      break;
+    }
+    continuing {
+      x++;
+    }
+  }
+})";
+    auto expected = R"(
+fn main() {
+  var x : i32 = 0;
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[1]->As<ast::LoopStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteContinuingBlock) {
+    auto original = R"(
+fn main() {
+  var x : i32 = 0;
+  loop {
+    if (x > 100) {
+      break;
+    }
+    continuing {
+      x++;
+    }
+  }
+})";
+    auto expected = R"(
+fn main() {
+  var x : i32 = 0;
+  loop {
+    if (x > 100) {
+      break;
+    }
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[1]
+            ->As<ast::LoopStatement>()
+            ->continuing;
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteContinue) {
+    auto original = R"(
+fn main() {
+  var x : i32 = 0;
+  loop {
+    if (x > 100) {
+      break;
+    }
+    continue;
+    continuing {
+      x++;
+    }
+  }
+})";
+    auto expected = R"(
+fn main() {
+  var x : i32 = 0;
+  loop {
+    if (x > 100) {
+      break;
+    }
+    continuing {
+      x++;
+    }
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[1]
+            ->As<ast::LoopStatement>()
+            ->body->statements[1]
+            ->As<ast::ContinueStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteIncrement) {
+    auto original = R"(
+fn main() {
+  var x : i32 = 0;
+  loop {
+    if (x > 100) {
+      break;
+    }
+    continuing {
+      x++;
+    }
+  }
+})";
+    auto expected = R"(
+fn main() {
+  var x : i32 = 0;
+  loop {
+    if (x > 100) {
+      break;
+    }
+    continuing {
+    }
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[1]
+            ->As<ast::LoopStatement>()
+            ->continuing->statements[0]
+            ->As<ast::IncrementDecrementStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteForLoopInitializer) {
+    auto original = R"(
+fn main() {
+  var x : i32;
+  for (x = 0; x < 100; x++) {
+  }
+})";
+    auto expected = R"(
+fn main() {
+  var x : i32;
+  for (; x < 100; x++) {
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[1]
+            ->As<ast::ForLoopStatement>()
+            ->initializer->As<ast::AssignmentStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DeleteForLoopContinuing) {
+    auto original = R"(
+fn main() {
+  var x : i32;
+  for (x = 0; x < 100; x++) {
+  }
+})";
+    auto expected = R"(
+fn main() {
+  var x : i32;
+  for (x = 0; x < 100;) {
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[1]
+            ->As<ast::ForLoopStatement>()
+            ->continuing->As<ast::IncrementDecrementStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, AllowDeletionOfInnerLoopWithBreak) {
+    auto original = R"(
+fn main() {
+  loop {
+    loop {
+      break;
+    }
+    break;
+  }
+})";
+    auto expected = R"(
+fn main() {
+  loop {
+    break;
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[0]
+            ->As<ast::LoopStatement>()
+            ->body->statements[0]
+            ->As<ast::LoopStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, AllowDeletionOfInnerCaseWithBreak) {
+    auto original = R"(
+fn main() {
+  loop {
+    switch(0) {
+      case 1: {
+        break;
+      }
+      default: {
+      }
+    }
+    break;
+  }
+})";
+    auto expected = R"(
+fn main() {
+  loop {
+    switch(0) {
+      default: {
+      }
+    }
+    break;
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[0]
+            ->As<ast::LoopStatement>()
+            ->body->statements[0]
+            ->As<ast::SwitchStatement>()
+            ->body[0];
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, AllowDeletionOfBreakFromSwitch) {
+    auto original = R"(
+fn main() {
+  switch(0) {
+    case 1: {
+      break;
+    }
+    default: {
+    }
+  }
+})";
+    auto expected = R"(
+fn main() {
+  switch(0) {
+    case 1: {
+    }
+    default: {
+    }
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[0]
+            ->As<ast::SwitchStatement>()
+            ->body[0]
+            ->body->statements[0]
+            ->As<ast::BreakStatement>();
+    };
+    CheckStatementDeletionWorks(original, expected, statement_finder);
+}
+
+TEST(DeleteStatementTest, DoNotDeleteVariableDeclaration) {
+    auto original = R"(
+fn main() {
+  var x : i32;
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[0]->As<ast::VariableDeclStatement>();
+    };
+    CheckStatementDeletionNotAllowed(original, statement_finder);
+}
+
+TEST(DeleteStatementTest, DoNotDeleteCaseDueToFallthrough) {
+    auto original = R"(
+fn main() {
+  switch(1) {
+    default: {
+      fallthrough;
+    }
+    case 2: {
+    }
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[0]
+            ->As<ast::SwitchStatement>()
+            ->body[1]
+            ->As<ast::CaseStatement>();
+    };
+    CheckStatementDeletionNotAllowed(original, statement_finder);
+}
+
+TEST(DeleteStatementTest, DoNotMakeLoopInfinite1) {
+    auto original = R"(
+fn main() {
+  loop {
+    break;
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[0]
+            ->As<ast::LoopStatement>()
+            ->body->statements[0]
+            ->As<ast::BreakStatement>();
+    };
+    CheckStatementDeletionNotAllowed(original, statement_finder);
+}
+
+TEST(DeleteStatementTest, DoNotMakeLoopInfinite2) {
+    auto original = R"(
+fn main() {
+  loop {
+    if (true) {
+      break;
+    }
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST()
+            .Functions()[0]
+            ->body->statements[0]
+            ->As<ast::LoopStatement>()
+            ->body->statements[0]
+            ->As<ast::IfStatement>();
+    };
+    CheckStatementDeletionNotAllowed(original, statement_finder);
+}
+
+TEST(DeleteStatementTest, DoNotRemoveReturn) {
+    auto original = R"(
+fn main() {
+  return;
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[0]->As<ast::ReturnStatement>();
+    };
+    CheckStatementDeletionNotAllowed(original, statement_finder);
+}
+
+TEST(DeleteStatementTest, DoNotRemoveStatementContainingReturn) {
+    auto original = R"(
+fn foo() -> i32 {
+  if (true) {
+    return 1;
+  } else {
+    return 2;
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[0]->As<ast::IfStatement>();
+    };
+    CheckStatementDeletionNotAllowed(original, statement_finder);
+}
+
+TEST(DeleteStatementTest, DoNotRemoveDiscard) {
+    auto original = R"(
+fn main() {
+  discard;
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[0]->As<ast::DiscardStatement>();
+    };
+    CheckStatementDeletionNotAllowed(original, statement_finder);
+}
+
+TEST(DeleteStatementTest, DoNotRemoveStatementContainingDiscard) {
+    auto original = R"(
+fn foo() -> i32 {
+  if (true) {
+    discard;
+  } else {
+    discard;
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[0]->As<ast::IfStatement>();
+    };
+    CheckStatementDeletionNotAllowed(original, statement_finder);
+}
+
+TEST(DeleteStatementTest, DoNotRemoveLoopBody) {
+    auto original = R"(
+fn foo() {
+  discard;
+}
+fn main() {
+  loop {
+    foo();
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[1]->body->statements[0]->As<ast::LoopStatement>()->body;
+    };
+    CheckStatementDeletionNotAllowed(original, statement_finder);
+}
+
+TEST(DeleteStatementTest, DoNotRemoveForLoopBody) {
+    auto original = R"(
+fn main() {
+  for(var i : i32 = 0; i < 10; i++) {
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[0]->As<ast::ForLoopStatement>()->body;
+    };
+    CheckStatementDeletionNotAllowed(original, statement_finder);
+}
+
+TEST(DeleteStatementTest, DoNotRemoveWhileBody) {
+    auto original = R"(
+fn main() {
+  var i : i32 = 0;
+  while(i < 10) {
+    i++;
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[1]->As<ast::WhileStatement>()->body;
+    };
+    CheckStatementDeletionNotAllowed(original, statement_finder);
+}
+
+TEST(DeleteStatementTest, DoNotRemoveIfBody) {
+    auto original = R"(
+fn main() {
+  if(true) {
+  }
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body->statements[0]->As<ast::IfStatement>()->body;
+    };
+    CheckStatementDeletionNotAllowed(original, statement_finder);
+}
+
+TEST(DeleteStatementTest, DoNotRemoveFunctionBody) {
+    auto original = R"(
+fn main() {
+})";
+    auto statement_finder = [](const Program& program) -> const ast::Statement* {
+        return program.AST().Functions()[0]->body;
+    };
+    CheckStatementDeletionNotAllowed(original, statement_finder);
+}
+
+}  // namespace
+}  // namespace tint::fuzzers::ast_fuzzer
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
index f70a947..7153f0c 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
@@ -22,6 +22,7 @@
 
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
@@ -48,6 +49,8 @@
                                                             probability_context, &result);
         MaybeAddFinder<MutationFinderChangeUnaryOperators>(enable_all_mutations,
                                                            probability_context, &result);
+        MaybeAddFinder<MutationFinderDeleteStatements>(enable_all_mutations, probability_context,
+                                                       &result);
         MaybeAddFinder<MutationFinderReplaceIdentifiers>(enable_all_mutations, probability_context,
                                                          &result);
         MaybeAddFinder<MutationFinderWrapUnaryOperators>(enable_all_mutations, probability_context,
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc
index 639bd60..f2cb159 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc
@@ -21,6 +21,7 @@
 
 const std::pair<uint32_t, uint32_t> kChanceOfChangingBinaryOperators = {30, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfChangingUnaryOperators = {30, 70};
+const std::pair<uint32_t, uint32_t> kChanceOfDeletingStatements = {30, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdentifiers = {30, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfWrappingUnaryOperators = {30, 70};
 
@@ -30,6 +31,7 @@
     : generator_(generator),
       chance_of_changing_binary_operators_(RandomFromRange(kChanceOfChangingBinaryOperators)),
       chance_of_changing_unary_operators_(RandomFromRange(kChanceOfChangingUnaryOperators)),
+      chance_of_deleting_statements_(RandomFromRange(kChanceOfDeletingStatements)),
       chance_of_replacing_identifiers_(RandomFromRange(kChanceOfReplacingIdentifiers)),
       chance_of_wrapping_unary_operators_(RandomFromRange(kChanceOfWrappingUnaryOperators)) {
     assert(generator != nullptr && "generator must not be nullptr");
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h
index ce46738..410d7cf 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h
+++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h
@@ -61,6 +61,9 @@
         return chance_of_changing_unary_operators_;
     }
 
+    /// @return the probability of changing operator for a binary expression.
+    uint32_t GetChanceOfDeletingStatements() const { return chance_of_deleting_statements_; }
+
     /// @return the probability of replacing some identifier with some other one.
     uint32_t GetChanceOfReplacingIdentifiers() const { return chance_of_replacing_identifiers_; }
 
@@ -78,6 +81,7 @@
 
     uint32_t chance_of_changing_binary_operators_;
     uint32_t chance_of_changing_unary_operators_;
+    uint32_t chance_of_deleting_statements_;
     uint32_t chance_of_replacing_identifiers_;
     uint32_t chance_of_wrapping_unary_operators_;
 };
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto
index fd3cd81..b1fa5e3 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto
+++ b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto
@@ -22,6 +22,7 @@
     MutationChangeBinaryOperator change_binary_operator = 2;
     MutationWrapUnaryOperator wrap_unary_operator = 3;
     MutationChangeUnaryOperator change_unary_operator = 4;
+    MutationDeleteStatement delete_statement = 5;
   };
 }
 
@@ -64,6 +65,14 @@
     uint32 new_operator = 2;
 }
 
+message MutationDeleteStatement {
+    // This transformation deletes a statement, as long as doing so does not
+    // invalidate the program.
+
+    // The id of a statement to be deleted.
+    uint32 statement_id = 1;
+}
+
 message MutationReplaceIdentifier {
     // This transformation replaces a use of one variable with another.
 
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/fuzzer.cc b/src/tint/fuzzers/tint_regex_fuzzer/fuzzer.cc
index 9ffea24..f17987d 100644
--- a/src/tint/fuzzers/tint_regex_fuzzer/fuzzer.cc
+++ b/src/tint/fuzzers/tint_regex_fuzzer/fuzzer.cc
@@ -24,6 +24,7 @@
 #include "src/tint/fuzzers/transform_builder.h"
 #include "src/tint/reader/wgsl/parser.h"
 #include "src/tint/writer/wgsl/generator.h"
+#include "testing/libfuzzer/libfuzzer_exports.h"
 
 namespace tint::fuzzers::regex_fuzzer {
 namespace {
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc
index c35e0ce..41e417b 100644
--- a/src/tint/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc
+++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc
@@ -27,6 +27,7 @@
 #include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h"
 #include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h"
 #include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h"
+#include "testing/libfuzzer/libfuzzer_exports.h"
 
 namespace tint::fuzzers::spvtools_fuzzer {
 namespace {
diff --git a/src/tint/inspector/inspector.cc b/src/tint/inspector/inspector.cc
index ae56de5..e9c4d88 100644
--- a/src/tint/inspector/inspector.cc
+++ b/src/tint/inspector/inspector.cc
@@ -107,7 +107,7 @@
 
 std::tuple<InterpolationType, InterpolationSampling> CalculateInterpolationData(
     const sem::Type* type,
-    const ast::AttributeList& attributes) {
+    utils::VectorRef<const ast::Attribute*> attributes) {
     auto* interpolation_attribute = ast::GetAttribute<ast::InterpolateAttribute>(attributes);
     if (type->is_integer_scalar_or_vector()) {
         return {InterpolationType::kFlat, InterpolationSampling::kNone};
@@ -608,7 +608,7 @@
 
 void Inspector::AddEntryPointInOutVariables(std::string name,
                                             const sem::Type* type,
-                                            const ast::AttributeList& attributes,
+                                            utils::VectorRef<const ast::Attribute*> attributes,
                                             std::vector<StageVariable>& variables) const {
     // Skip builtins.
     if (ast::HasAttribute<ast::BuiltinAttribute>(attributes)) {
@@ -647,7 +647,7 @@
 
 bool Inspector::ContainsBuiltin(ast::BuiltinValue builtin,
                                 const sem::Type* type,
-                                const ast::AttributeList& attributes) const {
+                                utils::VectorRef<const ast::Attribute*> attributes) const {
     auto* unwrapped_type = type->UnwrapRef();
 
     if (auto* struct_ty = unwrapped_type->As<sem::Struct>()) {
diff --git a/src/tint/inspector/inspector.h b/src/tint/inspector/inspector.h
index 2690581..97707db 100644
--- a/src/tint/inspector/inspector.h
+++ b/src/tint/inspector/inspector.h
@@ -170,7 +170,7 @@
     /// @param variables the list to add the variables to
     void AddEntryPointInOutVariables(std::string name,
                                      const sem::Type* type,
-                                     const ast::AttributeList& attributes,
+                                     utils::VectorRef<const ast::Attribute*> attributes,
                                      std::vector<StageVariable>& variables) const;
 
     /// Recursively determine if the type contains builtin.
@@ -178,7 +178,7 @@
     /// Otherwise, check `attributes` for the attribute.
     bool ContainsBuiltin(ast::BuiltinValue builtin,
                          const sem::Type* type,
-                         const ast::AttributeList& attributes) const;
+                         utils::VectorRef<const ast::Attribute*> attributes) const;
 
     /// Gathers all the texture resource bindings of the given type for the given
     /// entry point.
diff --git a/src/tint/inspector/inspector_test.cc b/src/tint/inspector/inspector_test.cc
index 2bb862d..543239c 100644
--- a/src/tint/inspector/inspector_test.cc
+++ b/src/tint/inspector/inspector_test.cc
@@ -154,7 +154,7 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, OneEntryPoint) {
-    MakeEmptyBodyFunction("foo", {
+    MakeEmptyBodyFunction("foo", utils::Vector{
                                      Stage(ast::PipelineStage::kFragment),
                                  });
 
@@ -172,11 +172,11 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, MultipleEntryPoints) {
-    MakeEmptyBodyFunction("foo", {
+    MakeEmptyBodyFunction("foo", utils::Vector{
                                      Stage(ast::PipelineStage::kFragment),
                                  });
 
-    MakeEmptyBodyFunction("bar", {
+    MakeEmptyBodyFunction("bar", utils::Vector{
                                      Stage(ast::PipelineStage::kCompute),
                                      WorkgroupSize(1_i),
                                  });
@@ -198,16 +198,16 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, MixFunctionsAndEntryPoints) {
-    MakeEmptyBodyFunction("func", {});
+    MakeEmptyBodyFunction("func", utils::Empty);
 
-    MakeCallerBodyFunction("foo", {"func"},
-                           {
+    MakeCallerBodyFunction("foo", utils::Vector{std::string("func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kCompute),
                                WorkgroupSize(1_i),
                            });
 
-    MakeCallerBodyFunction("bar", {"func"},
-                           {
+    MakeCallerBodyFunction("bar", utils::Vector{std::string("func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -228,7 +228,7 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, DefaultWorkgroupSize) {
-    MakeEmptyBodyFunction("foo", {
+    MakeEmptyBodyFunction("foo", utils::Vector{
                                      Stage(ast::PipelineStage::kCompute),
                                      WorkgroupSize(8_i, 2_i, 1_i),
                                  });
@@ -247,7 +247,7 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, NonDefaultWorkgroupSize) {
-    MakeEmptyBodyFunction("foo", {
+    MakeEmptyBodyFunction("foo", utils::Vector{
                                      Stage(ast::PipelineStage::kCompute),
                                      WorkgroupSize(8_i, 2_i, 1_i),
                                  });
@@ -266,10 +266,10 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, NoInOutVariables) {
-    MakeEmptyBodyFunction("func", {});
+    MakeEmptyBodyFunction("func", utils::Empty);
 
-    MakeCallerBodyFunction("foo", {"func"},
-                           {
+    MakeCallerBodyFunction("foo", utils::Vector{std::string("func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -289,15 +289,19 @@
     std::tie(component, composition) = GetParam();
     std::function<const ast::Type*()> tint_type = GetTypeFunction(component, composition);
 
-    auto* in_var = Param("in_var", tint_type(), {Location(0u), Flat()});
-    Func("foo", {in_var}, tint_type(),
-         {
+    auto* in_var = Param("in_var", tint_type(),
+                         utils::Vector{
+                             Location(0u),
+                             Flat(),
+                         });
+    Func("foo", utils::Vector{in_var}, tint_type(),
+         utils::Vector{
              Return("in_var"),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(0u),
          });
     Inspector& inspector = Build();
@@ -330,17 +334,29 @@
                                                           CompositionType::kVec4)));
 
 TEST_F(InspectorGetEntryPointTest, MultipleInOutVariables) {
-    auto* in_var0 = Param("in_var0", ty.u32(), {Location(0u), Flat()});
-    auto* in_var1 = Param("in_var1", ty.u32(), {Location(1u), Flat()});
-    auto* in_var4 = Param("in_var4", ty.u32(), {Location(4u), Flat()});
-    Func("foo", {in_var0, in_var1, in_var4}, ty.u32(),
-         {
+    auto* in_var0 = Param("in_var0", ty.u32(),
+                          utils::Vector{
+                              Location(0u),
+                              Flat(),
+                          });
+    auto* in_var1 = Param("in_var1", ty.u32(),
+                          utils::Vector{
+                              Location(1u),
+                              Flat(),
+                          });
+    auto* in_var4 = Param("in_var4", ty.u32(),
+                          utils::Vector{
+                              Location(4u),
+                              Flat(),
+                          });
+    Func("foo", utils::Vector{in_var0, in_var1, in_var4}, ty.u32(),
+         utils::Vector{
              Return("in_var0"),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(0u),
          });
     Inspector& inspector = Build();
@@ -375,27 +391,35 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutVariables) {
-    auto* in_var_foo = Param("in_var_foo", ty.u32(), {Location(0u), Flat()});
-    Func("foo", {in_var_foo}, ty.u32(),
-         {
+    auto* in_var_foo = Param("in_var_foo", ty.u32(),
+                             utils::Vector{
+                                 Location(0u),
+                                 Flat(),
+                             });
+    Func("foo", utils::Vector{in_var_foo}, ty.u32(),
+         utils::Vector{
              Return("in_var_foo"),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(0u),
          });
 
-    auto* in_var_bar = Param("in_var_bar", ty.u32(), {Location(0u), Flat()});
-    Func("bar", {in_var_bar}, ty.u32(),
-         {
+    auto* in_var_bar = Param("in_var_bar", ty.u32(),
+                             utils::Vector{
+                                 Location(0u),
+                                 Flat(),
+                             });
+    Func("bar", utils::Vector{in_var_bar}, ty.u32(),
+         utils::Vector{
              Return("in_var_bar"),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(1u),
          });
 
@@ -434,16 +458,22 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, BuiltInsNotStageVariables) {
-    auto* in_var0 = Param("in_var0", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)});
-    auto* in_var1 = Param("in_var1", ty.f32(), {Location(0u)});
-    Func("foo", {in_var0, in_var1}, ty.f32(),
-         {
+    auto* in_var0 = Param("in_var0", ty.u32(),
+                          utils::Vector{
+                              Builtin(ast::BuiltinValue::kSampleIndex),
+                          });
+    auto* in_var1 = Param("in_var1", ty.f32(),
+                          utils::Vector{
+                              Location(0u),
+                          });
+    Func("foo", utils::Vector{in_var0, in_var1}, ty.f32(),
+         utils::Vector{
              Return("in_var1"),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Builtin(ast::BuiltinValue::kFragDepth),
          });
     Inspector& inspector = Build();
@@ -463,16 +493,19 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, InOutStruct) {
-    auto* interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}});
+    auto* interface = MakeInOutStruct("interface", utils::Vector{
+                                                       InOutInfo{"a", 0u},
+                                                       InOutInfo{"b", 1u},
+                                                   });
     Func("foo",
-         {
+         utils::Vector{
              Param("param", ty.Of(interface)),
          },
          ty.Of(interface),
-         {
+         utils::Vector{
              Return("param"),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
     Inspector& inspector = Build();
@@ -504,16 +537,19 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutSharedStruct) {
-    auto* interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}});
-    Func("foo", {}, ty.Of(interface),
-         {
+    auto* interface = MakeInOutStruct("interface", utils::Vector{
+                                                       InOutInfo{"a", 0u},
+                                                       InOutInfo{"b", 1u},
+                                                   });
+    Func("foo", utils::Empty, ty.Of(interface),
+         utils::Vector{
              Return(Construct(ty.Of(interface))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
-    Func("bar", {Param("param", ty.Of(interface))}, ty.void_(), {},
-         {
+    Func("bar", utils::Vector{Param("param", ty.Of(interface))}, ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
     Inspector& inspector = Build();
@@ -549,20 +585,25 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, MixInOutVariablesAndStruct) {
-    auto* struct_a = MakeInOutStruct("struct_a", {{"a", 0u}, {"b", 1u}});
-    auto* struct_b = MakeInOutStruct("struct_b", {{"a", 2u}});
+    auto* struct_a = MakeInOutStruct("struct_a", utils::Vector{
+                                                     InOutInfo{"a", 0u},
+                                                     InOutInfo{"b", 1u},
+                                                 });
+    auto* struct_b = MakeInOutStruct("struct_b", utils::Vector{
+                                                     InOutInfo{"a", 2u},
+                                                 });
     Func("foo",
-         {
+         utils::Vector{
              Param("param_a", ty.Of(struct_a)),
              Param("param_b", ty.Of(struct_b)),
-             Param("param_c", ty.f32(), {Location(3u)}),
-             Param("param_d", ty.f32(), {Location(4u)}),
+             Param("param_c", ty.f32(), utils::Vector{Location(3u)}),
+             Param("param_d", ty.f32(), utils::Vector{Location(4u)}),
          },
          ty.Of(struct_a),
-         {
+         utils::Vector{
              Return("param_a"),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
     Inspector& inspector = Build();
@@ -607,7 +648,7 @@
 
 TEST_F(InspectorGetEntryPointTest, OverrideUnreferenced) {
     Override("foo", ty.f32(), nullptr);
-    MakeEmptyBodyFunction("ep_func", {
+    MakeEmptyBodyFunction("ep_func", utils::Vector{
                                          Stage(ast::PipelineStage::kCompute),
                                          WorkgroupSize(1_i),
                                      });
@@ -623,7 +664,7 @@
 TEST_F(InspectorGetEntryPointTest, OverrideReferencedByEntryPoint) {
     Override("foo", ty.f32(), nullptr);
     MakePlainGlobalReferenceBodyFunction("ep_func", "foo", ty.f32(),
-                                         {
+                                         utils::Vector{
                                              Stage(ast::PipelineStage::kCompute),
                                              WorkgroupSize(1_i),
                                          });
@@ -639,9 +680,9 @@
 
 TEST_F(InspectorGetEntryPointTest, OverrideReferencedByCallee) {
     Override("foo", ty.f32(), nullptr);
-    MakePlainGlobalReferenceBodyFunction("callee_func", "foo", ty.f32(), {});
-    MakeCallerBodyFunction("ep_func", {"callee_func"},
-                           {
+    MakePlainGlobalReferenceBodyFunction("callee_func", "foo", ty.f32(), utils::Empty);
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("callee_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kCompute),
                                WorkgroupSize(1_i),
                            });
@@ -656,11 +697,17 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, OverrideSomeReferenced) {
-    Override("foo", ty.f32(), nullptr, {Id(1)});
-    Override("bar", ty.f32(), nullptr, {Id(2)});
-    MakePlainGlobalReferenceBodyFunction("callee_func", "foo", ty.f32(), {});
-    MakeCallerBodyFunction("ep_func", {"callee_func"},
-                           {
+    Override("foo", ty.f32(), nullptr,
+             utils::Vector{
+                 Id(1),
+             });
+    Override("bar", ty.f32(), nullptr,
+             utils::Vector{
+                 Id(2),
+             });
+    MakePlainGlobalReferenceBodyFunction("callee_func", "foo", ty.f32(), utils::Empty);
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("callee_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kCompute),
                                WorkgroupSize(1_i),
                            });
@@ -681,16 +728,17 @@
     Override("u32_var", ty.u32(), nullptr);
     Override("i32_var", ty.i32(), nullptr);
 
-    MakePlainGlobalReferenceBodyFunction("bool_func", "bool_var", ty.bool_(), {});
-    MakePlainGlobalReferenceBodyFunction("float_func", "float_var", ty.f32(), {});
-    MakePlainGlobalReferenceBodyFunction("u32_func", "u32_var", ty.u32(), {});
-    MakePlainGlobalReferenceBodyFunction("i32_func", "i32_var", ty.i32(), {});
+    MakePlainGlobalReferenceBodyFunction("bool_func", "bool_var", ty.bool_(), utils::Empty);
+    MakePlainGlobalReferenceBodyFunction("float_func", "float_var", ty.f32(), utils::Empty);
+    MakePlainGlobalReferenceBodyFunction("u32_func", "u32_var", ty.u32(), utils::Empty);
+    MakePlainGlobalReferenceBodyFunction("i32_func", "i32_var", ty.i32(), utils::Empty);
 
-    MakeCallerBodyFunction("ep_func", {"bool_func", "float_func", "u32_func", "i32_func"},
-                           {
-                               Stage(ast::PipelineStage::kCompute),
-                               WorkgroupSize(1_i),
-                           });
+    MakeCallerBodyFunction(
+        "ep_func", utils::Vector{std::string("bool_func"), "float_func", "u32_func", "i32_func"},
+        utils::Vector{
+            Stage(ast::PipelineStage::kCompute),
+            WorkgroupSize(1_i),
+        });
 
     Inspector& inspector = Build();
 
@@ -711,7 +759,7 @@
 TEST_F(InspectorGetEntryPointTest, OverrideInitialized) {
     Override("foo", ty.f32(), Expr(0_f));
     MakePlainGlobalReferenceBodyFunction("ep_func", "foo", ty.f32(),
-                                         {
+                                         utils::Vector{
                                              Stage(ast::PipelineStage::kCompute),
                                              WorkgroupSize(1_i),
                                          });
@@ -729,7 +777,7 @@
 TEST_F(InspectorGetEntryPointTest, OverrideUninitialized) {
     Override("foo", ty.f32(), nullptr);
     MakePlainGlobalReferenceBodyFunction("ep_func", "foo", ty.f32(),
-                                         {
+                                         utils::Vector{
                                              Stage(ast::PipelineStage::kCompute),
                                              WorkgroupSize(1_i),
                                          });
@@ -747,13 +795,16 @@
 
 TEST_F(InspectorGetEntryPointTest, OverrideNumericIDSpecified) {
     Override("foo_no_id", ty.f32(), nullptr);
-    Override("foo_id", ty.f32(), nullptr, {Id(1234)});
+    Override("foo_id", ty.f32(), nullptr,
+             utils::Vector{
+                 Id(1234),
+             });
 
-    MakePlainGlobalReferenceBodyFunction("no_id_func", "foo_no_id", ty.f32(), {});
-    MakePlainGlobalReferenceBodyFunction("id_func", "foo_id", ty.f32(), {});
+    MakePlainGlobalReferenceBodyFunction("no_id_func", "foo_no_id", ty.f32(), utils::Empty);
+    MakePlainGlobalReferenceBodyFunction("id_func", "foo_id", ty.f32(), utils::Empty);
 
-    MakeCallerBodyFunction("ep_func", {"no_id_func", "id_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("no_id_func"), "id_func"},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kCompute),
                                WorkgroupSize(1_i),
                            });
@@ -773,11 +824,16 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, NonOverrideSkipped) {
-    auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()});
+    auto* foo_struct_type = MakeUniformBufferType("foo_type", utils::Vector{
+                                                                  ty.i32(),
+                                                              });
     AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
-    MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
-    MakeCallerBodyFunction("ep_func", {"ub_func"},
-                           {
+    MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("ub_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -790,7 +846,7 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, BuiltinNotReferenced) {
-    MakeEmptyBodyFunction("ep_func", {
+    MakeEmptyBodyFunction("ep_func", utils::Vector{
                                          Stage(ast::PipelineStage::kFragment),
                                      });
 
@@ -808,12 +864,15 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, InputSampleMaskSimpleReferenced) {
-    auto* in_var = Param("in_var", ty.u32(), {Builtin(ast::BuiltinValue::kSampleMask)});
-    Func("ep_func", {in_var}, ty.void_(),
-         {
+    auto* in_var = Param("in_var", ty.u32(),
+                         utils::Vector{
+                             Builtin(ast::BuiltinValue::kSampleMask),
+                         });
+    Func("ep_func", utils::Vector{in_var}, ty.void_(),
+         utils::Vector{
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -826,20 +885,21 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, InputSampleMaskStructReferenced) {
-    ast::StructMemberList members;
-    members.push_back(
-        Member("inner_position", ty.u32(), {Builtin(ast::BuiltinValue::kSampleMask)}));
+    utils::Vector members{
+        Member("inner_position", ty.u32(), utils::Vector{Builtin(ast::BuiltinValue::kSampleMask)}),
+    };
+
     Structure("in_struct", members);
 
     Func("ep_func",
-         {
-             Param("in_var", ty.type_name("in_struct"), {}),
+         utils::Vector{
+             Param("in_var", ty.type_name("in_struct"), utils::Empty),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -853,17 +913,19 @@
 
 TEST_F(InspectorGetEntryPointTest, OutputSampleMaskSimpleReferenced) {
     Func("ep_func",
-         {
-             Param("in_var", ty.u32(), {Builtin(ast::BuiltinValue::kSampleMask)}),
+         utils::Vector{
+             Param("in_var", ty.u32(), utils::Vector{Builtin(ast::BuiltinValue::kSampleMask)}),
          },
          ty.u32(),
-         {
+         utils::Vector{
              Return("in_var"),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {Builtin(ast::BuiltinValue::kSampleMask)});
+         utils::Vector{
+             Builtin(ast::BuiltinValue::kSampleMask),
+         });
 
     Inspector& inspector = Build();
 
@@ -874,17 +936,17 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, OutputSampleMaskStructReferenced) {
-    Structure("out_struct",
-              {
-                  Member("inner_sample_mask", ty.u32(), {Builtin(ast::BuiltinValue::kSampleMask)}),
-              });
+    Structure("out_struct", utils::Vector{
+                                Member("inner_sample_mask", ty.u32(),
+                                       utils::Vector{Builtin(ast::BuiltinValue::kSampleMask)}),
+                            });
 
-    Func("ep_func", {}, ty.type_name("out_struct"),
-         {
+    Func("ep_func", utils::Empty, ty.type_name("out_struct"),
+         utils::Vector{
              Decl(Var("out_var", ty.type_name("out_struct"))),
              Return("out_var"),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -898,14 +960,14 @@
 
 TEST_F(InspectorGetEntryPointTest, InputPositionSimpleReferenced) {
     Func("ep_func",
-         {
-             Param("in_var", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
+         utils::Vector{
+             Param("in_var", ty.vec4<f32>(), utils::Vector{Builtin(ast::BuiltinValue::kPosition)}),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -918,20 +980,20 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, InputPositionStructReferenced) {
-    Structure("in_struct",
-              {
-                  Member("inner_position", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
-              });
+    Structure("in_struct", utils::Vector{
+                               Member("inner_position", ty.vec4<f32>(),
+                                      utils::Vector{Builtin(ast::BuiltinValue::kPosition)}),
+                           });
 
     Func("ep_func",
-         {
-             Param("in_var", ty.type_name("in_struct"), {}),
+         utils::Vector{
+             Param("in_var", ty.type_name("in_struct"), utils::Empty),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -945,14 +1007,14 @@
 
 TEST_F(InspectorGetEntryPointTest, FrontFacingSimpleReferenced) {
     Func("ep_func",
-         {
-             Param("in_var", ty.bool_(), {Builtin(ast::BuiltinValue::kFrontFacing)}),
+         utils::Vector{
+             Param("in_var", ty.bool_(), utils::Vector{Builtin(ast::BuiltinValue::kFrontFacing)}),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -965,20 +1027,20 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, FrontFacingStructReferenced) {
-    Structure("in_struct",
-              {
-                  Member("inner_position", ty.bool_(), {Builtin(ast::BuiltinValue::kFrontFacing)}),
-              });
+    Structure("in_struct", utils::Vector{
+                               Member("inner_position", ty.bool_(),
+                                      utils::Vector{Builtin(ast::BuiltinValue::kFrontFacing)}),
+                           });
 
     Func("ep_func",
-         {
-             Param("in_var", ty.type_name("in_struct"), {}),
+         utils::Vector{
+             Param("in_var", ty.type_name("in_struct"), utils::Empty),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -992,14 +1054,14 @@
 
 TEST_F(InspectorGetEntryPointTest, SampleIndexSimpleReferenced) {
     Func("ep_func",
-         {
-             Param("in_var", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)}),
+         utils::Vector{
+             Param("in_var", ty.u32(), utils::Vector{Builtin(ast::BuiltinValue::kSampleIndex)}),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -1012,20 +1074,20 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, SampleIndexStructReferenced) {
-    Structure("in_struct",
-              {
-                  Member("inner_position", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)}),
-              });
+    Structure("in_struct", utils::Vector{
+                               Member("inner_position", ty.u32(),
+                                      utils::Vector{Builtin(ast::BuiltinValue::kSampleIndex)}),
+                           });
 
     Func("ep_func",
-         {
-             Param("in_var", ty.type_name("in_struct"), {}),
+         utils::Vector{
+             Param("in_var", ty.type_name("in_struct"), utils::Empty),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -1039,14 +1101,15 @@
 
 TEST_F(InspectorGetEntryPointTest, NumWorkgroupsSimpleReferenced) {
     Func("ep_func",
-         {
-             Param("in_var", ty.vec3<u32>(), {Builtin(ast::BuiltinValue::kNumWorkgroups)}),
+         utils::Vector{
+             Param("in_var", ty.vec3<u32>(),
+                   utils::Vector{Builtin(ast::BuiltinValue::kNumWorkgroups)}),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          },
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)}, {});
+         utils::Vector{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)}, utils::Empty);
 
     Inspector& inspector = Build();
 
@@ -1057,20 +1120,20 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, NumWorkgroupsStructReferenced) {
-    Structure("in_struct", {
+    Structure("in_struct", utils::Vector{
                                Member("inner_position", ty.vec3<u32>(),
-                                      {Builtin(ast::BuiltinValue::kNumWorkgroups)}),
+                                      utils::Vector{Builtin(ast::BuiltinValue::kNumWorkgroups)}),
                            });
 
     Func("ep_func",
-         {
-             Param("in_var", ty.type_name("in_struct"), {}),
+         utils::Vector{
+             Param("in_var", ty.type_name("in_struct"), utils::Empty),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          },
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)}, {});
+         utils::Vector{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)}, utils::Empty);
 
     Inspector& inspector = Build();
 
@@ -1081,19 +1144,19 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, ImplicitInterpolate) {
-    Structure("in_struct", {
-                               Member("struct_inner", ty.f32(), {Location(0)}),
+    Structure("in_struct", utils::Vector{
+                               Member("struct_inner", ty.f32(), utils::Vector{Location(0)}),
                            });
 
     Func("ep_func",
-         {
-             Param("in_var", ty.type_name("in_struct"), {}),
+         utils::Vector{
+             Param("in_var", ty.type_name("in_struct"), utils::Empty),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -1109,21 +1172,22 @@
 
 TEST_P(InspectorGetEntryPointInterpolateTest, Test) {
     auto& params = GetParam();
-    Structure("in_struct",
-              {
-                  Member("struct_inner", ty.f32(),
-                         {Interpolate(params.in_type, params.in_sampling), Location(0)}),
-              });
+    Structure(
+        "in_struct",
+        utils::Vector{
+            Member("struct_inner", ty.f32(),
+                   utils::Vector{Interpolate(params.in_type, params.in_sampling), Location(0)}),
+        });
 
     Func("ep_func",
-         {
-             Param("in_var", ty.type_name("in_struct"), {}),
+         utils::Vector{
+             Param("in_var", ty.type_name("in_struct"), utils::Empty),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -1170,9 +1234,18 @@
             InterpolationType::kFlat, InterpolationSampling::kNone}));
 
 TEST_F(InspectorGetOverrideDefaultValuesTest, Bool) {
-    Override("foo", ty.bool_(), nullptr, {Id(1)});
-    Override("bar", ty.bool_(), Expr(true), {Id(20)});
-    Override("baz", ty.bool_(), Expr(false), {Id(300)});
+    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),
+             });
 
     Inspector& inspector = Build();
 
@@ -1192,8 +1265,14 @@
 }
 
 TEST_F(InspectorGetOverrideDefaultValuesTest, U32) {
-    Override("foo", ty.u32(), nullptr, {Id(1)});
-    Override("bar", ty.u32(), Expr(42_u), {Id(20)});
+    Override("foo", ty.u32(), nullptr,
+             utils::Vector{
+                 Id(1),
+             });
+    Override("bar", ty.u32(), Expr(42_u),
+             utils::Vector{
+                 Id(20),
+             });
 
     Inspector& inspector = Build();
 
@@ -1209,9 +1288,18 @@
 }
 
 TEST_F(InspectorGetOverrideDefaultValuesTest, I32) {
-    Override("foo", ty.i32(), nullptr, {Id(1)});
-    Override("bar", ty.i32(), Expr(-42_i), {Id(20)});
-    Override("baz", ty.i32(), Expr(42_i), {Id(300)});
+    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),
+             });
 
     Inspector& inspector = Build();
 
@@ -1231,10 +1319,22 @@
 }
 
 TEST_F(InspectorGetOverrideDefaultValuesTest, Float) {
-    Override("foo", ty.f32(), nullptr, {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)});
+    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),
+             });
 
     Inspector& inspector = Build();
 
@@ -1258,9 +1358,18 @@
 }
 
 TEST_F(InspectorGetConstantNameToIdMapTest, WithAndWithoutIds) {
-    Override("v1", ty.f32(), nullptr, {Id(1)});
-    Override("v20", ty.f32(), nullptr, {Id(20)});
-    Override("v300", ty.f32(), nullptr, {Id(300)});
+    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);
@@ -1293,7 +1402,7 @@
 }
 
 TEST_F(InspectorGetStorageSizeTest, Empty) {
-    MakeEmptyBodyFunction("ep_func", {
+    MakeEmptyBodyFunction("ep_func", utils::Vector{
                                          Stage(ast::PipelineStage::kCompute),
                                          WorkgroupSize(1_i),
                                      });
@@ -1305,13 +1414,13 @@
     AddUniformBuffer("ub_var", ty.i32(), 0, 0);
     AddStorageBuffer("sb_var", ty.i32(), ast::Access::kReadWrite, 1, 0);
     AddStorageBuffer("rosb_var", ty.i32(), ast::Access::kRead, 1, 1);
-    Func("ep_func", {}, ty.void_(),
-         {
+    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"))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
@@ -1322,20 +1431,36 @@
 }
 
 TEST_F(InspectorGetStorageSizeTest, Simple_Struct) {
-    auto* ub_struct_type = MakeUniformBufferType("ub_type", {ty.i32(), ty.i32()});
+    auto* ub_struct_type = MakeUniformBufferType("ub_type", utils::Vector{
+                                                                ty.i32(),
+                                                                ty.i32(),
+                                                            });
     AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0);
-    MakeStructVariableReferenceBodyFunction("ub_func", "ub_var", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("ub_func", "ub_var",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    auto sb = MakeStorageBufferTypes("sb_type", {ty.i32()});
+    auto sb = MakeStorageBufferTypes("sb_type", utils::Vector{
+                                                    ty.i32(),
+                                                });
     AddStorageBuffer("sb_var", sb(), ast::Access::kReadWrite, 1, 0);
-    MakeStructVariableReferenceBodyFunction("sb_func", "sb_var", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("sb_func", "sb_var",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    auto ro_sb = MakeStorageBufferTypes("rosb_type", {ty.i32()});
+    auto ro_sb = MakeStorageBufferTypes("rosb_type", utils::Vector{
+                                                         ty.i32(),
+                                                     });
     AddStorageBuffer("rosb_var", ro_sb(), ast::Access::kRead, 1, 1);
-    MakeStructVariableReferenceBodyFunction("rosb_func", "rosb_var", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("rosb_func", "rosb_var",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"ub_func", "sb_func", "rosb_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("ub_func"), "sb_func", "rosb_func"},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kCompute),
                                WorkgroupSize(1_i),
                            });
@@ -1347,11 +1472,11 @@
 
 TEST_F(InspectorGetStorageSizeTest, NonStructVec3) {
     AddUniformBuffer("ub_var", ty.vec3<f32>(), 0, 0);
-    Func("ep_func", {}, ty.void_(),
-         {
+    Func("ep_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Let("ub", nullptr, Expr("ub_var"))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
@@ -1362,13 +1487,15 @@
 }
 
 TEST_F(InspectorGetStorageSizeTest, StructVec3) {
-    auto* ub_struct_type = MakeUniformBufferType("ub_type", {ty.vec3<f32>()});
+    auto* ub_struct_type = MakeUniformBufferType("ub_type", utils::Vector{
+                                                                ty.vec3<f32>(),
+                                                            });
     AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0);
-    Func("ep_func", {}, ty.void_(),
-         {
+    Func("ep_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Let("ub", nullptr, Expr("ub_var"))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
@@ -1379,8 +1506,8 @@
 }
 
 TEST_F(InspectorGetResourceBindingsTest, Empty) {
-    MakeCallerBodyFunction("ep_func", {},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Empty,
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1392,23 +1519,39 @@
 }
 
 TEST_F(InspectorGetResourceBindingsTest, Simple) {
-    auto* ub_struct_type = MakeUniformBufferType("ub_type", {ty.i32()});
+    auto* ub_struct_type = MakeUniformBufferType("ub_type", utils::Vector{
+                                                                ty.i32(),
+                                                            });
     AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0);
-    MakeStructVariableReferenceBodyFunction("ub_func", "ub_var", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("ub_func", "ub_var",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    auto sb = MakeStorageBufferTypes("sb_type", {ty.i32()});
+    auto sb = MakeStorageBufferTypes("sb_type", utils::Vector{
+                                                    ty.i32(),
+                                                });
     AddStorageBuffer("sb_var", sb(), ast::Access::kReadWrite, 1, 0);
-    MakeStructVariableReferenceBodyFunction("sb_func", "sb_var", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("sb_func", "sb_var",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    auto ro_sb = MakeStorageBufferTypes("rosb_type", {ty.i32()});
+    auto ro_sb = MakeStorageBufferTypes("rosb_type", utils::Vector{
+                                                         ty.i32(),
+                                                     });
     AddStorageBuffer("rosb_var", ro_sb(), ast::Access::kRead, 1, 1);
-    MakeStructVariableReferenceBodyFunction("rosb_func", "rosb_var", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("rosb_func", "rosb_var",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
     auto* s_texture_type = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
     AddResource("s_texture", s_texture_type, 2, 0);
     AddSampler("s_var", 3, 0);
     AddGlobalVariable("s_coords", ty.f32());
-    MakeSamplerReferenceBodyFunction("s_func", "s_texture", "s_var", "s_coords", ty.f32(), {});
+    MakeSamplerReferenceBodyFunction("s_func", "s_texture", "s_var", "s_coords", ty.f32(),
+                                     utils::Empty);
 
     auto* cs_depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d);
     AddResource("cs_texture", cs_depth_texture_type, 3, 1);
@@ -1416,22 +1559,32 @@
     AddGlobalVariable("cs_coords", ty.vec2<f32>());
     AddGlobalVariable("cs_depth", ty.f32());
     MakeComparisonSamplerReferenceBodyFunction("cs_func", "cs_texture", "cs_var", "cs_coords",
-                                               "cs_depth", ty.f32(), {});
+                                               "cs_depth", ty.f32(), utils::Empty);
 
     auto* depth_ms_texture_type = ty.depth_multisampled_texture(ast::TextureDimension::k2d);
     AddResource("depth_ms_texture", depth_ms_texture_type, 3, 3);
-    Func("depth_ms_func", {}, ty.void_(), {Ignore("depth_ms_texture")});
+    Func("depth_ms_func", utils::Empty, ty.void_(),
+         utils::Vector{
+             Ignore("depth_ms_texture"),
+         });
 
     auto* st_type = MakeStorageTextureTypes(ast::TextureDimension::k2d, ast::TexelFormat::kR32Uint);
     AddStorageTexture("st_var", st_type, 4, 0);
-    MakeStorageTextureBodyFunction("st_func", "st_var", ty.vec2<i32>(), {});
+    MakeStorageTextureBodyFunction("st_func", "st_var", ty.vec2<i32>(), utils::Empty);
 
-    MakeCallerBodyFunction(
-        "ep_func",
-        {"ub_func", "sb_func", "rosb_func", "s_func", "cs_func", "depth_ms_func", "st_func"},
-        {
-            Stage(ast::PipelineStage::kFragment),
-        });
+    MakeCallerBodyFunction("ep_func",
+                           utils::Vector{
+                               std::string("ub_func"),
+                               std::string("sb_func"),
+                               std::string("rosb_func"),
+                               std::string("s_func"),
+                               std::string("cs_func"),
+                               std::string("depth_ms_func"),
+                               std::string("st_func"),
+                           },
+                           utils::Vector{
+                               Stage(ast::PipelineStage::kFragment),
+                           });
 
     Inspector& inspector = Build();
 
@@ -1486,13 +1639,18 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, NonEntryPointFunc) {
-    auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()});
+    auto* foo_struct_type = MakeUniformBufferType("foo_type", utils::Vector{
+                                                                  ty.i32(),
+                                                              });
     AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
 
-    MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"ub_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("ub_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1505,10 +1663,10 @@
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, Simple_NonStruct) {
     AddUniformBuffer("foo_ub", ty.i32(), 0, 0);
-    MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.i32(), {});
+    MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.i32(), utils::Empty);
 
-    MakeCallerBodyFunction("ep_func", {"ub_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("ub_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1526,13 +1684,18 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, Simple_Struct) {
-    auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()});
+    auto* foo_struct_type = MakeUniformBufferType("foo_type", utils::Vector{
+                                                                  ty.i32(),
+                                                              });
     AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
 
-    MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"ub_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("ub_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1550,14 +1713,22 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleMembers) {
-    auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32(), ty.u32(), ty.f32()});
+    auto* foo_struct_type = MakeUniformBufferType("foo_type", utils::Vector{
+                                                                  ty.i32(),
+                                                                  ty.u32(),
+                                                                  ty.f32(),
+                                                              });
     AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
 
     MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub",
-                                            {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                                MemberInfo{1, ty.u32()},
+                                                MemberInfo{2, ty.f32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"ub_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("ub_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1575,13 +1746,18 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, ContainingPadding) {
-    auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.vec3<f32>()});
+    auto* foo_struct_type = MakeUniformBufferType("foo_type", utils::Vector{
+                                                                  ty.vec3<f32>(),
+                                                              });
     AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
 
-    MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.vec3<f32>()}});
+    MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.vec3<f32>()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"ub_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("ub_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1600,10 +1776,10 @@
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, NonStructVec3) {
     AddUniformBuffer("foo_ub", ty.vec3<f32>(), 0, 0);
-    MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.vec3<f32>(), {});
+    MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.vec3<f32>(), utils::Empty);
 
-    MakeCallerBodyFunction("ep_func", {"ub_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("ub_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1621,14 +1797,22 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleUniformBuffers) {
-    auto* ub_struct_type = MakeUniformBufferType("ub_type", {ty.i32(), ty.u32(), ty.f32()});
+    auto* ub_struct_type = MakeUniformBufferType("ub_type", utils::Vector{
+                                                                ty.i32(),
+                                                                ty.u32(),
+                                                                ty.f32(),
+                                                            });
     AddUniformBuffer("ub_foo", ty.Of(ub_struct_type), 0, 0);
     AddUniformBuffer("ub_bar", ty.Of(ub_struct_type), 0, 1);
     AddUniformBuffer("ub_baz", ty.Of(ub_struct_type), 2, 0);
 
     auto AddReferenceFunc = [this](const std::string& func_name, const std::string& var_name) {
         MakeStructVariableReferenceBodyFunction(func_name, var_name,
-                                                {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
+                                                utils::Vector{
+                                                    MemberInfo{0, ty.i32()},
+                                                    MemberInfo{1, ty.u32()},
+                                                    MemberInfo{2, ty.f32()},
+                                                });
     };
     AddReferenceFunc("ub_foo_func", "ub_foo");
     AddReferenceFunc("ub_bar_func", "ub_bar");
@@ -1638,14 +1822,14 @@
         return create<ast::CallStatement>(Call(callee));
     };
 
-    Func("ep_func", {}, ty.void_(),
-         {
+    Func("ep_func", utils::Empty, ty.void_(),
+         utils::Vector{
              FuncCall("ub_foo_func"),
              FuncCall("ub_bar_func"),
              FuncCall("ub_baz_func"),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -1678,15 +1862,21 @@
     // Manually create uniform buffer to make sure it had a valid layout (array
     // with elem stride of 16, and that is 16-byte aligned within the struct)
     auto* foo_struct_type = Structure(
-        "foo_type", {Member("0i32", ty.i32()),
-                     Member("b", ty.array(ty.u32(), 4_u, /*stride*/ 16), {MemberAlign(16)})});
+        "foo_type",
+        utils::Vector{
+            Member("0i32", ty.i32()),
+            Member("b", ty.array(ty.u32(), 4_u, /*stride*/ 16), utils::Vector{MemberAlign(16)}),
+        });
 
     AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
 
-    MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"ub_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("ub_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1705,10 +1895,10 @@
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, Simple_NonStruct) {
     AddStorageBuffer("foo_sb", ty.i32(), ast::Access::kReadWrite, 0, 0);
-    MakePlainGlobalReferenceBodyFunction("sb_func", "foo_sb", ty.i32(), {});
+    MakePlainGlobalReferenceBodyFunction("sb_func", "foo_sb", ty.i32(), utils::Empty);
 
-    MakeCallerBodyFunction("ep_func", {"sb_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("sb_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1726,13 +1916,18 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, Simple_Struct) {
-    auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
+    auto foo_struct_type = MakeStorageBufferTypes("foo_type", utils::Vector{
+                                                                  ty.i32(),
+                                                              });
     AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
 
-    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"sb_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("sb_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1750,7 +1945,7 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleMembers) {
-    auto foo_struct_type = MakeStorageBufferTypes("foo_type", {
+    auto foo_struct_type = MakeStorageBufferTypes("foo_type", utils::Vector{
                                                                   ty.i32(),
                                                                   ty.u32(),
                                                                   ty.f32(),
@@ -1758,10 +1953,14 @@
     AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
 
     MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb",
-                                            {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                                MemberInfo{1, ty.u32()},
+                                                MemberInfo{2, ty.f32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"sb_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("sb_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1779,7 +1978,7 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleStorageBuffers) {
-    auto sb_struct_type = MakeStorageBufferTypes("sb_type", {
+    auto sb_struct_type = MakeStorageBufferTypes("sb_type", utils::Vector{
                                                                 ty.i32(),
                                                                 ty.u32(),
                                                                 ty.f32(),
@@ -1790,7 +1989,11 @@
 
     auto AddReferenceFunc = [this](const std::string& func_name, const std::string& var_name) {
         MakeStructVariableReferenceBodyFunction(func_name, var_name,
-                                                {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
+                                                utils::Vector{
+                                                    MemberInfo{0, ty.i32()},
+                                                    MemberInfo{1, ty.u32()},
+                                                    MemberInfo{2, ty.f32()},
+                                                });
     };
     AddReferenceFunc("sb_foo_func", "sb_foo");
     AddReferenceFunc("sb_bar_func", "sb_bar");
@@ -1800,14 +2003,14 @@
         return create<ast::CallStatement>(Call(callee));
     };
 
-    Func("ep_func", {}, ty.void_(),
-         {
+    Func("ep_func", utils::Empty, ty.void_(),
+         utils::Vector{
              FuncCall("sb_foo_func"),
              FuncCall("sb_bar_func"),
              FuncCall("sb_baz_func"),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -1837,13 +2040,19 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingArray) {
-    auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32(), ty.array<u32, 4>()});
+    auto foo_struct_type = MakeStorageBufferTypes("foo_type", utils::Vector{
+                                                                  ty.i32(),
+                                                                  ty.array<u32, 4>(),
+                                                              });
     AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
 
-    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"sb_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("sb_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1861,16 +2070,19 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingRuntimeArray) {
-    auto foo_struct_type = MakeStorageBufferTypes("foo_type", {
+    auto foo_struct_type = MakeStorageBufferTypes("foo_type", utils::Vector{
                                                                   ty.i32(),
                                                                   ty.array<u32>(),
                                                               });
     AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
 
-    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"sb_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("sb_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1888,13 +2100,18 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingPadding) {
-    auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.vec3<f32>()});
+    auto foo_struct_type = MakeStorageBufferTypes("foo_type", utils::Vector{
+                                                                  ty.vec3<f32>(),
+                                                              });
     AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
 
-    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.vec3<f32>()}});
+    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.vec3<f32>()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"sb_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("sb_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1913,10 +2130,10 @@
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, NonStructVec3) {
     AddStorageBuffer("foo_ub", ty.vec3<f32>(), ast::Access::kReadWrite, 0, 0);
-    MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.vec3<f32>(), {});
+    MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.vec3<f32>(), utils::Empty);
 
-    MakeCallerBodyFunction("ep_func", {"ub_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("ub_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1934,13 +2151,18 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, SkipReadOnly) {
-    auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
+    auto foo_struct_type = MakeStorageBufferTypes("foo_type", utils::Vector{
+                                                                  ty.i32(),
+                                                              });
     AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0);
 
-    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"sb_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("sb_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1952,13 +2174,18 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, Simple) {
-    auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
+    auto foo_struct_type = MakeStorageBufferTypes("foo_type", utils::Vector{
+                                                                  ty.i32(),
+                                                              });
     AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0);
 
-    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"sb_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("sb_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -1976,7 +2203,7 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, MultipleStorageBuffers) {
-    auto sb_struct_type = MakeStorageBufferTypes("sb_type", {
+    auto sb_struct_type = MakeStorageBufferTypes("sb_type", utils::Vector{
                                                                 ty.i32(),
                                                                 ty.u32(),
                                                                 ty.f32(),
@@ -1987,7 +2214,11 @@
 
     auto AddReferenceFunc = [this](const std::string& func_name, const std::string& var_name) {
         MakeStructVariableReferenceBodyFunction(func_name, var_name,
-                                                {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
+                                                utils::Vector{
+                                                    MemberInfo{0, ty.i32()},
+                                                    MemberInfo{1, ty.u32()},
+                                                    MemberInfo{2, ty.f32()},
+                                                });
     };
     AddReferenceFunc("sb_foo_func", "sb_foo");
     AddReferenceFunc("sb_bar_func", "sb_bar");
@@ -1997,14 +2228,14 @@
         return create<ast::CallStatement>(Call(callee));
     };
 
-    Func("ep_func", {}, ty.void_(),
-         {
+    Func("ep_func", utils::Empty, ty.void_(),
+         utils::Vector{
              FuncCall("sb_foo_func"),
              FuncCall("sb_bar_func"),
              FuncCall("sb_baz_func"),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -2034,16 +2265,19 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, ContainingArray) {
-    auto foo_struct_type = MakeStorageBufferTypes("foo_type", {
+    auto foo_struct_type = MakeStorageBufferTypes("foo_type", utils::Vector{
                                                                   ty.i32(),
                                                                   ty.array<u32, 4>(),
                                                               });
     AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0);
 
-    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"sb_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("sb_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -2061,16 +2295,19 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, ContainingRuntimeArray) {
-    auto foo_struct_type = MakeStorageBufferTypes("foo_type", {
+    auto foo_struct_type = MakeStorageBufferTypes("foo_type", utils::Vector{
                                                                   ty.i32(),
                                                                   ty.array<u32>(),
                                                               });
     AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0);
 
-    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"sb_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("sb_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -2088,13 +2325,18 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, SkipNonReadOnly) {
-    auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
+    auto foo_struct_type = MakeStorageBufferTypes("foo_type", utils::Vector{
+                                                                  ty.i32(),
+                                                              });
     AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0);
 
-    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"sb_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("sb_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -2112,7 +2354,7 @@
     AddGlobalVariable("foo_coords", ty.f32());
 
     MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", "foo_coords", ty.f32(),
-                                     {
+                                     utils::Vector{
                                          Stage(ast::PipelineStage::kFragment),
                                      });
 
@@ -2128,7 +2370,7 @@
 }
 
 TEST_F(InspectorGetSamplerResourceBindingsTest, NoSampler) {
-    MakeEmptyBodyFunction("ep_func", {
+    MakeEmptyBodyFunction("ep_func", utils::Vector{
                                          Stage(ast::PipelineStage::kFragment),
                                      });
 
@@ -2147,10 +2389,10 @@
     AddGlobalVariable("foo_coords", ty.f32());
 
     MakeSamplerReferenceBodyFunction("foo_func", "foo_texture", "foo_sampler", "foo_coords",
-                                     ty.f32(), {});
+                                     ty.f32(), utils::Empty);
 
-    MakeCallerBodyFunction("ep_func", {"foo_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("foo_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -2172,7 +2414,7 @@
     AddGlobalVariable("foo_coords", ty.f32());
 
     MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", "foo_coords", ty.f32(),
-                                     {
+                                     utils::Vector{
                                          Stage(ast::PipelineStage::kFragment),
                                      });
 
@@ -2191,7 +2433,7 @@
 
     MakeComparisonSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", "foo_coords",
                                                "foo_depth", ty.f32(),
-                                               {
+                                               utils::Vector{
                                                    Stage(ast::PipelineStage::kFragment),
                                                });
 
@@ -2212,7 +2454,7 @@
 
     MakeComparisonSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", "foo_coords",
                                                "foo_depth", ty.f32(),
-                                               {
+                                               utils::Vector{
                                                    Stage(ast::PipelineStage::kFragment),
                                                });
 
@@ -2228,7 +2470,7 @@
 }
 
 TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, NoSampler) {
-    MakeEmptyBodyFunction("ep_func", {
+    MakeEmptyBodyFunction("ep_func", utils::Vector{
                                          Stage(ast::PipelineStage::kFragment),
                                      });
 
@@ -2248,10 +2490,10 @@
     AddGlobalVariable("foo_depth", ty.f32());
 
     MakeComparisonSamplerReferenceBodyFunction("foo_func", "foo_texture", "foo_sampler",
-                                               "foo_coords", "foo_depth", ty.f32(), {});
+                                               "foo_coords", "foo_depth", ty.f32(), utils::Empty);
 
-    MakeCallerBodyFunction("ep_func", {"foo_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("foo_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kFragment),
                            });
 
@@ -2275,7 +2517,7 @@
 
     MakeComparisonSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", "foo_coords",
                                                "foo_depth", ty.f32(),
-                                               {
+                                               utils::Vector{
                                                    Stage(ast::PipelineStage::kFragment),
                                                });
 
@@ -2292,7 +2534,7 @@
     AddGlobalVariable("foo_coords", ty.f32());
 
     MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", "foo_coords", ty.f32(),
-                                     {
+                                     utils::Vector{
                                          Stage(ast::PipelineStage::kFragment),
                                      });
 
@@ -2305,7 +2547,7 @@
 }
 
 TEST_F(InspectorGetSampledTextureResourceBindingsTest, Empty) {
-    MakeEmptyBodyFunction("foo", {
+    MakeEmptyBodyFunction("foo", utils::Vector{
                                      Stage(ast::PipelineStage::kFragment),
                                  });
 
@@ -2327,7 +2569,7 @@
 
     MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", "foo_coords",
                                      GetBaseType(GetParam().sampled_kind),
-                                     {
+                                     utils::Vector{
                                          Stage(ast::PipelineStage::kFragment),
                                      });
 
@@ -2377,7 +2619,7 @@
 
     MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", "foo_coords",
                                      "foo_array_index", GetBaseType(GetParam().sampled_kind),
-                                     {
+                                     utils::Vector{
                                          Stage(ast::PipelineStage::kFragment),
                                      });
 
@@ -2413,11 +2655,11 @@
     AddGlobalVariable("foo_coords", coord_type);
     AddGlobalVariable("foo_sample_index", ty.i32());
 
-    Func("ep", {}, ty.void_(),
-         {
+    Func("ep", utils::Empty, ty.void_(),
+         utils::Vector{
              CallStmt(Call("textureLoad", "foo_texture", "foo_coords", "foo_sample_index")),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -2455,7 +2697,7 @@
                                          inspector::ResourceBinding::SampledKind::kUInt}));
 
 TEST_F(InspectorGetMultisampledArrayTextureResourceBindingsTest, Empty) {
-    MakeEmptyBodyFunction("foo", {
+    MakeEmptyBodyFunction("foo", utils::Vector{
                                      Stage(ast::PipelineStage::kFragment),
                                  });
 
@@ -2478,7 +2720,7 @@
 
     MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", "foo_coords",
                                      "foo_array_index", GetBaseType(GetParam().sampled_kind),
-                                     {
+                                     utils::Vector{
                                          Stage(ast::PipelineStage::kFragment),
                                      });
 
@@ -2510,7 +2752,7 @@
                                          inspector::ResourceBinding::SampledKind::kUInt}));
 
 TEST_F(InspectorGetStorageTextureResourceBindingsTest, Empty) {
-    MakeEmptyBodyFunction("ep", {
+    MakeEmptyBodyFunction("ep", utils::Vector{
                                     Stage(ast::PipelineStage::kFragment),
                                 });
 
@@ -2557,7 +2799,7 @@
     ASSERT_FALSE(dim_type == nullptr);
 
     MakeStorageTextureBodyFunction("ep", "st_var", dim_type,
-                                   {
+                                   utils::Vector{
                                        Stage(ast::PipelineStage::kFragment),
                                    });
 
@@ -2639,11 +2881,11 @@
     auto* depth_texture_type = ty.depth_texture(GetParam().type_dim);
     AddResource("dt", depth_texture_type, 0, 0);
 
-    Func("ep", {}, ty.void_(),
-         {
+    Func("ep", utils::Empty, ty.void_(),
+         utils::Vector{
              CallStmt(Call("textureDimensions", "dt")),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -2676,11 +2918,11 @@
     auto* depth_ms_texture_type = ty.depth_multisampled_texture(ast::TextureDimension::k2d);
     AddResource("tex", depth_ms_texture_type, 0, 0);
 
-    Func("ep", {}, ty.void_(),
-         {
+    Func("ep", utils::Empty, ty.void_(),
+         utils::Vector{
              CallStmt(Call("textureDimensions", "tex")),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -2700,11 +2942,11 @@
     auto* external_texture_type = ty.external_texture();
     AddResource("et", external_texture_type, 0, 0);
 
-    Func("ep", {}, ty.void_(),
-         {
+    Func("ep", utils::Empty, ty.void_(),
+         utils::Vector{
              CallStmt(Call("textureDimensions", "et")),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -2980,7 +3222,7 @@
 }
 
 TEST_F(InspectorGetWorkgroupStorageSizeTest, Empty) {
-    MakeEmptyBodyFunction("ep_func", {
+    MakeEmptyBodyFunction("ep_func", utils::Vector{
                                          Stage(ast::PipelineStage::kCompute),
                                          WorkgroupSize(1_i),
                                      });
@@ -2990,10 +3232,10 @@
 
 TEST_F(InspectorGetWorkgroupStorageSizeTest, Simple) {
     AddWorkgroupStorage("wg_f32", ty.f32());
-    MakePlainGlobalReferenceBodyFunction("f32_func", "wg_f32", ty.f32(), {});
+    MakePlainGlobalReferenceBodyFunction("f32_func", "wg_f32", ty.f32(), utils::Empty);
 
-    MakeCallerBodyFunction("ep_func", {"f32_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("f32_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kCompute),
                                WorkgroupSize(1_i),
                            });
@@ -3005,17 +3247,22 @@
 TEST_F(InspectorGetWorkgroupStorageSizeTest, CompoundTypes) {
     // This struct should occupy 68 bytes. 4 from the i32 field, and another 64
     // from the 4-element array with 16-byte stride.
-    auto* wg_struct_type =
-        MakeStructType("WgStruct", {ty.i32(), ty.array(ty.i32(), 4_u, /*stride=*/16)});
+    auto* wg_struct_type = MakeStructType("WgStruct", utils::Vector{
+                                                          ty.i32(),
+                                                          ty.array(ty.i32(), 4_u, /*stride=*/16),
+                                                      });
     AddWorkgroupStorage("wg_struct_var", ty.Of(wg_struct_type));
-    MakeStructVariableReferenceBodyFunction("wg_struct_func", "wg_struct_var", {{0, ty.i32()}});
+    MakeStructVariableReferenceBodyFunction("wg_struct_func", "wg_struct_var",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.i32()},
+                                            });
 
     // Plus another 4 bytes from this other workgroup-class f32.
     AddWorkgroupStorage("wg_f32", ty.f32());
-    MakePlainGlobalReferenceBodyFunction("f32_func", "wg_f32", ty.f32(), {});
+    MakePlainGlobalReferenceBodyFunction("f32_func", "wg_f32", ty.f32(), utils::Empty);
 
-    MakeCallerBodyFunction("ep_func", {"wg_struct_func", "f32_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("wg_struct_func"), "f32_func"},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kCompute),
                                WorkgroupSize(1_i),
                            });
@@ -3028,10 +3275,10 @@
     // vec3<f32> has an alignment of 16 but a size of 12. We leverage this to test
     // that our padded size calculation for workgroup storage is accurate.
     AddWorkgroupStorage("wg_vec3", ty.vec3<f32>());
-    MakePlainGlobalReferenceBodyFunction("wg_func", "wg_vec3", ty.vec3<f32>(), {});
+    MakePlainGlobalReferenceBodyFunction("wg_func", "wg_vec3", ty.vec3<f32>(), utils::Empty);
 
-    MakeCallerBodyFunction("ep_func", {"wg_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("wg_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kCompute),
                                WorkgroupSize(1_i),
                            });
@@ -3046,13 +3293,19 @@
     // here the struct is expected to occupy 1024 bytes of workgroup storage.
     const auto* wg_struct_type = MakeStructTypeFromMembers(
         "WgStruct",
-        {MakeStructMember(0, ty.f32(), {create<ast::StructMemberAlignAttribute>(1024u)})});
+        utils::Vector{
+            MakeStructMember(0, ty.f32(),
+                             utils::Vector{create<ast::StructMemberAlignAttribute>(1024u)}),
+        });
 
     AddWorkgroupStorage("wg_struct_var", ty.Of(wg_struct_type));
-    MakeStructVariableReferenceBodyFunction("wg_struct_func", "wg_struct_var", {{0, ty.f32()}});
+    MakeStructVariableReferenceBodyFunction("wg_struct_func", "wg_struct_var",
+                                            utils::Vector{
+                                                MemberInfo{0, ty.f32()},
+                                            });
 
-    MakeCallerBodyFunction("ep_func", {"wg_struct_func"},
-                           {
+    MakeCallerBodyFunction("ep_func", utils::Vector{std::string("wg_struct_func")},
+                           utils::Vector{
                                Stage(ast::PipelineStage::kCompute),
                                WorkgroupSize(1_i),
                            });
diff --git a/src/tint/inspector/test_inspector_builder.cc b/src/tint/inspector/test_inspector_builder.cc
index 23b473c..5b89d7c 100644
--- a/src/tint/inspector/test_inspector_builder.cc
+++ b/src/tint/inspector/test_inspector_builder.cc
@@ -27,32 +27,36 @@
 InspectorBuilder::InspectorBuilder() = default;
 InspectorBuilder::~InspectorBuilder() = default;
 
-void InspectorBuilder::MakeEmptyBodyFunction(std::string name, ast::AttributeList attributes) {
-    Func(name, {}, ty.void_(), {Return()}, attributes);
+void InspectorBuilder::MakeEmptyBodyFunction(std::string name,
+                                             utils::VectorRef<const ast::Attribute*> attributes) {
+    Func(name, utils::Empty, ty.void_(), utils::Vector{Return()}, attributes);
 }
 
 void InspectorBuilder::MakeCallerBodyFunction(std::string caller,
-                                              std::vector<std::string> callees,
-                                              ast::AttributeList attributes) {
-    ast::StatementList body;
-    body.reserve(callees.size() + 1);
+                                              utils::VectorRef<std::string> callees,
+                                              utils::VectorRef<const ast::Attribute*> attributes) {
+    utils::Vector<const ast::Statement*, 8> body;
+    body.Reserve(callees.Length() + 1);
     for (auto callee : callees) {
-        body.push_back(CallStmt(Call(callee)));
+        body.Push(CallStmt(Call(callee)));
     }
-    body.push_back(Return());
+    body.Push(Return());
 
-    Func(caller, {}, ty.void_(), body, attributes);
+    Func(caller, utils::Empty, ty.void_(), body, attributes);
 }
 
-const ast::Struct* InspectorBuilder::MakeInOutStruct(
-    std::string name,
-    std::vector<std::tuple<std::string, uint32_t>> inout_vars) {
-    ast::StructMemberList members;
+const ast::Struct* InspectorBuilder::MakeInOutStruct(std::string name,
+                                                     utils::VectorRef<InOutInfo> inout_vars) {
+    utils::Vector<const ast::StructMember*, 8> members;
     for (auto var : inout_vars) {
         std::string member_name;
         uint32_t location;
         std::tie(member_name, location) = var;
-        members.push_back(Member(member_name, ty.u32(), {Location(location), Flat()}));
+        members.Push(Member(member_name, ty.u32(),
+                            utils::Vector{
+                                Location(location),
+                                Flat(),
+                            }));
     }
     return Structure(name, members);
 }
@@ -61,17 +65,15 @@
     std::string func,
     std::string var,
     const ast::Type* type,
-    ast::AttributeList attributes) {
-    ast::StatementList stmts;
-    stmts.emplace_back(Decl(Var("local_" + var, type)));
-    stmts.emplace_back(Assign("local_" + var, var));
-    stmts.emplace_back(Return());
-
-    return Func(func, {}, ty.void_(), stmts, attributes);
+    utils::VectorRef<const ast::Attribute*> attributes) {
+    utils::Vector<const ast::Statement*, 3> stmts;
+    stmts.Push(Decl(Var("local_" + var, type)));
+    stmts.Push(Assign("local_" + var, var));
+    stmts.Push(Return());
+    return Func(func, utils::Empty, ty.void_(), std::move(stmts), std::move(attributes));
 }
 
-bool InspectorBuilder::ContainsName(const std::vector<StageVariable>& vec,
-                                    const std::string& name) {
+bool InspectorBuilder::ContainsName(utils::VectorRef<StageVariable> vec, const std::string& name) {
     for (auto& s : vec) {
         if (s.name == name) {
             return true;
@@ -84,35 +86,38 @@
     return std::to_string(idx) + type->FriendlyName(Symbols());
 }
 
-const ast::Struct* InspectorBuilder::MakeStructType(const std::string& name,
-                                                    std::vector<const ast::Type*> member_types) {
-    ast::StructMemberList members;
+const ast::Struct* InspectorBuilder::MakeStructType(
+    const std::string& name,
+    utils::VectorRef<const ast::Type*> member_types) {
+    utils::Vector<const ast::StructMember*, 8> members;
     for (auto* type : member_types) {
-        members.push_back(MakeStructMember(members.size(), type, {}));
+        members.Push(MakeStructMember(members.Length(), type, {}));
     }
     return MakeStructTypeFromMembers(name, std::move(members));
 }
 
-const ast::Struct* InspectorBuilder::MakeStructTypeFromMembers(const std::string& name,
-                                                               ast::StructMemberList members) {
+const ast::Struct* InspectorBuilder::MakeStructTypeFromMembers(
+    const std::string& name,
+    utils::VectorRef<const ast::StructMember*> members) {
     return Structure(name, std::move(members));
 }
 
-const ast::StructMember* InspectorBuilder::MakeStructMember(size_t index,
-                                                            const ast::Type* type,
-                                                            ast::AttributeList attributes) {
+const ast::StructMember* InspectorBuilder::MakeStructMember(
+    size_t index,
+    const ast::Type* type,
+    utils::VectorRef<const ast::Attribute*> attributes) {
     return Member(StructMemberName(index, type), type, std::move(attributes));
 }
 
 const ast::Struct* InspectorBuilder::MakeUniformBufferType(
     const std::string& name,
-    std::vector<const ast::Type*> member_types) {
+    utils::VectorRef<const ast::Type*> member_types) {
     return MakeStructType(name, member_types);
 }
 
 std::function<const ast::TypeName*()> InspectorBuilder::MakeStorageBufferTypes(
     const std::string& name,
-    std::vector<const ast::Type*> member_types) {
+    utils::VectorRef<const ast::Type*> member_types) {
     MakeStructType(name, member_types);
     return [this, name] { return ty.type_name(name); };
 }
@@ -122,7 +127,7 @@
                                         uint32_t group,
                                         uint32_t binding) {
     GlobalVar(name, type, ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(binding),
                   create<ast::GroupAttribute>(group),
               });
@@ -138,7 +143,7 @@
                                         uint32_t group,
                                         uint32_t binding) {
     GlobalVar(name, type, ast::StorageClass::kStorage, access,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(binding),
                   create<ast::GroupAttribute>(group),
               });
@@ -147,15 +152,15 @@
 void InspectorBuilder::MakeStructVariableReferenceBodyFunction(
     std::string func_name,
     std::string struct_name,
-    std::vector<std::tuple<size_t, const ast::Type*>> members) {
-    ast::StatementList stmts;
+    utils::VectorRef<std::tuple<size_t, const ast::Type*>> members) {
+    utils::Vector<const ast::Statement*, 8> stmts;
     for (auto member : members) {
         size_t member_idx;
         const ast::Type* member_type;
         std::tie(member_idx, member_type) = member;
         std::string member_name = StructMemberName(member_idx, member_type);
 
-        stmts.emplace_back(Decl(Var("local" + member_name, member_type)));
+        stmts.Push(Decl(Var("local" + member_name, member_type)));
     }
 
     for (auto member : members) {
@@ -164,17 +169,17 @@
         std::tie(member_idx, member_type) = member;
         std::string member_name = StructMemberName(member_idx, member_type);
 
-        stmts.emplace_back(Assign("local" + member_name, MemberAccessor(struct_name, member_name)));
+        stmts.Push(Assign("local" + member_name, MemberAccessor(struct_name, member_name)));
     }
 
-    stmts.emplace_back(Return());
+    stmts.Push(Return());
 
-    Func(func_name, {}, ty.void_(), stmts);
+    Func(func_name, utils::Empty, ty.void_(), stmts);
 }
 
 void InspectorBuilder::AddSampler(const std::string& name, uint32_t group, uint32_t binding) {
     GlobalVar(name, sampler_type(),
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(binding),
                   create<ast::GroupAttribute>(group),
               });
@@ -184,7 +189,7 @@
                                             uint32_t group,
                                             uint32_t binding) {
     GlobalVar(name, comparison_sampler_type(),
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(binding),
                   create<ast::GroupAttribute>(group),
               });
@@ -195,7 +200,7 @@
                                    uint32_t group,
                                    uint32_t binding) {
     GlobalVar(name, type,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(binding),
                   create<ast::GroupAttribute>(group),
               });
@@ -211,17 +216,15 @@
     const std::string& sampler_name,
     const std::string& coords_name,
     const ast::Type* base_type,
-    ast::AttributeList attributes) {
+    utils::VectorRef<const ast::Attribute*> attributes) {
     std::string result_name = "sampler_result";
 
-    ast::StatementList stmts;
-    stmts.emplace_back(Decl(Var(result_name, ty.vec(base_type, 4))));
-
-    stmts.emplace_back(
-        Assign(result_name, Call("textureSample", texture_name, sampler_name, coords_name)));
-    stmts.emplace_back(Return());
-
-    return Func(func_name, {}, ty.void_(), stmts, attributes);
+    utils::Vector stmts{
+        Decl(Var(result_name, ty.vec(base_type, 4))),
+        Assign(result_name, Call("textureSample", texture_name, sampler_name, coords_name)),
+        Return(),
+    };
+    return Func(func_name, utils::Empty, ty.void_(), std::move(stmts), std::move(attributes));
 }
 
 const ast::Function* InspectorBuilder::MakeSamplerReferenceBodyFunction(
@@ -231,18 +234,16 @@
     const std::string& coords_name,
     const std::string& array_index,
     const ast::Type* base_type,
-    ast::AttributeList attributes) {
+    utils::VectorRef<const ast::Attribute*> attributes) {
     std::string result_name = "sampler_result";
 
-    ast::StatementList stmts;
-
-    stmts.emplace_back(Decl(Var("sampler_result", ty.vec(base_type, 4))));
-
-    stmts.emplace_back(Assign("sampler_result", Call("textureSample", texture_name, sampler_name,
-                                                     coords_name, array_index)));
-    stmts.emplace_back(Return());
-
-    return Func(func_name, {}, ty.void_(), stmts, attributes);
+    utils::Vector stmts{
+        Decl(Var("sampler_result", ty.vec(base_type, 4))),
+        Assign("sampler_result",
+               Call("textureSample", texture_name, sampler_name, coords_name, array_index)),
+        Return(),
+    };
+    return Func(func_name, utils::Empty, ty.void_(), std::move(stmts), std::move(attributes));
 }
 
 const ast::Function* InspectorBuilder::MakeComparisonSamplerReferenceBodyFunction(
@@ -252,17 +253,16 @@
     const std::string& coords_name,
     const std::string& depth_name,
     const ast::Type* base_type,
-    ast::AttributeList attributes) {
+    utils::VectorRef<const ast::Attribute*> attributes) {
     std::string result_name = "sampler_result";
 
-    ast::StatementList stmts;
-
-    stmts.emplace_back(Decl(Var("sampler_result", base_type)));
-    stmts.emplace_back(Assign("sampler_result", Call("textureSampleCompare", texture_name,
-                                                     sampler_name, coords_name, depth_name)));
-    stmts.emplace_back(Return());
-
-    return Func(func_name, {}, ty.void_(), stmts, attributes);
+    utils::Vector stmts{
+        Decl(Var("sampler_result", base_type)),
+        Assign("sampler_result",
+               Call("textureSampleCompare", texture_name, sampler_name, coords_name, depth_name)),
+        Return(),
+    };
+    return Func(func_name, utils::Empty, ty.void_(), std::move(stmts), std::move(attributes));
 }
 
 const ast::Type* InspectorBuilder::GetBaseType(ResourceBinding::SampledKind sampled_kind) {
@@ -306,7 +306,7 @@
                                          uint32_t group,
                                          uint32_t binding) {
     GlobalVar(name, type,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(binding),
                   create<ast::GroupAttribute>(group),
               });
@@ -316,14 +316,14 @@
     const std::string& func_name,
     const std::string& st_name,
     const ast::Type* dim_type,
-    ast::AttributeList attributes) {
-    ast::StatementList stmts;
+    utils::VectorRef<const ast::Attribute*> attributes) {
+    utils::Vector stmts{
+        Decl(Var("dim", dim_type)),
+        Assign("dim", Call("textureDimensions", st_name)),
+        Return(),
+    };
 
-    stmts.emplace_back(Decl(Var("dim", dim_type)));
-    stmts.emplace_back(Assign("dim", Call("textureDimensions", st_name)));
-    stmts.emplace_back(Return());
-
-    return Func(func_name, {}, ty.void_(), stmts, attributes);
+    return Func(func_name, utils::Empty, ty.void_(), std::move(stmts), std::move(attributes));
 }
 
 std::function<const ast::Type*()> InspectorBuilder::GetTypeFunction(ComponentType component,
diff --git a/src/tint/inspector/test_inspector_builder.h b/src/tint/inspector/test_inspector_builder.h
index a3ccb13..fe43c69 100644
--- a/src/tint/inspector/test_inspector_builder.h
+++ b/src/tint/inspector/test_inspector_builder.h
@@ -44,28 +44,31 @@
     /// Generates an empty function
     /// @param name name of the function created
     /// @param attributes the function attributes
-    void MakeEmptyBodyFunction(std::string name, ast::AttributeList attributes);
+    void MakeEmptyBodyFunction(std::string name,
+                               utils::VectorRef<const ast::Attribute*> attributes);
 
     /// Generates a function that calls other functions
     /// @param caller name of the function created
     /// @param callees names of the functions to be called
     /// @param attributes the function attributes
     void MakeCallerBodyFunction(std::string caller,
-                                std::vector<std::string> callees,
-                                ast::AttributeList attributes);
+                                utils::VectorRef<std::string> callees,
+                                utils::VectorRef<const ast::Attribute*> attributes);
+
+    /// InOutInfo is a tuple of name and location for a structure member
+    using InOutInfo = std::tuple<std::string, uint32_t>;
 
     /// Generates a struct that contains user-defined IO members
     /// @param name the name of the generated struct
     /// @param inout_vars tuples of {name, loc} that will be the struct members
     /// @returns a structure object
-    const ast::Struct* MakeInOutStruct(std::string name,
-                                       std::vector<std::tuple<std::string, uint32_t>> inout_vars);
+    const ast::Struct* MakeInOutStruct(std::string name, utils::VectorRef<InOutInfo> inout_vars);
 
     // TODO(crbug.com/tint/697): Remove this.
     /// Add In/Out variables to the global variables
     /// @param inout_vars tuples of {in, out} that will be added as entries to the
     ///                   global variables
-    void AddInOutVariables(std::vector<std::tuple<std::string, std::string>> inout_vars);
+    void AddInOutVariables(utils::VectorRef<std::tuple<std::string, std::string>> inout_vars);
 
     // TODO(crbug.com/tint/697): Remove this.
     /// Generates a function that references in/out variables
@@ -73,9 +76,10 @@
     /// @param inout_vars tuples of {in, out} that will be converted into out = in
     ///                   calls in the function body
     /// @param attributes the function attributes
-    void MakeInOutVariableBodyFunction(std::string name,
-                                       std::vector<std::tuple<std::string, std::string>> inout_vars,
-                                       ast::AttributeList attributes);
+    void MakeInOutVariableBodyFunction(
+        std::string name,
+        utils::VectorRef<std::tuple<std::string, std::string>> inout_vars,
+        utils::VectorRef<const ast::Attribute*> attributes);
 
     // TODO(crbug.com/tint/697): Remove this.
     /// Generates a function that references in/out variables and calls another
@@ -89,9 +93,8 @@
     const ast::Function* MakeInOutVariableCallerBodyFunction(
         std::string caller,
         std::string callee,
-        std::vector<std::tuple<std::string, std::string>> inout_vars,
-        ast::AttributeList attributes);
-
+        utils::VectorRef<std::tuple<std::string, std::string>> inout_vars,
+        utils::VectorRef<const ast::Attribute*> attributes);
 
     /// Generates a function that references module-scoped, plain-typed constant
     /// or variable.
@@ -100,15 +103,16 @@
     /// @param type type of the const being referenced
     /// @param attributes the function attributes
     /// @returns a function object
-    const ast::Function* MakePlainGlobalReferenceBodyFunction(std::string func,
-                                                              std::string var,
-                                                              const ast::Type* type,
-                                                              ast::AttributeList attributes);
+    const ast::Function* MakePlainGlobalReferenceBodyFunction(
+        std::string func,
+        std::string var,
+        const ast::Type* type,
+        utils::VectorRef<const ast::Attribute*> attributes);
 
     /// @param vec Vector of StageVariable to be searched
     /// @param name Name to be searching for
     /// @returns true if name is in vec, otherwise false
-    bool ContainsName(const std::vector<StageVariable>& vec, const std::string& name);
+    bool ContainsName(utils::VectorRef<StageVariable> vec, const std::string& name);
 
     /// Builds a string for accessing a member in a generated struct
     /// @param idx index of member
@@ -121,14 +125,15 @@
     /// @param member_types a vector of member types
     /// @returns a struct type
     const ast::Struct* MakeStructType(const std::string& name,
-                                      std::vector<const ast::Type*> member_types);
+                                      utils::VectorRef<const ast::Type*> member_types);
 
     /// Generates a struct type from a list of member nodes.
     /// @param name name for the struct type
     /// @param members a vector of members
     /// @returns a struct type
-    const ast::Struct* MakeStructTypeFromMembers(const std::string& name,
-                                                 ast::StructMemberList members);
+    const ast::Struct* MakeStructTypeFromMembers(
+        const std::string& name,
+        utils::VectorRef<const ast::StructMember*> members);
 
     /// Generates a struct member with a specified index and type.
     /// @param index index of the field within the struct
@@ -137,14 +142,14 @@
     /// @returns a struct member
     const ast::StructMember* MakeStructMember(size_t index,
                                               const ast::Type* type,
-                                              ast::AttributeList attributes);
+                                              utils::VectorRef<const ast::Attribute*> attributes);
 
     /// Generates types appropriate for using in an uniform buffer
     /// @param name name for the type
     /// @param member_types a vector of member types
     /// @returns a struct type that has the layout for an uniform buffer.
     const ast::Struct* MakeUniformBufferType(const std::string& name,
-                                             std::vector<const ast::Type*> member_types);
+                                             utils::VectorRef<const ast::Type*> member_types);
 
     /// Generates types appropriate for using in a storage buffer
     /// @param name name for the type
@@ -152,7 +157,7 @@
     /// @returns a function that returns the created structure.
     std::function<const ast::TypeName*()> MakeStorageBufferTypes(
         const std::string& name,
-        std::vector<const ast::Type*> member_types);
+        utils::VectorRef<const ast::Type*> member_types);
 
     /// Adds an uniform buffer variable to the program
     /// @param name the name of the variable
@@ -181,14 +186,16 @@
                           uint32_t group,
                           uint32_t binding);
 
+    /// MemberInfo is a tuple of member index and type.
+    using MemberInfo = std::tuple<size_t, const ast::Type*>;
+
     /// Generates a function that references a specific struct variable
     /// @param func_name name of the function created
     /// @param struct_name name of the struct variabler to be accessed
     /// @param members list of members to access, by index and type
-    void MakeStructVariableReferenceBodyFunction(
-        std::string func_name,
-        std::string struct_name,
-        std::vector<std::tuple<size_t, const ast::Type*>> members);
+    void MakeStructVariableReferenceBodyFunction(std::string func_name,
+                                                 std::string struct_name,
+                                                 utils::VectorRef<MemberInfo> members);
 
     /// Adds a regular sampler variable to the program
     /// @param name the name of the variable
@@ -225,12 +232,13 @@
     /// @param base_type sampler base type
     /// @param attributes the function attributes
     /// @returns a function that references all of the values specified
-    const ast::Function* MakeSamplerReferenceBodyFunction(const std::string& func_name,
-                                                          const std::string& texture_name,
-                                                          const std::string& sampler_name,
-                                                          const std::string& coords_name,
-                                                          const ast::Type* base_type,
-                                                          ast::AttributeList attributes);
+    const ast::Function* MakeSamplerReferenceBodyFunction(
+        const std::string& func_name,
+        const std::string& texture_name,
+        const std::string& sampler_name,
+        const std::string& coords_name,
+        const ast::Type* base_type,
+        utils::VectorRef<const ast::Attribute*> attributes);
 
     /// Generates a function that references a specific sampler variable
     /// @param func_name name of the function created
@@ -241,13 +249,14 @@
     /// @param base_type sampler base type
     /// @param attributes the function attributes
     /// @returns a function that references all of the values specified
-    const ast::Function* MakeSamplerReferenceBodyFunction(const std::string& func_name,
-                                                          const std::string& texture_name,
-                                                          const std::string& sampler_name,
-                                                          const std::string& coords_name,
-                                                          const std::string& array_index,
-                                                          const ast::Type* base_type,
-                                                          ast::AttributeList attributes);
+    const ast::Function* MakeSamplerReferenceBodyFunction(
+        const std::string& func_name,
+        const std::string& texture_name,
+        const std::string& sampler_name,
+        const std::string& coords_name,
+        const std::string& array_index,
+        const ast::Type* base_type,
+        utils::VectorRef<const ast::Attribute*> attributes);
 
     /// Generates a function that references a specific comparison sampler
     /// variable.
@@ -259,13 +268,14 @@
     /// @param base_type sampler base type
     /// @param attributes the function attributes
     /// @returns a function that references all of the values specified
-    const ast::Function* MakeComparisonSamplerReferenceBodyFunction(const std::string& func_name,
-                                                                    const std::string& texture_name,
-                                                                    const std::string& sampler_name,
-                                                                    const std::string& coords_name,
-                                                                    const std::string& depth_name,
-                                                                    const ast::Type* base_type,
-                                                                    ast::AttributeList attributes);
+    const ast::Function* MakeComparisonSamplerReferenceBodyFunction(
+        const std::string& func_name,
+        const std::string& texture_name,
+        const std::string& sampler_name,
+        const std::string& coords_name,
+        const std::string& depth_name,
+        const ast::Type* base_type,
+        utils::VectorRef<const ast::Attribute*> attributes);
 
     /// Gets an appropriate type for the data in a given texture type.
     /// @param sampled_kind type of in the texture
@@ -301,10 +311,11 @@
     /// @param dim_type type expected by textureDimensons to return
     /// @param attributes the function attributes
     /// @returns a function that references all of the values specified
-    const ast::Function* MakeStorageTextureBodyFunction(const std::string& func_name,
-                                                        const std::string& st_name,
-                                                        const ast::Type* dim_type,
-                                                        ast::AttributeList attributes);
+    const ast::Function* MakeStorageTextureBodyFunction(
+        const std::string& func_name,
+        const std::string& st_name,
+        const ast::Type* dim_type,
+        utils::VectorRef<const ast::Attribute*> attributes);
 
     /// Get a generator function that returns a type appropriate for a stage
     /// variable with the given combination of component and composition type.
diff --git a/src/tint/intrinsics.def b/src/tint/intrinsics.def
index 6e1d8fb..54db7f5 100644
--- a/src/tint/intrinsics.def
+++ b/src/tint/intrinsics.def
@@ -49,6 +49,8 @@
   chromium_experimental_dp4a
   // A Chromium-specific extension for disabling uniformity analysis.
   chromium_disable_uniformity_analysis
+  // A Chromium-specific extension for push constants
+  chromium_experimental_push_constant
 }
 
 // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
@@ -59,6 +61,7 @@
   workgroup
   uniform
   storage
+  push_constant
   @internal handle
   @internal in
   @internal out
@@ -145,10 +148,10 @@
 type texture_storage_3d<F: texel_format, A: access>
 type texture_external
 
-type __modf_result
-@display("__modf_result_vec{N}") type __modf_result_vec<N: num>
-type __frexp_result
-@display("__frexp_result_vec{N}") type __frexp_result_vec<N: num>
+@display("__modf_result_{T}")        type __modf_result<T>
+@display("__modf_result_vec{N}_{T}") type __modf_result_vec<N: num, T>
+@display("__frexp_result_{T}")        type __frexp_result<T>
+@display("__frexp_result_vec{N}_{T}") type __frexp_result_vec<N: num, T>
 
 type __atomic_compare_exchange_result<T>
 
@@ -460,8 +463,8 @@
 fn fma<N: num, T: f32_f16>(vec<N, T>, vec<N, T>, vec<N, T>) -> vec<N, T>
 fn fract<T: f32_f16>(T) -> T
 fn fract<N: num, T: f32_f16>(vec<N, T>) -> vec<N, T>
-fn frexp(f32) -> __frexp_result
-fn frexp<N: num>(vec<N, f32>) -> __frexp_result_vec<N>
+fn frexp<T: f32_f16>(T) -> __frexp_result<T>
+fn frexp<N: num, T: f32_f16>(vec<N, T>) -> __frexp_result_vec<N, T>
 @stage("fragment") fn fwidth(f32) -> f32
 @stage("fragment") fn fwidth<N: num>(vec<N, f32>) -> vec<N, f32>
 @stage("fragment") fn fwidthCoarse(f32) -> f32
@@ -487,8 +490,8 @@
 fn mix<T: f32_f16>(T, T, T) -> T
 fn mix<N: num, T: f32_f16>(vec<N, T>, vec<N, T>, vec<N, T>) -> vec<N, T>
 fn mix<N: num, T: f32_f16>(vec<N, T>, vec<N, T>, T) -> vec<N, T>
-fn modf(f32) -> __modf_result
-fn modf<N: num>(vec<N, f32>) -> __modf_result_vec<N>
+fn modf<T: f32_f16>(T) -> __modf_result<T>
+fn modf<N: num, T: f32_f16>(vec<N, T>) -> __modf_result_vec<N, T>
 fn normalize<N: num, T: f32_f16>(vec<N, T>) -> vec<N, T>
 fn pack2x16float(vec2<f32>) -> u32
 fn pack2x16snorm(vec2<f32>) -> u32
@@ -883,11 +886,11 @@
 ////////////////////////////////////////////////////////////////////////////////
 // Binary Operators                                                           //
 ////////////////////////////////////////////////////////////////////////////////
-op + <T: fiu32_f16>(T, T) -> T
-op + <T: fiu32_f16, N: num> (vec<N, T>, vec<N, T>) -> vec<N, T>
-op + <T: fiu32_f16, N: num> (vec<N, T>, T) -> vec<N, T>
-op + <T: fiu32_f16, N: num> (T, vec<N, T>) -> vec<N, T>
-op + <T: f32_f16, N: num, M: num> (mat<N, M, T>, mat<N, M, T>) -> mat<N, M, T>
+@const op + <T: fia_fiu32_f16>(T, T) -> T
+@const op + <T: fia_fiu32_f16, N: num> (vec<N, T>, vec<N, T>) -> vec<N, T>
+@const op + <T: fia_fiu32_f16, N: num> (vec<N, T>, T) -> vec<N, T>
+@const op + <T: fia_fiu32_f16, N: num> (T, vec<N, T>) -> vec<N, T>
+@const op + <T: fa_f32_f16, N: num, M: num> (mat<N, M, T>, mat<N, M, T>) -> mat<N, M, T>
 
 op - <T: fiu32_f16>(T, T) -> T
 op - <T: fiu32_f16, N: num> (vec<N, T>, vec<N, T>) -> vec<N, T>
diff --git a/src/tint/number.h b/src/tint/number.h
index 13ba76c..3c8d5a0 100644
--- a/src/tint/number.h
+++ b/src/tint/number.h
@@ -363,6 +363,15 @@
     return AInt(result);
 }
 
+/// @returns a + b, or an empty optional if the resulting value overflowed the AFloat
+inline std::optional<AFloat> CheckedAdd(AFloat a, AFloat b) {
+    auto result = a.value + b.value;
+    if (!std::isfinite(result)) {
+        return {};
+    }
+    return AFloat{result};
+}
+
 /// @returns a * b, or an empty optional if the resulting value overflowed the AInt
 inline std::optional<AInt> CheckedMul(AInt a, AInt b) {
     int64_t result;
diff --git a/src/tint/number_test.cc b/src/tint/number_test.cc
index 16925ed..c245fb4 100644
--- a/src/tint/number_test.cc
+++ b/src/tint/number_test.cc
@@ -356,24 +356,25 @@
         /////////////////////////////////////
     }));
 
-using BinaryCheckedCase = std::tuple<std::optional<AInt>, AInt, AInt>;
-
 #undef OVERFLOW  // corecrt_math.h :(
 #define OVERFLOW \
     {}
 
-using CheckedAddTest = testing::TestWithParam<BinaryCheckedCase>;
-TEST_P(CheckedAddTest, Test) {
+using BinaryCheckedCase_AInt = std::tuple<std::optional<AInt>, AInt, AInt>;
+using BinaryCheckedCase_AFloat = std::tuple<std::optional<AFloat>, AFloat, AFloat>;
+
+using CheckedAddTest_AInt = testing::TestWithParam<BinaryCheckedCase_AInt>;
+TEST_P(CheckedAddTest_AInt, Test) {
     auto expect = std::get<0>(GetParam());
     auto a = std::get<1>(GetParam());
     auto b = std::get<2>(GetParam());
-    EXPECT_EQ(CheckedAdd(a, b), expect) << std::hex << "0x" << a << " * 0x" << b;
-    EXPECT_EQ(CheckedAdd(b, a), expect) << std::hex << "0x" << a << " * 0x" << b;
+    EXPECT_EQ(CheckedAdd(a, b), expect) << std::hex << "0x" << a << " + 0x" << b;
+    EXPECT_EQ(CheckedAdd(b, a), expect) << std::hex << "0x" << a << " + 0x" << b;
 }
 INSTANTIATE_TEST_SUITE_P(
-    CheckedAddTest,
-    CheckedAddTest,
-    testing::ValuesIn(std::vector<BinaryCheckedCase>{
+    CheckedAddTest_AInt,
+    CheckedAddTest_AInt,
+    testing::ValuesIn(std::vector<BinaryCheckedCase_AInt>{
         {AInt(0), AInt(0), AInt(0)},
         {AInt(1), AInt(1), AInt(0)},
         {AInt(2), AInt(1), AInt(1)},
@@ -398,8 +399,37 @@
         ////////////////////////////////////////////////////////////////////////
     }));
 
-using CheckedMulTest = testing::TestWithParam<BinaryCheckedCase>;
-TEST_P(CheckedMulTest, Test) {
+using CheckedAddTest_AFloat = testing::TestWithParam<BinaryCheckedCase_AFloat>;
+TEST_P(CheckedAddTest_AFloat, Test) {
+    auto expect = std::get<0>(GetParam());
+    auto a = std::get<1>(GetParam());
+    auto b = std::get<2>(GetParam());
+    EXPECT_EQ(CheckedAdd(a, b), expect) << std::hex << "0x" << a << " + 0x" << b;
+    EXPECT_EQ(CheckedAdd(b, a), expect) << std::hex << "0x" << a << " + 0x" << b;
+}
+INSTANTIATE_TEST_SUITE_P(
+    CheckedAddTest_AFloat,
+    CheckedAddTest_AFloat,
+    testing::ValuesIn(std::vector<BinaryCheckedCase_AFloat>{
+        {AFloat(0), AFloat(0), AFloat(0)},
+        {AFloat(1), AFloat(1), AFloat(0)},
+        {AFloat(2), AFloat(1), AFloat(1)},
+        {AFloat(0), AFloat(-1), AFloat(1)},
+        {AFloat(3), AFloat(2), AFloat(1)},
+        {AFloat(-1), AFloat(-2), AFloat(1)},
+        {AFloat(0x300), AFloat(0x100), AFloat(0x200)},
+        {AFloat(0x100), AFloat(-0x100), AFloat(0x200)},
+        {AFloat::Highest(), AFloat(1), AFloat(AFloat::kHighestValue - 1)},
+        {AFloat::Lowest(), AFloat(-1), AFloat(AFloat::kLowestValue + 1)},
+        {AFloat::Highest(), AFloat::Highest(), AFloat(0)},
+        {AFloat::Lowest(), AFloat::Lowest(), AFloat(0)},
+        {OVERFLOW, AFloat::Highest(), AFloat::Highest()},
+        {OVERFLOW, AFloat::Lowest(), AFloat::Lowest()},
+        ////////////////////////////////////////////////////////////////////////
+    }));
+
+using CheckedMulTest_AInt = testing::TestWithParam<BinaryCheckedCase_AInt>;
+TEST_P(CheckedMulTest_AInt, Test) {
     auto expect = std::get<0>(GetParam());
     auto a = std::get<1>(GetParam());
     auto b = std::get<2>(GetParam());
@@ -407,9 +437,9 @@
     EXPECT_EQ(CheckedMul(b, a), expect) << std::hex << "0x" << a << " * 0x" << b;
 }
 INSTANTIATE_TEST_SUITE_P(
-    CheckedMulTest,
-    CheckedMulTest,
-    testing::ValuesIn(std::vector<BinaryCheckedCase>{
+    CheckedMulTest_AInt,
+    CheckedMulTest_AInt,
+    testing::ValuesIn(std::vector<BinaryCheckedCase_AInt>{
         {AInt(0), AInt(0), AInt(0)},
         {AInt(0), AInt(1), AInt(0)},
         {AInt(1), AInt(1), AInt(1)},
@@ -446,8 +476,8 @@
 
 using TernaryCheckedCase = std::tuple<std::optional<AInt>, AInt, AInt, AInt>;
 
-using CheckedMaddTest = testing::TestWithParam<TernaryCheckedCase>;
-TEST_P(CheckedMaddTest, Test) {
+using CheckedMaddTest_AInt = testing::TestWithParam<TernaryCheckedCase>;
+TEST_P(CheckedMaddTest_AInt, Test) {
     auto expect = std::get<0>(GetParam());
     auto a = std::get<1>(GetParam());
     auto b = std::get<2>(GetParam());
@@ -458,8 +488,8 @@
         << std::hex << "0x" << a << " * 0x" << b << " + 0x" << c;
 }
 INSTANTIATE_TEST_SUITE_P(
-    CheckedMaddTest,
-    CheckedMaddTest,
+    CheckedMaddTest_AInt,
+    CheckedMaddTest_AInt,
     testing::ValuesIn(std::vector<TernaryCheckedCase>{
         {AInt(0), AInt(0), AInt(0), AInt(0)},
         {AInt(0), AInt(1), AInt(0), AInt(0)},
diff --git a/src/tint/program_builder.cc b/src/tint/program_builder.cc
index 19f20b4..1507cd3 100644
--- a/src/tint/program_builder.cc
+++ b/src/tint/program_builder.cc
@@ -129,9 +129,9 @@
     return stmt;
 }
 
-const ast::Function* ProgramBuilder::WrapInFunction(const ast::StatementList stmts) {
+const ast::Function* ProgramBuilder::WrapInFunction(utils::VectorRef<const ast::Statement*> stmts) {
     return Func("test_function", {}, ty.void_(), std::move(stmts),
-                {
+                utils::Vector{
                     create<ast::StageAttribute>(ast::PipelineStage::kCompute),
                     WorkgroupSize(1_i, 1_i, 1_i),
                 });
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index 1a3313b..25766e6 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -70,6 +70,7 @@
 #include "src/tint/ast/sampled_texture.h"
 #include "src/tint/ast/sampler.h"
 #include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/static_assert.h"
 #include "src/tint/ast/storage_texture.h"
 #include "src/tint/ast/stride_attribute.h"
 #include "src/tint/ast/struct_member_align_attribute.h"
@@ -120,6 +121,30 @@
 
 namespace tint {
 
+namespace detail {
+
+/// IsVectorLike<T>::value is true if T is a utils::Vector or utils::VectorRef.
+template <typename T>
+struct IsVectorLike {
+    /// Non-specialized form of IsVectorLike defaults to false
+    static constexpr bool value = false;
+};
+
+/// IsVectorLike specialization for utils::Vector
+template <typename T, size_t N>
+struct IsVectorLike<utils::Vector<T, N>> {
+    /// True for the IsVectorLike specialization of utils::Vector
+    static constexpr bool value = true;
+};
+
+/// IsVectorLike specialization for utils::VectorRef
+template <typename T>
+struct IsVectorLike<utils::VectorRef<T>> {
+    /// True for the IsVectorLike specialization of utils::VectorRef
+    static constexpr bool value = true;
+};
+}  // namespace detail
+
 /// ProgramBuilder is a mutable builder for a Program.
 /// To construct a Program, populate the builder and then `std::move` it to a
 /// Program.
@@ -131,6 +156,12 @@
     using DisableIfSource =
         traits::EnableIfIsNotType<traits::Decay<traits::NthTypeOf<0, TYPES..., void>>, Source>;
 
+    /// A helper used to disable overloads if the first type in `TYPES` is a utils::Vector,
+    /// utils::VectorRef or utils::VectorRef.
+    template <typename... TYPES>
+    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 {
@@ -143,13 +174,13 @@
         ast::StorageClass storage = ast::StorageClass::kNone;
         ast::Access access = ast::Access::kUndefined;
         const ast::Expression* constructor = nullptr;
-        ast::AttributeList attributes = {};
+        utils::Vector<const ast::Attribute*, 4> attributes;
 
       private:
         void Set(ast::StorageClass sc) { storage = sc; }
         void Set(ast::Access ac) { access = ac; }
         void Set(const ast::Expression* c) { constructor = c; }
-        void Set(const ast::AttributeList& l) { attributes = l; }
+        void Set(utils::VectorRef<const ast::Attribute*> l) { attributes = std::move(l); }
 
         template <typename FIRST, typename... ARGS>
         void Apply(FIRST&& first, ARGS&&... args) {
@@ -687,11 +718,12 @@
         /// @param attrs the optional attributes for the array
         /// @return the tint AST type for a array of size `n` of type `T`
         template <typename EXPR = ast::Expression*>
-        const ast::Array* array(const ast::Type* subtype,
-                                EXPR&& n = nullptr,
-                                ast::AttributeList attrs = {}) const {
+        const ast::Array* array(
+            const ast::Type* subtype,
+            EXPR&& n = nullptr,
+            utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
             return builder->create<ast::Array>(subtype, builder->Expr(std::forward<EXPR>(n)),
-                                               attrs);
+                                               std::move(attrs));
         }
 
         /// @param source the Source of the node
@@ -700,12 +732,13 @@
         /// @param attrs the optional attributes for the array
         /// @return the tint AST type for a array of size `n` of type `T`
         template <typename EXPR = ast::Expression*>
-        const ast::Array* array(const Source& source,
-                                const ast::Type* subtype,
-                                EXPR&& n = nullptr,
-                                ast::AttributeList attrs = {}) const {
-            return builder->create<ast::Array>(source, subtype,
-                                               builder->Expr(std::forward<EXPR>(n)), attrs);
+        const ast::Array* array(
+            const Source& source,
+            const ast::Type* subtype,
+            EXPR&& n = nullptr,
+            utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
+            return builder->create<ast::Array>(
+                source, subtype, builder->Expr(std::forward<EXPR>(n)), std::move(attrs));
         }
 
         /// @param subtype the array element type
@@ -714,9 +747,9 @@
         /// @return the tint AST type for a array of size `n` of type `T`
         template <typename EXPR>
         const ast::Array* array(const ast::Type* subtype, EXPR&& n, uint32_t stride) const {
-            ast::AttributeList attrs;
+            utils::Vector<const ast::Attribute*, 2> attrs;
             if (stride) {
-                attrs.emplace_back(builder->create<ast::StrideAttribute>(stride));
+                attrs.Push(builder->create<ast::StrideAttribute>(stride));
             }
             return array(subtype, std::forward<EXPR>(n), std::move(attrs));
         }
@@ -731,9 +764,9 @@
                                 const ast::Type* subtype,
                                 EXPR&& n,
                                 uint32_t stride) const {
-            ast::AttributeList attrs;
+            utils::Vector<const ast::Attribute*, 2> attrs;
             if (stride) {
-                attrs.emplace_back(builder->create<ast::StrideAttribute>(stride));
+                attrs.Push(builder->create<ast::StrideAttribute>(stride));
             }
             return array(source, subtype, std::forward<EXPR>(n), std::move(attrs));
         }
@@ -1182,9 +1215,9 @@
     /// `list`.
     /// @param list the list to append too
     /// @param arg the arg to create
-    template <typename ARG>
-    void Append(ast::ExpressionList& list, ARG&& arg) {
-        list.emplace_back(Expr(std::forward<ARG>(arg)));
+    template <size_t N, typename ARG>
+    void Append(utils::Vector<const ast::Expression*, N>& list, ARG&& arg) {
+        list.Push(Expr(std::forward<ARG>(arg)));
     }
 
     /// Converts `arg0` and `args` to `ast::Expression`s using `Expr()`,
@@ -1192,29 +1225,38 @@
     /// @param list the list to append too
     /// @param arg0 the first argument
     /// @param args the rest of the arguments
-    template <typename ARG0, typename... ARGS>
-    void Append(ast::ExpressionList& list, ARG0&& arg0, ARGS&&... args) {
+    template <size_t N, typename ARG0, typename... ARGS>
+    void Append(utils::Vector<const ast::Expression*, N>& list, ARG0&& arg0, ARGS&&... args) {
         Append(list, std::forward<ARG0>(arg0));
         Append(list, std::forward<ARGS>(args)...);
     }
 
-    /// @return an empty list of expressions
-    ast::ExpressionList ExprList() { return {}; }
+    /// @return utils::EmptyType
+    utils::EmptyType ExprList() { return utils::Empty; }
 
     /// @param args the list of expressions
     /// @return the list of expressions converted to `ast::Expression`s using
     /// `Expr()`,
-    template <typename... ARGS>
-    ast::ExpressionList ExprList(ARGS&&... args) {
-        ast::ExpressionList list;
-        list.reserve(sizeof...(args));
+    template <typename... ARGS, typename = DisableIfVectorLike<ARGS...>>
+    auto ExprList(ARGS&&... args) {
+        utils::Vector<const ast::Expression*, sizeof...(ARGS)> list;
         Append(list, std::forward<ARGS>(args)...);
         return list;
     }
 
     /// @param list the list of expressions
     /// @return `list`
-    ast::ExpressionList ExprList(ast::ExpressionList list) { return list; }
+    template <typename T, size_t N>
+    utils::Vector<T, N> ExprList(utils::Vector<T, N>&& list) {
+        return std::move(list);
+    }
+
+    /// @param list the list of expressions
+    /// @return `list`
+    utils::VectorRef<const ast::Expression*> ExprList(
+        utils::VectorRef<const ast::Expression*> list) {
+        return list;
+    }
 
     /// @param args the arguments for the type constructor
     /// @return an `ast::CallExpression` of type `ty`, with the values
@@ -1589,7 +1631,7 @@
     const ast::Const* Const(NAME&& name,
                             const ast::Type* type,
                             const ast::Expression* constructor,
-                            ast::AttributeList attributes = {}) {
+                            utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return create<ast::Const>(Sym(std::forward<NAME>(name)), type, constructor, attributes);
     }
 
@@ -1604,7 +1646,7 @@
                             NAME&& name,
                             const ast::Type* type,
                             const ast::Expression* constructor,
-                            ast::AttributeList attributes = {}) {
+                            utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return create<ast::Const>(source, Sym(std::forward<NAME>(name)), type, constructor,
                                   attributes);
     }
@@ -1618,7 +1660,7 @@
     const ast::Let* Let(NAME&& name,
                         const ast::Type* type,
                         const ast::Expression* constructor,
-                        ast::AttributeList attributes = {}) {
+                        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return create<ast::Let>(Sym(std::forward<NAME>(name)), type, constructor, attributes);
     }
 
@@ -1633,7 +1675,7 @@
                         NAME&& name,
                         const ast::Type* type,
                         const ast::Expression* constructor,
-                        ast::AttributeList attributes = {}) {
+                        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return create<ast::Let>(source, Sym(std::forward<NAME>(name)), type, constructor,
                                 attributes);
     }
@@ -1645,7 +1687,7 @@
     template <typename NAME>
     const ast::Parameter* Param(NAME&& name,
                                 const ast::Type* type,
-                                ast::AttributeList attributes = {}) {
+                                utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return create<ast::Parameter>(Sym(std::forward<NAME>(name)), type, attributes);
     }
 
@@ -1658,7 +1700,7 @@
     const ast::Parameter* Param(const Source& source,
                                 NAME&& name,
                                 const ast::Type* type,
-                                ast::AttributeList attributes = {}) {
+                                utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return create<ast::Parameter>(source, Sym(std::forward<NAME>(name)), type, attributes);
     }
 
@@ -1712,10 +1754,11 @@
     /// @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,
-                                  ast::AttributeList attributes = {}) {
+    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;
@@ -1730,11 +1773,12 @@
     /// 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,
-                                  ast::AttributeList attributes = {}) {
+    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);
@@ -1748,10 +1792,11 @@
     /// @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,
-                                  ast::AttributeList attributes = {}) {
+    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);
@@ -1766,11 +1811,12 @@
     /// @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,
-                                  ast::AttributeList attributes = {}) {
+    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);
@@ -1778,6 +1824,42 @@
     }
 
     /// @param source the source information
+    /// @param condition the assertion condition
+    /// @returns a new `ast::StaticAssert`, which is automatically registered as a global statement
+    /// with the ast::Module.
+    template <typename EXPR>
+    const ast::StaticAssert* GlobalStaticAssert(const Source& source, EXPR&& condition) {
+        auto* sa = StaticAssert(source, std::forward<EXPR>(condition));
+        AST().AddStaticAssert(sa);
+        return sa;
+    }
+
+    /// @param condition the assertion condition
+    /// @returns a new `ast::StaticAssert`, which is automatically registered as a global statement
+    /// with the ast::Module.
+    template <typename EXPR, typename = DisableIfSource<EXPR>>
+    const ast::StaticAssert* GlobalStaticAssert(EXPR&& condition) {
+        auto* sa = StaticAssert(std::forward<EXPR>(condition));
+        AST().AddStaticAssert(sa);
+        return sa;
+    }
+
+    /// @param source the source information
+    /// @param condition the assertion condition
+    /// @returns a new `ast::StaticAssert` with the given assertion condition
+    template <typename EXPR>
+    const ast::StaticAssert* StaticAssert(const Source& source, EXPR&& condition) {
+        return create<ast::StaticAssert>(source, Expr(std::forward<EXPR>(condition)));
+    }
+
+    /// @param condition the assertion condition
+    /// @returns a new `ast::StaticAssert` with the given assertion condition
+    template <typename EXPR, typename = DisableIfSource<EXPR>>
+    const ast::StaticAssert* StaticAssert(EXPR&& condition) {
+        return create<ast::StaticAssert>(Expr(std::forward<EXPR>(condition)));
+    }
+
+    /// @param source the source information
     /// @param expr the expression to take the address of
     /// @return an ast::UnaryOpExpression that takes the address of `expr`
     template <typename EXPR>
@@ -1896,6 +1978,17 @@
                                              Expr(std::forward<RHS>(rhs)));
     }
 
+    /// @param source the source information
+    /// @param lhs the left hand argument to the addition operation
+    /// @param rhs the right hand argument to the addition operation
+    /// @returns a `ast::BinaryExpression` summing the arguments `lhs` and `rhs`
+    template <typename LHS, typename RHS>
+    const ast::BinaryExpression* Add(const Source& source, LHS&& lhs, RHS&& rhs) {
+        return create<ast::BinaryExpression>(source, ast::BinaryOp::kAdd,
+                                             Expr(std::forward<LHS>(lhs)),
+                                             Expr(std::forward<RHS>(rhs)));
+    }
+
     /// @param lhs the left hand argument to the and operation
     /// @param rhs the right hand argument to the and operation
     /// @returns a `ast::BinaryExpression` bitwise anding `lhs` and `rhs`
@@ -2158,7 +2251,7 @@
     /// @param group the group index
     /// @param binding the binding index
     /// @returns a attribute list with both the group and binding attributes
-    ast::AttributeList GroupAndBinding(uint32_t group, uint32_t binding) {
+    utils::Vector<const ast::Attribute*, 2> GroupAndBinding(uint32_t group, uint32_t binding) {
         return {Group(group), Binding(binding)};
     }
 
@@ -2173,16 +2266,18 @@
     /// attributes
     /// @returns the function pointer
     template <typename NAME>
-    const ast::Function* Func(const Source& source,
-                              NAME&& name,
-                              ast::ParameterList params,
-                              const ast::Type* type,
-                              ast::StatementList body,
-                              ast::AttributeList attributes = {},
-                              ast::AttributeList return_type_attributes = {}) {
-        auto* func = create<ast::Function>(source, Sym(std::forward<NAME>(name)), params, type,
-                                           create<ast::BlockStatement>(body), attributes,
-                                           return_type_attributes);
+    const ast::Function* Func(
+        const Source& source,
+        NAME&& name,
+        utils::VectorRef<const ast::Parameter*> params,
+        const ast::Type* type,
+        utils::VectorRef<const ast::Statement*> body,
+        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty,
+        utils::VectorRef<const ast::Attribute*> return_type_attributes = utils::Empty) {
+        auto* func =
+            create<ast::Function>(source, Sym(std::forward<NAME>(name)), std::move(params), type,
+                                  create<ast::BlockStatement>(std::move(body)),
+                                  std::move(attributes), std::move(return_type_attributes));
         AST().AddFunction(func);
         return func;
     }
@@ -2197,15 +2292,17 @@
     /// attributes
     /// @returns the function pointer
     template <typename NAME>
-    const ast::Function* Func(NAME&& name,
-                              ast::ParameterList params,
-                              const ast::Type* type,
-                              ast::StatementList body,
-                              ast::AttributeList attributes = {},
-                              ast::AttributeList return_type_attributes = {}) {
-        auto* func = create<ast::Function>(Sym(std::forward<NAME>(name)), params, type,
-                                           create<ast::BlockStatement>(body), attributes,
-                                           return_type_attributes);
+    const ast::Function* Func(
+        NAME&& name,
+        utils::VectorRef<const ast::Parameter*> params,
+        const ast::Type* type,
+        utils::VectorRef<const ast::Statement*> body,
+        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty,
+        utils::VectorRef<const ast::Attribute*> return_type_attributes = utils::Empty) {
+        auto* func =
+            create<ast::Function>(Sym(std::forward<NAME>(name)), std::move(params), type,
+                                  create<ast::BlockStatement>(std::move(body)),
+                                  std::move(attributes), std::move(return_type_attributes));
         AST().AddFunction(func);
         return func;
     }
@@ -2300,9 +2397,11 @@
     /// @param members the struct members
     /// @returns the struct type
     template <typename NAME>
-    const ast::Struct* Structure(const Source& source, NAME&& name, ast::StructMemberList members) {
+    const ast::Struct* Structure(const Source& source,
+                                 NAME&& name,
+                                 utils::VectorRef<const ast::StructMember*> members) {
         auto sym = Sym(std::forward<NAME>(name));
-        auto* type = create<ast::Struct>(source, sym, std::move(members), ast::AttributeList{});
+        auto* type = create<ast::Struct>(source, sym, std::move(members), utils::Empty);
         AST().AddTypeDecl(type);
         return type;
     }
@@ -2312,9 +2411,9 @@
     /// @param members the struct members
     /// @returns the struct type
     template <typename NAME>
-    const ast::Struct* Structure(NAME&& name, ast::StructMemberList members) {
+    const ast::Struct* Structure(NAME&& name, utils::VectorRef<const ast::StructMember*> members) {
         auto sym = Sym(std::forward<NAME>(name));
-        auto* type = create<ast::Struct>(sym, std::move(members), ast::AttributeList{});
+        auto* type = create<ast::Struct>(sym, std::move(members), utils::Empty);
         AST().AddTypeDecl(type);
         return type;
     }
@@ -2326,10 +2425,11 @@
     /// @param attributes the optional struct member attributes
     /// @returns the struct member pointer
     template <typename NAME>
-    const ast::StructMember* Member(const Source& source,
-                                    NAME&& name,
-                                    const ast::Type* type,
-                                    ast::AttributeList attributes = {}) {
+    const ast::StructMember* Member(
+        const Source& source,
+        NAME&& name,
+        const ast::Type* type,
+        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return create<ast::StructMember>(source, Sym(std::forward<NAME>(name)), type,
                                          std::move(attributes));
     }
@@ -2340,22 +2440,23 @@
     /// @param attributes the optional struct member attributes
     /// @returns the struct member pointer
     template <typename NAME>
-    const ast::StructMember* Member(NAME&& name,
-                                    const ast::Type* type,
-                                    ast::AttributeList attributes = {}) {
+    const ast::StructMember* Member(
+        NAME&& name,
+        const ast::Type* type,
+        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return create<ast::StructMember>(source_, Sym(std::forward<NAME>(name)), type,
                                          std::move(attributes));
     }
 
     /// Creates a ast::StructMember with the given byte offset
-    /// @param offset the offset to use in the StructMemberOffsetattribute
+    /// @param offset the offset to use in the StructMemberOffsetAttribute
     /// @param name the struct member name
     /// @param type the struct member type
     /// @returns the struct member pointer
     template <typename NAME>
     const ast::StructMember* Member(uint32_t offset, NAME&& name, const ast::Type* type) {
         return create<ast::StructMember>(source_, Sym(std::forward<NAME>(name)), type,
-                                         ast::AttributeList{
+                                         utils::Vector<const ast::Attribute*, 1>{
                                              create<ast::StructMemberOffsetAttribute>(offset),
                                          });
     }
@@ -2367,7 +2468,9 @@
     template <typename... Statements>
     const ast::BlockStatement* Block(const Source& source, Statements&&... statements) {
         return create<ast::BlockStatement>(
-            source, ast::StatementList{std::forward<Statements>(statements)...});
+            source, utils::Vector<const ast::Statement*, sizeof...(statements)>{
+                        std::forward<Statements>(statements)...,
+                    });
     }
 
     /// Creates a ast::BlockStatement with input statements
@@ -2376,7 +2479,9 @@
     template <typename... STATEMENTS, typename = DisableIfSource<STATEMENTS...>>
     const ast::BlockStatement* Block(STATEMENTS&&... statements) {
         return create<ast::BlockStatement>(
-            ast::StatementList{std::forward<STATEMENTS>(statements)...});
+            utils::Vector<const ast::Statement*, sizeof...(statements)>{
+                std::forward<STATEMENTS>(statements)...,
+            });
     }
 
     /// A wrapper type for the Else statement used to create If statements.
@@ -2618,8 +2723,10 @@
     const ast::SwitchStatement* Switch(const Source& source,
                                        ExpressionInit&& condition,
                                        Cases&&... cases) {
-        return create<ast::SwitchStatement>(source, Expr(std::forward<ExpressionInit>(condition)),
-                                            ast::CaseStatementList{std::forward<Cases>(cases)...});
+        return create<ast::SwitchStatement>(
+            source, Expr(std::forward<ExpressionInit>(condition)),
+            utils::Vector<const ast::CaseStatement*, sizeof...(cases)>{
+                std::forward<Cases>(cases)...});
     }
 
     /// Creates a ast::SwitchStatement with input expression and cases
@@ -2630,8 +2737,10 @@
               typename... Cases,
               typename = DisableIfSource<ExpressionInit>>
     const ast::SwitchStatement* Switch(ExpressionInit&& condition, Cases&&... cases) {
-        return create<ast::SwitchStatement>(Expr(std::forward<ExpressionInit>(condition)),
-                                            ast::CaseStatementList{std::forward<Cases>(cases)...});
+        return create<ast::SwitchStatement>(
+            Expr(std::forward<ExpressionInit>(condition)),
+            utils::Vector<const ast::CaseStatement*, sizeof...(cases)>{
+                std::forward<Cases>(cases)...});
     }
 
     /// Creates a ast::CaseStatement with input list of selectors, and body
@@ -2640,7 +2749,7 @@
     /// @param body the case body
     /// @returns the case statement pointer
     const ast::CaseStatement* Case(const Source& source,
-                                   ast::CaseSelectorList selectors,
+                                   utils::VectorRef<const ast::IntLiteralExpression*> selectors,
                                    const ast::BlockStatement* body = nullptr) {
         return create<ast::CaseStatement>(source, std::move(selectors), body ? body : Block());
     }
@@ -2649,7 +2758,7 @@
     /// @param selectors list of selectors
     /// @param body the case body
     /// @returns the case statement pointer
-    const ast::CaseStatement* Case(ast::CaseSelectorList selectors,
+    const ast::CaseStatement* Case(utils::VectorRef<const ast::IntLiteralExpression*> selectors,
                                    const ast::BlockStatement* body = nullptr) {
         return create<ast::CaseStatement>(std::move(selectors), body ? body : Block());
     }
@@ -2660,7 +2769,7 @@
     /// @returns the case statement pointer
     const ast::CaseStatement* Case(const ast::IntLiteralExpression* selector,
                                    const ast::BlockStatement* body = nullptr) {
-        return Case(ast::CaseSelectorList{selector}, body);
+        return Case(utils::Vector{selector}, body);
     }
 
     /// Convenience function that creates a 'default' ast::CaseStatement
@@ -2669,14 +2778,14 @@
     /// @returns the case statement pointer
     const ast::CaseStatement* DefaultCase(const Source& source,
                                           const ast::BlockStatement* body = nullptr) {
-        return Case(source, ast::CaseSelectorList{}, body);
+        return Case(source, utils::Empty, body);
     }
 
     /// Convenience function that creates a 'default' ast::CaseStatement
     /// @param body the case body
     /// @returns the case statement pointer
     const ast::CaseStatement* DefaultCase(const ast::BlockStatement* body = nullptr) {
-        return Case(ast::CaseSelectorList{}, body);
+        return Case(utils::Empty, body);
     }
 
     /// Creates an ast::FallthroughStatement
@@ -2957,13 +3066,15 @@
     /// @returns the function
     template <typename... ARGS>
     const ast::Function* WrapInFunction(ARGS&&... args) {
-        ast::StatementList stmts{WrapInStatement(std::forward<ARGS>(args))...};
-        return WrapInFunction(std::move(stmts));
+        utils::Vector stmts{
+            WrapInStatement(std::forward<ARGS>(args))...,
+        };
+        return WrapInFunction(utils::VectorRef<const ast::Statement*>{std::move(stmts)});
     }
     /// @param stmts a list of ast::Statement that will be wrapped by a function,
     /// so that each statement is reachable by the Resolver.
     /// @returns the function
-    const ast::Function* WrapInFunction(ast::StatementList stmts);
+    const ast::Function* WrapInFunction(utils::VectorRef<const ast::Statement*> stmts);
 
     /// The builder types
     TypesBuilder const ty{this};
diff --git a/src/tint/program_builder_test.cc b/src/tint/program_builder_test.cc
index dd7e7a8..24e7344 100644
--- a/src/tint/program_builder_test.cc
+++ b/src/tint/program_builder_test.cc
@@ -38,14 +38,14 @@
         return builder;
     }());
 
-    ASSERT_EQ(inner.AST().Functions().size(), 1u);
+    ASSERT_EQ(inner.AST().Functions().Length(), 1u);
     ASSERT_TRUE(inner.Symbols().Get("a").IsValid());
     ASSERT_FALSE(inner.Symbols().Get("b").IsValid());
 
     ProgramBuilder outer = ProgramBuilder::Wrap(&inner);
 
-    ASSERT_EQ(inner.AST().Functions().size(), 1u);
-    ASSERT_EQ(outer.AST().Functions().size(), 1u);
+    ASSERT_EQ(inner.AST().Functions().Length(), 1u);
+    ASSERT_EQ(outer.AST().Functions().Length(), 1u);
     EXPECT_EQ(inner.AST().Functions()[0], outer.AST().Functions()[0]);
     EXPECT_TRUE(inner.Symbols().Get("a").IsValid());
     EXPECT_EQ(inner.Symbols().Get("a"), outer.Symbols().Get("a"));
@@ -57,8 +57,8 @@
     auto* ty = outer.ty.f32();
     outer.Func("b", {}, ty, {}, {});
 
-    ASSERT_EQ(inner.AST().Functions().size(), 1u);
-    ASSERT_EQ(outer.AST().Functions().size(), 2u);
+    ASSERT_EQ(inner.AST().Functions().Length(), 1u);
+    ASSERT_EQ(outer.AST().Functions().Length(), 2u);
     EXPECT_EQ(inner.AST().Functions()[0], outer.AST().Functions()[0]);
     EXPECT_EQ(outer.AST().Functions()[1]->symbol, outer.Symbols().Get("b"));
     EXPECT_EQ(inner.Symbols().Get("a"), outer.Symbols().Get("a"));
diff --git a/src/tint/program_test.cc b/src/tint/program_test.cc
index ae6aabe..39d7c07 100644
--- a/src/tint/program_test.cc
+++ b/src/tint/program_test.cc
@@ -28,7 +28,7 @@
 
 TEST_F(ProgramTest, Creation) {
     Program program(std::move(*this));
-    EXPECT_EQ(program.AST().Functions().size(), 0u);
+    EXPECT_EQ(program.AST().Functions().Length(), 0u);
 }
 
 TEST_F(ProgramTest, EmptyIsValid) {
diff --git a/src/tint/reader/spirv/construct.h b/src/tint/reader/spirv/construct.h
index de4e477..cd0804a 100644
--- a/src/tint/reader/spirv/construct.h
+++ b/src/tint/reader/spirv/construct.h
@@ -18,7 +18,8 @@
 #include <memory>
 #include <sstream>
 #include <string>
-#include <vector>
+
+#include "src/tint/utils/vector.h"
 
 namespace tint::reader::spirv {
 
@@ -146,7 +147,7 @@
 };
 
 /// ConstructList is a list of Construct unique pointers.
-using ConstructList = std::vector<std::unique_ptr<Construct>>;
+using ConstructList = utils::Vector<std::unique_ptr<Construct>, 8>;
 
 /// Converts a construct kind to a string.
 /// @param kind the construct kind to convert
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
index 06f9ecb..4975387 100644
--- a/src/tint/reader/spirv/function.cc
+++ b/src/tint/reader/spirv/function.cc
@@ -622,7 +622,7 @@
     //  - a continue target always follows the associated loop construct, if any
     // @returns the IDs of blocks in reverse structured post order
     std::vector<uint32_t> ReverseStructuredPostOrder() {
-        visit_order_.clear();
+        visit_order_.Clear();
         visited_.clear();
         VisitBackward(function_.entry()->id());
 
@@ -660,20 +660,20 @@
             VisitBackward(terminator->GetSingleWordInOperand(0));
         } else if (opcode == SpvOpSwitch) {
             // TODO(dneto): Consider visiting the labels in literal-value order.
-            std::vector<uint32_t> successors;
+            utils::Vector<uint32_t, 32> successors;
             bb->ForEachSuccessorLabel(
-                [&successors](const uint32_t succ_id) { successors.push_back(succ_id); });
+                [&successors](const uint32_t succ_id) { successors.Push(succ_id); });
             for (auto succ_id : successors) {
                 VisitBackward(succ_id);
             }
         }
 
-        visit_order_.push_back(id);
+        visit_order_.Push(id);
     }
 
     const spvtools::opt::Function& function_;
     std::unordered_map<uint32_t, const spvtools::opt::BasicBlock*> id_to_block_;
-    std::vector<uint32_t> visit_order_;
+    utils::Vector<uint32_t, 32> visit_order_;
     std::unordered_set<uint32_t> visited_;
 };
 
@@ -692,13 +692,14 @@
         auto reversed_cases = cases;
         std::reverse(reversed_cases.begin(), reversed_cases.end());
 
-        return builder->create<ast::SwitchStatement>(Source{}, condition, reversed_cases);
+        return builder->create<ast::SwitchStatement>(Source{}, condition,
+                                                     std::move(reversed_cases));
     }
 
     /// Switch statement condition
     const ast::Expression* const condition;
     /// Switch statement cases
-    ast::CaseStatementList cases;
+    utils::Vector<ast::CaseStatement*, 4> cases;
 };
 
 /// A StatementBuilder for ast::IfStatement
@@ -744,7 +745,7 @@
 
 /// @param decos a list of parsed decorations
 /// @returns true if the decorations include a SampleMask builtin
-bool HasBuiltinSampleMask(const ast::AttributeList& decos) {
+bool HasBuiltinSampleMask(utils::VectorRef<const ast::Attribute*> decos) {
     if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(decos)) {
         return builtin->builtin == ast::BuiltinValue::kSampleMask;
     }
@@ -808,7 +809,7 @@
       sample_mask_in_id(other.sample_mask_out_id),
       sample_mask_out_id(other.sample_mask_in_id),
       ep_info_(other.ep_info_) {
-    other.statements_stack_.clear();
+    other.statements_stack_.Clear();
     PushNewStatementBlock(nullptr, 0, nullptr);
 }
 
@@ -826,7 +827,7 @@
 void FunctionEmitter::StatementBlock::Finalize(ProgramBuilder* pb) {
     TINT_ASSERT(Reader, !finalized_ /* Finalize() must only be called once */);
 
-    for (size_t i = 0; i < statements_.size(); i++) {
+    for (size_t i = 0; i < statements_.Length(); i++) {
         if (auto* sb = statements_[i]->As<StatementBuilder>()) {
             statements_[i] = sb->Build(pb);
         }
@@ -841,64 +842,64 @@
 
 void FunctionEmitter::StatementBlock::Add(const ast::Statement* statement) {
     TINT_ASSERT(Reader, !finalized_ /* Add() must not be called after Finalize() */);
-    statements_.emplace_back(statement);
+    statements_.Push(statement);
 }
 
 void FunctionEmitter::PushNewStatementBlock(const Construct* construct,
                                             uint32_t end_id,
                                             CompletionAction action) {
-    statements_stack_.emplace_back(StatementBlock{construct, end_id, action});
+    statements_stack_.Push(StatementBlock{construct, end_id, action});
 }
 
 void FunctionEmitter::PushGuard(const std::string& guard_name, uint32_t end_id) {
-    TINT_ASSERT(Reader, !statements_stack_.empty());
+    TINT_ASSERT(Reader, !statements_stack_.IsEmpty());
     TINT_ASSERT(Reader, !guard_name.empty());
     // Guard control flow by the guard variable.  Introduce a new
     // if-selection with a then-clause ending at the same block
     // as the statement block at the top of the stack.
-    const auto& top = statements_stack_.back();
+    const auto& top = statements_stack_.Back();
 
     auto* cond =
         create<ast::IdentifierExpression>(Source{}, builder_.Symbols().Register(guard_name));
     auto* builder = AddStatementBuilder<IfStatementBuilder>(cond);
 
-    PushNewStatementBlock(top.GetConstruct(), end_id, [=](const ast::StatementList& stmts) {
+    PushNewStatementBlock(top.GetConstruct(), end_id, [=](const StatementList& stmts) {
         builder->body = create<ast::BlockStatement>(Source{}, stmts);
     });
 }
 
 void FunctionEmitter::PushTrueGuard(uint32_t end_id) {
-    TINT_ASSERT(Reader, !statements_stack_.empty());
-    const auto& top = statements_stack_.back();
+    TINT_ASSERT(Reader, !statements_stack_.IsEmpty());
+    const auto& top = statements_stack_.Back();
 
     auto* cond = MakeTrue(Source{});
     auto* builder = AddStatementBuilder<IfStatementBuilder>(cond);
 
-    PushNewStatementBlock(top.GetConstruct(), end_id, [=](const ast::StatementList& stmts) {
+    PushNewStatementBlock(top.GetConstruct(), end_id, [=](const StatementList& stmts) {
         builder->body = create<ast::BlockStatement>(Source{}, stmts);
     });
 }
 
-const ast::StatementList FunctionEmitter::ast_body() {
-    TINT_ASSERT(Reader, !statements_stack_.empty());
+FunctionEmitter::StatementList FunctionEmitter::ast_body() {
+    TINT_ASSERT(Reader, !statements_stack_.IsEmpty());
     auto& entry = statements_stack_[0];
     entry.Finalize(&builder_);
     return entry.GetStatements();
 }
 
 const ast::Statement* FunctionEmitter::AddStatement(const ast::Statement* statement) {
-    TINT_ASSERT(Reader, !statements_stack_.empty());
+    TINT_ASSERT(Reader, !statements_stack_.IsEmpty());
     if (statement != nullptr) {
-        statements_stack_.back().Add(statement);
+        statements_stack_.Back().Add(statement);
     }
     return statement;
 }
 
 const ast::Statement* FunctionEmitter::LastStatement() {
-    TINT_ASSERT(Reader, !statements_stack_.empty());
-    auto& statement_list = statements_stack_.back().GetStatements();
-    TINT_ASSERT(Reader, !statement_list.empty());
-    return statement_list.back();
+    TINT_ASSERT(Reader, !statements_stack_.IsEmpty());
+    auto& statement_list = statements_stack_.Back().GetStatements();
+    TINT_ASSERT(Reader, !statement_list.IsEmpty());
+    return statement_list.Back();
 }
 
 bool FunctionEmitter::Emit() {
@@ -937,10 +938,9 @@
             return false;
         }
 
-        builder_.AST().AddFunction(
-            create<ast::Function>(decl.source, builder_.Symbols().Register(decl.name),
-                                  std::move(decl.params), decl.return_type->Build(builder_), body,
-                                  std::move(decl.attributes), ast::AttributeList{}));
+        builder_.AST().AddFunction(create<ast::Function>(
+            decl.source, builder_.Symbols().Register(decl.name), std::move(decl.params),
+            decl.return_type->Build(builder_), body, std::move(decl.attributes), utils::Empty));
     }
 
     if (ep_info_ && !ep_info_->inner_name.empty()) {
@@ -951,17 +951,17 @@
 }
 
 const ast::BlockStatement* FunctionEmitter::MakeFunctionBody() {
-    TINT_ASSERT(Reader, statements_stack_.size() == 1);
+    TINT_ASSERT(Reader, statements_stack_.Length() == 1);
 
     if (!EmitBody()) {
         return nullptr;
     }
 
     // Set the body of the AST function node.
-    if (statements_stack_.size() != 1) {
+    if (statements_stack_.Length() != 1) {
         Fail() << "internal error: statement-list stack should have 1 "
                   "element but has "
-               << statements_stack_.size();
+               << statements_stack_.Length();
         return nullptr;
     }
 
@@ -970,7 +970,7 @@
     auto* body = create<ast::BlockStatement>(Source{}, statements);
 
     // Maintain the invariant by repopulating the one and only element.
-    statements_stack_.clear();
+    statements_stack_.Clear();
     PushNewStatementBlock(constructs_[0].get(), 0, nullptr);
 
     return body;
@@ -978,12 +978,12 @@
 
 bool FunctionEmitter::EmitPipelineInput(std::string var_name,
                                         const Type* var_type,
-                                        ast::AttributeList* attrs,
-                                        std::vector<int> index_prefix,
+                                        AttributeList* attrs,
+                                        utils::Vector<int, 8> index_prefix,
                                         const Type* tip_type,
                                         const Type* forced_param_type,
-                                        ast::ParameterList* params,
-                                        ast::StatementList* statements) {
+                                        ParameterList* params,
+                                        StatementList* statements) {
     // TODO(dneto): Handle structs where the locations are annotated on members.
     tip_type = tip_type->UnwrapAlias();
     if (auto* ref_type = tip_type->As<Reference>()) {
@@ -994,11 +994,11 @@
     return Switch(
         tip_type,
         [&](const Matrix* matrix_type) -> bool {
-            index_prefix.push_back(0);
+            index_prefix.Push(0);
             const auto num_columns = static_cast<int>(matrix_type->columns);
             const Type* vec_ty = ty_.Vector(matrix_type->type, matrix_type->rows);
             for (int col = 0; col < num_columns; col++) {
-                index_prefix.back() = col;
+                index_prefix.Back() = col;
                 if (!EmitPipelineInput(var_name, var_type, attrs, index_prefix, vec_ty,
                                        forced_param_type, params, statements)) {
                     return false;
@@ -1010,10 +1010,10 @@
             if (array_type->size == 0) {
                 return Fail() << "runtime-size array not allowed on pipeline IO";
             }
-            index_prefix.push_back(0);
+            index_prefix.Push(0);
             const Type* elem_ty = array_type->type;
             for (int i = 0; i < static_cast<int>(array_type->size); i++) {
-                index_prefix.back() = i;
+                index_prefix.Back() = i;
                 if (!EmitPipelineInput(var_name, var_type, attrs, index_prefix, elem_ty,
                                        forced_param_type, params, statements)) {
                     return false;
@@ -1023,10 +1023,10 @@
         },
         [&](const Struct* struct_type) -> bool {
             const auto& members = struct_type->members;
-            index_prefix.push_back(0);
+            index_prefix.Push(0);
             for (size_t i = 0; i < members.size(); ++i) {
-                index_prefix.back() = static_cast<int>(i);
-                ast::AttributeList member_attrs(*attrs);
+                index_prefix.Back() = static_cast<int>(i);
+                AttributeList member_attrs(*attrs);
                 if (!parser_impl_.ConvertPipelineDecorations(
                         struct_type,
                         parser_impl_.GetMemberPipelineDecorations(*struct_type,
@@ -1039,7 +1039,8 @@
                     return false;
                 }
                 // Copy the location as updated by nested expansion of the member.
-                parser_impl_.SetLocation(attrs, GetLocation(member_attrs));
+                parser_impl_.SetLocation(attrs,
+                                         ast::GetAttribute<ast::LocationAttribute>(member_attrs));
             }
             return success();
         },
@@ -1056,7 +1057,7 @@
             // disallowed but currently the SPIR-V reader will make duplicates when
             // the entire AST is cloned at the top level of the SPIR-V reader flow.
             // Consider rewriting this to avoid this node-sharing.
-            params->push_back(builder_.Param(param_name, param_type->Build(builder_), *attrs));
+            params->Push(builder_.Param(param_name, param_type->Build(builder_), *attrs));
 
             // Add a body statement to copy the parameter to the corresponding
             // private variable.
@@ -1091,7 +1092,7 @@
                     create<ast::BitcastExpression>(tip_type->Build(builder_), param_value);
             }
 
-            statements->push_back(builder_.Assign(store_dest, param_value));
+            statements->Push(builder_.Assign(store_dest, param_value));
 
             // Increment the location attribute, in case more parameters will
             // follow.
@@ -1101,7 +1102,7 @@
         });
 }
 
-void FunctionEmitter::IncrementLocation(ast::AttributeList* attributes) {
+void FunctionEmitter::IncrementLocation(AttributeList* attributes) {
     for (auto*& attr : *attributes) {
         if (auto* loc_attr = attr->As<ast::LocationAttribute>()) {
             // Replace this location attribute with a new one with one higher index.
@@ -1112,23 +1113,14 @@
     }
 }
 
-const ast::Attribute* FunctionEmitter::GetLocation(const ast::AttributeList& attributes) {
-    for (auto* const& attr : attributes) {
-        if (attr->Is<ast::LocationAttribute>()) {
-            return attr;
-        }
-    }
-    return nullptr;
-}
-
 bool FunctionEmitter::EmitPipelineOutput(std::string var_name,
                                          const Type* var_type,
-                                         ast::AttributeList* decos,
-                                         std::vector<int> index_prefix,
+                                         AttributeList* decos,
+                                         utils::Vector<int, 8> index_prefix,
                                          const Type* tip_type,
                                          const Type* forced_member_type,
-                                         ast::StructMemberList* return_members,
-                                         ast::ExpressionList* return_exprs) {
+                                         StructMemberList* return_members,
+                                         ExpressionList* return_exprs) {
     tip_type = tip_type->UnwrapAlias();
     if (auto* ref_type = tip_type->As<Reference>()) {
         tip_type = ref_type->type;
@@ -1138,12 +1130,12 @@
     return Switch(
         tip_type,
         [&](const Matrix* matrix_type) {
-            index_prefix.push_back(0);
+            index_prefix.Push(0);
             const auto num_columns = static_cast<int>(matrix_type->columns);
             const Type* vec_ty = ty_.Vector(matrix_type->type, matrix_type->rows);
             for (int col = 0; col < num_columns; col++) {
-                index_prefix.back() = col;
-                if (!EmitPipelineOutput(var_name, var_type, decos, index_prefix, vec_ty,
+                index_prefix.Back() = col;
+                if (!EmitPipelineOutput(var_name, var_type, std::move(decos), index_prefix, vec_ty,
                                         forced_member_type, return_members, return_exprs)) {
                     return false;
                 }
@@ -1154,11 +1146,11 @@
             if (array_type->size == 0) {
                 return Fail() << "runtime-size array not allowed on pipeline IO";
             }
-            index_prefix.push_back(0);
+            index_prefix.Push(0);
             const Type* elem_ty = array_type->type;
             for (int i = 0; i < static_cast<int>(array_type->size); i++) {
-                index_prefix.back() = i;
-                if (!EmitPipelineOutput(var_name, var_type, decos, index_prefix, elem_ty,
+                index_prefix.Back() = i;
+                if (!EmitPipelineOutput(var_name, var_type, std::move(decos), index_prefix, elem_ty,
                                         forced_member_type, return_members, return_exprs)) {
                     return false;
                 }
@@ -1167,10 +1159,10 @@
         },
         [&](const Struct* struct_type) -> bool {
             const auto& members = struct_type->members;
-            index_prefix.push_back(0);
+            index_prefix.Push(0);
             for (int i = 0; i < static_cast<int>(members.size()); ++i) {
-                index_prefix.back() = i;
-                ast::AttributeList member_attrs(*decos);
+                index_prefix.Back() = i;
+                AttributeList member_attrs(*decos);
                 if (!parser_impl_.ConvertPipelineDecorations(
                         struct_type, parser_impl_.GetMemberPipelineDecorations(*struct_type, i),
                         &member_attrs)) {
@@ -1182,7 +1174,8 @@
                     return false;
                 }
                 // Copy the location as updated by nested expansion of the member.
-                parser_impl_.SetLocation(decos, GetLocation(member_attrs));
+                parser_impl_.SetLocation(decos,
+                                         ast::GetAttribute<ast::LocationAttribute>(member_attrs));
             }
             return success();
         },
@@ -1200,7 +1193,7 @@
             // disallowed but currently the SPIR-V reader will make duplicates when
             // the entire AST is cloned at the top level of the SPIR-V reader flow.
             // Consider rewriting this to avoid this node-sharing.
-            return_members->push_back(
+            return_members->Push(
                 builder_.Member(member_name, member_type->Build(builder_), *decos));
 
             // Create an expression to evaluate the part of the variable indexed by
@@ -1236,7 +1229,7 @@
                 load_source = create<ast::BitcastExpression>(forced_member_type->Build(builder_),
                                                              load_source);
             }
-            return_exprs->push_back(load_source);
+            return_exprs->Push(load_source);
 
             // Increment the location attribute, in case more parameters will
             // follow.
@@ -1250,7 +1243,7 @@
     Source source;
 
     // The statements in the body.
-    ast::StatementList stmts;
+    utils::Vector<const ast::Statement*, 8> stmts;
 
     FunctionDeclaration decl;
     decl.source = source;
@@ -1266,7 +1259,7 @@
         TINT_ASSERT(Reader, var->opcode() == SpvOpVariable);
         auto* store_type = GetVariableStoreType(*var);
         auto* forced_param_type = store_type;
-        ast::AttributeList param_decos;
+        AttributeList param_decos;
         if (!parser_impl_.ConvertDecorationsForVariable(var_id, &forced_param_type, &param_decos,
                                                         true)) {
             // This occurs, and is not an error, for the PointSize builtin.
@@ -1302,12 +1295,12 @@
     }
 
     // Call the inner function.  It has no parameters.
-    stmts.push_back(create<ast::CallStatement>(
+    stmts.Push(create<ast::CallStatement>(
         source,
         create<ast::CallExpression>(source,
                                     create<ast::IdentifierExpression>(
                                         source, builder_.Symbols().Register(ep_info_->inner_name)),
-                                    ast::ExpressionList{})));
+                                    utils::Empty)));
 
     // Pipeline outputs are mapped to the return value.
     if (ep_info_->outputs.empty()) {
@@ -1321,8 +1314,8 @@
         const auto return_struct_sym = builder_.Symbols().Register(return_struct_name);
 
         // Define the structure.
-        std::vector<const ast::StructMember*> return_members;
-        ast::ExpressionList return_exprs;
+        StructMemberList return_members;
+        ExpressionList return_exprs;
 
         const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
 
@@ -1331,13 +1324,13 @@
                 // The SPIR-V gl_PerVertex variable has already been remapped to
                 // a gl_Position variable.  Substitute the type.
                 const Type* param_type = ty_.Vector(ty_.F32(), 4);
-                ast::AttributeList out_decos{
+                AttributeList out_decos{
                     create<ast::BuiltinAttribute>(source, ast::BuiltinValue::kPosition)};
 
                 const auto var_name = namer_.GetName(var_id);
-                return_members.push_back(
+                return_members.Push(
                     builder_.Member(var_name, param_type->Build(builder_), out_decos));
-                return_exprs.push_back(builder_.Expr(var_name));
+                return_exprs.Push(builder_.Expr(var_name));
 
             } else {
                 const auto* var = def_use_mgr_->GetDef(var_id);
@@ -1345,7 +1338,7 @@
                 TINT_ASSERT(Reader, var->opcode() == SpvOpVariable);
                 const Type* store_type = GetVariableStoreType(*var);
                 const Type* forced_member_type = store_type;
-                ast::AttributeList out_decos;
+                AttributeList out_decos;
                 if (!parser_impl_.ConvertDecorationsForVariable(var_id, &forced_member_type,
                                                                 &out_decos, true)) {
                     // This occurs, and is not an error, for the PointSize builtin.
@@ -1378,26 +1371,26 @@
             }
         }
 
-        if (return_members.empty()) {
+        if (return_members.IsEmpty()) {
             // This can occur if only the PointSize member is accessed, because we
             // never emit it.
             return_type = ty_.Void()->Build(builder_);
         } else {
             // Create and register the result type.
-            auto* str = create<ast::Struct>(Source{}, return_struct_sym, return_members,
-                                            ast::AttributeList{});
+            auto* str =
+                create<ast::Struct>(Source{}, return_struct_sym, return_members, AttributeList{});
             parser_impl_.AddTypeDecl(return_struct_sym, str);
             return_type = builder_.ty.Of(str);
 
             // Add the return-value statement.
-            stmts.push_back(create<ast::ReturnStatement>(
+            stmts.Push(create<ast::ReturnStatement>(
                 source, builder_.Construct(source, return_type, std::move(return_exprs))));
         }
     }
 
     auto* body = create<ast::BlockStatement>(source, stmts);
-    ast::AttributeList fn_attrs;
-    fn_attrs.emplace_back(create<ast::StageAttribute>(source, ep_info_->stage));
+    AttributeList fn_attrs;
+    fn_attrs.Push(create<ast::StageAttribute>(source, ep_info_->stage));
 
     if (ep_info_->stage == ast::PipelineStage::kCompute) {
         auto& size = ep_info_->workgroup_size;
@@ -1405,13 +1398,13 @@
             const ast::Expression* x = builder_.Expr(i32(size.x));
             const ast::Expression* y = size.y ? builder_.Expr(i32(size.y)) : nullptr;
             const ast::Expression* z = size.z ? builder_.Expr(i32(size.z)) : nullptr;
-            fn_attrs.emplace_back(create<ast::WorkgroupAttribute>(Source{}, x, y, z));
+            fn_attrs.Push(create<ast::WorkgroupAttribute>(Source{}, x, y, z));
         }
     }
 
     builder_.AST().AddFunction(create<ast::Function>(
         source, builder_.Symbols().Register(ep_info_->name), std::move(decl.params), return_type,
-        body, std::move(fn_attrs), ast::AttributeList{}));
+        body, std::move(fn_attrs), AttributeList{}));
 
     return true;
 }
@@ -1435,14 +1428,13 @@
                       << function_.result_id();
     }
 
-    ast::ParameterList ast_params;
+    ParameterList ast_params;
     function_.ForEachParam([this, &ast_params](const spvtools::opt::Instruction* param) {
         auto* type = parser_impl_.ConvertType(param->type_id());
         if (type != nullptr) {
-            auto* ast_param =
-                parser_impl_.MakeParameter(param->result_id(), type, ast::AttributeList{});
+            auto* ast_param = parser_impl_.MakeParameter(param->result_id(), type, AttributeList{});
             // Parameters are treated as const declarations.
-            ast_params.emplace_back(ast_param);
+            ast_params.Push(ast_param);
             // The value is accessible by name.
             identifier_types_.emplace(param->result_id(), type);
         } else {
@@ -1456,7 +1448,7 @@
     decl->name = name;
     decl->params = std::move(ast_params);
     decl->return_type = ret_ty;
-    decl->attributes.clear();
+    decl->attributes.Clear();
 
     return success();
 }
@@ -1765,11 +1757,11 @@
     //      haven't reached the backedge block.
 
     TINT_ASSERT(Reader, block_order_.size() > 0);
-    constructs_.clear();
+    constructs_.Clear();
     const auto entry_id = block_order_[0];
 
     // The stack of enclosing constructs.
-    std::vector<Construct*> enclosing;
+    utils::Vector<Construct*, 4> enclosing;
 
     // Creates a control flow construct and pushes it onto the stack.
     // Its parent is the top of the stack, or nullptr if the stack is empty.
@@ -1779,7 +1771,7 @@
         const auto begin_pos = GetBlockInfo(begin_id)->pos;
         const auto end_pos =
             end_id == 0 ? uint32_t(block_order_.size()) : GetBlockInfo(end_id)->pos;
-        const auto* parent = enclosing.empty() ? nullptr : enclosing.back();
+        const auto* parent = enclosing.IsEmpty() ? nullptr : enclosing.Back();
         auto scope_end_pos = end_pos;
         // A loop construct is added right after its associated continue construct.
         // In that case, adjust the parent up.
@@ -1789,11 +1781,10 @@
             scope_end_pos = parent->end_pos;
             parent = parent->parent;
         }
-        constructs_.push_back(std::make_unique<Construct>(parent, static_cast<int>(depth), k,
-                                                          begin_id, end_id, begin_pos, end_pos,
-                                                          scope_end_pos));
-        Construct* result = constructs_.back().get();
-        enclosing.push_back(result);
+        constructs_.Push(std::make_unique<Construct>(parent, static_cast<int>(depth), k, begin_id,
+                                                     end_id, begin_pos, end_pos, scope_end_pos));
+        Construct* result = constructs_.Back().get();
+        enclosing.Push(result);
         return result;
     };
 
@@ -1808,19 +1799,19 @@
         auto* block_info = GetBlockInfo(block_id);
         TINT_ASSERT(Reader, block_info);
 
-        if (enclosing.empty()) {
+        if (enclosing.IsEmpty()) {
             return Fail() << "internal error: too many merge blocks before block " << block_id;
         }
-        const Construct* top = enclosing.back();
+        const Construct* top = enclosing.Back();
 
         while (block_id == top->end_id) {
             // We've reached a predeclared end of the construct.  Pop it off the
             // stack.
-            enclosing.pop_back();
-            if (enclosing.empty()) {
+            enclosing.Pop();
+            if (enclosing.IsEmpty()) {
                 return Fail() << "internal error: too many merge blocks before block " << block_id;
             }
-            top = enclosing.back();
+            top = enclosing.Back();
         }
 
         const auto merge = block_info->merge_for_header;
@@ -1851,10 +1842,10 @@
                     // If the loop header branches to two different blocks inside the loop
                     // construct, then the loop body should be modeled as an if-selection
                     // construct
-                    std::vector<uint32_t> targets;
+                    utils::Vector<uint32_t, 4> targets;
                     header_info->basic_block->ForEachSuccessorLabel(
-                        [&targets](const uint32_t target) { targets.push_back(target); });
-                    if ((targets.size() == 2u) && targets[0] != targets[1]) {
+                        [&targets](const uint32_t target) { targets.Push(target); });
+                    if ((targets.Length() == 2u) && targets[0] != targets[1]) {
                         const auto target0_pos = GetBlockInfo(targets[0])->pos;
                         const auto target1_pos = GetBlockInfo(targets[1])->pos;
                         if (top->ContainsPos(target0_pos) && top->ContainsPos(target1_pos)) {
@@ -1881,10 +1872,10 @@
 
     // At the end of the block list, we should only have the kFunction construct
     // left.
-    if (enclosing.size() != 1) {
+    if (enclosing.Length() != 1) {
         return Fail() << "internal error: unbalanced structured constructs when "
                          "labeling structured constructs: ended with "
-                      << enclosing.size() - 1 << " unterminated constructs";
+                      << enclosing.Length() - 1 << " unterminated constructs";
     }
     const auto* top = enclosing[0];
     if (top->kind != Construct::kFunction || top->depth != 0) {
@@ -1945,8 +1936,8 @@
         default_block->default_is_merge = default_block->pos == construct->end_pos;
 
         // Map a case target to the list of values selecting that case.
-        std::unordered_map<uint32_t, std::vector<uint64_t>> block_to_values;
-        std::vector<uint32_t> case_targets;
+        std::unordered_map<uint32_t, utils::Vector<uint64_t, 4>> block_to_values;
+        utils::Vector<uint32_t, 4> case_targets;
         std::unordered_set<uint64_t> case_values;
 
         // Process case targets.
@@ -1960,16 +1951,15 @@
             }
             case_values.insert(value);
             if (block_to_values.count(case_target_id) == 0) {
-                case_targets.push_back(case_target_id);
+                case_targets.Push(case_target_id);
             }
-            block_to_values[case_target_id].push_back(value);
+            block_to_values[case_target_id].Push(value);
         }
 
         for (uint32_t case_target_id : case_targets) {
             auto* case_block = GetBlockInfo(case_target_id);
 
-            case_block->case_values =
-                std::make_unique<std::vector<uint64_t>>(std::move(block_to_values[case_target_id]));
+            case_block->case_values = std::move(block_to_values[case_target_id]);
 
             // A case target can't be a back-edge.
             if (construct->begin_pos >= case_block->pos) {
@@ -2085,13 +2075,13 @@
         const auto& src_construct = *(src_info->construct);
 
         // Compute the ordered list of unique successors.
-        std::vector<uint32_t> successors;
+        utils::Vector<uint32_t, 4> successors;
         {
             std::unordered_set<uint32_t> visited;
             src_info->basic_block->ForEachSuccessorLabel(
                 [&successors, &visited](const uint32_t succ) {
                     if (visited.count(succ) == 0) {
-                        successors.push_back(succ);
+                        successors.Push(succ);
                         visited.insert(succ);
                     }
                 });
@@ -2105,10 +2095,10 @@
         // to have a merge instruction.  We also track kIfBreak edges
         // because when used with normal forward edges, we'll need
         // to generate a flow guard variable.
-        std::vector<uint32_t> normal_forward_edges;
-        std::vector<uint32_t> if_break_edges;
+        utils::Vector<uint32_t, 4> normal_forward_edges;
+        utils::Vector<uint32_t, 4> if_break_edges;
 
-        if (successors.empty() && src_construct.enclosing_continue) {
+        if (successors.IsEmpty() && src_construct.enclosing_continue) {
             // Kill and return are not allowed in a continue construct.
             return Fail() << "Invalid function exit at block " << src
                           << " from continue construct starting at "
@@ -2223,10 +2213,10 @@
 
                 if ((edge_kind == EdgeKind::kForward) ||
                     (edge_kind == EdgeKind::kCaseFallThrough)) {
-                    normal_forward_edges.push_back(dest);
+                    normal_forward_edges.Push(dest);
                 }
                 if (edge_kind == EdgeKind::kIfBreak) {
-                    if_break_edges.push_back(dest);
+                    if_break_edges.Push(dest);
                 }
 
                 if ((edge_kind == EdgeKind::kForward) ||
@@ -2288,13 +2278,13 @@
         if (num_backedges > 1) {
             return Fail() << "Block " << src << " has too many backedges: " << num_backedges;
         }
-        if ((normal_forward_edges.size() > 1) && (src_info->merge_for_header == 0)) {
+        if ((normal_forward_edges.Length() > 1) && (src_info->merge_for_header == 0)) {
             return Fail() << "Control flow diverges at block " << src << " (to "
                           << normal_forward_edges[0] << ", " << normal_forward_edges[1]
                           << ") but it is not a structured header (it has no merge "
                              "instruction)";
         }
-        if ((normal_forward_edges.size() + if_break_edges.size() > 1) &&
+        if ((normal_forward_edges.Length() + if_break_edges.Length() > 1) &&
             (src_info->merge_for_header == 0)) {
             // There is a branch to the merge of an if-selection combined
             // with an other normal forward branch.  Control within the
@@ -2504,7 +2494,7 @@
             }
         }
         auto* var = parser_impl_.MakeVar(inst.result_id(), ast::StorageClass::kNone, var_store_type,
-                                         constructor, ast::AttributeList{});
+                                         constructor, AttributeList{});
         auto* var_decl_stmt = create<ast::VariableDeclStatement>(Source{}, var);
         AddStatement(var_decl_stmt);
         auto* var_type = ty_.Reference(var_store_type, ast::StorageClass::kNone);
@@ -2627,7 +2617,7 @@
 
     // Upon entry, the statement stack has one entry representing the whole
     // function.
-    TINT_ASSERT(Reader, !constructs_.empty());
+    TINT_ASSERT(Reader, !constructs_.IsEmpty());
     Construct* function_construct = constructs_[0].get();
     TINT_ASSERT(Reader, function_construct != nullptr);
     TINT_ASSERT(Reader, function_construct->kind == Construct::kFunction);
@@ -2635,7 +2625,7 @@
     // had not been computed at the time the entry was first created.
     // TODO(dneto): refactor how the first construct is created vs.
     // this statements stack entry is populated.
-    TINT_ASSERT(Reader, statements_stack_.size() == 1);
+    TINT_ASSERT(Reader, statements_stack_.Length() == 1);
     statements_stack_[0].SetConstruct(function_construct);
 
     for (auto block_id : block_order()) {
@@ -2648,24 +2638,24 @@
 
 bool FunctionEmitter::EmitBasicBlock(const BlockInfo& block_info) {
     // Close off previous constructs.
-    while (!statements_stack_.empty() && (statements_stack_.back().GetEndId() == block_info.id)) {
-        statements_stack_.back().Finalize(&builder_);
-        statements_stack_.pop_back();
+    while (!statements_stack_.IsEmpty() && (statements_stack_.Back().GetEndId() == block_info.id)) {
+        statements_stack_.Back().Finalize(&builder_);
+        statements_stack_.Pop();
     }
-    if (statements_stack_.empty()) {
+    if (statements_stack_.IsEmpty()) {
         return Fail() << "internal error: statements stack empty at block " << block_info.id;
     }
 
     // Enter new constructs.
 
-    std::vector<const Construct*> entering_constructs;  // inner most comes first
+    utils::Vector<const Construct*, 4> entering_constructs;  // inner most comes first
     {
         auto* here = block_info.construct;
-        auto* const top_construct = statements_stack_.back().GetConstruct();
+        auto* const top_construct = statements_stack_.Back().GetConstruct();
         while (here != top_construct) {
             // Only enter a construct at its header block.
             if (here->begin_id == block_info.id) {
-                entering_constructs.push_back(here);
+                entering_constructs.Push(here);
             }
             here = here->parent;
         }
@@ -2721,10 +2711,10 @@
     //  - We are entering 0 or 1 constructs, or
     //  - We are entering 2 constructs, with the outer one being a kContinue or
     //    kLoop, the inner one is not a continue.
-    if (entering_constructs.size() > 2) {
+    if (entering_constructs.Length() > 2) {
         return Fail() << "internal error: bad construct nesting found";
     }
-    if (entering_constructs.size() == 2) {
+    if (entering_constructs.Length() == 2) {
         auto inner_kind = entering_constructs[0]->kind;
         auto outer_kind = entering_constructs[1]->kind;
         if (outer_kind != Construct::kContinue && outer_kind != Construct::kLoop) {
@@ -2896,9 +2886,9 @@
     // But make sure we do it in the right order.
     auto push_else = [this, builder, else_end, construct, false_is_break, false_is_continue]() {
         // Push the else clause onto the stack first.
-        PushNewStatementBlock(construct, else_end, [=](const ast::StatementList& stmts) {
+        PushNewStatementBlock(construct, else_end, [=](const StatementList& stmts) {
             // Only set the else-clause if there are statements to fill it.
-            if (!stmts.empty()) {
+            if (!stmts.IsEmpty()) {
                 // The "else" consists of the statement list from the top of
                 // statements stack, without an "else if" condition.
                 builder->else_stmt = create<ast::BlockStatement>(Source{}, stmts);
@@ -2947,7 +2937,7 @@
         }
 
         // Push the then clause onto the stack.
-        PushNewStatementBlock(construct, then_end, [=](const ast::StatementList& stmts) {
+        PushNewStatementBlock(construct, then_end, [=](const StatementList& stmts) {
             builder->body = create<ast::BlockStatement>(Source{}, stmts);
         });
         if (true_is_break) {
@@ -2984,17 +2974,17 @@
     // We will push statement-blocks onto the stack to gather the statements in
     // the default clause and cases clauses. Determine the list of blocks
     // that start each clause.
-    std::vector<const BlockInfo*> clause_heads;
+    utils::Vector<const BlockInfo*, 4> clause_heads;
 
     // Collect the case clauses, even if they are just the merge block.
     // First the default clause.
     const auto default_id = branch->GetSingleWordInOperand(1);
     const auto* default_info = GetBlockInfo(default_id);
-    clause_heads.push_back(default_info);
+    clause_heads.Push(default_info);
     // Now the case clauses.
     for (uint32_t iarg = 2; iarg + 1 < branch->NumInOperands(); iarg += 2) {
         const auto case_target_id = branch->GetSingleWordInOperand(iarg + 1);
-        clause_heads.push_back(GetBlockInfo(case_target_id));
+        clause_heads.Push(GetBlockInfo(case_target_id));
     }
 
     std::stable_sort(
@@ -3005,37 +2995,36 @@
         // Use read index r, and write index w.
         // Invariant: w <= r;
         size_t w = 0;
-        for (size_t r = 0; r < clause_heads.size(); ++r) {
+        for (size_t r = 0; r < clause_heads.Length(); ++r) {
             if (clause_heads[r] != clause_heads[w]) {
                 ++w;  // Advance the write cursor.
             }
             clause_heads[w] = clause_heads[r];
         }
         // We know it's not empty because it always has at least a default clause.
-        TINT_ASSERT(Reader, !clause_heads.empty());
-        clause_heads.resize(w + 1);
+        TINT_ASSERT(Reader, !clause_heads.IsEmpty());
+        clause_heads.Resize(w + 1);
     }
 
     // Push them on in reverse order.
-    const auto last_clause_index = clause_heads.size() - 1;
+    const auto last_clause_index = clause_heads.Length() - 1;
     for (size_t i = last_clause_index;; --i) {
         // Create a list of integer literals for the selector values leading to
         // this case clause.
-        ast::CaseSelectorList selectors;
-        const auto* values_ptr = clause_heads[i]->case_values.get();
-        const bool has_selectors = (values_ptr && !values_ptr->empty());
+        utils::Vector<const ast::IntLiteralExpression*, 4> selectors;
+        const bool has_selectors = clause_heads[i]->case_values.has_value();
         if (has_selectors) {
-            std::vector<uint64_t> values(values_ptr->begin(), values_ptr->end());
+            auto values = clause_heads[i]->case_values.value();
             std::stable_sort(values.begin(), values.end());
             for (auto value : values) {
                 // The rest of this module can handle up to 64 bit switch values.
                 // The Tint AST handles 32-bit values.
                 const uint32_t value32 = uint32_t(value & 0xFFFFFFFF);
                 if (selector.type->IsUnsignedScalarOrVector()) {
-                    selectors.emplace_back(create<ast::IntLiteralExpression>(
+                    selectors.Push(create<ast::IntLiteralExpression>(
                         Source{}, value32, ast::IntLiteralExpression::Suffix::kU));
                 } else {
-                    selectors.emplace_back(
+                    selectors.Push(
                         create<ast::IntLiteralExpression>(Source{}, static_cast<int32_t>(value32),
                                                           ast::IntLiteralExpression::Suffix::kI));
                 }
@@ -3044,13 +3033,13 @@
 
         // Where does this clause end?
         const auto end_id =
-            (i + 1 < clause_heads.size()) ? clause_heads[i + 1]->id : construct->end_id;
+            (i + 1 < clause_heads.Length()) ? clause_heads[i + 1]->id : construct->end_id;
 
         // Reserve the case clause slot in swch->cases, push the new statement block
         // for the case, and fill the case clause once the block is generated.
-        auto case_idx = swch->cases.size();
-        swch->cases.emplace_back(nullptr);
-        PushNewStatementBlock(construct, end_id, [=](const ast::StatementList& stmts) {
+        auto case_idx = swch->cases.Length();
+        swch->cases.Push(nullptr);
+        PushNewStatementBlock(construct, end_id, [=](const StatementList& stmts) {
             auto* body = create<ast::BlockStatement>(Source{}, stmts);
             swch->cases[case_idx] = create<ast::CaseStatement>(Source{}, selectors, body);
         });
@@ -3059,11 +3048,11 @@
             construct->ContainsPos(default_info->pos)) {
             // Generate a default clause with a just fallthrough.
             auto* stmts = create<ast::BlockStatement>(
-                Source{}, ast::StatementList{
+                Source{}, StatementList{
                               create<ast::FallthroughStatement>(Source{}),
                           });
-            auto* case_stmt = create<ast::CaseStatement>(Source{}, ast::CaseSelectorList{}, stmts);
-            swch->cases.emplace_back(case_stmt);
+            auto* case_stmt = create<ast::CaseStatement>(Source{}, utils::Empty, stmts);
+            swch->cases.Push(case_stmt);
         }
 
         if (i == 0) {
@@ -3076,7 +3065,7 @@
 
 bool FunctionEmitter::EmitLoopStart(const Construct* construct) {
     auto* builder = AddStatementBuilder<LoopStatementBuilder>();
-    PushNewStatementBlock(construct, construct->end_id, [=](const ast::StatementList& stmts) {
+    PushNewStatementBlock(construct, construct->end_id, [=](const StatementList& stmts) {
         builder->body = create<ast::BlockStatement>(Source{}, stmts);
     });
     return success();
@@ -3091,7 +3080,7 @@
         return Fail() << "internal error: starting continue construct, "
                          "expected loop on top of stack";
     }
-    PushNewStatementBlock(construct, construct->end_id, [=](const ast::StatementList& stmts) {
+    PushNewStatementBlock(construct, construct->end_id, [=](const StatementList& stmts) {
         loop->continuing = create<ast::BlockStatement>(Source{}, stmts);
     });
 
@@ -3185,7 +3174,7 @@
 
             AddStatement(MakeSimpleIf(cond, true_branch, false_branch));
             if (!flow_guard.empty()) {
-                PushGuard(flow_guard, statements_stack_.back().GetEndId());
+                PushGuard(flow_guard, statements_stack_.Back().GetEndId());
             }
             return true;
         }
@@ -3285,15 +3274,15 @@
     if ((then_stmt == nullptr) && (else_stmt == nullptr)) {
         return nullptr;
     }
-    ast::StatementList if_stmts;
+    StatementList if_stmts;
     if (then_stmt != nullptr) {
-        if_stmts.emplace_back(then_stmt);
+        if_stmts.Push(then_stmt);
     }
     auto* if_block = create<ast::BlockStatement>(Source{}, if_stmts);
 
     const ast::Statement* else_block = nullptr;
     if (else_stmt) {
-        else_block = create<ast::BlockStatement>(ast::StatementList{else_stmt});
+        else_block = create<ast::BlockStatement>(StatementList{else_stmt});
     }
 
     auto* if_stmt = create<ast::IfStatement>(Source{}, condition, if_block, else_block);
@@ -3347,7 +3336,7 @@
         return true;
     }
     // Returns the given list of local definition IDs, sorted by their index.
-    auto sorted_by_index = [this](const std::vector<uint32_t>& ids) {
+    auto sorted_by_index = [this](auto& ids) {
         auto sorted = ids;
         std::stable_sort(sorted.begin(), sorted.end(),
                          [this](const uint32_t lhs, const uint32_t rhs) {
@@ -3363,7 +3352,7 @@
         auto* storage_type = RemapStorageClass(parser_impl_.ConvertType(def_inst->type_id()), id);
         AddStatement(create<ast::VariableDeclStatement>(
             Source{}, parser_impl_.MakeVar(id, ast::StorageClass::kNone, storage_type, nullptr,
-                                           ast::AttributeList{})));
+                                           AttributeList{})));
         auto* type = ty_.Reference(storage_type, ast::StorageClass::kNone);
         identifier_types_.emplace(id, type);
     }
@@ -3394,7 +3383,7 @@
 
     // Emit assignments to carry values to phi nodes in potential destinations.
     // Do it in index order.
-    if (!block_info.phi_assignments.empty()) {
+    if (!block_info.phi_assignments.IsEmpty()) {
         auto sorted = block_info.phi_assignments;
         std::stable_sort(
             sorted.begin(), sorted.end(),
@@ -3813,8 +3802,8 @@
 
     const char* unary_builtin_name = GetUnaryBuiltInFunctionName(opcode);
     if (unary_builtin_name != nullptr) {
-        ast::ExpressionList params;
-        params.emplace_back(MakeOperand(inst, 0).expr);
+        ExpressionList params;
+        params.Push(MakeOperand(inst, 0).expr);
         return {ast_type, create<ast::CallExpression>(
                               Source{},
                               create<ast::IdentifierExpression>(
@@ -3892,9 +3881,9 @@
     }
 
     if (opcode == SpvOpCompositeConstruct) {
-        ast::ExpressionList operands;
+        ExpressionList operands;
         for (uint32_t iarg = 0; iarg < inst.NumInOperands(); ++iarg) {
-            operands.emplace_back(MakeOperand(inst, iarg).expr);
+            operands.Push(MakeOperand(inst, iarg).expr);
         }
         return {ast_type,
                 builder_.Construct(Source{}, ast_type->Build(builder_), std::move(operands))};
@@ -3962,7 +3951,7 @@
         auto e1 = MakeOperand(inst, 2);
         auto e2 = ToSignedIfUnsigned(MakeOperand(inst, 3));
 
-        return {e1.type, builder_.Call(Source{}, "ldexp", ast::ExpressionList{e1.expr, e2.expr})};
+        return {e1.type, builder_.Call(Source{}, "ldexp", utils::Vector{e1.expr, e2.expr})};
     }
 
     auto* result_type = parser_impl_.ConvertType(inst.type_id());
@@ -3988,16 +3977,19 @@
                 TINT_ASSERT(Reader, normal.type->Is<F32>());
                 TINT_ASSERT(Reader, incident.type->Is<F32>());
                 TINT_ASSERT(Reader, nref.type->Is<F32>());
-                return {ty_.F32(),
-                        builder_.Call(
-                            Source{}, "select",
-                            ast::ExpressionList{create<ast::UnaryOpExpression>(
-                                                    Source{}, ast::UnaryOp::kNegation, normal.expr),
-                                                normal.expr,
-                                                create<ast::BinaryExpression>(
-                                                    Source{}, ast::BinaryOp::kLessThan,
-                                                    builder_.Mul({}, incident.expr, nref.expr),
-                                                    builder_.Expr(0_f))})};
+                return {
+                    ty_.F32(),
+                    builder_.Call(
+                        Source{}, "select",
+                        utils::Vector{
+                            create<ast::UnaryOpExpression>(Source{}, ast::UnaryOp::kNegation,
+                                                           normal.expr),
+                            normal.expr,
+                            create<ast::BinaryExpression>(
+                                Source{}, ast::BinaryOp::kLessThan,
+                                builder_.Mul({}, incident.expr, nref.expr), builder_.Expr(0_f)),
+                        }),
+                };
             }
 
             case GLSLstd450Reflect: {
@@ -4028,13 +4020,17 @@
                     return {};
                 }
                 const Type* f32 = eta.type;
-                return {f32, builder_.MemberAccessor(
-                                 builder_.Call(
-                                     Source{}, "refract",
-                                     ast::ExpressionList{
-                                         builder_.vec2<tint::f32>(incident.expr, 0_f),
-                                         builder_.vec2<tint::f32>(normal.expr, 0_f), eta.expr}),
-                                 "x")};
+                return {
+                    f32,
+                    builder_.MemberAccessor(
+                        builder_.Call(Source{}, "refract",
+                                      utils::Vector{
+                                          builder_.vec2<tint::f32>(incident.expr, 0_f),
+                                          builder_.vec2<tint::f32>(normal.expr, 0_f),
+                                          eta.expr,
+                                      }),
+                        "x"),
+                };
             }
             default:
                 break;
@@ -4048,7 +4044,7 @@
     }
 
     auto* func = create<ast::IdentifierExpression>(Source{}, builder_.Symbols().Register(name));
-    ast::ExpressionList operands;
+    ExpressionList operands;
     const Type* first_operand_type = nullptr;
     // All parameters to GLSL.std.450 extended instructions are IDs.
     for (uint32_t iarg = 2; iarg < inst.NumInOperands(); ++iarg) {
@@ -4056,7 +4052,7 @@
         if (first_operand_type == nullptr) {
             first_operand_type = operand.type;
         }
-        operands.emplace_back(operand.expr);
+        operands.Push(operand.expr);
     }
     auto* call = create<ast::CallExpression>(Source{}, func, std::move(operands));
     TypedExpression call_expr{result_type, call};
@@ -4483,7 +4479,7 @@
     // Assume the literal indices are valid, and there is a valid number of them.
     auto source = GetSourceForInst(inst);
     const Vector* result_type = As<Vector>(parser_impl_.ConvertType(inst.type_id()));
-    ast::ExpressionList values;
+    ExpressionList values;
     for (uint32_t i = 2; i < inst.NumInOperands(); ++i) {
         const auto index = inst.GetSingleWordInOperand(i);
         if (index < vec0_len) {
@@ -4491,8 +4487,7 @@
             if (!expr) {
                 return {};
             }
-            values.emplace_back(
-                create<ast::MemberAccessorExpression>(source, expr.expr, Swizzle(index)));
+            values.Push(create<ast::MemberAccessorExpression>(source, expr.expr, Swizzle(index)));
         } else if (index < vec0_len + vec1_len) {
             const auto sub_index = index - vec0_len;
             TINT_ASSERT(Reader, sub_index < kMaxVectorLen);
@@ -4500,18 +4495,19 @@
             if (!expr) {
                 return {};
             }
-            values.emplace_back(
+            values.Push(
                 create<ast::MemberAccessorExpression>(source, expr.expr, Swizzle(sub_index)));
         } else if (index == 0xFFFFFFFF) {
             // By rule, this maps to OpUndef.  Instead, make it zero.
-            values.emplace_back(parser_impl_.MakeNullValue(result_type->type));
+            values.Push(parser_impl_.MakeNullValue(result_type->type));
         } else {
             Fail() << "invalid vectorshuffle ID %" << inst.result_id()
                    << ": index too large: " << index;
             return {};
         }
     }
-    return {result_type, builder_.Construct(source, result_type->Build(builder_), values)};
+    return {result_type,
+            builder_.Construct(source, result_type->Build(builder_), std::move(values))};
 }
 
 bool FunctionEmitter::RegisterSpecialBuiltInVariables() {
@@ -4732,7 +4728,7 @@
                     if (IsInBlockOrder(pred_block_info)) {
                         // Record the assignment that needs to occur at the end
                         // of the predecessor block.
-                        pred_block_info->phi_assignments.push_back({phi_id, value_id});
+                        pred_block_info->phi_assignments.Push({phi_id, value_id});
                         first_pos = std::min(first_pos, pred_block_info->pos);
                         last_pos = std::max(last_pos, pred_block_info->pos);
                     }
@@ -4740,8 +4736,7 @@
 
                 // Schedule the declaration of the state variable.
                 const auto* enclosing_construct = GetEnclosingScope(first_pos, last_pos);
-                GetBlockInfo(enclosing_construct->begin_id)
-                    ->phis_needing_state_vars.push_back(phi_id);
+                GetBlockInfo(enclosing_construct->begin_id)->phis_needing_state_vars.Push(phi_id);
             }
         }
     }
@@ -4812,7 +4807,7 @@
                 // TODO(dneto): Handle non-storable types, particularly pointers.
                 def_info->requires_hoisted_def = true;
                 auto* hoist_to_block = GetBlockInfo(enclosing_construct->begin_id);
-                hoist_to_block->hoisted_ids.push_back(def_id);
+                hoist_to_block->hoisted_ids.Push(def_id);
             }
         }
     }
@@ -4874,8 +4869,8 @@
         return {};
     }
 
-    ast::ExpressionList params;
-    params.push_back(arg_expr.expr);
+    ExpressionList params;
+    params.Push(arg_expr.expr);
     TypedExpression result{
         expr_type,
         builder_.Construct(GetSourceForInst(inst), expr_type->Build(builder_), std::move(params))};
@@ -4893,7 +4888,7 @@
     auto name = namer_.Name(inst.GetSingleWordInOperand(0));
     auto* function = create<ast::IdentifierExpression>(Source{}, builder_.Symbols().Register(name));
 
-    ast::ExpressionList args;
+    ExpressionList args;
     for (uint32_t iarg = 1; iarg < inst.NumInOperands(); ++iarg) {
         auto expr = MakeOperand(inst, iarg);
         if (!expr) {
@@ -4902,7 +4897,7 @@
         // Functions cannot use references as parameters, so we need to pass by
         // pointer if the operand is of pointer type.
         expr = AddressOfIfNeeded(expr, def_use_mgr_->GetDef(inst.GetSingleWordInOperand(iarg)));
-        args.emplace_back(expr.expr);
+        args.Push(expr.expr);
     }
     if (failed()) {
         return false;
@@ -4969,14 +4964,14 @@
     auto* name = sem::str(builtin);
     auto* ident = create<ast::IdentifierExpression>(Source{}, builder_.Symbols().Register(name));
 
-    ast::ExpressionList params;
+    ExpressionList params;
     const Type* first_operand_type = nullptr;
     for (uint32_t iarg = 0; iarg < inst.NumInOperands(); ++iarg) {
         TypedExpression operand = MakeOperand(inst, iarg);
         if (first_operand_type == nullptr) {
             first_operand_type = operand.type;
         }
-        params.emplace_back(operand.expr);
+        params.Push(operand.expr);
     }
     auto* call_expr = create<ast::CallExpression>(Source{}, ident, std::move(params));
     auto* result_type = parser_impl_.ConvertType(inst.type_id());
@@ -5001,11 +4996,11 @@
     auto* op_ty = true_value.type;
     if (op_ty->Is<Vector>() || op_ty->IsFloatScalar() || op_ty->IsIntegerScalar() ||
         op_ty->Is<Bool>()) {
-        ast::ExpressionList params;
-        params.push_back(false_value.expr);
-        params.push_back(true_value.expr);
+        ExpressionList params;
+        params.Push(false_value.expr);
+        params.Push(true_value.expr);
         // The condition goes last.
-        params.push_back(condition.expr);
+        params.Push(condition.expr);
         return {op_ty,
                 create<ast::CallExpression>(Source{},
                                             create<ast::IdentifierExpression>(
@@ -5080,7 +5075,7 @@
 }
 
 bool FunctionEmitter::EmitImageAccess(const spvtools::opt::Instruction& inst) {
-    ast::ExpressionList args;
+    ExpressionList args;
     const auto opcode = inst.opcode();
 
     // Form the texture operand.
@@ -5088,13 +5083,13 @@
     if (!image) {
         return false;
     }
-    args.push_back(GetImageExpression(inst));
+    args.Push(GetImageExpression(inst));
 
     // Form the sampler operand, if needed.
     if (IsSampledImageAccess(opcode)) {
         // Form the sampler operand.
         if (auto* sampler = GetSamplerExpression(inst)) {
-            args.push_back(sampler);
+            args.Push(sampler);
         } else {
             return false;
         }
@@ -5116,10 +5111,12 @@
 
     // Push the coordinates operands.
     auto coords = MakeCoordinateOperandsForImageAccess(inst);
-    if (coords.empty()) {
+    if (coords.IsEmpty()) {
         return false;
     }
-    args.insert(args.end(), coords.begin(), coords.end());
+    for (auto* coord : coords) {
+        args.Push(coord);
+    }
     // Skip the coordinates operand.
     arg_index++;
 
@@ -5129,7 +5126,7 @@
     // the parameter list. Issues a diagnostic and returns false on error.
     auto consume_dref = [&]() -> bool {
         if (arg_index < num_args) {
-            args.push_back(MakeOperand(inst, arg_index).expr);
+            args.Push(MakeOperand(inst, arg_index).expr);
             arg_index++;
         } else {
             return Fail() << "image depth-compare instruction is missing a Dref operand: "
@@ -5166,7 +5163,12 @@
             builtin_name = "textureGather";
             if (!texture_type->Is<DepthTexture>()) {
                 // The explicit component is the *first* argument in WGSL.
-                args.insert(args.begin(), ToI32(MakeOperand(inst, arg_index)).expr);
+                ExpressionList gather_args;
+                gather_args.Push(ToI32(MakeOperand(inst, arg_index)).expr);
+                for (auto* arg : args) {
+                    gather_args.Push(arg);
+                }
+                args = std::move(gather_args);
             }
             // Skip over the component operand, even for depth textures.
             arg_index++;
@@ -5194,7 +5196,7 @@
                     return false;
                 }
 
-                args.push_back(converted_texel);
+                args.Push(converted_texel);
                 arg_index++;
             } else {
                 return Fail() << "image write is missing a Texel operand: " << inst.PrettyPrint();
@@ -5223,7 +5225,7 @@
                           << inst.PrettyPrint();
         }
         builtin_name += "Bias";
-        args.push_back(MakeOperand(inst, arg_index).expr);
+        args.Push(MakeOperand(inst, arg_index).expr);
         image_operands_mask ^= SpvImageOperandsBiasMask;
         arg_index++;
     }
@@ -5250,7 +5252,7 @@
                 // Convert it to a signed integer type.
                 lod = ToI32(lod);
             }
-            args.push_back(lod.expr);
+            args.Push(lod.expr);
         }
 
         image_operands_mask ^= SpvImageOperandsLodMask;
@@ -5259,7 +5261,7 @@
                !texture_type->IsAnyOf<DepthMultisampledTexture, MultisampledTexture>()) {
         // textureLoad requires an explicit level-of-detail parameter for
         // non-multisampled texture types.
-        args.push_back(parser_impl_.MakeNullValue(ty_.I32()));
+        args.Push(parser_impl_.MakeNullValue(ty_.I32()));
     }
     if (arg_index + 1 < num_args && (image_operands_mask & SpvImageOperandsGradMask)) {
         if (is_dref_sample) {
@@ -5273,8 +5275,8 @@
                           << inst.PrettyPrint();
         }
         builtin_name += "Grad";
-        args.push_back(MakeOperand(inst, arg_index).expr);
-        args.push_back(MakeOperand(inst, arg_index + 1).expr);
+        args.Push(MakeOperand(inst, arg_index).expr);
+        args.Push(MakeOperand(inst, arg_index + 1).expr);
         image_operands_mask ^= SpvImageOperandsGradMask;
         arg_index += 2;
     }
@@ -5295,13 +5297,13 @@
                               << inst.PrettyPrint();
         }
 
-        args.push_back(ToSignedIfUnsigned(MakeOperand(inst, arg_index)).expr);
+        args.Push(ToSignedIfUnsigned(MakeOperand(inst, arg_index)).expr);
         image_operands_mask ^= SpvImageOperandsConstOffsetMask;
         arg_index++;
     }
     if (arg_index < num_args && (image_operands_mask & SpvImageOperandsSampleMask)) {
         // TODO(dneto): only permitted with ImageFetch
-        args.push_back(ToI32(MakeOperand(inst, arg_index)).expr);
+        args.Push(ToI32(MakeOperand(inst, arg_index)).expr);
         image_operands_mask ^= SpvImageOperandsSampleMask;
         arg_index++;
     }
@@ -5343,12 +5345,14 @@
         // first component.
         if (texture_type->IsAnyOf<DepthTexture, DepthMultisampledTexture>()) {
             if (is_non_dref_sample || (opcode == SpvOpImageFetch)) {
-                value = builder_.Construct(
-                    Source{},
-                    result_type->Build(builder_),  // a vec4
-                    ast::ExpressionList{value, parser_impl_.MakeNullValue(result_component_type),
-                                        parser_impl_.MakeNullValue(result_component_type),
-                                        parser_impl_.MakeNullValue(result_component_type)});
+                value = builder_.Construct(Source{},
+                                           result_type->Build(builder_),  // a vec4
+                                           utils::Vector{
+                                               value,
+                                               parser_impl_.MakeNullValue(result_component_type),
+                                               parser_impl_.MakeNullValue(result_component_type),
+                                               parser_impl_.MakeNullValue(result_component_type),
+                                           });
             }
         }
 
@@ -5399,15 +5403,15 @@
     switch (opcode) {
         case SpvOpImageQuerySize:
         case SpvOpImageQuerySizeLod: {
-            ast::ExpressionList exprs;
+            ExpressionList exprs;
             // Invoke textureDimensions.
             // If the texture is arrayed, combine with the result from
             // textureNumLayers.
             auto* dims_ident = create<ast::IdentifierExpression>(
                 Source{}, builder_.Symbols().Register("textureDimensions"));
-            ast::ExpressionList dims_args{GetImageExpression(inst)};
+            ExpressionList dims_args{GetImageExpression(inst)};
             if (opcode == SpvOpImageQuerySizeLod) {
-                dims_args.push_back(ToI32(MakeOperand(inst, 1)).expr);
+                dims_args.Push(ToI32(MakeOperand(inst, 1)).expr);
             }
             const ast::Expression* dims_call =
                 create<ast::CallExpression>(Source{}, dims_ident, dims_args);
@@ -5418,16 +5422,17 @@
                 dims_call =
                     create<ast::MemberAccessorExpression>(Source{}, dims_call, PrefixSwizzle(2));
             }
-            exprs.push_back(dims_call);
+            exprs.Push(dims_call);
             if (ast::IsTextureArray(dims)) {
                 auto* layers_ident = create<ast::IdentifierExpression>(
                     Source{}, builder_.Symbols().Register("textureNumLayers"));
-                exprs.push_back(create<ast::CallExpression>(
-                    Source{}, layers_ident, ast::ExpressionList{GetImageExpression(inst)}));
+                exprs.Push(create<ast::CallExpression>(Source{}, layers_ident,
+                                                       utils::Vector{GetImageExpression(inst)}));
             }
             auto* result_type = parser_impl_.ConvertType(inst.type_id());
             TypedExpression expr = {
-                result_type, builder_.Construct(Source{}, result_type->Build(builder_), exprs)};
+                result_type,
+                builder_.Construct(Source{}, result_type->Build(builder_), std::move(exprs))};
             return EmitConstDefOrWriteToHoistedVar(inst, expr);
         }
         case SpvOpImageQueryLod:
@@ -5441,13 +5446,13 @@
             auto* levels_ident =
                 create<ast::IdentifierExpression>(Source{}, builder_.Symbols().Register(name));
             const ast::Expression* ast_expr = create<ast::CallExpression>(
-                Source{}, levels_ident, ast::ExpressionList{GetImageExpression(inst)});
+                Source{}, levels_ident, utils::Vector{GetImageExpression(inst)});
             auto* result_type = parser_impl_.ConvertType(inst.type_id());
             // The SPIR-V result type must be integer scalar. The WGSL bulitin
             // returns i32. If they aren't the same then convert the result.
             if (!result_type->Is<I32>()) {
                 ast_expr = builder_.Construct(Source{}, result_type->Build(builder_),
-                                              ast::ExpressionList{ast_expr});
+                                              utils::Vector{ast_expr});
             }
             TypedExpression expr{result_type, ast_expr};
             return EmitConstDefOrWriteToHoistedVar(inst, expr);
@@ -5461,14 +5466,14 @@
 bool FunctionEmitter::EmitAtomicOp(const spvtools::opt::Instruction& inst) {
     auto emit_atomic = [&](sem::BuiltinType builtin, std::initializer_list<TypedExpression> args) {
         // Split args into params and expressions
-        ast::ParameterList params;
-        params.reserve(args.size());
-        ast::ExpressionList exprs;
-        exprs.reserve(args.size());
+        ParameterList params;
+        params.Reserve(args.size());
+        ExpressionList exprs;
+        exprs.Reserve(args.size());
         size_t i = 0;
         for (auto& a : args) {
-            params.emplace_back(builder_.Param("p" + std::to_string(i++), a.type->Build(builder_)));
-            exprs.emplace_back(a.expr);
+            params.Push(builder_.Param("p" + std::to_string(i++), a.type->Build(builder_)));
+            exprs.Push(a.expr);
         }
 
         // Function return type
@@ -5486,15 +5491,15 @@
         auto* stub =
             create<ast::Function>(Source{}, sym, std::move(params), ret_type,
                                   /* body */ nullptr,
-                                  ast::AttributeList{
+                                  AttributeList{
                                       stub_deco,
                                       builder_.Disable(ast::DisabledValidation::kFunctionHasNoBody),
                                   },
-                                  ast::AttributeList{});
+                                  AttributeList{});
         builder_.AST().AddFunction(stub);
 
         // Emit call to stub, will be replaced with call to atomic builtin by transform::SpirvAtomic
-        auto* call = builder_.Call(Source{}, sym, exprs);
+        auto* call = builder_.Call(Source{}, sym, std::move(exprs));
         if (inst.type_id() != 0) {
             auto* result_type = parser_impl_.ConvertType(inst.type_id());
             TypedExpression expr{result_type, call};
@@ -5567,7 +5572,7 @@
     return Fail() << "unhandled atomic op: " << inst.PrettyPrint();
 }
 
-ast::ExpressionList FunctionEmitter::MakeCoordinateOperandsForImageAccess(
+FunctionEmitter::ExpressionList FunctionEmitter::MakeCoordinateOperandsForImageAccess(
     const spvtools::opt::Instruction& inst) {
     if (!parser_impl_.success()) {
         Fail();
@@ -5642,7 +5647,7 @@
         return {};
     }
 
-    ast::ExpressionList result;
+    ExpressionList result;
 
     // Generates the expression for the WGSL coordinates, when it is a prefix
     // swizzle with num_axes.  If the result would be unsigned, also converts
@@ -5669,7 +5674,7 @@
         // The source must be a vector. It has at least one coordinate component
         // and it must have an array component.  Use a vector swizzle to get the
         // first `num_axes` components.
-        result.push_back(prefix_swizzle_expr());
+        result.Push(prefix_swizzle_expr());
 
         // Now get the array index.
         const ast::Expression* array_index =
@@ -5682,16 +5687,16 @@
             array_index = builder_.Call("round", array_index);
         }
         // Convert it to a signed integer type, if needed.
-        result.push_back(ToI32({component_type, array_index}).expr);
+        result.Push(ToI32({component_type, array_index}).expr);
     } else {
         if (num_coords_supplied == num_coords_required && !is_proj) {
             // Pass the value through, with possible unsigned->signed conversion.
-            result.push_back(ToSignedIfUnsigned(raw_coords).expr);
+            result.Push(ToSignedIfUnsigned(raw_coords).expr);
         } else {
             // There are more coordinates supplied than needed. So the source type
             // is a vector. Use a vector swizzle to get the first `num_axes`
             // components.
-            result.push_back(prefix_swizzle_expr());
+            result.Push(prefix_swizzle_expr());
         }
     }
     return result;
@@ -5752,10 +5757,10 @@
         // Expand the texel to a 4 element vector.
         auto* component_type = texel.type->IsScalar() ? texel.type : texel.type->As<Vector>()->type;
         texel.type = ty_.Vector(component_type, dest_count);
-        ast::ExpressionList exprs;
-        exprs.push_back(texel.expr);
+        ExpressionList exprs;
+        exprs.Push(texel.expr);
         for (auto i = src_count; i < dest_count; i++) {
-            exprs.push_back(parser_impl_.MakeNullExpression(component_type).expr);
+            exprs.Push(parser_impl_.MakeNullExpression(component_type).expr);
         }
         texel.expr = builder_.Construct(Source{}, texel.type->Build(builder_), std::move(exprs));
     }
@@ -5767,8 +5772,7 @@
     if (!value || value.type->Is<I32>()) {
         return value;
     }
-    return {ty_.I32(),
-            builder_.Construct(Source{}, builder_.ty.i32(), ast::ExpressionList{value.expr})};
+    return {ty_.I32(), builder_.Construct(Source{}, builder_.ty.i32(), utils::Vector{value.expr})};
 }
 
 TypedExpression FunctionEmitter::ToSignedIfUnsigned(TypedExpression value) {
@@ -5777,8 +5781,7 @@
     }
     if (auto* vec_type = value.type->As<Vector>()) {
         auto* new_type = ty_.Vector(ty_.I32(), vec_type->size);
-        return {new_type,
-                builder_.Construct(new_type->Build(builder_), ast::ExpressionList{value.expr})};
+        return {new_type, builder_.Construct(new_type->Build(builder_), utils::Vector{value.expr})};
     }
     return ToI32(value);
 }
@@ -5841,20 +5844,22 @@
     //      | c.y * r.x   c.y * r.y |
     //      | c.z * r.x   c.z * r.y |
 
-    ast::ExpressionList result_columns;
+    ExpressionList result_columns;
     for (uint32_t icol = 0; icol < result_ty->columns; icol++) {
-        ast::ExpressionList result_row;
+        ExpressionList result_row;
         auto* row_factor = create<ast::MemberAccessorExpression>(Source{}, row.expr, Swizzle(icol));
         for (uint32_t irow = 0; irow < result_ty->rows; irow++) {
             auto* column_factor =
                 create<ast::MemberAccessorExpression>(Source{}, col.expr, Swizzle(irow));
             auto* elem = create<ast::BinaryExpression>(Source{}, ast::BinaryOp::kMultiply,
                                                        row_factor, column_factor);
-            result_row.push_back(elem);
+            result_row.Push(elem);
         }
-        result_columns.push_back(builder_.Construct(Source{}, col_ty->Build(builder_), result_row));
+        result_columns.Push(
+            builder_.Construct(Source{}, col_ty->Build(builder_), std::move(result_row)));
     }
-    return {result_ty, builder_.Construct(Source{}, result_ty->Build(builder_), result_columns)};
+    return {result_ty,
+            builder_.Construct(Source{}, result_ty->Build(builder_), std::move(result_columns))};
 }
 
 bool FunctionEmitter::MakeVectorInsertDynamic(const spvtools::opt::Instruction& inst) {
diff --git a/src/tint/reader/spirv/function.h b/src/tint/reader/spirv/function.h
index ae7a9a1..7a2a86b 100644
--- a/src/tint/reader/spirv/function.h
+++ b/src/tint/reader/spirv/function.h
@@ -16,6 +16,7 @@
 #define SRC_TINT_READER_SPIRV_FUNCTION_H_
 
 #include <memory>
+#include <optional>
 #include <string>
 #include <unordered_map>
 #include <unordered_set>
@@ -125,7 +126,7 @@
     /// switch?
     bool default_is_merge = false;
     /// The list of switch values that cause a branch to this block.
-    std::unique_ptr<std::vector<uint64_t>> case_values;
+    std::optional<utils::Vector<uint64_t, 4>> case_values;
 
     /// The following fields record relationships among blocks in a selection
     /// construct for an OpBranchConditional instruction.
@@ -158,7 +159,7 @@
     /// The result IDs that this block is responsible for declaring as a
     /// hoisted variable.
     /// @see DefInfo#requires_hoisted_def
-    std::vector<uint32_t> hoisted_ids;
+    utils::Vector<uint32_t, 4> hoisted_ids;
 
     /// A PhiAssignment represents the assignment of a value to the state
     /// variable associated with an OpPhi in a successor block.
@@ -170,10 +171,10 @@
     };
     /// If this basic block branches to a visited basic block containing phis,
     /// then this is the list of writes to the variables associated those phis.
-    std::vector<PhiAssignment> phi_assignments;
+    utils::Vector<PhiAssignment, 4> phi_assignments;
     /// The IDs of OpPhi instructions which require their associated state
     /// variable to be declared in this basic block.
-    std::vector<uint32_t> phis_needing_state_vars;
+    utils::Vector<uint32_t, 4> phis_needing_state_vars;
 };
 
 /// Writes the BlockInfo to the ostream
@@ -388,6 +389,12 @@
 
 /// A FunctionEmitter emits a SPIR-V function onto a Tint AST module.
 class FunctionEmitter {
+    using AttributeList = utils::Vector<const ast::Attribute*, 8>;
+    using StructMemberList = utils::Vector<const ast::StructMember*, 8>;
+    using ExpressionList = utils::Vector<const ast::Expression*, 8>;
+    using ParameterList = utils::Vector<const ast::Parameter*, 8>;
+    using StatementList = utils::Vector<const ast::Statement*, 8>;
+
   public:
     /// Creates a FunctionEmitter, and prepares to write to the AST module
     /// in `pi`
@@ -420,7 +427,7 @@
     /// Finalizes any StatementBuilders returns the body of the function.
     /// Must only be called once, and to be used only for testing.
     /// @returns the body of the function.
-    const ast::StatementList ast_body();
+    StatementList ast_body();
 
     /// Records failure.
     /// @returns a FailStream on which to emit diagnostics.
@@ -455,12 +462,12 @@
     /// @returns false if emission failed
     bool EmitPipelineInput(std::string var_name,
                            const Type* var_type,
-                           ast::AttributeList* decos,
-                           std::vector<int> index_prefix,
+                           AttributeList* decos,
+                           utils::Vector<int, 8> index_prefix,
                            const Type* tip_type,
                            const Type* forced_param_type,
-                           ast::ParameterList* params,
-                           ast::StatementList* statements);
+                           ParameterList* params,
+                           StatementList* statements);
 
     /// Creates one or more struct members from an output variable, and the
     /// expressions that compute the value they contribute to the entry point
@@ -471,37 +478,30 @@
     /// @param var_name The name of the variable
     /// @param var_type The store type of the variable
     /// @param decos The variable's decorations
-    /// @param index_prefix Indices stepping into the variable, indicating
-    /// what part of the variable to populate.
-    /// @param tip_type The type of the component inside variable, after indexing
-    /// with the indices in `index_prefix`.
-    /// @param forced_member_type The type forced by WGSL, if the variable is a
-    /// builtin, otherwise the same as var_type.
-    /// @param return_members The struct member list where the new member is
-    /// added.
-    /// @param return_exprs The expression list where the return expression is
-    /// added.
+    /// @param index_prefix Indices stepping into the variable, indicating what part of the variable
+    /// to populate.
+    /// @param tip_type The type of the component inside variable, after indexing with the indices
+    /// in `index_prefix`.
+    /// @param forced_member_type The type forced by WGSL, if the variable is a builtin, otherwise
+    /// the same as var_type.
+    /// @param return_members The struct member list where the new member is added.
+    /// @param return_exprs The expression list where the return expression is added.
     /// @returns false if emission failed
     bool EmitPipelineOutput(std::string var_name,
                             const Type* var_type,
-                            ast::AttributeList* decos,
-                            std::vector<int> index_prefix,
+                            AttributeList* decos,
+                            utils::Vector<int, 8> index_prefix,
                             const Type* tip_type,
                             const Type* forced_member_type,
-                            ast::StructMemberList* return_members,
-                            ast::ExpressionList* return_exprs);
+                            StructMemberList* return_members,
+                            ExpressionList* return_exprs);
 
     /// Updates the attribute list, replacing an existing Location attribute
     /// with another having one higher location value. Does nothing if no
     /// location attribute exists.
     /// Assumes the list contains at most one Location attribute.
     /// @param attributes the attribute list to modify
-    void IncrementLocation(ast::AttributeList* attributes);
-
-    /// Returns the Location attribute, if it exists.
-    /// @param attributes the list of attributes to search
-    /// @returns the Location attribute, or nullptr if it doesn't exist
-    const ast::Attribute* GetLocation(const ast::AttributeList& attributes);
+    void IncrementLocation(AttributeList* attributes);
 
     /// Create an ast::BlockStatement representing the body of the function.
     /// This creates the statement stack, which is non-empty for the lifetime
@@ -913,7 +913,7 @@
     /// On failure, issues an error and returns an empty expression list.
     /// @param image_access the image access instruction
     /// @returns an ExpressionList of the coordinate and array index (if any)
-    ast::ExpressionList MakeCoordinateOperandsForImageAccess(
+    ExpressionList MakeCoordinateOperandsForImageAccess(
         const spvtools::opt::Instruction& image_access);
 
     /// Returns the given value as an I32.  If it's already an I32 then this
@@ -951,11 +951,11 @@
         /// Function name
         std::string name;
         /// Function parameters
-        ast::ParameterList params;
+        ParameterList params;
         /// Function return type
         const Type* return_type;
         /// Function attributes
-        ast::AttributeList attributes;
+        AttributeList attributes;
     };
 
     /// Parse the function declaration, which comprises the name, parameters, and
@@ -1117,8 +1117,8 @@
     /// @return the built StatementBuilder
     template <typename T, typename... ARGS>
     T* AddStatementBuilder(ARGS&&... args) {
-        TINT_ASSERT(Reader, !statements_stack_.empty());
-        return statements_stack_.back().AddStatementBuilder<T>(std::forward<ARGS>(args)...);
+        TINT_ASSERT(Reader, !statements_stack_.IsEmpty());
+        return statements_stack_.Back().AddStatementBuilder<T>(std::forward<ARGS>(args)...);
     }
 
     /// Returns the source record for the given instruction.
@@ -1126,10 +1126,10 @@
     /// @return the Source record, or a default one
     Source GetSourceForInst(const spvtools::opt::Instruction& inst) const;
 
-    /// @returns the last statetment in the top of the statement stack.
+    /// @returns the last statement in the top of the statement stack.
     const ast::Statement* LastStatement();
 
-    using CompletionAction = std::function<void(const ast::StatementList&)>;
+    using CompletionAction = std::function<void(const StatementList&)>;
 
     // A StatementBlock represents a braced-list of statements while it is being
     // constructed.
@@ -1181,7 +1181,7 @@
 
         /// @return the list of statements being built, if this construct is not a
         /// switch.
-        const ast::StatementList& GetStatements() const { return statements_; }
+        const StatementList& GetStatements() const { return statements_; }
 
       private:
         /// The construct to which this construct constributes.
@@ -1193,7 +1193,7 @@
         /// The completion action finishes processing this statement block.
         FunctionEmitter::CompletionAction const completion_action_;
         /// The list of statements being built, if this construct is not a switch.
-        ast::StatementList statements_;
+        StatementList statements_;
 
         /// Owned statement builders
         std::vector<std::unique_ptr<StatementBuilder>> builders_;
@@ -1251,7 +1251,6 @@
         return builder_.create<T>(std::forward<ARGS>(args)...);
     }
 
-    using StatementsStack = std::vector<StatementBlock>;
     using PtrAs = ParserImpl::PtrAs;
 
     ParserImpl& parser_impl_;
@@ -1275,7 +1274,7 @@
     // for the entire function.  This stack is never empty.
     // The `construct` member for the 0th element is only valid during the
     // lifetime of the EmitFunctionBodyStatements method.
-    StatementsStack statements_stack_;
+    utils::Vector<StatementBlock, 8> statements_stack_;
 
     // The map of IDs that have already had an identifier name generated for it,
     // to their Type.
diff --git a/src/tint/reader/spirv/function_call_test.cc b/src/tint/reader/spirv/function_call_test.cc
index 584ad16..6b12ece 100644
--- a/src/tint/reader/spirv/function_call_test.cc
+++ b/src/tint/reader/spirv/function_call_test.cc
@@ -87,13 +87,13 @@
      OpFunctionEnd
   )"));
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-    ast::StatementList f100;
+    utils::Vector<const ast::Statement*, 4> f100;
     {
         auto fe = p->function_emitter(100);
         EXPECT_TRUE(fe.EmitBody()) << p->error();
         f100 = fe.ast_body();
     }
-    ast::StatementList f50;
+    utils::Vector<const ast::Statement*, 4> f50;
     {
         auto fe = p->function_emitter(50);
         EXPECT_TRUE(fe.EmitBody()) << p->error();
@@ -128,13 +128,13 @@
      OpFunctionEnd
   )"));
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
-    ast::StatementList f100;
+    utils::Vector<const ast::Statement*, 4> f100;
     {
         auto fe = p->function_emitter(100);
         EXPECT_TRUE(fe.EmitBody()) << p->error();
         f100 = fe.ast_body();
     }
-    ast::StatementList f50;
+    utils::Vector<const ast::Statement*, 4> f50;
     {
         auto fe = p->function_emitter(50);
         EXPECT_TRUE(fe.EmitBody()) << p->error();
diff --git a/src/tint/reader/spirv/function_cfg_test.cc b/src/tint/reader/spirv/function_cfg_test.cc
index efeeee8..fb5aff7 100644
--- a/src/tint/reader/spirv/function_cfg_test.cc
+++ b/src/tint/reader/spirv/function_cfg_test.cc
@@ -2894,8 +2894,8 @@
     fe.ComputeBlockOrderAndPositions();
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
-    EXPECT_EQ(fe.constructs().size(), 1u);
-    auto& c = fe.constructs().front();
+    EXPECT_EQ(fe.constructs().Length(), 1u);
+    auto& c = fe.constructs().Front();
     EXPECT_THAT(ToString(c), Eq("Construct{ Function [0,1) begin_id:10 end_id:0 "
                                 "depth:0 parent:null }"));
     EXPECT_EQ(fe.GetBlockInfo(10)->construct, c.get());
@@ -2920,8 +2920,8 @@
     fe.ComputeBlockOrderAndPositions();
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
-    EXPECT_EQ(fe.constructs().size(), 1u);
-    auto& c = fe.constructs().front();
+    EXPECT_EQ(fe.constructs().Length(), 1u);
+    auto& c = fe.constructs().Front();
     EXPECT_THAT(ToString(c), Eq("Construct{ Function [0,2) begin_id:10 end_id:0 "
                                 "depth:0 parent:null }"));
     EXPECT_EQ(fe.GetBlockInfo(10)->construct, c.get());
@@ -2955,7 +2955,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 2u);
+    EXPECT_EQ(constructs.Length(), 2u);
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
   Construct{ Function [0,4) begin_id:10 end_id:0 depth:0 parent:null }
   Construct{ IfSelection [0,3) begin_id:10 end_id:99 depth:1 parent:Function@10 }
@@ -3001,7 +3001,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 2u);
+    EXPECT_EQ(constructs.Length(), 2u);
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
   Construct{ Function [0,6) begin_id:5 end_id:0 depth:0 parent:null }
   Construct{ IfSelection [1,4) begin_id:10 end_id:99 depth:1 parent:Function@5 }
@@ -3045,7 +3045,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 2u);
+    EXPECT_EQ(constructs.Length(), 2u);
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
   Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
   Construct{ SwitchSelection [0,4) begin_id:10 end_id:99 depth:1 parent:Function@10 in-c-l-s:SwitchSelection@10 }
@@ -3082,7 +3082,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 2u);
+    EXPECT_EQ(constructs.Length(), 2u);
     // A single-block loop consists *only* of a continue target with one block in
     // it.
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
@@ -3188,7 +3188,7 @@
     EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[1].get());
     EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[1].get());
     EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
-    
+
     // SPIR-V 1.6 Rev 2 made this invalid SPIR-V.
     p->DeliberatelyInvalidSpirv();
 }
@@ -3223,7 +3223,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 3u);
+    EXPECT_EQ(constructs.Length(), 3u);
     // A single-block loop consists *only* of a continue target with one block in
     // it.
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
@@ -3271,7 +3271,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 4u);
+    EXPECT_EQ(constructs.Length(), 4u);
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
   Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
   Construct{ IfSelection [0,2) begin_id:10 end_id:50 depth:1 parent:Function@10 }
@@ -3330,7 +3330,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 4u);
+    EXPECT_EQ(constructs.Length(), 4u);
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
   Construct{ Function [0,9) begin_id:10 end_id:0 depth:0 parent:null }
   Construct{ IfSelection [0,8) begin_id:10 end_id:99 depth:1 parent:Function@10 }
@@ -3390,7 +3390,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 4u);
+    EXPECT_EQ(constructs.Length(), 4u);
     // The ordering among siblings depends on the computed block order.
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
   Construct{ Function [0,8) begin_id:10 end_id:0 depth:0 parent:null }
@@ -3440,7 +3440,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 3u);
+    EXPECT_EQ(constructs.Length(), 3u);
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
   Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
   Construct{ IfSelection [0,4) begin_id:10 end_id:99 depth:1 parent:Function@10 }
@@ -3494,7 +3494,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 4u);
+    EXPECT_EQ(constructs.Length(), 4u);
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
   Construct{ Function [0,8) begin_id:10 end_id:0 depth:0 parent:null }
   Construct{ Continue [4,6) begin_id:50 end_id:89 depth:1 parent:Function@10 in-c:Continue@50 }
@@ -3549,7 +3549,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 4u);
+    EXPECT_EQ(constructs.Length(), 4u);
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
   Construct{ Function [0,7) begin_id:10 end_id:0 depth:0 parent:null }
   Construct{ Continue [5,6) begin_id:80 end_id:99 depth:1 parent:Function@10 in-c:Continue@80 }
@@ -3600,7 +3600,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 4u);
+    EXPECT_EQ(constructs.Length(), 4u);
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
   Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
   Construct{ Continue [2,5) begin_id:30 end_id:99 depth:1 parent:Function@10 in-c:Continue@30 }
@@ -3644,7 +3644,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 3u);
+    EXPECT_EQ(constructs.Length(), 3u);
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
   Construct{ Function [0,4) begin_id:10 end_id:0 depth:0 parent:null }
   Construct{ IfSelection [0,3) begin_id:10 end_id:99 depth:1 parent:Function@10 }
@@ -3693,7 +3693,7 @@
     fe.RegisterMerges();
     EXPECT_TRUE(fe.LabelControlFlowConstructs());
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 4u);
+    EXPECT_EQ(constructs.Length(), 4u);
     EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
   Construct{ Function [0,7) begin_id:10 end_id:0 depth:0 parent:null }
   Construct{ IfSelection [0,6) begin_id:10 end_id:99 depth:1 parent:Function@10 }
@@ -3743,7 +3743,7 @@
     auto fe = p->function_emitter(100);
     ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
     const auto& constructs = fe.constructs();
-    EXPECT_EQ(constructs.size(), 4u);
+    EXPECT_EQ(constructs.Length(), 4u);
     ASSERT_THAT(ToString(constructs), Eq(R"(ConstructList{
   Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
   Construct{ Continue [4,5) begin_id:90 end_id:99 depth:1 parent:Function@10 in-c:Continue@90 }
@@ -4157,7 +4157,7 @@
     EXPECT_EQ(bi10->case_head_for, nullptr);
     EXPECT_EQ(bi10->default_head_for, nullptr);
     EXPECT_FALSE(bi10->default_is_merge);
-    EXPECT_EQ(bi10->case_values.get(), nullptr);
+    EXPECT_FALSE(bi10->case_values.has_value());
 }
 
 TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultIsMerge) {
@@ -4192,7 +4192,7 @@
     ASSERT_NE(bi99->default_head_for, nullptr);
     EXPECT_EQ(bi99->default_head_for->begin_id, 10u);
     EXPECT_TRUE(bi99->default_is_merge);
-    EXPECT_EQ(bi99->case_values.get(), nullptr);
+    EXPECT_FALSE(bi99->case_values.has_value());
 }
 
 TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_DefaultIsNotMerge) {
@@ -4230,7 +4230,7 @@
     ASSERT_NE(bi30->default_head_for, nullptr);
     EXPECT_EQ(bi30->default_head_for->begin_id, 10u);
     EXPECT_FALSE(bi30->default_is_merge);
-    EXPECT_EQ(bi30->case_values.get(), nullptr);
+    EXPECT_FALSE(bi30->case_values.has_value());
 }
 
 TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseIsNotDefault) {
@@ -4268,7 +4268,7 @@
     EXPECT_EQ(bi20->case_head_for->begin_id, 10u);
     EXPECT_EQ(bi20->default_head_for, nullptr);
     EXPECT_FALSE(bi20->default_is_merge);
-    EXPECT_THAT(*(bi20->case_values.get()), UnorderedElementsAre(200));
+    EXPECT_THAT(bi20->case_values.value(), UnorderedElementsAre(200));
 }
 
 TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_CaseIsDefault) {
@@ -4303,7 +4303,7 @@
     EXPECT_EQ(bi20->case_head_for->begin_id, 10u);
     EXPECT_EQ(bi20->default_head_for, bi20->case_head_for);
     EXPECT_FALSE(bi20->default_is_merge);
-    EXPECT_THAT(*(bi20->case_values.get()), UnorderedElementsAre(200));
+    EXPECT_THAT(bi20->case_values.value(), UnorderedElementsAre(200));
 }
 
 TEST_F(SpvParserCFGTest, FindSwitchCaseHeaders_ManyCasesWithSameValue_IsError) {
@@ -4370,7 +4370,7 @@
     EXPECT_EQ(bi20->case_head_for->begin_id, 10u);
     EXPECT_EQ(bi20->default_head_for, nullptr);
     EXPECT_FALSE(bi20->default_is_merge);
-    EXPECT_THAT(*(bi20->case_values.get()), UnorderedElementsAre(200, 300));
+    EXPECT_THAT(bi20->case_values.value(), UnorderedElementsAre(200, 300));
 }
 
 TEST_F(SpvParserCFGTest, ClassifyCFGEdges_BranchEscapesIfConstruct) {
diff --git a/src/tint/reader/spirv/parser_impl.cc b/src/tint/reader/spirv/parser_impl.cc
index 18d5761..e1bff31 100644
--- a/src/tint/reader/spirv/parser_impl.cc
+++ b/src/tint/reader/spirv/parser_impl.cc
@@ -447,10 +447,10 @@
     return "SPIR-V type " + std::to_string(type_id);
 }
 
-ast::AttributeList ParserImpl::ConvertMemberDecoration(uint32_t struct_type_id,
-                                                       uint32_t member_index,
-                                                       const Type* member_ty,
-                                                       const Decoration& decoration) {
+ParserImpl::AttributeList ParserImpl::ConvertMemberDecoration(uint32_t struct_type_id,
+                                                              uint32_t member_index,
+                                                              const Type* member_ty,
+                                                              const Decoration& decoration) {
     if (decoration.empty()) {
         Fail() << "malformed SPIR-V decoration: it's empty";
         return {};
@@ -1067,7 +1067,7 @@
     }
 
     // Compute members
-    ast::StructMemberList ast_members;
+    utils::Vector<const ast::StructMember*, 8> ast_members;
     const auto members = struct_ty->element_types();
     if (members.empty()) {
         Fail() << "WGSL does not support empty structures. can't convert type: "
@@ -1123,7 +1123,7 @@
         }
 
         bool is_non_writable = false;
-        ast::AttributeList ast_member_decorations;
+        AttributeList ast_member_decorations;
         for (auto& decoration : GetDecorationsForMember(type_id, member_index)) {
             if (IsPipelineDecoration(decoration)) {
                 // IO decorations are handled when emitting the entry point.
@@ -1137,7 +1137,7 @@
                 auto decos =
                     ConvertMemberDecoration(type_id, member_index, ast_member_ty, decoration);
                 for (auto* deco : decos) {
-                    ast_member_decorations.emplace_back(deco);
+                    ast_member_decorations.Push(deco);
                 }
                 if (!success_) {
                     return nullptr;
@@ -1154,10 +1154,10 @@
         auto* ast_struct_member = create<ast::StructMember>(
             Source{}, builder_.Symbols().Register(member_name), ast_member_ty->Build(builder_),
             std::move(ast_member_decorations));
-        ast_members.push_back(ast_struct_member);
+        ast_members.Push(ast_struct_member);
     }
 
-    if (ast_members.empty()) {
+    if (ast_members.IsEmpty()) {
         // All members were likely built-ins. Don't generate an empty AST structure.
         return nullptr;
     }
@@ -1168,8 +1168,7 @@
 
     // Now make the struct.
     auto sym = builder_.Symbols().Register(name);
-    auto* ast_struct =
-        create<ast::Struct>(Source{}, sym, std::move(ast_members), ast::AttributeList());
+    auto* ast_struct = create<ast::Struct>(Source{}, sym, std::move(ast_members), utils::Empty);
     if (num_non_writable_members == members.size()) {
         read_only_struct_types_.insert(ast_struct->name);
     }
@@ -1358,7 +1357,7 @@
                 break;
         }
         if (ast_type && ast_expr) {
-            ast::AttributeList spec_id_decos;
+            AttributeList spec_id_decos;
             for (const auto& deco : GetDecorationsFor(inst.result_id())) {
                 if ((deco.size() == 2) && (deco[0] == SpvDecorationSpecId)) {
                     const uint32_t id = deco[1];
@@ -1368,7 +1367,7 @@
                                       << inst.result_id() << " has SpecId " << id;
                     }
                     auto* cid = create<ast::IdAttribute>(Source{}, id);
-                    spec_id_decos.push_back(cid);
+                    spec_id_decos.Push(cid);
                     break;
                 }
             }
@@ -1491,7 +1490,7 @@
             ast_constructor = MakeConstantExpression(var.GetSingleWordInOperand(1)).expr;
         }
         auto* ast_var = MakeVar(var.result_id(), ast_storage_class, ast_store_type, ast_constructor,
-                                ast::AttributeList{});
+                                utils::Empty);
         // TODO(dneto): initializers (a.k.a. constructor expression)
         if (ast_var) {
             builder_.AST().AddGlobalVariable(ast_var);
@@ -1558,7 +1557,7 @@
                               ast::StorageClass sc,
                               const Type* storage_type,
                               const ast::Expression* constructor,
-                              ast::AttributeList decorations) {
+                              AttributeList decorations) {
     if (storage_type == nullptr) {
         Fail() << "internal error: can't make ast::Variable for null type";
         return nullptr;
@@ -1593,14 +1592,13 @@
 
 ast::Let* ParserImpl::MakeLet(uint32_t id, const Type* type, const ast::Expression* constructor) {
     auto sym = builder_.Symbols().Register(namer_.Name(id));
-    return create<ast::Let>(Source{}, sym, type->Build(builder_), constructor,
-                            ast::AttributeList{});
+    return create<ast::Let>(Source{}, sym, type->Build(builder_), constructor, utils::Empty);
 }
 
 ast::Override* ParserImpl::MakeOverride(uint32_t id,
                                         const Type* type,
                                         const ast::Expression* constructor,
-                                        ast::AttributeList decorations) {
+                                        AttributeList decorations) {
     if (!ConvertDecorationsForVariable(id, &type, &decorations, false)) {
         return nullptr;
     }
@@ -1610,7 +1608,7 @@
 
 ast::Parameter* ParserImpl::MakeParameter(uint32_t id,
                                           const Type* type,
-                                          ast::AttributeList decorations) {
+                                          AttributeList decorations) {
     if (!ConvertDecorationsForVariable(id, &type, &decorations, false)) {
         return nullptr;
     }
@@ -1621,7 +1619,7 @@
 
 bool ParserImpl::ConvertDecorationsForVariable(uint32_t id,
                                                const Type** store_type,
-                                               ast::AttributeList* decorations,
+                                               AttributeList* decorations,
                                                bool transfer_pipeline_io) {
     DecorationList non_builtin_pipeline_decorations;
     for (auto& deco : GetDecorationsFor(id)) {
@@ -1681,7 +1679,7 @@
                 return false;
             }
             if (transfer_pipeline_io) {
-                decorations->emplace_back(create<ast::BuiltinAttribute>(Source{}, ast_builtin));
+                decorations->Push(create<ast::BuiltinAttribute>(Source{}, ast_builtin));
             }
         }
         if (transfer_pipeline_io && IsPipelineDecoration(deco)) {
@@ -1692,13 +1690,13 @@
                 return Fail() << "malformed DescriptorSet decoration on ID " << id
                               << ": has no operand";
             }
-            decorations->emplace_back(create<ast::GroupAttribute>(Source{}, deco[1]));
+            decorations->Push(create<ast::GroupAttribute>(Source{}, deco[1]));
         }
         if (deco[0] == SpvDecorationBinding) {
             if (deco.size() == 1) {
                 return Fail() << "malformed Binding decoration on ID " << id << ": has no operand";
             }
-            decorations->emplace_back(create<ast::BindingAttribute>(Source{}, deco[1]));
+            decorations->Push(create<ast::BindingAttribute>(Source{}, deco[1]));
         }
     }
 
@@ -1725,7 +1723,7 @@
     return result;
 }
 
-const ast::Attribute* ParserImpl::SetLocation(ast::AttributeList* attributes,
+const ast::Attribute* ParserImpl::SetLocation(AttributeList* attributes,
                                               const ast::Attribute* replacement) {
     if (!replacement) {
         return nullptr;
@@ -1742,13 +1740,13 @@
         }
     }
     // The list didn't have a location. Add it.
-    attributes->push_back(replacement);
+    attributes->Push(replacement);
     return nullptr;
 }
 
 bool ParserImpl::ConvertPipelineDecorations(const Type* store_type,
                                             const DecorationList& decorations,
-                                            ast::AttributeList* attributes) {
+                                            AttributeList* attributes) {
     // Vulkan defaults to perspective-correct interpolation.
     ast::InterpolationType type = ast::InterpolationType::kPerspective;
     ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone;
@@ -1809,7 +1807,7 @@
         sampling == ast::InterpolationSampling::kNone) {
         // This is the default. Don't add a decoration.
     } else {
-        attributes->emplace_back(create<ast::InterpolateAttribute>(type, sampling));
+        attributes->Push(create<ast::InterpolateAttribute>(type, sampling));
     }
 
     return success();
@@ -1842,7 +1840,7 @@
         auto z = MakeConstantExpression(workgroup_size_builtin_.z_id);
         auto* ast_type = ty_.Vector(x.type, 3);
         return {ast_type, builder_.Construct(Source{}, ast_type->Build(builder_),
-                                             ast::ExpressionList{x.expr, y.expr, z.expr})};
+                                             utils::Vector{x.expr, y.expr, z.expr})};
     } else if (id == workgroup_size_builtin_.x_id) {
         return MakeConstantExpressionForScalarSpirvConstant(
             Source{}, ConvertType(workgroup_size_builtin_.component_type_id),
@@ -1898,14 +1896,14 @@
             // Handle vector, matrix, array, and struct
 
             // Generate a composite from explicit components.
-            ast::ExpressionList ast_components;
+            ExpressionList ast_components;
             if (!inst->WhileEachInId([&](const uint32_t* id_ref) -> bool {
                     auto component = MakeConstantExpression(*id_ref);
                     if (!component) {
                         this->Fail() << "invalid constant with ID " << *id_ref;
                         return false;
                     }
-                    ast_components.emplace_back(component.expr);
+                    ast_components.Push(component.expr);
                     return true;
                 })) {
                 // We've already emitted a diagnostic.
@@ -1996,9 +1994,9 @@
         [&](const Array*) { return builder_.Construct(Source{}, type->Build(builder_)); },
         [&](const Bool*) { return create<ast::BoolLiteralExpression>(Source{}, false); },
         [&](const Struct* struct_ty) {
-            ast::ExpressionList ast_components;
+            ExpressionList ast_components;
             for (auto* member : struct_ty->members) {
-                ast_components.emplace_back(MakeNullValue(member));
+                ast_components.Push(MakeNullValue(member));
             }
             return builder_.Construct(Source{}, original_type->Build(builder_),
                                       std::move(ast_components));
diff --git a/src/tint/reader/spirv/parser_impl.h b/src/tint/reader/spirv/parser_impl.h
index ce790d3..12d6226 100644
--- a/src/tint/reader/spirv/parser_impl.h
+++ b/src/tint/reader/spirv/parser_impl.h
@@ -123,6 +123,9 @@
 
 /// Parser implementation for SPIR-V.
 class ParserImpl : Reader {
+    using AttributeList = utils::Vector<const ast::Attribute*, 8>;
+    using ExpressionList = utils::Vector<const ast::Expression*, 8>;
+
   public:
     /// Creates a new parser
     /// @param input the input data to parse
@@ -252,15 +255,14 @@
     /// a diagnostic), or when the variable should not be emitted, e.g. for a
     /// PointSize builtin.
     /// @param id the ID of the SPIR-V variable
-    /// @param store_type the WGSL store type for the variable, which should be
-    /// prepopulatd
+    /// @param store_type the WGSL store type for the variable, which should be prepopulated
     /// @param attributes the attribute list to populate
     /// @param transfer_pipeline_io true if pipeline IO decorations (builtins,
     /// or locations) will update the store type and the decorations list
     /// @returns false when the variable should not be emitted as a variable
     bool ConvertDecorationsForVariable(uint32_t id,
                                        const Type** store_type,
-                                       ast::AttributeList* attributes,
+                                       AttributeList* attributes,
                                        bool transfer_pipeline_io);
 
     /// Converts SPIR-V decorations for pipeline IO into AST decorations.
@@ -270,7 +272,7 @@
     /// @returns false if conversion fails
     bool ConvertPipelineDecorations(const Type* store_type,
                                     const DecorationList& decorations,
-                                    ast::AttributeList* attributes);
+                                    AttributeList* attributes);
 
     /// Updates the attribute list, placing a non-null location decoration into
     /// the list, replacing an existing one if it exists. Does nothing if the
@@ -280,7 +282,7 @@
     /// @param replacement the location decoration to place into the list
     /// @returns the location decoration that was replaced, if one was replaced,
     /// or null otherwise.
-    const ast::Attribute* SetLocation(ast::AttributeList* decos, const ast::Attribute* replacement);
+    const ast::Attribute* SetLocation(AttributeList* decos, const ast::Attribute* replacement);
 
     /// Converts a SPIR-V struct member decoration into a number of AST
     /// decorations. If the decoration is recognized but deliberately dropped,
@@ -291,10 +293,10 @@
     /// @param member_ty the type of the member
     /// @param decoration an encoded SPIR-V Decoration
     /// @returns the AST decorations
-    ast::AttributeList ConvertMemberDecoration(uint32_t struct_type_id,
-                                               uint32_t member_index,
-                                               const Type* member_ty,
-                                               const Decoration& decoration);
+    AttributeList ConvertMemberDecoration(uint32_t struct_type_id,
+                                          uint32_t member_index,
+                                          const Type* member_ty,
+                                          const Decoration& decoration);
 
     /// Returns a string for the given type.  If the type ID is invalid,
     /// then the resulting string only names the type ID.
@@ -434,7 +436,7 @@
                       ast::StorageClass sc,
                       const Type* storage_type,
                       const ast::Expression* constructor,
-                      ast::AttributeList decorations);
+                      AttributeList decorations);
 
     /// Creates an AST 'let' node for a SPIR-V ID, including any attached decorations,.
     /// @param id the SPIR-V result ID
@@ -452,7 +454,7 @@
     ast::Override* MakeOverride(uint32_t id,
                                 const Type* type,
                                 const ast::Expression* constructor,
-                                ast::AttributeList decorations);
+                                AttributeList decorations);
 
     /// Creates an AST parameter node for a SPIR-V ID, including any attached decorations, unless
     /// it's an ignorable builtin variable.
@@ -460,7 +462,7 @@
     /// @param type the type of the parameter
     /// @param decorations the parameter decorations
     /// @returns the AST parameter node
-    ast::Parameter* MakeParameter(uint32_t id, const Type* type, ast::AttributeList decorations);
+    ast::Parameter* MakeParameter(uint32_t id, const Type* type, AttributeList decorations);
 
     /// Returns true if a constant expression can be generated.
     /// @param id the SPIR-V ID of the value
diff --git a/src/tint/reader/spirv/parser_impl_barrier_test.cc b/src/tint/reader/spirv/parser_impl_barrier_test.cc
index fdfa3bf..a594098 100644
--- a/src/tint/reader/spirv/parser_impl_barrier_test.cc
+++ b/src/tint/reader/spirv/parser_impl_barrier_test.cc
@@ -65,10 +65,10 @@
     ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
     auto* helper = program.AST().Functions().Find(program.Symbols().Get("helper"));
     ASSERT_NE(helper, nullptr);
-    ASSERT_GT(helper->body->statements.size(), 0u);
+    ASSERT_GT(helper->body->statements.Length(), 0u);
     auto* call = helper->body->statements[0]->As<ast::CallStatement>();
     ASSERT_NE(call, nullptr);
-    EXPECT_EQ(call->expr->args.size(), 0u);
+    EXPECT_EQ(call->expr->args.Length(), 0u);
     auto* sem_call = program.Sem().Get<sem::Call>(call->expr);
     ASSERT_NE(sem_call, nullptr);
     auto* builtin = sem_call->Target()->As<sem::Builtin>();
@@ -98,10 +98,10 @@
     ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
     auto* helper = program.AST().Functions().Find(program.Symbols().Get("helper"));
     ASSERT_NE(helper, nullptr);
-    ASSERT_GT(helper->body->statements.size(), 0u);
+    ASSERT_GT(helper->body->statements.Length(), 0u);
     auto* call = helper->body->statements[0]->As<ast::CallStatement>();
     ASSERT_NE(call, nullptr);
-    EXPECT_EQ(call->expr->args.size(), 0u);
+    EXPECT_EQ(call->expr->args.Length(), 0u);
     auto* sem_call = program.Sem().Get<sem::Call>(call->expr);
     ASSERT_NE(sem_call, nullptr);
     auto* builtin = sem_call->Target()->As<sem::Builtin>();
diff --git a/src/tint/reader/spirv/parser_impl_convert_member_decoration_test.cc b/src/tint/reader/spirv/parser_impl_convert_member_decoration_test.cc
index 3147ac1..cd3e7b5 100644
--- a/src/tint/reader/spirv/parser_impl_convert_member_decoration_test.cc
+++ b/src/tint/reader/spirv/parser_impl_convert_member_decoration_test.cc
@@ -20,11 +20,11 @@
 
 using ::testing::Eq;
 
-TEST_F(SpvParserTest, ConvertMemberDecoration_Empty) {
+TEST_F(SpvParserTest, ConvertMemberDecoration_IsEmpty) {
     auto p = parser(std::vector<uint32_t>{});
 
     auto result = p->ConvertMemberDecoration(1, 1, nullptr, {});
-    EXPECT_TRUE(result.empty());
+    EXPECT_TRUE(result.IsEmpty());
     EXPECT_THAT(p->error(), Eq("malformed SPIR-V decoration: it's empty"));
 }
 
@@ -32,7 +32,7 @@
     auto p = parser(std::vector<uint32_t>{});
 
     auto result = p->ConvertMemberDecoration(12, 13, nullptr, {SpvDecorationOffset});
-    EXPECT_TRUE(result.empty());
+    EXPECT_TRUE(result.IsEmpty());
     EXPECT_THAT(p->error(), Eq("malformed Offset decoration: expected 1 literal "
                                "operand, has 0: member 13 of SPIR-V type 12"));
 }
@@ -41,7 +41,7 @@
     auto p = parser(std::vector<uint32_t>{});
 
     auto result = p->ConvertMemberDecoration(12, 13, nullptr, {SpvDecorationOffset, 3, 4});
-    EXPECT_TRUE(result.empty());
+    EXPECT_TRUE(result.IsEmpty());
     EXPECT_THAT(p->error(), Eq("malformed Offset decoration: expected 1 literal "
                                "operand, has 2: member 13 of SPIR-V type 12"));
 }
@@ -50,7 +50,7 @@
     auto p = parser(std::vector<uint32_t>{});
 
     auto result = p->ConvertMemberDecoration(1, 1, nullptr, {SpvDecorationOffset, 8});
-    ASSERT_FALSE(result.empty());
+    ASSERT_FALSE(result.IsEmpty());
     EXPECT_TRUE(result[0]->Is<ast::StructMemberOffsetAttribute>());
     auto* offset_deco = result[0]->As<ast::StructMemberOffsetAttribute>();
     ASSERT_NE(offset_deco, nullptr);
@@ -64,7 +64,7 @@
     spirv::F32 f32;
     spirv::Matrix matrix(&f32, 2, 2);
     auto result = p->ConvertMemberDecoration(1, 1, &matrix, {SpvDecorationMatrixStride, 8});
-    EXPECT_TRUE(result.empty());
+    EXPECT_TRUE(result.IsEmpty());
     EXPECT_TRUE(p->error().empty());
 }
 
@@ -74,7 +74,7 @@
     spirv::F32 f32;
     spirv::Matrix matrix(&f32, 2, 2);
     auto result = p->ConvertMemberDecoration(1, 1, &matrix, {SpvDecorationMatrixStride, 16});
-    ASSERT_FALSE(result.empty());
+    ASSERT_FALSE(result.IsEmpty());
     EXPECT_TRUE(result[0]->Is<ast::StrideAttribute>());
     auto* stride_deco = result[0]->As<ast::StrideAttribute>();
     ASSERT_NE(stride_deco, nullptr);
@@ -88,7 +88,7 @@
     spirv::F32 f32;
     spirv::Matrix matrix(&f32, 2, 4);
     auto result = p->ConvertMemberDecoration(1, 1, &matrix, {SpvDecorationMatrixStride, 16});
-    EXPECT_TRUE(result.empty());
+    EXPECT_TRUE(result.IsEmpty());
     EXPECT_TRUE(p->error().empty());
 }
 
@@ -98,7 +98,7 @@
     spirv::F32 f32;
     spirv::Matrix matrix(&f32, 2, 4);
     auto result = p->ConvertMemberDecoration(1, 1, &matrix, {SpvDecorationMatrixStride, 64});
-    ASSERT_FALSE(result.empty());
+    ASSERT_FALSE(result.IsEmpty());
     EXPECT_TRUE(result[0]->Is<ast::StrideAttribute>());
     auto* stride_deco = result[0]->As<ast::StrideAttribute>();
     ASSERT_NE(stride_deco, nullptr);
@@ -112,7 +112,7 @@
     spirv::F32 f32;
     spirv::Matrix matrix(&f32, 2, 3);
     auto result = p->ConvertMemberDecoration(1, 1, &matrix, {SpvDecorationMatrixStride, 32});
-    ASSERT_FALSE(result.empty());
+    ASSERT_FALSE(result.IsEmpty());
     EXPECT_TRUE(result[0]->Is<ast::StrideAttribute>());
     auto* stride_deco = result[0]->As<ast::StrideAttribute>();
     ASSERT_NE(stride_deco, nullptr);
@@ -127,7 +127,7 @@
     auto p = parser(std::vector<uint32_t>{});
 
     auto result = p->ConvertMemberDecoration(1, 1, nullptr, {SpvDecorationRelaxedPrecision});
-    EXPECT_TRUE(result.empty());
+    EXPECT_TRUE(result.IsEmpty());
     EXPECT_TRUE(p->error().empty());
 }
 
@@ -135,7 +135,7 @@
     auto p = parser(std::vector<uint32_t>{});
 
     auto result = p->ConvertMemberDecoration(12, 13, nullptr, {12345678});
-    EXPECT_TRUE(result.empty());
+    EXPECT_TRUE(result.IsEmpty());
     EXPECT_THAT(p->error(), Eq("unhandled member decoration: 12345678 on member "
                                "13 of SPIR-V type 12"));
 }
diff --git a/src/tint/reader/spirv/parser_impl_handle_test.cc b/src/tint/reader/spirv/parser_impl_handle_test.cc
index 84f3dcb..e1b4889 100644
--- a/src/tint/reader/spirv/parser_impl_handle_test.cc
+++ b/src/tint/reader/spirv/parser_impl_handle_test.cc
@@ -3076,7 +3076,7 @@
         ASSERT_NE(anchor, nullptr);
         const spvtools::opt::Instruction& image_access = *(anchor->PreviousNode());
 
-        ast::ExpressionList result = fe.MakeCoordinateOperandsForImageAccess(image_access);
+        auto result = fe.MakeCoordinateOperandsForImageAccess(image_access);
         if (GetParam().expected_error.empty()) {
             EXPECT_TRUE(fe.success()) << p->error();
             EXPECT_TRUE(p->error().empty());
@@ -3090,7 +3090,7 @@
         } else {
             EXPECT_FALSE(fe.success());
             EXPECT_THAT(p->error(), Eq(GetParam().expected_error)) << assembly;
-            EXPECT_TRUE(result.empty());
+            EXPECT_TRUE(result.IsEmpty());
         }
     }
 
diff --git a/src/tint/reader/spirv/parser_impl_test_helper.cc b/src/tint/reader/spirv/parser_impl_test_helper.cc
index 1e52d81..e6249849 100644
--- a/src/tint/reader/spirv/parser_impl_test_helper.cc
+++ b/src/tint/reader/spirv/parser_impl_test_helper.cc
@@ -39,7 +39,7 @@
     return writer.result();
 }
 
-std::string ToString(const Program& program, const ast::StatementList& stmts) {
+std::string ToString(const Program& program, utils::VectorRef<const ast::Statement*> stmts) {
     writer::wgsl::GeneratorImpl writer(&program);
     for (const auto* stmt : stmts) {
         if (!writer.EmitStatement(stmt)) {
diff --git a/src/tint/reader/spirv/parser_impl_test_helper.h b/src/tint/reader/spirv/parser_impl_test_helper.h
index f2c2dfe..d1b0e35 100644
--- a/src/tint/reader/spirv/parser_impl_test_helper.h
+++ b/src/tint/reader/spirv/parser_impl_test_helper.h
@@ -195,10 +195,10 @@
     /// @param member_ty the type of the member
     /// @param decoration an encoded SPIR-V Decoration
     /// @returns the AST decorations
-    ast::AttributeList ConvertMemberDecoration(uint32_t struct_type_id,
-                                               uint32_t member_index,
-                                               const Type* member_ty,
-                                               const Decoration& decoration) {
+    auto ConvertMemberDecoration(uint32_t struct_type_id,
+                                 uint32_t member_index,
+                                 const Type* member_ty,
+                                 const Decoration& decoration) {
         return impl_.ConvertMemberDecoration(struct_type_id, member_index, member_ty, decoration);
     }
 
@@ -275,7 +275,7 @@
 /// @param program the Program
 /// @param stmts the statement list
 /// @returns the WGSL printed string of a statement list.
-std::string ToString(const Program& program, const ast::StatementList& stmts);
+std::string ToString(const Program& program, utils::VectorRef<const ast::Statement*> stmts);
 
 /// Returns the WGSL printed string of an AST node.
 /// @param program the Program
diff --git a/src/tint/reader/wgsl/lexer.cc b/src/tint/reader/wgsl/lexer.cc
index 778cb3f..0bc19ad 100644
--- a/src/tint/reader/wgsl/lexer.cc
+++ b/src/tint/reader/wgsl/lexer.cc
@@ -1025,8 +1025,13 @@
             type = Token::Type::kGreaterThanEqual;
             advance(2);
         } else if (matches(pos() + 1, '>')) {
-            type = Token::Type::kShiftRight;
-            advance(2);
+            if (matches(pos() + 2, '=')) {
+                type = Token::Type::kShiftRightEqual;
+                advance(3);
+            } else {
+                type = Token::Type::kShiftRight;
+                advance(2);
+            }
         } else {
             type = Token::Type::kGreaterThan;
             advance(1);
@@ -1036,8 +1041,13 @@
             type = Token::Type::kLessThanEqual;
             advance(2);
         } else if (matches(pos() + 1, '<')) {
-            type = Token::Type::kShiftLeft;
-            advance(2);
+            if (matches(pos() + 2, '=')) {
+                type = Token::Type::kShiftLeftEqual;
+                advance(3);
+            } else {
+                type = Token::Type::kShiftLeft;
+                advance(2);
+            }
         } else {
             type = Token::Type::kLessThan;
             advance(1);
@@ -1185,9 +1195,6 @@
     if (str == "if") {
         return {Token::Type::kIf, source, "if"};
     }
-    if (str == "import") {
-        return {Token::Type::kImport, source, "import"};
-    }
     if (str == "let") {
         return {Token::Type::kLet, source, "let"};
     }
@@ -1236,6 +1243,9 @@
     if (str == "sampler_comparison") {
         return {Token::Type::kComparisonSampler, source, "sampler_comparison"};
     }
+    if (str == "static_assert") {
+        return {Token::Type::kStaticAssert, source, "static_assert"};
+    }
     if (str == "struct") {
         return {Token::Type::kStruct, source, "struct"};
     }
diff --git a/src/tint/reader/wgsl/lexer_test.cc b/src/tint/reader/wgsl/lexer_test.cc
index f82045a..6cac798 100644
--- a/src/tint/reader/wgsl/lexer_test.cc
+++ b/src/tint/reader/wgsl/lexer_test.cc
@@ -1049,7 +1049,9 @@
                                          TokenData{"%=", Token::Type::kModuloEqual},
                                          TokenData{"&=", Token::Type::kAndEqual},
                                          TokenData{"|=", Token::Type::kOrEqual},
-                                         TokenData{"^=", Token::Type::kXorEqual}));
+                                         TokenData{"^=", Token::Type::kXorEqual},
+                                         TokenData{">>=", Token::Type::kShiftRightEqual},
+                                         TokenData{"<<=", Token::Type::kShiftLeftEqual}));
 
 using SplittablePunctuationTest = testing::TestWithParam<TokenData>;
 TEST_P(SplittablePunctuationTest, Parses) {
@@ -1127,7 +1129,6 @@
                     TokenData{"for", Token::Type::kFor},
                     TokenData{"i32", Token::Type::kI32},
                     TokenData{"if", Token::Type::kIf},
-                    TokenData{"import", Token::Type::kImport},
                     TokenData{"let", Token::Type::kLet},
                     TokenData{"loop", Token::Type::kLoop},
                     TokenData{"mat2x2", Token::Type::kMat2x2},
@@ -1144,6 +1145,7 @@
                     TokenData{"return", Token::Type::kReturn},
                     TokenData{"sampler", Token::Type::kSampler},
                     TokenData{"sampler_comparison", Token::Type::kComparisonSampler},
+                    TokenData{"static_assert", Token::Type::kStaticAssert},
                     TokenData{"struct", Token::Type::kStruct},
                     TokenData{"switch", Token::Type::kSwitch},
                     TokenData{"texture_1d", Token::Type::kTextureSampled1d},
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index 59f4fac..bf26f8e 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -62,32 +62,42 @@
 /// parser on error.
 constexpr size_t const kMaxResynchronizeLookahead = 32;
 
-const char kVertexStage[] = "vertex";
-const char kFragmentStage[] = "fragment";
-const char kComputeStage[] = "compute";
-
-const char kReadAccess[] = "read";
-const char kWriteAccess[] = "write";
-const char kReadWriteAccess[] = "read_write";
-
-const char kBindingAttribute[] = "binding";
-const char kBuiltinAttribute[] = "builtin";
-const char kGroupAttribute[] = "group";
-const char kIdAttribute[] = "id";
-const char kInterpolateAttribute[] = "interpolate";
-const char kInvariantAttribute[] = "invariant";
-const char kLocationAttribute[] = "location";
-const char kSizeAttribute[] = "size";
-const char kAlignAttribute[] = "align";
-const char kStageAttribute[] = "stage";
-const char kWorkgroupSizeAttribute[] = "workgroup_size";
-
 // https://gpuweb.github.io/gpuweb/wgsl.html#reserved-keywords
 bool is_reserved(const Token& t) {
-    return t == "asm" || t == "bf16" || t == "do" || t == "enum" || t == "f64" || t == "handle" ||
-           t == "i8" || t == "i16" || t == "i64" || t == "mat" || t == "premerge" ||
-           t == "regardless" || t == "typedef" || t == "u8" || t == "u16" || t == "u64" ||
-           t == "unless" || t == "using" || t == "vec" || t == "void" || t == "while";
+    return t == "CompileShader" || t == "ComputeShader" || t == "DomainShader" ||
+           t == "GeometryShader" || t == "Hullshader" || t == "NULL" || t == "Self" ||
+           t == "abstract" || t == "active" || t == "alignas" || t == "alignof" || t == "as" ||
+           t == "asm" || t == "asm_fragment" || t == "async" || t == "attribute" || t == "auto" ||
+           t == "await" || t == "become" || t == "binding_array" || t == "cast" || t == "catch" ||
+           t == "class" || t == "co_await" || t == "co_return" || t == "co_yield" ||
+           t == "coherent" || t == "column_major" || t == "common" || t == "compile" ||
+           t == "compile_fragment" || t == "concept" || t == "const_cast" || t == "consteval" ||
+           t == "constexpr" || t == "constinit" || t == "crate" || t == "debugger" ||
+           t == "decltype" || t == "delete" || t == "demote" || t == "demote_to_helper" ||
+           t == "do" || t == "dynamic_cast" || t == "enum" || t == "explicit" || t == "export" ||
+           t == "extends" || t == "extern" || t == "external" || t == "filter" || t == "final" ||
+           t == "finally" || t == "friend" || t == "from" || t == "fxgroup" || t == "get" ||
+           t == "goto" || t == "groupshared" || t == "handle" || t == "highp" || t == "impl" ||
+           t == "implements" || t == "import" || t == "inline" || t == "inout" ||
+           t == "instanceof" || t == "interface" || t == "invariant" || t == "layout" ||
+           t == "line" || t == "lineadj" || t == "lowp" || t == "macro" || t == "macro_rules" ||
+           t == "match" || t == "mediump" || t == "meta" || t == "mod" || t == "module" ||
+           t == "move" || t == "mut" || t == "mutable" || t == "namespace" || t == "new" ||
+           t == "nil" || t == "noexcept" || t == "noinline" || t == "nointerpolation" ||
+           t == "noperspective" || t == "null" || t == "nullptr" || t == "of" || t == "operator" ||
+           t == "package" || t == "packoffset" || t == "partition" || t == "pass" || t == "patch" ||
+           t == "pixelfragment" || t == "point" || t == "precise" || t == "precision" ||
+           t == "premerge" || t == "priv" || t == "protected" || t == "pub" || t == "public" ||
+           t == "readonly" || t == "ref" || t == "regardless" || t == "register" ||
+           t == "reinterpret_cast" || t == "requires" || t == "resource" || t == "restrict" ||
+           t == "self" || t == "set" || t == "shared" || t == "signed" || t == "sizeof" ||
+           t == "smooth" || t == "snorm" || t == "static" || t == "static_cast" || t == "std" ||
+           t == "subroutine" || t == "super" || t == "target" || t == "template" || t == "this" ||
+           t == "thread_local" || t == "throw" || t == "trait" || t == "try" || t == "typedef" ||
+           t == "typeid" || t == "typename" || t == "typeof" || t == "union" || t == "unless" ||
+           t == "unorm" || t == "unsafe" || t == "unsized" || t == "use" || t == "using" ||
+           t == "varying" || t == "virtual" || t == "volatile" || t == "wgsl" || t == "where" ||
+           t == "with" || t == "writeonly" || t == "yield";
 }
 
 /// Enter-exit counters for block token types.
@@ -170,9 +180,9 @@
 
 ParserImpl::FunctionHeader::FunctionHeader(Source src,
                                            std::string n,
-                                           ast::ParameterList p,
+                                           utils::VectorRef<const ast::Parameter*> p,
                                            const ast::Type* ret_ty,
-                                           ast::AttributeList ret_attrs)
+                                           utils::VectorRef<const ast::Attribute*> ret_attrs)
     : source(src),
       name(n),
       params(std::move(p)),
@@ -310,7 +320,7 @@
 }
 
 // translation_unit
-//  : enable_directive* global_decl* EOF
+//  : global_directive* global_decl* EOF
 void ParserImpl::translation_unit() {
     bool after_global_decl = false;
     while (continue_parsing()) {
@@ -319,17 +329,9 @@
             break;
         }
 
-        auto ed = enable_directive();
-        if (ed.matched) {
-            if (after_global_decl) {
-                add_error(p, "enable directives must come before all global declarations");
-            }
-        } else if (ed.errored) {
-            // Found a invalid enable directive.
-            continue;
-        } else {
+        auto ed = global_directive(after_global_decl);
+        if (!ed.matched && !ed.errored) {
             auto gd = global_decl();
-
             if (gd.matched) {
                 after_global_decl = true;
             }
@@ -347,6 +349,17 @@
     }
 }
 
+// global_directive
+//  : enable_directive
+Maybe<bool> ParserImpl::global_directive(bool have_parsed_decl) {
+    auto& p = peek();
+    auto ed = enable_directive();
+    if (ed.matched && have_parsed_decl) {
+        return add_error(p, "enable directives must come before all global declarations");
+    }
+    return ed;
+}
+
 // enable_directive
 //  : enable name SEMICLON
 Maybe<bool> ParserImpl::enable_directive() {
@@ -405,18 +418,18 @@
 
 // global_decl
 //  : SEMICOLON
-//  | global_variable_decl SEMICLON
+//  | global_variable_decl SEMICOLON
 //  | global_constant_decl SEMICOLON
-//  | type_alias SEMICOLON
+//  | type_alias_decl SEMICOLON
 //  | struct_decl
 //  | function_decl
+//  | static_assert_statement SEMICOLON
 Maybe<bool> ParserImpl::global_decl() {
     if (match(Token::Type::kSemicolon) || match(Token::Type::kEOF)) {
         return true;
     }
 
     bool errored = false;
-
     auto attrs = attribute_list();
     if (attrs.errored) {
         errored = true;
@@ -443,7 +456,6 @@
         if (gc.errored) {
             return Failure::kErrored;
         }
-
         if (gc.matched) {
             // Avoid the cost of the string allocation for the common no-error case
             if (!peek().Is(Token::Type::kSemicolon)) {
@@ -457,11 +469,10 @@
             return true;
         }
 
-        auto ta = type_alias();
+        auto ta = type_alias_decl();
         if (ta.errored) {
             return Failure::kErrored;
         }
-
         if (ta.matched) {
             if (!expect("type alias", Token::Type::kSemicolon)) {
                 return Failure::kErrored;
@@ -471,13 +482,15 @@
             return true;
         }
 
-        auto str = struct_decl();
-        if (str.errored) {
+        auto assertion = static_assert_statement();
+        if (assertion.errored) {
             return Failure::kErrored;
         }
-
-        if (str.matched) {
-            builder_.AST().AddTypeDecl(str.value);
+        if (assertion.matched) {
+            builder_.AST().AddStaticAssert(assertion.value);
+            if (!expect("static assertion declaration", Token::Type::kSemicolon)) {
+                return Failure::kErrored;
+            }
             return true;
         }
 
@@ -491,6 +504,15 @@
         return expect_attributes_consumed(attrs.value);
     }
 
+    auto str = struct_decl();
+    if (str.errored) {
+        errored = true;
+    }
+    if (str.matched) {
+        builder_.AST().AddTypeDecl(str.value);
+        return true;
+    }
+
     auto func = function_decl(attrs.value);
     if (func.errored) {
         errored = true;
@@ -507,7 +529,7 @@
     // Invalid syntax found - try and determine the best error message
 
     // We have attributes parsed, but nothing to consume them?
-    if (attrs.value.size() > 0) {
+    if (attrs.value.Length() > 0) {
         return add_error(next(), "expected declaration after attributes");
     }
 
@@ -538,9 +560,8 @@
 }
 
 // global_variable_decl
-//  : variable_attribute_list* variable_decl
-//  | variable_attribute_list* variable_decl EQUAL const_expr
-Maybe<const ast::Variable*> ParserImpl::global_variable_decl(ast::AttributeList& attrs) {
+//  : variable_attribute_list* variable_decl (EQUAL expression)?
+Maybe<const ast::Variable*> ParserImpl::global_variable_decl(AttributeList& attrs) {
     auto decl = variable_decl();
     if (decl.errored) {
         return Failure::kErrored;
@@ -549,24 +570,26 @@
         return Failure::kNoMatch;
     }
 
-    const ast::Expression* initalizer = nullptr;
+    const ast::Expression* initializer = nullptr;
     if (match(Token::Type::kEqual)) {
-        auto expr = logical_or_expression();
+        auto expr = expression();
         if (expr.errored) {
             return Failure::kErrored;
         }
         if (!expr.matched) {
-            return add_error(peek(), "missing initalizer for 'var' declaration");
+            return add_error(peek(), "missing initializer for 'var' declaration");
         }
-        initalizer = expr.value;
+        initializer = expr.value;
     }
 
+    TINT_DEFER(attrs.Clear());
+
     return create<ast::Var>(decl->source,                             // source
                             builder_.Symbols().Register(decl->name),  // symbol
                             decl->type,                               // type
                             decl->storage_class,                      // storage class
                             decl->access,                             // access control
-                            initalizer,                               // initializer
+                            initializer,                              // initializer
                             std::move(attrs));                        // attributes
 }
 
@@ -575,7 +598,7 @@
 //  | attribute* override (ident | variable_ident_decl) (equal expression)?
 // global_const_initializer
 //  : EQUAL const_expr
-Maybe<const ast::Variable*> ParserImpl::global_constant_decl(ast::AttributeList& attrs) {
+Maybe<const ast::Variable*> ParserImpl::global_constant_decl(AttributeList& attrs) {
     bool is_const = false;
     bool is_overridable = false;
     const char* use = nullptr;
@@ -592,7 +615,7 @@
         return Failure::kNoMatch;
     }
 
-    auto decl = expect_variable_ident_decl(use, /* allow_inferred = */ true);
+    auto decl = expect_ident_or_variable_ident_decl(use);
     if (decl.errored) {
         return Failure::kErrored;
     }
@@ -609,7 +632,7 @@
 
     const ast::Expression* initializer = nullptr;
     if (has_initializer) {
-        auto expr = logical_or_expression();
+        auto expr = expression();
         if (expr.errored) {
             return Failure::kErrored;
         }
@@ -619,6 +642,8 @@
         initializer = std::move(expr.value);
     }
 
+    TINT_DEFER(attrs.Clear());
+
     if (is_const) {
         return create<ast::Const>(decl->source,                             // source
                                   builder_.Symbols().Register(decl->name),  // symbol
@@ -641,7 +666,7 @@
 }
 
 // variable_decl
-//   : VAR variable_qualifier? variable_ident_decl
+//   : VAR variable_qualifier? (ident | variable_ident_decl)
 Maybe<ParserImpl::VarDeclInfo> ParserImpl::variable_decl() {
     Source source;
     if (!match(Token::Type::kVar, &source)) {
@@ -657,8 +682,7 @@
         vq = explicit_vq.value;
     }
 
-    auto decl = expect_variable_ident_decl("variable declaration",
-                                           /*allow_inferred = */ true);
+    auto decl = expect_ident_or_variable_ident_decl("variable declaration");
     if (decl.errored) {
         return Failure::kErrored;
     }
@@ -894,10 +918,9 @@
     return fmt;
 }
 
-// variable_ident_decl
-//   : IDENT COLON type_decl
-Expect<ParserImpl::TypedIdentifier> ParserImpl::expect_variable_ident_decl(std::string_view use,
-                                                                           bool allow_inferred) {
+Expect<ParserImpl::TypedIdentifier> ParserImpl::expect_ident_or_variable_ident_decl_impl(
+    std::string_view use,
+    bool allow_inferred) {
     auto ident = expect_ident(use);
     if (ident.errored) {
         return Failure::kErrored;
@@ -923,19 +946,35 @@
     return TypedIdentifier{type.value, ident.value, ident.source};
 }
 
+// (ident | variable_ident_decl)
+Expect<ParserImpl::TypedIdentifier> ParserImpl::expect_ident_or_variable_ident_decl(
+    std::string_view use) {
+    return expect_ident_or_variable_ident_decl_impl(use, true);
+}
+
+// variable_ident_decl
+//   : IDENT COLON type_decl
+Expect<ParserImpl::TypedIdentifier> ParserImpl::expect_variable_ident_decl(std::string_view use) {
+    return expect_ident_or_variable_ident_decl_impl(use, false);
+}
+
+// access_mode
+//   : 'read'
+//   | 'write'
+//   | 'read_write'
 Expect<ast::Access> ParserImpl::expect_access(std::string_view use) {
     auto ident = expect_ident(use);
     if (ident.errored) {
         return Failure::kErrored;
     }
 
-    if (ident.value == kReadAccess) {
+    if (ident.value == "read") {
         return {ast::Access::kRead, ident.source};
     }
-    if (ident.value == kWriteAccess) {
+    if (ident.value == "write") {
         return {ast::Access::kWrite, ident.source};
     }
-    if (ident.value == kReadWriteAccess) {
+    if (ident.value == "read_write") {
         return {ast::Access::kReadWrite, ident.source};
     }
 
@@ -952,7 +991,7 @@
     auto* use = "variable declaration";
     auto vq = expect_lt_gt_block(use, [&]() -> Expect<VariableQualifier> {
         auto source = make_source_range();
-        auto sc = expect_storage_class(use);
+        auto sc = expect_address_space(use);
         if (sc.errored) {
             return Failure::kErrored;
         }
@@ -974,9 +1013,9 @@
     return vq;
 }
 
-// type_alias
+// type_alias_decl
 //   : TYPE IDENT EQUAL type_decl
-Maybe<const ast::Alias*> ParserImpl::type_alias() {
+Maybe<const ast::Alias*> ParserImpl::type_alias_decl() {
     if (!peek_is(Token::Type::kType)) {
         return Failure::kNoMatch;
     }
@@ -1014,10 +1053,8 @@
 //   | 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
-//          GREATER_THAN
+//   | array_attribute_list* ARRAY LESS_THAN type_decl COMMA INT_LITERAL 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
 //   | MAT2x4 LESS_THAN type_decl GREATER_THAN
@@ -1106,7 +1143,7 @@
     auto access = ast::Access::kUndefined;
 
     auto subtype = expect_lt_gt_block(use, [&]() -> Expect<const ast::Type*> {
-        auto sc = expect_storage_class(use);
+        auto sc = expect_address_space(use);
         if (sc.errored) {
             return Failure::kErrored;
         }
@@ -1238,7 +1275,15 @@
     return builder_.ty.mat(make_source_range_from(t.source()), subtype, columns, rows);
 }
 
-Expect<ast::StorageClass> ParserImpl::expect_storage_class(std::string_view use) {
+// address_space
+//   : 'function'
+//   | 'private'
+//   | 'workgroup'
+//   | 'uniform'
+//   | 'storage'
+//
+// Note, we also parse `push_constant` from the experimental extension
+Expect<ast::StorageClass> ParserImpl::expect_address_space(std::string_view use) {
     auto& t = peek();
     auto ident = expect_ident("storage class");
     if (ident.errored) {
@@ -1273,14 +1318,14 @@
     }
 
     auto sym = builder_.Symbols().Register(name.value);
-    return create<ast::Struct>(t.source(), sym, std::move(body.value), ast::AttributeList{});
+    return create<ast::Struct>(t.source(), sym, std::move(body.value), utils::Empty);
 }
 
 // struct_body_decl
 //   : BRACE_LEFT (struct_member COMMA)* struct_member COMMA? BRACE_RIGHT
-Expect<ast::StructMemberList> ParserImpl::expect_struct_body_decl() {
-    return expect_brace_block("struct declaration", [&]() -> Expect<ast::StructMemberList> {
-        ast::StructMemberList members;
+Expect<ParserImpl::StructMemberList> ParserImpl::expect_struct_body_decl() {
+    return expect_brace_block("struct declaration", [&]() -> Expect<StructMemberList> {
+        StructMemberList members;
         bool errored = false;
         while (continue_parsing()) {
             // Check for the end of the list.
@@ -1296,7 +1341,7 @@
                     return Failure::kErrored;
                 }
             } else {
-                members.push_back(member.value);
+                members.Push(member.value);
             }
 
             if (!match(Token::Type::kComma)) {
@@ -1318,8 +1363,7 @@
         return Failure::kErrored;
     }
 
-    auto decl = expect_variable_ident_decl("struct member",
-                                           /*allow_inferred = */ false);
+    auto decl = expect_variable_ident_decl("struct member");
     if (decl.errored) {
         return Failure::kErrored;
     }
@@ -1328,9 +1372,29 @@
                                      decl->type, std::move(attrs.value));
 }
 
+// static_assert_statement
+//   : STATIC_ASSERT expression
+Maybe<const ast::StaticAssert*> ParserImpl::static_assert_statement() {
+    Source start;
+    if (!match(Token::Type::kStaticAssert, &start)) {
+        return Failure::kNoMatch;
+    }
+
+    auto condition = expression();
+    if (condition.errored) {
+        return Failure::kErrored;
+    }
+    if (!condition.matched) {
+        return add_error(peek(), "unable to parse condition expression");
+    }
+
+    Source source = make_source_range_from(start);
+    return create<ast::StaticAssert>(source, condition.value);
+}
+
 // function_decl
-//   : function_header body_stmt
-Maybe<const ast::Function*> ParserImpl::function_decl(ast::AttributeList& attrs) {
+//   : function_header compound_statement
+Maybe<const ast::Function*> ParserImpl::function_decl(AttributeList& attrs) {
     auto header = function_header();
     if (header.errored) {
         if (sync_to(Token::Type::kBraceLeft, /* consume: */ false)) {
@@ -1340,7 +1404,7 @@
             // function body. The AST isn't used as we've already errored, but this
             // catches any errors inside the body, and can help keep the parser in
             // sync.
-            expect_body_stmt();
+            expect_compound_statement();
         }
         return Failure::kErrored;
     }
@@ -1350,7 +1414,7 @@
 
     bool errored = false;
 
-    auto body = expect_body_stmt();
+    auto body = expect_compound_statement();
     if (body.errored) {
         errored = true;
     }
@@ -1359,8 +1423,10 @@
         return Failure::kErrored;
     }
 
+    TINT_DEFER(attrs.Clear());
+
     return create<ast::Function>(header->source, builder_.Symbols().Register(header->name),
-                                 header->params, header->return_type, body.value, attrs,
+                                 header->params, header->return_type, body.value, std::move(attrs),
                                  header->return_type_attributes);
 }
 
@@ -1395,7 +1461,7 @@
     }
 
     const ast::Type* return_type = nullptr;
-    ast::AttributeList return_attributes;
+    AttributeList return_attributes;
 
     if (match(Token::Type::kArrow)) {
         auto attrs = attribute_list();
@@ -1420,15 +1486,17 @@
         return Failure::kErrored;
     }
 
-    return FunctionHeader{source, name.value, std::move(params.value), return_type,
-                          std::move(return_attributes)};
+    return FunctionHeader{
+        source,      std::move(name.value),        std::move(params.value),
+        return_type, std::move(return_attributes),
+    };
 }
 
 // param_list
 //   :
 //   | (param COMMA)* param COMMA?
-Expect<ast::ParameterList> ParserImpl::expect_param_list() {
-    ast::ParameterList ret;
+Expect<ParserImpl::ParameterList> ParserImpl::expect_param_list() {
+    ParameterList ret;
     while (continue_parsing()) {
         // Check for the end of the list.
         auto& t = peek();
@@ -1440,7 +1508,7 @@
         if (param.errored) {
             return Failure::kErrored;
         }
-        ret.push_back(param.value);
+        ret.Push(param.value);
 
         if (!match(Token::Type::kComma)) {
             break;
@@ -1455,8 +1523,7 @@
 Expect<ast::Parameter*> ParserImpl::expect_param() {
     auto attrs = attribute_list();
 
-    auto decl = expect_variable_ident_decl("parameter",
-                                           /*allow_inferred = */ false);
+    auto decl = expect_variable_ident_decl("parameter");
     if (decl.errored) {
         return Failure::kErrored;
     }
@@ -1471,23 +1538,83 @@
 //   : VERTEX
 //   | FRAGMENT
 //   | COMPUTE
+//
+// TODO(crbug.com/tint/1503): Remove when deprecation period is over.
 Expect<ast::PipelineStage> ParserImpl::expect_pipeline_stage() {
     auto& t = peek();
-    if (t == kVertexStage) {
+    if (t == "vertex") {
         next();  // Consume the peek
         return {ast::PipelineStage::kVertex, t.source()};
     }
-    if (t == kFragmentStage) {
+    if (t == "fragment") {
         next();  // Consume the peek
         return {ast::PipelineStage::kFragment, t.source()};
     }
-    if (t == kComputeStage) {
+    if (t == "compute") {
         next();  // Consume the peek
         return {ast::PipelineStage::kCompute, t.source()};
     }
     return add_error(peek(), "invalid value for stage attribute");
 }
 
+// interpolation_sample_name
+//   :  'center'
+//   | 'centroid'
+//   | 'sample'
+Expect<ast::InterpolationSampling> ParserImpl::expect_interpolation_sample_name() {
+    auto ident = expect_ident("interpolation sample name");
+    if (ident.errored) {
+        return Failure::kErrored;
+    }
+
+    if (ident.value == "center") {
+        return {ast::InterpolationSampling::kCenter, ident.source};
+    }
+    if (ident.value == "centroid") {
+        return {ast::InterpolationSampling::kCentroid, ident.source};
+    }
+    if (ident.value == "sample") {
+        return {ast::InterpolationSampling::kSample, ident.source};
+    }
+    return add_error(ident.source, "invalid interpolation sampling");
+}
+
+// interpolation_type_name
+//   : 'perspective'
+//   | 'linear'
+//   | 'flat'
+Expect<ast::InterpolationType> ParserImpl::expect_interpolation_type_name() {
+    auto ident = expect_ident("interpolation type name");
+    if (ident.errored) {
+        return Failure::kErrored;
+    }
+
+    if (ident.value == "perspective") {
+        return {ast::InterpolationType::kPerspective, ident.source};
+    }
+    if (ident.value == "linear") {
+        return {ast::InterpolationType::kLinear, ident.source};
+    }
+    if (ident.value == "flat") {
+        return {ast::InterpolationType::kFlat, ident.source};
+    }
+
+    return add_error(ident.source, "invalid interpolation type");
+}
+
+// builtin_value_name
+//   : 'vertex_index'
+//   | 'instance_index'
+//   | 'position'
+//   | 'front_facing'
+//   | 'frag_depth'
+//   | 'local_invocation_id'
+//   | 'local_invocation_index'
+//   | 'global_invocation_id'
+//   | 'workgroup_id'
+//   | 'num_workgroups'
+//   | 'sample_index'
+//   | 'sample_mask'
 Expect<ast::BuiltinValue> ParserImpl::expect_builtin() {
     auto ident = expect_ident("builtin");
     if (ident.errored) {
@@ -1502,9 +1629,9 @@
     return {builtin, ident.source};
 }
 
-// body_stmt
-//   : BRACE_LEFT statements BRACE_RIGHT
-Expect<ast::BlockStatement*> ParserImpl::expect_body_stmt() {
+// compound_statement
+//   : BRACE_LEFT statement* BRACE_RIGHT
+Expect<ast::BlockStatement*> ParserImpl::expect_compound_statement() {
     return expect_brace_block("", [&]() -> Expect<ast::BlockStatement*> {
         auto stmts = expect_statements();
         if (stmts.errored) {
@@ -1515,10 +1642,10 @@
 }
 
 // paren_expression
-//   : PAREN_LEFT logical_or_expression PAREN_RIGHT
+//   : PAREN_LEFT expression PAREN_RIGHT
 Expect<const ast::Expression*> ParserImpl::expect_paren_expression() {
     return expect_paren_block("", [&]() -> Expect<const ast::Expression*> {
-        auto expr = logical_or_expression();
+        auto expr = expression();
         if (expr.errored) {
             return Failure::kErrored;
         }
@@ -1532,16 +1659,16 @@
 
 // statements
 //   : statement*
-Expect<ast::StatementList> ParserImpl::expect_statements() {
+Expect<ParserImpl::StatementList> ParserImpl::expect_statements() {
     bool errored = false;
-    ast::StatementList stmts;
+    StatementList stmts;
 
     while (continue_parsing()) {
         auto stmt = statement();
         if (stmt.errored) {
             errored = true;
         } else if (stmt.matched) {
-            stmts.emplace_back(stmt.value);
+            stmts.Push(stmt.value);
         } else {
             break;
         }
@@ -1556,30 +1683,20 @@
 
 // statement
 //   : SEMICOLON
-//   | body_stmt?
-//   | if_stmt
-//   | switch_stmt
-//   | loop_stmt
-//   | for_stmt
-//   | while_stmt
-//   | non_block_statement
-//      : return_stmt SEMICOLON
-//      | func_call_stmt SEMICOLON
-//      | variable_stmt SEMICOLON
-//      | break_stmt SEMICOLON
-//      | continue_stmt SEMICOLON
-//      | DISCARD SEMICOLON
-//      | assignment_stmt SEMICOLON
-//      | increment_stmt SEMICOLON
-//      | decrement_stmt SEMICOLON
+//   | if_statement
+//   | switch_statement
+//   | loop_statement
+//   | for_statement
+//   | while_statement
+//   | compound_statement
+//   | non_block_statement   // Note, we inject an extra rule in here for simpler parsing
 Maybe<const ast::Statement*> ParserImpl::statement() {
     while (match(Token::Type::kSemicolon)) {
         // Skip empty statements
     }
 
-    // Non-block statments that error can resynchronize on semicolon.
+    // Non-block statements that error can resynchronize on semicolon.
     auto stmt = sync(Token::Type::kSemicolon, [&] { return non_block_statement(); });
-
     if (stmt.errored) {
         return Failure::kErrored;
     }
@@ -1587,7 +1704,7 @@
         return stmt;
     }
 
-    auto stmt_if = if_stmt();
+    auto stmt_if = if_statement();
     if (stmt_if.errored) {
         return Failure::kErrored;
     }
@@ -1595,7 +1712,7 @@
         return stmt_if.value;
     }
 
-    auto sw = switch_stmt();
+    auto sw = switch_statement();
     if (sw.errored) {
         return Failure::kErrored;
     }
@@ -1603,7 +1720,7 @@
         return sw.value;
     }
 
-    auto loop = loop_stmt();
+    auto loop = loop_statement();
     if (loop.errored) {
         return Failure::kErrored;
     }
@@ -1611,7 +1728,7 @@
         return loop.value;
     }
 
-    auto stmt_for = for_stmt();
+    auto stmt_for = for_statement();
     if (stmt_for.errored) {
         return Failure::kErrored;
     }
@@ -1619,7 +1736,7 @@
         return stmt_for.value;
     }
 
-    auto stmt_while = while_stmt();
+    auto stmt_while = while_statement();
     if (stmt_while.errored) {
         return Failure::kErrored;
     }
@@ -1628,7 +1745,7 @@
     }
 
     if (peek_is(Token::Type::kBraceLeft)) {
-        auto body = expect_body_stmt();
+        auto body = expect_compound_statement();
         if (body.errored) {
             return Failure::kErrored;
         }
@@ -1638,19 +1755,20 @@
     return Failure::kNoMatch;
 }
 
-// statement (continued)
-//   : return_stmt SEMICOLON
-//   | func_call_stmt SEMICOLON
-//   | variable_stmt SEMICOLON
-//   | break_stmt SEMICOLON
-//   | continue_stmt SEMICOLON
+// non_block_statement (continued)
+//   : return_statement SEMICOLON
+//   | func_call_statement SEMICOLON
+//   | variable_statement SEMICOLON
+//   | break_statement SEMICOLON
+//   | continue_statement SEMICOLON
 //   | DISCARD SEMICOLON
-//   | assignment_stmt SEMICOLON
-//   | increment_stmt SEMICOLON
-//   | decrement_stmt SEMICOLON
+//   | assignment_statement SEMICOLON
+//   | increment_statement SEMICOLON
+//   | decrement_statement SEMICOLON
+//   | static_assert_statement SEMICOLON
 Maybe<const ast::Statement*> ParserImpl::non_block_statement() {
     auto stmt = [&]() -> Maybe<const ast::Statement*> {
-        auto ret_stmt = return_stmt();
+        auto ret_stmt = return_statement();
         if (ret_stmt.errored) {
             return Failure::kErrored;
         }
@@ -1658,7 +1776,7 @@
             return ret_stmt.value;
         }
 
-        auto func = func_call_stmt();
+        auto func = func_call_statement();
         if (func.errored) {
             return Failure::kErrored;
         }
@@ -1666,7 +1784,7 @@
             return func.value;
         }
 
-        auto var = variable_stmt();
+        auto var = variable_statement();
         if (var.errored) {
             return Failure::kErrored;
         }
@@ -1674,7 +1792,7 @@
             return var.value;
         }
 
-        auto b = break_stmt();
+        auto b = break_statement();
         if (b.errored) {
             return Failure::kErrored;
         }
@@ -1682,7 +1800,7 @@
             return b.value;
         }
 
-        auto cont = continue_stmt();
+        auto cont = continue_statement();
         if (cont.errored) {
             return Failure::kErrored;
         }
@@ -1690,7 +1808,13 @@
             return cont.value;
         }
 
-        auto assign = assignment_stmt();
+        Source source;
+        if (match(Token::Type::kDiscard, &source)) {
+            return create<ast::DiscardStatement>(source);
+        }
+
+        // Note, this covers assignment, increment and decrement
+        auto assign = assignment_statement();
         if (assign.errored) {
             return Failure::kErrored;
         }
@@ -1698,9 +1822,12 @@
             return assign.value;
         }
 
-        Source source;
-        if (match(Token::Type::kDiscard, &source)) {
-            return create<ast::DiscardStatement>(source);
+        auto stmt_static_assert = static_assert_statement();
+        if (stmt_static_assert.errored) {
+            return Failure::kErrored;
+        }
+        if (stmt_static_assert.matched) {
+            return stmt_static_assert.value;
         }
 
         return Failure::kNoMatch;
@@ -1709,13 +1836,12 @@
     if (stmt.matched && !expect(stmt->Name(), Token::Type::kSemicolon)) {
         return Failure::kErrored;
     }
-
     return stmt;
 }
 
-// return_stmt
-//   : RETURN logical_or_expression?
-Maybe<const ast::ReturnStatement*> ParserImpl::return_stmt() {
+// return_statement
+//   : RETURN expression?
+Maybe<const ast::ReturnStatement*> ParserImpl::return_statement() {
     Source source;
     if (!match(Token::Type::kReturn, &source)) {
         return Failure::kNoMatch;
@@ -1725,7 +1851,7 @@
         return create<ast::ReturnStatement>(source, nullptr);
     }
 
-    auto expr = logical_or_expression();
+    auto expr = expression();
     if (expr.errored) {
         return Failure::kErrored;
     }
@@ -1734,14 +1860,14 @@
     return create<ast::ReturnStatement>(source, expr.value);
 }
 
-// variable_stmt
+// variable_statement
 //   : variable_decl
-//   | variable_decl EQUAL logical_or_expression
-//   | CONST variable_ident_decl EQUAL logical_or_expression
-Maybe<const ast::VariableDeclStatement*> ParserImpl::variable_stmt() {
+//   | variable_decl EQUAL expression
+//   | LET (ident | variable_ident_decl) EQUAL expression
+//   | CONST (ident | variable_ident_decl) EQUAL expression
+Maybe<const ast::VariableDeclStatement*> ParserImpl::variable_statement() {
     if (match(Token::Type::kConst)) {
-        auto decl = expect_variable_ident_decl("'const' declaration",
-                                               /*allow_inferred = */ true);
+        auto decl = expect_ident_or_variable_ident_decl("'const' declaration");
         if (decl.errored) {
             return Failure::kErrored;
         }
@@ -1750,7 +1876,7 @@
             return Failure::kErrored;
         }
 
-        auto initializer = logical_or_expression();
+        auto initializer = expression();
         if (initializer.errored) {
             return Failure::kErrored;
         }
@@ -1762,14 +1888,13 @@
                                           builder_.Symbols().Register(decl->name),  // symbol
                                           decl->type,                               // type
                                           initializer.value,                        // initializer
-                                          ast::AttributeList{});                    // attributes
+                                          utils::Empty);                            // attributes
 
         return create<ast::VariableDeclStatement>(decl->source, const_);
     }
 
     if (match(Token::Type::kLet)) {
-        auto decl = expect_variable_ident_decl("'let' declaration",
-                                               /*allow_inferred = */ true);
+        auto decl = expect_ident_or_variable_ident_decl("'let' declaration");
         if (decl.errored) {
             return Failure::kErrored;
         }
@@ -1778,7 +1903,7 @@
             return Failure::kErrored;
         }
 
-        auto initializer = logical_or_expression();
+        auto initializer = expression();
         if (initializer.errored) {
             return Failure::kErrored;
         }
@@ -1790,7 +1915,7 @@
                                      builder_.Symbols().Register(decl->name),  // symbol
                                      decl->type,                               // type
                                      initializer.value,                        // initializer
-                                     ast::AttributeList{});                    // attributes
+                                     utils::Empty);                            // attributes
 
         return create<ast::VariableDeclStatement>(decl->source, let);
     }
@@ -1805,7 +1930,7 @@
 
     const ast::Expression* initializer = nullptr;
     if (match(Token::Type::kEqual)) {
-        auto initializer_expr = logical_or_expression();
+        auto initializer_expr = expression();
         if (initializer_expr.errored) {
             return Failure::kErrored;
         }
@@ -1822,17 +1947,17 @@
                                  decl->storage_class,                      // storage class
                                  decl->access,                             // access control
                                  initializer,                              // initializer
-                                 ast::AttributeList{});                    // attributes
+                                 utils::Empty);                            // attributes
 
     return create<ast::VariableDeclStatement>(var->source, var);
 }
 
-// if_stmt
+// if_statement
 //   : IF expression compound_stmt ( ELSE else_stmt ) ?
 // else_stmt
-//  : body_stmt
-//  | if_stmt
-Maybe<const ast::IfStatement*> ParserImpl::if_stmt() {
+//  : compound_statement
+//  | if_statement
+Maybe<const ast::IfStatement*> ParserImpl::if_statement() {
     // Parse if-else chains iteratively instead of recursively, to avoid
     // stack-overflow for long chains of if-else statements.
 
@@ -1849,7 +1974,7 @@
             return Failure::kNoMatch;
         }
 
-        auto condition = logical_or_expression();
+        auto condition = expression();
         if (condition.errored) {
             return Failure::kErrored;
         }
@@ -1857,7 +1982,7 @@
             return add_error(peek(), "unable to parse condition expression");
         }
 
-        auto body = expect_body_stmt();
+        auto body = expect_compound_statement();
         if (body.errored) {
             return Failure::kErrored;
         }
@@ -1893,7 +2018,7 @@
         }
 
         // If it wasn't an "else if", it must just be an "else".
-        auto else_body = expect_body_stmt();
+        auto else_body = expect_compound_statement();
         if (else_body.errored) {
             return Failure::kErrored;
         }
@@ -1909,15 +2034,15 @@
     return last_stmt->As<ast::IfStatement>();
 }
 
-// switch_stmt
-//   : SWITCH paren_expression BRACKET_LEFT switch_body+ BRACKET_RIGHT
-Maybe<const ast::SwitchStatement*> ParserImpl::switch_stmt() {
+// switch_statement
+//   : SWITCH expression BRACKET_LEFT switch_body+ BRACKET_RIGHT
+Maybe<const ast::SwitchStatement*> ParserImpl::switch_statement() {
     Source source;
     if (!match(Token::Type::kSwitch, &source)) {
         return Failure::kNoMatch;
     }
 
-    auto condition = logical_or_expression();
+    auto condition = expression();
     if (condition.errored) {
         return Failure::kErrored;
     }
@@ -1925,9 +2050,9 @@
         return add_error(peek(), "unable to parse selector expression");
     }
 
-    auto body = expect_brace_block("switch statement", [&]() -> Expect<ast::CaseStatementList> {
+    auto body = expect_brace_block("switch statement", [&]() -> Expect<CaseStatementList> {
         bool errored = false;
-        ast::CaseStatementList list;
+        CaseStatementList list;
         while (continue_parsing()) {
             auto stmt = switch_body();
             if (stmt.errored) {
@@ -1937,7 +2062,7 @@
             if (!stmt.matched) {
                 break;
             }
-            list.push_back(stmt.value);
+            list.Push(stmt.value);
         }
         if (errored) {
             return Failure::kErrored;
@@ -1962,7 +2087,7 @@
 
     auto& t = next();
 
-    ast::CaseSelectorList selector_list;
+    CaseSelectorList selector_list;
     if (t.Is(Token::Type::kCase)) {
         auto selectors = expect_case_selectors();
         if (selectors.errored) {
@@ -1990,8 +2115,8 @@
 
 // case_selectors
 //   : const_literal (COMMA const_literal)* COMMA?
-Expect<ast::CaseSelectorList> ParserImpl::expect_case_selectors() {
-    ast::CaseSelectorList selectors;
+Expect<ParserImpl::CaseSelectorList> ParserImpl::expect_case_selectors() {
+    CaseSelectorList selectors;
 
     while (continue_parsing()) {
         auto cond = const_literal();
@@ -2003,14 +2128,14 @@
             return add_error(cond.value->source, "invalid case selector must be an integer value");
         }
 
-        selectors.push_back(cond.value->As<ast::IntLiteralExpression>());
+        selectors.Push(cond.value->As<ast::IntLiteralExpression>());
 
         if (!match(Token::Type::kComma)) {
             break;
         }
     }
 
-    if (selectors.empty()) {
+    if (selectors.IsEmpty()) {
         return add_error(peek(), "unable to parse case selectors");
     }
 
@@ -2022,7 +2147,7 @@
 //   | statement case_body
 //   | FALLTHROUGH SEMICOLON
 Maybe<const ast::BlockStatement*> ParserImpl::case_body() {
-    ast::StatementList stmts;
+    StatementList stmts;
     while (continue_parsing()) {
         Source source;
         if (match(Token::Type::kFallthrough, &source)) {
@@ -2030,7 +2155,12 @@
                 return Failure::kErrored;
             }
 
-            stmts.emplace_back(create<ast::FallthroughStatement>(source));
+            deprecated(source,
+                       "fallthrough is set to be removed from WGSL. "
+                       "Case can accept multiple selectors if the existing case bodies are empty. "
+                       "default is not yet supported in a case selector list.");
+
+            stmts.Push(create<ast::FallthroughStatement>(source));
             break;
         }
 
@@ -2042,15 +2172,15 @@
             break;
         }
 
-        stmts.emplace_back(stmt.value);
+        stmts.Push(stmt.value);
     }
 
     return create<ast::BlockStatement>(Source{}, stmts);
 }
 
-// loop_stmt
-//   : LOOP BRACKET_LEFT statements continuing_stmt? BRACKET_RIGHT
-Maybe<const ast::LoopStatement*> ParserImpl::loop_stmt() {
+// loop_statement
+//   : LOOP BRACKET_LEFT statements continuing_statement? BRACKET_RIGHT
+Maybe<const ast::LoopStatement*> ParserImpl::loop_statement() {
     Source source;
     if (!match(Token::Type::kLoop, &source)) {
         return Failure::kNoMatch;
@@ -2062,7 +2192,7 @@
             return Failure::kErrored;
         }
 
-        auto continuing = continuing_stmt();
+        auto continuing = continuing_statement();
         if (continuing.errored) {
             return Failure::kErrored;
         }
@@ -2079,10 +2209,10 @@
 
 ForHeader::~ForHeader() = default;
 
-// (variable_stmt | increment_stmt | decrement_stmt | assignment_stmt |
-// func_call_stmt)?
+// (variable_statement | increment_statement | decrement_statement | assignment_statement |
+// func_call_statement)?
 Maybe<const ast::Statement*> ParserImpl::for_header_initializer() {
-    auto call = func_call_stmt();
+    auto call = func_call_statement();
     if (call.errored) {
         return Failure::kErrored;
     }
@@ -2090,7 +2220,7 @@
         return call.value;
     }
 
-    auto var = variable_stmt();
+    auto var = variable_statement();
     if (var.errored) {
         return Failure::kErrored;
     }
@@ -2098,7 +2228,7 @@
         return var.value;
     }
 
-    auto assign = assignment_stmt();
+    auto assign = assignment_statement();
     if (assign.errored) {
         return Failure::kErrored;
     }
@@ -2109,9 +2239,9 @@
     return Failure::kNoMatch;
 }
 
-// (increment_stmt | decrement_stmt | assignment_stmt | func_call_stmt)?
+// (increment_statement | decrement_statement | assignment_statement | func_call_statement)?
 Maybe<const ast::Statement*> ParserImpl::for_header_continuing() {
-    auto call_stmt = func_call_stmt();
+    auto call_stmt = func_call_statement();
     if (call_stmt.errored) {
         return Failure::kErrored;
     }
@@ -2119,7 +2249,7 @@
         return call_stmt.value;
     }
 
-    auto assign = assignment_stmt();
+    auto assign = assignment_statement();
     if (assign.errored) {
         return Failure::kErrored;
     }
@@ -2131,10 +2261,10 @@
 }
 
 // for_header
-//   : (variable_stmt | assignment_stmt | func_call_stmt)?
+//   : (variable_statement | assignment_statement | func_call_statement)?
 //   SEMICOLON
-//      logical_or_expression? SEMICOLON
-//      (assignment_stmt | func_call_stmt)?
+//      expression? SEMICOLON
+//      (assignment_statement | func_call_statement)?
 Expect<std::unique_ptr<ForHeader>> ParserImpl::expect_for_header() {
     auto initializer = for_header_initializer();
     if (initializer.errored) {
@@ -2145,7 +2275,7 @@
         return Failure::kErrored;
     }
 
-    auto condition = logical_or_expression();
+    auto condition = expression();
     if (condition.errored) {
         return Failure::kErrored;
     }
@@ -2164,7 +2294,7 @@
 
 // for_statement
 //   : FOR PAREN_LEFT for_header PAREN_RIGHT BRACE_LEFT statements BRACE_RIGHT
-Maybe<const ast::ForLoopStatement*> ParserImpl::for_stmt() {
+Maybe<const ast::ForLoopStatement*> ParserImpl::for_statement() {
     Source source;
     if (!match(Token::Type::kFor, &source)) {
         return Failure::kNoMatch;
@@ -2187,13 +2317,13 @@
 
 // while_statement
 //   :  WHILE expression compound_statement
-Maybe<const ast::WhileStatement*> ParserImpl::while_stmt() {
+Maybe<const ast::WhileStatement*> ParserImpl::while_statement() {
     Source source;
     if (!match(Token::Type::kWhile, &source)) {
         return Failure::kNoMatch;
     }
 
-    auto condition = logical_or_expression();
+    auto condition = expression();
     if (condition.errored) {
         return Failure::kErrored;
     }
@@ -2201,7 +2331,7 @@
         return add_error(peek(), "unable to parse while condition expression");
     }
 
-    auto body = expect_body_stmt();
+    auto body = expect_compound_statement();
     if (body.errored) {
         return Failure::kErrored;
     }
@@ -2209,9 +2339,9 @@
     return create<ast::WhileStatement>(source, condition.value, body.value);
 }
 
-// func_call_stmt
+// func_call_statement
 //    : IDENT argument_expression_list
-Maybe<const ast::CallStatement*> ParserImpl::func_call_stmt() {
+Maybe<const ast::CallStatement*> ParserImpl::func_call_statement() {
     auto& t = peek();
     auto& t2 = peek(1);
     if (!t.IsIdentifier() || !t2.Is(Token::Type::kParenLeft)) {
@@ -2233,9 +2363,9 @@
             std::move(params.value)));
 }
 
-// break_stmt
+// break_statement
 //   : BREAK
-Maybe<const ast::BreakStatement*> ParserImpl::break_stmt() {
+Maybe<const ast::BreakStatement*> ParserImpl::break_statement() {
     Source source;
     if (!match(Token::Type::kBreak, &source)) {
         return Failure::kNoMatch;
@@ -2244,9 +2374,9 @@
     return create<ast::BreakStatement>(source);
 }
 
-// continue_stmt
+// continue_statement
 //   : CONTINUE
-Maybe<const ast::ContinueStatement*> ParserImpl::continue_stmt() {
+Maybe<const ast::ContinueStatement*> ParserImpl::continue_statement() {
     Source source;
     if (!match(Token::Type::kContinue, &source)) {
         return Failure::kNoMatch;
@@ -2255,14 +2385,42 @@
     return create<ast::ContinueStatement>(source);
 }
 
-// continuing_stmt
-//   : CONTINUING body_stmt
-Maybe<const ast::BlockStatement*> ParserImpl::continuing_stmt() {
+// break_if_statement:
+//    'break' 'if' expression semicolon
+Maybe<const ast::Statement*> ParserImpl::break_if_statement() {
+    // TODO(crbug.com/tint/1451): Add support for break-if
+    return Failure::kNoMatch;
+}
+
+// continuing_compound_statement:
+//   brace_left statement* break_if_statement? brace_right
+Maybe<const ast::BlockStatement*> ParserImpl::continuing_compound_statement() {
+    return expect_brace_block("", [&]() -> Expect<ast::BlockStatement*> {
+        auto stmts = expect_statements();
+        if (stmts.errored) {
+            return Failure::kErrored;
+        }
+
+        auto break_if = break_if_statement();
+        if (break_if.errored) {
+            return Failure::kErrored;
+        }
+        if (break_if.matched) {
+            stmts.value.Push(break_if.value);
+        }
+
+        return create<ast::BlockStatement>(Source{}, stmts.value);
+    });
+}
+
+// continuing_statement
+//   : CONTINUING continuing_compound_statement
+Maybe<const ast::BlockStatement*> ParserImpl::continuing_statement() {
     if (!match(Token::Type::kContinuing)) {
-        return create<ast::BlockStatement>(Source{}, ast::StatementList{});
+        return create<ast::BlockStatement>(Source{}, utils::Empty);
     }
 
-    return expect_body_stmt();
+    return continuing_compound_statement();
 }
 
 // primary_expression
@@ -2343,7 +2501,7 @@
 
 // postfix_expression
 //   :
-//   | BRACE_LEFT logical_or_expression BRACE_RIGHT postfix_expr
+//   | BRACE_LEFT expression BRACE_RIGHT postfix_expr
 //   | PERIOD IDENTIFIER postfix_expr
 Maybe<const ast::Expression*> ParserImpl::postfix_expression(const ast::Expression* prefix) {
     Source source;
@@ -2351,7 +2509,7 @@
     while (continue_parsing()) {
         if (match(Token::Type::kBracketLeft, &source)) {
             auto res = sync(Token::Type::kBracketRight, [&]() -> Maybe<const ast::Expression*> {
-                auto param = logical_or_expression();
+                auto param = expression();
                 if (param.errored) {
                     return Failure::kErrored;
                 }
@@ -2407,19 +2565,19 @@
 }
 
 // argument_expression_list
-//   : PAREN_LEFT ((logical_or_expression COMMA)* logical_or_expression COMMA?)?
-//   PAREN_RIGHT
-Expect<ast::ExpressionList> ParserImpl::expect_argument_expression_list(std::string_view use) {
-    return expect_paren_block(use, [&]() -> Expect<ast::ExpressionList> {
-        ast::ExpressionList ret;
+//   : PAREN_LEFT ((expression COMMA)* expression COMMA?)? PAREN_RIGHT
+Expect<ParserImpl::ExpressionList> ParserImpl::expect_argument_expression_list(
+    std::string_view use) {
+    return expect_paren_block(use, [&]() -> Expect<ExpressionList> {
+        ExpressionList ret;
         while (continue_parsing()) {
-            auto arg = logical_or_expression();
+            auto arg = expression();
             if (arg.errored) {
                 return Failure::kErrored;
             } else if (!arg.matched) {
                 break;
             }
-            ret.push_back(arg.value);
+            ret.Push(arg.value);
 
             if (!match(Token::Type::kComma)) {
                 break;
@@ -2906,15 +3064,26 @@
     return expect_logical_or_expr(lhs.value);
 }
 
-// compound_assignment_operator:
-// | plus_equal
-// | minus_equal
-// | times_equal
-// | division_equal
-// | modulo_equal
-// | and_equal
-// | or_equal
-// | xor_equal
+// expression:
+//   : relational_expression
+//   | short_circuit_or_expression or_or relational_expression
+//   | short_circuit_and_expression and_and relational_expression
+//   | bitwise_expression
+Maybe<const ast::Expression*> ParserImpl::expression() {
+    return logical_or_expression();
+}
+
+// compound_assignment_operator
+//   : plus_equal
+//   | minus_equal
+//   | times_equal
+//   | division_equal
+//   | modulo_equal
+//   | and_equal
+//   | or_equal
+//   | xor_equal
+//   | shift_right_equal
+//   | shift_left_equal
 Maybe<ast::BinaryOp> ParserImpl::compound_assignment_operator() {
     ast::BinaryOp compound_op = ast::BinaryOp::kNone;
     if (peek_is(Token::Type::kPlusEqual)) {
@@ -2933,6 +3102,10 @@
         compound_op = ast::BinaryOp::kOr;
     } else if (peek_is(Token::Type::kXorEqual)) {
         compound_op = ast::BinaryOp::kXor;
+    } else if (peek_is(Token::Type::kShiftLeftEqual)) {
+        compound_op = ast::BinaryOp::kShiftLeft;
+    } else if (peek_is(Token::Type::kShiftRightEqual)) {
+        compound_op = ast::BinaryOp::kShiftRight;
     }
     if (compound_op != ast::BinaryOp::kNone) {
         next();
@@ -2941,16 +3114,16 @@
     return Failure::kNoMatch;
 }
 
-// assignment_stmt
-// | lhs_expression ( equal | compound_assignment_operator ) expression
-// | underscore equal expression
+// assignment_statement
+// | lhs_expression ( EQUAL | compound_assignment_operator ) expression
+// | UNDERSCORE EQUAL expression
 //
-// increment_stmt
+// increment_statement
 // | lhs_expression PLUS_PLUS
 //
-// decrement_stmt
+// decrement_statement
 // | lhs_expression MINUS_MINUS
-Maybe<const ast::Statement*> ParserImpl::assignment_stmt() {
+Maybe<const ast::Statement*> ParserImpl::assignment_statement() {
     auto& t = peek();
 
     // tint:295 - Test for `ident COLON` - this is invalid grammar, and without
@@ -2992,7 +3165,7 @@
         }
     }
 
-    auto rhs = logical_or_expression();
+    auto rhs = expression();
     if (rhs.errored) {
         return Failure::kErrored;
     }
@@ -3011,6 +3184,9 @@
 // const_literal
 //   : INT_LITERAL
 //   | FLOAT_LITERAL
+//   | bool_literal
+//
+// bool_literal:
 //   | TRUE
 //   | FALSE
 Maybe<const ast::LiteralExpression*> ParserImpl::const_literal() {
@@ -3051,16 +3227,16 @@
     return Failure::kNoMatch;
 }
 
-Maybe<ast::AttributeList> ParserImpl::attribute_list() {
+Maybe<ParserImpl::AttributeList> ParserImpl::attribute_list() {
     bool errored = false;
-    ast::AttributeList attrs;
+    AttributeList attrs;
 
     while (continue_parsing()) {
         if (match(Token::Type::kAttr)) {
             if (auto attr = expect_attribute(); attr.errored) {
                 errored = true;
             } else {
-                attrs.emplace_back(attr.value);
+                attrs.Push(attr.value);
             }
         } else {
             break;
@@ -3071,7 +3247,7 @@
         return Failure::kErrored;
     }
 
-    if (attrs.empty()) {
+    if (attrs.IsEmpty()) {
         return Failure::kNoMatch;
     }
 
@@ -3090,6 +3266,29 @@
     return add_error(t, "expected attribute");
 }
 
+// attribute
+//   : ATTR 'align' PAREN_LEFT expression attrib_end
+//   | ATTR 'binding' PAREN_LEFT expression attrib_end
+//   | ATTR 'builtin' PAREN_LEFT builtin_value_name attrib_end
+//   | ATTR 'const'
+//   | ATTR 'group' PAREN_LEFT expression attrib_end
+//   | ATTR 'id' PAREN_LEFT expression attrib_end
+//   | ATTR 'interpolate' PAREN_LEFT interpolation_type_name attrib_end
+//   | ATTR 'interpolate' PAREN_LEFT interpolation_type_name COMMA
+//                                   interpolation_sample_name attrib_end
+//   | ATTR 'invariant'
+//   | ATTR 'location' PAREN_LEFT expression attrib_end
+//   | ATTR 'size' PAREN_LEFT expression attrib_end
+//   | ATTR 'workgroup_size' PAREN_LEFT expression attrib_end
+//   | ATTR 'workgroup_size' PAREN_LEFT expression COMMA expression attrib_end
+//   | ATTR 'workgroup_size' PAREN_LEFT expression COMMA expression COMMA expression attrib_end
+//   | ATTR 'vertex'
+//   | ATTR 'fragment'
+//   | ATTR 'compute'
+//
+// attrib_end
+//   : COMMA? PAREN_RIGHT
+//
 Maybe<const ast::Attribute*> ParserImpl::attribute() {
     using Result = Maybe<const ast::Attribute*>;
     auto& t = next();
@@ -3098,8 +3297,8 @@
         return Failure::kNoMatch;
     }
 
-    if (t == kLocationAttribute) {
-        const char* use = "location attribute";
+    if (t == "align") {
+        const char* use = "align attribute";
         return expect_paren_block(use, [&]() -> Result {
             auto val = expect_positive_sint(use);
             if (val.errored) {
@@ -3107,11 +3306,11 @@
             }
             match(Token::Type::kComma);
 
-            return create<ast::LocationAttribute>(t.source(), val.value);
+            return create<ast::StructMemberAlignAttribute>(t.source(), val.value);
         });
     }
 
-    if (t == kBindingAttribute) {
+    if (t == "binding") {
         const char* use = "binding attribute";
         return expect_paren_block(use, [&]() -> Result {
             auto val = expect_positive_sint(use);
@@ -3124,7 +3323,29 @@
         });
     }
 
-    if (t == kGroupAttribute) {
+    if (t == "builtin") {
+        return expect_paren_block("builtin attribute", [&]() -> Result {
+            auto builtin = expect_builtin();
+            if (builtin.errored) {
+                return Failure::kErrored;
+            }
+            match(Token::Type::kComma);
+
+            return create<ast::BuiltinAttribute>(t.source(), builtin.value);
+        });
+    }
+
+    if (t == "compute") {
+        return create<ast::StageAttribute>(t.source(), ast::PipelineStage::kCompute);
+    }
+
+    // Note, `const` is not valid in a WGSL source file, it's internal only
+
+    if (t == "fragment") {
+        return create<ast::StageAttribute>(t.source(), ast::PipelineStage::kFragment);
+    }
+
+    if (t == "group") {
         const char* use = "group attribute";
         return expect_paren_block(use, [&]() -> Result {
             auto val = expect_positive_sint(use);
@@ -3137,59 +3358,106 @@
         });
     }
 
-    if (t == kInterpolateAttribute) {
-        return expect_paren_block("interpolate attribute", [&]() -> Result {
-            ast::InterpolationType type;
-            ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone;
+    if (t == "id") {
+        const char* use = "id attribute";
+        return expect_paren_block(use, [&]() -> Result {
+            auto val = expect_positive_sint(use);
+            if (val.errored) {
+                return Failure::kErrored;
+            }
+            match(Token::Type::kComma);
 
-            auto& type_tok = next();
-            if (type_tok == "perspective") {
-                type = ast::InterpolationType::kPerspective;
-            } else if (type_tok == "linear") {
-                type = ast::InterpolationType::kLinear;
-            } else if (type_tok == "flat") {
-                type = ast::InterpolationType::kFlat;
-            } else {
-                return add_error(type_tok, "invalid interpolation type");
+            return create<ast::IdAttribute>(t.source(), val.value);
+        });
+    }
+
+    if (t == "interpolate") {
+        return expect_paren_block("interpolate attribute", [&]() -> Result {
+            auto type = expect_interpolation_type_name();
+            if (type.errored) {
+                return Failure::kErrored;
             }
 
+            ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone;
             if (match(Token::Type::kComma)) {
                 if (!peek_is(Token::Type::kParenRight)) {
-                    auto& sampling_tok = next();
-                    if (sampling_tok == "center") {
-                        sampling = ast::InterpolationSampling::kCenter;
-                    } else if (sampling_tok == "centroid") {
-                        sampling = ast::InterpolationSampling::kCentroid;
-                    } else if (sampling_tok == "sample") {
-                        sampling = ast::InterpolationSampling::kSample;
-                    } else {
-                        return add_error(sampling_tok, "invalid interpolation sampling");
+                    auto sample = expect_interpolation_sample_name();
+                    if (sample.errored) {
+                        return Failure::kErrored;
                     }
+
+                    sampling = sample.value;
                     match(Token::Type::kComma);
                 }
             }
 
-            return create<ast::InterpolateAttribute>(t.source(), type, sampling);
+            return create<ast::InterpolateAttribute>(t.source(), type.value, sampling);
         });
     }
 
-    if (t == kInvariantAttribute) {
+    if (t == "invariant") {
         return create<ast::InvariantAttribute>(t.source());
     }
 
-    if (t == kBuiltinAttribute) {
-        return expect_paren_block("builtin attribute", [&]() -> Result {
-            auto builtin = expect_builtin();
-            if (builtin.errored) {
+    if (t == "location") {
+        const char* use = "location attribute";
+        return expect_paren_block(use, [&]() -> Result {
+            auto val = expect_positive_sint(use);
+            if (val.errored) {
                 return Failure::kErrored;
             }
-
             match(Token::Type::kComma);
-            return create<ast::BuiltinAttribute>(t.source(), builtin.value);
+
+            return create<ast::LocationAttribute>(t.source(), val.value);
         });
     }
 
-    if (t == kWorkgroupSizeAttribute) {
+    if (t == "size") {
+        const char* use = "size attribute";
+        return expect_paren_block(use, [&]() -> Result {
+            auto val = expect_positive_sint(use);
+            if (val.errored) {
+                return Failure::kErrored;
+            }
+            match(Token::Type::kComma);
+
+            return create<ast::StructMemberSizeAttribute>(t.source(), val.value);
+        });
+    }
+
+    // TODO(crbug.com/tint/1503): Remove when deprecation period is over.
+    if (t == "stage") {
+        return expect_paren_block("stage attribute", [&]() -> Result {
+            auto stage = expect_pipeline_stage();
+            if (stage.errored) {
+                return Failure::kErrored;
+            }
+
+            std::string warning = "remove stage and use @";
+            switch (stage.value) {
+                case ast::PipelineStage::kVertex:
+                    warning += "vertex";
+                    break;
+                case ast::PipelineStage::kFragment:
+                    warning += "fragment";
+                    break;
+                case ast::PipelineStage::kCompute:
+                    warning += "compute";
+                    break;
+                case ast::PipelineStage::kNone:
+                    break;
+            }
+            deprecated(t.source(), warning);
+
+            return create<ast::StageAttribute>(t.source(), stage.value);
+        });
+    }
+
+    if (t == "vertex") {
+        return create<ast::StageAttribute>(t.source(), ast::PipelineStage::kVertex);
+    }
+
+    if (t == "workgroup_size") {
         return expect_paren_block("workgroup_size attribute", [&]() -> Result {
             const ast::Expression* x = nullptr;
             const ast::Expression* y = nullptr;
@@ -3232,88 +3500,11 @@
             return create<ast::WorkgroupAttribute>(t.source(), x, y, z);
         });
     }
-
-    // TODO(crbug.com/tint/1503): Remove when deprecation period is over.
-    if (t == kStageAttribute) {
-        return expect_paren_block("stage attribute", [&]() -> Result {
-            auto stage = expect_pipeline_stage();
-            if (stage.errored) {
-                return Failure::kErrored;
-            }
-
-            std::string warning = "remove stage and use @";
-            switch (stage.value) {
-                case ast::PipelineStage::kVertex:
-                    warning += "vertex";
-                    break;
-                case ast::PipelineStage::kFragment:
-                    warning += "fragment";
-                    break;
-                case ast::PipelineStage::kCompute:
-                    warning += "compute";
-                    break;
-                case ast::PipelineStage::kNone:
-                    break;
-            }
-            deprecated(t.source(), warning);
-
-            return create<ast::StageAttribute>(t.source(), stage.value);
-        });
-    }
-    if (t == kComputeStage) {
-        return create<ast::StageAttribute>(t.source(), ast::PipelineStage::kCompute);
-    }
-    if (t == kVertexStage) {
-        return create<ast::StageAttribute>(t.source(), ast::PipelineStage::kVertex);
-    }
-    if (t == kFragmentStage) {
-        return create<ast::StageAttribute>(t.source(), ast::PipelineStage::kFragment);
-    }
-
-    if (t == kSizeAttribute) {
-        const char* use = "size attribute";
-        return expect_paren_block(use, [&]() -> Result {
-            auto val = expect_positive_sint(use);
-            if (val.errored) {
-                return Failure::kErrored;
-            }
-            match(Token::Type::kComma);
-
-            return create<ast::StructMemberSizeAttribute>(t.source(), val.value);
-        });
-    }
-
-    if (t == kAlignAttribute) {
-        const char* use = "align attribute";
-        return expect_paren_block(use, [&]() -> Result {
-            auto val = expect_positive_sint(use);
-            if (val.errored) {
-                return Failure::kErrored;
-            }
-            match(Token::Type::kComma);
-
-            return create<ast::StructMemberAlignAttribute>(t.source(), val.value);
-        });
-    }
-
-    if (t == kIdAttribute) {
-        const char* use = "id attribute";
-        return expect_paren_block(use, [&]() -> Result {
-            auto val = expect_positive_sint(use);
-            if (val.errored) {
-                return Failure::kErrored;
-            }
-            match(Token::Type::kComma);
-
-            return create<ast::IdAttribute>(t.source(), val.value);
-        });
-    }
-
     return Failure::kNoMatch;
 }
 
-bool ParserImpl::expect_attributes_consumed(ast::AttributeList& in) {
-    if (in.empty()) {
+bool ParserImpl::expect_attributes_consumed(utils::VectorRef<const ast::Attribute*> in) {
+    if (in.IsEmpty()) {
         return true;
     }
     add_error(in[0]->source, "unexpected attributes");
@@ -3423,7 +3614,7 @@
         next();
 
         if (is_reserved(t)) {
-            return add_error(t.source(), "'" + t.to_str() + "' is a reserved keyword");
+            deprecated(t.source(), "'" + t.to_str() + "' is a reserved keyword");
         }
 
         return {t.to_str(), t.source()};
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h
index 3e378c7..14f86d5 100644
--- a/src/tint/reader/wgsl/parser_impl.h
+++ b/src/tint/reader/wgsl/parser_impl.h
@@ -71,6 +71,17 @@
     };
 
   public:
+    /// Pre-determined small vector sizes for AST pointers
+    //! @cond Doxygen_Suppress
+    using AttributeList = utils::Vector<const ast::Attribute*, 4>;
+    using CaseSelectorList = utils::Vector<const ast::IntLiteralExpression*, 4>;
+    using CaseStatementList = utils::Vector<const ast::CaseStatement*, 4>;
+    using ExpressionList = utils::Vector<const ast::Expression*, 8>;
+    using ParameterList = utils::Vector<const ast::Parameter*, 8>;
+    using StatementList = utils::Vector<const ast::Statement*, 8>;
+    using StructMemberList = utils::Vector<const ast::StructMember*, 8>;
+    //! @endcond
+
     /// Expect is the return type of the parser methods that are expected to
     /// return a parsed value of type T, unless there was an parse error.
     /// In the case of a parse error the called method will have called
@@ -231,9 +242,9 @@
         /// @param ret_attrs return type attributes
         FunctionHeader(Source src,
                        std::string n,
-                       ast::ParameterList p,
+                       utils::VectorRef<const ast::Parameter*> p,
                        const ast::Type* ret_ty,
-                       ast::AttributeList ret_attrs);
+                       utils::VectorRef<const ast::Attribute*> ret_attrs);
         /// Destructor
         ~FunctionHeader();
         /// Assignment operator
@@ -246,11 +257,11 @@
         /// Function name
         std::string name;
         /// Function parameters
-        ast::ParameterList params;
+        utils::Vector<const ast::Parameter*, 8> params;
         /// Function return type
         const ast::Type* return_type = nullptr;
         /// Function return type attributes
-        ast::AttributeList return_type_attributes;
+        AttributeList return_type_attributes;
     };
 
     /// VarDeclInfo contains the parsed information for variable declaration.
@@ -374,6 +385,10 @@
     void deprecated(const Source& source, const std::string& msg);
     /// Parses the `translation_unit` grammar element
     void translation_unit();
+    /// Parses the `global_directive` grammar element, erroring on parse failure.
+    /// @param has_parsed_decl flag indicating if the parser has consumed a global declaration.
+    /// @return true on parse success, otherwise an error or no-match.
+    Maybe<bool> global_directive(bool has_parsed_decl);
     /// Parses the `enable_directive` grammar element, erroring on parse failure.
     /// @return true on parse success, otherwise an error or no-match.
     Maybe<bool> enable_directive();
@@ -383,50 +398,61 @@
     /// Parses a `global_variable_decl` grammar element with the initial
     /// `variable_attribute_list*` provided as `attrs`
     /// @returns the variable parsed or nullptr
-    /// @param attrs the list of attributes for the variable declaration.
-    Maybe<const ast::Variable*> global_variable_decl(ast::AttributeList& attrs);
+    /// @param attrs the list of attributes for the variable declaration. If attributes are consumed
+    ///        by the declaration, then this vector is cleared before returning.
+    Maybe<const ast::Variable*> global_variable_decl(AttributeList& attrs);
     /// Parses a `global_constant_decl` grammar element with the initial
     /// `variable_attribute_list*` provided as `attrs`
     /// @returns the const object or nullptr
-    /// @param attrs the list of attributes for the constant declaration.
-    Maybe<const ast::Variable*> global_constant_decl(ast::AttributeList& attrs);
+    /// @param attrs the list of attributes for the constant declaration. If attributes are consumed
+    ///        by the declaration, then this vector is cleared before returning.
+    Maybe<const ast::Variable*> global_constant_decl(AttributeList& attrs);
     /// Parses a `variable_decl` grammar element
     /// @returns the parsed variable declaration info
     Maybe<VarDeclInfo> variable_decl();
-    /// Parses a `variable_ident_decl` grammar element, erroring on parse
-    /// failure.
+    /// Helper for parsing ident or variable_ident_decl. Should not be called directly,
+    /// use the specific version below.
     /// @param use a description of what was being parsed if an error was raised.
-    /// @param allow_inferred if true, do not fail if variable decl does not
-    /// specify type
+    /// @param allow_inferred allow the identifier to be parsed without a type
+    /// @returns the parsed identifier, and possibly type, or empty otherwise
+    Expect<TypedIdentifier> expect_ident_or_variable_ident_decl_impl(std::string_view use,
+                                                                     bool allow_inferred);
+    /// Parses a `ident` or a `variable_ident_decl` grammar element, erroring on parse failure.
+    /// @param use a description of what was being parsed if an error was raised.
+    /// @returns the identifier or empty otherwise.
+    Expect<TypedIdentifier> expect_ident_or_variable_ident_decl(std::string_view use);
+    /// Parses a `variable_ident_decl` grammar element, erroring on parse failure.
+    /// @param use a description of what was being parsed if an error was raised.
     /// @returns the identifier and type parsed or empty otherwise
-    Expect<TypedIdentifier> expect_variable_ident_decl(std::string_view use, bool allow_inferred);
+    Expect<TypedIdentifier> expect_variable_ident_decl(std::string_view use);
     /// Parses a `variable_qualifier` grammar element
     /// @returns the variable qualifier information
     Maybe<VariableQualifier> variable_qualifier();
-    /// Parses a `type_alias` grammar element
+    /// Parses a `type_alias_decl` grammar element
     /// @returns the type alias or nullptr on error
-    Maybe<const ast::Alias*> type_alias();
+    Maybe<const ast::Alias*> type_alias_decl();
     /// Parses a `type_decl` grammar element
     /// @returns the parsed Type or nullptr if none matched.
     Maybe<const ast::Type*> type_decl();
-    /// Parses a `storage_class` grammar element, erroring on parse failure.
+    /// Parses an `address_space` grammar element, erroring on parse failure.
     /// @param use a description of what was being parsed if an error was raised.
-    /// @returns the storage class or StorageClass::kNone if none matched
-    Expect<ast::StorageClass> expect_storage_class(std::string_view use);
+    /// @returns the address space or StorageClass::kNone if none matched
+    Expect<ast::StorageClass> expect_address_space(std::string_view use);
     /// Parses a `struct_decl` grammar element.
     /// @returns the struct type or nullptr on error
     Maybe<const ast::Struct*> struct_decl();
     /// Parses a `struct_body_decl` grammar element, erroring on parse failure.
     /// @returns the struct members
-    Expect<ast::StructMemberList> expect_struct_body_decl();
+    Expect<StructMemberList> expect_struct_body_decl();
     /// Parses a `struct_member` grammar element, erroring on parse failure.
     /// @returns the struct member or nullptr
     Expect<ast::StructMember*> expect_struct_member();
     /// Parses a `function_decl` grammar element with the initial
     /// `function_attribute_decl*` provided as `attrs`.
-    /// @param attrs the list of attributes for the function declaration.
+    /// @param attrs the list of attributes for the function declaration. If attributes are consumed
+    ///        by the declaration, then this vector is cleared before returning.
     /// @returns the parsed function, nullptr otherwise
-    Maybe<const ast::Function*> function_decl(ast::AttributeList& attrs);
+    Maybe<const ast::Function*> function_decl(AttributeList& attrs);
     /// Parses a `texture_samplers` grammar element
     /// @returns the parsed Type or nullptr if none matched.
     Maybe<const ast::Type*> texture_samplers();
@@ -454,12 +480,15 @@
     /// @param use a description of what was being parsed if an error was raised
     /// @returns returns the texel format or kNone if none matched.
     Expect<ast::TexelFormat> expect_texel_format(std::string_view use);
+    /// Parses a `static_assert_statement` grammar element
+    /// @returns returns the static assert, if it matched.
+    Maybe<const ast::StaticAssert*> static_assert_statement();
     /// Parses a `function_header` grammar element
     /// @returns the parsed function header
     Maybe<FunctionHeader> function_header();
     /// Parses a `param_list` grammar element, erroring on parse failure.
     /// @returns the parsed variables
-    Expect<ast::ParameterList> expect_param_list();
+    Expect<ParameterList> expect_param_list();
     /// Parses a `param` grammar element, erroring on parse failure.
     /// @returns the parsed variable
     Expect<ast::Parameter*> expect_param();
@@ -472,67 +501,81 @@
     /// @param use a description of what was being parsed if an error was raised
     /// @returns the parsed access control.
     Expect<ast::Access> expect_access(std::string_view use);
+    /// Parses an interpolation sample name identifier, erroring if the next token does not match a
+    /// valid sample name.
+    /// @returns the parsed sample name.
+    Expect<ast::InterpolationSampling> expect_interpolation_sample_name();
+    /// Parses an interpolation type name identifier, erroring if the next token does not match a
+    /// value type name.
+    /// @returns the parsed type name
+    Expect<ast::InterpolationType> expect_interpolation_type_name();
     /// Parses a builtin identifier, erroring if the next token does not match a
     /// valid builtin name.
     /// @returns the parsed builtin.
     Expect<ast::BuiltinValue> expect_builtin();
-    /// Parses a `body_stmt` grammar element, erroring on parse failure.
+    /// Parses a `compound_statement` grammar element, erroring on parse failure.
     /// @returns the parsed statements
-    Expect<ast::BlockStatement*> expect_body_stmt();
+    Expect<ast::BlockStatement*> expect_compound_statement();
     /// Parses a `paren_expression` grammar element, erroring on parse failure.
     /// @returns the parsed element or nullptr
     Expect<const ast::Expression*> expect_paren_expression();
     /// Parses a `statements` grammar element
     /// @returns the statements parsed
-    Expect<ast::StatementList> expect_statements();
+    Expect<StatementList> expect_statements();
     /// Parses a `statement` grammar element
     /// @returns the parsed statement or nullptr
     Maybe<const ast::Statement*> statement();
-    /// Parses a `break_stmt` grammar element
+    /// Parses a `break_statement` grammar element
     /// @returns the parsed statement or nullptr
-    Maybe<const ast::BreakStatement*> break_stmt();
-    /// Parses a `return_stmt` grammar element
+    Maybe<const ast::BreakStatement*> break_statement();
+    /// Parses a `return_statement` grammar element
     /// @returns the parsed statement or nullptr
-    Maybe<const ast::ReturnStatement*> return_stmt();
-    /// Parses a `continue_stmt` grammar element
+    Maybe<const ast::ReturnStatement*> return_statement();
+    /// Parses a `continue_statement` grammar element
     /// @returns the parsed statement or nullptr
-    Maybe<const ast::ContinueStatement*> continue_stmt();
-    /// Parses a `variable_stmt` grammar element
+    Maybe<const ast::ContinueStatement*> continue_statement();
+    /// Parses a `variable_statement` grammar element
     /// @returns the parsed variable or nullptr
-    Maybe<const ast::VariableDeclStatement*> variable_stmt();
-    /// Parses a `if_stmt` grammar element
+    Maybe<const ast::VariableDeclStatement*> variable_statement();
+    /// Parses a `if_statement` grammar element
     /// @returns the parsed statement or nullptr
-    Maybe<const ast::IfStatement*> if_stmt();
-    /// Parses a `switch_stmt` grammar element
+    Maybe<const ast::IfStatement*> if_statement();
+    /// Parses a `switch_statement` grammar element
     /// @returns the parsed statement or nullptr
-    Maybe<const ast::SwitchStatement*> switch_stmt();
+    Maybe<const ast::SwitchStatement*> switch_statement();
     /// Parses a `switch_body` grammar element
     /// @returns the parsed statement or nullptr
     Maybe<const ast::CaseStatement*> switch_body();
     /// Parses a `case_selectors` grammar element
     /// @returns the list of literals
-    Expect<ast::CaseSelectorList> expect_case_selectors();
+    Expect<CaseSelectorList> expect_case_selectors();
     /// Parses a `case_body` grammar element
     /// @returns the parsed statements
     Maybe<const ast::BlockStatement*> case_body();
-    /// Parses a `func_call_stmt` grammar element
+    /// Parses a `func_call_statement` grammar element
     /// @returns the parsed function call or nullptr
-    Maybe<const ast::CallStatement*> func_call_stmt();
-    /// Parses a `loop_stmt` grammar element
+    Maybe<const ast::CallStatement*> func_call_statement();
+    /// Parses a `loop_statement` grammar element
     /// @returns the parsed loop or nullptr
-    Maybe<const ast::LoopStatement*> loop_stmt();
+    Maybe<const ast::LoopStatement*> loop_statement();
     /// Parses a `for_header` grammar element, erroring on parse failure.
     /// @returns the parsed for header or nullptr
     Expect<std::unique_ptr<ForHeader>> expect_for_header();
-    /// Parses a `for_stmt` grammar element
+    /// Parses a `for_statement` grammar element
     /// @returns the parsed for loop or nullptr
-    Maybe<const ast::ForLoopStatement*> for_stmt();
-    /// Parses a `while_stmt` grammar element
+    Maybe<const ast::ForLoopStatement*> for_statement();
+    /// Parses a `while_statement` grammar element
     /// @returns the parsed while loop or nullptr
-    Maybe<const ast::WhileStatement*> while_stmt();
-    /// Parses a `continuing_stmt` grammar element
+    Maybe<const ast::WhileStatement*> while_statement();
+    /// Parses a `break_if_statement` grammar element
+    /// @returns the parsed statement or nullptr
+    Maybe<const ast::Statement*> break_if_statement();
+    /// Parses a `continuing_compound_statement` grammar element
     /// @returns the parsed statements
-    Maybe<const ast::BlockStatement*> continuing_stmt();
+    Maybe<const ast::BlockStatement*> continuing_compound_statement();
+    /// Parses a `continuing_statement` grammar element
+    /// @returns the parsed statements
+    Maybe<const ast::BlockStatement*> continuing_statement();
     /// Parses a `const_literal` grammar element
     /// @returns the const literal parsed or nullptr if none found
     Maybe<const ast::LiteralExpression*> const_literal();
@@ -543,7 +586,7 @@
     /// failure.
     /// @param use a description of what was being parsed if an error was raised
     /// @returns the list of arguments
-    Expect<ast::ExpressionList> expect_argument_expression_list(std::string_view use);
+    Expect<ExpressionList> expect_argument_expression_list(std::string_view use);
     /// Parses the recursive portion of the postfix_expression
     /// @param prefix the left side of the expression
     /// @returns the parsed expression or nullptr
@@ -634,15 +677,18 @@
     /// Parses a `logical_or_expression` grammar element
     /// @returns the parsed expression or nullptr
     Maybe<const ast::Expression*> logical_or_expression();
+    /// Parses an `expression` grammar element
+    /// @returns the parsed expression or nullptr
+    Maybe<const ast::Expression*> expression();
     /// Parses a `compound_assignment_operator` grammar element
     /// @returns the parsed compound assignment operator
     Maybe<ast::BinaryOp> compound_assignment_operator();
-    /// Parses a `assignment_stmt` grammar element
+    /// Parses a `assignment_statement` grammar element
     /// @returns the parsed assignment or nullptr
-    Maybe<const ast::Statement*> assignment_stmt();
+    Maybe<const ast::Statement*> assignment_statement();
     /// Parses one or more attribute lists.
     /// @return the parsed attribute list, or an empty list on error.
-    Maybe<ast::AttributeList> attribute_list();
+    Maybe<AttributeList> attribute_list();
     /// Parses a single attribute of the following types:
     /// * `struct_attribute`
     /// * `struct_member_attribute`
@@ -813,7 +859,7 @@
 
     /// Reports an error if the attribute list `list` is not empty.
     /// Used to ensure that all attributes are consumed.
-    bool expect_attributes_consumed(ast::AttributeList& list);
+    bool expect_attributes_consumed(utils::VectorRef<const ast::Attribute*> list);
 
     Expect<const ast::Type*> expect_type_decl_pointer(const Token& t);
     Expect<const ast::Type*> expect_type_decl_atomic(const Token& t);
diff --git a/src/tint/reader/wgsl/parser_impl_argument_expression_list_test.cc b/src/tint/reader/wgsl/parser_impl_argument_expression_list_test.cc
index 3042c20..7f1132c 100644
--- a/src/tint/reader/wgsl/parser_impl_argument_expression_list_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_argument_expression_list_test.cc
@@ -23,7 +23,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(e.errored);
 
-    ASSERT_EQ(e.value.size(), 1u);
+    ASSERT_EQ(e.value.Length(), 1u);
     ASSERT_TRUE(e.value[0]->Is<ast::IdentifierExpression>());
 }
 
@@ -33,7 +33,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(e.errored);
 
-    ASSERT_EQ(e.value.size(), 0u);
+    ASSERT_EQ(e.value.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, ArgumentExpressionList_ParsesMultiple) {
@@ -42,7 +42,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(e.errored);
 
-    ASSERT_EQ(e.value.size(), 3u);
+    ASSERT_EQ(e.value.Length(), 3u);
     ASSERT_TRUE(e.value[0]->Is<ast::IdentifierExpression>());
     ASSERT_TRUE(e.value[1]->Is<ast::LiteralExpression>());
     ASSERT_TRUE(e.value[2]->Is<ast::BinaryExpression>());
@@ -54,7 +54,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(e.errored);
 
-    ASSERT_EQ(e.value.size(), 2u);
+    ASSERT_EQ(e.value.Length(), 2u);
     ASSERT_TRUE(e.value[0]->Is<ast::IdentifierExpression>());
     ASSERT_TRUE(e.value[1]->Is<ast::LiteralExpression>());
 }
diff --git a/src/tint/reader/wgsl/parser_impl_assignment_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_assignment_stmt_test.cc
index 4fd23ce..4581d98 100644
--- a/src/tint/reader/wgsl/parser_impl_assignment_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_assignment_stmt_test.cc
@@ -19,7 +19,7 @@
 
 TEST_F(ParserImplTest, AssignmentStmt_Parses_ToVariable) {
     auto p = parser("a = 123");
-    auto e = p->assignment_stmt();
+    auto e = p->assignment_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -42,7 +42,7 @@
 
 TEST_F(ParserImplTest, AssignmentStmt_Parses_ToMember) {
     auto p = parser("a.b.c[2].d = 123");
-    auto e = p->assignment_stmt();
+    auto e = p->assignment_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -92,7 +92,7 @@
 
 TEST_F(ParserImplTest, AssignmentStmt_Parses_ToPhony) {
     auto p = parser("_ = 123i");
-    auto e = p->assignment_stmt();
+    auto e = p->assignment_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -111,9 +111,15 @@
     ASSERT_TRUE(a->lhs->Is<ast::PhonyExpression>());
 }
 
-TEST_F(ParserImplTest, AssignmentStmt_Parses_CompoundOp) {
-    auto p = parser("a += 123u");
-    auto e = p->assignment_stmt();
+struct CompoundData {
+    std::string str;
+    ast::BinaryOp op;
+};
+using CompoundOpTest = ParserImplTestWithParam<CompoundData>;
+TEST_P(CompoundOpTest, CompoundOp) {
+    auto params = GetParam();
+    auto p = parser("a " + params.str + " 123u");
+    auto e = p->assignment_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -123,7 +129,7 @@
     ASSERT_NE(a, nullptr);
     ASSERT_NE(a->lhs, nullptr);
     ASSERT_NE(a->rhs, nullptr);
-    EXPECT_EQ(a->op, ast::BinaryOp::kAdd);
+    EXPECT_EQ(a->op, params.op);
 
     ASSERT_TRUE(a->lhs->Is<ast::IdentifierExpression>());
     auto* ident = a->lhs->As<ast::IdentifierExpression>();
@@ -134,10 +140,22 @@
     EXPECT_EQ(a->rhs->As<ast::IntLiteralExpression>()->suffix,
               ast::IntLiteralExpression::Suffix::kU);
 }
+INSTANTIATE_TEST_SUITE_P(ParserImplTest,
+                         CompoundOpTest,
+                         testing::Values(CompoundData{"+=", ast::BinaryOp::kAdd},
+                                         CompoundData{"-=", ast::BinaryOp::kSubtract},
+                                         CompoundData{"*=", ast::BinaryOp::kMultiply},
+                                         CompoundData{"/=", ast::BinaryOp::kDivide},
+                                         CompoundData{"%=", ast::BinaryOp::kModulo},
+                                         CompoundData{"&=", ast::BinaryOp::kAnd},
+                                         CompoundData{"|=", ast::BinaryOp::kOr},
+                                         CompoundData{"^=", ast::BinaryOp::kXor},
+                                         CompoundData{">>=", ast::BinaryOp::kShiftRight},
+                                         CompoundData{"<<=", ast::BinaryOp::kShiftLeft}));
 
 TEST_F(ParserImplTest, AssignmentStmt_MissingEqual) {
     auto p = parser("a.b.c[2].d 123");
-    auto e = p->assignment_stmt();
+    auto e = p->assignment_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_TRUE(p->has_error());
@@ -147,7 +165,7 @@
 
 TEST_F(ParserImplTest, AssignmentStmt_Compound_MissingEqual) {
     auto p = parser("a + 123");
-    auto e = p->assignment_stmt();
+    auto e = p->assignment_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_TRUE(p->has_error());
@@ -157,7 +175,7 @@
 
 TEST_F(ParserImplTest, AssignmentStmt_InvalidLHS) {
     auto p = parser("if (true) {} = 123");
-    auto e = p->assignment_stmt();
+    auto e = p->assignment_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -166,7 +184,7 @@
 
 TEST_F(ParserImplTest, AssignmentStmt_InvalidRHS) {
     auto p = parser("a.b.c[2].d = if (true) {}");
-    auto e = p->assignment_stmt();
+    auto e = p->assignment_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -176,7 +194,7 @@
 
 TEST_F(ParserImplTest, AssignmentStmt_InvalidCompoundOp) {
     auto p = parser("a &&= true");
-    auto e = p->assignment_stmt();
+    auto e = p->assignment_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
diff --git a/src/tint/reader/wgsl/parser_impl_break_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_break_stmt_test.cc
index ca8802a..21c5267 100644
--- a/src/tint/reader/wgsl/parser_impl_break_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_break_stmt_test.cc
@@ -20,7 +20,7 @@
 
 TEST_F(ParserImplTest, BreakStmt) {
     auto p = parser("break");
-    auto e = p->break_stmt();
+    auto e = p->break_statement();
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(e.value, nullptr);
diff --git a/src/tint/reader/wgsl/parser_impl_call_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_call_stmt_test.cc
index d044f8d..59aeb3c 100644
--- a/src/tint/reader/wgsl/parser_impl_call_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_call_stmt_test.cc
@@ -36,7 +36,7 @@
 
     EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
 
-    EXPECT_EQ(c->args.size(), 0u);
+    EXPECT_EQ(c->args.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, Statement_Call_WithParams) {
@@ -52,7 +52,7 @@
 
     EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
 
-    EXPECT_EQ(c->args.size(), 3u);
+    EXPECT_EQ(c->args.Length(), 3u);
     EXPECT_TRUE(c->args[0]->Is<ast::IntLiteralExpression>());
     EXPECT_TRUE(c->args[1]->Is<ast::IdentifierExpression>());
     EXPECT_TRUE(c->args[2]->Is<ast::BinaryExpression>());
@@ -71,7 +71,7 @@
 
     EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
 
-    EXPECT_EQ(c->args.size(), 2u);
+    EXPECT_EQ(c->args.Length(), 2u);
     EXPECT_TRUE(c->args[0]->Is<ast::IntLiteralExpression>());
     EXPECT_TRUE(c->args[1]->Is<ast::IdentifierExpression>());
 }
diff --git a/src/tint/reader/wgsl/parser_impl_case_body_test.cc b/src/tint/reader/wgsl/parser_impl_case_body_test.cc
index f8c07a3..c162be3 100644
--- a/src/tint/reader/wgsl/parser_impl_case_body_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_case_body_test.cc
@@ -24,7 +24,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(e.errored);
     EXPECT_TRUE(e.matched);
-    EXPECT_EQ(e->statements.size(), 0u);
+    EXPECT_EQ(e->statements.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, CaseBody_Statements) {
@@ -36,7 +36,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(e.errored);
     EXPECT_TRUE(e.matched);
-    ASSERT_EQ(e->statements.size(), 2u);
+    ASSERT_EQ(e->statements.Length(), 2u);
     EXPECT_TRUE(e->statements[0]->Is<ast::VariableDeclStatement>());
     EXPECT_TRUE(e->statements[1]->Is<ast::AssignmentStatement>());
 }
@@ -56,7 +56,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(e.errored);
     EXPECT_TRUE(e.matched);
-    ASSERT_EQ(e->statements.size(), 1u);
+    ASSERT_EQ(e->statements.Length(), 1u);
     EXPECT_TRUE(e->statements[0]->Is<ast::FallthroughStatement>());
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_body_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_compound_stmt_test.cc
similarity index 76%
rename from src/tint/reader/wgsl/parser_impl_body_stmt_test.cc
rename to src/tint/reader/wgsl/parser_impl_compound_stmt_test.cc
index f84a5ba..947cb48 100644
--- a/src/tint/reader/wgsl/parser_impl_body_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_compound_stmt_test.cc
@@ -18,38 +18,38 @@
 namespace tint::reader::wgsl {
 namespace {
 
-TEST_F(ParserImplTest, BodyStmt) {
+TEST_F(ParserImplTest, CompoundStmt) {
     auto p = parser(R"({
   discard;
   return 1 + b / 2;
 })");
-    auto e = p->expect_body_stmt();
+    auto e = p->expect_compound_statement();
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(e.errored);
-    ASSERT_EQ(e->statements.size(), 2u);
+    ASSERT_EQ(e->statements.Length(), 2u);
     EXPECT_TRUE(e->statements[0]->Is<ast::DiscardStatement>());
     EXPECT_TRUE(e->statements[1]->Is<ast::ReturnStatement>());
 }
 
-TEST_F(ParserImplTest, BodyStmt_Empty) {
+TEST_F(ParserImplTest, CompoundStmt_Empty) {
     auto p = parser("{}");
-    auto e = p->expect_body_stmt();
+    auto e = p->expect_compound_statement();
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(e.errored);
-    EXPECT_EQ(e->statements.size(), 0u);
+    EXPECT_EQ(e->statements.Length(), 0u);
 }
 
-TEST_F(ParserImplTest, BodyStmt_InvalidStmt) {
+TEST_F(ParserImplTest, CompoundStmt_InvalidStmt) {
     auto p = parser("{fn main() {}}");
-    auto e = p->expect_body_stmt();
+    auto e = p->expect_compound_statement();
     ASSERT_TRUE(p->has_error());
     ASSERT_TRUE(e.errored);
     EXPECT_EQ(p->error(), "1:2: expected '}'");
 }
 
-TEST_F(ParserImplTest, BodyStmt_MissingRightParen) {
+TEST_F(ParserImplTest, CompoundStmt_MissingRightParen) {
     auto p = parser("{return;");
-    auto e = p->expect_body_stmt();
+    auto e = p->expect_compound_statement();
     ASSERT_TRUE(p->has_error());
     ASSERT_TRUE(e.errored);
     EXPECT_EQ(p->error(), "1:9: expected '}'");
diff --git a/src/tint/reader/wgsl/parser_impl_continue_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_continue_stmt_test.cc
index 002f638..349bf5e 100644
--- a/src/tint/reader/wgsl/parser_impl_continue_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_continue_stmt_test.cc
@@ -20,7 +20,7 @@
 
 TEST_F(ParserImplTest, ContinueStmt) {
     auto p = parser("continue");
-    auto e = p->continue_stmt();
+    auto e = p->continue_statement();
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(e.value, nullptr);
diff --git a/src/tint/reader/wgsl/parser_impl_continuing_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_continuing_stmt_test.cc
index d7a5779..d2113af 100644
--- a/src/tint/reader/wgsl/parser_impl_continuing_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_continuing_stmt_test.cc
@@ -20,17 +20,17 @@
 
 TEST_F(ParserImplTest, ContinuingStmt) {
     auto p = parser("continuing { discard; }");
-    auto e = p->continuing_stmt();
+    auto e = p->continuing_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
-    ASSERT_EQ(e->statements.size(), 1u);
+    ASSERT_EQ(e->statements.Length(), 1u);
     ASSERT_TRUE(e->statements[0]->Is<ast::DiscardStatement>());
 }
 
 TEST_F(ParserImplTest, ContinuingStmt_InvalidBody) {
     auto p = parser("continuing { discard }");
-    auto e = p->continuing_stmt();
+    auto e = p->continuing_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
diff --git a/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc b/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc
index 240ab38..ed57be8 100644
--- a/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc
@@ -28,10 +28,10 @@
     EXPECT_FALSE(p->has_error()) << p->error();
     auto program = p->program();
     auto& ast = program.AST();
-    ASSERT_EQ(ast.Enables().size(), 1u);
+    ASSERT_EQ(ast.Enables().Length(), 1u);
     auto* enable = ast.Enables()[0];
     EXPECT_EQ(enable->extension, ast::Extension::kF16);
-    ASSERT_EQ(ast.GlobalDeclarations().size(), 1u);
+    ASSERT_EQ(ast.GlobalDeclarations().Length(), 1u);
     EXPECT_EQ(ast.GlobalDeclarations()[0], enable);
 }
 
@@ -45,12 +45,12 @@
     EXPECT_FALSE(p->has_error()) << p->error();
     auto program = p->program();
     auto& ast = program.AST();
-    ASSERT_EQ(ast.Enables().size(), 2u);
+    ASSERT_EQ(ast.Enables().Length(), 2u);
     auto* enable_a = ast.Enables()[0];
     auto* enable_b = ast.Enables()[1];
     EXPECT_EQ(enable_a->extension, ast::Extension::kF16);
     EXPECT_EQ(enable_b->extension, ast::Extension::kF16);
-    ASSERT_EQ(ast.GlobalDeclarations().size(), 2u);
+    ASSERT_EQ(ast.GlobalDeclarations().Length(), 2u);
     EXPECT_EQ(ast.GlobalDeclarations()[0], enable_a);
     EXPECT_EQ(ast.GlobalDeclarations()[1], enable_b);
 }
@@ -64,8 +64,8 @@
     EXPECT_EQ(p->error(), "1:8: unsupported extension: 'NotAValidExtensionName'");
     auto program = p->program();
     auto& ast = program.AST();
-    EXPECT_EQ(ast.Enables().size(), 0u);
-    EXPECT_EQ(ast.GlobalDeclarations().size(), 0u);
+    EXPECT_EQ(ast.Enables().Length(), 0u);
+    EXPECT_EQ(ast.GlobalDeclarations().Length(), 0u);
 }
 
 // Test an enable directive missing ending semicolon.
@@ -76,8 +76,8 @@
     EXPECT_EQ(p->error(), "1:11: expected ';' for enable directive");
     auto program = p->program();
     auto& ast = program.AST();
-    EXPECT_EQ(ast.Enables().size(), 0u);
-    EXPECT_EQ(ast.GlobalDeclarations().size(), 0u);
+    EXPECT_EQ(ast.Enables().Length(), 0u);
+    EXPECT_EQ(ast.GlobalDeclarations().Length(), 0u);
 }
 
 // Test the special error message when enable are used with parenthesis.
@@ -88,8 +88,8 @@
     EXPECT_EQ(p->error(), "1:7: enable directives don't take parenthesis");
     auto program = p->program();
     auto& ast = program.AST();
-    EXPECT_EQ(ast.Enables().size(), 0u);
-    EXPECT_EQ(ast.GlobalDeclarations().size(), 0u);
+    EXPECT_EQ(ast.Enables().Length(), 0u);
+    EXPECT_EQ(ast.GlobalDeclarations().Length(), 0u);
 }
 
 // Test using invalid tokens in an enable directive.
@@ -101,8 +101,8 @@
         EXPECT_EQ(p->error(), "1:11: expected ';' for enable directive");
         auto program = p->program();
         auto& ast = program.AST();
-        EXPECT_EQ(ast.Enables().size(), 0u);
-        EXPECT_EQ(ast.GlobalDeclarations().size(), 0u);
+        EXPECT_EQ(ast.Enables().Length(), 0u);
+        EXPECT_EQ(ast.GlobalDeclarations().Length(), 0u);
     }
     {
         auto p = parser("enable <f16;");
@@ -111,8 +111,8 @@
         EXPECT_EQ(p->error(), "1:8: invalid extension name");
         auto program = p->program();
         auto& ast = program.AST();
-        EXPECT_EQ(ast.Enables().size(), 0u);
-        EXPECT_EQ(ast.GlobalDeclarations().size(), 0u);
+        EXPECT_EQ(ast.Enables().Length(), 0u);
+        EXPECT_EQ(ast.GlobalDeclarations().Length(), 0u);
     }
     {
         auto p = parser("enable =;");
@@ -121,8 +121,8 @@
         EXPECT_EQ(p->error(), "1:8: invalid extension name");
         auto program = p->program();
         auto& ast = program.AST();
-        EXPECT_EQ(ast.Enables().size(), 0u);
-        EXPECT_EQ(ast.GlobalDeclarations().size(), 0u);
+        EXPECT_EQ(ast.Enables().Length(), 0u);
+        EXPECT_EQ(ast.GlobalDeclarations().Length(), 0u);
     }
     {
         auto p = parser("enable vec4;");
@@ -131,8 +131,8 @@
         EXPECT_EQ(p->error(), "1:8: invalid extension name");
         auto program = p->program();
         auto& ast = program.AST();
-        EXPECT_EQ(ast.Enables().size(), 0u);
-        EXPECT_EQ(ast.GlobalDeclarations().size(), 0u);
+        EXPECT_EQ(ast.Enables().Length(), 0u);
+        EXPECT_EQ(ast.GlobalDeclarations().Length(), 0u);
     }
 }
 
@@ -148,10 +148,10 @@
     auto program = p->program();
     auto& ast = program.AST();
     // Accept the enable directive although it caused an error
-    ASSERT_EQ(ast.Enables().size(), 1u);
+    ASSERT_EQ(ast.Enables().Length(), 1u);
     auto* enable = ast.Enables()[0];
     EXPECT_EQ(enable->extension, ast::Extension::kF16);
-    ASSERT_EQ(ast.GlobalDeclarations().size(), 2u);
+    ASSERT_EQ(ast.GlobalDeclarations().Length(), 2u);
     EXPECT_EQ(ast.GlobalDeclarations()[1], enable);
 }
 
@@ -168,10 +168,10 @@
     auto program = p->program();
     auto& ast = program.AST();
     // Accept the enable directive although it cause an error
-    ASSERT_EQ(ast.Enables().size(), 1u);
+    ASSERT_EQ(ast.Enables().Length(), 1u);
     auto* enable = ast.Enables()[0];
     EXPECT_EQ(enable->extension, ast::Extension::kF16);
-    ASSERT_EQ(ast.GlobalDeclarations().size(), 1u);
+    ASSERT_EQ(ast.GlobalDeclarations().Length(), 1u);
     EXPECT_EQ(ast.GlobalDeclarations()[0], enable);
 }
 
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 81613b4..85e888d 100644
--- a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
@@ -306,6 +306,51 @@
 )");
 }
 
+TEST_F(ParserImplErrorTest, FunctionDeclStaticAssertMissingCondThenEOF) {
+    EXPECT("fn f() { static_assert }", R"(test.wgsl:1:24 error: unable to parse condition expression
+fn f() { static_assert }
+                       ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclStaticAssertMissingCondThenSemicolon) {
+    EXPECT("fn f() { static_assert; }",
+           R"(test.wgsl:1:23 error: unable to parse condition expression
+fn f() { static_assert; }
+                      ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclStaticAssertMissingCondThenLet) {
+    EXPECT("fn f() { static_assert\nlet x = 0; }",
+           R"(test.wgsl:2:1 error: unable to parse condition expression
+let x = 0; }
+^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclStaticAssertMissingLParen) {
+    EXPECT("fn f() { static_assert true);", R"(test.wgsl:1:28 error: expected ';' for statement
+fn f() { static_assert true);
+                           ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclStaticAssertMissingRParen) {
+    EXPECT("fn f() { static_assert (true;", R"(test.wgsl:1:29 error: expected ')'
+fn f() { static_assert (true;
+                            ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, FunctionDeclStaticAssertMissingSemicolon) {
+    EXPECT("fn f() { static_assert true }",
+           R"(test.wgsl:1:29 error: expected ';' for statement
+fn f() { static_assert true }
+                            ^
+)");
+}
+
 // TODO(crbug.com/tint/1503): Remove this when @stage is removed
 TEST_F(ParserImplErrorTest, FunctionDeclStageMissingLParen) {
     EXPECT("@stage vertex) fn f() {}",
@@ -697,6 +742,50 @@
 )");
 }
 
+TEST_F(ParserImplErrorTest, GlobalDeclStaticAssertMissingCondThenEOF) {
+    EXPECT("static_assert", R"(test.wgsl:1:14 error: unable to parse condition expression
+static_assert
+             ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStaticAssertMissingCondThenSemicolon) {
+    EXPECT("static_assert;", R"(test.wgsl:1:14 error: unable to parse condition expression
+static_assert;
+             ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStaticAssertMissingCondThenAlias) {
+    EXPECT("static_assert\ntype T = i32;",
+           R"(test.wgsl:2:1 error: unable to parse condition expression
+type T = i32;
+^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStaticAssertMissingLParen) {
+    EXPECT("static_assert true);", R"(test.wgsl:1:19 error: expected ';' for static assertion declaration
+static_assert true);
+                  ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStaticAssertMissingRParen) {
+    EXPECT("static_assert (true;", R"(test.wgsl:1:20 error: expected ')'
+static_assert (true;
+                   ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStaticAssertMissingSemicolon) {
+    EXPECT("static_assert true static_assert true;",
+           R"(test.wgsl:1:20 error: expected ';' for static assertion declaration
+static_assert true static_assert true;
+                   ^^^^^^^^^^^^^
+)");
+}
+
 TEST_F(ParserImplErrorTest, GlobalDeclStorageTextureMissingLessThan) {
     EXPECT("var x : texture_storage_2d;",
            R"(test.wgsl:1:27 error: expected '<' for storage texture type
diff --git a/src/tint/reader/wgsl/parser_impl_for_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_for_stmt_test.cc
index 4f6beb0..a8d7873 100644
--- a/src/tint/reader/wgsl/parser_impl_for_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_for_stmt_test.cc
@@ -24,7 +24,7 @@
 // Test an empty for loop.
 TEST_F(ForStmtTest, Empty) {
     auto p = parser("for (;;) { }");
-    auto fl = p->for_stmt();
+    auto fl = p->for_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -37,21 +37,21 @@
 // Test a for loop with non-empty body.
 TEST_F(ForStmtTest, Body) {
     auto p = parser("for (;;) { discard; }");
-    auto fl = p->for_stmt();
+    auto fl = p->for_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
     EXPECT_EQ(fl->initializer, nullptr);
     EXPECT_EQ(fl->condition, nullptr);
     EXPECT_EQ(fl->continuing, nullptr);
-    ASSERT_EQ(fl->body->statements.size(), 1u);
+    ASSERT_EQ(fl->body->statements.Length(), 1u);
     EXPECT_TRUE(fl->body->statements[0]->Is<ast::DiscardStatement>());
 }
 
 // Test a for loop declaring a variable in the initializer statement.
 TEST_F(ForStmtTest, InitializerStatementDecl) {
     auto p = parser("for (var i: i32 ;;) { }");
-    auto fl = p->for_stmt();
+    auto fl = p->for_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -68,7 +68,7 @@
 // statement.
 TEST_F(ForStmtTest, InitializerStatementDeclEqual) {
     auto p = parser("for (var i: i32 = 0 ;;) { }");
-    auto fl = p->for_stmt();
+    auto fl = p->for_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -84,7 +84,7 @@
 // Test a for loop declaring a const variable in the initializer statement.
 TEST_F(ForStmtTest, InitializerStatementConstDecl) {
     auto p = parser("for (let i: i32 = 0 ;;) { }");
-    auto fl = p->for_stmt();
+    auto fl = p->for_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -100,7 +100,7 @@
 // Test a for loop assigning a variable in the initializer statement.
 TEST_F(ForStmtTest, InitializerStatementAssignment) {
     auto p = parser("for (i = 0 ;;) { }");
-    auto fl = p->for_stmt();
+    auto fl = p->for_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -113,7 +113,7 @@
 // Test a for loop incrementing a variable in the initializer statement.
 TEST_F(ForStmtTest, InitializerStatementIncrement) {
     auto p = parser("for (i++;;) { }");
-    auto fl = p->for_stmt();
+    auto fl = p->for_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -126,7 +126,7 @@
 // Test a for loop calling a function in the initializer statement.
 TEST_F(ForStmtTest, InitializerStatementFuncCall) {
     auto p = parser("for (a(b,c) ;;) { }");
-    auto fl = p->for_stmt();
+    auto fl = p->for_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -139,7 +139,7 @@
 // Test a for loop with a break condition
 TEST_F(ForStmtTest, BreakCondition) {
     auto p = parser("for (; 0 == 1;) { }");
-    auto fl = p->for_stmt();
+    auto fl = p->for_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -152,7 +152,7 @@
 // Test a for loop assigning a variable in the continuing statement.
 TEST_F(ForStmtTest, ContinuingAssignment) {
     auto p = parser("for (;; x = 2) { }");
-    auto fl = p->for_stmt();
+    auto fl = p->for_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -165,7 +165,7 @@
 // Test a for loop with an increment statement as the continuing statement.
 TEST_F(ForStmtTest, ContinuingIncrement) {
     auto p = parser("for (;; x++) { }");
-    auto fl = p->for_stmt();
+    auto fl = p->for_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -178,7 +178,7 @@
 // Test a for loop calling a function in the continuing statement.
 TEST_F(ForStmtTest, ContinuingFuncCall) {
     auto p = parser("for (;; a(b,c)) { }");
-    auto fl = p->for_stmt();
+    auto fl = p->for_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -192,7 +192,7 @@
   public:
     void TestForWithError(std::string for_str, std::string error_str) {
         auto p_for = parser(for_str);
-        auto e_for = p_for->for_stmt();
+        auto e_for = p_for->for_statement();
 
         EXPECT_FALSE(e_for.matched);
         EXPECT_TRUE(e_for.errored);
diff --git a/src/tint/reader/wgsl/parser_impl_function_attribute_list_test.cc b/src/tint/reader/wgsl/parser_impl_function_attribute_list_test.cc
index 0773ffd..28b84a9 100644
--- a/src/tint/reader/wgsl/parser_impl_function_attribute_list_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_function_attribute_list_test.cc
@@ -25,7 +25,7 @@
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(attrs.errored);
     EXPECT_TRUE(attrs.matched);
-    ASSERT_EQ(attrs.value.size(), 2u);
+    ASSERT_EQ(attrs.value.Length(), 2u);
 
     auto* attr_0 = attrs.value[0]->As<ast::Attribute>();
     auto* attr_1 = attrs.value[1]->As<ast::Attribute>();
@@ -51,7 +51,7 @@
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(attrs.errored);
     EXPECT_TRUE(attrs.matched);
-    ASSERT_EQ(attrs.value.size(), 2u);
+    ASSERT_EQ(attrs.value.Length(), 2u);
 
     auto* attr_0 = attrs.value[0]->As<ast::Attribute>();
     auto* attr_1 = attrs.value[1]->As<ast::Attribute>();
@@ -77,7 +77,7 @@
     EXPECT_TRUE(p->has_error());
     EXPECT_TRUE(attrs.errored);
     EXPECT_FALSE(attrs.matched);
-    EXPECT_TRUE(attrs.value.empty());
+    EXPECT_TRUE(attrs.value.IsEmpty());
     EXPECT_EQ(p->error(), "1:2: expected attribute");
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_function_decl_test.cc b/src/tint/reader/wgsl/parser_impl_function_decl_test.cc
index 4d7857b..cba4a7d 100644
--- a/src/tint/reader/wgsl/parser_impl_function_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_function_decl_test.cc
@@ -35,7 +35,7 @@
     ASSERT_NE(f->return_type, nullptr);
     EXPECT_TRUE(f->return_type->Is<ast::Void>());
 
-    ASSERT_EQ(f->params.size(), 2u);
+    ASSERT_EQ(f->params.Length(), 2u);
     EXPECT_EQ(f->params[0]->symbol, p->builder().Symbols().Get("a"));
     EXPECT_EQ(f->params[1]->symbol, p->builder().Symbols().Get("b"));
 
@@ -43,7 +43,7 @@
     EXPECT_TRUE(f->return_type->Is<ast::Void>());
 
     auto* body = f->body;
-    ASSERT_EQ(body->statements.size(), 1u);
+    ASSERT_EQ(body->statements.Length(), 1u);
     EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
 }
 
@@ -80,7 +80,7 @@
     ASSERT_NE(f->return_type, nullptr);
     EXPECT_TRUE(f->return_type->Is<ast::Void>());
 
-    ASSERT_EQ(f->params.size(), 2u);
+    ASSERT_EQ(f->params.Length(), 2u);
     EXPECT_EQ(f->params[0]->symbol, p->builder().Symbols().Get(param_a_ident));
     EXPECT_EQ(f->params[1]->symbol, p->builder().Symbols().Get(param_b_ident));
 
@@ -88,7 +88,7 @@
     EXPECT_TRUE(f->return_type->Is<ast::Void>());
 
     auto* body = f->body;
-    ASSERT_EQ(body->statements.size(), 1u);
+    ASSERT_EQ(body->statements.Length(), 1u);
     EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
 }
 
@@ -107,10 +107,10 @@
     EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
     ASSERT_NE(f->return_type, nullptr);
     EXPECT_TRUE(f->return_type->Is<ast::Void>());
-    ASSERT_EQ(f->params.size(), 0u);
+    ASSERT_EQ(f->params.Length(), 0u);
 
     auto& attributes = f->attributes;
-    ASSERT_EQ(attributes.size(), 1u);
+    ASSERT_EQ(attributes.Length(), 1u);
     ASSERT_TRUE(attributes[0]->Is<ast::WorkgroupAttribute>());
 
     auto values = attributes[0]->As<ast::WorkgroupAttribute>()->Values();
@@ -131,7 +131,7 @@
               ast::IntLiteralExpression::Suffix::kNone);
 
     auto* body = f->body;
-    ASSERT_EQ(body->statements.size(), 1u);
+    ASSERT_EQ(body->statements.Length(), 1u);
     EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
 }
 
@@ -152,10 +152,10 @@
     EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
     ASSERT_NE(f->return_type, nullptr);
     EXPECT_TRUE(f->return_type->Is<ast::Void>());
-    ASSERT_EQ(f->params.size(), 0u);
+    ASSERT_EQ(f->params.Length(), 0u);
 
     auto& attributes = f->attributes;
-    ASSERT_EQ(attributes.size(), 2u);
+    ASSERT_EQ(attributes.Length(), 2u);
 
     ASSERT_TRUE(attributes[0]->Is<ast::WorkgroupAttribute>());
     auto values = attributes[0]->As<ast::WorkgroupAttribute>()->Values();
@@ -179,7 +179,7 @@
     EXPECT_EQ(attributes[1]->As<ast::StageAttribute>()->stage, ast::PipelineStage::kCompute);
 
     auto* body = f->body;
-    ASSERT_EQ(body->statements.size(), 1u);
+    ASSERT_EQ(body->statements.Length(), 1u);
     EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
 }
 
@@ -201,10 +201,10 @@
     EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
     ASSERT_NE(f->return_type, nullptr);
     EXPECT_TRUE(f->return_type->Is<ast::Void>());
-    ASSERT_EQ(f->params.size(), 0u);
+    ASSERT_EQ(f->params.Length(), 0u);
 
     auto& attrs = f->attributes;
-    ASSERT_EQ(attrs.size(), 2u);
+    ASSERT_EQ(attrs.Length(), 2u);
 
     ASSERT_TRUE(attrs[0]->Is<ast::WorkgroupAttribute>());
     auto values = attrs[0]->As<ast::WorkgroupAttribute>()->Values();
@@ -228,7 +228,7 @@
     EXPECT_EQ(attrs[1]->As<ast::StageAttribute>()->stage, ast::PipelineStage::kCompute);
 
     auto* body = f->body;
-    ASSERT_EQ(body->statements.size(), 1u);
+    ASSERT_EQ(body->statements.Length(), 1u);
     EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
 }
 
@@ -247,19 +247,19 @@
     EXPECT_EQ(f->symbol, p->builder().Symbols().Get("main"));
     ASSERT_NE(f->return_type, nullptr);
     EXPECT_TRUE(f->return_type->Is<ast::F32>());
-    ASSERT_EQ(f->params.size(), 0u);
+    ASSERT_EQ(f->params.Length(), 0u);
 
     auto& attributes = f->attributes;
-    EXPECT_EQ(attributes.size(), 0u);
+    EXPECT_EQ(attributes.Length(), 0u);
 
     auto& ret_type_attributes = f->return_type_attributes;
-    ASSERT_EQ(ret_type_attributes.size(), 1u);
+    ASSERT_EQ(ret_type_attributes.Length(), 1u);
     auto* loc = ret_type_attributes[0]->As<ast::LocationAttribute>();
     ASSERT_TRUE(loc != nullptr);
     EXPECT_EQ(loc->value, 1u);
 
     auto* body = f->body;
-    ASSERT_EQ(body->statements.size(), 1u);
+    ASSERT_EQ(body->statements.Length(), 1u);
     EXPECT_TRUE(body->statements[0]->Is<ast::ReturnStatement>());
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_function_header_test.cc b/src/tint/reader/wgsl/parser_impl_function_header_test.cc
index 6f2e6cb..1a8704e 100644
--- a/src/tint/reader/wgsl/parser_impl_function_header_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_function_header_test.cc
@@ -25,7 +25,7 @@
     EXPECT_FALSE(f.errored);
 
     EXPECT_EQ(f->name, "main");
-    ASSERT_EQ(f->params.size(), 2u);
+    ASSERT_EQ(f->params.Length(), 2u);
     EXPECT_EQ(f->params[0]->symbol, p->builder().Symbols().Get("a"));
     EXPECT_EQ(f->params[1]->symbol, p->builder().Symbols().Get("b"));
     EXPECT_TRUE(f->return_type->Is<ast::Void>());
@@ -38,7 +38,7 @@
     EXPECT_FALSE(f.errored);
 
     EXPECT_EQ(f->name, "main");
-    ASSERT_EQ(f->params.size(), 1u);
+    ASSERT_EQ(f->params.Length(), 1u);
     EXPECT_EQ(f->params[0]->symbol, p->builder().Symbols().Get("a"));
     EXPECT_TRUE(f->return_type->Is<ast::Void>());
 }
@@ -51,9 +51,9 @@
     EXPECT_FALSE(f.errored);
 
     EXPECT_EQ(f->name, "main");
-    EXPECT_EQ(f->params.size(), 0u);
+    EXPECT_EQ(f->params.Length(), 0u);
     EXPECT_TRUE(f->return_type->Is<ast::F32>());
-    ASSERT_EQ(f->return_type_attributes.size(), 1u);
+    ASSERT_EQ(f->return_type_attributes.Length(), 1u);
     auto* loc = f->return_type_attributes[0]->As<ast::LocationAttribute>();
     ASSERT_TRUE(loc != nullptr);
     EXPECT_EQ(loc->value, 1u);
@@ -67,9 +67,9 @@
     EXPECT_FALSE(f.errored);
 
     EXPECT_EQ(f->name, "main");
-    EXPECT_EQ(f->params.size(), 0u);
+    EXPECT_EQ(f->params.Length(), 0u);
     EXPECT_TRUE(f->return_type->Is<ast::F32>());
-    ASSERT_EQ(f->return_type_attributes.size(), 1u);
+    ASSERT_EQ(f->return_type_attributes.Length(), 1u);
     EXPECT_TRUE(f->return_type_attributes[0]->Is<ast::InvariantAttribute>());
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_global_decl_test.cc b/src/tint/reader/wgsl/parser_impl_global_decl_test.cc
index 5abe58f..d9001c5 100644
--- a/src/tint/reader/wgsl/parser_impl_global_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_global_decl_test.cc
@@ -29,7 +29,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
 
     auto program = p->program();
-    ASSERT_EQ(program.AST().GlobalVariables().size(), 1u);
+    ASSERT_EQ(program.AST().GlobalVariables().Length(), 1u);
 
     auto* v = program.AST().GlobalVariables()[0];
     EXPECT_EQ(v->symbol, program.Symbols().Get("a"));
@@ -42,7 +42,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
 
     auto program = p->program();
-    ASSERT_EQ(program.AST().GlobalVariables().size(), 1u);
+    ASSERT_EQ(program.AST().GlobalVariables().Length(), 1u);
 
     auto* v = program.AST().GlobalVariables()[0];
     EXPECT_EQ(v->symbol, program.Symbols().Get("a"));
@@ -62,7 +62,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
 
     auto program = p->program();
-    ASSERT_EQ(program.AST().GlobalVariables().size(), 1u);
+    ASSERT_EQ(program.AST().GlobalVariables().Length(), 1u);
 
     auto* v = program.AST().GlobalVariables()[0];
     EXPECT_EQ(v->symbol, program.Symbols().Get("a"));
@@ -104,7 +104,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
 
     auto program = p->program();
-    ASSERT_EQ(program.AST().GlobalVariables().size(), 1u);
+    ASSERT_EQ(program.AST().GlobalVariables().Length(), 1u);
 
     auto* v = program.AST().GlobalVariables()[0];
     EXPECT_EQ(v->symbol, program.Symbols().Get("a"));
@@ -137,7 +137,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
 
     auto program = p->program();
-    ASSERT_EQ(program.AST().TypeDecls().size(), 1u);
+    ASSERT_EQ(program.AST().TypeDecls().Length(), 1u);
     ASSERT_TRUE(program.AST().TypeDecls()[0]->Is<ast::Alias>());
     EXPECT_EQ(program.Symbols().NameFor(program.AST().TypeDecls()[0]->As<ast::Alias>()->name), "A");
 }
@@ -152,7 +152,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
 
     auto program = p->program();
-    ASSERT_EQ(program.AST().TypeDecls().size(), 2u);
+    ASSERT_EQ(program.AST().TypeDecls().Length(), 2u);
     ASSERT_TRUE(program.AST().TypeDecls()[0]->Is<ast::Struct>());
     auto* str = program.AST().TypeDecls()[0]->As<ast::Struct>();
     EXPECT_EQ(str->name, program.Symbols().Get("A"));
@@ -178,7 +178,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
 
     auto program = p->program();
-    ASSERT_EQ(program.AST().Functions().size(), 1u);
+    ASSERT_EQ(program.AST().Functions().Length(), 1u);
     EXPECT_EQ(program.Symbols().NameFor(program.AST().Functions()[0]->symbol), "main");
 }
 
@@ -188,7 +188,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
 
     auto program = p->program();
-    ASSERT_EQ(program.AST().Functions().size(), 1u);
+    ASSERT_EQ(program.AST().Functions().Length(), 1u);
     EXPECT_EQ(program.Symbols().NameFor(program.AST().Functions()[0]->symbol), "main");
 }
 
@@ -205,7 +205,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
 
     auto program = p->program();
-    ASSERT_EQ(program.AST().TypeDecls().size(), 1u);
+    ASSERT_EQ(program.AST().TypeDecls().Length(), 1u);
 
     auto* t = program.AST().TypeDecls()[0];
     ASSERT_NE(t, nullptr);
@@ -213,7 +213,7 @@
 
     auto* str = t->As<ast::Struct>();
     EXPECT_EQ(str->name, program.Symbols().Get("A"));
-    EXPECT_EQ(str->members.size(), 2u);
+    EXPECT_EQ(str->members.Length(), 2u);
 }
 
 TEST_F(ParserImplTest, GlobalDecl_Struct_Invalid) {
@@ -233,5 +233,47 @@
     }
 }
 
+TEST_F(ParserImplTest, GlobalDecl_StaticAssert_WithParen) {
+    auto p = parser("static_assert(true);");
+    p->global_decl();
+    ASSERT_FALSE(p->has_error()) << p->error();
+
+    auto program = p->program();
+    ASSERT_EQ(program.AST().StaticAsserts().Length(), 1u);
+    auto* sa = program.AST().StaticAsserts()[0];
+    EXPECT_EQ(sa->source.range.begin.line, 1u);
+    EXPECT_EQ(sa->source.range.begin.column, 1u);
+    EXPECT_EQ(sa->source.range.end.line, 1u);
+    EXPECT_EQ(sa->source.range.end.column, 20u);
+
+    EXPECT_TRUE(sa->condition->Is<ast::BoolLiteralExpression>());
+    EXPECT_EQ(sa->condition->source.range.begin.line, 1u);
+    EXPECT_EQ(sa->condition->source.range.begin.column, 15u);
+    EXPECT_EQ(sa->condition->source.range.end.line, 1u);
+    EXPECT_EQ(sa->condition->source.range.end.column, 19u);
+}
+
+TEST_F(ParserImplTest, GlobalDecl_StaticAssert_WithoutParen) {
+    auto p = parser("static_assert  true;");
+    p->global_decl();
+    ASSERT_FALSE(p->has_error()) << p->error();
+
+    auto program = p->program();
+    ASSERT_EQ(program.AST().StaticAsserts().Length(), 1u);
+    auto* sa = program.AST().StaticAsserts()[0];
+    EXPECT_TRUE(sa->condition->Is<ast::BoolLiteralExpression>());
+
+    EXPECT_EQ(sa->source.range.begin.line, 1u);
+    EXPECT_EQ(sa->source.range.begin.column, 1u);
+    EXPECT_EQ(sa->source.range.end.line, 1u);
+    EXPECT_EQ(sa->source.range.end.column, 20u);
+
+    EXPECT_TRUE(sa->condition->Is<ast::BoolLiteralExpression>());
+    EXPECT_EQ(sa->condition->source.range.begin.line, 1u);
+    EXPECT_EQ(sa->condition->source.range.begin.column, 16u);
+    EXPECT_EQ(sa->condition->source.range.end.line, 1u);
+    EXPECT_EQ(sa->condition->source.range.end.column, 20u);
+}
+
 }  // namespace
 }  // namespace tint::reader::wgsl
diff --git a/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc b/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc
index f671042..85f4702 100644
--- a/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc
@@ -91,7 +91,7 @@
     ASSERT_EQ(var->constructor, nullptr);
 
     auto& attributes = var->attributes;
-    ASSERT_EQ(attributes.size(), 2u);
+    ASSERT_EQ(attributes.Length(), 2u);
     ASSERT_TRUE(attributes[0]->Is<ast::BindingAttribute>());
     ASSERT_TRUE(attributes[1]->Is<ast::GroupAttribute>());
 }
@@ -122,7 +122,7 @@
     ASSERT_EQ(var->constructor, nullptr);
 
     auto& attributes = var->attributes;
-    ASSERT_EQ(attributes.size(), 2u);
+    ASSERT_EQ(attributes.Length(), 2u);
     ASSERT_TRUE(attributes[0]->Is<ast::BindingAttribute>());
     ASSERT_TRUE(attributes[1]->Is<ast::GroupAttribute>());
 }
@@ -152,7 +152,7 @@
     EXPECT_TRUE(e.errored);
     EXPECT_FALSE(e.matched);
     EXPECT_EQ(e.value, nullptr);
-    EXPECT_EQ(p->error(), "1:24: missing initalizer for 'var' declaration");
+    EXPECT_EQ(p->error(), "1:24: missing initializer for 'var' declaration");
 }
 
 TEST_F(ParserImplTest, GlobalVariableDecl_InvalidVariableDecl) {
diff --git a/src/tint/reader/wgsl/parser_impl_if_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_if_stmt_test.cc
index b9e5566..59e5ad3 100644
--- a/src/tint/reader/wgsl/parser_impl_if_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_if_stmt_test.cc
@@ -19,7 +19,7 @@
 
 TEST_F(ParserImplTest, IfStmt) {
     auto p = parser("if a == 4 { a = b; c = d; }");
-    auto e = p->if_stmt();
+    auto e = p->if_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -28,13 +28,13 @@
     ASSERT_TRUE(e->Is<ast::IfStatement>());
     ASSERT_NE(e->condition, nullptr);
     ASSERT_TRUE(e->condition->Is<ast::BinaryExpression>());
-    EXPECT_EQ(e->body->statements.size(), 2u);
+    EXPECT_EQ(e->body->statements.Length(), 2u);
     EXPECT_EQ(e->else_statement, nullptr);
 }
 
 TEST_F(ParserImplTest, IfStmt_WithElse) {
     auto p = parser("if a == 4 { a = b; c = d; } else if(c) { d = 2; } else {}");
-    auto e = p->if_stmt();
+    auto e = p->if_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -43,21 +43,21 @@
     ASSERT_TRUE(e->Is<ast::IfStatement>());
     ASSERT_NE(e->condition, nullptr);
     ASSERT_TRUE(e->condition->Is<ast::BinaryExpression>());
-    EXPECT_EQ(e->body->statements.size(), 2u);
+    EXPECT_EQ(e->body->statements.Length(), 2u);
 
     auto* elseif = As<ast::IfStatement>(e->else_statement);
     ASSERT_NE(elseif, nullptr);
     ASSERT_TRUE(elseif->condition->Is<ast::IdentifierExpression>());
-    EXPECT_EQ(elseif->body->statements.size(), 1u);
+    EXPECT_EQ(elseif->body->statements.Length(), 1u);
 
     auto* el = As<ast::BlockStatement>(elseif->else_statement);
     ASSERT_NE(el, nullptr);
-    EXPECT_EQ(el->statements.size(), 0u);
+    EXPECT_EQ(el->statements.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, IfStmt_WithElse_WithParens) {
     auto p = parser("if(a==4) { a = b; c = d; } else if(c) { d = 2; } else {}");
-    auto e = p->if_stmt();
+    auto e = p->if_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -66,21 +66,21 @@
     ASSERT_TRUE(e->Is<ast::IfStatement>());
     ASSERT_NE(e->condition, nullptr);
     ASSERT_TRUE(e->condition->Is<ast::BinaryExpression>());
-    EXPECT_EQ(e->body->statements.size(), 2u);
+    EXPECT_EQ(e->body->statements.Length(), 2u);
 
     auto* elseif = As<ast::IfStatement>(e->else_statement);
     ASSERT_NE(elseif, nullptr);
     ASSERT_TRUE(elseif->condition->Is<ast::IdentifierExpression>());
-    EXPECT_EQ(elseif->body->statements.size(), 1u);
+    EXPECT_EQ(elseif->body->statements.Length(), 1u);
 
     auto* el = As<ast::BlockStatement>(elseif->else_statement);
     ASSERT_NE(el, nullptr);
-    EXPECT_EQ(el->statements.size(), 0u);
+    EXPECT_EQ(el->statements.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, IfStmt_InvalidCondition) {
     auto p = parser("if a = 3 {}");
-    auto e = p->if_stmt();
+    auto e = p->if_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -90,7 +90,7 @@
 
 TEST_F(ParserImplTest, IfStmt_MissingCondition) {
     auto p = parser("if {}");
-    auto e = p->if_stmt();
+    auto e = p->if_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -100,7 +100,7 @@
 
 TEST_F(ParserImplTest, IfStmt_InvalidBody) {
     auto p = parser("if a { fn main() {}}");
-    auto e = p->if_stmt();
+    auto e = p->if_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -110,7 +110,7 @@
 
 TEST_F(ParserImplTest, IfStmt_MissingBody) {
     auto p = parser("if a");
-    auto e = p->if_stmt();
+    auto e = p->if_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -120,7 +120,7 @@
 
 TEST_F(ParserImplTest, IfStmt_InvalidElseif) {
     auto p = parser("if a {} else if a { fn main() -> a{}}");
-    auto e = p->if_stmt();
+    auto e = p->if_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -130,7 +130,7 @@
 
 TEST_F(ParserImplTest, IfStmt_InvalidElse) {
     auto p = parser("if a {} else { fn main() -> a{}}");
-    auto e = p->if_stmt();
+    auto e = p->if_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
diff --git a/src/tint/reader/wgsl/parser_impl_increment_decrement_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_increment_decrement_stmt_test.cc
index 8041735..d4e52b2 100644
--- a/src/tint/reader/wgsl/parser_impl_increment_decrement_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_increment_decrement_stmt_test.cc
@@ -19,7 +19,7 @@
 
 TEST_F(ParserImplTest, IncrementDecrementStmt_Increment) {
     auto p = parser("a++");
-    auto e = p->assignment_stmt();
+    auto e = p->assignment_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -38,7 +38,7 @@
 
 TEST_F(ParserImplTest, IncrementDecrementStmt_Decrement) {
     auto p = parser("a--");
-    auto e = p->assignment_stmt();
+    auto e = p->assignment_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -57,7 +57,7 @@
 
 TEST_F(ParserImplTest, IncrementDecrementStmt_Parenthesized) {
     auto p = parser("(a)++");
-    auto e = p->assignment_stmt();
+    auto e = p->assignment_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -76,7 +76,7 @@
 
 TEST_F(ParserImplTest, IncrementDecrementStmt_ToMember) {
     auto p = parser("a.b.c[2].d++");
-    auto e = p->assignment_stmt();
+    auto e = p->assignment_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -121,7 +121,7 @@
 
 TEST_F(ParserImplTest, IncrementDecrementStmt_InvalidLHS) {
     auto p = parser("{}++");
-    auto e = p->assignment_stmt();
+    auto e = p->assignment_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
diff --git a/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc
index 20cd4f6..a3e51b4 100644
--- a/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc
@@ -20,59 +20,59 @@
 
 TEST_F(ParserImplTest, LoopStmt_BodyNoContinuing) {
     auto p = parser("loop { discard; }");
-    auto e = p->loop_stmt();
+    auto e = p->loop_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(e.value, nullptr);
 
-    ASSERT_EQ(e->body->statements.size(), 1u);
+    ASSERT_EQ(e->body->statements.Length(), 1u);
     EXPECT_TRUE(e->body->statements[0]->Is<ast::DiscardStatement>());
 
-    EXPECT_EQ(e->continuing->statements.size(), 0u);
+    EXPECT_EQ(e->continuing->statements.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, LoopStmt_BodyWithContinuing) {
     auto p = parser("loop { discard; continuing { discard; }}");
-    auto e = p->loop_stmt();
+    auto e = p->loop_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(e.value, nullptr);
 
-    ASSERT_EQ(e->body->statements.size(), 1u);
+    ASSERT_EQ(e->body->statements.Length(), 1u);
     EXPECT_TRUE(e->body->statements[0]->Is<ast::DiscardStatement>());
 
-    EXPECT_EQ(e->continuing->statements.size(), 1u);
+    EXPECT_EQ(e->continuing->statements.Length(), 1u);
     EXPECT_TRUE(e->continuing->statements[0]->Is<ast::DiscardStatement>());
 }
 
 TEST_F(ParserImplTest, LoopStmt_NoBodyNoContinuing) {
     auto p = parser("loop { }");
-    auto e = p->loop_stmt();
+    auto e = p->loop_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(e.value, nullptr);
-    ASSERT_EQ(e->body->statements.size(), 0u);
-    ASSERT_EQ(e->continuing->statements.size(), 0u);
+    ASSERT_EQ(e->body->statements.Length(), 0u);
+    ASSERT_EQ(e->continuing->statements.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, LoopStmt_NoBodyWithContinuing) {
     auto p = parser("loop { continuing { discard; }}");
-    auto e = p->loop_stmt();
+    auto e = p->loop_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(e.value, nullptr);
-    ASSERT_EQ(e->body->statements.size(), 0u);
-    ASSERT_EQ(e->continuing->statements.size(), 1u);
+    ASSERT_EQ(e->body->statements.Length(), 0u);
+    ASSERT_EQ(e->continuing->statements.Length(), 1u);
     EXPECT_TRUE(e->continuing->statements[0]->Is<ast::DiscardStatement>());
 }
 
 TEST_F(ParserImplTest, LoopStmt_MissingBracketLeft) {
     auto p = parser("loop discard; }");
-    auto e = p->loop_stmt();
+    auto e = p->loop_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -82,7 +82,7 @@
 
 TEST_F(ParserImplTest, LoopStmt_MissingBracketRight) {
     auto p = parser("loop { discard; ");
-    auto e = p->loop_stmt();
+    auto e = p->loop_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -92,7 +92,7 @@
 
 TEST_F(ParserImplTest, LoopStmt_InvalidStatements) {
     auto p = parser("loop { discard }");
-    auto e = p->loop_stmt();
+    auto e = p->loop_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -102,7 +102,7 @@
 
 TEST_F(ParserImplTest, LoopStmt_InvalidContinuing) {
     auto p = parser("loop { continuing { discard }}");
-    auto e = p->loop_stmt();
+    auto e = p->loop_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
diff --git a/src/tint/reader/wgsl/parser_impl_param_list_test.cc b/src/tint/reader/wgsl/parser_impl_param_list_test.cc
index 86a2f68..ce542e4 100644
--- a/src/tint/reader/wgsl/parser_impl_param_list_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_param_list_test.cc
@@ -23,7 +23,7 @@
     auto e = p->expect_param_list();
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(e.errored);
-    EXPECT_EQ(e.value.size(), 1u);
+    EXPECT_EQ(e.value.Length(), 1u);
 
     EXPECT_EQ(e.value[0]->symbol, p->builder().Symbols().Get("a"));
     EXPECT_TRUE(e.value[0]->type->Is<ast::I32>());
@@ -41,7 +41,7 @@
     auto e = p->expect_param_list();
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(e.errored);
-    EXPECT_EQ(e.value.size(), 3u);
+    EXPECT_EQ(e.value.Length(), 3u);
 
     EXPECT_EQ(e.value[0]->symbol, p->builder().Symbols().Get("a"));
     EXPECT_TRUE(e.value[0]->type->Is<ast::I32>());
@@ -78,7 +78,7 @@
     auto e = p->expect_param_list();
     ASSERT_FALSE(p->has_error());
     ASSERT_FALSE(e.errored);
-    EXPECT_EQ(e.value.size(), 0u);
+    EXPECT_EQ(e.value.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, ParamList_TrailingComma) {
@@ -86,7 +86,7 @@
     auto e = p->expect_param_list();
     ASSERT_FALSE(p->has_error());
     ASSERT_FALSE(e.errored);
-    EXPECT_EQ(e.value.size(), 1u);
+    EXPECT_EQ(e.value.Length(), 1u);
 }
 
 TEST_F(ParserImplTest, ParamList_Attributes) {
@@ -95,7 +95,7 @@
     auto e = p->expect_param_list();
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(e.errored);
-    ASSERT_EQ(e.value.size(), 2u);
+    ASSERT_EQ(e.value.Length(), 2u);
 
     EXPECT_EQ(e.value[0]->symbol, p->builder().Symbols().Get("coord"));
     ASSERT_TRUE(e.value[0]->type->Is<ast::Vector>());
@@ -103,7 +103,7 @@
     EXPECT_EQ(e.value[0]->type->As<ast::Vector>()->width, 4u);
     EXPECT_TRUE(e.value[0]->Is<ast::Parameter>());
     auto attrs_0 = e.value[0]->attributes;
-    ASSERT_EQ(attrs_0.size(), 1u);
+    ASSERT_EQ(attrs_0.Length(), 1u);
     EXPECT_TRUE(attrs_0[0]->Is<ast::BuiltinAttribute>());
     EXPECT_EQ(attrs_0[0]->As<ast::BuiltinAttribute>()->builtin, ast::BuiltinValue::kPosition);
 
@@ -116,7 +116,7 @@
     EXPECT_TRUE(e.value[1]->type->Is<ast::F32>());
     EXPECT_TRUE(e.value[1]->Is<ast::Parameter>());
     auto attrs_1 = e.value[1]->attributes;
-    ASSERT_EQ(attrs_1.size(), 1u);
+    ASSERT_EQ(attrs_1.Length(), 1u);
     EXPECT_TRUE(attrs_1[0]->Is<ast::LocationAttribute>());
     EXPECT_EQ(attrs_1[0]->As<ast::LocationAttribute>()->value, 1u);
 
diff --git a/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc b/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc
index 493ce87..82f64c3 100644
--- a/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc
@@ -42,7 +42,7 @@
 
     EXPECT_NE(call->target.type, nullptr);
 
-    ASSERT_EQ(call->args.size(), 4u);
+    ASSERT_EQ(call->args.Length(), 4u);
     const auto& val = call->args;
     ASSERT_TRUE(val[0]->Is<ast::IntLiteralExpression>());
     EXPECT_EQ(val[0]->As<ast::IntLiteralExpression>()->value, 1);
@@ -76,7 +76,7 @@
     ASSERT_TRUE(e->Is<ast::CallExpression>());
     auto* call = e->As<ast::CallExpression>();
 
-    ASSERT_EQ(call->args.size(), 0u);
+    ASSERT_EQ(call->args.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_InvalidTypeDecl) {
@@ -140,7 +140,7 @@
     ASSERT_NE(call->target.name, nullptr);
     EXPECT_EQ(call->target.name->symbol, p->builder().Symbols().Get("S"));
 
-    ASSERT_EQ(call->args.size(), 0u);
+    ASSERT_EQ(call->args.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_StructConstructor_NotEmpty) {
@@ -164,7 +164,7 @@
     ASSERT_NE(call->target.name, nullptr);
     EXPECT_EQ(call->target.name->symbol, p->builder().Symbols().Get("S"));
 
-    ASSERT_EQ(call->args.size(), 2u);
+    ASSERT_EQ(call->args.Length(), 2u);
 
     ASSERT_TRUE(call->args[0]->Is<ast::IntLiteralExpression>());
     EXPECT_EQ(call->args[0]->As<ast::IntLiteralExpression>()->value, 1u);
@@ -239,7 +239,7 @@
     auto* call = e->As<ast::CallExpression>();
 
     ASSERT_TRUE(call->target.type->Is<ast::F32>());
-    ASSERT_EQ(call->args.size(), 1u);
+    ASSERT_EQ(call->args.Length(), 1u);
 
     ASSERT_TRUE(call->args[0]->Is<ast::IntLiteralExpression>());
 }
diff --git a/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc b/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc
index cb61eed..0ad3cc1 100644
--- a/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc
@@ -21,88 +21,232 @@
 TEST_P(ParserImplReservedKeywordTest, Function) {
     auto name = GetParam();
     auto p = parser("fn " + name + "() {}");
-    EXPECT_FALSE(p->Parse());
-    EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:4: '" + name + "' is a reserved keyword");
+    EXPECT_TRUE(p->Parse());
+    EXPECT_FALSE(p->has_error());
+    EXPECT_EQ(p->error(),
+              "1:4: use of deprecated language feature: '" + name + "' is a reserved keyword");
 }
 TEST_P(ParserImplReservedKeywordTest, ModuleConst) {
     auto name = GetParam();
     auto p = parser("const " + name + " : i32 = 1;");
-    EXPECT_FALSE(p->Parse());
-    EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:7: '" + name + "' is a reserved keyword");
+    EXPECT_TRUE(p->Parse());
+    EXPECT_FALSE(p->has_error());
+    EXPECT_EQ(p->error(),
+              "1:7: use of deprecated language feature: '" + name + "' is a reserved keyword");
 }
 TEST_P(ParserImplReservedKeywordTest, ModuleVar) {
     auto name = GetParam();
     auto p = parser("var " + name + " : i32 = 1;");
-    EXPECT_FALSE(p->Parse());
-    EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:5: '" + name + "' is a reserved keyword");
+    EXPECT_TRUE(p->Parse());
+    EXPECT_FALSE(p->has_error());
+    EXPECT_EQ(p->error(),
+              "1:5: use of deprecated language feature: '" + name + "' is a reserved keyword");
 }
 TEST_P(ParserImplReservedKeywordTest, FunctionLet) {
     auto name = GetParam();
     auto p = parser("fn f() { let " + name + " : i32 = 1; }");
-    EXPECT_FALSE(p->Parse());
-    EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:14: '" + name + "' is a reserved keyword");
+    EXPECT_TRUE(p->Parse());
+    EXPECT_FALSE(p->has_error());
+    EXPECT_EQ(p->error(),
+              "1:14: use of deprecated language feature: '" + name + "' is a reserved keyword");
 }
 TEST_P(ParserImplReservedKeywordTest, FunctionVar) {
     auto name = GetParam();
     auto p = parser("fn f() { var " + name + " : i32 = 1; }");
-    EXPECT_FALSE(p->Parse());
-    EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:14: '" + name + "' is a reserved keyword");
+    EXPECT_TRUE(p->Parse());
+    EXPECT_FALSE(p->has_error());
+    EXPECT_EQ(p->error(),
+              "1:14: use of deprecated language feature: '" + name + "' is a reserved keyword");
 }
 TEST_P(ParserImplReservedKeywordTest, FunctionParam) {
     auto name = GetParam();
     auto p = parser("fn f(" + name + " : i32) {}");
-    EXPECT_FALSE(p->Parse());
-    EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:6: '" + name + "' is a reserved keyword");
+    EXPECT_TRUE(p->Parse());
+    EXPECT_FALSE(p->has_error());
+    EXPECT_EQ(p->error(),
+              "1:6: use of deprecated language feature: '" + name + "' is a reserved keyword");
 }
 TEST_P(ParserImplReservedKeywordTest, Struct) {
     auto name = GetParam();
     auto p = parser("struct " + name + " {};");
-    EXPECT_FALSE(p->Parse());
-    EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:8: '" + name + "' is a reserved keyword");
+    EXPECT_TRUE(p->Parse());
+    EXPECT_FALSE(p->has_error());
+    EXPECT_EQ(p->error(),
+              "1:8: use of deprecated language feature: '" + name + "' is a reserved keyword");
 }
 TEST_P(ParserImplReservedKeywordTest, StructMember) {
     auto name = GetParam();
     auto p = parser("struct S { " + name + " : i32, };");
-    EXPECT_FALSE(p->Parse());
-    EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:12: '" + name + "' is a reserved keyword");
+    EXPECT_TRUE(p->Parse());
+    EXPECT_FALSE(p->has_error());
+    EXPECT_EQ(p->error(),
+              "1:12: use of deprecated language feature: '" + name + "' is a reserved keyword");
 }
 TEST_P(ParserImplReservedKeywordTest, Alias) {
     auto name = GetParam();
     auto p = parser("type " + name + " = i32;");
-    EXPECT_FALSE(p->Parse());
-    EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:6: '" + name + "' is a reserved keyword");
+    EXPECT_TRUE(p->Parse());
+    EXPECT_FALSE(p->has_error());
+    EXPECT_EQ(p->error(),
+              "1:6: use of deprecated language feature: '" + name + "' is a reserved keyword");
 }
 INSTANTIATE_TEST_SUITE_P(ParserImplReservedKeywordTest,
                          ParserImplReservedKeywordTest,
-                         testing::Values("asm",
-                                         "bf16",
+                         testing::Values("ComputeShader",
+                                         "DomainShader",
+                                         "GeometryShader",
+                                         "Hullshader",
+                                         "NULL",
+                                         "Self",
+                                         "abstract",
+                                         "active",
+                                         "alignas",
+                                         "alignof",
+                                         "as",
+                                         "asm",
+                                         "asm_fragment",
+                                         "async",
+                                         "attribute",
+                                         "auto",
+                                         "await",
+                                         "become",
+                                         "binding_array",
+                                         "cast",
+                                         "catch",
+                                         "class",
+                                         "co_await",
+                                         "co_return",
+                                         "co_yield",
+                                         "coherent",
+                                         "column_major",
+                                         "common",
+                                         "compile",
+                                         "compile_fragment",
+                                         "concept",
+                                         "const_cast",
+                                         "consteval",
+                                         "constexpr",
+                                         "constinit",
+                                         "crate",
+                                         "debugger",
+                                         "decltype",
+                                         "delete",
+                                         "demote",
+                                         "demote_to_helper",
                                          "do",
+                                         "dynamic_cast",
                                          "enum",
-                                         "f64",
+                                         "explicit",
+                                         "export",
+                                         "extends",
+                                         "extern",
+                                         "external",
+                                         "filter",
+                                         "final",
+                                         "finally",
+                                         "friend",
+                                         "from",
+                                         "fxgroup",
+                                         "get",
+                                         "goto",
+                                         "groupshared",
                                          "handle",
-                                         "i8",
-                                         "i16",
-                                         "i64",
-                                         "mat",
+                                         "highp",
+                                         "impl",
+                                         "implements",
+                                         "import",
+                                         "inline",
+                                         "inout",
+                                         "instanceof",
+                                         "interface",
+                                         "invariant",
+                                         "layout",
+                                         "line",
+                                         "lineadj",
+                                         "lowp",
+                                         "macro",
+                                         "macro_rules",
+                                         "match",
+                                         "mediump",
+                                         "meta",
+                                         "mod",
+                                         "module",
+                                         "move",
+                                         "mut",
+                                         "mutable",
+                                         "namespace",
+                                         "new",
+                                         "nil",
+                                         "noexcept",
+                                         "noinline",
+                                         "nointerpolation",
+                                         "noperspective",
+                                         "null",
+                                         "nullptr",
+                                         "of",
+                                         "operator",
+                                         "package",
+                                         "packoffset",
+                                         "partition",
+                                         "pass",
+                                         "patch",
+                                         "pixelfragment",
+                                         "point",
+                                         "precise",
+                                         "precision",
                                          "premerge",
+                                         "priv",
+                                         "protected",
+                                         "pub",
+                                         "public",
+                                         "readonly",
+                                         "ref",
                                          "regardless",
+                                         "register",
+                                         "reinterpret_cast",
+                                         "requires",
+                                         "resource",
+                                         "restrict",
+                                         "self",
+                                         "set",
+                                         "shared",
+                                         "signed",
+                                         "sizeof",
+                                         "smooth",
+                                         "snorm",
+                                         "static",
+                                         "static_cast",
+                                         "std",
+                                         "subroutine",
+                                         "super",
+                                         "target",
+                                         "template",
+                                         "this",
+                                         "thread_local",
+                                         "throw",
+                                         "trait",
+                                         "try",
                                          "typedef",
-                                         "u8",
-                                         "u16",
-                                         "u64",
+                                         "typeid",
+                                         "typename",
+                                         "typeof",
+                                         "union",
                                          "unless",
+                                         "unorm",
+                                         "unsafe",
+                                         "unsized",
+                                         "use",
                                          "using",
-                                         "vec",
-                                         "void"));
+                                         "varying",
+                                         "virtual",
+                                         "volatile",
+                                         "wgsl",
+                                         "where",
+                                         "with",
+                                         "writeonly",
+                                         "yield"
+
+                                         ));
 
 }  // namespace
 }  // namespace tint::reader::wgsl
diff --git a/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc b/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc
index 4ab185b..a08b45a 100644
--- a/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc
@@ -99,7 +99,7 @@
 
     EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
 
-    EXPECT_EQ(c->args.size(), 0u);
+    EXPECT_EQ(c->args.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, SingularExpression_Call_WithArgs) {
@@ -115,7 +115,7 @@
 
     EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("test"));
 
-    EXPECT_EQ(c->args.size(), 3u);
+    EXPECT_EQ(c->args.Length(), 3u);
     EXPECT_TRUE(c->args[0]->Is<ast::IntLiteralExpression>());
     EXPECT_TRUE(c->args[1]->Is<ast::IdentifierExpression>());
     EXPECT_TRUE(c->args[2]->Is<ast::BinaryExpression>());
@@ -130,7 +130,7 @@
 
     ASSERT_TRUE(e->Is<ast::CallExpression>());
     auto* c = e->As<ast::CallExpression>();
-    EXPECT_EQ(c->args.size(), 1u);
+    EXPECT_EQ(c->args.Length(), 1u);
 }
 
 TEST_F(ParserImplTest, SingularExpression_Call_InvalidArg) {
diff --git a/src/tint/reader/wgsl/parser_impl_statement_test.cc b/src/tint/reader/wgsl/parser_impl_statement_test.cc
index 677daa0..7bb51d3 100644
--- a/src/tint/reader/wgsl/parser_impl_statement_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_statement_test.cc
@@ -272,5 +272,47 @@
     EXPECT_EQ(p->error(), "1:3: expected '}'");
 }
 
+TEST_F(ParserImplTest, Statement_StaticAssert_WithParen) {
+    auto p = parser("static_assert(true);");
+    auto e = p->statement();
+    ASSERT_FALSE(p->has_error()) << p->error();
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+
+    auto* sa = As<ast::StaticAssert>(e.value);
+    ASSERT_NE(sa, nullptr);
+    EXPECT_EQ(sa->source.range.begin.line, 1u);
+    EXPECT_EQ(sa->source.range.begin.column, 1u);
+    EXPECT_EQ(sa->source.range.end.line, 1u);
+    EXPECT_EQ(sa->source.range.end.column, 20u);
+
+    EXPECT_TRUE(sa->condition->Is<ast::BoolLiteralExpression>());
+    EXPECT_EQ(sa->condition->source.range.begin.line, 1u);
+    EXPECT_EQ(sa->condition->source.range.begin.column, 15u);
+    EXPECT_EQ(sa->condition->source.range.end.line, 1u);
+    EXPECT_EQ(sa->condition->source.range.end.column, 19u);
+}
+
+TEST_F(ParserImplTest, Statement_StaticAssert_WithoutParen) {
+    auto p = parser("static_assert  true;");
+    auto e = p->statement();
+    ASSERT_FALSE(p->has_error()) << p->error();
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+
+    auto* sa = As<ast::StaticAssert>(e.value);
+    ASSERT_NE(sa, nullptr);
+    EXPECT_EQ(sa->source.range.begin.line, 1u);
+    EXPECT_EQ(sa->source.range.begin.column, 1u);
+    EXPECT_EQ(sa->source.range.end.line, 1u);
+    EXPECT_EQ(sa->source.range.end.column, 20u);
+
+    EXPECT_TRUE(sa->condition->Is<ast::BoolLiteralExpression>());
+    EXPECT_EQ(sa->condition->source.range.begin.line, 1u);
+    EXPECT_EQ(sa->condition->source.range.begin.column, 16u);
+    EXPECT_EQ(sa->condition->source.range.end.line, 1u);
+    EXPECT_EQ(sa->condition->source.range.end.column, 20u);
+}
+
 }  // namespace
 }  // namespace tint::reader::wgsl
diff --git a/src/tint/reader/wgsl/parser_impl_statements_test.cc b/src/tint/reader/wgsl/parser_impl_statements_test.cc
index b96a28a..c631f7a 100644
--- a/src/tint/reader/wgsl/parser_impl_statements_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_statements_test.cc
@@ -23,7 +23,7 @@
     auto e = p->expect_statements();
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
-    ASSERT_EQ(e->size(), 2u);
+    ASSERT_EQ(e->Length(), 2u);
     EXPECT_TRUE(e.value[0]->Is<ast::DiscardStatement>());
     EXPECT_TRUE(e.value[1]->Is<ast::ReturnStatement>());
 }
@@ -33,7 +33,7 @@
     auto e = p->expect_statements();
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
-    ASSERT_EQ(e->size(), 0u);
+    ASSERT_EQ(e->Length(), 0u);
 }
 
 }  // namespace
diff --git a/src/tint/reader/wgsl/parser_impl_storage_class_test.cc b/src/tint/reader/wgsl/parser_impl_storage_class_test.cc
index c40752a..c6ef7eb 100644
--- a/src/tint/reader/wgsl/parser_impl_storage_class_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_storage_class_test.cc
@@ -32,7 +32,7 @@
     auto params = GetParam();
     auto p = parser(params.input);
 
-    auto sc = p->expect_storage_class("test");
+    auto sc = p->expect_address_space("test");
     EXPECT_FALSE(sc.errored);
     EXPECT_FALSE(p->has_error());
     EXPECT_EQ(sc.value, params.result);
@@ -51,7 +51,7 @@
 
 TEST_F(ParserImplTest, StorageClass_NoMatch) {
     auto p = parser("not-a-storage-class");
-    auto sc = p->expect_storage_class("test");
+    auto sc = p->expect_address_space("test");
     EXPECT_EQ(sc.errored, true);
     EXPECT_TRUE(p->has_error());
     EXPECT_EQ(p->error(), "1:1: invalid storage class for test");
diff --git a/src/tint/reader/wgsl/parser_impl_struct_attribute_decl_test.cc b/src/tint/reader/wgsl/parser_impl_struct_attribute_decl_test.cc
index a22abee..e015033 100644
--- a/src/tint/reader/wgsl/parser_impl_struct_attribute_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_struct_attribute_decl_test.cc
@@ -24,7 +24,7 @@
     EXPECT_FALSE(p->has_error());
     EXPECT_FALSE(attrs.errored);
     EXPECT_TRUE(attrs.matched);
-    ASSERT_EQ(attrs.value.size(), 1u);
+    ASSERT_EQ(attrs.value.Length(), 1u);
     auto* invariant = attrs.value[0]->As<ast::Attribute>();
     EXPECT_TRUE(invariant->Is<ast::InvariantAttribute>());
 }
@@ -35,7 +35,7 @@
     EXPECT_TRUE(p->has_error());
     EXPECT_TRUE(attrs.errored);
     EXPECT_FALSE(attrs.matched);
-    EXPECT_TRUE(attrs.value.empty());
+    EXPECT_TRUE(attrs.value.IsEmpty());
     EXPECT_EQ(p->error(), "1:11: expected '(' for location attribute");
 }
 
@@ -45,7 +45,7 @@
     EXPECT_TRUE(p->has_error());
     EXPECT_TRUE(attrs.errored);
     EXPECT_FALSE(attrs.matched);
-    EXPECT_TRUE(attrs.value.empty());
+    EXPECT_TRUE(attrs.value.IsEmpty());
     EXPECT_EQ(p->error(), "1:11: expected signed integer literal for location attribute");
 }
 
@@ -55,7 +55,7 @@
     EXPECT_TRUE(p->has_error());
     EXPECT_TRUE(attrs.errored);
     EXPECT_FALSE(attrs.matched);
-    EXPECT_TRUE(attrs.value.empty());
+    EXPECT_TRUE(attrs.value.IsEmpty());
     EXPECT_EQ(p->error(), "1:12: expected ')' for location attribute");
 }
 
@@ -65,7 +65,7 @@
     EXPECT_TRUE(p->has_error());
     EXPECT_TRUE(attrs.errored);
     EXPECT_FALSE(attrs.matched);
-    EXPECT_TRUE(attrs.value.empty());
+    EXPECT_TRUE(attrs.value.IsEmpty());
 }
 
 }  // namespace
diff --git a/src/tint/reader/wgsl/parser_impl_struct_body_decl_test.cc b/src/tint/reader/wgsl/parser_impl_struct_body_decl_test.cc
index b02fb5a..3499b5c 100644
--- a/src/tint/reader/wgsl/parser_impl_struct_body_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_struct_body_decl_test.cc
@@ -25,12 +25,12 @@
     auto m = p->expect_struct_body_decl();
     ASSERT_FALSE(p->has_error());
     ASSERT_FALSE(m.errored);
-    ASSERT_EQ(m.value.size(), 1u);
+    ASSERT_EQ(m.value.Length(), 1u);
 
     const auto* mem = m.value[0];
     EXPECT_EQ(mem->symbol, builder.Symbols().Get("a"));
     EXPECT_TRUE(mem->type->Is<ast::I32>());
-    EXPECT_EQ(mem->attributes.size(), 0u);
+    EXPECT_EQ(mem->attributes.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, StructBodyDecl_Parses_TrailingComma) {
@@ -41,12 +41,12 @@
     auto m = p->expect_struct_body_decl();
     ASSERT_FALSE(p->has_error());
     ASSERT_FALSE(m.errored);
-    ASSERT_EQ(m.value.size(), 1u);
+    ASSERT_EQ(m.value.Length(), 1u);
 
     const auto* mem = m.value[0];
     EXPECT_EQ(mem->symbol, builder.Symbols().Get("a"));
     EXPECT_TRUE(mem->type->Is<ast::I32>());
-    EXPECT_EQ(mem->attributes.size(), 0u);
+    EXPECT_EQ(mem->attributes.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, StructBodyDecl_ParsesEmpty) {
@@ -54,7 +54,7 @@
     auto m = p->expect_struct_body_decl();
     ASSERT_FALSE(p->has_error());
     ASSERT_FALSE(m.errored);
-    ASSERT_EQ(m.value.size(), 0u);
+    ASSERT_EQ(m.value.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, StructBodyDecl_InvalidAlign) {
diff --git a/src/tint/reader/wgsl/parser_impl_struct_decl_test.cc b/src/tint/reader/wgsl/parser_impl_struct_decl_test.cc
index 1c6d6bc..dbb9ca6 100644
--- a/src/tint/reader/wgsl/parser_impl_struct_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_struct_decl_test.cc
@@ -30,7 +30,7 @@
     EXPECT_TRUE(s.matched);
     ASSERT_NE(s.value, nullptr);
     ASSERT_EQ(s->name, p->builder().Symbols().Register("S"));
-    ASSERT_EQ(s->members.size(), 2u);
+    ASSERT_EQ(s->members.Length(), 2u);
     EXPECT_EQ(s->members[0]->symbol, p->builder().Symbols().Register("a"));
     EXPECT_EQ(s->members[1]->symbol, p->builder().Symbols().Register("b"));
 }
@@ -64,7 +64,7 @@
     EXPECT_TRUE(s.matched);
     ASSERT_NE(s.value, nullptr);
     ASSERT_EQ(s->name, p->builder().Symbols().Register(struct_ident));
-    ASSERT_EQ(s->members.size(), 2u);
+    ASSERT_EQ(s->members.Length(), 2u);
     EXPECT_EQ(s->members[0]->symbol, p->builder().Symbols().Register(member_a_ident));
     EXPECT_EQ(s->members[1]->symbol, p->builder().Symbols().Register(member_b_ident));
 }
@@ -77,7 +77,7 @@
     EXPECT_FALSE(s.errored);
     EXPECT_TRUE(s.matched);
     ASSERT_NE(s.value, nullptr);
-    ASSERT_EQ(s->members.size(), 0u);
+    ASSERT_EQ(s->members.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, StructDecl_MissingIdent) {
diff --git a/src/tint/reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc b/src/tint/reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc
index 2695074..09d5274 100644
--- a/src/tint/reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc
@@ -23,7 +23,7 @@
     EXPECT_FALSE(p->has_error());
     EXPECT_FALSE(attrs.errored);
     EXPECT_FALSE(attrs.matched);
-    EXPECT_EQ(attrs.value.size(), 0u);
+    EXPECT_EQ(attrs.value.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, AttributeDecl_Single) {
@@ -32,7 +32,7 @@
     EXPECT_FALSE(p->has_error());
     EXPECT_FALSE(attrs.errored);
     EXPECT_TRUE(attrs.matched);
-    ASSERT_EQ(attrs.value.size(), 1u);
+    ASSERT_EQ(attrs.value.Length(), 1u);
     auto* attr = attrs.value[0]->As<ast::Attribute>();
     ASSERT_NE(attr, nullptr);
     EXPECT_TRUE(attr->Is<ast::StructMemberSizeAttribute>());
diff --git a/src/tint/reader/wgsl/parser_impl_struct_member_test.cc b/src/tint/reader/wgsl/parser_impl_struct_member_test.cc
index 3e8b60e..d2ab916 100644
--- a/src/tint/reader/wgsl/parser_impl_struct_member_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_struct_member_test.cc
@@ -29,7 +29,7 @@
 
     EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
     EXPECT_TRUE(m->type->Is<ast::I32>());
-    EXPECT_EQ(m->attributes.size(), 0u);
+    EXPECT_EQ(m->attributes.Length(), 0u);
 
     EXPECT_EQ(m->source.range, (Source::Range{{1u, 1u}, {1u, 2u}}));
     EXPECT_EQ(m->type->source.range, (Source::Range{{1u, 5u}, {1u, 8u}}));
@@ -47,7 +47,7 @@
 
     EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
     EXPECT_TRUE(m->type->Is<ast::I32>());
-    EXPECT_EQ(m->attributes.size(), 1u);
+    EXPECT_EQ(m->attributes.Length(), 1u);
     EXPECT_TRUE(m->attributes[0]->Is<ast::StructMemberAlignAttribute>());
     EXPECT_EQ(m->attributes[0]->As<ast::StructMemberAlignAttribute>()->align, 2u);
 
@@ -67,7 +67,7 @@
 
     EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
     EXPECT_TRUE(m->type->Is<ast::I32>());
-    EXPECT_EQ(m->attributes.size(), 1u);
+    EXPECT_EQ(m->attributes.Length(), 1u);
     EXPECT_TRUE(m->attributes[0]->Is<ast::StructMemberSizeAttribute>());
     EXPECT_EQ(m->attributes[0]->As<ast::StructMemberSizeAttribute>()->size, 2u);
 
@@ -88,7 +88,7 @@
 
     EXPECT_EQ(m->symbol, builder.Symbols().Get("a"));
     EXPECT_TRUE(m->type->Is<ast::I32>());
-    EXPECT_EQ(m->attributes.size(), 2u);
+    EXPECT_EQ(m->attributes.Length(), 2u);
     EXPECT_TRUE(m->attributes[0]->Is<ast::StructMemberSizeAttribute>());
     EXPECT_EQ(m->attributes[0]->As<ast::StructMemberSizeAttribute>()->size, 2u);
     EXPECT_TRUE(m->attributes[1]->Is<ast::StructMemberAlignAttribute>());
diff --git a/src/tint/reader/wgsl/parser_impl_switch_body_test.cc b/src/tint/reader/wgsl/parser_impl_switch_body_test.cc
index 0f7384d..076b1c3 100644
--- a/src/tint/reader/wgsl/parser_impl_switch_body_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_switch_body_test.cc
@@ -27,10 +27,10 @@
     ASSERT_TRUE(e->Is<ast::CaseStatement>());
     EXPECT_FALSE(e->IsDefault());
     auto* stmt = e->As<ast::CaseStatement>();
-    ASSERT_EQ(stmt->selectors.size(), 1u);
+    ASSERT_EQ(stmt->selectors.Length(), 1u);
     EXPECT_EQ(stmt->selectors[0]->value, 1);
     EXPECT_EQ(stmt->selectors[0]->suffix, ast::IntLiteralExpression::Suffix::kNone);
-    ASSERT_EQ(e->body->statements.size(), 1u);
+    ASSERT_EQ(e->body->statements.Length(), 1u);
     EXPECT_TRUE(e->body->statements[0]->Is<ast::AssignmentStatement>());
 }
 
@@ -44,10 +44,10 @@
     ASSERT_TRUE(e->Is<ast::CaseStatement>());
     EXPECT_FALSE(e->IsDefault());
     auto* stmt = e->As<ast::CaseStatement>();
-    ASSERT_EQ(stmt->selectors.size(), 1u);
+    ASSERT_EQ(stmt->selectors.Length(), 1u);
     EXPECT_EQ(stmt->selectors[0]->value, 1);
     EXPECT_EQ(stmt->selectors[0]->suffix, ast::IntLiteralExpression::Suffix::kNone);
-    ASSERT_EQ(e->body->statements.size(), 1u);
+    ASSERT_EQ(e->body->statements.Length(), 1u);
     EXPECT_TRUE(e->body->statements[0]->Is<ast::AssignmentStatement>());
 }
 
@@ -61,7 +61,7 @@
     ASSERT_TRUE(e->Is<ast::CaseStatement>());
     EXPECT_FALSE(e->IsDefault());
     auto* stmt = e->As<ast::CaseStatement>();
-    ASSERT_EQ(stmt->selectors.size(), 2u);
+    ASSERT_EQ(stmt->selectors.Length(), 2u);
     EXPECT_EQ(stmt->selectors[0]->value, 1);
     EXPECT_EQ(stmt->selectors[0]->suffix, ast::IntLiteralExpression::Suffix::kNone);
     EXPECT_EQ(stmt->selectors[1]->value, 2);
@@ -77,7 +77,7 @@
     ASSERT_TRUE(e->Is<ast::CaseStatement>());
     EXPECT_FALSE(e->IsDefault());
     auto* stmt = e->As<ast::CaseStatement>();
-    ASSERT_EQ(stmt->selectors.size(), 2u);
+    ASSERT_EQ(stmt->selectors.Length(), 2u);
     EXPECT_EQ(stmt->selectors[0]->value, 1);
     EXPECT_EQ(stmt->selectors[0]->suffix, ast::IntLiteralExpression::Suffix::kNone);
     EXPECT_EQ(stmt->selectors[1]->value, 2);
@@ -162,8 +162,8 @@
     ASSERT_NE(e.value, nullptr);
     ASSERT_TRUE(e->Is<ast::CaseStatement>());
     EXPECT_FALSE(e->IsDefault());
-    ASSERT_EQ(e->body->statements.size(), 0u);
-    ASSERT_EQ(e->selectors.size(), 2u);
+    ASSERT_EQ(e->body->statements.Length(), 0u);
+    ASSERT_EQ(e->selectors.Length(), 2u);
     ASSERT_EQ(e->selectors[0]->value, 1);
     EXPECT_EQ(e->selectors[0]->suffix, ast::IntLiteralExpression::Suffix::kNone);
     ASSERT_EQ(e->selectors[1]->value, 2);
@@ -179,8 +179,8 @@
     ASSERT_NE(e.value, nullptr);
     ASSERT_TRUE(e->Is<ast::CaseStatement>());
     EXPECT_FALSE(e->IsDefault());
-    ASSERT_EQ(e->body->statements.size(), 0u);
-    ASSERT_EQ(e->selectors.size(), 2u);
+    ASSERT_EQ(e->body->statements.Length(), 0u);
+    ASSERT_EQ(e->selectors.Length(), 2u);
     ASSERT_EQ(e->selectors[0]->value, 1);
     EXPECT_EQ(e->selectors[0]->suffix, ast::IntLiteralExpression::Suffix::kNone);
     ASSERT_EQ(e->selectors[1]->value, 2);
@@ -216,7 +216,7 @@
     ASSERT_NE(e.value, nullptr);
     ASSERT_TRUE(e->Is<ast::CaseStatement>());
     EXPECT_TRUE(e->IsDefault());
-    ASSERT_EQ(e->body->statements.size(), 1u);
+    ASSERT_EQ(e->body->statements.Length(), 1u);
     EXPECT_TRUE(e->body->statements[0]->Is<ast::AssignmentStatement>());
 }
 
@@ -229,7 +229,7 @@
     ASSERT_NE(e.value, nullptr);
     ASSERT_TRUE(e->Is<ast::CaseStatement>());
     EXPECT_TRUE(e->IsDefault());
-    ASSERT_EQ(e->body->statements.size(), 1u);
+    ASSERT_EQ(e->body->statements.Length(), 1u);
     EXPECT_TRUE(e->body->statements[0]->Is<ast::AssignmentStatement>());
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_switch_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_switch_stmt_test.cc
index c898b12..014d850 100644
--- a/src/tint/reader/wgsl/parser_impl_switch_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_switch_stmt_test.cc
@@ -22,26 +22,26 @@
   case 1: {}
   case 2: {}
 })");
-    auto e = p->switch_stmt();
+    auto e = p->switch_statement();
     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::SwitchStatement>());
-    ASSERT_EQ(e->body.size(), 2u);
+    ASSERT_EQ(e->body.Length(), 2u);
     EXPECT_FALSE(e->body[0]->IsDefault());
     EXPECT_FALSE(e->body[1]->IsDefault());
 }
 
 TEST_F(ParserImplTest, SwitchStmt_Empty) {
     auto p = parser("switch a { }");
-    auto e = p->switch_stmt();
+    auto e = p->switch_statement();
     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::SwitchStatement>());
-    ASSERT_EQ(e->body.size(), 0u);
+    ASSERT_EQ(e->body.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, SwitchStmt_DefaultInMiddle) {
@@ -50,14 +50,14 @@
   default: {}
   case 2: {}
 })");
-    auto e = p->switch_stmt();
+    auto e = p->switch_statement();
     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::SwitchStatement>());
 
-    ASSERT_EQ(e->body.size(), 3u);
+    ASSERT_EQ(e->body.Length(), 3u);
     ASSERT_FALSE(e->body[0]->IsDefault());
     ASSERT_TRUE(e->body[1]->IsDefault());
     ASSERT_FALSE(e->body[2]->IsDefault());
@@ -65,18 +65,18 @@
 
 TEST_F(ParserImplTest, SwitchStmt_WithParens) {
     auto p = parser("switch(a+b) { }");
-    auto e = p->switch_stmt();
+    auto e = p->switch_statement();
     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::SwitchStatement>());
-    ASSERT_EQ(e->body.size(), 0u);
+    ASSERT_EQ(e->body.Length(), 0u);
 }
 
 TEST_F(ParserImplTest, SwitchStmt_InvalidExpression) {
     auto p = parser("switch a=b {}");
-    auto e = p->switch_stmt();
+    auto e = p->switch_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -86,7 +86,7 @@
 
 TEST_F(ParserImplTest, SwitchStmt_MissingExpression) {
     auto p = parser("switch {}");
-    auto e = p->switch_stmt();
+    auto e = p->switch_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -96,7 +96,7 @@
 
 TEST_F(ParserImplTest, SwitchStmt_MissingBracketLeft) {
     auto p = parser("switch a }");
-    auto e = p->switch_stmt();
+    auto e = p->switch_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -106,7 +106,7 @@
 
 TEST_F(ParserImplTest, SwitchStmt_MissingBracketRight) {
     auto p = parser("switch a {");
-    auto e = p->switch_stmt();
+    auto e = p->switch_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -118,7 +118,7 @@
     auto p = parser(R"(switch a {
   case: {}
 })");
-    auto e = p->switch_stmt();
+    auto e = p->switch_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
diff --git a/src/tint/reader/wgsl/parser_impl_test.cc b/src/tint/reader/wgsl/parser_impl_test.cc
index 99ca25d..c234811 100644
--- a/src/tint/reader/wgsl/parser_impl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_test.cc
@@ -32,7 +32,7 @@
     ASSERT_TRUE(p->Parse()) << p->error();
 
     Program program = p->program();
-    ASSERT_EQ(1u, program.AST().Functions().size());
+    ASSERT_EQ(1u, program.AST().Functions().Length());
 }
 
 TEST_F(ParserImplTest, Parses_ExtraSemicolons) {
@@ -52,8 +52,8 @@
     ASSERT_TRUE(p->Parse()) << p->error();
 
     Program program = p->program();
-    ASSERT_EQ(1u, program.AST().Functions().size());
-    ASSERT_EQ(1u, program.AST().TypeDecls().size());
+    ASSERT_EQ(1u, program.AST().Functions().Length());
+    ASSERT_EQ(1u, program.AST().TypeDecls().Length());
 }
 
 TEST_F(ParserImplTest, HandlesError) {
@@ -121,7 +121,7 @@
 }/* block comments are OK at EOF...*/)");
 
     ASSERT_TRUE(p->Parse()) << p->error();
-    ASSERT_EQ(1u, p->program().AST().Functions().size());
+    ASSERT_EQ(1u, p->program().AST().Functions().Length());
 }
 
 TEST_F(ParserImplTest, Comments_UnterminatedBlockComment) {
diff --git a/src/tint/reader/wgsl/parser_impl_type_alias_test.cc b/src/tint/reader/wgsl/parser_impl_type_alias_test.cc
index ea5cbe2..c9bb72c 100644
--- a/src/tint/reader/wgsl/parser_impl_type_alias_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_type_alias_test.cc
@@ -20,7 +20,7 @@
 TEST_F(ParserImplTest, TypeDecl_ParsesType) {
     auto p = parser("type a = i32");
 
-    auto t = p->type_alias();
+    auto t = p->type_alias_decl();
     EXPECT_FALSE(p->has_error());
     EXPECT_FALSE(t.errored);
     EXPECT_TRUE(t.matched);
@@ -35,7 +35,7 @@
 TEST_F(ParserImplTest, TypeDecl_Parses_Ident) {
     auto p = parser("type a = B");
 
-    auto t = p->type_alias();
+    auto t = p->type_alias_decl();
     EXPECT_FALSE(p->has_error());
     EXPECT_FALSE(t.errored);
     EXPECT_TRUE(t.matched);
@@ -54,7 +54,7 @@
 
     auto p = parser("type " + ident + " = i32");
 
-    auto t = p->type_alias();
+    auto t = p->type_alias_decl();
     EXPECT_FALSE(p->has_error());
     EXPECT_FALSE(t.errored);
     EXPECT_TRUE(t.matched);
@@ -68,7 +68,7 @@
 
 TEST_F(ParserImplTest, TypeDecl_MissingIdent) {
     auto p = parser("type = i32");
-    auto t = p->type_alias();
+    auto t = p->type_alias_decl();
     EXPECT_TRUE(t.errored);
     EXPECT_FALSE(t.matched);
     EXPECT_TRUE(p->has_error());
@@ -78,7 +78,7 @@
 
 TEST_F(ParserImplTest, TypeDecl_InvalidIdent) {
     auto p = parser("type 123 = i32");
-    auto t = p->type_alias();
+    auto t = p->type_alias_decl();
     EXPECT_TRUE(t.errored);
     EXPECT_FALSE(t.matched);
     EXPECT_TRUE(p->has_error());
@@ -88,7 +88,7 @@
 
 TEST_F(ParserImplTest, TypeDecl_MissingEqual) {
     auto p = parser("type a i32");
-    auto t = p->type_alias();
+    auto t = p->type_alias_decl();
     EXPECT_TRUE(t.errored);
     EXPECT_FALSE(t.matched);
     EXPECT_TRUE(p->has_error());
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 880f87a..1cf66c0 100644
--- a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
@@ -399,7 +399,7 @@
     auto* a = t.value->As<ast::Array>();
     ASSERT_FALSE(a->IsRuntimeArray());
     ASSERT_TRUE(a->type->Is<ast::F32>());
-    EXPECT_EQ(a->attributes.size(), 0u);
+    EXPECT_EQ(a->attributes.Length(), 0u);
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 14u}}));
 
     auto* size = a->count->As<ast::IntLiteralExpression>();
@@ -420,7 +420,7 @@
     auto* a = t.value->As<ast::Array>();
     ASSERT_FALSE(a->IsRuntimeArray());
     ASSERT_TRUE(a->type->Is<ast::F32>());
-    EXPECT_EQ(a->attributes.size(), 0u);
+    EXPECT_EQ(a->attributes.Length(), 0u);
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 15u}}));
 
     auto* size = a->count->As<ast::IntLiteralExpression>();
@@ -441,7 +441,7 @@
     auto* a = t.value->As<ast::Array>();
     ASSERT_FALSE(a->IsRuntimeArray());
     ASSERT_TRUE(a->type->Is<ast::F32>());
-    EXPECT_EQ(a->attributes.size(), 0u);
+    EXPECT_EQ(a->attributes.Length(), 0u);
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 15u}}));
 
     auto* size = a->count->As<ast::IntLiteralExpression>();
@@ -461,7 +461,7 @@
     auto* a = t.value->As<ast::Array>();
     ASSERT_FALSE(a->IsRuntimeArray());
     ASSERT_TRUE(a->type->Is<ast::F32>());
-    EXPECT_EQ(a->attributes.size(), 0u);
+    EXPECT_EQ(a->attributes.Length(), 0u);
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
 
     auto* count_expr = a->count->As<ast::IdentifierExpression>();
diff --git a/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc b/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc
index e1da7b7..2745e5f 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc
@@ -23,7 +23,7 @@
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(attrs.errored);
     ASSERT_TRUE(attrs.matched);
-    ASSERT_EQ(attrs.value.size(), 2u);
+    ASSERT_EQ(attrs.value.Length(), 2u);
 
     auto* attr_0 = attrs.value[0]->As<ast::Attribute>();
     auto* attr_1 = attrs.value[1]->As<ast::Attribute>();
@@ -42,7 +42,7 @@
     EXPECT_TRUE(p->has_error());
     EXPECT_TRUE(attrs.errored);
     EXPECT_FALSE(attrs.matched);
-    EXPECT_TRUE(attrs.value.empty());
+    EXPECT_TRUE(attrs.value.IsEmpty());
     EXPECT_EQ(p->error(), R"(1:2: expected attribute)");
 }
 
@@ -52,7 +52,7 @@
     EXPECT_TRUE(p->has_error());
     EXPECT_TRUE(attrs.errored);
     EXPECT_FALSE(attrs.matched);
-    EXPECT_TRUE(attrs.value.empty());
+    EXPECT_TRUE(attrs.value.IsEmpty());
     EXPECT_EQ(p->error(), "1:10: invalid value for builtin attribute");
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
index f338f62..ef77891 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
@@ -235,7 +235,7 @@
     EXPECT_TRUE(attr.errored);
     EXPECT_EQ(attr.value, nullptr);
     EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:18: invalid interpolation sampling");
+    EXPECT_EQ(p->error(), "1:18: expected identifier for interpolation sample name");
 }
 
 TEST_F(ParserImplTest, Attribute_Interpolate_Perspective_Center) {
@@ -329,7 +329,7 @@
     EXPECT_TRUE(attr.errored);
     EXPECT_EQ(attr.value, nullptr);
     EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:13: invalid interpolation type");
+    EXPECT_EQ(p->error(), "1:13: expected identifier for interpolation type name");
 }
 
 TEST_F(ParserImplTest, Attribute_Interpolate_InvalidFirstValue) {
diff --git a/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc b/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc
index 3ed0948..2fee83c 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc
@@ -88,6 +88,17 @@
     EXPECT_EQ(v->source.range.end.column, 20u);
 }
 
+TEST_F(ParserImplTest, VariableDecl_WithPushConstant) {
+    auto p = parser("var<push_constant> my_var : f32");
+    auto v = p->variable_decl();
+    EXPECT_TRUE(v.matched);
+    EXPECT_FALSE(v.errored);
+    EXPECT_FALSE(p->has_error());
+    EXPECT_EQ(v->name, "my_var");
+    EXPECT_TRUE(v->type->Is<ast::F32>());
+    EXPECT_EQ(v->storage_class, ast::StorageClass::kPushConstant);
+}
+
 TEST_F(ParserImplTest, VariableDecl_InvalidStorageClass) {
     auto p = parser("var<unknown> my_var : f32");
     auto v = p->variable_decl();
diff --git a/src/tint/reader/wgsl/parser_impl_variable_ident_decl_test.cc b/src/tint/reader/wgsl/parser_impl_variable_ident_decl_test.cc
index a811a82..2679983 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_ident_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_ident_decl_test.cc
@@ -19,7 +19,7 @@
 
 TEST_F(ParserImplTest, VariableIdentDecl_Parses) {
     auto p = parser("my_var : f32");
-    auto decl = p->expect_variable_ident_decl("test", /*allow_inferred = */ false);
+    auto decl = p->expect_variable_ident_decl("test");
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(decl.errored);
     ASSERT_EQ(decl->name, "my_var");
@@ -32,7 +32,7 @@
 
 TEST_F(ParserImplTest, VariableIdentDecl_Parses_AllowInferredType) {
     auto p = parser("my_var : f32");
-    auto decl = p->expect_variable_ident_decl("test", /*allow_inferred = */ true);
+    auto decl = p->expect_ident_or_variable_ident_decl("test");
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(decl.errored);
     ASSERT_EQ(decl->name, "my_var");
@@ -45,14 +45,14 @@
 
 TEST_F(ParserImplTest, VariableIdentDecl_Inferred_Parse_Failure) {
     auto p = parser("my_var = 1.0");
-    auto decl = p->expect_variable_ident_decl("test", /*allow_inferred = */ false);
+    auto decl = p->expect_variable_ident_decl("test");
     ASSERT_TRUE(p->has_error());
     ASSERT_EQ(p->error(), "1:8: expected ':' for test");
 }
 
 TEST_F(ParserImplTest, VariableIdentDecl_Inferred_Parses_AllowInferredType) {
     auto p = parser("my_var = 1.0");
-    auto decl = p->expect_variable_ident_decl("test", /*allow_inferred = */ true);
+    auto decl = p->expect_ident_or_variable_ident_decl("test");
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(decl.errored);
     ASSERT_EQ(decl->name, "my_var");
@@ -63,7 +63,7 @@
 
 TEST_F(ParserImplTest, VariableIdentDecl_MissingIdent) {
     auto p = parser(": f32");
-    auto decl = p->expect_variable_ident_decl("test", /*allow_inferred = */ false);
+    auto decl = p->expect_variable_ident_decl("test");
     ASSERT_TRUE(p->has_error());
     ASSERT_TRUE(decl.errored);
     ASSERT_EQ(p->error(), "1:1: expected identifier for test");
@@ -71,7 +71,7 @@
 
 TEST_F(ParserImplTest, VariableIdentDecl_MissingIdent_AllowInferredType) {
     auto p = parser(": f32");
-    auto decl = p->expect_variable_ident_decl("test", /*allow_inferred = */ true);
+    auto decl = p->expect_ident_or_variable_ident_decl("test");
     ASSERT_TRUE(p->has_error());
     ASSERT_TRUE(decl.errored);
     ASSERT_EQ(p->error(), "1:1: expected identifier for test");
@@ -79,7 +79,7 @@
 
 TEST_F(ParserImplTest, VariableIdentDecl_MissingType) {
     auto p = parser("my_var :");
-    auto decl = p->expect_variable_ident_decl("test", /*allow_inferred = */ false);
+    auto decl = p->expect_variable_ident_decl("test");
     ASSERT_TRUE(p->has_error());
     ASSERT_TRUE(decl.errored);
     ASSERT_EQ(p->error(), "1:9: invalid type for test");
@@ -87,7 +87,7 @@
 
 TEST_F(ParserImplTest, VariableIdentDecl_MissingType_AllowInferredType) {
     auto p = parser("my_var :");
-    auto decl = p->expect_variable_ident_decl("test", /*allow_inferred = */ true);
+    auto decl = p->expect_ident_or_variable_ident_decl("test");
     ASSERT_TRUE(p->has_error());
     ASSERT_TRUE(decl.errored);
     ASSERT_EQ(p->error(), "1:9: invalid type for test");
@@ -95,7 +95,7 @@
 
 TEST_F(ParserImplTest, VariableIdentDecl_InvalidIdent) {
     auto p = parser("123 : f32");
-    auto decl = p->expect_variable_ident_decl("test", /*allow_inferred = */ false);
+    auto decl = p->expect_variable_ident_decl("test");
     ASSERT_TRUE(p->has_error());
     ASSERT_TRUE(decl.errored);
     ASSERT_EQ(p->error(), "1:1: expected identifier for test");
@@ -103,7 +103,7 @@
 
 TEST_F(ParserImplTest, VariableIdentDecl_InvalidIdent_AllowInferredType) {
     auto p = parser("123 : f32");
-    auto decl = p->expect_variable_ident_decl("test", /*allow_inferred = */ true);
+    auto decl = p->expect_ident_or_variable_ident_decl("test");
     ASSERT_TRUE(p->has_error());
     ASSERT_TRUE(decl.errored);
     ASSERT_EQ(p->error(), "1:1: expected identifier for test");
diff --git a/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc
index 21ab6a8..c370262 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc
@@ -19,7 +19,7 @@
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl) {
     auto p = parser("var a : i32;");
-    auto e = p->variable_stmt();
+    auto e = p->variable_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -38,7 +38,7 @@
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_WithInit) {
     auto p = parser("var a : i32 = 1;");
-    auto e = p->variable_stmt();
+    auto e = p->variable_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -58,7 +58,7 @@
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_ConstructorInvalid) {
     auto p = parser("var a : i32 = if(a) {}");
-    auto e = p->variable_stmt();
+    auto e = p->variable_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -68,7 +68,7 @@
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_ArrayInit) {
     auto p = parser("var a : array<i32> = array<i32>();");
-    auto e = p->variable_stmt();
+    auto e = p->variable_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -86,7 +86,7 @@
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_ArrayInit_NoSpace) {
     auto p = parser("var a : array<i32>=array<i32>();");
-    auto e = p->variable_stmt();
+    auto e = p->variable_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -104,7 +104,7 @@
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_VecInit) {
     auto p = parser("var a : vec2<i32> = vec2<i32>();");
-    auto e = p->variable_stmt();
+    auto e = p->variable_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -122,7 +122,7 @@
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_VecInit_NoSpace) {
     auto p = parser("var a : vec2<i32>=vec2<i32>();");
-    auto e = p->variable_stmt();
+    auto e = p->variable_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -140,7 +140,7 @@
 
 TEST_F(ParserImplTest, VariableStmt_Let) {
     auto p = parser("let a : i32 = 1");
-    auto e = p->variable_stmt();
+    auto e = p->variable_statement();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -155,7 +155,7 @@
 
 TEST_F(ParserImplTest, VariableStmt_Let_MissingEqual) {
     auto p = parser("let a : i32 1");
-    auto e = p->variable_stmt();
+    auto e = p->variable_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -165,7 +165,7 @@
 
 TEST_F(ParserImplTest, VariableStmt_Let_MissingConstructor) {
     auto p = parser("let a : i32 =");
-    auto e = p->variable_stmt();
+    auto e = p->variable_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -175,7 +175,7 @@
 
 TEST_F(ParserImplTest, VariableStmt_Let_InvalidConstructor) {
     auto p = parser("let a : i32 = if (a) {}");
-    auto e = p->variable_stmt();
+    auto e = p->variable_statement();
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
diff --git a/src/tint/reader/wgsl/parser_impl_while_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_while_stmt_test.cc
index 45c4990..247f340 100644
--- a/src/tint/reader/wgsl/parser_impl_while_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_while_stmt_test.cc
@@ -24,7 +24,7 @@
 // Test an empty while loop.
 TEST_F(WhileStmtTest, Empty) {
     auto p = parser("while (true) { }");
-    auto wl = p->while_stmt();
+    auto wl = p->while_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(wl.errored);
     ASSERT_TRUE(wl.matched);
@@ -35,19 +35,19 @@
 // Test a while loop with non-empty body.
 TEST_F(WhileStmtTest, Body) {
     auto p = parser("while (true) { discard; }");
-    auto wl = p->while_stmt();
+    auto wl = p->while_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(wl.errored);
     ASSERT_TRUE(wl.matched);
     EXPECT_TRUE(Is<ast::Expression>(wl->condition));
-    ASSERT_EQ(wl->body->statements.size(), 1u);
+    ASSERT_EQ(wl->body->statements.Length(), 1u);
     EXPECT_TRUE(wl->body->statements[0]->Is<ast::DiscardStatement>());
 }
 
 // Test a while loop with complex condition.
 TEST_F(WhileStmtTest, ComplexCondition) {
     auto p = parser("while ((a + 1 - 2) == 3) { }");
-    auto wl = p->while_stmt();
+    auto wl = p->while_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(wl.errored);
     ASSERT_TRUE(wl.matched);
@@ -58,7 +58,7 @@
 // Test a while loop with no brackets.
 TEST_F(WhileStmtTest, NoBrackets) {
     auto p = parser("while (a + 1 - 2) == 3 { }");
-    auto wl = p->while_stmt();
+    auto wl = p->while_statement();
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(wl.errored);
     ASSERT_TRUE(wl.matched);
@@ -70,7 +70,7 @@
   public:
     void TestForWithError(std::string for_str, std::string error_str) {
         auto p_for = parser(for_str);
-        auto e_for = p_for->while_stmt();
+        auto e_for = p_for->while_statement();
 
         EXPECT_FALSE(e_for.matched);
         EXPECT_TRUE(e_for.errored);
diff --git a/src/tint/reader/wgsl/parser_test.cc b/src/tint/reader/wgsl/parser_test.cc
index 1f0e206..860120c 100644
--- a/src/tint/reader/wgsl/parser_test.cc
+++ b/src/tint/reader/wgsl/parser_test.cc
@@ -41,7 +41,7 @@
     auto errs = diag::Formatter().format(program.Diagnostics());
     ASSERT_TRUE(program.IsValid()) << errs;
 
-    ASSERT_EQ(1u, program.AST().Functions().size());
+    ASSERT_EQ(1u, program.AST().Functions().Length());
 }
 
 TEST_F(ParserTest, HandlesError) {
diff --git a/src/tint/reader/wgsl/token.cc b/src/tint/reader/wgsl/token.cc
index a7b437f..ac5eb08 100644
--- a/src/tint/reader/wgsl/token.cc
+++ b/src/tint/reader/wgsl/token.cc
@@ -130,6 +130,10 @@
             return "|=";
         case Token::Type::kXorEqual:
             return "^=";
+        case Token::Type::kShiftLeftEqual:
+            return "<<=";
+        case Token::Type::kShiftRightEqual:
+            return ">>=";
 
         case Token::Type::kArray:
             return "array";
@@ -173,8 +177,6 @@
             return "i32";
         case Token::Type::kIf:
             return "if";
-        case Token::Type::kImport:
-            return "import";
         case Token::Type::kLet:
             return "let";
         case Token::Type::kLoop:
@@ -207,6 +209,8 @@
             return "sampler";
         case Token::Type::kComparisonSampler:
             return "sampler_comparison";
+        case Token::Type::kStaticAssert:
+            return "static_assert";
         case Token::Type::kStruct:
             return "struct";
         case Token::Type::kSwitch:
diff --git a/src/tint/reader/wgsl/token.h b/src/tint/reader/wgsl/token.h
index 352c18f..68cb6c6 100644
--- a/src/tint/reader/wgsl/token.h
+++ b/src/tint/reader/wgsl/token.h
@@ -140,6 +140,10 @@
         kOrEqual,
         /// A '^='
         kXorEqual,
+        /// A '>>='
+        kShiftRightEqual,
+        /// A '<<='
+        kShiftLeftEqual,
 
         /// A 'array'
         kArray,
@@ -183,8 +187,6 @@
         kI32,
         /// A 'if'
         kIf,
-        /// A 'import'
-        kImport,
         /// A 'let'
         kLet,
         /// A 'loop'
@@ -217,6 +219,8 @@
         kSampler,
         /// A 'sampler_comparison'
         kComparisonSampler,
+        /// A 'static_assert'
+        kStaticAssert,
         /// A 'struct'
         kStruct,
         /// A 'switch'
diff --git a/src/tint/resolver/array_accessor_test.cc b/src/tint/resolver/array_accessor_test.cc
index 0f72cf8..a07f6df 100644
--- a/src/tint/resolver/array_accessor_test.cc
+++ b/src/tint/resolver/array_accessor_test.cc
@@ -282,8 +282,8 @@
     auto* idx = Var("idx", ty.i32(), Construct(ty.i32()));
     auto* acc = IndexAccessor("a", Expr(Source{{12, 34}}, idx));
     auto* f = Var("f", ty.f32(), acc);
-    Func("my_func", {}, ty.void_(),
-         {
+    Func("my_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(a),
              Decl(idx),
              Decl(f),
@@ -303,8 +303,8 @@
     // var f : f32 = a[2.0f];
     auto* a = Let("a", ty.array<f32, 3>(), array<f32, 3>());
     auto* f = Var("a_2", ty.f32(), IndexAccessor("a", Expr(Source{{12, 34}}, 2_f)));
-    Func("my_func", {}, ty.void_(),
-         {
+    Func("my_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(a),
              Decl(f),
          });
@@ -318,8 +318,8 @@
     auto* a = Let("a", ty.array<f32, 3>(), array<f32, 3>());
     auto* acc = IndexAccessor("a", 2_i);
     auto* f = Var("a_2", ty.f32(), acc);
-    Func("my_func", {}, ty.void_(),
-         {
+    Func("my_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(a),
              Decl(f),
          });
@@ -342,7 +342,7 @@
     auto* star_p = Deref(p);
     auto* acc = IndexAccessor(Source{{12, 34}}, star_p, idx);
     auto* x = Var("x", ty.f32(), acc);
-    Func("func", {p}, ty.f32(), {Decl(idx), Decl(x), Return(x)});
+    Func("func", utils::Vector{p}, ty.f32(), utils::Vector{Decl(idx), Decl(x), Return(x)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -363,7 +363,7 @@
     auto* accessor_expr = IndexAccessor(Source{{12, 34}}, p, idx);
     auto* star_p = Deref(accessor_expr);
     auto* x = Var("x", ty.f32(), star_p);
-    Func("func", {p}, ty.f32(), {Decl(idx), Decl(x), Return(x)});
+    Func("func", utils::Vector{p}, ty.f32(), utils::Vector{Decl(idx), Decl(x), Return(x)});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
diff --git a/src/tint/resolver/assignment_validation_test.cc b/src/tint/resolver/assignment_validation_test.cc
index e204a62..d6a7e5c 100644
--- a/src/tint/resolver/assignment_validation_test.cc
+++ b/src/tint/resolver/assignment_validation_test.cc
@@ -29,9 +29,11 @@
     // struct S { m : i32 };
     // @group(0) @binding(0)
     // var<storage,read> a : S;
-    auto* s = Structure("S", {Member("m", ty.i32())});
+    auto* s = Structure("S", utils::Vector{
+                                 Member("m", ty.i32()),
+                             });
     GlobalVar(Source{{12, 34}}, "a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -236,12 +238,12 @@
     };
 
     GlobalVar("a", make_type(), ast::StorageClass::kNone,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
     GlobalVar("b", make_type(), ast::StorageClass::kNone,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -257,9 +259,11 @@
     // @group(0) @binding(0) var<storage, read_write> v : S;
     // v.a = v.a;
 
-    auto* s = Structure("S", {Member("a", ty.atomic(ty.i32()))});
+    auto* s = Structure("S", utils::Vector{
+                                 Member("a", ty.atomic(ty.i32())),
+                             });
     GlobalVar(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -275,9 +279,11 @@
     // @group(0) @binding(0) var<storage, read_write> v : S;
     // v.a = v.a;
 
-    auto* s = Structure("S", {Member("a", ty.array(ty.f32()))});
+    auto* s = Structure("S", utils::Vector{
+                                 Member("a", ty.array(ty.f32())),
+                             });
     GlobalVar(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -296,7 +302,9 @@
     // fn f() {
     //   _ = s;
     // }
-    auto* s = Structure("S", {Member("arr", ty.array<i32>())});
+    auto* s = Structure("S", utils::Vector{
+                                 Member("arr", ty.array<i32>()),
+                             });
     GlobalVar("s", ty.Of(s), ast::StorageClass::kStorage, GroupAndBinding(0, 0));
 
     WrapInFunction(Assign(Phony(), Expr(Source{{12, 34}}, "s")));
@@ -316,7 +324,9 @@
     // fn f() {
     //   _ = s.arr;
     // }
-    auto* s = Structure("S", {Member("arr", ty.array<i32>())});
+    auto* s = Structure("S", utils::Vector{
+                                 Member("arr", ty.array<i32>()),
+                             });
     GlobalVar("s", ty.Of(s), ast::StorageClass::kStorage, GroupAndBinding(0, 0));
 
     WrapInFunction(Assign(Phony(), MemberAccessor(Source{{12, 34}}, "s", "arr")));
@@ -360,11 +370,13 @@
     //   _ = wg;
     //   _ = wg[3i];
     // }
-    auto* S = Structure("S", {
+    auto* S = Structure("S", utils::Vector{
                                  Member("i", ty.i32()),
                                  Member("arr", ty.array<i32>()),
                              });
-    auto* U = Structure("U", {Member("i", ty.i32())});
+    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));
diff --git a/src/tint/resolver/atomics_test.cc b/src/tint/resolver/atomics_test.cc
index 09ca0ba..d7b3824 100644
--- a/src/tint/resolver/atomics_test.cc
+++ b/src/tint/resolver/atomics_test.cc
@@ -45,9 +45,9 @@
 }
 
 TEST_F(ResolverAtomicTest, GlobalStorageStruct) {
-    auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
+    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,
-                        ast::AttributeList{
+                        utils::Vector{
                             create<ast::BindingAttribute>(0u),
                             create<ast::GroupAttribute>(0u),
                         });
diff --git a/src/tint/resolver/atomics_validation_test.cc b/src/tint/resolver/atomics_validation_test.cc
index 5ec4e01..0961397 100644
--- a/src/tint/resolver/atomics_validation_test.cc
+++ b/src/tint/resolver/atomics_validation_test.cc
@@ -40,7 +40,7 @@
 }
 
 TEST_F(ResolverAtomicValidationTest, StorageClass_Storage_Struct) {
-    auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
+    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));
 
@@ -73,7 +73,7 @@
 }
 
 TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Struct) {
-    auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
+    auto* s = Structure("s", utils::Vector{Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
     GlobalVar("g", ty.Of(s), ast::StorageClass::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
@@ -88,8 +88,9 @@
     // struct Outer { m : array<Inner, 4>; };
     // var<private> g : Outer;
 
-    auto* Inner = Structure("Inner", {Member("m", ty.atomic(Source{{12, 34}}, ty.i32()))});
-    auto* Outer = Structure("Outer", {Member("m", ty.Of(Inner))});
+    auto* Inner =
+        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("g", ty.Of(Outer), ast::StorageClass::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
@@ -104,8 +105,9 @@
     // struct Outer { m : array<Inner, 4>; };
     // var<private> g : Outer;
 
-    auto* Inner = Structure("Inner", {Member(Source{{12, 34}}, "m", ty.atomic(ty.i32()))});
-    auto* Outer = Structure("Outer", {Member("m", ty.Of(Inner))});
+    auto* Inner =
+        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("g", ty.Of(Outer), ast::StorageClass::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
@@ -135,7 +137,7 @@
     // };
     // var<private> v: array<S, 5u>;
 
-    auto* s = Structure("S", {Member("m", ty.atomic<u32>())});
+    auto* s = Structure("S", utils::Vector{Member("m", ty.atomic<u32>())});
     GlobalVar(Source{{56, 78}}, "v", ty.array(ty.Of(s), 5_u), ast::StorageClass::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
@@ -154,7 +156,7 @@
 
     auto* atomic_array =
         Alias(Source{{12, 34}}, "AtomicArray", ty.atomic(Source{{12, 34}}, ty.i32()));
-    auto* s = Structure("S", {Member("m", ty.Of(atomic_array))});
+    auto* s = Structure("S", utils::Vector{Member("m", ty.Of(atomic_array))});
     GlobalVar(Source{{56, 78}}, "v", ty.array(ty.Of(s), 5_u), ast::StorageClass::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
@@ -185,17 +187,17 @@
     auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8_u);
     auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4_u);
 
-    auto* s6 = Structure("S6", {Member("x", array_i32_4)});
-    auto* s5 = Structure("S5", {Member("x", ty.Of(s6)),             //
-                                Member("y", ty.Of(atomic_array)),   //
-                                Member("z", array_atomic_u32_8)});  //
-    auto* s4 = Structure("S4", {Member("x", ty.Of(s6)),             //
-                                Member("y", ty.Of(s5)),             //
-                                Member("z", array_atomic_i32_4)});  //
-    auto* s3 = Structure("S3", {Member("x", ty.Of(s4))});
-    auto* s2 = Structure("S2", {Member("x", ty.Of(s3))});
-    auto* s1 = Structure("S1", {Member("x", ty.Of(s2))});
-    auto* s0 = Structure("S0", {Member("x", ty.Of(s1))});
+    auto* s6 = Structure("S6", utils::Vector{Member("x", array_i32_4)});
+    auto* s5 = Structure("S5", utils::Vector{Member("x", ty.Of(s6)),             //
+                                             Member("y", ty.Of(atomic_array)),   //
+                                             Member("z", array_atomic_u32_8)});  //
+    auto* s4 = Structure("S4", utils::Vector{Member("x", ty.Of(s6)),             //
+                                             Member("y", ty.Of(s5)),             //
+                                             Member("z", array_atomic_i32_4)});  //
+    auto* s3 = Structure("S3", utils::Vector{Member("x", ty.Of(s4))});
+    auto* s2 = Structure("S2", utils::Vector{Member("x", ty.Of(s3))});
+    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::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
@@ -206,7 +208,7 @@
 }
 
 TEST_F(ResolverAtomicValidationTest, Struct_AccessMode_Read) {
-    auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
+    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));
 
@@ -218,7 +220,7 @@
 }
 
 TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_Struct) {
-    auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
+    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));
 
@@ -234,8 +236,9 @@
     // struct Outer { m : array<Inner, 4>; };
     // var<storage, read> g : Outer;
 
-    auto* Inner = Structure("Inner", {Member("m", ty.atomic(Source{{12, 34}}, ty.i32()))});
-    auto* Outer = Structure("Outer", {Member("m", ty.Of(Inner))});
+    auto* Inner =
+        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));
 
@@ -251,8 +254,9 @@
     // struct Outer { m : array<Inner, 4>; };
     // var<storage, read> g : Outer;
 
-    auto* Inner = Structure("Inner", {Member(Source{{12, 34}}, "m", ty.atomic(ty.i32()))});
-    auto* Outer = Structure("Outer", {Member("m", ty.Of(Inner))});
+    auto* Inner =
+        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));
 
@@ -284,17 +288,17 @@
     auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8_u);
     auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4_u);
 
-    auto* s6 = Structure("S6", {Member("x", array_i32_4)});
-    auto* s5 = Structure("S5", {Member("x", ty.Of(s6)),             //
-                                Member("y", ty.Of(atomic_array)),   //
-                                Member("z", array_atomic_u32_8)});  //
-    auto* s4 = Structure("S4", {Member("x", ty.Of(s6)),             //
-                                Member("y", ty.Of(s5)),             //
-                                Member("z", array_atomic_i32_4)});  //
-    auto* s3 = Structure("S3", {Member("x", ty.Of(s4))});
-    auto* s2 = Structure("S2", {Member("x", ty.Of(s3))});
-    auto* s1 = Structure("S1", {Member("x", ty.Of(s2))});
-    auto* s0 = Structure("S0", {Member("x", ty.Of(s1))});
+    auto* s6 = Structure("S6", utils::Vector{Member("x", array_i32_4)});
+    auto* s5 = Structure("S5", utils::Vector{Member("x", ty.Of(s6)),             //
+                                             Member("y", ty.Of(atomic_array)),   //
+                                             Member("z", array_atomic_u32_8)});  //
+    auto* s4 = Structure("S4", utils::Vector{Member("x", ty.Of(s6)),             //
+                                             Member("y", ty.Of(s5)),             //
+                                             Member("z", array_atomic_i32_4)});  //
+    auto* s3 = Structure("S3", utils::Vector{Member("x", ty.Of(s4))});
+    auto* s2 = Structure("S2", utils::Vector{Member("x", ty.Of(s3))});
+    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));
 
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc
index 36221e2..ed0227e 100644
--- a/src/tint/resolver/attribute_validation_test.cc
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -84,9 +84,9 @@
 };
 struct TestWithParams : ResolverTestWithParam<TestParams> {};
 
-static ast::AttributeList createAttributes(const Source& source,
-                                           ProgramBuilder& builder,
-                                           AttributeKind kind) {
+static utils::Vector<const ast::Attribute*, 2> createAttributes(const Source& source,
+                                                                ProgramBuilder& builder,
+                                                                AttributeKind kind) {
     switch (kind) {
         case AttributeKind::kAlign:
             return {builder.create<ast::StructMemberAlignAttribute>(source, 4u)};
@@ -128,10 +128,10 @@
     auto& params = GetParam();
 
     Func("main",
-         {
+         utils::Vector{
              Param("a", ty.vec4<f32>(), createAttributes({}, *this, params.kind)),
          },
-         ty.void_(), {});
+         ty.void_(), utils::Empty);
 
     if (params.should_pass) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -163,11 +163,11 @@
 TEST_P(FunctionReturnTypeAttributeTest, IsValid) {
     auto& params = GetParam();
 
-    Func("main", {}, ty.f32(),
-         {
+    Func("main", utils::Empty, ty.f32(),
+         utils::Vector{
              Return(1_f),
          },
-         {}, createAttributes({}, *this, params.kind));
+         utils::Empty, createAttributes({}, *this, params.kind));
 
     if (params.should_pass) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -201,11 +201,11 @@
 TEST_P(ComputeShaderParameterAttributeTest, IsValid) {
     auto& params = GetParam();
     Func("main",
-         {
+         utils::Vector{
              Param("a", ty.vec4<f32>(), createAttributes(Source{{12, 34}}, *this, params.kind)),
          },
-         ty.void_(), {},
-         {
+         ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
@@ -250,11 +250,11 @@
     auto& params = GetParam();
     auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
     if (params.kind != AttributeKind::kBuiltin && params.kind != AttributeKind::kLocation) {
-        attrs.push_back(Builtin(Source{{34, 56}}, ast::BuiltinValue::kPosition));
+        attrs.Push(Builtin(Source{{34, 56}}, ast::BuiltinValue::kPosition));
     }
     auto* p = Param("a", ty.vec4<f32>(), attrs);
-    Func("frag_main", {p}, ty.void_(), {},
-         {
+    Func("frag_main", utils::Vector{p}, ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -287,17 +287,17 @@
     auto& params = GetParam();
     auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
     if (params.kind != AttributeKind::kLocation) {
-        attrs.push_back(Location(Source{{34, 56}}, 2));
+        attrs.Push(Location(Source{{34, 56}}, 2));
     }
     auto* p = Param("a", ty.vec4<f32>(), attrs);
-    Func("vertex_main", {p}, ty.vec4<f32>(),
-         {
+    Func("vertex_main", utils::Vector{p}, ty.vec4<f32>(),
+         utils::Vector{
              Return(Construct(ty.vec4<f32>())),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kVertex),
          },
-         {
+         utils::Vector{
              Builtin(ast::BuiltinValue::kPosition),
          });
 
@@ -338,11 +338,11 @@
 using ComputeShaderReturnTypeAttributeTest = TestWithParams;
 TEST_P(ComputeShaderReturnTypeAttributeTest, IsValid) {
     auto& params = GetParam();
-    Func("main", {}, ty.vec4<f32>(),
-         {
+    Func("main", utils::Empty, ty.vec4<f32>(),
+         utils::Vector{
              Return(Construct(ty.vec4<f32>(), 1_f)),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          },
@@ -389,9 +389,10 @@
 TEST_P(FragmentShaderReturnTypeAttributeTest, IsValid) {
     auto& params = GetParam();
     auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
-    attrs.push_back(Location(Source{{34, 56}}, 2));
-    Func("frag_main", {}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
-         {
+    attrs.Push(Location(Source{{34, 56}}, 2));
+    Func("frag_main", utils::Empty, ty.vec4<f32>(),
+         utils::Vector{Return(Construct(ty.vec4<f32>()))},
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
          attrs);
@@ -442,13 +443,13 @@
     auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
     // a vertex shader must include the 'position' builtin in its return type
     if (params.kind != AttributeKind::kBuiltin) {
-        attrs.push_back(Builtin(Source{{34, 56}}, ast::BuiltinValue::kPosition));
+        attrs.Push(Builtin(Source{{34, 56}}, ast::BuiltinValue::kPosition));
     }
-    Func("vertex_main", {}, ty.vec4<f32>(),
-         {
+    Func("vertex_main", utils::Empty, ty.vec4<f32>(),
+         utils::Vector{
              Return(Construct(ty.vec4<f32>())),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kVertex),
          },
          attrs);
@@ -487,14 +488,14 @@
 
 using EntryPointParameterAttributeTest = TestWithParams;
 TEST_F(EntryPointParameterAttributeTest, DuplicateAttribute) {
-    Func("main", {}, ty.f32(),
-         {
+    Func("main", utils::Empty, ty.f32(),
+         utils::Vector{
              Return(1_f),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(Source{{12, 34}}, 2),
              Location(Source{{56, 78}}, 3),
          });
@@ -507,14 +508,14 @@
 
 TEST_F(EntryPointParameterAttributeTest, DuplicateInternalAttribute) {
     auto* s = Param("s", ty.sampler(ast::SamplerKind::kSampler),
-                    ast::AttributeList{
+                    utils::Vector{
                         create<ast::BindingAttribute>(0u),
                         create<ast::GroupAttribute>(0u),
                         Disable(ast::DisabledValidation::kBindingPointCollision),
                         Disable(ast::DisabledValidation::kEntryPointParameter),
                     });
-    Func("f", {s}, ty.void_(), {},
-         {
+    Func("f", utils::Vector{s}, ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -523,14 +524,14 @@
 
 using EntryPointReturnTypeAttributeTest = ResolverTest;
 TEST_F(EntryPointReturnTypeAttributeTest, DuplicateAttribute) {
-    Func("main", {}, ty.f32(),
-         {
+    Func("main", utils::Empty, ty.f32(),
+         utils::Vector{
              Return(1_f),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         ast::AttributeList{
+         utils::Vector{
              Location(Source{{12, 34}}, 2),
              Location(Source{{56, 78}}, 3),
          });
@@ -542,11 +543,11 @@
 }
 
 TEST_F(EntryPointReturnTypeAttributeTest, DuplicateInternalAttribute) {
-    Func("f", {}, ty.i32(), {Return(1_i)},
-         {
+    Func("f", utils::Empty, ty.i32(), utils::Vector{Return(1_i)},
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         ast::AttributeList{
+         utils::Vector{
              Disable(ast::DisabledValidation::kBindingPointCollision),
              Disable(ast::DisabledValidation::kEntryPointParameter),
          });
@@ -561,7 +562,7 @@
 TEST_P(StructAttributeTest, IsValid) {
     auto& params = GetParam();
 
-    auto* str = create<ast::Struct>(Sym("mystruct"), ast::StructMemberList{Member("a", ty.f32())},
+    auto* str = create<ast::Struct>(Sym("mystruct"), utils::Vector{Member("a", ty.f32())},
                                     createAttributes(Source{{12, 34}}, *this, params.kind));
     AST().AddGlobalDeclaration(str);
 
@@ -592,16 +593,14 @@
 using StructMemberAttributeTest = TestWithParams;
 TEST_P(StructMemberAttributeTest, IsValid) {
     auto& params = GetParam();
-    ast::StructMemberList members;
+    utils::Vector<const ast::StructMember*, 1> members;
     if (params.kind == AttributeKind::kBuiltin) {
-        members.push_back(
-            {Member("a", ty.vec4<f32>(), createAttributes(Source{{12, 34}}, *this, params.kind))});
+        members.Push(
+            Member("a", ty.vec4<f32>(), createAttributes(Source{{12, 34}}, *this, params.kind)));
     } else {
-        members.push_back(
-            {Member("a", ty.f32(), createAttributes(Source{{12, 34}}, *this, params.kind))});
+        members.Push(Member("a", ty.f32(), createAttributes(Source{{12, 34}}, *this, params.kind)));
     }
     Structure("mystruct", members);
-    WrapInFunction();
     if (params.should_pass) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
     } else {
@@ -627,38 +626,35 @@
                                          TestParams{AttributeKind::kBindingAndGroup, false}));
 TEST_F(StructMemberAttributeTest, DuplicateAttribute) {
     Structure("mystruct",
-              {
+              utils::Vector{
                   Member("a", ty.i32(),
-                         {
+                         utils::Vector{
                              create<ast::StructMemberAlignAttribute>(Source{{12, 34}}, 4u),
                              create<ast::StructMemberAlignAttribute>(Source{{56, 78}}, 8u),
                          }),
               });
-    WrapInFunction();
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               R"(56:78 error: duplicate align attribute
 12:34 note: first attribute declared here)");
 }
 TEST_F(StructMemberAttributeTest, InvariantAttributeWithPosition) {
-    Structure("mystruct", {
+    Structure("mystruct", utils::Vector{
                               Member("a", ty.vec4<f32>(),
-                                     {
+                                     utils::Vector{
                                          Invariant(),
                                          Builtin(ast::BuiltinValue::kPosition),
                                      }),
                           });
-    WrapInFunction();
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 TEST_F(StructMemberAttributeTest, InvariantAttributeWithoutPosition) {
-    Structure("mystruct", {
+    Structure("mystruct", utils::Vector{
                               Member("a", ty.vec4<f32>(),
-                                     {
+                                     utils::Vector{
                                          Invariant(Source{{12, 34}}),
                                      }),
                           });
-    WrapInFunction();
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               "12:34 error: invariant attribute must only be applied to a "
@@ -672,12 +668,10 @@
     auto& params = GetParam();
 
     auto* arr = ty.array(ty.f32(), nullptr, createAttributes(Source{{12, 34}}, *this, params.kind));
-    Structure("mystruct", {
+    Structure("mystruct", utils::Vector{
                               Member("a", arr),
                           });
 
-    WrapInFunction();
-
     if (params.should_pass) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
     } else {
@@ -714,8 +708,6 @@
                   createAttributes(Source{{12, 34}}, *this, params.kind));
     }
 
-    WrapInFunction();
-
     if (params.should_pass) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
     } else {
@@ -744,14 +736,12 @@
 
 TEST_F(VariableAttributeTest, DuplicateAttribute) {
     GlobalVar("a", ty.sampler(ast::SamplerKind::kSampler),
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(Source{{12, 34}}, 2u),
                   create<ast::GroupAttribute>(2u),
                   create<ast::BindingAttribute>(Source{{56, 78}}, 3u),
               });
 
-    WrapInFunction();
-
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               R"(56:78 error: duplicate binding attribute
@@ -760,7 +750,7 @@
 
 TEST_F(VariableAttributeTest, LocalVariable) {
     auto* v = Var("a", ty.f32(),
-                  ast::AttributeList{
+                  utils::Vector{
                       create<ast::BindingAttribute>(Source{{12, 34}}, 2u),
                   });
 
@@ -777,8 +767,6 @@
     GlobalConst("a", ty.f32(), Expr(1.23_f),
                 createAttributes(Source{{12, 34}}, *this, params.kind));
 
-    WrapInFunction();
-
     if (params.should_pass) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
     } else {
@@ -806,13 +794,11 @@
 
 TEST_F(ConstantAttributeTest, DuplicateAttribute) {
     GlobalConst("a", ty.f32(), Expr(1.23_f),
-                ast::AttributeList{
+                utils::Vector{
                     create<ast::IdAttribute>(Source{{12, 34}}, 0u),
                     create<ast::IdAttribute>(Source{{56, 78}}, 1u),
                 });
 
-    WrapInFunction();
-
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               R"(56:78 error: duplicate id attribute
@@ -825,8 +811,6 @@
 
     Override("a", ty.f32(), Expr(1.23_f), createAttributes(Source{{12, 34}}, *this, params.kind));
 
-    WrapInFunction();
-
     if (params.should_pass) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
     } else {
@@ -853,13 +837,11 @@
 
 TEST_F(OverrideAttributeTest, DuplicateAttribute) {
     Override("a", ty.f32(), Expr(1.23_f),
-             ast::AttributeList{
+             utils::Vector{
                  create<ast::IdAttribute>(Source{{12, 34}}, 0u),
                  create<ast::IdAttribute>(Source{{56, 78}}, 1u),
              });
 
-    WrapInFunction();
-
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               R"(56:78 error: duplicate id attribute
@@ -895,8 +877,10 @@
        << ", should_pass: " << params.should_pass;
     SCOPED_TRACE(ss.str());
 
-    auto* arr =
-        ty.array(el_ty, 4_u, {create<ast::StrideAttribute>(Source{{12, 34}}, params.stride)});
+    auto* arr = ty.array(el_ty, 4_u,
+                         utils::Vector{
+                             create<ast::StrideAttribute>(Source{{12, 34}}, params.stride),
+                         });
 
     GlobalVar("myarray", arr, ast::StorageClass::kPrivate);
 
@@ -976,7 +960,7 @@
 
 TEST_F(ArrayStrideTest, DuplicateAttribute) {
     auto* arr = ty.array(Source{{12, 34}}, ty.i32(), 4_u,
-                         {
+                         utils::Vector{
                              create<ast::StrideAttribute>(Source{{12, 34}}, 4u),
                              create<ast::StrideAttribute>(Source{{56, 78}}, 4u),
                          });
@@ -997,7 +981,9 @@
 
 using ResourceAttributeTest = ResolverTest;
 TEST_F(ResourceAttributeTest, UniformBufferMissingBinding) {
-    auto* s = Structure("S", {Member("x", ty.i32())});
+    auto* s = Structure("S", utils::Vector{
+                                 Member("x", ty.i32()),
+                             });
     GlobalVar(Source{{12, 34}}, "G", ty.Of(s), ast::StorageClass::kUniform);
 
     EXPECT_FALSE(r()->Resolve());
@@ -1006,7 +992,9 @@
 }
 
 TEST_F(ResourceAttributeTest, StorageBufferMissingBinding) {
-    auto* s = Structure("S", {Member("x", ty.i32())});
+    auto* s = Structure("S", utils::Vector{
+                                 Member("x", ty.i32()),
+                             });
     GlobalVar(Source{{12, 34}}, "G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead);
 
     EXPECT_FALSE(r()->Resolve());
@@ -1035,7 +1023,7 @@
 TEST_F(ResourceAttributeTest, BindingPairMissingBinding) {
     GlobalVar(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
               ast::StorageClass::kNone,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::GroupAttribute>(1u),
               });
 
@@ -1047,7 +1035,7 @@
 TEST_F(ResourceAttributeTest, BindingPairMissingGroup) {
     GlobalVar(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
               ast::StorageClass::kNone,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
               });
 
@@ -1059,25 +1047,25 @@
 TEST_F(ResourceAttributeTest, BindingPointUsedTwiceByEntryPoint) {
     GlobalVar(Source{{12, 34}}, "A", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
               ast::StorageClass::kNone,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
     GlobalVar(Source{{56, 78}}, "B", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
               ast::StorageClass::kNone,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("F", {}, ty.void_(),
-         {
+    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))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -1091,31 +1079,31 @@
 TEST_F(ResourceAttributeTest, BindingPointUsedTwiceByDifferentEntryPoints) {
     GlobalVar(Source{{12, 34}}, "A", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
               ast::StorageClass::kNone,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
     GlobalVar(Source{{56, 78}}, "B", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
               ast::StorageClass::kNone,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("F_A", {}, ty.void_(),
-         {
+    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))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
-    Func("F_B", {}, ty.void_(),
-         {
+    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))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -1124,7 +1112,7 @@
 
 TEST_F(ResourceAttributeTest, BindingPointOnNonResource) {
     GlobalVar(Source{{12, 34}}, "G", ty.f32(), ast::StorageClass::kPrivate,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
@@ -1141,32 +1129,38 @@
 namespace {
 using InvariantAttributeTests = ResolverTest;
 TEST_F(InvariantAttributeTests, InvariantWithPosition) {
-    auto* param = Param(
-        "p", ty.vec4<f32>(),
-        {Invariant(Source{{12, 34}}), Builtin(Source{{56, 78}}, ast::BuiltinValue::kPosition)});
-    Func("main", {param}, ty.vec4<f32>(),
-         {
+    auto* param = Param("p", ty.vec4<f32>(),
+                        utils::Vector{
+                            Invariant(Source{{12, 34}}),
+                            Builtin(Source{{56, 78}}, ast::BuiltinValue::kPosition),
+                        });
+    Func("main", utils::Vector{param}, ty.vec4<f32>(),
+         utils::Vector{
              Return(Construct(ty.vec4<f32>())),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(0),
          });
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(InvariantAttributeTests, InvariantWithoutPosition) {
-    auto* param = Param("p", ty.vec4<f32>(), {Invariant(Source{{12, 34}}), Location(0)});
-    Func("main", {param}, ty.vec4<f32>(),
-         {
+    auto* param = Param("p", ty.vec4<f32>(),
+                        utils::Vector{
+                            Invariant(Source{{12, 34}}),
+                            Location(0),
+                        });
+    Func("main", utils::Vector{param}, ty.vec4<f32>(),
+         utils::Vector{
              Return(Construct(ty.vec4<f32>())),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(0),
          });
     EXPECT_FALSE(r()->Resolve());
@@ -1182,15 +1176,20 @@
 
 using WorkgroupAttribute = ResolverTest;
 TEST_F(WorkgroupAttribute, ComputeShaderPass) {
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute),
-          create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i))});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i)),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(WorkgroupAttribute, Missing) {
-    Func(Source{{12, 34}}, "main", {}, ty.void_(), {}, {Stage(ast::PipelineStage::kCompute)});
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1199,8 +1198,10 @@
 }
 
 TEST_F(WorkgroupAttribute, NotAnEntryPoint) {
-    Func("main", {}, ty.void_(), {},
-         {create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i))});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i)),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1209,9 +1210,11 @@
 }
 
 TEST_F(WorkgroupAttribute, NotAComputeShader) {
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kFragment),
-          create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i))});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+             create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i)),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1220,8 +1223,8 @@
 }
 
 TEST_F(WorkgroupAttribute, DuplicateAttribute) {
-    Func(Source{{12, 34}}, "main", {}, ty.void_(), {},
-         {
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(Source{{12, 34}}, 1_i, nullptr, nullptr),
              WorkgroupSize(Source{{56, 78}}, 2_i, nullptr, nullptr),
@@ -1254,15 +1257,15 @@
     auto& params = GetParam();
 
     Func("main",
-         {
+         utils::Vector{
              Param("a", ty.f32(),
-                   {
+                   utils::Vector{
                        Location(0),
                        Interpolate(Source{{12, 34}}, params.type, params.sampling),
                    }),
          },
-         ty.void_(), {},
-         {
+         ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -1280,15 +1283,15 @@
     auto& params = GetParam();
 
     Func("main",
-         {
+         utils::Vector{
              Param("a", ty.i32(),
-                   {
+                   utils::Vector{
                        Location(0),
                        Interpolate(Source{{12, 34}}, params.type, params.sampling),
                    }),
          },
-         ty.void_(), {},
-         {
+         ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -1311,15 +1314,15 @@
     auto& params = GetParam();
 
     Func("main",
-         {
+         utils::Vector{
              Param("a", ty.vec4<u32>(),
-                   {
+                   utils::Vector{
                        Location(0),
                        Interpolate(Source{{12, 34}}, params.type, params.sampling),
                    }),
          },
-         ty.void_(), {},
-         {
+         ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -1357,8 +1360,9 @@
         Params{ast::InterpolationType::kFlat, ast::InterpolationSampling::kSample, false}));
 
 TEST_F(InterpolateTest, FragmentInput_Integer_MissingFlatInterpolation) {
-    Func("main", {Param(Source{{12, 34}}, "a", ty.i32(), {Location(0)})}, ty.void_(), {},
-         {
+    Func("main", utils::Vector{Param(Source{{12, 34}}, "a", ty.i32(), utils::Vector{Location(0)})},
+         ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -1369,16 +1373,17 @@
 }
 
 TEST_F(InterpolateTest, VertexOutput_Integer_MissingFlatInterpolation) {
-    auto* s =
-        Structure("S", {
-                           Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
-                           Member(Source{{12, 34}}, "u", ty.u32(), {Location(0)}),
-                       });
-    Func("main", {}, ty.Of(s),
-         {
+    auto* s = Structure(
+        "S",
+        utils::Vector{
+            Member("pos", ty.vec4<f32>(), utils::Vector{Builtin(ast::BuiltinValue::kPosition)}),
+            Member(Source{{12, 34}}, "u", ty.u32(), utils::Vector{Location(0)}),
+        });
+    Func("main", utils::Empty, ty.Of(s),
+         utils::Vector{
              Return(Construct(ty.Of(s))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kVertex),
          });
 
@@ -1391,16 +1396,16 @@
 
 TEST_F(InterpolateTest, MissingLocationAttribute_Parameter) {
     Func("main",
-         {
+         utils::Vector{
              Param("a", ty.vec4<f32>(),
-                   {
+                   utils::Vector{
                        Builtin(ast::BuiltinValue::kPosition),
                        Interpolate(Source{{12, 34}}, ast::InterpolationType::kFlat,
                                    ast::InterpolationSampling::kNone),
                    }),
          },
-         ty.void_(), {},
-         {
+         ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -1410,14 +1415,14 @@
 }
 
 TEST_F(InterpolateTest, MissingLocationAttribute_ReturnType) {
-    Func("main", {}, ty.vec4<f32>(),
-         {
+    Func("main", utils::Empty, ty.vec4<f32>(),
+         utils::Vector{
              Return(Construct(ty.vec4<f32>())),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kVertex),
          },
-         {
+         utils::Vector{
              Builtin(ast::BuiltinValue::kPosition),
              Interpolate(Source{{12, 34}}, ast::InterpolationType::kFlat,
                          ast::InterpolationSampling::kNone),
@@ -1429,9 +1434,12 @@
 }
 
 TEST_F(InterpolateTest, MissingLocationAttribute_Struct) {
-    Structure("S", {Member("a", ty.f32(),
-                           {Interpolate(Source{{12, 34}}, ast::InterpolationType::kFlat,
-                                        ast::InterpolationSampling::kNone)})});
+    Structure("S",
+              utils::Vector{
+                  Member("a", ty.f32(),
+                         utils::Vector{Interpolate(Source{{12, 34}}, ast::InterpolationType::kFlat,
+                                                   ast::InterpolationSampling::kNone)}),
+              });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
diff --git a/src/tint/resolver/builtin_test.cc b/src/tint/resolver/builtin_test.cc
index 7bb7eb2..6046a73 100644
--- a/src/tint/resolver/builtin_test.cc
+++ b/src/tint/resolver/builtin_test.cc
@@ -44,6 +44,8 @@
 namespace tint::resolver {
 namespace {
 
+using ExpressionList = utils::Vector<const ast::Expression*, 8>;
+
 using BuiltinType = sem::BuiltinType;
 
 using ResolverBuiltinTest = ResolverTest;
@@ -209,9 +211,9 @@
 
 TEST_F(ResolverBuiltinArrayTest, ArrayLength_Vector) {
     auto* ary = ty.array<i32>();
-    auto* str = Structure("S", {Member("x", ary)});
+    auto* str = Structure("S", utils::Vector{Member("x", ary)});
     GlobalVar("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -899,8 +901,9 @@
 )");
 }
 
-// frexp: (f32) -> __frexp_result, (vecN<f32>) -> __frexp_result_vecN
-TEST_F(ResolverBuiltinFloatTest, FrexpScalar) {
+// frexp: (f32) -> __frexp_result, (vecN<f32>) -> __frexp_result_vecN, (f16) -> __frexp_result_16,
+// (vecN<f16>) -> __frexp_result_vecN_f16
+TEST_F(ResolverBuiltinFloatTest, FrexpScalar_f32) {
     auto* call = Call("frexp", 1_f);
     WrapInFunction(call);
 
@@ -929,7 +932,38 @@
     EXPECT_EQ(ty->SizeNoPadding(), 8u);
 }
 
-TEST_F(ResolverBuiltinFloatTest, FrexpVector) {
+TEST_F(ResolverBuiltinFloatTest, FrexpScalar_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("frexp", 1_h);
+    WrapInFunction(call);
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+    ASSERT_NE(TypeOf(call), nullptr);
+    auto* ty = TypeOf(call)->As<sem::Struct>();
+    ASSERT_NE(ty, nullptr);
+    ASSERT_EQ(ty->Members().size(), 2u);
+
+    auto* sig = ty->Members()[0];
+    EXPECT_TRUE(sig->Type()->Is<sem::F16>());
+    EXPECT_EQ(sig->Offset(), 0u);
+    EXPECT_EQ(sig->Size(), 2u);
+    EXPECT_EQ(sig->Align(), 2u);
+    EXPECT_EQ(sig->Name(), Sym("sig"));
+
+    auto* exp = ty->Members()[1];
+    EXPECT_TRUE(exp->Type()->Is<sem::I32>());
+    EXPECT_EQ(exp->Offset(), 4u);
+    EXPECT_EQ(exp->Size(), 4u);
+    EXPECT_EQ(exp->Align(), 4u);
+    EXPECT_EQ(exp->Name(), Sym("exp"));
+
+    EXPECT_EQ(ty->Size(), 8u);
+    EXPECT_EQ(ty->SizeNoPadding(), 8u);
+}
+
+TEST_F(ResolverBuiltinFloatTest, FrexpVector_f32) {
     auto* call = Call("frexp", vec3<f32>());
     WrapInFunction(call);
 
@@ -962,6 +996,41 @@
     EXPECT_EQ(ty->SizeNoPadding(), 28u);
 }
 
+TEST_F(ResolverBuiltinFloatTest, FrexpVector_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("frexp", vec3<f16>());
+    WrapInFunction(call);
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+    ASSERT_NE(TypeOf(call), nullptr);
+    auto* ty = TypeOf(call)->As<sem::Struct>();
+    ASSERT_NE(ty, nullptr);
+    ASSERT_EQ(ty->Members().size(), 2u);
+
+    auto* sig = ty->Members()[0];
+    ASSERT_TRUE(sig->Type()->Is<sem::Vector>());
+    EXPECT_EQ(sig->Type()->As<sem::Vector>()->Width(), 3u);
+    EXPECT_TRUE(sig->Type()->As<sem::Vector>()->type()->Is<sem::F16>());
+    EXPECT_EQ(sig->Offset(), 0u);
+    EXPECT_EQ(sig->Size(), 6u);
+    EXPECT_EQ(sig->Align(), 8u);
+    EXPECT_EQ(sig->Name(), Sym("sig"));
+
+    auto* exp = ty->Members()[1];
+    ASSERT_TRUE(exp->Type()->Is<sem::Vector>());
+    EXPECT_EQ(exp->Type()->As<sem::Vector>()->Width(), 3u);
+    EXPECT_TRUE(exp->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+    EXPECT_EQ(exp->Offset(), 16u);
+    EXPECT_EQ(exp->Size(), 12u);
+    EXPECT_EQ(exp->Align(), 16u);
+    EXPECT_EQ(exp->Name(), Sym("exp"));
+
+    EXPECT_EQ(ty->Size(), 32u);
+    EXPECT_EQ(ty->SizeNoPadding(), 28u);
+}
+
 TEST_F(ResolverBuiltinFloatTest, Frexp_Error_FirstParamInt) {
     GlobalVar("v", ty.i32(), ast::StorageClass::kWorkgroup);
     auto* call = Call("frexp", 1_i, AddressOf("v"));
@@ -973,8 +1042,8 @@
               R"(error: no matching call to frexp(i32, ptr<workgroup, i32, read_write>)
 
 2 candidate functions:
-  frexp(f32) -> __frexp_result
-  frexp(vecN<f32>) -> __frexp_result_vecN
+  frexp(T) -> __frexp_result_T  where: T is f32 or f16
+  frexp(vecN<T>) -> __frexp_result_vecN_T  where: T is f32 or f16
 )");
 }
 
@@ -989,8 +1058,8 @@
               R"(error: no matching call to frexp(f32, ptr<workgroup, f32, read_write>)
 
 2 candidate functions:
-  frexp(f32) -> __frexp_result
-  frexp(vecN<f32>) -> __frexp_result_vecN
+  frexp(T) -> __frexp_result_T  where: T is f32 or f16
+  frexp(vecN<T>) -> __frexp_result_vecN_T  where: T is f32 or f16
 )");
 }
 
@@ -1003,8 +1072,8 @@
     EXPECT_EQ(r()->error(), R"(error: no matching call to frexp(f32, i32)
 
 2 candidate functions:
-  frexp(f32) -> __frexp_result
-  frexp(vecN<f32>) -> __frexp_result_vecN
+  frexp(T) -> __frexp_result_T  where: T is f32 or f16
+  frexp(vecN<T>) -> __frexp_result_vecN_T  where: T is f32 or f16
 )");
 }
 
@@ -1019,8 +1088,8 @@
               R"(error: no matching call to frexp(vec2<f32>, ptr<workgroup, vec4<i32>, read_write>)
 
 2 candidate functions:
-  frexp(vecN<f32>) -> __frexp_result_vecN
-  frexp(f32) -> __frexp_result
+  frexp(T) -> __frexp_result_T  where: T is f32 or f16
+  frexp(vecN<T>) -> __frexp_result_vecN_T  where: T is f32 or f16
 )");
 }
 
@@ -1127,8 +1196,9 @@
     EXPECT_TRUE(TypeOf(call)->As<sem::Vector>()->type()->Is<sem::F16>());
 }
 
-// modf: (f32) -> __modf_result, (vecN<f32>) -> __modf_result_vecN
-TEST_F(ResolverBuiltinFloatTest, ModfScalar) {
+// modf: (f32) -> __modf_result, (vecN<f32>) -> __modf_result_vecN, (f16) -> __modf_result_f16,
+// (vecN<f16>) -> __modf_result_vecN_f16
+TEST_F(ResolverBuiltinFloatTest, ModfScalar_f32) {
     auto* call = Call("modf", 1_f);
     WrapInFunction(call);
 
@@ -1157,7 +1227,38 @@
     EXPECT_EQ(ty->SizeNoPadding(), 8u);
 }
 
-TEST_F(ResolverBuiltinFloatTest, ModfVector) {
+TEST_F(ResolverBuiltinFloatTest, ModfScalar_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("modf", 1_h);
+    WrapInFunction(call);
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+    ASSERT_NE(TypeOf(call), nullptr);
+    auto* ty = TypeOf(call)->As<sem::Struct>();
+    ASSERT_NE(ty, nullptr);
+    ASSERT_EQ(ty->Members().size(), 2u);
+
+    auto* fract = ty->Members()[0];
+    EXPECT_TRUE(fract->Type()->Is<sem::F16>());
+    EXPECT_EQ(fract->Offset(), 0u);
+    EXPECT_EQ(fract->Size(), 2u);
+    EXPECT_EQ(fract->Align(), 2u);
+    EXPECT_EQ(fract->Name(), Sym("fract"));
+
+    auto* whole = ty->Members()[1];
+    EXPECT_TRUE(whole->Type()->Is<sem::F16>());
+    EXPECT_EQ(whole->Offset(), 2u);
+    EXPECT_EQ(whole->Size(), 2u);
+    EXPECT_EQ(whole->Align(), 2u);
+    EXPECT_EQ(whole->Name(), Sym("whole"));
+
+    EXPECT_EQ(ty->Size(), 4u);
+    EXPECT_EQ(ty->SizeNoPadding(), 4u);
+}
+
+TEST_F(ResolverBuiltinFloatTest, ModfVector_f32) {
     auto* call = Call("modf", vec3<f32>());
     WrapInFunction(call);
 
@@ -1190,6 +1291,41 @@
     EXPECT_EQ(ty->SizeNoPadding(), 28u);
 }
 
+TEST_F(ResolverBuiltinFloatTest, ModfVector_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("modf", vec3<f16>());
+    WrapInFunction(call);
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+    ASSERT_NE(TypeOf(call), nullptr);
+    auto* ty = TypeOf(call)->As<sem::Struct>();
+    ASSERT_NE(ty, nullptr);
+    ASSERT_EQ(ty->Members().size(), 2u);
+
+    auto* fract = ty->Members()[0];
+    ASSERT_TRUE(fract->Type()->Is<sem::Vector>());
+    EXPECT_EQ(fract->Type()->As<sem::Vector>()->Width(), 3u);
+    EXPECT_TRUE(fract->Type()->As<sem::Vector>()->type()->Is<sem::F16>());
+    EXPECT_EQ(fract->Offset(), 0u);
+    EXPECT_EQ(fract->Size(), 6u);
+    EXPECT_EQ(fract->Align(), 8u);
+    EXPECT_EQ(fract->Name(), Sym("fract"));
+
+    auto* whole = ty->Members()[1];
+    ASSERT_TRUE(whole->Type()->Is<sem::Vector>());
+    EXPECT_EQ(whole->Type()->As<sem::Vector>()->Width(), 3u);
+    EXPECT_TRUE(whole->Type()->As<sem::Vector>()->type()->Is<sem::F16>());
+    EXPECT_EQ(whole->Offset(), 8u);
+    EXPECT_EQ(whole->Size(), 6u);
+    EXPECT_EQ(whole->Align(), 8u);
+    EXPECT_EQ(whole->Name(), Sym("whole"));
+
+    EXPECT_EQ(ty->Size(), 16u);
+    EXPECT_EQ(ty->SizeNoPadding(), 14u);
+}
+
 TEST_F(ResolverBuiltinFloatTest, Modf_Error_FirstParamInt) {
     GlobalVar("whole", ty.f32(), ast::StorageClass::kWorkgroup);
     auto* call = Call("modf", 1_i, AddressOf("whole"));
@@ -1201,8 +1337,8 @@
               R"(error: no matching call to modf(i32, ptr<workgroup, f32, read_write>)
 
 2 candidate functions:
-  modf(f32) -> __modf_result
-  modf(vecN<f32>) -> __modf_result_vecN
+  modf(T) -> __modf_result_T  where: T is f32 or f16
+  modf(vecN<T>) -> __modf_result_vecN_T  where: T is f32 or f16
 )");
 }
 
@@ -1217,8 +1353,8 @@
               R"(error: no matching call to modf(f32, ptr<workgroup, i32, read_write>)
 
 2 candidate functions:
-  modf(f32) -> __modf_result
-  modf(vecN<f32>) -> __modf_result_vecN
+  modf(T) -> __modf_result_T  where: T is f32 or f16
+  modf(vecN<T>) -> __modf_result_vecN_T  where: T is f32 or f16
 )");
 }
 
@@ -1231,8 +1367,8 @@
     EXPECT_EQ(r()->error(), R"(error: no matching call to modf(f32, f32)
 
 2 candidate functions:
-  modf(f32) -> __modf_result
-  modf(vecN<f32>) -> __modf_result_vecN
+  modf(T) -> __modf_result_T  where: T is f32 or f16
+  modf(vecN<T>) -> __modf_result_vecN_T  where: T is f32 or f16
 )");
 }
 
@@ -1247,8 +1383,8 @@
               R"(error: no matching call to modf(vec2<f32>, ptr<workgroup, vec4<f32>, read_write>)
 
 2 candidate functions:
-  modf(vecN<f32>) -> __modf_result_vecN
-  modf(f32) -> __modf_result
+  modf(T) -> __modf_result_T  where: T is f32 or f16
+  modf(vecN<T>) -> __modf_result_vecN_T  where: T is f32 or f16
 )");
 }
 
@@ -1906,8 +2042,8 @@
     GlobalVar("ident", ty.f32(), ast::StorageClass::kPrivate);
 
     auto* expr = Call(name, "ident");
-    Func("func", {}, ty.void_(), {Ignore(expr)},
-         {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(), utils::Vector{Ignore(expr)},
+         utils::Vector{create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1920,8 +2056,8 @@
     GlobalVar("ident", ty.vec4<f32>(), ast::StorageClass::kPrivate);
 
     auto* expr = Call(name, "ident");
-    Func("func", {}, ty.void_(), {Ignore(expr)},
-         {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(), utils::Vector{Ignore(expr)},
+         utils::Vector{create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -2008,10 +2144,10 @@
         return nullptr;
     }
 
-    void add_call_param(std::string name, const ast::Type* type, ast::ExpressionList* call_params) {
+    void add_call_param(std::string name, const ast::Type* type, ExpressionList* call_params) {
         if (type->IsAnyOf<ast::Texture, ast::Sampler>()) {
             GlobalVar(name, type,
-                      ast::AttributeList{
+                      utils::Vector{
                           create<ast::BindingAttribute>(0u),
                           create<ast::GroupAttribute>(0u),
                       });
@@ -2020,7 +2156,7 @@
             GlobalVar(name, type, ast::StorageClass::kPrivate);
         }
 
-        call_params->push_back(Expr(name));
+        call_params->Push(Expr(name));
     }
     const ast::Type* subtype(Texture type) {
         if (type == Texture::kF32) {
@@ -2042,7 +2178,7 @@
     auto* coords_type = GetCoordsType(dim, ty.i32());
     auto* texture_type = ty.sampled_texture(dim, s);
 
-    ast::ExpressionList call_params;
+    ExpressionList call_params;
 
     add_call_param("texture", texture_type, &call_params);
     add_call_param("coords", coords_type, &call_params);
@@ -2082,7 +2218,7 @@
                          testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases()));
 
 static std::string to_str(const std::string& function,
-                          utils::ConstVectorRef<const sem::Parameter*> params) {
+                          utils::VectorRef<const sem::Parameter*> params) {
     std::stringstream out;
     out << function << "(";
     bool first = true;
@@ -2337,7 +2473,8 @@
 
     auto* call = Call(param.function, param.args(this));
     auto* stmt = CallStmt(call);
-    Func("func", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(), utils::Vector{stmt},
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
diff --git a/src/tint/resolver/builtin_validation_test.cc b/src/tint/resolver/builtin_validation_test.cc
index be7a23f..83603c3 100644
--- a/src/tint/resolver/builtin_validation_test.cc
+++ b/src/tint/resolver/builtin_validation_test.cc
@@ -24,8 +24,8 @@
 
 TEST_F(ResolverBuiltinValidationTest, FunctionTypeMustMatchReturnStatementType_void_fail) {
     // fn func { return workgroupBarrier(); }
-    Func("func", {}, ty.void_(),
-         {
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(Call(Source{Source::Location{12, 34}}, "workgroupBarrier")),
          });
 
@@ -36,13 +36,15 @@
 TEST_F(ResolverBuiltinValidationTest, InvalidPipelineStageDirect) {
     // @compute @workgroup_size(1) fn func { return dpdx(1.0); }
 
-    auto* dpdx =
-        create<ast::CallExpression>(Source{{3, 4}}, Expr("dpdx"), ast::ExpressionList{Expr(1_f)});
-    Func(Source{{1, 2}}, "func", {}, ty.void_(),
-         {
+    auto* dpdx = create<ast::CallExpression>(Source{{3, 4}}, Expr("dpdx"),
+                                             utils::Vector{
+                                                 Expr(1_f),
+                                             });
+    Func(Source{{1, 2}}, "func", utils::Empty, ty.void_(),
+         utils::Vector{
              CallStmt(dpdx),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
@@ -57,16 +59,33 @@
     // fn f2 { f1(); }
     // @compute @workgroup_size(1) fn main { return f2(); }
 
-    auto* dpdx =
-        create<ast::CallExpression>(Source{{3, 4}}, Expr("dpdx"), ast::ExpressionList{Expr(1_f)});
-    Func(Source{{1, 2}}, "f0", {}, ty.void_(), {CallStmt(dpdx)});
+    auto* dpdx = create<ast::CallExpression>(Source{{3, 4}}, Expr("dpdx"),
+                                             utils::Vector{
+                                                 Expr(1_f),
+                                             });
+    Func(Source{{1, 2}}, "f0", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(dpdx),
+         });
 
-    Func(Source{{3, 4}}, "f1", {}, ty.void_(), {CallStmt(Call("f0"))});
+    Func(Source{{3, 4}}, "f1", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("f0")),
+         });
 
-    Func(Source{{5, 6}}, "f2", {}, ty.void_(), {CallStmt(Call("f1"))});
+    Func(Source{{5, 6}}, "f2", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("f1")),
+         });
 
-    Func(Source{{7, 8}}, "main", {}, ty.void_(), {CallStmt(Call("f2"))},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
+    Func(Source{{7, 8}}, "main", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("f2")),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1_i),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -78,7 +97,7 @@
 }
 
 TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsFunction) {
-    Func(Source{{12, 34}}, "mix", {}, ty.i32(), {});
+    Func(Source{{12, 34}}, "mix", utils::Empty, ty.i32(), {});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -111,7 +130,10 @@
 }
 
 TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsStruct) {
-    Structure(Source{{12, 34}}, "mix", {Member("m", ty.i32())});
+    Structure(Source{{12, 34}}, "mix",
+              utils::Vector{
+                  Member("m", ty.i32()),
+              });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -232,7 +254,7 @@
     overload.BuildSamplerVariable(this);
 
     auto args = overload.args(this);
-    auto*& arg_to_replace = (param.position == Position::kFirst) ? args.front() : args.back();
+    auto*& arg_to_replace = (param.position == Position::kFirst) ? args.Front() : args.Back();
 
     // BuildTextureVariable() uses a Literal for scalars, and a CallExpression for
     // a vector constructor.
@@ -245,8 +267,13 @@
     arg_to_replace = expr(Source{{12, 34}}, *this);
 
     // Call the builtin with the constexpr argument replaced
-    Func("func", {}, ty.void_(), {CallStmt(Call(overload.function, args))},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call(overload.function, args)),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     if (expr.invalid_index == Constexpr::kValid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -281,7 +308,7 @@
     GlobalConst("G", nullptr, expr({}, *this));
 
     auto args = overload.args(this);
-    auto*& arg_to_replace = (param.position == Position::kFirst) ? args.front() : args.back();
+    auto*& arg_to_replace = (param.position == Position::kFirst) ? args.Front() : args.Back();
 
     // Make the expression to be replaced, reachable. This keeps the resolver
     // happy.
@@ -290,8 +317,13 @@
     arg_to_replace = Expr(Source{{12, 34}}, "G");
 
     // Call the builtin with the constexpr argument replaced
-    Func("func", {}, ty.void_(), {CallStmt(Call(overload.function, args))},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call(overload.function, args)),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     std::stringstream err;
@@ -386,10 +418,10 @@
     // fn func { return dot4I8Packed(1u, 2u); }
     Enable(ast::Extension::kChromiumExperimentalDp4A);
 
-    Func("func", {}, ty.i32(),
-         {
+    Func("func", utils::Empty, ty.i32(),
+         utils::Vector{
              Return(Call(Source{Source::Location{12, 34}}, "dot4I8Packed",
-                         ast::ExpressionList{Expr(1_u), Expr(2_u)})),
+                         utils::Vector{Expr(1_u), Expr(2_u)})),
          });
 
     EXPECT_TRUE(r()->Resolve());
@@ -397,10 +429,10 @@
 
 TEST_F(ResolverDP4aExtensionValidationTest, Dot4I8PackedWithoutExtension) {
     // fn func { return dot4I8Packed(1u, 2u); }
-    Func("func", {}, ty.i32(),
-         {
+    Func("func", utils::Empty, ty.i32(),
+         utils::Vector{
              Return(Call(Source{Source::Location{12, 34}}, "dot4I8Packed",
-                         ast::ExpressionList{Expr(1_u), Expr(2_u)})),
+                         utils::Vector{Expr(1_u), Expr(2_u)})),
          });
 
     EXPECT_FALSE(r()->Resolve());
@@ -414,10 +446,10 @@
     // fn func { return dot4U8Packed(1u, 2u); }
     Enable(ast::Extension::kChromiumExperimentalDp4A);
 
-    Func("func", {}, ty.u32(),
-         {
+    Func("func", utils::Empty, ty.u32(),
+         utils::Vector{
              Return(Call(Source{Source::Location{12, 34}}, "dot4U8Packed",
-                         ast::ExpressionList{Expr(1_u), Expr(2_u)})),
+                         utils::Vector{Expr(1_u), Expr(2_u)})),
          });
 
     EXPECT_TRUE(r()->Resolve());
@@ -425,10 +457,10 @@
 
 TEST_F(ResolverDP4aExtensionValidationTest, Dot4U8PackedWithoutExtension) {
     // fn func { return dot4U8Packed(1u, 2u); }
-    Func("func", {}, ty.u32(),
-         {
+    Func("func", utils::Empty, ty.u32(),
+         utils::Vector{
              Return(Call(Source{Source::Location{12, 34}}, "dot4U8Packed",
-                         ast::ExpressionList{Expr(1_u), Expr(2_u)})),
+                         utils::Vector{Expr(1_u), Expr(2_u)})),
          });
 
     EXPECT_FALSE(r()->Resolve());
diff --git a/src/tint/resolver/builtins_validation_test.cc b/src/tint/resolver/builtins_validation_test.cc
index c3ec4a7..6318901 100644
--- a/src/tint/resolver/builtins_validation_test.cc
+++ b/src/tint/resolver/builtins_validation_test.cc
@@ -101,18 +101,26 @@
     const Params& params = GetParam();
 
     auto* p = GlobalVar("p", ty.vec4<f32>(), ast::StorageClass::kPrivate);
-    auto* input = Param("input", params.type(*this), {Builtin(Source{{12, 34}}, params.builtin)});
+    auto* input = Param("input", params.type(*this),
+                        utils::Vector{Builtin(Source{{12, 34}}, params.builtin)});
     switch (params.stage) {
         case ast::PipelineStage::kVertex:
-            Func("main", {input}, ty.vec4<f32>(), {Return(p)}, {Stage(ast::PipelineStage::kVertex)},
-                 {Builtin(Source{{12, 34}}, ast::BuiltinValue::kPosition)});
+            Func("main", utils::Vector{input}, ty.vec4<f32>(), utils::Vector{Return(p)},
+                 utils::Vector{Stage(ast::PipelineStage::kVertex)},
+                 utils::Vector{
+                     Builtin(Source{{12, 34}}, ast::BuiltinValue::kPosition),
+                 });
             break;
         case ast::PipelineStage::kFragment:
-            Func("main", {input}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)}, {});
+            Func("main", utils::Vector{input}, ty.void_(), utils::Empty,
+                 utils::Vector{
+                     Stage(ast::PipelineStage::kFragment),
+                 },
+                 {});
             break;
         case ast::PipelineStage::kCompute:
-            Func("main", {input}, ty.void_(), {},
-                 {
+            Func("main", utils::Vector{input}, ty.void_(), utils::Empty,
+                 utils::Vector{
                      Stage(ast::PipelineStage::kCompute),
                      WorkgroupSize(1_i),
                  });
@@ -141,17 +149,20 @@
     //   @builtin(frag_depth) fd: f32,
     // ) -> @location(0) f32 { return 1.0; }
     Func("fs_main",
-         {
-             Param("fd", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kFragDepth)}),
+         utils::Vector{
+             Param("fd", ty.f32(),
+                   utils::Vector{
+                       Builtin(Source{{12, 34}}, ast::BuiltinValue::kFragDepth),
+                   }),
          },
          ty.f32(),
-         {
+         utils::Vector{
              Return(1_f),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(0),
          });
     EXPECT_FALSE(r()->Resolve());
@@ -168,23 +179,25 @@
     // fn fragShader(arg: MyInputs) -> @location(0) f32 { return 1.0; }
 
     auto* s = Structure("MyInputs",
-                        {
+                        utils::Vector{
                             Member("frag_depth", ty.f32(),
-                                   {Builtin(Source{{12, 34}}, ast::BuiltinValue::kFragDepth)}),
+                                   utils::Vector{
+                                       Builtin(Source{{12, 34}}, ast::BuiltinValue::kFragDepth),
+                                   }),
                         });
 
     Func("fragShader",
-         {
+         utils::Vector{
              Param("arg", ty.Of(s)),
          },
          ty.f32(),
-         {
+         utils::Vector{
              Return(1_f),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(0),
          });
     EXPECT_FALSE(r()->Resolve());
@@ -201,10 +214,17 @@
     // @fragment
     // fn fragShader() { var s : S; }
 
-    Structure("S", {Member("idx", ty.u32(), {Builtin(ast::BuiltinValue::kVertexIndex)})});
+    Structure("S", utils::Vector{
+                       Member("idx", ty.u32(),
+                              utils::Vector{
+                                  Builtin(ast::BuiltinValue::kVertexIndex),
+                              }),
+                   });
 
-    Func("fragShader", {}, ty.void_(), {Decl(Var("s", ty.type_name("S")))},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("fragShader", utils::Empty, ty.void_(), utils::Vector{Decl(Var("s", ty.type_name("S")))},
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
     EXPECT_TRUE(r()->Resolve());
 }
 
@@ -217,23 +237,25 @@
     // @fragment
     // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
 
-    auto* s =
-        Structure("MyInputs", {
-                                  Member("position", ty.vec4<u32>(),
-                                         {Builtin(Source{{12, 34}}, ast::BuiltinValue::kPosition)}),
-                              });
+    auto* s = Structure("MyInputs",
+                        utils::Vector{
+                            Member("position", ty.vec4<u32>(),
+                                   utils::Vector{
+                                       Builtin(Source{{12, 34}}, ast::BuiltinValue::kPosition),
+                                   }),
+                        });
     Func("fragShader",
-         {
+         utils::Vector{
              Param("arg", ty.Of(s)),
          },
          ty.f32(),
-         {
+         utils::Vector{
              Return(1_f),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(0),
          });
 
@@ -244,8 +266,14 @@
 TEST_F(ResolverBuiltinsValidationTest, PositionNotF32_ReturnType_Fail) {
     // @vertex
     // fn main() -> @builtin(position) f32 { return 1.0; }
-    Func("main", {}, ty.f32(), {Return(1_f)}, {Stage(ast::PipelineStage::kVertex)},
-         {Builtin(Source{{12, 34}}, ast::BuiltinValue::kPosition)});
+    Func("main", utils::Empty, ty.f32(),
+         utils::Vector{
+             Return(1_f),
+         },
+         utils::Vector{Stage(ast::PipelineStage::kVertex)},
+         utils::Vector{
+             Builtin(Source{{12, 34}}, ast::BuiltinValue::kPosition),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(position) must be 'vec4<f32>'");
@@ -259,18 +287,20 @@
     // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
 
     auto* s = Structure("MyInputs",
-                        {
+                        utils::Vector{
                             Member("frag_depth", ty.i32(),
-                                   {Builtin(Source{{12, 34}}, ast::BuiltinValue::kFragDepth)}),
+                                   utils::Vector{
+                                       Builtin(Source{{12, 34}}, ast::BuiltinValue::kFragDepth),
+                                   }),
                         });
-    Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(),
-         {
+    Func("fragShader", utils::Vector{Param("arg", ty.Of(s))}, ty.f32(),
+         utils::Vector{
              Return(1_f),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(0),
          });
 
@@ -285,13 +315,23 @@
     // @fragment
     // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
 
-    auto* s = Structure(
-        "MyInputs",
-        {
-            Member("m", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleMask)}),
-        });
-    Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1_f)},
-         {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+    auto* s = Structure("MyInputs",
+                        utils::Vector{
+                            Member("m", ty.f32(),
+                                   utils::Vector{
+                                       Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleMask),
+                                   }),
+                        });
+    Func("fragShader", utils::Vector{Param("arg", ty.Of(s))}, ty.f32(),
+         utils::Vector{
+             Return(1_f),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         utils::Vector{
+             Location(0),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(sample_mask) must be 'u32'");
@@ -300,8 +340,13 @@
 TEST_F(ResolverBuiltinsValidationTest, SampleMaskNotU32_ReturnType_Fail) {
     // @fragment
     // fn main() -> @builtin(sample_mask) i32 { return 1; }
-    Func("main", {}, ty.i32(), {Return(1_i)}, {Stage(ast::PipelineStage::kFragment)},
-         {Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleMask)});
+    Func("main", utils::Empty, ty.i32(), utils::Vector{Return(1_i)},
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         utils::Vector{
+             Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleMask),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(sample_mask) must be 'u32'");
@@ -313,17 +358,20 @@
     //   @builtin(sample_mask) arg: bool
     // ) -> @location(0) f32 { return 1.0; }
     Func("fs_main",
-         {
-             Param("arg", ty.bool_(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleMask)}),
+         utils::Vector{
+             Param("arg", ty.bool_(),
+                   utils::Vector{
+                       Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleMask),
+                   }),
          },
          ty.f32(),
-         {
+         utils::Vector{
              Return(1_f),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(0),
          });
     EXPECT_FALSE(r()->Resolve());
@@ -337,13 +385,23 @@
     // @fragment
     // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
 
-    auto* s = Structure(
-        "MyInputs",
-        {
-            Member("m", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleIndex)}),
-        });
-    Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1_f)},
-         {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+    auto* s = Structure("MyInputs",
+                        utils::Vector{
+                            Member("m", ty.f32(),
+                                   utils::Vector{
+                                       Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleIndex),
+                                   }),
+                        });
+    Func("fragShader", utils::Vector{Param("arg", ty.Of(s))}, ty.f32(),
+         utils::Vector{
+             Return(1_f),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         utils::Vector{
+             Location(0),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(sample_index) must be 'u32'");
@@ -355,14 +413,20 @@
     //   @builtin(sample_index) arg: bool
     // ) -> @location(0) f32 { return 1.0; }
     Func("fs_main",
-         {
-             Param("arg", ty.bool_(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleIndex)}),
+         utils::Vector{
+             Param("arg", ty.bool_(),
+                   utils::Vector{
+                       Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleIndex),
+                   }),
          },
-         ty.f32(), {Return(1_f)},
-         {
+         ty.f32(),
+         utils::Vector{
+             Return(1_f),
+         },
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(0),
          });
     EXPECT_FALSE(r()->Resolve());
@@ -375,14 +439,20 @@
     //   @builtin(kPosition) p: vec3<f32>,
     // ) -> @location(0) f32 { return 1.0; }
     Func("fs_main",
-         {
-             Param("p", ty.vec3<f32>(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kPosition)}),
+         utils::Vector{
+             Param("p", ty.vec3<f32>(),
+                   utils::Vector{
+                       Builtin(Source{{12, 34}}, ast::BuiltinValue::kPosition),
+                   }),
          },
-         ty.f32(), {Return(1_f)},
-         {
+         ty.f32(),
+         utils::Vector{
+             Return(1_f),
+         },
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(0),
          });
     EXPECT_FALSE(r()->Resolve());
@@ -393,8 +463,17 @@
     // @fragment
     // fn fs_main() -> @builtin(kFragDepth) f32 { var fd: i32; return fd; }
     auto* fd = Var("fd", ty.i32());
-    Func("fs_main", {}, ty.i32(), {Decl(fd), Return(fd)}, {Stage(ast::PipelineStage::kFragment)},
-         {Builtin(Source{{12, 34}}, ast::BuiltinValue::kFragDepth)});
+    Func("fs_main", utils::Empty, ty.i32(),
+         utils::Vector{
+             Decl(fd),
+             Return(fd),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         utils::Vector{
+             Builtin(Source{{12, 34}}, ast::BuiltinValue::kFragDepth),
+         });
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(frag_depth) must be 'f32'");
 }
@@ -405,10 +484,21 @@
     //   @builtin(kVertexIndex) vi : f32,
     //   @builtin(kPosition) p :vec4<f32>
     // ) -> @builtin(kPosition) vec4<f32> { return vec4<f32>(); }
-    auto* p = Param("p", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
-    auto* vi = Param("vi", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kVertexIndex)});
-    Func("main", {vi, p}, ty.vec4<f32>(), {Return(Expr("p"))}, {Stage(ast::PipelineStage::kVertex)},
-         {Builtin(ast::BuiltinValue::kPosition)});
+    auto* p = Param("p", ty.vec4<f32>(),
+                    utils::Vector{
+                        Builtin(ast::BuiltinValue::kPosition),
+                    });
+    auto* vi = Param("vi", ty.f32(),
+                     utils::Vector{
+                         Builtin(Source{{12, 34}}, ast::BuiltinValue::kVertexIndex),
+                     });
+    Func("main", utils::Vector{vi, p}, ty.vec4<f32>(), utils::Vector{Return(Expr("p"))},
+         utils::Vector{
+             Stage(ast::PipelineStage::kVertex),
+         },
+         utils::Vector{
+             Builtin(ast::BuiltinValue::kPosition),
+         });
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(vertex_index) must be 'u32'");
 }
@@ -419,11 +509,21 @@
     //   @builtin(kInstanceIndex) ii : f32,
     //   @builtin(kPosition) p :vec4<f32>
     // ) -> @builtin(kPosition) vec4<f32> { return vec4<f32>(); }
-    auto* p = Param("p", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
-    auto* ii =
-        Param("ii", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kInstanceIndex)});
-    Func("main", {ii, p}, ty.vec4<f32>(), {Return(Expr("p"))}, {Stage(ast::PipelineStage::kVertex)},
-         {Builtin(ast::BuiltinValue::kPosition)});
+    auto* p = Param("p", ty.vec4<f32>(),
+                    utils::Vector{
+                        Builtin(ast::BuiltinValue::kPosition),
+                    });
+    auto* ii = Param("ii", ty.f32(),
+                     utils::Vector{
+                         Builtin(Source{{12, 34}}, ast::BuiltinValue::kInstanceIndex),
+                     });
+    Func("main", utils::Vector{ii, p}, ty.vec4<f32>(), utils::Vector{Return(Expr("p"))},
+         utils::Vector{
+             Stage(ast::PipelineStage::kVertex),
+         },
+         utils::Vector{
+             Builtin(ast::BuiltinValue::kPosition),
+         });
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(instance_index) must be 'u32'");
 }
@@ -436,13 +536,34 @@
     //   @builtin(sample_index) si: u32,
     //   @builtin(sample_mask) sm : u32
     // ) -> @builtin(frag_depth) f32 { var fd: f32; return fd; }
-    auto* p = Param("p", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
-    auto* ff = Param("ff", ty.bool_(), {Builtin(ast::BuiltinValue::kFrontFacing)});
-    auto* si = Param("si", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)});
-    auto* sm = Param("sm", ty.u32(), {Builtin(ast::BuiltinValue::kSampleMask)});
+    auto* p = Param("p", ty.vec4<f32>(),
+                    utils::Vector{
+                        Builtin(ast::BuiltinValue::kPosition),
+                    });
+    auto* ff = Param("ff", ty.bool_(),
+                     utils::Vector{
+                         Builtin(ast::BuiltinValue::kFrontFacing),
+                     });
+    auto* si = Param("si", ty.u32(),
+                     utils::Vector{
+                         Builtin(ast::BuiltinValue::kSampleIndex),
+                     });
+    auto* sm = Param("sm", ty.u32(),
+                     utils::Vector{
+                         Builtin(ast::BuiltinValue::kSampleMask),
+                     });
     auto* var_fd = Var("fd", ty.f32());
-    Func("fs_main", {p, ff, si, sm}, ty.f32(), {Decl(var_fd), Return(var_fd)},
-         {Stage(ast::PipelineStage::kFragment)}, {Builtin(ast::BuiltinValue::kFragDepth)});
+    Func("fs_main", utils::Vector{p, ff, si, sm}, ty.f32(),
+         utils::Vector{
+             Decl(var_fd),
+             Return(var_fd),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         utils::Vector{
+             Builtin(ast::BuiltinValue::kFragDepth),
+         });
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
@@ -452,17 +573,25 @@
     //   @builtin(vertex_index) vi : u32,
     //   @builtin(instance_index) ii : u32,
     // ) -> @builtin(position) vec4<f32> { var p :vec4<f32>; return p; }
-    auto* vi = Param("vi", ty.u32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kVertexIndex)});
+    auto* vi = Param("vi", ty.u32(),
+                     utils::Vector{
+                         Builtin(Source{{12, 34}}, ast::BuiltinValue::kVertexIndex),
+                     });
 
-    auto* ii =
-        Param("ii", ty.u32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kInstanceIndex)});
+    auto* ii = Param("ii", ty.u32(),
+                     utils::Vector{
+                         Builtin(Source{{12, 34}}, ast::BuiltinValue::kInstanceIndex),
+                     });
     auto* p = Var("p", ty.vec4<f32>());
-    Func("main", {vi, ii}, ty.vec4<f32>(),
-         {
+    Func("main", utils::Vector{vi, ii}, ty.vec4<f32>(),
+         utils::Vector{
              Decl(p),
              Return(p),
          },
-         {Stage(ast::PipelineStage::kVertex)}, {Builtin(ast::BuiltinValue::kPosition)});
+         utils::Vector{Stage(ast::PipelineStage::kVertex)},
+         utils::Vector{
+             Builtin(ast::BuiltinValue::kPosition),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -477,25 +606,42 @@
     //   @builtin(num_workgroups) nwgs: vec3<u32>,
     // ) {}
 
-    auto* li_id = Param("li_id", ty.vec3<u32>(), {Builtin(ast::BuiltinValue::kLocalInvocationId)});
-    auto* li_index =
-        Param("li_index", ty.u32(), {Builtin(ast::BuiltinValue::kLocalInvocationIndex)});
-    auto* gi = Param("gi", ty.vec3<u32>(), {Builtin(ast::BuiltinValue::kGlobalInvocationId)});
-    auto* wi = Param("wi", ty.vec3<u32>(), {Builtin(ast::BuiltinValue::kWorkgroupId)});
-    auto* nwgs = Param("nwgs", ty.vec3<u32>(), {Builtin(ast::BuiltinValue::kNumWorkgroups)});
+    auto* li_id = Param("li_id", ty.vec3<u32>(),
+                        utils::Vector{
+                            Builtin(ast::BuiltinValue::kLocalInvocationId),
+                        });
+    auto* li_index = Param("li_index", ty.u32(),
+                           utils::Vector{
+                               Builtin(ast::BuiltinValue::kLocalInvocationIndex),
+                           });
+    auto* gi = Param("gi", ty.vec3<u32>(),
+                     utils::Vector{
+                         Builtin(ast::BuiltinValue::kGlobalInvocationId),
+                     });
+    auto* wi = Param("wi", ty.vec3<u32>(),
+                     utils::Vector{
+                         Builtin(ast::BuiltinValue::kWorkgroupId),
+                     });
+    auto* nwgs = Param("nwgs", ty.vec3<u32>(),
+                       utils::Vector{
+                           Builtin(ast::BuiltinValue::kNumWorkgroups),
+                       });
 
-    Func("main", {li_id, li_index, gi, wi, nwgs}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute),
-          WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
+    Func("main", utils::Vector{li_id, li_index, gi, wi, nwgs}, ty.void_(), utils::Empty,
+         utils::Vector{Stage(ast::PipelineStage::kCompute),
+                       WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_WorkGroupIdNotVec3U32) {
-    auto* wi = Param("wi", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kWorkgroupId)});
-    Func("main", {wi}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute),
-          WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
+    auto* wi = Param("wi", ty.f32(),
+                     utils::Vector{
+                         Builtin(Source{{12, 34}}, ast::BuiltinValue::kWorkgroupId),
+                     });
+    Func("main", utils::Vector{wi}, ty.void_(), utils::Empty,
+         utils::Vector{Stage(ast::PipelineStage::kCompute),
+                       WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -504,11 +650,13 @@
 }
 
 TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_NumWorkgroupsNotVec3U32) {
-    auto* nwgs =
-        Param("nwgs", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kNumWorkgroups)});
-    Func("main", {nwgs}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute),
-          WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
+    auto* nwgs = Param("nwgs", ty.f32(),
+                       utils::Vector{
+                           Builtin(Source{{12, 34}}, ast::BuiltinValue::kNumWorkgroups),
+                       });
+    Func("main", utils::Vector{nwgs}, ty.void_(), utils::Empty,
+         utils::Vector{Stage(ast::PipelineStage::kCompute),
+                       WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -518,10 +666,12 @@
 
 TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_GlobalInvocationNotVec3U32) {
     auto* gi = Param("gi", ty.vec3<i32>(),
-                     {Builtin(Source{{12, 34}}, ast::BuiltinValue::kGlobalInvocationId)});
-    Func("main", {gi}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute),
-          WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
+                     utils::Vector{
+                         Builtin(Source{{12, 34}}, ast::BuiltinValue::kGlobalInvocationId),
+                     });
+    Func("main", utils::Vector{gi}, ty.void_(), utils::Empty,
+         utils::Vector{Stage(ast::PipelineStage::kCompute),
+                       WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -531,10 +681,12 @@
 
 TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_LocalInvocationIndexNotU32) {
     auto* li_index = Param("li_index", ty.vec3<u32>(),
-                           {Builtin(Source{{12, 34}}, ast::BuiltinValue::kLocalInvocationIndex)});
-    Func("main", {li_index}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute),
-          WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
+                           utils::Vector{
+                               Builtin(Source{{12, 34}}, ast::BuiltinValue::kLocalInvocationIndex),
+                           });
+    Func("main", utils::Vector{li_index}, ty.void_(), utils::Empty,
+         utils::Vector{Stage(ast::PipelineStage::kCompute),
+                       WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -544,10 +696,12 @@
 
 TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_LocalInvocationNotVec3U32) {
     auto* li_id = Param("li_id", ty.vec2<u32>(),
-                        {Builtin(Source{{12, 34}}, ast::BuiltinValue::kLocalInvocationId)});
-    Func("main", {li_id}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute),
-          WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
+                        utils::Vector{
+                            Builtin(Source{{12, 34}}, ast::BuiltinValue::kLocalInvocationId),
+                        });
+    Func("main", utils::Vector{li_id}, ty.void_(), utils::Empty,
+         utils::Vector{Stage(ast::PipelineStage::kCompute),
+                       WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -565,13 +719,34 @@
     // @fragment
     // fn fragShader(arg: MyInputs) -> @location(0) f32 { return 1.0; }
 
-    auto* s = Structure(
-        "MyInputs", {Member("position", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
-                     Member("front_facing", ty.bool_(), {Builtin(ast::BuiltinValue::kFrontFacing)}),
-                     Member("sample_index", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)}),
-                     Member("sample_mask", ty.u32(), {Builtin(ast::BuiltinValue::kSampleMask)})});
-    Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1_f)},
-         {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+    auto* s = Structure("MyInputs", utils::Vector{
+                                        Member("position", ty.vec4<f32>(),
+                                               utils::Vector{
+                                                   Builtin(ast::BuiltinValue::kPosition),
+                                               }),
+                                        Member("front_facing", ty.bool_(),
+                                               utils::Vector{
+                                                   Builtin(ast::BuiltinValue::kFrontFacing),
+                                               }),
+                                        Member("sample_index", ty.u32(),
+                                               utils::Vector{
+                                                   Builtin(ast::BuiltinValue::kSampleIndex),
+                                               }),
+                                        Member("sample_mask", ty.u32(),
+                                               utils::Vector{
+                                                   Builtin(ast::BuiltinValue::kSampleMask),
+                                               }),
+                                    });
+    Func("fragShader", utils::Vector{Param("arg", ty.Of(s))}, ty.f32(),
+         utils::Vector{
+             Return(1_f),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         utils::Vector{
+             Location(0),
+         });
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
@@ -581,10 +756,20 @@
     //   @builtin(front_facing) is_front: i32;
     // ) -> @location(0) f32 { return 1.0; }
 
-    auto* is_front =
-        Param("is_front", ty.i32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kFrontFacing)});
-    Func("fs_main", {is_front}, ty.f32(), {Return(1_f)}, {Stage(ast::PipelineStage::kFragment)},
-         {Location(0)});
+    auto* is_front = Param("is_front", ty.i32(),
+                           utils::Vector{
+                               Builtin(Source{{12, 34}}, ast::BuiltinValue::kFrontFacing),
+                           });
+    Func("fs_main", utils::Vector{is_front}, ty.f32(),
+         utils::Vector{
+             Return(1_f),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         utils::Vector{
+             Location(0),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(front_facing) must be 'bool'");
@@ -597,11 +782,23 @@
     // @fragment
     // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
 
-    auto* s = Structure(
-        "MyInputs",
-        {Member("pos", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kFrontFacing)})});
-    Func("fragShader", {Param("is_front", ty.Of(s))}, ty.f32(), {Return(1_f)},
-         {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+    auto* s = Structure("MyInputs",
+                        utils::Vector{
+                            Member("pos", ty.f32(),
+                                   utils::Vector{
+                                       Builtin(Source{{12, 34}}, ast::BuiltinValue::kFrontFacing),
+                                   }),
+                        });
+    Func("fragShader", utils::Vector{Param("is_front", ty.Of(s))}, ty.f32(),
+         utils::Vector{
+             Return(1_f),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         utils::Vector{
+             Location(0),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(front_facing) must be 'bool'");
@@ -900,13 +1097,18 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(Expr(1_f));
+        params.Push(Expr(1_f));
     }
     auto* builtin = Call(name, params);
-    Func("func", {}, ty.void_(), {CallStmt(builtin)},
-         {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(builtin),
+         },
+         utils::Vector{
+             create<ast::StageAttribute>(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     EXPECT_TRUE(TypeOf(builtin)->Is<sem::F32>());
@@ -916,13 +1118,18 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(vec2<f32>(1_f, 1_f));
+        params.Push(vec2<f32>(1_f, 1_f));
     }
     auto* builtin = Call(name, params);
-    Func("func", {}, ty.void_(), {CallStmt(builtin)},
-         {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(builtin),
+         },
+         utils::Vector{
+             create<ast::StageAttribute>(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     EXPECT_TRUE(TypeOf(builtin)->is_float_vector());
@@ -932,13 +1139,18 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(vec3<f32>(1_f, 1_f, 1_f));
+        params.Push(vec3<f32>(1_f, 1_f, 1_f));
     }
     auto* builtin = Call(name, params);
-    Func("func", {}, ty.void_(), {CallStmt(builtin)},
-         {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(builtin),
+         },
+         utils::Vector{
+             create<ast::StageAttribute>(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     EXPECT_TRUE(TypeOf(builtin)->is_float_vector());
@@ -948,13 +1160,18 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(vec4<f32>(1_f, 1_f, 1_f, 1_f));
+        params.Push(vec4<f32>(1_f, 1_f, 1_f, 1_f));
     }
     auto* builtin = Call(name, params);
-    Func("func", {}, ty.void_(), {CallStmt(builtin)},
-         {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(builtin),
+         },
+         utils::Vector{
+             create<ast::StageAttribute>(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     EXPECT_TRUE(TypeOf(builtin)->is_float_vector());
@@ -1010,9 +1227,9 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(Construct<u32>(1_i));
+        params.Push(Construct<u32>(1_i));
     }
     auto* builtin = Call(name, params);
     WrapInFunction(builtin);
@@ -1025,9 +1242,9 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(vec2<u32>(1_u, 1_u));
+        params.Push(vec2<u32>(1_u, 1_u));
     }
     auto* builtin = Call(name, params);
     WrapInFunction(builtin);
@@ -1040,9 +1257,9 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(vec3<u32>(1_u, 1_u, 1_u));
+        params.Push(vec3<u32>(1_u, 1_u, 1_u));
     }
     auto* builtin = Call(name, params);
     WrapInFunction(builtin);
@@ -1055,9 +1272,9 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(vec4<u32>(1_u, 1_u, 1_u, 1_u));
+        params.Push(vec4<u32>(1_u, 1_u, 1_u, 1_u));
     }
     auto* builtin = Call(name, params);
     WrapInFunction(builtin);
@@ -1070,9 +1287,9 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(Construct<i32>(1_i));
+        params.Push(Construct<i32>(1_i));
     }
     auto* builtin = Call(name, params);
     WrapInFunction(builtin);
@@ -1085,9 +1302,9 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(vec2<i32>(1_i, 1_i));
+        params.Push(vec2<i32>(1_i, 1_i));
     }
     auto* builtin = Call(name, params);
     WrapInFunction(builtin);
@@ -1100,9 +1317,9 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(vec3<i32>(1_i, 1_i, 1_i));
+        params.Push(vec3<i32>(1_i, 1_i, 1_i));
     }
     auto* builtin = Call(name, params);
     WrapInFunction(builtin);
@@ -1115,9 +1332,9 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(vec4<i32>(1_i, 1_i, 1_i, 1_i));
+        params.Push(vec4<i32>(1_i, 1_i, 1_i, 1_i));
     }
     auto* builtin = Call(name, params);
     WrapInFunction(builtin);
@@ -1142,9 +1359,9 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(vec2<bool>(true, true));
+        params.Push(vec2<bool>(true, true));
     }
     auto* builtin = Call(name, params);
     WrapInFunction(builtin);
@@ -1156,9 +1373,9 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(vec3<bool>(true, true, true));
+        params.Push(vec3<bool>(true, true, true));
     }
     auto* builtin = Call(name, params);
     WrapInFunction(builtin);
@@ -1170,9 +1387,9 @@
     std::string name = std::get<0>(GetParam());
     uint32_t num_params = std::get<1>(GetParam());
 
-    ast::ExpressionList params;
+    utils::Vector<const ast::Expression*, 8> params;
     for (uint32_t i = 0; i < num_params; ++i) {
-        params.push_back(vec4<bool>(true, true, true, true));
+        params.Push(vec4<bool>(true, true, true, true));
     }
     auto* builtin = Call(name, params);
     WrapInFunction(builtin);
diff --git a/src/tint/resolver/call_test.cc b/src/tint/resolver/call_test.cc
index 3020254..37aaffa 100644
--- a/src/tint/resolver/call_test.cc
+++ b/src/tint/resolver/call_test.cc
@@ -84,14 +84,14 @@
 TEST_F(ResolverCallTest, Valid) {
     Enable(ast::Extension::kF16);
 
-    ast::ParameterList params;
-    ast::ExpressionList args;
+    utils::Vector<const ast::Parameter*, 4> params;
+    utils::Vector<const ast::Expression*, 4> args;
     for (auto& p : all_param_types) {
-        params.push_back(Param(Sym(), p.create_type(*this)));
-        args.push_back(p.create_value(*this, 0));
+        params.Push(Param(Sym(), p.create_type(*this)));
+        args.Push(p.create_value(*this, 0));
     }
 
-    auto* func = Func("foo", std::move(params), ty.f32(), {Return(1.23_f)});
+    auto* func = Func("foo", std::move(params), ty.f32(), utils::Vector{Return(1.23_f)});
     auto* call_expr = Call("foo", std::move(args));
     WrapInFunction(call_expr);
 
@@ -104,8 +104,8 @@
 
 TEST_F(ResolverCallTest, OutOfOrder) {
     auto* call_expr = Call("b");
-    Func("a", {}, ty.void_(), {CallStmt(call_expr)});
-    auto* b = Func("b", {}, ty.void_(), {});
+    Func("a", utils::Empty, ty.void_(), utils::Vector{CallStmt(call_expr)});
+    auto* b = Func("b", utils::Empty, ty.void_(), utils::Empty);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
diff --git a/src/tint/resolver/call_validation_test.cc b/src/tint/resolver/call_validation_test.cc
index 9625be7..4243093 100644
--- a/src/tint/resolver/call_validation_test.cc
+++ b/src/tint/resolver/call_validation_test.cc
@@ -26,7 +26,15 @@
 using ResolverCallValidationTest = ResolverTest;
 
 TEST_F(ResolverCallValidationTest, TooFewArgs) {
-    Func("foo", {Param(Sym(), ty.i32()), Param(Sym(), ty.f32())}, ty.void_(), {Return()});
+    Func("foo",
+         utils::Vector{
+             Param(Sym(), ty.i32()),
+             Param(Sym(), ty.f32()),
+         },
+         ty.void_(),
+         utils::Vector{
+             Return(),
+         });
     auto* call = Call(Source{{12, 34}}, "foo", 1_i);
     WrapInFunction(call);
 
@@ -35,7 +43,15 @@
 }
 
 TEST_F(ResolverCallValidationTest, TooManyArgs) {
-    Func("foo", {Param(Sym(), ty.i32()), Param(Sym(), ty.f32())}, ty.void_(), {Return()});
+    Func("foo",
+         utils::Vector{
+             Param(Sym(), ty.i32()),
+             Param(Sym(), ty.f32()),
+         },
+         ty.void_(),
+         utils::Vector{
+             Return(),
+         });
     auto* call = Call(Source{{12, 34}}, "foo", 1_i, 1_f, 1_f);
     WrapInFunction(call);
 
@@ -44,7 +60,15 @@
 }
 
 TEST_F(ResolverCallValidationTest, MismatchedArgs) {
-    Func("foo", {Param(Sym(), ty.i32()), Param(Sym(), ty.f32())}, ty.void_(), {Return()});
+    Func("foo",
+         utils::Vector{
+             Param(Sym(), ty.i32()),
+             Param(Sym(), ty.f32()),
+         },
+         ty.void_(),
+         utils::Vector{
+             Return(),
+         });
     auto* call = Call("foo", Expr(Source{{12, 34}}, true), 1_f);
     WrapInFunction(call);
 
@@ -58,10 +82,14 @@
     // fn func() -> f32 { return 1.0; }
     // fn main() {func(); return; }
 
-    Func("func", {}, ty.f32(), {Return(Expr(1_f))}, {});
+    Func("func", utils::Empty, ty.f32(),
+         utils::Vector{
+             Return(Expr(1_f)),
+         },
+         utils::Empty);
 
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              CallStmt(Source{{12, 34}}, Call("func")),
              Return(),
          });
@@ -76,9 +104,9 @@
     //   foo(&z);
     // }
     auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
-    Func("foo", {param}, ty.void_(), {});
-    Func("main", {}, ty.void_(),
-         {
+    Func("foo", utils::Vector{param}, ty.void_(), utils::Empty);
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("z", ty.i32(), Expr(1_i))),
              CallStmt(Call("foo", AddressOf(Source{{12, 34}}, Expr("z")))),
          });
@@ -93,9 +121,9 @@
     //   foo(&z);
     // }
     auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
-    Func("foo", {param}, ty.void_(), {});
-    Func("main", {}, ty.void_(),
-         {
+    Func("foo", utils::Vector{param}, ty.void_(), utils::Empty);
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Let("z", ty.i32(), Expr(1_i))),
              CallStmt(Call("foo", AddressOf(Expr(Source{{12, 34}}, "z")))),
          });
@@ -111,11 +139,13 @@
     //   var v: S;
     //   foo(&v.m);
     // }
-    auto* S = Structure("S", {Member("m", ty.i32())});
+    auto* S = Structure("S", utils::Vector{
+                                 Member("m", ty.i32()),
+                             });
     auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
-    Func("foo", {param}, ty.void_(), {});
-    Func("main", {}, ty.void_(),
-         {
+    Func("foo", utils::Vector{param}, ty.void_(), utils::Empty);
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("v", ty.Of(S))),
              CallStmt(Call("foo", AddressOf(Source{{12, 34}}, MemberAccessor("v", "m")))),
          });
@@ -133,11 +163,13 @@
     //   let v: S = S();
     //   foo(&v.m);
     // }
-    auto* S = Structure("S", {Member("m", ty.i32())});
+    auto* S = Structure("S", utils::Vector{
+                                 Member("m", ty.i32()),
+                             });
     auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
-    Func("foo", {param}, ty.void_(), {});
-    Func("main", {}, ty.void_(),
-         {
+    Func("foo", utils::Vector{param}, ty.void_(), utils::Empty);
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Let("v", ty.Of(S), Construct(ty.Of(S)))),
              CallStmt(Call("foo", AddressOf(MemberAccessor(Source{{12, 34}}, "v", "m")))),
          });
@@ -151,9 +183,19 @@
     // fn bar(p: ptr<function, i32>) {
     // foo(p);
     // }
-    Func("foo", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))}, ty.void_(), {});
-    Func("bar", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))}, ty.void_(),
-         ast::StatementList{CallStmt(Call("foo", Expr("p")))});
+    Func("foo",
+         utils::Vector{
+             Param("p", ty.pointer<i32>(ast::StorageClass::kFunction)),
+         },
+         ty.void_(), utils::Empty);
+    Func("bar",
+         utils::Vector{
+             Param("p", ty.pointer<i32>(ast::StorageClass::kFunction)),
+         },
+         ty.void_(),
+         utils::Vector{
+             CallStmt(Call("foo", Expr("p"))),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -168,15 +210,25 @@
     //   var v: i32;
     //   bar(&v);
     // }
-    Func("foo", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))}, ty.void_(), {});
-    Func("bar", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))}, ty.void_(),
-         ast::StatementList{CallStmt(Call("foo", Expr("p")))});
-    Func("main", {}, ty.void_(),
-         {
+    Func("foo",
+         utils::Vector{
+             Param("p", ty.pointer<i32>(ast::StorageClass::kFunction)),
+         },
+         ty.void_(), utils::Empty);
+    Func("bar",
+         utils::Vector{
+             Param("p", ty.pointer<i32>(ast::StorageClass::kFunction)),
+         },
+         ty.void_(),
+         utils::Vector{
+             CallStmt(Call("foo", Expr("p"))),
+         });
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("v", ty.i32(), Expr(1_i))),
              CallStmt(Call("foo", AddressOf(Expr("v")))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -191,17 +243,21 @@
     //   let p: ptr<function, i32> = &v;
     //   var c: i32 = x(p);
     // }
-    Func("x", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))}, ty.void_(), {});
+    Func("x",
+         utils::Vector{
+             Param("p", ty.pointer<i32>(ast::StorageClass::kFunction)),
+         },
+         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)));
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(v),
              Decl(p),
              Decl(c),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
     EXPECT_FALSE(r()->Resolve());
@@ -218,16 +274,20 @@
     // fn main() {
     //   var c: i32 = foo(p);
     // }
-    Func("foo", {Param("p", ty.pointer<i32>(ast::StorageClass::kPrivate))}, ty.void_(), {});
+    Func("foo",
+         utils::Vector{
+             Param("p", ty.pointer<i32>(ast::StorageClass::kPrivate)),
+         },
+         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)));
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(p),
              Decl(c),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
     EXPECT_FALSE(r()->Resolve());
@@ -242,7 +302,10 @@
     //   v();
     // }
     GlobalVar("v", ty.i32(), ast::StorageClass::kPrivate);
-    Func("f", {}, ty.void_(), {CallStmt(Call(Source{{12, 34}}, "v"))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call(Source{{12, 34}}, "v")),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), R"(error: cannot call variable 'v'
@@ -255,9 +318,9 @@
     //   var x : i32;
     //   x();
     // }
-    Func("x", {}, ty.void_(), {});
-    Func("f", {}, ty.void_(),
-         {
+    Func("x", utils::Empty, ty.void_(), utils::Empty);
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var(Source{{56, 78}}, "x", ty.i32())),
              CallStmt(Call(Source{{12, 34}}, "x")),
          });
diff --git a/src/tint/resolver/compound_statement_test.cc b/src/tint/resolver/compound_statement_test.cc
index d962b3e..0a96ced 100644
--- a/src/tint/resolver/compound_statement_test.cc
+++ b/src/tint/resolver/compound_statement_test.cc
@@ -35,7 +35,7 @@
     //   var x : 32;
     // }
     auto* stmt = Decl(Var("x", ty.i32()));
-    auto* f = Func("F", {}, ty.void_(), {stmt});
+    auto* f = Func("F", utils::Empty, ty.void_(), utils::Vector{stmt});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -57,7 +57,7 @@
     // }
     auto* stmt = Decl(Var("x", ty.i32()));
     auto* block = Block(stmt);
-    auto* f = Func("F", {}, ty.void_(), {block});
+    auto* f = Func("F", utils::Empty, ty.void_(), utils::Vector{block});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -92,7 +92,7 @@
     auto* brk = Break();
     auto* stmt = Ignore(1_i);
     auto* loop = Loop(Block(brk), Block(stmt));
-    auto* f = Func("F", {}, ty.void_(), {loop});
+    auto* f = Func("F", utils::Empty, ty.void_(), utils::Vector{loop});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -155,7 +155,7 @@
     // }
     auto* brk = Break();
     auto* loop = Loop(Block(brk), Block());
-    Func("F", {}, ty.void_(), {loop});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{loop});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -186,7 +186,7 @@
     auto* stmt = Return();
     auto* body = Block(stmt);
     auto* for_ = For(init, cond, cont, body);
-    auto* f = Func("F", {}, ty.void_(), {for_});
+    auto* f = Func("F", utils::Empty, ty.void_(), utils::Vector{for_});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -250,7 +250,7 @@
     auto* stmt = Return();
     auto* body = Block(stmt);
     auto* while_ = While(cond, body);
-    auto* f = Func("W", {}, ty.void_(), {while_});
+    auto* f = Func("W", utils::Empty, ty.void_(), utils::Vector{while_});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
diff --git a/src/tint/resolver/const_eval.cc b/src/tint/resolver/const_eval.cc
index dec4992..5144e63 100644
--- a/src/tint/resolver/const_eval.cc
+++ b/src/tint/resolver/const_eval.cc
@@ -458,7 +458,7 @@
 }
 
 /// TransformElements constructs a new constant by applying the transformation function 'f' on each
-/// of the most deeply nested elements of 'cs'.
+/// of the most deeply nested elements of 'cs'. Assumes that all constants are the same type.
 template <typename F, typename... CONSTANTS>
 const Constant* TransformElements(ProgramBuilder& builder, F&& f, CONSTANTS&&... cs) {
     uint32_t n = 0;
@@ -470,17 +470,51 @@
     utils::Vector<const sem::Constant*, 8> els;
     els.Reserve(n);
     for (uint32_t i = 0; i < n; i++) {
-        els.Push(TransformElements(builder, f, cs->Index(i)...));
+        els.Push(TransformElements(builder, std::forward<F>(f), cs->Index(i)...));
     }
     return CreateComposite(builder, ty, std::move(els));
 }
 
+/// TransformBinaryElements constructs a new constant by applying the transformation function 'f' on
+/// each of the most deeply nested elements of both `c0` and `c1`. Unlike TransformElements, this
+/// function handles the constants being of different types, e.g. vector-scalar, scalar-vector.
+template <typename F>
+const Constant* TransformBinaryElements(ProgramBuilder& builder,
+                                        F&& f,
+                                        const sem::Constant* c0,
+                                        const sem::Constant* c1) {
+    uint32_t n0 = 0, n1 = 0;
+    sem::Type::ElementOf(c0->Type(), &n0);
+    sem::Type::ElementOf(c1->Type(), &n1);
+    uint32_t max_n = std::max(n0, n1);
+    // If arity of both constants is 1, invoke callback
+    if (max_n == 1u) {
+        return f(c0, c1);
+    }
+
+    utils::Vector<const sem::Constant*, 8> els;
+    els.Reserve(max_n);
+    for (uint32_t i = 0; i < max_n; i++) {
+        auto nested_or_self = [&](auto& c, uint32_t num_elems) {
+            if (num_elems == 1) {
+                return c;
+            }
+            return c->Index(i);
+        };
+        els.Push(TransformBinaryElements(builder, std::forward<F>(f), nested_or_self(c0, n0),
+                                         nested_or_self(c1, n1)));
+    }
+    // Use larger type
+    auto* ty = n0 > n1 ? c0->Type() : c1->Type();
+    return CreateComposite(builder, ty, std::move(els));
+}
+
 }  // namespace
 
 ConstEval::ConstEval(ProgramBuilder& b) : builder(b) {}
 
-const sem::Constant* ConstEval::Literal(const sem::Type* ty,
-                                        const ast::LiteralExpression* literal) {
+ConstEval::ConstantResult ConstEval::Literal(const sem::Type* ty,
+                                             const ast::LiteralExpression* literal) {
     return Switch(
         literal,
         [&](const ast::BoolLiteralExpression* lit) {
@@ -510,9 +544,9 @@
         });
 }
 
-const sem::Constant* ConstEval::ArrayOrStructCtor(
+ConstEval::ConstantResult ConstEval::ArrayOrStructCtor(
     const sem::Type* ty,
-    utils::ConstVectorRef<const sem::Expression*> args) {
+    utils::VectorRef<const sem::Expression*> args) {
     if (args.IsEmpty()) {
         return ZeroValue(builder, ty);
     }
@@ -531,59 +565,59 @@
     return CreateComposite(builder, ty, std::move(els));
 }
 
-const sem::Constant* ConstEval::Conv(const sem::Type* ty,
-                                     utils::ConstVectorRef<const sem::Expression*> args) {
+ConstEval::ConstantResult ConstEval::Conv(const sem::Type* ty,
+                                          utils::VectorRef<const sem::Constant*> args,
+                                          const Source& source) {
     uint32_t el_count = 0;
     auto* el_ty = sem::Type::ElementOf(ty, &el_count);
     if (!el_ty) {
         return nullptr;
     }
 
-    auto& src = args[0]->Declaration()->source;
-    auto* arg = args[0]->ConstantValue();
-    if (!arg) {
+    if (!args[0]) {
         return nullptr;  // Single argument is not constant.
     }
 
-    if (auto conv = Convert(ty, arg, src)) {
+    if (auto conv = Convert(ty, args[0], source)) {
         return conv.Get();
     }
 
     return nullptr;
 }
 
-const sem::Constant* ConstEval::Zero(const sem::Type* ty,
-                                     utils::ConstVectorRef<const sem::Expression*>) {
+ConstEval::ConstantResult ConstEval::Zero(const sem::Type* ty,
+                                          utils::VectorRef<const sem::Constant*>,
+                                          const Source&) {
     return ZeroValue(builder, ty);
 }
 
-const sem::Constant* ConstEval::Identity(const sem::Type*,
-                                         utils::ConstVectorRef<const sem::Expression*> args) {
-    return args[0]->ConstantValue();
+ConstEval::ConstantResult ConstEval::Identity(const sem::Type*,
+                                              utils::VectorRef<const sem::Constant*> args,
+                                              const Source&) {
+    return args[0];
 }
 
-const sem::Constant* ConstEval::VecSplat(const sem::Type* ty,
-                                         utils::ConstVectorRef<const sem::Expression*> args) {
-    if (auto* arg = args[0]->ConstantValue()) {
+ConstEval::ConstantResult ConstEval::VecSplat(const sem::Type* ty,
+                                              utils::VectorRef<const sem::Constant*> args,
+                                              const Source&) {
+    if (auto* arg = args[0]) {
         return builder.create<Splat>(ty, arg, static_cast<const sem::Vector*>(ty)->Width());
     }
     return nullptr;
 }
 
-const sem::Constant* ConstEval::VecCtorS(const sem::Type* ty,
-                                         utils::ConstVectorRef<const sem::Expression*> args) {
-    utils::Vector<const sem::Constant*, 4> els;
-    for (auto* arg : args) {
-        els.Push(arg->ConstantValue());
-    }
-    return CreateComposite(builder, ty, std::move(els));
+ConstEval::ConstantResult ConstEval::VecCtorS(const sem::Type* ty,
+                                              utils::VectorRef<const sem::Constant*> args,
+                                              const Source&) {
+    return CreateComposite(builder, ty, args);
 }
 
-const sem::Constant* ConstEval::VecCtorM(const sem::Type* ty,
-                                         utils::ConstVectorRef<const sem::Expression*> args) {
+ConstEval::ConstantResult ConstEval::VecCtorM(const sem::Type* ty,
+                                              utils::VectorRef<const sem::Constant*> args,
+                                              const Source&) {
     utils::Vector<const sem::Constant*, 4> els;
     for (auto* arg : args) {
-        auto* val = arg->ConstantValue();
+        auto* val = arg;
         if (!val) {
             return nullptr;
         }
@@ -604,8 +638,9 @@
     return CreateComposite(builder, ty, std::move(els));
 }
 
-const sem::Constant* ConstEval::MatCtorS(const sem::Type* ty,
-                                         utils::ConstVectorRef<const sem::Expression*> args) {
+ConstEval::ConstantResult ConstEval::MatCtorS(const sem::Type* ty,
+                                              utils::VectorRef<const sem::Constant*> args,
+                                              const Source&) {
     auto* m = static_cast<const sem::Matrix*>(ty);
 
     utils::Vector<const sem::Constant*, 4> els;
@@ -613,32 +648,29 @@
         utils::Vector<const sem::Constant*, 4> column;
         for (uint32_t r = 0; r < m->rows(); r++) {
             auto i = r + c * m->rows();
-            column.Push(args[i]->ConstantValue());
+            column.Push(args[i]);
         }
         els.Push(CreateComposite(builder, m->ColumnType(), std::move(column)));
     }
     return CreateComposite(builder, ty, std::move(els));
 }
 
-const sem::Constant* ConstEval::MatCtorV(const sem::Type* ty,
-                                         utils::ConstVectorRef<const sem::Expression*> args) {
-    utils::Vector<const sem::Constant*, 4> els;
-    for (auto* arg : args) {
-        els.Push(arg->ConstantValue());
-    }
-    return CreateComposite(builder, ty, std::move(els));
+ConstEval::ConstantResult ConstEval::MatCtorV(const sem::Type* ty,
+                                              utils::VectorRef<const sem::Constant*> args,
+                                              const Source&) {
+    return CreateComposite(builder, ty, args);
 }
 
-const sem::Constant* ConstEval::Index(const sem::Expression* obj_expr,
-                                      const sem::Expression* idx_expr) {
+ConstEval::ConstantResult ConstEval::Index(const sem::Expression* obj_expr,
+                                           const sem::Expression* idx_expr) {
     auto obj_val = obj_expr->ConstantValue();
     if (!obj_val) {
-        return {};
+        return nullptr;
     }
 
     auto idx_val = idx_expr->ConstantValue();
     if (!idx_val) {
-        return {};
+        return nullptr;
     }
 
     uint32_t el_count = 0;
@@ -657,18 +689,18 @@
     return obj_val->Index(static_cast<size_t>(idx));
 }
 
-const sem::Constant* ConstEval::MemberAccess(const sem::Expression* obj_expr,
-                                             const sem::StructMember* member) {
+ConstEval::ConstantResult ConstEval::MemberAccess(const sem::Expression* obj_expr,
+                                                  const sem::StructMember* member) {
     auto obj_val = obj_expr->ConstantValue();
     if (!obj_val) {
-        return {};
+        return nullptr;
     }
     return obj_val->Index(static_cast<size_t>(member->Index()));
 }
 
-const sem::Constant* ConstEval::Swizzle(const sem::Type* ty,
-                                        const sem::Expression* vec_expr,
-                                        utils::ConstVectorRef<uint32_t> indices) {
+ConstEval::ConstantResult ConstEval::Swizzle(const sem::Type* ty,
+                                             const sem::Expression* vec_expr,
+                                             utils::VectorRef<uint32_t> indices) {
     auto* vec_val = vec_expr->ConstantValue();
     if (!vec_val) {
         return nullptr;
@@ -682,30 +714,32 @@
     }
 }
 
-const sem::Constant* ConstEval::Bitcast(const sem::Type*, const sem::Expression*) {
+ConstEval::ConstantResult ConstEval::Bitcast(const sem::Type*, const sem::Expression*) {
     // TODO(crbug.com/tint/1581): Implement @const intrinsics
     return nullptr;
 }
 
-const sem::Constant* ConstEval::OpComplement(const sem::Type*,
-                                             utils::ConstVectorRef<const sem::Expression*> args) {
+ConstEval::ConstantResult ConstEval::OpComplement(const sem::Type*,
+                                                  utils::VectorRef<const sem::Constant*> args,
+                                                  const Source&) {
     auto transform = [&](const sem::Constant* c) {
         auto create = [&](auto i) {
             return CreateElement(builder, c->Type(), decltype(i)(~i.value));
         };
         return Dispatch_ia_iu32(create, c);
     };
-    return TransformElements(builder, transform, args[0]->ConstantValue());
+    return TransformElements(builder, transform, args[0]);
 }
 
-const sem::Constant* ConstEval::OpMinus(const sem::Type*,
-                                        utils::ConstVectorRef<const sem::Expression*> args) {
+ConstEval::ConstantResult ConstEval::OpMinus(const sem::Type*,
+                                             utils::VectorRef<const sem::Constant*> args,
+                                             const Source&) {
     auto transform = [&](const sem::Constant* c) {
-        auto create = [&](auto i) {  //
-                                     // For signed integrals, avoid C++ UB by not negating the
-                                     // smallest negative number. In WGSL, this operation is well
-                                     // defined to return the same value, see:
-                                     // https://gpuweb.github.io/gpuweb/wgsl/#arithmetic-expr.
+        auto create = [&](auto i) {
+            // For signed integrals, avoid C++ UB by not negating the
+            // smallest negative number. In WGSL, this operation is well
+            // defined to return the same value, see:
+            // https://gpuweb.github.io/gpuweb/wgsl/#arithmetic-expr.
             using T = UnwrapNumber<decltype(i)>;
             if constexpr (std::is_integral_v<T>) {
                 auto v = i.value;
@@ -719,23 +753,69 @@
         };
         return Dispatch_fia_fi32_f16(create, c);
     };
-    return TransformElements(builder, transform, args[0]->ConstantValue());
+    return TransformElements(builder, transform, args[0]);
 }
 
-const sem::Constant* ConstEval::atan2(const sem::Type*,
-                                      utils::ConstVectorRef<const sem::Expression*> args) {
+ConstEval::ConstantResult ConstEval::OpPlus(const sem::Type* ty,
+                                            utils::VectorRef<const sem::Constant*> args,
+                                            const Source& source) {
+    auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
+        auto create = [&](auto i, auto j) -> const Constant* {
+            using NumberT = decltype(i);
+            using T = UnwrapNumber<NumberT>;
+
+            auto add_values = [](T lhs, T rhs) {
+                if constexpr (std::is_integral_v<T> && std::is_signed_v<T>) {
+                    // Ensure no UB for signed overflow
+                    using UT = std::make_unsigned_t<T>;
+                    return static_cast<T>(static_cast<UT>(lhs) + static_cast<UT>(rhs));
+                } else {
+                    return lhs + rhs;
+                }
+            };
+
+            NumberT result;
+            if constexpr (std::is_same_v<NumberT, AInt> || std::is_same_v<NumberT, AFloat>) {
+                // Check for over/underflow for abstract values
+                if (auto r = CheckedAdd(i, j)) {
+                    result = r->value;
+                } else {
+                    AddError("'" + std::to_string(add_values(i.value, j.value)) +
+                                 "' cannot be represented as '" +
+                                 ty->FriendlyName(builder.Symbols()) + "'",
+                             source);
+                    return nullptr;
+                }
+            } else {
+                result = add_values(i.value, j.value);
+            }
+            return CreateElement(builder, c0->Type(), result);
+        };
+        return Dispatch_fia_fiu32_f16(create, c0, c1);
+    };
+
+    auto r = TransformBinaryElements(builder, transform, args[0], args[1]);
+    if (builder.Diagnostics().contains_errors()) {
+        return utils::Failure;
+    }
+    return r;
+}
+
+ConstEval::ConstantResult ConstEval::atan2(const sem::Type*,
+                                           utils::VectorRef<const sem::Constant*> args,
+                                           const Source&) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
         auto create = [&](auto i, auto j) {
             return CreateElement(builder, c0->Type(), decltype(i)(std::atan2(i.value, j.value)));
         };
         return Dispatch_fa_f32_f16(create, c0, c1);
     };
-    return TransformElements(builder, transform, args[0]->ConstantValue(),
-                             args[1]->ConstantValue());
+    return TransformElements(builder, transform, args[0], args[1]);
 }
 
-const sem::Constant* ConstEval::clamp(const sem::Type*,
-                                      utils::ConstVectorRef<const sem::Expression*> args) {
+ConstEval::ConstantResult ConstEval::clamp(const sem::Type*,
+                                           utils::VectorRef<const sem::Constant*> args,
+                                           const Source&) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1,
                          const sem::Constant* c2) {
         auto create = [&](auto e, auto low, auto high) {
@@ -744,8 +824,7 @@
         };
         return Dispatch_fia_fiu32_f16(create, c0, c1, c2);
     };
-    return TransformElements(builder, transform, args[0]->ConstantValue(), args[1]->ConstantValue(),
-                             args[2]->ConstantValue());
+    return TransformElements(builder, transform, args[0], args[1], args[2]);
 }
 
 utils::Result<const sem::Constant*> ConstEval::Convert(const sem::Type* target_ty,
diff --git a/src/tint/resolver/const_eval.h b/src/tint/resolver/const_eval.h
index 4d299f4..dbc3dbd 100644
--- a/src/tint/resolver/const_eval.h
+++ b/src/tint/resolver/const_eval.h
@@ -44,10 +44,6 @@
 /// before calling a method to evaluate an expression's value.
 class ConstEval {
   public:
-    /// Typedef for a constant evaluation function
-    using Function = const sem::Constant* (
-        ConstEval::*)(const sem::Type* result_ty, utils::ConstVectorRef<const sem::Expression*>);
-
     /// The result type of a method that may raise a diagnostic error and the caller should abort
     /// resolving. Can be one of three distinct values:
     /// * A non-null sem::Constant pointer. Returned when a expression resolves to a creation time
@@ -59,6 +55,11 @@
     ///   resolving.
     using ConstantResult = utils::Result<const sem::Constant*>;
 
+    /// Typedef for a constant evaluation function
+    using Function = ConstantResult (ConstEval::*)(const sem::Type* result_ty,
+                                                   utils::VectorRef<const sem::Constant*>,
+                                                   const Source&);
+
     /// Constructor
     /// @param b the program builder
     explicit ConstEval(ProgramBuilder& b);
@@ -70,37 +71,37 @@
     /// @param ty the target type - must be an array or constructor
     /// @param args the input arguments
     /// @return the constructed value, or null if the value cannot be calculated
-    const sem::Constant* ArrayOrStructCtor(const sem::Type* ty,
-                                           utils::ConstVectorRef<const sem::Expression*> args);
+    ConstantResult ArrayOrStructCtor(const sem::Type* ty,
+                                     utils::VectorRef<const sem::Expression*> args);
 
     /// @param ty the target type
     /// @param expr the input expression
     /// @return the bit-cast of the given expression to the given type, or null if the value cannot
     ///         be calculated
-    const sem::Constant* Bitcast(const sem::Type* ty, const sem::Expression* expr);
+    ConstantResult Bitcast(const sem::Type* ty, const sem::Expression* expr);
 
     /// @param obj the object being indexed
     /// @param idx the index expression
     /// @return the result of the index, or null if the value cannot be calculated
-    const sem::Constant* Index(const sem::Expression* obj, const sem::Expression* idx);
+    ConstantResult Index(const sem::Expression* obj, const sem::Expression* idx);
 
     /// @param ty the result type
     /// @param lit the literal AST node
     /// @return the constant value of the literal
-    const sem::Constant* Literal(const sem::Type* ty, const ast::LiteralExpression* lit);
+    ConstantResult Literal(const sem::Type* ty, const ast::LiteralExpression* lit);
 
     /// @param obj the object being accessed
     /// @param member the member
     /// @return the result of the member access, or null if the value cannot be calculated
-    const sem::Constant* MemberAccess(const sem::Expression* obj, const sem::StructMember* member);
+    ConstantResult MemberAccess(const sem::Expression* obj, const sem::StructMember* member);
 
     /// @param ty the result type
     /// @param vector the vector being swizzled
     /// @param indices the swizzle indices
     /// @return the result of the swizzle, or null if the value cannot be calculated
-    const sem::Constant* Swizzle(const sem::Type* ty,
-                                 const sem::Expression* vector,
-                                 utils::ConstVectorRef<uint32_t> indices);
+    ConstantResult Swizzle(const sem::Type* ty,
+                           const sem::Expression* vector,
+                           utils::VectorRef<uint32_t> indices);
 
     /// Convert the `value` to `target_type`
     /// @param ty the result type
@@ -116,76 +117,109 @@
     /// Type conversion
     /// @param ty the result type
     /// @param args the input arguments
+    /// @param source the source location of the conversion
     /// @return the converted value, or null if the value cannot be calculated
-    const sem::Constant* Conv(const sem::Type* ty,
-                              utils::ConstVectorRef<const sem::Expression*> args);
+    ConstantResult Conv(const sem::Type* ty,
+                        utils::VectorRef<const sem::Constant*> args,
+                        const Source& source);
 
     /// Zero value type constructor
     /// @param ty the result type
     /// @param args the input arguments (no arguments provided)
+    /// @param source the source location of the conversion
     /// @return the constructed value, or null if the value cannot be calculated
-    const sem::Constant* Zero(const sem::Type* ty,
-                              utils::ConstVectorRef<const sem::Expression*> args);
+    ConstantResult Zero(const sem::Type* ty,
+                        utils::VectorRef<const sem::Constant*> args,
+                        const Source& source);
 
     /// Identity value type constructor
     /// @param ty the result type
     /// @param args the input arguments
+    /// @param source the source location of the conversion
     /// @return the constructed value, or null if the value cannot be calculated
-    const sem::Constant* Identity(const sem::Type* ty,
-                                  utils::ConstVectorRef<const sem::Expression*> args);
+    ConstantResult Identity(const sem::Type* ty,
+                            utils::VectorRef<const sem::Constant*> args,
+                            const Source& source);
 
     /// Vector splat constructor
     /// @param ty the vector type
     /// @param args the input arguments
+    /// @param source the source location of the conversion
     /// @return the constructed value, or null if the value cannot be calculated
-    const sem::Constant* VecSplat(const sem::Type* ty,
-                                  utils::ConstVectorRef<const sem::Expression*> args);
+    ConstantResult VecSplat(const sem::Type* ty,
+                            utils::VectorRef<const sem::Constant*> args,
+                            const Source& source);
 
     /// Vector constructor using scalars
     /// @param ty the vector type
     /// @param args the input arguments
+    /// @param source the source location of the conversion
     /// @return the constructed value, or null if the value cannot be calculated
-    const sem::Constant* VecCtorS(const sem::Type* ty,
-                                  utils::ConstVectorRef<const sem::Expression*> args);
+    ConstantResult VecCtorS(const sem::Type* ty,
+                            utils::VectorRef<const sem::Constant*> args,
+                            const Source& source);
 
     /// Vector constructor using a mix of scalars and smaller vectors
     /// @param ty the vector type
     /// @param args the input arguments
+    /// @param source the source location of the conversion
     /// @return the constructed value, or null if the value cannot be calculated
-    const sem::Constant* VecCtorM(const sem::Type* ty,
-                                  utils::ConstVectorRef<const sem::Expression*> args);
+    ConstantResult VecCtorM(const sem::Type* ty,
+                            utils::VectorRef<const sem::Constant*> args,
+                            const Source& source);
 
     /// Matrix constructor using scalar values
     /// @param ty the matrix type
     /// @param args the input arguments
+    /// @param source the source location of the conversion
     /// @return the constructed value, or null if the value cannot be calculated
-    const sem::Constant* MatCtorS(const sem::Type* ty,
-                                  utils::ConstVectorRef<const sem::Expression*> args);
+    ConstantResult MatCtorS(const sem::Type* ty,
+                            utils::VectorRef<const sem::Constant*> args,
+                            const Source& source);
 
     /// Matrix constructor using column vectors
     /// @param ty the matrix type
     /// @param args the input arguments
+    /// @param source the source location of the conversion
     /// @return the constructed value, or null if the value cannot be calculated
-    const sem::Constant* MatCtorV(const sem::Type* ty,
-                                  utils::ConstVectorRef<const sem::Expression*> args);
+    ConstantResult MatCtorV(const sem::Type* ty,
+                            utils::VectorRef<const sem::Constant*> args,
+                            const Source& source);
 
     ////////////////////////////////////////////////////////////////////////////
-    // Operators
+    // Unary Operators
     ////////////////////////////////////////////////////////////////////////////
 
     /// Complement operator '~'
     /// @param ty the integer type
     /// @param args the input arguments
+    /// @param source the source location of the conversion
     /// @return the result value, or null if the value cannot be calculated
-    const sem::Constant* OpComplement(const sem::Type* ty,
-                                      utils::ConstVectorRef<const sem::Expression*> args);
+    ConstantResult OpComplement(const sem::Type* ty,
+                                utils::VectorRef<const sem::Constant*> args,
+                                const Source& source);
 
     /// Minus operator '-'
     /// @param ty the expression type
     /// @param args the input arguments
+    /// @param source the source location of the conversion
     /// @return the result value, or null if the value cannot be calculated
-    const sem::Constant* OpMinus(const sem::Type* ty,
-                                 utils::ConstVectorRef<const sem::Expression*> args);
+    ConstantResult OpMinus(const sem::Type* ty,
+                           utils::VectorRef<const sem::Constant*> args,
+                           const Source& source);
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Binary Operators
+    ////////////////////////////////////////////////////////////////////////////
+
+    /// Plus operator '+'
+    /// @param ty the expression type
+    /// @param args the input arguments
+    /// @param source the source location of the conversion
+    /// @return the result value, or null if the value cannot be calculated
+    ConstantResult OpPlus(const sem::Type* ty,
+                          utils::VectorRef<const sem::Constant*> args,
+                          const Source& source);
 
     ////////////////////////////////////////////////////////////////////////////
     // Builtins
@@ -194,16 +228,20 @@
     /// atan2 builtin
     /// @param ty the expression type
     /// @param args the input arguments
+    /// @param source the source location of the conversion
     /// @return the result value, or null if the value cannot be calculated
-    const sem::Constant* atan2(const sem::Type* ty,
-                               utils::ConstVectorRef<const sem::Expression*> args);
+    ConstantResult atan2(const sem::Type* ty,
+                         utils::VectorRef<const sem::Constant*> args,
+                         const Source& source);
 
     /// clamp builtin
     /// @param ty the expression type
     /// @param args the input arguments
+    /// @param source the source location of the conversion
     /// @return the result value, or null if the value cannot be calculated
-    const sem::Constant* clamp(const sem::Type* ty,
-                               utils::ConstVectorRef<const sem::Expression*> args);
+    ConstantResult clamp(const sem::Type* ty,
+                         utils::VectorRef<const sem::Constant*> args,
+                         const Source& source);
 
   private:
     /// Adds the given error message to the diagnostics
diff --git a/src/tint/resolver/const_eval_test.cc b/src/tint/resolver/const_eval_test.cc
index 3a6c971..6623a5a 100644
--- a/src/tint/resolver/const_eval_test.cc
+++ b/src/tint/resolver/const_eval_test.cc
@@ -43,15 +43,26 @@
 
 template <typename T>
 constexpr auto Negate(const Number<T>& v) {
-    // For signed integrals, avoid C++ UB by not negating the smallest negative number. In
-    // WGSL, this operation is well defined to return the same value, see:
-    // https://gpuweb.github.io/gpuweb/wgsl/#arithmetic-expr.
-    if constexpr (std::is_integral_v<T> && std::is_signed_v<T>) {
-        if (v == std::numeric_limits<T>::min()) {
-            return v;
+    if constexpr (std::is_integral_v<T>) {
+        if constexpr (std::is_signed_v<T>) {
+            // For signed integrals, avoid C++ UB by not negating the smallest negative number. In
+            // WGSL, this operation is well defined to return the same value, see:
+            // https://gpuweb.github.io/gpuweb/wgsl/#arithmetic-expr.
+            if (v == std::numeric_limits<T>::min()) {
+                return v;
+            }
+            return -v;
+
+        } else {
+            // Allow negating unsigned values
+            using ST = std::make_signed_t<T>;
+            auto as_signed = Number<ST>{static_cast<ST>(v)};
+            return Number<T>{static_cast<T>(Negate(as_signed))};
         }
+    } else {
+        // float case
+        return -v;
     }
-    return -v;
 }
 
 template <typename T>
@@ -65,7 +76,7 @@
 
 // Concats any number of std::vectors
 template <typename Vec, typename... Vecs>
-auto Concat(Vec&& v1, Vecs&&... vs) {
+[[nodiscard]] auto Concat(Vec&& v1, Vecs&&... vs) {
     auto total_size = v1.size() + (vs.size() + ...);
     v1.reserve(total_size);
     (std::move(vs.begin(), vs.end(), std::back_inserter(v1)), ...);
@@ -1771,7 +1782,7 @@
 }
 
 TEST_F(ResolverConstEvalTest, Array_Struct_f32_Zero) {
-    Structure("S", {
+    Structure("S", utils::Vector{
                        Member("m1", ty.f32()),
                        Member("m2", ty.f32()),
                    });
@@ -1914,7 +1925,7 @@
 }
 
 TEST_F(ResolverConstEvalTest, Array_Struct_f32_Elements) {
-    Structure("S", {
+    Structure("S", utils::Vector{
                        Member("m1", ty.f32()),
                        Member("m2", ty.f32()),
                    });
@@ -1958,7 +1969,8 @@
 }
 
 TEST_F(ResolverConstEvalTest, Struct_I32s_ZeroInit) {
-    Structure("S", {Member("m1", ty.i32()), Member("m2", ty.i32()), Member("m3", ty.i32())});
+    Structure(
+        "S", utils::Vector{Member("m1", ty.i32()), Member("m2", ty.i32()), Member("m3", ty.i32())});
     auto* expr = Construct(ty.type_name("S"));
     WrapInFunction(expr);
 
@@ -1997,7 +2009,7 @@
 TEST_F(ResolverConstEvalTest, Struct_MixedScalars_ZeroInit) {
     Enable(ast::Extension::kF16);
 
-    Structure("S", {
+    Structure("S", utils::Vector{
                        Member("m1", ty.i32()),
                        Member("m2", ty.u32()),
                        Member("m3", ty.f32()),
@@ -2052,7 +2064,7 @@
 }
 
 TEST_F(ResolverConstEvalTest, Struct_VectorF32s_ZeroInit) {
-    Structure("S", {
+    Structure("S", utils::Vector{
                        Member("m1", ty.vec3<f32>()),
                        Member("m2", ty.vec3<f32>()),
                        Member("m3", ty.vec3<f32>()),
@@ -2104,7 +2116,7 @@
 TEST_F(ResolverConstEvalTest, Struct_MixedVectors_ZeroInit) {
     Enable(ast::Extension::kF16);
 
-    Structure("S", {
+    Structure("S", utils::Vector{
                        Member("m1", ty.vec2<i32>()),
                        Member("m2", ty.vec3<u32>()),
                        Member("m3", ty.vec4<f32>()),
@@ -2173,13 +2185,13 @@
 }
 
 TEST_F(ResolverConstEvalTest, Struct_Struct_ZeroInit) {
-    Structure("Inner", {
+    Structure("Inner", utils::Vector{
                            Member("m1", ty.i32()),
                            Member("m2", ty.u32()),
                            Member("m3", ty.f32()),
                        });
 
-    Structure("Outer", {
+    Structure("Outer", utils::Vector{
                            Member("m1", ty.type_name("Inner")),
                            Member("m2", ty.type_name("Inner")),
                        });
@@ -2219,7 +2231,7 @@
 TEST_F(ResolverConstEvalTest, Struct_MixedScalars_Construct) {
     Enable(ast::Extension::kF16);
 
-    Structure("S", {
+    Structure("S", utils::Vector{
                        Member("m1", ty.i32()),
                        Member("m2", ty.u32()),
                        Member("m3", ty.f32()),
@@ -2276,7 +2288,7 @@
 TEST_F(ResolverConstEvalTest, Struct_MixedVectors_Construct) {
     Enable(ast::Extension::kF16);
 
-    Structure("S", {
+    Structure("S", utils::Vector{
                        Member("m1", ty.vec2<i32>()),
                        Member("m2", ty.vec3<u32>()),
                        Member("m3", ty.vec4<f32>()),
@@ -2346,13 +2358,13 @@
 }
 
 TEST_F(ResolverConstEvalTest, Struct_Struct_Construct) {
-    Structure("Inner", {
+    Structure("Inner", utils::Vector{
                            Member("m1", ty.i32()),
                            Member("m2", ty.u32()),
                            Member("m3", ty.f32()),
                        });
 
-    Structure("Outer", {
+    Structure("Outer", utils::Vector{
                            Member("m1", ty.type_name("Inner")),
                            Member("m2", ty.type_name("Inner")),
                        });
@@ -2392,7 +2404,7 @@
 }
 
 TEST_F(ResolverConstEvalTest, Struct_Array_Construct) {
-    Structure("S", {
+    Structure("S", utils::Vector{
                        Member("m1", ty.array<i32, 2>()),
                        Member("m2", ty.array<f32, 3>()),
                    });
@@ -2921,13 +2933,13 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 
 TEST_F(ResolverConstEvalTest, MemberAccess) {
-    Structure("Inner", {
+    Structure("Inner", utils::Vector{
                            Member("i1", ty.i32()),
                            Member("i2", ty.u32()),
                            Member("i3", ty.f32()),
                        });
 
-    Structure("Outer", {
+    Structure("Outer", utils::Vector{
                            Member("o1", ty.type_name("Inner")),
                            Member("o2", ty.type_name("Inner")),
                        });
@@ -3114,34 +3126,195 @@
 
 }  // namespace unary_op
 
-namespace builtin {
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Binary op
+////////////////////////////////////////////////////////////////////////////////////////////////////
+namespace binary_op {
 
-template <typename T>
-struct Values {
-    std::vector<T> args;
-    T result;
-    bool result_pos_or_neg;
-};
+using Types = std::variant<AInt, AFloat, u32, i32, f32, f16>;
 
 struct Case {
-    std::variant<Values<AInt>, Values<AFloat>, Values<u32>, Values<i32>, Values<f32>, Values<f16>>
-        values;
+    Types lhs;
+    Types rhs;
+    Types expected;
+    bool is_overflow;
 };
 
 static std::ostream& operator<<(std::ostream& o, const Case& c) {
     std::visit(
-        [&](auto&& v) {
-            for (auto& e : v.args) {
-                o << e << ((&e != &v.args.back()) ? " " : "");
+        [&](auto&& lhs, auto&& rhs, auto&& expected) {
+            o << "lhs: " << lhs << ", rhs: " << rhs << ", expected: " << expected;
+        },
+        c.lhs, c.rhs, c.expected);
+    return o;
+}
+
+template <typename T, typename U, typename V>
+Case C(T lhs, U rhs, V expected, bool is_overflow = false) {
+    return Case{lhs, rhs, expected, is_overflow};
+}
+
+using ResolverConstEvalBinaryOpTest = ResolverTestWithParam<std::tuple<ast::BinaryOp, Case>>;
+TEST_P(ResolverConstEvalBinaryOpTest, Test) {
+    Enable(ast::Extension::kF16);
+
+    auto op = std::get<0>(GetParam());
+    auto c = std::get<1>(GetParam());
+    std::visit(
+        [&](auto&& lhs, auto&& rhs, auto&& expected) {
+            using T = std::decay_t<decltype(expected)>;
+
+            if constexpr (std::is_same_v<T, AInt> || std::is_same_v<T, AFloat>) {
+                if (c.is_overflow) {
+                    return;
+                }
+            }
+
+            auto* expr = create<ast::BinaryExpression>(op, Expr(lhs), Expr(rhs));
+            GlobalConst("C", nullptr, expr);
+
+            EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+            auto* sem = Sem().Get(expr);
+            const sem::Constant* value = sem->ConstantValue();
+            ASSERT_NE(value, nullptr);
+            EXPECT_TYPE(value->Type(), sem->Type());
+            EXPECT_EQ(value->As<T>(), expected);
+
+            if constexpr (IsInteger<UnwrapNumber<T>>) {
+                // Check that the constant's integer doesn't contain unexpected data in the MSBs
+                // that are outside of the bit-width of T.
+                EXPECT_EQ(value->As<AInt>(), AInt(expected));
             }
         },
-        c.values);
+        c.lhs, c.rhs, c.expected);
+}
+
+INSTANTIATE_TEST_SUITE_P(MixedAbstractArgs,
+                         ResolverConstEvalBinaryOpTest,
+                         testing::Combine(testing::Values(ast::BinaryOp::kAdd),
+                                          testing::ValuesIn(std::vector{
+                                              // Mixed abstract type args
+                                              C(1_a, 2.3_a, 3.3_a),
+                                              C(2.3_a, 1_a, 3.3_a),
+                                          })));
+
+template <typename T>
+std::vector<Case> OpAddIntCases() {
+    static_assert(IsInteger<UnwrapNumber<T>>);
+    return {
+        C(T{0}, T{0}, T{0}),
+        C(T{1}, T{2}, T{3}),
+        C(T::Lowest(), T{1}, T{T::Lowest() + 1}),
+        C(T::Highest(), Negate(T{1}), T{T::Highest() - 1}),
+        C(T::Lowest(), T::Highest(), Negate(T{1})),
+        C(T::Highest(), T{1}, T::Lowest(), true),
+        C(T::Lowest(), Negate(T{1}), T::Highest(), true),
+    };
+}
+template <typename T>
+std::vector<Case> OpAddFloatCases() {
+    static_assert(IsFloatingPoint<UnwrapNumber<T>>);
+    return {
+        C(T{0}, T{0}, T{0}),
+        C(T{1}, T{2}, T{3}),
+        C(T::Lowest(), T{1}, T{T::Lowest() + 1}),
+        C(T::Highest(), Negate(T{1}), T{T::Highest() - 1}),
+        C(T::Lowest(), T::Highest(), T{0}),
+        C(T::Highest(), T::Highest(), T::Inf(), true),
+        C(T::Lowest(), Negate(T::Highest()), -T::Inf(), true),
+    };
+}
+INSTANTIATE_TEST_SUITE_P(Add,
+                         ResolverConstEvalBinaryOpTest,
+                         testing::Combine(testing::Values(ast::BinaryOp::kAdd),
+                                          testing::ValuesIn(Concat(  //
+                                              OpAddIntCases<AInt>(),
+                                              OpAddIntCases<i32>(),
+                                              OpAddIntCases<u32>(),
+                                              OpAddFloatCases<AFloat>(),
+                                              OpAddFloatCases<f32>(),
+                                              OpAddFloatCases<f16>()))));
+
+TEST_F(ResolverConstEvalTest, BinaryAbstractAddOverflow_AInt) {
+    GlobalConst("c", nullptr, 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));
+    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()));
+    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()));
+    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* c = Add(Expr("a"), Expr("b"));
+    WrapInFunction(a, b, c);
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+    auto* sem = Sem().Get(c);
+    ASSERT_TRUE(sem);
+    ASSERT_TRUE(sem->ConstantValue());
+    auto result = sem->ConstantValue()->As<AFloat>();
+    EXPECT_EQ(result, 3.3f);
+}
+
+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* c = Add(Expr("a"), Expr("b"));
+    WrapInFunction(a, b, c);
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+    auto* sem = Sem().Get(c);
+    ASSERT_TRUE(sem);
+    ASSERT_TRUE(sem->ConstantValue());
+    EXPECT_EQ(sem->ConstantValue()->Index(0)->As<AFloat>(), 3.3f);
+    EXPECT_EQ(sem->ConstantValue()->Index(1)->As<AFloat>(), 3.3f);
+    EXPECT_EQ(sem->ConstantValue()->Index(2)->As<AFloat>(), 3.3f);
+}
+
+}  // namespace binary_op
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Builtin
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+namespace builtin {
+
+using Types = std::variant<AInt, AFloat, u32, i32, f32, f16>;
+
+struct Case {
+    utils::Vector<Types, 8> args;
+    Types result;
+    bool result_pos_or_neg;
+};
+
+static std::ostream& operator<<(std::ostream& o, const Case& c) {
+    for (auto& a : c.args) {
+        std::visit([&](auto&& v) { o << v << ((&a != &c.args.Back()) ? " " : ""); }, a);
+    }
     return o;
 }
 
 template <typename T>
-Case C(std::vector<T> args, T result, bool result_pos_or_neg = false) {
-    return Case{Values<T>{std::move(args), result, result_pos_or_neg}};
+Case C(std::initializer_list<Types> args, T result, bool result_pos_or_neg = false) {
+    return Case{std::move(args), std::move(result), result_pos_or_neg};
 }
 
 using ResolverConstEvalBuiltinTest = ResolverTestWithParam<std::tuple<sem::BuiltinType, Case>>;
@@ -3151,12 +3324,15 @@
 
     auto builtin = std::get<0>(GetParam());
     auto c = std::get<1>(GetParam());
+
+    utils::Vector<const ast::Expression*, 8> args;
+    for (auto& a : c.args) {
+        std::visit([&](auto&& v) { args.Push(Expr(v)); }, a);
+    }
+
     std::visit(
-        [&](auto&& values) {
-            using T = decltype(values.result);
-            auto args = utils::Transform(values.args, [&](auto&& a) {
-                return static_cast<const ast::Expression*>(Expr(a));
-            });
+        [&](auto&& result) {
+            using T = std::decay_t<decltype(result)>;
             auto* expr = Call(sem::str(builtin), std::move(args));
 
             GlobalConst("C", nullptr, expr);
@@ -3171,22 +3347,22 @@
             auto actual = value->As<T>();
 
             if constexpr (IsFloatingPoint<UnwrapNumber<T>>) {
-                if (std::isnan(values.result)) {
+                if (std::isnan(result)) {
                     EXPECT_TRUE(std::isnan(actual));
                 } else {
-                    EXPECT_FLOAT_EQ(values.result_pos_or_neg ? Abs(actual) : actual, values.result);
+                    EXPECT_FLOAT_EQ(c.result_pos_or_neg ? Abs(actual) : actual, result);
                 }
             } else {
-                EXPECT_EQ(values.result_pos_or_neg ? Abs(actual) : actual, values.result);
+                EXPECT_EQ(c.result_pos_or_neg ? Abs(actual) : actual, result);
             }
 
             if constexpr (IsInteger<UnwrapNumber<T>>) {
                 // Check that the constant's integer doesn't contain unexpected data in the MSBs
                 // that are outside of the bit-width of T.
-                EXPECT_EQ(value->As<AInt>(), AInt(values.result));
+                EXPECT_EQ(value->As<AInt>(), AInt(result));
             }
         },
-        c.values);
+        c.result);
 }
 
 template <typename T, bool finite_only>
@@ -3246,6 +3422,15 @@
 }
 
 INSTANTIATE_TEST_SUITE_P(  //
+    MixedAbstractArgs,
+    ResolverConstEvalBuiltinTest,
+    testing::Combine(testing::Values(sem::BuiltinType::kAtan2),
+                     testing::ValuesIn(std::vector{
+                         C({1_a, 1.0_a}, 0.78539819_a),
+                         C({1.0_a, 1_a}, 0.78539819_a),
+                     })));
+
+INSTANTIATE_TEST_SUITE_P(  //
     Atan2,
     ResolverConstEvalBuiltinTest,
     testing::Combine(testing::Values(sem::BuiltinType::kAtan2),
diff --git a/src/tint/resolver/control_block_validation_test.cc b/src/tint/resolver/control_block_validation_test.cc
index a5ba7ca..9b0d289 100644
--- a/src/tint/resolver/control_block_validation_test.cc
+++ b/src/tint/resolver/control_block_validation_test.cc
@@ -213,8 +213,8 @@
     // }
     auto* var = Var("a", ty.i32(), Expr(2_i));
 
-    auto* block = Block(Decl(var), Switch("a",                                  //
-                                          Case(Source{{12, 34}}, {Expr(1_u)}),  //
+    auto* block = Block(Decl(var), Switch("a",                                               //
+                                          Case(Source{{12, 34}}, utils::Vector{Expr(1_u)}),  //
                                           DefaultCase()));
     WrapInFunction(block);
 
@@ -232,9 +232,9 @@
     // }
     auto* var = Var("a", ty.u32(), Expr(2_u));
 
-    auto* block = Block(Decl(var),                                    //
-                        Switch("a",                                   //
-                               Case(Source{{12, 34}}, {Expr(-1_i)}),  //
+    auto* block = Block(Decl(var),                                                 //
+                        Switch("a",                                                //
+                               Case(Source{{12, 34}}, utils::Vector{Expr(-1_i)}),  //
                                DefaultCase()));
     WrapInFunction(block);
 
@@ -256,7 +256,7 @@
     auto* block = Block(Decl(var),   //
                         Switch("a",  //
                                Case(Expr(0_u)),
-                               Case({
+                               Case(utils::Vector{
                                    Expr(Source{{12, 34}}, 2_u),
                                    Expr(3_u),
                                    Expr(Source{{56, 78}}, 2_u),
@@ -282,7 +282,7 @@
     auto* block = Block(Decl(var),   //
                         Switch("a",  //
                                Case(Expr(Source{{12, 34}}, -10_i)),
-                               Case({
+                               Case(utils::Vector{
                                    Expr(0_i),
                                    Expr(1_i),
                                    Expr(2_i),
diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc
index 3d550ff..6dd35a5 100644
--- a/src/tint/resolver/dependency_graph.cc
+++ b/src/tint/resolver/dependency_graph.cc
@@ -204,6 +204,7 @@
             [&](const ast::Enable*) {
                 // Enable directives do not effect the dependency graph.
             },
+            [&](const ast::StaticAssert* assertion) { TraverseExpression(assertion->condition); },
             [&](Default) { UnhandledNode(diagnostics_, global->node); });
     }
 
@@ -237,7 +238,7 @@
 
     /// Traverses the statements, performing symbol resolution and determining
     /// global dependencies.
-    void TraverseStatements(const ast::StatementList& stmts) {
+    void TraverseStatements(utils::VectorRef<const ast::Statement*> stmts) {
         for (auto* s : stmts) {
             TraverseStatement(s);
         }
@@ -315,6 +316,7 @@
                 TraverseExpression(w->condition);
                 TraverseStatement(w->body);
             },
+            [&](const ast::StaticAssert* assertion) { TraverseExpression(assertion->condition); },
             [&](Default) {
                 if (!stmt->IsAnyOf<ast::BreakStatement, ast::ContinueStatement,
                                    ast::DiscardStatement, ast::FallthroughStatement>()) {
@@ -404,7 +406,7 @@
 
     /// Traverses the attribute list, performing symbol resolution and
     /// determining global dependencies.
-    void TraverseAttributes(const ast::AttributeList& attrs) {
+    void TraverseAttributes(utils::VectorRef<const ast::Attribute*> attrs) {
         for (auto* attr : attrs) {
             TraverseAttribute(attr);
         }
@@ -487,6 +489,10 @@
     /// #diagnostics.
     /// @returns true if analysis found no errors, otherwise false.
     bool Run(const ast::Module& module) {
+        // Reserve container memory
+        graph_.resolved_symbols.reserve(module.GlobalDeclarations().Length());
+        sorted_.reserve(module.GlobalDeclarations().Length());
+
         // Collect all the named globals from the AST module
         GatherGlobals(module);
 
@@ -515,6 +521,8 @@
             [&](const ast::TypeDecl* td) { return td->name; },
             [&](const ast::Function* func) { return func->symbol; },
             [&](const ast::Variable* var) { return var->symbol; },
+            [&](const ast::Enable*) { return Symbol(); },
+            [&](const ast::StaticAssert*) { return Symbol(); },
             [&](Default) {
                 UnhandledNode(diagnostics_, node);
                 return Symbol{};
@@ -533,11 +541,12 @@
     /// declaration
     std::string KindOf(const ast::Node* node) {
         return Switch(
-            node,                                               //
-            [&](const ast::Struct*) { return "struct"; },       //
-            [&](const ast::Alias*) { return "alias"; },         //
-            [&](const ast::Function*) { return "function"; },   //
-            [&](const ast::Variable* v) { return v->Kind(); },  //
+            node,                                                       //
+            [&](const ast::Struct*) { return "struct"; },               //
+            [&](const ast::Alias*) { return "alias"; },                 //
+            [&](const ast::Function*) { return "function"; },           //
+            [&](const ast::Variable* v) { return v->Kind(); },          //
+            [&](const ast::StaticAssert*) { return "static_assert"; },  //
             [&](Default) {
                 UnhandledNode(diagnostics_, node);
                 return "<error>";
@@ -549,9 +558,8 @@
     void GatherGlobals(const ast::Module& module) {
         for (auto* node : module.GlobalDeclarations()) {
             auto* global = allocator_.Create(node);
-            // Enable directives do not form a symbol. Skip them.
-            if (!node->Is<ast::Enable>()) {
-                globals_.emplace(SymbolOf(node), global);
+            if (auto symbol = SymbolOf(node); symbol.IsValid()) {
+                globals_.emplace(symbol, global);
             }
             declaration_order_.emplace_back(global);
         }
diff --git a/src/tint/resolver/dependency_graph_test.cc b/src/tint/resolver/dependency_graph_test.cc
index b7ffa79..7a8110b 100644
--- a/src/tint/resolver/dependency_graph_test.cc
+++ b/src/tint/resolver/dependency_graph_test.cc
@@ -384,13 +384,13 @@
     /// The program builder
     ProgramBuilder* const builder;
     /// Parameters to a function that may need to be built
-    ast::ParameterList parameters;
+    utils::Vector<const ast::Parameter*, 8> parameters;
     /// Shallow function var / let declaration statements
-    ast::StatementList statements;
+    utils::Vector<const ast::Statement*, 8> statements;
     /// Nested function local var / let declaration statements
-    ast::StatementList nested_statements;
+    utils::Vector<const ast::Statement*, 8> nested_statements;
     /// Function attributes
-    ast::AttributeList func_attrs;
+    utils::Vector<const ast::Attribute*, 8> func_attrs;
 
     /// Constructor
     /// @param builder the program builder
@@ -431,32 +431,32 @@
         case SymbolDeclKind::Alias:
             return b.Alias(source, symbol, b.ty.i32());
         case SymbolDeclKind::Struct:
-            return b.Structure(source, symbol, {b.Member("m", b.ty.i32())});
+            return b.Structure(source, symbol, utils::Vector{b.Member("m", b.ty.i32())});
         case SymbolDeclKind::Function:
-            return b.Func(source, symbol, {}, b.ty.void_(), {});
+            return b.Func(source, symbol, utils::Empty, b.ty.void_(), utils::Empty);
         case SymbolDeclKind::Parameter: {
             auto* node = b.Param(source, symbol, b.ty.i32());
-            parameters.emplace_back(node);
+            parameters.Push(node);
             return node;
         }
         case SymbolDeclKind::LocalVar: {
             auto* node = b.Var(source, symbol, b.ty.i32());
-            statements.emplace_back(b.Decl(node));
+            statements.Push(b.Decl(node));
             return node;
         }
         case SymbolDeclKind::LocalLet: {
             auto* node = b.Let(source, symbol, b.ty.i32(), b.Expr(1_i));
-            statements.emplace_back(b.Decl(node));
+            statements.Push(b.Decl(node));
             return node;
         }
         case SymbolDeclKind::NestedLocalVar: {
             auto* node = b.Var(source, symbol, b.ty.i32());
-            nested_statements.emplace_back(b.Decl(node));
+            nested_statements.Push(b.Decl(node));
             return node;
         }
         case SymbolDeclKind::NestedLocalLet: {
             auto* node = b.Let(source, symbol, b.ty.i32(), b.Expr(1_i));
-            nested_statements.emplace_back(b.Decl(node));
+            nested_statements.Push(b.Decl(node));
             return node;
         }
     }
@@ -543,83 +543,82 @@
         }
         case SymbolUseKind::StructMemberType: {
             auto* node = b.ty.type_name(source, symbol);
-            b.Structure(b.Sym(), {b.Member("m", node)});
+            b.Structure(b.Sym(), utils::Vector{b.Member("m", node)});
             return node;
         }
         case SymbolUseKind::CallFunction: {
             auto* node = b.Expr(source, symbol);
-            statements.emplace_back(b.CallStmt(b.Call(node)));
+            statements.Push(b.CallStmt(b.Call(node)));
             return node;
         }
         case SymbolUseKind::ParameterType: {
             auto* node = b.ty.type_name(source, symbol);
-            parameters.emplace_back(b.Param(b.Sym(), node));
+            parameters.Push(b.Param(b.Sym(), node));
             return node;
         }
         case SymbolUseKind::LocalVarType: {
             auto* node = b.ty.type_name(source, symbol);
-            statements.emplace_back(b.Decl(b.Var(b.Sym(), node)));
+            statements.Push(b.Decl(b.Var(b.Sym(), node)));
             return node;
         }
         case SymbolUseKind::LocalVarArrayElemType: {
             auto* node = b.ty.type_name(source, symbol);
-            statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.array(node, 4_u), b.Expr(1_i))));
+            statements.Push(b.Decl(b.Var(b.Sym(), b.ty.array(node, 4_u), b.Expr(1_i))));
             return node;
         }
         case SymbolUseKind::LocalVarArraySizeValue: {
             auto* node = b.Expr(source, symbol);
-            statements.emplace_back(
-                b.Decl(b.Var(b.Sym(), b.ty.array(b.ty.i32(), node), b.Expr(1_i))));
+            statements.Push(b.Decl(b.Var(b.Sym(), b.ty.array(b.ty.i32(), node), b.Expr(1_i))));
             return node;
         }
         case SymbolUseKind::LocalVarVectorElemType: {
             auto* node = b.ty.type_name(source, symbol);
-            statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.vec3(node))));
+            statements.Push(b.Decl(b.Var(b.Sym(), b.ty.vec3(node))));
             return node;
         }
         case SymbolUseKind::LocalVarMatrixElemType: {
             auto* node = b.ty.type_name(source, symbol);
-            statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.mat3x4(node))));
+            statements.Push(b.Decl(b.Var(b.Sym(), b.ty.mat3x4(node))));
             return node;
         }
         case SymbolUseKind::LocalVarValue: {
             auto* node = b.Expr(source, symbol);
-            statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.i32(), node)));
+            statements.Push(b.Decl(b.Var(b.Sym(), b.ty.i32(), node)));
             return node;
         }
         case SymbolUseKind::LocalLetType: {
             auto* node = b.ty.type_name(source, symbol);
-            statements.emplace_back(b.Decl(b.Let(b.Sym(), node, b.Expr(1_i))));
+            statements.Push(b.Decl(b.Let(b.Sym(), node, b.Expr(1_i))));
             return node;
         }
         case SymbolUseKind::LocalLetValue: {
             auto* node = b.Expr(source, symbol);
-            statements.emplace_back(b.Decl(b.Let(b.Sym(), b.ty.i32(), node)));
+            statements.Push(b.Decl(b.Let(b.Sym(), b.ty.i32(), node)));
             return node;
         }
         case SymbolUseKind::NestedLocalVarType: {
             auto* node = b.ty.type_name(source, symbol);
-            nested_statements.emplace_back(b.Decl(b.Var(b.Sym(), node)));
+            nested_statements.Push(b.Decl(b.Var(b.Sym(), node)));
             return node;
         }
         case SymbolUseKind::NestedLocalVarValue: {
             auto* node = b.Expr(source, symbol);
-            nested_statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.i32(), node)));
+            nested_statements.Push(b.Decl(b.Var(b.Sym(), b.ty.i32(), node)));
             return node;
         }
         case SymbolUseKind::NestedLocalLetType: {
             auto* node = b.ty.type_name(source, symbol);
-            nested_statements.emplace_back(b.Decl(b.Let(b.Sym(), node, b.Expr(1_i))));
+            nested_statements.Push(b.Decl(b.Let(b.Sym(), node, b.Expr(1_i))));
             return node;
         }
         case SymbolUseKind::NestedLocalLetValue: {
             auto* node = b.Expr(source, symbol);
-            nested_statements.emplace_back(b.Decl(b.Let(b.Sym(), b.ty.i32(), node)));
+            nested_statements.Push(b.Decl(b.Let(b.Sym(), b.ty.i32(), node)));
             return node;
         }
         case SymbolUseKind::WorkgroupSizeValue: {
             auto* node = b.Expr(source, symbol);
-            func_attrs.emplace_back(b.WorkgroupSize(1_i, node, 2_i));
+            func_attrs.Push(b.WorkgroupSize(1_i, node, 2_i));
             return node;
         }
     }
@@ -628,15 +627,15 @@
 
 void SymbolTestHelper::Build() {
     auto& b = *builder;
-    if (!nested_statements.empty()) {
-        statements.emplace_back(b.Block(nested_statements));
-        nested_statements.clear();
+    if (!nested_statements.IsEmpty()) {
+        statements.Push(b.Block(nested_statements));
+        nested_statements.Clear();
     }
-    if (!parameters.empty() || !statements.empty() || !func_attrs.empty()) {
+    if (!parameters.IsEmpty() || !statements.IsEmpty() || !func_attrs.IsEmpty()) {
         b.Func("func", parameters, b.ty.void_(), statements, func_attrs);
-        parameters.clear();
-        statements.clear();
-        func_attrs.clear();
+        parameters.Clear();
+        statements.Clear();
+        func_attrs.Clear();
     }
 }
 
@@ -651,8 +650,8 @@
     // fn A() { B(); }
     // fn B() {}
 
-    Func("A", {}, ty.void_(), {CallStmt(Call(Expr(Source{{12, 34}}, "B")))});
-    Func(Source{{56, 78}}, "B", {}, ty.void_(), {Return()});
+    Func("A", utils::Empty, ty.void_(), utils::Vector{CallStmt(Call(Expr(Source{{12, 34}}, "B")))});
+    Func(Source{{56, 78}}, "B", utils::Empty, ty.void_(), utils::Vector{Return()});
 
     Build();
 }
@@ -663,7 +662,8 @@
     // }
     // type T = i32;
 
-    Func("F", {}, ty.void_(), {Block(Ignore(Construct(ty.type_name(Source{{12, 34}}, "T"))))});
+    Func("F", utils::Empty, ty.void_(),
+         utils::Vector{Block(Ignore(Construct(ty.type_name(Source{{12, 34}}, "T"))))});
     Alias(Source{{56, 78}}, "T", ty.i32());
 
     Build();
@@ -675,7 +675,8 @@
     // }
     // type T = i32;
 
-    Func("F", {}, ty.void_(), {Block(Decl(Var("v", ty.type_name(Source{{12, 34}}, "T"))))});
+    Func("F", utils::Empty, ty.void_(),
+         utils::Vector{Block(Decl(Var("v", ty.type_name(Source{{12, 34}}, "T"))))});
     Alias(Source{{56, 78}}, "T", ty.i32());
 
     Build();
@@ -685,7 +686,8 @@
     // fn F(p : T) {}
     // type T = i32;
 
-    Func("F", {Param("p", ty.type_name(Source{{12, 34}}, "T"))}, ty.void_(), {});
+    Func("F", utils::Vector{Param("p", ty.type_name(Source{{12, 34}}, "T"))}, ty.void_(),
+         utils::Empty);
     Alias(Source{{56, 78}}, "T", ty.i32());
 
     Build();
@@ -695,7 +697,7 @@
     // fn F() -> T {}
     // type T = i32;
 
-    Func("F", {}, ty.type_name(Source{{12, 34}}, "T"), {});
+    Func("F", utils::Empty, ty.type_name(Source{{12, 34}}, "T"), utils::Empty);
     Alias(Source{{56, 78}}, "T", ty.i32());
 
     Build();
@@ -705,7 +707,7 @@
     // struct S { m : T };
     // type T = i32;
 
-    Structure("S", {Member("m", ty.type_name(Source{{12, 34}}, "T"))});
+    Structure("S", utils::Vector{Member("m", ty.type_name(Source{{12, 34}}, "T"))});
     Alias(Source{{56, 78}}, "T", ty.i32());
 
     Build();
@@ -717,8 +719,8 @@
     // }
     // var G: f32 = 2.1;
 
-    Func("F", {}, ty.void_(),
-         {
+    Func("F", utils::Empty, ty.void_(),
+         utils::Vector{
              Block(Assign(Expr(Source{{12, 34}}, "G"), 3.14_f)),
          });
 
@@ -808,8 +810,8 @@
 TEST_F(ResolverDependencyGraphCyclicRefTest, DirectCall) {
     // fn main() { main(); }
 
-    Func(Source{{12, 34}}, "main", {}, ty.void_(),
-         {CallStmt(Call(Expr(Source{{56, 78}}, "main")))});
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.void_(),
+         utils::Vector{CallStmt(Call(Expr(Source{{56, 78}}, "main")))});
 
     Build(R"(12:34 error: cyclic dependency found: 'main' -> 'main'
 56:78 note: function 'main' calls function 'main' here)");
@@ -822,15 +824,18 @@
     // 4: fn c() { d(); }
     // 5: fn b() { c(); }
 
-    Func(Source{{1, 1}}, "a", {}, ty.void_(), {CallStmt(Call(Expr(Source{{1, 10}}, "b")))});
-    Func(Source{{2, 1}}, "e", {}, ty.void_(), {});
-    Func(Source{{3, 1}}, "d", {}, ty.void_(),
-         {
+    Func(Source{{1, 1}}, "a", utils::Empty, ty.void_(),
+         utils::Vector{CallStmt(Call(Expr(Source{{1, 10}}, "b")))});
+    Func(Source{{2, 1}}, "e", utils::Empty, ty.void_(), utils::Empty);
+    Func(Source{{3, 1}}, "d", utils::Empty, ty.void_(),
+         utils::Vector{
              CallStmt(Call(Expr(Source{{3, 10}}, "e"))),
              CallStmt(Call(Expr(Source{{3, 10}}, "b"))),
          });
-    Func(Source{{4, 1}}, "c", {}, ty.void_(), {CallStmt(Call(Expr(Source{{4, 10}}, "d")))});
-    Func(Source{{5, 1}}, "b", {}, ty.void_(), {CallStmt(Call(Expr(Source{{5, 10}}, "c")))});
+    Func(Source{{4, 1}}, "c", utils::Empty, ty.void_(),
+         utils::Vector{CallStmt(Call(Expr(Source{{4, 10}}, "d")))});
+    Func(Source{{5, 1}}, "b", utils::Empty, ty.void_(),
+         utils::Vector{CallStmt(Call(Expr(Source{{5, 10}}, "c")))});
 
     Build(R"(5:1 error: cyclic dependency found: 'b' -> 'c' -> 'd' -> 'b'
 5:10 note: function 'b' calls function 'c' here
@@ -871,7 +876,8 @@
     //   a: S;
     // };
 
-    Structure(Source{{12, 34}}, "S", {Member("a", ty.type_name(Source{{56, 78}}, "S"))});
+    Structure(Source{{12, 34}}, "S",
+              utils::Vector{Member("a", ty.type_name(Source{{56, 78}}, "S"))});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -884,9 +890,9 @@
     // 2: struct X { y: Y; };
     // 3: struct Z { x: X; };
 
-    Structure(Source{{1, 1}}, "Y", {Member("z", ty.type_name(Source{{1, 10}}, "Z"))});
-    Structure(Source{{2, 1}}, "X", {Member("y", ty.type_name(Source{{2, 10}}, "Y"))});
-    Structure(Source{{3, 1}}, "Z", {Member("x", ty.type_name(Source{{3, 10}}, "X"))});
+    Structure(Source{{1, 1}}, "Y", utils::Vector{Member("z", ty.type_name(Source{{1, 10}}, "Z"))});
+    Structure(Source{{2, 1}}, "X", utils::Vector{Member("y", ty.type_name(Source{{2, 10}}, "Y"))});
+    Structure(Source{{3, 1}}, "Z", utils::Vector{Member("x", ty.type_name(Source{{3, 10}}, "X"))});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -960,10 +966,10 @@
     // 5: type R = A;
     // 6: const L : S = Z;
 
-    Func(Source{{1, 1}}, "F", {}, ty.type_name(Source{{1, 5}}, "R"),
-         {Return(Expr(Source{{1, 10}}, "Z"))});
+    Func(Source{{1, 1}}, "F", utils::Empty, ty.type_name(Source{{1, 5}}, "R"),
+         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", {Member("a", ty.type_name(Source{{3, 10}}, "A"))});
+    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"));
     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"));
@@ -1039,7 +1045,7 @@
     helper.Add(use_kind, symbol, Source{{56, 78}});
     helper.Build();
 
-    ASSERT_EQ(AST().GlobalDeclarations().size(), 2u);
+    ASSERT_EQ(AST().GlobalDeclarations().Length(), 2u);
 
     auto* decl = AST().GlobalDeclarations()[0];
     auto* use = AST().GlobalDeclarations()[1];
@@ -1059,7 +1065,7 @@
     helper.Add(decl_kind, symbol, Source{{12, 34}});
     helper.Build();
 
-    ASSERT_EQ(AST().GlobalDeclarations().size(), 2u);
+    ASSERT_EQ(AST().GlobalDeclarations().Length(), 2u);
 
     auto* use = AST().GlobalDeclarations()[0];
     auto* decl = AST().GlobalDeclarations()[1];
@@ -1164,9 +1170,9 @@
     SymbolTestHelper helper(this);
     auto* outer = helper.Add(outer_kind, symbol, Source{{12, 34}});
     helper.Add(inner_kind, symbol, Source{{56, 78}});
-    auto* inner_var = helper.nested_statements.size()
+    auto* inner_var = helper.nested_statements.Length()
                           ? helper.nested_statements[0]->As<ast::VariableDeclStatement>()->variable
-                      : helper.statements.size()
+                      : helper.statements.Length()
                           ? helper.statements[0]->As<ast::VariableDeclStatement>()->variable
                           : helper.parameters[0];
     helper.Build();
@@ -1203,7 +1209,7 @@
 
     const auto* value_decl = GlobalVar(value_sym, ty.i32(), ast::StorageClass::kPrivate);
     const auto* type_decl = Alias(type_sym, ty.i32());
-    const auto* func_decl = Func(func_sym, {}, ty.void_(), {});
+    const auto* func_decl = Func(func_sym, utils::Empty, ty.void_(), utils::Empty);
 
     struct SymbolUse {
         const ast::Node* decl = nullptr;
@@ -1211,10 +1217,10 @@
         std::string where;
     };
 
-    std::vector<SymbolUse> symbol_uses;
+    utils::Vector<SymbolUse, 64> symbol_uses;
 
     auto add_use = [&](const ast::Node* decl, auto* use, int line, const char* kind) {
-        symbol_uses.emplace_back(
+        symbol_uses.Push(
             SymbolUse{decl, use, std::string(__FILE__) + ":" + std::to_string(line) + ": " + kind});
         return use;
     };
@@ -1223,13 +1229,13 @@
 #define F add_use(func_decl, Expr(func_sym), __LINE__, "F()")
 
     Alias(Sym(), T);
-    Structure(Sym(), {Member(Sym(), T)});
+    Structure(Sym(), utils::Vector{Member(Sym(), T)});
     GlobalVar(Sym(), T, V);
     GlobalConst(Sym(), T, V);
-    Func(Sym(),              //
-         {Param(Sym(), T)},  //
-         T,                  // Return type
-         {
+    Func(Sym(),                           //
+         utils::Vector{Param(Sym(), T)},  //
+         T,                               // Return type
+         utils::Vector{
              Decl(Var(Sym(), T, V)),                    //
              Decl(Let(Sym(), T, V)),                    //
              CallStmt(Call(F, V)),                      //
@@ -1278,7 +1284,7 @@
     GlobalVar(Sym(), ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Float,
                                         ast::Access::kRead));  //
     GlobalVar(Sym(), ty.sampler(ast::SamplerKind::kSampler));
-    Func(Sym(), {}, ty.void_(), {});
+    Func(Sym(), utils::Empty, ty.void_(), utils::Empty);
 #undef V
 #undef T
 #undef F
@@ -1303,10 +1309,10 @@
 // DependencyAnalysis::SortGlobals(), found by clusterfuzz.
 // See: crbug.com/chromium/1273451
 TEST_F(ResolverDependencyGraphTraversalTest, chromium_1273451) {
-    Structure("A", {Member("a", ty.i32())});
-    Structure("B", {Member("b", ty.i32())});
-    Func("f", {Param("a", ty.type_name("A"))}, ty.type_name("B"),
-         {
+    Structure("A", utils::Vector{Member("a", ty.i32())});
+    Structure("B", utils::Vector{Member("b", ty.i32())});
+    Func("f", utils::Vector{Param("a", ty.type_name("A"))}, ty.type_name("B"),
+         utils::Vector{
              Return(Construct(ty.type_name("B"))),
          });
     Build();
diff --git a/src/tint/resolver/entry_point_validation_test.cc b/src/tint/resolver/entry_point_validation_test.cc
index 8df0425..d997912 100644
--- a/src/tint/resolver/entry_point_validation_test.cc
+++ b/src/tint/resolver/entry_point_validation_test.cc
@@ -49,8 +49,16 @@
 TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Location) {
     // @fragment
     // fn main() -> @location(0) f32 { return 1.0; }
-    Func(Source{{12, 34}}, "main", {}, ty.f32(), {Return(1_f)},
-         {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.f32(),
+         utils::Vector{
+             Return(1_f),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         utils::Vector{
+             Location(0),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -58,8 +66,16 @@
 TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Builtin) {
     // @vertex
     // fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(); }
-    Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
-         {Stage(ast::PipelineStage::kVertex)}, {Builtin(ast::BuiltinValue::kPosition)});
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.vec4<f32>(),
+         utils::Vector{
+             Return(Construct(ty.vec4<f32>())),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kVertex),
+         },
+         utils::Vector{
+             Builtin(ast::BuiltinValue::kPosition),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -69,8 +85,13 @@
     // fn main() -> f32 {
     //   return 1.0;
     // }
-    Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
-         {Stage(ast::PipelineStage::kVertex)});
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.vec4<f32>(),
+         utils::Vector{
+             Return(Construct(ty.vec4<f32>())),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kVertex),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: missing entry point IO attribute on return type");
@@ -81,9 +102,17 @@
     // fn main() -> @location(0) @builtin(position) vec4<f32> {
     //   return vec4<f32>();
     // }
-    Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
-         {Stage(ast::PipelineStage::kVertex)},
-         {Location(Source{{13, 43}}, 0), Builtin(Source{{14, 52}}, ast::BuiltinValue::kPosition)});
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.vec4<f32>(),
+         utils::Vector{
+             Return(Construct(ty.vec4<f32>())),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kVertex),
+         },
+         utils::Vector{
+             Location(Source{{13, 43}}, 0),
+             Builtin(Source{{14, 52}}, ast::BuiltinValue::kPosition),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
@@ -99,11 +128,18 @@
     // fn main() -> Output {
     //   return Output();
     // }
-    auto* output =
-        Structure("Output", {Member("a", ty.f32(), {Location(0)}),
-                             Member("b", ty.f32(), {Builtin(ast::BuiltinValue::kFragDepth)})});
-    Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))},
-         {Stage(ast::PipelineStage::kFragment)});
+    auto* output = Structure(
+        "Output", utils::Vector{
+                      Member("a", ty.f32(), utils::Vector{Location(0)}),
+                      Member("b", ty.f32(), utils::Vector{Builtin(ast::BuiltinValue::kFragDepth)}),
+                  });
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.Of(output),
+         utils::Vector{
+             Return(Construct(ty.Of(output))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -116,12 +152,20 @@
     // fn main() -> Output {
     //   return Output();
     // }
-    auto* output =
-        Structure("Output", {Member("a", ty.f32(),
-                                    {Location(Source{{13, 43}}, 0),
-                                     Builtin(Source{{14, 52}}, ast::BuiltinValue::kFragDepth)})});
-    Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))},
-         {Stage(ast::PipelineStage::kFragment)});
+    auto* output = Structure(
+        "Output",
+        utils::Vector{
+            Member("a", ty.f32(),
+                   utils::Vector{Location(Source{{13, 43}}, 0),
+                                 Builtin(Source{{14, 52}}, ast::BuiltinValue::kFragDepth)}),
+        });
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.Of(output),
+         utils::Vector{
+             Return(Construct(ty.Of(output))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
@@ -138,10 +182,18 @@
     // fn main() -> Output {
     //   return Output();
     // }
-    auto* output = Structure("Output", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)}),
-                                        Member(Source{{14, 52}}, "b", ty.f32(), {})});
-    Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))},
-         {Stage(ast::PipelineStage::kFragment)});
+    auto* output =
+        Structure("Output", utils::Vector{
+                                Member(Source{{13, 43}}, "a", ty.f32(), utils::Vector{Location(0)}),
+                                Member(Source{{14, 52}}, "b", ty.f32(), {}),
+                            });
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.Of(output),
+         utils::Vector{
+             Return(Construct(ty.Of(output))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -158,11 +210,18 @@
     // fn main() -> Output {
     //   return Output();
     // }
-    auto* output =
-        Structure("Output", {Member("a", ty.f32(), {Builtin(ast::BuiltinValue::kFragDepth)}),
-                             Member("b", ty.f32(), {Builtin(ast::BuiltinValue::kFragDepth)})});
-    Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))},
-         {Stage(ast::PipelineStage::kFragment)});
+    auto* output = Structure(
+        "Output", utils::Vector{
+                      Member("a", ty.f32(), utils::Vector{Builtin(ast::BuiltinValue::kFragDepth)}),
+                      Member("b", ty.f32(), utils::Vector{Builtin(ast::BuiltinValue::kFragDepth)}),
+                  });
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.Of(output),
+         utils::Vector{
+             Return(Construct(ty.Of(output))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -174,8 +233,18 @@
 TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Location) {
     // @fragment
     // fn main(@location(0) param : f32) {}
-    auto* param = Param("param", ty.f32(), {Location(0)});
-    Func(Source{{12, 34}}, "main", {param}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    auto* param = Param("param", ty.f32(),
+                        utils::Vector{
+                            Location(0),
+                        });
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             param,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -184,7 +253,14 @@
     // @fragment
     // fn main(param : f32) {}
     auto* param = Param(Source{{13, 43}}, "param", ty.vec4<f32>());
-    Func(Source{{12, 34}}, "main", {param}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             param,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "13:43 error: missing entry point IO attribute on parameter");
@@ -194,9 +270,18 @@
     // @fragment
     // fn main(@location(0) @builtin(sample_index) param : u32) {}
     auto* param = Param("param", ty.u32(),
-                        {Location(Source{{13, 43}}, 0),
-                         Builtin(Source{{14, 52}}, ast::BuiltinValue::kSampleIndex)});
-    Func(Source{{12, 34}}, "main", {param}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+                        utils::Vector{
+                            Location(Source{{13, 43}}, 0),
+                            Builtin(Source{{14, 52}}, ast::BuiltinValue::kSampleIndex),
+                        });
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             param,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
@@ -210,11 +295,20 @@
     // };
     // @fragment
     // fn main(param : Input) {}
-    auto* input =
-        Structure("Input", {Member("a", ty.f32(), {Location(0)}),
-                            Member("b", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)})});
+    auto* input = Structure(
+        "Input", utils::Vector{
+                     Member("a", ty.f32(), utils::Vector{Location(0)}),
+                     Member("b", ty.u32(), utils::Vector{Builtin(ast::BuiltinValue::kSampleIndex)}),
+                 });
     auto* param = Param("param", ty.Of(input));
-    Func(Source{{12, 34}}, "main", {param}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             param,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -225,12 +319,22 @@
     // };
     // @fragment
     // fn main(param : Input) {}
-    auto* input =
-        Structure("Input", {Member("a", ty.u32(),
-                                   {Location(Source{{13, 43}}, 0),
-                                    Builtin(Source{{14, 52}}, ast::BuiltinValue::kSampleIndex)})});
+    auto* input = Structure(
+        "Input",
+        utils::Vector{
+            Member("a", ty.u32(),
+                   utils::Vector{Location(Source{{13, 43}}, 0),
+                                 Builtin(Source{{14, 52}}, ast::BuiltinValue::kSampleIndex)}),
+        });
     auto* param = Param("param", ty.Of(input));
-    Func(Source{{12, 34}}, "main", {param}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             param,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
@@ -245,10 +349,20 @@
     // };
     // @fragment
     // fn main(param : Input) {}
-    auto* input = Structure("Input", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)}),
-                                      Member(Source{{14, 52}}, "b", ty.f32(), {})});
+    auto* input =
+        Structure("Input", utils::Vector{
+                               Member(Source{{13, 43}}, "a", ty.f32(), utils::Vector{Location(0)}),
+                               Member(Source{{14, 52}}, "b", ty.f32(), {}),
+                           });
     auto* param = Param("param", ty.Of(input));
-    Func(Source{{12, 34}}, "main", {param}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             param,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), R"(14:52 error: missing entry point IO attribute
@@ -259,10 +373,23 @@
     // @fragment
     // fn main(@builtin(sample_index) param_a : u32,
     //         @builtin(sample_index) param_b : u32) {}
-    auto* param_a = Param("param_a", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)});
-    auto* param_b = Param("param_b", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)});
-    Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kFragment)});
+    auto* param_a = Param("param_a", ty.u32(),
+                          utils::Vector{
+                              Builtin(ast::BuiltinValue::kSampleIndex),
+                          });
+    auto* param_b = Param("param_b", ty.u32(),
+                          utils::Vector{
+                              Builtin(ast::BuiltinValue::kSampleIndex),
+                          });
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             param_a,
+             param_b,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -279,14 +406,27 @@
     // };
     // @fragment
     // fn main(param_a : InputA, param_b : InputB) {}
-    auto* input_a =
-        Structure("InputA", {Member("a", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)})});
-    auto* input_b =
-        Structure("InputB", {Member("a", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)})});
+    auto* input_a = Structure(
+        "InputA",
+        utils::Vector{
+            Member("a", ty.u32(), utils::Vector{Builtin(ast::BuiltinValue::kSampleIndex)}),
+        });
+    auto* input_b = Structure(
+        "InputB",
+        utils::Vector{
+            Member("a", ty.u32(), utils::Vector{Builtin(ast::BuiltinValue::kSampleIndex)}),
+        });
     auto* param_a = Param("param_a", ty.Of(input_a));
     auto* param_b = Param("param_b", ty.Of(input_b));
-    Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             param_a,
+             param_b,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(
@@ -298,7 +438,10 @@
 TEST_F(ResolverEntryPointValidationTest, VertexShaderMustReturnPosition) {
     // @vertex
     // fn main() {}
-    Func(Source{{12, 34}}, "main", {}, ty.void_(), {}, {Stage(ast::PipelineStage::kVertex)});
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kVertex),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -306,6 +449,135 @@
               "in its return type");
 }
 
+TEST_F(ResolverEntryPointValidationTest, PushConstantAllowedWithEnable) {
+    // enable chromium_experimental_push_constant;
+    // var<push_constant> a : u32;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    GlobalVar("a", ty.u32(), ast::StorageClass::kPushConstant);
+
+    EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverEntryPointValidationTest, PushConstantDisallowedWithoutEnable) {
+    // var<push_constant> a : u32;
+    GlobalVar(Source{{1, 2}}, "a", ty.u32(), ast::StorageClass::kPushConstant);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "1:2 error: use of variable storage class 'push_constant' requires enabling "
+              "extension 'chromium_experimental_push_constant'");
+}
+
+TEST_F(ResolverEntryPointValidationTest, PushConstantAllowedWithIgnoreStorageClassAttribute) {
+    // var<push_constant> a : u32; // With ast::DisabledValidation::kIgnoreStorageClass
+    GlobalVar("a", ty.u32(), ast::StorageClass::kPushConstant,
+              utils::Vector{Disable(ast::DisabledValidation::kIgnoreStorageClass)});
+
+    EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverEntryPointValidationTest, PushConstantOneVariableUsedInEntryPoint) {
+    // enable chromium_experimental_push_constant;
+    // var<push_constant> a : u32;
+    // @compute @workgroup_size(1) fn main() {
+    //   _ = a;
+    // }
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    GlobalVar("a", ty.u32(), ast::StorageClass::kPushConstant);
+
+    Func("main", {}, ty.void_(), utils::Vector{Assign(Phony(), "a")},
+         utils::Vector{Stage(ast::PipelineStage::kCompute),
+                       create<ast::WorkgroupAttribute>(Expr(1_i))});
+
+    EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverEntryPointValidationTest, PushConstantTwoVariablesUsedInEntryPoint) {
+    // enable chromium_experimental_push_constant;
+    // var<push_constant> a : u32;
+    // var<push_constant> b : u32;
+    // @compute @workgroup_size(1) fn main() {
+    //   _ = a;
+    //   _ = b;
+    // }
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    GlobalVar(Source{{1, 2}}, "a", ty.u32(), ast::StorageClass::kPushConstant);
+    GlobalVar(Source{{3, 4}}, "b", ty.u32(), ast::StorageClass::kPushConstant);
+
+    Func(Source{{5, 6}}, "main", {}, ty.void_(),
+         utils::Vector{Assign(Phony(), "a"), Assign(Phony(), "b")},
+         utils::Vector{Stage(ast::PipelineStage::kCompute),
+                       create<ast::WorkgroupAttribute>(Expr(1_i))});
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(5:6 error: entry point 'main' uses two different 'push_constant' variables.
+3:4 note: first 'push_constant' variable declaration is here
+1:2 note: second 'push_constant' variable declaration is here)");
+}
+
+TEST_F(ResolverEntryPointValidationTest,
+       PushConstantTwoVariablesUsedInEntryPointWithFunctionGraph) {
+    // enable chromium_experimental_push_constant;
+    // var<push_constant> a : u32;
+    // var<push_constant> b : u32;
+    // fn uses_a() {
+    //   _ = a;
+    // }
+    // fn uses_b() {
+    //   _ = b;
+    // }
+    // @compute @workgroup_size(1) fn main() {
+    //   uses_a();
+    //   uses_b();
+    // }
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    GlobalVar(Source{{1, 2}}, "a", ty.u32(), ast::StorageClass::kPushConstant);
+    GlobalVar(Source{{3, 4}}, "b", ty.u32(), ast::StorageClass::kPushConstant);
+
+    Func(Source{{5, 6}}, "uses_a", {}, ty.void_(), utils::Vector{Assign(Phony(), "a")});
+    Func(Source{{7, 8}}, "uses_b", {}, ty.void_(), utils::Vector{Assign(Phony(), "b")});
+
+    Func(Source{{9, 10}}, "main", {}, ty.void_(),
+         utils::Vector{CallStmt(Call("uses_a")), CallStmt(Call("uses_b"))},
+         utils::Vector{Stage(ast::PipelineStage::kCompute),
+                       create<ast::WorkgroupAttribute>(Expr(1_i))});
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(9:10 error: entry point 'main' uses two different 'push_constant' variables.
+3:4 note: first 'push_constant' variable declaration is here
+7:8 note: called by function 'uses_b'
+9:10 note: called by entry point 'main'
+1:2 note: second 'push_constant' variable declaration is here
+5:6 note: called by function 'uses_a'
+9:10 note: called by entry point 'main')");
+}
+
+TEST_F(ResolverEntryPointValidationTest, PushConstantTwoVariablesUsedInDifferentEntryPoint) {
+    // enable chromium_experimental_push_constant;
+    // var<push_constant> a : u32;
+    // var<push_constant> b : u32;
+    // @compute @workgroup_size(1) fn uses_a() {
+    //   _ = a;
+    // }
+    // @compute @workgroup_size(1) fn uses_b() {
+    //   _ = a;
+    // }
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    GlobalVar("a", ty.u32(), ast::StorageClass::kPushConstant);
+    GlobalVar("b", ty.u32(), ast::StorageClass::kPushConstant);
+
+    Func("uses_a", {}, ty.void_(), utils::Vector{Assign(Phony(), "a")},
+         utils::Vector{Stage(ast::PipelineStage::kCompute),
+                       create<ast::WorkgroupAttribute>(Expr(1_i))});
+    Func("uses_b", {}, ty.void_(), utils::Vector{Assign(Phony(), "b")},
+         utils::Vector{Stage(ast::PipelineStage::kCompute),
+                       create<ast::WorkgroupAttribute>(Expr(1_i))});
+
+    EXPECT_TRUE(r()->Resolve());
+}
+
 namespace TypeValidationTests {
 struct Params {
     builder::ast_type_func_ptr create_ast_type;
@@ -354,8 +626,19 @@
 
     Enable(ast::Extension::kF16);
 
-    auto* a = Param("a", params.create_ast_type(*this), {Location(0), Flat()});
-    Func(Source{{12, 34}}, "main", {a}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    auto* a = Param("a", params.create_ast_type(*this),
+                    utils::Vector{
+                        Location(0),
+                        Flat(),
+                    });
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             a,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -374,10 +657,19 @@
 
     Enable(ast::Extension::kF16);
 
-    auto* input =
-        Structure("Input", {Member("a", params.create_ast_type(*this), {Location(0), Flat()})});
+    auto* input = Structure(
+        "Input", utils::Vector{
+                     Member("a", params.create_ast_type(*this), utils::Vector{Location(0), Flat()}),
+                 });
     auto* a = Param("a", ty.Of(input), {});
-    Func(Source{{12, 34}}, "main", {a}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             a,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -395,9 +687,16 @@
 
     Enable(ast::Extension::kF16);
 
-    Func(Source{{12, 34}}, "main", {}, params.create_ast_type(*this),
-         {Return(Construct(params.create_ast_type(*this)))}, {Stage(ast::PipelineStage::kFragment)},
-         {Location(0)});
+    Func(Source{{12, 34}}, "main", utils::Empty, params.create_ast_type(*this),
+         utils::Vector{
+             Return(Construct(params.create_ast_type(*this))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         utils::Vector{
+             Location(0),
+         });
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -418,9 +717,17 @@
 
     Enable(ast::Extension::kF16);
 
-    auto* output = Structure("Output", {Member("a", params.create_ast_type(*this), {Location(0)})});
-    Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))},
-         {Stage(ast::PipelineStage::kFragment)});
+    auto* output = Structure(
+        "Output", utils::Vector{
+                      Member("a", params.create_ast_type(*this), utils::Vector{Location(0)}),
+                  });
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.Of(output),
+         utils::Vector{
+             Return(Construct(ty.Of(output))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -442,8 +749,19 @@
     // @fragment
     // fn frag_main(@location(0) @interpolate(flat) a: i32) {}
 
-    auto* p = Param(Source{{12, 34}}, "a", ty.i32(), {Location(0), Flat()});
-    Func("frag_main", {p}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    auto* p = Param(Source{{12, 34}}, "a", ty.i32(),
+                    utils::Vector{
+                        Location(0),
+                        Flat(),
+                    });
+    Func("frag_main",
+         utils::Vector{
+             p,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -452,8 +770,18 @@
     // @fragment
     // fn frag_main(@location(0) a: bool) {}
 
-    auto* p = Param(Source{{12, 34}}, "a", ty.bool_(), {Location(Source{{34, 56}}, 0)});
-    Func("frag_main", {p}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    auto* p = Param(Source{{12, 34}}, "a", ty.bool_(),
+                    utils::Vector{
+                        Location(Source{{34, 56}}, 0),
+                    });
+    Func("frag_main",
+         utils::Vector{
+             p,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -467,9 +795,16 @@
     // @fragment
     // fn frag_main()->@location(0) array<f32, 2> { return array<f32, 2>(); }
 
-    Func(Source{{12, 34}}, "frag_main", {}, ty.array<f32, 2>(),
-         {Return(Construct(ty.array<f32, 2>()))}, {Stage(ast::PipelineStage::kFragment)},
-         {Location(Source{{34, 56}}, 0)});
+    Func(Source{{12, 34}}, "frag_main", utils::Empty, ty.array<f32, 2>(),
+         utils::Vector{
+             Return(Construct(ty.array<f32, 2>())),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         utils::Vector{
+             Location(Source{{34, 56}}, 0),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -485,9 +820,21 @@
     // };
     // @fragment
     // fn main(@location(0) param : Input) {}
-    auto* input = Structure("Input", {Member("a", ty.f32())});
-    auto* param = Param(Source{{12, 34}}, "param", ty.Of(input), {Location(Source{{13, 43}}, 0)});
-    Func(Source{{12, 34}}, "main", {param}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    auto* input = Structure("Input", utils::Vector{
+                                         Member("a", ty.f32()),
+                                     });
+    auto* param = Param(Source{{12, 34}}, "param", ty.Of(input),
+                        utils::Vector{
+                            Location(Source{{13, 43}}, 0),
+                        });
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             param,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -506,10 +853,22 @@
     // };
     // @fragment
     // fn main(param : Input) {}
-    auto* inner = Structure("Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
-    auto* input = Structure("Input", {Member(Source{{14, 52}}, "a", ty.Of(inner))});
+    auto* inner =
+        Structure("Inner", utils::Vector{
+                               Member(Source{{13, 43}}, "a", ty.f32(), utils::Vector{Location(0)}),
+                           });
+    auto* input = Structure("Input", utils::Vector{
+                                         Member(Source{{14, 52}}, "a", ty.Of(inner)),
+                                     });
     auto* param = Param("param", ty.Of(input));
-    Func(Source{{12, 34}}, "main", {param}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             param,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -523,10 +882,19 @@
     // };
     // @fragment
     // fn main(param : Input) {}
-    auto* input =
-        Structure("Input", {Member(Source{{13, 43}}, "a", ty.array<f32>(), {Location(0)})});
+    auto* input = Structure(
+        "Input", utils::Vector{
+                     Member(Source{{13, 43}}, "a", ty.array<f32>(), utils::Vector{Location(0)}),
+                 });
     auto* param = Param("param", ty.Of(input));
-    Func(Source{{12, 34}}, "main", {param}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             param,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -542,11 +910,20 @@
     // fn frag_main( a: S) {}
 
     auto* m = Member(Source{{34, 56}}, "m", ty.array<i32>(),
-                     ast::AttributeList{Location(Source{{12, 34}}, 0u)});
-    auto* s = Structure("S", {m});
+                     utils::Vector{
+                         Location(Source{{12, 34}}, 0u),
+                     });
+    auto* s = Structure("S", utils::Vector{m});
     auto* p = Param("a", ty.Of(s));
 
-    Func("frag_main", {p}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    Func("frag_main",
+         utils::Vector{
+             p,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -561,11 +938,19 @@
     // @fragment
     // fn frag_main() -> S {}
     auto* m = Member(Source{{34, 56}}, "m", ty.atomic<i32>(),
-                     ast::AttributeList{Location(Source{{12, 34}}, 0u)});
-    auto* s = Structure("S", {m});
+                     utils::Vector{
+                         Location(Source{{12, 34}}, 0u),
+                     });
+    auto* s = Structure("S", utils::Vector{m});
 
-    Func("frag_main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
-         {Stage(ast::PipelineStage::kFragment)}, {});
+    Func("frag_main", utils::Empty, ty.Of(s),
+         utils::Vector{
+             Return(Construct(ty.Of(s))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         {});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -579,8 +964,10 @@
     // struct S { @location(0) m: mat3x2<f32>; };
 
     auto* m = Member(Source{{34, 56}}, "m", ty.mat3x2<f32>(),
-                     ast::AttributeList{Location(Source{{12, 34}}, 0u)});
-    Structure("S", {m});
+                     utils::Vector{
+                         Location(Source{{12, 34}}, 0u),
+                     });
+    Structure("S", utils::Vector{m});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -599,11 +986,18 @@
     // fn main() -> Output {
     //   return Output();
     // }
-    auto* output =
-        Structure("Output", {Member("a", ty.f32(), {Location(0)}),
-                             Member("b", ty.f32(), {Builtin(ast::BuiltinValue::kFragDepth)})});
-    Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))},
-         {Stage(ast::PipelineStage::kFragment)});
+    auto* output = Structure(
+        "Output", utils::Vector{
+                      Member("a", ty.f32(), utils::Vector{Location(0)}),
+                      Member("b", ty.f32(), utils::Vector{Builtin(ast::BuiltinValue::kFragDepth)}),
+                  });
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.Of(output),
+         utils::Vector{
+             Return(Construct(ty.Of(output))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -616,9 +1010,19 @@
     // fn main() -> @location(0) Output {
     //   return Output();
     // }
-    auto* output = Structure("Output", {Member("a", ty.f32())});
-    Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))},
-         {Stage(ast::PipelineStage::kVertex)}, {Location(Source{{13, 43}}, 0)});
+    auto* output = Structure("Output", utils::Vector{
+                                           Member("a", ty.f32()),
+                                       });
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.Of(output),
+         utils::Vector{
+             Return(Construct(ty.Of(output))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kVertex),
+         },
+         utils::Vector{
+             Location(Source{{13, 43}}, 0),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -637,10 +1041,20 @@
     // };
     // @fragment
     // fn main() -> Output { return Output(); }
-    auto* inner = Structure("Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
-    auto* output = Structure("Output", {Member(Source{{14, 52}}, "a", ty.Of(inner))});
-    Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))},
-         {Stage(ast::PipelineStage::kFragment)});
+    auto* inner =
+        Structure("Inner", utils::Vector{
+                               Member(Source{{13, 43}}, "a", ty.f32(), utils::Vector{Location(0)}),
+                           });
+    auto* output = Structure("Output", utils::Vector{
+                                           Member(Source{{14, 52}}, "a", ty.Of(inner)),
+                                       });
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.Of(output),
+         utils::Vector{
+             Return(Construct(ty.Of(output))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -656,10 +1070,17 @@
     // fn main() -> Output {
     //   return Output();
     // }
-    auto* output = Structure("Output", {Member(Source{{13, 43}}, "a", ty.array<f32>(),
-                                               {Location(Source{{12, 34}}, 0)})});
-    Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))},
-         {Stage(ast::PipelineStage::kFragment)});
+    auto* output = Structure("Output", utils::Vector{
+                                           Member(Source{{13, 43}}, "a", ty.array<f32>(),
+                                                  utils::Vector{Location(Source{{12, 34}}, 0)}),
+                                       });
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.Of(output),
+         utils::Vector{
+             Return(Construct(ty.Of(output))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -670,32 +1091,51 @@
 }
 
 TEST_F(LocationAttributeTests, ComputeShaderLocation_Input) {
-    Func("main", {}, ty.i32(), {Return(Expr(1_i))},
-         {Stage(ast::PipelineStage::kCompute),
-          create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i))},
-         ast::AttributeList{Location(Source{{12, 34}}, 1)});
+    Func("main", utils::Empty, ty.i32(),
+         utils::Vector{
+             Return(Expr(1_i)),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i)),
+         },
+         utils::Vector{
+             Location(Source{{12, 34}}, 1),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: attribute is not valid for compute shader output");
 }
 
 TEST_F(LocationAttributeTests, ComputeShaderLocation_Output) {
-    auto* input = Param("input", ty.i32(), ast::AttributeList{Location(Source{{12, 34}}, 0u)});
-    Func("main", {input}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute),
-          create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i))});
+    auto* input = Param("input", ty.i32(),
+                        utils::Vector{
+                            Location(Source{{12, 34}}, 0u),
+                        });
+    Func("main", utils::Vector{input}, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i)),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: attribute is not valid for compute shader inputs");
 }
 
 TEST_F(LocationAttributeTests, ComputeShaderLocationStructMember_Output) {
-    auto* m = Member("m", ty.i32(), ast::AttributeList{Location(Source{{12, 34}}, 0u)});
-    auto* s = Structure("S", {m});
-    Func(Source{{56, 78}}, "main", {}, ty.Of(s),
-         ast::StatementList{Return(Expr(Construct(ty.Of(s))))},
-         {Stage(ast::PipelineStage::kCompute),
-          create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i))});
+    auto* m = Member("m", ty.i32(),
+                     utils::Vector{
+                         Location(Source{{12, 34}}, 0u),
+                     });
+    auto* s = Structure("S", utils::Vector{m});
+    Func(Source{{56, 78}}, "main", utils::Empty, ty.Of(s),
+         utils::Vector{
+             Return(Expr(Construct(ty.Of(s)))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i)),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -704,12 +1144,17 @@
 }
 
 TEST_F(LocationAttributeTests, ComputeShaderLocationStructMember_Input) {
-    auto* m = Member("m", ty.i32(), ast::AttributeList{Location(Source{{12, 34}}, 0u)});
-    auto* s = Structure("S", {m});
+    auto* m = Member("m", ty.i32(),
+                     utils::Vector{
+                         Location(Source{{12, 34}}, 0u),
+                     });
+    auto* s = Structure("S", utils::Vector{m});
     auto* input = Param("input", ty.Of(s));
-    Func(Source{{56, 78}}, "main", {input}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute),
-          create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i))});
+    Func(Source{{56, 78}}, "main", utils::Vector{input}, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i)),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -721,10 +1166,23 @@
     // @fragment
     // fn main(@location(1) param_a : f32,
     //         @location(1) param_b : f32) {}
-    auto* param_a = Param("param_a", ty.f32(), {Location(1)});
-    auto* param_b = Param("param_b", ty.f32(), {Location(Source{{12, 34}}, 1)});
-    Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kFragment)});
+    auto* param_a = Param("param_a", ty.f32(),
+                          utils::Vector{
+                              Location(1),
+                          });
+    auto* param_b = Param("param_b", ty.f32(),
+                          utils::Vector{
+                              Location(Source{{12, 34}}, 1),
+                          });
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             param_a,
+             param_b,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: location(1) attribute appears multiple times");
@@ -739,12 +1197,24 @@
     // };
     // @fragment
     // fn main(param_a : InputA, param_b : InputB) {}
-    auto* input_a = Structure("InputA", {Member("a", ty.f32(), {Location(1)})});
-    auto* input_b = Structure("InputB", {Member("a", ty.f32(), {Location(Source{{34, 56}}, 1)})});
+    auto* input_a = Structure("InputA", utils::Vector{
+                                            Member("a", ty.f32(), utils::Vector{Location(1)}),
+                                        });
+    auto* input_b =
+        Structure("InputB", utils::Vector{
+                                Member("a", ty.f32(), utils::Vector{Location(Source{{34, 56}}, 1)}),
+                            });
     auto* param_a = Param("param_a", ty.Of(input_a));
     auto* param_b = Param("param_b", ty.Of(input_b));
-    Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func(Source{{12, 34}}, "main",
+         utils::Vector{
+             param_a,
+             param_b,
+         },
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
diff --git a/src/tint/resolver/evaluation_stage_test.cc b/src/tint/resolver/evaluation_stage_test.cc
index 236e4b3..4b603bb 100644
--- a/src/tint/resolver/evaluation_stage_test.cc
+++ b/src/tint/resolver/evaluation_stage_test.cc
@@ -269,7 +269,7 @@
     // struct S { m : i32 };
     // const str = S();
     // str.m
-    Structure("S", {Member("m", ty.i32())});
+    Structure("S", utils::Vector{Member("m", ty.i32())});
     auto* str = Const("str", nullptr, Construct(ty.type_name("S")));
     auto* expr = MemberAccessor(str, "m");
     WrapInFunction(str, expr);
@@ -283,7 +283,7 @@
     // struct S { m : i32 };
     // var str = S();
     // str.m
-    Structure("S", {Member("m", ty.i32())});
+    Structure("S", utils::Vector{Member("m", ty.i32())});
     auto* str = Var("str", nullptr, 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 c1ea33c..372d91a 100644
--- a/src/tint/resolver/function_validation_test.cc
+++ b/src/tint/resolver/function_validation_test.cc
@@ -30,8 +30,8 @@
 TEST_F(ResolverFunctionValidationTest, DuplicateParameterName) {
     // fn func_a(common_name : f32) { }
     // fn func_b(common_name : f32) { }
-    Func("func_a", {Param("common_name", ty.f32())}, ty.void_(), {});
-    Func("func_b", {Param("common_name", ty.f32())}, ty.void_(), {});
+    Func("func_a", utils::Vector{Param("common_name", ty.f32())}, ty.void_(), utils::Empty);
+    Func("func_b", utils::Vector{Param("common_name", ty.f32())}, ty.void_(), utils::Empty);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -40,7 +40,7 @@
     // var<private> common_name : f32;
     // fn func(common_name : f32) { }
     GlobalVar("common_name", ty.f32(), ast::StorageClass::kPrivate);
-    Func("func", {Param("common_name", ty.f32())}, ty.void_(), {});
+    Func("func", utils::Vector{Param("common_name", ty.f32())}, ty.void_(), utils::Empty);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -49,8 +49,10 @@
     // fn func(common_name : f32) {
     //   let common_name = 1i;
     // }
-    Func("func", {Param(Source{{12, 34}}, "common_name", ty.f32())}, ty.void_(),
-         {Decl(Let(Source{{56, 78}}, "common_name", nullptr, Expr(1_i)))});
+    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))),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), R"(56:78 error: redeclaration of 'common_name'
@@ -59,12 +61,14 @@
 
 TEST_F(ResolverFunctionValidationTest, NestedLocalMayShadowParameter) {
     // fn func(common_name : f32) {
-    //   {
+    // utils::Vector  {
     //     let common_name = 1i;
     //   }
     // }
-    Func("func", {Param(Source{{12, 34}}, "common_name", ty.f32())}, ty.void_(),
-         {Block(Decl(Let(Source{{56, 78}}, "common_name", nullptr, Expr(1_i))))});
+    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)))),
+         });
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -73,8 +77,8 @@
     // fn func { var a:i32 = 2i; }
     auto* var = Var("a", ty.i32(), Expr(2_i));
 
-    Func(Source{{12, 34}}, "func", {}, ty.void_(),
-         {
+    Func(Source{{12, 34}}, "func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
          });
 
@@ -88,8 +92,8 @@
     // }
 
     auto* var = Var("func", ty.i32(), Expr(0_i));
-    Func("func", {}, ty.i32(),
-         {
+    Func("func", utils::Empty, ty.i32(),
+         utils::Vector{
              Decl(var),
              Return(Source{{12, 34}}, Expr("func")),
          });
@@ -102,13 +106,13 @@
     // fn b() -> i32 { return 2; }
 
     auto* var = Var("b", ty.i32(), Expr(0_i));
-    Func("a", {}, ty.void_(),
-         {
+    Func("a", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
          });
 
-    Func(Source{{12, 34}}, "b", {}, ty.i32(),
-         {
+    Func(Source{{12, 34}}, "b", utils::Empty, ty.i32(),
+         utils::Vector{
              Return(2_i),
          });
 
@@ -126,7 +130,7 @@
     auto* ret = Return();
     auto* assign_a = Assign(Source{{12, 34}}, "a", 2_i);
 
-    Func("func", {}, ty.void_(), {decl_a, ret, assign_a});
+    Func("func", utils::Empty, ty.void_(), utils::Vector{decl_a, ret, assign_a});
 
     ASSERT_TRUE(r()->Resolve());
 
@@ -139,7 +143,7 @@
 TEST_F(ResolverFunctionValidationTest, UnreachableCode_return_InBlocks) {
     // fn func() -> {
     //  var a : i32;
-    //  {{{return;}}}
+    // utils::Vector  {{{return;}}}
     //  a = 2i;
     //}
 
@@ -147,7 +151,8 @@
     auto* ret = Return();
     auto* assign_a = Assign(Source{{12, 34}}, "a", 2_i);
 
-    Func("func", {}, ty.void_(), {decl_a, Block(Block(Block(ret))), assign_a});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{decl_a, Block(Block(Block(ret))), assign_a});
 
     ASSERT_TRUE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
@@ -167,7 +172,7 @@
     auto* discard = Discard();
     auto* assign_a = Assign(Source{{12, 34}}, "a", 2_i);
 
-    Func("func", {}, ty.void_(), {decl_a, discard, assign_a});
+    Func("func", utils::Empty, ty.void_(), utils::Vector{decl_a, discard, assign_a});
 
     ASSERT_TRUE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
@@ -179,7 +184,7 @@
 TEST_F(ResolverFunctionValidationTest, UnreachableCode_discard_InBlocks) {
     // fn func() -> {
     //  var a : i32;
-    //  {{{discard;}}}
+    // utils::Vector  {{{discard;}}}
     //  a = 2i;
     //}
 
@@ -187,7 +192,8 @@
     auto* discard = Discard();
     auto* assign_a = Assign(Source{{12, 34}}, "a", 2_i);
 
-    Func("func", {}, ty.void_(), {decl_a, Block(Block(Block(discard))), assign_a});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{decl_a, Block(Block(Block(discard))), assign_a});
 
     ASSERT_TRUE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
@@ -201,8 +207,8 @@
 
     auto* var = Var("a", ty.i32(), Expr(2_i));
 
-    Func(Source{{12, 34}}, "func", {}, ty.i32(),
-         {
+    Func(Source{{12, 34}}, "func", utils::Empty, ty.i32(),
+         utils::Vector{
              Decl(var),
          });
 
@@ -213,7 +219,7 @@
 TEST_F(ResolverFunctionValidationTest, VoidFunctionEndWithoutReturnStatementEmptyBody_Pass) {
     // fn func {}
 
-    Func(Source{{12, 34}}, "func", {}, ty.void_(), {});
+    Func(Source{{12, 34}}, "func", utils::Empty, ty.void_(), utils::Empty);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -221,7 +227,7 @@
 TEST_F(ResolverFunctionValidationTest, FunctionEndWithoutReturnStatementEmptyBody_Fail) {
     // fn func() -> int {}
 
-    Func(Source{{12, 34}}, "func", {}, ty.i32(), {});
+    Func(Source{{12, 34}}, "func", utils::Empty, ty.i32(), utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: missing return at end of function");
@@ -230,14 +236,20 @@
 TEST_F(ResolverFunctionValidationTest, FunctionTypeMustMatchReturnStatementType_Pass) {
     // fn func { return; }
 
-    Func("func", {}, ty.void_(), {Return()});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
+             Return(),
+         });
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(ResolverFunctionValidationTest, VoidFunctionReturnsAInt) {
     // fn func { return 2; }
-    Func("func", {}, ty.void_(), {Return(Source{{12, 34}}, Expr(2_a))});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
+             Return(Source{{12, 34}}, Expr(2_a)),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -247,7 +259,10 @@
 
 TEST_F(ResolverFunctionValidationTest, VoidFunctionReturnsAFloat) {
     // fn func { return 2.0; }
-    Func("func", {}, ty.void_(), {Return(Source{{12, 34}}, Expr(2.0_a))});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
+             Return(Source{{12, 34}}, Expr(2.0_a)),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -257,7 +272,10 @@
 
 TEST_F(ResolverFunctionValidationTest, VoidFunctionReturnsI32) {
     // fn func { return 2i; }
-    Func("func", {}, ty.void_(), {Return(Source{{12, 34}}, Expr(2_i))});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
+             Return(Source{{12, 34}}, Expr(2_i)),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -268,9 +286,12 @@
 TEST_F(ResolverFunctionValidationTest, FunctionTypeMustMatchReturnStatementType_void_fail) {
     // fn v { return; }
     // fn func { return v(); }
-    Func("v", {}, ty.void_(), {Return()});
-    Func("func", {}, ty.void_(),
-         {
+    Func("v", utils::Empty, ty.void_(),
+         utils::Vector{
+             Return(),
+         });
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(Call(Source{{12, 34}}, "v")),
          });
 
@@ -280,8 +301,8 @@
 
 TEST_F(ResolverFunctionValidationTest, FunctionTypeMustMatchReturnStatementTypeMissing_fail) {
     // fn func() -> f32 { return; }
-    Func("func", {}, ty.f32(),
-         {
+    Func("func", utils::Empty, ty.f32(),
+         utils::Vector{
              Return(Source{{12, 34}}, nullptr),
          });
 
@@ -293,8 +314,8 @@
 
 TEST_F(ResolverFunctionValidationTest, FunctionTypeMustMatchReturnStatementTypeF32_pass) {
     // fn func() -> f32 { return 2.0; }
-    Func("func", {}, ty.f32(),
-         {
+    Func("func", utils::Empty, ty.f32(),
+         utils::Vector{
              Return(Source{{12, 34}}, Expr(2_f)),
          });
 
@@ -303,8 +324,8 @@
 
 TEST_F(ResolverFunctionValidationTest, FunctionTypeMustMatchReturnStatementTypeF32_fail) {
     // fn func() -> f32 { return 2i; }
-    Func("func", {}, ty.f32(),
-         {
+    Func("func", utils::Empty, ty.f32(),
+         utils::Vector{
              Return(Source{{12, 34}}, Expr(2_i)),
          });
 
@@ -318,8 +339,8 @@
     // type myf32 = f32;
     // fn func() -> myf32 { return 2.0; }
     auto* myf32 = Alias("myf32", ty.f32());
-    Func("func", {}, ty.Of(myf32),
-         {
+    Func("func", utils::Empty, ty.Of(myf32),
+         utils::Vector{
              Return(Source{{12, 34}}, Expr(2_f)),
          });
 
@@ -330,8 +351,8 @@
     // type myf32 = f32;
     // fn func() -> myf32 { return 2u; }
     auto* myf32 = Alias("myf32", ty.f32());
-    Func("func", {}, ty.Of(myf32),
-         {
+    Func("func", utils::Empty, ty.Of(myf32),
+         utils::Vector{
              Return(Source{{12, 34}}, Expr(2_u)),
          });
 
@@ -344,11 +365,14 @@
 TEST_F(ResolverFunctionValidationTest, CannotCallEntryPoint) {
     // @compute @workgroup_size(1) fn entrypoint() {}
     // fn func() { return entrypoint(); }
-    Func("entrypoint", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
+    Func("entrypoint", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1_i),
+         });
 
-    Func("func", {}, ty.void_(),
-         {
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
              CallStmt(Call(Source{{12, 34}}, "entrypoint")),
          });
 
@@ -360,7 +384,10 @@
 TEST_F(ResolverFunctionValidationTest, CannotCallFunctionAtModuleScope) {
     // fn F() -> i32 { return 1; }
     // var x = F();
-    Func("F", {}, ty.i32(), {Return(1_i)});
+    Func("F", utils::Empty, ty.i32(),
+         utils::Vector{
+             Return(1_i),
+         });
     GlobalVar("x", nullptr, Call(Source{{12, 34}}, "F"), ast::StorageClass::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
@@ -371,11 +398,11 @@
     // @fragment
     // @vertex
     // fn main() { return; }
-    Func(Source{{12, 34}}, "main", {}, ty.void_(),
-         {
+    Func(Source{{12, 34}}, "main", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(),
          },
-         ast::AttributeList{
+         utils::Vector{
              Stage(Source{{12, 34}}, ast::PipelineStage::kVertex),
              Stage(Source{{56, 78}}, ast::PipelineStage::kFragment),
          });
@@ -387,8 +414,8 @@
 }
 
 TEST_F(ResolverFunctionValidationTest, NoPipelineEntryPoints) {
-    Func("vtx_func", {}, ty.void_(),
-         {
+    Func("vtx_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(),
          });
 
@@ -403,8 +430,8 @@
     auto* bar = Param("bar", ty.f32());
     auto* baz = Var("baz", ty.f32(), Expr("bar"));
 
-    Func("foo", {bar}, ty.void_(),
-         {
+    Func("foo", utils::Vector{bar}, ty.void_(),
+         utils::Vector{
              Decl(baz),
          });
 
@@ -419,8 +446,8 @@
     auto* bar = Param("bar", ty.f32());
     auto* baz = Let("baz", ty.f32(), Expr("bar"));
 
-    Func("foo", {bar}, ty.void_(),
-         {
+    Func("foo", utils::Vector{bar}, ty.void_(),
+         utils::Vector{
              Decl(baz),
          });
 
@@ -428,8 +455,11 @@
 }
 
 TEST_F(ResolverFunctionValidationTest, FunctionParamsConst) {
-    Func("foo", {Param(Sym("arg"), ty.i32())}, ty.void_(),
-         {Assign(Expr(Source{{12, 34}}, "arg"), Expr(1_i)), Return()});
+    Func("foo", utils::Vector{Param(Sym("arg"), ty.i32())}, ty.void_(),
+         utils::Vector{
+             Assign(Expr(Source{{12, 34}}, "arg"), Expr(1_i)),
+             Return(),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -443,8 +473,11 @@
     // fn main() {}
     auto* x = GlobalConst("x", ty.u32(), Expr(4_u));
     auto* y = GlobalConst("y", ty.u32(), Expr(8_u));
-    auto* func = Func("main", {}, ty.void_(), {},
-                      {Stage(ast::PipelineStage::kCompute), WorkgroupSize("x", "y", 16_u)});
+    auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{
+                          Stage(ast::PipelineStage::kCompute),
+                          WorkgroupSize("x", "y", 16_u),
+                      });
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -464,8 +497,11 @@
     // @compute @workgroup_size(1i, 2i, 3i)
     // fn main() {}
 
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Source{{12, 34}}, 1_i, 2_i, 3_i)});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Source{{12, 34}}, 1_i, 2_i, 3_i),
+         });
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -474,8 +510,11 @@
     // @compute @workgroup_size(1u, 2u, 3u)
     // fn main() {}
 
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Source{{12, 34}}, 1_u, 2_u, 3_u)});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Source{{12, 34}}, 1_u, 2_u, 3_u),
+         });
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -484,8 +523,11 @@
     // @compute @workgroup_size(1, 2i, 3)
     // fn main() {}
 
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Source{{12, 34}}, 1_a, 2_i, 3_a)});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Source{{12, 34}}, 1_a, 2_i, 3_a),
+         });
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -494,8 +536,11 @@
     // @compute @workgroup_size(1u, 2, 3u)
     // fn main() {}
 
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Source{{12, 34}}, 1_u, 2_a, 3_u)});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Source{{12, 34}}, 1_u, 2_a, 3_u),
+         });
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -504,8 +549,11 @@
     // @compute @workgroup_size(1u, 2, 3_i)
     // fn main() {}
 
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Source{{12, 34}}, 1_u, 2_a, 3_i)});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Source{{12, 34}}, 1_u, 2_a, 3_i),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -516,8 +564,11 @@
     // @compute @workgroup_size(1_i, 2u, 3)
     // fn main() {}
 
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Source{{12, 34}}, 1_i, 2_u, 3_a)});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Source{{12, 34}}, 1_i, 2_u, 3_a),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -529,8 +580,11 @@
     // @compute @workgroup_size(1i, x)
     // fn main() {}
     GlobalConst("x", ty.u32(), Expr(64_u));
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Source{{12, 34}}, 1_i, "x")});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Source{{12, 34}}, 1_i, "x"),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -544,8 +598,11 @@
     // fn main() {}
     GlobalConst("x", ty.u32(), Expr(64_u));
     GlobalConst("y", ty.i32(), Expr(32_i));
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Source{{12, 34}}, "x", "y")});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Source{{12, 34}}, "x", "y"),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -559,8 +616,11 @@
     // fn main() {}
     GlobalConst("x", ty.u32(), Expr(4_u));
     GlobalConst("y", ty.u32(), Expr(8_u));
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Source{{12, 34}}, "x", "y", 16_i)});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Source{{12, 34}}, "x", "y", 16_i),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -571,8 +631,11 @@
     // @compute @workgroup_size(64.0)
     // fn main() {}
 
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Expr(Source{{12, 34}}, 64_f))});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Expr(Source{{12, 34}}, 64_f)),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -584,8 +647,11 @@
     // @compute @workgroup_size(-2i)
     // fn main() {}
 
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Expr(Source{{12, 34}}, -2_i))});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Expr(Source{{12, 34}}, -2_i)),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: workgroup_size argument must be at least 1");
@@ -595,8 +661,11 @@
     // @compute @workgroup_size(0i)
     // fn main() {}
 
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Expr(Source{{12, 34}}, 0_i))});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Expr(Source{{12, 34}}, 0_i)),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: workgroup_size argument must be at least 1");
@@ -607,8 +676,11 @@
     // @compute @workgroup_size(x)
     // fn main() {}
     GlobalConst("x", ty.f32(), Expr(64_f));
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Expr(Source{{12, 34}}, "x")),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -621,8 +693,11 @@
     // @compute @workgroup_size(x)
     // fn main() {}
     GlobalConst("x", ty.i32(), Expr(-2_i));
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Expr(Source{{12, 34}}, "x")),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: workgroup_size argument must be at least 1");
@@ -633,8 +708,11 @@
     // @compute @workgroup_size(x)
     // fn main() {}
     GlobalConst("x", ty.i32(), Expr(0_i));
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Expr(Source{{12, 34}}, "x")),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: workgroup_size argument must be at least 1");
@@ -645,8 +723,11 @@
     // @compute @workgroup_size(x)
     // fn main() {}
     GlobalConst("x", ty.i32(), Construct(ty.i32(), Construct(ty.i32(), Construct(ty.i32()))));
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Expr(Source{{12, 34}}, "x")),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: workgroup_size argument must be at least 1");
@@ -657,8 +738,11 @@
     // @compute @workgroup_size(x)
     // fn main() {}
     GlobalVar("x", ty.i32(), ast::StorageClass::kPrivate, Expr(64_i));
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Expr(Source{{12, 34}}, "x")),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -669,9 +753,11 @@
 TEST_F(ResolverFunctionValidationTest, WorkgroupSize_InvalidExpr) {
     // @compute @workgroup_size(i32(1))
     // fn main() {}
-    Func("main", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute),
-          WorkgroupSize(Construct(Source{{12, 34}}, ty.i32(), 1_i))});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(Construct(Source{{12, 34}}, ty.i32(), 1_i)),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -681,7 +767,7 @@
 
 TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_NonPlain) {
     auto* ret_type = ty.pointer(Source{{12, 34}}, ty.i32(), ast::StorageClass::kFunction);
-    Func("f", {}, ret_type, {});
+    Func("f", utils::Empty, ret_type, utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: function return type must be a constructible type");
@@ -689,7 +775,7 @@
 
 TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_AtomicInt) {
     auto* ret_type = ty.atomic(Source{{12, 34}}, ty.i32());
-    Func("f", {}, ret_type, {});
+    Func("f", utils::Empty, ret_type, utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: function return type must be a constructible type");
@@ -697,16 +783,18 @@
 
 TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_ArrayOfAtomic) {
     auto* ret_type = ty.array(Source{{12, 34}}, ty.atomic(ty.i32()), 10_u);
-    Func("f", {}, ret_type, {});
+    Func("f", utils::Empty, ret_type, utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: function return type must be a constructible type");
 }
 
 TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_StructOfAtomic) {
-    Structure("S", {Member("m", ty.atomic(ty.i32()))});
+    Structure("S", utils::Vector{
+                       Member("m", ty.atomic(ty.i32())),
+                   });
     auto* ret_type = ty.type_name(Source{{12, 34}}, "S");
-    Func("f", {}, ret_type, {});
+    Func("f", utils::Empty, ret_type, utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: function return type must be a constructible type");
@@ -714,47 +802,51 @@
 
 TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_RuntimeArray) {
     auto* ret_type = ty.array(Source{{12, 34}}, ty.i32());
-    Func("f", {}, ret_type, {});
+    Func("f", utils::Empty, ret_type, utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: function return type must be a constructible type");
 }
 
 TEST_F(ResolverFunctionValidationTest, ParameterStoreType_NonAtomicFree) {
-    Structure("S", {Member("m", ty.atomic(ty.i32()))});
+    Structure("S", utils::Vector{
+                       Member("m", ty.atomic(ty.i32())),
+                   });
     auto* ret_type = ty.type_name(Source{{12, 34}}, "S");
     auto* bar = Param(Source{{12, 34}}, "bar", ret_type);
-    Func("f", {bar}, ty.void_(), {});
+    Func("f", utils::Vector{bar}, ty.void_(), utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: type of function parameter must be constructible");
 }
 
 TEST_F(ResolverFunctionValidationTest, ParameterSotreType_AtomicFree) {
-    Structure("S", {Member("m", ty.i32())});
+    Structure("S", utils::Vector{
+                       Member("m", ty.i32()),
+                   });
     auto* ret_type = ty.type_name(Source{{12, 34}}, "S");
     auto* bar = Param(Source{{12, 34}}, "bar", ret_type);
-    Func("f", {bar}, ty.void_(), {});
+    Func("f", utils::Vector{bar}, ty.void_(), utils::Empty);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(ResolverFunctionValidationTest, ParametersAtLimit) {
-    ast::ParameterList params;
+    utils::Vector<const ast::Parameter*, 256> params;
     for (int i = 0; i < 255; i++) {
-        params.emplace_back(Param("param_" + std::to_string(i), ty.i32()));
+        params.Push(Param("param_" + std::to_string(i), ty.i32()));
     }
-    Func(Source{{12, 34}}, "f", params, ty.void_(), {});
+    Func(Source{{12, 34}}, "f", params, ty.void_(), utils::Empty);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(ResolverFunctionValidationTest, ParametersOverLimit) {
-    ast::ParameterList params;
+    utils::Vector<const ast::Parameter*, 256> params;
     for (int i = 0; i < 256; i++) {
-        params.emplace_back(Param("param_" + std::to_string(i), ty.i32()));
+        params.Push(Param("param_" + std::to_string(i), ty.i32()));
     }
-    Func(Source{{12, 34}}, "f", params, ty.void_(), {});
+    Func(Source{{12, 34}}, "f", params, ty.void_(), utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: functions may declare at most 255 parameters");
@@ -763,8 +855,9 @@
 TEST_F(ResolverFunctionValidationTest, ParameterVectorNoType) {
     // fn f(p : vec3) {}
 
-    Func(Source{{12, 34}}, "f", {Param("p", create<ast::Vector>(Source{{12, 34}}, nullptr, 3u))},
-         ty.void_(), {});
+    Func(Source{{12, 34}}, "f",
+         utils::Vector{Param("p", create<ast::Vector>(Source{{12, 34}}, nullptr, 3u))}, ty.void_(),
+         utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
@@ -774,7 +867,8 @@
     // fn f(p : vec3) {}
 
     Func(Source{{12, 34}}, "f",
-         {Param("p", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3u, 3u))}, ty.void_(), {});
+         utils::Vector{Param("p", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3u, 3u))},
+         ty.void_(), utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
@@ -792,7 +886,7 @@
     auto& param = GetParam();
     auto* ptr_type = ty.pointer(Source{{12, 34}}, ty.i32(), param.storage_class);
     auto* arg = Param(Source{{12, 34}}, "p", ptr_type);
-    Func("f", {arg}, ty.void_(), {});
+    Func("f", utils::Vector{arg}, ty.void_(), utils::Empty);
 
     if (param.should_pass) {
         ASSERT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/tint/resolver/host_shareable_validation_test.cc b/src/tint/resolver/host_shareable_validation_test.cc
index 1bd9ed7..d67a108 100644
--- a/src/tint/resolver/host_shareable_validation_test.cc
+++ b/src/tint/resolver/host_shareable_validation_test.cc
@@ -24,10 +24,10 @@
 using ResolverHostShareableValidationTest = ResolverTest;
 
 TEST_F(ResolverHostShareableValidationTest, BoolMember) {
-    auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -42,10 +42,10 @@
 }
 
 TEST_F(ResolverHostShareableValidationTest, BoolVectorMember) {
-    auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3<bool>())});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -61,10 +61,10 @@
 
 TEST_F(ResolverHostShareableValidationTest, Aliases) {
     auto* a1 = Alias("a1", ty.bool_());
-    auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.Of(a1))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -79,14 +79,14 @@
 }
 
 TEST_F(ResolverHostShareableValidationTest, NestedStructures) {
-    auto* i1 = Structure("I1", {Member(Source{{1, 2}}, "x", ty.bool_())});
-    auto* i2 = Structure("I2", {Member(Source{{3, 4}}, "y", ty.Of(i1))});
-    auto* i3 = Structure("I3", {Member(Source{{5, 6}}, "z", ty.Of(i2))});
+    auto* i1 = Structure("I1", utils::Vector{Member(Source{{1, 2}}, "x", ty.bool_())});
+    auto* i2 = Structure("I2", utils::Vector{Member(Source{{3, 4}}, "y", ty.Of(i1))});
+    auto* i3 = Structure("I3", utils::Vector{Member(Source{{5, 6}}, "z", ty.Of(i2))});
 
-    auto* s = Structure("S", {Member(Source{{7, 8}}, "m", ty.Of(i3))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -106,33 +106,32 @@
 TEST_F(ResolverHostShareableValidationTest, NoError) {
     Enable(ast::Extension::kF16);
 
-    auto* i1 = Structure("I1", {
+    auto* i1 = Structure("I1", utils::Vector{
                                    Member(Source{{1, 1}}, "w1", ty.f32()),
                                    Member(Source{{2, 1}}, "x1", ty.f32()),
                                    Member(Source{{3, 1}}, "y1", ty.vec3<f32>()),
                                    Member(Source{{4, 1}}, "z1", ty.array<i32, 4>()),
                                });
     auto* a1 = Alias("a1", ty.Of(i1));
-    auto* i2 = Structure("I2", {
+    auto* i2 = Structure("I2", utils::Vector{
                                    Member(Source{{5, 1}}, "x2", ty.mat2x2<f32>()),
                                    Member(Source{{6, 1}}, "w2", ty.mat3x4<f32>()),
                                    Member(Source{{7, 1}}, "z2", ty.Of(i1)),
                                });
     auto* a2 = Alias("a2", ty.Of(i2));
-    auto* i3 = Structure("I3", {
+    auto* i3 = Structure("I3", utils::Vector{
                                    Member(Source{{4, 1}}, "x3", ty.Of(a1)),
                                    Member(Source{{5, 1}}, "y3", ty.Of(i2)),
                                    Member(Source{{6, 1}}, "z3", ty.Of(a2)),
                                });
 
-    auto* s = Structure("S", {Member(Source{{7, 8}}, "m", ty.Of(i3))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
-    WrapInFunction();
 
     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 5061e49..a7e636f 100644
--- a/src/tint/resolver/increment_decrement_validation_test.cc
+++ b/src/tint/resolver/increment_decrement_validation_test.cc
@@ -161,7 +161,10 @@
     //   a++;
     // }
     auto* a = Param(Source{{12, 34}}, "a", ty.i32());
-    Func("func", {a}, ty.void_(), {Increment(Expr(Source{{56, 78}}, "a"))});
+    Func("func", utils::Vector{a}, ty.void_(),
+         utils::Vector{
+             Increment(Expr(Source{{56, 78}}, "a")),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), R"(56:78 error: cannot modify function parameter
@@ -175,7 +178,10 @@
     // {
     //   a++;
     // }
-    Func("func", {}, ty.i32(), {Return(0_i)});
+    Func("func", utils::Empty, ty.i32(),
+         utils::Vector{
+             Return(0_i),
+         });
     WrapInFunction(Increment(Call(Source{{56, 78}}, "func")));
 
     EXPECT_FALSE(r()->Resolve());
diff --git a/src/tint/resolver/inferred_type_test.cc b/src/tint/resolver/inferred_type_test.cc
index a183b20..4051d82 100644
--- a/src/tint/resolver/inferred_type_test.cc
+++ b/src/tint/resolver/inferred_type_test.cc
@@ -83,7 +83,6 @@
     // const a = <type constructor>;
     auto* ctor_expr = params.create_value(*this, 0);
     auto* a = GlobalConst("a", nullptr, ctor_expr);
-    WrapInFunction();
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     EXPECT_EQ(TypeOf(a), expected_type);
@@ -97,7 +96,6 @@
     // var a = <type constructor>;
     auto* ctor_expr = params.create_value(*this, 0);
     auto* var = GlobalVar("a", nullptr, ast::StorageClass::kPrivate, ctor_expr);
-    WrapInFunction();
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
@@ -147,7 +145,7 @@
 
 TEST_F(ResolverInferredTypeTest, InferStruct_Pass) {
     auto* member = Member("x", ty.i32());
-    auto* str = Structure("S", {member});
+    auto* str = Structure("S", utils::Vector{member});
 
     auto* expected_type =
         create<sem::Struct>(str, str->name,
diff --git a/src/tint/resolver/intrinsic_table.cc b/src/tint/resolver/intrinsic_table.cc
index bb5791f..67a75c3 100644
--- a/src/tint/resolver/intrinsic_table.cc
+++ b/src/tint/resolver/intrinsic_table.cc
@@ -123,17 +123,17 @@
     /// If none of the above applies, then `ty` is a type mismatch for the template type, and
     /// nullptr is returned.
     const sem::Type* Type(size_t idx, const sem::Type* ty) {
-        auto res = types_.emplace(idx, ty);
-        if (res.second) {
+        if (idx >= types_.Length()) {
+            types_.Resize(idx + 1);
+        }
+        auto& t = types_[idx];
+        if (t == nullptr) {
+            t = ty;
             return ty;
         }
-        auto* existing = res.first->second;
-        if (existing == ty) {
-            return ty;
-        }
-        ty = sem::Type::Common(utils::Vector{existing, ty});
+        ty = sem::Type::Common(utils::Vector{t, ty});
         if (ty) {
-            res.first->second = ty;
+            t = ty;
         }
         return ty;
     }
@@ -142,28 +142,44 @@
     /// Num() returns true. If the number is defined, then `Num()` returns true iff it is equal to
     /// `ty`.
     bool Num(size_t idx, Number number) {
-        auto res = numbers_.emplace(idx, number.Value());
-        return res.second || res.first->second == number.Value();
+        if (idx >= numbers_.Length()) {
+            numbers_.Resize(idx + 1, Number::invalid);
+        }
+        auto& n = numbers_[idx];
+        if (!n.IsValid()) {
+            n = number.Value();
+            return true;
+        }
+        return n.Value() == number.Value();
     }
 
     /// Type returns the template type with index `idx`, or nullptr if the type was not defined.
     const sem::Type* Type(size_t idx) const {
-        auto it = types_.find(idx);
-        return (it != types_.end()) ? it->second : nullptr;
+        if (idx >= types_.Length()) {
+            return nullptr;
+        }
+        return types_[idx];
     }
 
     /// SetType replaces the template type with index `idx` with type `ty`.
-    void SetType(size_t idx, const sem::Type* ty) { types_[idx] = ty; }
+    void SetType(size_t idx, const sem::Type* ty) {
+        if (idx >= types_.Length()) {
+            types_.Resize(idx + 1);
+        }
+        types_[idx] = ty;
+    }
 
     /// Type returns the number type with index `idx`.
     Number Num(size_t idx) const {
-        auto it = numbers_.find(idx);
-        return (it != numbers_.end()) ? Number(it->second) : Number::invalid;
+        if (idx >= numbers_.Length()) {
+            return Number::invalid;
+        }
+        return numbers_[idx];
     }
 
   private:
-    std::unordered_map<size_t, const sem::Type*> types_;
-    std::unordered_map<size_t, uint32_t> numbers_;
+    utils::Vector<const sem::Type*, 4> types_;
+    utils::Vector<Number, 2> numbers_;
 };
 
 /// Index type used for matcher indices
@@ -716,24 +732,34 @@
 // Builtin types starting with a _ prefix cannot be declared in WGSL, so they
 // can only be used as return types. Because of this, they must only match Any,
 // which is used as the return type matcher.
-bool match_modf_result(const sem::Type* ty) {
-    return ty->Is<Any>();
-}
-bool match_modf_result_vec(const sem::Type* ty, Number& N) {
+bool match_modf_result(const sem::Type* ty, const sem::Type*& T) {
     if (!ty->Is<Any>()) {
         return false;
     }
-    N = Number::any;
+    T = ty;
     return true;
 }
-bool match_frexp_result(const sem::Type* ty) {
-    return ty->Is<Any>();
-}
-bool match_frexp_result_vec(const sem::Type* ty, Number& N) {
+bool match_modf_result_vec(const sem::Type* ty, Number& N, const sem::Type*& T) {
     if (!ty->Is<Any>()) {
         return false;
     }
     N = Number::any;
+    T = ty;
+    return true;
+}
+bool match_frexp_result(const sem::Type* ty, const sem::Type*& T) {
+    if (!ty->Is<Any>()) {
+        return false;
+    }
+    T = ty;
+    return true;
+}
+bool match_frexp_result_vec(const sem::Type* ty, Number& N, const sem::Type*& T) {
+    if (!ty->Is<Any>()) {
+        return false;
+    }
+    N = Number::any;
+    T = ty;
     return true;
 }
 
@@ -747,7 +773,7 @@
 
 struct NameAndType {
     std::string name;
-    sem::Type* type;
+    const sem::Type* type;
 };
 const sem::Struct* build_struct(MatchState& state,
                                 std::string name,
@@ -781,27 +807,46 @@
         /* size_no_padding */ size_without_padding);
 }
 
-const sem::Struct* build_modf_result(MatchState& state) {
-    auto* f32 = state.builder.create<sem::F32>();
-    return build_struct(state, "__modf_result", {{"fract", f32}, {"whole", f32}});
+const sem::Struct* build_modf_result(MatchState& state, const sem::Type* el) {
+    std::string display_name;
+    if (el->Is<sem::F16>()) {
+        display_name = "__modf_result_f16";
+    } else {
+        display_name = "__modf_result";
+    }
+    return build_struct(state, display_name, {{"fract", el}, {"whole", el}});
 }
-const sem::Struct* build_modf_result_vec(MatchState& state, Number& n) {
-    auto* vec_f32 = state.builder.create<sem::Vector>(state.builder.create<sem::F32>(), n.Value());
-    return build_struct(state, "__modf_result_vec" + std::to_string(n.Value()),
-                        {{"fract", vec_f32}, {"whole", vec_f32}});
+const sem::Struct* build_modf_result_vec(MatchState& state, Number& n, const sem::Type* el) {
+    std::string display_name;
+    if (el->Is<sem::F16>()) {
+        display_name = "__modf_result_vec" + std::to_string(n.Value()) + "_f16";
+    } else {
+        display_name = "__modf_result_vec" + std::to_string(n.Value());
+    }
+    auto* vec = state.builder.create<sem::Vector>(el, n.Value());
+    return build_struct(state, display_name, {{"fract", vec}, {"whole", vec}});
 }
-const sem::Struct* build_frexp_result(MatchState& state) {
-    auto* f32 = state.builder.create<sem::F32>();
+const sem::Struct* build_frexp_result(MatchState& state, const sem::Type* el) {
+    std::string display_name;
+    if (el->Is<sem::F16>()) {
+        display_name = "__frexp_result_f16";
+    } else {
+        display_name = "__frexp_result";
+    }
     auto* i32 = state.builder.create<sem::I32>();
-    return build_struct(state, "__frexp_result", {{"sig", f32}, {"exp", i32}});
+    return build_struct(state, display_name, {{"sig", el}, {"exp", i32}});
 }
-const sem::Struct* build_frexp_result_vec(MatchState& state, Number& n) {
-    auto* vec_f32 = state.builder.create<sem::Vector>(state.builder.create<sem::F32>(), n.Value());
+const sem::Struct* build_frexp_result_vec(MatchState& state, Number& n, const sem::Type* el) {
+    std::string display_name;
+    if (el->Is<sem::F16>()) {
+        display_name = "__frexp_result_vec" + std::to_string(n.Value()) + "_f16";
+    } else {
+        display_name = "__frexp_result_vec" + std::to_string(n.Value());
+    }
+    auto* vec = state.builder.create<sem::Vector>(el, n.Value());
     auto* vec_i32 = state.builder.create<sem::Vector>(state.builder.create<sem::I32>(), n.Value());
-    return build_struct(state, "__frexp_result_vec" + std::to_string(n.Value()),
-                        {{"sig", vec_f32}, {"exp", vec_i32}});
+    return build_struct(state, display_name, {{"sig", vec}, {"exp", vec_i32}});
 }
-
 const sem::Struct* build_atomic_compare_exchange_result(MatchState& state, const sem::Type* ty) {
     return build_struct(
         state, "__atomic_compare_exchange_result" + ty->FriendlyName(state.builder.Symbols()),
@@ -1026,14 +1071,14 @@
 
     // Prints the list of candidates for emitting diagnostics
     void PrintCandidates(std::ostream& ss,
-                         utils::ConstVectorRef<Candidate> candidates,
+                         utils::VectorRef<Candidate> candidates,
                          const char* intrinsic_name) const;
 
     /// Raises an error when no overload is a clear winner of overload resolution
     void ErrAmbiguousOverload(const char* intrinsic_name,
-                              utils::ConstVectorRef<const sem::Type*> args,
+                              utils::VectorRef<const sem::Type*> args,
                               TemplateState templates,
-                              utils::ConstVectorRef<Candidate> candidates) const;
+                              utils::VectorRef<Candidate> candidates) const;
 
     ProgramBuilder& builder;
     Matchers matchers;
@@ -1604,7 +1649,7 @@
 }
 
 void Impl::PrintCandidates(std::ostream& ss,
-                           utils::ConstVectorRef<Candidate> candidates,
+                           utils::VectorRef<Candidate> candidates,
                            const char* intrinsic_name) const {
     for (auto& candidate : candidates) {
         ss << "  ";
@@ -1638,9 +1683,9 @@
 }
 
 void Impl::ErrAmbiguousOverload(const char* intrinsic_name,
-                                utils::ConstVectorRef<const sem::Type*> args,
+                                utils::VectorRef<const sem::Type*> args,
                                 TemplateState templates,
-                                utils::ConstVectorRef<Candidate> candidates) const {
+                                utils::VectorRef<Candidate> candidates) const {
     std::stringstream ss;
     ss << "ambiguous overload while attempting to match " << intrinsic_name;
     for (size_t i = 0; i < std::numeric_limits<size_t>::max(); i++) {
diff --git a/src/tint/resolver/intrinsic_table.inl b/src/tint/resolver/intrinsic_table.inl
index 28bcc51..16c177c 100644
--- a/src/tint/resolver/intrinsic_table.inl
+++ b/src/tint/resolver/intrinsic_table.inl
@@ -1386,14 +1386,22 @@
 };
 
 const sem::Type* ModfResult::Match(MatchState& state, const sem::Type* ty) const {
-  if (!match_modf_result(ty)) {
+  const sem::Type* T = nullptr;
+  if (!match_modf_result(ty, T)) {
     return nullptr;
   }
-  return build_modf_result(state);
+  T = state.Type(T);
+  if (T == nullptr) {
+    return nullptr;
+  }
+  return build_modf_result(state, T);
 }
 
-std::string ModfResult::String(MatchState*) const {
-  return "__modf_result";
+std::string ModfResult::String(MatchState* state) const {
+  const std::string T = state->TypeName();
+  std::stringstream ss;
+  ss << "__modf_result_" << T;
+  return ss.str();
 }
 
 /// TypeMatcher for 'type __modf_result_vec'
@@ -1413,20 +1421,26 @@
 
 const sem::Type* ModfResultVec::Match(MatchState& state, const sem::Type* ty) const {
   Number N = Number::invalid;
-  if (!match_modf_result_vec(ty, N)) {
+  const sem::Type* T = nullptr;
+  if (!match_modf_result_vec(ty, N, T)) {
     return nullptr;
   }
   N = state.Num(N);
   if (!N.IsValid()) {
     return nullptr;
   }
-  return build_modf_result_vec(state, N);
+  T = state.Type(T);
+  if (T == nullptr) {
+    return nullptr;
+  }
+  return build_modf_result_vec(state, N, T);
 }
 
 std::string ModfResultVec::String(MatchState* state) const {
   const std::string N = state->NumName();
+  const std::string T = state->TypeName();
   std::stringstream ss;
-  ss << "__modf_result_vec" << N;
+  ss << "__modf_result_vec" << N << "_" << T;
   return ss.str();
 }
 
@@ -1446,14 +1460,22 @@
 };
 
 const sem::Type* FrexpResult::Match(MatchState& state, const sem::Type* ty) const {
-  if (!match_frexp_result(ty)) {
+  const sem::Type* T = nullptr;
+  if (!match_frexp_result(ty, T)) {
     return nullptr;
   }
-  return build_frexp_result(state);
+  T = state.Type(T);
+  if (T == nullptr) {
+    return nullptr;
+  }
+  return build_frexp_result(state, T);
 }
 
-std::string FrexpResult::String(MatchState*) const {
-  return "__frexp_result";
+std::string FrexpResult::String(MatchState* state) const {
+  const std::string T = state->TypeName();
+  std::stringstream ss;
+  ss << "__frexp_result_" << T;
+  return ss.str();
 }
 
 /// TypeMatcher for 'type __frexp_result_vec'
@@ -1473,20 +1495,26 @@
 
 const sem::Type* FrexpResultVec::Match(MatchState& state, const sem::Type* ty) const {
   Number N = Number::invalid;
-  if (!match_frexp_result_vec(ty, N)) {
+  const sem::Type* T = nullptr;
+  if (!match_frexp_result_vec(ty, N, T)) {
     return nullptr;
   }
   N = state.Num(N);
   if (!N.IsValid()) {
     return nullptr;
   }
-  return build_frexp_result_vec(state, N);
+  T = state.Type(T);
+  if (T == nullptr) {
+    return nullptr;
+  }
+  return build_frexp_result_vec(state, N, T);
 }
 
 std::string FrexpResultVec::String(MatchState* state) const {
   const std::string N = state->NumName();
+  const std::string T = state->TypeName();
   std::stringstream ss;
-  ss << "__frexp_result_vec" << N;
+  ss << "__frexp_result_vec" << N << "_" << T;
   return ss.str();
 }
 
@@ -2724,12 +2752,12 @@
   /* [42] */ 41,
   /* [43] */ 5,
   /* [44] */ 6,
-  /* [45] */ 42,
-  /* [46] */ 0,
-  /* [47] */ 1,
-  /* [48] */ 40,
-  /* [49] */ 5,
-  /* [50] */ 6,
+  /* [45] */ 40,
+  /* [46] */ 5,
+  /* [47] */ 6,
+  /* [48] */ 42,
+  /* [49] */ 0,
+  /* [50] */ 1,
   /* [51] */ 43,
   /* [52] */ 4,
   /* [53] */ 6,
@@ -2757,162 +2785,166 @@
   /* [75] */ 41,
   /* [76] */ 3,
   /* [77] */ 6,
-  /* [78] */ 43,
+  /* [78] */ 48,
   /* [79] */ 0,
-  /* [80] */ 1,
-  /* [81] */ 41,
+  /* [80] */ 0,
+  /* [81] */ 43,
   /* [82] */ 0,
   /* [83] */ 1,
-  /* [84] */ 40,
+  /* [84] */ 41,
   /* [85] */ 0,
   /* [86] */ 1,
-  /* [87] */ 21,
+  /* [87] */ 40,
   /* [88] */ 0,
-  /* [89] */ 5,
+  /* [89] */ 1,
   /* [90] */ 21,
   /* [91] */ 0,
-  /* [92] */ 6,
-  /* [93] */ 40,
-  /* [94] */ 3,
+  /* [92] */ 5,
+  /* [93] */ 21,
+  /* [94] */ 0,
   /* [95] */ 6,
-  /* [96] */ 17,
+  /* [96] */ 46,
   /* [97] */ 0,
-  /* [98] */ 10,
-  /* [99] */ 1,
-  /* [100] */ 10,
-  /* [101] */ 7,
-  /* [102] */ 10,
-  /* [103] */ 8,
+  /* [98] */ 0,
+  /* [99] */ 40,
+  /* [100] */ 3,
+  /* [101] */ 6,
+  /* [102] */ 17,
+  /* [103] */ 0,
   /* [104] */ 10,
-  /* [105] */ 5,
-  /* [106] */ 9,
-  /* [107] */ 0,
+  /* [105] */ 7,
+  /* [106] */ 10,
+  /* [107] */ 1,
   /* [108] */ 10,
-  /* [109] */ 0,
+  /* [109] */ 8,
   /* [110] */ 10,
-  /* [111] */ 6,
+  /* [111] */ 5,
   /* [112] */ 10,
-  /* [113] */ 2,
-  /* [114] */ 11,
-  /* [115] */ 0,
+  /* [113] */ 0,
+  /* [114] */ 10,
+  /* [115] */ 6,
   /* [116] */ 9,
-  /* [117] */ 2,
-  /* [118] */ 9,
-  /* [119] */ 1,
+  /* [117] */ 0,
+  /* [118] */ 10,
+  /* [119] */ 2,
   /* [120] */ 11,
-  /* [121] */ 7,
+  /* [121] */ 0,
   /* [122] */ 9,
-  /* [123] */ 6,
+  /* [123] */ 2,
   /* [124] */ 9,
-  /* [125] */ 5,
-  /* [126] */ 9,
-  /* [127] */ 8,
+  /* [125] */ 1,
+  /* [126] */ 11,
+  /* [127] */ 7,
   /* [128] */ 9,
-  /* [129] */ 7,
-  /* [130] */ 46,
-  /* [131] */ 0,
-  /* [132] */ 28,
-  /* [133] */ 0,
-  /* [134] */ 11,
-  /* [135] */ 1,
-  /* [136] */ 29,
+  /* [129] */ 6,
+  /* [130] */ 9,
+  /* [131] */ 5,
+  /* [132] */ 9,
+  /* [133] */ 8,
+  /* [134] */ 9,
+  /* [135] */ 7,
+  /* [136] */ 45,
   /* [137] */ 0,
-  /* [138] */ 30,
+  /* [138] */ 28,
   /* [139] */ 0,
-  /* [140] */ 11,
-  /* [141] */ 8,
-  /* [142] */ 31,
+  /* [140] */ 29,
+  /* [141] */ 0,
+  /* [142] */ 30,
   /* [143] */ 0,
   /* [144] */ 11,
-  /* [145] */ 5,
-  /* [146] */ 32,
+  /* [145] */ 1,
+  /* [146] */ 31,
   /* [147] */ 0,
-  /* [148] */ 33,
+  /* [148] */ 32,
   /* [149] */ 0,
   /* [150] */ 11,
-  /* [151] */ 6,
-  /* [152] */ 34,
+  /* [151] */ 8,
+  /* [152] */ 33,
   /* [153] */ 0,
-  /* [154] */ 11,
-  /* [155] */ 2,
-  /* [156] */ 12,
-  /* [157] */ 0,
-  /* [158] */ 12,
-  /* [159] */ 7,
-  /* [160] */ 31,
-  /* [161] */ 7,
+  /* [154] */ 34,
+  /* [155] */ 0,
+  /* [156] */ 11,
+  /* [157] */ 5,
+  /* [158] */ 11,
+  /* [159] */ 6,
+  /* [160] */ 11,
+  /* [161] */ 2,
   /* [162] */ 12,
-  /* [163] */ 8,
-  /* [164] */ 13,
-  /* [165] */ 0,
-  /* [166] */ 48,
-  /* [167] */ 0,
+  /* [163] */ 0,
+  /* [164] */ 12,
+  /* [165] */ 7,
+  /* [166] */ 12,
+  /* [167] */ 8,
   /* [168] */ 13,
-  /* [169] */ 7,
+  /* [169] */ 0,
   /* [170] */ 13,
-  /* [171] */ 8,
-  /* [172] */ 14,
+  /* [171] */ 7,
+  /* [172] */ 47,
   /* [173] */ 0,
-  /* [174] */ 14,
-  /* [175] */ 7,
+  /* [174] */ 13,
+  /* [175] */ 8,
   /* [176] */ 14,
-  /* [177] */ 8,
-  /* [178] */ 15,
-  /* [179] */ 0,
-  /* [180] */ 15,
-  /* [181] */ 7,
+  /* [177] */ 0,
+  /* [178] */ 14,
+  /* [179] */ 7,
+  /* [180] */ 14,
+  /* [181] */ 8,
   /* [182] */ 15,
-  /* [183] */ 8,
-  /* [184] */ 16,
-  /* [185] */ 0,
-  /* [186] */ 16,
-  /* [187] */ 7,
+  /* [183] */ 0,
+  /* [184] */ 15,
+  /* [185] */ 7,
+  /* [186] */ 15,
+  /* [187] */ 8,
   /* [188] */ 16,
-  /* [189] */ 8,
-  /* [190] */ 17,
+  /* [189] */ 0,
+  /* [190] */ 16,
   /* [191] */ 7,
-  /* [192] */ 17,
+  /* [192] */ 16,
   /* [193] */ 8,
-  /* [194] */ 18,
-  /* [195] */ 0,
-  /* [196] */ 18,
-  /* [197] */ 7,
+  /* [194] */ 17,
+  /* [195] */ 7,
+  /* [196] */ 17,
+  /* [197] */ 8,
   /* [198] */ 18,
-  /* [199] */ 8,
-  /* [200] */ 19,
-  /* [201] */ 0,
-  /* [202] */ 49,
-  /* [203] */ 0,
-  /* [204] */ 28,
-  /* [205] */ 7,
-  /* [206] */ 29,
-  /* [207] */ 7,
-  /* [208] */ 30,
+  /* [199] */ 0,
+  /* [200] */ 18,
+  /* [201] */ 7,
+  /* [202] */ 18,
+  /* [203] */ 8,
+  /* [204] */ 19,
+  /* [205] */ 0,
+  /* [206] */ 49,
+  /* [207] */ 0,
+  /* [208] */ 28,
   /* [209] */ 7,
-  /* [210] */ 19,
+  /* [210] */ 29,
   /* [211] */ 7,
-  /* [212] */ 19,
-  /* [213] */ 8,
-  /* [214] */ 32,
+  /* [212] */ 30,
+  /* [213] */ 7,
+  /* [214] */ 31,
   /* [215] */ 7,
-  /* [216] */ 33,
+  /* [216] */ 19,
   /* [217] */ 7,
-  /* [218] */ 20,
-  /* [219] */ 0,
-  /* [220] */ 20,
+  /* [218] */ 19,
+  /* [219] */ 8,
+  /* [220] */ 32,
   /* [221] */ 7,
-  /* [222] */ 20,
-  /* [223] */ 8,
-  /* [224] */ 26,
-  /* [225] */ 38,
-  /* [226] */ 27,
-  /* [227] */ 37,
-  /* [228] */ 36,
-  /* [229] */ 35,
-  /* [230] */ 44,
-  /* [231] */ 39,
-  /* [232] */ 45,
-  /* [233] */ 47,
+  /* [222] */ 33,
+  /* [223] */ 7,
+  /* [224] */ 20,
+  /* [225] */ 0,
+  /* [226] */ 20,
+  /* [227] */ 7,
+  /* [228] */ 20,
+  /* [229] */ 8,
+  /* [230] */ 26,
+  /* [231] */ 38,
+  /* [232] */ 27,
+  /* [233] */ 37,
+  /* [234] */ 36,
+  /* [235] */ 35,
+  /* [236] */ 44,
+  /* [237] */ 39,
 };
 
 // Assert that the MatcherIndex is big enough to index all the matchers, plus
@@ -3250,17 +3282,17 @@
   {
     /* [65] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[208],
+    /* matcher indices */ &kMatcherIndices[212],
   },
   {
     /* [66] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [67] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [68] */
@@ -3270,32 +3302,32 @@
   {
     /* [69] */
     /* usage */ ParameterUsage::kDdx,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [70] */
     /* usage */ ParameterUsage::kDdy,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [71] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [72] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [73] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [74] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [75] */
@@ -3310,37 +3342,37 @@
   {
     /* [77] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [78] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[160],
+    /* matcher indices */ &kMatcherIndices[214],
   },
   {
     /* [79] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [80] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [81] */
     /* usage */ ParameterUsage::kDdx,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [82] */
     /* usage */ ParameterUsage::kDdy,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [83] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[104],
+    /* matcher indices */ &kMatcherIndices[110],
   },
   {
     /* [84] */
@@ -3375,17 +3407,17 @@
   {
     /* [90] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [91] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [92] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [93] */
@@ -3400,7 +3432,7 @@
   {
     /* [95] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [96] */
@@ -3410,17 +3442,17 @@
   {
     /* [97] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[138],
+    /* matcher indices */ &kMatcherIndices[142],
   },
   {
     /* [98] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [99] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [100] */
@@ -3430,22 +3462,22 @@
   {
     /* [101] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [102] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[208],
+    /* matcher indices */ &kMatcherIndices[212],
   },
   {
     /* [103] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [104] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [105] */
@@ -3455,42 +3487,42 @@
   {
     /* [106] */
     /* usage */ ParameterUsage::kDdx,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [107] */
     /* usage */ ParameterUsage::kDdy,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [108] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[206],
+    /* matcher indices */ &kMatcherIndices[210],
   },
   {
     /* [109] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [110] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [111] */
     /* usage */ ParameterUsage::kDdx,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [112] */
     /* usage */ ParameterUsage::kDdy,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [113] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [114] */
@@ -3525,17 +3557,17 @@
   {
     /* [120] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[216],
+    /* matcher indices */ &kMatcherIndices[222],
   },
   {
     /* [121] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [122] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [123] */
@@ -3545,27 +3577,27 @@
   {
     /* [124] */
     /* usage */ ParameterUsage::kDdx,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [125] */
     /* usage */ ParameterUsage::kDdy,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [126] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [127] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [128] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [129] */
@@ -3580,22 +3612,22 @@
   {
     /* [131] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [132] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[208],
+    /* matcher indices */ &kMatcherIndices[212],
   },
   {
     /* [133] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [134] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [135] */
@@ -3610,22 +3642,22 @@
   {
     /* [137] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [138] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [139] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [140] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [141] */
@@ -3640,22 +3672,22 @@
   {
     /* [143] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [144] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[208],
+    /* matcher indices */ &kMatcherIndices[212],
   },
   {
     /* [145] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [146] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [147] */
@@ -3670,22 +3702,22 @@
   {
     /* [149] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [150] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[208],
+    /* matcher indices */ &kMatcherIndices[212],
   },
   {
     /* [151] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [152] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [153] */
@@ -3700,17 +3732,17 @@
   {
     /* [155] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [156] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [157] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [158] */
@@ -3725,17 +3757,17 @@
   {
     /* [160] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [161] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [162] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [163] */
@@ -3745,22 +3777,22 @@
   {
     /* [164] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [165] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [166] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [167] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [168] */
@@ -3770,7 +3802,7 @@
   {
     /* [169] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [170] */
@@ -3780,17 +3812,17 @@
   {
     /* [171] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[148],
+    /* matcher indices */ &kMatcherIndices[152],
   },
   {
     /* [172] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [173] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [174] */
@@ -3800,17 +3832,17 @@
   {
     /* [175] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[225],
+    /* matcher indices */ &kMatcherIndices[231],
   },
   {
     /* [176] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [177] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [178] */
@@ -3825,42 +3857,42 @@
   {
     /* [180] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[206],
+    /* matcher indices */ &kMatcherIndices[210],
   },
   {
     /* [181] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [182] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [183] */
     /* usage */ ParameterUsage::kDdx,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [184] */
     /* usage */ ParameterUsage::kDdy,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [185] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [186] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [187] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [188] */
@@ -3870,22 +3902,22 @@
   {
     /* [189] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [190] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [191] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [192] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [193] */
@@ -3900,17 +3932,17 @@
   {
     /* [195] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [196] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [197] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [198] */
@@ -3920,22 +3952,22 @@
   {
     /* [199] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [200] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[160],
+    /* matcher indices */ &kMatcherIndices[214],
   },
   {
     /* [201] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [202] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [203] */
@@ -3945,22 +3977,22 @@
   {
     /* [204] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[104],
+    /* matcher indices */ &kMatcherIndices[110],
   },
   {
     /* [205] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[160],
+    /* matcher indices */ &kMatcherIndices[214],
   },
   {
     /* [206] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [207] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [208] */
@@ -3970,22 +4002,22 @@
   {
     /* [209] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[104],
+    /* matcher indices */ &kMatcherIndices[110],
   },
   {
     /* [210] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[225],
+    /* matcher indices */ &kMatcherIndices[231],
   },
   {
     /* [211] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [212] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [213] */
@@ -4005,17 +4037,17 @@
   {
     /* [216] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[138],
+    /* matcher indices */ &kMatcherIndices[142],
   },
   {
     /* [217] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [218] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [219] */
@@ -4025,17 +4057,17 @@
   {
     /* [220] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [221] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [222] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [223] */
@@ -4050,17 +4082,17 @@
   {
     /* [225] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[206],
+    /* matcher indices */ &kMatcherIndices[210],
   },
   {
     /* [226] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [227] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [228] */
@@ -4070,22 +4102,22 @@
   {
     /* [229] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [230] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[208],
+    /* matcher indices */ &kMatcherIndices[212],
   },
   {
     /* [231] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [232] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [233] */
@@ -4095,47 +4127,47 @@
   {
     /* [234] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [235] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[160],
+    /* matcher indices */ &kMatcherIndices[214],
   },
   {
     /* [236] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [237] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [238] */
     /* usage */ ParameterUsage::kDdx,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [239] */
     /* usage */ ParameterUsage::kDdy,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [240] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [241] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [242] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [243] */
@@ -4145,32 +4177,32 @@
   {
     /* [244] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [245] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[214],
+    /* matcher indices */ &kMatcherIndices[220],
   },
   {
     /* [246] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [247] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [248] */
     /* usage */ ParameterUsage::kDdx,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [249] */
     /* usage */ ParameterUsage::kDdy,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [250] */
@@ -4180,37 +4212,37 @@
   {
     /* [251] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[136],
+    /* matcher indices */ &kMatcherIndices[140],
   },
   {
     /* [252] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [253] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [254] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [255] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[225],
+    /* matcher indices */ &kMatcherIndices[231],
   },
   {
     /* [256] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [257] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [258] */
@@ -4225,17 +4257,17 @@
   {
     /* [260] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[208],
+    /* matcher indices */ &kMatcherIndices[212],
   },
   {
     /* [261] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [262] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [263] */
@@ -4250,17 +4282,17 @@
   {
     /* [265] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[206],
+    /* matcher indices */ &kMatcherIndices[210],
   },
   {
     /* [266] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [267] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [268] */
@@ -4270,22 +4302,22 @@
   {
     /* [269] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [270] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [271] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [272] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [273] */
@@ -4300,17 +4332,17 @@
   {
     /* [275] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [276] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [277] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [278] */
@@ -4320,22 +4352,22 @@
   {
     /* [279] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [280] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[216],
+    /* matcher indices */ &kMatcherIndices[222],
   },
   {
     /* [281] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [282] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [283] */
@@ -4350,17 +4382,17 @@
   {
     /* [285] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[216],
+    /* matcher indices */ &kMatcherIndices[222],
   },
   {
     /* [286] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [287] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [288] */
@@ -4375,17 +4407,17 @@
   {
     /* [290] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[225],
+    /* matcher indices */ &kMatcherIndices[231],
   },
   {
     /* [291] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [292] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [293] */
@@ -4400,17 +4432,17 @@
   {
     /* [295] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[214],
+    /* matcher indices */ &kMatcherIndices[220],
   },
   {
     /* [296] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [297] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [298] */
@@ -4420,17 +4452,17 @@
   {
     /* [299] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[160],
+    /* matcher indices */ &kMatcherIndices[214],
   },
   {
     /* [300] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [301] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [302] */
@@ -4440,17 +4472,17 @@
   {
     /* [303] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [304] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [305] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [306] */
@@ -4460,17 +4492,17 @@
   {
     /* [307] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[227],
+    /* matcher indices */ &kMatcherIndices[233],
   },
   {
     /* [308] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [309] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [310] */
@@ -4485,7 +4517,7 @@
   {
     /* [312] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [313] */
@@ -4495,22 +4527,22 @@
   {
     /* [314] */
     /* usage */ ParameterUsage::kValue,
-    /* matcher indices */ &kMatcherIndices[144],
+    /* matcher indices */ &kMatcherIndices[156],
   },
   {
     /* [315] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[206],
+    /* matcher indices */ &kMatcherIndices[210],
   },
   {
     /* [316] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [317] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [318] */
@@ -4520,17 +4552,17 @@
   {
     /* [319] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [320] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [321] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [322] */
@@ -4540,37 +4572,37 @@
   {
     /* [323] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [324] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [325] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [326] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [327] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[216],
+    /* matcher indices */ &kMatcherIndices[222],
   },
   {
     /* [328] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [329] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [330] */
@@ -4580,22 +4612,22 @@
   {
     /* [331] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[160],
+    /* matcher indices */ &kMatcherIndices[214],
   },
   {
     /* [332] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [333] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [334] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[104],
+    /* matcher indices */ &kMatcherIndices[110],
   },
   {
     /* [335] */
@@ -4605,7 +4637,7 @@
   {
     /* [336] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [337] */
@@ -4615,22 +4647,22 @@
   {
     /* [338] */
     /* usage */ ParameterUsage::kValue,
-    /* matcher indices */ &kMatcherIndices[120],
+    /* matcher indices */ &kMatcherIndices[126],
   },
   {
     /* [339] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[225],
+    /* matcher indices */ &kMatcherIndices[231],
   },
   {
     /* [340] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [341] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [342] */
@@ -4640,17 +4672,17 @@
   {
     /* [343] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[206],
+    /* matcher indices */ &kMatcherIndices[210],
   },
   {
     /* [344] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [345] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [346] */
@@ -4660,17 +4692,17 @@
   {
     /* [347] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[208],
+    /* matcher indices */ &kMatcherIndices[212],
   },
   {
     /* [348] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [349] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [350] */
@@ -4680,37 +4712,37 @@
   {
     /* [351] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[206],
+    /* matcher indices */ &kMatcherIndices[210],
   },
   {
     /* [352] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [353] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [354] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [355] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[227],
+    /* matcher indices */ &kMatcherIndices[233],
   },
   {
     /* [356] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [357] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [358] */
@@ -4725,7 +4757,7 @@
   {
     /* [360] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [361] */
@@ -4735,17 +4767,17 @@
   {
     /* [362] */
     /* usage */ ParameterUsage::kValue,
-    /* matcher indices */ &kMatcherIndices[150],
+    /* matcher indices */ &kMatcherIndices[158],
   },
   {
     /* [363] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[138],
+    /* matcher indices */ &kMatcherIndices[142],
   },
   {
     /* [364] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [365] */
@@ -4760,17 +4792,17 @@
   {
     /* [367] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [368] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [369] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [370] */
@@ -4780,17 +4812,17 @@
   {
     /* [371] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[227],
+    /* matcher indices */ &kMatcherIndices[233],
   },
   {
     /* [372] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [373] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [374] */
@@ -4800,17 +4832,17 @@
   {
     /* [375] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[225],
+    /* matcher indices */ &kMatcherIndices[231],
   },
   {
     /* [376] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [377] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [378] */
@@ -4820,17 +4852,17 @@
   {
     /* [379] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [380] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [381] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [382] */
@@ -4840,12 +4872,12 @@
   {
     /* [383] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [384] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [385] */
@@ -4860,57 +4892,57 @@
   {
     /* [387] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [388] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [389] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [390] */
     /* usage */ ParameterUsage::kOffset,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [391] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [392] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [393] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [394] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [395] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [396] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [397] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [398] */
@@ -4940,17 +4972,17 @@
   {
     /* [403] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[227],
+    /* matcher indices */ &kMatcherIndices[233],
   },
   {
     /* [404] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [405] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [406] */
@@ -4965,17 +4997,17 @@
   {
     /* [408] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[146],
+    /* matcher indices */ &kMatcherIndices[148],
   },
   {
     /* [409] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [410] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [411] */
@@ -5000,37 +5032,37 @@
   {
     /* [415] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [416] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [417] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [418] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [419] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [420] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [421] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [422] */
@@ -5085,32 +5117,32 @@
   {
     /* [432] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[136],
+    /* matcher indices */ &kMatcherIndices[140],
   },
   {
     /* [433] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [434] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [435] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[214],
+    /* matcher indices */ &kMatcherIndices[220],
   },
   {
     /* [436] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [437] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [438] */
@@ -5120,17 +5152,17 @@
   {
     /* [439] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[160],
+    /* matcher indices */ &kMatcherIndices[214],
   },
   {
     /* [440] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [441] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [442] */
@@ -5140,22 +5172,22 @@
   {
     /* [443] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[114],
+    /* matcher indices */ &kMatcherIndices[120],
   },
   {
     /* [444] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[114],
+    /* matcher indices */ &kMatcherIndices[120],
   },
   {
     /* [445] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[114],
+    /* matcher indices */ &kMatcherIndices[120],
   },
   {
     /* [446] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[114],
+    /* matcher indices */ &kMatcherIndices[120],
   },
   {
     /* [447] */
@@ -5235,17 +5267,17 @@
   {
     /* [462] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[227],
+    /* matcher indices */ &kMatcherIndices[233],
   },
   {
     /* [463] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [464] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [465] */
@@ -5255,57 +5287,57 @@
   {
     /* [466] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [467] */
     /* usage */ ParameterUsage::kValue,
-    /* matcher indices */ &kMatcherIndices[150],
+    /* matcher indices */ &kMatcherIndices[158],
   },
   {
     /* [468] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[114],
+    /* matcher indices */ &kMatcherIndices[120],
   },
   {
     /* [469] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[114],
+    /* matcher indices */ &kMatcherIndices[120],
   },
   {
     /* [470] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[114],
+    /* matcher indices */ &kMatcherIndices[120],
   },
   {
     /* [471] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[230],
+    /* matcher indices */ &kMatcherIndices[236],
   },
   {
     /* [472] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [473] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [474] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [475] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [476] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [477] */
@@ -5355,7 +5387,7 @@
   {
     /* [486] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[93],
+    /* matcher indices */ &kMatcherIndices[99],
   },
   {
     /* [487] */
@@ -5365,22 +5397,22 @@
   {
     /* [488] */
     /* usage */ ParameterUsage::kValue,
-    /* matcher indices */ &kMatcherIndices[120],
+    /* matcher indices */ &kMatcherIndices[126],
   },
   {
     /* [489] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[214],
+    /* matcher indices */ &kMatcherIndices[220],
   },
   {
     /* [490] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [491] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [492] */
@@ -5390,27 +5422,27 @@
   {
     /* [493] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [494] */
     /* usage */ ParameterUsage::kValue,
-    /* matcher indices */ &kMatcherIndices[120],
+    /* matcher indices */ &kMatcherIndices[126],
   },
   {
     /* [495] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[160],
+    /* matcher indices */ &kMatcherIndices[214],
   },
   {
     /* [496] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [497] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [498] */
@@ -5420,12 +5452,12 @@
   {
     /* [499] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[104],
+    /* matcher indices */ &kMatcherIndices[110],
   },
   {
     /* [500] */
     /* usage */ ParameterUsage::kValue,
-    /* matcher indices */ &kMatcherIndices[120],
+    /* matcher indices */ &kMatcherIndices[126],
   },
   {
     /* [501] */
@@ -5440,7 +5472,7 @@
   {
     /* [503] */
     /* usage */ ParameterUsage::kValue,
-    /* matcher indices */ &kMatcherIndices[144],
+    /* matcher indices */ &kMatcherIndices[156],
   },
   {
     /* [504] */
@@ -5450,37 +5482,37 @@
   {
     /* [505] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [506] */
     /* usage */ ParameterUsage::kValue,
-    /* matcher indices */ &kMatcherIndices[144],
+    /* matcher indices */ &kMatcherIndices[156],
   },
   {
     /* [507] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[206],
+    /* matcher indices */ &kMatcherIndices[210],
   },
   {
     /* [508] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [509] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [510] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[204],
+    /* matcher indices */ &kMatcherIndices[208],
   },
   {
     /* [511] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [512] */
@@ -5495,17 +5527,17 @@
   {
     /* [514] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[104],
+    /* matcher indices */ &kMatcherIndices[110],
   },
   {
     /* [515] */
     /* usage */ ParameterUsage::kValue,
-    /* matcher indices */ &kMatcherIndices[144],
+    /* matcher indices */ &kMatcherIndices[156],
   },
   {
     /* [516] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[48],
+    /* matcher indices */ &kMatcherIndices[45],
   },
   {
     /* [517] */
@@ -5515,7 +5547,7 @@
   {
     /* [518] */
     /* usage */ ParameterUsage::kValue,
-    /* matcher indices */ &kMatcherIndices[150],
+    /* matcher indices */ &kMatcherIndices[158],
   },
   {
     /* [519] */
@@ -5525,17 +5557,17 @@
   {
     /* [520] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[104],
+    /* matcher indices */ &kMatcherIndices[110],
   },
   {
     /* [521] */
     /* usage */ ParameterUsage::kValue,
-    /* matcher indices */ &kMatcherIndices[150],
+    /* matcher indices */ &kMatcherIndices[158],
   },
   {
     /* [522] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[132],
+    /* matcher indices */ &kMatcherIndices[138],
   },
   {
     /* [523] */
@@ -5565,12 +5597,12 @@
   {
     /* [528] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[136],
+    /* matcher indices */ &kMatcherIndices[140],
   },
   {
     /* [529] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [530] */
@@ -5580,12 +5612,12 @@
   {
     /* [531] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[142],
+    /* matcher indices */ &kMatcherIndices[146],
   },
   {
     /* [532] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[104],
+    /* matcher indices */ &kMatcherIndices[110],
   },
   {
     /* [533] */
@@ -5670,12 +5702,12 @@
   {
     /* [549] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[152],
+    /* matcher indices */ &kMatcherIndices[154],
   },
   {
     /* [550] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [551] */
@@ -5685,12 +5717,12 @@
   {
     /* [552] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [553] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [554] */
@@ -5700,12 +5732,12 @@
   {
     /* [555] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[231],
+    /* matcher indices */ &kMatcherIndices[237],
   },
   {
     /* [556] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [557] */
@@ -5715,17 +5747,17 @@
   {
     /* [558] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [559] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [560] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [561] */
@@ -5745,7 +5777,7 @@
   {
     /* [564] */
     /* usage */ ParameterUsage::kXy,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [565] */
@@ -5795,7 +5827,7 @@
   {
     /* [574] */
     /* usage */ ParameterUsage::kYz,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [575] */
@@ -5815,52 +5847,52 @@
   {
     /* [578] */
     /* usage */ ParameterUsage::kZw,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [579] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [580] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [581] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [582] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [583] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [584] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [585] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[227],
+    /* matcher indices */ &kMatcherIndices[233],
   },
   {
     /* [586] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [587] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[100],
+    /* matcher indices */ &kMatcherIndices[104],
   },
   {
     /* [588] */
@@ -5890,32 +5922,32 @@
   {
     /* [593] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[114],
+    /* matcher indices */ &kMatcherIndices[120],
   },
   {
     /* [594] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[114],
+    /* matcher indices */ &kMatcherIndices[120],
   },
   {
     /* [595] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [596] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [597] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [598] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [599] */
@@ -5935,12 +5967,12 @@
   {
     /* [602] */
     /* usage */ ParameterUsage::kZyw,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [603] */
     /* usage */ ParameterUsage::kXyz,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [604] */
@@ -5950,12 +5982,12 @@
   {
     /* [605] */
     /* usage */ ParameterUsage::kXy,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [606] */
     /* usage */ ParameterUsage::kZw,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [607] */
@@ -5965,12 +5997,12 @@
   {
     /* [608] */
     /* usage */ ParameterUsage::kYz,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [609] */
     /* usage */ ParameterUsage::kXy,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [610] */
@@ -6005,12 +6037,12 @@
   {
     /* [616] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[90],
+    /* matcher indices */ &kMatcherIndices[93],
   },
   {
     /* [617] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[132],
+    /* matcher indices */ &kMatcherIndices[138],
   },
   {
     /* [618] */
@@ -6030,7 +6062,7 @@
   {
     /* [621] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[136],
+    /* matcher indices */ &kMatcherIndices[140],
   },
   {
     /* [622] */
@@ -6045,12 +6077,12 @@
   {
     /* [624] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[90],
+    /* matcher indices */ &kMatcherIndices[93],
   },
   {
     /* [625] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[138],
+    /* matcher indices */ &kMatcherIndices[142],
   },
   {
     /* [626] */
@@ -6070,7 +6102,7 @@
   {
     /* [629] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[142],
+    /* matcher indices */ &kMatcherIndices[146],
   },
   {
     /* [630] */
@@ -6090,7 +6122,7 @@
   {
     /* [633] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[146],
+    /* matcher indices */ &kMatcherIndices[148],
   },
   {
     /* [634] */
@@ -6110,7 +6142,7 @@
   {
     /* [637] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[148],
+    /* matcher indices */ &kMatcherIndices[152],
   },
   {
     /* [638] */
@@ -6140,7 +6172,7 @@
   {
     /* [643] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [644] */
@@ -6160,7 +6192,7 @@
   {
     /* [647] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [648] */
@@ -6180,7 +6212,7 @@
   {
     /* [651] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[227],
+    /* matcher indices */ &kMatcherIndices[233],
   },
   {
     /* [652] */
@@ -6200,7 +6232,7 @@
   {
     /* [655] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[225],
+    /* matcher indices */ &kMatcherIndices[231],
   },
   {
     /* [656] */
@@ -6815,7 +6847,7 @@
   {
     /* [778] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[87],
+    /* matcher indices */ &kMatcherIndices[90],
   },
   {
     /* [779] */
@@ -6840,22 +6872,22 @@
   {
     /* [783] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[230],
+    /* matcher indices */ &kMatcherIndices[236],
   },
   {
     /* [784] */
     /* usage */ ParameterUsage::kCoords,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [785] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [786] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [787] */
@@ -6915,7 +6947,7 @@
   {
     /* [798] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[4],
+    /* matcher indices */ &kMatcherIndices[1],
   },
   {
     /* [799] */
@@ -6975,7 +7007,7 @@
   {
     /* [810] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[60],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [811] */
@@ -7100,7 +7132,7 @@
   {
     /* [835] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[225],
+    /* matcher indices */ &kMatcherIndices[231],
   },
   {
     /* [836] */
@@ -7200,12 +7232,12 @@
   {
     /* [855] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[4],
+    /* matcher indices */ &kMatcherIndices[1],
   },
   {
     /* [856] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[60],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [857] */
@@ -7245,112 +7277,112 @@
   {
     /* [864] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [865] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [866] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [867] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[120],
+    /* matcher indices */ &kMatcherIndices[126],
   },
   {
     /* [868] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[120],
+    /* matcher indices */ &kMatcherIndices[126],
   },
   {
     /* [869] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[231],
+    /* matcher indices */ &kMatcherIndices[237],
   },
   {
     /* [870] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[152],
+    /* matcher indices */ &kMatcherIndices[154],
   },
   {
     /* [871] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[225],
+    /* matcher indices */ &kMatcherIndices[231],
   },
   {
     /* [872] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[227],
+    /* matcher indices */ &kMatcherIndices[233],
   },
   {
     /* [873] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [874] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [875] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[148],
+    /* matcher indices */ &kMatcherIndices[152],
   },
   {
     /* [876] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[146],
+    /* matcher indices */ &kMatcherIndices[148],
   },
   {
     /* [877] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[142],
+    /* matcher indices */ &kMatcherIndices[146],
   },
   {
     /* [878] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[138],
+    /* matcher indices */ &kMatcherIndices[142],
   },
   {
     /* [879] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[136],
+    /* matcher indices */ &kMatcherIndices[140],
   },
   {
     /* [880] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[132],
+    /* matcher indices */ &kMatcherIndices[138],
   },
   {
     /* [881] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[45],
+    /* matcher indices */ &kMatcherIndices[48],
   },
   {
     /* [882] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[225],
+    /* matcher indices */ &kMatcherIndices[231],
   },
   {
     /* [883] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [884] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[148],
+    /* matcher indices */ &kMatcherIndices[152],
   },
   {
     /* [885] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[138],
+    /* matcher indices */ &kMatcherIndices[142],
   },
   {
     /* [886] */
@@ -7430,92 +7462,92 @@
   {
     /* [901] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[230],
+    /* matcher indices */ &kMatcherIndices[236],
   },
   {
     /* [902] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[78],
+    /* matcher indices */ &kMatcherIndices[81],
   },
   {
     /* [903] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[45],
+    /* matcher indices */ &kMatcherIndices[48],
   },
   {
     /* [904] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[81],
+    /* matcher indices */ &kMatcherIndices[84],
   },
   {
     /* [905] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[84],
+    /* matcher indices */ &kMatcherIndices[87],
   },
   {
     /* [906] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[222],
+    /* matcher indices */ &kMatcherIndices[228],
   },
   {
     /* [907] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[231],
+    /* matcher indices */ &kMatcherIndices[237],
   },
   {
     /* [908] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[220],
+    /* matcher indices */ &kMatcherIndices[226],
   },
   {
     /* [909] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[227],
+    /* matcher indices */ &kMatcherIndices[233],
   },
   {
     /* [910] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [911] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[229],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [912] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[152],
+    /* matcher indices */ &kMatcherIndices[154],
   },
   {
     /* [913] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[148],
+    /* matcher indices */ &kMatcherIndices[152],
   },
   {
     /* [914] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[146],
+    /* matcher indices */ &kMatcherIndices[148],
   },
   {
     /* [915] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[142],
+    /* matcher indices */ &kMatcherIndices[146],
   },
   {
     /* [916] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[138],
+    /* matcher indices */ &kMatcherIndices[142],
   },
   {
     /* [917] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[136],
+    /* matcher indices */ &kMatcherIndices[140],
   },
   {
     /* [918] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[132],
+    /* matcher indices */ &kMatcherIndices[138],
   },
   {
     /* [919] */
@@ -7535,7 +7567,7 @@
   {
     /* [922] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[218],
+    /* matcher indices */ &kMatcherIndices[224],
   },
   {
     /* [923] */
@@ -7550,7 +7582,7 @@
   {
     /* [925] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[212],
+    /* matcher indices */ &kMatcherIndices[218],
   },
   {
     /* [926] */
@@ -7565,12 +7597,12 @@
   {
     /* [928] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[210],
+    /* matcher indices */ &kMatcherIndices[216],
   },
   {
     /* [929] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[103],
+    /* matcher indices */ &kMatcherIndices[109],
   },
   {
     /* [930] */
@@ -7600,7 +7632,7 @@
   {
     /* [935] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [936] */
@@ -7615,37 +7647,37 @@
   {
     /* [938] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[118],
+    /* matcher indices */ &kMatcherIndices[124],
   },
   {
     /* [939] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[118],
+    /* matcher indices */ &kMatcherIndices[124],
   },
   {
     /* [940] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[118],
+    /* matcher indices */ &kMatcherIndices[124],
   },
   {
     /* [941] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[118],
+    /* matcher indices */ &kMatcherIndices[124],
   },
   {
     /* [942] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[118],
+    /* matcher indices */ &kMatcherIndices[124],
   },
   {
     /* [943] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[200],
+    /* matcher indices */ &kMatcherIndices[204],
   },
   {
     /* [944] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [945] */
@@ -7670,37 +7702,37 @@
   {
     /* [949] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[98],
+    /* matcher indices */ &kMatcherIndices[106],
   },
   {
     /* [950] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[98],
+    /* matcher indices */ &kMatcherIndices[106],
   },
   {
     /* [951] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[98],
+    /* matcher indices */ &kMatcherIndices[106],
   },
   {
     /* [952] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[98],
+    /* matcher indices */ &kMatcherIndices[106],
   },
   {
     /* [953] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[98],
+    /* matcher indices */ &kMatcherIndices[106],
   },
   {
     /* [954] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[198],
+    /* matcher indices */ &kMatcherIndices[202],
   },
   {
     /* [955] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[114],
+    /* matcher indices */ &kMatcherIndices[120],
   },
   {
     /* [956] */
@@ -7750,32 +7782,32 @@
   {
     /* [965] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[134],
+    /* matcher indices */ &kMatcherIndices[144],
   },
   {
     /* [966] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[134],
+    /* matcher indices */ &kMatcherIndices[144],
   },
   {
     /* [967] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[134],
+    /* matcher indices */ &kMatcherIndices[144],
   },
   {
     /* [968] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[134],
+    /* matcher indices */ &kMatcherIndices[144],
   },
   {
     /* [969] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[200],
   },
   {
     /* [970] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[156],
+    /* matcher indices */ &kMatcherIndices[162],
   },
   {
     /* [971] */
@@ -7790,12 +7822,12 @@
   {
     /* [973] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[158],
+    /* matcher indices */ &kMatcherIndices[164],
   },
   {
     /* [974] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[162],
+    /* matcher indices */ &kMatcherIndices[166],
   },
   {
     /* [975] */
@@ -7805,7 +7837,7 @@
   {
     /* [976] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[164],
+    /* matcher indices */ &kMatcherIndices[168],
   },
   {
     /* [977] */
@@ -7820,12 +7852,12 @@
   {
     /* [979] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[168],
+    /* matcher indices */ &kMatcherIndices[170],
   },
   {
     /* [980] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[170],
+    /* matcher indices */ &kMatcherIndices[174],
   },
   {
     /* [981] */
@@ -7835,7 +7867,7 @@
   {
     /* [982] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[172],
+    /* matcher indices */ &kMatcherIndices[176],
   },
   {
     /* [983] */
@@ -7850,22 +7882,22 @@
   {
     /* [985] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[174],
+    /* matcher indices */ &kMatcherIndices[178],
   },
   {
     /* [986] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[176],
+    /* matcher indices */ &kMatcherIndices[180],
   },
   {
     /* [987] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[194],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [988] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[178],
+    /* matcher indices */ &kMatcherIndices[182],
   },
   {
     /* [989] */
@@ -7880,22 +7912,22 @@
   {
     /* [991] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[180],
+    /* matcher indices */ &kMatcherIndices[184],
   },
   {
     /* [992] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[182],
+    /* matcher indices */ &kMatcherIndices[186],
   },
   {
     /* [993] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[196],
   },
   {
     /* [994] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[184],
+    /* matcher indices */ &kMatcherIndices[188],
   },
   {
     /* [995] */
@@ -7910,22 +7942,22 @@
   {
     /* [997] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[186],
+    /* matcher indices */ &kMatcherIndices[190],
   },
   {
     /* [998] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[188],
+    /* matcher indices */ &kMatcherIndices[192],
   },
   {
     /* [999] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[190],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [1000] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[96],
+    /* matcher indices */ &kMatcherIndices[102],
   },
   {
     /* [1001] */
@@ -7940,7 +7972,7 @@
   {
     /* [1003] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[134],
+    /* matcher indices */ &kMatcherIndices[144],
   },
 };
 
@@ -7948,57 +7980,57 @@
   {
     /* [0] */
     /* name */ "T",
-    /* matcher index */ 2,
+    /* matcher index */ 7,
   },
   {
     /* [1] */
     /* name */ "U",
-    /* matcher index */ 56,
+    /* matcher index */ 52,
   },
   {
     /* [2] */
     /* name */ "T",
-    /* matcher index */ 7,
+    /* matcher index */ 8,
   },
   {
     /* [3] */
     /* name */ "U",
-    /* matcher index */ 52,
+    /* matcher index */ 53,
   },
   {
     /* [4] */
     /* name */ "T",
-    /* matcher index */ 8,
+    /* matcher index */ 5,
   },
   {
     /* [5] */
     /* name */ "U",
-    /* matcher index */ 53,
+    /* matcher index */ 54,
   },
   {
     /* [6] */
     /* name */ "T",
-    /* matcher index */ 5,
+    /* matcher index */ 6,
   },
   {
     /* [7] */
     /* name */ "U",
-    /* matcher index */ 54,
+    /* matcher index */ 55,
   },
   {
     /* [8] */
     /* name */ "T",
-    /* matcher index */ 6,
+    /* matcher index */ 2,
   },
   {
     /* [9] */
     /* name */ "U",
-    /* matcher index */ 55,
+    /* matcher index */ 56,
   },
   {
     /* [10] */
     /* name */ "T",
-    /* matcher index */ 68,
+    /* matcher index */ 62,
   },
   {
     /* [11] */
@@ -8018,62 +8050,62 @@
   {
     /* [14] */
     /* name */ "T",
-    /* matcher index */ 63,
+    /* matcher index */ 68,
   },
   {
     /* [15] */
     /* name */ "T",
-    /* matcher index */ 51,
+    /* matcher index */ 63,
   },
   {
     /* [16] */
     /* name */ "T",
-    /* matcher index */ 64,
+    /* matcher index */ 51,
   },
   {
     /* [17] */
     /* name */ "T",
-    /* matcher index */ 50,
+    /* matcher index */ 64,
   },
   {
     /* [18] */
     /* name */ "T",
-    /* matcher index */ kNoMatcher,
+    /* matcher index */ 50,
   },
   {
     /* [19] */
     /* name */ "T",
-    /* matcher index */ 56,
+    /* matcher index */ kNoMatcher,
   },
   {
     /* [20] */
     /* name */ "T",
-    /* matcher index */ 53,
+    /* matcher index */ 56,
   },
   {
     /* [21] */
     /* name */ "T",
-    /* matcher index */ 52,
+    /* matcher index */ 53,
   },
   {
     /* [22] */
     /* name */ "T",
-    /* matcher index */ 55,
+    /* matcher index */ 52,
   },
   {
     /* [23] */
     /* name */ "T",
-    /* matcher index */ 54,
+    /* matcher index */ 55,
   },
   {
     /* [24] */
     /* name */ "T",
-    /* matcher index */ 58,
+    /* matcher index */ 54,
   },
   {
     /* [25] */
     /* name */ "T",
-    /* matcher index */ 62,
+    /* matcher index */ 58,
   },
 };
 
@@ -8136,7 +8168,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[918],
     /* return matcher indices */ &kMatcherIndices[34],
@@ -8148,7 +8180,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[617],
     /* return matcher indices */ &kMatcherIndices[34],
@@ -8160,10 +8192,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[917],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8172,10 +8204,10 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[621],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8184,10 +8216,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[916],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8196,10 +8228,10 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[625],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8208,10 +8240,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[915],
-    /* return matcher indices */ &kMatcherIndices[104],
+    /* return matcher indices */ &kMatcherIndices[110],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8220,10 +8252,10 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[629],
-    /* return matcher indices */ &kMatcherIndices[104],
+    /* return matcher indices */ &kMatcherIndices[110],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8232,10 +8264,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[914],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8244,10 +8276,10 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[633],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8256,10 +8288,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[913],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8268,10 +8300,10 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[637],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8280,10 +8312,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[912],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8295,7 +8327,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[911],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8307,7 +8339,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[643],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8319,7 +8351,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[910],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8331,7 +8363,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[647],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8343,7 +8375,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[909],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8355,7 +8387,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[651],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8367,7 +8399,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[835],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8379,7 +8411,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[655],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8391,7 +8423,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[907],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8415,7 +8447,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[3],
     /* parameters */ &kParameters[904],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8427,7 +8459,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[3],
     /* parameters */ &kParameters[903],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8439,7 +8471,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[3],
     /* parameters */ &kParameters[902],
-    /* return matcher indices */ &kMatcherIndices[104],
+    /* return matcher indices */ &kMatcherIndices[110],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8451,7 +8483,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[901],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8460,10 +8492,10 @@
     /* num parameters */ 0,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[15],
+    /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1004],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
@@ -8472,10 +8504,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[15],
+    /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[955],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
@@ -8484,10 +8516,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[956],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecSplat,
   },
@@ -8496,10 +8528,10 @@
     /* num parameters */ 4,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[399],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecCtorS,
   },
@@ -8508,10 +8540,10 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[564],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecCtorM,
   },
@@ -8520,10 +8552,10 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[573],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecCtorM,
   },
@@ -8532,10 +8564,10 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[576],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecCtorM,
   },
@@ -8544,10 +8576,10 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[605],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecCtorM,
   },
@@ -8556,10 +8588,10 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[603],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecCtorM,
   },
@@ -8568,10 +8600,10 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[601],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecCtorM,
   },
@@ -8580,10 +8612,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[2],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1003],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -8592,10 +8624,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[4],
+    /* template types */ &kTemplateTypes[2],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[965],
-    /* return matcher indices */ &kMatcherIndices[140],
+    /* return matcher indices */ &kMatcherIndices[150],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -8604,10 +8636,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[6],
+    /* template types */ &kTemplateTypes[4],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[966],
-    /* return matcher indices */ &kMatcherIndices[144],
+    /* return matcher indices */ &kMatcherIndices[156],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -8616,10 +8648,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[8],
+    /* template types */ &kTemplateTypes[6],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[967],
-    /* return matcher indices */ &kMatcherIndices[150],
+    /* return matcher indices */ &kMatcherIndices[158],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -8628,10 +8660,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[0],
+    /* template types */ &kTemplateTypes[8],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[968],
-    /* return matcher indices */ &kMatcherIndices[154],
+    /* return matcher indices */ &kMatcherIndices[160],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -8643,7 +8675,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[510],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -8655,7 +8687,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[507],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -8667,7 +8699,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[351],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -8679,7 +8711,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[347],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -8691,7 +8723,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[230],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -8703,7 +8735,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[495],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -8715,7 +8747,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[331],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -8727,7 +8759,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[489],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -8739,7 +8771,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[327],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -8823,7 +8855,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[315],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8835,7 +8867,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[265],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8847,7 +8879,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[260],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8859,7 +8891,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[132],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8871,7 +8903,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[299],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8883,7 +8915,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[205],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8895,7 +8927,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[295],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8907,7 +8939,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[285],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -8991,7 +9023,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[471],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9000,10 +9032,10 @@
     /* num parameters */ 4,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[431],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9012,10 +9044,10 @@
     /* num parameters */ 5,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[250],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9024,10 +9056,10 @@
     /* num parameters */ 5,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[215],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9036,10 +9068,10 @@
     /* num parameters */ 6,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[96],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9048,10 +9080,10 @@
     /* num parameters */ 4,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[407],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9060,10 +9092,10 @@
     /* num parameters */ 5,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[170],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9075,7 +9107,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[558],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9087,7 +9119,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[387],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9099,7 +9131,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[379],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9111,7 +9143,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[160],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9123,7 +9155,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[585],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9135,7 +9167,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[375],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9288,10 +9320,10 @@
     /* num parameters */ 0,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[15],
+    /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1004],
-    /* return matcher indices */ &kMatcherIndices[108],
+    /* return matcher indices */ &kMatcherIndices[112],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
@@ -9300,10 +9332,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[15],
+    /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[944],
-    /* return matcher indices */ &kMatcherIndices[108],
+    /* return matcher indices */ &kMatcherIndices[112],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
@@ -9312,10 +9344,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[945],
-    /* return matcher indices */ &kMatcherIndices[108],
+    /* return matcher indices */ &kMatcherIndices[112],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecSplat,
   },
@@ -9324,10 +9356,10 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[588],
-    /* return matcher indices */ &kMatcherIndices[108],
+    /* return matcher indices */ &kMatcherIndices[112],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecCtorS,
   },
@@ -9336,10 +9368,10 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[609],
-    /* return matcher indices */ &kMatcherIndices[108],
+    /* return matcher indices */ &kMatcherIndices[112],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecCtorM,
   },
@@ -9348,10 +9380,10 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[607],
-    /* return matcher indices */ &kMatcherIndices[108],
+    /* return matcher indices */ &kMatcherIndices[112],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecCtorM,
   },
@@ -9360,10 +9392,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[2],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[949],
-    /* return matcher indices */ &kMatcherIndices[100],
+    /* return matcher indices */ &kMatcherIndices[104],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -9372,10 +9404,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[4],
+    /* template types */ &kTemplateTypes[2],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[950],
-    /* return matcher indices */ &kMatcherIndices[102],
+    /* return matcher indices */ &kMatcherIndices[108],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -9384,10 +9416,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[6],
+    /* template types */ &kTemplateTypes[4],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[951],
-    /* return matcher indices */ &kMatcherIndices[104],
+    /* return matcher indices */ &kMatcherIndices[110],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -9396,10 +9428,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[8],
+    /* template types */ &kTemplateTypes[6],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[952],
-    /* return matcher indices */ &kMatcherIndices[110],
+    /* return matcher indices */ &kMatcherIndices[114],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -9408,10 +9440,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[0],
+    /* template types */ &kTemplateTypes[8],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[953],
-    /* return matcher indices */ &kMatcherIndices[112],
+    /* return matcher indices */ &kMatcherIndices[118],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -9420,7 +9452,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[880],
     /* return matcher indices */ &kMatcherIndices[34],
@@ -9432,7 +9464,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[879],
     /* return matcher indices */ &kMatcherIndices[34],
@@ -9444,7 +9476,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[878],
     /* return matcher indices */ &kMatcherIndices[34],
@@ -9456,7 +9488,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[877],
     /* return matcher indices */ &kMatcherIndices[34],
@@ -9468,7 +9500,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[876],
     /* return matcher indices */ &kMatcherIndices[34],
@@ -9480,7 +9512,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[875],
     /* return matcher indices */ &kMatcherIndices[34],
@@ -9540,7 +9572,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[727],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -9552,7 +9584,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[725],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -9564,7 +9596,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[723],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -9576,7 +9608,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[721],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -9648,10 +9680,10 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[522],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9660,10 +9692,10 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[528],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9672,10 +9704,10 @@
     /* num parameters */ 4,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[363],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9684,10 +9716,10 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[531],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9696,10 +9728,10 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[549],
-    /* return matcher indices */ &kMatcherIndices[114],
+    /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9747,7 +9779,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[783],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9756,10 +9788,10 @@
     /* num parameters */ 0,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[15],
+    /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1004],
-    /* return matcher indices */ &kMatcherIndices[106],
+    /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
@@ -9768,10 +9800,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[15],
+    /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[935],
-    /* return matcher indices */ &kMatcherIndices[106],
+    /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
@@ -9780,10 +9812,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[936],
-    /* return matcher indices */ &kMatcherIndices[106],
+    /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecSplat,
   },
@@ -9792,10 +9824,10 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[17],
+    /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[611],
-    /* return matcher indices */ &kMatcherIndices[106],
+    /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecCtorS,
   },
@@ -9804,10 +9836,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[2],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[938],
-    /* return matcher indices */ &kMatcherIndices[128],
+    /* return matcher indices */ &kMatcherIndices[134],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -9816,10 +9848,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[4],
+    /* template types */ &kTemplateTypes[2],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[939],
-    /* return matcher indices */ &kMatcherIndices[126],
+    /* return matcher indices */ &kMatcherIndices[132],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -9828,10 +9860,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[6],
+    /* template types */ &kTemplateTypes[4],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[940],
-    /* return matcher indices */ &kMatcherIndices[124],
+    /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -9840,10 +9872,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[8],
+    /* template types */ &kTemplateTypes[6],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[941],
-    /* return matcher indices */ &kMatcherIndices[122],
+    /* return matcher indices */ &kMatcherIndices[128],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -9852,10 +9884,10 @@
     /* num parameters */ 1,
     /* num template types */ 2,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[0],
+    /* template types */ &kTemplateTypes[8],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[942],
-    /* return matcher indices */ &kMatcherIndices[116],
+    /* return matcher indices */ &kMatcherIndices[122],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -9867,7 +9899,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[180],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9879,7 +9911,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[108],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9891,7 +9923,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[102],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9903,7 +9935,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[65],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9915,7 +9947,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[235],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9927,7 +9959,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[78],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9939,7 +9971,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[245],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9951,7 +9983,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[120],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -9963,7 +9995,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[343],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -9975,7 +10007,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[225],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -9987,7 +10019,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[150],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -9999,7 +10031,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[144],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -10011,7 +10043,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[439],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -10023,7 +10055,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[200],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -10035,7 +10067,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[435],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -10047,7 +10079,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[280],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
     /* const eval */ nullptr,
   },
@@ -10059,7 +10091,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1004],
-    /* return matcher indices */ &kMatcherIndices[218],
+    /* return matcher indices */ &kMatcherIndices[224],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
@@ -10071,7 +10103,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[922],
-    /* return matcher indices */ &kMatcherIndices[218],
+    /* return matcher indices */ &kMatcherIndices[224],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
@@ -10083,7 +10115,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[0],
-    /* return matcher indices */ &kMatcherIndices[218],
+    /* return matcher indices */ &kMatcherIndices[224],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorS,
   },
@@ -10095,7 +10127,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[443],
-    /* return matcher indices */ &kMatcherIndices[218],
+    /* return matcher indices */ &kMatcherIndices[224],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorV,
   },
@@ -10104,10 +10136,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[4],
+    /* template types */ &kTemplateTypes[2],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[908],
-    /* return matcher indices */ &kMatcherIndices[222],
+    /* return matcher indices */ &kMatcherIndices[228],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10116,10 +10148,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[2],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[906],
-    /* return matcher indices */ &kMatcherIndices[220],
+    /* return matcher indices */ &kMatcherIndices[226],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10203,7 +10235,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1004],
-    /* return matcher indices */ &kMatcherIndices[172],
+    /* return matcher indices */ &kMatcherIndices[176],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
@@ -10215,7 +10247,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[982],
-    /* return matcher indices */ &kMatcherIndices[172],
+    /* return matcher indices */ &kMatcherIndices[176],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
@@ -10227,7 +10259,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[57],
-    /* return matcher indices */ &kMatcherIndices[172],
+    /* return matcher indices */ &kMatcherIndices[176],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorS,
   },
@@ -10239,7 +10271,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[593],
-    /* return matcher indices */ &kMatcherIndices[172],
+    /* return matcher indices */ &kMatcherIndices[176],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorV,
   },
@@ -10248,10 +10280,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[4],
+    /* template types */ &kTemplateTypes[2],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[985],
-    /* return matcher indices */ &kMatcherIndices[176],
+    /* return matcher indices */ &kMatcherIndices[180],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10260,10 +10292,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[2],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[986],
-    /* return matcher indices */ &kMatcherIndices[174],
+    /* return matcher indices */ &kMatcherIndices[178],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10275,7 +10307,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1004],
-    /* return matcher indices */ &kMatcherIndices[156],
+    /* return matcher indices */ &kMatcherIndices[162],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
@@ -10287,7 +10319,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[970],
-    /* return matcher indices */ &kMatcherIndices[156],
+    /* return matcher indices */ &kMatcherIndices[162],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
@@ -10299,7 +10331,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[411],
-    /* return matcher indices */ &kMatcherIndices[156],
+    /* return matcher indices */ &kMatcherIndices[162],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorS,
   },
@@ -10311,7 +10343,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[597],
-    /* return matcher indices */ &kMatcherIndices[156],
+    /* return matcher indices */ &kMatcherIndices[162],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorV,
   },
@@ -10320,10 +10352,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[4],
+    /* template types */ &kTemplateTypes[2],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[973],
-    /* return matcher indices */ &kMatcherIndices[162],
+    /* return matcher indices */ &kMatcherIndices[166],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10332,10 +10364,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[2],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[974],
-    /* return matcher indices */ &kMatcherIndices[158],
+    /* return matcher indices */ &kMatcherIndices[164],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10419,7 +10451,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[367],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -10431,7 +10463,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[185],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -10443,7 +10475,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[190],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -10455,7 +10487,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[126],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -10467,7 +10499,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[355],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -10479,7 +10511,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[210],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -10491,7 +10523,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1004],
-    /* return matcher indices */ &kMatcherIndices[164],
+    /* return matcher indices */ &kMatcherIndices[168],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
@@ -10503,7 +10535,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[976],
-    /* return matcher indices */ &kMatcherIndices[164],
+    /* return matcher indices */ &kMatcherIndices[168],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
@@ -10515,7 +10547,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[84],
-    /* return matcher indices */ &kMatcherIndices[164],
+    /* return matcher indices */ &kMatcherIndices[168],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorS,
   },
@@ -10527,7 +10559,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[595],
-    /* return matcher indices */ &kMatcherIndices[164],
+    /* return matcher indices */ &kMatcherIndices[168],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorV,
   },
@@ -10536,10 +10568,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[4],
+    /* template types */ &kTemplateTypes[2],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[979],
-    /* return matcher indices */ &kMatcherIndices[170],
+    /* return matcher indices */ &kMatcherIndices[174],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10548,10 +10580,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[2],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[980],
-    /* return matcher indices */ &kMatcherIndices[168],
+    /* return matcher indices */ &kMatcherIndices[170],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10563,7 +10595,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1004],
-    /* return matcher indices */ &kMatcherIndices[194],
+    /* return matcher indices */ &kMatcherIndices[198],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
@@ -10575,7 +10607,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[987],
-    /* return matcher indices */ &kMatcherIndices[194],
+    /* return matcher indices */ &kMatcherIndices[198],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
@@ -10587,7 +10619,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[49],
-    /* return matcher indices */ &kMatcherIndices[194],
+    /* return matcher indices */ &kMatcherIndices[198],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorS,
   },
@@ -10599,7 +10631,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[415],
-    /* return matcher indices */ &kMatcherIndices[194],
+    /* return matcher indices */ &kMatcherIndices[198],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorV,
   },
@@ -10608,10 +10640,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[4],
+    /* template types */ &kTemplateTypes[2],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[969],
-    /* return matcher indices */ &kMatcherIndices[198],
+    /* return matcher indices */ &kMatcherIndices[202],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10620,10 +10652,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[2],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[954],
-    /* return matcher indices */ &kMatcherIndices[196],
+    /* return matcher indices */ &kMatcherIndices[200],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10635,7 +10667,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1004],
-    /* return matcher indices */ &kMatcherIndices[200],
+    /* return matcher indices */ &kMatcherIndices[204],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
@@ -10647,7 +10679,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[943],
-    /* return matcher indices */ &kMatcherIndices[200],
+    /* return matcher indices */ &kMatcherIndices[204],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
@@ -10659,7 +10691,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[16],
-    /* return matcher indices */ &kMatcherIndices[200],
+    /* return matcher indices */ &kMatcherIndices[204],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorS,
   },
@@ -10671,7 +10703,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[391],
-    /* return matcher indices */ &kMatcherIndices[200],
+    /* return matcher indices */ &kMatcherIndices[204],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorV,
   },
@@ -10680,10 +10712,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[4],
+    /* template types */ &kTemplateTypes[2],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[928],
-    /* return matcher indices */ &kMatcherIndices[212],
+    /* return matcher indices */ &kMatcherIndices[218],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10692,10 +10724,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[2],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[925],
-    /* return matcher indices */ &kMatcherIndices[210],
+    /* return matcher indices */ &kMatcherIndices[216],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10707,7 +10739,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1004],
-    /* return matcher indices */ &kMatcherIndices[96],
+    /* return matcher indices */ &kMatcherIndices[102],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
@@ -10719,7 +10751,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1000],
-    /* return matcher indices */ &kMatcherIndices[96],
+    /* return matcher indices */ &kMatcherIndices[102],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
@@ -10731,7 +10763,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[28],
-    /* return matcher indices */ &kMatcherIndices[96],
+    /* return matcher indices */ &kMatcherIndices[102],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorS,
   },
@@ -10743,7 +10775,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[468],
-    /* return matcher indices */ &kMatcherIndices[96],
+    /* return matcher indices */ &kMatcherIndices[102],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorV,
   },
@@ -10752,10 +10784,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[4],
+    /* template types */ &kTemplateTypes[2],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[999],
-    /* return matcher indices */ &kMatcherIndices[192],
+    /* return matcher indices */ &kMatcherIndices[196],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10764,10 +10796,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[2],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[993],
-    /* return matcher indices */ &kMatcherIndices[190],
+    /* return matcher indices */ &kMatcherIndices[194],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10779,7 +10811,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1004],
-    /* return matcher indices */ &kMatcherIndices[184],
+    /* return matcher indices */ &kMatcherIndices[188],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
@@ -10791,7 +10823,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[994],
-    /* return matcher indices */ &kMatcherIndices[184],
+    /* return matcher indices */ &kMatcherIndices[188],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
@@ -10803,7 +10835,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[40],
-    /* return matcher indices */ &kMatcherIndices[184],
+    /* return matcher indices */ &kMatcherIndices[188],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorS,
   },
@@ -10815,7 +10847,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[582],
-    /* return matcher indices */ &kMatcherIndices[184],
+    /* return matcher indices */ &kMatcherIndices[188],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorV,
   },
@@ -10824,10 +10856,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[4],
+    /* template types */ &kTemplateTypes[2],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[997],
-    /* return matcher indices */ &kMatcherIndices[188],
+    /* return matcher indices */ &kMatcherIndices[192],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10836,10 +10868,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[2],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[998],
-    /* return matcher indices */ &kMatcherIndices[186],
+    /* return matcher indices */ &kMatcherIndices[190],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10851,7 +10883,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1004],
-    /* return matcher indices */ &kMatcherIndices[178],
+    /* return matcher indices */ &kMatcherIndices[182],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
@@ -10863,7 +10895,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[988],
-    /* return matcher indices */ &kMatcherIndices[178],
+    /* return matcher indices */ &kMatcherIndices[182],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
@@ -10875,7 +10907,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[114],
-    /* return matcher indices */ &kMatcherIndices[178],
+    /* return matcher indices */ &kMatcherIndices[182],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorS,
   },
@@ -10887,7 +10919,7 @@
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[579],
-    /* return matcher indices */ &kMatcherIndices[178],
+    /* return matcher indices */ &kMatcherIndices[182],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatCtorV,
   },
@@ -10896,10 +10928,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[4],
+    /* template types */ &kTemplateTypes[2],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[991],
-    /* return matcher indices */ &kMatcherIndices[182],
+    /* return matcher indices */ &kMatcherIndices[186],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10908,10 +10940,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[2],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[992],
-    /* return matcher indices */ &kMatcherIndices[180],
+    /* return matcher indices */ &kMatcherIndices[184],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -10920,7 +10952,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[737],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -10932,7 +10964,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[735],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -10944,7 +10976,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[733],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -10956,7 +10988,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[731],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -10980,7 +11012,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[885],
     /* return matcher indices */ &kMatcherIndices[34],
@@ -10992,7 +11024,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[884],
     /* return matcher indices */ &kMatcherIndices[34],
@@ -11040,67 +11072,67 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[13],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[749],
     /* return matcher indices */ &kMatcherIndices[1],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
+    /* const eval */ &ConstEval::OpPlus,
   },
   {
     /* [243] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[13],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[747],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
+    /* const eval */ &ConstEval::OpPlus,
   },
   {
     /* [244] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[13],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[745],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
+    /* const eval */ &ConstEval::OpPlus,
   },
   {
     /* [245] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[13],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[743],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
+    /* const eval */ &ConstEval::OpPlus,
   },
   {
     /* [246] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 2,
-    /* template types */ &kTemplateTypes[11],
+    /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[741],
     /* return matcher indices */ &kMatcherIndices[10],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
+    /* const eval */ &ConstEval::OpPlus,
   },
   {
     /* [247] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[701],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -11112,7 +11144,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[699],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11124,7 +11156,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[695],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11136,7 +11168,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[693],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11172,7 +11204,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[683],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -11184,7 +11216,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[681],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11220,7 +11252,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[675],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -11232,7 +11264,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[673],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11244,7 +11276,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[709],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -11256,7 +11288,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[707],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11268,7 +11300,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[705],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11280,7 +11312,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[781],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11316,7 +11348,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
+    /* template types */ &kTemplateTypes[24],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[921],
     /* return matcher indices */ &kMatcherIndices[34],
@@ -11352,7 +11384,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[19],
+    /* template types */ &kTemplateTypes[20],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[933],
     /* return matcher indices */ &kMatcherIndices[16],
@@ -11367,7 +11399,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1004],
-    /* return matcher indices */ &kMatcherIndices[103],
+    /* return matcher indices */ &kMatcherIndices[109],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
@@ -11379,7 +11411,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[929],
-    /* return matcher indices */ &kMatcherIndices[103],
+    /* return matcher indices */ &kMatcherIndices[109],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
@@ -11388,10 +11420,10 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[20],
+    /* template types */ &kTemplateTypes[21],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[930],
-    /* return matcher indices */ &kMatcherIndices[103],
+    /* return matcher indices */ &kMatcherIndices[109],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
@@ -11424,7 +11456,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[21],
+    /* template types */ &kTemplateTypes[22],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[927],
     /* return matcher indices */ &kMatcherIndices[4],
@@ -11436,7 +11468,7 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[15],
+    /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[540],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -11448,7 +11480,7 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[15],
+    /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[543],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11460,7 +11492,7 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[15],
+    /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[546],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11496,7 +11528,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[22],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[924],
     /* return matcher indices */ &kMatcherIndices[35],
@@ -11544,7 +11576,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[25],
+    /* template types */ &kTemplateTypes[10],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[860],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -11556,7 +11588,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[25],
+    /* template types */ &kTemplateTypes[10],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[861],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11566,24 +11598,24 @@
   {
     /* [286] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[26],
+    /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[798],
-    /* return matcher indices */ &kMatcherIndices[233],
+    /* return matcher indices */ &kMatcherIndices[172],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
   {
     /* [287] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[26],
+    /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[810],
-    /* return matcher indices */ &kMatcherIndices[166],
+    /* return matcher indices */ &kMatcherIndices[78],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -11664,7 +11696,7 @@
     /* num parameters */ 4,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[423],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -11676,7 +11708,7 @@
     /* num parameters */ 4,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[427],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11808,7 +11840,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[763],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -11820,7 +11852,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[761],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11832,7 +11864,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[759],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -11844,7 +11876,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[757],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11878,24 +11910,24 @@
   {
     /* [312] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[26],
+    /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[855],
-    /* return matcher indices */ &kMatcherIndices[232],
+    /* return matcher indices */ &kMatcherIndices[136],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
   {
     /* [313] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[26],
+    /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[856],
-    /* return matcher indices */ &kMatcherIndices[130],
+    /* return matcher indices */ &kMatcherIndices[96],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -11952,7 +11984,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[806],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -11964,7 +11996,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[805],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11976,7 +12008,7 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[459],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -11988,7 +12020,7 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[456],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -12144,7 +12176,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[889],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -12156,7 +12188,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[890],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -12360,7 +12392,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[804],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -12372,7 +12404,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[803],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -12504,7 +12536,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[619],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -12516,7 +12548,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[615],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -12552,7 +12584,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[635],
     /* return matcher indices */ &kMatcherIndices[16],
@@ -12564,7 +12596,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[631],
     /* return matcher indices */ &kMatcherIndices[39],
@@ -12576,7 +12608,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[641],
     /* return matcher indices */ &kMatcherIndices[16],
@@ -12588,7 +12620,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[639],
     /* return matcher indices */ &kMatcherIndices[39],
@@ -12600,7 +12632,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[649],
     /* return matcher indices */ &kMatcherIndices[16],
@@ -12612,7 +12644,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[645],
     /* return matcher indices */ &kMatcherIndices[39],
@@ -12672,7 +12704,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[1002],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -12684,7 +12716,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[996],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -12696,7 +12728,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[832],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -12708,7 +12740,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[831],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -12720,7 +12752,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[834],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -12732,7 +12764,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[833],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -12744,7 +12776,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[16],
+    /* template types */ &kTemplateTypes[17],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[870],
     /* return matcher indices */ &kMatcherIndices[34],
@@ -12768,7 +12800,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[837],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -12780,7 +12812,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[836],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -12792,7 +12824,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[657],
     /* return matcher indices */ &kMatcherIndices[16],
@@ -12804,7 +12836,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[653],
     /* return matcher indices */ &kMatcherIndices[39],
@@ -12960,7 +12992,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[15],
+    /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[659],
     /* return matcher indices */ &kMatcherIndices[16],
@@ -12972,7 +13004,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[15],
+    /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[599],
     /* return matcher indices */ &kMatcherIndices[39],
@@ -12984,7 +13016,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[15],
+    /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[663],
     /* return matcher indices */ &kMatcherIndices[16],
@@ -12996,7 +13028,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[15],
+    /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[661],
     /* return matcher indices */ &kMatcherIndices[39],
@@ -13056,7 +13088,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[691],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -13068,7 +13100,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[689],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -13200,7 +13232,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[24],
+    /* template types */ &kTemplateTypes[25],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[862],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -13212,7 +13244,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[24],
+    /* template types */ &kTemplateTypes[25],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[863],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -13248,7 +13280,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[627],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -13260,7 +13292,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[623],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -13272,10 +13304,10 @@
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[9],
     /* parameters */ &kParameters[561],
-    /* return matcher indices */ &kMatcherIndices[202],
+    /* return matcher indices */ &kMatcherIndices[206],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -13284,7 +13316,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[9],
     /* parameters */ &kParameters[753],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -13296,7 +13328,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[9],
     /* parameters */ &kParameters[755],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -13308,7 +13340,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[9],
     /* parameters */ &kParameters[765],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -13320,7 +13352,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[9],
     /* parameters */ &kParameters[767],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -13332,7 +13364,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[9],
     /* parameters */ &kParameters[769],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -13344,7 +13376,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[9],
     /* parameters */ &kParameters[771],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -13356,7 +13388,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[9],
     /* parameters */ &kParameters[773],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -13368,7 +13400,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[9],
     /* parameters */ &kParameters[775],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -13404,7 +13436,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[9],
     /* parameters */ &kParameters[591],
     /* return matcher indices */ nullptr,
@@ -13416,7 +13448,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
+    /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[9],
     /* parameters */ &kParameters[847],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -13455,7 +13487,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[937],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -13467,7 +13499,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[947],
-    /* return matcher indices */ &kMatcherIndices[120],
+    /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -13479,7 +13511,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[948],
-    /* return matcher indices */ &kMatcherIndices[128],
+    /* return matcher indices */ &kMatcherIndices[134],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -13491,7 +13523,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[961],
-    /* return matcher indices */ &kMatcherIndices[128],
+    /* return matcher indices */ &kMatcherIndices[134],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -13503,7 +13535,7 @@
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[962],
-    /* return matcher indices */ &kMatcherIndices[128],
+    /* return matcher indices */ &kMatcherIndices[134],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -13611,7 +13643,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[785],
-    /* return matcher indices */ &kMatcherIndices[108],
+    /* return matcher indices */ &kMatcherIndices[112],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -13656,7 +13688,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[14],
+    /* template types */ &kTemplateTypes[15],
     /* template numbers */ &kTemplateNumbers[6],
     /* parameters */ &kParameters[791],
     /* return matcher indices */ &kMatcherIndices[1],
@@ -13680,7 +13712,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[18],
+    /* template types */ &kTemplateTypes[19],
     /* template numbers */ &kTemplateNumbers[8],
     /* parameters */ &kParameters[960],
     /* return matcher indices */ &kMatcherIndices[35],
@@ -13977,8 +14009,8 @@
   },
   {
     /* [40] */
-    /* fn frexp(f32) -> __frexp_result */
-    /* fn frexp<N : num>(vec<N, f32>) -> __frexp_result_vec<N> */
+    /* fn frexp<T : f32_f16>(T) -> __frexp_result<T> */
+    /* fn frexp<N : num, T : f32_f16>(vec<N, T>) -> __frexp_result_vec<N, T> */
     /* num overloads */ 2,
     /* overloads */ &kOverloads[286],
   },
@@ -14069,8 +14101,8 @@
   },
   {
     /* [53] */
-    /* fn modf(f32) -> __modf_result */
-    /* fn modf<N : num>(vec<N, f32>) -> __modf_result_vec<N> */
+    /* fn modf<T : f32_f16>(T) -> __modf_result<T> */
+    /* fn modf<N : num, T : f32_f16>(vec<N, T>) -> __modf_result_vec<N, T> */
     /* num overloads */ 2,
     /* overloads */ &kOverloads[312],
   },
@@ -14578,11 +14610,11 @@
 constexpr IntrinsicInfo kBinaryOperators[] = {
   {
     /* [0] */
-    /* op +<T : fiu32_f16>(T, T) -> T */
-    /* op +<T : fiu32_f16, N : num>(vec<N, T>, vec<N, T>) -> vec<N, T> */
-    /* op +<T : fiu32_f16, N : num>(vec<N, T>, T) -> vec<N, T> */
-    /* op +<T : fiu32_f16, N : num>(T, vec<N, T>) -> vec<N, T> */
-    /* op +<T : f32_f16, N : num, M : num>(mat<N, M, T>, mat<N, M, T>) -> mat<N, M, T> */
+    /* op +<T : fia_fiu32_f16>(T, T) -> T */
+    /* op +<T : fia_fiu32_f16, N : num>(vec<N, T>, vec<N, T>) -> vec<N, T> */
+    /* op +<T : fia_fiu32_f16, N : num>(vec<N, T>, T) -> vec<N, T> */
+    /* op +<T : fia_fiu32_f16, N : num>(T, vec<N, T>) -> vec<N, T> */
+    /* op +<T : fa_f32_f16, N : num, M : num>(mat<N, M, T>, mat<N, M, T>) -> mat<N, M, T> */
     /* num overloads */ 5,
     /* overloads */ &kOverloads[242],
   },
diff --git a/src/tint/resolver/intrinsic_table_test.cc b/src/tint/resolver/intrinsic_table_test.cc
index 9301386..02c97f7 100644
--- a/src/tint/resolver/intrinsic_table_test.cc
+++ b/src/tint/resolver/intrinsic_table_test.cc
@@ -886,20 +886,21 @@
                          IntrinsicTableAbstractBinaryTest,
                          testing::Values(  // clang-format off
 //            result   | param lhs | param rhs |  arg lhs  |  arg rhs
-Case::Create<f32,        f32,        f32,        AFloat,     AFloat>(),
-Case::Create<f32,        f32,        f32,        AFloat,     AInt>(),
-Case::Create<f32,        f32,        f32,        AInt,       AFloat>(),
-Case::Create<i32,        i32,        i32,        AInt,       AInt>()
+Case::Create<AFloat,     AFloat,     AFloat,     AFloat,     AFloat>(),
+Case::Create<AFloat,     AFloat,     AFloat,     AFloat,     AInt>(),
+Case::Create<AFloat,     AFloat,     AFloat,     AInt,       AFloat>(),
+Case::Create<AInt,       AInt,       AInt,       AInt,       AInt>()
                              ));  // clang-format on
 
-INSTANTIATE_TEST_SUITE_P(VecAFloat_VecAInt,
-                         IntrinsicTableAbstractBinaryTest,
-                         testing::Values(  // clang-format off
+INSTANTIATE_TEST_SUITE_P(
+    VecAFloat_VecAInt,
+    IntrinsicTableAbstractBinaryTest,
+    testing::Values(  // clang-format off
 //            result   | param lhs | param rhs |  arg lhs  |  arg rhs
-Case::Create<f32V,       f32V,       f32V,       AFloatV,    AFloatV>(),
-Case::Create<f32V,       f32V,       f32V,       AFloatV,    AIntV>(),
-Case::Create<f32V,       f32V,       f32V,       AIntV,      AFloatV>(),
-Case::Create<i32V,       i32V,       i32V,       AIntV,      AIntV>()
+Case::Create<AFloatV,    AFloatV,    AFloatV,    AFloatV,    AFloatV>(),
+Case::Create<AFloatV,    AFloatV,    AFloatV,    AFloatV,    AIntV>(),
+Case::Create<AFloatV,    AFloatV,    AFloatV,    AIntV,      AFloatV>(),
+Case::Create<AIntV,      AIntV,      AIntV,      AIntV,      AIntV>()
                              ));  // clang-format on
 
 INSTANTIATE_TEST_SUITE_P(AFloat_f32,
diff --git a/src/tint/resolver/is_storeable_test.cc b/src/tint/resolver/is_storeable_test.cc
index 80c3e20..0423f72 100644
--- a/src/tint/resolver/is_storeable_test.cc
+++ b/src/tint/resolver/is_storeable_test.cc
@@ -99,7 +99,7 @@
 }
 
 TEST_F(ResolverIsStorableTest, Struct_AllMembersStorable) {
-    Structure("S", {
+    Structure("S", utils::Vector{
                        Member("a", ty.i32()),
                        Member("b", ty.f32()),
                    });
@@ -108,7 +108,7 @@
 }
 
 TEST_F(ResolverIsStorableTest, Struct_SomeMembersNonStorable) {
-    Structure("S", {
+    Structure("S", utils::Vector{
                        Member("a", ty.i32()),
                        Member("b", ty.pointer<i32>(ast::StorageClass::kPrivate)),
                    });
@@ -120,11 +120,11 @@
 }
 
 TEST_F(ResolverIsStorableTest, Struct_NestedStorable) {
-    auto* storable = Structure("Storable", {
+    auto* storable = Structure("Storable", utils::Vector{
                                                Member("a", ty.i32()),
                                                Member("b", ty.f32()),
                                            });
-    Structure("S", {
+    Structure("S", utils::Vector{
                        Member("a", ty.i32()),
                        Member("b", ty.Of(storable)),
                    });
@@ -134,11 +134,11 @@
 
 TEST_F(ResolverIsStorableTest, Struct_NestedNonStorable) {
     auto* non_storable =
-        Structure("nonstorable", {
+        Structure("nonstorable", utils::Vector{
                                      Member("a", ty.i32()),
                                      Member("b", ty.pointer<i32>(ast::StorageClass::kPrivate)),
                                  });
-    Structure("S", {
+    Structure("S", utils::Vector{
                        Member("a", ty.i32()),
                        Member("b", ty.Of(non_storable)),
                    });
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index 70985dd..26f109f 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -334,20 +334,20 @@
             WrapInFunction(Assign(Phony(), abstract_expr));
             break;
         case Method::kFnArg:
-            Func("F", {Param("P", target_ty())}, ty.void_(), {});
+            Func("F", utils::Vector{Param("P", target_ty())}, ty.void_(), utils::Empty);
             WrapInFunction(CallStmt(Call("F", abstract_expr)));
             break;
         case Method::kBuiltinArg:
             WrapInFunction(CallStmt(Call("min", target_expr(), abstract_expr)));
             break;
         case Method::kReturn:
-            Func("F", {}, target_ty(), {Return(abstract_expr)});
+            Func("F", utils::Empty, target_ty(), utils::Vector{Return(abstract_expr)});
             break;
         case Method::kArray:
             WrapInFunction(Construct(ty.array(target_ty(), 1_i), abstract_expr));
             break;
         case Method::kStruct:
-            Structure("S", {Member("v", target_ty())});
+            Structure("S", utils::Vector{Member("v", target_ty())});
             WrapInFunction(Construct(ty.type_name("S"), abstract_expr));
             break;
         case Method::kBinaryOp:
@@ -376,9 +376,9 @@
                                   DefaultCase()));
             break;
         case Method::kWorkgroupSize:
-            Func("f", {}, ty.void_(), {},
-                 {WorkgroupSize(target_expr(), abstract_expr, Expr(123_a)),
-                  Stage(ast::PipelineStage::kCompute)});
+            Func("f", utils::Empty, ty.void_(), utils::Empty,
+                 utils::Vector{WorkgroupSize(target_expr(), abstract_expr, Expr(123_a)),
+                               Stage(ast::PipelineStage::kCompute)});
             break;
         case Method::kRuntimeIndex:
             auto* runtime_index = Var("runtime_index", nullptr, Expr(1_i));
@@ -464,9 +464,10 @@
 
 /// Methods that do not materialize
 constexpr Method kNoMaterializeMethods[] = {
-    Method::kPhonyAssign,
-    // TODO(crbug.com/tint/1504): Enable once we have abstract overloads of builtins / binary
-    // ops: Method::kBuiltinArg, Method::kBinaryOp,
+    Method::kPhonyAssign,  //
+    Method::kBinaryOp,
+    // TODO(crbug.com/tint/1504): Enable once "min" supports const evaluation
+    // Method::kBuiltinArg,
 };
 INSTANTIATE_TEST_SUITE_P(
     MaterializeScalar,
@@ -863,10 +864,10 @@
     const auto& method = std::get<1>(param);
     const auto& data = std::get<2>(param);
 
-    ast::ExpressionList abstract_exprs;
+    utils::Vector<const ast::Expression*, 4> abstract_exprs;
     auto abstract_expr = [&] {
         auto* expr = data.abstract_expr(*this, data.literal_value);
-        abstract_exprs.emplace_back(expr);
+        abstract_exprs.Push(expr);
         return expr;
     };
     switch (method) {
@@ -894,8 +895,9 @@
                                   DefaultCase()));
             break;
         case Method::kWorkgroupSize:
-            Func("f", {}, ty.void_(), {},
-                 {WorkgroupSize(abstract_expr()), Stage(ast::PipelineStage::kCompute)});
+            Func(
+                "f", utils::Empty, ty.void_(), utils::Empty,
+                utils::Vector{WorkgroupSize(abstract_expr()), Stage(ast::PipelineStage::kCompute)});
             break;
         case Method::kIndex:
             GlobalVar("arr", ty.array<i32, 4>(), ast::StorageClass::kPrivate);
@@ -1163,7 +1165,7 @@
 using MaterializeAbstractNumericToUnrelatedType = resolver::ResolverTest;
 
 TEST_F(MaterializeAbstractNumericToUnrelatedType, AIntToStructVarCtor) {
-    Structure("S", {Member("a", ty.i32())});
+    Structure("S", utils::Vector{Member("a", ty.i32())});
     WrapInFunction(Decl(Var("v", ty.type_name("S"), Expr(Source{{12, 34}}, 1_a))));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1172,7 +1174,7 @@
 }
 
 TEST_F(MaterializeAbstractNumericToUnrelatedType, AIntToStructLetCtor) {
-    Structure("S", {Member("a", ty.i32())});
+    Structure("S", utils::Vector{Member("a", ty.i32())});
     WrapInFunction(Decl(Let("v", ty.type_name("S"), Expr(Source{{12, 34}}, 1_a))));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
diff --git a/src/tint/resolver/override_test.cc b/src/tint/resolver/override_test.cc
index f1e13e9..2615d73 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), {Id(7u)});
+    auto* a = Override("a", ty.f32(), Expr(1_f), utils::Vector{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), {Id(2u)});
-    auto* d = Override("d", ty.f32(), Expr(1_f), {Id(4u)});
+    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* e = Override("e", ty.f32(), Expr(1_f));
-    auto* f = Override("f", ty.f32(), Expr(1_f), {Id(1u)});
+    auto* f = Override("f", ty.f32(), Expr(1_f), utils::Vector{Id(1u)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -86,8 +86,8 @@
 }
 
 TEST_F(ResolverOverrideTest, DuplicateIds) {
-    Override("a", ty.f32(), Expr(1_f), {Id(Source{{12, 34}}, 7u)});
-    Override("b", ty.f32(), Expr(1_f), {Id(Source{{56, 78}}, 7u)});
+    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)});
 
     EXPECT_FALSE(r()->Resolve());
 
@@ -96,7 +96,7 @@
 }
 
 TEST_F(ResolverOverrideTest, IdTooLarge) {
-    Override("a", ty.f32(), Expr(1_f), {Id(Source{{12, 34}}, 65536u)});
+    Override("a", ty.f32(), Expr(1_f), utils::Vector{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), {Id(1u)});
+    Override(Source{{12, 34}}, "a", ty.f16(), Expr(1_h), utils::Vector{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 037c6ff..0a757b4 100644
--- a/src/tint/resolver/ptr_ref_test.cc
+++ b/src/tint/resolver/ptr_ref_test.cc
@@ -57,17 +57,17 @@
 TEST_F(ResolverPtrRefTest, DefaultPtrStorageClass) {
     // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
 
-    auto* buf = Structure("S", {Member("m", ty.i32())});
+    auto* buf = Structure("S", utils::Vector{Member("m", ty.i32())});
     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,
-                              ast::AttributeList{
+                              utils::Vector{
                                   create<ast::BindingAttribute>(0u),
                                   create<ast::GroupAttribute>(0u),
                               });
     auto* storage = GlobalVar("sb", ty.Of(buf), ast::StorageClass::kStorage,
-                              ast::AttributeList{
+                              utils::Vector{
                                   create<ast::BindingAttribute>(1u),
                                   create<ast::GroupAttribute>(0u),
                               });
diff --git a/src/tint/resolver/ptr_ref_validation_test.cc b/src/tint/resolver/ptr_ref_validation_test.cc
index 768783b..5de5007 100644
--- a/src/tint/resolver/ptr_ref_validation_test.cc
+++ b/src/tint/resolver/ptr_ref_validation_test.cc
@@ -141,10 +141,10 @@
     // fn f() {
     //   let p : pointer<storage, i32> = &s.inner.arr[2i];
     // }
-    auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
-    auto* buf = Structure("S", {Member("inner", ty.Of(inner))});
+    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,
-                              ast::AttributeList{
+                              utils::Vector{
                                   create<ast::BindingAttribute>(0u),
                                   create<ast::GroupAttribute>(0u),
                               });
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 097d591..2edab1f 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -140,6 +140,7 @@
                 [&](const ast::TypeDecl* td) { return TypeDecl(td); },
                 [&](const ast::Function* func) { return Function(func); },
                 [&](const ast::Variable* var) { return GlobalVariable(var); },
+                [&](const ast::StaticAssert* sa) { return StaticAssert(sa); },
                 [&](Default) {
                     TINT_UNREACHABLE(Resolver, diagnostics_)
                         << "unhandled global declaration: " << decl->TypeInfo().name;
@@ -159,6 +160,10 @@
         return false;
     }
 
+    if (!validator_.PushConstants(entry_points_)) {
+        return false;
+    }
+
     if (!enabled_extensions_.contains(ast::Extension::kChromiumDisableUniformityAnalysis)) {
         if (!AnalyzeUniformity(builder_, dependencies_)) {
             // TODO(jrprice): Reject programs that fail uniformity analysis.
@@ -726,13 +731,40 @@
 
     // TODO(bclayton): Call this at the end of resolve on all uniform and storage
     // referenced structs
-    if (!validator_.StorageClassLayout(sem, valid_type_storage_layouts_)) {
+    if (!validator_.StorageClassLayout(sem, enabled_extensions_, valid_type_storage_layouts_)) {
         return nullptr;
     }
 
     return sem;
 }
 
+sem::Statement* Resolver::StaticAssert(const ast::StaticAssert* assertion) {
+    auto* expr = Expression(assertion->condition);
+    if (!expr) {
+        return nullptr;
+    }
+    auto* cond = expr->ConstantValue();
+    if (!cond) {
+        AddError("static assertion condition must be a constant expression",
+                 assertion->condition->source);
+        return nullptr;
+    }
+    if (auto* ty = cond->Type(); !ty->Is<sem::Bool>()) {
+        AddError(
+            "static assertion condition must be a bool, got '" + builder_->FriendlyName(ty) + "'",
+            assertion->condition->source);
+        return nullptr;
+    }
+    if (!cond->As<bool>()) {
+        AddError("static assertion failed", assertion->source);
+        return nullptr;
+    }
+    auto* sem =
+        builder_->create<sem::Statement>(assertion, current_compound_statement_, current_function_);
+    builder_->Sem().Add(assertion, sem);
+    return sem;
+}
+
 sem::Function* Resolver::Function(const ast::Function* decl) {
     uint32_t parameter_index = 0;
     std::unordered_map<Symbol, Source> parameter_names;
@@ -988,7 +1020,7 @@
     return true;
 }
 
-bool Resolver::Statements(const ast::StatementList& stmts) {
+bool Resolver::Statements(utils::VectorRef<const ast::Statement*> stmts) {
     sem::Behaviors behaviors{sem::Behavior::kNext};
 
     bool reachable = true;
@@ -1038,6 +1070,7 @@
         [&](const ast::IncrementDecrementStatement* i) { return IncrementDecrementStatement(i); },
         [&](const ast::ReturnStatement* r) { return ReturnStatement(r); },
         [&](const ast::VariableDeclStatement* v) { return VariableDeclStatement(v); },
+        [&](const ast::StaticAssert* sa) { return StaticAssert(sa); },
 
         // Error cases
         [&](const ast::CaseStatement*) {
@@ -1054,7 +1087,7 @@
     auto* sem =
         builder_->create<sem::CaseStatement>(stmt, current_compound_statement_, current_function_);
     return StatementScope(stmt, sem, [&] {
-        sem->Selectors().reserve(stmt->selectors.size());
+        sem->Selectors().reserve(stmt->selectors.Length());
         for (auto* sel : stmt->selectors) {
             auto* expr = Expression(sel);
             if (!expr) {
@@ -1243,7 +1276,7 @@
 }
 
 sem::Expression* Resolver::Expression(const ast::Expression* root) {
-    utils::Vector<const ast::Expression*, 128> sorted;
+    utils::Vector<const ast::Expression*, 64> sorted;
     constexpr size_t kMaxExpressionDepth = 512U;
     bool failed = false;
     if (!ast::TraverseExpressions<ast::TraverseOrder::RightToLeft>(
@@ -1402,7 +1435,8 @@
     return m;
 }
 
-bool Resolver::MaterializeArguments(utils::VectorRef<const sem::Expression*> args,
+template <size_t N>
+bool Resolver::MaterializeArguments(utils::Vector<const sem::Expression*, N>& args,
                                     const sem::CallTarget* target) {
     for (size_t i = 0, n = std::min(args.Length(), target->Parameters().Length()); i < n; i++) {
         const auto* param_ty = target->Parameters()[i]->Type();
@@ -1418,10 +1452,33 @@
 }
 
 bool Resolver::ShouldMaterializeArgument(const sem::Type* parameter_ty) const {
-    const auto* param_el_ty = sem::Type::ElementOf(parameter_ty);
+    const auto* param_el_ty = sem::Type::DeepestElementOf(parameter_ty);
     return param_el_ty && !param_el_ty->Is<sem::AbstractNumeric>();
 }
 
+bool Resolver::Convert(const sem::Constant*& c, const sem::Type* target_ty, const Source& source) {
+    auto r = const_eval_.Convert(target_ty, c, source);
+    if (!r) {
+        return false;
+    }
+    c = r.Get();
+    return true;
+}
+
+template <size_t N>
+utils::Result<utils::Vector<const sem::Constant*, N>> Resolver::ConvertArguments(
+    const utils::Vector<const sem::Expression*, N>& args,
+    const sem::CallTarget* target) {
+    auto const_args = utils::Transform(args, [](auto* arg) { return arg->ConstantValue(); });
+    for (size_t i = 0, n = std::min(args.Length(), target->Parameters().Length()); i < n; i++) {
+        if (!Convert(const_args[i], target->Parameters()[i]->Type(),
+                     args[i]->Declaration()->source)) {
+            return utils::Failure;
+        }
+    }
+    return const_args;
+}
+
 sem::Expression* Resolver::IndexAccessor(const ast::IndexAccessorExpression* expr) {
     auto* idx = Materialize(sem_.Get(expr->index));
     if (!idx) {
@@ -1467,7 +1524,12 @@
     }
 
     auto stage = sem::EarliestStage(obj->Stage(), idx->Stage());
-    auto val = const_eval_.Index(obj, idx);
+    const sem::Constant* val = nullptr;
+    if (auto r = const_eval_.Index(obj, idx)) {
+        val = r.Get();
+    } else {
+        return nullptr;
+    }
     bool has_side_effects = idx->HasSideEffects() || obj->HasSideEffects();
     auto* sem = builder_->create<sem::IndexAccessorExpression>(
         expr, ty, stage, obj, idx, current_statement_, std::move(val), has_side_effects,
@@ -1486,7 +1548,12 @@
         return nullptr;
     }
 
-    auto val = const_eval_.Bitcast(ty, inner);
+    const sem::Constant* val = nullptr;
+    if (auto r = const_eval_.Bitcast(ty, inner)) {
+        val = r.Get();
+    } else {
+        return nullptr;
+    }
     auto stage = sem::EvaluationStage::kRuntime;  // TODO(crbug.com/tint/1581)
     auto* sem = builder_->create<sem::Expression>(expr, ty, stage, current_statement_,
                                                   std::move(val), inner->HasSideEffects());
@@ -1509,10 +1576,10 @@
 
     // Resolve all of the arguments, their types and the set of behaviors.
     utils::Vector<const sem::Expression*, 8> args;
-    args.Reserve(expr->args.size());
+    args.Reserve(expr->args.Length());
     auto args_stage = sem::EvaluationStage::kConstant;
     sem::Behaviors arg_behaviors;
-    for (size_t i = 0; i < expr->args.size(); i++) {
+    for (size_t i = 0; i < expr->args.Length(); i++) {
         auto* arg = sem_.Get(expr->args[i]);
         if (!arg) {
             return nullptr;
@@ -1541,8 +1608,14 @@
         const sem::Constant* value = nullptr;
         auto stage = sem::EarliestStage(ctor_or_conv.target->Stage(), args_stage);
         if (stage == sem::EvaluationStage::kConstant) {
-            value =
-                (const_eval_.*ctor_or_conv.const_eval_fn)(ctor_or_conv.target->ReturnType(), args);
+            auto const_args =
+                utils::Transform(args, [](auto* arg) { return arg->ConstantValue(); });
+            if (auto r = (const_eval_.*ctor_or_conv.const_eval_fn)(
+                    ctor_or_conv.target->ReturnType(), const_args, expr->source)) {
+                value = r.Get();
+            } else {
+                return nullptr;
+            }
         }
         return builder_->create<sem::Call>(expr, ctor_or_conv.target, stage, std::move(args),
                                            current_statement_, value, has_side_effects);
@@ -1559,7 +1632,11 @@
         auto stage = args_stage;               // The evaluation stage of the call
         const sem::Constant* value = nullptr;  // The constant value for the call
         if (stage == sem::EvaluationStage::kConstant) {
-            value = const_eval_.ArrayOrStructCtor(ty, args);
+            if (auto r = const_eval_.ArrayOrStructCtor(ty, args)) {
+                value = r.Get();
+            } else {
+                return nullptr;
+            }
             if (!value) {
                 // Constant evaluation failed.
                 // Can happen for expressions that will fail validation (later).
@@ -1774,9 +1851,7 @@
                 // there's no need to infer element types.
                 return ty_ctor_or_conv(ty);
             },
-            [&](sem::Function* func) {
-                return FunctionCall(expr, func, std::move(args), arg_behaviors);
-            },
+            [&](sem::Function* func) { return FunctionCall(expr, func, args, arg_behaviors); },
             [&](sem::Variable* var) {
                 auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
                 AddError("cannot call variable '" + name + "'", ident->source);
@@ -1787,7 +1862,7 @@
                 auto name = builder_->Symbols().NameFor(ident->symbol);
                 auto builtin_type = sem::ParseBuiltinType(name);
                 if (builtin_type != sem::BuiltinType::kNone) {
-                    return BuiltinCall(expr, builtin_type, std::move(args));
+                    return BuiltinCall(expr, builtin_type, args);
                 }
 
                 TINT_ICE(Resolver, diagnostics_)
@@ -1805,12 +1880,13 @@
     return validator_.Call(call, current_statement_) ? call : nullptr;
 }
 
+template <size_t N>
 sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr,
                                  sem::BuiltinType builtin_type,
-                                 utils::VectorRef<const sem::Expression*> args) {
+                                 utils::Vector<const sem::Expression*, N>& args) {
     IntrinsicTable::Builtin builtin;
     {
-        auto arg_tys = utils::Transform<8>(args, [](auto* arg) { return arg->Type(); });
+        auto arg_tys = utils::Transform(args, [](auto* arg) { return arg->Type(); });
         builtin = intrinsic_table_->Lookup(builtin_type, arg_tys, expr->source);
         if (!builtin.sem) {
             return nullptr;
@@ -1840,7 +1916,16 @@
     // If the builtin is @const, and all arguments have constant values, evaluate the builtin now.
     const sem::Constant* value = nullptr;
     if (stage == sem::EvaluationStage::kConstant) {
-        value = (const_eval_.*builtin.const_eval_fn)(builtin.sem->ReturnType(), args);
+        auto const_args = ConvertArguments(args, builtin.sem);
+        if (!const_args) {
+            return nullptr;
+        }
+        if (auto r = (const_eval_.*builtin.const_eval_fn)(builtin.sem->ReturnType(),
+                                                          const_args.Get(), expr->source)) {
+            value = r.Get();
+        } else {
+            return nullptr;
+        }
     }
 
     bool has_side_effects =
@@ -1872,9 +1957,8 @@
     return call;
 }
 
-void Resolver::CollectTextureSamplerPairs(
-    const sem::Builtin* builtin,
-    utils::ConstVectorRef<const sem::Expression*> args) const {
+void Resolver::CollectTextureSamplerPairs(const sem::Builtin* builtin,
+                                          utils::VectorRef<const sem::Expression*> args) const {
     // Collect a texture/sampler pair for this builtin.
     const auto& signature = builtin->Signature();
     int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture);
@@ -1892,9 +1976,10 @@
     }
 }
 
+template <size_t N>
 sem::Call* Resolver::FunctionCall(const ast::CallExpression* expr,
                                   sem::Function* target,
-                                  utils::VectorRef<const sem::Expression*> args,
+                                  utils::Vector<const sem::Expression*, N>& args,
                                   sem::Behaviors arg_behaviors) {
     auto sym = expr->target.name->symbol;
     auto name = builder_->Symbols().NameFor(sym);
@@ -1940,9 +2025,8 @@
     return call;
 }
 
-void Resolver::CollectTextureSamplerPairs(
-    sem::Function* func,
-    utils::ConstVectorRef<const sem::Expression*> args) const {
+void Resolver::CollectTextureSamplerPairs(sem::Function* func,
+                                          utils::VectorRef<const sem::Expression*> args) const {
     // Map all texture/sampler pairs from the target function to the
     // current function. These can only be global or parameter
     // variables. Resolve any parameter variables to the corresponding
@@ -2003,7 +2087,12 @@
         return nullptr;
     }
 
-    auto val = const_eval_.Literal(ty, literal);
+    const sem::Constant* val = nullptr;
+    if (auto r = const_eval_.Literal(ty, literal)) {
+        val = r.Get();
+    } else {
+        return nullptr;
+    }
     return builder_->create<sem::Expression>(literal, ty, sem::EvaluationStage::kConstant,
                                              current_statement_, std::move(val),
                                              /* has_side_effects */ false);
@@ -2124,7 +2213,12 @@
             ret = builder_->create<sem::Reference>(ret, ref->StorageClass(), ref->Access());
         }
 
-        auto* val = const_eval_.MemberAccess(object, member);
+        const sem::Constant* val = nullptr;
+        if (auto r = const_eval_.MemberAccess(object, member)) {
+            val = r.Get();
+        } else {
+            return nullptr;
+        }
         return builder_->create<sem::StructMemberAccess>(expr, ret, current_statement_, val, object,
                                                          member, has_side_effects, source_var);
     }
@@ -2192,9 +2286,12 @@
             // the swizzle.
             ret = builder_->create<sem::Vector>(vec->type(), static_cast<uint32_t>(size));
         }
-        auto* val = const_eval_.Swizzle(ret, object, swizzle);
-        return builder_->create<sem::Swizzle>(expr, ret, current_statement_, val, object,
-                                              std::move(swizzle), has_side_effects, source_var);
+        if (auto r = const_eval_.Swizzle(ret, object, swizzle)) {
+            auto* val = r.Get();
+            return builder_->create<sem::Swizzle>(expr, ret, current_statement_, val, object,
+                                                  std::move(swizzle), has_side_effects, source_var);
+        }
+        return nullptr;
     }
 
     AddError("invalid member accessor expression. Expected vector or struct, got '" +
@@ -2208,7 +2305,6 @@
     const auto* rhs = sem_.Get(expr->rhs);
     auto* lhs_ty = lhs->Type()->UnwrapRef();
     auto* rhs_ty = rhs->Type()->UnwrapRef();
-    auto stage = sem::EvaluationStage::kRuntime;  // TODO(crbug.com/tint/1581)
 
     auto op = intrinsic_table_->Lookup(expr->op, lhs_ty, rhs_ty, expr->source, false);
     if (!op.result) {
@@ -2228,8 +2324,26 @@
     }
 
     const sem::Constant* value = nullptr;
-    if (op.const_eval_fn) {
-        value = (const_eval_.*op.const_eval_fn)(op.result, utils::Vector{lhs, rhs});
+    auto stage = sem::EarliestStage(lhs->Stage(), rhs->Stage());
+    if (stage == sem::EvaluationStage::kConstant) {
+        if (op.const_eval_fn) {
+            auto const_args = utils::Vector{lhs->ConstantValue(), rhs->ConstantValue()};
+            // Implicit conversion (e.g. AInt -> AFloat)
+            if (!Convert(const_args[0], op.lhs, lhs->Declaration()->source)) {
+                return nullptr;
+            }
+            if (!Convert(const_args[1], op.rhs, rhs->Declaration()->source)) {
+                return nullptr;
+            }
+
+            if (auto r = (const_eval_.*op.const_eval_fn)(op.result, const_args, expr->source)) {
+                value = r.Get();
+            } else {
+                return nullptr;
+            }
+        } else {
+            stage = sem::EvaluationStage::kRuntime;
+        }
     }
 
     bool has_side_effects = lhs->HasSideEffects() || rhs->HasSideEffects();
@@ -2305,7 +2419,13 @@
             stage = expr->Stage();
             if (stage == sem::EvaluationStage::kConstant) {
                 if (op.const_eval_fn) {
-                    value = (const_eval_.*op.const_eval_fn)(ty, utils::Vector{expr});
+                    if (auto r = (const_eval_.*op.const_eval_fn)(
+                            ty, utils::Vector{expr->ConstantValue()},
+                            expr->Declaration()->source)) {
+                        value = r.Get();
+                    } else {
+                        return nullptr;
+                    }
                 } else {
                     stage = sem::EvaluationStage::kRuntime;
                 }
@@ -2419,7 +2539,7 @@
     return static_cast<uint32_t>(count);
 }
 
-bool Resolver::ArrayAttributes(const ast::AttributeList& attributes,
+bool Resolver::ArrayAttributes(utils::VectorRef<const ast::Attribute*> attributes,
                                const sem::Type* el_ty,
                                uint32_t& explicit_stride) {
     if (!validator_.NoDuplicateAttributes(attributes)) {
@@ -2490,7 +2610,7 @@
     }
 
     sem::StructMemberList sem_members;
-    sem_members.reserve(str->members.size());
+    sem_members.reserve(str->members.Length());
 
     // Calculate the effective size and alignment of each field, and the overall
     // size of the structure.
@@ -2689,8 +2809,8 @@
         utils::Vector<const sem::Type*, 8> types;
         types.Push(cond_ty);
 
-        std::vector<sem::CaseStatement*> cases;
-        cases.reserve(stmt->body.size());
+        utils::Vector<sem::CaseStatement*, 4> cases;
+        cases.Reserve(stmt->body.Length());
         for (auto* case_stmt : stmt->body) {
             Mark(case_stmt);
             auto* c = CaseStatement(case_stmt);
@@ -2700,7 +2820,7 @@
             for (auto* expr : c->Selectors()) {
                 types.Push(expr->Type()->UnwrapRef());
             }
-            cases.emplace_back(c);
+            cases.Push(c);
             behaviors.Add(c->Behaviors());
             sem->Cases().emplace_back(c);
         }
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index fa5a569..4c61c47 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -160,19 +160,6 @@
     /// ProgramBuilder.
     void CreateSemanticNodes() const;
 
-    /// Retrieves information for the requested import.
-    /// @param src the source of the import
-    /// @param path the import path
-    /// @param name the method name to get information on
-    /// @param params the parameters to the method call
-    /// @param id out parameter for the external call ID. Must not be a nullptr.
-    /// @returns the return type of `name` in `path` or nullptr on error.
-    sem::Type* GetImportData(const Source& src,
-                             const std::string& path,
-                             const std::string& name,
-                             const ast::ExpressionList& params,
-                             uint32_t* id);
-
     /// Expression traverses the graph of expressions starting at `expr`, building a postordered
     /// list (leaf-first) of all the expression nodes. Each of the expressions are then resolved by
     /// dispatching to the appropriate expression handlers below.
@@ -193,14 +180,16 @@
     sem::Expression* Bitcast(const ast::BitcastExpression*);
     sem::Call* Call(const ast::CallExpression*);
     sem::Function* Function(const ast::Function*);
+    template <size_t N>
     sem::Call* FunctionCall(const ast::CallExpression*,
                             sem::Function* target,
-                            utils::VectorRef<const sem::Expression*> args,
+                            utils::Vector<const sem::Expression*, N>& args,
                             sem::Behaviors arg_behaviors);
     sem::Expression* Identifier(const ast::IdentifierExpression*);
+    template <size_t N>
     sem::Call* BuiltinCall(const ast::CallExpression*,
                            sem::BuiltinType,
-                           utils::VectorRef<const sem::Expression*> args);
+                           utils::Vector<const sem::Expression*, N>& args);
     sem::Expression* Literal(const ast::LiteralExpression*);
     sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*);
     sem::Expression* UnaryOp(const ast::UnaryOpExpression*);
@@ -223,13 +212,26 @@
 
     /// Materializes all the arguments in `args` to the parameter types of `target`.
     /// @returns true on success, false on failure.
-    bool MaterializeArguments(utils::VectorRef<const sem::Expression*> args,
+    template <size_t N>
+    bool MaterializeArguments(utils::Vector<const sem::Expression*, N>& args,
                               const sem::CallTarget* target);
 
     /// @returns true if an argument of an abstract numeric type, passed to a parameter of type
     /// `parameter_ty` should be materialized.
     bool ShouldMaterializeArgument(const sem::Type* parameter_ty) const;
 
+    /// Converts `c` to `target_ty`
+    /// @returns true on success, false on failure.
+    bool Convert(const sem::Constant*& c, const sem::Type* target_ty, const Source& source);
+
+    /// Transforms `args` to a vector of constants, and converts each constant to the call target's
+    /// parameter type.
+    /// @returns the vector of constants, `utils::Failure` on failure.
+    template <size_t N>
+    utils::Result<utils::Vector<const sem::Constant*, N>> ConvertArguments(
+        const utils::Vector<const sem::Expression*, N>& args,
+        const sem::CallTarget* target);
+
     /// @param ty the type that may hold abstract numeric types
     /// @param target_ty the target type for the expression (variable type, parameter type, etc).
     ///        May be nullptr.
@@ -260,16 +262,17 @@
     sem::LoopStatement* LoopStatement(const ast::LoopStatement*);
     sem::Statement* ReturnStatement(const ast::ReturnStatement*);
     sem::Statement* Statement(const ast::Statement*);
+    sem::Statement* StaticAssert(const ast::StaticAssert*);
     sem::SwitchStatement* SwitchStatement(const ast::SwitchStatement* s);
     sem::Statement* VariableDeclStatement(const ast::VariableDeclStatement*);
-    bool Statements(const ast::StatementList&);
+    bool Statements(utils::VectorRef<const ast::Statement*>);
 
     // CollectTextureSamplerPairs() collects all the texture/sampler pairs from the target function
     // / builtin, and records these on the current function by calling AddTextureSamplerPair().
     void CollectTextureSamplerPairs(sem::Function* func,
-                                    utils::ConstVectorRef<const sem::Expression*> args) const;
+                                    utils::VectorRef<const sem::Expression*> args) const;
     void CollectTextureSamplerPairs(const sem::Builtin* builtin,
-                                    utils::ConstVectorRef<const sem::Expression*> args) const;
+                                    utils::VectorRef<const sem::Expression*> args) const;
 
     /// Resolves the WorkgroupSize for the given function, assigning it to
     /// current_function_
@@ -306,7 +309,7 @@
     /// @param el_ty the element type of the array.
     /// @param explicit_stride assigned the specified stride of the array in bytes.
     /// @returns true on success, false on failure
-    bool ArrayAttributes(const ast::AttributeList& attributes,
+    bool ArrayAttributes(utils::VectorRef<const ast::Attribute*> attributes,
                          const sem::Type* el_ty,
                          uint32_t& explicit_stride);
 
diff --git a/src/tint/resolver/resolver_behavior_test.cc b/src/tint/resolver/resolver_behavior_test.cc
index b125e39..43f0bf1 100644
--- a/src/tint/resolver/resolver_behavior_test.cc
+++ b/src/tint/resolver/resolver_behavior_test.cc
@@ -33,8 +33,8 @@
         // Create a function called 'DiscardOrNext' which returns an i32, and has
         // the behavior of {Discard, Return}, which when called, will have the
         // behavior {Discard, Next}.
-        Func("DiscardOrNext", {}, ty.i32(),
-             {
+        Func("DiscardOrNext", utils::Empty, ty.i32(),
+             utils::Vector{
                  If(true, Block(Discard())),
                  Return(1_i),
              });
@@ -72,8 +72,8 @@
 }
 
 TEST_F(ResolverBehaviorTest, ExprIndex_Arr) {
-    Func("ArrayDiscardOrNext", {}, ty.array<i32, 4>(),
-         {
+    Func("ArrayDiscardOrNext", utils::Empty, ty.array<i32, 4>(),
+         utils::Vector{
              If(true, Block(Discard())),
              Return(Construct(ty.array<i32, 4>())),
          });
@@ -165,7 +165,7 @@
 }
 
 TEST_F(ResolverBehaviorTest, StmtCallReturn) {
-    Func("f", {}, ty.void_(), {Return()});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Return()});
     auto* stmt = CallStmt(Call("f"));
     WrapInFunction(stmt);
 
@@ -176,7 +176,7 @@
 }
 
 TEST_F(ResolverBehaviorTest, StmtCallFuncDiscard) {
-    Func("f", {}, ty.void_(), {Discard()});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Discard()});
     auto* stmt = CallStmt(Call("f"));
     WrapInFunction(stmt);
 
@@ -522,7 +522,7 @@
 
 TEST_F(ResolverBehaviorTest, StmtReturn_DiscardOrNext) {
     auto* stmt = Return(Call("DiscardOrNext"));
-    Func("F", {}, ty.i32(), {stmt});
+    Func("F", utils::Empty, ty.i32(), utils::Vector{stmt});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
diff --git a/src/tint/resolver/resolver_test.cc b/src/tint/resolver/resolver_test.cc
index bacbcd6..ecc251a 100644
--- a/src/tint/resolver/resolver_test.cc
+++ b/src/tint/resolver/resolver_test.cc
@@ -233,7 +233,7 @@
     auto* cond = Expr(2_i);
 
     auto* ret = Return(cond);
-    Func("test", {}, ty.i32(), {ret}, {});
+    Func("test", utils::Empty, ty.i32(), utils::Vector{ret}, utils::Empty);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -270,8 +270,8 @@
 }
 
 TEST_F(ResolverTest, Stmt_Call) {
-    Func("my_func", {}, ty.void_(),
-         {
+    Func("my_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(),
          });
 
@@ -357,7 +357,7 @@
     auto* bar_f32_init = bar_f32->constructor;
     auto* bar_f32_decl = Decl(bar_f32);
 
-    Func("func", {}, ty.void_(), {inner, foo_f32_decl, bar_f32_decl});
+    Func("func", utils::Empty, ty.void_(), utils::Vector{inner, foo_f32_decl, bar_f32_decl});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     ASSERT_NE(TypeOf(foo_i32_init), nullptr);
@@ -372,8 +372,8 @@
     EXPECT_EQ(StmtOf(bar_i32_init), bar_i32_decl);
     EXPECT_EQ(StmtOf(foo_f32_init), foo_f32_decl);
     EXPECT_EQ(StmtOf(bar_f32_init), bar_f32_decl);
-    EXPECT_TRUE(CheckVarUsers(foo_i32, {bar_i32->constructor}));
-    EXPECT_TRUE(CheckVarUsers(foo_f32, {bar_f32->constructor}));
+    EXPECT_TRUE(CheckVarUsers(foo_i32, utils::Vector{bar_i32->constructor}));
+    EXPECT_TRUE(CheckVarUsers(foo_f32, utils::Vector{bar_f32->constructor}));
     ASSERT_NE(VarOf(bar_i32->constructor), nullptr);
     EXPECT_EQ(VarOf(bar_i32->constructor)->Declaration(), foo_i32);
     ASSERT_NE(VarOf(bar_f32->constructor), nullptr);
@@ -393,7 +393,7 @@
     auto* fn_i32 = Var("foo", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
     auto* fn_i32_init = fn_i32->constructor;
     auto* fn_i32_decl = Decl(fn_i32);
-    Func("func_i32", {}, ty.void_(), {fn_i32_decl});
+    Func("func_i32", utils::Empty, ty.void_(), utils::Vector{fn_i32_decl});
 
     // Declare f32 "foo" at module scope
     auto* mod_f32 = Var("foo", ty.f32(), ast::StorageClass::kPrivate, Expr(2_f));
@@ -404,7 +404,7 @@
     auto* fn_f32 = Var("bar", ty.f32(), ast::StorageClass::kNone, Expr("foo"));
     auto* fn_f32_init = fn_f32->constructor;
     auto* fn_f32_decl = Decl(fn_f32);
-    Func("func_f32", {}, ty.void_(), {fn_f32_decl});
+    Func("func_f32", utils::Empty, ty.void_(), utils::Vector{fn_f32_decl});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     ASSERT_NE(TypeOf(mod_init), nullptr);
@@ -416,8 +416,8 @@
     EXPECT_EQ(StmtOf(fn_i32_init), fn_i32_decl);
     EXPECT_EQ(StmtOf(mod_init), nullptr);
     EXPECT_EQ(StmtOf(fn_f32_init), fn_f32_decl);
-    EXPECT_TRUE(CheckVarUsers(fn_i32, {}));
-    EXPECT_TRUE(CheckVarUsers(mod_f32, {fn_f32->constructor}));
+    EXPECT_TRUE(CheckVarUsers(fn_i32, utils::Empty));
+    EXPECT_TRUE(CheckVarUsers(mod_f32, utils::Vector{fn_f32->constructor}));
     ASSERT_NE(VarOf(fn_f32->constructor), nullptr);
     EXPECT_EQ(VarOf(fn_f32->constructor)->Declaration(), mod_f32);
 }
@@ -491,7 +491,7 @@
 }
 
 TEST_F(ResolverTest, Expr_Call) {
-    Func("my_func", {}, ty.f32(), {Return(0_f)});
+    Func("my_func", utils::Empty, ty.f32(), utils::Vector{Return(0_f)});
 
     auto* call = Call("my_func");
     WrapInFunction(call);
@@ -503,7 +503,7 @@
 }
 
 TEST_F(ResolverTest, Expr_Call_InBinaryOp) {
-    Func("func", {}, ty.f32(), {Return(0_f)});
+    Func("func", utils::Empty, ty.f32(), utils::Vector{Return(0_f)});
 
     auto* expr = Add(Call("func"), Call("func"));
     WrapInFunction(expr);
@@ -515,8 +515,8 @@
 }
 
 TEST_F(ResolverTest, Expr_Call_WithParams) {
-    Func("my_func", {Param(Sym(), ty.f32())}, ty.f32(),
-         {
+    Func("my_func", utils::Vector{Param(Sym(), ty.f32())}, ty.f32(),
+         utils::Vector{
              Return(1.2_f),
          });
 
@@ -610,7 +610,7 @@
     ASSERT_NE(TypeOf(ident), nullptr);
     ASSERT_TRUE(TypeOf(ident)->Is<sem::Reference>());
     EXPECT_TRUE(TypeOf(ident)->UnwrapRef()->Is<sem::F32>());
-    EXPECT_TRUE(CheckVarUsers(my_var, {ident}));
+    EXPECT_TRUE(CheckVarUsers(my_var, utils::Vector{ident}));
     ASSERT_NE(VarOf(ident), nullptr);
     EXPECT_EQ(VarOf(ident)->Declaration(), my_var);
 }
@@ -625,7 +625,7 @@
 
     ASSERT_NE(TypeOf(ident), nullptr);
     EXPECT_TRUE(TypeOf(ident)->Is<sem::F32>());
-    EXPECT_TRUE(CheckVarUsers(my_var, {ident}));
+    EXPECT_TRUE(CheckVarUsers(my_var, utils::Vector{ident}));
     ASSERT_NE(VarOf(ident), nullptr);
     EXPECT_EQ(VarOf(ident)->Declaration(), my_var);
 }
@@ -635,8 +635,8 @@
     auto* var = Let("my_var", ty.f32(), Construct(ty.f32()));
     auto* decl = Decl(Var("b", ty.f32(), ast::StorageClass::kNone, my_var_a));
 
-    Func("my_func", {}, ty.void_(),
-         {
+    Func("my_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              decl,
          });
@@ -646,7 +646,7 @@
     ASSERT_NE(TypeOf(my_var_a), nullptr);
     EXPECT_TRUE(TypeOf(my_var_a)->Is<sem::F32>());
     EXPECT_EQ(StmtOf(my_var_a), decl);
-    EXPECT_TRUE(CheckVarUsers(var, {my_var_a}));
+    EXPECT_TRUE(CheckVarUsers(var, utils::Vector{my_var_a}));
     ASSERT_NE(VarOf(my_var_a), nullptr);
     EXPECT_EQ(VarOf(my_var_a)->Declaration(), var);
 }
@@ -658,8 +658,8 @@
     auto* a = Var("a", ty.array<bool, 10>(), array<bool, 10>());
     auto* idx = Var("idx", ty.f32(), Construct(ty.f32()));
     auto* f = Var("f", ty.f32(), IndexAccessor("a", Expr(Source{{12, 34}}, idx)));
-    Func("my_func", {}, ty.void_(),
-         {
+    Func("my_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(a),
              Decl(idx),
              Decl(f),
@@ -676,8 +676,8 @@
 
     auto* var = Var("my_var", ty.f32());
 
-    Func("my_func", {}, ty.void_(),
-         {
+    Func("my_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              assign,
          });
@@ -692,7 +692,7 @@
     ASSERT_TRUE(TypeOf(my_var_b)->Is<sem::Reference>());
     EXPECT_TRUE(TypeOf(my_var_b)->UnwrapRef()->Is<sem::F32>());
     EXPECT_EQ(StmtOf(my_var_b), assign);
-    EXPECT_TRUE(CheckVarUsers(var, {my_var_a, my_var_b}));
+    EXPECT_TRUE(CheckVarUsers(var, utils::Vector{my_var_a, my_var_b}));
     ASSERT_NE(VarOf(my_var_a), nullptr);
     EXPECT_EQ(VarOf(my_var_a)->Declaration(), var);
     ASSERT_NE(VarOf(my_var_b), nullptr);
@@ -705,8 +705,8 @@
     auto* v_decl = Decl(Var("v", ty.f32()));
     auto* p_decl = Decl(Let("p", ty.pointer<f32>(ast::StorageClass::kFunction), AddressOf(v)));
     auto* assign = Assign(Deref(p), 1.23_f);
-    Func("my_func", {}, ty.void_(),
-         {
+    Func("my_func", utils::Empty, ty.void_(),
+         utils::Vector{
              v_decl,
              p_decl,
              assign,
@@ -725,8 +725,8 @@
 }
 
 TEST_F(ResolverTest, Expr_Call_Function) {
-    Func("my_func", {}, ty.f32(),
-         {
+    Func("my_func", utils::Empty, ty.f32(),
+         utils::Vector{
              Return(0_f),
          });
 
@@ -752,12 +752,12 @@
     auto* param_c = Param("c", ty.u32());
 
     auto* func = Func("my_func",
-                      {
+                      utils::Vector{
                           param_a,
                           param_b,
                           param_c,
                       },
-                      ty.void_(), {});
+                      ty.void_(), utils::Empty);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -774,19 +774,19 @@
 }
 
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
-    auto* s = Structure("S", {Member("m", ty.u32())});
+    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,
-                  ast::AttributeList{
+                  utils::Vector{
                       create<ast::BindingAttribute>(0u),
                       create<ast::GroupAttribute>(0u),
                   });
     auto* wg_var = GlobalVar("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
     auto* priv_var = GlobalVar("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
-    auto* func = Func("my_func", {}, ty.void_(),
-                      {
+    auto* func = Func("my_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign("wg_var", "wg_var"),
                           Assign("sb_var", "sb_var"),
                           Assign("priv_var", "priv_var"),
@@ -807,26 +807,26 @@
 }
 
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) {
-    auto* s = Structure("S", {Member("m", ty.u32())});
+    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,
-                  ast::AttributeList{
+                  utils::Vector{
                       create<ast::BindingAttribute>(0u),
                       create<ast::GroupAttribute>(0u),
                   });
     auto* wg_var = GlobalVar("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
     auto* priv_var = GlobalVar("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
-    Func("my_func", {}, ty.f32(),
-         {Assign("wg_var", "wg_var"), Assign("sb_var", "sb_var"), Assign("priv_var", "priv_var"),
-          Return(0_f)});
+    Func("my_func", utils::Empty, ty.f32(),
+         utils::Vector{Assign("wg_var", "wg_var"), Assign("sb_var", "sb_var"),
+                       Assign("priv_var", "priv_var"), Return(0_f)});
 
-    auto* func2 = Func("func", {}, ty.void_(),
-                       {
+    auto* func2 = Func("func", utils::Empty, ty.void_(),
+                       utils::Vector{
                            WrapInStatement(Call("my_func")),
                        },
-                       ast::AttributeList{});
+                       utils::Empty);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -842,8 +842,8 @@
 }
 
 TEST_F(ResolverTest, Function_NotRegisterFunctionVariable) {
-    auto* func = Func("my_func", {}, ty.void_(),
-                      {
+    auto* func = Func("my_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(Var("var", ty.f32())),
                           Assign("var", 1_f),
                       });
@@ -858,8 +858,8 @@
 }
 
 TEST_F(ResolverTest, Function_NotRegisterFunctionConstant) {
-    auto* func = Func("my_func", {}, ty.void_(),
-                      {
+    auto* func = Func("my_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(Let("var", ty.f32(), Construct(ty.f32()))),
                       });
 
@@ -873,7 +873,7 @@
 }
 
 TEST_F(ResolverTest, Function_NotRegisterFunctionParams) {
-    auto* func = Func("my_func", {Param("var", ty.f32())}, ty.void_(), {});
+    auto* func = Func("my_func", utils::Vector{Param("var", ty.f32())}, ty.void_(), utils::Empty);
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     auto* func_sem = Sem().Get(func);
@@ -884,12 +884,12 @@
 }
 
 TEST_F(ResolverTest, Function_CallSites) {
-    auto* foo = Func("foo", {}, ty.void_(), {});
+    auto* foo = Func("foo", utils::Empty, ty.void_(), utils::Empty);
 
     auto* call_1 = Call("foo");
     auto* call_2 = Call("foo");
-    auto* bar = Func("bar", {}, ty.void_(),
-                     {
+    auto* bar = Func("bar", utils::Empty, ty.void_(),
+                     utils::Vector{
                          CallStmt(call_1),
                          CallStmt(call_2),
                      });
@@ -910,7 +910,7 @@
 TEST_F(ResolverTest, Function_WorkgroupSize_NotSet) {
     // @compute @workgroup_size(1)
     // fn main() {}
-    auto* func = Func("main", {}, ty.void_(), {});
+    auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -928,8 +928,8 @@
 TEST_F(ResolverTest, Function_WorkgroupSize_Literals) {
     // @compute @workgroup_size(8, 2, 3)
     // fn main() {}
-    auto* func = Func("main", {}, ty.void_(), {},
-                      {
+    auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{
                           Stage(ast::PipelineStage::kCompute),
                           WorkgroupSize(8_i, 2_i, 3_i),
                       });
@@ -956,8 +956,8 @@
     GlobalConst("width", ty.i32(), Expr(16_i));
     GlobalConst("height", ty.i32(), Expr(8_i));
     GlobalConst("depth", ty.i32(), Expr(2_i));
-    auto* func = Func("main", {}, ty.void_(), {},
-                      {
+    auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{
                           Stage(ast::PipelineStage::kCompute),
                           WorkgroupSize("width", "height", "depth"),
                       });
@@ -984,8 +984,8 @@
                 Construct(ty.i32(), Construct(ty.i32(), Construct(ty.i32(), 8_i))));
     GlobalConst("height", ty.i32(),
                 Construct(ty.i32(), Construct(ty.i32(), Construct(ty.i32(), 4_i))));
-    auto* func = Func("main", {}, ty.void_(), {},
-                      {
+    auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{
                           Stage(ast::PipelineStage::kCompute),
                           WorkgroupSize("width", "height"),
                       });
@@ -1009,11 +1009,11 @@
     // @id(2) override depth = 2i;
     // @compute @workgroup_size(width, height, depth)
     // fn main() {}
-    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", {}, ty.void_(), {},
-                      {
+    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* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{
                           Stage(ast::PipelineStage::kCompute),
                           WorkgroupSize("width", "height", "depth"),
                       });
@@ -1037,11 +1037,11 @@
     // @id(2) override depth : i32;
     // @compute @workgroup_size(width, height, depth)
     // fn main() {}
-    auto* width = Override("width", ty.i32(), nullptr, {Id(0)});
-    auto* height = Override("height", ty.i32(), nullptr, {Id(1)});
-    auto* depth = Override("depth", ty.i32(), nullptr, {Id(2)});
-    auto* func = Func("main", {}, ty.void_(), {},
-                      {
+    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* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{
                           Stage(ast::PipelineStage::kCompute),
                           WorkgroupSize("width", "height", "depth"),
                       });
@@ -1064,10 +1064,10 @@
     // const depth = 3i;
     // @compute @workgroup_size(8, height, depth)
     // fn main() {}
-    auto* height = Override("height", ty.i32(), Expr(2_i), {Id(0)});
+    auto* height = Override("height", ty.i32(), Expr(2_i), utils::Vector{Id(0)});
     GlobalConst("depth", ty.i32(), Expr(3_i));
-    auto* func = Func("main", {}, ty.void_(), {},
-                      {
+    auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{
                           Stage(ast::PipelineStage::kCompute),
                           WorkgroupSize(8_i, "height", "depth"),
                       });
@@ -1086,8 +1086,8 @@
 }
 
 TEST_F(ResolverTest, Expr_MemberAccessor_Struct) {
-    auto* st =
-        Structure("S", {Member("first_member", ty.i32()), Member("second_member", ty.f32())});
+    auto* st = Structure(
+        "S", utils::Vector{Member("first_member", ty.i32()), Member("second_member", ty.f32())});
     GlobalVar("my_struct", ty.Of(st), ast::StorageClass::kPrivate);
 
     auto* mem = MemberAccessor("my_struct", "second_member");
@@ -1109,8 +1109,8 @@
 }
 
 TEST_F(ResolverTest, Expr_MemberAccessor_Struct_Alias) {
-    auto* st =
-        Structure("S", {Member("first_member", ty.i32()), Member("second_member", ty.f32())});
+    auto* st = Structure(
+        "S", utils::Vector{Member("first_member", ty.i32()), Member("second_member", ty.f32())});
     auto* alias = Alias("alias", ty.Of(st));
     GlobalVar("my_struct", ty.Of(alias), ast::StorageClass::kPrivate);
 
@@ -1184,8 +1184,8 @@
     // }
     //
 
-    auto* stB = Structure("B", {Member("foo", ty.vec4<f32>())});
-    auto* stA = Structure("A", {Member("mem", ty.array(ty.Of(stB), 3_i))});
+    auto* stB = Structure("B", utils::Vector{Member("foo", ty.vec4<f32>())});
+    auto* stA = Structure("A", utils::Vector{Member("mem", ty.array(ty.Of(stB), 3_i))});
     GlobalVar("c", ty.Of(stA), ast::StorageClass::kPrivate);
 
     auto* mem =
@@ -1202,8 +1202,8 @@
 }
 
 TEST_F(ResolverTest, Expr_MemberAccessor_InBinaryOp) {
-    auto* st =
-        Structure("S", {Member("first_member", ty.f32()), Member("second_member", ty.f32())});
+    auto* st = Structure(
+        "S", utils::Vector{Member("first_member", ty.f32()), Member("second_member", ty.f32())});
     GlobalVar("my_struct", ty.Of(st), ast::StorageClass::kPrivate);
 
     auto* expr = Add(MemberAccessor("my_struct", "first_member"),
@@ -1726,7 +1726,7 @@
     auto* var = Var("var", ty.i32());
 
     auto* stmt = Decl(var);
-    Func("func", {}, ty.void_(), {stmt});
+    Func("func", utils::Empty, ty.void_(), utils::Vector{stmt});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1736,7 +1736,7 @@
 TEST_F(ResolverTest, StorageClass_SetForSampler) {
     auto* t = ty.sampler(ast::SamplerKind::kSampler);
     auto* var = GlobalVar("var", t,
-                          ast::AttributeList{
+                          utils::Vector{
                               create<ast::BindingAttribute>(0u),
                               create<ast::GroupAttribute>(0u),
                           });
@@ -1749,7 +1749,7 @@
 TEST_F(ResolverTest, StorageClass_SetForTexture) {
     auto* t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
     auto* var = GlobalVar("var", t,
-                          ast::AttributeList{
+                          utils::Vector{
                               create<ast::BindingAttribute>(0u),
                               create<ast::GroupAttribute>(0u),
                           });
@@ -1762,7 +1762,7 @@
 TEST_F(ResolverTest, StorageClass_DoesNotSetOnConst) {
     auto* var = Let("var", ty.i32(), Construct(ty.i32()));
     auto* stmt = Decl(var);
-    Func("func", {}, ty.void_(), {stmt});
+    Func("func", utils::Empty, ty.void_(), utils::Vector{stmt});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1772,9 +1772,9 @@
 TEST_F(ResolverTest, Access_SetForStorageBuffer) {
     // struct S { x : i32 };
     // var<storage> g : S;
-    auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
+    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,
-                          ast::AttributeList{
+                          utils::Vector{
                               create<ast::BindingAttribute>(0u),
                               create<ast::GroupAttribute>(0u),
                           });
@@ -1789,10 +1789,10 @@
     // @group(3) @binding(4) var s2 : sampler;
     auto* s1 = GlobalVar(
         Sym(), ty.sampler(ast::SamplerKind::kSampler),
-        ast::AttributeList{create<ast::GroupAttribute>(1u), create<ast::BindingAttribute>(2u)});
+        utils::Vector{create<ast::GroupAttribute>(1u), create<ast::BindingAttribute>(2u)});
     auto* s2 = GlobalVar(
         Sym(), ty.sampler(ast::SamplerKind::kSampler),
-        ast::AttributeList{create<ast::GroupAttribute>(3u), create<ast::BindingAttribute>(4u)});
+        utils::Vector{create<ast::GroupAttribute>(3u), create<ast::BindingAttribute>(4u)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1819,37 +1819,37 @@
     GlobalVar("call_b", ty.f32(), ast::StorageClass::kPrivate);
     GlobalVar("call_c", ty.f32(), ast::StorageClass::kPrivate);
 
-    auto* func_b = Func("b", {}, ty.f32(),
-                        {
+    auto* func_b = Func("b", utils::Empty, ty.f32(),
+                        utils::Vector{
                             Return(0_f),
                         });
-    auto* func_c = Func("c", {}, ty.f32(),
-                        {
+    auto* func_c = Func("c", utils::Empty, ty.f32(),
+                        utils::Vector{
                             Assign("second", Call("b")),
                             Return(0_f),
                         });
 
-    auto* func_a = Func("a", {}, ty.f32(),
-                        {
+    auto* func_a = Func("a", utils::Empty, ty.f32(),
+                        utils::Vector{
                             Assign("first", Call("c")),
                             Return(0_f),
                         });
 
-    auto* ep_1 = Func("ep_1", {}, ty.void_(),
-                      {
+    auto* ep_1 = Func("ep_1", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign("call_a", Call("a")),
                           Assign("call_b", Call("b")),
                       },
-                      {
+                      utils::Vector{
                           Stage(ast::PipelineStage::kCompute),
                           WorkgroupSize(1_i),
                       });
 
-    auto* ep_2 = Func("ep_2", {}, ty.void_(),
-                      {
+    auto* ep_2 = Func("ep_2", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign("call_c", Call("c")),
                       },
-                      {
+                      utils::Vector{
                           Stage(ast::PipelineStage::kCompute),
                           WorkgroupSize(1_i),
                       });
@@ -1906,38 +1906,38 @@
     auto fn_a = [](int level) { return "l" + std::to_string(level + 1) + "a"; };
     auto fn_b = [](int level) { return "l" + std::to_string(level + 1) + "b"; };
 
-    Func(fn_a(levels), {}, ty.void_(), {});
-    Func(fn_b(levels), {}, ty.void_(), {});
+    Func(fn_a(levels), utils::Empty, ty.void_(), utils::Empty);
+    Func(fn_b(levels), utils::Empty, ty.void_(), utils::Empty);
 
     for (int i = levels - 1; i >= 0; i--) {
-        Func(fn_a(i), {}, ty.void_(),
-             {
+        Func(fn_a(i), utils::Empty, ty.void_(),
+             utils::Vector{
                  CallStmt(Call(fn_a(i + 1))),
                  CallStmt(Call(fn_b(i + 1))),
              },
-             {});
-        Func(fn_b(i), {}, ty.void_(),
-             {
+             utils::Empty);
+        Func(fn_b(i), utils::Empty, ty.void_(),
+             utils::Vector{
                  CallStmt(Call(fn_a(i + 1))),
                  CallStmt(Call(fn_b(i + 1))),
              },
-             {});
+             utils::Empty);
     }
 
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              CallStmt(Call(fn_a(0))),
              CallStmt(Call(fn_b(0))),
          },
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
+         utils::Vector{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
 // Test for crbug.com/tint/728
 TEST_F(ResolverTest, ASTNodesAreReached) {
-    Structure("A", {Member("x", ty.array<f32, 4>(4))});
-    Structure("B", {Member("x", ty.array<f32, 4>(4))});
+    Structure("A", utils::Vector{Member("x", ty.array<f32, 4>(4))});
+    Structure("B", utils::Vector{Member("x", ty.array<f32, 4>(4))});
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
@@ -1999,8 +1999,8 @@
     GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
 
     auto* call = CallStmt(Call("textureSample", "t", "s", vec2<f32>(1_f, 2_f)));
-    const ast::Function* f =
-        Func("test_function", {}, ty.void_(), {call}, {Stage(ast::PipelineStage::kFragment)});
+    const ast::Function* f = Func("test_function", utils::Empty, ty.void_(), utils::Vector{call},
+                                  utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -2016,10 +2016,12 @@
     GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
 
     auto* inner_call = CallStmt(Call("textureSample", "t", "s", vec2<f32>(1_f, 2_f)));
-    const ast::Function* inner_func = Func("inner_func", {}, ty.void_(), {inner_call});
+    const ast::Function* inner_func =
+        Func("inner_func", utils::Empty, ty.void_(), utils::Vector{inner_call});
     auto* outer_call = CallStmt(Call("inner_func"));
     const ast::Function* outer_func =
-        Func("outer_func", {}, ty.void_(), {outer_call}, {Stage(ast::PipelineStage::kFragment)});
+        Func("outer_func", utils::Empty, ty.void_(), utils::Vector{outer_call},
+             utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -2039,14 +2041,16 @@
     GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
 
     auto* inner_call_1 = CallStmt(Call("textureSample", "t", "s", vec2<f32>(1_f, 2_f)));
-    const ast::Function* inner_func_1 = Func("inner_func_1", {}, ty.void_(), {inner_call_1});
+    const ast::Function* inner_func_1 =
+        Func("inner_func_1", utils::Empty, ty.void_(), utils::Vector{inner_call_1});
     auto* inner_call_2 = CallStmt(Call("textureSample", "t", "s", vec2<f32>(3_f, 4_f)));
-    const ast::Function* inner_func_2 = Func("inner_func_2", {}, ty.void_(), {inner_call_2});
+    const ast::Function* inner_func_2 =
+        Func("inner_func_2", utils::Empty, ty.void_(), utils::Vector{inner_call_2});
     auto* outer_call_1 = CallStmt(Call("inner_func_1"));
     auto* outer_call_2 = CallStmt(Call("inner_func_2"));
     const ast::Function* outer_func =
-        Func("outer_func", {}, ty.void_(), {outer_call_1, outer_call_2},
-             {Stage(ast::PipelineStage::kFragment)});
+        Func("outer_func", utils::Empty, ty.void_(), utils::Vector{outer_call_1, outer_call_2},
+             utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -2074,14 +2078,16 @@
     GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 3));
 
     auto* inner_call_1 = CallStmt(Call("textureSample", "t1", "s", vec2<f32>(1_f, 2_f)));
-    const ast::Function* inner_func_1 = Func("inner_func_1", {}, ty.void_(), {inner_call_1});
+    const ast::Function* inner_func_1 =
+        Func("inner_func_1", utils::Empty, ty.void_(), utils::Vector{inner_call_1});
     auto* inner_call_2 = CallStmt(Call("textureSample", "t2", "s", vec2<f32>(3_f, 4_f)));
-    const ast::Function* inner_func_2 = Func("inner_func_2", {}, ty.void_(), {inner_call_2});
+    const ast::Function* inner_func_2 =
+        Func("inner_func_2", utils::Empty, ty.void_(), utils::Vector{inner_call_2});
     auto* outer_call_1 = CallStmt(Call("inner_func_1"));
     auto* outer_call_2 = CallStmt(Call("inner_func_2"));
     const ast::Function* outer_func =
-        Func("outer_func", {}, ty.void_(), {outer_call_1, outer_call_2},
-             {Stage(ast::PipelineStage::kFragment)});
+        Func("outer_func", utils::Empty, ty.void_(), utils::Vector{outer_call_1, outer_call_2},
+             utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -2119,18 +2125,18 @@
 }
 
 TEST_F(ResolverTest, ModuleDependencyOrderedDeclarations) {
-    auto* f0 = Func("f0", {}, ty.void_(), {});
+    auto* f0 = Func("f0", utils::Empty, ty.void_(), utils::Empty);
     auto* v0 = GlobalVar("v0", ty.i32(), ast::StorageClass::kPrivate);
     auto* a0 = Alias("a0", ty.i32());
-    auto* s0 = Structure("s0", {Member("m", ty.i32())});
-    auto* f1 = Func("f1", {}, ty.void_(), {});
+    auto* s0 = Structure("s0", utils::Vector{Member("m", ty.i32())});
+    auto* f1 = Func("f1", utils::Empty, ty.void_(), utils::Empty);
     auto* v1 = GlobalVar("v1", ty.i32(), ast::StorageClass::kPrivate);
     auto* a1 = Alias("a1", ty.i32());
-    auto* s1 = Structure("s1", {Member("m", ty.i32())});
-    auto* f2 = Func("f2", {}, ty.void_(), {});
+    auto* s1 = Structure("s1", utils::Vector{Member("m", ty.i32())});
+    auto* f2 = Func("f2", utils::Empty, ty.void_(), utils::Empty);
     auto* v2 = GlobalVar("v2", ty.i32(), ast::StorageClass::kPrivate);
     auto* a2 = Alias("a2", ty.i32());
-    auto* s2 = Structure("s2", {Member("m", ty.i32())});
+    auto* s2 = Structure("s2", utils::Vector{Member("m", ty.i32())});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
diff --git a/src/tint/resolver/resolver_test_helper.h b/src/tint/resolver/resolver_test_helper.h
index efb215b..bab1d84 100644
--- a/src/tint/resolver/resolver_test_helper.h
+++ b/src/tint/resolver/resolver_test_helper.h
@@ -18,7 +18,6 @@
 #include <functional>
 #include <memory>
 #include <string>
-#include <vector>
 
 #include "gtest/gtest.h"
 #include "src/tint/program_builder.h"
@@ -28,6 +27,7 @@
 #include "src/tint/sem/expression.h"
 #include "src/tint/sem/statement.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/utils/vector.h"
 
 namespace tint::resolver {
 
@@ -88,9 +88,9 @@
     /// @param expected_users the expected users of the variable
     /// @return true if all users are as expected
     bool CheckVarUsers(const ast::Variable* var,
-                       std::vector<const ast::Expression*>&& expected_users) {
+                       utils::VectorRef<const ast::Expression*> expected_users) {
         auto& var_users = Sem().Get(var)->Users();
-        if (var_users.size() != expected_users.size()) {
+        if (var_users.size() != expected_users.Length()) {
             return false;
         }
         for (size_t i = 0; i < var_users.size(); i++) {
@@ -389,10 +389,10 @@
     /// @param b the ProgramBuilder
     /// @param elem_value the value each element will be initialized with
     /// @return the list of expressions that are used to construct the vector
-    static inline ast::ExpressionList ExprArgs(ProgramBuilder& b, double elem_value) {
-        ast::ExpressionList args;
+    static inline auto ExprArgs(ProgramBuilder& b, double elem_value) {
+        utils::Vector<const ast::Expression*, N> args;
         for (uint32_t i = 0; i < N; i++) {
-            args.emplace_back(DataType<T>::Expr(b, elem_value));
+            args.Push(DataType<T>::Expr(b, elem_value));
         }
         return args;
     }
@@ -433,10 +433,10 @@
     /// @param b the ProgramBuilder
     /// @param elem_value the value each element will be initialized with
     /// @return the list of expressions that are used to construct the matrix
-    static inline ast::ExpressionList ExprArgs(ProgramBuilder& b, double elem_value) {
-        ast::ExpressionList args;
+    static inline auto ExprArgs(ProgramBuilder& b, double elem_value) {
+        utils::Vector<const ast::Expression*, N> args;
         for (uint32_t i = 0; i < N; i++) {
-            args.emplace_back(DataType<vec<M, T>>::Expr(b, elem_value));
+            args.Push(DataType<vec<M, T>>::Expr(b, elem_value));
         }
         return args;
     }
@@ -566,10 +566,10 @@
     /// @param b the ProgramBuilder
     /// @param elem_value the value each element will be initialized with
     /// @return the list of expressions that are used to construct the array
-    static inline ast::ExpressionList ExprArgs(ProgramBuilder& b, double elem_value) {
-        ast::ExpressionList args;
+    static inline auto ExprArgs(ProgramBuilder& b, double elem_value) {
+        utils::Vector<const ast::Expression*, N> args;
         for (uint32_t i = 0; i < N; i++) {
-            args.emplace_back(DataType<T>::Expr(b, elem_value));
+            args.Push(DataType<T>::Expr(b, elem_value));
         }
         return args;
     }
diff --git a/src/tint/resolver/side_effects_test.cc b/src/tint/resolver/side_effects_test.cc
index 689c7b8..d0cb32c 100644
--- a/src/tint/resolver/side_effects_test.cc
+++ b/src/tint/resolver/side_effects_test.cc
@@ -19,6 +19,7 @@
 #include "src/tint/sem/expression.h"
 #include "src/tint/sem/index_accessor_expression.h"
 #include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/utils/vector.h"
 
 using namespace tint::number_suffixes;  // NOLINT
 
@@ -31,8 +32,8 @@
         auto global = Sym();
         GlobalVar(global, ty.Of<T>(), ast::StorageClass::kPrivate);
         auto local = Sym();
-        Func(name, {}, ty.Of<T>(),
-             {
+        Func(name, utils::Empty, ty.Of<T>(),
+             utils::Vector{
                  Decl(Var(local, ty.Of<T>())),
                  Assign(global, local),
                  Return(global),
@@ -44,8 +45,8 @@
         auto global = Sym();
         GlobalVar(global, make_type(), ast::StorageClass::kPrivate);
         auto local = Sym();
-        Func(name, {}, make_type(),
-             {
+        Func(name, utils::Empty, make_type(),
+             utils::Vector{
                  Decl(Var(local, make_type())),
                  Assign(global, local),
                  Return(global),
@@ -89,8 +90,8 @@
 TEST_F(SideEffectsTest, Call_Builtin_NoSE) {
     GlobalVar("a", ty.f32(), ast::StorageClass::kPrivate);
     auto* expr = Call("dpdx", "a");
-    Func("f", {}, ty.void_(), {Ignore(expr)},
-         {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Ignore(expr)},
+         utils::Vector{create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     auto* sem = Sem().Get(expr);
@@ -102,8 +103,8 @@
 TEST_F(SideEffectsTest, Call_Builtin_NoSE_WithSEArg) {
     MakeSideEffectFunc<f32>("se");
     auto* expr = Call("dpdx", Call("se"));
-    Func("f", {}, ty.void_(), {Ignore(expr)},
-         {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Ignore(expr)},
+         utils::Vector{create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     auto* sem = Sem().Get(expr);
@@ -124,8 +125,230 @@
     EXPECT_TRUE(sem->HasSideEffects());
 }
 
+namespace builtin {
+struct Case {
+    const char* name;
+    utils::Vector<const char*, 3> args;
+    bool has_side_effects;
+    ast::PipelineStage pipeline_stage;
+};
+static Case C(const char* name,
+              utils::VectorRef<const char*> args,
+              bool has_side_effects,
+              ast::PipelineStage stage = ast::PipelineStage::kFragment) {
+    Case c;
+    c.name = name;
+    c.args = std::move(args);
+    c.has_side_effects = has_side_effects;
+    c.pipeline_stage = stage;
+    return c;
+}
+static std::ostream& operator<<(std::ostream& o, const Case& c) {
+    o << c.name << "(";
+    for (size_t i = 0; i < c.args.Length(); ++i) {
+        o << c.args[i];
+        if (i + 1 != c.args.Length()) {
+            o << ", ";
+        }
+    }
+    o << "), ";
+    o << "has_side_effects = " << c.has_side_effects;
+    return o;
+}
+
+using SideEffectsBuiltinTest = resolver::ResolverTestWithParam<Case>;
+
+TEST_P(SideEffectsBuiltinTest, Test) {
+    Enable(ast::Extension::kChromiumExperimentalDp4A);
+    auto& c = GetParam();
+
+    uint32_t next_binding = 0;
+    GlobalVar("f", ty.f32(), ast::StorageClass::kPrivate);
+    GlobalVar("i", ty.i32(), ast::StorageClass::kPrivate);
+    GlobalVar("u", ty.u32(), ast::StorageClass::kPrivate);
+    GlobalVar("b", ty.bool_(), ast::StorageClass::kPrivate);
+    GlobalVar("vf", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+    GlobalVar("vf2", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+    GlobalVar("vi2", ty.vec2<i32>(), ast::StorageClass::kPrivate);
+    GlobalVar("vf4", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+    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("a", ty.atomic(ty.i32()), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+              GroupAndBinding(0, 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_arr", ty.sampled_texture(ast::TextureDimension::k2dArray, ty.f32()),
+                  GroupAndBinding(0, next_binding++));
+        GlobalVar("t2d_multi", ty.multisampled_texture(ast::TextureDimension::k2d, ty.f32()),
+                  GroupAndBinding(0, 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++));
+    }
+
+    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"))));
+
+    utils::Vector<const ast::Expression*, 5> args;
+    for (auto& a : c.args) {
+        args.Push(Expr(a));
+    }
+    auto* expr = Call(c.name, args);
+
+    utils::Vector<const ast::Attribute*, 2> attrs;
+    attrs.Push(create<ast::StageAttribute>(c.pipeline_stage));
+    if (c.pipeline_stage == ast::PipelineStage::kCompute) {
+        attrs.Push(WorkgroupSize(Expr(1_u)));
+    }
+
+    stmts.Push(create<ast::CallStatement>(expr));
+
+    Func("func", utils::Empty, ty.void_(), stmts, attrs);
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+    auto* sem = Sem().Get(expr);
+    ASSERT_NE(sem, nullptr);
+    EXPECT_TRUE(sem->Is<sem::Call>());
+    EXPECT_EQ(c.has_side_effects, sem->HasSideEffects());
+}
+INSTANTIATE_TEST_SUITE_P(
+    SideEffectsTest_Builtins,
+    SideEffectsBuiltinTest,
+    testing::ValuesIn(std::vector<Case>{
+        // No side-effect builts
+        C("abs", utils::Vector{"f"}, false),                                                    //
+        C("acos", utils::Vector{"f"}, false),                                                   //
+        C("acosh", utils::Vector{"f"}, false),                                                  //
+        C("all", utils::Vector{"vb"}, false),                                                   //
+        C("any", utils::Vector{"vb"}, false),                                                   //
+        C("arrayLength", utils::Vector{"pstorage_arr"}, false),                                 //
+        C("asin", utils::Vector{"f"}, false),                                                   //
+        C("asinh", utils::Vector{"f"}, false),                                                  //
+        C("atan", utils::Vector{"f"}, false),                                                   //
+        C("atan2", utils::Vector{"f", "f"}, false),                                             //
+        C("atanh", utils::Vector{"f"}, false),                                                  //
+        C("atomicLoad", utils::Vector{"pa"}, false),                                            //
+        C("ceil", utils::Vector{"f"}, false),                                                   //
+        C("clamp", utils::Vector{"f", "f", "f"}, false),                                        //
+        C("cos", utils::Vector{"f"}, false),                                                    //
+        C("cosh", utils::Vector{"f"}, false),                                                   //
+        C("countLeadingZeros", utils::Vector{"i"}, false),                                      //
+        C("countOneBits", utils::Vector{"i"}, false),                                           //
+        C("countTrailingZeros", utils::Vector{"i"}, false),                                     //
+        C("cross", utils::Vector{"vf", "vf"}, false),                                           //
+        C("degrees", utils::Vector{"f"}, false),                                                //
+        C("determinant", utils::Vector{"m"}, false),                                            //
+        C("distance", utils::Vector{"f", "f"}, false),                                          //
+        C("dot", utils::Vector{"vf", "vf"}, false),                                             //
+        C("dot4I8Packed", utils::Vector{"u", "u"}, false),                                      //
+        C("dot4U8Packed", utils::Vector{"u", "u"}, false),                                      //
+        C("exp", utils::Vector{"f"}, false),                                                    //
+        C("exp2", utils::Vector{"f"}, false),                                                   //
+        C("extractBits", utils::Vector{"i", "u", "u"}, false),                                  //
+        C("faceForward", utils::Vector{"vf", "vf", "vf"}, false),                               //
+        C("firstLeadingBit", utils::Vector{"u"}, false),                                        //
+        C("firstTrailingBit", utils::Vector{"u"}, false),                                       //
+        C("floor", utils::Vector{"f"}, false),                                                  //
+        C("fma", utils::Vector{"f", "f", "f"}, false),                                          //
+        C("fract", utils::Vector{"vf"}, false),                                                 //
+        C("frexp", utils::Vector{"f"}, false),                                                  //
+        C("insertBits", utils::Vector{"i", "i", "u", "u"}, false),                              //
+        C("inverseSqrt", utils::Vector{"f"}, false),                                            //
+        C("ldexp", utils::Vector{"f", "i"}, false),                                             //
+        C("length", utils::Vector{"vf"}, false),                                                //
+        C("log", utils::Vector{"f"}, false),                                                    //
+        C("log2", utils::Vector{"f"}, false),                                                   //
+        C("max", utils::Vector{"f", "f"}, false),                                               //
+        C("min", utils::Vector{"f", "f"}, false),                                               //
+        C("mix", utils::Vector{"f", "f", "f"}, false),                                          //
+        C("modf", utils::Vector{"f"}, false),                                                   //
+        C("normalize", utils::Vector{"vf"}, false),                                             //
+        C("pack2x16float", utils::Vector{"vf2"}, false),                                        //
+        C("pack2x16snorm", utils::Vector{"vf2"}, false),                                        //
+        C("pack2x16unorm", utils::Vector{"vf2"}, false),                                        //
+        C("pack4x8snorm", utils::Vector{"vf4"}, false),                                         //
+        C("pack4x8unorm", utils::Vector{"vf4"}, false),                                         //
+        C("pow", utils::Vector{"f", "f"}, false),                                               //
+        C("radians", utils::Vector{"f"}, false),                                                //
+        C("reflect", utils::Vector{"vf", "vf"}, false),                                         //
+        C("refract", utils::Vector{"vf", "vf", "f"}, false),                                    //
+        C("reverseBits", utils::Vector{"u"}, false),                                            //
+        C("round", utils::Vector{"f"}, false),                                                  //
+        C("select", utils::Vector{"f", "f", "b"}, false),                                       //
+        C("sign", utils::Vector{"f"}, false),                                                   //
+        C("sin", utils::Vector{"f"}, false),                                                    //
+        C("sinh", utils::Vector{"f"}, false),                                                   //
+        C("smoothstep", utils::Vector{"f", "f", "f"}, false),                                   //
+        C("sqrt", utils::Vector{"f"}, false),                                                   //
+        C("step", utils::Vector{"f", "f"}, false),                                              //
+        C("tan", utils::Vector{"f"}, false),                                                    //
+        C("tanh", utils::Vector{"f"}, false),                                                   //
+        C("textureDimensions", utils::Vector{"t2d"}, false),                                    //
+        C("textureGather", utils::Vector{"tdepth2d", "s2d", "vf2"}, false),                     //
+        C("textureGatherCompare", utils::Vector{"tdepth2d", "scomp", "vf2", "f"}, false),       //
+        C("textureLoad", utils::Vector{"t2d", "vi2", "i"}, false),                              //
+        C("textureNumLayers", utils::Vector{"t2d_arr"}, false),                                 //
+        C("textureNumLevels", utils::Vector{"t2d"}, false),                                     //
+        C("textureNumSamples", utils::Vector{"t2d_multi"}, false),                              //
+        C("textureSampleCompareLevel", utils::Vector{"tdepth2d", "scomp", "vf2", "f"}, false),  //
+        C("textureSampleGrad", utils::Vector{"t2d", "s2d", "vf2", "vf2", "vf2"}, false),        //
+        C("textureSampleLevel", utils::Vector{"t2d", "s2d", "vf2", "f"}, false),                //
+        C("transpose", utils::Vector{"m"}, false),                                              //
+        C("trunc", utils::Vector{"f"}, false),                                                  //
+        C("unpack2x16float", utils::Vector{"u"}, false),                                        //
+        C("unpack2x16snorm", utils::Vector{"u"}, false),                                        //
+        C("unpack2x16unorm", utils::Vector{"u"}, false),                                        //
+        C("unpack4x8snorm", utils::Vector{"u"}, false),                                         //
+        C("unpack4x8unorm", utils::Vector{"u"}, false),                                         //
+        C("storageBarrier", utils::Empty, false, ast::PipelineStage::kCompute),                 //
+        C("workgroupBarrier", utils::Empty, false, ast::PipelineStage::kCompute),               //
+        C("textureSample", utils::Vector{"t2d", "s2d", "vf2"}, false),                          //
+        C("textureSampleBias", utils::Vector{"t2d", "s2d", "vf2", "f"}, false),                 //
+        C("textureSampleCompare", utils::Vector{"tdepth2d", "scomp", "vf2", "f"}, false),       //
+        C("dpdx", utils::Vector{"f"}, false),                                                   //
+        C("dpdxCoarse", utils::Vector{"f"}, false),                                             //
+        C("dpdxFine", utils::Vector{"f"}, false),                                               //
+        C("dpdy", utils::Vector{"f"}, false),                                                   //
+        C("dpdyCoarse", utils::Vector{"f"}, false),                                             //
+        C("dpdyFine", utils::Vector{"f"}, false),                                               //
+        C("fwidth", utils::Vector{"f"}, false),                                                 //
+        C("fwidthCoarse", utils::Vector{"f"}, false),                                           //
+        C("fwidthFine", utils::Vector{"f"}, false),                                             //
+
+        // Side-effect builtins
+        C("atomicAdd", utils::Vector{"pa", "i"}, true),                       //
+        C("atomicAnd", utils::Vector{"pa", "i"}, true),                       //
+        C("atomicCompareExchangeWeak", utils::Vector{"pa", "i", "i"}, true),  //
+        C("atomicExchange", utils::Vector{"pa", "i"}, true),                  //
+        C("atomicMax", utils::Vector{"pa", "i"}, true),                       //
+        C("atomicMin", utils::Vector{"pa", "i"}, true),                       //
+        C("atomicOr", utils::Vector{"pa", "i"}, true),                        //
+        C("atomicStore", utils::Vector{"pa", "i"}, true),                     //
+        C("atomicSub", utils::Vector{"pa", "i"}, true),                       //
+        C("atomicXor", utils::Vector{"pa", "i"}, true),                       //
+        C("textureStore", utils::Vector{"tstorage2d", "vi2", "vf4"}, true),   //
+
+        // Unimplemented builtins
+        // C("quantizeToF16", utils::Vector{"f"}, false), //
+        // C("saturate", utils::Vector{"f"}, false), //
+    }));
+
+}  // namespace builtin
+
 TEST_F(SideEffectsTest, Call_Function) {
-    Func("f", {}, ty.i32(), {Return(1_i)});
+    Func("f", utils::Empty, ty.i32(), utils::Vector{Return(1_i)});
     auto* expr = Call("f");
     WrapInFunction(expr);
 
@@ -185,7 +408,7 @@
 }
 
 TEST_F(SideEffectsTest, MemberAccessor_Struct_NoSE) {
-    auto* s = Structure("S", {Member("m", ty.i32())});
+    auto* s = Structure("S", utils::Vector{Member("m", ty.i32())});
     auto* var = Decl(Var("a", ty.Of(s)));
     auto* expr = MemberAccessor("a", "m");
     WrapInFunction(var, expr);
@@ -197,7 +420,7 @@
 }
 
 TEST_F(SideEffectsTest, MemberAccessor_Struct_SE) {
-    auto* s = Structure("S", {Member("m", ty.i32())});
+    auto* s = Structure("S", utils::Vector{Member("m", ty.i32())});
     MakeSideEffectFunc("se", [&] { return ty.Of(s); });
     auto* expr = MemberAccessor(Call("se"), "m");
     WrapInFunction(expr);
diff --git a/src/tint/resolver/source_variable_test.cc b/src/tint/resolver/source_variable_test.cc
index 2648e93..f80c980 100644
--- a/src/tint/resolver/source_variable_test.cc
+++ b/src/tint/resolver/source_variable_test.cc
@@ -128,7 +128,7 @@
 TEST_F(ResolverSourceVariableTest, Parameter) {
     auto* a = Param("a", ty.f32());
     auto* expr = Expr(a);
-    Func("foo", {a}, ty.void_(), {WrapInStatement(expr)});
+    Func("foo", utils::Vector{a}, ty.void_(), utils::Vector{WrapInStatement(expr)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -145,7 +145,8 @@
     auto* expr_param = Expr(param);
     auto* let = Let("b", nullptr, expr_param);
     auto* expr_let = Expr("b");
-    Func("foo", {param}, ty.void_(), {WrapInStatement(let), WrapInStatement(expr_let)});
+    Func("foo", utils::Vector{param}, ty.void_(),
+         utils::Vector{WrapInStatement(let), WrapInStatement(expr_let)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -213,7 +214,7 @@
     // {
     //   a.f
     // }
-    auto* S = Structure("S", {Member("f", ty.f32())});
+    auto* S = Structure("S", utils::Vector{Member("f", ty.f32())});
     auto* a = GlobalVar("a", ty.Of(S), ast::StorageClass::kPrivate);
     auto* expr = MemberAccessor(a, "f");
     WrapInFunction(expr);
diff --git a/src/tint/resolver/static_assert_test.cc b/src/tint/resolver/static_assert_test.cc
new file mode 100644
index 0000000..3cb67c9
--- /dev/null
+++ b/src/tint/resolver/static_assert_test.cc
@@ -0,0 +1,110 @@
+// 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/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+using namespace tint::number_suffixes;  // NOLINT
+
+namespace tint::resolver {
+namespace {
+
+using ResolverStaticAssertTest = ResolverTest;
+
+TEST_F(ResolverStaticAssertTest, Global_True_Pass) {
+    GlobalStaticAssert(true);
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStaticAssertTest, Global_False_Fail) {
+    GlobalStaticAssert(Source{{12, 34}}, false);
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "12:34 error: static assertion failed");
+}
+
+TEST_F(ResolverStaticAssertTest, Global_Const_Pass) {
+    GlobalConst("C", ty.bool_(), Expr(true));
+    GlobalStaticAssert("C");
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStaticAssertTest, Global_Const_Fail) {
+    GlobalConst("C", ty.bool_(), Expr(false));
+    GlobalStaticAssert(Source{{12, 34}}, "C");
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "12:34 error: static assertion failed");
+}
+
+// TODO(crbug.com/tint/1581): Enable once the '<' operator is implemented for constant evaluation.
+TEST_F(ResolverStaticAssertTest, DISABLED_Global_LessThan_Pass) {
+    GlobalStaticAssert(LessThan(2_i, 3_i));
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+// TODO(crbug.com/tint/1581): Enable once the '<' operator is implemented for constant evaluation.
+TEST_F(ResolverStaticAssertTest, DISABLED_Global_LessThan_Fail) {
+    GlobalStaticAssert(Source{{12, 34}}, LessThan(4_i, 3_i));
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "12:34 error: static assertion failed");
+}
+
+TEST_F(ResolverStaticAssertTest, Local_True_Pass) {
+    WrapInFunction(StaticAssert(true));
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStaticAssertTest, Local_False_Fail) {
+    WrapInFunction(StaticAssert(Source{{12, 34}}, false));
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "12:34 error: static assertion failed");
+}
+
+TEST_F(ResolverStaticAssertTest, Local_Const_Pass) {
+    GlobalConst("C", ty.bool_(), Expr(true));
+    WrapInFunction(StaticAssert("C"));
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStaticAssertTest, Local_Const_Fail) {
+    GlobalConst("C", ty.bool_(), Expr(false));
+    WrapInFunction(StaticAssert(Source{{12, 34}}, "C"));
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "12:34 error: static assertion failed");
+}
+
+TEST_F(ResolverStaticAssertTest, Local_NonConst) {
+    GlobalVar("V", ty.bool_(), Expr(true), ast::StorageClass::kPrivate);
+    WrapInFunction(StaticAssert(Expr(Source{{12, 34}}, "V")));
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "12:34 error: static assertion condition must be a constant expression");
+}
+
+// TODO(crbug.com/tint/1581): Enable once the '<' operator is implemented for constant evaluation.
+TEST_F(ResolverStaticAssertTest, DISABLED_Local_LessThan_Pass) {
+    WrapInFunction(StaticAssert(LessThan(2_i, 3_i)));
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+// TODO(crbug.com/tint/1581): Enable once the '<' operator is implemented for constant evaluation.
+TEST_F(ResolverStaticAssertTest, DISABLED_Local_LessThan_Fail) {
+    WrapInFunction(StaticAssert(Source{{12, 34}}, LessThan(4_i, 3_i)));
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "12:34 error: static assertion failed");
+}
+
+}  // namespace
+}  // namespace tint::resolver
diff --git a/src/tint/resolver/storage_class_layout_validation_test.cc b/src/tint/resolver/storage_class_layout_validation_test.cc
index 770361a..c55aece 100644
--- a/src/tint/resolver/storage_class_layout_validation_test.cc
+++ b/src/tint/resolver/storage_class_layout_validation_test.cc
@@ -34,8 +34,10 @@
     // var<storage> a : S;
 
     Structure(Source{{12, 34}}, "S",
-              {Member("a", ty.f32(), {MemberSize(5)}),
-               Member(Source{{34, 56}}, "b", ty.f32(), {MemberAlign(1)})});
+              utils::Vector{
+                  Member("a", ty.f32(), utils::Vector{MemberSize(5)}),
+                  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));
@@ -62,8 +64,10 @@
     // var<storage> a : S;
 
     Structure(Source{{12, 34}}, "S",
-              {Member("a", ty.f32(), {MemberSize(5)}),
-               Member(Source{{34, 56}}, "b", ty.f32(), {MemberAlign(4)})});
+              utils::Vector{
+                  Member("a", ty.f32(), utils::Vector{MemberSize(5)}),
+                  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));
@@ -85,10 +89,13 @@
     // @group(0) @binding(0)
     // var<uniform> a : Outer;
 
-    Structure(Source{{12, 34}}, "Inner", {Member("scalar", ty.i32())});
+    Structure(Source{{12, 34}}, "Inner",
+              utils::Vector{
+                  Member("scalar", ty.i32()),
+              });
 
     Structure(Source{{34, 56}}, "Outer",
-              {
+              utils::Vector{
                   Member("scalar", ty.f32()),
                   Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
               });
@@ -126,12 +133,16 @@
     // @group(0) @binding(0)
     // var<uniform> a : Outer;
 
-    Structure(Source{{12, 34}}, "Inner", {Member("scalar", ty.i32())});
+    Structure(Source{{12, 34}}, "Inner",
+              utils::Vector{
+                  Member("scalar", ty.i32()),
+              });
 
     Structure(Source{{34, 56}}, "Outer",
-              {
+              utils::Vector{
                   Member("scalar", ty.f32()),
-                  Member(Source{{56, 78}}, "inner", ty.type_name("Inner"), {MemberAlign(16)}),
+                  Member(Source{{56, 78}}, "inner", ty.type_name("Inner"),
+                         utils::Vector{MemberAlign(16)}),
               });
 
     GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform,
@@ -154,7 +165,7 @@
     Alias("Inner", ty.array(ty.f32(), 10_u, 16));
 
     Structure(Source{{12, 34}}, "Outer",
-              {
+              utils::Vector{
                   Member("scalar", ty.f32()),
                   Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
               });
@@ -187,9 +198,10 @@
     Alias("Inner", ty.array(ty.f32(), 10_u, 16));
 
     Structure(Source{{12, 34}}, "Outer",
-              {
+              utils::Vector{
                   Member("scalar", ty.f32()),
-                  Member(Source{{34, 56}}, "inner", ty.type_name("Inner"), {MemberAlign(16)}),
+                  Member(Source{{34, 56}}, "inner", ty.type_name("Inner"),
+                         utils::Vector{MemberAlign(16)}),
               });
 
     GlobalVar(Source{{78, 90}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform,
@@ -214,10 +226,12 @@
     // var<uniform> a : Outer;
 
     Structure(Source{{12, 34}}, "Inner",
-              {Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)})});
+              utils::Vector{
+                  Member("scalar", ty.i32(), utils::Vector{MemberAlign(1), MemberSize(5)}),
+              });
 
     Structure(Source{{34, 56}}, "Outer",
-              {
+              utils::Vector{
                   Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
                   Member(Source{{78, 90}}, "scalar", ty.i32()),
               });
@@ -261,15 +275,15 @@
     // var<uniform> a : Outer;
 
     Structure(Source{{12, 34}}, "Inner",
-              {
+              utils::Vector{
                   Member("a", ty.i32()),
                   Member("b", ty.i32()),
                   Member("c", ty.i32()),
-                  Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)}),
+                  Member("scalar", ty.i32(), utils::Vector{MemberAlign(1), MemberSize(5)}),
               });
 
     Structure(Source{{34, 56}}, "Outer",
-              {
+              utils::Vector{
                   Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
                   Member(Source{{78, 90}}, "scalar", ty.i32()),
               });
@@ -312,12 +326,14 @@
     // var<uniform> a : Outer;
 
     Structure(Source{{12, 34}}, "Inner",
-              {Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)})});
+              utils::Vector{
+                  Member("scalar", ty.i32(), utils::Vector{MemberAlign(1), MemberSize(5)}),
+              });
 
     Structure(Source{{34, 56}}, "Outer",
-              {
+              utils::Vector{
                   Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
-                  Member(Source{{78, 90}}, "scalar", ty.i32(), {MemberAlign(16)}),
+                  Member(Source{{78, 90}}, "scalar", ty.i32(), utils::Vector{MemberAlign(16)}),
               });
 
     GlobalVar(Source{{22, 34}}, "a", ty.type_name("Outer"), ast::StorageClass::kUniform,
@@ -336,7 +352,7 @@
     // @group(0) @binding(0)
     // var<uniform> a : ScalarPackedAtEndOfVec3;
 
-    Structure("ScalarPackedAtEndOfVec3", {
+    Structure("ScalarPackedAtEndOfVec3", utils::Vector{
                                              Member("v", ty.vec3(ty.f32())),
                                              Member("s", ty.f32()),
                                          });
@@ -362,7 +378,7 @@
     Alias("Inner", ty.array(ty.f32(), 10_u));
 
     Structure(Source{{12, 34}}, "Outer",
-              {
+              utils::Vector{
                   Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
                   Member("scalar", ty.i32()),
               });
@@ -396,7 +412,7 @@
     Alias("Inner", ty.array(ty.vec2<f32>(), 10_u));
 
     Structure(Source{{12, 34}}, "Outer",
-              {
+              utils::Vector{
                   Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
                   Member("scalar", ty.i32()),
               });
@@ -432,14 +448,14 @@
     // @group(0) @binding(0)
     // var<uniform> a : Outer;
 
-    auto* array_elem = Structure("ArrayElem", {
+    auto* array_elem = Structure("ArrayElem", utils::Vector{
                                                   Member("a", ty.f32()),
                                                   Member("b", ty.i32()),
                                               });
     Alias("Inner", ty.array(ty.Of(array_elem), 10_u));
 
     Structure(Source{{12, 34}}, "Outer",
-              {
+              utils::Vector{
                   Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
                   Member("scalar", ty.i32()),
               });
@@ -480,7 +496,7 @@
     // var<uniform> a : array<Outer, 4u>;
 
     Structure(Source{{12, 34}}, "Outer",
-              {
+              utils::Vector{
                   Member("inner", ty.array(Source{{34, 56}}, ty.array(ty.f32(), 4_u), 4_u)),
               });
 
@@ -512,7 +528,7 @@
     Alias("Inner", ty.array(ty.f32(), 10_u, 16));
 
     Structure(Source{{12, 34}}, "Outer",
-              {
+              utils::Vector{
                   Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
                   Member("scalar", ty.i32()),
               });
@@ -523,5 +539,48 @@
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
+// Detect unaligned member for push constants buffers
+TEST_F(ResolverStorageClassLayoutValidationTest, PushConstant_UnalignedMember) {
+    // enable chromium_experimental_push_constant;
+    // struct S {
+    //     @size(5) a : f32;
+    //     @align(1) b : f32;
+    // };
+    // var<push_constant> a : S;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    Structure(
+        Source{{12, 34}}, "S",
+        utils::Vector{Member("a", ty.f32(), utils::Vector{MemberSize(5)}),
+                      Member(Source{{34, 56}}, "b", ty.f32(), utils::Vector{MemberAlign(1)})});
+    GlobalVar(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kPushConstant);
+
+    ASSERT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(34:56 error: the offset of a struct member of type 'f32' in storage class 'push_constant' must be a multiple of 4 bytes, but 'b' is currently at offset 5. Consider setting @align(4) on this member
+12:34 note: see layout of struct:
+/*           align(4) size(12) */ struct S {
+/* offset(0) align(4) size( 5) */   a : f32;
+/* offset(5) align(1) size( 4) */   b : f32;
+/* offset(9) align(1) size( 3) */   // -- implicit struct size padding --;
+/*                             */ };
+78:90 note: see declaration of variable)");
+}
+
+TEST_F(ResolverStorageClassLayoutValidationTest, PushConstant_Aligned) {
+    // enable chromium_experimental_push_constant;
+    // struct S {
+    //     @size(5) a : f32;
+    //     @align(4) b : f32;
+    // };
+    // var<push_constant> a : S;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{MemberSize(5)}),
+                                 Member("b", ty.f32(), utils::Vector{MemberAlign(4)})});
+    GlobalVar("a", ty.type_name("S"), ast::StorageClass::kPushConstant);
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/storage_class_validation_test.cc b/src/tint/resolver/storage_class_validation_test.cc
index f3e6f32..e1b3dab 100644
--- a/src/tint/resolver/storage_class_validation_test.cc
+++ b/src/tint/resolver/storage_class_validation_test.cc
@@ -55,7 +55,7 @@
 }
 
 TEST_F(ResolverStorageClassValidationTest, Private_RuntimeArrayInStruct) {
-    auto* s = Structure("S", {Member("m", ty.array(ty.i32()))});
+    auto* s = Structure("S", utils::Vector{Member("m", ty.array(ty.i32()))});
     GlobalVar(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
@@ -75,7 +75,7 @@
 }
 
 TEST_F(ResolverStorageClassValidationTest, Workgroup_RuntimeArrayInStruct) {
-    auto* s = Structure("S", {Member("m", ty.array(ty.i32()))});
+    auto* s = Structure("S", utils::Vector{Member("m", ty.array(ty.i32()))});
     GlobalVar(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kWorkgroup);
 
     EXPECT_FALSE(r()->Resolve());
@@ -88,7 +88,7 @@
 TEST_F(ResolverStorageClassValidationTest, StorageBufferBool) {
     // var<storage> g : bool;
     GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kStorage,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -106,7 +106,7 @@
     // var<storage, read> g : a;
     auto* a = Alias("a", ty.bool_());
     GlobalVar(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kStorage,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -126,7 +126,7 @@
     Enable(ast::Extension::kF16);
 
     GlobalVar("g", ty.f16(Source{{56, 78}}), ast::StorageClass::kStorage,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -145,7 +145,7 @@
 
     auto* a = Alias("a", ty.f16());
     GlobalVar("g", ty.type_name(Source{{56, 78}}, a->name), ast::StorageClass::kStorage,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -161,7 +161,7 @@
     // var<storage> g : vec4<f16>;
     Enable(ast::Extension::kF16);
     GlobalVar("g", ty.vec(Source{{56, 78}}, ty.Of<f16>(), 4u), ast::StorageClass::kStorage,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -178,10 +178,10 @@
     // var<storage, read> g : array<S, 3u>;
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", {Member("a", ty.f16(Source{{56, 78}}))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -197,9 +197,9 @@
     // var<storage, read> g : S;
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", {Member("x", ty.f16(Source{{12, 34}}))});
+    auto* s = Structure("S", utils::Vector{Member("x", ty.f16(Source{{12, 34}}))});
     GlobalVar("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -216,11 +216,11 @@
     // var<storage, read> g : a1;
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", {Member("x", ty.f16(Source{{12, 34}}))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -235,7 +235,7 @@
     // var<storage> g : ptr<private, f32>;
     GlobalVar(Source{{56, 78}}, "g", ty.pointer(ty.f32(), ast::StorageClass::kPrivate),
               ast::StorageClass::kStorage,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -251,7 +251,7 @@
 TEST_F(ResolverStorageClassValidationTest, StorageBufferIntScalar) {
     // var<storage> g : i32;
     GlobalVar(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kStorage,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -262,7 +262,7 @@
 TEST_F(ResolverStorageClassValidationTest, StorageBufferVectorF32) {
     // var<storage> g : vec4<f32>;
     GlobalVar(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kStorage,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -272,10 +272,10 @@
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferArrayF32) {
     // var<storage, read> g : array<S, 3u>;
-    auto* s = Structure("S", {Member("a", ty.f32())});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -324,9 +324,9 @@
 TEST_F(ResolverStorageClassValidationTest, StorageBufferStructI32) {
     // struct S { x : i32 };
     // var<storage, read> g : S;
-    auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -338,11 +338,11 @@
     // struct S { x : i32 };
     // type a1 = S;
     // var<storage, read> g : a1;
-    auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
+    auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.i32())});
     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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -354,10 +354,10 @@
     // struct S { m:  array<f32>; };
     // @group(0) @binding(0) var<uniform, > svar : S;
 
-    auto* s = Structure(Source{{12, 34}}, "S", {Member("m", ty.array<i32>())});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -372,7 +372,7 @@
 TEST_F(ResolverStorageClassValidationTest, UniformBufferBool) {
     // var<uniform> g : bool;
     GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -390,7 +390,7 @@
     // var<uniform> g : a;
     auto* a = Alias("a", ty.bool_());
     GlobalVar(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -410,7 +410,7 @@
     Enable(ast::Extension::kF16);
 
     GlobalVar("g", ty.f16(Source{{56, 78}}), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -429,7 +429,7 @@
 
     auto* a = Alias("a", ty.f16());
     GlobalVar("g", ty.type_name(Source{{56, 78}}, a->name), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -445,7 +445,7 @@
     // var<uniform> g : vec4<f16>;
     Enable(ast::Extension::kF16);
     GlobalVar("g", ty.vec(Source{{56, 78}}, ty.Of<f16>(), 4u), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -463,10 +463,11 @@
     // var<uniform> g : array<S, 3u>;
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", {Member("a", ty.f16(Source{{56, 78}}), {MemberSize(16)})});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -482,9 +483,9 @@
     // var<uniform> g :  S;
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", {Member("x", ty.f16(Source{{12, 34}}))});
+    auto* s = Structure("S", utils::Vector{Member("x", ty.f16(Source{{12, 34}}))});
     GlobalVar("g", ty.Of(s), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -501,10 +502,10 @@
     // var<uniform> g : a1;
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", {Member("x", ty.f16(Source{{12, 34}}))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -519,7 +520,7 @@
     // var<uniform> g : ptr<private, f32>;
     GlobalVar(Source{{56, 78}}, "g", ty.pointer(ty.f32(), ast::StorageClass::kPrivate),
               ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -535,7 +536,7 @@
 TEST_F(ResolverStorageClassValidationTest, UniformBufferIntScalar) {
     // var<uniform> g : i32;
     GlobalVar(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -546,7 +547,7 @@
 TEST_F(ResolverStorageClassValidationTest, UniformBufferVectorF32) {
     // var<uniform> g : vec4<f32>;
     GlobalVar(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -559,10 +560,10 @@
     //   @size(16) f : f32;
     // }
     // var<uniform> g : array<S, 3u>;
-    auto* s = Structure("S", {Member("a", ty.f32(), {MemberSize(16)})});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -573,9 +574,9 @@
 TEST_F(ResolverStorageClassValidationTest, UniformBufferStructI32) {
     // struct S { x : i32 };
     // var<uniform> g :  S;
-    auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
+    auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.i32())});
     GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -587,10 +588,10 @@
     // struct S { x : i32 };
     // type a1 = S;
     // var<uniform> g : a1;
-    auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -598,5 +599,89 @@
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
+TEST_F(ResolverStorageClassValidationTest, PushConstantBool) {
+    // enable chromium_experimental_push_constant;
+    // var<push_constant> g : bool;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kPushConstant);
+
+    ASSERT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(56:78 error: Type 'bool' cannot be used in storage class 'push_constant' as it is non-host-shareable
+56:78 note: while instantiating 'var' g)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, PushConstantF16) {
+    // enable chromium_experimental_push_constant;
+    // enable f16;
+    // var<push_constant> g : f16;
+    Enable(ast::Extension::kF16);
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    GlobalVar("g", ty.f16(Source{{56, 78}}), ast::StorageClass::kPushConstant);
+
+    ASSERT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "56:78 error: using f16 types in 'push_constant' storage class is not "
+              "implemented yet");
+}
+
+TEST_F(ResolverStorageClassValidationTest, PushConstantPointer) {
+    // enable chromium_experimental_push_constant;
+    // var<push_constant> g : ptr<private, f32>;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    GlobalVar(Source{{56, 78}}, "g", ty.pointer(ty.f32(), ast::StorageClass::kPrivate),
+              ast::StorageClass::kPushConstant);
+
+    ASSERT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(56:78 error: Type 'ptr<private, f32, read_write>' cannot be used in storage class 'push_constant' as it is non-host-shareable
+56:78 note: while instantiating 'var' g)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, PushConstantIntScalar) {
+    // enable chromium_experimental_push_constant;
+    // var<push_constant> g : i32;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    GlobalVar("g", ty.i32(), ast::StorageClass::kPushConstant);
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStorageClassValidationTest, PushConstantVectorF32) {
+    // enable chromium_experimental_push_constant;
+    // var<push_constant> g : vec4<f32>;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    GlobalVar("g", ty.vec4<f32>(), ast::StorageClass::kPushConstant);
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStorageClassValidationTest, PushConstantArrayF32) {
+    // enable chromium_experimental_push_constant;
+    // struct S { a : f32}
+    // var<push_constant> g : array<S, 3u>;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
+    auto* a = ty.array(ty.Of(s), 3_u);
+    GlobalVar("g", a, ast::StorageClass::kPushConstant);
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverStorageClassValidationTest, PushConstantWithInitializer) {
+    // enable chromium_experimental_push_constant;
+    // var<push_constant> a : u32 = 0u;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    GlobalVar(Source{{1u, 2u}}, "a", ty.u32(), ast::StorageClass::kPushConstant,
+              Expr(Source{{3u, 4u}}, u32(0)));
+
+    ASSERT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(1:2 error: var of storage class 'push_constant' cannot have an initializer. var initializers are only supported for the storage classes 'private' and 'function')");
+}
+
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/struct_layout_test.cc b/src/tint/resolver/struct_layout_test.cc
index 73ecd4e..59faf8d 100644
--- a/src/tint/resolver/struct_layout_test.cc
+++ b/src/tint/resolver/struct_layout_test.cc
@@ -26,7 +26,7 @@
 using ResolverStructLayoutTest = ResolverTest;
 
 TEST_F(ResolverStructLayoutTest, Scalars) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.f32()),
                                  Member("b", ty.u32()),
                                  Member("c", ty.i32()),
@@ -57,7 +57,7 @@
 TEST_F(ResolverStructLayoutTest, ScalarsWithF16) {
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.f32()),
                                  Member("b", ty.f16()),
                                  Member("c", ty.u32()),
@@ -109,7 +109,7 @@
     auto* alias_a = Alias("a", ty.f32());
     auto* alias_b = Alias("b", ty.f32());
 
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.Of(alias_a)),
                                  Member("b", ty.Of(alias_b)),
                              });
@@ -136,7 +136,7 @@
 TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayStaticSize) {
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.array<i32, 3>()),
                                  Member("b", ty.array<f32, 5>()),
                                  Member("c", ty.array<f16, 7>()),
@@ -176,7 +176,7 @@
 TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayStaticSize) {
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.array<i32, 3>(/*stride*/ 8)),
                                  Member("b", ty.array<f32, 5>(/*stride*/ 16)),
                                  Member("c", ty.array<f16, 7>(/*stride*/ 4)),
@@ -214,7 +214,7 @@
 }
 
 TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayRuntimeSized) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("c", ty.array<f32>()),
                              });
 
@@ -235,7 +235,7 @@
 }
 
 TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayRuntimeSized) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("c", ty.array<f32>(/*stride*/ 32)),
                              });
 
@@ -258,7 +258,7 @@
 TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfExplicitStrideArray) {
     auto* inner = ty.array<i32, 2>(/*stride*/ 16);  // size: 32
     auto* outer = ty.array(inner, 12_u);            // size: 12 * 32
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("c", outer),
                              });
 
@@ -279,13 +279,13 @@
 }
 
 TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfStructure) {
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.vec2<i32>()),
                                          Member("b", ty.vec3<i32>()),
                                          Member("c", ty.vec4<i32>()),
                                      });         // size: 48
     auto* outer = ty.array(ty.Of(inner), 12_u);  // size: 12 * 48
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("c", outer),
                              });
 
@@ -306,7 +306,7 @@
 }
 
 TEST_F(ResolverStructLayoutTest, Vector) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.vec2<i32>()),
                                  Member("b", ty.vec3<i32>()),
                                  Member("c", ty.vec4<i32>()),
@@ -337,7 +337,7 @@
 TEST_F(ResolverStructLayoutTest, Matrix) {
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a_1", ty.mat2x2<f32>()),
                                  Member("a_2", ty.mat2x2<f16>()),
                                  Member("b_1", ty.mat2x3<f32>()),
@@ -427,10 +427,10 @@
 }
 
 TEST_F(ResolverStructLayoutTest, NestedStruct) {
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.mat3x3<f32>()),
                                      });
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", ty.Of(inner)),
                                  Member("c", ty.i32()),
@@ -459,16 +459,16 @@
 }
 
 TEST_F(ResolverStructLayoutTest, SizeAttributes) {
-    auto* inner = Structure("Inner", {
-                                         Member("a", ty.f32(), {MemberSize(8)}),
-                                         Member("b", ty.f32(), {MemberSize(16)}),
-                                         Member("c", ty.f32(), {MemberSize(8)}),
+    auto* inner = Structure("Inner", utils::Vector{
+                                         Member("a", ty.f32(), utils::Vector{MemberSize(8)}),
+                                         Member("b", ty.f32(), utils::Vector{MemberSize(16)}),
+                                         Member("c", ty.f32(), utils::Vector{MemberSize(8)}),
                                      });
-    auto* s = Structure("S", {
-                                 Member("a", ty.f32(), {MemberSize(4)}),
-                                 Member("b", ty.u32(), {MemberSize(8)}),
+    auto* s = Structure("S", utils::Vector{
+                                 Member("a", ty.f32(), utils::Vector{MemberSize(4)}),
+                                 Member("b", ty.u32(), utils::Vector{MemberSize(8)}),
                                  Member("c", ty.Of(inner)),
-                                 Member("d", ty.i32(), {MemberSize(32)}),
+                                 Member("d", ty.i32(), utils::Vector{MemberSize(32)}),
                              });
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -497,16 +497,16 @@
 }
 
 TEST_F(ResolverStructLayoutTest, AlignAttributes) {
-    auto* inner = Structure("Inner", {
-                                         Member("a", ty.f32(), {MemberAlign(8)}),
-                                         Member("b", ty.f32(), {MemberAlign(16)}),
-                                         Member("c", ty.f32(), {MemberAlign(4)}),
+    auto* inner = Structure("Inner", utils::Vector{
+                                         Member("a", ty.f32(), utils::Vector{MemberAlign(8)}),
+                                         Member("b", ty.f32(), utils::Vector{MemberAlign(16)}),
+                                         Member("c", ty.f32(), utils::Vector{MemberAlign(4)}),
                                      });
-    auto* s = Structure("S", {
-                                 Member("a", ty.f32(), {MemberAlign(4)}),
-                                 Member("b", ty.u32(), {MemberAlign(8)}),
+    auto* s = Structure("S", utils::Vector{
+                                 Member("a", ty.f32(), utils::Vector{MemberAlign(4)}),
+                                 Member("b", ty.u32(), utils::Vector{MemberAlign(8)}),
                                  Member("c", ty.Of(inner)),
-                                 Member("d", ty.i32(), {MemberAlign(32)}),
+                                 Member("d", ty.i32(), utils::Vector{MemberAlign(32)}),
                              });
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -535,8 +535,8 @@
 }
 
 TEST_F(ResolverStructLayoutTest, StructWithLotsOfPadding) {
-    auto* s = Structure("S", {
-                                 Member("a", ty.i32(), {MemberAlign(1024)}),
+    auto* s = Structure("S", utils::Vector{
+                                 Member("a", ty.i32(), utils::Vector{MemberAlign(1024)}),
                              });
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/tint/resolver/struct_pipeline_stage_use_test.cc b/src/tint/resolver/struct_pipeline_stage_use_test.cc
index 58c7cae..acf30f6 100644
--- a/src/tint/resolver/struct_pipeline_stage_use_test.cc
+++ b/src/tint/resolver/struct_pipeline_stage_use_test.cc
@@ -29,7 +29,7 @@
 using ResolverPipelineStageUseTest = ResolverTest;
 
 TEST_F(ResolverPipelineStageUseTest, UnusedStruct) {
-    auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{Location(0)})});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -39,9 +39,9 @@
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsNonEntryPointParam) {
-    auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{Location(0)})});
 
-    Func("foo", {Param("param", ty.Of(s))}, ty.void_(), {}, {});
+    Func("foo", utils::Vector{Param("param", ty.Of(s))}, ty.void_(), utils::Empty, utils::Empty);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -51,9 +51,10 @@
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsNonEntryPointReturnType) {
-    auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{Location(0)})});
 
-    Func("foo", {}, ty.Of(s), {Return(Construct(ty.Of(s), Expr(0_f)))}, {});
+    Func("foo", utils::Empty, ty.Of(s), utils::Vector{Return(Construct(ty.Of(s), Expr(0_f)))},
+         utils::Empty);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -63,10 +64,12 @@
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderParam) {
-    auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{Location(0)})});
 
-    Func("main", {Param("param", ty.Of(s))}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
-         {Stage(ast::PipelineStage::kVertex)}, {Builtin(ast::BuiltinValue::kPosition)});
+    Func("main", utils::Vector{Param("param", ty.Of(s))}, ty.vec4<f32>(),
+         utils::Vector{Return(Construct(ty.vec4<f32>()))},
+         utils::Vector{Stage(ast::PipelineStage::kVertex)},
+         utils::Vector{Builtin(ast::BuiltinValue::kPosition)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -78,9 +81,11 @@
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderReturnType) {
     auto* s =
-        Structure("S", {Member("a", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)})});
+        Structure("S", utils::Vector{Member("a", ty.vec4<f32>(),
+                                            utils::Vector{Builtin(ast::BuiltinValue::kPosition)})});
 
-    Func("main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))}, {Stage(ast::PipelineStage::kVertex)});
+    Func("main", utils::Empty, ty.Of(s), utils::Vector{Return(Construct(ty.Of(s)))},
+         utils::Vector{Stage(ast::PipelineStage::kVertex)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -91,10 +96,10 @@
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsFragmentShaderParam) {
-    auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{Location(0)})});
 
-    Func("main", {Param("param", ty.Of(s))}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Vector{Param("param", ty.Of(s))}, ty.void_(), utils::Empty,
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -105,10 +110,10 @@
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsFragmentShaderReturnType) {
-    auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{Location(0)})});
 
-    Func("main", {}, ty.Of(s), {Return(Construct(ty.Of(s), Expr(0_f)))},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.Of(s), utils::Vector{Return(Construct(ty.Of(s), Expr(0_f)))},
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -120,10 +125,11 @@
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsComputeShaderParam) {
     auto* s = Structure(
-        "S", {Member("a", ty.u32(), {Builtin(ast::BuiltinValue::kLocalInvocationIndex)})});
+        "S", utils::Vector{Member(
+                 "a", ty.u32(), utils::Vector{Builtin(ast::BuiltinValue::kLocalInvocationIndex)})});
 
-    Func("main", {Param("param", ty.Of(s))}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
+    Func("main", utils::Vector{Param("param", ty.Of(s))}, ty.void_(), utils::Empty,
+         utils::Vector{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -135,13 +141,14 @@
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedMultipleStages) {
     auto* s =
-        Structure("S", {Member("a", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)})});
+        Structure("S", utils::Vector{Member("a", ty.vec4<f32>(),
+                                            utils::Vector{Builtin(ast::BuiltinValue::kPosition)})});
 
-    Func("vert_main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
-         {Stage(ast::PipelineStage::kVertex)});
+    Func("vert_main", utils::Empty, ty.Of(s), utils::Vector{Return(Construct(ty.Of(s)))},
+         utils::Vector{Stage(ast::PipelineStage::kVertex)});
 
-    Func("frag_main", {Param("param", ty.Of(s))}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("frag_main", utils::Vector{Param("param", ty.Of(s))}, ty.void_(), utils::Empty,
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -153,11 +160,11 @@
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderParamViaAlias) {
-    auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{Location(0)})});
     auto* s_alias = Alias("S_alias", ty.Of(s));
 
-    Func("main", {Param("param", ty.Of(s_alias))}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Vector{Param("param", ty.Of(s_alias))}, ty.void_(), utils::Empty,
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -168,11 +175,12 @@
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderReturnTypeViaAlias) {
-    auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{Location(0)})});
     auto* s_alias = Alias("S_alias", ty.Of(s));
 
-    Func("main", {}, ty.Of(s_alias), {Return(Construct(ty.Of(s_alias), Expr(0_f)))},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.Of(s_alias),
+         utils::Vector{Return(Construct(ty.Of(s_alias), Expr(0_f)))},
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     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 5a929a5..d58c906 100644
--- a/src/tint/resolver/struct_storage_class_use_test.cc
+++ b/src/tint/resolver/struct_storage_class_use_test.cc
@@ -28,7 +28,7 @@
 using ResolverStorageClassUseTest = ResolverTest;
 
 TEST_F(ResolverStorageClassUseTest, UnreachableStruct) {
-    auto* s = Structure("S", {Member("a", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -38,9 +38,9 @@
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableFromParameter) {
-    auto* s = Structure("S", {Member("a", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
 
-    Func("f", {Param("param", ty.Of(s))}, ty.void_(), {}, {});
+    Func("f", utils::Vector{Param("param", ty.Of(s))}, ty.void_(), utils::Empty, utils::Empty);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -50,9 +50,9 @@
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableFromReturnType) {
-    auto* s = Structure("S", {Member("a", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
 
-    Func("f", {}, ty.Of(s), {Return(Construct(ty.Of(s)))}, {});
+    Func("f", utils::Empty, ty.Of(s), utils::Vector{Return(Construct(ty.Of(s)))}, utils::Empty);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -62,7 +62,7 @@
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableFromGlobal) {
-    auto* s = Structure("S", {Member("a", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
 
     GlobalVar("g", ty.Of(s), ast::StorageClass::kPrivate);
 
@@ -74,7 +74,7 @@
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalAlias) {
-    auto* s = Structure("S", {Member("a", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
     auto* a = Alias("A", ty.Of(s));
     GlobalVar("g", ty.Of(a), ast::StorageClass::kPrivate);
 
@@ -86,8 +86,8 @@
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalStruct) {
-    auto* s = Structure("S", {Member("a", ty.f32())});
-    auto* o = Structure("O", {Member("a", ty.Of(s))});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
+    auto* o = Structure("O", utils::Vector{Member("a", ty.Of(s))});
     GlobalVar("g", ty.Of(o), ast::StorageClass::kPrivate);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -98,7 +98,7 @@
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalArray) {
-    auto* s = Structure("S", {Member("a", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
     auto* a = ty.array(ty.Of(s), 3_u);
     GlobalVar("g", a, ast::StorageClass::kPrivate);
 
@@ -110,7 +110,7 @@
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableFromLocal) {
-    auto* s = Structure("S", {Member("a", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
 
     WrapInFunction(Var("g", ty.Of(s)));
 
@@ -122,7 +122,7 @@
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalAlias) {
-    auto* s = Structure("S", {Member("a", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
     auto* a = Alias("A", ty.Of(s));
     WrapInFunction(Var("g", ty.Of(a)));
 
@@ -134,8 +134,8 @@
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalStruct) {
-    auto* s = Structure("S", {Member("a", ty.f32())});
-    auto* o = Structure("O", {Member("a", ty.Of(s))});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
+    auto* o = Structure("O", utils::Vector{Member("a", ty.Of(s))});
     WrapInFunction(Var("g", ty.Of(o)));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -146,7 +146,7 @@
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalArray) {
-    auto* s = Structure("S", {Member("a", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
     auto* a = ty.array(ty.Of(s), 3_u);
     WrapInFunction(Var("g", a));
 
@@ -158,14 +158,14 @@
 }
 
 TEST_F(ResolverStorageClassUseTest, StructMultipleStorageClassUses) {
-    auto* s = Structure("S", {Member("a", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
     GlobalVar("x", ty.Of(s), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
     GlobalVar("y", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(0u),
               });
diff --git a/src/tint/resolver/type_constructor_validation_test.cc b/src/tint/resolver/type_constructor_validation_test.cc
index fe1973b..33c15f3 100644
--- a/src/tint/resolver/type_constructor_validation_test.cc
+++ b/src/tint/resolver/type_constructor_validation_test.cc
@@ -186,8 +186,8 @@
 
     Enable(ast::Extension::kF16);
 
-    Func("foo", {}, params.create_rhs_ast_type(*this),
-         {Return(Construct(params.create_rhs_ast_type(*this)))}, {});
+    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"));
     // Self-assign 'a' to force the expression to be resolved so we can test its
@@ -2474,10 +2474,10 @@
 
     const std::string element_type_name = param.get_element_type_name();
     std::stringstream args_tys;
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns - 1; i++) {
         auto* vec_type = param.create_column_ast_type(*this);
-        args.push_back(Construct(vec_type));
+        args.Push(Construct(vec_type));
         if (i > 0) {
             args_tys << ", ";
         }
@@ -2503,9 +2503,9 @@
 
     const std::string element_type_name = param.get_element_type_name();
     std::stringstream args_tys;
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns * param.rows - 1; i++) {
-        args.push_back(Construct(param.create_element_ast_type(*this)));
+        args.Push(Construct(param.create_element_ast_type(*this)));
         if (i > 0) {
             args_tys << ", ";
         }
@@ -2531,10 +2531,10 @@
 
     const std::string element_type_name = param.get_element_type_name();
     std::stringstream args_tys;
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns + 1; i++) {
         auto* vec_type = param.create_column_ast_type(*this);
-        args.push_back(Construct(vec_type));
+        args.Push(Construct(vec_type));
         if (i > 0) {
             args_tys << ", ";
         }
@@ -2560,9 +2560,9 @@
 
     const std::string element_type_name = param.get_element_type_name();
     std::stringstream args_tys;
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns * param.rows + 1; i++) {
-        args.push_back(Construct(param.create_element_ast_type(*this)));
+        args.Push(Construct(param.create_element_ast_type(*this)));
         if (i > 0) {
             args_tys << ", ";
         }
@@ -2587,10 +2587,10 @@
     Enable(ast::Extension::kF16);
 
     std::stringstream args_tys;
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns; i++) {
         auto* vec_type = ty.vec<u32>(param.rows);
-        args.push_back(Construct(vec_type));
+        args.Push(Construct(vec_type));
         if (i > 0) {
             args_tys << ", ";
         }
@@ -2615,9 +2615,9 @@
     Enable(ast::Extension::kF16);
 
     std::stringstream args_tys;
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns; i++) {
-        args.push_back(Expr(1_u));
+        args.Push(Expr(1_u));
         if (i > 0) {
             args_tys << ", ";
         }
@@ -2648,10 +2648,10 @@
 
     const std::string element_type_name = param.get_element_type_name();
     std::stringstream args_tys;
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns; i++) {
         auto* valid_vec_type = param.create_column_ast_type(*this);
-        args.push_back(Construct(valid_vec_type));
+        args.Push(Construct(valid_vec_type));
         if (i > 0) {
             args_tys << ", ";
         }
@@ -2659,7 +2659,7 @@
     }
     const size_t kInvalidLoc = 2 * (param.columns - 1);
     auto* invalid_vec_type = ty.vec(param.create_element_ast_type(*this), param.rows - 1);
-    args.push_back(Construct(Source{{12, kInvalidLoc}}, invalid_vec_type));
+    args.Push(Construct(Source{{12, kInvalidLoc}}, invalid_vec_type));
     args_tys << ", vec" << (param.rows - 1) << "<" + element_type_name + ">";
 
     auto* matrix_type = param.create_mat_ast_type(*this);
@@ -2686,17 +2686,17 @@
 
     const std::string element_type_name = param.get_element_type_name();
     std::stringstream args_tys;
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns; i++) {
         auto* valid_vec_type = param.create_column_ast_type(*this);
-        args.push_back(Construct(valid_vec_type));
+        args.Push(Construct(valid_vec_type));
         if (i > 0) {
             args_tys << ", ";
         }
         args_tys << "vec" << param.rows << "<" + element_type_name + ">";
     }
     auto* invalid_vec_type = ty.vec(param.create_element_ast_type(*this), param.rows + 1);
-    args.push_back(Construct(invalid_vec_type));
+    args.Push(Construct(invalid_vec_type));
     args_tys << ", vec" << (param.rows + 1) << "<" + element_type_name + ">";
 
     auto* matrix_type = param.create_mat_ast_type(*this);
@@ -2731,10 +2731,10 @@
 
     Enable(ast::Extension::kF16);
 
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 4> args;
     for (uint32_t i = 0; i < param.columns; i++) {
         auto* vec_type = param.create_column_ast_type(*this);
-        args.push_back(Construct(vec_type));
+        args.Push(Construct(vec_type));
     }
 
     auto* matrix_type = param.create_mat_ast_type(*this);
@@ -2752,9 +2752,9 @@
 
     Enable(ast::Extension::kF16);
 
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 16> args;
     for (uint32_t i = 0; i < param.columns * param.rows; i++) {
-        args.push_back(Construct(param.create_element_ast_type(*this)));
+        args.Push(Construct(param.create_element_ast_type(*this)));
     }
 
     auto* matrix_type = param.create_mat_ast_type(*this);
@@ -2775,10 +2775,10 @@
     auto* elem_type_alias = Alias("ElemType", param.create_element_ast_type(*this));
 
     std::stringstream args_tys;
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 4> args;
     for (uint32_t i = 0; i < param.columns; i++) {
         auto* vec_type = ty.vec(ty.u32(), param.rows);
-        args.push_back(Construct(vec_type));
+        args.Push(Construct(vec_type));
         if (i > 0) {
             args_tys << ", ";
         }
@@ -2804,10 +2804,10 @@
 
     auto* elem_type_alias = Alias("ElemType", param.create_element_ast_type(*this));
 
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns; i++) {
         auto* vec_type = param.create_column_ast_type(*this);
-        args.push_back(Construct(vec_type));
+        args.Push(Construct(vec_type));
     }
 
     auto* matrix_type = ty.mat(ty.Of(elem_type_alias), param.columns, param.rows);
@@ -2837,9 +2837,9 @@
     auto* vec_type = param.create_column_ast_type(*this);
     auto* vec_alias = Alias("ColVectorAlias", vec_type);
 
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 4> args;
     for (uint32_t i = 0; i < param.columns; i++) {
-        args.push_back(Construct(ty.Of(vec_alias)));
+        args.Push(Construct(ty.Of(vec_alias)));
     }
 
     auto* tc = Construct(Source{}, matrix_type, std::move(args));
@@ -2857,10 +2857,10 @@
     auto* u32_type_alias = Alias("UnsignedInt", ty.u32());
 
     std::stringstream args_tys;
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 4> args;
     for (uint32_t i = 0; i < param.columns; i++) {
         auto* vec_type = ty.vec(ty.Of(u32_type_alias), param.rows);
-        args.push_back(Construct(vec_type));
+        args.Push(Construct(vec_type));
         if (i > 0) {
             args_tys << ", ";
         }
@@ -2882,10 +2882,10 @@
 
     auto* elem_type_alias = Alias("ElemType", param.create_element_ast_type(*this));
 
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 4> args;
     for (uint32_t i = 0; i < param.columns; i++) {
         auto* vec_type = ty.vec(ty.Of(elem_type_alias), param.rows);
-        args.push_back(Construct(vec_type));
+        args.Push(Construct(vec_type));
     }
 
     auto* matrix_type = param.create_mat_ast_type(*this);
@@ -2900,9 +2900,9 @@
 
     Enable(ast::Extension::kF16);
 
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns; i++) {
-        args.push_back(Construct(param.create_column_ast_type(*this)));
+        args.Push(Construct(param.create_column_ast_type(*this)));
     }
 
     auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
@@ -2917,9 +2917,9 @@
 
     Enable(ast::Extension::kF16);
 
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.rows * param.columns; i++) {
-        args.push_back(param.create_element_ast_value(*this, static_cast<double>(i)));
+        args.Push(param.create_element_ast_value(*this, static_cast<double>(i)));
     }
 
     auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
@@ -2937,17 +2937,17 @@
     err << "12:34 error: no matching constructor for mat" << param.columns << "x" << param.rows
         << "(";
 
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns; i++) {
         if (i > 0) {
             err << ", ";
         }
         if (i == 1) {
             // Odd one out
-            args.push_back(Construct(ty.vec<i32>(param.rows)));
+            args.Push(Construct(ty.vec<i32>(param.rows)));
             err << "vec" << param.rows << "<i32>";
         } else {
-            args.push_back(Construct(param.create_column_ast_type(*this)));
+            args.Push(Construct(param.create_column_ast_type(*this)));
             err << "vec" << param.rows << "<" + param.get_element_type_name() + ">";
         }
     }
@@ -2968,16 +2968,16 @@
     err << "12:34 error: no matching constructor for mat" << param.columns << "x" << param.rows
         << "(";
 
-    ast::ExpressionList args;
+    utils::Vector<const ast::Expression*, 16> args;
     for (uint32_t i = 0; i < param.rows * param.columns; i++) {
         if (i > 0) {
             err << ", ";
         }
         if (i == 3) {
-            args.push_back(Expr(static_cast<i32>(i)));  // The odd one out
+            args.Push(Expr(static_cast<i32>(i)));  // The odd one out
             err << "i32";
         } else {
-            args.push_back(param.create_element_ast_value(*this, static_cast<double>(i)));
+            args.Push(param.create_element_ast_value(*this, static_cast<double>(i)));
             err << param.get_element_type_name();
         }
     }
@@ -3054,14 +3054,14 @@
 
     Enable(ast::Extension::kF16);
 
-    ast::StructMemberList members;
-    ast::ExpressionList values;
+    utils::Vector<const ast::StructMember*, 16> members;
+    utils::Vector<const ast::Expression*, 16> values;
     for (uint32_t i = 0; i < N; i++) {
         auto* struct_type = str_params.ast(*this);
-        members.push_back(Member("member_" + std::to_string(i), struct_type));
+        members.Push(Member("member_" + std::to_string(i), struct_type));
         if (i < N - 1) {
             auto* ctor_value_expr = str_params.expr(*this, 0);
-            values.push_back(ctor_value_expr);
+            values.Push(ctor_value_expr);
         }
     }
     auto* s = Structure("s", members);
@@ -3079,15 +3079,15 @@
 
     Enable(ast::Extension::kF16);
 
-    ast::StructMemberList members;
-    ast::ExpressionList values;
+    utils::Vector<const ast::StructMember*, 16> members;
+    utils::Vector<const ast::Expression*, 8> values;
     for (uint32_t i = 0; i < N + 1; i++) {
         if (i < N) {
             auto* struct_type = str_params.ast(*this);
-            members.push_back(Member("member_" + std::to_string(i), struct_type));
+            members.Push(Member("member_" + std::to_string(i), struct_type));
         }
         auto* ctor_value_expr = str_params.expr(*this, 0);
-        values.push_back(ctor_value_expr);
+        values.Push(ctor_value_expr);
     }
     auto* s = Structure("s", members);
     auto* tc = Construct(Source{{12, 34}}, ty.Of(s), values);
@@ -3116,17 +3116,17 @@
         return;
     }
 
-    ast::StructMemberList members;
-    ast::ExpressionList values;
+    utils::Vector<const ast::StructMember*, 16> members;
+    utils::Vector<const ast::Expression*, 8> values;
     // make the last value of the constructor to have a different type
     uint32_t constructor_value_with_different_type = N - 1;
     for (uint32_t i = 0; i < N; i++) {
         auto* struct_type = str_params.ast(*this);
-        members.push_back(Member("member_" + std::to_string(i), struct_type));
+        members.Push(Member("member_" + std::to_string(i), struct_type));
         auto* ctor_value_expr = (i == constructor_value_with_different_type)
                                     ? ctor_params.expr(*this, 0)
                                     : str_params.expr(*this, 0);
-        values.push_back(ctor_value_expr);
+        values.Push(ctor_value_expr);
     }
     auto* s = Structure("s", members);
     auto* tc = Construct(ty.Of(s), values);
@@ -3149,12 +3149,12 @@
 
 TEST_F(ResolverTypeConstructorValidationTest, Struct_Nested) {
     auto* inner_m = Member("m", ty.i32());
-    auto* inner_s = Structure("inner_s", {inner_m});
+    auto* inner_s = Structure("inner_s", utils::Vector{inner_m});
 
     auto* m0 = Member("m0", ty.i32());
     auto* m1 = Member("m1", ty.Of(inner_s));
     auto* m2 = Member("m2", ty.i32());
-    auto* s = Structure("s", {m0, m1, m2});
+    auto* s = Structure("s", utils::Vector{m0, m1, m2});
 
     auto* tc = Construct(Source{{12, 34}}, ty.Of(s), 1_i, 1_i, 1_i);
     WrapInFunction(tc);
@@ -3166,14 +3166,14 @@
 
 TEST_F(ResolverTypeConstructorValidationTest, Struct) {
     auto* m = Member("m", ty.i32());
-    auto* s = Structure("MyInputs", {m});
+    auto* s = Structure("MyInputs", utils::Vector{m});
     auto* tc = Construct(Source{{12, 34}}, ty.Of(s));
     WrapInFunction(tc);
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(ResolverTypeConstructorValidationTest, Struct_Empty) {
-    auto* str = Structure("S", {
+    auto* str = Structure("S", utils::Vector{
                                    Member("a", ty.i32()),
                                    Member("b", ty.f32()),
                                    Member("c", ty.vec3<i32>()),
@@ -3200,7 +3200,7 @@
 }
 
 TEST_F(ResolverTypeConstructorValidationTest, NonConstructibleType_AtomicStructMember) {
-    auto* str = Structure("S", {Member("a", ty.atomic(ty.i32()))});
+    auto* str = Structure("S", utils::Vector{Member("a", ty.atomic(ty.i32()))});
     WrapInFunction(Assign(Phony(), Construct(Source{{12, 34}}, ty.Of(str))));
 
     EXPECT_FALSE(r()->Resolve());
diff --git a/src/tint/resolver/type_validation_test.cc b/src/tint/resolver/type_validation_test.cc
index e746e51..d7c2e64 100644
--- a/src/tint/resolver/type_validation_test.cc
+++ b/src/tint/resolver/type_validation_test.cc
@@ -75,7 +75,10 @@
 
 TEST_F(ResolverTypeValidationTest, GlobalOverrideNoConstructor_Pass) {
     // @id(0) override a :i32;
-    Override(Source{{12, 34}}, "a", ty.i32(), nullptr, ast::AttributeList{Id(0)});
+    Override(Source{{12, 34}}, "a", ty.i32(), nullptr,
+             utils::Vector{
+                 Id(0),
+             });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -111,8 +114,8 @@
     // }
     // var a: f32 = 2.1;
 
-    Func("my_func", {}, ty.void_(),
-         {
+    Func("my_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2_f))),
          });
 
@@ -163,14 +166,14 @@
 
     auto* var1 = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(1_f));
 
-    Func("func0", {}, ty.void_(),
-         {
+    Func("func0", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Source{{12, 34}}, var0),
              Return(),
          });
 
-    Func("func1", {}, ty.void_(),
-         {
+    Func("func1", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Source{{13, 34}}, var1),
              Return(),
          });
@@ -383,11 +386,11 @@
 
     auto* var = Var(Source{{12, 34}}, "a", ty.array<i32>(), ast::StorageClass::kNone);
 
-    Func("func", {}, ty.void_(),
-         {
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kVertex),
          });
 
@@ -402,7 +405,9 @@
     //   a: vec3;
     // };
 
-    Structure("S", {Member("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3u))});
+    Structure("S", utils::Vector{
+                       Member("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3u)),
+                   });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
@@ -412,7 +417,9 @@
     // struct S {
     //   a: mat3x3;
     // };
-    Structure("S", {Member("a", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3u, 3u))});
+    Structure("S", utils::Vector{
+                       Member("a", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3u, 3u)),
+                   });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
@@ -425,13 +432,11 @@
     // };
 
     Structure(Source{{12, 34}}, "Foo",
-              {
+              utils::Vector{
                   Member("a", ty.array<f32, 0x20000000>()),
                   Member("b", ty.array<f32, 0x20000000>()),
               });
 
-    WrapInFunction();
-
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               "12:34 error: struct size (0x100000000) must not exceed 0xffffffff bytes");
@@ -444,14 +449,12 @@
     //   c: f32;
     // };
 
-    Structure("Foo", {
+    Structure("Foo", utils::Vector{
                          Member("a", ty.array<f32, 0x3fffffff>()),
                          Member("b", ty.f32()),
                          Member(Source{{12, 34}}, "c", ty.f32()),
                      });
 
-    WrapInFunction();
-
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               "12:34 error: struct member offset (0x100000000) must not exceed 0xffffffff bytes");
@@ -463,13 +466,11 @@
     //   rt: array<f32>;
     // };
 
-    Structure("Foo", {
+    Structure("Foo", utils::Vector{
                          Member("vf", ty.f32()),
                          Member("rt", ty.array<f32>()),
                      });
 
-    WrapInFunction();
-
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
@@ -478,7 +479,9 @@
     //   rt : array<array<f32>, 4u>;
     // };
 
-    Structure("Foo", {Member("rt", ty.array(Source{{12, 34}}, ty.array<f32>(), 4_u))});
+    Structure("Foo", utils::Vector{
+                         Member("rt", ty.array(Source{{12, 34}}, ty.array<f32>(), 4_u)),
+                     });
 
     EXPECT_FALSE(r()->Resolve()) << r()->error();
     EXPECT_EQ(r()->error(),
@@ -491,7 +494,9 @@
     // };
     // var<private> a : array<Foo, 4>;
 
-    auto* foo = Structure("Foo", {Member("rt", ty.array<f32>())});
+    auto* foo = Structure("Foo", utils::Vector{
+                                     Member("rt", ty.array<f32>()),
+                                 });
     GlobalVar("v", ty.array(Source{{12, 34}}, ty.Of(foo), 4_u), ast::StorageClass::kPrivate);
 
     EXPECT_FALSE(r()->Resolve()) << r()->error();
@@ -507,8 +512,12 @@
     //   inner : Foo;
     // };
 
-    auto* foo = Structure("Foo", {Member("rt", ty.array<f32>())});
-    Structure("Outer", {Member(Source{{12, 34}}, "inner", ty.Of(foo))});
+    auto* foo = Structure("Foo", utils::Vector{
+                                     Member("rt", ty.array<f32>()),
+                                 });
+    Structure("Outer", utils::Vector{
+                           Member(Source{{12, 34}}, "inner", ty.Of(foo)),
+                       });
 
     EXPECT_FALSE(r()->Resolve()) << r()->error();
     EXPECT_EQ(r()->error(),
@@ -522,13 +531,11 @@
     //   vf: f32;
     // };
 
-    Structure("Foo", {
+    Structure("Foo", utils::Vector{
                          Member(Source{{12, 34}}, "rt", ty.array<f32>()),
                          Member("vf", ty.f32()),
                      });
 
-    WrapInFunction();
-
     EXPECT_FALSE(r()->Resolve()) << r()->error();
     EXPECT_EQ(r()->error(),
               R"(12:34 error: runtime arrays may only appear as the last member of a struct)");
@@ -561,16 +568,16 @@
 
     auto* param = Param(Source{{12, 34}}, "a", ty.array<i32>());
 
-    Func("func", {param}, ty.void_(),
-         {
+    Func("func", utils::Vector{param}, ty.void_(),
+         utils::Vector{
              Return(),
          });
 
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kVertex),
          });
 
@@ -586,8 +593,8 @@
     auto* param =
         Param(Source{{12, 34}}, "a", ty.pointer(ty.array<i32>(), ast::StorageClass::kWorkgroup));
 
-    Func("func", {param}, ty.void_(),
-         {
+    Func("func", utils::Vector{param}, ty.void_(),
+         utils::Vector{
              Return(),
          });
 
@@ -605,13 +612,11 @@
     //}
 
     auto* alias = Alias("RTArr", ty.array<u32>());
-    Structure("s", {
+    Structure("s", utils::Vector{
                        Member(Source{{12, 34}}, "b", ty.Of(alias)),
                        Member("a", ty.u32()),
                    });
 
-    WrapInFunction();
-
     EXPECT_FALSE(r()->Resolve()) << r()->error();
     EXPECT_EQ(r()->error(),
               "12:34 error: runtime arrays may only appear as the last member of a struct");
@@ -625,13 +630,11 @@
     //}
 
     auto* alias = Alias("RTArr", ty.array<u32>());
-    Structure("s", {
+    Structure("s", utils::Vector{
                        Member("a", ty.u32()),
                        Member("b", ty.Of(alias)),
                    });
 
-    WrapInFunction();
-
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
@@ -659,7 +662,7 @@
 TEST_F(ResolverTypeValidationTest, FunctionAsType) {
     // fn f() {}
     // var<private> v : f;
-    Func("f", {}, ty.void_(), {});
+    Func("f", utils::Empty, ty.void_(), {});
     GlobalVar("v", ty.type_name("f"), ast::StorageClass::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
@@ -761,7 +764,10 @@
 TEST_P(SampledTextureDimensionTest, All) {
     auto& params = GetParam();
     GlobalVar(Source{{12, 34}}, "a", ty.sampled_texture(params.dim, ty.i32()),
-              ast::StorageClass::kNone, nullptr, ast::AttributeList{GroupAndBinding(0, 0)});
+              ast::StorageClass::kNone, nullptr,
+              utils::Vector{
+                  GroupAndBinding(0, 0),
+              });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -779,7 +785,10 @@
 TEST_P(MultisampledTextureDimensionTest, All) {
     auto& params = GetParam();
     GlobalVar("a", ty.multisampled_texture(Source{{12, 34}}, params.dim, ty.i32()),
-              ast::StorageClass::kNone, nullptr, ast::AttributeList{GroupAndBinding(0, 0)});
+              ast::StorageClass::kNone, nullptr,
+              utils::Vector{
+                  GroupAndBinding(0, 0),
+              });
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -832,7 +841,10 @@
     GlobalVar(
         "a",
         ty.sampled_texture(Source{{12, 34}}, ast::TextureDimension::k2d, params.type_func(*this)),
-        ast::StorageClass::kNone, nullptr, ast::AttributeList{GroupAndBinding(0, 0)});
+        ast::StorageClass::kNone, nullptr,
+        utils::Vector{
+            GroupAndBinding(0, 0),
+        });
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -851,7 +863,10 @@
     GlobalVar("a",
               ty.multisampled_texture(Source{{12, 34}}, ast::TextureDimension::k2d,
                                       params.type_func(*this)),
-              ast::StorageClass::kNone, nullptr, ast::AttributeList{GroupAndBinding(0, 0)});
+              ast::StorageClass::kNone, nullptr,
+              utils::Vector{
+                  GroupAndBinding(0, 0),
+              });
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -890,7 +905,10 @@
     auto* st = ty.storage_texture(Source{{12, 34}}, params.dim, ast::TexelFormat::kR32Uint,
                                   ast::Access::kWrite);
 
-    GlobalVar("a", st, ast::StorageClass::kNone, ast::AttributeList{GroupAndBinding(0, 0)});
+    GlobalVar("a", st, ast::StorageClass::kNone,
+              utils::Vector{
+                  GroupAndBinding(0, 0),
+              });
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -940,17 +958,29 @@
 
     auto* st_a = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d, params.format,
                                     ast::Access::kWrite);
-    GlobalVar("a", st_a, ast::StorageClass::kNone, ast::AttributeList{GroupAndBinding(0, 0)});
+    GlobalVar("a", st_a, ast::StorageClass::kNone,
+              utils::Vector{
+                  GroupAndBinding(0, 0),
+              });
 
     auto* st_b = ty.storage_texture(ast::TextureDimension::k2d, params.format, ast::Access::kWrite);
-    GlobalVar("b", st_b, ast::StorageClass::kNone, ast::AttributeList{GroupAndBinding(0, 1)});
+    GlobalVar("b", st_b, ast::StorageClass::kNone,
+              utils::Vector{
+                  GroupAndBinding(0, 1),
+              });
 
     auto* st_c =
         ty.storage_texture(ast::TextureDimension::k2dArray, params.format, ast::Access::kWrite);
-    GlobalVar("c", st_c, ast::StorageClass::kNone, ast::AttributeList{GroupAndBinding(0, 2)});
+    GlobalVar("c", st_c, ast::StorageClass::kNone,
+              utils::Vector{
+                  GroupAndBinding(0, 2),
+              });
 
     auto* st_d = ty.storage_texture(ast::TextureDimension::k3d, params.format, ast::Access::kWrite);
-    GlobalVar("d", st_d, ast::StorageClass::kNone, ast::AttributeList{GroupAndBinding(0, 3)});
+    GlobalVar("d", st_d, ast::StorageClass::kNone,
+              utils::Vector{
+                  GroupAndBinding(0, 3),
+              });
 
     if (params.is_valid) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -974,7 +1004,10 @@
     auto* st = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
                                   ast::TexelFormat::kR32Uint, ast::Access::kUndefined);
 
-    GlobalVar("a", st, ast::StorageClass::kNone, ast::AttributeList{GroupAndBinding(0, 0)});
+    GlobalVar("a", st, ast::StorageClass::kNone,
+              utils::Vector{
+                  GroupAndBinding(0, 0),
+              });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: storage texture missing access control");
@@ -988,7 +1021,9 @@
                                   ast::TexelFormat::kR32Uint, ast::Access::kReadWrite);
 
     GlobalVar("a", st, ast::StorageClass::kNone, nullptr,
-              ast::AttributeList{GroupAndBinding(0, 0)});
+              utils::Vector{
+                  GroupAndBinding(0, 0),
+              });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1003,7 +1038,9 @@
                                   ast::TexelFormat::kR32Uint, ast::Access::kRead);
 
     GlobalVar("a", st, ast::StorageClass::kNone, nullptr,
-              ast::AttributeList{GroupAndBinding(0, 0)});
+              utils::Vector{
+                  GroupAndBinding(0, 0),
+              });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -1018,7 +1055,9 @@
                                   ast::Access::kWrite);
 
     GlobalVar("a", st, ast::StorageClass::kNone, nullptr,
-              ast::AttributeList{GroupAndBinding(0, 0)});
+              utils::Vector{
+                  GroupAndBinding(0, 0),
+              });
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
diff --git a/src/tint/resolver/uniformity.cc b/src/tint/resolver/uniformity.cc
index 905fc0e..9ccb7ef 100644
--- a/src/tint/resolver/uniformity.cc
+++ b/src/tint/resolver/uniformity.cc
@@ -152,8 +152,8 @@
         }
 
         // Create nodes for parameters.
-        parameters.resize(func->params.size());
-        for (size_t i = 0; i < func->params.size(); i++) {
+        parameters.resize(func->params.Length());
+        for (size_t i = 0; i < func->params.Length(); i++) {
             auto* param = func->params[i];
             auto param_name = builder->Symbols().NameFor(param->symbol);
             auto* sem = builder->Sem().Get<sem::Parameter>(param);
@@ -349,7 +349,7 @@
 
             // Set the parameter tag to ParameterRequiredToBeUniform for each parameter node that
             // was reachable.
-            for (size_t i = 0; i < func->params.size(); i++) {
+            for (size_t i = 0; i < func->params.Length(); i++) {
                 auto* param = func->params[i];
                 if (reachable.contains(current_function_->variables.Get(sem_.Get(param)))) {
                     current_function_->parameters[i].tag = ParameterRequiredToBeUniform;
@@ -367,7 +367,7 @@
 
             // Set the parameter tag to ParameterRequiredToBeUniformForSubsequentControlFlow for
             // each parameter node that was reachable.
-            for (size_t i = 0; i < func->params.size(); i++) {
+            for (size_t i = 0; i < func->params.Length(); i++) {
                 auto* param = func->params[i];
                 if (reachable.contains(current_function_->variables.Get(sem_.Get(param)))) {
                     current_function_->parameters[i].tag =
@@ -386,7 +386,7 @@
 
             // Set the parameter tag to ParameterRequiredToBeUniformForReturnValue for each
             // parameter node that was reachable.
-            for (size_t i = 0; i < func->params.size(); i++) {
+            for (size_t i = 0; i < func->params.Length(); i++) {
                 auto* param = func->params[i];
                 if (reachable.contains(current_function_->variables.Get(sem_.Get(param)))) {
                     current_function_->parameters[i].tag =
@@ -396,7 +396,7 @@
         }
 
         // Traverse the graph for each pointer parameter.
-        for (size_t i = 0; i < func->params.size(); i++) {
+        for (size_t i = 0; i < func->params.Length(); i++) {
             if (current_function_->parameters[i].pointer_return_value == nullptr) {
                 continue;
             }
@@ -411,7 +411,7 @@
             }
 
             // Check every other parameter to see if they feed into this parameter's final value.
-            for (size_t j = 0; j < func->params.size(); j++) {
+            for (size_t j = 0; j < func->params.Length(); j++) {
                 auto* param_source = sem_.Get<sem::Parameter>(func->params[j]);
                 if (reachable.contains(current_function_->parameters[j].init_value)) {
                     current_function_->parameters[i].pointer_param_output_sources.push_back(
@@ -847,6 +847,7 @@
                     return cfx;
                 }
             },
+
             [&](const ast::ReturnStatement* r) {
                 Node* cf_ret;
                 if (r->value) {
@@ -870,6 +871,7 @@
 
                 return cf_ret;
             },
+
             [&](const ast::SwitchStatement* s) {
                 auto* sem_switch = sem_.Get(s);
                 auto [cfx, v_cond] = ProcessExpression(cf, s->condition);
@@ -938,6 +940,7 @@
 
                 return cf_end ? cf_end : cf;
             },
+
             [&](const ast::VariableDeclStatement* decl) {
                 Node* node;
                 if (decl->variable->constructor) {
@@ -956,6 +959,11 @@
 
                 return cf;
             },
+
+            [&](const ast::StaticAssert*) {
+                return cf;  // No impact on uniformity
+            },
+
             [&](Default) {
                 TINT_ICE(Resolver, diagnostics_)
                     << "unknown statement type: " << std::string(stmt->TypeInfo().name);
@@ -1204,7 +1212,7 @@
         // Process call arguments
         Node* cf_last_arg = cf;
         std::vector<Node*> args;
-        for (size_t i = 0; i < call->args.size(); i++) {
+        for (size_t i = 0; i < call->args.Length(); i++) {
             auto [cf_i, arg_i] = ProcessExpression(cf_last_arg, call->args[i]);
 
             // Capture the index of this argument in a new node.
diff --git a/src/tint/resolver/uniformity_test.cc b/src/tint/resolver/uniformity_test.cc
index b335c27..bbf7a4b 100644
--- a/src/tint/resolver/uniformity_test.cc
+++ b/src/tint/resolver/uniformity_test.cc
@@ -3422,8 +3422,13 @@
 )";
 
     RunTest(src, false);
-    EXPECT_EQ(error_,
-              R"(test:14:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+    EXPECT_EQ(
+        error_,
+        R"(test:11:7 warning: use of deprecated language feature: fallthrough is set to be removed from WGSL. Case can accept multiple selectors if the existing case bodies are empty. default is not yet supported in a case selector list.
+      fallthrough;
+      ^^^^^^^^^^^
+
+test:14:7 warning: 'workgroupBarrier' must only be called from uniform control flow
       workgroupBarrier();
       ^^^^^^^^^^^^^^^^
 
@@ -3487,8 +3492,13 @@
 )";
 
     RunTest(src, false);
-    EXPECT_EQ(error_,
-              R"(test:14:9 warning: 'workgroupBarrier' must only be called from uniform control flow
+    EXPECT_EQ(
+        error_,
+        R"(test:10:7 warning: use of deprecated language feature: fallthrough is set to be removed from WGSL. Case can accept multiple selectors if the existing case bodies are empty. default is not yet supported in a case selector list.
+      fallthrough;
+      ^^^^^^^^^^^
+
+test:14:9 warning: 'workgroupBarrier' must only be called from uniform control flow
         workgroupBarrier();
         ^^^^^^^^^^^^^^^^
 
@@ -3541,32 +3551,6 @@
 )");
 }
 
-TEST_F(UniformityAnalysisTest, Switch_VarBecomesUniformInDifferentCase_WithFallthrough) {
-    std::string src = R"(
-@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
-@group(0) @binding(0) var<uniform> condition : i32;
-
-fn foo() {
-  var x = non_uniform;
-  switch (condition) {
-    case 0: {
-      x = 5;
-      fallthrough;
-    }
-    case 42: {
-      if (x == 0) {
-        workgroupBarrier();
-      }
-    }
-    default: {
-    }
-  }
-}
-)";
-
-    RunTest(src, true);
-}
-
 TEST_F(UniformityAnalysisTest, Switch_VarBecomesNonUniformInCase_BarrierAfter) {
     std::string src = R"(
 @group(0) @binding(0) var<storage, read_write> non_uniform : i32;
@@ -5299,18 +5283,18 @@
     //   ...
     //   *p254 = rhs;
     // }
-    ast::ParameterList params;
-    ast::StatementList foo_body;
+    utils::Vector<const ast::Parameter*, 8> params;
+    utils::Vector<const ast::Statement*, 8> foo_body;
     const ast::Expression* rhs_init = b.Deref("p0");
     for (int i = 1; i < 255; i++) {
         rhs_init = b.Add(rhs_init, b.Deref("p" + std::to_string(i)));
     }
-    foo_body.push_back(b.Decl(b.Let("rhs", nullptr, rhs_init)));
+    foo_body.Push(b.Decl(b.Let("rhs", nullptr, rhs_init)));
     for (int i = 0; i < 255; i++) {
-        params.push_back(
+        params.Push(
             b.Param("p" + std::to_string(i), ty.pointer(ty.i32(), ast::StorageClass::kFunction)));
         if (i > 0) {
-            foo_body.push_back(b.Assign(b.Deref("p" + std::to_string(i)), "rhs"));
+            foo_body.Push(b.Assign(b.Deref("p" + std::to_string(i)), "rhs"));
         }
     }
     b.Func("foo", std::move(params), ty.void_(), foo_body);
@@ -5328,18 +5312,17 @@
     //   }
     // }
     b.GlobalVar("non_uniform_global", ty.i32(), ast::StorageClass::kPrivate);
-    ast::StatementList main_body;
-    ast::ExpressionList args;
+    utils::Vector<const ast::Statement*, 8> main_body;
+    utils::Vector<const ast::Expression*, 8> args;
     for (int i = 0; i < 255; i++) {
         auto name = "v" + std::to_string(i);
-        main_body.push_back(b.Decl(b.Var(name, ty.i32())));
-        args.push_back(b.AddressOf(name));
+        main_body.Push(b.Decl(b.Var(name, ty.i32())));
+        args.Push(b.AddressOf(name));
     }
-    main_body.push_back(b.Assign("v0", "non_uniform_global"));
-    main_body.push_back(b.CallStmt(b.create<ast::CallExpression>(b.Expr("foo"), args)));
-    main_body.push_back(
-        b.If(b.Equal("v254", 0_i), b.Block(b.CallStmt(b.Call("workgroupBarrier")))));
-    b.Func("main", {}, ty.void_(), main_body);
+    main_body.Push(b.Assign("v0", "non_uniform_global"));
+    main_body.Push(b.CallStmt(b.create<ast::CallExpression>(b.Expr("foo"), args)));
+    main_body.Push(b.If(b.Equal("v254", 0_i), b.Block(b.CallStmt(b.Call("workgroupBarrier")))));
+    b.Func("main", utils::Empty, ty.void_(), main_body);
 
     // TODO(jrprice): Expect false when uniformity issues become errors.
     EXPECT_TRUE(RunTest(std::move(b))) << error_;
@@ -6539,15 +6522,15 @@
     //   }
     // }
     b.GlobalVar("v0", ty.i32(), ast::StorageClass::kPrivate, b.Expr(0_i));
-    ast::StatementList foo_body;
+    utils::Vector<const ast::Statement*, 8> foo_body;
     std::string v_last = "v0";
     for (int i = 1; i < 100000; i++) {
         auto v = "v" + std::to_string(i);
-        foo_body.push_back(b.Decl(b.Var(v, nullptr, b.Expr(v_last))));
+        foo_body.Push(b.Decl(b.Var(v, nullptr, b.Expr(v_last))));
         v_last = v;
     }
-    foo_body.push_back(b.If(b.Equal(v_last, 0_i), b.Block(b.CallStmt(b.Call("workgroupBarrier")))));
-    b.Func("foo", {}, ty.void_(), foo_body);
+    foo_body.Push(b.If(b.Equal(v_last, 0_i), b.Block(b.CallStmt(b.Call("workgroupBarrier")))));
+    b.Func("foo", utils::Empty, ty.void_(), foo_body);
 
     // TODO(jrprice): Expect false when uniformity issues become errors.
     EXPECT_TRUE(RunTest(std::move(b))) << error_;
diff --git a/src/tint/resolver/validation_test.cc b/src/tint/resolver/validation_test.cc
index 45c6d87..52d4c87 100644
--- a/src/tint/resolver/validation_test.cc
+++ b/src/tint/resolver/validation_test.cc
@@ -65,15 +65,15 @@
     GlobalVar("dst", ty.vec4<f32>(), ast::StorageClass::kPrivate);
     auto* stmt = Assign(Expr("dst"), Expr(Source{{3, 4}}, "wg"));
 
-    Func(Source{{9, 10}}, "f0", {}, ty.vec4<f32>(),
-         {
+    Func(Source{{9, 10}}, "f0", utils::Empty, ty.vec4<f32>(),
+         utils::Vector{
              stmt,
              Return(Expr("dst")),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kVertex),
          },
-         {
+         utils::Vector{
              Builtin(ast::BuiltinValue::kPosition),
          });
 
@@ -97,10 +97,15 @@
     GlobalVar("dst", ty.vec4<f32>(), ast::StorageClass::kPrivate);
     auto* stmt = Assign(Expr("dst"), Expr(Source{{3, 4}}, "wg"));
 
-    Func(Source{{5, 6}}, "f2", {}, ty.void_(), {stmt});
-    Func(Source{{7, 8}}, "f1", {}, ty.void_(), {CallStmt(Call("f2"))});
-    Func(Source{{9, 10}}, "f0", {}, ty.void_(), {CallStmt(Call("f1"))},
-         ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+    Func(Source{{5, 6}}, "f2", utils::Empty, ty.void_(), utils::Vector{stmt});
+    Func(Source{{7, 8}}, "f1", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("f2")),
+         });
+    Func(Source{{9, 10}}, "f0", utils::Empty, ty.void_(), utils::Vector{CallStmt(Call("f1"))},
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -153,7 +158,7 @@
 }
 
 TEST_F(ResolverValidationTest, Expr_DontCall_Function) {
-    Func("func", {}, ty.void_(), {}, {});
+    Func("func", utils::Empty, ty.void_(), utils::Empty, {});
     WrapInFunction(Expr(Source{{{3, 3}, {3, 8}}}, "func"));
 
     EXPECT_FALSE(r()->Resolve());
@@ -223,8 +228,8 @@
 
     GlobalVar("global_var", ty.f32(), ast::StorageClass::kPrivate, Expr(2.1_f));
 
-    Func("my_func", {}, ty.void_(),
-         {
+    Func("my_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Assign(Expr(Source{{12, 34}}, "global_var"), 3.14_f),
              Return(),
          });
@@ -297,8 +302,8 @@
 TEST_F(ResolverValidationTest, StorageClass_FunctionVariableWorkgroupClass) {
     auto* var = Var("var", ty.i32(), ast::StorageClass::kWorkgroup);
 
-    Func("func", {}, ty.void_(),
-         {
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
          });
 
@@ -311,8 +316,8 @@
 TEST_F(ResolverValidationTest, StorageClass_FunctionVariableI32) {
     auto* var = Var("s", ty.i32(), ast::StorageClass::kPrivate);
 
-    Func("func", {}, ty.void_(),
-         {
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
          });
 
@@ -325,7 +330,7 @@
 TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) {
     auto* t = ty.sampler(ast::SamplerKind::kSampler);
     GlobalVar(Source{{12, 34}}, "var", t, ast::StorageClass::kHandle,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -339,7 +344,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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -425,7 +430,11 @@
     auto* z = Expr(Source{{{3, 3}, {3, 8}}}, "z");
     auto* accessor_expr = MemberAccessor(star_p, z);
     auto* x = Var("x", ty.f32(), accessor_expr);
-    Func("func", {p}, ty.f32(), {Decl(x), Return(x)});
+    Func("func", utils::Vector{p}, ty.f32(),
+         utils::Vector{
+             Decl(x),
+             Return(x),
+         });
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
@@ -439,7 +448,11 @@
     auto* accessor_expr = MemberAccessor(p, z);
     auto* star_p = Deref(accessor_expr);
     auto* x = Var("x", ty.f32(), star_p);
-    Func("func", {p}, ty.f32(), {Decl(x), Return(x)});
+    Func("func", utils::Vector{p}, ty.f32(),
+         utils::Vector{
+             Decl(x),
+             Return(x),
+         });
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -806,8 +819,14 @@
     //   }
     // }
 
-    Func("MayDiscard", {}, ty.void_(), {If(true, Block(Discard()))});
-    Func("SomeFunc", {}, ty.void_(), {CallStmt(Call("MayDiscard"))});
+    Func("MayDiscard", utils::Empty, ty.void_(),
+         utils::Vector{
+             If(true, Block(Discard())),
+         });
+    Func("SomeFunc", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("MayDiscard")),
+         });
 
     WrapInFunction(Loop(         // outer loop
         Block(),                 //   outer loop block
@@ -926,8 +945,14 @@
     //   break;
     // }
 
-    Func("MayDiscard", {}, ty.void_(), {If(true, Block(Discard()))});
-    Func("F", {}, ty.void_(), {CallStmt(Call("MayDiscard"))});
+    Func("MayDiscard", utils::Empty, ty.void_(),
+         utils::Vector{
+             If(true, Block(Discard())),
+         });
+    Func("F", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("MayDiscard")),
+         });
 
     WrapInFunction(For(nullptr, nullptr,
                        Loop(Source{{56, 78}},                               //
@@ -1191,30 +1216,40 @@
 }
 
 TEST_F(ResolverValidationTest, StructMemberDuplicateName) {
-    Structure("S",
-              {Member(Source{{12, 34}}, "a", ty.i32()), Member(Source{{56, 78}}, "a", ty.i32())});
+    Structure("S", utils::Vector{
+                       Member(Source{{12, 34}}, "a", ty.i32()),
+                       Member(Source{{56, 78}}, "a", ty.i32()),
+                   });
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               "56:78 error: redefinition of 'a'\n12:34 note: previous definition "
               "is here");
 }
 TEST_F(ResolverValidationTest, StructMemberDuplicateNameDifferentTypes) {
-    Structure("S", {Member(Source{{12, 34}}, "a", ty.bool_()),
-                    Member(Source{{12, 34}}, "a", ty.vec3<f32>())});
+    Structure("S", utils::Vector{
+                       Member(Source{{12, 34}}, "a", ty.bool_()),
+                       Member(Source{{12, 34}}, "a", ty.vec3<f32>()),
+                   });
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               "12:34 error: redefinition of 'a'\n12:34 note: previous definition "
               "is here");
 }
 TEST_F(ResolverValidationTest, StructMemberDuplicateNamePass) {
-    Structure("S", {Member("a", ty.i32()), Member("b", ty.f32())});
-    Structure("S1", {Member("a", ty.i32()), Member("b", ty.f32())});
+    Structure("S", utils::Vector{
+                       Member("a", ty.i32()),
+                       Member("b", ty.f32()),
+                   });
+    Structure("S1", utils::Vector{
+                        Member("a", ty.i32()),
+                        Member("b", ty.f32()),
+                    });
     EXPECT_TRUE(r()->Resolve());
 }
 
 TEST_F(ResolverValidationTest, NonPOTStructMemberAlignAttribute) {
-    Structure("S", {
-                       Member("a", ty.f32(), {MemberAlign(Source{{12, 34}}, 3)}),
+    Structure("S", utils::Vector{
+                       Member("a", ty.f32(), utils::Vector{MemberAlign(Source{{12, 34}}, 3)}),
                    });
 
     EXPECT_FALSE(r()->Resolve());
@@ -1222,8 +1257,8 @@
 }
 
 TEST_F(ResolverValidationTest, ZeroStructMemberAlignAttribute) {
-    Structure("S", {
-                       Member("a", ty.f32(), {MemberAlign(Source{{12, 34}}, 0)}),
+    Structure("S", utils::Vector{
+                       Member("a", ty.f32(), utils::Vector{MemberAlign(Source{{12, 34}}, 0)}),
                    });
 
     EXPECT_FALSE(r()->Resolve());
@@ -1231,8 +1266,8 @@
 }
 
 TEST_F(ResolverValidationTest, ZeroStructMemberSizeAttribute) {
-    Structure("S", {
-                       Member("a", ty.f32(), {MemberSize(Source{{12, 34}}, 0)}),
+    Structure("S", utils::Vector{
+                       Member("a", ty.f32(), utils::Vector{MemberSize(Source{{12, 34}}, 0)}),
                    });
 
     EXPECT_FALSE(r()->Resolve());
@@ -1240,8 +1275,9 @@
 }
 
 TEST_F(ResolverValidationTest, OffsetAndSizeAttribute) {
-    Structure("S", {
-                       Member(Source{{12, 34}}, "a", ty.f32(), {MemberOffset(0), MemberSize(4)}),
+    Structure("S", utils::Vector{
+                       Member(Source{{12, 34}}, "a", ty.f32(),
+                              utils::Vector{MemberOffset(0), MemberSize(4)}),
                    });
 
     EXPECT_FALSE(r()->Resolve());
@@ -1251,8 +1287,9 @@
 }
 
 TEST_F(ResolverValidationTest, OffsetAndAlignAttribute) {
-    Structure("S", {
-                       Member(Source{{12, 34}}, "a", ty.f32(), {MemberOffset(0), MemberAlign(4)}),
+    Structure("S", utils::Vector{
+                       Member(Source{{12, 34}}, "a", ty.f32(),
+                              utils::Vector{MemberOffset(0), MemberAlign(4)}),
                    });
 
     EXPECT_FALSE(r()->Resolve());
@@ -1262,9 +1299,9 @@
 }
 
 TEST_F(ResolverValidationTest, OffsetAndAlignAndSizeAttribute) {
-    Structure("S", {
+    Structure("S", utils::Vector{
                        Member(Source{{12, 34}}, "a", ty.f32(),
-                              {MemberOffset(0), MemberAlign(4), MemberSize(4)}),
+                              utils::Vector{MemberOffset(0), MemberAlign(4), MemberSize(4)}),
                    });
 
     EXPECT_FALSE(r()->Resolve());
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index ec858c4..f3a02ed 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -395,7 +395,7 @@
 
     // Temporally forbid using f16 types in "uniform" and "storage" storage class.
     // TODO(tint:1473, tint:1502): Remove this error after f16 is supported in "uniform" and
-    // "storage" storage class.
+    // "storage" storage class but keep for "push_constant" storage class.
     if (Is<sem::F16>(sem::Type::DeepestElementOf(store_ty))) {
         AddError(
             "using f16 types in '" + utils::ToString(sc) + "' storage class is not implemented yet",
@@ -516,7 +516,19 @@
 }
 
 bool Validator::StorageClassLayout(const sem::Variable* var,
+                                   const ast::Extensions& enabled_extensions,
                                    ValidTypeStorageLayouts& layouts) const {
+    if (var->StorageClass() == ast::StorageClass::kPushConstant &&
+        !enabled_extensions.contains(ast::Extension::kChromiumExperimentalPushConstant) &&
+        IsValidationEnabled(var->Declaration()->attributes,
+                            ast::DisabledValidation::kIgnoreStorageClass)) {
+        AddError(
+            "use of variable storage class 'push_constant' requires enabling extension "
+            "'chromium_experimental_push_constant'",
+            var->Declaration()->source);
+        return false;
+    }
+
     if (auto* str = var->Type()->UnwrapRef()->As<sem::Struct>()) {
         if (!StorageClassLayout(str, var->StorageClass(), str->Declaration()->source, layouts)) {
             AddNote("see declaration of variable", var->Declaration()->source);
@@ -629,7 +641,7 @@
         },
         [&](const ast::Override*) { return Override(global, override_ids); },
         [&](const ast::Const*) {
-            if (!decl->attributes.empty()) {
+            if (!decl->attributes.IsEmpty()) {
                 AddError("attribute is not valid for module-scope 'const' declaration",
                          decl->attributes[0]->source);
                 return false;
@@ -1032,7 +1044,7 @@
         }
     }
 
-    if (decl->params.size() > 255) {
+    if (decl->params.Length() > 255) {
         AddError("functions may declare at most 255 parameters", decl->source);
         return false;
     }
@@ -1114,12 +1126,13 @@
     };
 
     // Inner lambda that is applied to a type and all of its members.
-    auto validate_entry_point_attributes_inner = [&](const ast::AttributeList& attrs,
+    auto validate_entry_point_attributes_inner = [&](utils::VectorRef<const ast::Attribute*> attrs,
                                                      const sem::Type* ty, Source source,
                                                      ParamOrRetType param_or_ret,
                                                      bool is_struct_member) {
         // Temporally forbid using f16 types in entry point IO.
-        // TODO(tint:1473, tint:1502): Remove this error after f16 is supported in entry point IO.
+        // TODO(tint:1473, tint:1502): Remove this error after f16 is supported in entry point
+        // IO.
         if (Is<sem::F16>(sem::Type::DeepestElementOf(ty))) {
             AddError("entry point IO of f16 types is not implemented yet", source);
             return false;
@@ -1257,8 +1270,9 @@
     };
 
     // Outer lambda for validating the entry point attributes for a type.
-    auto validate_entry_point_attributes = [&](const ast::AttributeList& attrs, const sem::Type* ty,
-                                               Source source, ParamOrRetType param_or_ret) {
+    auto validate_entry_point_attributes = [&](utils::VectorRef<const ast::Attribute*> attrs,
+                                               const sem::Type* ty, Source source,
+                                               ParamOrRetType param_or_ret) {
         if (!validate_entry_point_attributes_inner(attrs, ty, source, param_or_ret,
                                                    /*is_struct_member*/ false)) {
             return false;
@@ -1367,7 +1381,7 @@
     return true;
 }
 
-bool Validator::Statements(const ast::StatementList& stmts) const {
+bool Validator::Statements(utils::VectorRef<const ast::Statement*> stmts) const {
     for (auto* stmt : stmts) {
         if (!sem_.Get(stmt)->IsReachable()) {
             /// TODO(https://github.com/gpuweb/gpuweb/issues/2378): This may need to
@@ -1431,7 +1445,7 @@
                 return fail("break statement is not directly in if statement block",
                             stmt->Declaration()->source);
             }
-            if (block->Declaration()->statements.size() != 1) {
+            if (block->Declaration()->statements.Length() != 1) {
                 return fail("if statement block contains multiple statements",
                             block->Declaration()->source);
             }
@@ -1530,7 +1544,7 @@
         if (auto* c = As<sem::CaseStatement>(block->Parent())) {
             if (block->Declaration()->Last() == stmt->Declaration()) {
                 if (auto* s = As<sem::SwitchStatement>(c->Parent())) {
-                    if (c->Declaration() != s->Declaration()->body.back()) {
+                    if (c->Declaration() != s->Declaration()->body.Back()) {
                         return true;
                     }
                     AddError(
@@ -1737,8 +1751,8 @@
         return false;
     }
 
-    if (decl->args.size() != target->Parameters().Length()) {
-        bool more = decl->args.size() > target->Parameters().Length();
+    if (decl->args.Length() != target->Parameters().Length()) {
+        bool more = decl->args.Length() > target->Parameters().Length();
         AddError("too " + (more ? std::string("many") : std::string("few")) +
                      " arguments in call to '" + name + "', expected " +
                      std::to_string(target->Parameters().Length()) + ", got " +
@@ -1835,12 +1849,12 @@
         return false;
     }
 
-    if (ctor->args.size() > 0) {
-        if (ctor->args.size() != struct_type->Members().size()) {
-            std::string fm = ctor->args.size() < struct_type->Members().size() ? "few" : "many";
+    if (ctor->args.Length() > 0) {
+        if (ctor->args.Length() != struct_type->Members().size()) {
+            std::string fm = ctor->args.Length() < struct_type->Members().size() ? "few" : "many";
             AddError("struct constructor has too " + fm + " inputs: expected " +
                          std::to_string(struct_type->Members().size()) + ", found " +
-                         std::to_string(ctor->args.size()),
+                         std::to_string(ctor->args.Length()),
                      ctor->source);
             return false;
         }
@@ -1882,17 +1896,17 @@
     } else if (!elem_ty->IsConstructible()) {
         AddError("array constructor has non-constructible element type", ctor->source);
         return false;
-    } else if (!values.empty() && (values.size() != array_type->Count())) {
-        std::string fm = values.size() < array_type->Count() ? "few" : "many";
+    } else if (!values.IsEmpty() && (values.Length() != array_type->Count())) {
+        std::string fm = values.Length() < array_type->Count() ? "few" : "many";
         AddError("array constructor has too " + fm + " elements: expected " +
                      std::to_string(array_type->Count()) + ", found " +
-                     std::to_string(values.size()),
+                     std::to_string(values.Length()),
                  ctor->source);
         return false;
-    } else if (values.size() > array_type->Count()) {
+    } else if (values.Length() > array_type->Count()) {
         AddError("array constructor has too many elements: expected " +
                      std::to_string(array_type->Count()) + ", found " +
-                     std::to_string(values.size()),
+                     std::to_string(values.Length()),
                  ctor->source);
         return false;
     }
@@ -2000,6 +2014,73 @@
     return true;
 }
 
+bool Validator::PushConstants(const std::vector<sem::Function*>& entry_points) const {
+    for (auto* entry_point : entry_points) {
+        // State checked and modified by check_push_constant so that it remembers previously seen
+        // push_constant variables for an entry-point.
+        const sem::Variable* push_constant_var = nullptr;
+        const sem::Function* push_constant_func = nullptr;
+
+        auto check_push_constant = [&](const sem::Function* func, const sem::Function* ep) {
+            for (auto* var : func->DirectlyReferencedGlobals()) {
+                if (var->StorageClass() != ast::StorageClass::kPushConstant ||
+                    var == push_constant_var) {
+                    continue;
+                }
+
+                if (push_constant_var == nullptr) {
+                    push_constant_var = var;
+                    push_constant_func = func;
+                    continue;
+                }
+
+                AddError("entry point '" + symbols_.NameFor(ep->Declaration()->symbol) +
+                             "' uses two different 'push_constant' variables.",
+                         ep->Declaration()->source);
+                AddNote("first 'push_constant' variable declaration is here",
+                        var->Declaration()->source);
+                if (func != ep) {
+                    TraverseCallChain(diagnostics_, ep, func, [&](const sem::Function* f) {
+                        AddNote("called by function '" +
+                                    symbols_.NameFor(f->Declaration()->symbol) + "'",
+                                f->Declaration()->source);
+                    });
+                    AddNote("called by entry point '" +
+                                symbols_.NameFor(ep->Declaration()->symbol) + "'",
+                            ep->Declaration()->source);
+                }
+                AddNote("second 'push_constant' variable declaration is here",
+                        push_constant_var->Declaration()->source);
+                if (push_constant_func != ep) {
+                    TraverseCallChain(
+                        diagnostics_, ep, push_constant_func, [&](const sem::Function* f) {
+                            AddNote("called by function '" +
+                                        symbols_.NameFor(f->Declaration()->symbol) + "'",
+                                    f->Declaration()->source);
+                        });
+                    AddNote("called by entry point '" +
+                                symbols_.NameFor(ep->Declaration()->symbol) + "'",
+                            ep->Declaration()->source);
+                }
+                return false;
+            }
+
+            return true;
+        };
+
+        if (!check_push_constant(entry_point, entry_point)) {
+            return false;
+        }
+        for (auto* func : entry_point->TransitivelyCalledFunctions()) {
+            if (!check_push_constant(func, entry_point)) {
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
 bool Validator::Array(const sem::Array* arr, const Source& source) const {
     auto* el_ty = arr->ElemType();
 
@@ -2369,7 +2450,7 @@
     return true;
 }
 
-bool Validator::NoDuplicateAttributes(const ast::AttributeList& attributes) const {
+bool Validator::NoDuplicateAttributes(utils::VectorRef<const ast::Attribute*> attributes) const {
     std::unordered_map<const TypeInfo*, Source> seen;
     for (auto* d : attributes) {
         auto res = seen.emplace(&d->TypeInfo(), d->source);
@@ -2382,7 +2463,7 @@
     return true;
 }
 
-bool Validator::IsValidationDisabled(const ast::AttributeList& attributes,
+bool Validator::IsValidationDisabled(utils::VectorRef<const ast::Attribute*> attributes,
                                      ast::DisabledValidation validation) const {
     for (auto* attribute : attributes) {
         if (auto* dv = attribute->As<ast::DisableValidationAttribute>()) {
@@ -2394,7 +2475,7 @@
     return false;
 }
 
-bool Validator::IsValidationEnabled(const ast::AttributeList& attributes,
+bool Validator::IsValidationEnabled(utils::VectorRef<const ast::Attribute*> attributes,
                                     ast::DisabledValidation validation) const {
     return !IsValidationDisabled(attributes, validation);
 }
diff --git a/src/tint/resolver/validator.h b/src/tint/resolver/validator.h
index 385e020..57ac064 100644
--- a/src/tint/resolver/validator.h
+++ b/src/tint/resolver/validator.h
@@ -116,6 +116,11 @@
     /// @returns true on success, false otherwise.
     bool PipelineStages(const std::vector<sem::Function*>& entry_points) const;
 
+    /// Validates push_constant variables
+    /// @param entry_points the entry points to the module
+    /// @returns true on success, false otherwise.
+    bool PushConstants(const std::vector<sem::Function*>& entry_points) const;
+
     /// Validates aliases
     /// @param alias the alias to validate
     /// @returns true on success, false otherwise.
@@ -320,7 +325,7 @@
     /// Validates a list of statements
     /// @param stmts the statements to validate
     /// @returns true on success, false otherwise
-    bool Statements(const ast::StatementList& stmts) const;
+    bool Statements(utils::VectorRef<const ast::Statement*> stmts) const;
 
     /// Validates a storage texture
     /// @param t the texture to validate
@@ -417,7 +422,7 @@
     /// Validates there are no duplicate attributes
     /// @param attributes the list of attributes to validate
     /// @returns true on success, false otherwise.
-    bool NoDuplicateAttributes(const ast::AttributeList& attributes) const;
+    bool NoDuplicateAttributes(utils::VectorRef<const ast::Attribute*> attributes) const;
 
     /// Validates a storage class layout
     /// @param type the type to validate
@@ -433,15 +438,18 @@
     /// Validates a storage class layout
     /// @param var the variable to validate
     /// @param layouts previously validated storage layouts
+    /// @param enabled_extensions all the extensions declared in current module
     /// @returns true on success, false otherwise.
-    bool StorageClassLayout(const sem::Variable* var, ValidTypeStorageLayouts& layouts) const;
+    bool StorageClassLayout(const sem::Variable* var,
+                            const ast::Extensions& enabled_extensions,
+                            ValidTypeStorageLayouts& layouts) const;
 
     /// @returns true if the attribute list contains a
     /// ast::DisableValidationAttribute with the validation mode equal to
     /// `validation`
     /// @param attributes the attribute list to check
     /// @param validation the validation mode to check
-    bool IsValidationDisabled(const ast::AttributeList& attributes,
+    bool IsValidationDisabled(utils::VectorRef<const ast::Attribute*> attributes,
                               ast::DisabledValidation validation) const;
 
     /// @returns true if the attribute list does not contains a
@@ -449,7 +457,7 @@
     /// `validation`
     /// @param attributes the attribute list to check
     /// @param validation the validation mode to check
-    bool IsValidationEnabled(const ast::AttributeList& attributes,
+    bool IsValidationEnabled(utils::VectorRef<const ast::Attribute*> attributes,
                              ast::DisabledValidation validation) const;
 
   private:
diff --git a/src/tint/resolver/variable_test.cc b/src/tint/resolver/variable_test.cc
index 49c1f8b..cc4bd65 100644
--- a/src/tint/resolver/variable_test.cc
+++ b/src/tint/resolver/variable_test.cc
@@ -43,7 +43,7 @@
 
     Enable(ast::Extension::kF16);
 
-    auto* S = Structure("S", {Member("i", ty.i32())});
+    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);
@@ -54,8 +54,8 @@
     auto* s = Var("s", ty.Of(S), ast::StorageClass::kNone);
     auto* a = Var("a", ty.Of(A), ast::StorageClass::kNone);
 
-    Func("F", {}, ty.void_(),
-         {
+    Func("F", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(i),
              Decl(u),
              Decl(f),
@@ -108,7 +108,7 @@
 
     Enable(ast::Extension::kF16);
 
-    auto* S = Structure("S", {Member("i", ty.i32())});
+    auto* S = Structure("S", utils::Vector{Member("i", ty.i32())});
     auto* A = Alias("A", ty.Of(S));
 
     auto* i_c = Expr(1_i);
@@ -127,8 +127,8 @@
     auto* s = Var("s", ty.Of(S), ast::StorageClass::kNone, s_c);
     auto* a = Var("a", ty.Of(A), ast::StorageClass::kNone, a_c);
 
-    Func("F", {}, ty.void_(),
-         {
+    Func("F", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(i),
              Decl(u),
              Decl(f),
@@ -182,7 +182,7 @@
 
     auto* t = Alias("a", ty.i32());
     auto* v = Var("a", nullptr, Expr(false));
-    Func("F", {}, ty.void_(), {Decl(v)});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(v)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -201,9 +201,9 @@
     //   var a = true;
     // }
 
-    auto* t = Structure("a", {Member("m", ty.i32())});
+    auto* t = Structure("a", utils::Vector{Member("m", ty.i32())});
     auto* v = Var("a", nullptr, Expr(false));
-    Func("F", {}, ty.void_(), {Decl(v)});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(v)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -219,7 +219,7 @@
     // }
 
     auto* v = Var("a", nullptr, Expr(false));
-    auto* f = Func("a", {}, ty.void_(), {Decl(v)});
+    auto* f = Func("a", utils::Empty, ty.void_(), utils::Vector{Decl(v)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -240,7 +240,7 @@
 
     auto* g = GlobalVar("a", ty.i32(), ast::StorageClass::kPrivate);
     auto* v = Var("a", nullptr, Expr("a"));
-    Func("F", {}, ty.void_(), {Decl(v)});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(v)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -263,7 +263,7 @@
 
     auto* g = GlobalConst("a", ty.i32(), Expr(1_i));
     auto* v = Var("a", nullptr, Expr("a"));
-    Func("F", {}, ty.void_(), {Decl(v)});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(v)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -287,7 +287,7 @@
 
     auto* x = Var("a", ty.i32(), Expr(1_i));
     auto* y = Var("a", nullptr, Expr("a"));
-    Func("F", {}, ty.void_(), {Decl(x), Block(Decl(y))});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(x), Block(Decl(y))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -313,7 +313,7 @@
 
     auto* c = Const("a", ty.i32(), Expr(1_i));
     auto* v = Var("a", nullptr, Expr("a"));
-    Func("X", {}, ty.void_(), {Decl(c), Block(Decl(v))});
+    Func("X", utils::Empty, ty.void_(), utils::Vector{Decl(c), Block(Decl(v))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -339,7 +339,7 @@
 
     auto* l = Let("a", ty.i32(), Expr(1_i));
     auto* v = Var("a", nullptr, Expr("a"));
-    Func("X", {}, ty.void_(), {Decl(l), Block(Decl(v))});
+    Func("X", utils::Empty, ty.void_(), utils::Vector{Decl(l), Block(Decl(v))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -364,7 +364,7 @@
 
     auto* p = Param("a", ty.i32());
     auto* v = Var("a", nullptr, Expr("a"));
-    Func("X", {p}, ty.void_(), {Block(Decl(v))});
+    Func("X", utils::Vector{p}, ty.void_(), utils::Vector{Block(Decl(v))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -399,7 +399,7 @@
 
     Enable(ast::Extension::kF16);
 
-    auto* S = Structure("S", {Member("i", ty.i32())});
+    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);
 
@@ -421,8 +421,8 @@
     auto* a = Let("a", ty.Of(A), a_c);
     auto* p = Let("p", ty.pointer<i32>(ast::StorageClass::kFunction), p_c);
 
-    Func("F", {}, ty.void_(),
-         {
+    Func("F", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(v),
              Decl(i),
              Decl(u),
@@ -468,10 +468,10 @@
     // fn f() {
     //   let p = &s.inner.arr[4];
     // }
-    auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
-    auto* buf = Structure("S", {Member("inner", ty.Of(inner))});
+    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,
-                              ast::AttributeList{
+                              utils::Vector{
                                   create<ast::BindingAttribute>(0u),
                                   create<ast::GroupAttribute>(0u),
                               });
@@ -499,7 +499,7 @@
 
     auto* t = Alias("a", ty.i32());
     auto* l = Let("a", nullptr, Expr(false));
-    Func("F", {}, ty.void_(), {Decl(l)});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(l)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -518,9 +518,9 @@
     //   let a = false;
     // }
 
-    auto* t = Structure("a", {Member("m", ty.i32())});
+    auto* t = Structure("a", utils::Vector{Member("m", ty.i32())});
     auto* l = Let("a", nullptr, Expr(false));
-    Func("F", {}, ty.void_(), {Decl(l)});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(l)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -536,7 +536,7 @@
     // }
 
     auto* l = Let("a", nullptr, Expr(false));
-    auto* fb = Func("a", {}, ty.void_(), {Decl(l)});
+    auto* fb = Func("a", utils::Empty, ty.void_(), utils::Vector{Decl(l)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -557,7 +557,7 @@
 
     auto* g = GlobalVar("a", ty.i32(), ast::StorageClass::kPrivate);
     auto* l = Let("a", nullptr, Expr("a"));
-    Func("F", {}, ty.void_(), {Decl(l)});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(l)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -580,7 +580,7 @@
 
     auto* g = GlobalConst("a", ty.i32(), Expr(1_i));
     auto* l = Let("a", nullptr, Expr("a"));
-    Func("F", {}, ty.void_(), {Decl(l)});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(l)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -604,7 +604,7 @@
 
     auto* v = Var("a", ty.i32(), Expr(1_i));
     auto* l = Let("a", nullptr, Expr("a"));
-    Func("F", {}, ty.void_(), {Decl(v), Block(Decl(l))});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(v), Block(Decl(l))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -630,7 +630,7 @@
 
     auto* x = Const("a", ty.i32(), Expr(1_i));
     auto* y = Let("a", nullptr, Expr("a"));
-    Func("X", {}, ty.void_(), {Decl(x), Block(Decl(y))});
+    Func("X", utils::Empty, ty.void_(), utils::Vector{Decl(x), Block(Decl(y))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -656,7 +656,7 @@
 
     auto* x = Let("a", ty.i32(), Expr(1_i));
     auto* y = Let("a", nullptr, Expr("a"));
-    Func("X", {}, ty.void_(), {Decl(x), Block(Decl(y))});
+    Func("X", utils::Empty, ty.void_(), utils::Vector{Decl(x), Block(Decl(y))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -681,7 +681,7 @@
 
     auto* p = Param("a", ty.i32());
     auto* l = Let("a", nullptr, Expr("a"));
-    Func("X", {p}, ty.void_(), {Block(Decl(l))});
+    Func("X", utils::Vector{p}, ty.void_(), utils::Vector{Block(Decl(l))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -709,7 +709,7 @@
 
     auto* t = Alias("a", ty.i32());
     auto* c = Const("a", nullptr, Expr(false));
-    Func("F", {}, ty.void_(), {Decl(c)});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(c)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -728,9 +728,9 @@
     //   const a = false;
     // }
 
-    auto* t = Structure("a", {Member("m", ty.i32())});
+    auto* t = Structure("a", utils::Vector{Member("m", ty.i32())});
     auto* c = Const("a", nullptr, Expr(false));
-    Func("F", {}, ty.void_(), {Decl(c)});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(c)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -746,7 +746,7 @@
     // }
 
     auto* c = Const("a", nullptr, Expr(false));
-    auto* fb = Func("a", {}, ty.void_(), {Decl(c)});
+    auto* fb = Func("a", utils::Empty, ty.void_(), utils::Vector{Decl(c)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -767,7 +767,7 @@
 
     auto* g = GlobalVar("a", ty.i32(), ast::StorageClass::kPrivate);
     auto* c = Const("a", nullptr, Expr(1_i));
-    Func("F", {}, ty.void_(), {Decl(c)});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(c)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -786,7 +786,7 @@
 
     auto* g = GlobalConst("a", ty.i32(), Expr(1_i));
     auto* c = Const("a", nullptr, Expr("a"));
-    Func("F", {}, ty.void_(), {Decl(c)});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(c)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -810,7 +810,7 @@
 
     auto* v = Var("a", ty.i32(), Expr(1_i));
     auto* c = Const("a", nullptr, Expr(1_i));
-    Func("F", {}, ty.void_(), {Decl(v), Block(Decl(c))});
+    Func("F", utils::Empty, ty.void_(), utils::Vector{Decl(v), Block(Decl(c))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -832,7 +832,7 @@
 
     auto* x = Const("a", ty.i32(), Expr(1_i));
     auto* y = Const("a", nullptr, Expr("a"));
-    Func("X", {}, ty.void_(), {Decl(x), Block(Decl(y))});
+    Func("X", utils::Empty, ty.void_(), utils::Vector{Decl(x), Block(Decl(y))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -858,7 +858,7 @@
 
     auto* l = Let("a", ty.i32(), Expr(1_i));
     auto* c = Const("a", nullptr, Expr(1_i));
-    Func("X", {}, ty.void_(), {Decl(l), Block(Decl(c))});
+    Func("X", utils::Empty, ty.void_(), utils::Vector{Decl(l), Block(Decl(c))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -879,7 +879,7 @@
 
     auto* p = Param("a", ty.i32());
     auto* c = Const("a", nullptr, Expr(1_i));
-    Func("X", {p}, ty.void_(), {Block(Decl(c))});
+    Func("X", utils::Vector{p}, ty.void_(), utils::Vector{Block(Decl(c))});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -892,7 +892,7 @@
 }
 
 TEST_F(ResolverVariableTest, LocalConst_ExplicitType_Decls) {
-    Structure("S", {Member("m", ty.u32())});
+    Structure("S", utils::Vector{Member("m", ty.u32())});
 
     auto* c_i32 = Const("a", ty.i32(), Expr(0_i));
     auto* c_u32 = Const("b", ty.u32(), Expr(0_u));
@@ -936,7 +936,7 @@
 }
 
 TEST_F(ResolverVariableTest, LocalConst_ImplicitType_Decls) {
-    Structure("S", {Member("m", ty.u32())});
+    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));
@@ -1037,21 +1037,21 @@
 TEST_F(ResolverVariableTest, GlobalVar_StorageClass) {
     // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
 
-    auto* buf = Structure("S", {Member("m", ty.i32())});
+    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,
-                              ast::AttributeList{
+                              utils::Vector{
                                   create<ast::BindingAttribute>(0u),
                                   create<ast::GroupAttribute>(0u),
                               });
     auto* storage = GlobalVar("sb", ty.Of(buf), ast::StorageClass::kStorage,
-                              ast::AttributeList{
+                              utils::Vector{
                                   create<ast::BindingAttribute>(1u),
                                   create<ast::GroupAttribute>(0u),
                               });
     auto* handle = GlobalVar("h", ty.depth_texture(ast::TextureDimension::k2d),
-                             ast::AttributeList{
+                             utils::Vector{
                                  create<ast::BindingAttribute>(2u),
                                  create<ast::GroupAttribute>(0u),
                              });
@@ -1074,10 +1074,10 @@
 TEST_F(ResolverVariableTest, GlobalVar_ExplicitStorageClass) {
     // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
 
-    auto* buf = Structure("S", {Member("m", ty.i32())});
+    auto* buf = Structure("S", utils::Vector{Member("m", ty.i32())});
     auto* storage =
         GlobalVar("sb", ty.Of(buf), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-                  ast::AttributeList{
+                  utils::Vector{
                       create<ast::BindingAttribute>(1u),
                       create<ast::GroupAttribute>(0u),
                   });
@@ -1219,7 +1219,7 @@
     // }
 
     auto* p = Param("a", ty.bool_());
-    auto* f = Func("a", {p}, ty.void_(), {});
+    auto* f = Func("a", utils::Vector{p}, ty.void_(), utils::Empty);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1240,7 +1240,7 @@
 
     auto* g = GlobalVar("a", ty.i32(), ast::StorageClass::kPrivate);
     auto* p = Param("a", ty.bool_());
-    Func("F", {p}, ty.void_(), {});
+    Func("F", utils::Vector{p}, ty.void_(), utils::Empty);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1261,7 +1261,7 @@
 
     auto* g = GlobalConst("a", ty.i32(), Expr(1_i));
     auto* p = Param("a", ty.bool_());
-    Func("F", {p}, ty.void_(), {});
+    Func("F", utils::Vector{p}, ty.void_(), utils::Empty);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -1282,7 +1282,7 @@
 
     auto* a = Alias("a", ty.i32());
     auto* p = Param("a", ty.type_name("a"));
-    Func("F", {p}, ty.void_(), {});
+    Func("F", utils::Vector{p}, ty.void_(), utils::Empty);
 
     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 08312fe..97b177e 100644
--- a/src/tint/resolver/variable_validation_test.cc
+++ b/src/tint/resolver/variable_validation_test.cc
@@ -98,7 +98,10 @@
     // ...
     // @id(N) override oN : i32;
     constexpr size_t kLimit = std::numeric_limits<decltype(OverrideId::value)>::max();
-    Override("reserved", ty.i32(), nullptr, {Id(kLimit)});
+    Override("reserved", ty.i32(), nullptr,
+             utils::Vector{
+                 Id(kLimit),
+             });
     for (size_t i = 0; i < kLimit; i++) {
         Override("o" + std::to_string(i), ty.i32(), nullptr);
     }
@@ -288,10 +291,14 @@
     // fn f() {
     //   let p : pointer<storage, i32, read_write> = &s.inner.arr[2i];
     // }
-    auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
-    auto* buf = Structure("S", {Member("inner", ty.Of(inner))});
+    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::AttributeList{
+                              utils::Vector{
                                   create<ast::BindingAttribute>(0u),
                                   create<ast::GroupAttribute>(0u),
                               });
@@ -319,7 +326,9 @@
 }
 
 TEST_F(ResolverVariableValidationTest, NonConstructibleType_RuntimeArray) {
-    auto* s = Structure("S", {Member(Source{{56, 78}}, "m", ty.array(ty.i32()))});
+    auto* s = Structure("S", utils::Vector{
+                                 Member(Source{{56, 78}}, "m", ty.array(ty.i32())),
+                             });
     auto* v = Var(Source{{12, 34}}, "v", ty.Of(s));
     WrapInFunction(v);
 
@@ -331,7 +340,9 @@
 }
 
 TEST_F(ResolverVariableValidationTest, NonConstructibleType_Struct_WithAtomic) {
-    auto* s = Structure("S", {Member("m", ty.atomic(ty.i32()))});
+    auto* s = Structure("S", utils::Vector{
+                                 Member("m", ty.atomic(ty.i32())),
+                             });
     auto* v = Var("v", ty.Of(s));
     WrapInFunction(v);
 
diff --git a/src/tint/sem/builtin.cc b/src/tint/sem/builtin.cc
index 7d0c56e..ecb2331 100644
--- a/src/tint/sem/builtin.cc
+++ b/src/tint/sem/builtin.cc
@@ -153,11 +153,21 @@
 }
 
 bool Builtin::HasSideEffects() const {
-    if (IsAtomic() && type_ != sem::BuiltinType::kAtomicLoad) {
-        return true;
-    }
-    if (type_ == sem::BuiltinType::kTextureStore) {
-        return true;
+    switch (type_) {
+        case sem::BuiltinType::kAtomicAdd:
+        case sem::BuiltinType::kAtomicAnd:
+        case sem::BuiltinType::kAtomicCompareExchangeWeak:
+        case sem::BuiltinType::kAtomicExchange:
+        case sem::BuiltinType::kAtomicMax:
+        case sem::BuiltinType::kAtomicMin:
+        case sem::BuiltinType::kAtomicOr:
+        case sem::BuiltinType::kAtomicStore:
+        case sem::BuiltinType::kAtomicSub:
+        case sem::BuiltinType::kAtomicXor:
+        case sem::BuiltinType::kTextureStore:
+            return true;
+        default:
+            break;
     }
     return false;
 }
diff --git a/src/tint/sem/sem_struct_test.cc b/src/tint/sem/sem_struct_test.cc
index 4a63712..3ea14e1 100644
--- a/src/tint/sem/sem_struct_test.cc
+++ b/src/tint/sem/sem_struct_test.cc
@@ -23,7 +23,7 @@
 
 TEST_F(StructTest, Creation) {
     auto name = Sym("S");
-    auto* impl = create<ast::Struct>(name, ast::StructMemberList{}, ast::AttributeList{});
+    auto* impl = create<ast::Struct>(name, utils::Empty, utils::Empty);
     auto* ptr = impl;
     auto* s = create<sem::Struct>(impl, impl->name, StructMemberList{}, 4u /* align */,
                                   8u /* size */, 16u /* size_no_padding */);
@@ -34,10 +34,10 @@
 }
 
 TEST_F(StructTest, Hash) {
-    auto* a_impl = create<ast::Struct>(Sym("a"), ast::StructMemberList{}, ast::AttributeList{});
+    auto* a_impl = create<ast::Struct>(Sym("a"), utils::Empty, utils::Empty);
     auto* a = create<sem::Struct>(a_impl, a_impl->name, StructMemberList{}, 4u /* align */,
                                   4u /* size */, 4u /* size_no_padding */);
-    auto* b_impl = create<ast::Struct>(Sym("b"), ast::StructMemberList{}, ast::AttributeList{});
+    auto* b_impl = create<ast::Struct>(Sym("b"), utils::Empty, utils::Empty);
     auto* b = create<sem::Struct>(b_impl, b_impl->name, StructMemberList{}, 4u /* align */,
                                   4u /* size */, 4u /* size_no_padding */);
 
@@ -45,10 +45,10 @@
 }
 
 TEST_F(StructTest, Equals) {
-    auto* a_impl = create<ast::Struct>(Sym("a"), ast::StructMemberList{}, ast::AttributeList{});
+    auto* a_impl = create<ast::Struct>(Sym("a"), utils::Empty, utils::Empty);
     auto* a = create<sem::Struct>(a_impl, a_impl->name, StructMemberList{}, 4u /* align */,
                                   4u /* size */, 4u /* size_no_padding */);
-    auto* b_impl = create<ast::Struct>(Sym("b"), ast::StructMemberList{}, ast::AttributeList{});
+    auto* b_impl = create<ast::Struct>(Sym("b"), utils::Empty, utils::Empty);
     auto* b = create<sem::Struct>(b_impl, b_impl->name, StructMemberList{}, 4u /* align */,
                                   4u /* size */, 4u /* size_no_padding */);
 
@@ -59,7 +59,7 @@
 
 TEST_F(StructTest, FriendlyName) {
     auto name = Sym("my_struct");
-    auto* impl = create<ast::Struct>(name, ast::StructMemberList{}, ast::AttributeList{});
+    auto* impl = create<ast::Struct>(name, utils::Empty, utils::Empty);
     auto* s = create<sem::Struct>(impl, impl->name, StructMemberList{}, 4u /* align */,
                                   4u /* size */, 4u /* size_no_padding */);
     EXPECT_EQ(s->FriendlyName(Symbols()), "my_struct");
@@ -67,7 +67,7 @@
 
 TEST_F(StructTest, Layout) {
     auto* inner_st =  //
-        Structure("Inner", {
+        Structure("Inner", utils::Vector{
                                Member("a", ty.i32()),
                                Member("b", ty.u32()),
                                Member("c", ty.f32()),
@@ -75,7 +75,7 @@
                                Member("e", ty.mat4x2<f32>()),
                            });
 
-    auto* outer_st = Structure("Outer", {
+    auto* outer_st = Structure("Outer", utils::Vector{
                                             Member("inner", ty.type_name("Inner")),
                                             Member("a", ty.i32()),
                                         });
diff --git a/src/tint/sem/type.cc b/src/tint/sem/type.cc
index fcc1654..b4887dd 100644
--- a/src/tint/sem/type.cc
+++ b/src/tint/sem/type.cc
@@ -272,7 +272,7 @@
     return el_ty;
 }
 
-const sem::Type* Type::Common(utils::ConstVectorRef<const Type*> types) {
+const sem::Type* Type::Common(utils::VectorRef<const Type*> types) {
     const auto count = types.Length();
     if (count == 0) {
         return nullptr;
diff --git a/src/tint/sem/type.h b/src/tint/sem/type.h
index c2b83e5..8bac821 100644
--- a/src/tint/sem/type.h
+++ b/src/tint/sem/type.h
@@ -161,7 +161,7 @@
     /// @returns the lowest-ranking type that all types in `types` can be implicitly converted to,
     ///          or nullptr if there is no consistent common type across all types in `types`.
     /// @see https://www.w3.org/TR/WGSL/#conversion-rank
-    static const sem::Type* Common(utils::ConstVectorRef<const Type*> types);
+    static const sem::Type* Common(utils::VectorRef<const Type*> types);
 
   protected:
     Type();
diff --git a/src/tint/tint.natvis b/src/tint/tint.natvis
index bce3ce5..45ecf93 100644
--- a/src/tint/tint.natvis
+++ b/src/tint/tint.natvis
@@ -16,6 +16,34 @@
 -->
 
 <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
+	<Type Name="tint::utils::Slice&lt;*&gt;">
+		<DisplayString>{{ length={len}, capacity={cap} }}</DisplayString>
+		<Expand>
+			<!--<Item Name="[length]">len</Item>
+			<Item Name="[capacity]">cap</Item>-->
+			<ArrayItems>
+				<Size>len</Size>
+				<ValuePointer>data</ValuePointer>
+			</ArrayItems>
+		</Expand>
+	</Type>
+
+	<Type Name="tint::utils::Vector&lt;*,*&gt;">
+		<Expand>
+			<Item Name="[heap]">impl_.slice.cap > (int)$T2</Item>
+			<ExpandedItem>impl_.slice</ExpandedItem>
+			<!--<Item Name="[slice]">impl_.slice</Item>-->
+		</Expand>
+	</Type>
+
+	<Type Name="tint::utils::VectorRef&lt;*&gt;">
+		<Expand>
+			<Item Name="[can move]">can_move_</Item>
+			<ExpandedItem>slice_</ExpandedItem>
+			<!--<Item Name="[slice]">slice_</Item>-->
+		</Expand>
+	</Type>
+
 	<Type Name="tint::Symbol">
 		<!-- Requires TINT_SYMBOL_STORE_DEBUG_NAME defined to 1 -->
 		<DisplayString Optional="true">{debug_name_,sb}</DisplayString>
@@ -63,7 +91,7 @@
 	</Type>
 
 	<Type Name="tint::ast::IdentifierExpression">
-		 <!--the ",sb" specifier removes the double quotes on the displayed string -->
+		<!--the ",sb" specifier removes the double quotes on the displayed string -->
 		<DisplayString>{symbol}</DisplayString>
 		<Expand>
 			<Item Name="symbol">symbol</Item>
diff --git a/src/tint/traits.h b/src/tint/traits.h
index eb81b3f..d830d2c 100644
--- a/src/tint/traits.h
+++ b/src/tint/traits.h
@@ -91,7 +91,7 @@
 
 /// If `CONDITION` is true then EnableIf resolves to type T, otherwise an
 /// invalid type.
-template <bool CONDITION, typename T>
+template <bool CONDITION, typename T = void>
 using EnableIf = typename std::enable_if<CONDITION, T>::type;
 
 /// If `T` is of type `BASE`, or derives from `BASE`, then EnableIfIsType
diff --git a/src/tint/transform/add_empty_entry_point.cc b/src/tint/transform/add_empty_entry_point.cc
index f037649..5ef4fe8 100644
--- a/src/tint/transform/add_empty_entry_point.cc
+++ b/src/tint/transform/add_empty_entry_point.cc
@@ -39,7 +39,10 @@
 
 void AddEmptyEntryPoint::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
     ctx.dst->Func(ctx.dst->Symbols().New("unused_entry_point"), {}, ctx.dst->ty.void_(), {},
-                  {ctx.dst->Stage(ast::PipelineStage::kCompute), ctx.dst->WorkgroupSize(1_i)});
+                  utils::Vector{
+                      ctx.dst->Stage(ast::PipelineStage::kCompute),
+                      ctx.dst->WorkgroupSize(1_i),
+                  });
     ctx.Clone();
 }
 
diff --git a/src/tint/transform/add_spirv_block_attribute.cc b/src/tint/transform/add_spirv_block_attribute.cc
index 85c20c3..3615812 100644
--- a/src/tint/transform/add_spirv_block_attribute.cc
+++ b/src/tint/transform/add_spirv_block_attribute.cc
@@ -58,7 +58,8 @@
     for (auto* var : ctx.src->AST().Globals<ast::Var>()) {
         auto* sem_var = sem.Get<sem::GlobalVariable>(var);
         if (var->declared_storage_class != ast::StorageClass::kStorage &&
-            var->declared_storage_class != ast::StorageClass::kUniform) {
+            var->declared_storage_class != ast::StorageClass::kUniform &&
+            var->declared_storage_class != ast::StorageClass::kPushConstant) {
             continue;
         }
 
@@ -75,8 +76,8 @@
                 auto wrapper_name = ctx.src->Symbols().NameFor(var->symbol) + "_block";
                 auto* ret = ctx.dst->create<ast::Struct>(
                     ctx.dst->Symbols().New(wrapper_name),
-                    ast::StructMemberList{ctx.dst->Member(kMemberName, CreateASTTypeFor(ctx, ty))},
-                    ast::AttributeList{block});
+                    utils::Vector{ctx.dst->Member(kMemberName, CreateASTTypeFor(ctx, ty))},
+                    utils::Vector{block});
                 ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), var, ret);
                 return ret;
             });
diff --git a/src/tint/transform/add_spirv_block_attribute_test.cc b/src/tint/transform/add_spirv_block_attribute_test.cc
index 455be60..90f9219 100644
--- a/src/tint/transform/add_spirv_block_attribute_test.cc
+++ b/src/tint/transform/add_spirv_block_attribute_test.cc
@@ -196,6 +196,71 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(AddSpirvBlockAttributeTest, BasicScalar_PushConstant) {
+    auto* src = R"(
+enable chromium_experimental_push_constant;
+var<push_constant> u : f32;
+
+@fragment
+fn main() {
+  let f = u;
+}
+)";
+    auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+@internal(spirv_block)
+struct u_block {
+  inner : f32,
+}
+
+var<push_constant> u : u_block;
+
+@fragment
+fn main() {
+  let f = u.inner;
+}
+)";
+
+    auto got = Run<AddSpirvBlockAttribute>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AddSpirvBlockAttributeTest, BasicStruct_PushConstant) {
+    auto* src = R"(
+enable chromium_experimental_push_constant;
+struct S {
+  f : f32,
+};
+var<push_constant> u : S;
+
+@fragment
+fn main() {
+  let f = u.f;
+}
+)";
+    auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+@internal(spirv_block)
+struct S {
+  f : f32,
+}
+
+var<push_constant> u : S;
+
+@fragment
+fn main() {
+  let f = u.f;
+}
+)";
+
+    auto got = Run<AddSpirvBlockAttribute>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
 TEST_F(AddSpirvBlockAttributeTest, Nested_OuterBuffer_InnerNotBuffer) {
     auto* src = R"(
 struct Inner {
diff --git a/src/tint/transform/array_length_from_uniform.cc b/src/tint/transform/array_length_from_uniform.cc
index 81471ad..cdb75c8 100644
--- a/src/tint/transform/array_length_from_uniform.cc
+++ b/src/tint/transform/array_length_from_uniform.cc
@@ -141,13 +141,14 @@
             // aligned.
             auto* buffer_size_struct = ctx.dst->Structure(
                 ctx.dst->Sym(),
-                {ctx.dst->Member(kBufferSizeMemberName,
-                                 ctx.dst->ty.array(ctx.dst->ty.vec4(ctx.dst->ty.u32()),
-                                                   u32((max_buffer_size_index / 4) + 1)))});
+                utils::Vector{
+                    ctx.dst->Member(kBufferSizeMemberName,
+                                    ctx.dst->ty.array(ctx.dst->ty.vec4(ctx.dst->ty.u32()),
+                                                      u32((max_buffer_size_index / 4) + 1))),
+                });
             buffer_size_ubo = ctx.dst->GlobalVar(
                 ctx.dst->Sym(), ctx.dst->ty.Of(buffer_size_struct), ast::StorageClass::kUniform,
-                ast::AttributeList{
-                    ctx.dst->GroupAndBinding(cfg->ubo_binding.group, cfg->ubo_binding.binding)});
+                ctx.dst->GroupAndBinding(cfg->ubo_binding.group, 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 b0f6297..3ca7035 100644
--- a/src/tint/transform/builtin_polyfill.cc
+++ b/src/tint/transform/builtin_polyfill.cc
@@ -15,6 +15,7 @@
 #include "src/tint/transform/builtin_polyfill.h"
 
 #include <unordered_map>
+#include <utility>
 
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/builtin.h"
@@ -59,16 +60,16 @@
             return b.Construct(T(ty), expr);
         };
 
-        ast::StatementList body;
+        utils::Vector<const ast::Statement*, 4> body;
         switch (polyfill.acosh) {
             case Level::kFull:
                 // return log(x + sqrt(x*x - 1));
-                body.emplace_back(b.Return(
+                body.Push(b.Return(
                     b.Call("log", b.Add("x", b.Call("sqrt", b.Sub(b.Mul("x", "x"), 1_a))))));
                 break;
             case Level::kRangeCheck: {
                 // return select(acosh(x), 0, x < 1);
-                body.emplace_back(b.Return(
+                body.Push(b.Return(
                     b.Call("select", b.Call("acosh", "x"), V(0.0_a), b.LessThan("x", V(1.0_a)))));
                 break;
             }
@@ -78,7 +79,7 @@
                 return {};
         }
 
-        b.Func(name, {b.Param("x", T(ty))}, T(ty), body);
+        b.Func(name, utils::Vector{b.Param("x", T(ty))}, T(ty), body);
 
         return name;
     }
@@ -89,13 +90,11 @@
     Symbol asinh(const sem::Type* ty) {
         auto name = b.Symbols().New("tint_sinh");
 
-        ast::StatementList body;
-
         // return log(x + sqrt(x*x + 1));
-        body.emplace_back(
-            b.Return(b.Call("log", b.Add("x", b.Call("sqrt", b.Add(b.Mul("x", "x"), 1_a))))));
-
-        b.Func(name, {b.Param("x", T(ty))}, T(ty), body);
+        b.Func(name, utils::Vector{b.Param("x", T(ty))}, T(ty),
+               utils::Vector{
+                   b.Return(b.Call("log", b.Add("x", b.Call("sqrt", b.Add(b.Mul("x", "x"), 1_a))))),
+               });
 
         return name;
     }
@@ -115,17 +114,17 @@
             return b.Construct(T(ty), expr);
         };
 
-        ast::StatementList body;
+        utils::Vector<const ast::Statement*, 1> body;
         switch (polyfill.atanh) {
             case Level::kFull:
                 // return log((1+x) / (1-x)) * 0.5
-                body.emplace_back(
+                body.Push(
                     b.Return(b.Mul(b.Call("log", b.Div(b.Add(1_a, "x"), b.Sub(1_a, "x"))), 0.5_a)));
                 break;
             case Level::kRangeCheck:
                 // return select(atanh(x), 0, x >= 1);
-                body.emplace_back(b.Return(b.Call("select", b.Call("atanh", "x"), V(0.0_a),
-                                                  b.GreaterThanEqual("x", V(1.0_a)))));
+                body.Push(b.Return(b.Call("select", b.Call("atanh", "x"), V(0.0_a),
+                                          b.GreaterThanEqual("x", V(1.0_a)))));
                 break;
             default:
                 TINT_ICE(Transform, b.Diagnostics())
@@ -133,7 +132,7 @@
                 return {};
         }
 
-        b.Func(name, {b.Param("x", T(ty))}, T(ty), body);
+        b.Func(name, utils::Vector{b.Param("x", T(ty))}, T(ty), body);
 
         return name;
     }
@@ -156,8 +155,12 @@
             return ScalarOrVector(width, u32(value));
         };
         b.Func(
-            name, {b.Param("v", T(ty))}, T(ty),
-            {
+            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);
@@ -217,8 +220,12 @@
             return b.Construct(b.ty.vec<bool>(width), value);
         };
         b.Func(
-            name, {b.Param("v", T(ty))}, T(ty),
-            {
+            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(16, 0, bool(x & 0x0000ffff));
@@ -270,20 +277,20 @@
             return b.Construct(b.ty.vec<u32>(width), value);
         };
 
-        ast::StatementList body = {
+        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")))),
         };
 
         switch (polyfill.extract_bits) {
             case Level::kFull:
-                body.emplace_back(b.Decl(b.Let("shl", nullptr, b.Sub(u32(W), "e"))));
-                body.emplace_back(b.Decl(b.Let("shr", nullptr, b.Add("shl", "s"))));
-                body.emplace_back(
+                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.Return(b.Shr(b.Shl("v", vecN_u32(b.Expr("shl"))), vecN_u32(b.Expr("shr")))));
                 break;
             case Level::kClampParameters:
-                body.emplace_back(b.Return(b.Call("extractBits", "v", "s", b.Sub("e", "s"))));
+                body.Push(b.Return(b.Call("extractBits", "v", "s", b.Sub("e", "s"))));
                 break;
             default:
                 TINT_ICE(Transform, b.Diagnostics())
@@ -292,12 +299,12 @@
         }
 
         b.Func(name,
-               {
+               utils::Vector{
                    b.Param("v", T(ty)),
                    b.Param("offset", b.ty.u32()),
                    b.Param("count", b.ty.u32()),
                },
-               T(ty), body);
+               T(ty), std::move(body));
 
         return name;
     }
@@ -338,8 +345,12 @@
         }
 
         b.Func(
-            name, {b.Param("v", T(ty))}, T(ty),
-            {
+            name,
+            utils::Vector{
+                b.Param("v", T(ty)),
+            },
+            T(ty),
+            utils::Vector{
                 // var x = v;                          (unsigned)
                 // var x = select(U(v), ~U(v), v < 0); (signed)
                 b.Decl(b.Var("x", nullptr, x)),
@@ -400,8 +411,12 @@
             return b.Construct(b.ty.vec<bool>(width), value);
         };
         b.Func(
-            name, {b.Param("v", T(ty))}, T(ty),
-            {
+            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(16, 0, bool(x & 0x0000ffff));
@@ -463,7 +478,7 @@
             return b.vec(b.ty.u32(), width, value);
         };
 
-        ast::StatementList body = {
+        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")))),
         };
@@ -471,15 +486,15 @@
         switch (polyfill.insert_bits) {
             case Level::kFull:
                 // let mask = ((1 << s) - 1) ^ ((1 << e) - 1)
-                body.emplace_back(
+                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)))));
                 // return ((n << s) & mask) | (v & ~mask)
-                body.emplace_back(b.Return(b.Or(b.And(b.Shl("n", U("s")), V("mask")),
-                                                b.And("v", V(b.Complement("mask"))))));
+                body.Push(b.Return(b.Or(b.And(b.Shl("n", U("s")), V("mask")),
+                                        b.And("v", V(b.Complement("mask"))))));
                 break;
             case Level::kClampParameters:
-                body.emplace_back(b.Return(b.Call("insertBits", "v", "n", "s", b.Sub("e", "s"))));
+                body.Push(b.Return(b.Call("insertBits", "v", "n", "s", b.Sub("e", "s"))));
                 break;
             default:
                 TINT_ICE(Transform, b.Diagnostics())
@@ -488,7 +503,7 @@
         }
 
         b.Func(name,
-               {
+               utils::Vector{
                    b.Param("v", T(ty)),
                    b.Param("n", T(ty)),
                    b.Param("offset", b.ty.u32()),
diff --git a/src/tint/transform/calculate_array_length.cc b/src/tint/transform/calculate_array_length.cc
index cfeaca8..6c15e73 100644
--- a/src/tint/transform/calculate_array_length.cc
+++ b/src/tint/transform/calculate_array_length.cc
@@ -101,20 +101,20 @@
                 ctx.dst->Disable(ast::DisabledValidation::kFunctionParameter);
             ctx.dst->AST().AddFunction(ctx.dst->create<ast::Function>(
                 name,
-                ast::ParameterList{
+                utils::Vector{
                     ctx.dst->Param("buffer",
                                    ctx.dst->ty.pointer(type, buffer_type->StorageClass(),
                                                        buffer_type->Access()),
-                                   {disable_validation}),
+                                   utils::Vector{disable_validation}),
                     ctx.dst->Param("result", ctx.dst->ty.pointer(ctx.dst->ty.u32(),
                                                                  ast::StorageClass::kFunction)),
                 },
                 ctx.dst->ty.void_(), nullptr,
-                ast::AttributeList{
+                utils::Vector{
                     ctx.dst->ASTNodes().Create<BufferSizeIntrinsic>(ctx.dst->ID(),
                                                                     ctx.dst->AllocateNodeID()),
                 },
-                ast::AttributeList{}));
+                utils::Empty));
 
             return name;
         });
diff --git a/src/tint/transform/canonicalize_entry_point_io.cc b/src/tint/transform/canonicalize_entry_point_io.cc
index acec84f..7670f09 100644
--- a/src/tint/transform/canonicalize_entry_point_io.cc
+++ b/src/tint/transform/canonicalize_entry_point_io.cc
@@ -69,7 +69,7 @@
 }
 
 // Returns true if `attrs` contains a `sample_mask` builtin.
-bool HasSampleMask(const ast::AttributeList& attrs) {
+bool HasSampleMask(utils::VectorRef<const ast::Attribute*> attrs) {
     auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(attrs);
     return builtin && builtin->builtin == ast::BuiltinValue::kSampleMask;
 }
@@ -85,7 +85,7 @@
         /// The type of the output value.
         const ast::Type* type;
         /// The shader IO attributes.
-        ast::AttributeList attributes;
+        utils::Vector<const ast::Attribute*, 2> attributes;
         /// The value itself.
         const ast::Expression* value;
     };
@@ -100,19 +100,19 @@
     const sem::Function* func_sem;
 
     /// The new entry point wrapper function's parameters.
-    ast::ParameterList wrapper_ep_parameters;
+    utils::Vector<const ast::Parameter*, 8> wrapper_ep_parameters;
     /// The members of the wrapper function's struct parameter.
-    ast::StructMemberList wrapper_struct_param_members;
+    utils::Vector<const ast::StructMember*, 8> wrapper_struct_param_members;
     /// The name of the wrapper function's struct parameter.
     Symbol wrapper_struct_param_name;
     /// The parameters that will be passed to the original function.
-    ast::ExpressionList inner_call_parameters;
+    utils::Vector<const ast::Expression*, 8> inner_call_parameters;
     /// The members of the wrapper function's struct return type.
-    ast::StructMemberList wrapper_struct_output_members;
+    utils::Vector<const ast::StructMember*, 8> wrapper_struct_output_members;
     /// The wrapper function output values.
-    std::vector<OutputValue> wrapper_output_values;
+    utils::Vector<OutputValue, 8> wrapper_output_values;
     /// The body of the wrapper function.
-    ast::StatementList wrapper_body;
+    utils::Vector<const ast::Statement*, 8> wrapper_body;
     /// Input names used by the entrypoint
     std::unordered_set<std::string> input_names;
 
@@ -129,12 +129,13 @@
     /// @param src the attributes to clone
     /// @param do_interpolate whether to clone InterpolateAttribute
     /// @return the cloned attributes
-    ast::AttributeList CloneShaderIOAttributes(const ast::AttributeList& src, bool do_interpolate) {
-        ast::AttributeList new_attributes;
+    template <size_t N>
+    auto CloneShaderIOAttributes(utils::Vector<const ast::Attribute*, N> src, bool do_interpolate) {
+        utils::Vector<const ast::Attribute*, N> new_attributes;
         for (auto* attr : src) {
             if (IsShaderIOAttribute(attr) &&
-                (do_interpolate || !attr->Is<ast::InterpolateAttribute>())) {
-                new_attributes.push_back(ctx.Clone(attr));
+                (do_interpolate || !attr->template Is<ast::InterpolateAttribute>())) {
+                new_attributes.Push(ctx.Clone(attr));
             }
         }
         return new_attributes;
@@ -156,7 +157,7 @@
     /// @returns an expression which evaluates to the value of the shader input
     const ast::Expression* AddInput(std::string name,
                                     const sem::Type* type,
-                                    ast::AttributeList attributes) {
+                                    utils::Vector<const ast::Attribute*, 8> attributes) {
         auto* ast_type = CreateASTTypeFor(ctx, type);
         if (cfg.shader_style == ShaderStyle::kSpirv || cfg.shader_style == ShaderStyle::kGlsl) {
             // Vulkan requires that integer user-defined fragment inputs are always decorated with
@@ -169,12 +170,12 @@
                 !ast::HasAttribute<ast::InterpolateAttribute>(attributes) &&
                 (ast::HasAttribute<ast::LocationAttribute>(attributes) ||
                  cfg.shader_style == ShaderStyle::kSpirv)) {
-                attributes.push_back(ctx.dst->Interpolate(ast::InterpolationType::kFlat,
-                                                          ast::InterpolationSampling::kNone));
+                attributes.Push(ctx.dst->Interpolate(ast::InterpolationType::kFlat,
+                                                     ast::InterpolationSampling::kNone));
             }
 
             // Disable validation for use of the `input` storage class.
-            attributes.push_back(ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
+            attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
 
             // In GLSL, if it's a builtin, override the name with the
             // corresponding gl_ builtin name
@@ -206,14 +207,13 @@
             // parameter list and pass it directly to the inner function.
             Symbol symbol = input_names.emplace(name).second ? ctx.dst->Symbols().Register(name)
                                                              : ctx.dst->Symbols().New(name);
-            wrapper_ep_parameters.push_back(
-                ctx.dst->Param(symbol, ast_type, std::move(attributes)));
+            wrapper_ep_parameters.Push(ctx.dst->Param(symbol, ast_type, std::move(attributes)));
             return ctx.dst->Expr(symbol);
         } else {
             // Otherwise, move it to the new structure member list.
             Symbol symbol = input_names.emplace(name).second ? ctx.dst->Symbols().Register(name)
                                                              : ctx.dst->Symbols().New(name);
-            wrapper_struct_param_members.push_back(
+            wrapper_struct_param_members.Push(
                 ctx.dst->Member(symbol, ast_type, std::move(attributes)));
             return ctx.dst->MemberAccessor(InputStructSymbol(), symbol);
         }
@@ -226,7 +226,7 @@
     /// @param value the value of the shader output
     void AddOutput(std::string name,
                    const sem::Type* type,
-                   ast::AttributeList attributes,
+                   utils::Vector<const ast::Attribute*, 8> attributes,
                    const ast::Expression* value) {
         // Vulkan requires that integer user-defined vertex outputs are always decorated with
         // `Flat`.
@@ -237,8 +237,8 @@
             type->is_integer_scalar_or_vector() &&
             ast::HasAttribute<ast::LocationAttribute>(attributes) &&
             !ast::HasAttribute<ast::InterpolateAttribute>(attributes)) {
-            attributes.push_back(ctx.dst->Interpolate(ast::InterpolationType::kFlat,
-                                                      ast::InterpolationSampling::kNone));
+            attributes.Push(ctx.dst->Interpolate(ast::InterpolationType::kFlat,
+                                                 ast::InterpolationSampling::kNone));
         }
 
         // In GLSL, if it's a builtin, override the name with the
@@ -256,7 +256,7 @@
         output.type = CreateASTTypeFor(ctx, type);
         output.attributes = std::move(attributes);
         output.value = value;
-        wrapper_output_values.push_back(output);
+        wrapper_output_values.Push(output);
     }
 
     /// Process a non-struct parameter.
@@ -269,19 +269,19 @@
         bool do_interpolate = func_ast->PipelineStage() != ast::PipelineStage::kVertex;
         // Remove the shader IO attributes from the inner function parameter, and
         // attach them to the new object instead.
-        ast::AttributeList attributes;
+        utils::Vector<const ast::Attribute*, 8> attributes;
         for (auto* attr : param->Declaration()->attributes) {
             if (IsShaderIOAttribute(attr)) {
                 ctx.Remove(param->Declaration()->attributes, attr);
                 if ((do_interpolate || !attr->Is<ast::InterpolateAttribute>())) {
-                    attributes.push_back(ctx.Clone(attr));
+                    attributes.Push(ctx.Clone(attr));
                 }
             }
         }
 
         auto name = ctx.src->Symbols().NameFor(param->Declaration()->symbol);
         auto* input_expr = AddInput(name, param->Type(), std::move(attributes));
-        inner_call_parameters.push_back(input_expr);
+        inner_call_parameters.Push(input_expr);
     }
 
     /// Process a struct parameter.
@@ -297,7 +297,7 @@
 
         // Recreate struct members in the outer entry point and build an initializer
         // list to pass them through to the inner function.
-        ast::ExpressionList inner_struct_values;
+        utils::Vector<const ast::Expression*, 8> inner_struct_values;
         for (auto* member : str->Members()) {
             if (member->Type()->Is<sem::Struct>()) {
                 TINT_ICE(Transform, ctx.dst->Diagnostics()) << "nested IO struct";
@@ -309,11 +309,11 @@
 
             auto attributes = CloneShaderIOAttributes(member_ast->attributes, do_interpolate);
             auto* input_expr = AddInput(name, member->Type(), std::move(attributes));
-            inner_struct_values.push_back(input_expr);
+            inner_struct_values.Push(input_expr);
         }
 
         // Construct the original structure using the new shader input objects.
-        inner_call_parameters.push_back(
+        inner_call_parameters.Push(
             ctx.dst->Construct(ctx.Clone(param->Declaration()->type), inner_struct_values));
     }
 
@@ -394,19 +394,19 @@
 
         // Create the new struct type.
         auto struct_name = ctx.dst->Sym();
-        auto* in_struct = ctx.dst->create<ast::Struct>(struct_name, wrapper_struct_param_members,
-                                                       ast::AttributeList{});
+        auto* in_struct =
+            ctx.dst->create<ast::Struct>(struct_name, wrapper_struct_param_members, utils::Empty);
         ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func_ast, in_struct);
 
         // Create a new function parameter using this struct type.
         auto* param = ctx.dst->Param(InputStructSymbol(), ctx.dst->ty.type_name(struct_name));
-        wrapper_ep_parameters.push_back(param);
+        wrapper_ep_parameters.Push(param);
     }
 
     /// Create and return the wrapper function's struct result object.
     /// @returns the struct type
     ast::Struct* CreateOutputStruct() {
-        ast::StatementList assignments;
+        utils::Vector<const ast::Statement*, 8> assignments;
 
         auto wrapper_result = ctx.dst->Symbols().New("wrapper_result");
 
@@ -422,9 +422,9 @@
             }
             member_names.insert(ctx.dst->Symbols().NameFor(name));
 
-            wrapper_struct_output_members.push_back(
+            wrapper_struct_output_members.Push(
                 ctx.dst->Member(name, outval.type, std::move(outval.attributes)));
-            assignments.push_back(
+            assignments.Push(
                 ctx.dst->Assign(ctx.dst->MemberAccessor(wrapper_result, name), outval.value));
         }
 
@@ -434,14 +434,16 @@
 
         // Create the new struct type.
         auto* out_struct = ctx.dst->create<ast::Struct>(
-            ctx.dst->Sym(), wrapper_struct_output_members, ast::AttributeList{});
+            ctx.dst->Sym(), wrapper_struct_output_members, utils::Empty);
         ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func_ast, out_struct);
 
         // Create the output struct object, assign its members, and return it.
         auto* result_object = ctx.dst->Var(wrapper_result, ctx.dst->ty.type_name(out_struct->name));
-        wrapper_body.push_back(ctx.dst->Decl(result_object));
-        wrapper_body.insert(wrapper_body.end(), assignments.begin(), assignments.end());
-        wrapper_body.push_back(ctx.dst->Return(wrapper_result));
+        wrapper_body.Push(ctx.dst->Decl(result_object));
+        for (auto* assignment : assignments) {
+            wrapper_body.Push(assignment);
+        }
+        wrapper_body.Push(ctx.dst->Return(wrapper_result));
 
         return out_struct;
     }
@@ -450,8 +452,8 @@
     void CreateGlobalOutputVariables() {
         for (auto& outval : wrapper_output_values) {
             // Disable validation for use of the `output` storage class.
-            ast::AttributeList attributes = std::move(outval.attributes);
-            attributes.push_back(ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
+            utils::Vector<const ast::Attribute*, 8> attributes = std::move(outval.attributes);
+            attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
 
             // Create the global variable and assign it the output value.
             auto name = ctx.dst->Symbols().New(outval.name);
@@ -464,7 +466,7 @@
                 lhs = ctx.dst->IndexAccessor(lhs, 0_i);
             }
             ctx.dst->GlobalVar(name, type, ast::StorageClass::kOut, std::move(attributes));
-            wrapper_body.push_back(ctx.dst->Assign(lhs, outval.value));
+            wrapper_body.Push(ctx.dst->Assign(lhs, outval.value));
         }
     }
 
@@ -488,7 +490,7 @@
         // processing.
         auto* inner_function = ctx.dst->create<ast::Function>(
             inner_name, ctx.Clone(func_ast->params), ctx.Clone(func_ast->return_type),
-            ctx.Clone(func_ast->body), ast::AttributeList{}, ast::AttributeList{});
+            ctx.Clone(func_ast->body), utils::Empty, utils::Empty);
         ctx.Replace(func_ast, inner_function);
 
         // Call the function.
@@ -527,7 +529,7 @@
             }
 
             // Create a structure parameter for the outer entry point if necessary.
-            if (!wrapper_struct_param_members.empty()) {
+            if (!wrapper_struct_param_members.IsEmpty()) {
                 CreateInputStruct();
             }
         }
@@ -539,12 +541,12 @@
         std::function<const ast::Type*()> wrapper_ret_type = [&] { return ctx.dst->ty.void_(); };
         if (func_sem->ReturnType()->Is<sem::Void>()) {
             // The function call is just a statement with no result.
-            wrapper_body.push_back(ctx.dst->CallStmt(call_inner));
+            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);
-            wrapper_body.push_back(ctx.dst->Decl(inner_result));
+            wrapper_body.Push(ctx.dst->Decl(inner_result));
 
             // Process the original return type to determine the outputs that the
             // outer function needs to produce.
@@ -562,7 +564,7 @@
         }
 
         // Produce the entry point outputs, if necessary.
-        if (!wrapper_output_values.empty()) {
+        if (!wrapper_output_values.IsEmpty()) {
             if (cfg.shader_style == ShaderStyle::kSpirv || cfg.shader_style == ShaderStyle::kGlsl) {
                 CreateGlobalOutputVariables();
             } else {
@@ -578,11 +580,11 @@
             auto* pos_y = GLPosition("y");
             auto* negate_pos_y =
                 ctx.dst->create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, GLPosition("y"));
-            wrapper_body.push_back(ctx.dst->Assign(pos_y, negate_pos_y));
+            wrapper_body.Push(ctx.dst->Assign(pos_y, negate_pos_y));
 
             auto* two_z = ctx.dst->Mul(ctx.dst->Expr(2_f), GLPosition("z"));
             auto* fixed_z = ctx.dst->Sub(two_z, GLPosition("w"));
-            wrapper_body.push_back(ctx.dst->Assign(GLPosition("z"), fixed_z));
+            wrapper_body.Push(ctx.dst->Assign(GLPosition("z"), fixed_z));
         }
 
         // Create the wrapper entry point function.
@@ -597,7 +599,7 @@
 
         auto* wrapper_func = ctx.dst->create<ast::Function>(
             name, wrapper_ep_parameters, wrapper_ret_type(), ctx.dst->Block(wrapper_body),
-            ctx.Clone(func_ast->attributes), ast::AttributeList{});
+            ctx.Clone(func_ast->attributes), utils::Empty);
         ctx.InsertAfter(ctx.src->AST().GlobalDeclarations(), func_ast, wrapper_func);
     }
 
diff --git a/src/tint/transform/combine_samplers.cc b/src/tint/transform/combine_samplers.cc
index 4e31789..2f5101e 100644
--- a/src/tint/transform/combine_samplers.cc
+++ b/src/tint/transform/combine_samplers.cc
@@ -78,9 +78,9 @@
     /// Group and binding attributes used by all combined sampler globals.
     /// Group 0 and binding 0 are used, with collisions disabled.
     /// @returns the newly-created attribute list
-    ast::AttributeList Attributes() const {
-        auto attributes = ctx.dst->GroupAndBinding(0, 0);
-        attributes.push_back(ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision));
+    auto Attributes() const {
+        utils::Vector<const ast::Attribute*, 3> attributes = ctx.dst->GroupAndBinding(0, 0);
+        attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision));
         return attributes;
     }
 
@@ -170,7 +170,7 @@
                 if (pairs.empty()) {
                     return nullptr;
                 }
-                ast::ParameterList params;
+                utils::Vector<const ast::Parameter*, 8> params;
                 for (auto pair : func->TextureSamplerPairs()) {
                     const sem::Variable* texture_var = pair.first;
                     const sem::Variable* sampler_var = pair.second;
@@ -191,7 +191,7 @@
                         // add a new function parameter to represent the combined sampler.
                         auto* type = CreateCombinedASTTypeFor(texture_var, sampler_var);
                         auto* var = ctx.dst->Param(ctx.dst->Symbols().New(name), type);
-                        params.push_back(var);
+                        params.Push(var);
                         function_combined_texture_samplers_[func][pair] = var;
                     }
                 }
@@ -199,7 +199,7 @@
                 // function signature.
                 for (auto* var : src->params) {
                     if (!sem.Get(var->type)->IsAnyOf<sem::Texture, sem::Sampler>()) {
-                        params.push_back(ctx.Clone(var));
+                        params.Push(ctx.Clone(var));
                     }
                 }
                 // Create a new function signature that differs only in the parameter
@@ -221,7 +221,7 @@
         // the combined global samplers, as appropriate.
         ctx.ReplaceAll([&](const ast::CallExpression* expr) -> const ast::Expression* {
             if (auto* call = sem.Get(expr)->UnwrapMaterialize()->As<sem::Call>()) {
-                ast::ExpressionList args;
+                utils::Vector<const ast::Expression*, 8> args;
                 // Replace all texture builtin calls.
                 if (auto* builtin = call->Target()->As<sem::Builtin>()) {
                     const auto& signature = builtin->Signature();
@@ -252,7 +252,7 @@
                                     ? global_combined_texture_samplers_[new_pair]
                                     : function_combined_texture_samplers_[call->Stmt()->Function()]
                                                                          [new_pair];
-                            args.push_back(ctx.dst->Expr(var->symbol));
+                            args.Push(ctx.dst->Expr(var->symbol));
                         } else if (auto* sampler_type = type->As<sem::Sampler>()) {
                             ast::SamplerKind kind = sampler_type->kind();
                             int index = (kind == ast::SamplerKind::kSampler) ? 0 : 1;
@@ -260,9 +260,9 @@
                             if (!p) {
                                 p = CreatePlaceholder(kind);
                             }
-                            args.push_back(ctx.dst->Expr(p->symbol));
+                            args.Push(ctx.dst->Expr(p->symbol));
                         } else {
-                            args.push_back(ctx.Clone(arg));
+                            args.Push(ctx.Clone(arg));
                         }
                     }
                     const ast::Expression* value =
@@ -304,7 +304,7 @@
                                 : function_combined_texture_samplers_[call->Stmt()->Function()]
                                                                      [new_pair];
                         auto* arg = ctx.dst->Expr(var->symbol);
-                        args.push_back(arg);
+                        args.Push(arg);
                     }
                     // Append all of the remaining non-texture and non-sampler
                     // parameters.
@@ -312,7 +312,7 @@
                         if (!ctx.src->TypeOf(arg)
                                  ->UnwrapRef()
                                  ->IsAnyOf<sem::Texture, sem::Sampler>()) {
-                            args.push_back(ctx.Clone(arg));
+                            args.Push(ctx.Clone(arg));
                         }
                     }
                     return ctx.dst->Call(ctx.Clone(expr->target.name), args);
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index 135a42a..d725a4b 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -440,10 +440,10 @@
         auto access = var_user->Variable()->Access();
         return utils::GetOrCreate(
             load_funcs, LoadStoreKey{storage_class, access, buf_ty, el_ty}, [&] {
-                ast::ParameterList params = {
+                utils::Vector params{
                     b.Param("buffer",
                             b.ty.pointer(CreateASTTypeFor(ctx, buf_ty), storage_class, access),
-                            {b.Disable(ast::DisabledValidation::kFunctionParameter)}),
+                            utils::Vector{b.Disable(ast::DisabledValidation::kFunctionParameter)}),
                     b.Param("offset", b.ty.u32()),
                 };
 
@@ -453,11 +453,11 @@
                     auto* el_ast_ty = CreateASTTypeFor(ctx, el_ty);
                     auto* func = b.create<ast::Function>(
                         name, params, el_ast_ty, nullptr,
-                        ast::AttributeList{
+                        utils::Vector{
                             intrinsic,
                             b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
                         },
-                        ast::AttributeList{});
+                        utils::Empty);
                     b.AST().AddFunction(func);
                 } else if (auto* arr_ty = el_ty->As<sem::Array>()) {
                     // fn load_func(buffer : buf_ty, offset : u32) -> array<T, N> {
@@ -481,29 +481,29 @@
                         b.For(for_init, for_cond, for_cont, b.Block(b.Assign(arr_el, el_val)));
 
                     b.Func(name, params, CreateASTTypeFor(ctx, arr_ty),
-                           {
+                           utils::Vector{
                                b.Decl(arr),
                                for_loop,
                                b.Return(arr),
                            });
                 } else {
-                    ast::ExpressionList values;
+                    utils::Vector<const ast::Expression*, 8> values;
                     if (auto* mat_ty = el_ty->As<sem::Matrix>()) {
                         auto* vec_ty = mat_ty->ColumnType();
                         Symbol load = LoadFunc(buf_ty, vec_ty, var_user);
                         for (uint32_t i = 0; i < mat_ty->columns(); i++) {
                             auto* offset = b.Add("offset", u32(i * mat_ty->ColumnStride()));
-                            values.emplace_back(b.Call(load, "buffer", offset));
+                            values.Push(b.Call(load, "buffer", offset));
                         }
                     } else if (auto* str = el_ty->As<sem::Struct>()) {
                         for (auto* member : str->Members()) {
                             auto* offset = b.Add("offset", u32(member->Offset()));
                             Symbol load = LoadFunc(buf_ty, member->Type()->UnwrapRef(), var_user);
-                            values.emplace_back(b.Call(load, "buffer", offset));
+                            values.Push(b.Call(load, "buffer", offset));
                         }
                     }
                     b.Func(name, params, CreateASTTypeFor(ctx, el_ty),
-                           {
+                           utils::Vector{
                                b.Return(b.Construct(CreateASTTypeFor(ctx, el_ty), values)),
                            });
                 }
@@ -526,10 +526,10 @@
         auto access = var_user->Variable()->Access();
         return utils::GetOrCreate(
             store_funcs, LoadStoreKey{storage_class, access, buf_ty, el_ty}, [&] {
-                ast::ParameterList params{
+                utils::Vector params{
                     b.Param("buffer",
                             b.ty.pointer(CreateASTTypeFor(ctx, buf_ty), storage_class, access),
-                            {b.Disable(ast::DisabledValidation::kFunctionParameter)}),
+                            utils::Vector{b.Disable(ast::DisabledValidation::kFunctionParameter)}),
                     b.Param("offset", b.ty.u32()),
                     b.Param("value", CreateASTTypeFor(ctx, el_ty)),
                 };
@@ -539,14 +539,14 @@
                 if (auto* intrinsic = IntrinsicStoreFor(ctx.dst, storage_class, el_ty)) {
                     auto* func = b.create<ast::Function>(
                         name, params, b.ty.void_(), nullptr,
-                        ast::AttributeList{
+                        utils::Vector{
                             intrinsic,
                             b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
                         },
-                        ast::AttributeList{});
+                        utils::Empty);
                     b.AST().AddFunction(func);
                 } else {
-                    auto body = Switch(
+                    auto body = Switch<utils::Vector<const ast::Statement*, 8>>(
                         el_ty,  //
                         [&](const sem::Array* arr_ty) {
                             // fn store_func(buffer : buf_ty, offset : u32, value : el_ty) {
@@ -573,22 +573,22 @@
                             auto* for_loop =
                                 b.For(for_init, for_cond, for_cont, b.Block(store_stmt));
 
-                            return ast::StatementList{b.Decl(array), for_loop};
+                            return utils::Vector{b.Decl(array), for_loop};
                         },
                         [&](const sem::Matrix* mat_ty) {
                             auto* vec_ty = mat_ty->ColumnType();
                             Symbol store = StoreFunc(buf_ty, vec_ty, var_user);
-                            ast::StatementList stmts;
+                            utils::Vector<const ast::Statement*, 4> stmts;
                             for (uint32_t i = 0; i < mat_ty->columns(); i++) {
                                 auto* offset = b.Add("offset", u32(i * mat_ty->ColumnStride()));
                                 auto* element = b.IndexAccessor("value", u32(i));
                                 auto* call = b.Call(store, "buffer", offset, element);
-                                stmts.emplace_back(b.CallStmt(call));
+                                stmts.Push(b.CallStmt(call));
                             }
                             return stmts;
                         },
                         [&](const sem::Struct* str) {
-                            ast::StatementList stmts;
+                            utils::Vector<const ast::Statement*, 8> stmts;
                             for (auto* member : str->Members()) {
                                 auto* offset = b.Add("offset", u32(member->Offset()));
                                 auto* element = b.MemberAccessor(
@@ -596,7 +596,7 @@
                                 Symbol store =
                                     StoreFunc(buf_ty, member->Type()->UnwrapRef(), var_user);
                                 auto* call = b.Call(store, "buffer", offset, element);
-                                stmts.emplace_back(b.CallStmt(call));
+                                stmts.Push(b.CallStmt(call));
                             }
                             return stmts;
                         });
@@ -626,11 +626,11 @@
         return utils::GetOrCreate(atomic_funcs, AtomicKey{access, buf_ty, el_ty, op}, [&] {
             // The first parameter to all WGSL atomics is the expression to the
             // atomic. This is replaced with two parameters: the buffer and offset.
-            ast::ParameterList params = {
+            utils::Vector params{
                 b.Param("buffer",
                         b.ty.pointer(CreateASTTypeFor(ctx, buf_ty), ast::StorageClass::kStorage,
                                      access),
-                        {b.Disable(ast::DisabledValidation::kFunctionParameter)}),
+                        utils::Vector{b.Disable(ast::DisabledValidation::kFunctionParameter)}),
                 b.Param("offset", b.ty.u32()),
             };
 
@@ -638,7 +638,7 @@
             for (size_t i = 1; i < intrinsic->Parameters().Length(); i++) {
                 auto* param = intrinsic->Parameters()[i];
                 auto* ty = CreateASTTypeFor(ctx, param->Type());
-                params.emplace_back(b.Param("param_" + std::to_string(i), ty));
+                params.Push(b.Param("param_" + std::to_string(i), ty));
             }
 
             auto* atomic = IntrinsicAtomicFor(ctx.dst, op, el_ty);
@@ -655,10 +655,10 @@
                 auto* str = intrinsic->ReturnType()->As<sem::Struct>();
                 TINT_ASSERT(Transform, str && str->Declaration() == nullptr);
 
-                ast::StructMemberList ast_members;
-                ast_members.reserve(str->Members().size());
+                utils::Vector<const ast::StructMember*, 8> ast_members;
+                ast_members.Reserve(str->Members().size());
                 for (auto& m : str->Members()) {
-                    ast_members.push_back(
+                    ast_members.Push(
                         b.Member(ctx.Clone(m->Name()), CreateASTTypeFor(ctx, m->Type())));
                 }
 
@@ -671,11 +671,11 @@
 
             auto* func = b.create<ast::Function>(
                 b.Symbols().New(std::string{"tint_"} + intrinsic->str()), params, ret_ty, nullptr,
-                ast::AttributeList{
+                utils::Vector{
                     atomic,
                     b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
                 },
-                ast::AttributeList{});
+                utils::Empty);
 
             b.AST().AddFunction(func);
             return func->symbol;
@@ -933,10 +933,11 @@
                             Symbol func = state.AtomicFunc(buf_ty, el_ty, builtin,
                                                            access.var->As<sem::VariableUser>());
 
-                            ast::ExpressionList args{ctx.dst->AddressOf(ctx.Clone(buf)), offset};
-                            for (size_t i = 1; i < call_expr->args.size(); i++) {
+                            utils::Vector<const ast::Expression*, 8> args{
+                                ctx.dst->AddressOf(ctx.Clone(buf)), offset};
+                            for (size_t i = 1; i < call_expr->args.Length(); i++) {
                                 auto* arg = call_expr->args[i];
-                                args.emplace_back(ctx.Clone(arg));
+                                args.Push(ctx.Clone(arg));
                             }
                             return ctx.dst->Call(func, args);
                         });
diff --git a/src/tint/transform/decompose_strided_array.cc b/src/tint/transform/decompose_strided_array.cc
index ba6252b..61841f6 100644
--- a/src/tint/transform/decompose_strided_array.cc
+++ b/src/tint/transform/decompose_strided_array.cc
@@ -72,8 +72,10 @@
                     auto name = ctx.dst->Symbols().New("strided_arr");
                     auto* member_ty = ctx.Clone(ast->type);
                     auto* member = ctx.dst->Member(kMemberName, member_ty,
-                                                   {ctx.dst->MemberSize(arr->Stride())});
-                    ctx.dst->Structure(name, {member});
+                                                   utils::Vector{
+                                                       ctx.dst->MemberSize(arr->Stride()),
+                                                   });
+                    ctx.dst->Structure(name, utils::Vector{member});
                     return name;
                 });
                 auto* count = ctx.Clone(ast->count);
@@ -114,7 +116,7 @@
     // ->
     //   `array<strided_arr, 3>(strided_arr(1), strided_arr(2), strided_arr(3))`
     ctx.ReplaceAll([&](const ast::CallExpression* expr) -> const ast::Expression* {
-        if (!expr->args.empty()) {
+        if (!expr->args.IsEmpty()) {
             if (auto* call = sem.Get(expr)->UnwrapMaterialize()->As<sem::Call>()) {
                 if (auto* ctor = call->Target()->As<sem::TypeConstructor>()) {
                     if (auto* arr = ctor->ReturnType()->As<sem::Array>()) {
@@ -130,18 +132,18 @@
                             target.name = ctx.Clone(expr->target.name);
                         }
 
-                        ast::ExpressionList args;
+                        utils::Vector<const ast::Expression*, 8> args;
                         if (auto it = decomposed.find(arr); it != decomposed.end()) {
-                            args.reserve(expr->args.size());
+                            args.Reserve(expr->args.Length());
                             for (auto* arg : expr->args) {
-                                args.emplace_back(ctx.dst->Call(it->second, ctx.Clone(arg)));
+                                args.Push(ctx.dst->Call(it->second, ctx.Clone(arg)));
                             }
                         } else {
                             args = ctx.Clone(expr->args);
                         }
 
-                        return target.type ? ctx.dst->Construct(target.type, args)
-                                           : ctx.dst->Call(target.name, args);
+                        return target.type ? ctx.dst->Construct(target.type, std::move(args))
+                                           : ctx.dst->Call(target.name, std::move(args));
                     }
                 }
             }
diff --git a/src/tint/transform/decompose_strided_array_test.cc b/src/tint/transform/decompose_strided_array_test.cc
index 65b394a..4b0a335 100644
--- a/src/tint/transform/decompose_strided_array_test.cc
+++ b/src/tint/transform/decompose_strided_array_test.cc
@@ -79,12 +79,12 @@
 
     ProgramBuilder b;
     b.GlobalVar("arr", b.ty.array<f32, 4u>(4), ast::StorageClass::kPrivate);
-    b.Func("f", {}, b.ty.void_(),
-           {
+    b.Func("f", utils::Empty, b.ty.void_(),
+           utils::Vector{
                b.Decl(b.Let("a", b.ty.array<f32, 4u>(4), b.Expr("arr"))),
                b.Decl(b.Let("b", b.ty.f32(), b.IndexAccessor("arr", 1_i))),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -115,12 +115,12 @@
 
     ProgramBuilder b;
     b.GlobalVar("arr", b.ty.array<f32, 4u>(32), ast::StorageClass::kPrivate);
-    b.Func("f", {}, b.ty.void_(),
-           {
+    b.Func("f", utils::Empty, b.ty.void_(),
+           utils::Vector{
                b.Decl(b.Let("a", b.ty.array<f32, 4u>(32), b.Expr("arr"))),
                b.Decl(b.Let("b", b.ty.f32(), b.IndexAccessor("arr", 1_i))),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -157,14 +157,14 @@
     //   let b : f32 = s.a[1];
     // }
     ProgramBuilder b;
-    auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4u>(32))});
+    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.Func("f", {}, b.ty.void_(),
-           {
+    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"))),
                b.Decl(b.Let("b", b.ty.f32(), b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i))),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -205,16 +205,16 @@
     //   let b : f32 = s.a[1][2];
     // }
     ProgramBuilder b;
-    auto* S = b.Structure("S", {b.Member("a", b.ty.array(b.ty.vec4<f32>(), 4_u, 16))});
+    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.Func(
-        "f", {}, b.ty.void_(),
-        {
+        "f", utils::Empty, b.ty.void_(),
+        utils::Vector{
             b.Decl(b.Let("a", b.ty.array(b.ty.vec4<f32>(), 4_u, 16), b.MemberAccessor("s", "a"))),
             b.Decl(b.Let("b", b.ty.f32(),
                          b.IndexAccessor(b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i), 2_i))),
         },
-        {
+        utils::Vector{
             b.Stage(ast::PipelineStage::kCompute),
             b.WorkgroupSize(1_i),
         });
@@ -251,14 +251,14 @@
     //   let b : f32 = s.a[1];
     // }
     ProgramBuilder b;
-    auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4u>(32))});
+    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.Func("f", {}, b.ty.void_(),
-           {
+    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"))),
                b.Decl(b.Let("b", b.ty.f32(), b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i))),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -299,14 +299,14 @@
     //   let b : f32 = s.a[1];
     // }
     ProgramBuilder b;
-    auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4u>(4))});
+    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.Func("f", {}, b.ty.void_(),
-           {
+    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"))),
                b.Decl(b.Let("b", b.ty.f32(), b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i))),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -343,17 +343,17 @@
     //   s.a[1i] = 5.0;
     // }
     ProgramBuilder b;
-    auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4u>(32))});
+    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.Func("f", {}, b.ty.void_(),
-           {
+    b.Func("f", utils::Empty, b.ty.void_(),
+           utils::Vector{
                b.Assign(b.MemberAccessor("s", "a"), b.Construct(b.ty.array<f32, 4u>(32))),
                b.Assign(b.MemberAccessor("s", "a"),
                         b.Construct(b.ty.array<f32, 4u>(32), 1_f, 2_f, 3_f, 4_f)),
                b.Assign(b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i), 5_f),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -397,17 +397,17 @@
     //   s.a[1] = 5.0;
     // }
     ProgramBuilder b;
-    auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4u>(4))});
+    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.Func("f", {}, b.ty.void_(),
-           {
+    b.Func("f", utils::Empty, b.ty.void_(),
+           utils::Vector{
                b.Assign(b.MemberAccessor("s", "a"), b.Construct(b.ty.array<f32, 4u>(4))),
                b.Assign(b.MemberAccessor("s", "a"),
                         b.Construct(b.ty.array<f32, 4u>(4), 1_f, 2_f, 3_f, 4_f)),
                b.Assign(b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i), 5_f),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -449,11 +449,11 @@
     //   (*b)[1] = 5.0;
     // }
     ProgramBuilder b;
-    auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4u>(32))});
+    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.Func("f", {}, b.ty.void_(),
-           {
+    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"))),
@@ -461,7 +461,7 @@
                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),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -510,11 +510,11 @@
     // }
     ProgramBuilder b;
     b.Alias("ARR", b.ty.array<f32, 4u>(32));
-    auto* S = b.Structure("S", {b.Member("a", b.ty.type_name("ARR"))});
+    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.Func("f", {}, b.ty.void_(),
-           {
+    b.Func("f", utils::Empty, b.ty.void_(),
+           utils::Vector{
                b.Decl(b.Let("a", b.ty.type_name("ARR"), b.MemberAccessor("s", "a"))),
                b.Decl(b.Let("b", b.ty.f32(), b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i))),
                b.Assign(b.MemberAccessor("s", "a"), b.Construct(b.ty.type_name("ARR"))),
@@ -522,7 +522,7 @@
                         b.Construct(b.ty.type_name("ARR"), 1_f, 2_f, 3_f, 4_f)),
                b.Assign(b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i), 5_f),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -580,11 +580,11 @@
             b.ty.array(                                        //
                 b.ty.array(b.ty.type_name("ARR_A"), 3_u, 16),  //
                 4_u, 128));
-    auto* S = b.Structure("S", {b.Member("a", b.ty.type_name("ARR_B"))});
+    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.Func("f", {}, b.ty.void_(),
-           {
+    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"))),
                b.Decl(b.Let("b", b.ty.array(b.ty.type_name("ARR_A"), 3_u, 16),
                             b.IndexAccessor(                 //
@@ -614,7 +614,7 @@
                             1_i),
                         5_f),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
diff --git a/src/tint/transform/decompose_strided_matrix.cc b/src/tint/transform/decompose_strided_matrix.cc
index 3d474ed..75c96f6 100644
--- a/src/tint/transform/decompose_strided_matrix.cc
+++ b/src/tint/transform/decompose_strided_matrix.cc
@@ -170,16 +170,16 @@
                 auto array = [&] { return info.array(ctx.dst); };
 
                 auto mat = ctx.dst->Sym("m");
-                ast::ExpressionList columns(info.matrix->columns());
-                for (uint32_t i = 0; i < static_cast<uint32_t>(columns.size()); i++) {
-                    columns[i] = ctx.dst->IndexAccessor(mat, u32(i));
+                utils::Vector<const ast::Expression*, 4> columns;
+                for (uint32_t i = 0; i < static_cast<uint32_t>(info.matrix->columns()); i++) {
+                    columns.Push(ctx.dst->IndexAccessor(mat, u32(i)));
                 }
                 ctx.dst->Func(name,
-                              {
+                              utils::Vector{
                                   ctx.dst->Param(mat, matrix()),
                               },
                               array(),
-                              {
+                              utils::Vector{
                                   ctx.dst->Return(ctx.dst->Construct(array(), columns)),
                               });
                 return name;
@@ -211,16 +211,16 @@
                 auto array = [&] { return info.array(ctx.dst); };
 
                 auto arr = ctx.dst->Sym("arr");
-                ast::ExpressionList columns(info.matrix->columns());
-                for (uint32_t i = 0; i < static_cast<uint32_t>(columns.size()); i++) {
-                    columns[i] = ctx.dst->IndexAccessor(arr, u32(i));
+                utils::Vector<const ast::Expression*, 4> columns;
+                for (uint32_t i = 0; i < static_cast<uint32_t>(info.matrix->columns()); i++) {
+                    columns.Push(ctx.dst->IndexAccessor(arr, u32(i)));
                 }
                 ctx.dst->Func(name,
-                              {
+                              utils::Vector{
                                   ctx.dst->Param(arr, array()),
                               },
                               matrix(),
-                              {
+                              utils::Vector{
                                   ctx.dst->Return(ctx.dst->Construct(matrix(), columns)),
                               });
                 return name;
diff --git a/src/tint/transform/decompose_strided_matrix_test.cc b/src/tint/transform/decompose_strided_matrix_test.cc
index daff16f..afb06e8 100644
--- a/src/tint/transform/decompose_strided_matrix_test.cc
+++ b/src/tint/transform/decompose_strided_matrix_test.cc
@@ -68,20 +68,20 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure(
-        "S", {
+        "S", utils::Vector{
                  b.Member("m", b.ty.mat2x2<f32>(),
-                          {
+                          utils::Vector{
                               b.create<ast::StructMemberOffsetAttribute>(16u),
                               b.create<ast::StrideAttribute>(32u),
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
                           }),
              });
     b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.GroupAndBinding(0, 0));
-    b.Func("f", {}, b.ty.void_(),
-           {
+    b.Func("f", utils::Empty, b.ty.void_(),
+           utils::Vector{
                b.Decl(b.Let("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -124,9 +124,9 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure(
-        "S", {
+        "S", utils::Vector{
                  b.Member("m", b.ty.mat2x2<f32>(),
-                          {
+                          utils::Vector{
                               b.create<ast::StructMemberOffsetAttribute>(16u),
                               b.create<ast::StrideAttribute>(32u),
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
@@ -134,11 +134,11 @@
              });
     b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.GroupAndBinding(0, 0));
     b.Func(
-        "f", {}, b.ty.void_(),
-        {
+        "f", utils::Empty, b.ty.void_(),
+        utils::Vector{
             b.Decl(b.Let("x", b.ty.vec2<f32>(), b.IndexAccessor(b.MemberAccessor("s", "m"), 1_i))),
         },
-        {
+        utils::Vector{
             b.Stage(ast::PipelineStage::kCompute),
             b.WorkgroupSize(1_i),
         });
@@ -177,20 +177,20 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure(
-        "S", {
+        "S", utils::Vector{
                  b.Member("m", b.ty.mat2x2<f32>(),
-                          {
+                          utils::Vector{
                               b.create<ast::StructMemberOffsetAttribute>(16u),
                               b.create<ast::StrideAttribute>(8u),
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
                           }),
              });
     b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kUniform, b.GroupAndBinding(0, 0));
-    b.Func("f", {}, b.ty.void_(),
-           {
+    b.Func("f", utils::Empty, b.ty.void_(),
+           utils::Vector{
                b.Decl(b.Let("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -230,9 +230,9 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure(
-        "S", {
+        "S", utils::Vector{
                  b.Member("m", b.ty.mat2x2<f32>(),
-                          {
+                          utils::Vector{
                               b.create<ast::StructMemberOffsetAttribute>(8u),
                               b.create<ast::StrideAttribute>(32u),
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
@@ -240,11 +240,11 @@
              });
     b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite,
                 b.GroupAndBinding(0, 0));
-    b.Func("f", {}, b.ty.void_(),
-           {
+    b.Func("f", utils::Empty, b.ty.void_(),
+           utils::Vector{
                b.Decl(b.Let("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -287,9 +287,9 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure(
-        "S", {
+        "S", utils::Vector{
                  b.Member("m", b.ty.mat2x2<f32>(),
-                          {
+                          utils::Vector{
                               b.create<ast::StructMemberOffsetAttribute>(16u),
                               b.create<ast::StrideAttribute>(32u),
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
@@ -298,11 +298,11 @@
     b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite,
                 b.GroupAndBinding(0, 0));
     b.Func(
-        "f", {}, b.ty.void_(),
-        {
+        "f", utils::Empty, b.ty.void_(),
+        utils::Vector{
             b.Decl(b.Let("x", b.ty.vec2<f32>(), b.IndexAccessor(b.MemberAccessor("s", "m"), 1_i))),
         },
-        {
+        utils::Vector{
             b.Stage(ast::PipelineStage::kCompute),
             b.WorkgroupSize(1_i),
         });
@@ -341,9 +341,9 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure(
-        "S", {
+        "S", utils::Vector{
                  b.Member("m", b.ty.mat2x2<f32>(),
-                          {
+                          utils::Vector{
                               b.create<ast::StructMemberOffsetAttribute>(8u),
                               b.create<ast::StrideAttribute>(32u),
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
@@ -351,12 +351,12 @@
              });
     b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite,
                 b.GroupAndBinding(0, 0));
-    b.Func("f", {}, b.ty.void_(),
-           {
+    b.Func("f", utils::Empty, b.ty.void_(),
+           utils::Vector{
                b.Assign(b.MemberAccessor("s", "m"),
                         b.mat2x2<f32>(b.vec2<f32>(1_f, 2_f), b.vec2<f32>(3_f, 4_f))),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -399,9 +399,9 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure(
-        "S", {
+        "S", utils::Vector{
                  b.Member("m", b.ty.mat2x2<f32>(),
-                          {
+                          utils::Vector{
                               b.create<ast::StructMemberOffsetAttribute>(8u),
                               b.create<ast::StrideAttribute>(32u),
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
@@ -409,11 +409,11 @@
              });
     b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite,
                 b.GroupAndBinding(0, 0));
-    b.Func("f", {}, b.ty.void_(),
-           {
+    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)),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -458,9 +458,9 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure(
-        "S", {
+        "S", utils::Vector{
                  b.Member("m", b.ty.mat2x2<f32>(),
-                          {
+                          utils::Vector{
                               b.create<ast::StructMemberOffsetAttribute>(8u),
                               b.create<ast::StrideAttribute>(32u),
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
@@ -468,8 +468,8 @@
              });
     b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite,
                 b.GroupAndBinding(0, 0));
-    b.Func("f", {}, b.ty.void_(),
-           {
+    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"))),
@@ -478,7 +478,7 @@
                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)),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -529,20 +529,20 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure(
-        "S", {
+        "S", utils::Vector{
                  b.Member("m", b.ty.mat2x2<f32>(),
-                          {
+                          utils::Vector{
                               b.create<ast::StructMemberOffsetAttribute>(8u),
                               b.create<ast::StrideAttribute>(32u),
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
                           }),
              });
     b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kPrivate);
-    b.Func("f", {}, b.ty.void_(),
-           {
+    b.Func("f", utils::Empty, b.ty.void_(),
+           utils::Vector{
                b.Decl(b.Let("x", b.ty.mat2x2<f32>(), b.MemberAccessor("s", "m"))),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
@@ -582,21 +582,21 @@
     // }
     ProgramBuilder b;
     auto* S = b.Structure(
-        "S", {
+        "S", utils::Vector{
                  b.Member("m", b.ty.mat2x2<f32>(),
-                          {
+                          utils::Vector{
                               b.create<ast::StructMemberOffsetAttribute>(8u),
                               b.create<ast::StrideAttribute>(32u),
                               b.Disable(ast::DisabledValidation::kIgnoreStrideAttribute),
                           }),
              });
     b.GlobalVar("s", b.ty.Of(S), ast::StorageClass::kPrivate);
-    b.Func("f", {}, b.ty.void_(),
-           {
+    b.Func("f", utils::Empty, b.ty.void_(),
+           utils::Vector{
                b.Assign(b.MemberAccessor("s", "m"),
                         b.mat2x2<f32>(b.vec2<f32>(1_f, 2_f), b.vec2<f32>(3_f, 4_f))),
            },
-           {
+           utils::Vector{
                b.Stage(ast::PipelineStage::kCompute),
                b.WorkgroupSize(1_i),
            });
diff --git a/src/tint/transform/first_index_offset.cc b/src/tint/transform/first_index_offset.cc
index 6af162d..a966f7f 100644
--- a/src/tint/transform/first_index_offset.cc
+++ b/src/tint/transform/first_index_offset.cc
@@ -114,16 +114,16 @@
 
     if (has_vertex_or_instance_index) {
         // Add uniform buffer members and calculate byte offsets
-        ast::StructMemberList members;
-        members.push_back(ctx.dst->Member(kFirstVertexName, ctx.dst->ty.u32()));
-        members.push_back(ctx.dst->Member(kFirstInstanceName, ctx.dst->ty.u32()));
+        utils::Vector<const ast::StructMember*, 8> members;
+        members.Push(ctx.dst->Member(kFirstVertexName, ctx.dst->ty.u32()));
+        members.Push(ctx.dst->Member(kFirstInstanceName, ctx.dst->ty.u32()));
         auto* struct_ = ctx.dst->Structure(ctx.dst->Sym(), std::move(members));
 
         // 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,
-                           ast::AttributeList{
+                           utils::Vector{
                                ctx.dst->create<ast::BindingAttribute>(ub_binding),
                                ctx.dst->create<ast::GroupAttribute>(ub_group),
                            });
diff --git a/src/tint/transform/fold_trivial_single_use_lets.cc b/src/tint/transform/fold_trivial_single_use_lets.cc
index 4dbc8c2..8a949ec 100644
--- a/src/tint/transform/fold_trivial_single_use_lets.cc
+++ b/src/tint/transform/fold_trivial_single_use_lets.cc
@@ -52,7 +52,7 @@
     for (auto* node : ctx.src->ASTNodes().Objects()) {
         if (auto* block = node->As<ast::BlockStatement>()) {
             auto& stmts = block->statements;
-            for (size_t stmt_idx = 0; stmt_idx < stmts.size(); stmt_idx++) {
+            for (size_t stmt_idx = 0; stmt_idx < stmts.Length(); stmt_idx++) {
                 auto* stmt = stmts[stmt_idx];
                 if (auto* let_decl = AsTrivialLetDecl(stmt)) {
                     auto* let = let_decl->variable;
@@ -65,7 +65,7 @@
                     auto* user = users[0];
                     auto* user_stmt = user->Stmt()->Declaration();
 
-                    for (size_t i = stmt_idx; i < stmts.size(); i++) {
+                    for (size_t i = stmt_idx; i < stmts.Length(); i++) {
                         if (user_stmt == stmts[i]) {
                             auto* user_expr = user->Declaration();
                             ctx.Remove(stmts, let_decl);
diff --git a/src/tint/transform/for_loop_to_loop.cc b/src/tint/transform/for_loop_to_loop.cc
index 8fff0a8..e585790 100644
--- a/src/tint/transform/for_loop_to_loop.cc
+++ b/src/tint/transform/for_loop_to_loop.cc
@@ -35,7 +35,7 @@
 
 void ForLoopToLoop::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
     ctx.ReplaceAll([&](const ast::ForLoopStatement* for_loop) -> const ast::Statement* {
-        ast::StatementList stmts;
+        utils::Vector<const ast::Statement*, 8> stmts;
         if (auto* cond = for_loop->condition) {
             // !condition
             auto* not_cond =
@@ -45,10 +45,10 @@
             auto* break_body = ctx.dst->Block(ctx.dst->create<ast::BreakStatement>());
 
             // if (!condition) { break; }
-            stmts.emplace_back(ctx.dst->If(not_cond, break_body));
+            stmts.Push(ctx.dst->If(not_cond, break_body));
         }
         for (auto* stmt : for_loop->body->statements) {
-            stmts.emplace_back(ctx.Clone(stmt));
+            stmts.Push(ctx.Clone(stmt));
         }
 
         const ast::BlockStatement* continuing = nullptr;
diff --git a/src/tint/transform/localize_struct_array_assignment.cc b/src/tint/transform/localize_struct_array_assignment.cc
index 7c3d695..d43ddae 100644
--- a/src/tint/transform/localize_struct_array_assignment.cc
+++ b/src/tint/transform/localize_struct_array_assignment.cc
@@ -98,8 +98,8 @@
     void Run() {
         struct Shared {
             bool process_nested_nodes = false;
-            ast::StatementList insert_before_stmts;
-            ast::StatementList insert_after_stmts;
+            utils::Vector<const ast::Statement*, 4> insert_before_stmts;
+            utils::Vector<const ast::Statement*, 4> insert_after_stmts;
         } s;
 
         ctx.ReplaceAll([&](const ast::AssignmentStatement* assign_stmt) -> const ast::Statement* {
@@ -130,10 +130,12 @@
 
             // Combine insert_before_stmts + new_assign_stmt + insert_after_stmts into
             // a block and return it
-            ast::StatementList stmts = std::move(s.insert_before_stmts);
-            stmts.reserve(1 + s.insert_after_stmts.size());
-            stmts.emplace_back(new_assign_stmt);
-            stmts.insert(stmts.end(), s.insert_after_stmts.begin(), s.insert_after_stmts.end());
+            auto stmts = std::move(s.insert_before_stmts);
+            stmts.Reserve(1 + s.insert_after_stmts.Length());
+            stmts.Push(new_assign_stmt);
+            for (auto* stmt : s.insert_after_stmts) {
+                stmts.Push(stmt);
+            }
 
             return b.Block(std::move(stmts));
         });
@@ -156,7 +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_back(
+                s.insert_before_stmts.Push(
                     b.Decl(b.Let(mem_access_ptr, nullptr, b.AddressOf(mem_access))));
 
                 // Disable further transforms when cloning
@@ -165,7 +167,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_back(
+                s.insert_before_stmts.Push(
                     b.Decl(b.Var(tmp_var, nullptr, b.Deref(mem_access_ptr))));
 
                 // Replace input index_access with a clone of itself, but with its
@@ -177,8 +179,13 @@
                 // Assign temp var back to array
                 // e.g. *(tint_symbol) = tint_symbol_1;
                 auto* assign_rhs_to_temp = b.Assign(b.Deref(mem_access_ptr), tmp_var);
-                s.insert_after_stmts.insert(s.insert_after_stmts.begin(),
-                                            assign_rhs_to_temp);  // push_front
+                {
+                    utils::Vector<const ast::Statement*, 8> stmts{assign_rhs_to_temp};
+                    for (auto* stmt : s.insert_after_stmts) {
+                        stmts.Push(stmt);
+                    }
+                    s.insert_after_stmts = std::move(stmts);
+                }
 
                 return new_index_access;
             });
diff --git a/src/tint/transform/loop_to_for_loop.cc b/src/tint/transform/loop_to_for_loop.cc
index 3e0a4b5..00bf5ec 100644
--- a/src/tint/transform/loop_to_for_loop.cc
+++ b/src/tint/transform/loop_to_for_loop.cc
@@ -29,7 +29,7 @@
 namespace {
 
 bool IsBlockWithSingleBreak(const ast::BlockStatement* block) {
-    if (block->statements.size() != 1) {
+    if (block->statements.Length() != 1) {
         return false;
     }
     return block->statements[0]->Is<ast::BreakStatement>();
@@ -74,7 +74,7 @@
         //   loop {  if (condition) { break; } ... }
         //   loop {  if (condition) {} else { break; } ... }
         auto& stmts = loop->body->statements;
-        if (stmts.empty()) {
+        if (stmts.IsEmpty()) {
             return nullptr;
         }
         auto* if_stmt = stmts[0]->As<ast::IfStatement>();
@@ -96,7 +96,7 @@
         // function call statement.
         const ast::Statement* continuing = nullptr;
         if (auto* loop_cont = loop->continuing) {
-            if (loop_cont->statements.size() != 1) {
+            if (loop_cont->statements.Length() != 1) {
                 return nullptr;
             }
 
diff --git a/src/tint/transform/module_scope_var_to_entry_point_param.cc b/src/tint/transform/module_scope_var_to_entry_point_param.cc
index 7bd3991..122fb3c 100644
--- a/src/tint/transform/module_scope_var_to_entry_point_param.cc
+++ b/src/tint/transform/module_scope_var_to_entry_point_param.cc
@@ -26,12 +26,15 @@
 #include "src/tint/sem/module.h"
 #include "src/tint/sem/statement.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/utils/string.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::ModuleScopeVarToEntryPointParam);
 
 namespace tint::transform {
 namespace {
 
+using WorkgroupParameterMemberList = utils::Vector<const ast::StructMember*, 8>;
+
 // The name of the struct member for arrays that are wrapped in structures.
 const char* kWrappedArrayMemberName = "arr";
 
@@ -102,7 +105,7 @@
                                      const sem::Variable* var,
                                      Symbol new_var_symbol,
                                      std::function<Symbol()> workgroup_param,
-                                     ast::StructMemberList& workgroup_parameter_members,
+                                     WorkgroupParameterMemberList& workgroup_parameter_members,
                                      bool& is_pointer,
                                      bool& is_wrapped) {
         auto* var_ast = var->Declaration()->As<ast::Var>();
@@ -119,7 +122,7 @@
                 auto* disable_validation =
                     ctx.dst->Disable(ast::DisabledValidation::kEntryPointParameter);
                 auto attrs = ctx.Clone(var->Declaration()->attributes);
-                attrs.push_back(disable_validation);
+                attrs.Push(disable_validation);
                 auto* param = ctx.dst->Param(new_var_symbol, store_type(), attrs);
                 ctx.InsertFront(func->params, param);
 
@@ -130,10 +133,8 @@
                 // Variables into the Storage and Uniform storage classes are redeclared as entry
                 // point parameters with a pointer type.
                 auto attributes = ctx.Clone(var->Declaration()->attributes);
-                attributes.push_back(
-                    ctx.dst->Disable(ast::DisabledValidation::kEntryPointParameter));
-                attributes.push_back(
-                    ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
+                attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kEntryPointParameter));
+                attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
 
                 auto* param_type = store_type();
                 if (auto* arr = ty->As<sem::Array>(); arr && arr->IsRuntimeSized()) {
@@ -142,7 +143,9 @@
                     // representable in Tint's AST.
                     CloneStructTypes(ty);
                     auto* wrapper = ctx.dst->Structure(
-                        ctx.dst->Sym(), {ctx.dst->Member(kWrappedArrayMemberName, param_type)});
+                        ctx.dst->Sym(), utils::Vector{
+                                            ctx.dst->Member(kWrappedArrayMemberName, param_type),
+                                        });
                     param_type = ctx.dst->ty.Of(wrapper);
                     is_wrapped = true;
                 }
@@ -162,7 +165,7 @@
 
                     // Create a member in the workgroup parameter struct.
                     auto member = ctx.Clone(var->Declaration()->symbol);
-                    workgroup_parameter_members.push_back(ctx.dst->Member(member, store_type()));
+                    workgroup_parameter_members.Push(ctx.dst->Member(member, store_type()));
                     CloneStructTypes(var->Type()->UnwrapRef());
 
                     // Create a function-scope variable that is a pointer to the member.
@@ -186,14 +189,21 @@
                     ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass);
                 auto* constructor = ctx.Clone(var->Declaration()->constructor);
                 auto* local_var = ctx.dst->Var(new_var_symbol, store_type(), sc, constructor,
-                                               ast::AttributeList{disable_validation});
+                                               utils::Vector{disable_validation});
                 ctx.InsertFront(func->body->statements, ctx.dst->Decl(local_var));
 
                 break;
             }
+            case ast::StorageClass::kPushConstant: {
+                ctx.dst->Diagnostics().add_error(
+                    diag::System::Transform,
+                    "unhandled module-scope storage class (" + utils::ToString(sc) + ")");
+                break;
+            }
             default: {
                 TINT_ICE(Transform, ctx.dst->Diagnostics())
                     << "unhandled module-scope storage class (" << sc << ")";
+                break;
             }
         }
     }
@@ -219,6 +229,12 @@
             case ast::StorageClass::kHandle:
             case ast::StorageClass::kWorkgroup:
                 break;
+            case ast::StorageClass::kPushConstant: {
+                ctx.dst->Diagnostics().add_error(
+                    diag::System::Transform,
+                    "unhandled module-scope storage class (" + utils::ToString(sc) + ")");
+                break;
+            }
             default: {
                 TINT_ICE(Transform, ctx.dst->Diagnostics())
                     << "unhandled module-scope storage class (" << sc << ")";
@@ -226,19 +242,20 @@
         }
 
         // Use a pointer for non-handle types.
-        ast::AttributeList attributes;
+        utils::Vector<const ast::Attribute*, 2> attributes;
         if (!ty->is_handle()) {
             param_type = ctx.dst->ty.pointer(param_type, sc, var_ast->declared_access);
             is_pointer = true;
 
             // Disable validation of the parameter's storage class and of arguments passed to it.
-            attributes.push_back(ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
-            attributes.push_back(
+            attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kIgnoreStorageClass));
+            attributes.Push(
                 ctx.dst->Disable(ast::DisabledValidation::kIgnoreInvalidPointerArgument));
         }
 
         // Redeclare the variable as a parameter.
-        ctx.InsertBack(func->params, ctx.dst->Param(new_var_symbol, param_type, attributes));
+        ctx.InsertBack(func->params,
+                       ctx.dst->Param(new_var_symbol, param_type, std::move(attributes)));
     }
 
     /// Replace all uses of `var` in `func` with references to `new_var`.
@@ -278,10 +295,10 @@
     /// Process the module.
     void Process() {
         // Predetermine the list of function calls that need to be replaced.
-        using CallList = std::vector<const ast::CallExpression*>;
+        using CallList = utils::Vector<const ast::CallExpression*, 8>;
         std::unordered_map<const ast::Function*, CallList> calls_to_replace;
 
-        std::vector<const ast::Function*> functions_to_process;
+        utils::Vector<const ast::Function*, 8> functions_to_process;
 
         // Build a list of functions that transitively reference any module-scope variables.
         for (auto* decl : ctx.src->Sem().Module()->DependencyOrderedDeclarations()) {
@@ -300,11 +317,11 @@
                 }
             }
             if (needs_processing) {
-                functions_to_process.push_back(func_ast);
+                functions_to_process.Push(func_ast);
 
                 // Find all of the calls to this function that will need to be replaced.
                 for (auto* call : func_sem->CallSites()) {
-                    calls_to_replace[call->Stmt()->Function()->Declaration()].push_back(
+                    calls_to_replace[call->Stmt()->Function()->Declaration()].Push(
                         call->Declaration());
                 }
             }
@@ -340,7 +357,7 @@
             // We aggregate all workgroup variables into a struct to avoid hitting MSL's limit for
             // threadgroup memory arguments.
             Symbol workgroup_parameter_symbol;
-            ast::StructMemberList workgroup_parameter_members;
+            WorkgroupParameterMemberList workgroup_parameter_members;
             auto workgroup_param = [&]() {
                 if (!workgroup_parameter_symbol.IsValid()) {
                     workgroup_parameter_symbol = ctx.dst->Sym();
@@ -387,7 +404,7 @@
                     auto* local_var = ctx.dst->Var(new_var_symbol,
                                                    CreateASTTypeFor(ctx, var->Type()->UnwrapRef()),
                                                    ast::StorageClass::kPrivate, constructor,
-                                                   ast::AttributeList{disable_validation});
+                                                   utils::Vector{disable_validation});
                     ctx.InsertFront(func_ast->body->statements, ctx.dst->Decl(local_var));
                     local_private_vars_.insert(var);
                 } else {
@@ -408,7 +425,7 @@
                 ReplaceUsesInFunction(func_ast, var, new_var_symbol, is_pointer, is_wrapped);
             }
 
-            if (!workgroup_parameter_members.empty()) {
+            if (!workgroup_parameter_members.IsEmpty()) {
                 // Create the workgroup memory parameter.
                 // The parameter is a struct that contains members for each workgroup variable.
                 auto* str =
@@ -417,7 +434,8 @@
                     ctx.dst->ty.pointer(ctx.dst->ty.Of(str), ast::StorageClass::kWorkgroup);
                 auto* disable_validation =
                     ctx.dst->Disable(ast::DisabledValidation::kEntryPointParameter);
-                auto* param = ctx.dst->Param(workgroup_param(), param_type, {disable_validation});
+                auto* param = ctx.dst->Param(workgroup_param(), param_type,
+                                             utils::Vector{disable_validation});
                 ctx.InsertFront(func_ast->params, param);
             }
 
diff --git a/src/tint/transform/multiplanar_external_texture.cc b/src/tint/transform/multiplanar_external_texture.cc
index a554616..f68a165 100644
--- a/src/tint/transform/multiplanar_external_texture.cc
+++ b/src/tint/transform/multiplanar_external_texture.cc
@@ -95,7 +95,7 @@
             // If the attributes are empty, then this must be a texture_external
             // passed as a function parameter. These variables are transformed
             // elsewhere.
-            if (global->attributes.empty()) {
+            if (global->attributes.IsEmpty()) {
                 continue;
             }
 
@@ -140,7 +140,7 @@
 
             // Replace the original texture_external binding with a texture_2d<f32>
             // binding.
-            ast::AttributeList cloned_attributes = ctx.Clone(global->attributes);
+            auto cloned_attributes = ctx.Clone(global->attributes);
             const ast::Expression* cloned_constructor = ctx.Clone(global->constructor);
 
             auto* replacement =
@@ -239,7 +239,7 @@
     /// Creates the parameter structs associated with the transform.
     void createExtTexParamsStructs() {
         // Create GammaTransferParams struct.
-        ast::StructMemberList gamma_transfer_member_list = {
+        utils::Vector gamma_transfer_member_list{
             b.Member("G", b.ty.f32()), b.Member("A", b.ty.f32()),      b.Member("B", b.ty.f32()),
             b.Member("C", b.ty.f32()), b.Member("D", b.ty.f32()),      b.Member("E", b.ty.f32()),
             b.Member("F", b.ty.f32()), b.Member("padding", b.ty.u32())};
@@ -249,7 +249,7 @@
         b.Structure(gamma_transfer_struct_sym, gamma_transfer_member_list);
 
         // Create ExternalTextureParams struct.
-        ast::StructMemberList ext_tex_params_member_list = {
+        utils::Vector ext_tex_params_member_list{
             b.Member("numPlanes", b.ty.u32()),
             b.Member("doYuvToRgbConversionOnly", b.ty.u32()),
             b.Member("yuvToRgbConversionMatrix", b.ty.mat3x4(b.ty.f32())),
@@ -269,12 +269,12 @@
 
         b.Func(
             gamma_correction_sym,
-            {
+            utils::Vector{
                 b.Param("v", b.ty.vec3<f32>()),
                 b.Param("params", b.ty.type_name(gamma_transfer_struct_sym)),
             },
             b.ty.vec3<f32>(),
-            {
+            utils::Vector{
                 // let cond = abs(v) < vec3(params.D);
                 b.Decl(b.Let(
                     "cond", nullptr,
@@ -303,7 +303,7 @@
     /// bodies of the textureSampleExternal and textureLoadExternal functions.
     /// @param call_type determines which function body to generate
     /// @returns a statement list that makes of the body of the chosen function
-    ast::StatementList createTexFnExtStatementList(sem::BuiltinType call_type) {
+    auto createTexFnExtStatementList(sem::BuiltinType call_type) {
         const ast::CallExpression* single_plane_call = nullptr;
         const ast::CallExpression* plane_0_call = nullptr;
         const ast::CallExpression* plane_1_call = nullptr;
@@ -325,7 +325,7 @@
             TINT_ICE(Transform, b.Diagnostics()) << "unhandled builtin: " << call_type;
         }
 
-        return {
+        return utils::Vector{
             // var color: vec3<f32>;
             b.Decl(b.Var("color", b.ty.vec3(b.ty.f32()))),
             // if ((params.numPlanes == 1u))
@@ -366,13 +366,12 @@
     /// @returns a call expression to textureSampleExternal
     const ast::CallExpression* createTexSmpExt(const ast::CallExpression* expr,
                                                NewBindingSymbols syms) {
-        ast::ExpressionList params;
         const ast::Expression* plane_0_binding_param = ctx.Clone(expr->args[0]);
 
-        if (expr->args.size() != 3) {
+        if (expr->args.Length() != 3) {
             TINT_ICE(Transform, b.Diagnostics()) << "expected textureSampleLevel call with a "
                                                     "texture_external to have 3 parameters, found "
-                                                 << expr->args.size() << " parameters";
+                                                 << expr->args.Length() << " parameters";
         }
 
         // TextureSampleExternal calls the gammaCorrection function, so ensure it
@@ -387,7 +386,7 @@
             // Emit the textureSampleExternal function.
             b.Func(
                 texture_sample_external_sym,
-                {
+                utils::Vector{
                     b.Param("plane0", b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
                     b.Param("plane1", b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
                     b.Param("smp", b.ty.sampler(ast::SamplerKind::kSampler)),
@@ -395,15 +394,19 @@
                     b.Param("params", b.ty.type_name(params_struct_sym)),
                 },
                 b.ty.vec4(b.ty.f32()),
-                {
+                utils::Vector{
                     createTexFnExtStatementList(sem::BuiltinType::kTextureSampleLevel),
                 });
         }
 
         const ast::IdentifierExpression* exp = b.Expr(texture_sample_external_sym);
-        params = {plane_0_binding_param, b.Expr(syms.plane_1), ctx.Clone(expr->args[1]),
-                  ctx.Clone(expr->args[2]), b.Expr(syms.params)};
-        return b.Call(exp, params);
+        return b.Call(exp, utils::Vector{
+                               plane_0_binding_param,
+                               b.Expr(syms.plane_1),
+                               ctx.Clone(expr->args[1]),
+                               ctx.Clone(expr->args[2]),
+                               b.Expr(syms.params),
+                           });
     }
 
     /// Creates the textureLoadExternal function if needed and returns a call
@@ -413,14 +416,13 @@
     /// @returns a call expression to textureLoadExternal
     const ast::CallExpression* createTexLdExt(const ast::CallExpression* expr,
                                               NewBindingSymbols syms) {
-        ast::ExpressionList params;
         const ast::Expression* plane_0_binding_param = ctx.Clone(expr->args[0]);
 
-        if (expr->args.size() != 2) {
+        if (expr->args.Length() != 2) {
             TINT_ICE(Transform, b.Diagnostics())
                 << "expected textureLoad call with a texture_external "
                    "to have 2 parameters, found "
-                << expr->args.size() << " parameters";
+                << expr->args.Length() << " parameters";
         }
 
         // TextureLoadExternal calls the gammaCorrection function, so ensure it
@@ -435,14 +437,14 @@
             // Emit the textureLoadExternal function.
             b.Func(
                 texture_load_external_sym,
-                {
+                utils::Vector{
                     b.Param("plane0", b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
                     b.Param("plane1", b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
                     b.Param("coord", b.ty.vec2(b.ty.i32())),
                     b.Param("params", b.ty.type_name(params_struct_sym)),
                 },
                 b.ty.vec4(b.ty.f32()),
-                {
+                utils::Vector{
                     createTexFnExtStatementList(sem::BuiltinType::kTextureLoad),
                 });
         }
diff --git a/src/tint/transform/num_workgroups_from_uniform.cc b/src/tint/transform/num_workgroups_from_uniform.cc
index 86310a9..b9476f2 100644
--- a/src/tint/transform/num_workgroups_from_uniform.cc
+++ b/src/tint/transform/num_workgroups_from_uniform.cc
@@ -121,7 +121,9 @@
         if (!num_workgroups_ubo) {
             auto* num_workgroups_struct = ctx.dst->Structure(
                 ctx.dst->Sym(),
-                {ctx.dst->Member(kNumWorkgroupsMemberName, ctx.dst->ty.vec3(ctx.dst->ty.u32()))});
+                utils::Vector{
+                    ctx.dst->Member(kNumWorkgroupsMemberName, ctx.dst->ty.vec3(ctx.dst->ty.u32())),
+                });
 
             uint32_t group, binding;
             if (cfg->ubo_binding.has_value()) {
@@ -146,7 +148,7 @@
 
             num_workgroups_ubo = ctx.dst->GlobalVar(
                 ctx.dst->Sym(), ctx.dst->ty.Of(num_workgroups_struct), ast::StorageClass::kUniform,
-                ast::AttributeList{ctx.dst->GroupAndBinding(group, binding)});
+                ctx.dst->GroupAndBinding(group, 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 7551c23..46e8f46 100644
--- a/src/tint/transform/promote_side_effects_to_decl.cc
+++ b/src/tint/transform/promote_side_effects_to_decl.cc
@@ -180,11 +180,12 @@
     }
 
     // Hoists any expressions in `maybe_hoist` and clears it
-    void Flush(ast::ExpressionList& maybe_hoist) {
+    template <size_t N>
+    void Flush(tint::utils::Vector<const ast::Expression*, N>& maybe_hoist) {
         for (auto* m : maybe_hoist) {
             Hoist(m);
         }
-        maybe_hoist.clear();
+        maybe_hoist.Clear();
     }
 
     // Recursive function that processes expressions for side-effects. It
@@ -197,7 +198,9 @@
     // * For index and member accessor expressions, special care is taken to not
     // over-hoist the lhs expressions, as these may be be chained to refer to a
     // single memory location.
-    bool ProcessExpression(const ast::Expression* expr, ast::ExpressionList& maybe_hoist) {
+    template <size_t N>
+    bool ProcessExpression(const ast::Expression* expr,
+                           tint::utils::Vector<const ast::Expression*, N>& maybe_hoist) {
         auto process = [&](const ast::Expression* e) -> bool {
             return ProcessExpression(e, maybe_hoist);
         };
@@ -205,7 +208,7 @@
         auto default_process = [&](const ast::Expression* e) {
             auto maybe = process(e);
             if (maybe) {
-                maybe_hoist.emplace_back(e);
+                maybe_hoist.Push(e);
             }
             if (HasSideEffects(e)) {
                 Flush(maybe_hoist);
@@ -240,7 +243,7 @@
             // for "v[a][b][c] + g()" we want to hoist all of "v[a][b][c]", not "t1 =
             // v[a]", then "t2 = t1[b]" then "t3 = t2[c]").
             if (maybe && HasSideEffects(lhs)) {
-                maybe_hoist.emplace_back(lhs);
+                maybe_hoist.Push(lhs);
                 Flush(maybe_hoist);
                 maybe = false;
             }
@@ -337,7 +340,7 @@
             return;
         }
 
-        ast::ExpressionList maybe_hoist;
+        tint::utils::Vector<const ast::Expression*, 8> maybe_hoist;
         ProcessExpression(expr, maybe_hoist);
     }
 
@@ -345,9 +348,9 @@
     // evaluate the rhs before the lhs, and possibly hoist the rhs expression.
     void ProcessAssignment(const ast::Expression* lhs, const ast::Expression* rhs) {
         // Evaluate rhs before lhs
-        ast::ExpressionList maybe_hoist;
+        tint::utils::Vector<const ast::Expression*, 8> maybe_hoist;
         if (ProcessExpression(rhs, maybe_hoist)) {
-            maybe_hoist.emplace_back(rhs);
+            maybe_hoist.Push(rhs);
         }
 
         // If the rhs has side-effects, it may affect the lhs, so hoist it right
@@ -414,7 +417,9 @@
     }
 
     // Recursive function used to decompose an expression for short-circuit eval.
-    const ast::Expression* Decompose(const ast::Expression* expr, ast::StatementList* curr_stmts) {
+    template <size_t N>
+    const ast::Expression* Decompose(const ast::Expression* expr,
+                                     tint::utils::Vector<const ast::Statement*, N>* curr_stmts) {
         // Helper to avoid passing in same args.
         auto decompose = [&](auto& e) { return Decompose(e, curr_stmts); };
 
@@ -424,7 +429,7 @@
                 auto name = b.Symbols().New();
                 auto* v = b.Let(name, nullptr, ctx.Clone(e));
                 auto* decl = b.Decl(v);
-                curr_stmts->push_back(decl);
+                curr_stmts->Push(decl);
                 return b.Expr(name);
             }
             return ctx.Clone(e);
@@ -471,7 +476,7 @@
                 // let r = temp;
 
                 auto name = b.Sym();
-                curr_stmts->push_back(b.Decl(b.Var(name, nullptr, decompose(bin_expr->lhs))));
+                curr_stmts->Push(b.Decl(b.Var(name, nullptr, decompose(bin_expr->lhs))));
 
                 const ast::Expression* if_cond = nullptr;
                 if (bin_expr->IsLogicalOr()) {
@@ -482,14 +487,14 @@
 
                 const ast::BlockStatement* if_body = nullptr;
                 {
-                    ast::StatementList stmts;
+                    tint::utils::Vector<const ast::Statement*, N> stmts;
                     TINT_SCOPED_ASSIGNMENT(curr_stmts, &stmts);
                     auto* new_rhs = decompose(bin_expr->rhs);
-                    curr_stmts->push_back(b.Assign(name, new_rhs));
+                    curr_stmts->Push(b.Assign(name, new_rhs));
                     if_body = b.Block(std::move(*curr_stmts));
                 }
 
-                curr_stmts->push_back(b.If(if_cond, if_body));
+                curr_stmts->Push(b.If(if_cond, if_body));
 
                 return b.Expr(name);
             },
@@ -537,8 +542,10 @@
     }
 
     // Inserts statements in `stmts` before `stmt`
-    void InsertBefore(const ast::StatementList& stmts, const ast::Statement* stmt) {
-        if (!stmts.empty()) {
+    template <size_t N>
+    void InsertBefore(tint::utils::Vector<const ast::Statement*, N>& stmts,
+                      const ast::Statement* stmt) {
+        if (!stmts.IsEmpty()) {
             auto ip = utils::GetInsertionPoint(ctx, stmt);
             for (auto* s : stmts) {
                 ctx.InsertBefore(ip.first->Declaration()->statements, ip.second, s);
@@ -556,7 +563,7 @@
                     return nullptr;
                 }
                 // rhs before lhs
-                ast::StatementList stmts;
+                tint::utils::Vector<const ast::Statement*, 8> stmts;
                 ctx.Replace(s->rhs, Decompose(s->rhs, &stmts));
                 ctx.Replace(s->lhs, Decompose(s->lhs, &stmts));
                 InsertBefore(stmts, s);
@@ -566,7 +573,7 @@
                 if (!sem.Get(s->expr)->HasSideEffects()) {
                     return nullptr;
                 }
-                ast::StatementList stmts;
+                tint::utils::Vector<const ast::Statement*, 8> stmts;
                 ctx.Replace(s->expr, Decompose(s->expr, &stmts));
                 InsertBefore(stmts, s);
                 return ctx.CloneWithoutTransform(s);
@@ -575,7 +582,7 @@
                 if (!s->condition || !sem.Get(s->condition)->HasSideEffects()) {
                     return nullptr;
                 }
-                ast::StatementList stmts;
+                tint::utils::Vector<const ast::Statement*, 8> stmts;
                 ctx.Replace(s->condition, Decompose(s->condition, &stmts));
                 InsertBefore(stmts, s);
                 return ctx.CloneWithoutTransform(s);
@@ -584,7 +591,7 @@
                 if (!sem.Get(s->condition)->HasSideEffects()) {
                     return nullptr;
                 }
-                ast::StatementList stmts;
+                tint::utils::Vector<const ast::Statement*, 8> stmts;
                 ctx.Replace(s->condition, Decompose(s->condition, &stmts));
                 InsertBefore(stmts, s);
                 return ctx.CloneWithoutTransform(s);
@@ -593,7 +600,7 @@
                 if (!sem.Get(s->condition)->HasSideEffects()) {
                     return nullptr;
                 }
-                ast::StatementList stmts;
+                tint::utils::Vector<const ast::Statement*, 8> stmts;
                 ctx.Replace(s->condition, Decompose(s->condition, &stmts));
                 InsertBefore(stmts, s);
                 return ctx.CloneWithoutTransform(s);
@@ -602,7 +609,7 @@
                 if (!s->value || !sem.Get(s->value)->HasSideEffects()) {
                     return nullptr;
                 }
-                ast::StatementList stmts;
+                tint::utils::Vector<const ast::Statement*, 8> stmts;
                 ctx.Replace(s->value, Decompose(s->value, &stmts));
                 InsertBefore(stmts, s);
                 return ctx.CloneWithoutTransform(s);
@@ -611,7 +618,7 @@
                 if (!sem.Get(s->condition)) {
                     return nullptr;
                 }
-                ast::StatementList stmts;
+                tint::utils::Vector<const ast::Statement*, 8> stmts;
                 ctx.Replace(s->condition, Decompose(s->condition, &stmts));
                 InsertBefore(stmts, s);
                 return ctx.CloneWithoutTransform(s);
@@ -621,7 +628,7 @@
                 if (!var->constructor || !sem.Get(var->constructor)->HasSideEffects()) {
                     return nullptr;
                 }
-                ast::StatementList stmts;
+                tint::utils::Vector<const ast::Statement*, 8> stmts;
                 ctx.Replace(var->constructor, Decompose(var->constructor, &stmts));
                 InsertBefore(stmts, s);
                 return b.Decl(ctx.CloneWithoutTransform(var));
diff --git a/src/tint/transform/remove_phonies.cc b/src/tint/transform/remove_phonies.cc
index 7ba109a..5e64252 100644
--- a/src/tint/transform/remove_phonies.cc
+++ b/src/tint/transform/remove_phonies.cc
@@ -72,6 +72,11 @@
         if (node->Is<ast::PhonyExpression>()) {
             return true;
         }
+        if (auto* stmt = node->As<ast::CallStatement>()) {
+            if (program->Sem().Get(stmt->expr)->ConstantValue() != nullptr) {
+                return true;
+            }
+        }
     }
     return false;
 }
@@ -82,74 +87,86 @@
     std::unordered_map<SinkSignature, Symbol, SinkSignature::Hasher> sinks;
 
     for (auto* node : ctx.src->ASTNodes().Objects()) {
-        if (auto* stmt = node->As<ast::AssignmentStatement>()) {
-            if (stmt->lhs->Is<ast::PhonyExpression>()) {
-                std::vector<const ast::Expression*> side_effects;
-                if (!ast::TraverseExpressions(
-                        stmt->rhs, ctx.dst->Diagnostics(), [&](const ast::CallExpression* expr) {
-                            // ast::CallExpression may map to a function or builtin call
-                            // (both may have side-effects), or a type constructor or
-                            // type conversion (both do not have side effects).
-                            auto* call = sem.Get<sem::Call>(expr);
-                            if (!call) {
-                                // Semantic node must be a Materialize, in which case the expression
-                                // was creation-time (compile time), so could not have side effects.
-                                // Just skip.
-                                return ast::TraverseAction::Skip;
-                            }
-                            if (call->Target()->IsAnyOf<sem::Function, sem::Builtin>() &&
-                                call->HasSideEffects()) {
-                                side_effects.push_back(expr);
-                                return ast::TraverseAction::Skip;
-                            }
-                            return ast::TraverseAction::Descend;
-                        })) {
-                    return;
-                }
-
-                if (side_effects.empty()) {
-                    // Phony assignment with no side effects.
-                    // Just remove it.
-                    RemoveStatement(ctx, stmt);
-                    continue;
-                }
-
-                if (side_effects.size() == 1) {
-                    if (auto* call = side_effects[0]->As<ast::CallExpression>()) {
-                        // Phony assignment with single call side effect.
-                        // Replace phony assignment with call.
-                        ctx.Replace(stmt, [&, call] { return ctx.dst->CallStmt(ctx.Clone(call)); });
-                        continue;
+        Switch(
+            node,
+            [&](const ast::AssignmentStatement* stmt) {
+                if (stmt->lhs->Is<ast::PhonyExpression>()) {
+                    std::vector<const ast::Expression*> side_effects;
+                    if (!ast::TraverseExpressions(
+                            stmt->rhs, ctx.dst->Diagnostics(),
+                            [&](const ast::CallExpression* expr) {
+                                // ast::CallExpression may map to a function or builtin call
+                                // (both may have side-effects), or a type constructor or
+                                // type conversion (both do not have side effects).
+                                auto* call = sem.Get<sem::Call>(expr);
+                                if (!call) {
+                                    // Semantic node must be a Materialize, in which case the
+                                    // expression was creation-time (compile time), so could not
+                                    // have side effects. Just skip.
+                                    return ast::TraverseAction::Skip;
+                                }
+                                if (call->Target()->IsAnyOf<sem::Function, sem::Builtin>() &&
+                                    call->HasSideEffects()) {
+                                    side_effects.push_back(expr);
+                                    return ast::TraverseAction::Skip;
+                                }
+                                return ast::TraverseAction::Descend;
+                            })) {
+                        return;
                     }
-                }
 
-                // Phony assignment with multiple side effects.
-                // Generate a call to a placeholder function with the side
-                // effects as arguments.
-                ctx.Replace(stmt, [&, side_effects] {
-                    SinkSignature sig;
-                    for (auto* arg : side_effects) {
-                        sig.types.push_back(sem.Get(arg)->Type()->UnwrapRef());
+                    if (side_effects.empty()) {
+                        // Phony assignment with no side effects.
+                        // Just remove it.
+                        RemoveStatement(ctx, stmt);
+                        return;
                     }
-                    auto sink = utils::GetOrCreate(sinks, sig, [&] {
-                        auto name = ctx.dst->Symbols().New("phony_sink");
-                        ast::ParameterList params;
-                        for (auto* ty : sig.types) {
-                            auto* ast_ty = CreateASTTypeFor(ctx, ty);
-                            params.push_back(
-                                ctx.dst->Param("p" + std::to_string(params.size()), ast_ty));
+
+                    if (side_effects.size() == 1) {
+                        if (auto* call = side_effects[0]->As<ast::CallExpression>()) {
+                            // Phony assignment with single call side effect.
+                            // Replace phony assignment with call.
+                            ctx.Replace(stmt,
+                                        [&, call] { return ctx.dst->CallStmt(ctx.Clone(call)); });
+                            return;
                         }
-                        ctx.dst->Func(name, params, ctx.dst->ty.void_(), {});
-                        return name;
-                    });
-                    ast::ExpressionList args;
-                    for (auto* arg : side_effects) {
-                        args.push_back(ctx.Clone(arg));
                     }
-                    return ctx.dst->CallStmt(ctx.dst->Call(sink, args));
-                });
-            }
-        }
+
+                    // Phony assignment with multiple side effects.
+                    // Generate a call to a placeholder function with the side
+                    // effects as arguments.
+                    ctx.Replace(stmt, [&, side_effects] {
+                        SinkSignature sig;
+                        for (auto* arg : side_effects) {
+                            sig.types.push_back(sem.Get(arg)->Type()->UnwrapRef());
+                        }
+                        auto sink = utils::GetOrCreate(sinks, sig, [&] {
+                            auto name = ctx.dst->Symbols().New("phony_sink");
+                            utils::Vector<const ast::Parameter*, 8> params;
+                            for (auto* ty : sig.types) {
+                                auto* ast_ty = CreateASTTypeFor(ctx, ty);
+                                params.Push(
+                                    ctx.dst->Param("p" + std::to_string(params.Length()), ast_ty));
+                            }
+                            ctx.dst->Func(name, params, ctx.dst->ty.void_(), {});
+                            return name;
+                        });
+                        utils::Vector<const ast::Expression*, 8> args;
+                        for (auto* arg : side_effects) {
+                            args.Push(ctx.Clone(arg));
+                        }
+                        return ctx.dst->CallStmt(ctx.dst->Call(sink, args));
+                    });
+                }
+            },
+            [&](const ast::CallStatement* stmt) {
+                // Remove call statements to const value-returning functions.
+                // TODO(crbug.com/tint/1637): Remove if `stmt->expr` has no side-effects.
+                auto* sem_expr = sem.Get(stmt->expr);
+                if ((sem_expr->ConstantValue() != nullptr) && !sem_expr->HasSideEffects()) {
+                    ctx.Remove(sem.Get(stmt)->Block()->Declaration()->statements, stmt);
+                }
+            });
     }
 
     ctx.Clone();
diff --git a/src/tint/transform/remove_phonies.h b/src/tint/transform/remove_phonies.h
index d04023b..daa1812 100644
--- a/src/tint/transform/remove_phonies.h
+++ b/src/tint/transform/remove_phonies.h
@@ -24,7 +24,7 @@
 
 /// RemovePhonies is a Transform that removes all phony-assignment statements,
 /// while preserving function call expressions in the RHS of the assignment that
-/// may have side-effects.
+/// may have side-effects. It also removes calls to builtins that return a constant value.
 class RemovePhonies final : public Castable<RemovePhonies, Transform> {
   public:
     /// Constructor
diff --git a/src/tint/transform/remove_phonies_test.cc b/src/tint/transform/remove_phonies_test.cc
index dc91b5a..a072e51 100644
--- a/src/tint/transform/remove_phonies_test.cc
+++ b/src/tint/transform/remove_phonies_test.cc
@@ -67,6 +67,8 @@
   _ = mat2x2<f32>(9.0, 10.0, 11.0, 12.0);
   _ = atan2(1.0, 2.0);
   _ = clamp(1.0, 2.0, 3.0);
+  atan2(1.0, 2.0);
+  clamp(1.0, 2.0, 3.0);
 }
 )";
 
diff --git a/src/tint/transform/renamer_test.cc b/src/tint/transform/renamer_test.cc
index 516b164..c57dea3 100644
--- a/src/tint/transform/renamer_test.cc
+++ b/src/tint/transform/renamer_test.cc
@@ -328,234 +328,234 @@
 
 INSTANTIATE_TEST_SUITE_P(RenamerTestGlsl,
                          RenamerTestGlsl,
-                         testing::Values("active",
-                                         //    "asm",       // WGSL keyword
-                                         "atomic_uint",
-                                         "attribute",
-                                         //    "bool",      // WGSL keyword
-                                         //    "break",     // WGSL keyword
-                                         "buffer",
-                                         "bvec2",
-                                         "bvec3",
-                                         "bvec4",
-                                         //    "case",      // WGSL keyword
-                                         "cast",
-                                         "centroid",
-                                         "class",
-                                         "coherent",
-                                         "common",
-                                         //    "const",     // WGSL keyword
-                                         //    "continue",  // WGSL keyword
-                                         //    "default",   // WGSL keyword
-                                         //    "discard",   // WGSL keyword
-                                         "dmat2",
-                                         "dmat2x2",
-                                         "dmat2x3",
-                                         "dmat2x4",
-                                         "dmat3",
-                                         "dmat3x2",
-                                         "dmat3x3",
-                                         "dmat3x4",
-                                         "dmat4",
-                                         "dmat4x2",
-                                         "dmat4x3",
-                                         "dmat4x4",
-                                         //    "do",         // WGSL keyword
-                                         "double",
-                                         "dvec2",
-                                         "dvec3",
-                                         "dvec4",
-                                         //    "else"        // WGSL keyword
-                                         //    "enum",       // WGSL keyword
-                                         "extern",
-                                         "external",
-                                         //    "false",      // WGSL keyword
-                                         "filter",
-                                         "fixed",
-                                         "flat",
-                                         "float",
-                                         //    "for",        // WGSL keyword
-                                         "fvec2",
-                                         "fvec3",
-                                         "fvec4",
-                                         "gl_BaseInstance",
-                                         "gl_BaseVertex",
-                                         "gl_ClipDistance",
-                                         "gl_DepthRangeParameters",
-                                         "gl_DrawID",
-                                         "gl_FragCoord",
-                                         "gl_FragDepth",
-                                         "gl_FrontFacing",
-                                         "gl_GlobalInvocationID",
-                                         "gl_InstanceID",
-                                         "gl_LocalInvocationID",
-                                         "gl_LocalInvocationIndex",
-                                         "gl_NumSamples",
-                                         "gl_NumWorkGroups",
-                                         "gl_PerVertex",
-                                         "gl_PointCoord",
-                                         "gl_PointSize",
-                                         "gl_Position",
-                                         "gl_PrimitiveID",
-                                         "gl_SampleID",
-                                         "gl_SampleMask",
-                                         "gl_SampleMaskIn",
-                                         "gl_SamplePosition",
-                                         "gl_VertexID",
-                                         "gl_WorkGroupID",
-                                         "gl_WorkGroupSize",
-                                         "goto",
-                                         "half",
-                                         "highp",
-                                         "hvec2",
-                                         "hvec3",
-                                         "hvec4",
-                                         //    "if",         // WGSL keyword
-                                         "iimage1D",
-                                         "iimage1DArray",
-                                         "iimage2D",
-                                         "iimage2DArray",
-                                         "iimage2DMS",
-                                         "iimage2DMSArray",
-                                         "iimage2DRect",
-                                         "iimage3D",
-                                         "iimageBuffer",
-                                         "iimageCube",
-                                         "iimageCubeArray",
-                                         "image1D",
-                                         "image1DArray",
-                                         "image2D",
-                                         "image2DArray",
-                                         "image2DMS",
-                                         "image2DMSArray",
-                                         "image2DRect",
-                                         "image3D",
-                                         "imageBuffer",
-                                         "imageCube",
-                                         "imageCubeArray",
-                                         "in",
-                                         "inline",
-                                         "inout",
-                                         "input",
-                                         "int",
-                                         "interface",
-                                         "invariant",
-                                         "isampler1D",
-                                         "isampler1DArray",
-                                         "isampler2D",
-                                         "isampler2DArray",
-                                         "isampler2DMS",
-                                         "isampler2DMSArray",
-                                         "isampler2DRect",
-                                         "isampler3D",
-                                         "isamplerBuffer",
-                                         "isamplerCube",
-                                         "isamplerCubeArray",
-                                         "ivec2",
-                                         "ivec3",
-                                         "ivec4",
-                                         "layout",
-                                         "long",
-                                         "lowp",
-                                         //    "mat2x2",      // WGSL keyword
-                                         //    "mat2x3",      // WGSL keyword
-                                         //    "mat2x4",      // WGSL keyword
-                                         //    "mat2",
-                                         "mat3",
-                                         //    "mat3x2",      // WGSL keyword
-                                         //    "mat3x3",      // WGSL keyword
-                                         //    "mat3x4",      // WGSL keyword
-                                         "mat4",
-                                         //    "mat4x2",      // WGSL keyword
-                                         //    "mat4x3",      // WGSL keyword
-                                         //    "mat4x4",      // WGSL keyword
-                                         "mediump",
-                                         "namespace",
-                                         "noinline",
-                                         "noperspective",
-                                         "out",
-                                         "output",
-                                         "partition",
-                                         "patch",
-                                         "precise",
-                                         "precision",
-                                         "public",
-                                         "readonly",
-                                         "resource",
-                                         "restrict",
-                                         //    "return",     // WGSL keyword
-                                         "sample",
-                                         "sampler1D",
-                                         "sampler1DArray",
-                                         "sampler1DArrayShadow",
-                                         "sampler1DShadow",
-                                         "sampler2D",
-                                         "sampler2DArray",
-                                         "sampler2DArrayShadow",
-                                         "sampler2DMS",
-                                         "sampler2DMSArray",
-                                         "sampler2DRect",
-                                         "sampler2DRectShadow",
-                                         "sampler2DShadow",
-                                         "sampler3D",
-                                         "sampler3DRect",
-                                         "samplerBuffer",
-                                         "samplerCube",
-                                         "samplerCubeArray",
-                                         "samplerCubeArrayShadow",
-                                         "samplerCubeShadow",
-                                         "shared",
-                                         "short",
-                                         "sizeof",
-                                         "smooth",
-                                         "static",
-                                         //    "struct",     // WGSL keyword
-                                         "subroutine",
-                                         "superp",
-                                         //    "switch",     // WGSL keyword
-                                         "template",
-                                         "this",
-                                         //    "true",       // WGSL keyword
-                                         //    "typedef",    // WGSL keyword
-                                         "uimage1D",
-                                         "uimage1DArray",
-                                         "uimage2D",
-                                         "uimage2DArray",
-                                         "uimage2DMS",
-                                         "uimage2DMSArray",
-                                         "uimage2DRect",
-                                         "uimage3D",
-                                         "uimageBuffer",
-                                         "uimageCube",
-                                         "uimageCubeArray",
-                                         "uint",
-                                         //    "uniform",    // WGSL keyword
-                                         "union",
-                                         "unsigned",
-                                         "usampler1D",
-                                         "usampler1DArray",
-                                         "usampler2D",
-                                         "usampler2DArray",
-                                         "usampler2DMS",
-                                         "usampler2DMSArray",
-                                         "usampler2DRect",
-                                         "usampler3D",
-                                         "usamplerBuffer",
-                                         "usamplerCube",
-                                         "usamplerCubeArray",
-                                         //    "using",      // WGSL keyword
-                                         "uvec2",
-                                         "uvec3",
-                                         "uvec4",
-                                         "varying",
-                                         //    "vec2",       // WGSL keyword
-                                         //    "vec3",       // WGSL keyword
-                                         //    "vec4",       // WGSL keyword
-                                         //    "void",       // WGSL keyword
-                                         "volatile",
-                                         //    "while",      // WGSL keyword
-                                         "writeonly",
-                                         kUnicodeIdentifier));
+                         testing::Values(  // "active",   // Also reserved in WGSL
+                                           // "asm",       // WGSL keyword
+                             "atomic_uint",
+                             // "attribute",  // Also reserved in WGSL
+                             // "bool",      // WGSL keyword
+                             // "break",     // WGSL keyword
+                             "buffer",
+                             "bvec2",
+                             "bvec3",
+                             "bvec4",
+                             //    "case",      // WGSL keyword
+                             // "cast",  // Also reserved in WGSL
+                             "centroid",
+                             // "class",  // Also reserved in WGSL
+                             // "coherent",  // Also reserved in WGSL
+                             // "common",  // Also reserved in WGSL
+                             // "const",     // WGSL keyword
+                             // "continue",  // WGSL keyword
+                             // "default",   // WGSL keyword
+                             // "discard",   // WGSL keyword
+                             "dmat2",
+                             "dmat2x2",
+                             "dmat2x3",
+                             "dmat2x4",
+                             "dmat3",
+                             "dmat3x2",
+                             "dmat3x3",
+                             "dmat3x4",
+                             "dmat4",
+                             "dmat4x2",
+                             "dmat4x3",
+                             "dmat4x4",
+                             // "do",         // WGSL keyword
+                             "double",
+                             "dvec2",
+                             "dvec3",
+                             "dvec4",
+                             // "else"        // WGSL keyword
+                             // "enum",       // WGSL keyword
+                             // "extern",  // Also reserved in WGSL
+                             // "external",  // Also reserved in WGSL
+                             // "false",      // WGSL keyword
+                             // "filter",  // Also reserved in WGSL
+                             "fixed",
+                             "flat",
+                             "float",
+                             // "for",        // WGSL keyword
+                             "fvec2",
+                             "fvec3",
+                             "fvec4",
+                             "gl_BaseInstance",
+                             "gl_BaseVertex",
+                             "gl_ClipDistance",
+                             "gl_DepthRangeParameters",
+                             "gl_DrawID",
+                             "gl_FragCoord",
+                             "gl_FragDepth",
+                             "gl_FrontFacing",
+                             "gl_GlobalInvocationID",
+                             "gl_InstanceID",
+                             "gl_LocalInvocationID",
+                             "gl_LocalInvocationIndex",
+                             "gl_NumSamples",
+                             "gl_NumWorkGroups",
+                             "gl_PerVertex",
+                             "gl_PointCoord",
+                             "gl_PointSize",
+                             "gl_Position",
+                             "gl_PrimitiveID",
+                             "gl_SampleID",
+                             "gl_SampleMask",
+                             "gl_SampleMaskIn",
+                             "gl_SamplePosition",
+                             "gl_VertexID",
+                             "gl_WorkGroupID",
+                             "gl_WorkGroupSize",
+                             // "goto",  // Also reserved in WGSL
+                             "half",
+                             // "highp",  // Also reserved in WGSL
+                             "hvec2",
+                             "hvec3",
+                             "hvec4",
+                             // "if",         // WGSL keyword
+                             "iimage1D",
+                             "iimage1DArray",
+                             "iimage2D",
+                             "iimage2DArray",
+                             "iimage2DMS",
+                             "iimage2DMSArray",
+                             "iimage2DRect",
+                             "iimage3D",
+                             "iimageBuffer",
+                             "iimageCube",
+                             "iimageCubeArray",
+                             "image1D",
+                             "image1DArray",
+                             "image2D",
+                             "image2DArray",
+                             "image2DMS",
+                             "image2DMSArray",
+                             "image2DRect",
+                             "image3D",
+                             "imageBuffer",
+                             "imageCube",
+                             "imageCubeArray",
+                             "in",
+                             // "inline",  // Also reserved in WGSL
+                             // "inout",  // Also reserved in WGSL
+                             "input",
+                             "int",
+                             // "interface",  // Also reserved in WGSL
+                             // "invariant",  // Also reserved in WGSL
+                             "isampler1D",
+                             "isampler1DArray",
+                             "isampler2D",
+                             "isampler2DArray",
+                             "isampler2DMS",
+                             "isampler2DMSArray",
+                             "isampler2DRect",
+                             "isampler3D",
+                             "isamplerBuffer",
+                             "isamplerCube",
+                             "isamplerCubeArray",
+                             "ivec2",
+                             "ivec3",
+                             "ivec4",
+                             // "layout",  // Also reserved in WGSL
+                             "long",
+                             // "lowp",  // Also reserved in WGSL
+                             // "mat2x2",      // WGSL keyword
+                             // "mat2x3",      // WGSL keyword
+                             // "mat2x4",      // WGSL keyword
+                             // "mat2",
+                             "mat3",
+                             // "mat3x2",      // WGSL keyword
+                             // "mat3x3",      // WGSL keyword
+                             // "mat3x4",      // WGSL keyword
+                             "mat4",
+                             // "mat4x2",      // WGSL keyword
+                             // "mat4x3",      // WGSL keyword
+                             // "mat4x4",      // WGSL keyword
+                             // "mediump",  // Also reserved in WGSL
+                             // "namespace",  // Also reserved in WGSL
+                             // "noinline",  // Also reserved in WGSL
+                             // "noperspective",  // Also reserved in WGSL
+                             "out",
+                             "output",
+                             // "partition",  // Also reserved in WGSL
+                             // "patch",  // Also reserved in WGSL
+                             // "precise",  // Also reserved in WGSL
+                             // "precision",  // Also reserved in WGSL
+                             // "public",  // Also reserved in WGSL
+                             // "readonly",  // Also reserved in WGSL
+                             // "resource",  // Also reserved in WGSL
+                             // "restrict",  // Also reserved in WGSL
+                             // "return",     // WGSL keyword
+                             "sample",
+                             "sampler1D",
+                             "sampler1DArray",
+                             "sampler1DArrayShadow",
+                             "sampler1DShadow",
+                             "sampler2D",
+                             "sampler2DArray",
+                             "sampler2DArrayShadow",
+                             "sampler2DMS",
+                             "sampler2DMSArray",
+                             "sampler2DRect",
+                             "sampler2DRectShadow",
+                             "sampler2DShadow",
+                             "sampler3D",
+                             "sampler3DRect",
+                             "samplerBuffer",
+                             "samplerCube",
+                             "samplerCubeArray",
+                             "samplerCubeArrayShadow",
+                             "samplerCubeShadow",
+                             // "shared"  // Also reserved in WGSL,
+                             "short",
+                             // "sizeof",  // Also reserved in WGSL
+                             // "smooth",  // Also reserved in WGSL
+                             // "static",  // Also reserved in WGSL
+                             // "struct",     // WGSL keyword
+                             // "subroutine",  // Also reserved in WGSL
+                             "superp",
+                             // "switch",     // WGSL keyword
+                             // "template",  // Also reserved in WGSL
+                             // "this",  // Also reserved in WGSL
+                             // "true",       // WGSL keyword
+                             // "typedef",    // WGSL keyword
+                             "uimage1D",
+                             "uimage1DArray",
+                             "uimage2D",
+                             "uimage2DArray",
+                             "uimage2DMS",
+                             "uimage2DMSArray",
+                             "uimage2DRect",
+                             "uimage3D",
+                             "uimageBuffer",
+                             "uimageCube",
+                             "uimageCubeArray",
+                             "uint",
+                             // "uniform",    // WGSL keyword
+                             // "union",  // Also reserved in WGSL
+                             "unsigned",
+                             "usampler1D",
+                             "usampler1DArray",
+                             "usampler2D",
+                             "usampler2DArray",
+                             "usampler2DMS",
+                             "usampler2DMSArray",
+                             "usampler2DRect",
+                             "usampler3D",
+                             "usamplerBuffer",
+                             "usamplerCube",
+                             "usamplerCubeArray",
+                             // "using",      // WGSL keyword
+                             "uvec2",
+                             "uvec3",
+                             "uvec4",
+                             // "varying",  // Also reserved in WGSL
+                             // "vec2",       // WGSL keyword
+                             // "vec3",       // WGSL keyword
+                             // "vec4",       // WGSL keyword
+                             // "void",       // WGSL keyword
+                             // "volatile",  // Also reserved in WGSL
+                             // "while",      // WGSL keyword
+                             // "writeonly",  // Also reserved in WGSL
+                             kUnicodeIdentifier));
 
 INSTANTIATE_TEST_SUITE_P(RenamerTestHlsl,
                          RenamerTestHlsl,
@@ -575,8 +575,8 @@
                                          "COLOR",
                                          "CheckAccessFullyMapped",
                                          "ComparisonFunc",
-                                         "CompileShader",
-                                         "ComputeShader",
+                                         // "CompileShader",  // Also reserved in WGSL
+                                         // "ComputeShader",  // Also reserved in WGSL
                                          "ConsumeStructuredBuffer",
                                          "D3DCOLORtoUBYTE4",
                                          "DEPTH",
@@ -584,18 +584,18 @@
                                          "DepthStencilView",
                                          "DeviceMemoryBarrier",
                                          "DeviceMemroyBarrierWithGroupSync",
-                                         "DomainShader",
+                                         // "DomainShader",  // Also reserved in WGSL
                                          "EvaluateAttributeAtCentroid",
                                          "EvaluateAttributeAtSample",
                                          "EvaluateAttributeSnapped",
                                          "FOG",
                                          "Filter",
-                                         "GeometryShader",
+                                         // "GeometryShader",  // Also reserved in WGSL
                                          "GetRenderTargetSampleCount",
                                          "GetRenderTargetSamplePosition",
                                          "GroupMemoryBarrier",
                                          "GroupMemroyBarrierWithGroupSync",
-                                         "Hullshader",
+                                         // "Hullshader",  // Also reserved in WGSL
                                          "InputPatch",
                                          "InterlockedAdd",
                                          "InterlockedAnd",
@@ -612,7 +612,7 @@
                                          "MinLOD",
                                          "MipLODBias",
                                          "NORMAL",
-                                         "NULL",
+                                         // "NULL",  // Also reserved in WGSL
                                          "Normal",
                                          "OutputPatch",
                                          "POSITION",
@@ -703,11 +703,11 @@
                                          // "asin",  // WGSL builtin
                                          "asint",
                                          // "asm",  // WGSL keyword
-                                         "asm_fragment",
+                                         // "asm_fragment",  // Also reserved in WGSL
                                          "asuint",
                                          // "atan",  // WGSL builtin
                                          // "atan2",  // WGSL builtin
-                                         "auto",
+                                         // "auto",  // Also reserved in WGSL
                                          // "bool",  // WGSL keyword
                                          "bool1",
                                          "bool1x1",
@@ -733,19 +733,19 @@
                                          // "break",  // WGSL keyword
                                          // "call",  // WGSL builtin
                                          // "case",  // WGSL keyword
-                                         "catch",
+                                         // "catch",  // Also reserved in WGSL
                                          "cbuffer",
                                          // "ceil",  // WGSL builtin
                                          "centroid",
                                          "char",
                                          // "clamp",  // WGSL builtin
-                                         "class",
+                                         // "class",  // Also reserved in WGSL
                                          "clip",
-                                         "column_major",
-                                         "compile",
-                                         "compile_fragment",
+                                         // "column_major",  // Also reserved in WGSL
+                                         // "compile",  // Also reserved in WGSL
+                                         // "compile_fragment",  // Also reserved in WGSL
                                          // "const",  // WGSL keyword
-                                         "const_cast",
+                                         // "const_cast",  // Also reserved in WGSL
                                          // "continue",  // WGSL keyword
                                          // "cos",  // WGSL builtin
                                          // "cosh",  // WGSL builtin
@@ -759,7 +759,7 @@
                                          "ddy_fine",
                                          // "default",  // WGSL keyword
                                          "degrees",
-                                         "delete",
+                                         // "delete",  // Also reserved in WGSL
                                          // "determinant",  // WGSL builtin
                                          // "discard",  // WGSL keyword
                                          // "distance",  // WGSL builtin
@@ -808,15 +808,15 @@
                                          "dword4x2",
                                          "dword4x3",
                                          "dword4x4",
-                                         "dynamic_cast",
+                                         // "dynamic_cast",  // Also reserved in WGSL
                                          // "else",  // WGSL keyword
                                          // "enum",  // WGSL keyword
                                          "errorf",
                                          // "exp",  // WGSL builtin
                                          // "exp2",  // WGSL builtin
-                                         "explicit",
-                                         "export",
-                                         "extern",
+                                         // "explicit",  // Also reserved in WGSL
+                                         // "export",  // Also reserved in WGSL
+                                         // "extern",  // Also reserved in WGSL
                                          "f16to32",
                                          "f32tof16",
                                          // "faceforward",  // WGSL builtin
@@ -853,11 +853,11 @@
                                          "forcecase",
                                          "frac",
                                          // "frexp",  // WGSL builtin
-                                         "friend",
+                                         // "friend",  // Also reserved in WGSL
                                          // "fwidth",  // WGSL builtin
-                                         "fxgroup",
-                                         "goto",
-                                         "groupshared",
+                                         // "fxgroup",  // Also reserved in WGSL
+                                         // "goto",  // Also reserved in WGSL
+                                         // "groupshared",  // Also reserved in WGSL
                                          "half",
                                          "half1",
                                          "half1x1",
@@ -881,8 +881,8 @@
                                          "half4x4",
                                          // "if",  // WGSL keyword
                                          // "in",  // WGSL keyword
-                                         "inline",
-                                         "inout",
+                                         // "inline",  // Also reserved in WGSL
+                                         // "inout",  // Also reserved in WGSL
                                          "int",
                                          "int1",
                                          "int1x1",
@@ -904,15 +904,15 @@
                                          "int4x2",
                                          "int4x3",
                                          "int4x4",
-                                         "interface",
+                                         // "interface",  // Also reserved in WGSL
                                          "isfinite",
                                          "isinf",
                                          "isnan",
                                          // "ldexp",  // WGSL builtin
                                          // "length",  // WGSL builtin
                                          "lerp",
-                                         "line",
-                                         "lineadj",
+                                         // "line",  // Also reserved in WGSL
+                                         // "lineadj",  // Also reserved in WGSL
                                          "linear",
                                          "lit",
                                          // "log",  // WGSL builtin
@@ -1032,33 +1032,33 @@
                                          // "modf",  // WGSL builtin
                                          "msad4",
                                          "mul",
-                                         "mutable",
-                                         "namespace",
-                                         "new",
-                                         "nointerpolation",
+                                         // "mutable",  // Also reserved in WGSL
+                                         // "namespace",  // Also reserved in WGSL
+                                         // "new",  // Also reserved in WGSL
+                                         // "nointerpolation",  // Also reserved in WGSL
                                          "noise",
-                                         "noperspective",
+                                         // "noperspective",  // Also reserved in WGSL
                                          // "normalize",  // WGSL builtin
                                          "numthreads",
-                                         "operator",
+                                         // "operator",  // Also reserved in WGSL
                                          // "out",  // WGSL keyword
-                                         "packoffset",
-                                         "pass",
-                                         "pixelfragment",
+                                         // "packoffset",  // Also reserved in WGSL
+                                         // "pass",  // Also reserved in WGSL
+                                         // "pixelfragment",  // Also reserved in WGSL
                                          "pixelshader",
-                                         "point",
+                                         // "point",  // Also reserved in WGSL
                                          // "pow",  // WGSL builtin
-                                         "precise",
+                                         // "precise",  // Also reserved in WGSL
                                          "printf",
                                          // "private",  // WGSL keyword
-                                         "protected",
-                                         "public",
+                                         // "protected",  // Also reserved in WGSL
+                                         // "public",  // Also reserved in WGSL
                                          "radians",
                                          "rcp",
                                          // "reflect",  // WGSL builtin
                                          "refract",
-                                         "register",
-                                         "reinterpret_cast",
+                                         // "register",  // Also reserved in WGSL
+                                         // "reinterpret_cast",  // Also reserved in WGSL
                                          // "return",  // WGSL keyword
                                          // "reversebits",  // WGSL builtin
                                          // "round",  // WGSL builtin
@@ -1071,21 +1071,21 @@
                                          "samplerCUBE",
                                          "sampler_state",
                                          "saturate",
-                                         "shared",
+                                         // "shared",  // Also reserved in WGSL
                                          "short",
                                          // "sign",  // WGSL builtin
-                                         "signed",
+                                         // "signed",  // Also reserved in WGSL
                                          // "sin",  // WGSL builtin
                                          "sincos",
                                          // "sinh",  // WGSL builtin
-                                         "sizeof",
+                                         // "sizeof",  // Also reserved in WGSL
                                          // "smoothstep",  // WGSL builtin
-                                         "snorm",
+                                         // "snorm",  // Also reserved in WGSL
                                          // "sqrt",  // WGSL builtin
                                          "stateblock",
                                          "stateblock_state",
-                                         "static",
-                                         "static_cast",
+                                         // "static",  // Also reserved in WGSL
+                                         // "static_cast",  // Also reserved in WGSL
                                          // "step",  // WGSL builtin
                                          "string",
                                          // "struct",  // WGSL keyword
@@ -1096,7 +1096,7 @@
                                          "technique",
                                          "technique10",
                                          "technique11",
-                                         "template",
+                                         // "template",  // Also reserved in WGSL
                                          "tex1D",
                                          "tex1Dbias",
                                          "tex1Dgrad",
@@ -1127,16 +1127,16 @@
                                          "texture3D",
                                          "textureCube",
                                          "textureCubeArray",
-                                         "this",
-                                         "throw",
+                                         // "this",  // Also reserved in WGSL
+                                         // "throw",  // Also reserved in WGSL
                                          "transpose",
                                          "triangle",
                                          "triangleadj",
                                          // "true",  // WGSL keyword
                                          // "trunc",  // WGSL builtin
-                                         "try",
+                                         // "try",  // Also reserved in WGSL
                                          // "typedef",  // WGSL keyword
-                                         "typename",
+                                         // "typename",  // Also reserved in WGSL
                                          "uint",
                                          "uint1",
                                          "uint1x1",
@@ -1159,17 +1159,17 @@
                                          "uint4x3",
                                          "uint4x4",
                                          // "uniform",  // WGSL keyword
-                                         "union",
-                                         "unorm",
+                                         // "union",  // Also reserved in WGSL
+                                         // "unorm",  // Also reserved in WGSL
                                          "unroll",
                                          "unsigned",
                                          // "using",  // WGSL reserved keyword
                                          "vector",
                                          "vertexfragment",
                                          "vertexshader",
-                                         "virtual",
+                                         // "virtual",  // Also reserved in WGSL
                                          // "void",  // WGSL keyword
-                                         "volatile",
+                                         // "volatile",  // Also reserved in WGSL
                                          // "while"  // WGSL reserved keyword
                                          kUnicodeIdentifier));
 
@@ -1178,87 +1178,87 @@
     RenamerTestMsl,
     testing::Values(
         // c++14 spec
-        "alignas",
-        "alignof",
+        // "alignas",  // Also reserved in WGSL
+        // "alignof",  // Also reserved in WGSL
         "and",
         "and_eq",
         // "asm",  // Also reserved in WGSL
-        "auto",
+        // "auto",  // Also reserved in WGSL
         "bitand",
         "bitor",
         // "bool",   // Also used in WGSL
         // "break",  // Also used in WGSL
         // "case",   // Also used in WGSL
-        "catch",
+        // "catch",  // Also reserved in WGSL
         "char",
         "char16_t",
         "char32_t",
-        "class",
+        // "class",  // Also reserved in WGSL
         "compl",
         // "const",     // Also used in WGSL
-        "const_cast",
-        "constexpr",
+        // "const_cast",  // Also reserved in WGSL
+        // "constexpr",  // Also reserved in WGSL
         // "continue",  // Also used in WGSL
-        "decltype",
+        // "decltype",  // Also reserved in WGSL
         // "default",   // Also used in WGSL
-        "delete",
+        // "delete",  // Also reserved in WGSL
         // "do",  // Also used in WGSL
         "double",
-        "dynamic_cast",
+        // "dynamic_cast",  // Also reserved in WGSL
         // "else",  // Also used in WGSL
         // "enum",  // Also used in WGSL
-        "explicit",
-        "extern",
+        // "explicit",  // Also reserved in WGSL
+        // "extern",  // Also reserved in WGSL
         // "false",  // Also used in WGSL
-        "final",
+        // "final",  // Also reserved in WGSL
         "float",
         // "for",  // Also used in WGSL
-        "friend",
-        "goto",
+        // "friend",  // Also reserved in WGSL
+        // "goto",  // Also reserved in WGSL
         // "if",  // Also used in WGSL
-        "inline",
+        // "inline",  // Also reserved in WGSL
         "int",
         "long",
-        "mutable",
-        "namespace",
-        "new",
-        "noexcept",
+        // "mutable",  // Also reserved in WGSL
+        // "namespace",  // Also reserved in WGSL
+        // "new",  // Also reserved in WGSL
+        // "noexcept",  // Also reserved in WGSL
         "not",
         "not_eq",
-        "nullptr",
-        "operator",
+        // "nullptr",  // Also reserved in WGSL
+        // "operator",  // Also reserved in WGSL
         "or",
         "or_eq",
         // "override", // Also used in WGSL
         // "private",  // Also used in WGSL
-        "protected",
-        "public",
-        "register",
-        "reinterpret_cast",
+        // "protected",  // Also reserved in WGSL
+        // "public",  // Also reserved in WGSL
+        // "register",  // Also reserved in WGSL
+        // "reinterpret_cast",  // Also reserved in WGSL
         // "return",  // Also used in WGSL
         "short",
-        "signed",
-        "sizeof",
-        "static",
-        "static_assert",
-        "static_cast",
+        // "signed",  // Also reserved in WGSL
+        // "sizeof",  // Also reserved in WGSL
+        // "static",  // Also reserved in WGSL
+        // "static_assert",  // Also reserved in WGSL
+        // "static_cast",  // Also reserved in WGSL
         // "struct",  // Also used in WGSL
         // "switch",  // Also used in WGSL
-        "template",
-        "this",
-        "thread_local",
-        "throw",
+        // "template",  // Also reserved in WGSL
+        // "this",  // Also reserved in WGSL
+        // "thread_local",  // Also reserved in WGSL
+        // "throw",  // Also reserved in WGSL
         // "true",  // Also used in WGSL
-        "try",
+        // "try",  // Also reserved in WGSL
         // "typedef",  // Also used in WGSL
-        "typeid",
-        "typename",
-        "union",
+        // "typeid",  // Also reserved in WGSL
+        // "typename",  // Also reserved in WGSL
+        // "union",  // Also reserved in WGSL
         "unsigned",
         // "using",  // WGSL reserved keyword
-        "virtual",
+        // "virtual",  // Also reserved in WGSL
         // "void",  // Also used in WGSL
-        "volatile",
+        // "volatile",  // Also reserved in WGSL
         "wchar_t",
         // "while",  // WGSL reserved keyword
         "xor",
diff --git a/src/tint/transform/spirv_atomic.cc b/src/tint/transform/spirv_atomic.cc
index 0cdd4a3..b79f60f 100644
--- a/src/tint/transform/spirv_atomic.cc
+++ b/src/tint/transform/spirv_atomic.cc
@@ -116,15 +116,16 @@
                     const auto& forked = it->second;
 
                     // Re-create the structure swapping in the atomic-flavoured members
-                    std::vector<const ast::StructMember*> members(str->members.size());
-                    for (size_t i = 0; i < str->members.size(); i++) {
+                    utils::Vector<const ast::StructMember*, 8> members;
+                    members.Reserve(str->members.Length());
+                    for (size_t i = 0; i < str->members.Length(); i++) {
                         auto* member = str->members[i];
                         if (forked.atomic_members.count(i)) {
                             auto* type = AtomicTypeFor(ctx.src->Sem().Get(member)->Type());
                             auto name = ctx.src->Symbols().NameFor(member->symbol);
-                            members[i] = b.Member(name, type, ctx.Clone(member->attributes));
+                            members.Push(b.Member(name, type, ctx.Clone(member->attributes)));
                         } else {
-                            members[i] = ctx.Clone(member);
+                            members.Push(ctx.Clone(member));
                         }
                     }
                     b.Structure(forked.name, std::move(members));
diff --git a/src/tint/transform/spirv_atomic_test.cc b/src/tint/transform/spirv_atomic_test.cc
index cbd8a26..7f6db34 100644
--- a/src/tint/transform/spirv_atomic_test.cc
+++ b/src/tint/transform/spirv_atomic_test.cc
@@ -44,76 +44,99 @@
         };
         for (auto& a : two_params) {
             b.Func(std::string{"stub_"} + sem::str(a) + "_u32",
-                   {
+                   utils::Vector{
                        b.Param("p0", b.ty.u32()),
                        b.Param("p1", b.ty.u32()),
                    },
-                   b.ty.u32(), {b.Return(0_u)},
-                   {b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(), a)});
+                   b.ty.u32(),
+                   utils::Vector{
+                       b.Return(0_u),
+                   },
+                   utils::Vector{
+                       b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(), a),
+                   });
             b.Func(std::string{"stub_"} + sem::str(a) + "_i32",
-                   {
+                   utils::Vector{
                        b.Param("p0", b.ty.i32()),
                        b.Param("p1", b.ty.i32()),
                    },
-                   b.ty.i32(), {b.Return(0_i)},
-                   {b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(), a)});
+                   b.ty.i32(),
+                   utils::Vector{
+                       b.Return(0_i),
+                   },
+                   utils::Vector{
+                       b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(), a),
+                   });
         }
 
         b.Func("stub_atomicLoad_u32",
-               {
+               utils::Vector{
                    b.Param("p0", b.ty.u32()),
                },
-               b.ty.u32(), {b.Return(0_u)},
-               {
+               b.ty.u32(),
+               utils::Vector{
+                   b.Return(0_u),
+               },
+               utils::Vector{
                    b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(),
                                                           sem::BuiltinType::kAtomicLoad),
                });
         b.Func("stub_atomicLoad_i32",
-               {
+               utils::Vector{
                    b.Param("p0", b.ty.i32()),
                },
-               b.ty.i32(), {b.Return(0_i)},
-               {
+               b.ty.i32(),
+               utils::Vector{
+                   b.Return(0_i),
+               },
+               utils::Vector{
                    b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(),
                                                           sem::BuiltinType::kAtomicLoad),
                });
 
         b.Func("stub_atomicStore_u32",
-               {
+               utils::Vector{
                    b.Param("p0", b.ty.u32()),
                    b.Param("p1", b.ty.u32()),
                },
-               b.ty.void_(), {},
-               {
+               b.ty.void_(), utils::Empty,
+               utils::Vector{
                    b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(),
                                                           sem::BuiltinType::kAtomicStore),
                });
         b.Func("stub_atomicStore_i32",
-               {
+               utils::Vector{
                    b.Param("p0", b.ty.i32()),
                    b.Param("p1", b.ty.i32()),
                },
-               b.ty.void_(), {},
-               {
+               b.ty.void_(), utils::Empty,
+               utils::Vector{
                    b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(),
                                                           sem::BuiltinType::kAtomicStore),
                });
 
         b.Func("stub_atomic_compare_exchange_weak_u32",
-               {
+               utils::Vector{
                    b.Param("p0", b.ty.u32()),
                    b.Param("p1", b.ty.u32()),
                    b.Param("p2", b.ty.u32()),
                },
-               b.ty.u32(), {b.Return(0_u)},
-               {
+               b.ty.u32(),
+               utils::Vector{
+                   b.Return(0_u),
+               },
+               utils::Vector{
                    b.ASTNodes().Create<SpirvAtomic::Stub>(
                        b.ID(), b.AllocateNodeID(), sem::BuiltinType::kAtomicCompareExchangeWeak),
                });
         b.Func("stub_atomic_compare_exchange_weak_i32",
-               {b.Param("p0", b.ty.i32()), b.Param("p1", b.ty.i32()), b.Param("p2", b.ty.i32())},
-               b.ty.i32(), {b.Return(0_i)},
-               {
+               utils::Vector{b.Param("p0", b.ty.i32()), b.Param("p1", b.ty.i32()),
+                             b.Param("p2", b.ty.i32())},
+               b.ty.i32(),
+               utils::Vector{
+                   b.Return(0_i),
+               },
+               utils::Vector{
                    b.ASTNodes().Create<SpirvAtomic::Stub>(
                        b.ID(), b.AllocateNodeID(), sem::BuiltinType::kAtomicCompareExchangeWeak),
                });
diff --git a/src/tint/transform/transform.cc b/src/tint/transform/transform.cc
index f3ab173..3adfff6 100644
--- a/src/tint/transform/transform.cc
+++ b/src/tint/transform/transform.cc
@@ -106,9 +106,9 @@
     }
     if (auto* a = ty->As<sem::Array>()) {
         auto* el = CreateASTTypeFor(ctx, a->ElemType());
-        ast::AttributeList attrs;
+        utils::Vector<const ast::Attribute*, 1> attrs;
         if (!a->IsStrideImplicit()) {
-            attrs.emplace_back(ctx.dst->create<ast::StrideAttribute>(a->Stride()));
+            attrs.Push(ctx.dst->create<ast::StrideAttribute>(a->Stride()));
         }
         if (a->IsRuntimeSized()) {
             return ctx.dst->ty.array(el, nullptr, std::move(attrs));
diff --git a/src/tint/transform/transform_test.cc b/src/tint/transform/transform_test.cc
index 3e342c5..4ec4d1d 100644
--- a/src/tint/transform/transform_test.cc
+++ b/src/tint/transform/transform_test.cc
@@ -69,7 +69,7 @@
     });
     ASSERT_TRUE(arr->Is<ast::Array>());
     ASSERT_TRUE(arr->As<ast::Array>()->type->Is<ast::F32>());
-    ASSERT_EQ(arr->As<ast::Array>()->attributes.size(), 0u);
+    ASSERT_EQ(arr->As<ast::Array>()->attributes.Length(), 0u);
 
     auto* size = arr->As<ast::Array>()->count->As<ast::IntLiteralExpression>();
     ASSERT_NE(size, nullptr);
@@ -82,7 +82,7 @@
     });
     ASSERT_TRUE(arr->Is<ast::Array>());
     ASSERT_TRUE(arr->As<ast::Array>()->type->Is<ast::F32>());
-    ASSERT_EQ(arr->As<ast::Array>()->attributes.size(), 1u);
+    ASSERT_EQ(arr->As<ast::Array>()->attributes.Length(), 1u);
     ASSERT_TRUE(arr->As<ast::Array>()->attributes[0]->Is<ast::StrideAttribute>());
     ASSERT_EQ(arr->As<ast::Array>()->attributes[0]->As<ast::StrideAttribute>()->stride, 64u);
 
diff --git a/src/tint/transform/unwind_discard_functions.cc b/src/tint/transform/unwind_discard_functions.cc
index d41ef6c..8c62f04 100644
--- a/src/tint/transform/unwind_discard_functions.cc
+++ b/src/tint/transform/unwind_discard_functions.cc
@@ -67,7 +67,8 @@
     Symbol ModuleDiscardFuncName() {
         if (!module_discard_func_name.IsValid()) {
             module_discard_func_name = b.Symbols().New("tint_discard_func");
-            b.Func(module_discard_func_name, {}, b.ty.void_(), {b.Discard()});
+            b.Func(module_discard_func_name, tint::utils::Empty, b.ty.void_(),
+                   tint::utils::Vector{b.Discard()});
         }
         return module_discard_func_name;
     }
@@ -108,14 +109,14 @@
     //    }
     //
     const ast::IfStatement* IfDiscardReturn(const ast::Statement* stmt) {
-        ast::StatementList stmts;
+        tint::utils::Vector<const ast::Statement*, 2> stmts;
 
         // For entry point functions, also emit the discard statement
         if (IsInEntryPointFunc(stmt)) {
-            stmts.emplace_back(CallDiscardFunc());
+            stmts.Push(CallDiscardFunc());
         }
 
-        stmts.emplace_back(Return(stmt));
+        stmts.Push(Return(stmt));
 
         auto var_name = ModuleDiscardVarName();
         return b.If(var_name, b.Block(stmts));
diff --git a/src/tint/transform/utils/get_insertion_point_test.cc b/src/tint/transform/utils/get_insertion_point_test.cc
index 071cfea..72fedd0 100644
--- a/src/tint/transform/utils/get_insertion_point_test.cc
+++ b/src/tint/transform/utils/get_insertion_point_test.cc
@@ -35,7 +35,7 @@
     auto* expr = b.Expr(1_i);
     auto* var = b.Decl(b.Var("a", nullptr, expr));
     auto* block = b.Block(var);
-    b.Func("f", {}, b.ty.void_(), {block});
+    b.Func("f", tint::utils::Empty, b.ty.void_(), tint::utils::Vector{block});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -56,9 +56,9 @@
     ProgramBuilder b;
     auto* expr = b.Expr(1_i);
     auto* var = b.Decl(b.Var("a", nullptr, expr));
-    auto* fl = b.For(var, b.Expr(true), {}, b.Block());
+    auto* fl = b.For(var, b.Expr(true), nullptr, b.Block());
     auto* func_block = b.Block(fl);
-    b.Func("f", {}, b.ty.void_(), {func_block});
+    b.Func("f", tint::utils::Empty, b.ty.void_(), tint::utils::Vector{func_block});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -79,7 +79,7 @@
     auto* expr = b.Expr(1_i);
     auto* var = b.Decl(b.Var("a", nullptr, expr));
     auto* s = b.For({}, b.Expr(true), var, b.Block());
-    b.Func("f", {}, b.ty.void_(), {s});
+    b.Func("f", tint::utils::Empty, b.ty.void_(), tint::utils::Vector{s});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
diff --git a/src/tint/transform/utils/hoist_to_decl_before.cc b/src/tint/transform/utils/hoist_to_decl_before.cc
index 2995522..9133267 100644
--- a/src/tint/transform/utils/hoist_to_decl_before.cc
+++ b/src/tint/transform/utils/hoist_to_decl_before.cc
@@ -36,14 +36,14 @@
     /// loop, so that declaration statements can be inserted before the
     /// condition expression or continuing statement.
     struct LoopInfo {
-        ast::StatementList cond_decls;
-        ast::StatementList cont_decls;
+        utils::Vector<const ast::Statement*, 8> cond_decls;
+        utils::Vector<const ast::Statement*, 8> cont_decls;
     };
 
     /// Info for each else-if that needs decomposing
     struct ElseIfInfo {
         /// Decls to insert before condition
-        ast::StatementList cond_decls;
+        utils::Vector<const ast::Statement*, 8> cond_decls;
     };
 
     /// For-loops that need to be decomposed to loops.
@@ -85,10 +85,10 @@
                         // { break; }
                         auto* break_body = b.Block(b.create<ast::BreakStatement>());
                         // if (!condition) { break; }
-                        body_stmts.emplace_back(b.If(not_cond, break_body));
+                        body_stmts.Push(b.If(not_cond, break_body));
                     }
                     // Next emit the for-loop body
-                    body_stmts.emplace_back(ctx.Clone(for_loop->body));
+                    body_stmts.Push(ctx.Clone(for_loop->body));
 
                     // Finally create the continuing block if there was one.
                     const ast::BlockStatement* continuing = nullptr;
@@ -96,7 +96,7 @@
                         // Continuing block starts with any let declarations used by
                         // the continuing.
                         auto cont_stmts = info.cont_decls;
-                        cont_stmts.emplace_back(ctx.Clone(cont));
+                        cont_stmts.Push(ctx.Clone(cont));
                         continuing = b.Block(cont_stmts);
                     }
 
@@ -141,10 +141,10 @@
                     // { break; }
                     auto* break_body = b.Block(b.create<ast::BreakStatement>());
                     // if (!condition) { break; }
-                    body_stmts.emplace_back(b.If(not_cond, break_body));
+                    body_stmts.Push(b.If(not_cond, break_body));
 
                     // Next emit the body
-                    body_stmts.emplace_back(ctx.Clone(while_loop->body));
+                    body_stmts.Push(ctx.Clone(while_loop->body));
 
                     const ast::BlockStatement* continuing = nullptr;
 
@@ -173,7 +173,7 @@
             auto* cond = ctx.Clone(else_if->condition);
             auto* body = ctx.Clone(else_if->body);
             auto* new_if = b.If(cond, body, b.Else(ctx.Clone(else_if->else_statement)));
-            body_stmts.emplace_back(new_if);
+            body_stmts.Push(new_if);
 
             // Replace the 'else-if' with the new 'else' block.
             return b.Block(body_stmts);
@@ -231,7 +231,7 @@
             // Index the map to convert this else if, even if `stmt` is nullptr.
             auto& decls = else_if_info.cond_decls;
             if (stmt) {
-                decls.emplace_back(stmt);
+                decls.Push(stmt);
             }
             return true;
         }
@@ -243,7 +243,7 @@
             // Index the map to convert this for-loop, even if `stmt` is nullptr.
             auto& decls = for_loops[fl].cond_decls;
             if (stmt) {
-                decls.emplace_back(stmt);
+                decls.Push(stmt);
             }
             return true;
         }
@@ -255,7 +255,7 @@
             // Index the map to convert this while, even if `stmt` is nullptr.
             auto& decls = while_loops[w].cond_decls;
             if (stmt) {
-                decls.emplace_back(stmt);
+                decls.Push(stmt);
             }
             return true;
         }
@@ -290,7 +290,7 @@
                 // Index the map to convert this for-loop, even if `stmt` is nullptr.
                 auto& decls = for_loops[fl].cont_decls;
                 if (stmt) {
-                    decls.emplace_back(stmt);
+                    decls.Push(stmt);
                 }
                 return true;
             }
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 22784ba..6d4533d 100644
--- a/src/tint/transform/utils/hoist_to_decl_before_test.cc
+++ b/src/tint/transform/utils/hoist_to_decl_before_test.cc
@@ -36,7 +36,7 @@
     ProgramBuilder b;
     auto* expr = b.Expr(1_i);
     auto* var = b.Decl(b.Var("a", nullptr, expr));
-    b.Func("f", {}, b.ty.void_(), {var});
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -67,8 +67,8 @@
     // }
     ProgramBuilder b;
     auto* expr = b.Expr(1_i);
-    auto* s = b.For(b.Decl(b.Var("a", nullptr, expr)), b.Expr(true), {}, b.Block());
-    b.Func("f", {}, b.ty.void_(), {s});
+    auto* s = b.For(b.Decl(b.Var("a", nullptr, expr)), b.Expr(true), nullptr, b.Block());
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{s});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -102,8 +102,8 @@
     ProgramBuilder b;
     auto* var = b.Decl(b.Var("a", b.ty.bool_()));
     auto* expr = b.Expr("a");
-    auto* s = b.For({}, expr, {}, b.Block());
-    b.Func("f", {}, b.ty.void_(), {var, s});
+    auto* s = b.For(nullptr, expr, nullptr, b.Block());
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var, s});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -141,8 +141,8 @@
     // }
     ProgramBuilder b;
     auto* expr = b.Expr(1_i);
-    auto* s = b.For({}, b.Expr(true), b.Decl(b.Var("a", nullptr, expr)), b.Block());
-    b.Func("f", {}, b.ty.void_(), {s});
+    auto* s = b.For(nullptr, b.Expr(true), b.Decl(b.Var("a", nullptr, expr)), b.Block());
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{s});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -186,7 +186,7 @@
     auto* var = b.Decl(b.Var("a", b.ty.bool_()));
     auto* expr = b.Expr("a");
     auto* s = b.While(expr, b.Block());
-    b.Func("f", {}, b.ty.void_(), {var, s});
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var, s});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -231,7 +231,7 @@
     auto* s = b.If(b.Expr(true), b.Block(),      //
                    b.Else(b.If(expr, b.Block(),  //
                                b.Else(b.Block()))));
-    b.Func("f", {}, b.ty.void_(), {var, s});
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var, s});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -270,7 +270,7 @@
     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));
-    b.Func("f", {}, b.ty.void_(), {var1, var2});
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var1, var2});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -305,7 +305,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));
-    b.Func("f", {}, b.ty.void_(), {var1, var2});
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var1, var2});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -339,8 +339,8 @@
     ProgramBuilder b;
     auto* var = b.Decl(b.Var("a", b.ty.bool_()));
     auto* expr = b.Expr("a");
-    auto* s = b.For({}, expr, {}, b.Block());
-    b.Func("f", {}, b.ty.void_(), {var, s});
+    auto* s = b.For(nullptr, expr, nullptr, b.Block());
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var, s});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -377,8 +377,8 @@
     // }
     ProgramBuilder b;
     auto* expr = b.Expr(1_i);
-    auto* s = b.For({}, b.Expr(true), b.Decl(b.Var("a", nullptr, expr)), b.Block());
-    b.Func("f", {}, b.ty.void_(), {s});
+    auto* s = b.For(nullptr, b.Expr(true), b.Decl(b.Var("a", nullptr, expr)), b.Block());
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{s});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -425,7 +425,7 @@
     auto* s = b.If(b.Expr(true), b.Block(),      //
                    b.Else(b.If(expr, b.Block(),  //
                                b.Else(b.Block()))));
-    b.Func("f", {}, b.ty.void_(), {var, s});
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var, s});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -461,9 +461,9 @@
     //     var a = 1i;
     // }
     ProgramBuilder b;
-    b.Func("foo", {}, b.ty.void_(), {});
+    b.Func("foo", utils::Empty, b.ty.void_(), utils::Empty);
     auto* var = b.Decl(b.Var("a", nullptr, b.Expr(1_i)));
-    b.Func("f", {}, b.ty.void_(), {var});
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -499,10 +499,10 @@
     //     }
     // }
     ProgramBuilder b;
-    b.Func("foo", {}, b.ty.void_(), {});
+    b.Func("foo", utils::Empty, b.ty.void_(), utils::Empty);
     auto* var = b.Decl(b.Var("a", nullptr, b.Expr(1_i)));
-    auto* s = b.For(var, b.Expr(true), {}, b.Block());
-    b.Func("f", {}, b.ty.void_(), {s});
+    auto* s = b.For(var, b.Expr(true), nullptr, b.Block());
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{s});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -540,11 +540,11 @@
     //     }
     // }
     ProgramBuilder b;
-    b.Func("foo", {}, b.ty.void_(), {});
+    b.Func("foo", utils::Empty, b.ty.void_(), utils::Empty);
     auto* var = b.Decl(b.Var("a", nullptr, b.Expr(1_i)));
     auto* cont = b.CompoundAssign("a", b.Expr(1_i), ast::BinaryOp::kAdd);
-    auto* s = b.For({}, b.Expr(true), cont, b.Block());
-    b.Func("f", {}, b.ty.void_(), {var, s});
+    auto* s = b.For(nullptr, b.Expr(true), cont, b.Block());
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var, s});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
@@ -594,12 +594,12 @@
     //     }
     // }
     ProgramBuilder b;
-    b.Func("foo", {}, b.ty.void_(), {});
+    b.Func("foo", utils::Empty, b.ty.void_(), utils::Empty);
     auto* var = b.Decl(b.Var("a", b.ty.bool_()));
     auto* elseif = b.If(b.Expr("a"), b.Block(), b.Else(b.Block()));
     auto* s = b.If(b.Expr(true), b.Block(),  //
                    b.Else(elseif));
-    b.Func("f", {}, b.ty.void_(), {var, s});
+    b.Func("f", utils::Empty, b.ty.void_(), utils::Vector{var, s});
 
     Program original(std::move(b));
     ProgramBuilder cloned_b;
diff --git a/src/tint/transform/vectorize_scalar_matrix_constructors.cc b/src/tint/transform/vectorize_scalar_matrix_constructors.cc
index f48ac8e..113db5f 100644
--- a/src/tint/transform/vectorize_scalar_matrix_constructors.cc
+++ b/src/tint/transform/vectorize_scalar_matrix_constructors.cc
@@ -79,16 +79,16 @@
         // Constructs a matrix using vector columns, with the elements constructed using the
         // 'element(uint32_t c, uint32_t r)' callback.
         auto build_mat = [&](auto&& element) {
-            ast::ExpressionList columns(mat_type->columns());
+            utils::Vector<const ast::Expression*, 4> columns;
             for (uint32_t c = 0; c < mat_type->columns(); c++) {
-                ast::ExpressionList row_values(mat_type->rows());
+                utils::Vector<const ast::Expression*, 4> row_values;
                 for (uint32_t r = 0; r < mat_type->rows(); r++) {
-                    row_values[r] = element(c, r);
+                    row_values.Push(element(c, r));
                 }
 
                 // Construct the column vector.
-                columns[c] = ctx.dst->vec(CreateASTTypeFor(ctx, mat_type->type()), mat_type->rows(),
-                                          row_values);
+                columns.Push(ctx.dst->vec(CreateASTTypeFor(ctx, mat_type->type()), mat_type->rows(),
+                                          std::move(row_values)));
             }
             return ctx.dst->Construct(CreateASTTypeFor(ctx, mat_type), columns);
         };
@@ -102,12 +102,12 @@
                     ctx.dst->Symbols().New("build_mat" + std::to_string(mat_type->columns()) + "x" +
                                            std::to_string(mat_type->rows()));
                 ctx.dst->Func(name,
-                              {
+                              utils::Vector{
                                   // Single scalar parameter
                                   ctx.dst->Param("value", CreateASTTypeFor(ctx, mat_type->type())),
                               },
                               CreateASTTypeFor(ctx, mat_type),
-                              {
+                              utils::Vector{
                                   ctx.dst->Return(build_mat([&](uint32_t, uint32_t) {  //
                                       return ctx.dst->Expr("value");
                                   })),
diff --git a/src/tint/transform/vertex_pulling.cc b/src/tint/transform/vertex_pulling.cc
index 43531eb..6eb2bbf 100644
--- a/src/tint/transform/vertex_pulling.cc
+++ b/src/tint/transform/vertex_pulling.cc
@@ -228,7 +228,7 @@
     Symbol pulling_position_name;
     Symbol struct_buffer_name;
     std::unordered_map<uint32_t, Symbol> vertex_buffer_names;
-    ast::ParameterList new_function_parameters;
+    utils::Vector<const ast::Parameter*, 8> new_function_parameters;
 
     /// Generate the vertex buffer binding name
     /// @param index index to append to buffer name
@@ -254,14 +254,14 @@
         static const char kStructName[] = "TintVertexData";
         auto* struct_type =
             ctx.dst->Structure(ctx.dst->Symbols().New(kStructName),
-                               {
+                               utils::Vector{
                                    ctx.dst->Member(GetStructBufferName(), ctx.dst->ty.array<u32>()),
                                });
         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,
-                               ast::AttributeList{
+                               utils::Vector{
                                    ctx.dst->create<ast::BindingAttribute>(i),
                                    ctx.dst->create<ast::GroupAttribute>(cfg.pulling_group),
                                });
@@ -273,7 +273,7 @@
         // Assign by looking at the vertex descriptor to find attributes with
         // matching location.
 
-        ast::StatementList stmts;
+        utils::Vector<const ast::Statement*, 8> stmts;
 
         for (uint32_t buffer_idx = 0; buffer_idx < cfg.vertex_state.size(); ++buffer_idx) {
             const VertexBufferLayoutDescriptor& buffer_layout = cfg.vertex_state[buffer_idx];
@@ -303,8 +303,7 @@
             }
 
             // let pulling_offset_n = <attribute_offset>
-            stmts.emplace_back(
-                ctx.dst->Decl(ctx.dst->Let(buffer_array_base, nullptr, attribute_offset)));
+            stmts.Push(ctx.dst->Decl(ctx.dst->Let(buffer_array_base, nullptr, attribute_offset)));
 
             for (const VertexAttributeDescriptor& attribute_desc : buffer_layout.attributes) {
                 auto it = location_info.find(attribute_desc.shader_location);
@@ -356,24 +355,24 @@
                 } else if (var_dt.width > fmt_dt.width) {
                     // WGSL variable vector width is wider than the loaded vector width
                     const ast::Type* ty = nullptr;
-                    ast::ExpressionList values{fetch};
+                    utils::Vector<const ast::Expression*, 8> values{fetch};
                     switch (var_dt.base_type) {
                         case BaseType::kI32:
                             ty = ctx.dst->ty.i32();
                             for (uint32_t i = fmt_dt.width; i < var_dt.width; i++) {
-                                values.emplace_back(ctx.dst->Expr((i == 3) ? 1_i : 0_i));
+                                values.Push(ctx.dst->Expr((i == 3) ? 1_i : 0_i));
                             }
                             break;
                         case BaseType::kU32:
                             ty = ctx.dst->ty.u32();
                             for (uint32_t i = fmt_dt.width; i < var_dt.width; i++) {
-                                values.emplace_back(ctx.dst->Expr((i == 3) ? 1_u : 0_u));
+                                values.Push(ctx.dst->Expr((i == 3) ? 1_u : 0_u));
                             }
                             break;
                         case BaseType::kF32:
                             ty = ctx.dst->ty.f32();
                             for (uint32_t i = fmt_dt.width; i < var_dt.width; i++) {
-                                values.emplace_back(ctx.dst->Expr((i == 3) ? 1_f : 0_f));
+                                values.Push(ctx.dst->Expr((i == 3) ? 1_f : 0_f));
                             }
                             break;
                         default:
@@ -384,15 +383,15 @@
                 }
 
                 // Assign the value to the WGSL variable
-                stmts.emplace_back(ctx.dst->Assign(var.expr(), value));
+                stmts.Push(ctx.dst->Assign(var.expr(), value));
             }
         }
 
-        if (stmts.empty()) {
+        if (stmts.IsEmpty()) {
             return nullptr;
         }
 
-        return ctx.dst->create<ast::BlockStatement>(stmts);
+        return ctx.dst->create<ast::BlockStatement>(std::move(stmts));
     }
 
     /// Generates an expression reading from a buffer a specific format.
@@ -679,11 +678,11 @@
                                    const ast::Type* base_type,
                                    VertexFormat base_format,
                                    uint32_t count) {
-        ast::ExpressionList expr_list;
+        utils::Vector<const ast::Expression*, 8> expr_list;
         for (uint32_t i = 0; i < count; ++i) {
             // Offset read position by element_stride for each component
             uint32_t primitive_offset = offset + element_stride * i;
-            expr_list.push_back(LoadPrimitive(array_base, primitive_offset, buffer, base_format));
+            expr_list.Push(LoadPrimitive(array_base, primitive_offset, buffer, base_format));
         }
 
         return ctx.dst->Construct(ctx.dst->create<ast::Vector>(base_type, count),
@@ -718,7 +717,7 @@
                     return ctx.dst->Expr(ctx.Clone(param->symbol));
                 };
             }
-            new_function_parameters.push_back(ctx.Clone(param));
+            new_function_parameters.Push(ctx.Clone(param));
         } else {
             TINT_ICE(Transform, ctx.dst->Diagnostics()) << "Invalid entry point parameter";
         }
@@ -739,7 +738,7 @@
 
         // Process the struct members.
         bool has_locations = false;
-        ast::StructMemberList members_to_clone;
+        utils::Vector<const ast::StructMember*, 8> members_to_clone;
         for (auto* member : struct_ty->members) {
             auto member_sym = ctx.Clone(member->symbol);
             std::function<const ast::Expression*()> member_expr = [this, param_sym, member_sym]() {
@@ -761,7 +760,7 @@
                 } else if (builtin->builtin == ast::BuiltinValue::kInstanceIndex) {
                     instance_index_expr = member_expr;
                 }
-                members_to_clone.push_back(member);
+                members_to_clone.Push(member);
             } else {
                 TINT_ICE(Transform, ctx.dst->Diagnostics()) << "Invalid entry point parameter";
             }
@@ -769,7 +768,7 @@
 
         if (!has_locations) {
             // Nothing to do.
-            new_function_parameters.push_back(ctx.Clone(param));
+            new_function_parameters.Push(ctx.Clone(param));
             return;
         }
 
@@ -777,21 +776,20 @@
         auto* func_var = ctx.dst->Var(param_sym, ctx.Clone(param->type));
         ctx.InsertFront(func->body->statements, ctx.dst->Decl(func_var));
 
-        if (!members_to_clone.empty()) {
+        if (!members_to_clone.IsEmpty()) {
             // Create a new struct without the location attributes.
-            ast::StructMemberList new_members;
+            utils::Vector<const ast::StructMember*, 8> new_members;
             for (auto* member : members_to_clone) {
                 auto member_sym = ctx.Clone(member->symbol);
                 auto* member_type = ctx.Clone(member->type);
                 auto member_attrs = ctx.Clone(member->attributes);
-                new_members.push_back(
-                    ctx.dst->Member(member_sym, member_type, std::move(member_attrs)));
+                new_members.Push(ctx.dst->Member(member_sym, member_type, std::move(member_attrs)));
             }
             auto* new_struct = ctx.dst->Structure(ctx.dst->Sym(), new_members);
 
             // Create a new function parameter with this struct.
             auto* new_param = ctx.dst->Param(ctx.dst->Sym(), ctx.dst->ty.Of(new_struct));
-            new_function_parameters.push_back(new_param);
+            new_function_parameters.Push(new_param);
 
             // Copy values from the new parameter to the function-scope variable.
             for (auto* member : members_to_clone) {
@@ -825,9 +823,9 @@
             for (const VertexBufferLayoutDescriptor& layout : cfg.vertex_state) {
                 if (layout.step_mode == VertexStepMode::kVertex) {
                     auto name = ctx.dst->Symbols().New("tint_pulling_vertex_index");
-                    new_function_parameters.push_back(
-                        ctx.dst->Param(name, ctx.dst->ty.u32(),
-                                       {ctx.dst->Builtin(ast::BuiltinValue::kVertexIndex)}));
+                    new_function_parameters.Push(ctx.dst->Param(
+                        name, ctx.dst->ty.u32(),
+                        utils::Vector{ctx.dst->Builtin(ast::BuiltinValue::kVertexIndex)}));
                     vertex_index_expr = [this, name]() { return ctx.dst->Expr(name); };
                     break;
                 }
@@ -837,9 +835,9 @@
             for (const VertexBufferLayoutDescriptor& layout : cfg.vertex_state) {
                 if (layout.step_mode == VertexStepMode::kInstance) {
                     auto name = ctx.dst->Symbols().New("tint_pulling_instance_index");
-                    new_function_parameters.push_back(
-                        ctx.dst->Param(name, ctx.dst->ty.u32(),
-                                       {ctx.dst->Builtin(ast::BuiltinValue::kInstanceIndex)}));
+                    new_function_parameters.Push(ctx.dst->Param(
+                        name, ctx.dst->ty.u32(),
+                        utils::Vector{ctx.dst->Builtin(ast::BuiltinValue::kInstanceIndex)}));
                     instance_index_expr = [this, name]() { return ctx.dst->Expr(name); };
                     break;
                 }
diff --git a/src/tint/transform/while_to_loop.cc b/src/tint/transform/while_to_loop.cc
index 00ebb01..45944e6 100644
--- a/src/tint/transform/while_to_loop.cc
+++ b/src/tint/transform/while_to_loop.cc
@@ -36,7 +36,7 @@
 
 void WhileToLoop::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
     ctx.ReplaceAll([&](const ast::WhileStatement* w) -> const ast::Statement* {
-        ast::StatementList stmts;
+        utils::Vector<const ast::Statement*, 16> stmts;
         auto* cond = w->condition;
 
         // !condition
@@ -47,10 +47,10 @@
         auto* break_body = ctx.dst->Block(ctx.dst->create<ast::BreakStatement>());
 
         // if (!condition) { break; }
-        stmts.emplace_back(ctx.dst->If(not_cond, break_body));
+        stmts.Push(ctx.dst->If(not_cond, break_body));
 
         for (auto* stmt : w->body->statements) {
-            stmts.emplace_back(ctx.Clone(stmt));
+            stmts.Push(ctx.Clone(stmt));
         }
 
         const ast::BlockStatement* continuing = nullptr;
diff --git a/src/tint/transform/zero_init_workgroup_memory.cc b/src/tint/transform/zero_init_workgroup_memory.cc
index 890ecef..963ca01 100644
--- a/src/tint/transform/zero_init_workgroup_memory.cc
+++ b/src/tint/transform/zero_init_workgroup_memory.cc
@@ -32,6 +32,8 @@
 
 namespace tint::transform {
 
+using StatementList = utils::Vector<const ast::Statement*, 8>;
+
 /// PIMPL state for the ZeroInitWorkgroupMemory transform
 struct ZeroInitWorkgroupMemory::State {
     /// The clone context
@@ -162,7 +164,9 @@
         if (!local_index) {
             // No existing local index parameter. Append one to the entry point.
             auto* param = b.Param(b.Symbols().New("local_invocation_index"), b.ty.u32(),
-                                  {b.Builtin(ast::BuiltinValue::kLocalInvocationIndex)});
+                                  utils::Vector{
+                                      b.Builtin(ast::BuiltinValue::kLocalInvocationIndex),
+                                  });
             ctx.InsertBack(fn->params, param);
             local_index = [=] { return b.Expr(param->symbol); };
         }
@@ -216,7 +220,7 @@
                 auto block =
                     DeclareArrayIndices(num_iterations, array_indices, [&] { return b.Expr(idx); });
                 for (auto& s : stmts) {
-                    block.emplace_back(s.stmt);
+                    block.Push(s.stmt);
                 }
                 auto* for_loop = b.For(init, cond, cont, b.Block(block));
                 ctx.InsertFront(fn->body->statements, for_loop);
@@ -232,7 +236,7 @@
                 auto block = DeclareArrayIndices(num_iterations, array_indices,
                                                  [&] { return b.Expr(local_index()); });
                 for (auto& s : stmts) {
-                    block.emplace_back(s.stmt);
+                    block.Push(s.stmt);
                 }
                 auto* if_stmt = b.If(cond, b.Block(block));
                 ctx.InsertFront(fn->body->statements, if_stmt);
@@ -246,7 +250,7 @@
                 auto block = DeclareArrayIndices(num_iterations, array_indices,
                                                  [&] { return b.Expr(local_index()); });
                 for (auto& s : stmts) {
-                    block.emplace_back(s.stmt);
+                    block.Push(s.stmt);
                 }
                 ctx.InsertFront(fn->body->statements, b.Block(block));
             }
@@ -327,11 +331,10 @@
     /// @param iteration a function that returns the index of the current
     ///         iteration.
     /// @returns the list of `let` statements that declare the array indices
-    ast::StatementList DeclareArrayIndices(
-        uint32_t num_iterations,
-        const ArrayIndices& array_indices,
-        const std::function<const ast::Expression*()>& iteration) {
-        ast::StatementList stmts;
+    StatementList DeclareArrayIndices(uint32_t num_iterations,
+                                      const ArrayIndices& array_indices,
+                                      const std::function<const ast::Expression*()>& iteration) {
+        StatementList stmts;
         std::map<Symbol, ArrayIndex> indices_by_name;
         for (auto index : array_indices) {
             auto name = array_index_names.at(index);
@@ -341,7 +344,7 @@
                             : iteration();
             auto* div = (index.division != 1u) ? b.Div(mod, u32(index.division)) : mod;
             auto* decl = b.Decl(b.Let(name, b.ty.u32(), div));
-            stmts.emplace_back(decl);
+            stmts.Push(decl);
         }
         return stmts;
     }
diff --git a/src/tint/utils/io/command_posix.cc b/src/tint/utils/io/command_posix.cc
index ff0a138..b3371e7 100644
--- a/src/tint/utils/io/command_posix.cc
+++ b/src/tint/utils/io/command_posix.cc
@@ -251,7 +251,9 @@
         std::vector<const char*> args;
         args.emplace_back(path_.c_str());
         for (auto& arg : arguments) {
-            args.emplace_back(arg.c_str());
+            if (!arg.empty()) {
+                args.emplace_back(arg.c_str());
+            }
         }
         args.emplace_back(nullptr);
         auto res = execv(path_.c_str(), const_cast<char* const*>(args.data()));
diff --git a/src/tint/utils/io/command_windows.cc b/src/tint/utils/io/command_windows.cc
index 36c39c6..8c94e25 100644
--- a/src/tint/utils/io/command_windows.cc
+++ b/src/tint/utils/io/command_windows.cc
@@ -200,7 +200,9 @@
     std::stringstream args;
     args << path_;
     for (auto& arg : arguments) {
-        args << " " << arg;
+        if (!arg.empty()) {
+            args << " " << arg;
+        }
     }
 
     PROCESS_INFORMATION pi{};
diff --git a/src/tint/utils/result.h b/src/tint/utils/result.h
index 6cfe668..b535f4f 100644
--- a/src/tint/utils/result.h
+++ b/src/tint/utils/result.h
@@ -32,7 +32,7 @@
 ///         information about the failure, except that something failed. Must not be the same type
 ///         as SUCCESS_TYPE.
 template <typename SUCCESS_TYPE, typename FAILURE_TYPE = FailureType>
-struct Result {
+struct [[nodiscard]] Result {
     static_assert(!std::is_same_v<SUCCESS_TYPE, FAILURE_TYPE>,
                   "Result must not have the same type for SUCCESS_TYPE and FAILURE_TYPE");
 
diff --git a/src/tint/utils/transform.h b/src/tint/utils/transform.h
index 412dbed..2faca46 100644
--- a/src/tint/utils/transform.h
+++ b/src/tint/utils/transform.h
@@ -119,40 +119,6 @@
     return result;
 }
 
-/// Transform performs an element-wise transformation of a vector reference.
-/// @param in the input vector.
-/// @param transform the transformation function with signature: `OUT(IN)`
-/// @tparam N the small-array size of the returned Vector
-/// @returns a new vector with each element of the source vector transformed by `transform`.
-template <size_t N, typename IN, typename TRANSFORMER>
-auto Transform(ConstVectorRef<IN> in, TRANSFORMER&& transform)
-    -> Vector<decltype(transform(in[0])), N> {
-    const auto count = in.Length();
-    Vector<decltype(transform(in[0])), N> result;
-    result.Reserve(count);
-    for (size_t i = 0; i < count; ++i) {
-        result.Push(transform(in[i]));
-    }
-    return result;
-}
-
-/// Transform performs an element-wise transformation of a vector reference.
-/// @param in the input vector.
-/// @param transform the transformation function with signature: `OUT(IN, size_t)`
-/// @tparam N the small-array size of the returned Vector
-/// @returns a new vector with each element of the source vector transformed by `transform`.
-template <size_t N, typename IN, typename TRANSFORMER>
-auto Transform(ConstVectorRef<IN> in, TRANSFORMER&& transform)
-    -> Vector<decltype(transform(in[0], 1u)), N> {
-    const auto count = in.Length();
-    Vector<decltype(transform(in[0], 1u)), N> result;
-    result.Reserve(count);
-    for (size_t i = 0; i < count; ++i) {
-        result.Push(transform(in[i], i));
-    }
-    return result;
-}
-
 /// TransformN performs an element-wise transformation of a vector, transforming and returning at
 /// most `n` elements.
 /// @param in the input vector.
diff --git a/src/tint/utils/transform_test.cc b/src/tint/utils/transform_test.cc
index 656b827..71b3f0c 100644
--- a/src/tint/utils/transform_test.cc
+++ b/src/tint/utils/transform_test.cc
@@ -345,72 +345,5 @@
     }
 }
 
-TEST(TransformTest, ConstVectorRefEmpty) {
-    const Vector<int, 4> empty{};
-    ConstVectorRef<int> ref(empty);
-    {
-        auto transformed = Transform<4>(ref, [](int) -> int {
-            [] { FAIL() << "Callback should not be called for empty vector"; }();
-            return 0;
-        });
-        CHECK_ELEMENT_TYPE(transformed, int);
-        EXPECT_EQ(transformed.Length(), 0u);
-    }
-    {
-        auto transformed = Transform<4>(ref, [](int, size_t) -> int {
-            [] { FAIL() << "Callback should not be called for empty vector"; }();
-            return 0;
-        });
-        CHECK_ELEMENT_TYPE(transformed, int);
-        EXPECT_EQ(transformed.Length(), 0u);
-    }
-}
-
-TEST(TransformTest, ConstVectorRefIdentity) {
-    const Vector<int, 4> input{1, 2, 3, 4};
-    ConstVectorRef<int> ref(input);
-    auto transformed = Transform<8>(ref, [](int i) { return i; });
-    CHECK_ELEMENT_TYPE(transformed, int);
-    EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
-}
-
-TEST(TransformTest, ConstVectorRefIdentityWithIndex) {
-    const Vector<int, 4> input{1, 2, 3, 4};
-    ConstVectorRef<int> ref(input);
-    auto transformed = Transform<2>(ref, [](int i, size_t) { return i; });
-    CHECK_ELEMENT_TYPE(transformed, int);
-    EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
-}
-
-TEST(TransformTest, ConstVectorRefIndex) {
-    const Vector<int, 4> input{10, 20, 30, 40};
-    ConstVectorRef<int> ref(input);
-    {
-        auto transformed = Transform<4>(ref, [](int, size_t idx) { return idx; });
-        CHECK_ELEMENT_TYPE(transformed, size_t);
-        EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u, 2u, 3u));
-    }
-}
-
-TEST(TransformTest, TransformConstVectorRefSameType) {
-    const Vector<int, 4> input{1, 2, 3, 4};
-    ConstVectorRef<int> ref(input);
-    {
-        auto transformed = Transform<4>(ref, [](int i) { return i * 10; });
-        CHECK_ELEMENT_TYPE(transformed, int);
-        EXPECT_THAT(transformed, testing::ElementsAre(10, 20, 30, 40));
-    }
-}
-
-TEST(TransformTest, TransformConstVectorRefDifferentType) {
-    const Vector<int, 4> input{1, 2, 3, 4};
-    ConstVectorRef<int> ref(input);
-    {
-        auto transformed = Transform<4>(ref, [](int i) { return std::to_string(i); });
-        CHECK_ELEMENT_TYPE(transformed, std::string);
-        EXPECT_THAT(transformed, testing::ElementsAre("1", "2", "3", "4"));
-    }
-}
-
 }  // namespace
 }  // namespace tint::utils
diff --git a/src/tint/utils/unique_vector.h b/src/tint/utils/unique_vector.h
index 0f3f18d..d4018b2 100644
--- a/src/tint/utils/unique_vector.h
+++ b/src/tint/utils/unique_vector.h
@@ -93,6 +93,13 @@
     /// @returns a const reference to the internal vector
     operator const std::vector<T>&() const { return vector; }
 
+    /// Pre-allocates `count` elements in the vector and set
+    /// @param count the number of elements to pre-allocate
+    void reserve(size_t count) {
+        vector.reserve(count);
+        set.reserve(count);
+    }
+
     /// Removes the last element from the vector
     /// @returns the popped element
     T pop_back() {
diff --git a/src/tint/utils/vector.h b/src/tint/utils/vector.h
index d339a49..518a693 100644
--- a/src/tint/utils/vector.h
+++ b/src/tint/utils/vector.h
@@ -17,6 +17,7 @@
 
 #include <stddef.h>
 #include <stdint.h>
+#include <algorithm>
 #include <array>
 #include <iterator>
 #include <utility>
@@ -32,12 +33,18 @@
 template <typename>
 class VectorRef;
 template <typename>
-class ConstVectorRef;
+class VectorRef;
 
 }  // namespace tint::utils
 
 namespace tint::utils {
 
+/// A type used to indicate an empty array.
+struct EmptyType {};
+
+/// An instance of the EmptyType.
+static constexpr EmptyType Empty;
+
 /// A slice represents a contigious array of elements of type T.
 template <typename T>
 struct Slice {
@@ -165,6 +172,9 @@
     Vector() = default;
 
     /// Constructor
+    Vector(EmptyType) {}  // NOLINT(runtime/explicit)
+
+    /// Constructor
     /// @param elements the elements to place into the vector
     Vector(std::initializer_list<T> elements) {
         Reserve(elements.size());
@@ -217,10 +227,7 @@
 
     /// Copy constructor from an immutable vector reference
     /// @param other the vector reference to copy
-    explicit Vector(const ConstVectorRef<T>& other) { Copy(other.slice_); }
-
-    /// Move constructor from an immutable vector reference (invalid)
-    Vector(ConstVectorRef<T>&&) = delete;  // NOLINT(runtime/explicit)
+    explicit Vector(const VectorRef<T>& other) { Copy(other.slice_); }
 
     /// Destructor
     ~Vector() { ClearAndFree(); }
@@ -263,6 +270,26 @@
         return *this;
     }
 
+    /// Assignment operator (differing N length)
+    /// @param other the vector reference to copy
+    /// @returns this vector so calls can be chained
+    Vector& operator=(const VectorRef<T>& other) {
+        if (&other.slice_ != &impl_.slice) {
+            Copy(other.slice_);
+        }
+        return *this;
+    }
+
+    /// Move operator (differing N length)
+    /// @param other the vector reference to copy
+    /// @returns this vector so calls can be chained
+    Vector& operator=(VectorRef<T>&& other) {
+        if (&other.slice_ != &impl_.slice) {
+            MoveOrCopy(std::move(other));
+        }
+        return *this;
+    }
+
     /// Index operator
     /// @param i the element index. Must be less than `len`.
     /// @returns a reference to the i'th element.
@@ -367,7 +394,12 @@
 
     /// Removes and returns the last element from the vector.
     /// @returns the popped element
-    T Pop() { return std::move(impl_.slice.data[--impl_.slice.len]); }
+    T Pop() {
+        auto& el = impl_.slice.data[--impl_.slice.len];
+        auto val = std::move(el);
+        el.~T();
+        return val;
+    }
 
     /// @returns true if the vector is empty.
     bool IsEmpty() const { return impl_.slice.len == 0; }
@@ -419,7 +451,7 @@
 
     /// Friend class
     template <typename>
-    friend class ConstVectorRef;
+    friend class VectorRef;
 
     /// The slice type used by this vector
     using Slice = utils::Slice<T>;
@@ -430,7 +462,7 @@
     }
 
     /// Expands the capacity of the vector
-    void Grow() { Reserve(impl_.slice.cap * 2); }
+    void Grow() { Reserve(std::max(impl_.slice.cap, static_cast<size_t>(1)) * 2); }
 
     /// Moves 'other' to this vector, if possible, otherwise performs a copy.
     void MoveOrCopy(VectorRef<T>&& other) {
@@ -573,11 +605,10 @@
 
 /// VectorRef is a weak reference to a Vector, used to pass vectors as parameters, avoiding copies
 /// between the caller and the callee. VectorRef can accept a Vector of any 'N' value, decoupling
-/// the caller's vector internal size from the callee's vector size.
-///
-/// A VectorRef tracks the usage of moves either side of the call. If at the call site, a Vector
-/// argument is moved to a VectorRef parameter, and within the callee, the VectorRef parameter is
-/// moved to a Vector, then the Vector heap allocation will be moved. For example:
+/// the caller's vector internal size from the callee's vector size. A VectorRef tracks the usage of
+/// moves either side of the call. If at the call site, a Vector argument is moved to a VectorRef
+/// parameter, and within the callee, the VectorRef parameter is moved to a Vector, then the Vector
+/// heap allocation will be moved. For example:
 ///
 /// ```
 ///     void func_a() {
@@ -596,12 +627,30 @@
     /// The slice type used by this vector reference
     using Slice = utils::Slice<T>;
 
+    /// @returns an empty slice.
+    static Slice& EmptySlice() {
+        static Slice empty;
+        return empty;
+    }
+
   public:
+    /// Constructor - empty reference
+    VectorRef() : slice_(EmptySlice()) {}
+
+    /// Constructor
+    VectorRef(EmptyType) : slice_(EmptySlice()) {}  // NOLINT(runtime/explicit)
+
     /// Constructor from a Vector
     /// @param vector the vector to create a reference of
     template <size_t N>
     VectorRef(Vector<T, N>& vector)  // NOLINT(runtime/explicit)
-        : slice_(vector.impl_.slice), can_move_(false) {}
+        : slice_(vector.impl_.slice) {}
+
+    /// Constructor from a const Vector
+    /// @param vector the vector to create a reference of
+    template <size_t N>
+    VectorRef(const Vector<T, N>& vector)  // NOLINT(runtime/explicit)
+        : slice_(const_cast<Slice&>(vector.impl_.slice)) {}
 
     /// Constructor from a moved Vector
     /// @param vector the vector being moved
@@ -611,7 +660,7 @@
 
     /// Copy constructor
     /// @param other the vector reference
-    VectorRef(const VectorRef& other) : slice_(other.slice_), can_move_(false) {}
+    VectorRef(const VectorRef& other) : slice_(other.slice_) {}
 
     /// Move constructor
     /// @param other the vector reference
@@ -621,7 +670,7 @@
     /// @param other the other vector reference
     template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
     VectorRef(const VectorRef<U>& other)  // NOLINT(runtime/explicit)
-        : slice_(*ReinterpretSlice<T>(&other.slice_)), can_move_(false) {}
+        : slice_(*ReinterpretSlice<T>(&other.slice_)) {}
 
     /// Move constructor with covariance / const conversion
     /// @param other the vector reference
@@ -634,7 +683,7 @@
     /// @see CanReinterpretSlice for rules about conversion
     template <typename U, size_t N, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
     VectorRef(Vector<U, N>& vector)  // NOLINT(runtime/explicit)
-        : slice_(*ReinterpretSlice<T>(&vector.impl_.slice)), can_move_(false) {}
+        : slice_(*ReinterpretSlice<T>(&vector.impl_.slice)) {}
 
     /// Constructor from a moved Vector with covariance / const conversion
     /// @param vector the vector to create a reference of
@@ -646,11 +695,6 @@
     /// Index operator
     /// @param i the element index. Must be less than `len`.
     /// @returns a reference to the i'th element.
-    T& operator[](size_t i) { return slice_[i]; }
-
-    /// Index operator
-    /// @param i the element index. Must be less than `len`.
-    /// @returns a reference to the i'th element.
     const T& operator[](size_t i) const { return slice_[i]; }
 
     /// @return the number of elements in the vector
@@ -710,7 +754,7 @@
 
     /// Friend class
     template <typename>
-    friend class ConstVectorRef;
+    friend class VectorRef;
 
     /// The slice of the vector being referenced.
     Slice& slice_;
@@ -718,99 +762,6 @@
     bool can_move_ = false;
 };
 
-/// ConstVectorRef is a weak, immutable reference to a Vector, used to pass vectors as parameters,
-/// avoiding copies between the caller and the callee. VectorRef can accept a Vector of any 'N'
-/// value, decoupling the caller's vector internal size from the callee's vector size.
-template <typename T>
-class ConstVectorRef {
-    /// The slice type used by this vector reference
-    using Slice = utils::Slice<T>;
-
-  public:
-    /// Constructor from a Vector.
-    /// @param vector the vector reference
-    template <size_t N>
-    ConstVectorRef(const Vector<T, N>& vector)  // NOLINT(runtime/explicit)
-        : slice_(vector.impl_.slice) {}
-
-    /// Copy constructor
-    /// @param other the vector reference
-    ConstVectorRef(const ConstVectorRef& other) = default;
-
-    /// Conversion constructor to convert from a non-const to const vector reference
-    /// @param other the vector reference
-    ConstVectorRef(const VectorRef<T>& other) : slice_(other.slice_) {}  // NOLINT(runtime/explicit)
-
-    /// Move constructor. Deleted as this won't move anything.
-    ConstVectorRef(ConstVectorRef&&) = delete;
-
-    /// Constructor from a Vector with covariance / const conversion
-    /// @param vector the vector to create a reference of
-    /// @see CanReinterpretSlice for rules about conversion
-    template <typename U, size_t N, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
-    ConstVectorRef(const Vector<U, N>& vector)  // NOLINT(runtime/explicit)
-        : slice_(*ReinterpretSlice<T>(&vector.impl_.slice)) {}
-
-    /// Constructor from a VectorRef with covariance / const conversion
-    /// @param other the vector reference
-    /// @see CanReinterpretSlice for rules about conversion
-    template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
-    ConstVectorRef(const VectorRef<U>& other)  // NOLINT(runtime/explicit)
-        : slice_(*ReinterpretSlice<T>(&other.slice_)) {}
-
-    /// Constructor from a ConstVectorRef with covariance / const conversion
-    /// @param other the vector reference
-    /// @see CanReinterpretSlice for rules about conversion
-    template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
-    ConstVectorRef(const ConstVectorRef<U>& other)  // NOLINT(runtime/explicit)
-        : slice_(*ReinterpretSlice<T>(&other.slice_)) {}
-
-    /// Index operator
-    /// @param i the element index. Must be less than `len`.
-    /// @returns a reference to the i'th element.
-    const T& operator[](size_t i) const { return slice_[i]; }
-
-    /// @return the number of elements in the vector
-    size_t Length() const { return slice_.len; }
-
-    /// @return the number of elements that the vector could hold before a heap allocation needs to
-    /// be made
-    size_t Capacity() const { return slice_.cap; }
-
-    /// @returns true if the vector is empty.
-    bool IsEmpty() const { return slice_.len == 0; }
-
-    /// @returns a reference to the first element in the vector
-    const T& Front() const { return slice_.Front(); }
-
-    /// @returns a reference to the last element in the vector
-    const T& Back() const { return slice_.Back(); }
-
-    /// @returns a pointer to the first element in the vector
-    const T* begin() const { return slice_.begin(); }
-
-    /// @returns a pointer to one past the last element in the vector
-    const T* end() const { return slice_.end(); }
-
-    /// @returns a reverse iterator starting with the last element in the vector
-    auto rbegin() const { return slice_.rbegin(); }
-
-    /// @returns the end for a reverse iterator
-    auto rend() const { return slice_.rend(); }
-
-  private:
-    /// Friend class
-    template <typename, size_t>
-    friend class Vector;
-
-    /// Friend class
-    template <typename>
-    friend class ConstVectorRef;
-
-    /// The slice of the vector being referenced.
-    const Slice& slice_;
-};
-
 /// Helper for converting a Vector to a std::vector.
 /// @note This helper exists to help code migration. Avoid if possible.
 template <typename T, size_t N>
diff --git a/src/tint/utils/vector_test.cc b/src/tint/utils/vector_test.cc
index 6e529dc..b4e9fb5 100644
--- a/src/tint/utils/vector_test.cc
+++ b/src/tint/utils/vector_test.cc
@@ -105,12 +105,24 @@
     EXPECT_EQ(vec.Capacity(), 2u);
 }
 
-TEST(TintVectorTest, Empty_NoSmallArray) {
+TEST(TintVectorTest, NoSmallArray) {
     Vector<int, 0> vec;
     EXPECT_EQ(vec.Length(), 0u);
     EXPECT_EQ(vec.Capacity(), 0u);
 }
 
+TEST(TintVectorTest, Empty_SmallArray_Empty) {
+    Vector<int, 2> vec(Empty);
+    EXPECT_EQ(vec.Length(), 0u);
+    EXPECT_EQ(vec.Capacity(), 2u);
+}
+
+TEST(TintVectorTest, Empty_NoSmallArray) {
+    Vector<int, 0> vec(Empty);
+    EXPECT_EQ(vec.Length(), 0u);
+    EXPECT_EQ(vec.Capacity(), 0u);
+}
+
 TEST(TintVectorTest, InitializerList_NoSpill) {
     Vector<std::string, 2> vec{"one", "two"};
     EXPECT_EQ(vec.Length(), 2u);
@@ -139,6 +151,17 @@
     EXPECT_TRUE(AllExternallyHeld(vec));
 }
 
+TEST(TintVectorTest, Push_NoSmallArray) {
+    Vector<std::string, 0> vec;
+    vec.Push("one");
+    vec.Push("two");
+    EXPECT_EQ(vec.Length(), 2u);
+    EXPECT_EQ(vec.Capacity(), 2u);
+    EXPECT_EQ(vec[0], "one");
+    EXPECT_EQ(vec[1], "two");
+    EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
 TEST(TintVectorTest, InferTN_1CString) {
     auto vec = Vector{"one"};
     static_assert(std::is_same_v<decltype(vec)::value_type, const char*>);
@@ -800,7 +823,7 @@
     EXPECT_TRUE(AllInternallyHeld(vec));
 }
 
-TEST(TintVectorTest, DoubleMoveAssign_WithSpill) {
+TEST(TintVectorTest, RepeatMoveAssign_WithSpill) {
     Vector<std::string, 1> vec_a{"hello", "world"};
     Vector<std::string, 1> vec_b{"Ciao", "mondo"};
     Vector<std::string, 1> vec_c{"bonjour", "le", "monde"};
@@ -816,6 +839,288 @@
     EXPECT_TRUE(AllExternallyHeld(vec));
 }
 
+TEST(TintVectorTest, CopyAssignRef_NoSpill_N2_to_N2) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 2> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 2u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_WithSpill_N2_to_N2) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 2> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_NoSpill_N2_to_N1) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 1> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 2u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_WithSpill_N2_to_N1) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 1> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_NoSpill_N2_to_N3) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 3> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_WithSpill_N2_to_N3) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 3> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_NoSpill_N2_to_N0) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 0> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 2u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_WithSpill_N2_to_N0) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 0> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_Self_NoSpill) {
+    Vector<std::string, 2> vec{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec)};
+    vec = ref;
+    EXPECT_EQ(vec.Length(), 2u);
+    EXPECT_EQ(vec.Capacity(), 2u);
+    EXPECT_EQ(vec[0], "hello");
+    EXPECT_EQ(vec[1], "world");
+    EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, CopyAssignRef_Self_WithSpill) {
+    Vector<std::string, 1> vec{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec)};
+    vec = ref;
+    EXPECT_EQ(vec.Length(), 2u);
+    EXPECT_EQ(vec.Capacity(), 2u);
+    EXPECT_EQ(vec[0], "hello");
+    EXPECT_EQ(vec[1], "world");
+    EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, MoveAssignRef_NoSpill_N2_to_N2) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 2> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 2u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_WithSpill_N2_to_N2) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 2> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_NoSpill_N2_to_N1) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 1> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 2u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_SpillSpill_N2_to_N1) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 1> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_NoSpill_N2_to_N3) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 3> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_WithSpill_N2_to_N3) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 3> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_NoSpill_N2_to_N0) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 0> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 2u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_WithSpill_N2_to_N0) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 0> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_Self_NoSpill) {
+    Vector<std::string, 2> vec{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec)};
+    vec = std::move(ref);
+    EXPECT_EQ(vec.Length(), 2u);
+    EXPECT_EQ(vec.Capacity(), 2u);
+    EXPECT_EQ(vec[0], "hello");
+    EXPECT_EQ(vec[1], "world");
+    EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, MoveAssignRef_Self_WithSpill) {
+    Vector<std::string, 1> vec{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec)};
+    vec = std::move(ref);
+    EXPECT_EQ(vec.Length(), 2u);
+    EXPECT_EQ(vec.Capacity(), 2u);
+    EXPECT_EQ(vec[0], "hello");
+    EXPECT_EQ(vec[1], "world");
+    EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, RepeatMoveAssignRef_NoSpill) {
+    Vector<std::string, 3> vec_a{"hello", "world"};
+    Vector<std::string, 3> vec_b{"Ciao", "mondo"};
+    Vector<std::string, 3> vec_c{"Bonjour", "le", "monde"};
+    VectorRef<std::string> ref_a{std::move(vec_a)};
+    VectorRef<std::string> ref_b{std::move(vec_b)};
+    VectorRef<std::string> ref_c{std::move(vec_c)};
+    Vector<std::string, 3> vec;
+    vec = std::move(ref_a);
+    vec = std::move(ref_b);
+    vec = std::move(ref_c);
+    EXPECT_EQ(vec.Length(), 3u);
+    EXPECT_EQ(vec.Capacity(), 3u);
+    EXPECT_EQ(vec[0], "Bonjour");
+    EXPECT_EQ(vec[1], "le");
+    EXPECT_EQ(vec[2], "monde");
+    EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, RepeatMoveAssignRef_WithSpill) {
+    Vector<std::string, 1> vec_a{"hello", "world"};
+    Vector<std::string, 1> vec_b{"Ciao", "mondo"};
+    Vector<std::string, 1> vec_c{"bonjour", "le", "monde"};
+    VectorRef<std::string> ref_a{std::move(vec_a)};
+    VectorRef<std::string> ref_b{std::move(vec_b)};
+    VectorRef<std::string> ref_c{std::move(vec_c)};
+    Vector<std::string, 1> vec;
+    vec = std::move(ref_a);
+    vec = std::move(ref_b);
+    vec = std::move(ref_c);
+    EXPECT_EQ(vec.Length(), 3u);
+    EXPECT_EQ(vec.Capacity(), 3u);
+    EXPECT_EQ(vec[0], "bonjour");
+    EXPECT_EQ(vec[1], "le");
+    EXPECT_EQ(vec[2], "monde");
+    EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
 TEST(TintVectorTest, Index) {
     Vector<std::string, 2> vec{"hello", "world"};
     static_assert(!std::is_const_v<std::remove_reference_t<decltype(vec[0])>>);
@@ -1684,7 +1989,7 @@
 TEST(TintVectorRefTest, Index) {
     Vector<std::string, 2> vec{"one", "two"};
     VectorRef<std::string> vec_ref(vec);
-    static_assert(!std::is_const_v<std::remove_reference_t<decltype(vec_ref[0])>>);
+    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref[0])>>);
     EXPECT_EQ(vec_ref[0], "one");
     EXPECT_EQ(vec_ref[1], "two");
 }
@@ -1755,206 +2060,6 @@
     EXPECT_EQ(vec_ref.end(), &vec[0] + 3);
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// TintVectorConstRefTest
-////////////////////////////////////////////////////////////////////////////////
-TEST(TintVectorConstRefTest, CopyVectorConstRef) {
-    Vector<std::string, 1> vec_a{"one", "two"};
-    ConstVectorRef<std::string> vec_ref_a(vec_a);
-    ConstVectorRef<std::string> vec_ref_b(vec_ref_a);
-    Vector<std::string, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], "one");
-    EXPECT_EQ(vec_b[1], "two");
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVectorConstRef_Upcast) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    ConstVectorRef<C1*> vec_ref_a(vec_a);
-    ConstVectorRef<C0*> vec_ref_b(vec_ref_a);  // Up-cast
-    Vector<C0*, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVectorConstRef_AddConst) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    ConstVectorRef<C1*> vec_ref_a(vec_a);
-    ConstVectorRef<const C1*> vec_ref_b(vec_ref_a);  // Up-cast
-    Vector<const C1*, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVectorConstRef_UpcastAndAddConst) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    ConstVectorRef<C1*> vec_ref_a(vec_a);
-    ConstVectorRef<const C0*> vec_ref_b(vec_ref_a);  // Up-cast
-    Vector<const C0*, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVector) {
-    Vector<std::string, 1> vec_a{"one", "two"};
-    ConstVectorRef<std::string> vec_ref(vec_a);
-    Vector<std::string, 2> vec_b(vec_ref);
-    EXPECT_EQ(vec_b[0], "one");
-    EXPECT_EQ(vec_b[1], "two");
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVector_Upcast) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    ConstVectorRef<C0*> vec_ref(vec_a);
-    EXPECT_EQ(vec_ref[0], &c2a);
-    EXPECT_EQ(vec_ref[1], &c2b);
-    Vector<C0*, 2> vec_b(vec_ref);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVector_AddConst) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    ConstVectorRef<const C1*> vec_ref(vec_a);
-    EXPECT_EQ(vec_ref[0], &c2a);
-    EXPECT_EQ(vec_ref[1], &c2b);
-    Vector<const C1*, 2> vec_b(vec_ref);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVectorRef_Upcast) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    VectorRef<C1*> vec_ref_a(vec_a);
-    ConstVectorRef<C0*> vec_ref_b(vec_ref_a);
-    EXPECT_EQ(vec_ref_b[0], &c2a);
-    EXPECT_EQ(vec_ref_b[1], &c2b);
-    Vector<C0*, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVectorRef_AddConst) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    VectorRef<C1*> vec_ref_a(vec_a);
-    ConstVectorRef<const C1*> vec_ref_b(vec_ref_a);
-    EXPECT_EQ(vec_ref_b[0], &c2a);
-    EXPECT_EQ(vec_ref_b[1], &c2b);
-    Vector<const C1*, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVectorRef_UpcastAndAddConst) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    VectorRef<C1*> vec_ref_a(vec_a);
-    ConstVectorRef<const C0*> vec_ref_b(vec_ref_a);
-    EXPECT_EQ(vec_ref_b[0], &c2a);
-    EXPECT_EQ(vec_ref_b[1], &c2b);
-    Vector<const C0*, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, Index) {
-    Vector<std::string, 2> vec{"one", "two"};
-    ConstVectorRef<std::string> vec_ref(vec);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref[0])>>);
-    EXPECT_EQ(vec_ref[0], "one");
-    EXPECT_EQ(vec_ref[1], "two");
-}
-
-TEST(TintVectorConstRefTest, ConstIndex) {
-    Vector<std::string, 2> vec{"one", "two"};
-    const ConstVectorRef<std::string> vec_ref(vec);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref[0])>>);
-    EXPECT_EQ(vec_ref[0], "one");
-    EXPECT_EQ(vec_ref[1], "two");
-}
-
-TEST(TintVectorConstRefTest, Length) {
-    Vector<std::string, 2> vec{"one", "two", "three"};
-    ConstVectorRef<std::string> vec_ref(vec);
-    EXPECT_EQ(vec_ref.Length(), 3u);
-}
-
-TEST(TintVectorConstRefTest, Capacity) {
-    Vector<std::string, 5> vec{"one", "two", "three"};
-    ConstVectorRef<std::string> vec_ref(vec);
-    EXPECT_EQ(vec_ref.Capacity(), 5u);
-}
-
-TEST(TintVectorConstRefTest, IsEmpty) {
-    Vector<std::string, 1> vec;
-    ConstVectorRef<std::string> vec_ref(vec);
-    EXPECT_TRUE(vec_ref.IsEmpty());
-    vec.Push("one");
-    EXPECT_FALSE(vec_ref.IsEmpty());
-    vec.Pop();
-    EXPECT_TRUE(vec_ref.IsEmpty());
-}
-
-TEST(TintVectorConstRefTest, FrontBack) {
-    Vector<std::string, 3> vec{"front", "mid", "back"};
-    ConstVectorRef<std::string> vec_ref(vec);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref.Front())>>);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref.Back())>>);
-    EXPECT_EQ(vec_ref.Front(), "front");
-    EXPECT_EQ(vec_ref.Back(), "back");
-}
-
-TEST(TintVectorConstRefTest, ConstFrontBack) {
-    Vector<std::string, 3> vec{"front", "mid", "back"};
-    const ConstVectorRef<std::string> vec_ref(vec);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref.Front())>>);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref.Back())>>);
-    EXPECT_EQ(vec_ref.Front(), "front");
-    EXPECT_EQ(vec_ref.Back(), "back");
-}
-
-TEST(TintVectorConstRefTest, BeginEnd) {
-    Vector<std::string, 3> vec{"front", "mid", "back"};
-    ConstVectorRef<std::string> vec_ref(vec);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec_ref.begin())>>);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec_ref.end())>>);
-    EXPECT_EQ(vec_ref.begin(), &vec[0]);
-    EXPECT_EQ(vec_ref.end(), &vec[0] + 3);
-}
-
-TEST(TintVectorConstRefTest, ConstBeginEnd) {
-    Vector<std::string, 3> vec{"front", "mid", "back"};
-    const ConstVectorRef<std::string> vec_ref(vec);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec_ref.begin())>>);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec_ref.end())>>);
-    EXPECT_EQ(vec_ref.begin(), &vec[0]);
-    EXPECT_EQ(vec_ref.end(), &vec[0] + 3);
-}
-
 }  // namespace
 }  // namespace tint::utils
 
diff --git a/src/tint/val/hlsl.cc b/src/tint/val/hlsl.cc
index f1cfb05..eabbe68 100644
--- a/src/tint/val/hlsl.cc
+++ b/src/tint/val/hlsl.cc
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <string>
+
 #include "src/tint/val/val.h"
 
 #include "src/tint/utils/io/command.h"
@@ -31,7 +33,8 @@
 
 Result HlslUsingDXC(const std::string& dxc_path,
                     const std::string& source,
-                    const EntryPointList& entry_points) {
+                    const EntryPointList& entry_points,
+                    bool require_16bit_types) {
     Result result;
 
     auto dxc = utils::Command(dxc_path);
@@ -41,11 +44,14 @@
         return result;
     }
 
+    // Native 16-bit types, e.g. float16_t, require SM6.2. Otherwise we use SM6.0.
+    const char* shader_model_version = require_16bit_types ? "6_2" : "6_0";
+
     utils::TmpFile file;
     file << source;
 
     for (auto ep : entry_points) {
-        const char* profile = "";
+        const char* stage_prefix = "";
 
         switch (ep.second) {
             case ast::PipelineStage::kNone:
@@ -53,24 +59,26 @@
                 result.failed = true;
                 return result;
             case ast::PipelineStage::kVertex:
-                profile = "-T vs_6_0";
+                stage_prefix = "vs";
                 break;
             case ast::PipelineStage::kFragment:
-                profile = "-T ps_6_0";
+                stage_prefix = "ps";
                 break;
             case ast::PipelineStage::kCompute:
-                profile = "-T cs_6_0";
+                stage_prefix = "cs";
                 break;
         }
 
         // Match Dawn's compile flags
         // See dawn\src\dawn_native\d3d12\RenderPipelineD3D12.cpp
         // and dawn_native\d3d12\ShaderModuleD3D12.cpp (GetDXCArguments)
-        auto res = dxc(profile,
-                       "-E " + ep.first,  // Entry point
-                       "/Zpr",            // D3DCOMPILE_PACK_MATRIX_ROW_MAJOR
-                       "/Gis",            // D3DCOMPILE_IEEE_STRICTNESS
-                       file.Path());
+        auto res = dxc(
+            "-T " + std::string(stage_prefix) + "_" + std::string(shader_model_version),  // Profile
+            "-E " + ep.first,                                  // Entry point
+            "/Zpr",                                            // D3DCOMPILE_PACK_MATRIX_ROW_MAJOR
+            "/Gis",                                            // D3DCOMPILE_IEEE_STRICTNESS
+            require_16bit_types ? "-enable-16bit-types" : "",  // Enable 16-bit if required
+            file.Path());
         if (!res.out.empty()) {
             if (!result.output.empty()) {
                 result.output += "\n";
diff --git a/src/tint/val/val.h b/src/tint/val/val.h
index dae6fca..02ffc78 100644
--- a/src/tint/val/val.h
+++ b/src/tint/val/val.h
@@ -49,7 +49,8 @@
 /// @return the result of the compile
 Result HlslUsingDXC(const std::string& dxc_path,
                     const std::string& source,
-                    const EntryPointList& entry_points);
+                    const EntryPointList& entry_points,
+                    bool require_16bit_types);
 
 #ifdef _WIN32
 /// Hlsl attempts to compile the shader with FXC, verifying that the shader
diff --git a/src/tint/writer/append_vector.cc b/src/tint/writer/append_vector.cc
index d6eb017..527560e 100644
--- a/src/tint/writer/append_vector.cc
+++ b/src/tint/writer/append_vector.cc
@@ -150,9 +150,8 @@
     }
 
     auto* constructor_ast = b->Construct(
-        packed_ast_ty, utils::ToStdVector(utils::Transform(packed, [&](const sem::Expression* expr) {
-            return expr->Declaration();
-        })));
+        packed_ast_ty,
+        utils::Transform(packed, [&](const sem::Expression* expr) { return expr->Declaration(); }));
     auto* constructor_target = b->create<sem::TypeConstructor>(
         packed_sem_ty,
         utils::Transform(packed,
diff --git a/src/tint/writer/append_vector_test.cc b/src/tint/writer/append_vector_test.cc
index b4ab0c7..a8608f9 100644
--- a/src/tint/writer/append_vector_test.cc
+++ b/src/tint/writer/append_vector_test.cc
@@ -41,7 +41,7 @@
 
     auto* vec_123 = As<ast::CallExpression>(append->Declaration());
     ASSERT_NE(vec_123, nullptr);
-    ASSERT_EQ(vec_123->args.size(), 3u);
+    ASSERT_EQ(vec_123->args.Length(), 3u);
     EXPECT_EQ(vec_123->args[0], scalar_1);
     EXPECT_EQ(vec_123->args[1], scalar_2);
     EXPECT_EQ(vec_123->args[2], scalar_3);
@@ -81,13 +81,13 @@
 
     auto* vec_123 = As<ast::CallExpression>(append->Declaration());
     ASSERT_NE(vec_123, nullptr);
-    ASSERT_EQ(vec_123->args.size(), 3u);
+    ASSERT_EQ(vec_123->args.Length(), 3u);
     EXPECT_EQ(vec_123->args[0], scalar_1);
     EXPECT_EQ(vec_123->args[1], scalar_2);
     auto* u32_to_i32 = vec_123->args[2]->As<ast::CallExpression>();
     ASSERT_NE(u32_to_i32, nullptr);
     EXPECT_TRUE(u32_to_i32->target.type->Is<ast::I32>());
-    ASSERT_EQ(u32_to_i32->args.size(), 1u);
+    ASSERT_EQ(u32_to_i32->args.Length(), 1u);
     EXPECT_EQ(u32_to_i32->args[0], scalar_3);
 
     auto* call = Sem().Get<sem::Call>(vec_123);
@@ -127,19 +127,19 @@
 
     auto* vec_123 = As<ast::CallExpression>(append->Declaration());
     ASSERT_NE(vec_123, nullptr);
-    ASSERT_EQ(vec_123->args.size(), 2u);
+    ASSERT_EQ(vec_123->args.Length(), 2u);
     auto* v2u32_to_v2i32 = vec_123->args[0]->As<ast::CallExpression>();
     ASSERT_NE(v2u32_to_v2i32, nullptr);
     ASSERT_TRUE(v2u32_to_v2i32->target.type->Is<ast::Vector>());
     EXPECT_EQ(v2u32_to_v2i32->target.type->As<ast::Vector>()->width, 2u);
     EXPECT_TRUE(v2u32_to_v2i32->target.type->As<ast::Vector>()->type->Is<ast::I32>());
-    EXPECT_EQ(v2u32_to_v2i32->args.size(), 1u);
+    EXPECT_EQ(v2u32_to_v2i32->args.Length(), 1u);
     EXPECT_EQ(v2u32_to_v2i32->args[0], uvec_12);
 
     auto* u32_to_i32 = vec_123->args[1]->As<ast::CallExpression>();
     ASSERT_NE(u32_to_i32, nullptr);
     EXPECT_TRUE(u32_to_i32->target.type->Is<ast::I32>());
-    ASSERT_EQ(u32_to_i32->args.size(), 1u);
+    ASSERT_EQ(u32_to_i32->args.Length(), 1u);
     EXPECT_EQ(u32_to_i32->args[0], scalar_3);
 
     auto* call = Sem().Get<sem::Call>(vec_123);
@@ -175,13 +175,13 @@
 
     auto* vec_123 = As<ast::CallExpression>(append->Declaration());
     ASSERT_NE(vec_123, nullptr);
-    ASSERT_EQ(vec_123->args.size(), 3u);
+    ASSERT_EQ(vec_123->args.Length(), 3u);
     EXPECT_EQ(vec_123->args[0], scalar_1);
     EXPECT_EQ(vec_123->args[1], scalar_2);
     auto* f32_to_i32 = vec_123->args[2]->As<ast::CallExpression>();
     ASSERT_NE(f32_to_i32, nullptr);
     EXPECT_TRUE(f32_to_i32->target.type->Is<ast::I32>());
-    ASSERT_EQ(f32_to_i32->args.size(), 1u);
+    ASSERT_EQ(f32_to_i32->args.Length(), 1u);
     EXPECT_EQ(f32_to_i32->args[0], scalar_3);
 
     auto* call = Sem().Get<sem::Call>(vec_123);
@@ -220,7 +220,7 @@
 
     auto* vec_1234 = As<ast::CallExpression>(append->Declaration());
     ASSERT_NE(vec_1234, nullptr);
-    ASSERT_EQ(vec_1234->args.size(), 4u);
+    ASSERT_EQ(vec_1234->args.Length(), 4u);
     EXPECT_EQ(vec_1234->args[0], scalar_1);
     EXPECT_EQ(vec_1234->args[1], scalar_2);
     EXPECT_EQ(vec_1234->args[2], scalar_3);
@@ -262,7 +262,7 @@
 
     auto* vec_123 = As<ast::CallExpression>(append->Declaration());
     ASSERT_NE(vec_123, nullptr);
-    ASSERT_EQ(vec_123->args.size(), 2u);
+    ASSERT_EQ(vec_123->args.Length(), 2u);
     EXPECT_EQ(vec_123->args[0], vec_12);
     EXPECT_EQ(vec_123->args[1], scalar_3);
 
@@ -300,7 +300,7 @@
 
     auto* vec_123 = As<ast::CallExpression>(append->Declaration());
     ASSERT_NE(vec_123, nullptr);
-    ASSERT_EQ(vec_123->args.size(), 3u);
+    ASSERT_EQ(vec_123->args.Length(), 3u);
     EXPECT_EQ(vec_123->args[0], scalar_1);
     EXPECT_EQ(vec_123->args[1], scalar_2);
     EXPECT_EQ(vec_123->args[2], scalar_3);
@@ -340,7 +340,7 @@
 
     auto* vec_123 = As<ast::CallExpression>(append->Declaration());
     ASSERT_NE(vec_123, nullptr);
-    ASSERT_EQ(vec_123->args.size(), 2u);
+    ASSERT_EQ(vec_123->args.Length(), 2u);
     EXPECT_EQ(vec_123->args[0], vec_12);
     EXPECT_EQ(vec_123->args[1], scalar_3);
 
@@ -377,12 +377,12 @@
 
     auto* vec_123 = As<ast::CallExpression>(append->Declaration());
     ASSERT_NE(vec_123, nullptr);
-    ASSERT_EQ(vec_123->args.size(), 2u);
+    ASSERT_EQ(vec_123->args.Length(), 2u);
     EXPECT_EQ(vec_123->args[0], vec_12);
     auto* f32_to_i32 = vec_123->args[1]->As<ast::CallExpression>();
     ASSERT_NE(f32_to_i32, nullptr);
     EXPECT_TRUE(f32_to_i32->target.type->Is<ast::I32>());
-    ASSERT_EQ(f32_to_i32->args.size(), 1u);
+    ASSERT_EQ(f32_to_i32->args.Length(), 1u);
     EXPECT_EQ(f32_to_i32->args[0], scalar_3);
 
     auto* call = Sem().Get<sem::Call>(vec_123);
@@ -418,7 +418,7 @@
 
     auto* vec_123 = As<ast::CallExpression>(append->Declaration());
     ASSERT_NE(vec_123, nullptr);
-    ASSERT_EQ(vec_123->args.size(), 2u);
+    ASSERT_EQ(vec_123->args.Length(), 2u);
     EXPECT_EQ(vec_123->args[0], vec_12);
     EXPECT_EQ(vec_123->args[1], scalar_3);
 
@@ -453,7 +453,7 @@
 
     auto* vec_0004 = As<ast::CallExpression>(append->Declaration());
     ASSERT_NE(vec_0004, nullptr);
-    ASSERT_EQ(vec_0004->args.size(), 4u);
+    ASSERT_EQ(vec_0004->args.Length(), 4u);
     for (size_t i = 0; i < 3; i++) {
         auto* literal = As<ast::IntLiteralExpression>(vec_0004->args[i]);
         ASSERT_NE(literal, nullptr);
diff --git a/src/tint/writer/flatten_bindings_test.cc b/src/tint/writer/flatten_bindings_test.cc
index ae01abd..be5fb42 100644
--- a/src/tint/writer/flatten_bindings_test.cc
+++ b/src/tint/writer/flatten_bindings_test.cc
@@ -28,7 +28,6 @@
 
 TEST_F(FlattenBindingsTest, NoBindings) {
     ProgramBuilder b;
-    b.WrapInFunction();
 
     resolver::Resolver resolver(&b);
 
@@ -44,7 +43,6 @@
     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.WrapInFunction();
 
     resolver::Resolver resolver(&b);
 
diff --git a/src/tint/writer/generate_external_texture_bindings_test.cc b/src/tint/writer/generate_external_texture_bindings_test.cc
index 292b2ea..ee3fc47 100644
--- a/src/tint/writer/generate_external_texture_bindings_test.cc
+++ b/src/tint/writer/generate_external_texture_bindings_test.cc
@@ -27,7 +27,6 @@
 
 TEST_F(GenerateExternalTextureBindingsTest, None) {
     ProgramBuilder b;
-    b.WrapInFunction();
 
     tint::Program program(std::move(b));
     ASSERT_TRUE(program.IsValid());
@@ -38,7 +37,6 @@
 TEST_F(GenerateExternalTextureBindingsTest, One) {
     ProgramBuilder b;
     b.GlobalVar("v0", b.ty.external_texture(), b.GroupAndBinding(0, 0));
-    b.WrapInFunction();
 
     tint::Program program(std::move(b));
     ASSERT_TRUE(program.IsValid());
@@ -56,7 +54,6 @@
     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.WrapInFunction();
 
     tint::Program program(std::move(b));
     ASSERT_TRUE(program.IsValid());
@@ -80,7 +77,6 @@
     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.WrapInFunction();
 
     tint::Program program(std::move(b));
     ASSERT_TRUE(program.IsValid());
@@ -107,7 +103,6 @@
     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.WrapInFunction();
 
     tint::Program program(std::move(b));
     ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 4c513be..c66e174 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -70,6 +70,7 @@
 #include "src/tint/utils/defer.h"
 #include "src/tint/utils/map.h"
 #include "src/tint/utils/scoped_assignment.h"
+#include "src/tint/utils/string.h"
 #include "src/tint/writer/append_vector.h"
 #include "src/tint/writer/float_to_string.h"
 #include "src/tint/writer/generate_external_texture_bindings.h"
@@ -272,8 +273,8 @@
 
     auto* mod = builder_.Sem().Module();
     for (auto* decl : mod->DependencyOrderedDeclarations()) {
-        if (decl->Is<ast::Alias>()) {
-            continue;  // Ignore aliases.
+        if (decl->IsAnyOf<ast::Alias, ast::StaticAssert>()) {
+            continue;  // These are not emitted.
         }
 
         if (auto* global = decl->As<ast::Variable>()) {
@@ -686,7 +687,7 @@
     return true;
 }
 
-bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) {
+bool GeneratorImpl::EmitStatements(utils::VectorRef<const ast::Statement*> stmts) {
     for (auto* s : stmts) {
         if (!EmitStatement(s)) {
             return false;
@@ -695,7 +696,7 @@
     return true;
 }
 
-bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) {
+bool GeneratorImpl::EmitStatementsWithIndent(utils::VectorRef<const ast::Statement*> stmts) {
     ScopedIndent si(this);
     return EmitStatements(stmts);
 }
@@ -892,7 +893,7 @@
         out << name;
         {
             ScopedParen sp(out);
-            for (size_t i = 0; i < expr->args.size(); i++) {
+            for (size_t i = 0; i < expr->args.Length(); i++) {
                 auto* arg = expr->args[i];
                 if (i > 0) {
                     out << ", ";
@@ -1204,97 +1205,51 @@
 bool GeneratorImpl::EmitModfCall(std::ostream& out,
                                  const ast::CallExpression* expr,
                                  const sem::Builtin* builtin) {
-    if (expr->args.size() == 1) {
-        return CallBuiltinHelper(
-            out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
-                // Emit the builtin return type unique to this overload. This does not
-                // exist in the AST, so it will not be generated in Generate().
-                if (!EmitStructType(&helpers_, builtin->ReturnType()->As<sem::Struct>())) {
+    TINT_ASSERT(Writer, expr->args.Length() == 1);
+    return CallBuiltinHelper(
+        out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
+            // Emit the builtin return type unique to this overload. This does not
+            // exist in the AST, so it will not be generated in Generate().
+            if (!EmitStructType(&helpers_, builtin->ReturnType()->As<sem::Struct>())) {
+                return false;
+            }
+
+            {
+                auto l = line(b);
+                if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
+                              ast::Access::kUndefined, "")) {
                     return false;
                 }
-
-                {
-                    auto l = line(b);
-                    if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
-                                  ast::Access::kUndefined, "")) {
-                        return false;
-                    }
-                    l << " result;";
-                }
-                line(b) << "result.fract = modf(" << params[0] << ", result.whole);";
-                line(b) << "return result;";
-                return true;
-            });
-    }
-
-    // DEPRECATED
-    out << "modf";
-    ScopedParen sp(out);
-    if (!EmitExpression(out, expr->args[0])) {
-        return false;
-    }
-    out << ", ";
-    if (!EmitExpression(out, expr->args[1])) {
-        return false;
-    }
-    return true;
+                l << " result;";
+            }
+            line(b) << "result.fract = modf(" << params[0] << ", result.whole);";
+            line(b) << "return result;";
+            return true;
+        });
 }
 
 bool GeneratorImpl::EmitFrexpCall(std::ostream& out,
                                   const ast::CallExpression* expr,
                                   const sem::Builtin* builtin) {
-    if (expr->args.size() == 1) {
-        return CallBuiltinHelper(
-            out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
-                // Emit the builtin return type unique to this overload. This does not
-                // exist in the AST, so it will not be generated in Generate().
-                if (!EmitStructType(&helpers_, builtin->ReturnType()->As<sem::Struct>())) {
-                    return false;
-                }
-
-                {
-                    auto l = line(b);
-                    if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
-                                  ast::Access::kUndefined, "")) {
-                        return false;
-                    }
-                    l << " result;";
-                }
-                line(b) << "result.sig = frexp(" << params[0] << ", result.exp);";
-                line(b) << "return result;";
-                return true;
-            });
-    }
-    // DEPRECATED
-    // Exponent is an integer in WGSL, but HLSL wants a float.
-    // We need to make the call with a temporary float, and then cast.
+    TINT_ASSERT(Writer, expr->args.Length() == 1);
     return CallBuiltinHelper(
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
-            auto* significand_ty = builtin->Parameters()[0]->Type();
-            auto significand = params[0];
-            auto* exponent_ty = builtin->Parameters()[1]->Type();
-            auto exponent = params[1];
-
-            std::string width;
-            if (auto* vec = significand_ty->As<sem::Vector>()) {
-                width = std::to_string(vec->Width());
+            // Emit the builtin return type unique to this overload. This does not
+            // exist in the AST, so it will not be generated in Generate().
+            if (!EmitStructType(&helpers_, builtin->ReturnType()->As<sem::Struct>())) {
+                return false;
             }
 
-            // Exponent is an integer, which HLSL does not have an overload for.
-            // We need to cast from a float.
-            line(b) << "float" << width << " float_exp;";
-            line(b) << "float" << width << " significand = frexp(" << significand
-                    << ", float_exp);";
             {
                 auto l = line(b);
-                l << exponent << " = ";
-                if (!EmitType(l, exponent_ty->UnwrapPtr(), ast::StorageClass::kNone,
+                if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
                               ast::Access::kUndefined, "")) {
                     return false;
                 }
-                l << "(float_exp);";
+                l << " result;";
             }
-            line(b) << "return significand;";
+            line(b) << "result.sig = frexp(" << params[0] << ", result.exp);";
+            line(b) << "return result;";
             return true;
         });
 }
@@ -1745,7 +1700,7 @@
                 return false;
             }
             out << ":";
-            if (selector == stmt->selectors.back()) {
+            if (selector == stmt->selectors.Back()) {
                 out << " {";
             }
         }
@@ -1846,7 +1801,7 @@
                 return false;
             }
         } else {
-            if (!EmitStatementsWithIndent({stmt->else_statement})) {
+            if (!EmitStatementsWithIndent(utils::Vector{stmt->else_statement})) {
                 return false;
             }
         }
@@ -1936,6 +1891,11 @@
                 case ast::StorageClass::kIn:
                 case ast::StorageClass::kOut:
                     return EmitIOVariable(sem);
+                case ast::StorageClass::kPushConstant:
+                    diagnostics_.add_error(
+                        diag::System::Writer,
+                        "unhandled storage class " + utils::ToString(sem->StorageClass()));
+                    return false;
                 default: {
                     TINT_ICE(Writer, diagnostics_)
                         << "unhandled storage class " << sem->StorageClass();
@@ -2096,8 +2056,9 @@
     return true;
 }
 
-void GeneratorImpl::EmitInterpolationQualifiers(std::ostream& out,
-                                                const ast::AttributeList& attributes) {
+void GeneratorImpl::EmitInterpolationQualifiers(
+    std::ostream& out,
+    utils::VectorRef<const ast::Attribute*> attributes) {
     for (auto* attr : attributes) {
         if (auto* interpolate = attr->As<ast::InterpolateAttribute>()) {
             switch (interpolate->type) {
@@ -2121,8 +2082,9 @@
     }
 }
 
-bool GeneratorImpl::EmitAttributes(std::ostream& out, const ast::AttributeList& attributes) {
-    if (attributes.empty()) {
+bool GeneratorImpl::EmitAttributes(std::ostream& out,
+                                   utils::VectorRef<const ast::Attribute*> attributes) {
+    if (attributes.IsEmpty()) {
         return true;
     }
     bool first = true;
@@ -2644,69 +2606,53 @@
 }
 
 bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
-    if (auto* a = stmt->As<ast::AssignmentStatement>()) {
-        return EmitAssign(a);
-    }
-    if (auto* b = stmt->As<ast::BlockStatement>()) {
-        return EmitBlock(b);
-    }
-    if (auto* b = stmt->As<ast::BreakStatement>()) {
-        return EmitBreak(b);
-    }
-    if (auto* c = stmt->As<ast::CallStatement>()) {
-        auto out = line();
-        if (!EmitCall(out, c->expr)) {
-            return false;
-        }
-        out << ";";
-        return true;
-    }
-    if (auto* c = stmt->As<ast::ContinueStatement>()) {
-        return EmitContinue(c);
-    }
-    if (auto* d = stmt->As<ast::DiscardStatement>()) {
-        return EmitDiscard(d);
-    }
-    if (stmt->As<ast::FallthroughStatement>()) {
-        line() << "/* fallthrough */";
-        return true;
-    }
-    if (auto* i = stmt->As<ast::IfStatement>()) {
-        return EmitIf(i);
-    }
-    if (auto* l = stmt->As<ast::LoopStatement>()) {
-        return EmitLoop(l);
-    }
-    if (auto* l = stmt->As<ast::ForLoopStatement>()) {
-        return EmitForLoop(l);
-    }
-    if (auto* l = stmt->As<ast::WhileStatement>()) {
-        return EmitWhile(l);
-    }
-    if (auto* r = stmt->As<ast::ReturnStatement>()) {
-        return EmitReturn(r);
-    }
-    if (auto* s = stmt->As<ast::SwitchStatement>()) {
-        return EmitSwitch(s);
-    }
-    if (auto* v = stmt->As<ast::VariableDeclStatement>()) {
-        return Switch(
-            v->variable,  //
-            [&](const ast::Var* var) { return EmitVar(var); },
-            [&](const ast::Let* let) { return EmitLet(let); },
-            [&](const ast::Const*) {
-                return true;  // Constants are embedded at their use
-            },
-            [&](Default) {  //
-                TINT_ICE(Writer, diagnostics_)
-                    << "unknown variable type: " << v->variable->TypeInfo().name;
+    return Switch(
+        stmt,  //
+        [&](const ast::AssignmentStatement* a) { return EmitAssign(a); },
+        [&](const ast::BlockStatement* b) { return EmitBlock(b); },
+        [&](const ast::BreakStatement* b) { return EmitBreak(b); },
+        [&](const ast::CallStatement* c) {
+            auto out = line();
+            if (!EmitCall(out, c->expr)) {
                 return false;
-            });
-    }
-
-    diagnostics_.add_error(diag::System::Writer,
-                           "unknown statement type: " + std::string(stmt->TypeInfo().name));
-    return false;
+            }
+            out << ";";
+            return true;
+        },
+        [&](const ast::ContinueStatement* c) { return EmitContinue(c); },
+        [&](const ast::DiscardStatement* d) { return EmitDiscard(d); },
+        [&](const ast::FallthroughStatement*) {
+            line() << "/* fallthrough */";
+            return true;
+        },
+        [&](const ast::IfStatement* i) { return EmitIf(i); },
+        [&](const ast::LoopStatement* l) { return EmitLoop(l); },
+        [&](const ast::ForLoopStatement* l) { return EmitForLoop(l); },
+        [&](const ast::WhileStatement* l) { return EmitWhile(l); },
+        [&](const ast::ReturnStatement* r) { return EmitReturn(r); },
+        [&](const ast::SwitchStatement* s) { return EmitSwitch(s); },
+        [&](const ast::VariableDeclStatement* v) {
+            return Switch(
+                v->variable,  //
+                [&](const ast::Var* var) { return EmitVar(var); },
+                [&](const ast::Let* let) { return EmitLet(let); },
+                [&](const ast::Const*) {
+                    return true;  // Constants are embedded at their use
+                },
+                [&](Default) {  //
+                    TINT_ICE(Writer, diagnostics_)
+                        << "unknown variable type: " << v->variable->TypeInfo().name;
+                    return false;
+                });
+        },
+        [&](const ast::StaticAssert*) {
+            return true;  // Not emitted
+        },
+        [&](Default) {
+            diagnostics_.add_error(diag::System::Writer,
+                                   "unknown statement type: " + std::string(stmt->TypeInfo().name));
+            return false;
+        });
 }
 
 bool GeneratorImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
diff --git a/src/tint/writer/glsl/generator_impl.h b/src/tint/writer/glsl/generator_impl.h
index 86be974..502df8b 100644
--- a/src/tint/writer/glsl/generator_impl.h
+++ b/src/tint/writer/glsl/generator_impl.h
@@ -126,11 +126,11 @@
     /// Emits a list of statements
     /// @param stmts the statement list
     /// @returns true if the statements were emitted successfully
-    bool EmitStatements(const ast::StatementList& stmts);
+    bool EmitStatements(utils::VectorRef<const ast::Statement*> stmts);
     /// Emits a list of statements with an indentation
     /// @param stmts the statement list
     /// @returns true if the statements were emitted successfully
-    bool EmitStatementsWithIndent(const ast::StatementList& stmts);
+    bool EmitStatementsWithIndent(utils::VectorRef<const ast::Statement*> stmts);
     /// Handles a block statement
     /// @param stmt the statement to emit
     /// @returns true if the statement was emitted successfully
@@ -329,12 +329,13 @@
     /// Handles emitting interpolation qualifiers
     /// @param out the output of the expression stream
     /// @param attrs the attributes
-    void EmitInterpolationQualifiers(std::ostream& out, const ast::AttributeList& attrs);
+    void EmitInterpolationQualifiers(std::ostream& out,
+                                     utils::VectorRef<const ast::Attribute*> attrs);
     /// Handles emitting attributes
     /// @param out the output of the expression stream
     /// @param attrs the attributes
     /// @returns true if the attributes were emitted
-    bool EmitAttributes(std::ostream& out, const ast::AttributeList& attrs);
+    bool EmitAttributes(std::ostream& out, utils::VectorRef<const ast::Attribute*> attrs);
     /// Handles emitting the entry point function
     /// @param func the entry point
     /// @returns true if the entry point function was emitted
diff --git a/src/tint/writer/glsl/generator_impl_binary_test.cc b/src/tint/writer/glsl/generator_impl_binary_test.cc
index c1a0fa9..2a4994a 100644
--- a/src/tint/writer/glsl/generator_impl_binary_test.cc
+++ b/src/tint/writer/glsl/generator_impl_binary_test.cc
@@ -668,7 +668,7 @@
            Block(Return(1_i)),
            Else(If(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("b"), Expr("c")),
                    Block(Return(2_i)), Else(Block(Return(3_i))))));
-    Func("func", {}, ty.i32(), {WrapInStatement(expr)});
+    Func("func", utils::Empty, ty.i32(), utils::Vector{WrapInStatement(expr)});
 
     GeneratorImpl& gen = Build();
 
@@ -704,7 +704,7 @@
         ast::BinaryOp::kLogicalOr,
         create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")),
         Expr("c")));
-    Func("func", {}, ty.bool_(), {WrapInStatement(expr)});
+    Func("func", utils::Empty, ty.bool_(), utils::Vector{WrapInStatement(expr)});
 
     GeneratorImpl& gen = Build();
 
@@ -788,26 +788,25 @@
     // foo(a && b, c || d, (a || c) && (b || d))
 
     Func("foo",
-         {
+         utils::Vector{
              Param(Sym(), ty.bool_()),
              Param(Sym(), ty.bool_()),
              Param(Sym(), ty.bool_()),
          },
-         ty.void_(), ast::StatementList{}, ast::AttributeList{});
+         ty.void_(), utils::Empty, utils::Empty);
     GlobalVar("a", ty.bool_(), ast::StorageClass::kPrivate);
     GlobalVar("b", ty.bool_(), ast::StorageClass::kPrivate);
     GlobalVar("c", ty.bool_(), ast::StorageClass::kPrivate);
     GlobalVar("d", ty.bool_(), ast::StorageClass::kPrivate);
 
-    ast::ExpressionList params;
-    params.push_back(
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")));
-    params.push_back(
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("c"), Expr("d")));
-    params.push_back(create<ast::BinaryExpression>(
-        ast::BinaryOp::kLogicalAnd,
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("a"), Expr("c")),
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("b"), Expr("d"))));
+    utils::Vector params{
+        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")),
+        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("c"), Expr("d")),
+        create<ast::BinaryExpression>(
+            ast::BinaryOp::kLogicalAnd,
+            create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("a"), Expr("c")),
+            create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("b"), Expr("d"))),
+    };
 
     auto* expr = CallStmt(Call("foo", params));
     WrapInFunction(expr);
diff --git a/src/tint/writer/glsl/generator_impl_builtin_test.cc b/src/tint/writer/glsl/generator_impl_builtin_test.cc
index ad75046..b8161e2 100644
--- a/src/tint/writer/glsl/generator_impl_builtin_test.cc
+++ b/src/tint/writer/glsl/generator_impl_builtin_test.cc
@@ -214,8 +214,11 @@
 
     auto* call = GenerateCall(param.builtin, param.type, this);
     ASSERT_NE(nullptr, call) << "Unhandled builtin";
-    Func("func", {}, ty.void_(), {CallStmt(call)},
-         {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(call),
+         },
+         utils::Vector{create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
 
     GeneratorImpl& gen = Build();
 
@@ -409,7 +412,7 @@
     EXPECT_EQ(out.str(), "((a) * (b) + (c))");
 }
 
-TEST_F(GlslGeneratorImplTest_Builtin, Modf_Scalar) {
+TEST_F(GlslGeneratorImplTest_Builtin, Modf_Scalar_f32) {
     auto* call = Call("modf", 1_f);
     WrapInFunction(CallStmt(call));
 
@@ -442,7 +445,43 @@
 )");
 }
 
-TEST_F(GlslGeneratorImplTest_Builtin, Modf_Vector) {
+TEST_F(GlslGeneratorImplTest_Builtin, Modf_Scalar_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("modf", 1_h);
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+
+struct modf_result_f16 {
+  float16_t fract;
+  float16_t whole;
+};
+
+modf_result_f16 tint_modf(float16_t param_0) {
+  modf_result_f16 result;
+  result.fract = modf(param_0, result.whole);
+  return result;
+}
+
+
+void test_function() {
+  tint_modf(1.0hf);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Modf_Vector_f32) {
     auto* call = Call("modf", vec3<f32>());
     WrapInFunction(CallStmt(call));
 
@@ -475,7 +514,43 @@
 )");
 }
 
-TEST_F(GlslGeneratorImplTest_Builtin, Frexp_Scalar_i32) {
+TEST_F(GlslGeneratorImplTest_Builtin, Modf_Vector_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("modf", vec3<f16>());
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+
+struct modf_result_vec3_f16 {
+  f16vec3 fract;
+  f16vec3 whole;
+};
+
+modf_result_vec3_f16 tint_modf(f16vec3 param_0) {
+  modf_result_vec3_f16 result;
+  result.fract = modf(param_0, result.whole);
+  return result;
+}
+
+
+void test_function() {
+  tint_modf(f16vec3(0.0hf));
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+}
+)");
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Frexp_Scalar_f32) {
     auto* call = Call("frexp", 1_f);
     WrapInFunction(CallStmt(call));
 
@@ -502,7 +577,42 @@
 )"));
 }
 
-TEST_F(GlslGeneratorImplTest_Builtin, Frexp_Vector_i32) {
+TEST_F(GlslGeneratorImplTest_Builtin, Frexp_Scalar_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("frexp", 1_h);
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_THAT(gen.result(), HasSubstr(R"(#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+
+struct frexp_result_f16 {
+  float16_t sig;
+  int exp;
+};
+
+frexp_result_f16 tint_frexp(float16_t param_0) {
+  frexp_result_f16 result;
+  result.sig = frexp(param_0, result.exp);
+  return result;
+}
+
+
+void test_function() {
+  tint_frexp(1.0hf);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+)"));
+}
+
+TEST_F(GlslGeneratorImplTest_Builtin, Frexp_Vector_f32) {
     auto* call = Call("frexp", vec3<f32>());
     WrapInFunction(CallStmt(call));
 
@@ -534,6 +644,42 @@
 )"));
 }
 
+TEST_F(GlslGeneratorImplTest_Builtin, Frexp_Vector_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("frexp", vec3<f16>());
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_THAT(gen.result(), HasSubstr(R"(#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+
+struct frexp_result_vec3_f16 {
+  f16vec3 sig;
+  ivec3 exp;
+};
+
+frexp_result_vec3_f16 tint_frexp(f16vec3 param_0) {
+  frexp_result_vec3_f16 result;
+  result.sig = frexp(param_0, result.exp);
+  return result;
+}
+
+
+void test_function() {
+  tint_frexp(f16vec3(0.0hf));
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  test_function();
+  return;
+}
+)"));
+}
+
 TEST_F(GlslGeneratorImplTest_Builtin, Degrees_Scalar_f32) {
     auto* val = Var("val", ty.f32());
     auto* call = Call("degrees", val);
@@ -1019,8 +1165,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Builtin, StorageBarrier) {
-    Func("main", {}, ty.void_(), {CallStmt(Call("storageBarrier"))},
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("storageBarrier")),
+         },
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
@@ -1039,8 +1188,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Builtin, WorkgroupBarrier) {
-    Func("main", {}, ty.void_(), {CallStmt(Call("workgroupBarrier"))},
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("workgroupBarrier")),
+         },
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
diff --git a/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc b/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc
index 42db16b..8a22798 100644
--- a/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc
+++ b/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc
@@ -278,7 +278,8 @@
     auto* call = Call(param.function, param.args(this));
     auto* stmt = CallStmt(call);
 
-    Func("main", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.void_(), utils::Vector{stmt},
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
diff --git a/src/tint/writer/glsl/generator_impl_call_test.cc b/src/tint/writer/glsl/generator_impl_call_test.cc
index d1866ea..d264896 100644
--- a/src/tint/writer/glsl/generator_impl_call_test.cc
+++ b/src/tint/writer/glsl/generator_impl_call_test.cc
@@ -23,7 +23,7 @@
 using GlslGeneratorImplTest_Call = TestHelper;
 
 TEST_F(GlslGeneratorImplTest_Call, EmitExpression_Call_WithoutParams) {
-    Func("my_func", {}, ty.f32(), {Return(1.23_f)});
+    Func("my_func", utils::Empty, ty.f32(), utils::Vector{Return(1.23_f)});
 
     auto* call = Call("my_func");
     WrapInFunction(call);
@@ -37,11 +37,11 @@
 
 TEST_F(GlslGeneratorImplTest_Call, EmitExpression_Call_WithParams) {
     Func("my_func",
-         {
+         utils::Vector{
              Param(Sym(), ty.f32()),
              Param(Sym(), ty.f32()),
          },
-         ty.f32(), {Return(1.23_f)});
+         ty.f32(), utils::Vector{Return(1.23_f)});
     GlobalVar("param1", ty.f32(), ast::StorageClass::kPrivate);
     GlobalVar("param2", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -57,11 +57,11 @@
 
 TEST_F(GlslGeneratorImplTest_Call, EmitStatement_Call) {
     Func("my_func",
-         {
+         utils::Vector{
              Param(Sym(), ty.f32()),
              Param(Sym(), ty.f32()),
          },
-         ty.void_(), ast::StatementList{}, ast::AttributeList{});
+         ty.void_(), utils::Empty, utils::Empty);
     GlobalVar("param1", ty.f32(), ast::StorageClass::kPrivate);
     GlobalVar("param2", ty.f32(), ast::StorageClass::kPrivate);
 
diff --git a/src/tint/writer/glsl/generator_impl_case_test.cc b/src/tint/writer/glsl/generator_impl_case_test.cc
index 02d1665..1f60781 100644
--- a/src/tint/writer/glsl/generator_impl_case_test.cc
+++ b/src/tint/writer/glsl/generator_impl_case_test.cc
@@ -69,7 +69,13 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Case, Emit_Case_MultipleSelectors) {
-    auto* s = Switch(1_i, Case({Expr(5_i), Expr(6_i)}, Block(create<ast::BreakStatement>())),
+    auto* s = Switch(1_i,
+                     Case(
+                         utils::Vector{
+                             Expr(5_i),
+                             Expr(6_i),
+                         },
+                         Block(create<ast::BreakStatement>())),
                      DefaultCase());
     WrapInFunction(s);
 
diff --git a/src/tint/writer/glsl/generator_impl_constructor_test.cc b/src/tint/writer/glsl/generator_impl_constructor_test.cc
index 20d6b07..5b65c2b 100644
--- a/src/tint/writer/glsl/generator_impl_constructor_test.cc
+++ b/src/tint/writer/glsl/generator_impl_constructor_test.cc
@@ -396,7 +396,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct) {
-    auto* str = Structure("S", {
+    auto* str = Structure("S", utils::Vector{
                                    Member("a", ty.i32()),
                                    Member("b", ty.f32()),
                                    Member("c", ty.vec3<i32>()),
@@ -411,7 +411,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct_Empty) {
-    auto* str = Structure("S", {
+    auto* str = Structure("S", utils::Vector{
                                    Member("a", ty.i32()),
                                    Member("b", ty.f32()),
                                    Member("c", ty.vec3<i32>()),
diff --git a/src/tint/writer/glsl/generator_impl_function_test.cc b/src/tint/writer/glsl/generator_impl_function_test.cc
index d1f7a5e..cc01e5d 100644
--- a/src/tint/writer/glsl/generator_impl_function_test.cc
+++ b/src/tint/writer/glsl/generator_impl_function_test.cc
@@ -28,8 +28,8 @@
 using GlslGeneratorImplTest_Function = TestHelper;
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Function) {
-    Func("my_func", {}, ty.void_(),
-         {
+    Func("my_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(),
          });
 
@@ -48,8 +48,8 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Function_Name_Collision) {
-    Func("centroid", {}, ty.void_(),
-         {
+    Func("centroid", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(),
          });
 
@@ -65,12 +65,12 @@
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Function_WithParams) {
     Func("my_func",
-         {
+         utils::Vector{
              Param("a", ty.f32()),
              Param("b", ty.i32()),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          });
 
@@ -89,8 +89,8 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_NoReturn_Void) {
-    Func("func", {}, ty.void_(), {/* no explicit return */},
-         {
+    Func("func", utils::Empty, ty.void_(), utils::Empty /* no explicit return */,
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -110,8 +110,8 @@
     // fn f(foo : ptr<function, f32>) -> f32 {
     //   return *foo;
     // }
-    Func("f", {Param("foo", ty.pointer<f32>(ast::StorageClass::kFunction))}, ty.f32(),
-         {Return(Deref("foo"))});
+    Func("f", utils::Vector{Param("foo", ty.pointer<f32>(ast::StorageClass::kFunction))}, ty.f32(),
+         utils::Vector{Return(Deref("foo"))});
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -127,17 +127,17 @@
     //   return foo;
     // }
     Func("frag_main",
-         {
-             Param("foo", ty.f32(), {Location(0)}),
+         utils::Vector{
+             Param("foo", ty.f32(), utils::Vector{Location(0)}),
          },
          ty.f32(),
-         {
+         utils::Vector{
              Return("foo"),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(1),
          });
 
@@ -165,19 +165,20 @@
     // fn frag_main(@position(0) coord : vec4<f32>) -> @frag_depth f32 {
     //   return coord.x;
     // }
-    auto* coord_in = Param("coord", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
+    auto* coord_in =
+        Param("coord", ty.vec4<f32>(), utils::Vector{Builtin(ast::BuiltinValue::kPosition)});
     Func("frag_main",
-         {
+         utils::Vector{
              coord_in,
          },
          ty.f32(),
-         {
+         utils::Vector{
              Return(MemberAccessor("coord", "x")),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Builtin(ast::BuiltinValue::kFragDepth),
          });
 
@@ -214,24 +215,25 @@
     //   const p = inputs.pos;
     // }
     auto* interface_struct = Structure(
-        "Interface", {
-                         Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
-                         Member("col1", ty.f32(), {Location(1)}),
-                         Member("col2", ty.f32(), {Location(2)}),
-                     });
+        "Interface",
+        utils::Vector{
+            Member("pos", ty.vec4<f32>(), utils::Vector{Builtin(ast::BuiltinValue::kPosition)}),
+            Member("col1", ty.f32(), utils::Vector{Location(1)}),
+            Member("col2", ty.f32(), utils::Vector{Location(2)}),
+        });
 
-    Func("vert_main", {}, ty.Of(interface_struct),
-         {Return(Construct(ty.Of(interface_struct), Construct(ty.vec4<f32>()), Expr(0.5_f),
-                           Expr(0.25_f)))},
-         {Stage(ast::PipelineStage::kVertex)});
+    Func("vert_main", utils::Empty, ty.Of(interface_struct),
+         utils::Vector{Return(Construct(ty.Of(interface_struct), Construct(ty.vec4<f32>()),
+                                        Expr(0.5_f), Expr(0.25_f)))},
+         utils::Vector{Stage(ast::PipelineStage::kVertex)});
 
-    Func("frag_main", {Param("inputs", ty.Of(interface_struct))}, ty.void_(),
-         {
+    Func("frag_main", utils::Vector{Param("inputs", ty.Of(interface_struct))}, ty.void_(),
+         utils::Vector{
              Decl(Let("r", ty.f32(), MemberAccessor("inputs", "col1"))),
              Decl(Let("g", ty.f32(), MemberAccessor("inputs", "col2"))),
              Decl(Let("p", ty.vec4<f32>(), MemberAccessor("inputs", "pos"))),
          },
-         {Stage(ast::PipelineStage::kFragment)});
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -297,17 +299,17 @@
       "VertexOutput",
       {Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)})});
 
-  Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
+  Func("foo", utils::Vector{Param("x", ty.f32())}, ty.Of(vertex_output_struct),
        {Return(Construct(ty.Of(vertex_output_struct),
                          Construct(ty.vec4<f32>(), "x", "x", "x", Expr(1_f))))},
        {});
 
-  Func("vert_main1", {}, ty.Of(vertex_output_struct),
+  Func("vert_main1", utils::Empty, ty.Of(vertex_output_struct),
        {Return(Construct(ty.Of(vertex_output_struct),
                          Expr(Call("foo", Expr(0.5_f)))))},
        {Stage(ast::PipelineStage::kVertex)});
 
-  Func("vert_main2", {}, ty.Of(vertex_output_struct),
+  Func("vert_main2", utils::Empty, ty.Of(vertex_output_struct),
        {Return(Construct(ty.Of(vertex_output_struct),
                          Expr(Call("foo", Expr(0.25_f)))))},
        {Stage(ast::PipelineStage::kVertex)});
@@ -348,30 +350,30 @@
 #endif
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_Uniform) {
-    auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())});
+    auto* ubo_ty = Structure("UBO", utils::Vector{Member("coord", ty.vec4<f32>())});
     auto* ubo = GlobalVar("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform,
-                          ast::AttributeList{
+                          utils::Vector{
                               create<ast::BindingAttribute>(0u),
                               create<ast::GroupAttribute>(1u),
                           });
 
     Func("sub_func",
-         {
+         utils::Vector{
              Param("param", ty.f32()),
          },
          ty.f32(),
-         {
+         utils::Vector{
              Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")),
          });
 
     auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -401,10 +403,10 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_UniformStruct) {
-    auto* s = Structure("Uniforms", {Member("coord", ty.vec4<f32>())});
+    auto* s = Structure("Uniforms", utils::Vector{Member("coord", ty.vec4<f32>())});
 
     GlobalVar("uniforms", ty.Of(s), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
@@ -412,12 +414,12 @@
     auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
                     MemberAccessor(MemberAccessor("uniforms", "coord"), "x"));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -443,25 +445,25 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_RW_StorageBuffer_Read) {
-    auto* s = Structure("Data", {
+    auto* s = Structure("Data", utils::Vector{
                                     Member("a", ty.i32()),
                                     Member("b", ty.f32()),
                                 });
 
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
 
     auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("coord", "b"));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -493,25 +495,25 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_RO_StorageBuffer_Read) {
-    auto* s = Structure("Data", {
+    auto* s = Structure("Data", utils::Vector{
                                     Member("a", ty.i32()),
                                     Member("b", ty.f32()),
                                 });
 
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
 
     auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("coord", "b"));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -544,23 +546,23 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_WO_StorageBuffer_Store) {
-    auto* s = Structure("Data", {
+    auto* s = Structure("Data", utils::Vector{
                                     Member("a", ty.i32()),
                                     Member("b", ty.f32()),
                                 });
 
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Assign(MemberAccessor("coord", "b"), Expr(2_f)),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -592,23 +594,23 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_StorageBuffer_Store) {
-    auto* s = Structure("Data", {
+    auto* s = Structure("Data", utils::Vector{
                                     Member("a", ty.i32()),
                                     Member("b", ty.f32()),
                                 });
 
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Assign(MemberAccessor("coord", "b"), Expr(2_f)),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -640,26 +642,26 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_Called_By_EntryPoint_With_Uniform) {
-    auto* s = Structure("S", {Member("x", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("x", ty.f32())});
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
 
-    Func("sub_func", {Param("param", ty.f32())}, ty.f32(),
-         {
+    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));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -689,26 +691,26 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_Called_By_EntryPoint_With_StorageBuffer) {
-    auto* s = Structure("S", {Member("x", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("x", ty.f32())});
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
 
-    Func("sub_func", {Param("param", ty.f32())}, ty.f32(),
-         {
+    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));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -743,8 +745,8 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_WithNameCollision) {
-    Func("centroid", {}, ty.void_(), {},
-         {
+    Func("centroid", utils::Empty, ty.void_(), {},
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -765,11 +767,11 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_Compute) {
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
@@ -787,8 +789,8 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Literal) {
-    Func("main", {}, ty.void_(), {},
-         {
+    Func("main", utils::Empty, ty.void_(), {},
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(2_i, 4_i, 6_i),
          });
@@ -809,8 +811,8 @@
     GlobalConst("width", ty.i32(), Construct(ty.i32(), 2_i));
     GlobalConst("height", ty.i32(), Construct(ty.i32(), 3_i));
     GlobalConst("depth", ty.i32(), Construct(ty.i32(), 4_i));
-    Func("main", {}, ty.void_(), {},
-         {
+    Func("main", utils::Empty, ty.void_(), {},
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize("width", "height", "depth"),
          });
@@ -829,11 +831,11 @@
 
 TEST_F(GlslGeneratorImplTest_Function,
        Emit_Attribute_EntryPoint_Compute_WithWorkgroup_OverridableConst) {
-    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", {}, ty.void_(), {},
-         {
+    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)});
+    Func("main", utils::Empty, ty.void_(), {},
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize("width", "height", "depth"),
          });
@@ -863,8 +865,8 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Function_WithArrayParams) {
-    Func("my_func", {Param("a", ty.array<f32, 5>())}, ty.void_(),
-         {
+    Func("my_func", utils::Vector{Param("a", ty.array<f32, 5>())}, ty.void_(),
+         utils::Vector{
              Return(),
          });
 
@@ -881,8 +883,8 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Function, Emit_Function_WithArrayReturn) {
-    Func("my_func", {}, ty.array<f32, 5>(),
-         {
+    Func("my_func", utils::Empty, ty.array<f32, 5>(),
+         utils::Vector{
              Return(Construct(ty.array<f32, 5>())),
          });
 
@@ -917,10 +919,10 @@
     //   return;
     // }
 
-    auto* s = Structure("Data", {Member("d", ty.f32())});
+    auto* s = Structure("Data", utils::Vector{Member("d", ty.f32())});
 
     GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -928,12 +930,12 @@
     {
         auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
 
-        Func("a", {}, ty.void_(),
-             {
+        Func("a", utils::Empty, ty.void_(),
+             utils::Vector{
                  Decl(var),
                  Return(),
              },
-             {
+             utils::Vector{
                  Stage(ast::PipelineStage::kCompute),
                  WorkgroupSize(1_i),
              });
@@ -942,12 +944,12 @@
     {
         auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
 
-        Func("b", {}, ty.void_(),
-             {
+        Func("b", utils::Empty, ty.void_(),
+             utils::Vector{
                  Decl(var),
                  Return(),
              },
-             {
+             utils::Vector{
                  Stage(ast::PipelineStage::kCompute),
                  WorkgroupSize(1_i),
              });
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 875d211..ba6dc43 100644
--- a/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
+++ b/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
@@ -86,22 +86,19 @@
 template <typename BASE>
 class GlslGeneratorImplTest_MemberAccessorBase : public BASE {
   public:
-    void SetupStorageBuffer(ast::StructMemberList members) {
+    void SetupStorageBuffer(utils::VectorRef<const ast::StructMember*> members) {
         ProgramBuilder& b = *this;
 
         auto* s = b.Structure("Data", members);
 
         b.GlobalVar("data", b.ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-                    ast::AttributeList{
-                        b.create<ast::BindingAttribute>(0u),
-                        b.create<ast::GroupAttribute>(1u),
-                    });
+                    b.GroupAndBinding(1u, 0u));
     }
 
-    void SetupFunction(ast::StatementList statements) {
+    void SetupFunction(utils::VectorRef<const ast::Statement*> statements) {
         ProgramBuilder& b = *this;
-        b.Func("main", {}, b.ty.void_(), statements,
-               {
+        b.Func("main", utils::Empty, b.ty.void_(), statements,
+               utils::Vector<const ast::Attribute*, 1>{
                    b.Stage(ast::PipelineStage::kFragment),
                });
     }
@@ -114,7 +111,7 @@
     GlslGeneratorImplTest_MemberAccessorBase<TestParamHelper<T>>;
 
 TEST_F(GlslGeneratorImplTest_MemberAccessor, EmitExpression_MemberAccessor) {
-    auto* s = Structure("Data", {Member("mem", ty.f32())});
+    auto* s = Structure("Data", utils::Vector{Member("mem", ty.f32())});
     GlobalVar("str", ty.Of(s), ast::StorageClass::kPrivate);
 
     auto* expr = MemberAccessor("str", "mem");
@@ -165,12 +162,12 @@
 
     auto p = GetParam();
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("a", ty.i32()),
         Member("b", p.member_type(ty)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone, MemberAccessor("data", "b"))),
     });
 
@@ -216,12 +213,12 @@
 
     auto p = GetParam();
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("a", ty.i32()),
         Member("b", p.member_type(ty)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("value", p.member_type(ty), ast::StorageClass::kNone,
                  Construct(p.member_type(ty)))),
         Assign(MemberAccessor("data", "b"), Expr("value")),
@@ -265,13 +262,13 @@
     // var<storage> data : Data;
     // data.a = mat2x3<f32>();
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("a", ty.i32()),
         Member("b", ty.mat2x3<f32>()),
     });
 
-    SetupFunction({
-        Assign(MemberAccessor("data", "b"), Construct(ty.mat2x3<f32>(), ast::ExpressionList{})),
+    SetupFunction(utils::Vector{
+        Assign(MemberAccessor("data", "b"), Construct(ty.mat2x3<f32>())),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -310,12 +307,12 @@
     // var<storage> data : Data;
     // data.a[2i][1i];
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("z", ty.f32()),
         Member("a", ty.mat4x3<f32>()),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone,
                  IndexAccessor(IndexAccessor(MemberAccessor("data", "a"), 2_i), 1_i))),
     });
@@ -356,12 +353,12 @@
     // var<storage> data : Data;
     // data.a[2];
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("z", ty.f32()),
         Member("a", ty.array<i32, 5>(4)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone,
                  IndexAccessor(MemberAccessor("data", "a"), 2_i))),
     });
@@ -402,14 +399,17 @@
     // var<storage> data : Data;
     // data.a[(2i + 4i) - 3i];
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("z", ty.f32()),
         Member("a", ty.array<i32, 5>(4)),
     });
 
-    SetupFunction({
+    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(2_i, 4_i), 3_i)))),
+                 IndexAccessor(MemberAccessor("data", "a"), Sub(Add("a", "b"), "c")))),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -429,7 +429,10 @@
   int a[5];
 } data;
 void tint_symbol() {
-  int x = data.a[((2 + 4) - 3)];
+  int a = 2;
+  int b = 4;
+  int c = 3;
+  int x = data.a[((a + b) - c)];
 }
 
 void main() {
@@ -447,12 +450,12 @@
     // var<storage> data : Data;
     // data.a[2i] = 2i;
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("z", ty.f32()),
         Member("a", ty.array<i32, 5>(4)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Assign(IndexAccessor(MemberAccessor("data", "a"), 2_i), 2_i),
     });
 
@@ -496,16 +499,16 @@
     // var<storage> data : Pre;
     // data.c[2i].b
 
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.vec3<f32>()),
                                          Member("b", ty.vec3<f32>()),
                                      });
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("c", ty.array(ty.Of(inner), 4_u, 32)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone,
                  MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"))),
     });
@@ -553,16 +556,16 @@
     // var<storage> data : Pre;
     // data.c[2i].b.xy
 
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.vec3<f32>()),
                                          Member("b", ty.vec3<f32>()),
                                      });
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("c", ty.array(ty.Of(inner), 4_u, 32)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone,
                  MemberAccessor(
                      MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), "xy"))),
@@ -612,16 +615,16 @@
     // var<storage> data : Pre;
     // data.c[2i].b.g
 
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.vec3<f32>()),
                                          Member("b", ty.vec3<f32>()),
                                      });
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("c", ty.array(ty.Of(inner), 4_u, 32)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone,
                  MemberAccessor(
                      MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), "g"))),
@@ -670,16 +673,16 @@
     // var<storage> data : Pre;
     // data.c[2i].b[1i]
 
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.vec3<f32>()),
                                          Member("b", ty.vec3<f32>()),
                                      });
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("c", ty.array(ty.Of(inner), 4_u, 32)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone,
                  IndexAccessor(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"),
                                1_i))),
@@ -728,16 +731,16 @@
     // var<storage> data : Pre;
     // data.c[2i].b = vec3<f32>(1.f, 2.f, 3.f);
 
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.vec3<f32>()),
                                          Member("b", ty.vec3<f32>()),
                                      });
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("c", ty.array(ty.Of(inner), 4_u, 32)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Assign(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"),
                vec3<f32>(1_f, 2_f, 3_f)),
     });
@@ -785,16 +788,16 @@
     // var<storage> data : Pre;
     // data.c[2i].b.y = 1.f;
 
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.vec3<i32>()),
                                          Member("b", ty.vec3<f32>()),
                                      });
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("c", ty.array(ty.Of(inner), 4_u, 32)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Assign(MemberAccessor(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"),
                               "y"),
                Expr(1_f)),
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 fa269da..44cbb53 100644
--- a/src/tint/writer/glsl/generator_impl_module_constant_test.cc
+++ b/src/tint/writer/glsl/generator_impl_module_constant_test.cc
@@ -34,7 +34,10 @@
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_AInt) {
     auto* var = GlobalConst("G", nullptr, Expr(1_a));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -51,7 +54,10 @@
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_AFloat) {
     auto* var = GlobalConst("G", nullptr, Expr(1._a));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -68,7 +74,10 @@
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_i32) {
     auto* var = GlobalConst("G", nullptr, Expr(1_i));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -85,7 +94,10 @@
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_u32) {
     auto* var = GlobalConst("G", nullptr, Expr(1_u));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -102,7 +114,10 @@
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_f32) {
     auto* var = GlobalConst("G", nullptr, Expr(1_f));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -121,7 +136,10 @@
     Enable(ast::Extension::kF16);
 
     auto* var = GlobalConst("G", nullptr, Expr(1_h));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -139,7 +157,10 @@
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_AInt) {
     auto* var = GlobalConst("G", nullptr, Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -156,7 +177,10 @@
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_AFloat) {
     auto* var = GlobalConst("G", nullptr, Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -173,7 +197,10 @@
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_f32) {
     auto* var = GlobalConst("G", nullptr, vec3<f32>(1_f, 2_f, 3_f));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -192,7 +219,10 @@
     Enable(ast::Extension::kF16);
 
     auto* var = GlobalConst("G", nullptr, vec3<f16>(1_h, 2_h, 3_h));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -211,7 +241,10 @@
 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));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -228,7 +261,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));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -247,7 +283,10 @@
     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", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -265,7 +304,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));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -286,7 +328,10 @@
                                       vec2<bool>(true, false),         //
                                       vec2<bool>(false, true),         //
                                       vec2<bool>(true, true)));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("l", nullptr, Expr(var))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -303,7 +348,7 @@
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_Override) {
     auto* var = Override("pos", ty.f32(), Expr(3_f),
-                         ast::AttributeList{
+                         utils::Vector{
                              Id(23),
                          });
 
@@ -319,7 +364,7 @@
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_Override_NoConstructor) {
     auto* var = Override("pos", ty.f32(), nullptr,
-                         ast::AttributeList{
+                         utils::Vector{
                              Id(23),
                          });
 
@@ -335,7 +380,7 @@
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_Override_NoId) {
     auto* a = Override("a", ty.f32(), Expr(3_f),
-                       ast::AttributeList{
+                       utils::Vector{
                            Id(0),
                        });
     auto* b = Override("b", ty.f32(), Expr(2_f));
diff --git a/src/tint/writer/glsl/generator_impl_return_test.cc b/src/tint/writer/glsl/generator_impl_return_test.cc
index 59df385..28bbf51 100644
--- a/src/tint/writer/glsl/generator_impl_return_test.cc
+++ b/src/tint/writer/glsl/generator_impl_return_test.cc
@@ -35,7 +35,7 @@
 
 TEST_F(GlslGeneratorImplTest_Return, Emit_ReturnWithValue) {
     auto* r = Return(123_i);
-    Func("f", {}, ty.i32(), {r});
+    Func("f", utils::Empty, ty.i32(), utils::Vector{r});
 
     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 ae1b0ba..c2a4ff2 100644
--- a/src/tint/writer/glsl/generator_impl_sanitizer_test.cc
+++ b/src/tint/writer/glsl/generator_impl_sanitizer_test.cc
@@ -25,19 +25,19 @@
 using GlslSanitizerTest = TestHelper;
 
 TEST_F(GlslSanitizerTest, Call_ArrayLength) {
-    auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
                       Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -65,22 +65,22 @@
 }
 
 TEST_F(GlslSanitizerTest, Call_ArrayLength_OtherMembersInStruct) {
-    auto* s = Structure("my_struct", {
+    auto* s = Structure("my_struct", utils::Vector{
                                          Member(0, "z", ty.f32()),
                                          Member(4, "a", ty.array<f32>(4)),
                                      });
     GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
                       Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -110,9 +110,9 @@
 }
 
 TEST_F(GlslSanitizerTest, Call_ArrayLength_ViaLets) {
-    auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
@@ -120,13 +120,13 @@
     auto* p = Let("p", nullptr, AddressOf("b"));
     auto* p2 = Let("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
 
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(p),
              Decl(p2),
              Decl(Var("len", ty.u32(), ast::StorageClass::kNone, Call("arrayLength", p2))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -157,12 +157,12 @@
 TEST_F(GlslSanitizerTest, PromoteArrayInitializerToConstVar) {
     auto* array_init = array<i32, 4>(1_i, 2_i, 3_i, 4_i);
 
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("idx", nullptr, Expr(3_i))),
              Decl(Var("pos", ty.i32(), IndexAccessor(array_init, "idx"))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -189,7 +189,7 @@
 }
 
 TEST_F(GlslSanitizerTest, PromoteStructInitializerToConstVar) {
-    auto* str = Structure("S", {
+    auto* str = Structure("S", utils::Vector{
                                    Member("a", ty.i32()),
                                    Member("b", ty.vec3<f32>()),
                                    Member("c", ty.i32()),
@@ -198,11 +198,11 @@
     auto* struct_access = MemberAccessor(struct_init, "b");
     auto* pos = Var("pos", ty.vec3<f32>(), ast::StorageClass::kNone, struct_access);
 
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(pos),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -241,13 +241,13 @@
     auto* p = Let("p", ty.pointer<i32>(ast::StorageClass::kFunction), AddressOf(v));
     auto* x = Var("x", ty.i32(), ast::StorageClass::kNone, Deref(p));
 
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(v),
              Decl(p),
              Decl(x),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -287,15 +287,15 @@
                    AddressOf(IndexAccessor(Deref(mp), 2_i)));
     auto* v = Var("v", ty.vec4<f32>(), ast::StorageClass::kNone, Deref(vp));
 
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(a),
              Decl(ap),
              Decl(mp),
              Decl(vp),
              Decl(v),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
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 7a13b66..0323d74 100644
--- a/src/tint/writer/glsl/generator_impl_storage_buffer_test.cc
+++ b/src/tint/writer/glsl/generator_impl_storage_buffer_test.cc
@@ -29,14 +29,14 @@
     //   @align(256) louie : f32;
     // };
     // @group(0) @binding(0) var<storage, read_write> nephews : Nephews;
-    auto* nephews =
-        ctx->Structure("Nephews", {
-                                      ctx->Member("huey", ctx->ty.f32(), {ctx->MemberAlign(256)}),
-                                      ctx->Member("dewey", ctx->ty.f32(), {ctx->MemberAlign(256)}),
-                                      ctx->Member("louie", ctx->ty.f32(), {ctx->MemberAlign(256)}),
-                                  });
+    auto* nephews = ctx->Structure(
+        "Nephews", utils::Vector{
+                       ctx->Member("huey", ctx->ty.f32(), utils::Vector{ctx->MemberAlign(256)}),
+                       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,
-                   ast::AttributeList{
+                   utils::Vector{
                        ctx->create<ast::BindingAttribute>(0u),
                        ctx->create<ast::GroupAttribute>(0u),
                    });
diff --git a/src/tint/writer/glsl/generator_impl_switch_test.cc b/src/tint/writer/glsl/generator_impl_switch_test.cc
index c8957be..c2db3b5 100644
--- a/src/tint/writer/glsl/generator_impl_switch_test.cc
+++ b/src/tint/writer/glsl/generator_impl_switch_test.cc
@@ -25,18 +25,18 @@
     GlobalVar("cond", ty.i32(), ast::StorageClass::kPrivate);
 
     auto* def_body = Block(create<ast::BreakStatement>());
-    auto* def = create<ast::CaseStatement>(ast::CaseSelectorList{}, def_body);
+    auto* def = create<ast::CaseStatement>(utils::Empty, def_body);
 
-    ast::CaseSelectorList case_val;
-    case_val.push_back(Expr(5_i));
+    utils::Vector case_val{Expr(5_i)};
 
     auto* case_body = Block(create<ast::BreakStatement>());
 
     auto* case_stmt = create<ast::CaseStatement>(case_val, case_body);
 
-    ast::CaseStatementList body;
-    body.push_back(case_stmt);
-    body.push_back(def);
+    utils::Vector body{
+        case_stmt,
+        def,
+    };
 
     auto* cond = Expr("cond");
     auto* s = create<ast::SwitchStatement>(cond, body);
diff --git a/src/tint/writer/glsl/generator_impl_test.cc b/src/tint/writer/glsl/generator_impl_test.cc
index f45d0d0..919d684 100644
--- a/src/tint/writer/glsl/generator_impl_test.cc
+++ b/src/tint/writer/glsl/generator_impl_test.cc
@@ -29,7 +29,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest, Generate) {
-    Func("my_func", {}, ty.void_(), {});
+    Func("my_func", utils::Empty, ty.void_(), utils::Empty);
 
     GeneratorImpl& gen = Build();
 
@@ -43,7 +43,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest, GenerateDesktop) {
-    Func("my_func", {}, ty.void_(), {});
+    Func("my_func", utils::Empty, ty.void_(), utils::Empty);
 
     GeneratorImpl& gen = Build(Version(Version::Standard::kDesktop, 4, 4));
 
@@ -58,13 +58,13 @@
 
 TEST_F(GlslGeneratorImplTest, GenerateSampleIndexES) {
     GlobalVar("gl_SampleID", ty.i32(),
-              ast::AttributeList{
+              utils::Vector{
                   Builtin(ast::BuiltinValue::kSampleIndex),
                   Disable(ast::DisabledValidation::kIgnoreStorageClass),
               },
               ast::StorageClass::kIn);
-    Func("my_func", {}, ty.i32(),
-         {
+    Func("my_func", utils::Empty, ty.i32(),
+         utils::Vector{
              Return(Expr("gl_SampleID")),
          });
 
@@ -83,13 +83,13 @@
 
 TEST_F(GlslGeneratorImplTest, GenerateSampleIndexDesktop) {
     GlobalVar("gl_SampleID", ty.i32(),
-              ast::AttributeList{
+              utils::Vector{
                   Builtin(ast::BuiltinValue::kSampleIndex),
                   Disable(ast::DisabledValidation::kIgnoreStorageClass),
               },
               ast::StorageClass::kIn);
-    Func("my_func", {}, ty.i32(),
-         {
+    Func("my_func", utils::Empty, ty.i32(),
+         utils::Vector{
              Return(Expr("gl_SampleID")),
          });
 
diff --git a/src/tint/writer/glsl/generator_impl_type_test.cc b/src/tint/writer/glsl/generator_impl_type_test.cc
index 1c4cb03..aa2afab 100644
--- a/src/tint/writer/glsl/generator_impl_type_test.cc
+++ b/src/tint/writer/glsl/generator_impl_type_test.cc
@@ -158,7 +158,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Type, EmitType_StructDecl) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", ty.f32()),
                              });
@@ -178,7 +178,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Type, EmitType_Struct) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", ty.f32()),
                              });
@@ -194,7 +194,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Type, EmitType_Struct_NameCollision) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("double", ty.i32()),
                                  Member("float", ty.f32()),
                              });
@@ -211,9 +211,9 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Type, EmitType_Struct_WithOffsetAttributes) {
-    auto* s = Structure("S", {
-                                 Member("a", ty.i32(), {MemberOffset(0)}),
-                                 Member("b", ty.f32(), {MemberOffset(8)}),
+    auto* s = Structure("S", utils::Vector{
+                                 Member("a", ty.i32(), utils::Vector{MemberOffset(0)}),
+                                 Member("b", ty.f32(), utils::Vector{MemberOffset(8)}),
                              });
     GlobalVar("g", ty.Of(s), ast::StorageClass::kPrivate);
 
@@ -313,13 +313,18 @@
     auto* t = ty.depth_texture(params.dim);
 
     GlobalVar("tex", t,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("textureDimensions", "tex")),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -340,13 +345,18 @@
     auto* t = ty.depth_multisampled_texture(ast::TextureDimension::k2d);
 
     GlobalVar("tex", t,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("textureDimensions", "tex")),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -383,13 +393,18 @@
     auto* t = ty.sampled_texture(params.dim, datatype);
 
     GlobalVar("tex", t,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("textureDimensions", "tex")),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -517,13 +532,18 @@
     auto* t = ty.storage_texture(params.dim, params.imgfmt, ast::Access::kWrite);
 
     GlobalVar("tex", t,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("textureDimensions", "tex")),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     GeneratorImpl& gen = Build();
 
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 8e653b5..4ea5779 100644
--- a/src/tint/writer/glsl/generator_impl_uniform_buffer_test.cc
+++ b/src/tint/writer/glsl/generator_impl_uniform_buffer_test.cc
@@ -23,7 +23,7 @@
 using GlslGeneratorImplTest_UniformBuffer = TestHelper;
 
 TEST_F(GlslGeneratorImplTest_UniformBuffer, Simple) {
-    auto* simple = Structure("Simple", {Member("member", ty.f32())});
+    auto* simple = Structure("Simple", utils::Vector{Member("member", ty.f32())});
     GlobalVar("simple", ty.Of(simple), ast::StorageClass::kUniform, GroupAndBinding(0, 0));
 
     GeneratorImpl& gen = Build();
@@ -43,7 +43,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_UniformBuffer, Simple_Desktop) {
-    auto* simple = Structure("Simple", {Member("member", ty.f32())});
+    auto* simple = Structure("Simple", utils::Vector{Member("member", ty.f32())});
     GlobalVar("simple", ty.Of(simple), ast::StorageClass::kUniform, GroupAndBinding(0, 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 873afc1..1ad1c55 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
@@ -66,7 +66,11 @@
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_AInt) {
     auto* C = Const("C", nullptr, Expr(1_a));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -83,7 +87,11 @@
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_AFloat) {
     auto* C = Const("C", nullptr, Expr(1._a));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -100,7 +108,11 @@
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_i32) {
     auto* C = Const("C", nullptr, Expr(1_i));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -117,7 +129,11 @@
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_u32) {
     auto* C = Const("C", nullptr, Expr(1_u));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -134,7 +150,11 @@
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_f32) {
     auto* C = Const("C", nullptr, Expr(1_f));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -153,7 +173,11 @@
     Enable(ast::Extension::kF16);
 
     auto* C = Const("C", nullptr, Expr(1_h));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -171,7 +195,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));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -188,7 +216,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));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -205,7 +237,11 @@
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_f32) {
     auto* C = Const("C", nullptr, vec3<f32>(1_f, 2_f, 3_f));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -224,7 +260,11 @@
     Enable(ast::Extension::kF16);
 
     auto* C = Const("C", nullptr, vec3<f16>(1_h, 2_h, 3_h));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -243,7 +283,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));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -260,7 +304,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));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -279,7 +327,11 @@
     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", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -297,7 +349,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));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -318,7 +374,11 @@
                               vec2<bool>(true, false),         //
                               vec2<bool>(false, true),         //
                               vec2<bool>(true, true)));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
diff --git a/src/tint/writer/glsl/generator_impl_workgroup_var_test.cc b/src/tint/writer/glsl/generator_impl_workgroup_var_test.cc
index 1d96441..9b39fda 100644
--- a/src/tint/writer/glsl/generator_impl_workgroup_var_test.cc
+++ b/src/tint/writer/glsl/generator_impl_workgroup_var_test.cc
@@ -29,8 +29,8 @@
 TEST_F(GlslGeneratorImplTest_WorkgroupVar, Basic) {
     GlobalVar("wg", ty.f32(), ast::StorageClass::kWorkgroup);
 
-    Func("main", {}, ty.void_(), {Assign("wg", 1.2_f)},
-         {
+    Func("main", utils::Empty, ty.void_(), utils::Vector{Assign("wg", 1.2_f)},
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
@@ -45,8 +45,8 @@
 
     GlobalVar("wg", ty.Of(alias), ast::StorageClass::kWorkgroup);
 
-    Func("main", {}, ty.void_(), {Assign("wg", 1.2_f)},
-         {
+    Func("main", utils::Empty, ty.void_(), utils::Vector{Assign("wg", 1.2_f)},
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index d106804..444746c 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -72,6 +72,7 @@
 #include "src/tint/utils/defer.h"
 #include "src/tint/utils/map.h"
 #include "src/tint/utils/scoped_assignment.h"
+#include "src/tint/utils/string.h"
 #include "src/tint/writer/append_vector.h"
 #include "src/tint/writer/float_to_string.h"
 #include "src/tint/writer/generate_external_texture_bindings.h"
@@ -274,12 +275,8 @@
 
     auto* mod = builder_.Sem().Module();
     for (auto* decl : mod->DependencyOrderedDeclarations()) {
-        if (decl->Is<ast::Alias>()) {
-            continue;  // Ignore aliases.
-        }
-        if (decl->Is<ast::Enable>()) {
-            // Currently we don't have to do anything for using a extension in HLSL.
-            continue;
+        if (decl->IsAnyOf<ast::Alias, ast::Enable, ast::StaticAssert>()) {
+            continue;  // These are not emitted.
         }
 
         // Emit a new line between declarations if the type of declaration has
@@ -920,7 +917,7 @@
     return true;
 }
 
-bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) {
+bool GeneratorImpl::EmitStatements(utils::VectorRef<const ast::Statement*> stmts) {
     for (auto* s : stmts) {
         if (!EmitStatement(s)) {
             return false;
@@ -929,7 +926,7 @@
     return true;
 }
 
-bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) {
+bool GeneratorImpl::EmitStatementsWithIndent(utils::VectorRef<const ast::Statement*> stmts) {
     ScopedIndent si(this);
     return EmitStatements(stmts);
 }
@@ -1663,7 +1660,7 @@
 
         {
             ScopedParen sp(pre);
-            for (size_t i = 0; i < expr->args.size(); i++) {
+            for (size_t i = 0; i < expr->args.Length(); i++) {
                 auto* arg = expr->args[i];
                 if (i > 0) {
                     pre << ", ";
@@ -1858,16 +1855,15 @@
                 return false;
             }
 
-            line(b) << "float" << width << " whole;";
-            line(b) << "float" << width << " fract = modf(" << in << ", whole);";
             {
                 auto l = line(b);
                 if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
                               ast::Access::kUndefined, "")) {
                     return false;
                 }
-                l << " result = {fract, whole};";
+                l << " result;";
             }
+            line(b) << "result.fract = modf(" << params[0] << ", result.whole);";
             line(b) << "return result;";
             return true;
         });
@@ -1892,8 +1888,15 @@
                 return false;
             }
 
-            line(b) << "float" << width << " exp;";
-            line(b) << "float" << width << " sig = frexp(" << in << ", exp);";
+            std::string member_type;
+            if (Is<sem::F16>(sem::Type::DeepestElementOf(ty))) {
+                member_type = width.empty() ? "float16_t" : ("vector<float16_t, " + width + ">");
+            } else {
+                member_type = "float" + width;
+            }
+
+            line(b) << member_type << " exp;";
+            line(b) << member_type << " sig = frexp(" << in << ", exp);";
             {
                 auto l = line(b);
                 if (!EmitType(l, builtin->ReturnType(), ast::StorageClass::kNone,
@@ -2571,7 +2574,7 @@
                 return false;
             }
             out << ":";
-            if (selector == stmt->selectors.back()) {
+            if (selector == stmt->selectors.Back()) {
                 out << " {";
             }
         }
@@ -2686,7 +2689,7 @@
                 return false;
             }
         } else {
-            if (!EmitStatementsWithIndent({stmt->else_statement})) {
+            if (!EmitStatementsWithIndent(utils::Vector{stmt->else_statement})) {
                 return false;
             }
         }
@@ -2849,6 +2852,11 @@
                     return EmitPrivateVariable(sem);
                 case ast::StorageClass::kWorkgroup:
                     return EmitWorkgroupVariable(sem);
+                case ast::StorageClass::kPushConstant:
+                    diagnostics_.add_error(
+                        diag::System::Writer,
+                        "unhandled storage class " + utils::ToString(sem->StorageClass()));
+                    return false;
                 default: {
                     TINT_ICE(Writer, diagnostics_)
                         << "unhandled storage class " << sem->StorageClass();
@@ -2863,6 +2871,7 @@
         [&](Default) {
             TINT_ICE(Writer, diagnostics_)
                 << "unhandled global variable type " << global->TypeInfo().name;
+
             return false;
         });
 }
@@ -3639,6 +3648,9 @@
                     return false;
                 });
         },
+        [&](const ast::StaticAssert*) {
+            return true;  // Not emitted
+        },
         [&](Default) {  //
             diagnostics_.add_error(diag::System::Writer,
                                    "unknown statement type: " + std::string(stmt->TypeInfo().name));
@@ -3647,7 +3659,7 @@
 }
 
 bool GeneratorImpl::EmitDefaultOnlySwitch(const ast::SwitchStatement* stmt) {
-    TINT_ASSERT(Writer, stmt->body.size() == 1 && stmt->body[0]->IsDefault());
+    TINT_ASSERT(Writer, stmt->body.Length() == 1 && stmt->body[0]->IsDefault());
 
     // FXC fails to compile a switch with just a default case, ignoring the
     // default case body. We work around this here by emitting the default case
@@ -3680,7 +3692,7 @@
 
 bool GeneratorImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
     // BUG(crbug.com/tint/1188): work around default-only switches
-    if (stmt->body.size() == 1 && stmt->body[0]->IsDefault()) {
+    if (stmt->body.Length() == 1 && stmt->body[0]->IsDefault()) {
         return EmitDefaultOnlySwitch(stmt);
     }
 
@@ -3695,7 +3707,7 @@
 
     {
         ScopedIndent si(this);
-        for (size_t i = 0; i < stmt->body.size(); i++) {
+        for (size_t i = 0; i < stmt->body.Length(); i++) {
             if (!EmitCase(stmt, i)) {
                 return false;
             }
diff --git a/src/tint/writer/hlsl/generator_impl.h b/src/tint/writer/hlsl/generator_impl.h
index a282451..78680c8 100644
--- a/src/tint/writer/hlsl/generator_impl.h
+++ b/src/tint/writer/hlsl/generator_impl.h
@@ -112,11 +112,11 @@
     /// Emits a list of statements
     /// @param stmts the statement list
     /// @returns true if the statements were emitted successfully
-    bool EmitStatements(const ast::StatementList& stmts);
+    bool EmitStatements(utils::VectorRef<const ast::Statement*> stmts);
     /// Emits a list of statements with an indentation
     /// @param stmts the statement list
     /// @returns true if the statements were emitted successfully
-    bool EmitStatementsWithIndent(const ast::StatementList& stmts);
+    bool EmitStatementsWithIndent(utils::VectorRef<const ast::Statement*> stmts);
     /// Handles a block statement
     /// @param stmt the statement to emit
     /// @returns true if the statement was emitted successfully
diff --git a/src/tint/writer/hlsl/generator_impl_assign_test.cc b/src/tint/writer/hlsl/generator_impl_assign_test.cc
index 57e65a9..a01903e 100644
--- a/src/tint/writer/hlsl/generator_impl_assign_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_assign_test.cc
@@ -22,8 +22,8 @@
 using HlslGeneratorImplTest_Assign = TestHelper;
 
 TEST_F(HlslGeneratorImplTest_Assign, Emit_Assign) {
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("lhs", ty.i32())),
              Decl(Var("rhs", ty.i32())),
              Assign("lhs", "rhs"),
@@ -42,8 +42,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Assign, Emit_Vector_Assign_LetIndex) {
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("lhs", ty.vec3<f32>())),
              Decl(Var("rhs", ty.f32())),
              Decl(Let("index", ty.u32(), Expr(0_u))),
@@ -68,8 +68,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Assign, Emit_Vector_Assign_ConstIndex) {
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("lhs", ty.vec3<f32>())),
              Decl(Var("rhs", ty.f32())),
              Decl(Const("index", ty.u32(), Expr(0_u))),
@@ -89,8 +89,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Assign, Emit_Vector_Assign_DynamicIndex) {
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("lhs", ty.vec3<f32>())),
              Decl(Var("rhs", ty.f32())),
              Decl(Var("index", ty.u32())),
@@ -115,8 +115,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Vector_LetIndex) {
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("lhs", ty.mat4x2<f32>())),
              Decl(Var("rhs", ty.vec2<f32>())),
              Decl(Let("index", ty.u32(), Expr(0_u))),
@@ -146,8 +146,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Vector_ConstIndex) {
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("lhs", ty.mat4x2<f32>())),
              Decl(Var("rhs", ty.vec2<f32>())),
              Decl(Const("index", ty.u32(), Expr(0_u))),
@@ -167,8 +167,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Vector_DynamicIndex) {
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("lhs", ty.mat4x2<f32>())),
              Decl(Var("rhs", ty.vec2<f32>())),
              Decl(Var("index", ty.u32())),
@@ -198,8 +198,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Scalar_LetIndex) {
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("lhs", ty.mat4x2<f32>())),
              Decl(Var("rhs", ty.f32())),
              Decl(Let("index", ty.u32(), Expr(0_u))),
@@ -237,8 +237,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Scalar_ConstIndex) {
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("lhs", ty.mat4x2<f32>())),
              Decl(Var("rhs", ty.f32())),
              Decl(Const("index", ty.u32(), Expr(0_u))),
@@ -258,8 +258,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Assign, Emit_Matrix_Assign_Scalar_DynamicIndex) {
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("lhs", ty.mat4x2<f32>())),
              Decl(Var("rhs", ty.f32())),
              Decl(Var("index", ty.u32())),
diff --git a/src/tint/writer/hlsl/generator_impl_binary_test.cc b/src/tint/writer/hlsl/generator_impl_binary_test.cc
index 7836abc..d5f70fb 100644
--- a/src/tint/writer/hlsl/generator_impl_binary_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_binary_test.cc
@@ -483,7 +483,7 @@
            Block(Return(1_i)),
            Else(If(create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("b"), Expr("c")),
                    Block(Return(2_i)), Else(Block(Return(3_i))))));
-    Func("func", {}, ty.i32(), {WrapInStatement(expr)});
+    Func("func", utils::Empty, ty.i32(), utils::Vector{WrapInStatement(expr)});
 
     GeneratorImpl& gen = Build();
 
@@ -519,7 +519,7 @@
         ast::BinaryOp::kLogicalOr,
         create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")),
         Expr("c")));
-    Func("func", {}, ty.bool_(), {WrapInStatement(expr)});
+    Func("func", utils::Empty, ty.bool_(), utils::Vector{WrapInStatement(expr)});
 
     GeneratorImpl& gen = Build();
 
@@ -603,26 +603,25 @@
     // foo(a && b, c || d, (a || c) && (b || d))
 
     Func("foo",
-         {
+         utils::Vector{
              Param(Sym(), ty.bool_()),
              Param(Sym(), ty.bool_()),
              Param(Sym(), ty.bool_()),
          },
-         ty.void_(), ast::StatementList{}, ast::AttributeList{});
+         ty.void_(), utils::Empty, utils::Empty);
     GlobalVar("a", ty.bool_(), ast::StorageClass::kPrivate);
     GlobalVar("b", ty.bool_(), ast::StorageClass::kPrivate);
     GlobalVar("c", ty.bool_(), ast::StorageClass::kPrivate);
     GlobalVar("d", ty.bool_(), ast::StorageClass::kPrivate);
 
-    ast::ExpressionList params;
-    params.push_back(
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")));
-    params.push_back(
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("c"), Expr("d")));
-    params.push_back(create<ast::BinaryExpression>(
-        ast::BinaryOp::kLogicalAnd,
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("a"), Expr("c")),
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("b"), Expr("d"))));
+    utils::Vector params{
+        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")),
+        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("c"), Expr("d")),
+        create<ast::BinaryExpression>(
+            ast::BinaryOp::kLogicalAnd,
+            create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("a"), Expr("c")),
+            create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr("b"), Expr("d"))),
+    };
 
     auto* expr = CallStmt(Call("foo", params));
     WrapInFunction(expr);
@@ -676,8 +675,8 @@
                          testing::Values(Params{Params::Type::Div}, Params{Params::Type::Mod}));
 
 TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_i32) {
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("a", ty.i32())),
              Decl(Let("r", nullptr, Op("a", 0_i))),
          });
@@ -694,8 +693,8 @@
 }
 
 TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_u32) {
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("a", ty.u32())),
              Decl(Let("r", nullptr, Op("a", 0_u))),
          });
@@ -712,8 +711,8 @@
 }
 
 TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_vec_by_vec_i32) {
-    Func("fn", {}, ty.void_(),
-         {
+    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)))),
          });
@@ -730,8 +729,8 @@
 }
 
 TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_vec_by_scalar_i32) {
-    Func("fn", {}, ty.void_(),
-         {
+    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))),
          });
@@ -748,8 +747,8 @@
 }
 
 TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_i32) {
-    Func("fn", {Param("b", ty.i32())}, ty.void_(),
-         {
+    Func("fn", utils::Vector{Param("b", ty.i32())}, ty.void_(),
+         utils::Vector{
              Decl(Var("a", ty.i32())),
              Decl(Let("r", nullptr, Op("a", "b"))),
          });
@@ -766,8 +765,8 @@
 }
 
 TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_u32) {
-    Func("fn", {Param("b", ty.u32())}, ty.void_(),
-         {
+    Func("fn", utils::Vector{Param("b", ty.u32())}, ty.void_(),
+         utils::Vector{
              Decl(Var("a", ty.u32())),
              Decl(Let("r", nullptr, Op("a", "b"))),
          });
@@ -784,8 +783,8 @@
 }
 
 TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_vec_by_vec_i32) {
-    Func("fn", {Param("b", ty.vec3<i32>())}, ty.void_(),
-         {
+    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"))),
          });
@@ -802,8 +801,8 @@
 }
 
 TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_vec_by_scalar_i32) {
-    Func("fn", {Param("b", ty.i32())}, ty.void_(),
-         {
+    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"))),
          });
@@ -820,13 +819,13 @@
 }
 
 TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_i32) {
-    Func("zero", {}, ty.i32(),
-         {
+    Func("zero", utils::Empty, ty.i32(),
+         utils::Vector{
              Return(Expr(0_i)),
          });
 
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("a", ty.i32())),
              Decl(Let("r", nullptr, Op("a", Call("zero")))),
          });
@@ -851,13 +850,13 @@
 }
 
 TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_u32) {
-    Func("zero", {}, ty.u32(),
-         {
+    Func("zero", utils::Empty, ty.u32(),
+         utils::Vector{
              Return(Expr(0_u)),
          });
 
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("a", ty.u32())),
              Decl(Let("r", nullptr, Op("a", Call("zero")))),
          });
@@ -882,13 +881,13 @@
 }
 
 TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_vec_by_vec_i32) {
-    Func("zero", {}, ty.vec3<i32>(),
-         {
+    Func("zero", utils::Empty, ty.vec3<i32>(),
+         utils::Vector{
              Return(vec3<i32>(0_i, 0_i, 0_i)),
          });
 
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("a", ty.vec3<i32>())),
              Decl(Let("r", nullptr, Op("a", Call("zero")))),
          });
@@ -913,13 +912,13 @@
 }
 
 TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_vec_by_scalar_i32) {
-    Func("zero", {}, ty.i32(),
-         {
+    Func("zero", utils::Empty, ty.i32(),
+         utils::Vector{
              Return(0_i),
          });
 
-    Func("fn", {}, ty.void_(),
-         {
+    Func("fn", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("a", ty.vec3<i32>())),
              Decl(Let("r", nullptr, Op("a", Call("zero")))),
          });
diff --git a/src/tint/writer/hlsl/generator_impl_builtin_test.cc b/src/tint/writer/hlsl/generator_impl_builtin_test.cc
index c87d60d..3f9cfa7 100644
--- a/src/tint/writer/hlsl/generator_impl_builtin_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_builtin_test.cc
@@ -214,8 +214,11 @@
 
     auto* call = GenerateCall(param.builtin, param.type, this);
     ASSERT_NE(nullptr, call) << "Unhandled builtin";
-    Func("func", {}, ty.void_(), {CallStmt(call)},
-         {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(call),
+         },
+         utils::Vector{create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
 
     GeneratorImpl& gen = Build();
 
@@ -374,7 +377,7 @@
     EXPECT_EQ(out.str(), "(bool2(true, false) ? int2(3, 4) : int2(1, 2))");
 }
 
-TEST_F(HlslGeneratorImplTest_Builtin, Modf_Scalar) {
+TEST_F(HlslGeneratorImplTest_Builtin, Modf_Scalar_f32) {
     auto* call = Call("modf", 1_f);
     WrapInFunction(CallStmt(call));
 
@@ -386,9 +389,8 @@
   float whole;
 };
 modf_result tint_modf(float param_0) {
-  float whole;
-  float fract = modf(param_0, whole);
-  modf_result result = {fract, whole};
+  modf_result result;
+  result.fract = modf(param_0, result.whole);
   return result;
 }
 
@@ -400,7 +402,34 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest_Builtin, Modf_Vector) {
+TEST_F(HlslGeneratorImplTest_Builtin, Modf_Scalar_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("modf", 1_h);
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(struct modf_result_f16 {
+  float16_t fract;
+  float16_t whole;
+};
+modf_result_f16 tint_modf(float16_t param_0) {
+  modf_result_f16 result;
+  result.fract = modf(param_0, result.whole);
+  return result;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_modf(float16_t(1.0h));
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Modf_Vector_f32) {
     auto* call = Call("modf", vec3<f32>());
     WrapInFunction(CallStmt(call));
 
@@ -412,9 +441,8 @@
   float3 whole;
 };
 modf_result_vec3 tint_modf(float3 param_0) {
-  float3 whole;
-  float3 fract = modf(param_0, whole);
-  modf_result_vec3 result = {fract, whole};
+  modf_result_vec3 result;
+  result.fract = modf(param_0, result.whole);
   return result;
 }
 
@@ -426,7 +454,34 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest_Builtin, Frexp_Scalar_i32) {
+TEST_F(HlslGeneratorImplTest_Builtin, Modf_Vector_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("modf", vec3<f16>());
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(struct modf_result_vec3_f16 {
+  vector<float16_t, 3> fract;
+  vector<float16_t, 3> whole;
+};
+modf_result_vec3_f16 tint_modf(vector<float16_t, 3> param_0) {
+  modf_result_vec3_f16 result;
+  result.fract = modf(param_0, result.whole);
+  return result;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_modf((float16_t(0.0h)).xxx);
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Frexp_Scalar_f32) {
     auto* call = Call("frexp", 1_f);
     WrapInFunction(CallStmt(call));
 
@@ -452,7 +507,35 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest_Builtin, Frexp_Vector_i32) {
+TEST_F(HlslGeneratorImplTest_Builtin, Frexp_Scalar_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("frexp", 1_h);
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(struct frexp_result_f16 {
+  float16_t sig;
+  int exp;
+};
+frexp_result_f16 tint_frexp(float16_t param_0) {
+  float16_t exp;
+  float16_t sig = frexp(param_0, exp);
+  frexp_result_f16 result = {sig, int(exp)};
+  return result;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_frexp(float16_t(1.0h));
+  return;
+}
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Builtin, Frexp_Vector_f32) {
     auto* call = Call("frexp", vec3<f32>());
     WrapInFunction(CallStmt(call));
 
@@ -478,6 +561,34 @@
 )");
 }
 
+TEST_F(HlslGeneratorImplTest_Builtin, Frexp_Vector_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("frexp", vec3<f16>());
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(struct frexp_result_vec3_f16 {
+  vector<float16_t, 3> sig;
+  int3 exp;
+};
+frexp_result_vec3_f16 tint_frexp(vector<float16_t, 3> param_0) {
+  vector<float16_t, 3> exp;
+  vector<float16_t, 3> sig = frexp(param_0, exp);
+  frexp_result_vec3_f16 result = {sig, int3(exp)};
+  return result;
+}
+
+[numthreads(1, 1, 1)]
+void test_function() {
+  tint_frexp((float16_t(0.0h)).xxx);
+  return;
+}
+)");
+}
+
 TEST_F(HlslGeneratorImplTest_Builtin, Degrees_Scalar_f32) {
     auto* val = Var("val", ty.f32());
     auto* call = Call("degrees", val);
@@ -879,8 +990,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Builtin, StorageBarrier) {
-    Func("main", {}, ty.void_(), {CallStmt(Call("storageBarrier"))},
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("storageBarrier")),
+         },
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
@@ -897,8 +1011,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Builtin, WorkgroupBarrier) {
-    Func("main", {}, ty.void_(), {CallStmt(Call("workgroupBarrier"))},
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("workgroupBarrier")),
+         },
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
diff --git a/src/tint/writer/hlsl/generator_impl_builtin_texture_test.cc b/src/tint/writer/hlsl/generator_impl_builtin_texture_test.cc
index 371cc1d..b996b70 100644
--- a/src/tint/writer/hlsl/generator_impl_builtin_texture_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_builtin_texture_test.cc
@@ -370,7 +370,8 @@
     auto* call = Call(param.function, param.args(this));
     auto* stmt = CallStmt(call);
 
-    Func("main", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.void_(), utils::Vector{stmt},
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
diff --git a/src/tint/writer/hlsl/generator_impl_call_test.cc b/src/tint/writer/hlsl/generator_impl_call_test.cc
index fc41a81..82fb926 100644
--- a/src/tint/writer/hlsl/generator_impl_call_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_call_test.cc
@@ -23,7 +23,7 @@
 using HlslGeneratorImplTest_Call = TestHelper;
 
 TEST_F(HlslGeneratorImplTest_Call, EmitExpression_Call_WithoutParams) {
-    Func("my_func", {}, ty.f32(), {Return(1.23_f)});
+    Func("my_func", utils::Empty, ty.f32(), utils::Vector{Return(1.23_f)});
 
     auto* call = Call("my_func");
     WrapInFunction(call);
@@ -37,11 +37,11 @@
 
 TEST_F(HlslGeneratorImplTest_Call, EmitExpression_Call_WithParams) {
     Func("my_func",
-         {
+         utils::Vector{
              Param(Sym(), ty.f32()),
              Param(Sym(), ty.f32()),
          },
-         ty.f32(), {Return(1.23_f)});
+         ty.f32(), utils::Vector{Return(1.23_f)});
     GlobalVar("param1", ty.f32(), ast::StorageClass::kPrivate);
     GlobalVar("param2", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -57,11 +57,11 @@
 
 TEST_F(HlslGeneratorImplTest_Call, EmitStatement_Call) {
     Func("my_func",
-         {
+         utils::Vector{
              Param(Sym(), ty.f32()),
              Param(Sym(), ty.f32()),
          },
-         ty.void_(), ast::StatementList{}, ast::AttributeList{});
+         ty.void_(), utils::Empty, utils::Empty);
     GlobalVar("param1", ty.f32(), ast::StorageClass::kPrivate);
     GlobalVar("param2", ty.f32(), ast::StorageClass::kPrivate);
 
diff --git a/src/tint/writer/hlsl/generator_impl_case_test.cc b/src/tint/writer/hlsl/generator_impl_case_test.cc
index 5c7e427..c55f14b 100644
--- a/src/tint/writer/hlsl/generator_impl_case_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_case_test.cc
@@ -75,8 +75,9 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Case, Emit_Case_MultipleSelectors) {
-    auto* s = Switch(1_i, Case({Expr(5_i), Expr(6_i)}, Block(create<ast::BreakStatement>())),
-                     DefaultCase());
+    auto* s =
+        Switch(1_i, Case(utils::Vector{Expr(5_i), Expr(6_i)}, Block(create<ast::BreakStatement>())),
+               DefaultCase());
     WrapInFunction(s);
 
     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 f8b9357..bb708db 100644
--- a/src/tint/writer/hlsl/generator_impl_constructor_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_constructor_test.cc
@@ -416,7 +416,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct) {
-    auto* str = Structure("S", {
+    auto* str = Structure("S", utils::Vector{
                                    Member("a", ty.i32()),
                                    Member("b", ty.f32()),
                                    Member("c", ty.vec3<i32>()),
@@ -431,7 +431,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct_Empty) {
-    auto* str = Structure("S", {
+    auto* str = Structure("S", utils::Vector{
                                    Member("a", ty.i32()),
                                    Member("b", ty.f32()),
                                    Member("c", ty.vec3<i32>()),
diff --git a/src/tint/writer/hlsl/generator_impl_function_test.cc b/src/tint/writer/hlsl/generator_impl_function_test.cc
index 9314fb8..7e1ce60 100644
--- a/src/tint/writer/hlsl/generator_impl_function_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_function_test.cc
@@ -28,8 +28,8 @@
 using HlslGeneratorImplTest_Function = TestHelper;
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Function) {
-    Func("my_func", {}, ty.void_(),
-         {
+    Func("my_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(),
          });
 
@@ -45,8 +45,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Function_Name_Collision) {
-    Func("GeometryShader", {}, ty.void_(),
-         {
+    Func("GeometryShader", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(),
          });
 
@@ -62,12 +62,12 @@
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithParams) {
     Func("my_func",
-         {
+         utils::Vector{
              Param("a", ty.f32()),
              Param("b", ty.i32()),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          });
 
@@ -83,8 +83,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_NoReturn_Void) {
-    Func("main", {}, ty.void_(), {/* no explicit return */},
-         {
+    Func("main", utils::Empty, ty.void_(), utils::Empty /* no explicit return */,
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -101,8 +101,8 @@
     // fn f(foo : ptr<function, f32>) -> f32 {
     //   return *foo;
     // }
-    Func("f", {Param("foo", ty.pointer<f32>(ast::StorageClass::kFunction))}, ty.f32(),
-         {Return(Deref("foo"))});
+    Func("f", utils::Vector{Param("foo", ty.pointer<f32>(ast::StorageClass::kFunction))}, ty.f32(),
+         utils::Vector{Return(Deref("foo"))});
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -117,15 +117,15 @@
     // fn frag_main(@location(0) foo : f32) -> @location(1) f32 {
     //   return foo;
     // }
-    auto* foo_in = Param("foo", ty.f32(), {Location(0)});
-    Func("frag_main", {foo_in}, ty.f32(),
-         {
+    auto* foo_in = Param("foo", ty.f32(), utils::Vector{Location(0)});
+    Func("frag_main", utils::Vector{foo_in}, ty.f32(),
+         utils::Vector{
              Return("foo"),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(1),
          });
 
@@ -156,15 +156,16 @@
     // fn frag_main(@position(0) coord : vec4<f32>) -> @frag_depth f32 {
     //   return coord.x;
     // }
-    auto* coord_in = Param("coord", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
-    Func("frag_main", {coord_in}, ty.f32(),
-         {
+    auto* coord_in =
+        Param("coord", ty.vec4<f32>(), utils::Vector{Builtin(ast::BuiltinValue::kPosition)});
+    Func("frag_main", utils::Vector{coord_in}, ty.f32(),
+         utils::Vector{
              Return(MemberAccessor("coord", "x")),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Builtin(ast::BuiltinValue::kFragDepth),
          });
 
@@ -206,24 +207,27 @@
     //   const p = inputs.pos;
     // }
     auto* interface_struct = Structure(
-        "Interface", {
-                         Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
-                         Member("col1", ty.f32(), {Location(1)}),
-                         Member("col2", ty.f32(), {Location(2)}),
-                     });
+        "Interface",
+        utils::Vector{
+            Member("pos", ty.vec4<f32>(), utils::Vector{Builtin(ast::BuiltinValue::kPosition)}),
+            Member("col1", ty.f32(), utils::Vector{Location(1)}),
+            Member("col2", ty.f32(), utils::Vector{Location(2)}),
+        });
 
-    Func("vert_main", {}, ty.Of(interface_struct),
-         {Return(Construct(ty.Of(interface_struct), Construct(ty.vec4<f32>()), Expr(0.5_f),
-                           Expr(0.25_f)))},
-         {Stage(ast::PipelineStage::kVertex)});
+    Func("vert_main", utils::Empty, ty.Of(interface_struct),
+         utils::Vector{
+             Return(Construct(ty.Of(interface_struct), Construct(ty.vec4<f32>()), Expr(0.5_f),
+                              Expr(0.25_f))),
+         },
+         utils::Vector{Stage(ast::PipelineStage::kVertex)});
 
-    Func("frag_main", {Param("inputs", ty.Of(interface_struct))}, ty.void_(),
-         {
+    Func("frag_main", utils::Vector{Param("inputs", ty.Of(interface_struct))}, ty.void_(),
+         utils::Vector{
              Decl(Let("r", ty.f32(), MemberAccessor("inputs", "col1"))),
              Decl(Let("g", ty.f32(), MemberAccessor("inputs", "col2"))),
              Decl(Let("p", ty.vec4<f32>(), MemberAccessor("inputs", "pos"))),
          },
-         {Stage(ast::PipelineStage::kFragment)});
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -286,19 +290,29 @@
     // fn vert_main2() -> VertexOutput {
     //   return foo(0.25);
     // }
-    auto* vertex_output_struct = Structure(
-        "VertexOutput", {Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)})});
+    auto* vertex_output_struct =
+        Structure("VertexOutput",
+                  utils::Vector{Member("pos", ty.vec4<f32>(),
+                                       utils::Vector{Builtin(ast::BuiltinValue::kPosition)})});
 
-    Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
-         {Return(Construct(ty.Of(vertex_output_struct),
-                           Construct(ty.vec4<f32>(), "x", "x", "x", Expr(1_f))))},
-         {});
+    Func("foo", utils::Vector{Param("x", ty.f32())}, ty.Of(vertex_output_struct),
+         utils::Vector{
+             Return(Construct(ty.Of(vertex_output_struct),
+                              Construct(ty.vec4<f32>(), "x", "x", "x", Expr(1_f)))),
+         },
+         utils::Empty);
 
-    Func("vert_main1", {}, ty.Of(vertex_output_struct), {Return(Call("foo", Expr(0.5_f)))},
-         {Stage(ast::PipelineStage::kVertex)});
+    Func("vert_main1", utils::Empty, ty.Of(vertex_output_struct),
+         utils::Vector{
+             Return(Call("foo", Expr(0.5_f))),
+         },
+         utils::Vector{Stage(ast::PipelineStage::kVertex)});
 
-    Func("vert_main2", {}, ty.Of(vertex_output_struct), {Return(Call("foo", Expr(0.25_f)))},
-         {Stage(ast::PipelineStage::kVertex)});
+    Func("vert_main2", utils::Empty, ty.Of(vertex_output_struct),
+         utils::Vector{
+             Return(Call("foo", Expr(0.25_f))),
+         },
+         utils::Vector{Stage(ast::PipelineStage::kVertex)});
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -345,30 +359,30 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_Uniform) {
-    auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())});
+    auto* ubo_ty = Structure("UBO", utils::Vector{Member("coord", ty.vec4<f32>())});
     auto* ubo = GlobalVar("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform,
-                          ast::AttributeList{
+                          utils::Vector{
                               create<ast::BindingAttribute>(0u),
                               create<ast::GroupAttribute>(1u),
                           });
 
     Func("sub_func",
-         {
+         utils::Vector{
              Param("param", ty.f32()),
          },
          ty.f32(),
-         {
+         utils::Vector{
              Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")),
          });
 
     auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -391,10 +405,10 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_UniformStruct) {
-    auto* s = Structure("Uniforms", {Member("coord", ty.vec4<f32>())});
+    auto* s = Structure("Uniforms", utils::Vector{Member("coord", ty.vec4<f32>())});
 
     GlobalVar("uniforms", ty.Of(s), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
@@ -402,12 +416,12 @@
     auto* var = Var("v", ty.f32(), ast::StorageClass::kNone,
                     MemberAccessor(MemberAccessor("uniforms", "coord"), "x"));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -426,25 +440,25 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_RW_StorageBuffer_Read) {
-    auto* s = Structure("Data", {
+    auto* s = Structure("Data", utils::Vector{
                                     Member("a", ty.i32()),
                                     Member("b", ty.f32()),
                                 });
 
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
 
     auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("coord", "b"));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -462,25 +476,25 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_RO_StorageBuffer_Read) {
-    auto* s = Structure("Data", {
+    auto* s = Structure("Data", utils::Vector{
                                     Member("a", ty.i32()),
                                     Member("b", ty.f32()),
                                 });
 
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
 
     auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("coord", "b"));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -498,23 +512,23 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_WO_StorageBuffer_Store) {
-    auto* s = Structure("Data", {
+    auto* s = Structure("Data", utils::Vector{
                                     Member("a", ty.i32()),
                                     Member("b", ty.f32()),
                                 });
 
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Assign(MemberAccessor("coord", "b"), Expr(2_f)),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -532,23 +546,23 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_With_StorageBuffer_Store) {
-    auto* s = Structure("Data", {
+    auto* s = Structure("Data", utils::Vector{
                                     Member("a", ty.i32()),
                                     Member("b", ty.f32()),
                                 });
 
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Assign(MemberAccessor("coord", "b"), Expr(2_f)),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -566,30 +580,30 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_Called_By_EntryPoint_With_Uniform) {
-    auto* s = Structure("S", {Member("x", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("x", ty.f32())});
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kUniform,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
 
     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));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -612,30 +626,30 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_Called_By_EntryPoint_With_StorageBuffer) {
-    auto* s = Structure("S", {Member("x", ty.f32())});
+    auto* s = Structure("S", utils::Vector{Member("x", ty.f32())});
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(1u),
               });
 
     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));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -657,8 +671,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_WithNameCollision) {
-    Func("GeometryShader", {}, ty.void_(), {},
-         {
+    Func("GeometryShader", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -672,11 +686,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_Compute) {
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(),
          },
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
+         utils::Vector{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
 
     GeneratorImpl& gen = Build();
 
@@ -689,8 +703,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Literal) {
-    Func("main", {}, ty.void_(), {},
-         {
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(2_i, 4_i, 6_i),
          });
@@ -709,8 +723,8 @@
     GlobalConst("width", ty.i32(), Construct(ty.i32(), 2_i));
     GlobalConst("height", ty.i32(), Construct(ty.i32(), 3_i));
     GlobalConst("depth", ty.i32(), Construct(ty.i32(), 4_i));
-    Func("main", {}, ty.void_(), {},
-         {
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize("width", "height", "depth"),
          });
@@ -727,11 +741,11 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Attribute_EntryPoint_Compute_WithWorkgroup_OverridableConst) {
-    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", {}, ty.void_(), {},
-         {
+    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)});
+    Func("main", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize("width", "height", "depth"),
          });
@@ -761,11 +775,11 @@
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithArrayParams) {
     Func("my_func",
-         {
+         utils::Vector{
              Param("a", ty.array<f32, 5>()),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          });
 
@@ -779,8 +793,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithArrayReturn) {
-    Func("my_func", {}, ty.array<f32, 5>(),
-         {
+    Func("my_func", utils::Empty, ty.array<f32, 5>(),
+         utils::Vector{
              Return(Construct(ty.array<f32, 5>())),
          });
 
@@ -795,8 +809,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithDiscardAndVoidReturn) {
-    Func("my_func", {Param("a", ty.i32())}, ty.void_(),
-         {
+    Func("my_func", utils::Vector{Param("a", ty.i32())}, ty.void_(),
+         utils::Vector{
              If(Equal("a", 0_i),  //
                 Block(create<ast::DiscardStatement>())),
              Return(),
@@ -815,8 +829,8 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithDiscardAndNonVoidReturn) {
-    Func("my_func", {Param("a", ty.i32())}, ty.i32(),
-         {
+    Func("my_func", utils::Vector{Param("a", ty.i32())}, ty.i32(),
+         utils::Vector{
              If(Equal("a", 0_i),  //
                 Block(create<ast::DiscardStatement>())),
              Return(42_i),
@@ -857,10 +871,10 @@
     //   return;
     // }
 
-    auto* s = Structure("Data", {Member("d", ty.f32())});
+    auto* s = Structure("Data", utils::Vector{Member("d", ty.f32())});
 
     GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -868,12 +882,12 @@
     {
         auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
 
-        Func("a", {}, ty.void_(),
-             {
+        Func("a", utils::Empty, ty.void_(),
+             utils::Vector{
                  Decl(var),
                  Return(),
              },
-             {
+             utils::Vector{
                  Stage(ast::PipelineStage::kCompute),
                  WorkgroupSize(1_i),
              });
@@ -882,12 +896,12 @@
     {
         auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
 
-        Func("b", {}, ty.void_(),
-             {
+        Func("b", utils::Empty, ty.void_(),
+             utils::Vector{
                  Decl(var),
                  Return(),
              },
-             {
+             utils::Vector{
                  Stage(ast::PipelineStage::kCompute),
                  WorkgroupSize(1_i),
              });
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 8149b7f..a0068b9 100644
--- a/src/tint/writer/hlsl/generator_impl_member_accessor_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_member_accessor_test.cc
@@ -86,24 +86,20 @@
 template <typename BASE>
 class HlslGeneratorImplTest_MemberAccessorBase : public BASE {
   public:
-    void SetupStorageBuffer(ast::StructMemberList members) {
+    void SetupStorageBuffer(utils::VectorRef<const ast::StructMember*> members) {
         ProgramBuilder& b = *this;
-
         auto* s = b.Structure("Data", members);
 
         b.GlobalVar("data", b.ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-                    ast::AttributeList{
-                        b.create<ast::BindingAttribute>(0u),
-                        b.create<ast::GroupAttribute>(1u),
-                    });
+                    b.GroupAndBinding(1, 0));
     }
 
-    void SetupFunction(ast::StatementList statements) {
+    void SetupFunction(utils::VectorRef<const ast::Statement*> statements) {
         ProgramBuilder& b = *this;
-        b.Func("main", {}, b.ty.void_(), statements,
-               {
-                   b.Stage(ast::PipelineStage::kFragment),
-               });
+        utils::Vector attrs{
+            b.Stage(ast::PipelineStage::kFragment),
+        };
+        b.Func("main", utils::Empty, b.ty.void_(), std::move(statements), std::move(attrs));
     }
 };
 
@@ -114,7 +110,7 @@
     HlslGeneratorImplTest_MemberAccessorBase<TestParamHelper<T>>;
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor, EmitExpression_MemberAccessor) {
-    auto* s = Structure("Data", {Member("mem", ty.f32())});
+    auto* s = Structure("Data", utils::Vector{Member("mem", ty.f32())});
     GlobalVar("str", ty.Of(s), ast::StorageClass::kPrivate);
 
     auto* expr = MemberAccessor("str", "mem");
@@ -160,12 +156,12 @@
 
     auto p = GetParam();
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("a", ty.i32()),
         Member("b", p.member_type(ty)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone, MemberAccessor("data", "b"))),
     });
 
@@ -231,12 +227,12 @@
 
     auto p = GetParam();
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("a", ty.i32()),
         Member("b", p.member_type(ty)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("value", p.member_type(ty), ast::StorageClass::kNone,
                  Construct(p.member_type(ty)))),
         Assign(MemberAccessor("data", "b"), Expr("value")),
@@ -316,13 +312,13 @@
     // var<storage> data : Data;
     // data.a = mat2x3<f32>();
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("a", ty.i32()),
         Member("b", ty.mat2x3<f32>()),
     });
 
-    SetupFunction({
-        Assign(MemberAccessor("data", "b"), Construct(ty.mat2x3<f32>(), ast::ExpressionList{})),
+    SetupFunction(utils::Vector{
+        Assign(MemberAccessor("data", "b"), Construct(ty.mat2x3<f32>())),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -352,12 +348,12 @@
     // var<storage> data : Data;
     // data.a[2i][1i];
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("z", ty.f32()),
         Member("a", ty.mat4x3<f32>()),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone,
                  IndexAccessor(IndexAccessor(MemberAccessor("data", "a"), 2_i), 1_i))),
     });
@@ -384,12 +380,12 @@
     // var<storage> data : Data;
     // data.a[2];
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("z", ty.f32()),
         Member("a", ty.array<i32, 5>(4)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone,
                  IndexAccessor(MemberAccessor("data", "a"), 2_i))),
     });
@@ -416,14 +412,17 @@
     // var<storage> data : Data;
     // data.a[(2i + 4i) - 3i];
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("z", ty.f32()),
         Member("a", ty.array<i32, 5>(4)),
     });
 
-    SetupFunction({
+    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(2_i, Expr(4_i)), Expr(3_i))))),
+                 IndexAccessor(MemberAccessor("data", "a"), Sub(Add("a", "b"), "c")))),
     });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -433,7 +432,10 @@
         R"(RWByteAddressBuffer data : register(u0, space1);
 
 void main() {
-  int x = asint(data.Load((4u + (4u * uint(((2 + 4) - 3))))));
+  int a = 2;
+  int b = 4;
+  int c = 3;
+  int x = asint(data.Load((4u + (4u * uint(((a + b) - c))))));
   return;
 }
 )";
@@ -447,12 +449,12 @@
     // var<storage> data : Data;
     // data.a[2] = 2;
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("z", ty.f32()),
         Member("a", ty.array<i32, 5>(4)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Assign(IndexAccessor(MemberAccessor("data", "a"), 2_i), 2_i),
     });
 
@@ -482,16 +484,16 @@
     // var<storage> data : Pre;
     // data.c[2].b
 
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.vec3<f32>()),
                                          Member("b", ty.vec3<f32>()),
                                      });
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("c", ty.array(ty.Of(inner), 4_u, 32)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone,
                  MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"))),
     });
@@ -522,16 +524,16 @@
     // var<storage> data : Pre;
     // data.c[2].b.xy
 
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.vec3<f32>()),
                                          Member("b", ty.vec3<f32>()),
                                      });
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("c", ty.array(ty.Of(inner), 4_u, 32)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone,
                  MemberAccessor(
                      MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), "xy"))),
@@ -564,16 +566,16 @@
     // var<storage> data : Pre;
     // data.c[2].b.g
 
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.vec3<f32>()),
                                          Member("b", ty.vec3<f32>()),
                                      });
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("c", ty.array(ty.Of(inner), 4_u, 32)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone,
                  MemberAccessor(
                      MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), "g"))),
@@ -605,16 +607,16 @@
     // var<storage> data : Pre;
     // data.c[2].b[1]
 
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.vec3<f32>()),
                                          Member("b", ty.vec3<f32>()),
                                      });
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("c", ty.array(ty.Of(inner), 4_u, 32)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Decl(Var("x", nullptr, ast::StorageClass::kNone,
                  IndexAccessor(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"),
                                1_i))),
@@ -646,16 +648,16 @@
     // var<storage> data : Pre;
     // data.c[2].b = vec3<f32>(1_f, 2_f, 3_f);
 
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.vec3<f32>()),
                                          Member("b", ty.vec3<f32>()),
                                      });
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("c", ty.array(ty.Of(inner), 4_u, 32)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Assign(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"),
                vec3<f32>(1_f, 2_f, 3_f)),
     });
@@ -686,16 +688,16 @@
     // var<storage> data : Pre;
     // data.c[2].b.y = 1.f;
 
-    auto* inner = Structure("Inner", {
+    auto* inner = Structure("Inner", utils::Vector{
                                          Member("a", ty.vec3<i32>()),
                                          Member("b", ty.vec3<f32>()),
                                      });
 
-    SetupStorageBuffer({
+    SetupStorageBuffer(utils::Vector{
         Member("c", ty.array(ty.Of(inner), 4_u, 32)),
     });
 
-    SetupFunction({
+    SetupFunction(utils::Vector{
         Assign(MemberAccessor(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"),
                               "y"),
                Expr(1_f)),
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 9dc9acb..7296d13 100644
--- a/src/tint/writer/hlsl/generator_impl_module_constant_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_module_constant_test.cc
@@ -24,7 +24,7 @@
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_AInt) {
     auto* var = GlobalConst("G", nullptr, Expr(1_a));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -38,7 +38,7 @@
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_AFloat) {
     auto* var = GlobalConst("G", nullptr, Expr(1._a));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -52,7 +52,7 @@
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_i32) {
     auto* var = GlobalConst("G", nullptr, Expr(1_i));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -66,7 +66,7 @@
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_u32) {
     auto* var = GlobalConst("G", nullptr, Expr(1_u));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -80,7 +80,7 @@
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_f32) {
     auto* var = GlobalConst("G", nullptr, Expr(1_f));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -96,7 +96,7 @@
     Enable(ast::Extension::kF16);
 
     auto* var = GlobalConst("G", nullptr, Expr(1_h));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -110,7 +110,7 @@
 
 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", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -124,7 +124,7 @@
 
 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", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -138,7 +138,7 @@
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_f32) {
     auto* var = GlobalConst("G", nullptr, vec3<f32>(1_f, 2_f, 3_f));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -154,7 +154,7 @@
     Enable(ast::Extension::kF16);
 
     auto* var = GlobalConst("G", nullptr, vec3<f16>(1_h, 2_h, 3_h));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -169,7 +169,7 @@
 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", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -183,7 +183,7 @@
 
 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", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -199,7 +199,7 @@
     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", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -213,7 +213,7 @@
 
 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", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -231,7 +231,7 @@
                                       vec2<bool>(true, false),         //
                                       vec2<bool>(false, true),         //
                                       vec2<bool>(true, true)));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -244,7 +244,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_Override) {
-    auto* var = Override("pos", ty.f32(), Expr(3_f), {Id(23)});
+    auto* var = Override("pos", ty.f32(), Expr(3_f), utils::Vector{Id(23)});
 
     GeneratorImpl& gen = Build();
 
@@ -257,7 +257,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_Override_NoConstructor) {
-    auto* var = Override("pos", ty.f32(), nullptr, {Id(23)});
+    auto* var = Override("pos", ty.f32(), nullptr, utils::Vector{Id(23)});
 
     GeneratorImpl& gen = Build();
 
@@ -270,7 +270,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_Override_NoId) {
-    auto* a = Override("a", ty.f32(), Expr(3_f), {Id(0)});
+    auto* a = Override("a", ty.f32(), Expr(3_f), utils::Vector{Id(0)});
     auto* b = Override("b", ty.f32(), Expr(2_f));
 
     GeneratorImpl& gen = Build();
diff --git a/src/tint/writer/hlsl/generator_impl_return_test.cc b/src/tint/writer/hlsl/generator_impl_return_test.cc
index 1813645..91b4159 100644
--- a/src/tint/writer/hlsl/generator_impl_return_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_return_test.cc
@@ -35,7 +35,7 @@
 
 TEST_F(HlslGeneratorImplTest_Return, Emit_ReturnWithValue) {
     auto* r = Return(123_i);
-    Func("f", {}, ty.i32(), {r});
+    Func("f", utils::Empty, ty.i32(), utils::Vector{r});
 
     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 3bb1cec..3d0785a 100644
--- a/src/tint/writer/hlsl/generator_impl_sanitizer_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_sanitizer_test.cc
@@ -25,19 +25,19 @@
 using HlslSanitizerTest = TestHelper;
 
 TEST_F(HlslSanitizerTest, Call_ArrayLength) {
-    auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
                       Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -60,22 +60,22 @@
 }
 
 TEST_F(HlslSanitizerTest, Call_ArrayLength_OtherMembersInStruct) {
-    auto* s = Structure("my_struct", {
+    auto* s = Structure("my_struct", utils::Vector{
                                          Member(0, "z", ty.f32()),
                                          Member(4, "a", ty.array<f32>(4)),
                                      });
     GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
                       Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -99,9 +99,9 @@
 }
 
 TEST_F(HlslSanitizerTest, Call_ArrayLength_ViaLets) {
-    auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
@@ -109,13 +109,13 @@
     auto* p = Let("p", nullptr, AddressOf("b"));
     auto* p2 = Let("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
 
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(p),
              Decl(p2),
              Decl(Var("len", ty.u32(), ast::StorageClass::kNone, Call("arrayLength", p2))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -139,25 +139,25 @@
 }
 
 TEST_F(HlslSanitizerTest, Call_ArrayLength_ArrayLengthFromUniform) {
-    auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
     GlobalVar("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(2u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
                       Add(Call("arrayLength", AddressOf(MemberAccessor("b", "a"))),
                           Call("arrayLength", AddressOf(MemberAccessor("c", "a")))))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -189,12 +189,12 @@
 TEST_F(HlslSanitizerTest, PromoteArrayInitializerToConstVar) {
     auto* array_init = array<i32, 4>(1_i, 2_i, 3_i, 4_i);
 
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("idx", nullptr, Expr(3_i))),
              Decl(Var("pos", ty.i32(), IndexAccessor(array_init, "idx"))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -214,7 +214,7 @@
 }
 
 TEST_F(HlslSanitizerTest, PromoteStructInitializerToConstVar) {
-    auto* str = Structure("S", {
+    auto* str = Structure("S", utils::Vector{
                                    Member("a", ty.i32()),
                                    Member("b", ty.vec3<f32>()),
                                    Member("c", ty.i32()),
@@ -223,11 +223,11 @@
     auto* struct_access = MemberAccessor(struct_init, "b");
     auto* pos = Var("pos", ty.vec3<f32>(), ast::StorageClass::kNone, struct_access);
 
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(pos),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -259,13 +259,13 @@
     auto* p = Let("p", ty.pointer<i32>(ast::StorageClass::kFunction), AddressOf(v));
     auto* x = Var("x", ty.i32(), ast::StorageClass::kNone, Deref(p));
 
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(v),
              Decl(p),
              Decl(x),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -298,15 +298,15 @@
                    AddressOf(IndexAccessor(Deref(mp), 2_i)));
     auto* v = Var("v", ty.vec4<f32>(), ast::StorageClass::kNone, Deref(vp));
 
-    Func("main", {}, ty.void_(),
-         {
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(a),
              Decl(ap),
              Decl(mp),
              Decl(vp),
              Decl(v),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
diff --git a/src/tint/writer/hlsl/generator_impl_static_assert_test.cc b/src/tint/writer/hlsl/generator_impl_static_assert_test.cc
new file mode 100644
index 0000000..8ae5dd8
--- /dev/null
+++ b/src/tint/writer/hlsl/generator_impl_static_assert_test.cc
@@ -0,0 +1,47 @@
+// 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/writer/hlsl/test_helper.h"
+
+using namespace tint::number_suffixes;  // NOLINT
+
+namespace tint::writer::hlsl {
+namespace {
+
+using HlslGeneratorImplTest = TestHelper;
+
+TEST_F(HlslGeneratorImplTest, Emit_GlobalStaticAssert) {
+    GlobalStaticAssert(true);
+
+    GeneratorImpl& gen = Build();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    // static asserts are not emitted
+    EXPECT_EQ(gen.result(), "");
+}
+
+TEST_F(HlslGeneratorImplTest, Emit_FunctionStaticAssert) {
+    Func("f", utils::Empty, ty.void_(), utils::Vector{StaticAssert(true)});
+
+    GeneratorImpl& gen = Build();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    // static asserts are not emitted
+    EXPECT_EQ(gen.result(), R"(void f() {
+}
+)");
+}
+
+}  // namespace
+}  // namespace tint::writer::hlsl
diff --git a/src/tint/writer/hlsl/generator_impl_type_test.cc b/src/tint/writer/hlsl/generator_impl_type_test.cc
index 991c5c0..4e443d5 100644
--- a/src/tint/writer/hlsl/generator_impl_type_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_type_test.cc
@@ -154,7 +154,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_StructDecl) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", ty.f32()),
                              });
@@ -173,12 +173,12 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_StructDecl_OmittedIfStorageBuffer) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", ty.f32()),
                              });
     GlobalVar("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -190,7 +190,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", ty.f32()),
                              });
@@ -206,7 +206,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct_NameCollision) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("double", ty.i32()),
                                  Member("float", ty.f32()),
                              });
@@ -223,9 +223,9 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct_WithOffsetAttributes) {
-    auto* s = Structure("S", {
-                                 Member("a", ty.i32(), {MemberOffset(0)}),
-                                 Member("b", ty.f32(), {MemberOffset(8)}),
+    auto* s = Structure("S", utils::Vector{
+                                 Member("a", ty.i32(), utils::Vector{MemberOffset(0)}),
+                                 Member("b", ty.f32(), utils::Vector{MemberOffset(8)}),
                              });
     GlobalVar("g", ty.Of(s), ast::StorageClass::kPrivate);
 
@@ -312,13 +312,18 @@
     auto* t = ty.depth_texture(params.dim);
 
     GlobalVar("tex", t,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("textureDimensions", "tex")),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -342,13 +347,18 @@
     auto* t = ty.depth_multisampled_texture(ast::TextureDimension::k2d);
 
     GlobalVar("tex", t,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("textureDimensions", "tex")),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -385,13 +395,18 @@
     auto* t = ty.sampled_texture(params.dim, datatype);
 
     GlobalVar("tex", t,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("textureDimensions", "tex")),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -519,10 +534,18 @@
 
     auto* t = ty.storage_texture(params.dim, params.imgfmt, ast::Access::kWrite);
 
-    GlobalVar("tex", t, ast::AttributeList{GroupAndBinding(2, 1)});
+    GlobalVar("tex", t,
+              utils::Vector{
+                  GroupAndBinding(2, 1),
+              });
 
-    Func("main", {}, ty.void_(), {CallStmt(Call("textureDimensions", "tex"))},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call("textureDimensions", "tex")),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     GeneratorImpl& gen = Build();
 
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 9a741bc..db8fb77 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
@@ -66,7 +66,11 @@
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_AInt) {
     auto* C = Const("C", nullptr, Expr(1_a));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -80,7 +84,11 @@
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_AFloat) {
     auto* C = Const("C", nullptr, Expr(1._a));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -94,7 +102,11 @@
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_i32) {
     auto* C = Const("C", nullptr, Expr(1_i));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -108,7 +120,11 @@
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_u32) {
     auto* C = Const("C", nullptr, Expr(1_u));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -122,7 +138,11 @@
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_f32) {
     auto* C = Const("C", nullptr, Expr(1_f));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -138,7 +158,11 @@
     Enable(ast::Extension::kF16);
 
     auto* C = Const("C", nullptr, Expr(1_h));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -152,7 +176,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));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -166,7 +194,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));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -180,7 +212,11 @@
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_f32) {
     auto* C = Const("C", nullptr, vec3<f32>(1_f, 2_f, 3_f));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -196,7 +232,11 @@
     Enable(ast::Extension::kF16);
 
     auto* C = Const("C", nullptr, vec3<f16>(1_h, 2_h, 3_h));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -211,7 +251,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));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -225,7 +269,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));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -241,7 +289,11 @@
     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", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -255,7 +307,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));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -273,7 +329,11 @@
                               vec2<bool>(true, false),         //
                               vec2<bool>(false, true),         //
                               vec2<bool>(true, true)));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
diff --git a/src/tint/writer/hlsl/generator_impl_workgroup_var_test.cc b/src/tint/writer/hlsl/generator_impl_workgroup_var_test.cc
index 5de44b1..9e8be01 100644
--- a/src/tint/writer/hlsl/generator_impl_workgroup_var_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_workgroup_var_test.cc
@@ -29,8 +29,8 @@
 TEST_F(HlslGeneratorImplTest_WorkgroupVar, Basic) {
     GlobalVar("wg", ty.f32(), ast::StorageClass::kWorkgroup);
 
-    Func("main", {}, ty.void_(), {Assign("wg", 1.2_f)},
-         {
+    Func("main", utils::Empty, ty.void_(), utils::Vector{Assign("wg", 1.2_f)},
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
@@ -45,8 +45,8 @@
 
     GlobalVar("wg", ty.Of(alias), ast::StorageClass::kWorkgroup);
 
-    Func("main", {}, ty.void_(), {Assign("wg", 1.2_f)},
-         {
+    Func("main", utils::Empty, ty.void_(), utils::Vector{Assign("wg", 1.2_f)},
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 3c1525f..602ec71 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -286,6 +286,9 @@
                 // Do nothing for enabling extension in MSL
                 return true;
             },
+            [&](const ast::StaticAssert*) {
+                return true;  // Not emitted
+            },
             [&](Default) {
                 // These are pushed into the entry point by sanitizer transforms.
                 TINT_ICE(Writer, diagnostics_) << "unhandled type: " << decl->TypeInfo().name;
@@ -804,7 +807,7 @@
         out << name;
         {
             ScopedParen sp(out);
-            for (size_t i = 0; i < expr->args.size(); i++) {
+            for (size_t i = 0; i < expr->args.Length(); i++) {
                 auto* arg = expr->args[i];
                 if (i > 0) {
                     out << ", ";
@@ -1305,9 +1308,9 @@
                 return false;
             }
 
-            line(b) << "float" << width << " whole;";
-            line(b) << "float" << width << " fract = modf(" << in << ", whole);";
-            line(b) << "return {fract, whole};";
+            line(b) << StructName(builtin->ReturnType()->As<sem::Struct>()) << " result;";
+            line(b) << "result.fract = modf(" << in << ", result.whole);";
+            line(b) << "return result;";
             return true;
         });
 }
@@ -1331,9 +1334,9 @@
                 return false;
             }
 
-            line(b) << "int" << width << " exp;";
-            line(b) << "float" << width << " sig = frexp(" << in << ", exp);";
-            line(b) << "return {sig, exp};";
+            line(b) << StructName(builtin->ReturnType()->As<sem::Struct>()) << " result;";
+            line(b) << "result.sig = frexp(" << in << ", result.exp);";
+            line(b) << "return result;";
             return true;
         });
 }
@@ -1524,7 +1527,7 @@
                 return false;
             }
             out << ":";
-            if (selector == stmt->selectors.back()) {
+            if (selector == stmt->selectors.Back()) {
                 out << " {";
             }
         }
@@ -1695,7 +1698,7 @@
             return true;
         },
         [&](const sem::Struct* s) {
-            out << "{";
+            out << program_->Symbols().NameFor(s->Name()) << "{";
             TINT_DEFER(out << "}");
 
             if (constant->AllZero()) {
@@ -2268,7 +2271,7 @@
                 return false;
             }
         } else {
-            if (!EmitStatementsWithIndent({stmt->else_statement})) {
+            if (!EmitStatementsWithIndent(utils::Vector{stmt->else_statement})) {
                 return false;
             }
         }
@@ -2419,6 +2422,9 @@
                     return false;
                 });
         },
+        [&](const ast::StaticAssert*) {
+            return true;  // Not emitted
+        },
         [&](Default) {
             diagnostics_.add_error(diag::System::Writer,
                                    "unknown statement type: " + std::string(stmt->TypeInfo().name));
@@ -2426,7 +2432,7 @@
         });
 }
 
-bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) {
+bool GeneratorImpl::EmitStatements(utils::VectorRef<const ast::Statement*> stmts) {
     for (auto* s : stmts) {
         if (!EmitStatement(s)) {
             return false;
@@ -2435,7 +2441,7 @@
     return true;
 }
 
-bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) {
+bool GeneratorImpl::EmitStatementsWithIndent(utils::VectorRef<const ast::Statement*> stmts) {
     ScopedIndent si(this);
     return EmitStatements(stmts);
 }
diff --git a/src/tint/writer/msl/generator_impl.h b/src/tint/writer/msl/generator_impl.h
index 137b365..72ee42c 100644
--- a/src/tint/writer/msl/generator_impl.h
+++ b/src/tint/writer/msl/generator_impl.h
@@ -294,11 +294,11 @@
     /// Emits a list of statements
     /// @param stmts the statement list
     /// @returns true if the statements were emitted successfully
-    bool EmitStatements(const ast::StatementList& stmts);
+    bool EmitStatements(utils::VectorRef<const ast::Statement*> stmts);
     /// Emits a list of statements with an indentation
     /// @param stmts the statement list
     /// @returns true if the statements were emitted successfully
-    bool EmitStatementsWithIndent(const ast::StatementList& stmts);
+    bool EmitStatementsWithIndent(utils::VectorRef<const ast::Statement*> stmts);
     /// Handles generating a switch statement
     /// @param stmt the statement to emit
     /// @returns true if the statement was emitted
diff --git a/src/tint/writer/msl/generator_impl_builtin_test.cc b/src/tint/writer/msl/generator_impl_builtin_test.cc
index c8fab4b..9f75691 100644
--- a/src/tint/writer/msl/generator_impl_builtin_test.cc
+++ b/src/tint/writer/msl/generator_impl_builtin_test.cc
@@ -234,8 +234,8 @@
 
     auto* call = GenerateCall(param.builtin, param.type, this);
     ASSERT_NE(nullptr, call) << "Unhandled builtin";
-    Func("func", {}, ty.void_(), {Ignore(call)},
-         {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(), utils::Vector{Ignore(call)},
+         utils::Vector{create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
 
     GeneratorImpl& gen = Build();
 
@@ -405,6 +405,246 @@
     EXPECT_EQ(out.str(), "threadgroup_barrier(mem_flags::mem_threadgroup)");
 }
 
+TEST_F(MslGeneratorImplTest, Modf_Scalar_f32) {
+    auto* call = Call("modf", 1_f);
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+struct modf_result {
+  float fract;
+  float whole;
+};
+modf_result tint_modf(float param_0) {
+  modf_result result;
+  result.fract = modf(param_0, result.whole);
+  return result;
+}
+
+kernel void test_function() {
+  tint_modf(1.0f);
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Modf_Scalar_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("modf", 1_h);
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+struct modf_result_f16 {
+  half fract;
+  half whole;
+};
+modf_result_f16 tint_modf(half param_0) {
+  modf_result_f16 result;
+  result.fract = modf(param_0, result.whole);
+  return result;
+}
+
+kernel void test_function() {
+  tint_modf(1.0h);
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Modf_Vector_f32) {
+    auto* call = Call("modf", vec3<f32>());
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+struct modf_result_vec3 {
+  float3 fract;
+  float3 whole;
+};
+modf_result_vec3 tint_modf(float3 param_0) {
+  modf_result_vec3 result;
+  result.fract = modf(param_0, result.whole);
+  return result;
+}
+
+kernel void test_function() {
+  tint_modf(float3(0.0f));
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Modf_Vector_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("modf", vec3<f16>());
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+struct modf_result_vec3_f16 {
+  half3 fract;
+  half3 whole;
+};
+modf_result_vec3_f16 tint_modf(half3 param_0) {
+  modf_result_vec3_f16 result;
+  result.fract = modf(param_0, result.whole);
+  return result;
+}
+
+kernel void test_function() {
+  tint_modf(half3(0.0h));
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Frexp_Scalar_f32) {
+    auto* call = Call("frexp", 1_f);
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+struct frexp_result {
+  float sig;
+  int exp;
+};
+frexp_result tint_frexp(float param_0) {
+  frexp_result result;
+  result.sig = frexp(param_0, result.exp);
+  return result;
+}
+
+kernel void test_function() {
+  tint_frexp(1.0f);
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Frexp_Scalar_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("frexp", 1_h);
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+struct frexp_result_f16 {
+  half sig;
+  int exp;
+};
+frexp_result_f16 tint_frexp(half param_0) {
+  frexp_result_f16 result;
+  result.sig = frexp(param_0, result.exp);
+  return result;
+}
+
+kernel void test_function() {
+  tint_frexp(1.0h);
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Frexp_Vector_f32) {
+    auto* call = Call("frexp", vec3<f32>());
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+struct frexp_result_vec3 {
+  float3 sig;
+  int3 exp;
+};
+frexp_result_vec3 tint_frexp(float3 param_0) {
+  frexp_result_vec3 result;
+  result.sig = frexp(param_0, result.exp);
+  return result;
+}
+
+kernel void test_function() {
+  tint_frexp(float3(0.0f));
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Frexp_Vector_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* call = Call("frexp", vec3<f16>());
+    WrapInFunction(CallStmt(call));
+
+    GeneratorImpl& gen = SanitizeAndBuild();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+
+struct frexp_result_vec3_f16 {
+  half3 sig;
+  int3 exp;
+};
+frexp_result_vec3_f16 tint_frexp(half3 param_0) {
+  frexp_result_vec3_f16 result;
+  result.sig = frexp(param_0, result.exp);
+  return result;
+}
+
+kernel void test_function() {
+  tint_frexp(half3(0.0h));
+  return;
+}
+
+)");
+}
+
 TEST_F(MslGeneratorImplTest, Degrees_Scalar_f32) {
     auto* val = Var("val", ty.f32());
     auto* call = Call("degrees", val);
@@ -662,11 +902,11 @@
 }
 
 TEST_F(MslGeneratorImplTest, Ignore) {
-    Func("f", {Param("a", ty.i32()), Param("b", ty.i32()), Param("c", ty.i32())}, ty.i32(),
-         {Return(Mul(Add("a", "b"), "c"))});
+    Func("f", utils::Vector{Param("a", ty.i32()), Param("b", ty.i32()), Param("c", ty.i32())},
+         ty.i32(), utils::Vector{Return(Mul(Add("a", "b"), "c"))});
 
-    Func("func", {}, ty.void_(), {CallStmt(Call("f", 1_i, 2_i, 3_i))},
-         {
+    Func("func", utils::Empty, ty.void_(), utils::Vector{CallStmt(Call("f", 1_i, 2_i, 3_i))},
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
diff --git a/src/tint/writer/msl/generator_impl_builtin_texture_test.cc b/src/tint/writer/msl/generator_impl_builtin_texture_test.cc
index 20813fa..dd4a92f 100644
--- a/src/tint/writer/msl/generator_impl_builtin_texture_test.cc
+++ b/src/tint/writer/msl/generator_impl_builtin_texture_test.cc
@@ -280,7 +280,8 @@
     auto* call = Call(Expr(param.function), param.args(this));
     auto* stmt = CallStmt(call);
 
-    Func("main", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.void_(), utils::Vector{stmt},
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     GeneratorImpl& gen = Build();
 
diff --git a/src/tint/writer/msl/generator_impl_call_test.cc b/src/tint/writer/msl/generator_impl_call_test.cc
index c7f3a02..1011ebd 100644
--- a/src/tint/writer/msl/generator_impl_call_test.cc
+++ b/src/tint/writer/msl/generator_impl_call_test.cc
@@ -23,7 +23,7 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitExpression_Call_WithoutParams) {
-    Func("my_func", {}, ty.f32(), {Return(1.23_f)});
+    Func("my_func", utils::Empty, ty.f32(), utils::Vector{Return(1.23_f)});
 
     auto* call = Call("my_func");
     WrapInFunction(call);
@@ -37,11 +37,14 @@
 
 TEST_F(MslGeneratorImplTest, EmitExpression_Call_WithParams) {
     Func("my_func",
-         {
+         utils::Vector{
              Param(Sym(), ty.f32()),
              Param(Sym(), ty.f32()),
          },
-         ty.f32(), {Return(1.23_f)});
+         ty.f32(),
+         utils::Vector{
+             Return(1.23_f),
+         });
     GlobalVar("param1", ty.f32(), ast::StorageClass::kPrivate);
     GlobalVar("param2", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -57,11 +60,11 @@
 
 TEST_F(MslGeneratorImplTest, EmitStatement_Call) {
     Func("my_func",
-         {
+         utils::Vector{
              Param(Sym(), ty.f32()),
              Param(Sym(), ty.f32()),
          },
-         ty.void_(), ast::StatementList{}, ast::AttributeList{});
+         ty.void_(), utils::Empty, utils::Empty);
     GlobalVar("param1", ty.f32(), ast::StorageClass::kPrivate);
     GlobalVar("param2", ty.f32(), ast::StorageClass::kPrivate);
 
diff --git a/src/tint/writer/msl/generator_impl_case_test.cc b/src/tint/writer/msl/generator_impl_case_test.cc
index 8fc1bc9..250d67d 100644
--- a/src/tint/writer/msl/generator_impl_case_test.cc
+++ b/src/tint/writer/msl/generator_impl_case_test.cc
@@ -69,7 +69,13 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_Case_MultipleSelectors) {
-    auto* s = Switch(1_i, Case({Expr(5_i), Expr(6_i)}, Block(create<ast::BreakStatement>())),
+    auto* s = Switch(1_i,
+                     Case(
+                         utils::Vector{
+                             Expr(5_i),
+                             Expr(6_i),
+                         },
+                         Block(create<ast::BreakStatement>())),
                      DefaultCase());
     WrapInFunction(s);
 
diff --git a/src/tint/writer/msl/generator_impl_constructor_test.cc b/src/tint/writer/msl/generator_impl_constructor_test.cc
index 14fc4a5..b0bbbea 100644
--- a/src/tint/writer/msl/generator_impl_constructor_test.cc
+++ b/src/tint/writer/msl/generator_impl_constructor_test.cc
@@ -387,7 +387,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Struct) {
-    auto* str = Structure("S", {
+    auto* str = Structure("S", utils::Vector{
                                    Member("a", ty.i32()),
                                    Member("b", ty.f32()),
                                    Member("c", ty.vec3<i32>()),
@@ -402,7 +402,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Struct_Empty) {
-    auto* str = Structure("S", {
+    auto* str = Structure("S", utils::Vector{
                                    Member("a", ty.i32()),
                                    Member("b", ty.f32()),
                                    Member("c", ty.vec3<i32>()),
diff --git a/src/tint/writer/msl/generator_impl_function_test.cc b/src/tint/writer/msl/generator_impl_function_test.cc
index c3f926d..0f891a2 100644
--- a/src/tint/writer/msl/generator_impl_function_test.cc
+++ b/src/tint/writer/msl/generator_impl_function_test.cc
@@ -24,8 +24,8 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, Emit_Function) {
-    Func("my_func", {}, ty.void_(),
-         {
+    Func("my_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(),
          });
 
@@ -46,12 +46,12 @@
 
 TEST_F(MslGeneratorImplTest, Emit_Function_WithParams) {
     Func("my_func",
-         {
+         utils::Vector{
              Param("a", ty.f32()),
              Param("b", ty.i32()),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          });
 
@@ -71,8 +71,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_Attribute_EntryPoint_NoReturn_Void) {
-    Func("main", {}, ty.void_(), {/* no explicit return */},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.void_(), {/* no explicit return */},
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     GeneratorImpl& gen = Build();
 
@@ -91,15 +91,15 @@
     // fn frag_main(@location(0) foo : f32) -> @location(1) f32 {
     //   return foo;
     // }
-    auto* foo_in = Param("foo", ty.f32(), {Location(0)});
-    Func("frag_main", {foo_in}, ty.f32(),
-         {
+    auto* foo_in = Param("foo", ty.f32(), utils::Vector{Location(0)});
+    Func("frag_main", utils::Vector{foo_in}, ty.f32(),
+         utils::Vector{
              Return("foo"),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Location(1),
          });
 
@@ -135,15 +135,16 @@
     // fn frag_main(@position(0) coord : vec4<f32>) -> @frag_depth f32 {
     //   return coord.x;
     // }
-    auto* coord_in = Param("coord", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
-    Func("frag_main", {coord_in}, ty.f32(),
-         {
+    auto* coord_in =
+        Param("coord", ty.vec4<f32>(), utils::Vector{Builtin(ast::BuiltinValue::kPosition)});
+    Func("frag_main", utils::Vector{coord_in}, ty.f32(),
+         utils::Vector{
              Return(MemberAccessor("coord", "x")),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Builtin(ast::BuiltinValue::kFragDepth),
          });
 
@@ -185,23 +186,24 @@
     //   const g = colors.col2;
     // }
     auto* interface_struct = Structure(
-        "Interface", {
-                         Member("col1", ty.f32(), {Location(1)}),
-                         Member("col2", ty.f32(), {Location(2)}),
-                         Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
-                     });
+        "Interface",
+        utils::Vector{
+            Member("col1", ty.f32(), utils::Vector{Location(1)}),
+            Member("col2", ty.f32(), utils::Vector{Location(2)}),
+            Member("pos", ty.vec4<f32>(), utils::Vector{Builtin(ast::BuiltinValue::kPosition)}),
+        });
 
-    Func("vert_main", {}, ty.Of(interface_struct),
-         {Return(Construct(ty.Of(interface_struct), Expr(0.5_f), Expr(0.25_f),
-                           Construct(ty.vec4<f32>())))},
-         {Stage(ast::PipelineStage::kVertex)});
+    Func("vert_main", utils::Empty, ty.Of(interface_struct),
+         utils::Vector{Return(Construct(ty.Of(interface_struct), Expr(0.5_f), Expr(0.25_f),
+                                        Construct(ty.vec4<f32>())))},
+         utils::Vector{Stage(ast::PipelineStage::kVertex)});
 
-    Func("frag_main", {Param("colors", ty.Of(interface_struct))}, ty.void_(),
-         {
+    Func("frag_main", utils::Vector{Param("colors", ty.Of(interface_struct))}, ty.void_(),
+         utils::Vector{
              WrapInStatement(Let("r", ty.f32(), MemberAccessor("colors", "col1"))),
              WrapInStatement(Let("g", ty.f32(), MemberAccessor("colors", "col2"))),
          },
-         {Stage(ast::PipelineStage::kFragment)});
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -222,7 +224,7 @@
 };
 
 Interface vert_main_inner() {
-  Interface const tint_symbol_3 = {.col1=0.5f, .col2=0.25f, .pos=float4(0.0f)};
+  Interface const tint_symbol_3 = Interface{.col1=0.5f, .col2=0.25f, .pos=float4(0.0f)};
   return tint_symbol_3;
 }
 
@@ -268,18 +270,23 @@
     //   return foo(0.25);
     // }
     auto* vertex_output_struct = Structure(
-        "VertexOutput", {Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)})});
+        "VertexOutput",
+        utils::Vector{
+            Member("pos", ty.vec4<f32>(), utils::Vector{Builtin(ast::BuiltinValue::kPosition)}),
+        });
 
-    Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
-         {
+    Func("foo", utils::Vector{Param("x", ty.f32())}, ty.Of(vertex_output_struct),
+         utils::Vector{
              Return(Construct(ty.Of(vertex_output_struct),
                               Construct(ty.vec4<f32>(), "x", "x", "x", Expr(1_f)))),
          });
-    Func("vert_main1", {}, ty.Of(vertex_output_struct), {Return(Expr(Call("foo", Expr(0.5_f))))},
-         {Stage(ast::PipelineStage::kVertex)});
+    Func("vert_main1", utils::Empty, ty.Of(vertex_output_struct),
+         utils::Vector{Return(Expr(Call("foo", Expr(0.5_f))))},
+         utils::Vector{Stage(ast::PipelineStage::kVertex)});
 
-    Func("vert_main2", {}, ty.Of(vertex_output_struct), {Return(Expr(Call("foo", Expr(0.25_f))))},
-         {Stage(ast::PipelineStage::kVertex)});
+    Func("vert_main2", utils::Empty, ty.Of(vertex_output_struct),
+         utils::Vector{Return(Expr(Call("foo", Expr(0.25_f))))},
+         utils::Vector{Stage(ast::PipelineStage::kVertex)});
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -330,25 +337,22 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_FunctionAttribute_EntryPoint_With_RW_StorageBuffer) {
-    auto* s = Structure("Data", {
+    auto* s = Structure("Data", utils::Vector{
                                     Member("a", ty.i32()),
                                     Member("b", ty.f32()),
                                 });
 
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              GroupAndBinding(0, 0));
 
     auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("coord", "b"));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -372,25 +376,22 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_FunctionAttribute_EntryPoint_With_RO_StorageBuffer) {
-    auto* s = Structure("Data", {
+    auto* s = Structure("Data", utils::Vector{
                                     Member("a", ty.i32()),
                                     Member("b", ty.f32()),
                                 });
 
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              GroupAndBinding(0, 0));
 
     auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("coord", "b"));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -414,30 +415,26 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_Attribute_Called_By_EntryPoint_With_Uniform) {
-    auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())});
-    auto* ubo = GlobalVar("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform,
-                          ast::AttributeList{
-                              create<ast::BindingAttribute>(0u),
-                              create<ast::GroupAttribute>(0u),
-                          });
+    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));
 
     Func("sub_func",
-         {
+         utils::Vector{
              Param("param", ty.f32()),
          },
          ty.f32(),
-         {
+         utils::Vector{
              Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")),
          });
 
     auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -464,34 +461,31 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_FunctionAttribute_Called_By_EntryPoint_With_RW_StorageBuffer) {
-    auto* s = Structure("Data", {
+    auto* s = Structure("Data", utils::Vector{
                                     Member("a", ty.i32()),
                                     Member("b", ty.f32()),
                                 });
 
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              GroupAndBinding(0, 0));
 
     Func("sub_func",
-         {
+         utils::Vector{
              Param("param", ty.f32()),
          },
          ty.f32(),
-         {
+         utils::Vector{
              Return(MemberAccessor("coord", "b")),
          });
 
     auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -519,34 +513,31 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_FunctionAttribute_Called_By_EntryPoint_With_RO_StorageBuffer) {
-    auto* s = Structure("Data", {
+    auto* s = Structure("Data", utils::Vector{
                                     Member("a", ty.i32()),
                                     Member("b", ty.f32()),
                                 });
 
     GlobalVar("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              GroupAndBinding(0, 0));
 
     Func("sub_func",
-         {
+         utils::Vector{
              Param("param", ty.f32()),
          },
          ty.f32(),
-         {
+         utils::Vector{
              Return(MemberAccessor("coord", "b")),
          });
 
     auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, Call("sub_func", 1_f));
 
-    Func("frag_main", {}, ty.void_(),
-         {
+    Func("frag_main", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(var),
              Return(),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -575,11 +566,11 @@
 
 TEST_F(MslGeneratorImplTest, Emit_Function_WithArrayParams) {
     Func("my_func",
-         {
+         utils::Vector{
              Param("a", ty.array<f32, 5>()),
          },
          ty.void_(),
-         {
+         utils::Vector{
              Return(),
          });
 
@@ -612,8 +603,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_Function_WithArrayReturn) {
-    Func("my_func", {}, ty.array<f32, 5>(),
-         {
+    Func("my_func", utils::Empty, ty.array<f32, 5>(),
+         utils::Vector{
              Return(Construct(ty.array<f32, 5>())),
          });
 
@@ -663,23 +654,20 @@
     //   return;
     // }
 
-    auto* s = Structure("Data", {Member("d", ty.f32())});
+    auto* s = Structure("Data", utils::Vector{Member("d", ty.f32())});
 
     GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
-                  create<ast::BindingAttribute>(0u),
-                  create<ast::GroupAttribute>(0u),
-              });
+              GroupAndBinding(0, 0));
 
     {
         auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
 
-        Func("a", {}, ty.void_(),
-             {
+        Func("a", utils::Empty, ty.void_(),
+             utils::Vector{
                  Decl(var),
                  Return(),
              },
-             {
+             utils::Vector{
                  Stage(ast::PipelineStage::kCompute),
                  WorkgroupSize(1_i),
              });
@@ -688,8 +676,12 @@
     {
         auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
 
-        Func("b", {}, ty.void_(), {Decl(var), Return()},
-             {
+        Func("b", utils::Empty, ty.void_(),
+             utils::Vector{
+                 Decl(var),
+                 Return(),
+             },
+             utils::Vector{
                  Stage(ast::PipelineStage::kCompute),
                  WorkgroupSize(1_i),
              });
diff --git a/src/tint/writer/msl/generator_impl_loop_test.cc b/src/tint/writer/msl/generator_impl_loop_test.cc
index 50b69d0..7c84db0 100644
--- a/src/tint/writer/msl/generator_impl_loop_test.cc
+++ b/src/tint/writer/msl/generator_impl_loop_test.cc
@@ -40,7 +40,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_LoopWithContinuing) {
-    Func("a_statement", {}, ty.void_(), {});
+    Func("a_statement", {}, ty.void_(), utils::Empty);
 
     auto* body = Block(create<ast::DiscardStatement>());
     auto* continuing = Block(CallStmt(Call("a_statement")));
@@ -62,7 +62,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_LoopNestedWithContinuing) {
-    Func("a_statement", {}, ty.void_(), {});
+    Func("a_statement", {}, ty.void_(), utils::Empty);
 
     GlobalVar("lhs", ty.f32(), ast::StorageClass::kPrivate);
     GlobalVar("rhs", ty.f32(), ast::StorageClass::kPrivate);
@@ -181,7 +181,7 @@
     //   return;
     // }
 
-    Func("f", {Param("i", ty.i32())}, ty.void_(), {});
+    Func("f", utils::Vector{Param("i", ty.i32())}, ty.void_(), utils::Empty);
     auto f = [&](auto&& expr) { return CallStmt(Call("f", expr)); };
 
     GlobalVar("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
@@ -257,7 +257,7 @@
     //   return;
     // }
 
-    Func("f", {Param("i", ty.i32())}, ty.void_(), {});
+    Func("f", utils::Vector{Param("i", ty.i32())}, ty.void_(), utils::Empty);
     auto f = [&](auto&& expr) { return CallStmt(Call("f", expr)); };
 
     GlobalVar("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
@@ -286,7 +286,7 @@
     //   return;
     // }
 
-    Func("a_statement", {}, ty.void_(), {});
+    Func("a_statement", {}, ty.void_(), utils::Empty);
 
     auto* f = For(Decl(Var("i", ty.i32())), true, Assign("i", Add("i", 1_i)),
                   Block(CallStmt(Call("a_statement"))));
@@ -312,7 +312,7 @@
     //   return;
     // }
 
-    Func("f", {Param("i", ty.i32())}, ty.void_(), {});
+    Func("f", utils::Vector{Param("i", ty.i32())}, ty.void_(), utils::Empty);
     auto f = [&](auto&& expr) { return CallStmt(Call("f", expr)); };
 
     GlobalVar("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
diff --git a/src/tint/writer/msl/generator_impl_member_accessor_test.cc b/src/tint/writer/msl/generator_impl_member_accessor_test.cc
index 96824a1..e189910 100644
--- a/src/tint/writer/msl/generator_impl_member_accessor_test.cc
+++ b/src/tint/writer/msl/generator_impl_member_accessor_test.cc
@@ -20,7 +20,7 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitExpression_MemberAccessor) {
-    GlobalVar("str", ty.Of(Structure("my_str", {Member("mem", ty.f32())})),
+    GlobalVar("str", ty.Of(Structure("my_str", utils::Vector{Member("mem", ty.f32())})),
               ast::StorageClass::kPrivate);
     auto* expr = MemberAccessor("str", "mem");
     WrapInFunction(expr);
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 4834b3d..054a0f4 100644
--- a/src/tint/writer/msl/generator_impl_module_constant_test.cc
+++ b/src/tint/writer/msl/generator_impl_module_constant_test.cc
@@ -24,7 +24,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_AInt) {
     auto* var = GlobalConst("G", nullptr, Expr(1_a));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -42,7 +42,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_AFloat) {
     auto* var = GlobalConst("G", nullptr, Expr(1._a));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -60,7 +60,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_i32) {
     auto* var = GlobalConst("G", nullptr, Expr(1_i));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -78,7 +78,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_u32) {
     auto* var = GlobalConst("G", nullptr, Expr(1_u));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -96,7 +96,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_f32) {
     auto* var = GlobalConst("G", nullptr, Expr(1_f));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -116,7 +116,7 @@
     Enable(ast::Extension::kF16);
 
     auto* var = GlobalConst("G", nullptr, Expr(1_h));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -134,7 +134,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_vec3_AInt) {
     auto* var = GlobalConst("G", nullptr, Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -152,7 +152,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_vec3_AFloat) {
     auto* var = GlobalConst("G", nullptr, Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -170,7 +170,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_vec3_f32) {
     auto* var = GlobalConst("G", nullptr, vec3<f32>(1_f, 2_f, 3_f));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -190,7 +190,7 @@
     Enable(ast::Extension::kF16);
 
     auto* var = GlobalConst("G", nullptr, vec3<f16>(1_h, 2_h, 3_h));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -209,7 +209,7 @@
 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", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -227,7 +227,7 @@
 
 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", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -247,7 +247,7 @@
     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", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -265,7 +265,7 @@
 
 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", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -300,7 +300,7 @@
                                       vec2<bool>(true, false),         //
                                       vec2<bool>(false, true),         //
                                       vec2<bool>(true, true)));
-    Func("f", {}, ty.void_(), {Decl(Let("l", nullptr, Expr(var)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", nullptr, Expr(var)))});
 
     GeneratorImpl& gen = Build();
 
@@ -331,7 +331,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_Override) {
     auto* var = Override("pos", ty.f32(), Expr(3_f),
-                         ast::AttributeList{
+                         utils::Vector{
                              Id(23),
                          });
 
@@ -343,7 +343,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_Override_NoId) {
     auto* var_a = Override("a", ty.f32(), nullptr,
-                           ast::AttributeList{
+                           utils::Vector{
                                Id(0),
                            });
     auto* var_b = Override("b", ty.f32(), nullptr);
diff --git a/src/tint/writer/msl/generator_impl_return_test.cc b/src/tint/writer/msl/generator_impl_return_test.cc
index 1443209..c9c3de7 100644
--- a/src/tint/writer/msl/generator_impl_return_test.cc
+++ b/src/tint/writer/msl/generator_impl_return_test.cc
@@ -35,7 +35,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_ReturnWithValue) {
     auto* r = Return(123_i);
-    Func("f", {}, ty.i32(), {r});
+    Func("f", utils::Empty, ty.i32(), utils::Vector{r});
 
     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 1514373..62ada06 100644
--- a/src/tint/writer/msl/generator_impl_sanitizer_test.cc
+++ b/src/tint/writer/msl/generator_impl_sanitizer_test.cc
@@ -26,19 +26,19 @@
 using MslSanitizerTest = TestHelper;
 
 TEST_F(MslSanitizerTest, Call_ArrayLength) {
-    auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
                       Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -81,22 +81,22 @@
 }
 
 TEST_F(MslSanitizerTest, Call_ArrayLength_OtherMembersInStruct) {
-    auto* s = Structure("my_struct", {
+    auto* s = Structure("my_struct", utils::Vector{
                                          Member(0, "z", ty.f32()),
                                          Member(4, "a", ty.array<f32>(4)),
                                      });
     GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
                       Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -141,9 +141,9 @@
 }
 
 TEST_F(MslSanitizerTest, Call_ArrayLength_ViaLets) {
-    auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
@@ -151,13 +151,13 @@
     auto* p = Let("p", nullptr, AddressOf("b"));
     auto* p2 = Let("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
 
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(p),
              Decl(p2),
              Decl(Var("len", ty.u32(), ast::StorageClass::kNone, Call("arrayLength", p2))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -201,25 +201,25 @@
 }
 
 TEST_F(MslSanitizerTest, Call_ArrayLength_ArrayLengthFromUniform) {
-    auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(0u),
               });
     GlobalVar("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(2u),
                   create<ast::GroupAttribute>(0u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
                       Add(Call("arrayLength", AddressOf(MemberAccessor("b", "a"))),
                           Call("arrayLength", AddressOf(MemberAccessor("c", "a")))))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -266,25 +266,25 @@
 }
 
 TEST_F(MslSanitizerTest, Call_ArrayLength_ArrayLengthFromUniformMissingBinding) {
-    auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(0u),
               });
     GlobalVar("c", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(2u),
                   create<ast::GroupAttribute>(0u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
                       Add(Call("arrayLength", AddressOf(MemberAccessor("b", "a"))),
                           Call("arrayLength", AddressOf(MemberAccessor("c", "a")))))),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
diff --git a/src/tint/writer/msl/generator_impl_static_assert_test.cc b/src/tint/writer/msl/generator_impl_static_assert_test.cc
new file mode 100644
index 0000000..5b8ca3f
--- /dev/null
+++ b/src/tint/writer/msl/generator_impl_static_assert_test.cc
@@ -0,0 +1,54 @@
+// 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/writer/msl/test_helper.h"
+
+using namespace tint::number_suffixes;  // NOLINT
+
+namespace tint::writer::msl {
+namespace {
+
+using MslGeneratorImplTest = TestHelper;
+
+TEST_F(MslGeneratorImplTest, Emit_GlobalStaticAssert) {
+    GlobalStaticAssert(true);
+
+    GeneratorImpl& gen = Build();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    // static asserts are not emitted
+    EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+)");
+}
+
+TEST_F(MslGeneratorImplTest, Emit_FunctionStaticAssert) {
+    Func("f", utils::Empty, ty.void_(), utils::Vector{StaticAssert(true)});
+
+    GeneratorImpl& gen = Build();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    // static asserts are not emitted
+    EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+void f() {
+}
+
+)");
+}
+
+}  // namespace
+}  // namespace tint::writer::msl
diff --git a/src/tint/writer/msl/generator_impl_switch_test.cc b/src/tint/writer/msl/generator_impl_switch_test.cc
index ce8087b..b327f34 100644
--- a/src/tint/writer/msl/generator_impl_switch_test.cc
+++ b/src/tint/writer/msl/generator_impl_switch_test.cc
@@ -25,19 +25,15 @@
     auto* cond = Var("cond", ty.i32());
 
     auto* def_body = Block(create<ast::BreakStatement>());
-    auto* def = create<ast::CaseStatement>(ast::CaseSelectorList{}, def_body);
+    auto* def = create<ast::CaseStatement>(utils::Empty, def_body);
 
-    ast::CaseSelectorList case_val;
-    case_val.push_back(Expr(5_i));
+    utils::Vector case_val{Expr(5_i)};
 
     auto* case_body = Block(create<ast::BreakStatement>());
 
     auto* case_stmt = create<ast::CaseStatement>(case_val, case_body);
 
-    ast::CaseStatementList body;
-    body.push_back(case_stmt);
-    body.push_back(def);
-
+    utils::Vector body{case_stmt, def};
     auto* s = create<ast::SwitchStatement>(Expr(cond), body);
     WrapInFunction(cond, s);
     GeneratorImpl& gen = Build();
diff --git a/src/tint/writer/msl/generator_impl_test.cc b/src/tint/writer/msl/generator_impl_test.cc
index 45010b2..63c56b0 100644
--- a/src/tint/writer/msl/generator_impl_test.cc
+++ b/src/tint/writer/msl/generator_impl_test.cc
@@ -32,8 +32,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Generate) {
-    Func("my_func", {}, ty.void_(), {},
-         {
+    Func("my_func", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
@@ -86,10 +86,17 @@
         MslBuiltinData{ast::BuiltinValue::kPointSize, "point_size"}));
 
 TEST_F(MslGeneratorImplTest, HasInvariantAttribute_True) {
-    auto* out = Structure("Out", {Member("pos", ty.vec4<f32>(),
-                                         {Builtin(ast::BuiltinValue::kPosition), Invariant()})});
-    Func("vert_main", {}, ty.Of(out), {Return(Construct(ty.Of(out)))},
-         {Stage(ast::PipelineStage::kVertex)});
+    auto* out = Structure("Out", utils::Vector{
+                                     Member("pos", ty.vec4<f32>(),
+                                            utils::Vector{
+                                                Builtin(ast::BuiltinValue::kPosition),
+                                                Invariant(),
+                                            }),
+                                 });
+    Func("vert_main", utils::Empty, ty.Of(out), utils::Vector{Return(Construct(ty.Of(out)))},
+         utils::Vector{
+             Stage(ast::PipelineStage::kVertex),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -110,17 +117,23 @@
 };
 
 vertex Out vert_main() {
-  return {};
+  return Out{};
 }
 
 )");
 }
 
 TEST_F(MslGeneratorImplTest, HasInvariantAttribute_False) {
-    auto* out =
-        Structure("Out", {Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)})});
-    Func("vert_main", {}, ty.Of(out), {Return(Construct(ty.Of(out)))},
-         {Stage(ast::PipelineStage::kVertex)});
+    auto* out = Structure("Out", utils::Vector{
+                                     Member("pos", ty.vec4<f32>(),
+                                            utils::Vector{
+                                                Builtin(ast::BuiltinValue::kPosition),
+                                            }),
+                                 });
+    Func("vert_main", utils::Empty, ty.Of(out), utils::Vector{Return(Construct(ty.Of(out)))},
+         utils::Vector{
+             Stage(ast::PipelineStage::kVertex),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -134,7 +147,7 @@
 };
 
 vertex Out vert_main() {
-  return {};
+  return Out{};
 }
 
 )");
@@ -142,8 +155,11 @@
 
 TEST_F(MslGeneratorImplTest, WorkgroupMatrix) {
     GlobalVar("m", ty.mat2x2<f32>(), ast::StorageClass::kWorkgroup);
-    Func("comp_main", {}, ty.void_(), {Decl(Let("x", nullptr, Expr("m")))},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
+    Func("comp_main", utils::Empty, ty.void_(), utils::Vector{Decl(Let("x", nullptr, Expr("m")))},
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1_i),
+         });
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -179,8 +195,11 @@
 
 TEST_F(MslGeneratorImplTest, WorkgroupMatrixInArray) {
     GlobalVar("m", ty.array(ty.mat2x2<f32>(), 4_i), ast::StorageClass::kWorkgroup);
-    Func("comp_main", {}, ty.void_(), {Decl(Let("x", nullptr, Expr("m")))},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
+    Func("comp_main", utils::Empty, ty.void_(), utils::Vector{Decl(Let("x", nullptr, Expr("m")))},
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1_i),
+         });
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -229,16 +248,19 @@
 }
 
 TEST_F(MslGeneratorImplTest, WorkgroupMatrixInStruct) {
-    Structure("S1", {
+    Structure("S1", utils::Vector{
                         Member("m1", ty.mat2x2<f32>()),
                         Member("m2", ty.mat4x4<f32>()),
                     });
-    Structure("S2", {
+    Structure("S2", utils::Vector{
                         Member("s", ty.type_name("S1")),
                     });
     GlobalVar("s", ty.type_name("S2"), ast::StorageClass::kWorkgroup);
-    Func("comp_main", {}, ty.void_(), {Decl(Let("x", nullptr, Expr("s")))},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
+    Func("comp_main", utils::Empty, ty.void_(), utils::Vector{Decl(Let("x", nullptr, Expr("s")))},
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1_i),
+         });
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -261,7 +283,7 @@
 
 void comp_main_inner(uint local_invocation_index, threadgroup S2* const tint_symbol_1) {
   {
-    S2 const tint_symbol = {};
+    S2 const tint_symbol = S2{};
     *(tint_symbol_1) = tint_symbol;
   }
   threadgroup_barrier(mem_flags::mem_threadgroup);
@@ -292,29 +314,41 @@
     GlobalVar("m7", ty.mat4x2<f32>(), ast::StorageClass::kWorkgroup);
     GlobalVar("m8", ty.mat4x3<f32>(), ast::StorageClass::kWorkgroup);
     GlobalVar("m9", ty.mat4x4<f32>(), ast::StorageClass::kWorkgroup);
-    Func("main1", {}, ty.void_(),
-         {
+    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"))),
          },
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
-    Func("main2", {}, ty.void_(),
-         {
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1_i),
+         });
+    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"))),
          },
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
-    Func("main3", {}, ty.void_(),
-         {
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1_i),
+         });
+    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"))),
          },
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
-    Func("main4_no_usages", {}, ty.void_(), {},
-         {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1_i),
+         });
+    Func("main4_no_usages", utils::Empty, ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1_i),
+         });
 
     GeneratorImpl& gen = SanitizeAndBuild();
 
diff --git a/src/tint/writer/msl/generator_impl_type_test.cc b/src/tint/writer/msl/generator_impl_type_test.cc
index 4ed5a76..a3e85a5 100644
--- a/src/tint/writer/msl/generator_impl_type_test.cc
+++ b/src/tint/writer/msl/generator_impl_type_test.cc
@@ -221,7 +221,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Struct) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", ty.f32()),
                              });
@@ -234,7 +234,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_StructDecl) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", ty.f32()),
                              });
@@ -252,37 +252,38 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_NonComposites) {
-    auto* s = Structure("S", {
-                                 Member("a", ty.i32(), {MemberSize(32)}),
-                                 Member("b", ty.f32(), {MemberAlign(128), MemberSize(128)}),
-                                 Member("c", ty.vec2<f32>()),
-                                 Member("d", ty.u32()),
-                                 Member("e", ty.vec3<f32>()),
-                                 Member("f", ty.u32()),
-                                 Member("g", ty.vec4<f32>()),
-                                 Member("h", ty.u32()),
-                                 Member("i", ty.mat2x2<f32>()),
-                                 Member("j", ty.u32()),
-                                 Member("k", ty.mat2x3<f32>()),
-                                 Member("l", ty.u32()),
-                                 Member("m", ty.mat2x4<f32>()),
-                                 Member("n", ty.u32()),
-                                 Member("o", ty.mat3x2<f32>()),
-                                 Member("p", ty.u32()),
-                                 Member("q", ty.mat3x3<f32>()),
-                                 Member("r", ty.u32()),
-                                 Member("s", ty.mat3x4<f32>()),
-                                 Member("t", ty.u32()),
-                                 Member("u", ty.mat4x2<f32>()),
-                                 Member("v", ty.u32()),
-                                 Member("w", ty.mat4x3<f32>()),
-                                 Member("x", ty.u32()),
-                                 Member("y", ty.mat4x4<f32>()),
-                                 Member("z", ty.f32()),
-                             });
+    auto* s =
+        Structure("S", utils::Vector{
+                           Member("a", ty.i32(), utils::Vector{MemberSize(32)}),
+                           Member("b", ty.f32(), utils::Vector{MemberAlign(128), MemberSize(128)}),
+                           Member("c", ty.vec2<f32>()),
+                           Member("d", ty.u32()),
+                           Member("e", ty.vec3<f32>()),
+                           Member("f", ty.u32()),
+                           Member("g", ty.vec4<f32>()),
+                           Member("h", ty.u32()),
+                           Member("i", ty.mat2x2<f32>()),
+                           Member("j", ty.u32()),
+                           Member("k", ty.mat2x3<f32>()),
+                           Member("l", ty.u32()),
+                           Member("m", ty.mat2x4<f32>()),
+                           Member("n", ty.u32()),
+                           Member("o", ty.mat3x2<f32>()),
+                           Member("p", ty.u32()),
+                           Member("q", ty.mat3x3<f32>()),
+                           Member("r", ty.u32()),
+                           Member("s", ty.mat3x4<f32>()),
+                           Member("t", ty.u32()),
+                           Member("u", ty.mat4x2<f32>()),
+                           Member("v", ty.u32()),
+                           Member("w", ty.mat4x3<f32>()),
+                           Member("x", ty.u32()),
+                           Member("y", ty.mat4x4<f32>()),
+                           Member("z", ty.f32()),
+                       });
 
     GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -371,18 +372,18 @@
 
 TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_Structures) {
     // inner_x: size(1024), align(512)
-    auto* inner_x = Structure("inner_x", {
+    auto* inner_x = Structure("inner_x", utils::Vector{
                                              Member("a", ty.i32()),
-                                             Member("b", ty.f32(), {MemberAlign(512)}),
+                                             Member("b", ty.f32(), utils::Vector{MemberAlign(512)}),
                                          });
 
     // inner_y: size(516), align(4)
-    auto* inner_y = Structure("inner_y", {
-                                             Member("a", ty.i32(), {MemberSize(512)}),
+    auto* inner_y = Structure("inner_y", utils::Vector{
+                                             Member("a", ty.i32(), utils::Vector{MemberSize(512)}),
                                              Member("b", ty.f32()),
                                          });
 
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", ty.Of(inner_x)),
                                  Member("c", ty.f32()),
@@ -391,7 +392,7 @@
                              });
 
     GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -461,9 +462,9 @@
 
 TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_ArrayDefaultStride) {
     // inner: size(1024), align(512)
-    auto* inner = Structure("inner", {
+    auto* inner = Structure("inner", utils::Vector{
                                          Member("a", ty.i32()),
-                                         Member("b", ty.f32(), {MemberAlign(512)}),
+                                         Member("b", ty.f32(), utils::Vector{MemberAlign(512)}),
                                      });
 
     // array_x: size(28), align(4)
@@ -475,7 +476,7 @@
     // array_z: size(4), align(4)
     auto* array_z = ty.array<f32>();
 
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", array_x),
                                  Member("c", ty.f32()),
@@ -485,7 +486,7 @@
                              });
 
     GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -564,14 +565,14 @@
     // array: size(64), align(16)
     auto* array = ty.array(ty.vec3<f32>(), 4_u);
 
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", array),
                                  Member("c", ty.i32()),
                              });
 
     GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -603,39 +604,39 @@
 }
 
 TEST_F(MslGeneratorImplTest, AttemptTintPadSymbolCollision) {
-    auto* s =
-        Structure("S", {
-                           // uses symbols tint_pad_[0..9] and tint_pad_[20..35]
-                           Member("tint_pad_2", ty.i32(), {MemberSize(32)}),
-                           Member("tint_pad_20", ty.f32(), {MemberAlign(128), MemberSize(128)}),
-                           Member("tint_pad_33", ty.vec2<f32>()),
-                           Member("tint_pad_1", ty.u32()),
-                           Member("tint_pad_3", ty.vec3<f32>()),
-                           Member("tint_pad_7", ty.u32()),
-                           Member("tint_pad_25", ty.vec4<f32>()),
-                           Member("tint_pad_5", ty.u32()),
-                           Member("tint_pad_27", ty.mat2x2<f32>()),
-                           Member("tint_pad_24", ty.u32()),
-                           Member("tint_pad_23", ty.mat2x3<f32>()),
-                           Member("tint_pad", ty.u32()),
-                           Member("tint_pad_8", ty.mat2x4<f32>()),
-                           Member("tint_pad_26", ty.u32()),
-                           Member("tint_pad_29", ty.mat3x2<f32>()),
-                           Member("tint_pad_6", ty.u32()),
-                           Member("tint_pad_22", ty.mat3x3<f32>()),
-                           Member("tint_pad_32", ty.u32()),
-                           Member("tint_pad_34", ty.mat3x4<f32>()),
-                           Member("tint_pad_35", ty.u32()),
-                           Member("tint_pad_30", ty.mat4x2<f32>()),
-                           Member("tint_pad_9", ty.u32()),
-                           Member("tint_pad_31", ty.mat4x3<f32>()),
-                           Member("tint_pad_28", ty.u32()),
-                           Member("tint_pad_4", ty.mat4x4<f32>()),
-                           Member("tint_pad_21", ty.f32()),
-                       });
+    auto* s = Structure(
+        "S", utils::Vector{
+                 // uses symbols tint_pad_[0..9] and tint_pad_[20..35]
+                 Member("tint_pad_2", ty.i32(), utils::Vector{MemberSize(32)}),
+                 Member("tint_pad_20", ty.f32(), utils::Vector{MemberAlign(128), MemberSize(128)}),
+                 Member("tint_pad_33", ty.vec2<f32>()),
+                 Member("tint_pad_1", ty.u32()),
+                 Member("tint_pad_3", ty.vec3<f32>()),
+                 Member("tint_pad_7", ty.u32()),
+                 Member("tint_pad_25", ty.vec4<f32>()),
+                 Member("tint_pad_5", ty.u32()),
+                 Member("tint_pad_27", ty.mat2x2<f32>()),
+                 Member("tint_pad_24", ty.u32()),
+                 Member("tint_pad_23", ty.mat2x3<f32>()),
+                 Member("tint_pad", ty.u32()),
+                 Member("tint_pad_8", ty.mat2x4<f32>()),
+                 Member("tint_pad_26", ty.u32()),
+                 Member("tint_pad_29", ty.mat3x2<f32>()),
+                 Member("tint_pad_6", ty.u32()),
+                 Member("tint_pad_22", ty.mat3x3<f32>()),
+                 Member("tint_pad_32", ty.u32()),
+                 Member("tint_pad_34", ty.mat3x4<f32>()),
+                 Member("tint_pad_35", ty.u32()),
+                 Member("tint_pad_30", ty.mat4x2<f32>()),
+                 Member("tint_pad_9", ty.u32()),
+                 Member("tint_pad_31", ty.mat4x3<f32>()),
+                 Member("tint_pad_28", ty.u32()),
+                 Member("tint_pad_4", ty.mat4x4<f32>()),
+                 Member("tint_pad_21", ty.f32()),
+             });
 
     GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -690,13 +691,13 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Struct_WithAttribute) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", ty.f32()),
                              });
 
     GlobalVar("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -863,7 +864,7 @@
 
     auto* s = ty.storage_texture(params.dim, ast::TexelFormat::kR32Float, ast::Access::kWrite);
     GlobalVar("test_var", s,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
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 10c5b3d..726e634 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
@@ -66,7 +66,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_AInt) {
     auto* C = Const("C", nullptr, Expr(1_a));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -84,7 +84,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_AFloat) {
     auto* C = Const("C", nullptr, Expr(1._a));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -102,7 +102,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_i32) {
     auto* C = Const("C", nullptr, Expr(1_i));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -120,7 +120,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_u32) {
     auto* C = Const("C", nullptr, Expr(1_u));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -138,7 +138,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_f32) {
     auto* C = Const("C", nullptr, Expr(1_f));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -158,7 +158,7 @@
     Enable(ast::Extension::kF16);
 
     auto* C = Const("C", nullptr, Expr(1_h));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -176,7 +176,7 @@
 
 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", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -194,7 +194,7 @@
 
 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", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -212,7 +212,7 @@
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_f32) {
     auto* C = Const("C", nullptr, vec3<f32>(1_f, 2_f, 3_f));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -232,7 +232,7 @@
     Enable(ast::Extension::kF16);
 
     auto* C = Const("C", nullptr, vec3<f16>(1_h, 2_h, 3_h));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -251,7 +251,7 @@
 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", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -269,7 +269,7 @@
 
 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", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -289,7 +289,7 @@
     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", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -307,7 +307,7 @@
 
 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", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -342,7 +342,7 @@
                               vec2<bool>(true, false),         //
                               vec2<bool>(false, true),         //
                               vec2<bool>(true, true)));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", nullptr, Expr(C)))});
 
     GeneratorImpl& gen = Build();
 
@@ -385,7 +385,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Struct) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.f32()),
                                  Member("b", ty.f32()),
                              });
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index 488cbf8..933b562 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -2464,7 +2464,7 @@
             }
             // Runtime array must be the last member in the structure
             params.push_back(
-                Operand(uint32_t(type->As<sem::Struct>()->Declaration()->members.size() - 1)));
+                Operand(uint32_t(type->As<sem::Struct>()->Declaration()->members.Length() - 1)));
 
             if (!push_function_inst(spv::Op::OpArrayLength, params)) {
                 return 0;
@@ -3486,7 +3486,7 @@
         //    if (cond) {} else {break;}
         //  }
         auto is_just_a_break = [](const ast::BlockStatement* block) {
-            return block && (block->statements.size() == 1) &&
+            return block && (block->statements.Length() == 1) &&
                    block->Last()->Is<ast::BreakStatement>();
         };
         if (is_just_a_break(stmt->body) && stmt->else_statement == nullptr) {
@@ -3576,7 +3576,7 @@
     // source. Each fallthrough goes to the next case entry, so is a forward
     // branch, otherwise the branch is to the merge block which comes after
     // the switch statement.
-    for (uint32_t i = 0; i < body.size(); i++) {
+    for (uint32_t i = 0; i < body.Length(); i++) {
         auto* item = body[i];
 
         if (item->IsDefault()) {
@@ -3591,7 +3591,7 @@
         }
 
         if (LastIsFallthrough(item->body)) {
-            if (i == (body.size() - 1)) {
+            if (i == (body.Length() - 1)) {
                 // This case is caught by Resolver validation
                 TINT_UNREACHABLE(Writer, builder_.Diagnostics());
                 return false;
@@ -3723,12 +3723,8 @@
 bool Builder::GenerateStatement(const ast::Statement* stmt) {
     return Switch(
         stmt, [&](const ast::AssignmentStatement* a) { return GenerateAssignStatement(a); },
-        [&](const ast::BlockStatement* b) {  //
-            return GenerateBlockStatement(b);
-        },
-        [&](const ast::BreakStatement* b) {  //
-            return GenerateBreakStatement(b);
-        },
+        [&](const ast::BlockStatement* b) { return GenerateBlockStatement(b); },
+        [&](const ast::BreakStatement* b) { return GenerateBreakStatement(b); },
         [&](const ast::CallStatement* c) { return GenerateCallExpression(c->expr) != 0; },
         [&](const ast::ContinueStatement* c) { return GenerateContinueStatement(c); },
         [&](const ast::DiscardStatement* d) { return GenerateDiscardStatement(d); },
@@ -3736,19 +3732,14 @@
             // Do nothing here, the fallthrough gets handled by the switch code.
             return true;
         },
-        [&](const ast::IfStatement* i) {  //
-            return GenerateIfStatement(i);
-        },
-        [&](const ast::LoopStatement* l) {  //
-            return GenerateLoopStatement(l);
-        },
-        [&](const ast::ReturnStatement* r) {  //
-            return GenerateReturnStatement(r);
-        },
-        [&](const ast::SwitchStatement* s) {  //
-            return GenerateSwitchStatement(s);
-        },
+        [&](const ast::IfStatement* i) { return GenerateIfStatement(i); },
+        [&](const ast::LoopStatement* l) { return GenerateLoopStatement(l); },
+        [&](const ast::ReturnStatement* r) { return GenerateReturnStatement(r); },
+        [&](const ast::SwitchStatement* s) { return GenerateSwitchStatement(s); },
         [&](const ast::VariableDeclStatement* v) { return GenerateVariableDeclStatement(v); },
+        [&](const ast::StaticAssert*) {
+            return true;  // Not emitted
+        },
         [&](Default) {
             error_ = "Unknown statement: " + std::string(stmt->TypeInfo().name);
             return false;
@@ -4121,6 +4112,8 @@
             return SpvStorageClassUniform;
         case ast::StorageClass::kWorkgroup:
             return SpvStorageClassWorkgroup;
+        case ast::StorageClass::kPushConstant:
+            return SpvStorageClassPushConstant;
         case ast::StorageClass::kHandle:
             return SpvStorageClassUniformConstant;
         case ast::StorageClass::kStorage:
diff --git a/src/tint/writer/spirv/builder_accessor_expression_test.cc b/src/tint/writer/spirv/builder_accessor_expression_test.cc
index 23dc783..0c0dc3f 100644
--- a/src/tint/writer/spirv/builder_accessor_expression_test.cc
+++ b/src/tint/writer/spirv/builder_accessor_expression_test.cc
@@ -162,10 +162,10 @@
 
 TEST_F(BuilderTest, Const_IndexAccessor_Vector2) {
     // let ary : vec3<i32>(1, 2, 3);
-    // var x = ary[1i + 2i];
+    // 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, 2_i)));
+    auto* x = Var("x", nullptr, IndexAccessor(ary, Add(1_i, 1_i)));
     WrapInFunction(ary, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -180,14 +180,14 @@
 %8 = OpConstant %6 2
 %9 = OpConstant %6 3
 %10 = OpConstantComposite %5 %7 %8 %9
-%14 = OpTypePointer Function %6
-%15 = OpConstantNull %6
+%13 = OpTypePointer Function %6
+%14 = OpConstantNull %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%13 = OpVariable %14 Function %15
+    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%12 = OpVariable %13 Function %14
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%11 = OpIAdd %6 %7 %8
-%12 = OpVectorExtractDynamic %6 %10 %11
-OpStore %13 %12
+    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+              R"(%11 = OpCompositeExtract %6 %10 2
+OpStore %12 %11
 OpReturn
 )");
 
@@ -196,10 +196,10 @@
 
 TEST_F(BuilderTest, Runtime_IndexAccessor_Vector2) {
     // var ary : vec3<f32>;
-    // var x = ary[1i + 2i];
+    // var x = ary[1i + 1i];
 
     auto* ary = Var("ary", ty.vec3<f32>());
-    auto* x = Var("x", nullptr, IndexAccessor(ary, Add(1_i, 2_i)));
+    auto* x = Var("x", nullptr, IndexAccessor(ary, Add(1_i, 1_i)));
     WrapInFunction(ary, x);
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -213,18 +213,16 @@
 %6 = OpTypePointer Function %7
 %9 = OpConstantNull %7
 %10 = OpTypeInt 32 1
-%11 = OpConstant %10 1
-%12 = OpConstant %10 2
-%14 = OpTypePointer Function %8
-%18 = OpConstantNull %8
+%11 = OpConstant %10 2
+%12 = OpTypePointer Function %8
+%16 = OpConstantNull %8
 )");
     EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %9
-%17 = OpVariable %14 Function %18
+%15 = OpVariable %12 Function %16
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%13 = OpIAdd %10 %11 %12
-%15 = OpAccessChain %14 %5 %13
-%16 = OpLoad %8 %15
-OpStore %17 %16
+    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%13 = OpAccessChain %12 %5 %11
+%14 = OpLoad %8 %13
+OpStore %15 %14
 OpReturn
 )");
 
@@ -902,7 +900,7 @@
     // var ident : my_struct
     // ident.b
 
-    auto* s = Structure("my_struct", {
+    auto* s = Structure("my_struct", utils::Vector{
                                          Member("a", ty.f32()),
                                          Member("b", ty.f32()),
                                      });
@@ -947,12 +945,12 @@
     //
     // var ident : my_struct
     // ident.inner.a
-    auto* inner_struct = Structure("Inner", {
+    auto* inner_struct = Structure("Inner", utils::Vector{
                                                 Member("a", ty.f32()),
                                                 Member("b", ty.f32()),
                                             });
 
-    auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
+    auto* s_type = Structure("my_struct", utils::Vector{Member("inner", ty.Of(inner_struct))});
 
     auto* var = Var("ident", ty.Of(s_type));
     auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "b");
@@ -993,7 +991,7 @@
     // let ident : my_struct = my_struct();
     // ident.b
 
-    auto* s = Structure("my_struct", {
+    auto* s = Structure("my_struct", utils::Vector{
                                          Member("a", ty.f32()),
                                          Member("b", ty.f32()),
                                      });
@@ -1032,12 +1030,12 @@
     //
     // let ident : my_struct = my_struct();
     // ident.inner.a
-    auto* inner_struct = Structure("Inner", {
+    auto* inner_struct = Structure("Inner", utils::Vector{
                                                 Member("a", ty.f32()),
                                                 Member("b", ty.f32()),
                                             });
 
-    auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
+    auto* s_type = Structure("my_struct", utils::Vector{Member("inner", ty.Of(inner_struct))});
 
     auto* var = Let("ident", ty.Of(s_type),
                     Construct(ty.Of(s_type), Construct(ty.Of(inner_struct), 0_f, 0_f)));
@@ -1077,13 +1075,13 @@
     //
     // var ident : my_struct
     // ident.inner.a
-    auto* inner_struct = Structure("Inner", {
+    auto* inner_struct = Structure("Inner", utils::Vector{
                                                 Member("a", ty.f32()),
                                                 Member("b", ty.f32()),
                                             });
 
     auto* alias = Alias("Alias", ty.Of(inner_struct));
-    auto* s_type = Structure("Outer", {Member("inner", ty.Of(alias))});
+    auto* s_type = Structure("Outer", utils::Vector{Member("inner", ty.Of(alias))});
 
     auto* var = Var("ident", ty.Of(s_type));
     auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "a");
@@ -1125,12 +1123,12 @@
     //
     // var ident : my_struct
     // ident.inner.a = 2.0f;
-    auto* inner_struct = Structure("Inner", {
+    auto* inner_struct = Structure("Inner", utils::Vector{
                                                 Member("a", ty.f32()),
                                                 Member("b", ty.f32()),
                                             });
 
-    auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
+    auto* s_type = Structure("my_struct", utils::Vector{Member("inner", ty.Of(inner_struct))});
 
     auto* var = Var("ident", ty.Of(s_type));
     auto* expr = Assign(MemberAccessor(MemberAccessor("ident", "inner"), "a"), Expr(2_f));
@@ -1174,12 +1172,12 @@
     // var ident : my_struct
     // var store : f32 = ident.inner.a
 
-    auto* inner_struct = Structure("Inner", {
+    auto* inner_struct = Structure("Inner", utils::Vector{
                                                 Member("a", ty.f32()),
                                                 Member("b", ty.f32()),
                                             });
 
-    auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
+    auto* s_type = Structure("my_struct", utils::Vector{Member("inner", ty.Of(inner_struct))});
 
     auto* var = Var("ident", ty.Of(s_type));
     auto* store = Var("store", ty.f32());
@@ -1385,11 +1383,11 @@
     // var index : array<A, 2u>
     // index[0i].foo[2i].bar.baz.yx
 
-    auto* c_type = Structure("C", {Member("baz", ty.vec3<f32>())});
+    auto* c_type = Structure("C", utils::Vector{Member("baz", ty.vec3<f32>())});
 
-    auto* b_type = Structure("B", {Member("bar", ty.Of(c_type))});
+    auto* b_type = Structure("B", utils::Vector{Member("bar", ty.Of(c_type))});
     auto* b_ary_type = ty.array(ty.Of(b_type), 3_u);
-    auto* a_type = Structure("A", {Member("foo", b_ary_type)});
+    auto* a_type = Structure("A", utils::Vector{Member("foo", b_ary_type)});
 
     auto* a_ary_type = ty.array(ty.Of(a_type), 2_u);
     auto* var = Var("index", a_ary_type);
diff --git a/src/tint/writer/spirv/builder_assign_test.cc b/src/tint/writer/spirv/builder_assign_test.cc
index 526ede8..be30dc7 100644
--- a/src/tint/writer/spirv/builder_assign_test.cc
+++ b/src/tint/writer/spirv/builder_assign_test.cc
@@ -172,7 +172,7 @@
     // var ident : my_struct
     // ident.b = 4.0;
 
-    auto* s = Structure("my_struct", {
+    auto* s = Structure("my_struct", utils::Vector{
                                          Member("a", ty.f32()),
                                          Member("b", ty.f32()),
                                      });
diff --git a/src/tint/writer/spirv/builder_binary_expression_test.cc b/src/tint/writer/spirv/builder_binary_expression_test.cc
index 9a2709e..922473e 100644
--- a/src/tint/writer/spirv/builder_binary_expression_test.cc
+++ b/src/tint/writer/spirv/builder_binary_expression_test.cc
@@ -1219,30 +1219,48 @@
 
 enum class Type { f32, f16, i32, u32 };
 static const ast::Expression* MakeVectorExpr(ProgramBuilder* builder, Type type) {
+    auto name = builder->Symbols().New();
     switch (type) {
         case Type::f32:
-            return builder->vec3<f32>(1_f, 1_f, 1_f);
+            builder->GlobalVar(name, builder->ty.vec3<f32>(), builder->vec3<f32>(1_f, 1_f, 1_f),
+                               ast::StorageClass::kPrivate);
+            break;
         case Type::f16:
-            return builder->vec3<f16>(1_h, 1_h, 1_h);
+            builder->GlobalVar(name, builder->ty.vec3<f16>(), builder->vec3<f16>(1_h, 1_h, 1_h),
+                               ast::StorageClass::kPrivate);
+            break;
         case Type::i32:
-            return builder->vec3<i32>(1_i, 1_i, 1_i);
+            builder->GlobalVar(name, builder->ty.vec3<i32>(), builder->vec3<i32>(1_i, 1_i, 1_i),
+                               ast::StorageClass::kPrivate);
+            break;
         case Type::u32:
-            return builder->vec3<u32>(1_u, 1_u, 1_u);
+            builder->GlobalVar(name, builder->ty.vec3<u32>(), builder->vec3<u32>(1_u, 1_u, 1_u),
+                               ast::StorageClass::kPrivate);
+            break;
     }
-    return nullptr;
+    return builder->Expr(name);
 }
 static const ast::Expression* MakeScalarExpr(ProgramBuilder* builder, Type type) {
+    auto name = builder->Symbols().New();
     switch (type) {
         case Type::f32:
-            return builder->Expr(1_f);
+            builder->GlobalVar(name, builder->ty.f32(), builder->Expr(1_f),
+                               ast::StorageClass::kPrivate);
+            break;
         case Type::f16:
-            return builder->Expr(1_h);
+            builder->GlobalVar(name, builder->ty.f16(), builder->Expr(1_h),
+                               ast::StorageClass::kPrivate);
+            break;
         case Type::i32:
-            return builder->Expr(1_i);
+            builder->GlobalVar(name, builder->ty.i32(), builder->Expr(1_i),
+                               ast::StorageClass::kPrivate);
+            break;
         case Type::u32:
-            return builder->Expr(1_u);
+            builder->GlobalVar(name, builder->ty.u32(), builder->Expr(1_u),
+                               ast::StorageClass::kPrivate);
+            break;
     }
-    return nullptr;
+    return builder->Expr(name);
 }
 static std::string OpTypeDecl(Type type) {
     switch (type) {
@@ -1310,26 +1328,33 @@
 
     spirv::Builder& b = Build();
     ASSERT_TRUE(b.Build()) << b.error();
-
     EXPECT_EQ(DumpBuilder(b), capability_decl + R"(
 OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %3 "test_function"
-OpExecutionMode %3 LocalSize 1 1 1
-OpName %3 "test_function"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = )" + op_type_decl + R"(
-%5 = OpTypeVector %6 3
-%7 = OpConstant %6 )" + constant_value +
+OpEntryPoint GLCompute %11 "test_function"
+OpExecutionMode %11 LocalSize 1 1 1
+OpName %5 "tint_symbol"
+OpName %7 "tint_symbol_1"
+OpName %11 "test_function"
+%2 = )" + op_type_decl + R"(
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 )" + constant_value +
                                   R"(
-%8 = OpConstantComposite %5 %7 %7 %7
-%11 = OpTypePointer Function %5
-%12 = OpConstantNull %5
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%10 = OpVariable %11 Function %12
-%13 = OpCompositeConstruct %5 %7 %7 %7
-%9 = )" + param.name + R"( %5 %8 %13
+%4 = OpConstantComposite %1 %3 %3 %3
+%6 = OpTypePointer Private %1
+%5 = OpVariable %6 Private %4
+%8 = OpTypePointer Private %2
+%7 = OpVariable %8 Private %3
+%10 = OpTypeVoid
+%9 = OpTypeFunction %10
+%17 = OpTypePointer Function %1
+%18 = OpConstantNull %1
+%11 = OpFunction %10 None %9
+%12 = OpLabel
+%16 = OpVariable %17 Function %18
+%13 = OpLoad %1 %5
+%14 = OpLoad %2 %7
+%19 = OpCompositeConstruct %1 %14 %14 %14
+%15 = )" + param.name + R"( %1 %13 %19
 OpReturn
 OpFunctionEnd
 )");
@@ -1355,26 +1380,33 @@
 
     spirv::Builder& b = Build();
     ASSERT_TRUE(b.Build()) << b.error();
-
     EXPECT_EQ(DumpBuilder(b), capability_decl + R"(
 OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %3 "test_function"
-OpExecutionMode %3 LocalSize 1 1 1
-OpName %3 "test_function"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%5 = )" + op_type_decl + R"(
-%6 = OpConstant %5 )" + constant_value +
+OpEntryPoint GLCompute %11 "test_function"
+OpExecutionMode %11 LocalSize 1 1 1
+OpName %3 "tint_symbol"
+OpName %7 "tint_symbol_1"
+OpName %11 "test_function"
+%1 = )" + op_type_decl + R"(
+%2 = OpConstant %1 )" + constant_value +
                                   R"(
-%7 = OpTypeVector %5 3
-%8 = OpConstantComposite %7 %6 %6 %6
-%11 = OpTypePointer Function %7
-%12 = OpConstantNull %7
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%10 = OpVariable %11 Function %12
-%13 = OpCompositeConstruct %7 %6 %6 %6
-%9 = )" + param.name + R"( %7 %13 %8
+%4 = OpTypePointer Private %1
+%3 = OpVariable %4 Private %2
+%5 = OpTypeVector %1 3
+%6 = OpConstantComposite %5 %2 %2 %2
+%8 = OpTypePointer Private %5
+%7 = OpVariable %8 Private %6
+%10 = OpTypeVoid
+%9 = OpTypeFunction %10
+%17 = OpTypePointer Function %5
+%18 = OpConstantNull %5
+%11 = OpFunction %10 None %9
+%12 = OpLabel
+%16 = OpVariable %17 Function %18
+%13 = OpLoad %1 %3
+%14 = OpLoad %5 %7
+%19 = OpCompositeConstruct %5 %13 %13 %13
+%15 = )" + param.name + R"( %5 %19 %14
 OpReturn
 OpFunctionEnd
 )");
@@ -1428,22 +1460,29 @@
 
     spirv::Builder& b = Build();
     ASSERT_TRUE(b.Build()) << b.error();
-
     EXPECT_EQ(DumpBuilder(b), capability_decl + R"(
 OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %3 "test_function"
-OpExecutionMode %3 LocalSize 1 1 1
-OpName %3 "test_function"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%6 = )" + op_type_decl + R"(
-%5 = OpTypeVector %6 3
-%7 = OpConstant %6 )" + constant_value +
+OpEntryPoint GLCompute %11 "test_function"
+OpExecutionMode %11 LocalSize 1 1 1
+OpName %5 "tint_symbol"
+OpName %7 "tint_symbol_1"
+OpName %11 "test_function"
+%2 = )" + op_type_decl + R"(
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 )" + constant_value +
                                   R"(
-%8 = OpConstantComposite %5 %7 %7 %7
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%9 = OpVectorTimesScalar %5 %8 %7
+%4 = OpConstantComposite %1 %3 %3 %3
+%6 = OpTypePointer Private %1
+%5 = OpVariable %6 Private %4
+%8 = OpTypePointer Private %2
+%7 = OpVariable %8 Private %3
+%10 = OpTypeVoid
+%9 = OpTypeFunction %10
+%11 = OpFunction %10 None %9
+%12 = OpLabel
+%13 = OpLoad %1 %5
+%14 = OpLoad %2 %7
+%15 = OpVectorTimesScalar %1 %13 %14
 OpReturn
 OpFunctionEnd
 )");
@@ -1469,22 +1508,29 @@
 
     spirv::Builder& b = Build();
     ASSERT_TRUE(b.Build()) << b.error();
-
     EXPECT_EQ(DumpBuilder(b), capability_decl + R"(
 OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %3 "test_function"
-OpExecutionMode %3 LocalSize 1 1 1
-OpName %3 "test_function"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%5 = )" + op_type_decl + R"(
-%6 = OpConstant %5 )" + constant_value +
+OpEntryPoint GLCompute %11 "test_function"
+OpExecutionMode %11 LocalSize 1 1 1
+OpName %3 "tint_symbol"
+OpName %7 "tint_symbol_1"
+OpName %11 "test_function"
+%1 = )" + op_type_decl + R"(
+%2 = OpConstant %1 )" + constant_value +
                                   R"(
-%7 = OpTypeVector %5 3
-%8 = OpConstantComposite %7 %6 %6 %6
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%9 = OpVectorTimesScalar %7 %8 %6
+%4 = OpTypePointer Private %1
+%3 = OpVariable %4 Private %2
+%5 = OpTypeVector %1 3
+%6 = OpConstantComposite %5 %2 %2 %2
+%8 = OpTypePointer Private %5
+%7 = OpVariable %8 Private %6
+%10 = OpTypeVoid
+%9 = OpTypeFunction %10
+%11 = OpFunction %10 None %9
+%12 = OpLabel
+%13 = OpLoad %1 %3
+%14 = OpLoad %5 %7
+%15 = OpVectorTimesScalar %5 %14 %13
 OpReturn
 OpFunctionEnd
 )");
@@ -1502,22 +1548,31 @@
 
 enum class Type { f32, f16 };
 static const ast::Expression* MakeMat3x4Expr(ProgramBuilder* builder, Type type) {
+    auto name = builder->Symbols().New();
     switch (type) {
         case Type::f32:
-            return builder->mat3x4<f32>();
+            builder->GlobalVar(name, builder->ty.mat3x4<f32>(), builder->mat3x4<f32>(),
+                               ast::StorageClass::kPrivate);
+            break;
         case Type::f16:
-            return builder->mat3x4<f16>();
+            builder->GlobalVar(name, builder->ty.mat3x4<f16>(), builder->mat3x4<f16>(),
+                               ast::StorageClass::kPrivate);
+            break;
     }
-    return nullptr;
+    return builder->Expr(name);
 }
 static const ast::Expression* MakeMat4x3Expr(ProgramBuilder* builder, Type type) {
+    auto name = builder->Symbols().New();
     switch (type) {
         case Type::f32:
-            return builder->mat4x3<f32>();
+            builder->GlobalVar(name, builder->ty.mat4x3<f32>(), builder->mat4x3<f32>(),
+                               ast::StorageClass::kPrivate);
+            break;
         case Type::f16:
-            return builder->mat4x3<f16>();
+            builder->GlobalVar(name, builder->ty.mat4x3<f16>(), builder->mat4x3<f16>(),
+                               ast::StorageClass::kPrivate);
     }
-    return nullptr;
+    return builder->Expr(name);
 }
 static std::string OpTypeDecl(Type type) {
     switch (type) {
@@ -1567,30 +1622,36 @@
 
     spirv::Builder& b = Build();
     ASSERT_TRUE(b.Build()) << b.error();
-
     EXPECT_EQ(DumpBuilder(b), capability_decl + R"(
 OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %3 "test_function"
-OpExecutionMode %3 LocalSize 1 1 1
-OpName %3 "test_function"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = )" + op_type_decl + R"(
-%6 = OpTypeVector %7 4
-%5 = OpTypeMatrix %6 3
-%8 = OpConstantNull %5
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%10 = OpCompositeExtract %6 %8 0
-%11 = OpCompositeExtract %6 %8 0
-%12 = )" + param.name + R"( %6 %10 %11
-%13 = OpCompositeExtract %6 %8 1
-%14 = OpCompositeExtract %6 %8 1
-%15 = )" + param.name + R"( %6 %13 %14
-%16 = OpCompositeExtract %6 %8 2
-%17 = OpCompositeExtract %6 %8 2
-%18 = )" + param.name + R"( %6 %16 %17
-%19 = OpCompositeConstruct %5 %12 %15 %18
+OpEntryPoint GLCompute %10 "test_function"
+OpExecutionMode %10 LocalSize 1 1 1
+OpName %5 "tint_symbol"
+OpName %7 "tint_symbol_1"
+OpName %10 "test_function"
+%3 = )" + op_type_decl + R"(
+%2 = OpTypeVector %3 4
+%1 = OpTypeMatrix %2 3
+%4 = OpConstantNull %1
+%6 = OpTypePointer Private %1
+%5 = OpVariable %6 Private %4
+%7 = OpVariable %6 Private %4
+%9 = OpTypeVoid
+%8 = OpTypeFunction %9
+%10 = OpFunction %9 None %8
+%11 = OpLabel
+%12 = OpLoad %1 %5
+%13 = OpLoad %1 %7
+%15 = OpCompositeExtract %2 %12 0
+%16 = OpCompositeExtract %2 %13 0
+%17 = )" + param.name + R"( %2 %15 %16
+%18 = OpCompositeExtract %2 %12 1
+%19 = OpCompositeExtract %2 %13 1
+%20 = )" + param.name + R"( %2 %18 %19
+%21 = OpCompositeExtract %2 %12 2
+%22 = OpCompositeExtract %2 %13 2
+%23 = )" + param.name + R"( %2 %21 %22
+%24 = OpCompositeConstruct %1 %17 %20 %23
 OpReturn
 OpFunctionEnd
 )");
@@ -1624,25 +1685,32 @@
 
     spirv::Builder& b = Build();
     ASSERT_TRUE(b.Build()) << b.error();
-
     EXPECT_EQ(DumpBuilder(b), capability_decl + R"(
 OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %3 "test_function"
-OpExecutionMode %3 LocalSize 1 1 1
-OpName %3 "test_function"
-%2 = OpTypeVoid
-%1 = OpTypeFunction %2
-%7 = )" + op_type_decl + R"(
-%6 = OpTypeVector %7 4
-%5 = OpTypeMatrix %6 3
-%8 = OpConstantNull %5
-%10 = OpTypeVector %7 3
-%9 = OpTypeMatrix %10 4
-%11 = OpConstantNull %9
-%13 = OpTypeMatrix %6 4
-%3 = OpFunction %2 None %1
-%4 = OpLabel
-%12 = OpMatrixTimesMatrix %13 %8 %11
+OpEntryPoint GLCompute %14 "test_function"
+OpExecutionMode %14 LocalSize 1 1 1
+OpName %5 "tint_symbol"
+OpName %10 "tint_symbol_1"
+OpName %14 "test_function"
+%3 = )" + op_type_decl + R"(
+%2 = OpTypeVector %3 4
+%1 = OpTypeMatrix %2 3
+%4 = OpConstantNull %1
+%6 = OpTypePointer Private %1
+%5 = OpVariable %6 Private %4
+%8 = OpTypeVector %3 3
+%7 = OpTypeMatrix %8 4
+%9 = OpConstantNull %7
+%11 = OpTypePointer Private %7
+%10 = OpVariable %11 Private %9
+%13 = OpTypeVoid
+%12 = OpTypeFunction %13
+%19 = OpTypeMatrix %2 4
+%14 = OpFunction %13 None %12
+%15 = OpLabel
+%16 = OpLoad %1 %5
+%17 = OpLoad %7 %10
+%18 = OpMatrixTimesMatrix %19 %16 %17
 OpReturn
 OpFunctionEnd
 )");
diff --git a/src/tint/writer/spirv/builder_builtin_test.cc b/src/tint/writer/spirv/builder_builtin_test.cc
index cb93784..52254d2 100644
--- a/src/tint/writer/spirv/builder_builtin_test.cc
+++ b/src/tint/writer/spirv/builder_builtin_test.cc
@@ -44,13 +44,13 @@
     auto* t = ty.depth_texture(ast::TextureDimension::k2d);
 
     auto* tex = GlobalVar("texture", t,
-                          ast::AttributeList{
+                          utils::Vector{
                               create<ast::BindingAttribute>(0u),
                               create<ast::GroupAttribute>(0u),
                           });
 
     auto* sampler = GlobalVar("sampler", s,
-                              ast::AttributeList{
+                              utils::Vector{
                                   create<ast::BindingAttribute>(1u),
                                   create<ast::GroupAttribute>(0u),
                               });
@@ -58,8 +58,16 @@
     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);
 
-    Func("f1", {}, ty.void_(), {CallStmt(expr1)}, {});
-    Func("f2", {}, ty.void_(), {CallStmt(expr2)}, {});
+    Func("f1", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(expr1),
+         },
+         utils::Empty);
+    Func("f2", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(expr2),
+         },
+         utils::Empty);
 
     spirv::Builder& b = Build();
 
@@ -100,8 +108,8 @@
 TEST_F(BuiltinBuilderTest, Call_GLSLMethod_WithLoad_f32) {
     auto* var = GlobalVar("ident", ty.f32(), ast::StorageClass::kPrivate);
     auto* expr = Call("round", "ident");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -136,8 +144,8 @@
 
     auto* var = GlobalVar("ident", ty.f16(), ast::StorageClass::kPrivate);
     auto* expr = Call("round", "ident");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -175,8 +183,8 @@
     auto param = GetParam();
     auto* var = GlobalVar("v", ty.bool_(), ast::StorageClass::kPrivate);
     auto* expr = Call(param.name, "v");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -201,8 +209,8 @@
     auto param = GetParam();
     auto* var = GlobalVar("v", ty.vec3<bool>(), ast::StorageClass::kPrivate);
     auto* expr = Call(param.name, "v");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -236,8 +244,8 @@
 
     auto* bool_v3 = GlobalVar("bool_v3", ty.vec3<bool>(), ast::StorageClass::kPrivate);
     auto* expr = Call("select", "v3", "v3", "bool_v3");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -275,19 +283,21 @@
 namespace array_builtin_tests {
 
 TEST_F(BuiltinBuilderTest, Call_ArrayLength) {
-    auto* s = Structure("my_struct", {Member("a", ty.array<f32>(4))});
+    auto* s = Structure("my_struct", utils::Vector{
+                                         Member("a", ty.array<f32>(4)),
+                                     });
     GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
     auto* expr = Call("arrayLength", AddressOf(MemberAccessor("b", "a")));
 
-    Func("a_func", {}, ty.void_(),
-         ast::StatementList{
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              CallStmt(expr),
          },
-         ast::AttributeList{
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -319,22 +329,22 @@
 }
 
 TEST_F(BuiltinBuilderTest, Call_ArrayLength_OtherMembersInStruct) {
-    auto* s = Structure("my_struct", {
+    auto* s = Structure("my_struct", utils::Vector{
                                          Member("z", ty.f32()),
                                          Member(4, "a", ty.array<f32>(4)),
                                      });
     GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
     auto* expr = Call("arrayLength", AddressOf(MemberAccessor("b", "a")));
 
-    Func("a_func", {}, ty.void_(),
-         ast::StatementList{
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              CallStmt(expr),
          },
-         ast::AttributeList{
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -366,9 +376,11 @@
 }
 
 TEST_F(BuiltinBuilderTest, Call_ArrayLength_ViaLets) {
-    auto* s = Structure("my_struct", {Member("a", ty.array<f32>(4))});
+    auto* s = Structure("my_struct", utils::Vector{
+                                         Member("a", ty.array<f32>(4)),
+                                     });
     GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
@@ -377,13 +389,13 @@
     auto* p2 = Let("p2", nullptr, AddressOf(MemberAccessor(Deref(p), "a")));
     auto* expr = Call("arrayLength", p2);
 
-    Func("a_func", {}, ty.void_(),
-         ast::StatementList{
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(p),
              Decl(p2),
              CallStmt(expr),
          },
-         ast::AttributeList{
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -426,9 +438,11 @@
     //   let p3 = &((*p).a);
     //   arrayLength(&*p3);
     // }
-    auto* s = Structure("my_struct", {Member("a", ty.array<f32>(4))});
+    auto* s = Structure("my_struct", utils::Vector{
+                                         Member("a", ty.array<f32>(4)),
+                                     });
     GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
@@ -438,14 +452,14 @@
     auto* p3 = Let("p3", nullptr, AddressOf(MemberAccessor(Deref(p2), "a")));
     auto* expr = Call("arrayLength", AddressOf(Deref(p3)));
 
-    Func("a_func", {}, ty.void_(),
-         ast::StatementList{
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(p),
              Decl(p2),
              Decl(p3),
              CallStmt(expr),
          },
-         ast::AttributeList{
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -487,8 +501,8 @@
     // Use a variable to prevent the function being evaluated as constant.
     auto* scalar = Var("a", nullptr, Expr(1_f));
     auto* expr = Call(param.name, scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -528,8 +542,8 @@
     // Use a variable to prevent the function being evaluated as constant.
     auto* scalar = Var("a", nullptr, Expr(1_h));
     auto* expr = Call(param.name, scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -568,8 +582,8 @@
     // Use a variable to prevent the function being evaluated as constant.
     auto* vec = Var("a", nullptr, vec2<f32>(1_f, 1_f));
     auto* expr = Call(param.name, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -612,8 +626,8 @@
     // Use a variable to prevent the function being evaluated as constant.
     auto* vec = Var("a", nullptr, vec2<f16>(1_h, 1_h));
     auto* expr = Call(param.name, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -678,8 +692,8 @@
 TEST_F(BuiltinBuilderTest, Call_Length_Scalar_f32) {
     auto* scalar = Var("a", nullptr, Expr(1_f));
     auto* expr = Call("length", scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -715,8 +729,8 @@
 
     auto* scalar = Var("a", nullptr, Expr(1_h));
     auto* expr = Call("length", scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -750,8 +764,8 @@
 TEST_F(BuiltinBuilderTest, Call_Length_Vector_f32) {
     auto* vec = Var("a", nullptr, vec2<f32>(1_f, 1_f));
     auto* expr = Call("length", vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -789,8 +803,8 @@
 
     auto* vec = Var("a", nullptr, vec2<f16>(1_h, 1_h));
     auto* expr = Call("length", vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -826,8 +840,8 @@
 TEST_F(BuiltinBuilderTest, Call_Normalize_f32) {
     auto* vec = Var("a", nullptr, vec2<f32>(1_f, 1_f));
     auto* expr = Call("normalize", vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -865,8 +879,8 @@
 
     auto* vec = Var("a", nullptr, vec2<f16>(1_h, 1_h));
     auto* expr = Call("normalize", vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -904,8 +918,8 @@
     auto param = GetParam();
     auto* scalar = Var("scalar", nullptr, Expr(1_f));
     auto* expr = Call(param.name, scalar, scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -945,8 +959,8 @@
     auto param = GetParam();
     auto* scalar = Var("scalar", nullptr, Expr(1_h));
     auto* expr = Call(param.name, scalar, scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -984,8 +998,8 @@
     auto param = GetParam();
     auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 1_f));
     auto* expr = Call(param.name, vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -1027,8 +1041,8 @@
     auto param = GetParam();
     auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 1_h));
     auto* expr = Call(param.name, vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -1075,8 +1089,8 @@
 TEST_F(BuiltinBuilderTest, Call_Reflect_Vector_f32) {
     auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 1_f));
     auto* expr = Call("reflect", vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -1115,8 +1129,8 @@
 
     auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 1_h));
     auto* expr = Call("reflect", vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -1153,8 +1167,8 @@
 TEST_F(BuiltinBuilderTest, Call_Distance_Scalar_f32) {
     auto* scalar = Var("scalar", nullptr, Expr(1_f));
     auto* expr = Call("distance", scalar, scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -1191,8 +1205,8 @@
 
     auto* scalar = Var("scalar", nullptr, Expr(1_h));
     auto* expr = Call("distance", scalar, scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -1227,8 +1241,8 @@
 TEST_F(BuiltinBuilderTest, Call_Distance_Vector_f32) {
     auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 1_f));
     auto* expr = Call("distance", vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -1267,8 +1281,8 @@
 
     auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 1_h));
     auto* expr = Call("distance", vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -1305,8 +1319,8 @@
 TEST_F(BuiltinBuilderTest, Call_Cross_f32) {
     auto* vec = Var("vec", nullptr, vec3<f32>(1_f, 1_f, 1_f));
     auto* expr = Call("cross", vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -1345,8 +1359,8 @@
 
     auto* vec = Var("vec", nullptr, vec3<f16>(1_h, 1_h, 1_h));
     auto* expr = Call("cross", vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -1385,8 +1399,8 @@
     auto param = GetParam();
     auto* scalar = Var("scalar", nullptr, Expr(1_f));
     auto* expr = Call(param.name, scalar, scalar, scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -1427,8 +1441,8 @@
     auto param = GetParam();
     auto* scalar = Var("scalar", nullptr, Expr(1_h));
     auto* expr = Call(param.name, scalar, scalar, scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -1467,8 +1481,8 @@
     auto param = GetParam();
     auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 1_f));
     auto* expr = Call(param.name, vec, vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -1511,8 +1525,8 @@
     auto param = GetParam();
     auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 1_h));
     auto* expr = Call(param.name, vec, vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -1560,8 +1574,8 @@
 TEST_F(BuiltinBuilderTest, Call_FaceForward_Vector_f32) {
     auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 1_f));
     auto* expr = Call("faceForward", vec, vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -1601,8 +1615,8 @@
 
     auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 1_h));
     auto* expr = Call("faceForward", vec, vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -1637,11 +1651,17 @@
     EXPECT_EQ(got, expect);
 }
 
-TEST_F(BuiltinBuilderTest, Call_Modf) {
+TEST_F(BuiltinBuilderTest, Call_Modf_f32) {
     auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 2_f));
     auto* expr = Call("modf", vec);
-    Func("a_func", {}, ty.void_(), {Decl(vec), CallStmt(expr)},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(vec),
+             CallStmt(expr),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     spirv::Builder& b = Build();
 
@@ -1683,11 +1703,75 @@
     Validate(b);
 }
 
-TEST_F(BuiltinBuilderTest, Call_Frexp) {
+TEST_F(BuiltinBuilderTest, Call_Modf_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 2_h));
+    auto* expr = Call("modf", vec);
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(vec),
+             CallStmt(expr),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
+
+    spirv::Builder& b = Build();
+
+    ASSERT_TRUE(b.Build()) << b.error();
+    auto got = DumpBuilder(b);
+    auto* expect = R"(OpCapability Shader
+OpCapability Float16
+OpCapability UniformAndStorageBuffer16BitAccess
+OpCapability StorageBuffer16BitAccess
+OpCapability StorageInputOutput16
+%15 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %3 "a_func"
+OpExecutionMode %3 OriginUpperLeft
+OpName %3 "a_func"
+OpName %10 "vec"
+OpName %14 "__modf_result_vec2_f16"
+OpMemberName %14 0 "fract"
+OpMemberName %14 1 "whole"
+OpMemberDecorate %14 0 Offset 0
+OpMemberDecorate %14 1 Offset 4
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeFloat 16
+%5 = OpTypeVector %6 2
+%7 = OpConstant %6 0x1p+0
+%8 = OpConstant %6 0x1p+1
+%9 = OpConstantComposite %5 %7 %8
+%11 = OpTypePointer Function %5
+%12 = OpConstantNull %5
+%14 = OpTypeStruct %5 %5
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%10 = OpVariable %11 Function %12
+OpStore %10 %9
+%16 = OpLoad %5 %10
+%13 = OpExtInst %14 %15 ModfStruct %16
+OpReturn
+OpFunctionEnd
+)";
+    EXPECT_EQ(expect, got);
+
+    Validate(b);
+}
+
+TEST_F(BuiltinBuilderTest, Call_Frexp_f32) {
     auto* vec = Var("vec", nullptr, vec2<f32>(1_f, 2_f));
     auto* expr = Call("frexp", vec);
-    Func("a_func", {}, ty.void_(), {Decl(vec), CallStmt(expr)},
-         {Stage(ast::PipelineStage::kFragment)});
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(vec),
+             CallStmt(expr),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     spirv::Builder& b = Build();
 
@@ -1731,6 +1815,66 @@
     Validate(b);
 }
 
+TEST_F(BuiltinBuilderTest, Call_Frexp_f16) {
+    Enable(ast::Extension::kF16);
+
+    auto* vec = Var("vec", nullptr, vec2<f16>(1_h, 2_h));
+    auto* expr = Call("frexp", vec);
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(vec),
+             CallStmt(expr),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
+
+    spirv::Builder& b = Build();
+
+    ASSERT_TRUE(b.Build()) << b.error();
+    auto got = DumpBuilder(b);
+    auto* expect = R"(OpCapability Shader
+OpCapability Float16
+OpCapability UniformAndStorageBuffer16BitAccess
+OpCapability StorageBuffer16BitAccess
+OpCapability StorageInputOutput16
+%17 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %3 "a_func"
+OpExecutionMode %3 OriginUpperLeft
+OpName %3 "a_func"
+OpName %10 "vec"
+OpName %14 "__frexp_result_vec2_f16"
+OpMemberName %14 0 "sig"
+OpMemberName %14 1 "exp"
+OpMemberDecorate %14 0 Offset 0
+OpMemberDecorate %14 1 Offset 8
+%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%6 = OpTypeFloat 16
+%5 = OpTypeVector %6 2
+%7 = OpConstant %6 0x1p+0
+%8 = OpConstant %6 0x1p+1
+%9 = OpConstantComposite %5 %7 %8
+%11 = OpTypePointer Function %5
+%12 = OpConstantNull %5
+%16 = OpTypeInt 32 1
+%15 = OpTypeVector %16 2
+%14 = OpTypeStruct %5 %15
+%3 = OpFunction %2 None %1
+%4 = OpLabel
+%10 = OpVariable %11 Function %12
+OpStore %10 %9
+%18 = OpLoad %5 %10
+%13 = OpExtInst %14 %17 FrexpStruct %18
+OpReturn
+OpFunctionEnd
+)";
+    EXPECT_EQ(expect, got);
+
+    Validate(b);
+}
+
 }  // namespace float_builtin_tests
 
 // Tests for Numeric builtins with all integer parameter
@@ -1741,8 +1885,8 @@
     auto param = GetParam();
     auto* var = GlobalVar("v", ty.i32(), ast::StorageClass::kPrivate);
     auto* expr = Call(param.name, "v");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -1771,8 +1915,8 @@
     auto param = GetParam();
     auto* var = GlobalVar("v", ty.vec3<i32>(), ast::StorageClass::kPrivate);
     auto* expr = Call(param.name, "v");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -1802,8 +1946,8 @@
     auto param = GetParam();
     auto* var = GlobalVar("v", ty.u32(), ast::StorageClass::kPrivate);
     auto* expr = Call(param.name, "v");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -1832,8 +1976,8 @@
     auto param = GetParam();
     auto* var = GlobalVar("v", ty.vec3<u32>(), ast::StorageClass::kPrivate);
     auto* expr = Call(param.name, "v");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -1868,8 +2012,8 @@
     auto param = GetParam();
     auto* scalar = Var("scalar", nullptr, Expr(1_i));
     auto* expr = Call(param.name, scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -1906,8 +2050,8 @@
     auto param = GetParam();
     auto* vec = Var("vec", nullptr, vec2<i32>(1_i, 1_i));
     auto* expr = Call(param.name, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -1950,8 +2094,8 @@
 TEST_F(Builtin_Builder_Abs_Uint_Test, Call_Scalar) {
     auto* scalar = Var("scalar", nullptr, Expr(1_u));
     auto* expr = Call("abs", scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -1983,8 +2127,8 @@
 TEST_F(Builtin_Builder_Abs_Uint_Test, Call_Vector) {
     auto* scalar = Var("scalar", nullptr, vec2<u32>(1_u, 1_u));
     auto* expr = Call("abs", scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -2020,8 +2164,8 @@
     auto param = GetParam();
     auto* scalar = Var("scalar", nullptr, Expr(1_i));
     auto* expr = Call(param.name, scalar, scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -2059,8 +2203,8 @@
     auto param = GetParam();
     auto* vec = Var("vec", nullptr, vec2<i32>(1_i, 1_i));
     auto* expr = Call(param.name, vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -2104,8 +2248,8 @@
     auto param = GetParam();
     auto* scalar = Var("scalar", nullptr, Expr(1_u));
     auto* expr = Call(param.name, scalar, scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -2143,8 +2287,8 @@
     auto param = GetParam();
     auto* vec = Var("vec", nullptr, vec2<u32>(1_u, 1_u));
     auto* expr = Call(param.name, vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -2188,8 +2332,8 @@
     auto param = GetParam();
     auto* scalar = Var("scalar", nullptr, Expr(1_i));
     auto* expr = Call(param.name, scalar, scalar, scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -2228,8 +2372,8 @@
     auto param = GetParam();
     auto* vec = Var("vec", nullptr, vec2<i32>(1_i, 1_i));
     auto* expr = Call(param.name, vec, vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -2274,8 +2418,8 @@
     auto param = GetParam();
     auto* scalar = Var("scalar", nullptr, Expr(1_u));
     auto* expr = Call(param.name, scalar, scalar, scalar);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(scalar),
                           Assign(Phony(), expr),
                       });
@@ -2314,8 +2458,8 @@
     auto param = GetParam();
     auto* vec = Var("vec", nullptr, vec2<u32>(1_u, 1_u));
     auto* expr = Call(param.name, vec, vec, vec);
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Decl(vec),
                           Assign(Phony(), expr),
                       });
@@ -2703,8 +2847,8 @@
 TEST_F(BuiltinBuilderTest, Call_Determinant_f32) {
     auto* var = GlobalVar("var", ty.mat3x3<f32>(), ast::StorageClass::kPrivate);
     auto* expr = Call("determinant", "var");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -2740,8 +2884,8 @@
 
     auto* var = GlobalVar("var", ty.mat3x3<f16>(), ast::StorageClass::kPrivate);
     auto* expr = Call("determinant", "var");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -2775,8 +2919,8 @@
 TEST_F(BuiltinBuilderTest, Call_Transpose_f32) {
     auto* var = GlobalVar("var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
     auto* expr = Call("transpose", "var");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -2813,8 +2957,8 @@
 
     auto* var = GlobalVar("var", ty.mat2x3<f16>(), ast::StorageClass::kPrivate);
     auto* expr = Call("transpose", "var");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -2854,8 +2998,8 @@
 TEST_F(BuiltinBuilderTest, Call_Dot_F32) {
     auto* var = GlobalVar("v", ty.vec3<f32>(), ast::StorageClass::kPrivate);
     auto* expr = Call("dot", "v", "v");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -2885,8 +3029,8 @@
 
     auto* var = GlobalVar("v", ty.vec3<f16>(), ast::StorageClass::kPrivate);
     auto* expr = Call("dot", "v", "v");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -2914,8 +3058,8 @@
 TEST_F(BuiltinBuilderTest, Call_Dot_U32) {
     auto* var = GlobalVar("v", ty.vec3<u32>(), ast::StorageClass::kPrivate);
     auto* expr = Call("dot", "v", "v");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -2953,8 +3097,8 @@
 TEST_F(BuiltinBuilderTest, Call_Dot_I32) {
     auto* var = GlobalVar("v", ty.vec3<i32>(), ast::StorageClass::kPrivate);
     auto* expr = Call("dot", "v", "v");
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Assign(Phony(), expr),
                       });
 
@@ -2999,8 +3143,13 @@
     auto param = GetParam();
     auto* var = GlobalVar("v", ty.f32(), ast::StorageClass::kPrivate);
     auto* expr = Call(param.name, "v");
-    auto* func =
-        Func("func", {}, ty.void_(), {CallStmt(expr)}, {Stage(ast::PipelineStage::kFragment)});
+    auto* func = Func("func", utils::Empty, ty.void_(),
+                      utils::Vector{
+                          CallStmt(expr),
+                      },
+                      utils::Vector{
+                          Stage(ast::PipelineStage::kFragment),
+                      });
 
     spirv::Builder& b = Build();
 
@@ -3027,8 +3176,13 @@
     auto param = GetParam();
     auto* var = GlobalVar("v", ty.vec3<f32>(), ast::StorageClass::kPrivate);
     auto* expr = Call(param.name, "v");
-    auto* func =
-        Func("func", {}, ty.void_(), {CallStmt(expr)}, {Stage(ast::PipelineStage::kFragment)});
+    auto* func = Func("func", utils::Empty, ty.void_(),
+                      utils::Vector{
+                          CallStmt(expr),
+                      },
+                      utils::Vector{
+                          Stage(ast::PipelineStage::kFragment),
+                      });
 
     spirv::Builder& b = Build();
 
@@ -3086,22 +3240,24 @@
     //   let u : u32 = atomicLoad(&b.u);
     //   let i : i32 = atomicLoad(&b.i);
     // }
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("u", ty.atomic<u32>()),
                                  Member("i", ty.atomic<i32>()),
                              });
     GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         ast::StatementList{
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Let("u", ty.u32(), Call("atomicLoad", AddressOf(MemberAccessor("b", "u"))))),
              Decl(Let("i", ty.i32(), Call("atomicLoad", AddressOf(MemberAccessor("b", "i"))))),
          },
-         ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -3150,24 +3306,26 @@
     //   atomicStore(&b.u, u);
     //   atomicStore(&b.i, i);
     // }
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("u", ty.atomic<u32>()),
                                  Member("i", ty.atomic<i32>()),
                              });
     GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         ast::StatementList{
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("u", nullptr, Expr(1_u))),
              Decl(Var("i", nullptr, Expr(2_i))),
              CallStmt(Call("atomicStore", AddressOf(MemberAccessor("b", "u")), "u")),
              CallStmt(Call("atomicStore", AddressOf(MemberAccessor("b", "i")), "i")),
          },
-         ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -3223,22 +3381,24 @@
     //   var v = 10;
     //   let x : i32 = atomicOP(&b.v, v);
     // }
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("v", ty.atomic<i32>()),
                              });
     GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         ast::StatementList{
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("v", nullptr, Expr(10_i))),
              Decl(Let("x", ty.i32(),
                       Call(GetParam().name, AddressOf(MemberAccessor("b", "v")), "v"))),
          },
-         ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -3296,22 +3456,24 @@
     //   var v = 10u;
     //   let x : u32 = atomicOP(&b.v, v);
     // }
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("v", ty.atomic<u32>()),
                              });
     GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         ast::StatementList{
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("v", nullptr, Expr(10_u))),
              Decl(Let("x", ty.u32(),
                       Call(GetParam().name, AddressOf(MemberAccessor("b", "v")), "v"))),
          },
-         ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -3370,18 +3532,18 @@
     //   let r : u32 = atomicExchange(&b.u, u);
     //   let s : i32 = atomicExchange(&b.i, i);
     // }
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("u", ty.atomic<u32>()),
                                  Member("i", ty.atomic<i32>()),
                              });
     GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         ast::StatementList{
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Var("u", nullptr, Expr(10_u))),
              Decl(Var("i", nullptr, Expr(10_i))),
              Decl(Let("r", ty.u32(),
@@ -3389,7 +3551,9 @@
              Decl(Let("s", ty.i32(),
                       Call("atomicExchange", AddressOf(MemberAccessor("b", "i")), "i"))),
          },
-         ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -3446,18 +3610,18 @@
     //   let u = atomicCompareExchangeWeak(&b.u, 10u, 20u);
     //   let i = atomicCompareExchangeWeak(&b.i, 10, 10);
     // }
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("u", ty.atomic<u32>()),
                                  Member("i", ty.atomic<i32>()),
                              });
     GlobalVar("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
 
-    Func("a_func", {}, ty.void_(),
-         ast::StatementList{
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Decl(Let("u", nullptr,
                       Call("atomicCompareExchangeWeak", AddressOf(MemberAccessor("b", "u")), 10_u,
                            20_u))),
@@ -3465,7 +3629,9 @@
                       Call("atomicCompareExchangeWeak", AddressOf(MemberAccessor("b", "i")), 10_i,
                            20_i))),
          },
-         ast::AttributeList{Stage(ast::PipelineStage::kFragment)});
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -3523,7 +3689,10 @@
     bool pack4 = param.name == "pack4x8snorm" || param.name == "pack4x8unorm";
     auto* call = pack4 ? Call(param.name, vec4<f32>(1_f, 1_f, 1_f, 1_f))
                        : Call(param.name, vec2<f32>(1_f, 1_f));
-    auto* func = Func("a_func", {}, ty.void_(), {CallStmt(call)});
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
+                          CallStmt(call),
+                      });
 
     spirv::Builder& b = Build();
 
@@ -3590,7 +3759,10 @@
     auto param = GetParam();
 
     bool pack4 = param.name == "unpack4x8snorm" || param.name == "unpack4x8unorm";
-    auto* func = Func("a_func", {}, ty.void_(), {CallStmt(Call(param.name, 1_u))});
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
+                          CallStmt(Call(param.name, 1_u)),
+                      });
 
     spirv::Builder& b = Build();
 
@@ -3651,11 +3823,11 @@
 namespace synchronization_builtin_tests {
 
 TEST_F(BuiltinBuilderTest, Call_WorkgroupBarrier) {
-    Func("f", {}, ty.void_(),
-         ast::StatementList{
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
              CallStmt(Call("workgroupBarrier")),
          },
-         ast::AttributeList{
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
@@ -3685,11 +3857,11 @@
 }
 
 TEST_F(BuiltinBuilderTest, Call_StorageBarrier) {
-    Func("f", {}, ty.void_(),
-         ast::StatementList{
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
              CallStmt(Call("storageBarrier")),
          },
-         ast::AttributeList{
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
diff --git a/src/tint/writer/spirv/builder_builtin_texture_test.cc b/src/tint/writer/spirv/builder_builtin_texture_test.cc
index ea81211..b5fcc9c 100644
--- a/src/tint/writer/spirv/builder_builtin_texture_test.cc
+++ b/src/tint/writer/spirv/builder_builtin_texture_test.cc
@@ -3684,7 +3684,10 @@
 
     auto* call = Call(param.function, param.args(this));
     auto* stmt = CallStmt(call);
-    Func("func", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(), utils::Vector{stmt},
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     spirv::Builder& b = Build();
 
@@ -3710,7 +3713,10 @@
     auto* call = Call(param.function, param.args(this));
 
     auto* stmt = CallStmt(call);
-    Func("main", {}, ty.void_(), {stmt}, {Stage(ast::PipelineStage::kFragment)});
+    Func("main", utils::Empty, ty.void_(), utils::Vector{stmt},
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     spirv::Builder& b = Build();
 
@@ -3730,8 +3736,10 @@
 
     auto* call = Call(param.function, param.args(this));
     auto* stmt = CallStmt(call);
-    Func("func", {}, ty.void_(), {stmt},
-         {create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
+    Func("func", utils::Empty, ty.void_(), utils::Vector{stmt},
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     spirv::Builder& b = Build();
 
diff --git a/src/tint/writer/spirv/builder_call_test.cc b/src/tint/writer/spirv/builder_call_test.cc
index 73bc4fd..a0631ea 100644
--- a/src/tint/writer/spirv/builder_call_test.cc
+++ b/src/tint/writer/spirv/builder_call_test.cc
@@ -26,12 +26,13 @@
 
 TEST_F(BuilderTest, Expression_Call) {
     auto* a_func = Func("a_func",
-                        {
+                        utils::Vector{
                             Param("a", ty.f32()),
                             Param("b", ty.f32()),
                         },
-                        ty.f32(), {Return(Add("a", "b"))});
-    auto* func = Func("main", {}, ty.void_(), {Assign(Phony(), Call("a_func", 1_f, 1_f))});
+                        ty.f32(), utils::Vector{Return(Add("a", "b"))});
+    auto* func = Func("main", utils::Empty, ty.void_(),
+                      utils::Vector{Assign(Phony(), Call("a_func", 1_f, 1_f))});
 
     spirv::Builder& b = Build();
 
@@ -64,13 +65,14 @@
 
 TEST_F(BuilderTest, Statement_Call) {
     auto* a_func = Func("a_func",
-                        {
+                        utils::Vector{
                             Param("a", ty.f32()),
                             Param("b", ty.f32()),
                         },
-                        ty.f32(), {Return(Add("a", "b"))});
+                        ty.f32(), utils::Vector{Return(Add("a", "b"))});
 
-    auto* func = Func("main", {}, ty.void_(), {CallStmt(Call("a_func", 1_f, 1_f))});
+    auto* func =
+        Func("main", utils::Empty, ty.void_(), utils::Vector{CallStmt(Call("a_func", 1_f, 1_f))});
 
     spirv::Builder& b = Build();
 
diff --git a/src/tint/writer/spirv/builder_constructor_expression_test.cc b/src/tint/writer/spirv/builder_constructor_expression_test.cc
index 7fc7ac6..3ae4f65 100644
--- a/src/tint/writer/spirv/builder_constructor_expression_test.cc
+++ b/src/tint/writer/spirv/builder_constructor_expression_test.cc
@@ -3635,11 +3635,7 @@
 TEST_F(SpvBuilderConstructorTest, CommonInitializer_TwoVectors) {
     auto* v1 = vec3<f32>(2_f, 2_f, 2_f);
     auto* v2 = vec3<f32>(2_f, 2_f, 2_f);
-    ast::StatementList stmts = {
-        WrapInStatement(v1),
-        WrapInStatement(v2),
-    };
-    WrapInFunction(stmts);
+    WrapInFunction(WrapInStatement(v1), WrapInStatement(v2));
 
     spirv::Builder& b = Build();
 
@@ -3657,11 +3653,7 @@
 TEST_F(SpvBuilderConstructorTest, CommonInitializer_TwoArrays) {
     auto* a1 = array<f32, 3>(2_f, 2_f, 2_f);
     auto* a2 = array<f32, 3>(2_f, 2_f, 2_f);
-    ast::StatementList stmts = {
-        WrapInStatement(a1),
-        WrapInStatement(a2),
-    };
-    WrapInFunction(stmts);
+    WrapInFunction(WrapInStatement(a1), WrapInStatement(a2));
 
     spirv::Builder& b = Build();
 
@@ -3684,11 +3676,7 @@
     // crbug.com/tint/777
     auto* a1 = array<f32, 2>(1_f, 2_f);
     auto* a2 = vec2<f32>(1_f, 2_f);
-    ast::StatementList stmts = {
-        WrapInStatement(a1),
-        WrapInStatement(a2),
-    };
-    WrapInFunction(stmts);
+    WrapInFunction(WrapInStatement(a1), WrapInStatement(a2));
     spirv::Builder& b = Build();
 
     b.push_function(Function{});
@@ -3708,7 +3696,7 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Struct) {
-    auto* s = Structure("my_struct", {
+    auto* s = Structure("my_struct", utils::Vector{
                                          Member("a", ty.f32()),
                                          Member("b", ty.vec3<f32>()),
                                      });
@@ -3898,7 +3886,7 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Struct) {
-    auto* s = Structure("my_struct", {Member("a", ty.f32())});
+    auto* s = Structure("my_struct", utils::Vector{Member("a", ty.f32())});
     auto* t = Construct(ty.Of(s));
     WrapInFunction(t);
 
@@ -4546,7 +4534,7 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, IsConstructorConst_Struct) {
-    auto* s = Structure("my_struct", {
+    auto* s = Structure("my_struct", utils::Vector{
                                          Member("a", ty.f32()),
                                          Member("b", ty.vec3<f32>()),
                                      });
@@ -4561,7 +4549,7 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, IsConstructorConst_Struct_WithIdentSubExpression) {
-    auto* s = Structure("my_struct", {
+    auto* s = Structure("my_struct", utils::Vector{
                                          Member("a", ty.f32()),
                                          Member("b", ty.vec3<f32>()),
                                      });
diff --git a/src/tint/writer/spirv/builder_entry_point_test.cc b/src/tint/writer/spirv/builder_entry_point_test.cc
index 2c10ab3..ea36e35 100644
--- a/src/tint/writer/spirv/builder_entry_point_test.cc
+++ b/src/tint/writer/spirv/builder_entry_point_test.cc
@@ -42,12 +42,18 @@
     //              @location(1) loc1 : f32) {
     //   var col : f32 = (coord.x * loc1);
     // }
-    auto* coord = Param("coord", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
-    auto* loc1 = Param("loc1", ty.f32(), {Location(1u)});
+    auto* coord = Param("coord", ty.vec4<f32>(),
+                        utils::Vector{
+                            Builtin(ast::BuiltinValue::kPosition),
+                        });
+    auto* loc1 = Param("loc1", ty.f32(),
+                       utils::Vector{
+                           Location(1u),
+                       });
     auto* mul = Mul(Expr(MemberAccessor("coord", "x")), Expr("loc1"));
     auto* col = Var("col", ty.f32(), ast::StorageClass::kNone, mul);
-    Func("frag_main", {coord, loc1}, ty.void_(), {WrapInStatement(col)},
-         {
+    Func("frag_main", utils::Vector{coord, loc1}, ty.void_(), utils::Vector{WrapInStatement(col)},
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          });
 
@@ -112,18 +118,24 @@
     //   }
     //   return 1.0;
     // }
-    auto* loc_in = Param("loc_in", ty.u32(), {Location(0), Flat()});
+    auto* loc_in = Param("loc_in", ty.u32(),
+                         utils::Vector{
+                             Location(0),
+                             Flat(),
+                         });
     auto* cond =
         create<ast::BinaryExpression>(ast::BinaryOp::kGreaterThan, Expr("loc_in"), Expr(10_u));
-    Func("frag_main", {loc_in}, ty.f32(),
-         {
+    Func("frag_main", utils::Vector{loc_in}, ty.f32(),
+         utils::Vector{
              If(cond, Block(Return(0.5_f))),
              Return(1_f),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {Location(0)});
+         utils::Vector{
+             Location(0),
+         });
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -197,21 +209,27 @@
     // }
 
     auto* interface = Structure(
-        "Interface", {
-                         Member("value", ty.f32(), {Location(1u)}),
-                         Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
-                     });
+        "Interface",
+        utils::Vector{
+            Member("value", ty.f32(), utils::Vector{Location(1u)}),
+            Member("pos", ty.vec4<f32>(), utils::Vector{Builtin(ast::BuiltinValue::kPosition)}),
+        });
 
     auto* vert_retval = Construct(ty.Of(interface), 42_f, Construct(ty.vec4<f32>()));
-    Func("vert_main", {}, ty.Of(interface), {Return(vert_retval)},
-         {Stage(ast::PipelineStage::kVertex)});
+    Func("vert_main", utils::Empty, ty.Of(interface), utils::Vector{Return(vert_retval)},
+         utils::Vector{
+             Stage(ast::PipelineStage::kVertex),
+         });
 
     auto* frag_inputs = Param("inputs", ty.Of(interface));
-    Func("frag_main", {frag_inputs}, ty.f32(),
-         {
+    Func("frag_main", utils::Vector{frag_inputs}, ty.f32(),
+         utils::Vector{
              Return(MemberAccessor(Expr("inputs"), "value")),
          },
-         {Stage(ast::PipelineStage::kFragment)}, {Builtin(ast::BuiltinValue::kFragDepth)});
+         utils::Vector{Stage(ast::PipelineStage::kFragment)},
+         utils::Vector{
+             Builtin(ast::BuiltinValue::kFragDepth),
+         });
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -302,8 +320,13 @@
 }
 
 TEST_F(BuilderTest, SampleIndex_SampleRateShadingCapability) {
-    Func("main", {Param("sample_index", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)})},
-         ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+    Func("main",
+         utils::Vector{Param("sample_index", ty.u32(),
+                             utils::Vector{Builtin(ast::BuiltinValue::kSampleIndex)})},
+         ty.void_(), utils::Empty,
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
 
     spirv::Builder& b = SanitizeAndBuild();
 
diff --git a/src/tint/writer/spirv/builder_function_attribute_test.cc b/src/tint/writer/spirv/builder_function_attribute_test.cc
index ab1fa0b..449b75d 100644
--- a/src/tint/writer/spirv/builder_function_attribute_test.cc
+++ b/src/tint/writer/spirv/builder_function_attribute_test.cc
@@ -25,8 +25,8 @@
 using BuilderTest = TestHelper;
 
 TEST_F(BuilderTest, Attribute_Stage) {
-    auto* func = Func("main", {}, ty.void_(), {},
-                      {
+    auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{
                           Stage(ast::PipelineStage::kFragment),
                       });
 
@@ -52,22 +52,22 @@
 
     const ast::Variable* var = nullptr;
     const ast::Type* ret_type = nullptr;
-    ast::AttributeList ret_type_attrs;
-    ast::StatementList body;
+    utils::Vector<const ast::Attribute*, 2> ret_type_attrs;
+    utils::Vector<const ast::Statement*, 2> body;
     if (params.stage == ast::PipelineStage::kVertex) {
         ret_type = ty.vec4<f32>();
-        ret_type_attrs.push_back(Builtin(ast::BuiltinValue::kPosition));
-        body.push_back(Return(Construct(ty.vec4<f32>())));
+        ret_type_attrs.Push(Builtin(ast::BuiltinValue::kPosition));
+        body.Push(Return(Construct(ty.vec4<f32>())));
     } else {
         ret_type = ty.void_();
     }
 
-    auto deco_list = ast::AttributeList{Stage(params.stage)};
+    utils::Vector<const ast::Attribute*, 2> deco_list{Stage(params.stage)};
     if (params.stage == ast::PipelineStage::kCompute) {
-        deco_list.push_back(WorkgroupSize(1_i));
+        deco_list.Push(WorkgroupSize(1_i));
     }
 
-    auto* func = Func("main", {}, ret_type, body, deco_list, ret_type_attrs);
+    auto* func = Func("main", utils::Empty, ret_type, body, deco_list, ret_type_attrs);
 
     spirv::Builder& b = Build();
 
@@ -91,8 +91,8 @@
                     FunctionStageData{ast::PipelineStage::kCompute, SpvExecutionModelGLCompute}));
 
 TEST_F(BuilderTest, Decoration_ExecutionMode_Fragment_OriginUpperLeft) {
-    auto* func = Func("main", {}, ty.void_(), {},
-                      {
+    auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{
                           Stage(ast::PipelineStage::kFragment),
                       });
 
@@ -105,8 +105,8 @@
 }
 
 TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_Default) {
-    auto* func =
-        Func("main", {}, ty.void_(), {}, {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
+    auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
 
     spirv::Builder& b = Build();
 
@@ -117,8 +117,8 @@
 }
 
 TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_Literals) {
-    auto* func = Func("main", {}, ty.void_(), {},
-                      {
+    auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{
                           WorkgroupSize(2_i, 4_i, 6_i),
                           Stage(ast::PipelineStage::kCompute),
                       });
@@ -135,8 +135,8 @@
     GlobalConst("width", ty.i32(), Construct(ty.i32(), 2_i));
     GlobalConst("height", ty.i32(), Construct(ty.i32(), 3_i));
     GlobalConst("depth", ty.i32(), Construct(ty.i32(), 4_i));
-    auto* func = Func("main", {}, ty.void_(), {},
-                      {
+    auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{
                           WorkgroupSize("width", "height", "depth"),
                           Stage(ast::PipelineStage::kCompute),
                       });
@@ -150,11 +150,11 @@
 }
 
 TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_OverridableConst) {
-    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", {}, ty.void_(), {},
-                      {
+    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)});
+    auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{
                           WorkgroupSize("width", "height", "depth"),
                           Stage(ast::PipelineStage::kCompute),
                       });
@@ -180,10 +180,10 @@
 }
 
 TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_LiteralAndConst) {
-    Override("height", ty.i32(), Construct(ty.i32(), 2_i), {Id(7u)});
+    Override("height", ty.i32(), Construct(ty.i32(), 2_i), utils::Vector{Id(7u)});
     GlobalConst("depth", ty.i32(), Construct(ty.i32(), 3_i));
-    auto* func = Func("main", {}, ty.void_(), {},
-                      {
+    auto* func = Func("main", utils::Empty, ty.void_(), utils::Empty,
+                      utils::Vector{
                           WorkgroupSize(4_i, "height", "depth"),
                           Stage(ast::PipelineStage::kCompute),
                       });
@@ -207,13 +207,13 @@
 }
 
 TEST_F(BuilderTest, Decoration_ExecutionMode_MultipleFragment) {
-    auto* func1 = Func("main1", {}, ty.void_(), {},
-                       {
+    auto* func1 = Func("main1", utils::Empty, ty.void_(), utils::Empty,
+                       utils::Vector{
                            Stage(ast::PipelineStage::kFragment),
                        });
 
-    auto* func2 = Func("main2", {}, ty.void_(), {},
-                       {
+    auto* func2 = Func("main2", utils::Empty, ty.void_(), utils::Empty,
+                       utils::Vector{
                            Stage(ast::PipelineStage::kFragment),
                        });
 
@@ -242,14 +242,14 @@
 }
 
 TEST_F(BuilderTest, Decoration_ExecutionMode_FragDepth) {
-    Func("main", {}, ty.f32(),
-         {
+    Func("main", utils::Empty, ty.f32(),
+         utils::Vector{
              Return(Expr(1_f)),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kFragment),
          },
-         {
+         utils::Vector{
              Builtin(ast::BuiltinValue::kFragDepth),
          });
 
diff --git a/src/tint/writer/spirv/builder_function_test.cc b/src/tint/writer/spirv/builder_function_test.cc
index 91fa71a..7cbd442 100644
--- a/src/tint/writer/spirv/builder_function_test.cc
+++ b/src/tint/writer/spirv/builder_function_test.cc
@@ -24,7 +24,7 @@
 using BuilderTest = TestHelper;
 
 TEST_F(BuilderTest, Function_Empty) {
-    Func("a_func", {}, ty.void_(), {});
+    Func("a_func", utils::Empty, ty.void_(), utils::Empty);
 
     spirv::Builder& b = Build();
 
@@ -41,8 +41,8 @@
 }
 
 TEST_F(BuilderTest, Function_Terminator_Return) {
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(),
          });
 
@@ -63,7 +63,7 @@
 TEST_F(BuilderTest, Function_Terminator_ReturnValue) {
     GlobalVar("a", ty.f32(), ast::StorageClass::kPrivate);
 
-    Func("a_func", {}, ty.f32(), {Return("a")}, {});
+    Func("a_func", utils::Empty, ty.f32(), utils::Vector{Return("a")}, utils::Empty);
 
     spirv::Builder& b = Build();
 
@@ -88,8 +88,8 @@
 }
 
 TEST_F(BuilderTest, Function_Terminator_Discard) {
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              create<ast::DiscardStatement>(),
          });
 
@@ -109,11 +109,11 @@
 
 TEST_F(BuilderTest, Function_WithParams) {
     Func("a_func",
-         {
+         utils::Vector{
              Param("a", ty.f32()),
              Param("b", ty.i32()),
          },
-         ty.f32(), {Return("a")}, {});
+         ty.f32(), utils::Vector{Return("a")}, utils::Empty);
 
     spirv::Builder& b = Build();
 
@@ -135,8 +135,8 @@
 }
 
 TEST_F(BuilderTest, Function_WithBody) {
-    Func("a_func", {}, ty.void_(),
-         {
+    Func("a_func", utils::Empty, ty.void_(),
+         utils::Vector{
              Return(),
          });
 
@@ -155,7 +155,7 @@
 }
 
 TEST_F(BuilderTest, FunctionType) {
-    Func("a_func", {}, ty.void_(), {}, {});
+    Func("a_func", utils::Empty, ty.void_(), utils::Empty, utils::Empty);
 
     spirv::Builder& b = Build();
 
@@ -167,8 +167,8 @@
 }
 
 TEST_F(BuilderTest, FunctionType_DeDuplicate) {
-    auto* func1 = Func("a_func", {}, ty.void_(), {}, {});
-    auto* func2 = Func("b_func", {}, ty.void_(), {}, {});
+    auto* func1 = Func("a_func", utils::Empty, ty.void_(), utils::Empty, utils::Empty);
+    auto* func2 = Func("b_func", utils::Empty, ty.void_(), utils::Empty, utils::Empty);
 
     spirv::Builder& b = Build();
 
@@ -196,10 +196,10 @@
     //   return;
     // }
 
-    auto* s = Structure("Data", {Member("d", ty.f32())});
+    auto* s = Structure("Data", utils::Vector{Member("d", ty.f32())});
 
     GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -207,23 +207,23 @@
     {
         auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
 
-        Func("a", {}, ty.void_(),
-             {
+        Func("a", utils::Empty, ty.void_(),
+             utils::Vector{
                  Decl(var),
                  Return(),
              },
-             {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
+             utils::Vector{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
     }
 
     {
         auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
 
-        Func("b", {}, ty.void_(),
-             {
+        Func("b", utils::Empty, ty.void_(),
+             utils::Vector{
                  Decl(var),
                  Return(),
              },
-             {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
+             utils::Vector{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
     }
 
     spirv::Builder& b = SanitizeAndBuild();
diff --git a/src/tint/writer/spirv/builder_function_variable_test.cc b/src/tint/writer/spirv/builder_function_variable_test.cc
index 4a7026a..4045f4c 100644
--- a/src/tint/writer/spirv/builder_function_variable_test.cc
+++ b/src/tint/writer/spirv/builder_function_variable_test.cc
@@ -73,32 +73,34 @@
 }
 
 TEST_F(BuilderTest, FunctionVar_WithNonConstantConstructor) {
-    auto* init = vec2<f32>(1_f, Add(3_f, 3_f));
+    auto* a = Let("a", nullptr, 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);
-    WrapInFunction(v);
+    WrapInFunction(a, v);
 
     spirv::Builder& b = Build();
 
     b.push_function(Function{});
+    EXPECT_TRUE(b.GenerateFunctionVariable(a)) << b.error();
     EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
     ASSERT_FALSE(b.has_error()) << b.error();
 
     EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %7 "var"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
-%1 = OpTypeVector %2 2
-%3 = OpConstant %2 1
-%4 = OpConstant %2 3
-%8 = OpTypePointer Function %1
-%9 = OpConstantNull %1
+    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+%2 = OpConstant %1 3
+%3 = OpTypeVector %1 2
+%4 = OpConstant %1 1
+%8 = OpTypePointer Function %3
+%9 = OpConstantNull %3
 )");
     EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
               R"(%7 = OpVariable %8 Function %9
 )");
     EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%5 = OpFAdd %2 %4 %4
-%6 = OpCompositeConstruct %1 %3 %5
+              R"(%5 = OpFAdd %1 %2 %2
+%6 = OpCompositeConstruct %3 %4 %5
 OpStore %7 %6
 )");
 }
diff --git a/src/tint/writer/spirv/builder_global_variable_test.cc b/src/tint/writer/spirv/builder_global_variable_test.cc
index 5d1a5b0..e14a017 100644
--- a/src/tint/writer/spirv/builder_global_variable_test.cc
+++ b/src/tint/writer/spirv/builder_global_variable_test.cc
@@ -235,7 +235,7 @@
 TEST_F(BuilderTest, GlobalVar_WithBindingAndGroup) {
     auto* v =
         GlobalVar("var", ty.sampler(ast::SamplerKind::kSampler), ast::StorageClass::kNone, nullptr,
-                  ast::AttributeList{
+                  utils::Vector{
                       create<ast::BindingAttribute>(2u),
                       create<ast::GroupAttribute>(3u),
                   });
@@ -256,7 +256,7 @@
 
 TEST_F(BuilderTest, GlobalVar_Override_Bool) {
     auto* v = Override("var", ty.bool_(), Expr(true),
-                       ast::AttributeList{
+                       utils::Vector{
                            Id(1200),
                        });
 
@@ -274,7 +274,7 @@
 
 TEST_F(BuilderTest, GlobalVar_Override_Bool_ZeroValue) {
     auto* v = Override("var", ty.bool_(), Construct<bool>(),
-                       ast::AttributeList{
+                       utils::Vector{
                            Id(1200),
                        });
 
@@ -292,7 +292,7 @@
 
 TEST_F(BuilderTest, GlobalVar_Override_Bool_NoConstructor) {
     auto* v = Override("var", ty.bool_(), nullptr,
-                       ast::AttributeList{
+                       utils::Vector{
                            Id(1200),
                        });
 
@@ -310,7 +310,7 @@
 
 TEST_F(BuilderTest, GlobalVar_Override_Scalar) {
     auto* v = Override("var", ty.f32(), Expr(2_f),
-                       ast::AttributeList{
+                       utils::Vector{
                            Id(0),
                        });
 
@@ -328,7 +328,7 @@
 
 TEST_F(BuilderTest, GlobalVar_Override_Scalar_ZeroValue) {
     auto* v = Override("var", ty.f32(), Construct<f32>(),
-                       ast::AttributeList{
+                       utils::Vector{
                            Id(0),
                        });
 
@@ -346,7 +346,7 @@
 
 TEST_F(BuilderTest, GlobalVar_Override_Scalar_F32_NoConstructor) {
     auto* v = Override("var", ty.f32(), nullptr,
-                       ast::AttributeList{
+                       utils::Vector{
                            Id(0),
                        });
 
@@ -364,7 +364,7 @@
 
 TEST_F(BuilderTest, GlobalVar_Override_Scalar_I32_NoConstructor) {
     auto* v = Override("var", ty.i32(), nullptr,
-                       ast::AttributeList{
+                       utils::Vector{
                            Id(0),
                        });
 
@@ -382,7 +382,7 @@
 
 TEST_F(BuilderTest, GlobalVar_Override_Scalar_U32_NoConstructor) {
     auto* v = Override("var", ty.u32(), nullptr,
-                       ast::AttributeList{
+                       utils::Vector{
                            Id(0),
                        });
 
@@ -400,7 +400,7 @@
 
 TEST_F(BuilderTest, GlobalVar_Override_NoId) {
     auto* var_a = Override("a", ty.bool_(), Expr(true),
-                           ast::AttributeList{
+                           utils::Vector{
                                Id(0),
                            });
     auto* var_b = Override("b", ty.bool_(), Expr(false));
@@ -474,13 +474,13 @@
     // };
     // var b<storage, read> : A
 
-    auto* A = Structure("A", {
+    auto* A = Structure("A", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", ty.i32()),
                              });
 
     GlobalVar("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -518,10 +518,10 @@
     // type B = A;
     // var b<storage, read> : B
 
-    auto* A = Structure("A", {Member("a", ty.i32())});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -557,10 +557,10 @@
     // type B = A;
     // var<storage, read> b : B
 
-    auto* A = Structure("A", {Member("a", ty.i32())});
+    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,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -596,14 +596,14 @@
     // var<storage, read> b : A
     // var<storage, read_write> c : A
 
-    auto* A = Structure("A", {Member("a", ty.i32())});
+    auto* A = Structure("A", utils::Vector{Member("a", ty.i32())});
     GlobalVar("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::GroupAttribute>(0u),
                   create<ast::BindingAttribute>(0u),
               });
     GlobalVar("c", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::GroupAttribute>(1u),
                   create<ast::BindingAttribute>(0u),
               });
@@ -644,7 +644,7 @@
                                     ast::Access::kWrite);
 
     auto* var_a = GlobalVar("a", type,
-                            ast::AttributeList{
+                            utils::Vector{
                                 create<ast::BindingAttribute>(0u),
                                 create<ast::GroupAttribute>(0u),
                             });
@@ -675,7 +675,7 @@
     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,
-                            ast::AttributeList{
+                            utils::Vector{
                                 create<ast::BindingAttribute>(0u),
                                 create<ast::GroupAttribute>(0u),
                             });
@@ -683,7 +683,7 @@
     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,
-                            ast::AttributeList{
+                            utils::Vector{
                                 create<ast::BindingAttribute>(1u),
                                 create<ast::GroupAttribute>(0u),
                             });
@@ -718,7 +718,7 @@
     auto* type_array = ty.array<f32, 16>();
     auto* var_array = GlobalVar("b", type_array, ast::StorageClass::kWorkgroup);
 
-    auto* type_struct = Structure("C", {
+    auto* type_struct = Structure("C", utils::Vector{
                                            Member("a", ty.i32()),
                                            Member("b", ty.i32()),
                                        });
diff --git a/src/tint/writer/spirv/builder_if_test.cc b/src/tint/writer/spirv/builder_if_test.cc
index 391be43..6705cce 100644
--- a/src/tint/writer/spirv/builder_if_test.cc
+++ b/src/tint/writer/spirv/builder_if_test.cc
@@ -434,7 +434,10 @@
     //   return;
     // }
 
-    auto* fn = Func("f", {}, ty.void_(), {If(true, Block(Return()))});
+    auto* fn = Func("f", utils::Empty, ty.void_(),
+                    utils::Vector{
+                        If(true, Block(Return())),
+                    });
 
     spirv::Builder& b = Build();
 
@@ -460,8 +463,8 @@
     // }
     // return true;
 
-    auto* fn = Func("f", {}, ty.bool_(),
-                    {
+    auto* fn = Func("f", utils::Empty, ty.bool_(),
+                    utils::Vector{
                         If(true, Block(Return(false))),
                         Return(true),
                     });
@@ -491,8 +494,8 @@
     //   return true;
     // }
 
-    auto* fn = Func("f", {}, ty.bool_(),
-                    {
+    auto* fn = Func("f", utils::Empty, ty.bool_(),
+                    utils::Vector{
                         If(true,                 //
                            Block(Return(true)),  //
                            Else(Block(Return(true)))),
@@ -530,8 +533,8 @@
     // }
     // return true;
 
-    auto* fn = Func("f", {}, ty.bool_(),
-                    {
+    auto* fn = Func("f", utils::Empty, ty.bool_(),
+                    utils::Vector{
                         If(true, Block(Block(Block(Block(Return(false)))))),
                         Return(true),
                     });
@@ -560,7 +563,10 @@
     // }
 
     auto* var = GlobalVar("a", ty.bool_(), ast::StorageClass::kPrivate);
-    auto* fn = Func("f", {}, ty.void_(), {If("a", Block())});
+    auto* fn = Func("f", utils::Empty, ty.void_(),
+                    utils::Vector{
+                        If("a", Block()),
+                    });
 
     spirv::Builder& b = Build();
 
@@ -592,7 +598,7 @@
     // }
 
     auto* if_stmt = If(false, Block(), Else(If(true, Block(Return()))));
-    auto* fn = Func("f", {}, ty.void_(), {if_stmt});
+    auto* fn = Func("f", utils::Empty, ty.void_(), utils::Vector{if_stmt});
 
     spirv::Builder& b = Build();
 
@@ -630,7 +636,10 @@
     // }
 
     auto* if_stmt = If(false, Block(), Else(If(true, Block(Break()))));
-    auto* fn = Func("f", {}, ty.void_(), {Loop(Block(if_stmt))});
+    auto* fn = Func("f", utils::Empty, ty.void_(),
+                    utils::Vector{
+                        Loop(Block(if_stmt)),
+                    });
 
     spirv::Builder& b = Build();
 
diff --git a/src/tint/writer/spirv/builder_return_test.cc b/src/tint/writer/spirv/builder_return_test.cc
index 0027311..825529e 100644
--- a/src/tint/writer/spirv/builder_return_test.cc
+++ b/src/tint/writer/spirv/builder_return_test.cc
@@ -40,7 +40,7 @@
     auto* val = vec3<f32>(1_f, 1_f, 3_f);
 
     auto* ret = Return(val);
-    Func("test", {}, ty.vec3<f32>(), {ret}, {});
+    Func("test", utils::Empty, ty.vec3<f32>(), utils::Vector{ret}, utils::Empty);
 
     spirv::Builder& b = Build();
 
@@ -63,7 +63,7 @@
     auto* var = Var("param", ty.f32());
 
     auto* ret = Return(var);
-    Func("test", {}, ty.f32(), {Decl(var), ret}, {});
+    Func("test", utils::Empty, ty.f32(), utils::Vector{Decl(var), ret}, utils::Empty);
 
     spirv::Builder& b = Build();
 
diff --git a/src/tint/writer/spirv/builder_static_assert_test.cc b/src/tint/writer/spirv/builder_static_assert_test.cc
new file mode 100644
index 0000000..c6d6a51
--- /dev/null
+++ b/src/tint/writer/spirv/builder_static_assert_test.cc
@@ -0,0 +1,54 @@
+// 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/ast/fallthrough_statement.h"
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+using namespace tint::number_suffixes;  // NOLINT
+
+namespace tint::writer::spirv {
+namespace {
+
+using BuilderTest = TestHelper;
+
+TEST_F(BuilderTest, GlobalStaticAssert) {
+    GlobalStaticAssert(true);
+
+    spirv::Builder& b = Build();
+
+    ASSERT_TRUE(b.Build()) << b.error();
+
+    // static asserts are not emitted
+    EXPECT_EQ(DumpInstructions(b.types()), "");
+    EXPECT_EQ(b.functions().size(), 0u);
+}
+
+TEST_F(BuilderTest, FunctionStaticAssert) {
+    Func("f", utils::Empty, ty.void_(), utils::Vector{StaticAssert(true)});
+
+    spirv::Builder& b = Build();
+
+    ASSERT_TRUE(b.Build()) << b.error();
+
+    // static asserts are not emitted
+    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+)");
+    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpReturn
+)");
+}
+
+}  // namespace
+}  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/builder_switch_test.cc b/src/tint/writer/spirv/builder_switch_test.cc
index 3c905b6..c2f526e 100644
--- a/src/tint/writer/spirv/builder_switch_test.cc
+++ b/src/tint/writer/spirv/builder_switch_test.cc
@@ -60,8 +60,8 @@
     auto* v = GlobalVar("v", ty.i32(), ast::StorageClass::kPrivate);
     auto* a = GlobalVar("a", ty.i32(), ast::StorageClass::kPrivate);
 
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Switch("a",                                       //
                                  Case(Expr(1_i), Block(Assign("v", 1_i))),  //
                                  Case(Expr(2_i), Block(Assign("v", 2_i))),  //
@@ -117,8 +117,8 @@
     auto* v = GlobalVar("v", ty.i32(), ast::StorageClass::kPrivate);
     auto* a = GlobalVar("a", ty.u32(), ast::StorageClass::kPrivate);
 
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Switch("a",                                       //
                                  Case(Expr(1_u), Block(Assign("v", 1_i))),  //
                                  Case(Expr(2_u), Block(Assign("v", 2_i))),  //
@@ -174,8 +174,8 @@
     auto* v = GlobalVar("v", ty.i32(), ast::StorageClass::kPrivate);
     auto* a = GlobalVar("a", ty.i32(), ast::StorageClass::kPrivate);
 
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Switch("a",                                    //
                                  DefaultCase(Block(Assign("v", 1_i)))),  //
                       });
@@ -224,13 +224,13 @@
     auto* v = GlobalVar("v", ty.i32(), ast::StorageClass::kPrivate);
     auto* a = GlobalVar("a", ty.i32(), ast::StorageClass::kPrivate);
 
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
-                          Switch(Expr("a"),                      //
-                                 Case(Expr(1_i),                 //
-                                      Block(Assign("v", 1_i))),  //
-                                 Case({Expr(2_i), Expr(3_i)},    //
-                                      Block(Assign("v", 2_i))),  //
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
+                          Switch(Expr("a"),                                 //
+                                 Case(Expr(1_i),                            //
+                                      Block(Assign("v", 1_i))),             //
+                                 Case(utils::Vector{Expr(2_i), Expr(3_i)},  //
+                                      Block(Assign("v", 2_i))),             //
                                  DefaultCase(Block(Assign("v", 3_i)))),
                       });
 
@@ -287,8 +287,8 @@
     auto* v = GlobalVar("v", ty.i32(), ast::StorageClass::kPrivate);
     auto* a = GlobalVar("a", ty.i32(), ast::StorageClass::kPrivate);
 
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Switch(Expr("a"),                                     //
                                  Case(Expr(1_i),                                //
                                       Block(Assign("v", 1_i), Fallthrough())),  //
@@ -349,8 +349,8 @@
     auto* v = GlobalVar("v", ty.i32(), ast::StorageClass::kPrivate);
     auto* a = GlobalVar("a", ty.i32(), ast::StorageClass::kPrivate);
 
-    auto* func = Func("a_func", {}, ty.void_(),
-                      {
+    auto* func = Func("a_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Switch("a",             //
                                  Case(Expr(1_i),  //
                                       Block(      //
@@ -412,8 +412,8 @@
     //   }
     // }
 
-    auto* fn = Func("f", {}, ty.i32(),
-                    {
+    auto* fn = Func("f", utils::Empty, ty.i32(),
+                    utils::Vector{
                         Switch(1_i,                                    //
                                Case(Expr(1_i), Block(Return(1_i))),    //
                                Case(Expr(2_i), Block(Fallthrough())),  //
diff --git a/src/tint/writer/spirv/builder_type_test.cc b/src/tint/writer/spirv/builder_type_test.cc
index d0003c8..58fc174 100644
--- a/src/tint/writer/spirv/builder_type_test.cc
+++ b/src/tint/writer/spirv/builder_type_test.cc
@@ -27,9 +27,9 @@
 
 TEST_F(BuilderTest_Type, GenerateRuntimeArray) {
     auto* ary = ty.array(ty.i32());
-    auto* str = Structure("S", {Member("x", ary)});
+    auto* str = Structure("S", utils::Vector{Member("x", ary)});
     GlobalVar("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -47,9 +47,9 @@
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedRuntimeArray) {
     auto* ary = ty.array(ty.i32());
-    auto* str = Structure("S", {Member("x", ary)});
+    auto* str = Structure("S", utils::Vector{Member("x", ary)});
     GlobalVar("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -323,7 +323,7 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct) {
-    auto* s = Structure("my_struct", {Member("a", ty.f32())});
+    auto* s = Structure("my_struct", utils::Vector{Member("a", ty.f32())});
 
     spirv::Builder& b = Build();
 
@@ -340,9 +340,9 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.f32()),
-                                 Member("b", ty.f32(), {MemberAlign(8)}),
+                                 Member("b", ty.f32(), utils::Vector{MemberAlign(8)}),
                              });
 
     spirv::Builder& b = Build();
@@ -364,7 +364,7 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct_NonLayout_Matrix) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.mat2x2<f32>()),
                                  Member("b", ty.mat2x3<f32>()),
                                  Member("c", ty.mat4x4<f32>()),
@@ -404,7 +404,7 @@
 
 TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers_LayoutMatrix) {
     // We have to infer layout for matrix when it also has an offset.
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.mat2x2<f32>()),
                                  Member("b", ty.mat2x3<f32>()),
                                  Member("c", ty.mat4x4<f32>()),
@@ -450,7 +450,7 @@
     auto* arr_arr_mat2x3 = ty.array(ty.mat2x3<f32>(), 1_u);  // Doubly nested array
     auto* rtarr_mat4x4 = ty.array(ty.mat4x4<f32>());         // Runtime array
 
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", arr_mat2x2),
                                  Member("b", arr_arr_mat2x3),
                                  Member("c", rtarr_mat4x4),
@@ -843,7 +843,7 @@
                                  ast::Access::kWrite);
 
     GlobalVar("test_var", s,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -862,7 +862,7 @@
                                  ast::Access::kWrite);
 
     GlobalVar("test_var", s,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -881,7 +881,7 @@
                                  ast::Access::kWrite);
 
     GlobalVar("test_var", s,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -900,7 +900,7 @@
                                  ast::Access::kWrite);
 
     GlobalVar("test_var", s,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -919,7 +919,7 @@
                                  ast::Access::kWrite);
 
     GlobalVar("test_var", s,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -938,7 +938,7 @@
                                  ast::Access::kWrite);
 
     GlobalVar("test_var", s,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -957,7 +957,7 @@
                                  ast::Access::kWrite);
 
     GlobalVar("test_var", s,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
diff --git a/src/tint/writer/spirv/generator_impl.cc b/src/tint/writer/spirv/generator_impl.cc
index a435fb8..8586562 100644
--- a/src/tint/writer/spirv/generator_impl.cc
+++ b/src/tint/writer/spirv/generator_impl.cc
@@ -26,6 +26,7 @@
 #include "src/tint/transform/for_loop_to_loop.h"
 #include "src/tint/transform/manager.h"
 #include "src/tint/transform/promote_side_effects_to_decl.h"
+#include "src/tint/transform/remove_phonies.h"
 #include "src/tint/transform/remove_unreachable_statements.h"
 #include "src/tint/transform/simplify_pointers.h"
 #include "src/tint/transform/unshadow.h"
@@ -75,6 +76,7 @@
     manager.Add<transform::PromoteSideEffectsToDecl>();
     manager.Add<transform::UnwindDiscardFunctions>();
     manager.Add<transform::SimplifyPointers>();  // Required for arrayLength()
+    manager.Add<transform::RemovePhonies>();
     manager.Add<transform::VectorizeScalarMatrixConstructors>();
     manager.Add<transform::ForLoopToLoop>();  // Must come after
     manager.Add<transform::WhileToLoop>();    // ZeroInitWorkgroupMemory
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index f3129c0..efc9601 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -67,7 +67,7 @@
             return false;
         }
     }
-    if (!program_->AST().Enables().empty()) {
+    if (!program_->AST().Enables().IsEmpty()) {
         line();
     }
     // Generate global declarations in the order they appear in the module.
@@ -80,13 +80,14 @@
                 [&](const ast::TypeDecl* td) { return EmitTypeDecl(td); },
                 [&](const ast::Function* func) { return EmitFunction(func); },
                 [&](const ast::Variable* var) { return EmitVariable(line(), var); },
+                [&](const ast::StaticAssert* sa) { return EmitStaticAssert(sa); },
                 [&](Default) {
                     TINT_UNREACHABLE(Writer, diagnostics_);
                     return false;
                 })) {
             return false;
         }
-        if (decl != program_->AST().GlobalDeclarations().back()) {
+        if (decl != program_->AST().GlobalDeclarations().Back()) {
             line();
         }
     }
@@ -281,7 +282,7 @@
 }
 
 bool GeneratorImpl::EmitFunction(const ast::Function* func) {
-    if (func->attributes.size()) {
+    if (func->attributes.Length()) {
         if (!EmitAttributes(line(), func->attributes)) {
             return false;
         }
@@ -297,7 +298,7 @@
             }
             first = false;
 
-            if (!v->attributes.empty()) {
+            if (!v->attributes.IsEmpty()) {
                 if (!EmitAttributes(out, v->attributes)) {
                     return false;
                 }
@@ -313,10 +314,10 @@
 
         out << ")";
 
-        if (!func->return_type->Is<ast::Void>() || !func->return_type_attributes.empty()) {
+        if (!func->return_type->Is<ast::Void>() || !func->return_type_attributes.IsEmpty()) {
             out << " -> ";
 
-            if (!func->return_type_attributes.empty()) {
+            if (!func->return_type_attributes.IsEmpty()) {
                 if (!EmitAttributes(out, func->return_type_attributes)) {
                     return false;
                 }
@@ -582,7 +583,7 @@
 }
 
 bool GeneratorImpl::EmitStructType(const ast::Struct* str) {
-    if (str->attributes.size()) {
+    if (str->attributes.Length()) {
         if (!EmitAttributes(line(), str->attributes)) {
             return false;
         }
@@ -614,15 +615,15 @@
         // Offset attributes no longer exist in the WGSL spec, but are emitted
         // by the SPIR-V reader and are consumed by the Resolver(). These should not
         // be emitted, but instead struct padding fields should be emitted.
-        ast::AttributeList attributes_sanitized;
-        attributes_sanitized.reserve(mem->attributes.size());
+        utils::Vector<const ast::Attribute*, 4> attributes_sanitized;
+        attributes_sanitized.Reserve(mem->attributes.Length());
         for (auto* attr : mem->attributes) {
             if (!attr->Is<ast::StructMemberOffsetAttribute>()) {
-                attributes_sanitized.emplace_back(attr);
+                attributes_sanitized.Push(attr);
             }
         }
 
-        if (!attributes_sanitized.empty()) {
+        if (!attributes_sanitized.IsEmpty()) {
             if (!EmitAttributes(line(), attributes_sanitized)) {
                 return false;
             }
@@ -642,7 +643,7 @@
 }
 
 bool GeneratorImpl::EmitVariable(std::ostream& out, const ast::Variable* v) {
-    if (!v->attributes.empty()) {
+    if (!v->attributes.IsEmpty()) {
         if (!EmitAttributes(out, v->attributes)) {
             return false;
         }
@@ -707,7 +708,8 @@
     return true;
 }
 
-bool GeneratorImpl::EmitAttributes(std::ostream& out, const ast::AttributeList& attrs) {
+bool GeneratorImpl::EmitAttributes(std::ostream& out,
+                                   utils::VectorRef<const ast::Attribute*> attrs) {
     bool first = true;
     for (auto* attr : attrs) {
         if (!first) {
@@ -945,6 +947,7 @@
         [&](const ast::ForLoopStatement* l) { return EmitForLoop(l); },
         [&](const ast::WhileStatement* l) { return EmitWhile(l); },
         [&](const ast::ReturnStatement* r) { return EmitReturn(r); },
+        [&](const ast::StaticAssert* s) { return EmitStaticAssert(s); },
         [&](const ast::SwitchStatement* s) { return EmitSwitch(s); },
         [&](const ast::VariableDeclStatement* v) { return EmitVariable(line(), v->variable); },
         [&](Default) {
@@ -954,7 +957,7 @@
         });
 }
 
-bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) {
+bool GeneratorImpl::EmitStatements(utils::VectorRef<const ast::Statement*> stmts) {
     for (auto* s : stmts) {
         if (!EmitStatement(s)) {
             return false;
@@ -963,7 +966,7 @@
     return true;
 }
 
-bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) {
+bool GeneratorImpl::EmitStatementsWithIndent(utils::VectorRef<const ast::Statement*> stmts) {
     ScopedIndent si(this);
     return EmitStatements(stmts);
 }
@@ -1243,6 +1246,16 @@
     return true;
 }
 
+bool GeneratorImpl::EmitStaticAssert(const ast::StaticAssert* stmt) {
+    auto out = line();
+    out << "static_assert ";
+    if (!EmitExpression(out, stmt->condition)) {
+        return false;
+    }
+    out << ";";
+    return true;
+}
+
 bool GeneratorImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
     {
         auto out = line();
diff --git a/src/tint/writer/wgsl/generator_impl.h b/src/tint/writer/wgsl/generator_impl.h
index 8ceeab2..f4fc467 100644
--- a/src/tint/writer/wgsl/generator_impl.h
+++ b/src/tint/writer/wgsl/generator_impl.h
@@ -165,6 +165,10 @@
     /// @param stmt the statement to emit
     /// @returns true if the statement was successfully emitted
     bool EmitReturn(const ast::ReturnStatement* stmt);
+    /// Handles static assertion statements
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was successfully emitted
+    bool EmitStaticAssert(const ast::StaticAssert* stmt);
     /// Handles statement
     /// @param stmt the statement to emit
     /// @returns true if the statement was emitted
@@ -172,11 +176,11 @@
     /// Handles a statement list
     /// @param stmts the statements to emit
     /// @returns true if the statements were emitted
-    bool EmitStatements(const ast::StatementList& stmts);
+    bool EmitStatements(utils::VectorRef<const ast::Statement*> stmts);
     /// Handles a statement list with an increased indentation
     /// @param stmts the statements to emit
     /// @returns true if the statements were emitted
-    bool EmitStatementsWithIndent(const ast::StatementList& stmts);
+    bool EmitStatementsWithIndent(utils::VectorRef<const ast::Statement*> stmts);
     /// Handles generating a switch statement
     /// @param stmt the statement to emit
     /// @returns true if the statement was emitted
@@ -214,7 +218,7 @@
     /// @param out the output of the expression stream
     /// @param attrs the attribute list
     /// @returns true if the attributes were emitted
-    bool EmitAttributes(std::ostream& out, const ast::AttributeList& attrs);
+    bool EmitAttributes(std::ostream& out, utils::VectorRef<const ast::Attribute*> attrs);
 };
 
 }  // namespace tint::writer::wgsl
diff --git a/src/tint/writer/wgsl/generator_impl_alias_type_test.cc b/src/tint/writer/wgsl/generator_impl_alias_type_test.cc
index 92a6868..dd0584d 100644
--- a/src/tint/writer/wgsl/generator_impl_alias_type_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_alias_type_test.cc
@@ -30,7 +30,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitTypeDecl_Struct) {
-    auto* s = Structure("A", {
+    auto* s = Structure("A", utils::Vector{
                                  Member("a", ty.f32()),
                                  Member("b", ty.i32()),
                              });
@@ -50,7 +50,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitAlias_ToStruct) {
-    auto* s = Structure("A", {
+    auto* s = Structure("A", utils::Vector{
                                  Member("a", ty.f32()),
                                  Member("b", ty.i32()),
                              });
diff --git a/src/tint/writer/wgsl/generator_impl_call_test.cc b/src/tint/writer/wgsl/generator_impl_call_test.cc
index 24f7e4b..4746be2 100644
--- a/src/tint/writer/wgsl/generator_impl_call_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_call_test.cc
@@ -23,7 +23,10 @@
 using WgslGeneratorImplTest = TestHelper;
 
 TEST_F(WgslGeneratorImplTest, EmitExpression_Call_WithoutParams) {
-    Func("my_func", {}, ty.f32(), {Return(1.23_f)});
+    Func("my_func", utils::Empty, ty.f32(),
+         utils::Vector{
+             Return(1.23_f),
+         });
 
     auto* call = Call("my_func");
     WrapInFunction(call);
@@ -37,11 +40,14 @@
 
 TEST_F(WgslGeneratorImplTest, EmitExpression_Call_WithParams) {
     Func("my_func",
-         {
+         utils::Vector{
              Param(Sym(), ty.f32()),
              Param(Sym(), ty.f32()),
          },
-         ty.f32(), {Return(1.23_f)});
+         ty.f32(),
+         utils::Vector{
+             Return(1.23_f),
+         });
     GlobalVar("param1", ty.f32(), ast::StorageClass::kPrivate);
     GlobalVar("param2", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -57,11 +63,11 @@
 
 TEST_F(WgslGeneratorImplTest, EmitStatement_Call) {
     Func("my_func",
-         {
+         utils::Vector{
              Param(Sym(), ty.f32()),
              Param(Sym(), ty.f32()),
          },
-         ty.void_(), ast::StatementList{}, ast::AttributeList{});
+         ty.void_(), utils::Empty, utils::Empty);
     GlobalVar("param1", ty.f32(), ast::StorageClass::kPrivate);
     GlobalVar("param2", ty.f32(), ast::StorageClass::kPrivate);
 
diff --git a/src/tint/writer/wgsl/generator_impl_case_test.cc b/src/tint/writer/wgsl/generator_impl_case_test.cc
index c12997e..6d59970 100644
--- a/src/tint/writer/wgsl/generator_impl_case_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_case_test.cc
@@ -37,7 +37,13 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_Case_MultipleSelectors) {
-    auto* s = Switch(1_i, Case({Expr(5_i), Expr(6_i)}, Block(create<ast::BreakStatement>())),
+    auto* s = Switch(1_i,
+                     Case(
+                         utils::Vector{
+                             Expr(5_i),
+                             Expr(6_i),
+                         },
+                         Block(create<ast::BreakStatement>())),
                      DefaultCase());
     WrapInFunction(s);
 
diff --git a/src/tint/writer/wgsl/generator_impl_function_test.cc b/src/tint/writer/wgsl/generator_impl_function_test.cc
index a7dab7a..9e9c3a3 100644
--- a/src/tint/writer/wgsl/generator_impl_function_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_function_test.cc
@@ -25,8 +25,8 @@
 using WgslGeneratorImplTest = TestHelper;
 
 TEST_F(WgslGeneratorImplTest, Emit_Function) {
-    auto* func = Func("my_func", {}, ty.void_(),
-                      {
+    auto* func = Func("my_func", utils::Empty, ty.void_(),
+                      utils::Vector{
                           Return(),
                       });
 
@@ -42,8 +42,13 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_Function_WithParams) {
-    auto* func = Func("my_func", {Param("a", ty.f32()), Param("b", ty.i32())}, ty.void_(),
-                      {
+    auto* func = Func("my_func",
+                      utils::Vector{
+                          Param("a", ty.f32()),
+                          Param("b", ty.i32()),
+                      },
+                      ty.void_(),
+                      utils::Vector{
                           Return(),
                       });
 
@@ -59,8 +64,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_Function_WithAttribute_WorkgroupSize) {
-    auto* func = Func("my_func", {}, ty.void_(), {Return()},
-                      {
+    auto* func = Func("my_func", utils::Empty, ty.void_(),
+                      utils::Vector{
+                          Return(),
+                      },
+                      utils::Vector{
                           Stage(ast::PipelineStage::kCompute),
                           WorkgroupSize(2_i, 4_i, 6_i),
                       });
@@ -79,8 +87,11 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_Function_WithAttribute_WorkgroupSize_WithIdent) {
     GlobalConst("height", ty.i32(), Expr(2_i));
-    auto* func = Func("my_func", {}, ty.void_(), {Return()},
-                      {
+    auto* func = Func("my_func", utils::Empty, ty.void_(),
+                      utils::Vector{
+                          Return(),
+                      },
+                      utils::Vector{
                           Stage(ast::PipelineStage::kCompute),
                           WorkgroupSize(2_i, "height"),
                       });
@@ -99,10 +110,16 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_Function_EntryPoint_Parameters) {
     auto* vec4 = ty.vec4<f32>();
-    auto* coord = Param("coord", vec4, {Builtin(ast::BuiltinValue::kPosition)});
-    auto* loc1 = Param("loc1", ty.f32(), {Location(1u)});
-    auto* func = Func("frag_main", {coord, loc1}, ty.void_(), {},
-                      {
+    auto* coord = Param("coord", vec4,
+                        utils::Vector{
+                            Builtin(ast::BuiltinValue::kPosition),
+                        });
+    auto* loc1 = Param("loc1", ty.f32(),
+                       utils::Vector{
+                           Location(1u),
+                       });
+    auto* func = Func("frag_main", utils::Vector{coord, loc1}, ty.void_(), utils::Empty,
+                      utils::Vector{
                           Stage(ast::PipelineStage::kFragment),
                       });
 
@@ -118,14 +135,14 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_Function_EntryPoint_ReturnValue) {
-    auto* func = Func("frag_main", {}, ty.f32(),
-                      {
+    auto* func = Func("frag_main", utils::Empty, ty.f32(),
+                      utils::Vector{
                           Return(1_f),
                       },
-                      {
+                      utils::Vector{
                           Stage(ast::PipelineStage::kFragment),
                       },
-                      {
+                      utils::Vector{
                           Location(1u),
                       });
 
@@ -158,10 +175,12 @@
     //   return;
     // }
 
-    auto* s = Structure("Data", {Member("d", ty.f32())});
+    auto* s = Structure("Data", utils::Vector{
+                                    Member("d", ty.f32()),
+                                });
 
     GlobalVar("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(0u),
                   create<ast::GroupAttribute>(0u),
               });
@@ -169,12 +188,12 @@
     {
         auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
 
-        Func("a", {}, ty.void_(),
-             {
+        Func("a", utils::Empty, ty.void_(),
+             utils::Vector{
                  Decl(var),
                  Return(),
              },
-             {
+             utils::Vector{
                  Stage(ast::PipelineStage::kCompute),
                  WorkgroupSize(1_i),
              });
@@ -183,12 +202,12 @@
     {
         auto* var = Var("v", ty.f32(), ast::StorageClass::kNone, MemberAccessor("data", "d"));
 
-        Func("b", {}, ty.void_(),
-             {
+        Func("b", utils::Empty, ty.void_(),
+             utils::Vector{
                  Decl(var),
                  Return(),
              },
-             {
+             utils::Vector{
                  Stage(ast::PipelineStage::kCompute),
                  WorkgroupSize(1_i),
              });
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 ef9e11c..04f520a 100644
--- a/src/tint/writer/wgsl/generator_impl_global_decl_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_global_decl_test.cc
@@ -47,25 +47,29 @@
 TEST_F(WgslGeneratorImplTest, Emit_GlobalsInterleaved) {
     GlobalVar("a0", ty.f32(), ast::StorageClass::kPrivate);
 
-    auto* s0 = Structure("S0", {Member("a", ty.i32())});
+    auto* s0 = Structure("S0", utils::Vector{
+                                   Member("a", ty.i32()),
+                               });
 
     Func("func", {}, ty.f32(),
-         {
+         utils::Vector{
              Return("a0"),
          },
-         {});
+         utils::Empty);
 
     GlobalVar("a1", ty.f32(), ast::StorageClass::kPrivate);
 
-    auto* s1 = Structure("S1", {Member("a", ty.i32())});
+    auto* s1 = Structure("S1", utils::Vector{
+                                   Member("a", ty.i32()),
+                               });
 
     Func("main", {}, ty.void_(),
-         {
+         utils::Vector{
              Decl(Var("s0", ty.Of(s0))),
              Decl(Var("s1", ty.Of(s1))),
              Assign("a1", Call("func")),
          },
-         {
+         utils::Vector{
              Stage(ast::PipelineStage::kCompute),
              WorkgroupSize(1_i),
          });
@@ -102,7 +106,7 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_Global_Sampler) {
     GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler),
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::GroupAttribute>(0u),
                   create<ast::BindingAttribute>(0u),
               });
@@ -118,7 +122,7 @@
 TEST_F(WgslGeneratorImplTest, Emit_Global_Texture) {
     auto* st = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
     GlobalVar("t", st,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::GroupAttribute>(0u),
                   create<ast::BindingAttribute>(0u),
               });
@@ -148,7 +152,10 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_OverridableConstants) {
     Override("a", ty.f32(), nullptr);
-    Override("b", ty.f32(), nullptr, {Id(7u)});
+    Override("b", ty.f32(), nullptr,
+             utils::Vector{
+                 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 34760ba..7386cd0 100644
--- a/src/tint/writer/wgsl/generator_impl_member_accessor_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_member_accessor_test.cc
@@ -20,7 +20,7 @@
 using WgslGeneratorImplTest = TestHelper;
 
 TEST_F(WgslGeneratorImplTest, EmitExpression_MemberAccessor) {
-    auto* s = Structure("Data", {Member("mem", ty.f32())});
+    auto* s = Structure("Data", utils::Vector{Member("mem", ty.f32())});
     GlobalVar("str", ty.Of(s), ast::StorageClass::kPrivate);
 
     auto* expr = MemberAccessor("str", "mem");
@@ -34,7 +34,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitExpression_MemberAccessor_OfDref) {
-    auto* s = Structure("Data", {Member("mem", ty.f32())});
+    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"));
diff --git a/src/tint/writer/wgsl/generator_impl_return_test.cc b/src/tint/writer/wgsl/generator_impl_return_test.cc
index ed1027d..fbbf9de 100644
--- a/src/tint/writer/wgsl/generator_impl_return_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_return_test.cc
@@ -35,7 +35,7 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_ReturnWithValue) {
     auto* r = Return(123_i);
-    Func("f", {}, ty.i32(), {r});
+    Func("f", utils::Empty, ty.i32(), utils::Vector{r});
 
     GeneratorImpl& gen = Build();
 
diff --git a/src/tint/writer/wgsl/generator_impl_static_assert_test.cc b/src/tint/writer/wgsl/generator_impl_static_assert_test.cc
new file mode 100644
index 0000000..9e7a1c0
--- /dev/null
+++ b/src/tint/writer/wgsl/generator_impl_static_assert_test.cc
@@ -0,0 +1,47 @@
+// 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/writer/wgsl/test_helper.h"
+
+using namespace tint::number_suffixes;  // NOLINT
+
+namespace tint::writer::wgsl {
+namespace {
+
+using WgslGeneratorImplTest = TestHelper;
+
+TEST_F(WgslGeneratorImplTest, Emit_GlobalStaticAssert) {
+    GlobalStaticAssert(true);
+
+    GeneratorImpl& gen = Build();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(static_assert true;
+)");
+}
+
+TEST_F(WgslGeneratorImplTest, Emit_FunctionStaticAssert) {
+    Func("f", utils::Empty, ty.void_(), utils::Vector{StaticAssert(true)});
+
+    GeneratorImpl& gen = Build();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_EQ(gen.result(), R"(fn f() {
+  static_assert true;
+}
+)");
+}
+
+}  // namespace
+}  // namespace tint::writer::wgsl
diff --git a/src/tint/writer/wgsl/generator_impl_switch_test.cc b/src/tint/writer/wgsl/generator_impl_switch_test.cc
index 141a5d7..491660e 100644
--- a/src/tint/writer/wgsl/generator_impl_switch_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_switch_test.cc
@@ -25,18 +25,18 @@
     GlobalVar("cond", ty.i32(), ast::StorageClass::kPrivate);
 
     auto* def_body = Block(create<ast::BreakStatement>());
-    auto* def = create<ast::CaseStatement>(ast::CaseSelectorList{}, def_body);
+    auto* def = create<ast::CaseStatement>(utils::Empty, def_body);
 
-    ast::CaseSelectorList case_val;
-    case_val.push_back(Expr(5_i));
+    utils::Vector case_val{Expr(5_i)};
 
     auto* case_body = Block(create<ast::BreakStatement>());
 
     auto* case_stmt = create<ast::CaseStatement>(case_val, case_body);
 
-    ast::CaseStatementList body;
-    body.push_back(case_stmt);
-    body.push_back(def);
+    utils::Vector body{
+        case_stmt,
+        def,
+    };
 
     auto* cond = Expr("cond");
     auto* s = create<ast::SwitchStatement>(cond, body);
diff --git a/src/tint/writer/wgsl/generator_impl_type_test.cc b/src/tint/writer/wgsl/generator_impl_type_test.cc
index 9536b5b..a86c2a0 100644
--- a/src/tint/writer/wgsl/generator_impl_type_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_type_test.cc
@@ -162,7 +162,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Struct) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
                                  Member("b", ty.f32()),
                              });
@@ -177,9 +177,9 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_StructOffsetDecl) {
-    auto* s = Structure("S", {
-                                 Member("a", ty.i32(), {MemberOffset(8)}),
-                                 Member("b", ty.f32(), {MemberOffset(16)}),
+    auto* s = Structure("S", utils::Vector{
+                                 Member("a", ty.i32(), utils::Vector{MemberOffset(8)}),
+                                 Member("b", ty.f32(), utils::Vector{MemberOffset(16)}),
                              });
 
     GeneratorImpl& gen = Build();
@@ -197,10 +197,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_StructOffsetDecl_WithSymbolCollisions) {
-    auto* s = Structure("S", {
-                                 Member("tint_0_padding", ty.i32(), {MemberOffset(8)}),
-                                 Member("tint_2_padding", ty.f32(), {MemberOffset(16)}),
-                             });
+    auto* s =
+        Structure("S", utils::Vector{
+                           Member("tint_0_padding", ty.i32(), utils::Vector{MemberOffset(8)}),
+                           Member("tint_2_padding", ty.f32(), utils::Vector{MemberOffset(16)}),
+                       });
 
     GeneratorImpl& gen = Build();
 
@@ -217,9 +218,9 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_StructAlignDecl) {
-    auto* s = Structure("S", {
-                                 Member("a", ty.i32(), {MemberAlign(8)}),
-                                 Member("b", ty.f32(), {MemberAlign(16)}),
+    auto* s = Structure("S", utils::Vector{
+                                 Member("a", ty.i32(), utils::Vector{MemberAlign(8)}),
+                                 Member("b", ty.f32(), utils::Vector{MemberAlign(16)}),
                              });
 
     GeneratorImpl& gen = Build();
@@ -235,9 +236,9 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_StructSizeDecl) {
-    auto* s = Structure("S", {
-                                 Member("a", ty.i32(), {MemberSize(16)}),
-                                 Member("b", ty.f32(), {MemberSize(32)}),
+    auto* s = Structure("S", utils::Vector{
+                                 Member("a", ty.i32(), utils::Vector{MemberSize(16)}),
+                                 Member("b", ty.f32(), utils::Vector{MemberSize(32)}),
                              });
 
     GeneratorImpl& gen = Build();
@@ -253,9 +254,9 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Struct_WithAttribute) {
-    auto* s = Structure("S", {
+    auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
-                                 Member("b", ty.f32(), {MemberAlign(8)}),
+                                 Member("b", ty.f32(), utils::Vector{MemberAlign(8)}),
                              });
 
     GeneratorImpl& gen = Build();
@@ -270,9 +271,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Struct_WithEntryPointAttributes) {
-    auto* s = Structure("S", ast::StructMemberList{
-                                 Member("a", ty.u32(), {Builtin(ast::BuiltinValue::kVertexIndex)}),
-                                 Member("b", ty.f32(), {Location(2u)})});
+    auto* s = Structure(
+        "S", utils::Vector{
+                 Member("a", ty.u32(), utils::Vector{Builtin(ast::BuiltinValue::kVertexIndex)}),
+                 Member("b", ty.f32(), utils::Vector{Location(2u)}),
+             });
 
     GeneratorImpl& gen = Build();
 
@@ -460,7 +463,7 @@
 
     auto* t = ty.storage_texture(param.dim, param.fmt, param.access);
     GlobalVar("g", t,
-              ast::AttributeList{
+              utils::Vector{
                   create<ast::BindingAttribute>(1u),
                   create<ast::GroupAttribute>(2u),
               });
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 abfe4fec..1d5e4d4 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
@@ -52,7 +52,11 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_AInt) {
     auto* C = Const("C", nullptr, Expr(1_a));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -67,7 +71,11 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_AFloat) {
     auto* C = Const("C", nullptr, Expr(1._a));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -82,7 +90,11 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_i32) {
     auto* C = Const("C", nullptr, Expr(1_i));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -97,7 +109,11 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_u32) {
     auto* C = Const("C", nullptr, Expr(1_u));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -112,7 +128,11 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_f32) {
     auto* C = Const("C", nullptr, Expr(1_f));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -129,7 +149,11 @@
     Enable(ast::Extension::kF16);
 
     auto* C = Const("C", nullptr, Expr(1_h));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -146,7 +170,11 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_AInt) {
     auto* C = Const("C", nullptr, Construct(ty.vec3(nullptr), 1_a, 2_a, 3_a));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -161,7 +189,11 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_AFloat) {
     auto* C = Const("C", nullptr, Construct(ty.vec3(nullptr), 1._a, 2._a, 3._a));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -176,7 +208,11 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_f32) {
     auto* C = Const("C", nullptr, vec3<f32>(1_f, 2_f, 3_f));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -193,7 +229,11 @@
     Enable(ast::Extension::kF16);
 
     auto* C = Const("C", nullptr, vec3<f16>(1_h, 2_h, 3_h));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -211,7 +251,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));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -226,7 +270,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));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -243,7 +291,11 @@
     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", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -260,7 +312,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));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, Expr(C))),
+         });
 
     GeneratorImpl& gen = Build();
 
@@ -279,7 +335,11 @@
                               vec2<bool>(true, false),         //
                               vec2<bool>(false, true),         //
                               vec2<bool>(true, true)));
-    Func("f", {}, ty.void_(), {Decl(C), Decl(Let("l", nullptr, Expr(C)))});
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(C),
+             Decl(Let("l", nullptr, 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 a058bc7..78e518e 100644
--- a/src/tint/writer/wgsl/generator_impl_variable_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_variable_test.cc
@@ -42,9 +42,9 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitVariable_Access_Read) {
-    auto* s = Structure("S", {Member("a", ty.i32())});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.i32())});
     auto* v = GlobalVar("a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
-                        ast::AttributeList{
+                        utils::Vector{
                             create<ast::BindingAttribute>(0u),
                             create<ast::GroupAttribute>(0u),
                         });
@@ -57,9 +57,9 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitVariable_Access_ReadWrite) {
-    auto* s = Structure("S", {Member("a", ty.i32())});
+    auto* s = Structure("S", utils::Vector{Member("a", ty.i32())});
     auto* v = GlobalVar("a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
-                        ast::AttributeList{
+                        utils::Vector{
                             create<ast::BindingAttribute>(0u),
                             create<ast::GroupAttribute>(0u),
                         });
@@ -74,7 +74,7 @@
 TEST_F(WgslGeneratorImplTest, EmitVariable_Decorated) {
     auto* v =
         GlobalVar("a", ty.sampler(ast::SamplerKind::kSampler), ast::StorageClass::kNone, nullptr,
-                  ast::AttributeList{
+                  utils::Vector{
                       create<ast::GroupAttribute>(1u),
                       create<ast::BindingAttribute>(2u),
                   });