Cleanup code using RegisterSymbol

This CL goes through and converts things which call RegisterSymbol to
use the helper builders. Several variables are also updated to use the
helper as it will need RegisterSymbol in the near future.

Change-Id: Ib5a8e8be54c1eaad123384fab09f6625421d9fcd
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/35880
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Commit-Queue: David Neto <dneto@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Reviewed-by: David Neto <dneto@google.com>
Auto-Submit: dan sinclair <dsinclair@chromium.org>
diff --git a/src/ast/builder.h b/src/ast/builder.h
index 807dba2..62a874c 100644
--- a/src/ast/builder.h
+++ b/src/ast/builder.h
@@ -33,12 +33,14 @@
 #include "src/ast/struct.h"
 #include "src/ast/struct_member.h"
 #include "src/ast/struct_member_offset_decoration.h"
+#include "src/ast/type/alias_type.h"
 #include "src/ast/type/array_type.h"
 #include "src/ast/type/bool_type.h"
 #include "src/ast/type/f32_type.h"
 #include "src/ast/type/i32_type.h"
 #include "src/ast/type/matrix_type.h"
 #include "src/ast/type/pointer_type.h"
+#include "src/ast/type/struct_type.h"
 #include "src/ast/type/u32_type.h"
 #include "src/ast/type/vector_type.h"
 #include "src/ast/type/void_type.h"
@@ -159,6 +161,14 @@
     return array(Of<T>(), N);
   }
 
+  /// Creates an alias type
+  /// @param name the alias name
+  /// @param type the alias type
+  /// @returns the alias pointer
+  type::Alias* alias(const std::string& name, type::Type* type) const {
+    return mod_->create<type::Alias>(mod_->RegisterSymbol(name), name, type);
+  }
+
   /// @return the tint AST pointer to type `T` with the given StorageClass.
   /// @param storage_class the storage class of the pointer
   template <typename T>
@@ -166,6 +176,13 @@
     return mod_->create<type::Pointer>(Of<T>(), storage_class);
   }
 
+  /// @param name the struct name
+  /// @param impl the struct implementation
+  /// @returns a struct pointer
+  type::Struct* struct_(const std::string& name, ast::Struct* impl) const {
+    return mod_->create<type::Struct>(mod_->RegisterSymbol(name), name, impl);
+  }
+
  private:
   /// CToAST<T> is specialized for various `T` types and each specialization
   /// contains a single static `get()` method for obtaining the corresponding
@@ -213,6 +230,14 @@
     return create<IdentifierExpression>(mod->RegisterSymbol(name), name);
   }
 
+  /// @param source the source information
+  /// @param name the identifier name
+  /// @return an IdentifierExpression with the given name
+  IdentifierExpression* Expr(const Source& source, const std::string& name) {
+    return create<IdentifierExpression>(source, mod->RegisterSymbol(name),
+                                        name);
+  }
+
   /// @param name the identifier name
   /// @return an IdentifierExpression with the given name
   IdentifierExpression* Expr(const char* name) {
@@ -618,6 +643,18 @@
   }
 
   /// Creates a StructMember
+  /// @param source the source information
+  /// @param name the struct member name
+  /// @param type the struct member type
+  /// @returns the struct member pointer
+  StructMember* Member(const Source& source,
+                       const std::string& name,
+                       type::Type* type) {
+    return mod->create<StructMember>(source, mod->RegisterSymbol(name), name,
+                                     type, StructMemberDecorationList{});
+  }
+
+  /// Creates a StructMember
   /// @param name the struct member name
   /// @param type the struct member type
   /// @returns the struct member pointer
diff --git a/src/ast/expression_test.cc b/src/ast/expression_test.cc
index 35a381e..b48409d 100644
--- a/src/ast/expression_test.cc
+++ b/src/ast/expression_test.cc
@@ -41,11 +41,11 @@
 }
 
 TEST_F(ExpressionTest, set_result_type_alias) {
-  type::Alias a(mod->RegisterSymbol("a"), "a", ty.i32);
-  type::Alias b(mod->RegisterSymbol("b"), "b", &a);
+  auto* a = ty.alias("a", ty.i32);
+  auto* b = ty.alias("b", a);
 
   FakeExpr e;
-  e.set_result_type(&b);
+  e.set_result_type(b);
   ASSERT_NE(e.result_type(), nullptr);
   EXPECT_TRUE(e.result_type()->Is<type::I32>());
 }
diff --git a/src/ast/function_test.cc b/src/ast/function_test.cc
index 718a2ef..039bc28 100644
--- a/src/ast/function_test.cc
+++ b/src/ast/function_test.cc
@@ -29,16 +29,13 @@
 using FunctionTest = TestHelper;
 
 TEST_F(FunctionTest, Creation) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   VariableList params;
   params.push_back(Var("var", StorageClass::kNone, ty.i32));
   auto* var = params[0];
 
-  auto* f = create<Function>(func_sym, "func", params, ty.void_,
-                             create<BlockStatement>(StatementList{}),
-                             FunctionDecorationList{});
-  EXPECT_EQ(f->symbol(), func_sym);
+  auto* f =
+      Func("func", params, ty.void_, StatementList{}, FunctionDecorationList{});
+  EXPECT_EQ(f->symbol(), mod->RegisterSymbol("func"));
   EXPECT_EQ(f->name(), "func");
   ASSERT_EQ(f->params().size(), 1u);
   EXPECT_EQ(f->return_type(), ty.void_);
@@ -46,26 +43,20 @@
 }
 
 TEST_F(FunctionTest, Creation_WithSource) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   VariableList params;
   params.push_back(Var("var", StorageClass::kNone, ty.i32));
 
-  auto* f = create<Function>(
-      Source{Source::Location{20, 2}}, func_sym, "func", params, ty.void_,
-      create<BlockStatement>(StatementList{}), FunctionDecorationList{});
+  auto* f = Func(Source{Source::Location{20, 2}}, "func", params, ty.void_,
+                 StatementList{}, FunctionDecorationList{});
   auto src = f->source();
   EXPECT_EQ(src.range.begin.line, 20u);
   EXPECT_EQ(src.range.begin.column, 2u);
 }
 
 TEST_F(FunctionTest, AddDuplicateReferencedVariables) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   auto* v = Var("var", StorageClass::kInput, ty.i32);
-  auto* f = create<Function>(func_sym, "func", VariableList{}, ty.void_,
-                             create<BlockStatement>(StatementList{}),
-                             FunctionDecorationList{});
+  auto* f = Func("func", VariableList{}, ty.void_, StatementList{},
+                 FunctionDecorationList{});
 
   f->add_referenced_module_variable(v);
   ASSERT_EQ(f->referenced_module_variables().size(), 1u);
@@ -81,8 +72,6 @@
 }
 
 TEST_F(FunctionTest, GetReferenceLocations) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   auto* loc1 = Var("loc1", StorageClass::kInput, ty.i32, nullptr,
                    ast::VariableDecorationList{
                        create<LocationDecoration>(0),
@@ -103,9 +92,8 @@
                            create<BuiltinDecoration>(Builtin::kFragDepth),
                        });
 
-  auto* f = create<Function>(func_sym, "func", VariableList{}, ty.void_,
-                             create<BlockStatement>(StatementList{}),
-                             FunctionDecorationList{});
+  auto* f = Func("func", VariableList{}, ty.void_, StatementList{},
+                 FunctionDecorationList{});
 
   f->add_referenced_module_variable(loc1);
   f->add_referenced_module_variable(builtin1);
@@ -122,8 +110,6 @@
 }
 
 TEST_F(FunctionTest, GetReferenceBuiltins) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   auto* loc1 = Var("loc1", StorageClass::kInput, ty.i32, nullptr,
                    ast::VariableDecorationList{
                        create<LocationDecoration>(0),
@@ -144,9 +130,8 @@
                            create<BuiltinDecoration>(Builtin::kFragDepth),
                        });
 
-  auto* f = create<Function>(func_sym, "func", VariableList{}, ty.void_,
-                             create<BlockStatement>(StatementList{}),
-                             FunctionDecorationList{});
+  auto* f = Func("func", VariableList{}, ty.void_, StatementList{},
+                 FunctionDecorationList{});
 
   f->add_referenced_module_variable(loc1);
   f->add_referenced_module_variable(builtin1);
@@ -163,13 +148,10 @@
 }
 
 TEST_F(FunctionTest, AddDuplicateEntryPoints) {
-  auto func_sym = mod->RegisterSymbol("func");
+  auto* f = Func("func", VariableList{}, ty.void_, StatementList{},
+                 FunctionDecorationList{});
+
   auto main_sym = mod->RegisterSymbol("main");
-
-  auto* f = create<Function>(func_sym, "func", VariableList{}, ty.void_,
-                             create<BlockStatement>(StatementList{}),
-                             FunctionDecorationList{});
-
   f->add_ancestor_entry_point(main_sym);
   ASSERT_EQ(1u, f->ancestor_entry_points().size());
   EXPECT_EQ(main_sym, f->ancestor_entry_points()[0]);
@@ -180,111 +162,87 @@
 }
 
 TEST_F(FunctionTest, IsValid) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   VariableList params;
   params.push_back(Var("var", StorageClass::kNone, ty.i32));
 
-  auto* body = create<BlockStatement>(StatementList{
-      create<DiscardStatement>(),
-  });
-
-  auto* f = create<Function>(func_sym, "func", params, ty.void_, body,
-                             FunctionDecorationList{});
+  auto* f = Func("func", params, ty.void_,
+                 StatementList{
+                     create<DiscardStatement>(),
+                 },
+                 FunctionDecorationList{});
   EXPECT_TRUE(f->IsValid());
 }
 
 TEST_F(FunctionTest, IsValid_InvalidName) {
-  auto func_sym = mod->RegisterSymbol("");
-
   VariableList params;
   params.push_back(Var("var", StorageClass::kNone, ty.i32));
 
-  auto* f = create<Function>(func_sym, "", params, ty.void_,
-                             create<BlockStatement>(StatementList{}),
-                             FunctionDecorationList{});
+  auto* f =
+      Func("", params, ty.void_, StatementList{}, FunctionDecorationList{});
   EXPECT_FALSE(f->IsValid());
 }
 
 TEST_F(FunctionTest, IsValid_MissingReturnType) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   VariableList params;
   params.push_back(Var("var", StorageClass::kNone, ty.i32));
 
-  auto* f = create<Function>(func_sym, "func", params, nullptr,
-                             create<BlockStatement>(StatementList{}),
-                             FunctionDecorationList{});
+  auto* f =
+      Func("func", params, nullptr, StatementList{}, FunctionDecorationList{});
   EXPECT_FALSE(f->IsValid());
 }
 
 TEST_F(FunctionTest, IsValid_NullParam) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   VariableList params;
   params.push_back(Var("var", StorageClass::kNone, ty.i32));
   params.push_back(nullptr);
 
-  auto* f = create<Function>(func_sym, "func", params, ty.void_,
-                             create<BlockStatement>(StatementList{}),
-                             FunctionDecorationList{});
+  auto* f =
+      Func("func", params, ty.void_, StatementList{}, FunctionDecorationList{});
   EXPECT_FALSE(f->IsValid());
 }
 
 TEST_F(FunctionTest, IsValid_InvalidParam) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   VariableList params;
   params.push_back(Var("var", StorageClass::kNone, nullptr));
 
-  auto* f = create<Function>(func_sym, "func", params, ty.void_,
-                             create<BlockStatement>(StatementList{}),
-                             FunctionDecorationList{});
+  auto* f =
+      Func("func", params, ty.void_, StatementList{}, FunctionDecorationList{});
   EXPECT_FALSE(f->IsValid());
 }
 
 TEST_F(FunctionTest, IsValid_NullBodyStatement) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   VariableList params;
   params.push_back(Var("var", StorageClass::kNone, ty.i32));
 
-  auto* body = create<BlockStatement>(StatementList{
-      create<DiscardStatement>(),
-      nullptr,
-  });
-
-  auto* f = create<Function>(func_sym, "func", params, ty.void_, body,
-                             FunctionDecorationList{});
+  auto* f = Func("func", params, ty.void_,
+                 StatementList{
+                     create<DiscardStatement>(),
+                     nullptr,
+                 },
+                 FunctionDecorationList{});
 
   EXPECT_FALSE(f->IsValid());
 }
 
 TEST_F(FunctionTest, IsValid_InvalidBodyStatement) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   VariableList params;
   params.push_back(Var("var", StorageClass::kNone, ty.i32));
 
-  auto* body = create<BlockStatement>(StatementList{
-      create<DiscardStatement>(),
-      nullptr,
-  });
-
-  auto* f = create<Function>(func_sym, "func", params, ty.void_, body,
-                             FunctionDecorationList{});
+  auto* f = Func("func", params, ty.void_,
+                 StatementList{
+                     create<DiscardStatement>(),
+                     nullptr,
+                 },
+                 FunctionDecorationList{});
   EXPECT_FALSE(f->IsValid());
 }
 
 TEST_F(FunctionTest, ToStr) {
-  auto func_sym = mod->RegisterSymbol("func");
-
-  auto* body = create<BlockStatement>(StatementList{
-      create<DiscardStatement>(),
-  });
-
-  auto* f = create<Function>(func_sym, "func", VariableList{}, ty.void_, body,
-                             FunctionDecorationList{});
+  auto* f = Func("func", VariableList{}, ty.void_,
+                 StatementList{
+                     create<DiscardStatement>(),
+                 },
+                 FunctionDecorationList{});
 
   std::ostringstream out;
   f->to_str(out, 2);
@@ -297,14 +255,11 @@
 }
 
 TEST_F(FunctionTest, ToStr_WithDecoration) {
-  auto func_sym = mod->RegisterSymbol("func");
-
-  auto* body = create<BlockStatement>(StatementList{
-      create<DiscardStatement>(),
-  });
-  auto* f = create<Function>(
-      func_sym, "func", VariableList{}, ty.void_, body,
-      FunctionDecorationList{create<WorkgroupDecoration>(2, 4, 6)});
+  auto* f = Func("func", VariableList{}, ty.void_,
+                 StatementList{
+                     create<DiscardStatement>(),
+                 },
+                 FunctionDecorationList{create<WorkgroupDecoration>(2, 4, 6)});
 
   std::ostringstream out;
   f->to_str(out, 2);
@@ -318,16 +273,14 @@
 }
 
 TEST_F(FunctionTest, ToStr_WithParams) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   VariableList params;
   params.push_back(Var("var", StorageClass::kNone, ty.i32));
 
-  auto* body = create<BlockStatement>(StatementList{
-      create<DiscardStatement>(),
-  });
-  auto* f = create<Function>(func_sym, "func", params, ty.void_, body,
-                             FunctionDecorationList{});
+  auto* f = Func("func", params, ty.void_,
+                 StatementList{
+                     create<DiscardStatement>(),
+                 },
+                 FunctionDecorationList{});
 
   std::ostringstream out;
   f->to_str(out, 2);
@@ -346,56 +299,41 @@
 }
 
 TEST_F(FunctionTest, TypeName) {
-  auto func_sym = mod->RegisterSymbol("func");
-
-  auto* f = create<Function>(func_sym, "func", VariableList{}, ty.void_,
-                             create<BlockStatement>(StatementList{}),
-                             FunctionDecorationList{});
+  auto* f = Func("func", VariableList{}, ty.void_, StatementList{},
+                 FunctionDecorationList{});
   EXPECT_EQ(f->type_name(), "__func__void");
 }
 
 TEST_F(FunctionTest, TypeName_WithParams) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   VariableList params;
   params.push_back(Var("var1", StorageClass::kNone, ty.i32));
   params.push_back(Var("var2", StorageClass::kNone, ty.f32));
 
-  auto* f = create<Function>(func_sym, "func", params, ty.void_,
-                             create<BlockStatement>(StatementList{}),
-                             FunctionDecorationList{});
+  auto* f =
+      Func("func", params, ty.void_, StatementList{}, FunctionDecorationList{});
   EXPECT_EQ(f->type_name(), "__func__void__i32__f32");
 }
 
 TEST_F(FunctionTest, GetLastStatement) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   VariableList params;
   auto* stmt = create<DiscardStatement>();
-  auto* body = create<BlockStatement>(StatementList{stmt});
-  auto* f = create<Function>(func_sym, "func", params, ty.void_, body,
-                             FunctionDecorationList{});
+  auto* f = Func("func", params, ty.void_, StatementList{stmt},
+                 FunctionDecorationList{});
 
   EXPECT_EQ(f->get_last_statement(), stmt);
 }
 
 TEST_F(FunctionTest, GetLastStatement_nullptr) {
-  auto func_sym = mod->RegisterSymbol("func");
-
   VariableList params;
-  auto* body = create<BlockStatement>(StatementList{});
-  auto* f = create<Function>(func_sym, "func", params, ty.void_, body,
-                             FunctionDecorationList{});
+  auto* f =
+      Func("func", params, ty.void_, StatementList{}, FunctionDecorationList{});
 
   EXPECT_EQ(f->get_last_statement(), nullptr);
 }
 
 TEST_F(FunctionTest, WorkgroupSize_NoneSet) {
-  auto func_sym = mod->RegisterSymbol("func");
-
-  auto* f = create<Function>(func_sym, "func", VariableList{}, ty.void_,
-                             create<BlockStatement>(StatementList{}),
-                             FunctionDecorationList{});
+  auto* f = Func("func", VariableList{}, ty.void_, StatementList{},
+                 FunctionDecorationList{});
   uint32_t x = 0;
   uint32_t y = 0;
   uint32_t z = 0;
@@ -406,12 +344,9 @@
 }
 
 TEST_F(FunctionTest, WorkgroupSize) {
-  auto func_sym = mod->RegisterSymbol("func");
-
-  auto* f = create<Function>(
-      func_sym, "func", VariableList{}, ty.void_,
-      create<BlockStatement>(StatementList{}),
-      FunctionDecorationList{create<WorkgroupDecoration>(2u, 4u, 6u)});
+  auto* f =
+      Func("func", VariableList{}, ty.void_, StatementList{},
+           FunctionDecorationList{create<WorkgroupDecoration>(2u, 4u, 6u)});
 
   uint32_t x = 0;
   uint32_t y = 0;
diff --git a/src/ast/identifier_expression_test.cc b/src/ast/identifier_expression_test.cc
index 92b30d3..959cbb2 100644
--- a/src/ast/identifier_expression_test.cc
+++ b/src/ast/identifier_expression_test.cc
@@ -29,8 +29,7 @@
 }
 
 TEST_F(IdentifierExpressionTest, Creation_WithSource) {
-  auto* i = create<IdentifierExpression>(Source{Source::Location{20, 2}},
-                                         mod->RegisterSymbol("ident"), "ident");
+  auto* i = Expr(Source{Source::Location{20, 2}}, "ident");
   EXPECT_EQ(i->symbol(), Symbol(1));
   EXPECT_EQ(i->name(), "ident");
 
diff --git a/src/ast/if_statement_test.cc b/src/ast/if_statement_test.cc
index ba3e6f7..6e4bf62 100644
--- a/src/ast/if_statement_test.cc
+++ b/src/ast/if_statement_test.cc
@@ -56,8 +56,7 @@
   auto* stmt = create<IfStatement>(
       cond, body,
       ElseStatementList{
-          create<ElseStatement>(create<IdentifierExpression>(
-                                    mod->RegisterSymbol("Ident"), "Ident"),
+          create<ElseStatement>(Expr("Ident"),
                                 create<BlockStatement>(StatementList{})),
           create<ElseStatement>(nullptr,
                                 create<BlockStatement>(StatementList{})),
@@ -110,8 +109,7 @@
   auto* stmt = create<IfStatement>(
       cond, body,
       ElseStatementList{
-          create<ElseStatement>(create<IdentifierExpression>(
-                                    mod->RegisterSymbol("Ident"), "Ident"),
+          create<ElseStatement>(Expr("Ident"),
                                 create<BlockStatement>(StatementList{})),
           create<ElseStatement>(nullptr,
                                 create<BlockStatement>(StatementList{})),
@@ -157,8 +155,7 @@
       ElseStatementList{
           create<ElseStatement>(nullptr,
                                 create<BlockStatement>(StatementList{})),
-          create<ElseStatement>(create<IdentifierExpression>(
-                                    mod->RegisterSymbol("Ident"), "Ident"),
+          create<ElseStatement>(Expr("Ident"),
                                 create<BlockStatement>(StatementList{})),
       });
   EXPECT_FALSE(stmt->IsValid());
@@ -194,9 +191,7 @@
   auto* stmt = create<IfStatement>(
       cond, body,
       ElseStatementList{
-          create<ElseStatement>(create<IdentifierExpression>(
-                                    mod->RegisterSymbol("ident"), "ident"),
-                                else_if_body),
+          create<ElseStatement>(Expr("ident"), else_if_body),
           create<ElseStatement>(nullptr, else_body),
       });
 
diff --git a/src/ast/member_accessor_expression_test.cc b/src/ast/member_accessor_expression_test.cc
index fb1d3a3..c7d1dd5 100644
--- a/src/ast/member_accessor_expression_test.cc
+++ b/src/ast/member_accessor_expression_test.cc
@@ -26,8 +26,7 @@
 using MemberAccessorExpressionTest = TestHelper;
 
 TEST_F(MemberAccessorExpressionTest, Creation) {
-  auto* str = create<IdentifierExpression>(mod->RegisterSymbol("structure"),
-                                           "structure");
+  auto* str = Expr("structure");
   auto* mem = Expr("member");
 
   auto* stmt = create<MemberAccessorExpression>(str, mem);
@@ -36,73 +35,48 @@
 }
 
 TEST_F(MemberAccessorExpressionTest, Creation_WithSource) {
-  auto* str = create<IdentifierExpression>(mod->RegisterSymbol("structure"),
-                                           "structure");
-  auto* mem = Expr("member");
-
-  auto* stmt = create<MemberAccessorExpression>(Source{Source::Location{20, 2}},
-                                                str, mem);
+  auto* stmt = create<MemberAccessorExpression>(
+      Source{Source::Location{20, 2}}, Expr("structure"), Expr("member"));
   auto src = stmt->source();
   EXPECT_EQ(src.range.begin.line, 20u);
   EXPECT_EQ(src.range.begin.column, 2u);
 }
 
 TEST_F(MemberAccessorExpressionTest, IsMemberAccessor) {
-  auto* str = create<IdentifierExpression>(mod->RegisterSymbol("structure"),
-                                           "structure");
-  auto* mem = Expr("member");
-
-  auto* stmt = create<MemberAccessorExpression>(str, mem);
+  auto* stmt =
+      create<MemberAccessorExpression>(Expr("structure"), Expr("member"));
   EXPECT_TRUE(stmt->Is<MemberAccessorExpression>());
 }
 
 TEST_F(MemberAccessorExpressionTest, IsValid) {
-  auto* str = create<IdentifierExpression>(mod->RegisterSymbol("structure"),
-                                           "structure");
-  auto* mem = Expr("member");
-
-  auto* stmt = create<MemberAccessorExpression>(str, mem);
+  auto* stmt =
+      create<MemberAccessorExpression>(Expr("structure"), Expr("member"));
   EXPECT_TRUE(stmt->IsValid());
 }
 
 TEST_F(MemberAccessorExpressionTest, IsValid_NullStruct) {
-  auto* mem = Expr("member");
-
-  auto* stmt = create<MemberAccessorExpression>(nullptr, mem);
+  auto* stmt = create<MemberAccessorExpression>(nullptr, Expr("member"));
   EXPECT_FALSE(stmt->IsValid());
 }
 
 TEST_F(MemberAccessorExpressionTest, IsValid_InvalidStruct) {
-  auto* str = Expr("");
-  auto* mem = Expr("member");
-
-  auto* stmt = create<MemberAccessorExpression>(str, mem);
+  auto* stmt = create<MemberAccessorExpression>(Expr(""), Expr("member"));
   EXPECT_FALSE(stmt->IsValid());
 }
 
 TEST_F(MemberAccessorExpressionTest, IsValid_NullMember) {
-  auto* str = create<IdentifierExpression>(mod->RegisterSymbol("structure"),
-                                           "structure");
-
-  auto* stmt = create<MemberAccessorExpression>(str, nullptr);
+  auto* stmt = create<MemberAccessorExpression>(Expr("structure"), nullptr);
   EXPECT_FALSE(stmt->IsValid());
 }
 
 TEST_F(MemberAccessorExpressionTest, IsValid_InvalidMember) {
-  auto* str = create<IdentifierExpression>(mod->RegisterSymbol("structure"),
-                                           "structure");
-  auto* mem = Expr("");
-
-  auto* stmt = create<MemberAccessorExpression>(str, mem);
+  auto* stmt = create<MemberAccessorExpression>(Expr("structure"), Expr(""));
   EXPECT_FALSE(stmt->IsValid());
 }
 
 TEST_F(MemberAccessorExpressionTest, ToStr) {
-  auto* str = create<IdentifierExpression>(mod->RegisterSymbol("structure"),
-                                           "structure");
-  auto* mem = Expr("member");
-
-  auto* stmt = create<MemberAccessorExpression>(str, mem);
+  auto* stmt =
+      create<MemberAccessorExpression>(Expr("structure"), Expr("member"));
   std::ostringstream out;
   stmt->to_str(out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  MemberAccessor[not set]{
diff --git a/src/ast/module_test.cc b/src/ast/module_test.cc
index e826382..d195f32 100644
--- a/src/ast/module_test.cc
+++ b/src/ast/module_test.cc
@@ -32,122 +32,91 @@
 using ModuleTest = TestHelper;
 
 TEST_F(ModuleTest, Creation) {
-  Module m;
-
-  EXPECT_EQ(m.functions().size(), 0u);
+  EXPECT_EQ(mod->functions().size(), 0u);
 }
 
 TEST_F(ModuleTest, ToStrEmitsPreambleAndPostamble) {
-  Module m;
-  const auto str = m.to_str();
+  const auto str = mod->to_str();
   auto* const expected = "Module{\n}\n";
   EXPECT_EQ(str, expected);
 }
 
 TEST_F(ModuleTest, LookupFunction) {
-  Module m;
-
-  auto func_sym = m.RegisterSymbol("main");
-  auto* func = create<Function>(func_sym, "main", VariableList{}, ty.f32,
-                                create<BlockStatement>(StatementList{}),
-                                ast::FunctionDecorationList{});
-  m.AddFunction(func);
-  EXPECT_EQ(func, m.FindFunctionBySymbol(func_sym));
+  auto* func = Func("main", VariableList{}, ty.f32, StatementList{},
+                    ast::FunctionDecorationList{});
+  mod->AddFunction(func);
+  EXPECT_EQ(func, mod->FindFunctionBySymbol(mod->RegisterSymbol("main")));
 }
 
 TEST_F(ModuleTest, LookupFunctionMissing) {
-  Module m;
-  EXPECT_EQ(nullptr, m.FindFunctionBySymbol(m.RegisterSymbol("Missing")));
+  EXPECT_EQ(nullptr, mod->FindFunctionBySymbol(mod->RegisterSymbol("Missing")));
 }
 
 TEST_F(ModuleTest, IsValid_Empty) {
-  Module m;
-  EXPECT_TRUE(m.IsValid());
+  EXPECT_TRUE(mod->IsValid());
 }
 
 TEST_F(ModuleTest, IsValid_GlobalVariable) {
-  auto* var = create<Variable>("var", StorageClass::kInput, ty.f32, false,
-                               nullptr, ast::VariableDecorationList{});
-
-  Module m;
-  m.AddGlobalVariable(var);
-  EXPECT_TRUE(m.IsValid());
+  auto* var = Var("var", StorageClass::kInput, ty.f32);
+  mod->AddGlobalVariable(var);
+  EXPECT_TRUE(mod->IsValid());
 }
 
 TEST_F(ModuleTest, IsValid_Null_GlobalVariable) {
-  Module m;
-  m.AddGlobalVariable(nullptr);
-  EXPECT_FALSE(m.IsValid());
+  mod->AddGlobalVariable(nullptr);
+  EXPECT_FALSE(mod->IsValid());
 }
 
 TEST_F(ModuleTest, IsValid_Invalid_GlobalVariable) {
-  auto* var = create<Variable>("var", StorageClass::kInput, nullptr, false,
-                               nullptr, ast::VariableDecorationList{});
-
-  Module m;
-  m.AddGlobalVariable(var);
-  EXPECT_FALSE(m.IsValid());
+  auto* var = Var("var", StorageClass::kInput, nullptr);
+  mod->AddGlobalVariable(var);
+  EXPECT_FALSE(mod->IsValid());
 }
 
 TEST_F(ModuleTest, IsValid_Alias) {
-  type::Alias alias(mod->RegisterSymbol("alias"), "alias", ty.f32);
-
-  Module m;
-  m.AddConstructedType(&alias);
-  EXPECT_TRUE(m.IsValid());
+  auto* alias = ty.alias("alias", ty.f32);
+  mod->AddConstructedType(alias);
+  EXPECT_TRUE(mod->IsValid());
 }
 
 TEST_F(ModuleTest, IsValid_Null_Alias) {
-  Module m;
-  m.AddConstructedType(nullptr);
-  EXPECT_FALSE(m.IsValid());
+  mod->AddConstructedType(nullptr);
+  EXPECT_FALSE(mod->IsValid());
 }
 
 TEST_F(ModuleTest, IsValid_Struct) {
-  type::Struct st(mod->RegisterSymbol("name"), "name", {});
-  type::Alias alias(mod->RegisterSymbol("name"), "name", &st);
-
-  Module m;
-  m.AddConstructedType(&alias);
-  EXPECT_TRUE(m.IsValid());
+  auto* st = ty.struct_("name", {});
+  auto* alias = ty.alias("name", st);
+  mod->AddConstructedType(alias);
+  EXPECT_TRUE(mod->IsValid());
 }
 
 TEST_F(ModuleTest, IsValid_Struct_EmptyName) {
-  type::Struct st(mod->RegisterSymbol(""), "", {});
-  type::Alias alias(mod->RegisterSymbol("name"), "name", &st);
-
-  Module m;
-  m.AddConstructedType(&alias);
-  EXPECT_FALSE(m.IsValid());
+  auto* st = ty.struct_("", {});
+  auto* alias = ty.alias("name", st);
+  mod->AddConstructedType(alias);
+  EXPECT_FALSE(mod->IsValid());
 }
 
 TEST_F(ModuleTest, IsValid_Function) {
-  Module m;
+  auto* func = Func("main", VariableList(), ty.f32, StatementList{},
+                    ast::FunctionDecorationList{});
 
-  auto* func = create<Function>(
-      m.RegisterSymbol("main"), "main", VariableList(), ty.f32,
-      create<BlockStatement>(StatementList{}), ast::FunctionDecorationList{});
-
-  m.AddFunction(func);
-  EXPECT_TRUE(m.IsValid());
+  mod->AddFunction(func);
+  EXPECT_TRUE(mod->IsValid());
 }
 
 TEST_F(ModuleTest, IsValid_Null_Function) {
-  Module m;
-  m.AddFunction(nullptr);
-  EXPECT_FALSE(m.IsValid());
+  mod->AddFunction(nullptr);
+  EXPECT_FALSE(mod->IsValid());
 }
 
 TEST_F(ModuleTest, IsValid_Invalid_Function) {
-  VariableList p;
+  auto* func = Func("main", VariableList{}, nullptr, StatementList{},
+                    ast::FunctionDecorationList{});
 
-  Module m;
-
-  auto* func = create<Function>(m.RegisterSymbol("main"), "main", p, nullptr,
-                                nullptr, ast::FunctionDecorationList{});
-
-  m.AddFunction(func);
-  EXPECT_FALSE(m.IsValid());
+  mod->AddFunction(func);
+  EXPECT_FALSE(mod->IsValid());
 }
 
 }  // namespace
diff --git a/src/ast/struct_member_test.cc b/src/ast/struct_member_test.cc
index 0a526aa..cc7485b 100644
--- a/src/ast/struct_member_test.cc
+++ b/src/ast/struct_member_test.cc
@@ -28,11 +28,7 @@
 using StructMemberTest = TestHelper;
 
 TEST_F(StructMemberTest, Creation) {
-  StructMemberDecorationList decorations;
-  decorations.emplace_back(create<StructMemberOffsetDecoration>(4));
-
-  auto* st =
-      create<StructMember>(mod->RegisterSymbol("a"), "a", ty.i32, decorations);
+  auto* st = Member("a", ty.i32, {MemberOffset(4)});
   EXPECT_EQ(st->symbol(), Symbol(1));
   EXPECT_EQ(st->name(), "a");
   EXPECT_EQ(st->type(), ty.i32);
@@ -45,9 +41,9 @@
 }
 
 TEST_F(StructMemberTest, CreationWithSource) {
-  auto* st = create<StructMember>(
+  auto* st = Member(
       Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 8}}},
-      mod->RegisterSymbol("a"), "a", ty.i32, StructMemberDecorationList{});
+      "a", ty.i32);
   EXPECT_EQ(st->symbol(), Symbol(1));
   EXPECT_EQ(st->name(), "a");
   EXPECT_EQ(st->type(), ty.i32);
@@ -59,47 +55,34 @@
 }
 
 TEST_F(StructMemberTest, IsValid) {
-  auto* st = create<StructMember>(mod->RegisterSymbol("a"), "a", ty.i32,
-                                  StructMemberDecorationList{});
+  auto* st = Member("a", ty.i32);
   EXPECT_TRUE(st->IsValid());
 }
 
 TEST_F(StructMemberTest, IsValid_EmptySymbol) {
-  auto* st = create<StructMember>(mod->RegisterSymbol(""), "", ty.i32,
-                                  StructMemberDecorationList{});
+  auto* st = Member("", ty.i32);
   EXPECT_FALSE(st->IsValid());
 }
 
 TEST_F(StructMemberTest, IsValid_NullType) {
-  auto* st = create<StructMember>(mod->RegisterSymbol("a"), "a", nullptr,
-                                  StructMemberDecorationList{});
+  auto* st = Member("a", nullptr);
   EXPECT_FALSE(st->IsValid());
 }
 
 TEST_F(StructMemberTest, IsValid_Null_Decoration) {
-  StructMemberDecorationList decorations;
-  decorations.emplace_back(create<StructMemberOffsetDecoration>(4));
-  decorations.push_back(nullptr);
-
-  auto* st =
-      create<StructMember>(mod->RegisterSymbol("a"), "a", ty.i32, decorations);
+  auto* st = Member("a", ty.i32, {MemberOffset(4), nullptr});
   EXPECT_FALSE(st->IsValid());
 }
 
 TEST_F(StructMemberTest, ToStr) {
-  StructMemberDecorationList decorations;
-  decorations.emplace_back(create<StructMemberOffsetDecoration>(4));
-
-  auto* st =
-      create<StructMember>(mod->RegisterSymbol("a"), "a", ty.i32, decorations);
+  auto* st = Member("a", ty.i32, {MemberOffset(4)});
   std::ostringstream out;
   st->to_str(out, 2);
   EXPECT_EQ(demangle(out.str()), "  StructMember{[[ offset 4 ]] a: __i32}\n");
 }
 
 TEST_F(StructMemberTest, ToStrNoDecorations) {
-  auto* st = create<StructMember>(mod->RegisterSymbol("a"), "a", ty.i32,
-                                  StructMemberDecorationList{});
+  auto* st = Member("a", ty.i32);
   std::ostringstream out;
   st->to_str(out, 2);
   EXPECT_EQ(demangle(out.str()), "  StructMember{a: __i32}\n");
diff --git a/src/ast/struct_test.cc b/src/ast/struct_test.cc
index 443795f..a0af738 100644
--- a/src/ast/struct_test.cc
+++ b/src/ast/struct_test.cc
@@ -30,28 +30,21 @@
 using StructTest = TestHelper;
 
 TEST_F(StructTest, Creation) {
-  StructMemberList members;
-  members.push_back(create<StructMember>(mod->RegisterSymbol("a"), "a", ty.i32,
-                                         StructMemberDecorationList{}));
-
-  auto* s = create<Struct>(members, ast::StructDecorationList{});
+  auto* s = create<Struct>(StructMemberList{Member("a", ty.i32)},
+                           StructDecorationList{});
   EXPECT_EQ(s->members().size(), 1u);
   EXPECT_TRUE(s->decorations().empty());
   EXPECT_EQ(s->source().range.begin.line, 0u);
   EXPECT_EQ(s->source().range.begin.column, 0u);
   EXPECT_EQ(s->source().range.end.line, 0u);
   EXPECT_EQ(s->source().range.end.column, 0u);
-}  // namespace
+}
 
 TEST_F(StructTest, Creation_WithDecorations) {
-  StructMemberList members;
-  members.push_back(create<StructMember>(mod->RegisterSymbol("a"), "a", ty.i32,
-                                         StructMemberDecorationList{}));
-
   StructDecorationList decos;
   decos.push_back(create<StructBlockDecoration>());
 
-  auto* s = create<Struct>(members, decos);
+  auto* s = create<Struct>(StructMemberList{Member("a", ty.i32)}, decos);
   EXPECT_EQ(s->members().size(), 1u);
   ASSERT_EQ(s->decorations().size(), 1u);
   EXPECT_TRUE(s->decorations()[0]->Is<StructBlockDecoration>());
@@ -62,16 +55,12 @@
 }
 
 TEST_F(StructTest, CreationWithSourceAndDecorations) {
-  StructMemberList members;
-  members.emplace_back(create<StructMember>(
-      mod->RegisterSymbol("a"), "a", ty.i32, StructMemberDecorationList{}));
-
   StructDecorationList decos;
   decos.push_back(create<StructBlockDecoration>());
 
   auto* s = create<Struct>(
       Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 8}}},
-      members, decos);
+      StructMemberList{Member("a", ty.i32)}, decos);
   EXPECT_EQ(s->members().size(), 1u);
   ASSERT_EQ(s->decorations().size(), 1u);
   EXPECT_TRUE(s->decorations()[0]->Is<StructBlockDecoration>());
@@ -87,33 +76,21 @@
 }
 
 TEST_F(StructTest, IsValid_Null_StructMember) {
-  StructMemberList members;
-  members.push_back(create<StructMember>(mod->RegisterSymbol("a"), "a", ty.i32,
-                                         StructMemberDecorationList{}));
-  members.push_back(nullptr);
-
-  auto* s = create<Struct>(members, ast::StructDecorationList{});
+  auto* s = create<Struct>(StructMemberList{Member("a", ty.i32), nullptr},
+                           StructDecorationList{});
   EXPECT_FALSE(s->IsValid());
-}  // namespace ast
+}
 
 TEST_F(StructTest, IsValid_Invalid_StructMember) {
-  StructMemberList members;
-  members.push_back(create<StructMember>(mod->RegisterSymbol(""), "", ty.i32,
-                                         StructMemberDecorationList{}));
-
-  auto* s = create<Struct>(members, ast::StructDecorationList{});
+  auto* s = create<Struct>(StructMemberList{Member("", ty.i32)},
+                           ast::StructDecorationList{});
   EXPECT_FALSE(s->IsValid());
-}  // namespace tint
+}
 
 TEST_F(StructTest, ToStr) {
-  StructMemberList members;
-  members.emplace_back(create<StructMember>(
-      mod->RegisterSymbol("a"), "a", ty.i32, StructMemberDecorationList{}));
-
   StructDecorationList decos;
   decos.push_back(create<StructBlockDecoration>());
-
-  auto* s = create<Struct>(members, decos);
+  auto* s = create<Struct>(StructMemberList{Member("a", ty.i32)}, decos);
 
   std::ostringstream out;
   s->to_str(out, 2);
diff --git a/src/ast/type/access_control_type_test.cc b/src/ast/type/access_control_type_test.cc
index 64af335..882f144 100644
--- a/src/ast/type/access_control_type_test.cc
+++ b/src/ast/type/access_control_type_test.cc
@@ -119,24 +119,13 @@
 }
 
 TEST_F(AccessControlTest, MinBufferBindingSizeStruct) {
-  U32 u32;
-  StructMemberList members;
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.u32, {MemberOffset(0)}),
+                       Member("bar", ty.u32, {MemberOffset(4)})},
+      StructDecorationList{});
 
-  StructMemberDecorationList deco;
-  deco.push_back(create<StructMemberOffsetDecoration>(0));
-  members.push_back(
-      create<StructMember>(mod->RegisterSymbol("foo"), "foo", &u32, deco));
-
-  deco = StructMemberDecorationList();
-  deco.push_back(create<StructMemberOffsetDecoration>(4));
-  members.push_back(
-      create<StructMember>(mod->RegisterSymbol("bar"), "bar", &u32, deco));
-
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  AccessControl at{ast::AccessControl::kReadOnly, &struct_type};
+  auto* struct_type = ty.struct_("struct_type", str);
+  AccessControl at{ast::AccessControl::kReadOnly, struct_type};
   EXPECT_EQ(16u, at.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
   EXPECT_EQ(8u, at.MinBufferBindingSize(MemoryLayout::kStorageBuffer));
 }
@@ -162,26 +151,13 @@
 }
 
 TEST_F(AccessControlTest, BaseAlignmentStruct) {
-  U32 u32;
-  StructMemberList members;
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.u32, {MemberOffset(0)}),
+                       Member("bar", ty.u32, {MemberOffset(4)})},
+      StructDecorationList{});
+  auto* struct_type = ty.struct_("struct_type", str);
 
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &u32, deco));
-  }
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(4));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("bar"), "bar", &u32, deco));
-  }
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  AccessControl at{ast::AccessControl::kReadOnly, &struct_type};
+  AccessControl at{ast::AccessControl::kReadOnly, struct_type};
   EXPECT_EQ(16u, at.BaseAlignment(MemoryLayout::kUniformBuffer));
   EXPECT_EQ(4u, at.BaseAlignment(MemoryLayout::kStorageBuffer));
 }
diff --git a/src/ast/type/alias_type_test.cc b/src/ast/type/alias_type_test.cc
index 9669d21..c1c59a4 100644
--- a/src/ast/type/alias_type_test.cc
+++ b/src/ast/type/alias_type_test.cc
@@ -32,7 +32,6 @@
 #include "src/ast/type/pointer_type.h"
 #include "src/ast/type/struct_type.h"
 #include "src/ast/type/texture_type.h"
-#include "src/ast/type/u32_type.h"
 #include "src/ast/type/vector_type.h"
 
 namespace tint {
@@ -43,18 +42,14 @@
 using AliasTest = TestHelper;
 
 TEST_F(AliasTest, Create) {
-  U32 u32;
-  Alias a{mod->RegisterSymbol("a_type"), "a_type", &u32};
-  EXPECT_EQ(a.symbol(), Symbol(1));
-  // EXPECT_EQ(a.name(), "a_type");
-  EXPECT_EQ(a.type(), &u32);
+  auto* a = ty.alias("a_type", ty.u32);
+  EXPECT_EQ(a->symbol(), Symbol(1));
+  EXPECT_EQ(a->type(), ty.u32);
 }
 
 TEST_F(AliasTest, Is) {
-  I32 i32;
-
-  Alias at{mod->RegisterSymbol("a"), "a", &i32};
-  Type* ty = &at;
+  auto* at = ty.alias("a", ty.i32);
+  type::Type* ty = at;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_TRUE(ty->Is<Alias>());
   EXPECT_FALSE(ty->Is<Array>());
@@ -71,200 +66,159 @@
 }
 
 TEST_F(AliasTest, TypeName) {
-  I32 i32;
-  Alias at{mod->RegisterSymbol("Particle"), "Particle", &i32};
-  EXPECT_EQ(at.type_name(), "__alias_tint_symbol_1__i32");
+  auto* at = ty.alias("Particle", ty.i32);
+  EXPECT_EQ(at->type_name(), "__alias_tint_symbol_1__i32");
 }
 
 TEST_F(AliasTest, UnwrapIfNeeded_Alias) {
-  U32 u32;
-  Alias a{mod->RegisterSymbol("a_type"), "a_type", &u32};
-  EXPECT_EQ(a.symbol(), Symbol(1));
-  // EXPECT_EQ(a.name(), "a_type");
-  EXPECT_EQ(a.type(), &u32);
-  EXPECT_EQ(a.UnwrapIfNeeded(), &u32);
-  EXPECT_EQ(u32.UnwrapIfNeeded(), &u32);
+  auto* a = ty.alias("a_type", ty.u32);
+  EXPECT_EQ(a->symbol(), Symbol(1));
+  EXPECT_EQ(a->type(), ty.u32);
+  EXPECT_EQ(a->UnwrapIfNeeded(), ty.u32);
+  EXPECT_EQ(ty.u32->UnwrapIfNeeded(), ty.u32);
 }
 
 TEST_F(AliasTest, UnwrapIfNeeded_AccessControl) {
-  U32 u32;
-  AccessControl a{ast::AccessControl::kReadOnly, &u32};
-  EXPECT_EQ(a.type(), &u32);
-  EXPECT_EQ(a.UnwrapIfNeeded(), &u32);
+  AccessControl a{ast::AccessControl::kReadOnly, ty.u32};
+  EXPECT_EQ(a.type(), ty.u32);
+  EXPECT_EQ(a.UnwrapIfNeeded(), ty.u32);
 }
 
 TEST_F(AliasTest, UnwrapIfNeeded_MultiLevel) {
-  U32 u32;
-  Alias a{mod->RegisterSymbol("a_type"), "a_type", &u32};
-  Alias aa{mod->RegisterSymbol("aa_type"), "aa_type", &a};
-  EXPECT_EQ(aa.symbol(), Symbol(2));
-  // EXPECT_EQ(aa.name(), "aa_type");
-  EXPECT_EQ(aa.type(), &a);
-  EXPECT_EQ(aa.UnwrapIfNeeded(), &u32);
+  auto* a = ty.alias("a_type", ty.u32);
+  auto* aa = ty.alias("aa_type", a);
+
+  EXPECT_EQ(aa->symbol(), Symbol(2));
+  EXPECT_EQ(aa->type(), a);
+  EXPECT_EQ(aa->UnwrapIfNeeded(), ty.u32);
 }
 
 TEST_F(AliasTest, UnwrapIfNeeded_MultiLevel_AliasAccessControl) {
-  U32 u32;
-  Alias a{mod->RegisterSymbol("a_type"), "a_type", &u32};
-  AccessControl aa{ast::AccessControl::kReadWrite, &a};
-  EXPECT_EQ(aa.type(), &a);
-  EXPECT_EQ(aa.UnwrapIfNeeded(), &u32);
+  auto* a = ty.alias("a_type", ty.u32);
+
+  AccessControl aa{ast::AccessControl::kReadWrite, a};
+  EXPECT_EQ(aa.type(), a);
+  EXPECT_EQ(aa.UnwrapIfNeeded(), ty.u32);
 }
 
 TEST_F(AliasTest, UnwrapAll_TwiceAliasPointerTwiceAlias) {
-  U32 u32;
-  Alias a{mod->RegisterSymbol("a_type"), "a_type", &u32};
-  Alias aa{mod->RegisterSymbol("aa_type"), "aa_type", &a};
-  Pointer paa{&aa, StorageClass::kUniform};
-  Alias apaa{mod->RegisterSymbol("paa_type"), "paa_type", &paa};
-  Alias aapaa{mod->RegisterSymbol("aapaa_type"), "aapaa_type", &apaa};
-  EXPECT_EQ(aapaa.symbol(), Symbol(4));
-  // EXPECT_EQ(aapaa.name(), "aapaa_type");
-  EXPECT_EQ(aapaa.type(), &apaa);
-  EXPECT_EQ(aapaa.UnwrapAll(), &u32);
-  EXPECT_EQ(u32.UnwrapAll(), &u32);
+  auto* a = ty.alias("a_type", ty.u32);
+  auto* aa = ty.alias("aa_type", a);
+  Pointer paa{aa, StorageClass::kUniform};
+  auto* apaa = ty.alias("paa_type", &paa);
+  auto* aapaa = ty.alias("aapaa_type", apaa);
+
+  EXPECT_EQ(aapaa->symbol(), Symbol(4));
+  EXPECT_EQ(aapaa->type(), apaa);
+  EXPECT_EQ(aapaa->UnwrapAll(), ty.u32);
 }
 
 TEST_F(AliasTest, UnwrapAll_SecondConsecutivePointerBlocksUnrapping) {
-  U32 u32;
-  Alias a{mod->RegisterSymbol("a_type"), "a_type", &u32};
-  Alias aa{mod->RegisterSymbol("aa_type"), "aa_type", &a};
-  Pointer paa{&aa, StorageClass::kUniform};
+  auto* a = ty.alias("a_type", ty.u32);
+  auto* aa = ty.alias("aa_type", a);
+
+  Pointer paa{aa, StorageClass::kUniform};
   Pointer ppaa{&paa, StorageClass::kUniform};
-  Alias appaa{mod->RegisterSymbol("appaa_type"), "appaa_type", &ppaa};
-  EXPECT_EQ(appaa.UnwrapAll(), &paa);
+  auto* appaa = ty.alias("appaa_type", &ppaa);
+  EXPECT_EQ(appaa->UnwrapAll(), &paa);
 }
 
 TEST_F(AliasTest, UnwrapAll_SecondNonConsecutivePointerBlocksUnrapping) {
-  U32 u32;
-  Alias a{mod->RegisterSymbol("a_type"), "a_type", &u32};
-  Alias aa{mod->RegisterSymbol("aa_type"), "aa_type", &a};
-  Pointer paa{&aa, StorageClass::kUniform};
-  Alias apaa{mod->RegisterSymbol("apaa_type"), "apaa_type", &paa};
-  Alias aapaa{mod->RegisterSymbol("aapaa_type"), "aapaa_type", &apaa};
-  Pointer paapaa{&aapaa, StorageClass::kUniform};
-  Alias apaapaa{mod->RegisterSymbol("apaapaa_type"), "apaapaa_type", &paapaa};
-  EXPECT_EQ(apaapaa.UnwrapAll(), &paa);
+  auto* a = ty.alias("a_type", ty.u32);
+  auto* aa = ty.alias("aa_type", a);
+  Pointer paa{aa, StorageClass::kUniform};
+
+  auto* apaa = ty.alias("apaa_type", &paa);
+  auto* aapaa = ty.alias("aapaa_type", apaa);
+  Pointer paapaa{aapaa, StorageClass::kUniform};
+  auto* apaapaa = ty.alias("apaapaa_type", &paapaa);
+
+  EXPECT_EQ(apaapaa->UnwrapAll(), &paa);
 }
 
 TEST_F(AliasTest, UnwrapAll_AccessControlPointer) {
-  U32 u32;
-  AccessControl a{ast::AccessControl::kReadOnly, &u32};
+  AccessControl a{ast::AccessControl::kReadOnly, ty.u32};
   Pointer pa{&a, StorageClass::kUniform};
   EXPECT_EQ(pa.type(), &a);
-  EXPECT_EQ(pa.UnwrapAll(), &u32);
-  EXPECT_EQ(u32.UnwrapAll(), &u32);
+  EXPECT_EQ(pa.UnwrapAll(), ty.u32);
 }
 
 TEST_F(AliasTest, UnwrapAll_PointerAccessControl) {
-  U32 u32;
-  Pointer p{&u32, StorageClass::kUniform};
+  Pointer p{ty.u32, StorageClass::kUniform};
   AccessControl a{ast::AccessControl::kReadOnly, &p};
+
   EXPECT_EQ(a.type(), &p);
-  EXPECT_EQ(a.UnwrapAll(), &u32);
-  EXPECT_EQ(u32.UnwrapAll(), &u32);
+  EXPECT_EQ(a.UnwrapAll(), ty.u32);
 }
 
 TEST_F(AliasTest, MinBufferBindingSizeU32) {
-  U32 u32;
-  Alias alias{mod->RegisterSymbol("alias"), "alias", &u32};
-  EXPECT_EQ(4u, alias.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
+  auto* alias = ty.alias("alias", ty.u32);
+  EXPECT_EQ(4u, alias->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
 }
 
 TEST_F(AliasTest, MinBufferBindingSizeArray) {
-  U32 u32;
-  Array array(&u32, 4,
+  Array array(ty.u32, 4,
               ArrayDecorationList{
                   create<StrideDecoration>(4),
               });
-  Alias alias{mod->RegisterSymbol("alias"), "alias", &array};
-  EXPECT_EQ(16u, alias.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
+  auto* alias = ty.alias("alias", &array);
+  EXPECT_EQ(16u, alias->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
 }
 
 TEST_F(AliasTest, MinBufferBindingSizeRuntimeArray) {
-  U32 u32;
-  Array array(&u32, 0,
+  Array array(ty.u32, 0,
               ArrayDecorationList{
                   create<StrideDecoration>(4),
               });
-  Alias alias{mod->RegisterSymbol("alias"), "alias", &array};
-  EXPECT_EQ(4u, alias.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
+  auto* alias = ty.alias("alias", &array);
+  EXPECT_EQ(4u, alias->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
 }
 
 TEST_F(AliasTest, MinBufferBindingSizeStruct) {
-  U32 u32;
-  StructMemberList members;
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.u32, {MemberOffset(0)}),
+                       Member("bar", ty.u32, {MemberOffset(4)})},
+      StructDecorationList{});
+  auto* struct_type = ty.struct_("struct_type", str);
+  auto* alias = ty.alias("alias", struct_type);
 
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &u32, deco));
-  }
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(4));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("bar"), "bar", &u32, deco));
-  }
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  Alias alias{mod->RegisterSymbol("alias"), "alias", &struct_type};
-  EXPECT_EQ(16u, alias.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(8u, alias.MinBufferBindingSize(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(16u, alias->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
+  EXPECT_EQ(8u, alias->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
 }
 
 TEST_F(AliasTest, BaseAlignmentU32) {
-  U32 u32;
-  Alias alias{mod->RegisterSymbol("alias"), "alias", &u32};
-  EXPECT_EQ(4u, alias.BaseAlignment(MemoryLayout::kUniformBuffer));
+  auto* alias = ty.alias("alias", ty.u32);
+  EXPECT_EQ(4u, alias->BaseAlignment(MemoryLayout::kUniformBuffer));
 }
 
 TEST_F(AliasTest, BaseAlignmentArray) {
-  U32 u32;
-  Array array(&u32, 4,
+  Array array(ty.u32, 4,
               ArrayDecorationList{
                   create<StrideDecoration>(4),
               });
-  Alias alias{mod->RegisterSymbol("alias"), "alias", &array};
-  EXPECT_EQ(16u, alias.BaseAlignment(MemoryLayout::kUniformBuffer));
+  auto* alias = ty.alias("alias", &array);
+  EXPECT_EQ(16u, alias->BaseAlignment(MemoryLayout::kUniformBuffer));
 }
 
 TEST_F(AliasTest, BaseAlignmentRuntimeArray) {
-  U32 u32;
-  Array array(&u32, 0,
+  Array array(ty.u32, 0,
               ArrayDecorationList{
                   create<StrideDecoration>(4),
               });
-  Alias alias{mod->RegisterSymbol("alias"), "alias", &array};
-  EXPECT_EQ(16u, alias.BaseAlignment(MemoryLayout::kUniformBuffer));
+  auto* alias = ty.alias("alias", &array);
+  EXPECT_EQ(16u, alias->BaseAlignment(MemoryLayout::kUniformBuffer));
 }
 
 TEST_F(AliasTest, BaseAlignmentStruct) {
-  U32 u32;
-  StructMemberList members;
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.u32, {MemberOffset(0)}),
+                       Member("bar", ty.u32, {MemberOffset(4)})},
+      StructDecorationList{});
+  auto* struct_type = ty.struct_("struct_type", str);
+  auto* alias = ty.alias("alias", struct_type);
 
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &u32, deco));
-  }
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(4));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("bar"), "bar", &u32, deco));
-  }
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  Alias alias{mod->RegisterSymbol("alias"), "alias", &struct_type};
-  EXPECT_EQ(16u, alias.BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(4u, alias.BaseAlignment(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(16u, alias->BaseAlignment(MemoryLayout::kUniformBuffer));
+  EXPECT_EQ(4u, alias->BaseAlignment(MemoryLayout::kStorageBuffer));
 }
 
 }  // namespace
diff --git a/src/ast/type/struct_type_test.cc b/src/ast/type/struct_type_test.cc
index ae2d248..ca5be28 100644
--- a/src/ast/type/struct_type_test.cc
+++ b/src/ast/type/struct_type_test.cc
@@ -40,18 +40,16 @@
 using StructTest = TestHelper;
 
 TEST_F(StructTest, Creation) {
-  StructMemberList members;
-  auto* impl = create<ast::Struct>(members, ast::StructDecorationList{});
+  auto* impl = create<ast::Struct>(StructMemberList{}, StructDecorationList{});
   auto* ptr = impl;
-  Struct s{mod->RegisterSymbol("S"), "S", impl};
-  EXPECT_EQ(s.impl(), ptr);
+  auto* s = ty.struct_("S", impl);
+  EXPECT_EQ(s->impl(), ptr);
 }
 
 TEST_F(StructTest, Is) {
-  StructMemberList members;
-  auto* impl = create<ast::Struct>(members, ast::StructDecorationList{});
-  Struct s{mod->RegisterSymbol("S"), "S", impl};
-  Type* ty = &s;
+  auto* impl = create<ast::Struct>(StructMemberList{}, StructDecorationList{});
+  auto* s = ty.struct_("S", impl);
+  type::Type* ty = s;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
   EXPECT_FALSE(ty->Is<Array>());
@@ -68,303 +66,143 @@
 }
 
 TEST_F(StructTest, TypeName) {
-  StructMemberList members;
-  auto* impl = create<ast::Struct>(members, ast::StructDecorationList{});
-  Struct s{mod->RegisterSymbol("my_struct"), "my_struct", impl};
-  EXPECT_EQ(s.type_name(), "__struct_tint_symbol_1");
+  auto* impl = create<ast::Struct>(StructMemberList{}, StructDecorationList{});
+  auto* s = ty.struct_("my_struct", impl);
+  EXPECT_EQ(s->type_name(), "__struct_tint_symbol_1");
 }
 
 TEST_F(StructTest, MinBufferBindingSize) {
-  U32 u32;
-  StructMemberList members;
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.u32, {MemberOffset(0)}),
+                       Member("bar", ty.u32, {MemberOffset(4)})},
+      StructDecorationList{});
+  auto* s_ty = ty.struct_("s_ty", str);
 
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &u32, deco));
-  }
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(4));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("bar"), "bar", &u32, deco));
-  }
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  EXPECT_EQ(16u,
-            struct_type.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(8u, struct_type.MinBufferBindingSize(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(16u, s_ty->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
+  EXPECT_EQ(8u, s_ty->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
 }
 
 TEST_F(StructTest, MinBufferBindingSizeArray) {
-  U32 u32;
-  Array arr(&u32, 4, ArrayDecorationList{create<StrideDecoration>(4)});
+  Array arr(ty.u32, 4, ArrayDecorationList{create<StrideDecoration>(4)});
 
-  StructMemberList members;
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &u32, deco));
-  }
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(4));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("bar"), "bar", &u32, deco));
-  }
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(8));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("bar"), "bar", &arr, deco));
-  }
-  StructDecorationList decos;
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.u32, {MemberOffset(0)}),
+                       Member("bar", ty.u32, {MemberOffset(4)}),
+                       Member("bar", &arr, {MemberOffset(8)})},
+      StructDecorationList{});
+  auto* s_ty = ty.struct_("s_ty", str);
 
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  EXPECT_EQ(32u,
-            struct_type.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(24u,
-            struct_type.MinBufferBindingSize(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(32u, s_ty->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
+  EXPECT_EQ(24u, s_ty->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
 }
 
 TEST_F(StructTest, MinBufferBindingSizeRuntimeArray) {
-  U32 u32;
-  Array arr(&u32, 0, ArrayDecorationList{create<StrideDecoration>(4)});
+  Array arr(ty.u32, 0, ArrayDecorationList{create<StrideDecoration>(4)});
 
-  StructMemberList members;
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &u32, deco));
-  }
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(4));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("bar"), "bar", &u32, deco));
-  }
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(8));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("bar"), "bar", &u32, deco));
-  }
-  StructDecorationList decos;
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.u32, {MemberOffset(0)}),
+                       Member("bar", ty.u32, {MemberOffset(4)}),
+                       Member("bar", ty.u32, {MemberOffset(8)})},
+      StructDecorationList{});
+  auto* s_ty = ty.struct_("s_ty", str);
 
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  EXPECT_EQ(12u,
-            struct_type.MinBufferBindingSize(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(12u, s_ty->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
 }
 
 TEST_F(StructTest, MinBufferBindingSizeVec2) {
-  U32 u32;
-  Vector vec2(&u32, 2);
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.vec2<u32>(), {MemberOffset(0)})},
+      StructDecorationList{});
+  auto* s_ty = ty.struct_("s_ty", str);
 
-  StructMemberList members;
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &vec2, deco));
-  }
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  EXPECT_EQ(16u,
-            struct_type.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(8u, struct_type.MinBufferBindingSize(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(16u, s_ty->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
+  EXPECT_EQ(8u, s_ty->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
 }
 
 TEST_F(StructTest, MinBufferBindingSizeVec3) {
-  U32 u32;
-  Vector vec3(&u32, 3);
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.vec3<u32>(), {MemberOffset(0)})},
+      StructDecorationList{});
+  auto* s_ty = ty.struct_("s_ty", str);
 
-  StructMemberList members;
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &vec3, deco));
-  }
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  EXPECT_EQ(16u,
-            struct_type.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(16u,
-            struct_type.MinBufferBindingSize(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(16u, s_ty->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
+  EXPECT_EQ(16u, s_ty->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
 }
 
 TEST_F(StructTest, MinBufferBindingSizeVec4) {
-  U32 u32;
-  Vector vec4(&u32, 4);
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.vec4<u32>(), {MemberOffset(0)})},
+      StructDecorationList{});
+  auto* s_ty = ty.struct_("s_ty", str);
 
-  StructMemberList members;
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &vec4, deco));
-  }
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  EXPECT_EQ(16u,
-            struct_type.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(16u,
-            struct_type.MinBufferBindingSize(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(16u, s_ty->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
+  EXPECT_EQ(16u, s_ty->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
 }
 
 TEST_F(StructTest, BaseAlignment) {
-  U32 u32;
-  StructMemberList members;
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.u32, {MemberOffset(0)}),
+                       Member("bar", ty.u32, {MemberOffset(8)})},
+      StructDecorationList{});
+  auto* s_ty = ty.struct_("s_ty", str);
 
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &u32, deco));
-  }
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(4));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("bar"), "bar", &u32, deco));
-  }
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  EXPECT_EQ(16u, struct_type.BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(4u, struct_type.BaseAlignment(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kUniformBuffer));
+  EXPECT_EQ(4u, s_ty->BaseAlignment(MemoryLayout::kStorageBuffer));
 }
 
 TEST_F(StructTest, BaseAlignmentArray) {
-  U32 u32;
-  Array arr(&u32, 4, ArrayDecorationList{create<StrideDecoration>(4)});
+  Array arr(ty.u32, 4, ArrayDecorationList{create<StrideDecoration>(4)});
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.u32, {MemberOffset(0)}),
+                       Member("bar", ty.u32, {MemberOffset(4)}),
+                       Member("bar", &arr, {MemberOffset(8)})},
+      StructDecorationList{});
+  auto* s_ty = ty.struct_("s_ty", str);
 
-  StructMemberList members;
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &u32, deco));
-  }
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(4));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("bar"), "bar", &u32, deco));
-  }
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(8));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("bar"), "bar", &arr, deco));
-  }
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  EXPECT_EQ(16u, struct_type.BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(4u, struct_type.BaseAlignment(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kUniformBuffer));
+  EXPECT_EQ(4u, s_ty->BaseAlignment(MemoryLayout::kStorageBuffer));
 }
 
 TEST_F(StructTest, BaseAlignmentRuntimeArray) {
-  U32 u32;
-  Array arr(&u32, 0, ArrayDecorationList{create<StrideDecoration>(4)});
+  Array arr(ty.u32, 0, ArrayDecorationList{create<StrideDecoration>(4)});
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.u32, {MemberOffset(0)}),
+                       Member("bar", ty.u32, {MemberOffset(4)}),
+                       Member("bar", ty.u32, {MemberOffset(8)})},
+      StructDecorationList{});
+  auto* s_ty = ty.struct_("s_ty", str);
 
-  StructMemberList members;
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &u32, deco));
-  }
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(4));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("bar"), "bar", &u32, deco));
-  }
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(8));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("bar"), "bar", &u32, deco));
-  }
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  EXPECT_EQ(4u, struct_type.BaseAlignment(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(4u, s_ty->BaseAlignment(MemoryLayout::kStorageBuffer));
 }
 
 TEST_F(StructTest, BaseAlignmentVec2) {
-  U32 u32;
-  Vector vec2(&u32, 2);
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.vec2<u32>(), {MemberOffset(0)})},
+      StructDecorationList{});
+  auto* s_ty = ty.struct_("s_ty", str);
 
-  StructMemberList members;
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &vec2, deco));
-  }
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  EXPECT_EQ(16u, struct_type.BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(8u, struct_type.BaseAlignment(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kUniformBuffer));
+  EXPECT_EQ(8u, s_ty->BaseAlignment(MemoryLayout::kStorageBuffer));
 }
 
 TEST_F(StructTest, BaseAlignmentVec3) {
-  U32 u32;
-  Vector vec3(&u32, 3);
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.vec3<u32>(), {MemberOffset(0)})},
+      StructDecorationList{});
+  auto* s_ty = ty.struct_("s_ty", str);
 
-  StructMemberList members;
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &vec3, deco));
-  }
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  EXPECT_EQ(16u, struct_type.BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(16u, struct_type.BaseAlignment(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kUniformBuffer));
+  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kStorageBuffer));
 }
 
 TEST_F(StructTest, BaseAlignmentVec4) {
-  U32 u32;
-  Vector vec4(&u32, 4);
+  auto* str = create<ast::Struct>(
+      StructMemberList{Member("foo", ty.vec4<u32>(), {MemberOffset(0)})},
+      StructDecorationList{});
+  auto* s_ty = ty.struct_("s_ty", str);
 
-  StructMemberList members;
-  {
-    StructMemberDecorationList deco;
-    deco.push_back(create<StructMemberOffsetDecoration>(0));
-    members.push_back(
-        create<StructMember>(mod->RegisterSymbol("foo"), "foo", &vec4, deco));
-  }
-  StructDecorationList decos;
-
-  auto* str = create<ast::Struct>(members, decos);
-  Struct struct_type(mod->RegisterSymbol("struct_type"), "struct_type", str);
-  EXPECT_EQ(16u, struct_type.BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(16u, struct_type.BaseAlignment(MemoryLayout::kStorageBuffer));
+  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kUniformBuffer));
+  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kStorageBuffer));
 }
 
 }  // namespace
diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc
index fe18df0..0520ccb 100644
--- a/src/inspector/inspector_test.cc
+++ b/src/inspector/inspector_test.cc
@@ -83,7 +83,7 @@
       std::string name,
       ast::FunctionDecorationList decorations) {
     return Func(name, ast::VariableList(), ty.void_,
-                ast::StatementList{create<ast::ReturnStatement>(Source{})},
+                ast::StatementList{create<ast::ReturnStatement>()},
                 decorations);
   }
 
@@ -96,15 +96,10 @@
       std::string caller,
       std::string callee,
       ast::FunctionDecorationList decorations) {
-    auto* ident_expr = create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(callee), callee);
-    auto* call_expr = create<ast::CallExpression>(Source{}, ident_expr,
-                                                  ast::ExpressionList());
-
     return Func(caller, ast::VariableList(), ty.void_,
                 ast::StatementList{
-                    create<ast::CallStatement>(Source{}, call_expr),
-                    create<ast::ReturnStatement>(Source{}),
+                    create<ast::CallStatement>(Call(callee)),
+                    create<ast::ReturnStatement>(),
                 },
                 decorations);
   }
@@ -117,24 +112,9 @@
     for (auto inout : inout_vars) {
       std::string in, out;
       std::tie(in, out) = inout;
-      auto* in_var =
-          create<ast::Variable>(Source{},                   // source
-                                in,                         // name
-                                ast::StorageClass::kInput,  // storage_class
-                                ty.u32,                     // type
-                                false,                      // is_const
-                                nullptr,                    // constructor
-                                ast::VariableDecorationList{});  // decorations
-      auto* out_var =
-          create<ast::Variable>(Source{},                    // source
-                                out,                         // name
-                                ast::StorageClass::kOutput,  // storage_class
-                                ty.u32,                      // type
-                                false,                       // is_const
-                                nullptr,                     // constructor
-                                ast::VariableDecorationList{});  // decorations
-      mod->AddGlobalVariable(in_var);
-      mod->AddGlobalVariable(out_var);
+
+      mod->AddGlobalVariable(Var(in, ast::StorageClass::kInput, ty.u32));
+      mod->AddGlobalVariable(Var(out, ast::StorageClass::kOutput, ty.u32));
     }
   }
 
@@ -152,14 +132,9 @@
     for (auto inout : inout_vars) {
       std::string in, out;
       std::tie(in, out) = inout;
-      stmts.emplace_back(create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol(out),
-                                            out),
-          create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol(in),
-                                            in)));
+      stmts.emplace_back(create<ast::AssignmentStatement>(Expr(out), Expr(in)));
     }
-    stmts.emplace_back(create<ast::ReturnStatement>(Source{}));
+    stmts.emplace_back(create<ast::ReturnStatement>());
     return Func(name, ast::VariableList(), ty.void_, stmts, decorations);
   }
 
@@ -180,19 +155,10 @@
     for (auto inout : inout_vars) {
       std::string in, out;
       std::tie(in, out) = inout;
-      stmts.emplace_back(create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol(out),
-                                            out),
-          create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol(in),
-                                            in)));
+      stmts.emplace_back(create<ast::AssignmentStatement>(Expr(out), Expr(in)));
     }
-    auto* ident_expr = create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(callee), callee);
-    auto* call_expr = create<ast::CallExpression>(Source{}, ident_expr,
-                                                  ast::ExpressionList());
-    stmts.emplace_back(create<ast::CallStatement>(Source{}, call_expr));
-    stmts.emplace_back(create<ast::ReturnStatement>(Source{}));
+    stmts.emplace_back(create<ast::CallStatement>(Call(callee)));
+    stmts.emplace_back(create<ast::ReturnStatement>());
 
     return Func(caller, ast::VariableList(), ty.void_, stmts, decorations);
   }
@@ -210,20 +176,13 @@
                      T* val) {
     ast::Expression* constructor = nullptr;
     if (val) {
-      constructor = create<ast::ScalarConstructorExpression>(
-          Source{}, MakeLiteral(type, val));
+      constructor =
+          create<ast::ScalarConstructorExpression>(MakeLiteral(type, val));
     }
-    auto* var = create<ast::Variable>(
-        Source{},                  // source
-        name,                      // name
-        ast::StorageClass::kNone,  // storage_class
-        type,                      // type
-        true,                      // is_const
-        constructor,               // constructor
-        ast::VariableDecorationList{
-            // decorations
-            create<ast::ConstantIdDecoration>(Source{}, id),
-        });
+    auto* var = Const(name, ast::StorageClass::kNone, type, constructor,
+                      ast::VariableDecorationList{
+                          create<ast::ConstantIdDecoration>(id),
+                      });
     mod->AddGlobalVariable(var);
   }
 
@@ -231,28 +190,28 @@
   /// @param val scalar value for the literal to contain
   /// @returns a Literal of the expected type and value
   ast::Literal* MakeLiteral(ast::type::Type* type, bool* val) {
-    return create<ast::BoolLiteral>(Source{}, type, *val);
+    return create<ast::BoolLiteral>(type, *val);
   }
 
   /// @param type AST type of the literal, must resolve to UIntLiteral
   /// @param val scalar value for the literal to contain
   /// @returns a Literal of the expected type and value
   ast::Literal* MakeLiteral(ast::type::Type* type, uint32_t* val) {
-    return create<ast::UintLiteral>(Source{}, type, *val);
+    return create<ast::UintLiteral>(type, *val);
   }
 
   /// @param type AST type of the literal, must resolve to IntLiteral
   /// @param val scalar value for the literal to contain
   /// @returns a Literal of the expected type and value
   ast::Literal* MakeLiteral(ast::type::Type* type, int32_t* val) {
-    return create<ast::SintLiteral>(Source{}, type, *val);
+    return create<ast::SintLiteral>(type, *val);
   }
 
   /// @param type AST type of the literal, must resolve to FloattLiteral
   /// @param val scalar value for the literal to contain
   /// @returns a Literal of the expected type and value
   ast::Literal* MakeLiteral(ast::type::Type* type, float* val) {
-    return create<ast::FloatLiteral>(Source{}, type, *val);
+    return create<ast::FloatLiteral>(type, *val);
   }
 
   /// @param vec Vector of strings to be searched
@@ -282,7 +241,7 @@
   ///                     type and offset of a member of the struct
   /// @param is_block whether or not to decorate as a Block
   /// @returns a struct type
-  std::unique_ptr<ast::type::Struct> MakeStructType(
+  ast::type::Struct* MakeStructType(
       const std::string& name,
       std::vector<std::tuple<ast::type::Type*, uint32_t>> members_info,
       bool is_block) {
@@ -298,13 +257,11 @@
 
     ast::StructDecorationList decos;
     if (is_block) {
-      decos.push_back(create<ast::StructBlockDecoration>(Source{}));
+      decos.push_back(create<ast::StructBlockDecoration>());
     }
 
-    auto* str = create<ast::Struct>(Source{}, members, decos);
-
-    return std::make_unique<ast::type::Struct>(mod->RegisterSymbol(name), name,
-                                               str);
+    auto* str = create<ast::Struct>(members, decos);
+    return ty.struct_(name, str);
   }
 
   /// Generates types appropriate for using in an uniform buffer
@@ -314,15 +271,14 @@
   /// @returns a tuple {struct type, access control type}, where the struct has
   ///          the layout for an uniform buffer, and the control type wraps the
   ///          struct.
-  std::tuple<std::unique_ptr<ast::type::Struct>,
-             std::unique_ptr<ast::type::AccessControl>>
+  std::tuple<ast::type::Struct*, std::unique_ptr<ast::type::AccessControl>>
   MakeUniformBufferTypes(
       const std::string& name,
       std::vector<std::tuple<ast::type::Type*, uint32_t>> members_info) {
-    auto struct_type = MakeStructType(name, members_info, true);
+    auto* struct_type = MakeStructType(name, members_info, true);
     auto access_type = std::make_unique<ast::type::AccessControl>(
-        ast::AccessControl::kReadOnly, struct_type.get());
-    return {std::move(struct_type), std::move(access_type)};
+        ast::AccessControl::kReadOnly, struct_type);
+    return {struct_type, std::move(access_type)};
   }
 
   /// Generates types appropriate for using in a storage buffer
@@ -332,15 +288,14 @@
   /// @returns a tuple {struct type, access control type}, where the struct has
   ///          the layout for a storage buffer, and the control type wraps the
   ///          struct.
-  std::tuple<std::unique_ptr<ast::type::Struct>,
-             std::unique_ptr<ast::type::AccessControl>>
+  std::tuple<ast::type::Struct*, std::unique_ptr<ast::type::AccessControl>>
   MakeStorageBufferTypes(
       const std::string& name,
       std::vector<std::tuple<ast::type::Type*, uint32_t>> members_info) {
-    auto struct_type = MakeStructType(name, members_info, false);
+    auto* struct_type = MakeStructType(name, members_info, false);
     auto access_type = std::make_unique<ast::type::AccessControl>(
-        ast::AccessControl::kReadWrite, struct_type.get());
-    return {std::move(struct_type), std::move(access_type)};
+        ast::AccessControl::kReadWrite, struct_type);
+    return {struct_type, std::move(access_type)};
   }
 
   /// Generates types appropriate for using in a read-only storage buffer
@@ -350,15 +305,14 @@
   /// @returns a tuple {struct type, access control type}, where the struct has
   ///          the layout for a read-only storage buffer, and the control type
   ///          wraps the struct.
-  std::tuple<std::unique_ptr<ast::type::Struct>,
-             std::unique_ptr<ast::type::AccessControl>>
+  std::tuple<ast::type::Struct*, std::unique_ptr<ast::type::AccessControl>>
   MakeReadOnlyStorageBufferTypes(
       const std::string& name,
       std::vector<std::tuple<ast::type::Type*, uint32_t>> members_info) {
-    auto struct_type = MakeStructType(name, members_info, false);
+    auto* struct_type = MakeStructType(name, members_info, false);
     auto access_type = std::make_unique<ast::type::AccessControl>(
-        ast::AccessControl::kReadOnly, struct_type.get());
-    return {std::move(struct_type), std::move(access_type)};
+        ast::AccessControl::kReadOnly, struct_type);
+    return {struct_type, std::move(access_type)};
   }
 
   /// Adds a binding variable with a struct type to the module
@@ -372,18 +326,11 @@
                   ast::StorageClass storage_class,
                   uint32_t set,
                   uint32_t binding) {
-    auto* var = create<ast::Variable>(
-        Source{},       // source
-        name,           // name
-        storage_class,  // storage_class
-        type,           // type
-        false,          // is_const
-        nullptr,        // constructor
-        ast::VariableDecorationList{
-            // decorations
-            create<ast::BindingDecoration>(Source{}, binding),
-            create<ast::SetDecoration>(Source{}, set),
-        });
+    auto* var = Var(name, storage_class, type, nullptr,
+                    ast::VariableDecorationList{
+                        create<ast::BindingDecoration>(binding),
+                        create<ast::SetDecoration>(set),
+                    });
 
     mod->AddGlobalVariable(var);
   }
@@ -427,15 +374,9 @@
       ast::type::Type* member_type;
       std::tie(member_idx, member_type) = member;
       std::string member_name = StructMemberName(member_idx, member_type);
+
       stmts.emplace_back(create<ast::VariableDeclStatement>(
-          Source{}, create<ast::Variable>(
-                        Source{},                          // source
-                        "local" + member_name,             // name
-                        ast::StorageClass::kNone,          // storage_class
-                        member_type,                       // type
-                        false,                             // is_const
-                        nullptr,                           // constructor
-                        ast::VariableDecorationList{})));  // decorations
+          Var("local" + member_name, ast::StorageClass::kNone, member_type)));
     }
 
     for (auto member : members) {
@@ -443,20 +384,13 @@
       ast::type::Type* member_type;
       std::tie(member_idx, member_type) = member;
       std::string member_name = StructMemberName(member_idx, member_type);
+
       stmts.emplace_back(create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(
-              Source{}, mod->RegisterSymbol("local" + member_name),
-              "local" + member_name),
-          create<ast::MemberAccessorExpression>(
-              Source{},
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol(struct_name), struct_name),
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol(member_name), member_name))));
+          Expr("local" + member_name),
+          MemberAccessor(struct_name, member_name)));
     }
 
-    stmts.emplace_back(create<ast::ReturnStatement>(Source{}));
+    stmts.emplace_back(create<ast::ReturnStatement>());
 
     return Func(func_name, ast::VariableList(), ty.void_, stmts,
                 ast::FunctionDecorationList{});
@@ -535,28 +469,16 @@
   }
 
   void AddGlobalVariable(const std::string& name, ast::type::Type* type) {
-    mod->AddGlobalVariable(create<ast::Variable>(
-        Source{},                             // source
-        name,                                 // name
-        ast::StorageClass::kUniformConstant,  // storage_class
-        type,                                 // type
-        false,                                // is_const
-        nullptr,                              // constructor
-        ast::VariableDecorationList{}));      // decorations
+    mod->AddGlobalVariable(
+        Var(name, ast::StorageClass::kUniformConstant, type));
   }
 
   /// Adds a depth texture variable to the module
   /// @param name the name of the variable
   /// @param type the type to use
   void AddDepthTexture(const std::string& name, ast::type::Type* type) {
-    mod->AddGlobalVariable(create<ast::Variable>(
-        Source{},                             // source
-        name,                                 // name
-        ast::StorageClass::kUniformConstant,  // storage_class
-        type,                                 // type
-        false,                                // is_const
-        nullptr,                              // constructor
-        ast::VariableDecorationList{}));      // decorations
+    mod->AddGlobalVariable(
+        Var(name, ast::StorageClass::kUniformConstant, type));
   }
 
   /// Generates a function that references a specific sampler variable
@@ -577,36 +499,14 @@
     std::string result_name = "sampler_result";
 
     ast::StatementList stmts;
-    auto* call_result =
-        create<ast::Variable>(Source{},                        // source
-                              "sampler_result",                // name
-                              ast::StorageClass::kFunction,    // storage_class
-                              vec_type(base_type, 4),          // type
-                              false,                           // is_const
-                              nullptr,                         // constructor
-                              ast::VariableDecorationList{});  // decorations
-    stmts.emplace_back(
-        create<ast::VariableDeclStatement>(Source{}, call_result));
-
-    ast::ExpressionList call_params;
-    call_params.push_back(create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(texture_name), texture_name));
-    call_params.push_back(create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(sampler_name), sampler_name));
-    call_params.push_back(create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(coords_name), coords_name));
-    auto* call_expr = create<ast::CallExpression>(
-        Source{},
-        create<ast::IdentifierExpression>(
-            Source{}, mod->RegisterSymbol("textureSample"), "textureSample"),
-        call_params);
+    stmts.emplace_back(create<ast::VariableDeclStatement>(
+        Var("sampler_result", ast::StorageClass::kFunction,
+            vec_type(base_type, 4))));
 
     stmts.emplace_back(create<ast::AssignmentStatement>(
-        Source{},
-        create<ast::IdentifierExpression>(
-            Source{}, mod->RegisterSymbol("sampler_result"), "sampler_result"),
-        call_expr));
-    stmts.emplace_back(create<ast::ReturnStatement>(Source{}));
+        Expr("sampler_result"),
+        Call("textureSample", texture_name, sampler_name, coords_name)));
+    stmts.emplace_back(create<ast::ReturnStatement>());
 
     return Func(func_name, ast::VariableList(), ty.void_, stmts, decorations);
   }
@@ -632,38 +532,14 @@
 
     ast::StatementList stmts;
 
-    auto* call_result =
-        create<ast::Variable>(Source{},                        // source
-                              "sampler_result",                // name
-                              ast::StorageClass::kFunction,    // storage_class
-                              vec_type(base_type, 4),          // type
-                              false,                           // is_const
-                              nullptr,                         // constructor
-                              ast::VariableDecorationList{});  // decorations
-    stmts.emplace_back(
-        create<ast::VariableDeclStatement>(Source{}, call_result));
-
-    ast::ExpressionList call_params;
-    call_params.push_back(create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(texture_name), texture_name));
-    call_params.push_back(create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(sampler_name), sampler_name));
-    call_params.push_back(create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(coords_name), coords_name));
-    call_params.push_back(create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(array_index), array_index));
-    auto* call_expr = create<ast::CallExpression>(
-        Source{},
-        create<ast::IdentifierExpression>(
-            Source{}, mod->RegisterSymbol("textureSample"), "textureSample"),
-        call_params);
+    stmts.emplace_back(create<ast::VariableDeclStatement>(
+        Var("sampler_result", ast::StorageClass::kFunction,
+            vec_type(base_type, 4))));
 
     stmts.emplace_back(create<ast::AssignmentStatement>(
-        Source{},
-        create<ast::IdentifierExpression>(
-            Source{}, mod->RegisterSymbol("sampler_result"), "sampler_result"),
-        call_expr));
-    stmts.emplace_back(create<ast::ReturnStatement>(Source{}));
+        Expr("sampler_result"), Call("textureSample", texture_name,
+                                     sampler_name, coords_name, array_index)));
+    stmts.emplace_back(create<ast::ReturnStatement>());
 
     return Func(func_name, ast::VariableList(), ty.void_, stmts, decorations);
   }
@@ -690,39 +566,12 @@
 
     ast::StatementList stmts;
 
-    auto* call_result =
-        create<ast::Variable>(Source{},                        // source
-                              "sampler_result",                // name
-                              ast::StorageClass::kFunction,    // storage_class
-                              base_type,                       // type
-                              false,                           // is_const
-                              nullptr,                         // constructor
-                              ast::VariableDecorationList{});  // decorations
-    stmts.emplace_back(
-        create<ast::VariableDeclStatement>(Source{}, call_result));
-
-    ast::ExpressionList call_params;
-    call_params.push_back(create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(texture_name), texture_name));
-    call_params.push_back(create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(sampler_name), sampler_name));
-    call_params.push_back(create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(coords_name), coords_name));
-    call_params.push_back(create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(depth_name), depth_name));
-    auto* call_expr = create<ast::CallExpression>(
-        Source{},
-        create<ast::IdentifierExpression>(
-            Source{}, mod->RegisterSymbol("textureSampleCompare"),
-            "textureSampleCompare"),
-        call_params);
-
+    stmts.emplace_back(create<ast::VariableDeclStatement>(
+        Var("sampler_result", ast::StorageClass::kFunction, base_type)));
     stmts.emplace_back(create<ast::AssignmentStatement>(
-        Source{},
-        create<ast::IdentifierExpression>(
-            Source{}, mod->RegisterSymbol("sampler_result"), "sampler_result"),
-        call_expr));
-    stmts.emplace_back(create<ast::ReturnStatement>(Source{}));
+        Expr("sampler_result"), Call("textureSampleCompare", texture_name,
+                                     sampler_name, coords_name, depth_name)));
+    stmts.emplace_back(create<ast::ReturnStatement>());
 
     return Func(func_name, ast::VariableList(), ty.void_, stmts, decorations);
   }
@@ -767,11 +616,11 @@
 
   ast::type::Array* u32_array_type(uint32_t count) {
     if (array_type_memo_.find(count) == array_type_memo_.end()) {
-      array_type_memo_[count] = create<ast::type::Array>(
-          ty.u32, count,
-          ast::ArrayDecorationList{
-              create<ast::StrideDecoration>(Source{}, 4),
-          });
+      array_type_memo_[count] =
+          create<ast::type::Array>(ty.u32, count,
+                                   ast::ArrayDecorationList{
+                                       create<ast::StrideDecoration>(4),
+                                   });
     }
     return array_type_memo_[count];
   }
@@ -866,10 +715,9 @@
 
 TEST_F(InspectorGetEntryPointTest, OneEntryPoint) {
   auto* foo = MakeEmptyBodyFunction(
-      "foo",
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
-      });
+      "foo", ast::FunctionDecorationList{
+                 create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+             });
   mod->AddFunction(foo);
 
   // TODO(dsinclair): Update to run the namer transform when available.
@@ -885,17 +733,15 @@
 
 TEST_F(InspectorGetEntryPointTest, MultipleEntryPoints) {
   auto* foo = MakeEmptyBodyFunction(
-      "foo",
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
-      });
+      "foo", ast::FunctionDecorationList{
+                 create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+             });
   mod->AddFunction(foo);
 
   auto* bar = MakeEmptyBodyFunction(
-      "bar",
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kCompute),
-      });
+      "bar", ast::FunctionDecorationList{
+                 create<ast::StageDecoration>(ast::PipelineStage::kCompute),
+             });
   mod->AddFunction(bar);
 
   // TODO(dsinclair): Update to run the namer transform when available.
@@ -919,14 +765,14 @@
   auto* foo = MakeCallerBodyFunction(
       "foo", "func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(foo);
 
   auto* bar = MakeCallerBodyFunction(
       "bar", "func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
   mod->AddFunction(bar);
 
@@ -948,7 +794,7 @@
   auto* foo = MakeCallerBodyFunction(
       "foo", "func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(foo);
 
@@ -965,11 +811,10 @@
 
 TEST_F(InspectorGetEntryPointTest, NonDefaultWorkgroupSize) {
   auto* foo = MakeEmptyBodyFunction(
-      "foo",
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kCompute),
-          create<ast::WorkgroupDecoration>(Source{}, 8u, 2u, 1u),
-      });
+      "foo", ast::FunctionDecorationList{
+                 create<ast::StageDecoration>(ast::PipelineStage::kCompute),
+                 create<ast::WorkgroupDecoration>(8u, 2u, 1u),
+             });
   mod->AddFunction(foo);
 
   auto result = inspector()->GetEntryPoints();
@@ -990,7 +835,7 @@
   auto* foo = MakeCallerBodyFunction(
       "foo", "func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(foo);
 
@@ -1008,7 +853,7 @@
   auto* foo = MakeInOutVariableBodyFunction(
       "foo", {{"in_var", "out_var"}},
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(foo);
 
@@ -1035,7 +880,7 @@
   auto* foo = MakeCallerBodyFunction(
       "foo", "func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(foo);
 
@@ -1062,7 +907,7 @@
   auto* foo = MakeInOutVariableCallerBodyFunction(
       "foo", "func", {{"in_var", "out_var"}},
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(foo);
 
@@ -1085,7 +930,7 @@
   auto* foo = MakeInOutVariableBodyFunction(
       "foo", {{"in_var", "out_var"}, {"in2_var", "out2_var"}},
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(foo);
 
@@ -1114,7 +959,7 @@
   auto* foo = MakeCallerBodyFunction(
       "foo", "func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(foo);
 
@@ -1139,14 +984,14 @@
   auto* foo = MakeInOutVariableBodyFunction(
       "foo", {{"in_var", "out2_var"}},
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(foo);
 
   auto* bar = MakeInOutVariableBodyFunction(
       "bar", {{"in2_var", "out_var"}},
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kCompute),
+          create<ast::StageDecoration>(ast::PipelineStage::kCompute),
       });
   mod->AddFunction(bar);
 
@@ -1184,14 +1029,14 @@
   auto* foo = MakeInOutVariableCallerBodyFunction(
       "foo", "func", {{"in_var", "out_var"}},
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(foo);
 
   auto* bar = MakeCallerBodyFunction(
       "bar", "func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kCompute),
+          create<ast::StageDecoration>(ast::PipelineStage::kCompute),
       });
   mod->AddFunction(bar);
 
@@ -1245,10 +1090,9 @@
 // through
 TEST_F(InspectorGetRemappedNameForEntryPointTest, DISABLED_OneEntryPoint) {
   auto* foo = MakeEmptyBodyFunction(
-      "foo",
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
-      });
+      "foo", ast::FunctionDecorationList{
+                 create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+             });
   mod->AddFunction(foo);
 
   // TODO(dsinclair): Update to run the namer transform when available.
@@ -1264,19 +1108,17 @@
 TEST_F(InspectorGetRemappedNameForEntryPointTest,
        DISABLED_MultipleEntryPoints) {
   auto* foo = MakeEmptyBodyFunction(
-      "foo",
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
-      });
+      "foo", ast::FunctionDecorationList{
+                 create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+             });
   mod->AddFunction(foo);
 
   // TODO(dsinclair): Update to run the namer transform when available.
 
   auto* bar = MakeEmptyBodyFunction(
-      "bar",
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kCompute),
-      });
+      "bar", ast::FunctionDecorationList{
+                 create<ast::StageDecoration>(ast::PipelineStage::kCompute),
+             });
   mod->AddFunction(bar);
 
   {
@@ -1387,7 +1229,7 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, NonEntryPointFunc) {
-  std::unique_ptr<ast::type::Struct> foo_struct_type;
+  ast::type::Struct* foo_struct_type;
   std::unique_ptr<ast::type::AccessControl> foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeUniformBufferTypes("foo_type", {{ty.i32, 0}});
@@ -1400,7 +1242,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "ub_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1418,10 +1260,8 @@
           Member(StructMemberName(0, ty.i32), ty.i32, {MemberOffset(0)})},
       decos);
 
-  auto foo_type = std::make_unique<ast::type::Struct>(
-      mod->RegisterSymbol("foo_type"), "foo_type", str);
-
-  AddUniformBuffer("foo_ub", foo_type.get(), 0, 0);
+  auto* foo_type = ty.struct_("foo_type", str);
+  AddUniformBuffer("foo_ub", foo_type, 0, 0);
 
   auto* ub_func = MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub",
                                                           {{0, ty.i32}});
@@ -1430,7 +1270,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "ub_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1442,7 +1282,7 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, Simple) {
-  std::unique_ptr<ast::type::Struct> foo_struct_type;
+  ast::type::Struct* foo_struct_type;
   std::unique_ptr<ast::type::AccessControl> foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeUniformBufferTypes("foo_type", {{ty.i32, 0}});
@@ -1455,7 +1295,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "ub_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1471,7 +1311,7 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleMembers) {
-  std::unique_ptr<ast::type::Struct> foo_struct_type;
+  ast::type::Struct* foo_struct_type;
   std::unique_ptr<ast::type::AccessControl> foo_control_type;
   std::tie(foo_struct_type, foo_control_type) = MakeUniformBufferTypes(
       "foo_type", {{ty.i32, 0}, {ty.u32, 4}, {ty.f32, 8}});
@@ -1484,7 +1324,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "ub_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1500,7 +1340,7 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleUniformBuffers) {
-  std::unique_ptr<ast::type::Struct> ub_struct_type;
+  ast::type::Struct* ub_struct_type;
   std::unique_ptr<ast::type::AccessControl> ub_control_type;
   std::tie(ub_struct_type, ub_control_type) = MakeUniformBufferTypes(
       "ub_type", {{ty.i32, 0}, {ty.u32, 4}, {ty.f32, 8}});
@@ -1519,11 +1359,7 @@
   AddReferenceFunc("ub_baz_func", "ub_baz");
 
   auto FuncCall = [&](const std::string& callee) {
-    auto* ident_expr = create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(callee), callee);
-    auto* call_expr = create<ast::CallExpression>(Source{}, ident_expr,
-                                                  ast::ExpressionList());
-    return create<ast::CallStatement>(Source{}, call_expr);
+    return create<ast::CallStatement>(Call(callee));
   };
 
   ast::Function* func =
@@ -1556,7 +1392,7 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, ContainingArray) {
-  std::unique_ptr<ast::type::Struct> foo_struct_type;
+  ast::type::Struct* foo_struct_type;
   std::unique_ptr<ast::type::AccessControl> foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeUniformBufferTypes("foo_type", {{ty.i32, 0}, {u32_array_type(4), 4}});
@@ -1569,7 +1405,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "ub_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1585,7 +1421,7 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, Simple) {
-  std::unique_ptr<ast::type::Struct> foo_struct_type;
+  ast::type::Struct* foo_struct_type;
   std::unique_ptr<ast::type::AccessControl> foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeStorageBufferTypes("foo_type", {{ty.i32, 0}});
@@ -1598,7 +1434,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "sb_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1614,7 +1450,7 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleMembers) {
-  std::unique_ptr<ast::type::Struct> foo_struct_type;
+  ast::type::Struct* foo_struct_type;
   std::unique_ptr<ast::type::AccessControl> foo_control_type;
   std::tie(foo_struct_type, foo_control_type) = MakeStorageBufferTypes(
       "foo_type", {{ty.i32, 0}, {ty.u32, 4}, {ty.f32, 8}});
@@ -1627,7 +1463,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "sb_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1643,7 +1479,7 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleStorageBuffers) {
-  std::unique_ptr<ast::type::Struct> sb_struct_type;
+  ast::type::Struct* sb_struct_type;
   std::unique_ptr<ast::type::AccessControl> sb_control_type;
   std::tie(sb_struct_type, sb_control_type) = MakeStorageBufferTypes(
       "sb_type", {{ty.i32, 0}, {ty.u32, 4}, {ty.f32, 8}});
@@ -1662,24 +1498,20 @@
   AddReferenceFunc("sb_baz_func", "sb_baz");
 
   auto FuncCall = [&](const std::string& callee) {
-    auto* ident_expr = create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(callee), callee);
-    auto* call_expr = create<ast::CallExpression>(Source{}, ident_expr,
-                                                  ast::ExpressionList());
-    return create<ast::CallStatement>(Source{}, call_expr);
+    return create<ast::CallStatement>(Call(callee));
   };
 
-  ast::Function* func = Func(
-      "ep_func", ast::VariableList(), ty.void_,
-      ast::StatementList{
-          FuncCall("sb_foo_func"),
-          FuncCall("sb_bar_func"),
-          FuncCall("sb_baz_func"),
-          create<ast::ReturnStatement>(Source{}),
-      },
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
-      });
+  ast::Function* func =
+      Func("ep_func", ast::VariableList(), ty.void_,
+           ast::StatementList{
+               FuncCall("sb_foo_func"),
+               FuncCall("sb_bar_func"),
+               FuncCall("sb_baz_func"),
+               create<ast::ReturnStatement>(),
+           },
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           });
   mod->AddFunction(func);
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
@@ -1702,7 +1534,7 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingArray) {
-  std::unique_ptr<ast::type::Struct> foo_struct_type;
+  ast::type::Struct* foo_struct_type;
   std::unique_ptr<ast::type::AccessControl> foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeStorageBufferTypes("foo_type", {{ty.i32, 0}, {u32_array_type(4), 4}});
@@ -1715,7 +1547,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "sb_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1731,7 +1563,7 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingRuntimeArray) {
-  std::unique_ptr<ast::type::Struct> foo_struct_type;
+  ast::type::Struct* foo_struct_type;
   std::unique_ptr<ast::type::AccessControl> foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeStorageBufferTypes("foo_type", {{ty.i32, 0}, {u32_array_type(0), 4}});
@@ -1744,7 +1576,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "sb_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1760,7 +1592,7 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, SkipReadOnly) {
-  std::unique_ptr<ast::type::Struct> foo_struct_type;
+  ast::type::Struct* foo_struct_type;
   std::unique_ptr<ast::type::AccessControl> foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeReadOnlyStorageBufferTypes("foo_type", {{ty.i32, 0}});
@@ -1773,7 +1605,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "sb_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1785,7 +1617,7 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, Simple) {
-  std::unique_ptr<ast::type::Struct> foo_struct_type;
+  ast::type::Struct* foo_struct_type;
   std::unique_ptr<ast::type::AccessControl> foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeReadOnlyStorageBufferTypes("foo_type", {{ty.i32, 0}});
@@ -1798,7 +1630,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "sb_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1816,7 +1648,7 @@
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest,
        MultipleStorageBuffers) {
-  std::unique_ptr<ast::type::Struct> sb_struct_type;
+  ast::type::Struct* sb_struct_type;
   std::unique_ptr<ast::type::AccessControl> sb_control_type;
   std::tie(sb_struct_type, sb_control_type) = MakeReadOnlyStorageBufferTypes(
       "sb_type", {{ty.i32, 0}, {ty.u32, 4}, {ty.f32, 8}});
@@ -1835,24 +1667,20 @@
   AddReferenceFunc("sb_baz_func", "sb_baz");
 
   auto FuncCall = [&](const std::string& callee) {
-    auto* ident_expr = create<ast::IdentifierExpression>(
-        Source{}, mod->RegisterSymbol(callee), callee);
-    auto* call_expr = create<ast::CallExpression>(Source{}, ident_expr,
-                                                  ast::ExpressionList());
-    return create<ast::CallStatement>(Source{}, call_expr);
+    return create<ast::CallStatement>(Call(callee));
   };
 
-  ast::Function* func = Func(
-      "ep_func", ast::VariableList(), ty.void_,
-      ast::StatementList{
-          FuncCall("sb_foo_func"),
-          FuncCall("sb_bar_func"),
-          FuncCall("sb_baz_func"),
-          create<ast::ReturnStatement>(Source{}),
-      },
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
-      });
+  ast::Function* func =
+      Func("ep_func", ast::VariableList(), ty.void_,
+           ast::StatementList{
+               FuncCall("sb_foo_func"),
+               FuncCall("sb_bar_func"),
+               FuncCall("sb_baz_func"),
+               create<ast::ReturnStatement>(),
+           },
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           });
   mod->AddFunction(func);
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
@@ -1876,7 +1704,7 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, ContainingArray) {
-  std::unique_ptr<ast::type::Struct> foo_struct_type;
+  ast::type::Struct* foo_struct_type;
   std::unique_ptr<ast::type::AccessControl> foo_control_type;
   std::tie(foo_struct_type, foo_control_type) = MakeReadOnlyStorageBufferTypes(
       "foo_type", {{ty.i32, 0}, {u32_array_type(4), 4}});
@@ -1889,7 +1717,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "sb_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1907,7 +1735,7 @@
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest,
        ContainingRuntimeArray) {
-  std::unique_ptr<ast::type::Struct> foo_struct_type;
+  ast::type::Struct* foo_struct_type;
   std::unique_ptr<ast::type::AccessControl> foo_control_type;
   std::tie(foo_struct_type, foo_control_type) = MakeReadOnlyStorageBufferTypes(
       "foo_type", {{ty.i32, 0}, {u32_array_type(0), 4}});
@@ -1920,7 +1748,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "sb_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1937,7 +1765,7 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, SkipNonReadOnly) {
-  std::unique_ptr<ast::type::Struct> foo_struct_type;
+  ast::type::Struct* foo_struct_type;
   std::unique_ptr<ast::type::AccessControl> foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeStorageBufferTypes("foo_type", {{ty.i32, 0}});
@@ -1950,7 +1778,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "sb_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -1972,7 +1800,7 @@
   auto* func = MakeSamplerReferenceBodyFunction(
       "ep", "foo_texture", "foo_sampler", "foo_coords", ty.f32,
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(func);
 
@@ -1988,10 +1816,9 @@
 
 TEST_F(InspectorGetSamplerResourceBindingsTest, NoSampler) {
   auto* func = MakeEmptyBodyFunction(
-      "ep_func",
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
-      });
+      "ep_func", ast::FunctionDecorationList{
+                     create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+                 });
   mod->AddFunction(func);
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
@@ -2016,7 +1843,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "foo_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -2040,7 +1867,7 @@
   auto* func = MakeSamplerReferenceBodyFunction(
       "ep", "foo_texture", "foo_sampler", "foo_coords", ty.f32,
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(func);
 
@@ -2061,7 +1888,7 @@
   auto* func = MakeComparisonSamplerReferenceBodyFunction(
       "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32,
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(func);
 
@@ -2084,7 +1911,7 @@
   auto* func = MakeComparisonSamplerReferenceBodyFunction(
       "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32,
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(func);
 
@@ -2100,10 +1927,9 @@
 
 TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, NoSampler) {
   auto* func = MakeEmptyBodyFunction(
-      "ep_func",
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
-      });
+      "ep_func", ast::FunctionDecorationList{
+                     create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+                 });
   mod->AddFunction(func);
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
@@ -2130,7 +1956,7 @@
   auto* ep_func = MakeCallerBodyFunction(
       "ep_func", "foo_func",
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(ep_func);
 
@@ -2155,7 +1981,7 @@
   auto* func = MakeComparisonSamplerReferenceBodyFunction(
       "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32,
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(func);
 
@@ -2175,7 +2001,7 @@
   auto* func = MakeSamplerReferenceBodyFunction(
       "ep", "foo_texture", "foo_sampler", "foo_coords", ty.f32,
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(func);
 
@@ -2189,10 +2015,9 @@
 
 TEST_F(InspectorGetSampledTextureResourceBindingsTest, Empty) {
   auto* foo = MakeEmptyBodyFunction(
-      "foo",
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
-      });
+      "foo", ast::FunctionDecorationList{
+                 create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+             });
   mod->AddFunction(foo);
 
   auto result = inspector()->GetSampledTextureResourceBindings("foo");
@@ -2214,7 +2039,7 @@
       "ep", "foo_texture", "foo_sampler", "foo_coords",
       GetBaseType(GetParam().sampled_kind),
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(func);
 
@@ -2298,7 +2123,7 @@
       "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_array_index",
       GetBaseType(GetParam().sampled_kind),
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(func);
 
@@ -2369,7 +2194,7 @@
       "ep", "foo_texture", "foo_sampler", "foo_coords",
       GetBaseType(GetParam().sampled_kind),
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(func);
 
@@ -2416,10 +2241,9 @@
 
 TEST_F(InspectorGetMultisampledArrayTextureResourceBindingsTest, Empty) {
   auto* foo = MakeEmptyBodyFunction(
-      "foo",
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
-      });
+      "foo", ast::FunctionDecorationList{
+                 create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+             });
   mod->AddFunction(foo);
 
   auto result = inspector()->GetSampledTextureResourceBindings("foo");
@@ -2443,7 +2267,7 @@
       "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_array_index",
       GetBaseType(GetParam().sampled_kind),
       ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
       });
   mod->AddFunction(func);
 
diff --git a/src/transform/first_index_offset_test.cc b/src/transform/first_index_offset_test.cc
index 5538577..094df6d 100644
--- a/src/transform/first_index_offset_test.cc
+++ b/src/transform/first_index_offset_test.cc
@@ -71,12 +71,7 @@
   struct Builder : public ModuleBuilder {
     void Build() override {
       AddBuiltinInput("vert_idx", ast::Builtin::kVertexIdx);
-      AddFunction(
-          "test",
-          {
-              create<ast::ReturnStatement>(create<ast::IdentifierExpression>(
-                  mod->RegisterSymbol("vert_idx"), "vert_idx")),
-          });
+      AddFunction("test", {create<ast::ReturnStatement>(Expr("vert_idx"))});
     }
   };
 
@@ -116,12 +111,7 @@
   struct Builder : public ModuleBuilder {
     void Build() override {
       AddBuiltinInput("vert_idx", ast::Builtin::kVertexIdx);
-      AddFunction(
-          "test",
-          {
-              create<ast::ReturnStatement>(create<ast::IdentifierExpression>(
-                  mod->RegisterSymbol("vert_idx"), "vert_idx")),
-          });
+      AddFunction("test", {create<ast::ReturnStatement>(Expr("vert_idx"))});
     }
   };
 
@@ -196,12 +186,7 @@
   struct Builder : public ModuleBuilder {
     void Build() override {
       AddBuiltinInput("inst_idx", ast::Builtin::kInstanceIdx);
-      AddFunction(
-          "test",
-          {
-              create<ast::ReturnStatement>(create<ast::IdentifierExpression>(
-                  mod->RegisterSymbol("inst_idx"), "inst_idx")),
-          });
+      AddFunction("test", {create<ast::ReturnStatement>(Expr("inst_idx"))});
     }
   };
 
@@ -353,19 +338,8 @@
   struct Builder : public ModuleBuilder {
     void Build() override {
       AddBuiltinInput("vert_idx", ast::Builtin::kVertexIdx);
-      AddFunction(
-          "func1",
-          {
-              create<ast::ReturnStatement>(create<ast::IdentifierExpression>(
-                  mod->RegisterSymbol("vert_idx"), "vert_idx")),
-          });
-      AddFunction("func2",
-                  {
-                      create<ast::ReturnStatement>(create<ast::CallExpression>(
-                          create<ast::IdentifierExpression>(
-                              mod->RegisterSymbol("func1"), "func1"),
-                          ast::ExpressionList{})),
-                  });
+      AddFunction("func1", {create<ast::ReturnStatement>(Expr("vert_idx"))});
+      AddFunction("func2", {create<ast::ReturnStatement>(Call("func1"))});
     }
   };
 
diff --git a/src/type_determiner_test.cc b/src/type_determiner_test.cc
index f352140..e451160 100644
--- a/src/type_determiner_test.cc
+++ b/src/type_determiner_test.cc
@@ -384,10 +384,9 @@
 
 TEST_F(TypeDeterminerTest, Expr_ArrayAccessor_Alias_Array) {
   ast::type::Array ary(ty.f32, 3, ast::ArrayDecorationList{});
-  ast::type::Alias aary(mod->RegisterSymbol("myarrty"), "myarrty", &ary);
+  auto* aary = ty.alias("myarrty", &ary);
 
-  auto* var = Var("my_var", ast::StorageClass::kFunction, &aary);
-  mod->AddGlobalVariable(var);
+  mod->AddGlobalVariable(Var("my_var", ast::StorageClass::kFunction, aary));
 
   EXPECT_TRUE(td()->Determine());
 
@@ -770,9 +769,8 @@
                             Member("second_member", ty.f32)},
       ast::StructDecorationList{});
 
-  ast::type::Struct st(mod->RegisterSymbol("S"), "S", strct);
-
-  auto* var = Var("my_struct", ast::StorageClass::kNone, &st);
+  auto* st = ty.struct_("S", strct);
+  auto* var = Var("my_struct", ast::StorageClass::kNone, st);
 
   mod->AddGlobalVariable(var);
 
@@ -793,11 +791,9 @@
                             Member("second_member", ty.f32)},
       ast::StructDecorationList{});
 
-  auto st = std::make_unique<ast::type::Struct>(mod->RegisterSymbol("alias"),
-                                                "alias", strct);
-  ast::type::Alias alias(mod->RegisterSymbol("alias"), "alias", st.get());
-
-  auto* var = Var("my_struct", ast::StorageClass::kNone, &alias);
+  auto* st = ty.struct_("alias", strct);
+  auto* alias = ty.alias("alias", st);
+  auto* var = Var("my_struct", ast::StorageClass::kNone, alias);
 
   mod->AddGlobalVariable(var);
 
@@ -873,18 +869,15 @@
   auto* strctB =
       create<ast::Struct>(ast::StructMemberList{Member("foo", ty.vec4<f32>())},
                           ast::StructDecorationList{});
-  ast::type::Struct stB(mod->RegisterSymbol("B"), "B", strctB);
+  auto* stB = ty.struct_("B", strctB);
 
-  ast::type::Vector vecB(&stB, 3);
+  ast::type::Vector vecB(stB, 3);
   auto* strctA = create<ast::Struct>(
       ast::StructMemberList{Member("mem", &vecB)}, ast::StructDecorationList{});
 
-  ast::type::Struct stA(mod->RegisterSymbol("A"), "A", strctA);
-
-  auto* var = Var("c", ast::StorageClass::kNone, &stA);
-
+  auto* stA = ty.struct_("A", strctA);
+  auto* var = Var("c", ast::StorageClass::kNone, stA);
   mod->AddGlobalVariable(var);
-
   EXPECT_TRUE(td()->Determine());
 
   auto* mem = MemberAccessor(
diff --git a/src/validator/validator_control_block_test.cc b/src/validator/validator_control_block_test.cc
index 8009eef..65daa04 100644
--- a/src/validator/validator_control_block_test.cc
+++ b/src/validator/validator_control_block_test.cc
@@ -45,16 +45,15 @@
   auto* var = Var("a", ast::StorageClass::kNone, ty.f32, Expr(3.14f),
                   ast::VariableDecorationList{});
 
-  auto* cond = create<ast::IdentifierExpression>(
-      Source{Source::Location{12, 34}}, mod->RegisterSymbol("a"), "a");
-  ast::CaseSelectorList default_csl;
-  auto* block_default = create<ast::BlockStatement>(ast::StatementList{});
   ast::CaseStatementList body;
-  body.push_back(create<ast::CaseStatement>(default_csl, block_default));
+  auto* block_default = create<ast::BlockStatement>(ast::StatementList{});
+  body.push_back(
+      create<ast::CaseStatement>(ast::CaseSelectorList{}, block_default));
 
   auto* block = create<ast::BlockStatement>(ast::StatementList{
       create<ast::VariableDeclStatement>(var),
-      create<ast::SwitchStatement>(cond, body),
+      create<ast::SwitchStatement>(Expr(Source{Source::Location{12, 34}}, "a"),
+                                   body),
   });
 
   EXPECT_TRUE(td()->DetermineStatements(block)) << td()->error();
@@ -72,16 +71,16 @@
   auto* var = Var("a", ast::StorageClass::kNone, ty.i32, Expr(2),
                   ast::VariableDecorationList{});
 
-  auto* cond = Expr("a");
   ast::CaseSelectorList csl;
   csl.push_back(Literal(1));
+
   ast::CaseStatementList body;
   body.push_back(create<ast::CaseStatement>(
       csl, create<ast::BlockStatement>(ast::StatementList{})));
 
   auto* block = create<ast::BlockStatement>(ast::StatementList{
       create<ast::VariableDeclStatement>(var),
-      create<ast::SwitchStatement>(Source{Source::Location{12, 34}}, cond,
+      create<ast::SwitchStatement>(Source{Source::Location{12, 34}}, Expr("a"),
                                    body),
   });
 
@@ -103,8 +102,6 @@
                   ast::VariableDecorationList{});
 
   ast::CaseStatementList switch_body;
-  auto* cond = Expr("a");
-
   ast::CaseSelectorList default_csl_1;
   auto* block_default_1 = create<ast::BlockStatement>(ast::StatementList{});
   switch_body.push_back(
@@ -122,7 +119,7 @@
 
   auto* block = create<ast::BlockStatement>(ast::StatementList{
       create<ast::VariableDeclStatement>(var),
-      create<ast::SwitchStatement>(Source{Source::Location{12, 34}}, cond,
+      create<ast::SwitchStatement>(Source{Source::Location{12, 34}}, Expr("a"),
                                    switch_body),
   });
 
@@ -144,8 +141,6 @@
                   ast::VariableDecorationList{});
 
   ast::CaseStatementList switch_body;
-  auto* cond = Expr("a");
-
   ast::CaseSelectorList csl;
   csl.push_back(create<ast::UintLiteral>(ty.u32, 1));
   switch_body.push_back(create<ast::CaseStatement>(
@@ -158,7 +153,7 @@
 
   auto* block = create<ast::BlockStatement>(ast::StatementList{
       create<ast::VariableDeclStatement>(var),
-      create<ast::SwitchStatement>(cond, switch_body),
+      create<ast::SwitchStatement>(Expr("a"), switch_body),
   });
   EXPECT_TRUE(td()->DetermineStatements(block)) << td()->error();
   EXPECT_FALSE(v()->ValidateStatements(block));
@@ -180,8 +175,6 @@
                   ast::VariableDecorationList{});
 
   ast::CaseStatementList switch_body;
-  auto* cond = Expr("a");
-
   ast::CaseSelectorList csl;
   csl.push_back(Literal(-1));
   switch_body.push_back(create<ast::CaseStatement>(
@@ -194,7 +187,7 @@
 
   auto* block = create<ast::BlockStatement>(ast::StatementList{
       create<ast::VariableDeclStatement>(var),
-      create<ast::SwitchStatement>(cond, switch_body),
+      create<ast::SwitchStatement>(Expr("a"), switch_body),
   });
   EXPECT_TRUE(td()->DetermineStatements(block)) << td()->error();
   EXPECT_FALSE(v()->ValidateStatements(block));
@@ -216,8 +209,6 @@
                   ast::VariableDecorationList{});
 
   ast::CaseStatementList switch_body;
-  auto* cond = Expr("a");
-
   ast::CaseSelectorList csl_1;
   csl_1.push_back(create<ast::UintLiteral>(ty.u32, 0));
   switch_body.push_back(create<ast::CaseStatement>(
@@ -236,7 +227,7 @@
 
   auto* block = create<ast::BlockStatement>(ast::StatementList{
       create<ast::VariableDeclStatement>(var),
-      create<ast::SwitchStatement>(cond, switch_body),
+      create<ast::SwitchStatement>(Expr("a"), switch_body),
   });
   EXPECT_TRUE(td()->DetermineStatements(block)) << td()->error();
   EXPECT_FALSE(v()->ValidateStatements(block));
@@ -256,8 +247,6 @@
                   ast::VariableDecorationList{});
 
   ast::CaseStatementList switch_body;
-  auto* cond = Expr("a");
-
   ast::CaseSelectorList csl_1;
   csl_1.push_back(Literal(10));
   switch_body.push_back(create<ast::CaseStatement>(
@@ -278,7 +267,7 @@
 
   auto* block = create<ast::BlockStatement>(ast::StatementList{
       create<ast::VariableDeclStatement>(var),
-      create<ast::SwitchStatement>(cond, switch_body),
+      create<ast::SwitchStatement>(Expr("a"), switch_body),
   });
   EXPECT_TRUE(td()->DetermineStatements(block)) << td()->error();
   EXPECT_FALSE(v()->ValidateStatements(block));
@@ -295,7 +284,6 @@
   auto* var = Var("a", ast::StorageClass::kNone, ty.i32, Expr(2),
                   ast::VariableDecorationList{});
 
-  auto* cond = Expr("a");
   ast::CaseSelectorList default_csl;
   auto* block_default = create<ast::BlockStatement>(
 
@@ -307,7 +295,7 @@
 
   auto* block = create<ast::BlockStatement>(ast::StatementList{
       create<ast::VariableDeclStatement>(var),
-      create<ast::SwitchStatement>(cond, body),
+      create<ast::SwitchStatement>(Expr("a"), body),
   });
   EXPECT_TRUE(td()->DetermineStatements(block)) << td()->error();
   EXPECT_FALSE(v()->ValidateStatements(block));
@@ -325,7 +313,6 @@
   auto* var = Var("a", ast::StorageClass::kNone, ty.i32, Expr(2),
                   ast::VariableDecorationList{});
 
-  auto* cond = Expr("a");
   ast::CaseSelectorList default_csl;
   auto* block_default = create<ast::BlockStatement>(ast::StatementList{});
   ast::CaseStatementList body;
@@ -338,7 +325,7 @@
 
   auto* block = create<ast::BlockStatement>(ast::StatementList{
       create<ast::VariableDeclStatement>(var),
-      create<ast::SwitchStatement>(cond, body),
+      create<ast::SwitchStatement>(Expr("a"), body),
   });
   EXPECT_TRUE(td()->DetermineStatements(block)) << td()->error();
   EXPECT_TRUE(v()->ValidateStatements(block)) << v()->error();
@@ -351,12 +338,10 @@
   //   default: {}
   // }
 
-  ast::type::Alias my_int{mod->RegisterSymbol("MyInt"), "MyInt", ty.u32};
-
-  auto* var = Var("a", ast::StorageClass::kNone, &my_int, Expr(2u),
+  auto* my_int = ty.alias("MyInt", ty.u32);
+  auto* var = Var("a", ast::StorageClass::kNone, my_int, Expr(2u),
                   ast::VariableDecorationList{});
 
-  auto* cond = Expr("a");
   ast::CaseSelectorList default_csl;
   auto* block_default = create<ast::BlockStatement>(ast::StatementList{});
   ast::CaseStatementList body;
@@ -365,9 +350,9 @@
 
   auto* block = create<ast::BlockStatement>(ast::StatementList{
       create<ast::VariableDeclStatement>(var),
-      create<ast::SwitchStatement>(cond, body),
+      create<ast::SwitchStatement>(Expr("a"), body),
   });
-  mod->AddConstructedType(&my_int);
+  mod->AddConstructedType(my_int);
 
   EXPECT_TRUE(td()->DetermineStatements(block)) << td()->error();
   EXPECT_TRUE(v()->ValidateStatements(block)) << v()->error();
diff --git a/src/validator/validator_test.cc b/src/validator/validator_test.cc
index bc37db6..fb56f48 100644
--- a/src/validator/validator_test.cc
+++ b/src/validator/validator_test.cc
@@ -277,20 +277,16 @@
   mod->AddGlobalVariable(Var("global_var", ast::StorageClass::kPrivate, ty.f32,
                              Expr(2.1f), ast::VariableDecorationList{}));
 
-  auto* lhs = create<ast::IdentifierExpression>(
-      mod->RegisterSymbol("global_var"), "global_var");
-  auto* rhs = Expr(3.14f);
-
-  auto* func =
-      Func("my_func", ast::VariableList{}, ty.void_,
-           ast::StatementList{
-               create<ast::AssignmentStatement>(
-                   Source{Source::Location{12, 34}}, lhs, rhs),
-               create<ast::ReturnStatement>(),
-           },
-           ast::FunctionDecorationList{
-               create<ast::StageDecoration>(ast::PipelineStage::kVertex),
-           });
+  auto* func = Func(
+      "my_func", ast::VariableList{}, ty.void_,
+      ast::StatementList{
+          create<ast::AssignmentStatement>(Source{Source::Location{12, 34}},
+                                           Expr("global_var"), Expr(3.14f)),
+          create<ast::ReturnStatement>(),
+      },
+      ast::FunctionDecorationList{
+          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+      });
   mod->AddFunction(func);
 
   EXPECT_TRUE(td()->Determine()) << td()->error();
diff --git a/src/validator/validator_type_test.cc b/src/validator/validator_type_test.cc
index 597ce7d..fec698f 100644
--- a/src/validator/validator_type_test.cc
+++ b/src/validator/validator_type_test.cc
@@ -49,9 +49,9 @@
                                                 Member("rt", ty.array<f32>())},
                           decos);
 
-  ast::type::Struct struct_type(mod->RegisterSymbol("Foo"), "Foo", st);
+  auto* struct_type = ty.struct_("Foo", st);
 
-  mod->AddConstructedType(&struct_type);
+  mod->AddConstructedType(struct_type);
   EXPECT_TRUE(v()->ValidateConstructedTypes(mod->constructed_types()));
 }
 
@@ -67,9 +67,8 @@
                                                 Member("rt", ty.array<f32>())},
                           decos);
 
-  ast::type::Struct struct_type(mod->RegisterSymbol("Foo"), "Foo", st);
-
-  mod->AddConstructedType(&struct_type);
+  auto* struct_type = ty.struct_("Foo", st);
+  mod->AddConstructedType(struct_type);
   EXPECT_FALSE(v()->ValidateConstructedTypes(mod->constructed_types()));
   EXPECT_EQ(v()->error(),
             "v-0031: a struct containing a runtime-sized array must be "
@@ -90,9 +89,9 @@
                                                 Member("vf", ty.f32)},
                           decos);
 
-  ast::type::Struct struct_type(mod->RegisterSymbol("Foo"), "Foo", st);
+  auto* struct_type = ty.struct_("Foo", st);
 
-  mod->AddConstructedType(&struct_type);
+  mod->AddConstructedType(struct_type);
   EXPECT_FALSE(v()->ValidateConstructedTypes(mod->constructed_types()));
   EXPECT_EQ(v()->error(),
             "v-0015: runtime arrays may only appear as the last member "
@@ -107,16 +106,15 @@
   //  a: u32;
   //}
 
-  ast::type::Alias alias{mod->RegisterSymbol("RTArr"), "RTArr",
-                         ty.array<u32>()};
+  auto* alias = ty.alias("RTArr", ty.array<u32>());
 
   ast::StructDecorationList decos;
   decos.push_back(create<ast::StructBlockDecoration>());
   auto* st = create<ast::Struct>(
-      ast::StructMemberList{Member("b", &alias), Member("a", ty.u32)}, decos);
+      ast::StructMemberList{Member("b", alias), Member("a", ty.u32)}, decos);
 
-  ast::type::Struct struct_type(mod->RegisterSymbol("s"), "s", st);
-  mod->AddConstructedType(&struct_type);
+  auto* struct_type = ty.struct_("s", st);
+  mod->AddConstructedType(struct_type);
   EXPECT_FALSE(v()->ValidateConstructedTypes(mod->constructed_types()));
   EXPECT_EQ(v()->error(),
             "v-0015: runtime arrays may only appear as the last member "
@@ -131,16 +129,15 @@
   //  b: RTArr;
   //}
 
-  ast::type::Alias alias{mod->RegisterSymbol("RTArr"), "RTArr",
-                         ty.array<u32>()};
+  auto* alias = ty.alias("RTArr", ty.array<u32>());
 
   ast::StructDecorationList decos;
   decos.push_back(create<ast::StructBlockDecoration>());
   auto* st = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.u32), Member("b", &alias)}, decos);
+      ast::StructMemberList{Member("a", ty.u32), Member("b", alias)}, decos);
 
-  ast::type::Struct struct_type(mod->RegisterSymbol("s"), "s", st);
-  mod->AddConstructedType(&struct_type);
+  auto* struct_type = ty.struct_("s", st);
+  mod->AddConstructedType(struct_type);
   EXPECT_TRUE(v()->ValidateConstructedTypes(mod->constructed_types()));
 }
 
diff --git a/src/writer/hlsl/generator_impl_alias_type_test.cc b/src/writer/hlsl/generator_impl_alias_type_test.cc
index adf6b4c..544f4ea 100644
--- a/src/writer/hlsl/generator_impl_alias_type_test.cc
+++ b/src/writer/hlsl/generator_impl_alias_type_test.cc
@@ -27,17 +27,17 @@
 using HlslGeneratorImplTest_Alias = TestHelper;
 
 TEST_F(HlslGeneratorImplTest_Alias, EmitAlias_F32) {
-  ast::type::Alias alias(mod->RegisterSymbol("a"), "a", ty.f32);
+  auto* alias = ty.alias("a", ty.f32);
 
-  ASSERT_TRUE(gen.EmitConstructedType(out, &alias)) << gen.error();
+  ASSERT_TRUE(gen.EmitConstructedType(out, alias)) << gen.error();
   EXPECT_EQ(result(), R"(typedef float a;
 )");
 }
 
 TEST_F(HlslGeneratorImplTest_Alias, EmitAlias_NameCollision) {
-  ast::type::Alias alias(mod->RegisterSymbol("float"), "float", ty.f32);
+  auto* alias = ty.alias("float", ty.f32);
 
-  ASSERT_TRUE(gen.EmitConstructedType(out, &alias)) << gen.error();
+  ASSERT_TRUE(gen.EmitConstructedType(out, alias)) << gen.error();
   EXPECT_EQ(result(), R"(typedef float float_tint_0;
 )");
 }
@@ -48,10 +48,10 @@
                             Member("b", ty.i32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("A"), "A", str);
-  ast::type::Alias alias(mod->RegisterSymbol("B"), "B", &s);
+  auto* s = ty.struct_("A", str);
+  auto* alias = ty.alias("B", s);
 
-  ASSERT_TRUE(gen.EmitConstructedType(out, &alias)) << gen.error();
+  ASSERT_TRUE(gen.EmitConstructedType(out, alias)) << gen.error();
   EXPECT_EQ(result(), R"(struct B {
   float a;
   int b;
diff --git a/src/writer/hlsl/generator_impl_function_test.cc b/src/writer/hlsl/generator_impl_function_test.cc
index 7f3741e..1b4b46d 100644
--- a/src/writer/hlsl/generator_impl_function_test.cc
+++ b/src/writer/hlsl/generator_impl_function_test.cc
@@ -258,15 +258,15 @@
       ast::StructMemberList{Member("coord", ty.vec4<f32>())},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Uniforms"), "Uniforms", str);
+  auto* s = ty.struct_("Uniforms", str);
 
-  auto* coord_var = Var("uniforms", ast::StorageClass::kUniform, &s, nullptr,
+  auto* coord_var = Var("uniforms", ast::StorageClass::kUniform, s, nullptr,
                         ast::VariableDecorationList{
                             create<ast::BindingDecoration>(0),
                             create<ast::SetDecoration>(1),
                         });
 
-  mod->AddConstructedType(&s);
+  mod->AddConstructedType(s);
 
   td.RegisterVariableForTesting(coord_var);
   mod->AddGlobalVariable(coord_var);
@@ -311,8 +311,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, &s);
+  auto* s = ty.struct_("Data", str);
+  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
   auto* coord_var =
       Var("coord", ast::StorageClass::kStorageBuffer, &ac, nullptr,
@@ -358,8 +358,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  ast::type::AccessControl ac(ast::AccessControl::kReadOnly, &s);
+  auto* s = ty.struct_("Data", str);
+  ast::type::AccessControl ac(ast::AccessControl::kReadOnly, s);
 
   auto* coord_var =
       Var("coord", ast::StorageClass::kStorageBuffer, &ac, nullptr,
@@ -406,8 +406,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, &s);
+  auto* s = ty.struct_("Data", str);
+  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
   auto* coord_var =
       Var("coord", ast::StorageClass::kStorageBuffer, &ac, nullptr,
@@ -917,8 +917,8 @@
       ast::StructMemberList{Member("d", ty.f32, {MemberOffset(0)})},
       ast::StructDecorationList{create<ast::StructBlockDecoration>()});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, &s);
+  auto* s = ty.struct_("Data", str);
+  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
   auto* data_var = Var("data", ast::StorageClass::kStorageBuffer, &ac, nullptr,
                        ast::VariableDecorationList{
@@ -926,7 +926,7 @@
                            create<ast::SetDecoration>(0),
                        });
 
-  mod->AddConstructedType(&s);
+  mod->AddConstructedType(s);
   td.RegisterVariableForTesting(data_var);
   mod->AddGlobalVariable(data_var);
 
diff --git a/src/writer/hlsl/generator_impl_member_accessor_test.cc b/src/writer/hlsl/generator_impl_member_accessor_test.cc
index dee9662..4b13271 100644
--- a/src/writer/hlsl/generator_impl_member_accessor_test.cc
+++ b/src/writer/hlsl/generator_impl_member_accessor_test.cc
@@ -44,10 +44,8 @@
       ast::StructMemberList{Member("mem", ty.f32, {MemberOffset(0)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Str"), "Str", strct);
-
-  auto* str_var = Var("str", ast::StorageClass::kPrivate, &s);
-
+  auto* s = ty.struct_("Str", strct);
+  auto* str_var = Var("str", ast::StorageClass::kPrivate, s);
   auto* expr = MemberAccessor("str", "mem");
 
   td.RegisterVariableForTesting(str_var);
@@ -75,8 +73,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
   auto* expr = MemberAccessor("data", "b");
 
   td.RegisterVariableForTesting(coord_var);
@@ -105,8 +103,8 @@
       ast::StructMemberList{Member("a", ty.i32, {MemberOffset(0)}),
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
   auto* expr = MemberAccessor("data", "a");
 
   td.RegisterVariableForTesting(coord_var);
@@ -138,11 +136,9 @@
                             Member("a", ty.mat2x3<f32>(), {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-
+  auto* s = ty.struct_("Data", str);
   auto* b_var = Var("b", ast::StorageClass::kPrivate, ty.mat2x3<f32>());
-
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
 
   auto* lhs = MemberAccessor("data", "a");
   auto* rhs = Expr("b");
@@ -185,9 +181,8 @@
                             Member("a", ty.mat2x3<f32>(), {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
 
   auto* lhs = MemberAccessor("data", "a");
   auto* rhs = Construct(ty.mat2x3<f32>(), ast::ExpressionList{});
@@ -227,9 +222,8 @@
                             Member("a", ty.mat3x2<f32>(), {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
 
   auto* expr = MemberAccessor("data", "a");
 
@@ -268,8 +262,8 @@
       },
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
   auto* expr = MemberAccessor("data", "a");
 
   td.RegisterVariableForTesting(coord_var);
@@ -300,8 +294,8 @@
       ast::StructMemberList{Member("a", ty.mat3x3<f32>(), {MemberOffset(0)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
   auto* expr = MemberAccessor("data", "a");
 
   td.RegisterVariableForTesting(coord_var);
@@ -333,8 +327,8 @@
                             Member("a", ty.mat4x3<f32>(), {MemberOffset(16)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
   auto* expr = IndexAccessor(
       IndexAccessor(MemberAccessor("data", "a"), Expr(2)), Expr(1));
 
@@ -366,8 +360,8 @@
   auto* str = create<ast::Struct>(
       ast::StructMemberList{Member("a", &ary, {MemberOffset(0)})},
       ast::StructDecorationList{});
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
   auto* expr = IndexAccessor(MemberAccessor("data", "a"), Expr(2));
 
   td.RegisterVariableForTesting(coord_var);
@@ -398,8 +392,8 @@
   auto* str = create<ast::Struct>(
       ast::StructMemberList{Member("a", &ary, {MemberOffset(0)})},
       ast::StructDecorationList{});
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
   auto* expr = IndexAccessor(MemberAccessor("data", "a"),
                              Sub(Add(Expr(2), Expr(4)), Expr(3)));
 
@@ -430,8 +424,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
 
   td.RegisterVariableForTesting(coord_var);
   gen.register_global(coord_var);
@@ -468,9 +462,8 @@
       ast::StructMemberList{Member("a", &ary, {MemberOffset(0)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
 
   td.RegisterVariableForTesting(coord_var);
   gen.register_global(coord_var);
@@ -504,8 +497,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
 
   td.RegisterVariableForTesting(coord_var);
   gen.register_global(coord_var);
@@ -539,8 +532,8 @@
                             Member("b", ty.vec3<f32>(), {MemberOffset(16)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
 
   td.RegisterVariableForTesting(coord_var);
   gen.register_global(coord_var);
@@ -571,9 +564,8 @@
                             Member("b", ty.vec3<f32>(), {MemberOffset(16)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &s);
+  auto* s = ty.struct_("Data", str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, s);
 
   td.RegisterVariableForTesting(coord_var);
   gen.register_global(coord_var);
@@ -615,9 +607,8 @@
       },
       ast::StructDecorationList{});
 
-  ast::type::Struct data(mod->RegisterSymbol("Data"), "Data", data_str);
-
-  ast::type::Array ary(&data, 4,
+  auto* data = ty.struct_("Data", data_str);
+  ast::type::Array ary(data, 4,
                        ast::ArrayDecorationList{
                            create<ast::StrideDecoration>(32),
                        });
@@ -626,9 +617,8 @@
       ast::StructMemberList{Member("c", &ary, {MemberOffset(0)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct pre_struct(mod->RegisterSymbol("Pre"), "Pre", pre_str);
-
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &pre_struct);
+  auto* pre_struct = ty.struct_("Pre", pre_str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, pre_struct);
 
   td.RegisterVariableForTesting(coord_var);
   gen.register_global(coord_var);
@@ -666,18 +656,16 @@
       },
       ast::StructDecorationList{});
 
-  ast::type::Struct data(mod->RegisterSymbol("Data"), "Data", data_str);
-
+  auto* data = ty.struct_("Data", data_str);
   ast::type::Array ary(
-      &data, 4, ast::ArrayDecorationList{create<ast::StrideDecoration>(32)});
+      data, 4, ast::ArrayDecorationList{create<ast::StrideDecoration>(32)});
 
   auto* pre_str = create<ast::Struct>(
       ast::StructMemberList{Member("c", &ary, {MemberOffset(0)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct pre_struct(mod->RegisterSymbol("Pre"), "Pre", pre_str);
-
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &pre_struct);
+  auto* pre_struct = ty.struct_("Pre", pre_str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, pre_struct);
 
   td.RegisterVariableForTesting(coord_var);
   gen.register_global(coord_var);
@@ -717,9 +705,8 @@
       },
       ast::StructDecorationList{});
 
-  ast::type::Struct data(mod->RegisterSymbol("Data"), "Data", data_str);
-
-  ast::type::Array ary(&data, 4,
+  auto* data = ty.struct_("Data", data_str);
+  ast::type::Array ary(data, 4,
                        ast::ArrayDecorationList{
                            create<ast::StrideDecoration>(32),
                        });
@@ -728,9 +715,8 @@
       ast::StructMemberList{Member("c", &ary, {MemberOffset(0)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct pre_struct(mod->RegisterSymbol("Pre"), "Pre", pre_str);
-
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &pre_struct);
+  auto* pre_struct = ty.struct_("Pre", pre_str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, pre_struct);
 
   td.RegisterVariableForTesting(coord_var);
   gen.register_global(coord_var);
@@ -769,9 +755,8 @@
       },
       ast::StructDecorationList{});
 
-  ast::type::Struct data(mod->RegisterSymbol("Data"), "Data", data_str);
-
-  ast::type::Array ary(&data, 4,
+  auto* data = ty.struct_("Data", data_str);
+  ast::type::Array ary(data, 4,
                        ast::ArrayDecorationList{
                            create<ast::StrideDecoration>(32),
                        });
@@ -780,9 +765,8 @@
       ast::StructMemberList{Member("c", &ary, {MemberOffset(0)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct pre_struct(mod->RegisterSymbol("Pre"), "Pre", pre_str);
-
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &pre_struct);
+  auto* pre_struct = ty.struct_("Pre", pre_str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, pre_struct);
 
   td.RegisterVariableForTesting(coord_var);
   gen.register_global(coord_var);
@@ -821,9 +805,8 @@
       },
       ast::StructDecorationList{});
 
-  ast::type::Struct data(mod->RegisterSymbol("Data"), "Data", data_str);
-
-  ast::type::Array ary(&data, 4,
+  auto* data = ty.struct_("Data", data_str);
+  ast::type::Array ary(data, 4,
                        ast::ArrayDecorationList{
                            create<ast::StrideDecoration>(32),
                        });
@@ -832,9 +815,8 @@
       ast::StructMemberList{Member("c", &ary, {MemberOffset(0)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct pre_struct(mod->RegisterSymbol("Pre"), "Pre", pre_str);
-
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &pre_struct);
+  auto* pre_struct = ty.struct_("Pre", pre_str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, pre_struct);
 
   td.RegisterVariableForTesting(coord_var);
   gen.register_global(coord_var);
@@ -877,9 +859,8 @@
       },
       ast::StructDecorationList{});
 
-  ast::type::Struct data(mod->RegisterSymbol("Data"), "Data", data_str);
-
-  ast::type::Array ary(&data, 4,
+  auto* data = ty.struct_("Data", data_str);
+  ast::type::Array ary(data, 4,
                        ast::ArrayDecorationList{
                            create<ast::StrideDecoration>(32),
                        });
@@ -888,9 +869,8 @@
       ast::StructMemberList{Member("c", &ary, {MemberOffset(0)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct pre_struct(mod->RegisterSymbol("Pre"), "Pre", pre_str);
-
-  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, &pre_struct);
+  auto* pre_struct = ty.struct_("Pre", pre_str);
+  auto* coord_var = Var("data", ast::StorageClass::kStorageBuffer, pre_struct);
 
   td.RegisterVariableForTesting(coord_var);
   gen.register_global(coord_var);
diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc
index 6ca4983..644e75f 100644
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ b/src/writer/hlsl/generator_impl_type_test.cc
@@ -44,16 +44,16 @@
 using HlslGeneratorImplTest_Type = TestHelper;
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Alias) {
-  ast::type::Alias alias(mod->RegisterSymbol("alias"), "alias", ty.f32);
+  auto* alias = ty.alias("alias", ty.f32);
 
-  ASSERT_TRUE(gen.EmitType(out, &alias, "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, alias, "")) << gen.error();
   EXPECT_EQ(result(), "alias");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Alias_NameCollision) {
-  ast::type::Alias alias(mod->RegisterSymbol("bool"), "bool", ty.f32);
+  auto* alias = ty.alias("bool", ty.f32);
 
-  ASSERT_TRUE(gen.EmitType(out, &alias, "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, alias, "")) << gen.error();
   EXPECT_EQ(result(), "bool_tint_0");
 }
 
@@ -137,9 +137,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  ASSERT_TRUE(gen.EmitStructType(out, &s, "S")) << gen.error();
+  auto* s = ty.struct_("S", str);
+  ASSERT_TRUE(gen.EmitStructType(out, s, "S")) << gen.error();
   EXPECT_EQ(result(), R"(struct S {
   int a;
   float b;
@@ -153,9 +152,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  ASSERT_TRUE(gen.EmitType(out, &s, "")) << gen.error();
+  auto* s = ty.struct_("S", str);
+  ASSERT_TRUE(gen.EmitType(out, s, "")) << gen.error();
   EXPECT_EQ(result(), "S");
 }
 
@@ -166,9 +164,8 @@
                             Member("c", ty.f32, {MemberOffset(128)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  ASSERT_TRUE(gen.EmitType(out, &s, "")) << gen.error();
+  auto* s = ty.struct_("S", str);
+  ASSERT_TRUE(gen.EmitType(out, s, "")) << gen.error();
   EXPECT_EQ(result(), R"(struct {
   int8_t pad_0[4];
   int a;
@@ -184,9 +181,8 @@
       ast::StructMemberList{Member("double", ty.i32), Member("float", ty.f32)},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  ASSERT_TRUE(gen.EmitStructType(out, &s, "S")) << gen.error();
+  auto* s = ty.struct_("S", str);
+  ASSERT_TRUE(gen.EmitStructType(out, s, "S")) << gen.error();
   EXPECT_EQ(result(), R"(struct S {
   int double_tint_0;
   float float_tint_0;
@@ -204,9 +200,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       decos);
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  ASSERT_TRUE(gen.EmitStructType(out, &s, "B")) << gen.error();
+  auto* s = ty.struct_("S", str);
+  ASSERT_TRUE(gen.EmitStructType(out, s, "B")) << gen.error();
   EXPECT_EQ(result(), R"(struct B {
   int a;
   float b;
diff --git a/src/writer/msl/generator_impl_alias_type_test.cc b/src/writer/msl/generator_impl_alias_type_test.cc
index 1cd8a4c..b22aa74 100644
--- a/src/writer/msl/generator_impl_alias_type_test.cc
+++ b/src/writer/msl/generator_impl_alias_type_test.cc
@@ -28,17 +28,17 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitConstructedType_F32) {
-  ast::type::Alias alias(mod->RegisterSymbol("a"), "a", ty.f32);
+  auto* alias = ty.alias("a", ty.f32);
 
-  ASSERT_TRUE(gen.EmitConstructedType(&alias)) << gen.error();
+  ASSERT_TRUE(gen.EmitConstructedType(alias)) << gen.error();
   EXPECT_EQ(gen.result(), R"(typedef float a;
 )");
 }
 
 TEST_F(MslGeneratorImplTest, EmitConstructedType_NameCollision) {
-  ast::type::Alias alias(mod->RegisterSymbol("float"), "float", ty.f32);
+  auto* alias = ty.alias("float", ty.f32);
 
-  ASSERT_TRUE(gen.EmitConstructedType(&alias)) << gen.error();
+  ASSERT_TRUE(gen.EmitConstructedType(alias)) << gen.error();
   EXPECT_EQ(gen.result(), R"(typedef float float_tint_0;
 )");
 }
@@ -49,9 +49,8 @@
                             Member("b", ty.i32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("a"), "a", str);
-
-  ASSERT_TRUE(gen.EmitConstructedType(&s)) << gen.error();
+  auto* s = ty.struct_("a", str);
+  ASSERT_TRUE(gen.EmitConstructedType(s)) << gen.error();
   EXPECT_EQ(gen.result(), R"(struct a {
   float a;
   int b;
@@ -65,10 +64,10 @@
                             Member("b", ty.i32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("b"), "b", str);
-  ast::type::Alias alias(mod->RegisterSymbol("a"), "a", &s);
+  auto* s = ty.struct_("b", str);
+  auto* alias = ty.alias("a", s);
 
-  ASSERT_TRUE(gen.EmitConstructedType(&alias)) << gen.error();
+  ASSERT_TRUE(gen.EmitConstructedType(alias)) << gen.error();
   EXPECT_EQ(gen.result(), R"(typedef b a;
 )");
 }
diff --git a/src/writer/msl/generator_impl_array_accessor_test.cc b/src/writer/msl/generator_impl_array_accessor_test.cc
index 541ab24..5b5c54d 100644
--- a/src/writer/msl/generator_impl_array_accessor_test.cc
+++ b/src/writer/msl/generator_impl_array_accessor_test.cc
@@ -32,27 +32,17 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitExpression_ArrayAccessor) {
-  ast::type::I32 i32;
-  auto* lit = create<ast::SintLiteral>(Source{}, &i32, 5);
-  auto* idx = create<ast::ScalarConstructorExpression>(Source{}, lit);
-  auto* ary = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("ary"), "ary");
+  auto* expr = IndexAccessor(Expr("ary"), 5);
 
-  ast::ArrayAccessorExpression expr(Source{}, ary, idx);
-
-  ASSERT_TRUE(gen.EmitExpression(&expr)) << gen.error();
+  ASSERT_TRUE(gen.EmitExpression(expr)) << gen.error();
   EXPECT_EQ(gen.result(), "ary[5]");
 }
 
 TEST_F(MslGeneratorImplTest, EmitArrayAccessor) {
-  auto* ary = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("ary"), "ary");
-  auto* idx = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("idx"), "idx");
+  auto* expr = IndexAccessor(Expr("ary"), Expr("idx"));
 
-  ast::ArrayAccessorExpression expr(Source{}, ary, idx);
-
-  ASSERT_TRUE(gen.EmitArrayAccessor(&expr)) << gen.error();
+  ASSERT_TRUE(gen.EmitArrayAccessor(expr->As<ast::ArrayAccessorExpression>()))
+      << gen.error();
   EXPECT_EQ(gen.result(), "ary[idx]");
 }
 
diff --git a/src/writer/msl/generator_impl_assign_test.cc b/src/writer/msl/generator_impl_assign_test.cc
index 44cd4af..5a723cf 100644
--- a/src/writer/msl/generator_impl_assign_test.cc
+++ b/src/writer/msl/generator_impl_assign_test.cc
@@ -30,12 +30,7 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, Emit_Assign) {
-  auto* lhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("lhs"), "lhs");
-  auto* rhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("rhs"), "rhs");
-  ast::AssignmentStatement assign(Source{}, lhs, rhs);
-
+  ast::AssignmentStatement assign(Source{}, Expr("lhs"), Expr("rhs"));
   gen.increment_indent();
 
   ASSERT_TRUE(gen.EmitStatement(&assign)) << gen.error();
diff --git a/src/writer/msl/generator_impl_binary_test.cc b/src/writer/msl/generator_impl_binary_test.cc
index 0855853..e85113c 100644
--- a/src/writer/msl/generator_impl_binary_test.cc
+++ b/src/writer/msl/generator_impl_binary_test.cc
@@ -38,13 +38,7 @@
 TEST_P(MslBinaryTest, Emit) {
   auto params = GetParam();
 
-  auto* left = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("left"), "left");
-  auto* right = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("right"), "right");
-
-  ast::BinaryExpression expr(Source{}, params.op, left, right);
-
+  ast::BinaryExpression expr(Source{}, params.op, Expr("left"), Expr("right"));
   ASSERT_TRUE(gen.EmitExpression(&expr)) << gen.error();
   EXPECT_EQ(gen.result(), params.result);
 }
diff --git a/src/writer/msl/generator_impl_bitcast_test.cc b/src/writer/msl/generator_impl_bitcast_test.cc
index fe8f8e5..04cf30d 100644
--- a/src/writer/msl/generator_impl_bitcast_test.cc
+++ b/src/writer/msl/generator_impl_bitcast_test.cc
@@ -30,11 +30,7 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitExpression_Bitcast) {
-  ast::type::F32 f32;
-  auto* id = create<ast::IdentifierExpression>(Source{},
-                                               mod->RegisterSymbol("id"), "id");
-  ast::BitcastExpression bitcast(Source{}, &f32, id);
-
+  ast::BitcastExpression bitcast(Source{}, ty.f32, Expr("id"));
   ASSERT_TRUE(gen.EmitExpression(&bitcast)) << gen.error();
   EXPECT_EQ(gen.result(), "as_type<float>(id)");
 }
diff --git a/src/writer/msl/generator_impl_call_test.cc b/src/writer/msl/generator_impl_call_test.cc
index ea149a3..2c96829 100644
--- a/src/writer/msl/generator_impl_call_test.cc
+++ b/src/writer/msl/generator_impl_call_test.cc
@@ -32,59 +32,35 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitExpression_Call_WithoutParams) {
-  ast::type::Void void_type;
-
-  auto* id = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("my_func"), "my_func");
-  ast::CallExpression call(Source{}, id, {});
-
-  auto* func = Func("my_func", ast::VariableList{}, &void_type,
+  auto* call = Call("my_func");
+  auto* func = Func("my_func", ast::VariableList{}, ty.void_,
                     ast::StatementList{}, ast::FunctionDecorationList{});
   mod->AddFunction(func);
 
-  ASSERT_TRUE(gen.EmitExpression(&call)) << gen.error();
+  ASSERT_TRUE(gen.EmitExpression(call)) << gen.error();
   EXPECT_EQ(gen.result(), "my_func()");
 }
 
 TEST_F(MslGeneratorImplTest, EmitExpression_Call_WithParams) {
-  ast::type::Void void_type;
-
-  auto* id = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("my_func"), "my_func");
-  ast::ExpressionList params;
-  params.push_back(create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("param1"), "param1"));
-  params.push_back(create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("param2"), "param2"));
-  ast::CallExpression call(Source{}, id, params);
-
-  auto* func = Func("my_func", ast::VariableList{}, &void_type,
+  auto* call = Call("my_func", "param1", "param2");
+  auto* func = Func("my_func", ast::VariableList{}, ty.void_,
                     ast::StatementList{}, ast::FunctionDecorationList{});
   mod->AddFunction(func);
 
-  ASSERT_TRUE(gen.EmitExpression(&call)) << gen.error();
+  ASSERT_TRUE(gen.EmitExpression(call)) << gen.error();
   EXPECT_EQ(gen.result(), "my_func(param1, param2)");
 }
 
 TEST_F(MslGeneratorImplTest, EmitStatement_Call) {
-  ast::type::Void void_type;
-
-  auto* id = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("my_func"), "my_func");
-  ast::ExpressionList params;
-  params.push_back(create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("param1"), "param1"));
-  params.push_back(create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("param2"), "param2"));
-  ast::CallStatement call(Source{},
-                          create<ast::CallExpression>(Source{}, id, params));
-
-  auto* func = Func("my_func", ast::VariableList{}, &void_type,
+  auto* call = Call("my_func", "param1", "param2");
+  auto* func = Func("my_func", ast::VariableList{}, ty.void_,
                     ast::StatementList{}, ast::FunctionDecorationList{});
   mod->AddFunction(func);
 
+  ast::CallStatement expr(Source{}, call);
+
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitStatement(&call)) << gen.error();
+  ASSERT_TRUE(gen.EmitStatement(&expr)) << gen.error();
   EXPECT_EQ(gen.result(), "  my_func(param1, param2);\n");
 }
 
diff --git a/src/writer/msl/generator_impl_cast_test.cc b/src/writer/msl/generator_impl_cast_test.cc
index a7f96a5..9280ed0 100644
--- a/src/writer/msl/generator_impl_cast_test.cc
+++ b/src/writer/msl/generator_impl_cast_test.cc
@@ -31,27 +31,20 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitExpression_Cast_Scalar) {
-  ast::type::F32 f32;
-
   ast::ExpressionList params;
-  params.push_back(create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("id"), "id"));
+  params.push_back(Expr("id"));
 
-  ast::TypeConstructorExpression cast(Source{}, &f32, params);
+  ast::TypeConstructorExpression cast(Source{}, ty.f32, params);
 
   ASSERT_TRUE(gen.EmitExpression(&cast)) << gen.error();
   EXPECT_EQ(gen.result(), "float(id)");
 }
 
 TEST_F(MslGeneratorImplTest, EmitExpression_Cast_Vector) {
-  ast::type::F32 f32;
-  ast::type::Vector vec3(&f32, 3);
-
   ast::ExpressionList params;
-  params.push_back(create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("id"), "id"));
+  params.push_back(Expr("id"));
 
-  ast::TypeConstructorExpression cast(Source{}, &vec3, params);
+  ast::TypeConstructorExpression cast(Source{}, ty.vec3<f32>(), params);
 
   ASSERT_TRUE(gen.EmitExpression(&cast)) << gen.error();
   EXPECT_EQ(gen.result(), "float3(id)");
diff --git a/src/writer/msl/generator_impl_function_entry_point_data_test.cc b/src/writer/msl/generator_impl_function_entry_point_data_test.cc
index 0851047..7828fdd 100644
--- a/src/writer/msl/generator_impl_function_entry_point_data_test.cc
+++ b/src/writer/msl/generator_impl_function_entry_point_data_test.cc
@@ -47,32 +47,13 @@
   //   int bar [[attribute(1)]];
   // };
 
-  ast::type::F32 f32;
-  ast::type::I32 i32;
-
   auto* foo_var =
-      create<ast::Variable>(Source{},                   // source
-                            "foo",                      // name
-                            ast::StorageClass::kInput,  // storage_class
-                            &f32,                       // type
-                            false,                      // is_const
-                            nullptr,                    // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 0),
-                            });
+      Var("foo", ast::StorageClass::kInput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(0)});
 
   auto* bar_var =
-      create<ast::Variable>(Source{},                   // source
-                            "bar",                      // name
-                            ast::StorageClass::kInput,  // storage_class
-                            &i32,                       // type
-                            false,                      // is_const
-                            nullptr,                    // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 1),
-                            });
+      Var("bar", ast::StorageClass::kInput, ty.i32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(1)});
 
   td.RegisterVariableForTesting(foo_var);
   td.RegisterVariableForTesting(bar_var);
@@ -81,21 +62,11 @@
   mod->AddGlobalVariable(bar_var);
 
   auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo")),
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar")),
+      create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
+      create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
   };
   auto* func = Func(
-      "vtx_main", ast::VariableList{}, &f32, body,
+      "vtx_main", ast::VariableList{}, ty.f32, body,
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
       });
@@ -122,32 +93,13 @@
   //   int bar [[user(locn1)]];
   // };
 
-  ast::type::F32 f32;
-  ast::type::I32 i32;
-
   auto* foo_var =
-      create<ast::Variable>(Source{},                    // source
-                            "foo",                       // name
-                            ast::StorageClass::kOutput,  // storage_class
-                            &f32,                        // type
-                            false,                       // is_const
-                            nullptr,                     // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 0),
-                            });
+      Var("foo", ast::StorageClass::kOutput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(0)});
 
   auto* bar_var =
-      create<ast::Variable>(Source{},                    // source
-                            "bar",                       // name
-                            ast::StorageClass::kOutput,  // storage_class
-                            &i32,                        // type
-                            false,                       // is_const
-                            nullptr,                     // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 1),
-                            });
+      Var("bar", ast::StorageClass::kOutput, ty.i32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(1)});
 
   td.RegisterVariableForTesting(foo_var);
   td.RegisterVariableForTesting(bar_var);
@@ -156,21 +108,11 @@
   mod->AddGlobalVariable(bar_var);
 
   auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo")),
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar")),
+      create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
+      create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
   };
   auto* func = Func(
-      "vtx_main", ast::VariableList{}, &f32, body,
+      "vtx_main", ast::VariableList{}, ty.f32, body,
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
       });
@@ -197,32 +139,13 @@
   //   int bar [[user(locn1)]];
   // };
 
-  ast::type::F32 f32;
-  ast::type::I32 i32;
-
   auto* foo_var =
-      create<ast::Variable>(Source{},                   // source
-                            "foo",                      // name
-                            ast::StorageClass::kInput,  // storage_class
-                            &f32,                       // type
-                            false,                      // is_const
-                            nullptr,                    // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 0),
-                            });
+      Var("foo", ast::StorageClass::kInput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(0)});
 
   auto* bar_var =
-      create<ast::Variable>(Source{},                   // source
-                            "bar",                      // name
-                            ast::StorageClass::kInput,  // storage_class
-                            &i32,                       // type
-                            false,                      // is_const
-                            nullptr,                    // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 1),
-                            });
+      Var("bar", ast::StorageClass::kInput, ty.i32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(1)});
 
   td.RegisterVariableForTesting(foo_var);
   td.RegisterVariableForTesting(bar_var);
@@ -231,21 +154,11 @@
   mod->AddGlobalVariable(bar_var);
 
   auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo")),
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar")),
+      create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
+      create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
   };
   auto* func = Func(
-      "main", ast::VariableList{}, &f32, body,
+      "main", ast::VariableList{}, ty.f32, body,
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
       });
@@ -272,32 +185,13 @@
   //   int bar [[color(1)]];
   // };
 
-  ast::type::F32 f32;
-  ast::type::I32 i32;
-
   auto* foo_var =
-      create<ast::Variable>(Source{},                    // source
-                            "foo",                       // name
-                            ast::StorageClass::kOutput,  // storage_class
-                            &f32,                        // type
-                            false,                       // is_const
-                            nullptr,                     // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 0),
-                            });
+      Var("foo", ast::StorageClass::kOutput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(0)});
 
   auto* bar_var =
-      create<ast::Variable>(Source{},                    // source
-                            "bar",                       // name
-                            ast::StorageClass::kOutput,  // storage_class
-                            &i32,                        // type
-                            false,                       // is_const
-                            nullptr,                     // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 1),
-                            });
+      Var("bar", ast::StorageClass::kOutput, ty.i32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(1)});
 
   td.RegisterVariableForTesting(foo_var);
   td.RegisterVariableForTesting(bar_var);
@@ -306,21 +200,11 @@
   mod->AddGlobalVariable(bar_var);
 
   auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo")),
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar")),
+      create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
+      create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
   };
   auto* func = Func(
-      "main", ast::VariableList{}, &f32, body,
+      "main", ast::VariableList{}, ty.f32, body,
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
       });
@@ -344,32 +228,13 @@
   //
   // -> Error, not allowed
 
-  ast::type::F32 f32;
-  ast::type::I32 i32;
-
   auto* foo_var =
-      create<ast::Variable>(Source{},                   // source
-                            "foo",                      // name
-                            ast::StorageClass::kInput,  // storage_class
-                            &f32,                       // type
-                            false,                      // is_const
-                            nullptr,                    // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 0),
-                            });
+      Var("foo", ast::StorageClass::kInput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(0)});
 
   auto* bar_var =
-      create<ast::Variable>(Source{},                   // source
-                            "bar",                      // name
-                            ast::StorageClass::kInput,  // storage_class
-                            &i32,                       // type
-                            false,                      // is_const
-                            nullptr,                    // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 1),
-                            });
+      Var("bar", ast::StorageClass::kInput, ty.i32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(1)});
 
   td.RegisterVariableForTesting(foo_var);
   td.RegisterVariableForTesting(bar_var);
@@ -378,21 +243,11 @@
   mod->AddGlobalVariable(bar_var);
 
   auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo")),
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar")),
+      create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
+      create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
   };
   auto* func = Func(
-      "main", ast::VariableList{}, &f32, body,
+      "main", ast::VariableList{}, ty.f32, body,
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kCompute),
       });
@@ -411,32 +266,13 @@
   //
   // -> Error not allowed
 
-  ast::type::F32 f32;
-  ast::type::I32 i32;
-
   auto* foo_var =
-      create<ast::Variable>(Source{},                    // source
-                            "foo",                       // name
-                            ast::StorageClass::kOutput,  // storage_class
-                            &f32,                        // type
-                            false,                       // is_const
-                            nullptr,                     // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 0),
-                            });
+      Var("foo", ast::StorageClass::kOutput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(0)});
 
   auto* bar_var =
-      create<ast::Variable>(Source{},                    // source
-                            "bar",                       // name
-                            ast::StorageClass::kOutput,  // storage_class
-                            &i32,                        // type
-                            false,                       // is_const
-                            nullptr,                     // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 1),
-                            });
+      Var("bar", ast::StorageClass::kOutput, ty.i32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(1)});
 
   td.RegisterVariableForTesting(foo_var);
   td.RegisterVariableForTesting(bar_var);
@@ -445,21 +281,11 @@
   mod->AddGlobalVariable(bar_var);
 
   auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo")),
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar")),
+      create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
+      create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
   };
   auto* func = Func(
-      "main", ast::VariableList{}, &f32, body,
+      "main", ast::VariableList{}, ty.f32, body,
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kCompute),
       });
@@ -483,33 +309,15 @@
   //   float depth [[depth(any)]];
   // };
 
-  ast::type::F32 f32;
-  ast::type::Void void_type;
-  ast::type::Vector vec4(&f32, 4);
+  auto* coord_var =
+      Var("coord", ast::StorageClass::kInput, ty.vec4<f32>(), nullptr,
+          ast::VariableDecorationList{
+              create<ast::BuiltinDecoration>(ast::Builtin::kFragCoord)});
 
-  auto* coord_var = create<ast::Variable>(
-      Source{},                   // source
-      "coord",                    // name
-      ast::StorageClass::kInput,  // storage_class
-      &vec4,                      // type
-      false,                      // is_const
-      nullptr,                    // constructor
-      ast::VariableDecorationList{
-          // decorations
-          create<ast::BuiltinDecoration>(Source{}, ast::Builtin::kFragCoord),
-      });
-
-  auto* depth_var = create<ast::Variable>(
-      Source{},                    // source
-      "depth",                     // name
-      ast::StorageClass::kOutput,  // storage_class
-      &f32,                        // type
-      false,                       // is_const
-      nullptr,                     // constructor
-      ast::VariableDecorationList{
-          // decorations
-          create<ast::BuiltinDecoration>(Source{}, ast::Builtin::kFragDepth),
-      });
+  auto* depth_var =
+      Var("depth", ast::StorageClass::kOutput, ty.f32, nullptr,
+          ast::VariableDecorationList{
+              create<ast::BuiltinDecoration>(ast::Builtin::kFragDepth)});
 
   td.RegisterVariableForTesting(coord_var);
   td.RegisterVariableForTesting(depth_var);
@@ -517,20 +325,10 @@
   mod->AddGlobalVariable(coord_var);
   mod->AddGlobalVariable(depth_var);
 
-  auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(
-              Source{}, mod->RegisterSymbol("depth"), "depth"),
-          create<ast::MemberAccessorExpression>(
-              Source{},
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol("coord"), "coord"),
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol("x"), "x"))),
-  };
+  auto body = ast::StatementList{create<ast::AssignmentStatement>(
+      Expr("depth"), MemberAccessor("coord", "x"))};
   auto* func = Func(
-      "main", ast::VariableList{}, &void_type, body,
+      "main", ast::VariableList{}, ty.void_, body,
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
       });
diff --git a/src/writer/msl/generator_impl_function_test.cc b/src/writer/msl/generator_impl_function_test.cc
index f926bbf..1fec635 100644
--- a/src/writer/msl/generator_impl_function_test.cc
+++ b/src/writer/msl/generator_impl_function_test.cc
@@ -56,11 +56,9 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, Emit_Function) {
-  ast::type::Void void_type;
-
-  auto* func = Func("my_func", ast::VariableList{}, &void_type,
+  auto* func = Func("my_func", ast::VariableList{}, ty.void_,
                     ast::StatementList{
-                        create<ast::ReturnStatement>(Source{}),
+                        create<ast::ReturnStatement>(),
                     },
                     ast::FunctionDecorationList{});
 
@@ -78,11 +76,9 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_Function_Name_Collision) {
-  ast::type::Void void_type;
-
-  auto* func = Func("main", ast::VariableList{}, &void_type,
+  auto* func = Func("main", ast::VariableList{}, ty.void_,
                     ast::StatementList{
-                        create<ast::ReturnStatement>(Source{}),
+                        create<ast::ReturnStatement>(),
                     },
                     ast::FunctionDecorationList{});
 
@@ -100,32 +96,13 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_Function_WithParams) {
-  ast::type::F32 f32;
-  ast::type::I32 i32;
-
   ast::VariableList params;
-  params.push_back(
-      create<ast::Variable>(Source{},                         // source
-                            "a",                              // name
-                            ast::StorageClass::kNone,         // storage_class
-                            &f32,                             // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{}));  // decorations
-  params.push_back(
-      create<ast::Variable>(Source{},                         // source
-                            "b",                              // name
-                            ast::StorageClass::kNone,         // storage_class
-                            &i32,                             // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{}));  // decorations
+  params.push_back(Var("a", ast::StorageClass::kNone, ty.f32));
+  params.push_back(Var("b", ast::StorageClass::kNone, ty.i32));
 
-  ast::type::Void void_type;
-
-  auto* func = Func("my_func", params, &void_type,
+  auto* func = Func("my_func", params, ty.void_,
                     ast::StatementList{
-                        create<ast::ReturnStatement>(Source{}),
+                        create<ast::ReturnStatement>(),
                     },
                     ast::FunctionDecorationList{});
 
@@ -143,32 +120,13 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_FunctionDecoration_EntryPoint_WithInOutVars) {
-  ast::type::Void void_type;
-  ast::type::F32 f32;
-
   auto* foo_var =
-      create<ast::Variable>(Source{},                   // source
-                            "foo",                      // name
-                            ast::StorageClass::kInput,  // storage_class
-                            &f32,                       // type
-                            false,                      // is_const
-                            nullptr,                    // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 0),
-                            });
+      Var("foo", ast::StorageClass::kInput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(0)});
 
   auto* bar_var =
-      create<ast::Variable>(Source{},                    // source
-                            "bar",                       // name
-                            ast::StorageClass::kOutput,  // storage_class
-                            &f32,                        // type
-                            false,                       // is_const
-                            nullptr,                     // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 1),
-                            });
+      Var("bar", ast::StorageClass::kOutput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(1)});
 
   td.RegisterVariableForTesting(foo_var);
   td.RegisterVariableForTesting(bar_var);
@@ -177,17 +135,12 @@
   mod->AddGlobalVariable(bar_var);
 
   auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo")),
-      create<ast::ReturnStatement>(Source{}),
+      create<ast::AssignmentStatement>(Expr("bar"), Expr("foo")),
+      create<ast::ReturnStatement>(),
   };
-  auto* func = Func("frag_main", ast::VariableList{}, &void_type, body,
+  auto* func = Func("frag_main", ast::VariableList{}, ty.void_, body,
                     ast::FunctionDecorationList{create<ast::StageDecoration>(
-                        Source{}, ast::PipelineStage::kFragment)});
+                        ast::PipelineStage::kFragment)});
 
   mod->AddFunction(func);
 
@@ -215,33 +168,15 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_EntryPoint_WithInOut_Builtins) {
-  ast::type::Void void_type;
-  ast::type::F32 f32;
-  ast::type::Vector vec4(&f32, 4);
+  auto* coord_var =
+      Var("coord", ast::StorageClass::kInput, ty.vec4<f32>(), nullptr,
+          ast::VariableDecorationList{
+              create<ast::BuiltinDecoration>(ast::Builtin::kFragCoord)});
 
-  auto* coord_var = create<ast::Variable>(
-      Source{},                   // source
-      "coord",                    // name
-      ast::StorageClass::kInput,  // storage_class
-      &vec4,                      // type
-      false,                      // is_const
-      nullptr,                    // constructor
-      ast::VariableDecorationList{
-          // decorations
-          create<ast::BuiltinDecoration>(Source{}, ast::Builtin::kFragCoord),
-      });
-
-  auto* depth_var = create<ast::Variable>(
-      Source{},                    // source
-      "depth",                     // name
-      ast::StorageClass::kOutput,  // storage_class
-      &f32,                        // type
-      false,                       // is_const
-      nullptr,                     // constructor
-      ast::VariableDecorationList{
-          // decorations
-          create<ast::BuiltinDecoration>(Source{}, ast::Builtin::kFragDepth),
-      });
+  auto* depth_var =
+      Var("depth", ast::StorageClass::kOutput, ty.f32, nullptr,
+          ast::VariableDecorationList{
+              create<ast::BuiltinDecoration>(ast::Builtin::kFragDepth)});
 
   td.RegisterVariableForTesting(coord_var);
   td.RegisterVariableForTesting(depth_var);
@@ -250,23 +185,15 @@
   mod->AddGlobalVariable(depth_var);
 
   auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(
-              Source{}, mod->RegisterSymbol("depth"), "depth"),
-          create<ast::MemberAccessorExpression>(
-              Source{},
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol("coord"), "coord"),
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol("x"), "x"))),
-      create<ast::ReturnStatement>(Source{}),
+      create<ast::AssignmentStatement>(Expr("depth"),
+                                       MemberAccessor("coord", "x")),
+      create<ast::ReturnStatement>(),
   };
-  auto* func = Func(
-      "frag_main", ast::VariableList{}, &void_type, body,
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
-      });
+  auto* func =
+      Func("frag_main", ast::VariableList{}, ty.void_, body,
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+           });
 
   mod->AddFunction(func);
 
@@ -289,50 +216,27 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_FunctionDecoration_EntryPoint_With_Uniform) {
-  ast::type::Void void_type;
-  ast::type::F32 f32;
-  ast::type::Vector vec4(&f32, 4);
-
   auto* coord_var =
-      create<ast::Variable>(Source{},                     // source
-                            "coord",                      // name
-                            ast::StorageClass::kUniform,  // storage_class
-                            &vec4,                        // type
-                            false,                        // is_const
-                            nullptr,                      // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::BindingDecoration>(Source{}, 0),
-                                create<ast::SetDecoration>(Source{}, 1),
-                            });
+      Var("coord", ast::StorageClass::kUniform, ty.vec4<f32>(), nullptr,
+          ast::VariableDecorationList{create<ast::BindingDecoration>(0),
+                                      create<ast::SetDecoration>(1)});
 
   td.RegisterVariableForTesting(coord_var);
 
   mod->AddGlobalVariable(coord_var);
 
-  auto* var = create<ast::Variable>(
-      Source{},                      // source
-      "v",                           // name
-      ast::StorageClass::kFunction,  // storage_class
-      &f32,                          // type
-      false,                         // is_const
-      create<ast::MemberAccessorExpression>(
-          Source{},
-          create<ast::IdentifierExpression>(
-              Source{}, mod->RegisterSymbol("coord"), "coord"),
-          create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol("x"),
-                                            "x")),  // constructor
-      ast::VariableDecorationList{});               // decorations
+  auto* var = Var("v", ast::StorageClass::kFunction, ty.f32,
+                  MemberAccessor("coord", "x"), ast::VariableDecorationList{});
 
-  auto* func = Func(
-      "frag_main", ast::VariableList{}, &void_type,
-      ast::StatementList{
-          create<ast::VariableDeclStatement>(Source{}, var),
-          create<ast::ReturnStatement>(Source{}),
-      },
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
-      });
+  auto* func =
+      Func("frag_main", ast::VariableList{}, ty.void_,
+           ast::StatementList{
+               create<ast::VariableDeclStatement>(var),
+               create<ast::ReturnStatement>(),
+           },
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+           });
 
   mod->AddFunction(func);
 
@@ -351,58 +255,37 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_EntryPoint_With_RW_StorageBuffer) {
-  ast::type::Void void_type;
-
   auto* str = create<ast::Struct>(
       ast::StructMemberList{Member("a", ty.i32, {MemberOffset(0)}),
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, &s);
+  auto* s = ty.struct_("Data", str);
+  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
-  mod->AddConstructedType(&s);
+  mod->AddConstructedType(s);
 
   auto* coord_var =
-      create<ast::Variable>(Source{},                           // source
-                            "coord",                            // name
-                            ast::StorageClass::kStorageBuffer,  // storage_class
-                            &ac,                                // type
-                            false,                              // is_const
-                            nullptr,                            // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::BindingDecoration>(Source{}, 0),
-                                create<ast::SetDecoration>(Source{}, 1),
-                            });
+      Var("coord", ast::StorageClass::kStorageBuffer, &ac, nullptr,
+          ast::VariableDecorationList{create<ast::BindingDecoration>(0),
+                                      create<ast::SetDecoration>(1)});
 
   td.RegisterVariableForTesting(coord_var);
 
   mod->AddGlobalVariable(coord_var);
 
-  auto* var = create<ast::Variable>(
-      Source{},                      // source
-      "v",                           // name
-      ast::StorageClass::kFunction,  // storage_class
-      ty.f32,                        // type
-      false,                         // is_const
-      create<ast::MemberAccessorExpression>(
-          Source{},
-          create<ast::IdentifierExpression>(
-              Source{}, mod->RegisterSymbol("coord"), "coord"),
-          create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol("b"),
-                                            "b")),  // constructor
-      ast::VariableDecorationList{});               // decorations
+  auto* var = Var("v", ast::StorageClass::kFunction, ty.f32,
+                  MemberAccessor("coord", "b"), ast::VariableDecorationList{});
 
-  auto* func = Func(
-      "frag_main", ast::VariableList{}, &void_type,
-      ast::StatementList{
-          create<ast::VariableDeclStatement>(Source{}, var),
-          create<ast::ReturnStatement>(Source{}),
-      },
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
-      });
+  auto* func =
+      Func("frag_main", ast::VariableList{}, ty.void_,
+           ast::StatementList{
+               create<ast::VariableDeclStatement>(var),
+               create<ast::ReturnStatement>(),
+           },
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+           });
 
   mod->AddFunction(func);
 
@@ -425,57 +308,35 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_EntryPoint_With_RO_StorageBuffer) {
-  ast::type::Void void_type;
-
   auto* str = create<ast::Struct>(
       ast::StructMemberList{Member("a", ty.i32, {MemberOffset(0)}),
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  ast::type::AccessControl ac(ast::AccessControl::kReadOnly, &s);
-
-  mod->AddConstructedType(&s);
+  auto* s = ty.struct_("Data", str);
+  ast::type::AccessControl ac(ast::AccessControl::kReadOnly, s);
+  mod->AddConstructedType(s);
 
   auto* coord_var =
-      create<ast::Variable>(Source{},                           // source
-                            "coord",                            // name
-                            ast::StorageClass::kStorageBuffer,  // storage_class
-                            &ac,                                // type
-                            false,                              // is_const
-                            nullptr,                            // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::BindingDecoration>(Source{}, 0),
-                                create<ast::SetDecoration>(Source{}, 1),
-                            });
+      Var("coord", ast::StorageClass::kStorageBuffer, &ac, nullptr,
+          ast::VariableDecorationList{create<ast::BindingDecoration>(0),
+                                      create<ast::SetDecoration>(1)});
 
   td.RegisterVariableForTesting(coord_var);
   mod->AddGlobalVariable(coord_var);
 
-  auto* var = create<ast::Variable>(
-      Source{},                      // source
-      "v",                           // name
-      ast::StorageClass::kFunction,  // storage_class
-      ty.f32,                        // type
-      false,                         // is_const
-      create<ast::MemberAccessorExpression>(
-          Source{},
-          create<ast::IdentifierExpression>(
-              Source{}, mod->RegisterSymbol("coord"), "coord"),
-          create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol("b"),
-                                            "b")),  // constructor
-      ast::VariableDecorationList{});               // decorations
+  auto* var = Var("v", ast::StorageClass::kFunction, ty.f32,
+                  MemberAccessor("coord", "b"), ast::VariableDecorationList{});
 
-  auto* func = Func(
-      "frag_main", ast::VariableList{}, &void_type,
-      ast::StatementList{
-          create<ast::VariableDeclStatement>(Source{}, var),
-          create<ast::ReturnStatement>(Source{}),
-      },
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
-      });
+  auto* func =
+      Func("frag_main", ast::VariableList{}, ty.void_,
+           ast::StatementList{
+               create<ast::VariableDeclStatement>(var),
+               create<ast::ReturnStatement>(),
+           },
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+           });
 
   mod->AddFunction(func);
 
@@ -500,44 +361,17 @@
 TEST_F(
     MslGeneratorImplTest,
     Emit_FunctionDecoration_Called_By_EntryPoints_WithLocationGlobals_And_Params) {  // NOLINT
-  ast::type::Void void_type;
-  ast::type::F32 f32;
-
   auto* foo_var =
-      create<ast::Variable>(Source{},                   // source
-                            "foo",                      // name
-                            ast::StorageClass::kInput,  // storage_class
-                            &f32,                       // type
-                            false,                      // is_const
-                            nullptr,                    // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 0),
-                            });
+      Var("foo", ast::StorageClass::kInput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(0)});
 
   auto* bar_var =
-      create<ast::Variable>(Source{},                    // source
-                            "bar",                       // name
-                            ast::StorageClass::kOutput,  // storage_class
-                            &f32,                        // type
-                            false,                       // is_const
-                            nullptr,                     // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 1),
-                            });
+      Var("bar", ast::StorageClass::kOutput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(1)});
 
   auto* val_var =
-      create<ast::Variable>(Source{},                    // source
-                            "val",                       // name
-                            ast::StorageClass::kOutput,  // storage_class
-                            &f32,                        // type
-                            false,                       // is_const
-                            nullptr,                     // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 0),
-                            });
+      Var("val", ast::StorageClass::kOutput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(0)});
 
   td.RegisterVariableForTesting(foo_var);
   td.RegisterVariableForTesting(bar_var);
@@ -548,58 +382,26 @@
   mod->AddGlobalVariable(val_var);
 
   ast::VariableList params;
-  params.push_back(
-      create<ast::Variable>(Source{},                         // source
-                            "param",                          // name
-                            ast::StorageClass::kFunction,     // storage_class
-                            &f32,                             // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{}));  // decorations
+  params.push_back(Var("param", ast::StorageClass::kFunction, ty.f32));
 
   auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar"),
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("foo"), "foo")),
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("val"), "val"),
-          create<ast::IdentifierExpression>(
-              Source{}, mod->RegisterSymbol("param"), "param")),
-      create<ast::ReturnStatement>(
-          Source{}, create<ast::IdentifierExpression>(
-                        Source{}, mod->RegisterSymbol("foo"), "foo")),
-  };
+      create<ast::AssignmentStatement>(Expr("bar"), Expr("foo")),
+      create<ast::AssignmentStatement>(Expr("val"), Expr("param")),
+      create<ast::ReturnStatement>(Expr("foo"))};
   auto* sub_func =
-      Func("sub_func", params, &f32, body, ast::FunctionDecorationList{});
+      Func("sub_func", params, ty.f32, body, ast::FunctionDecorationList{});
 
   mod->AddFunction(sub_func);
 
-  ast::ExpressionList expr;
-  expr.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-
   body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar"),
-          create<ast::CallExpression>(
-              Source{},
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol("sub_func"), "sub_func"),
-              expr)),
-      create<ast::ReturnStatement>(Source{}),
+      create<ast::AssignmentStatement>(Expr("bar"), Call("sub_func", 1.0f)),
+      create<ast::ReturnStatement>(),
   };
-  auto* func_1 = Func(
-      "ep_1", ast::VariableList{}, &void_type, body,
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
-      });
+  auto* func_1 =
+      Func("ep_1", ast::VariableList{}, ty.void_, body,
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+           });
 
   mod->AddFunction(func_1);
 
@@ -634,69 +436,35 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_Called_By_EntryPoints_NoUsedGlobals) {
-  ast::type::Void void_type;
-  ast::type::F32 f32;
-  ast::type::Vector vec4(&f32, 4);
-
-  auto* depth_var = create<ast::Variable>(
-      Source{},                    // source
-      "depth",                     // name
-      ast::StorageClass::kOutput,  // storage_class
-      &f32,                        // type
-      false,                       // is_const
-      nullptr,                     // constructor
-      ast::VariableDecorationList{
-          // decorations
-          create<ast::BuiltinDecoration>(Source{}, ast::Builtin::kFragDepth),
-      });
+  auto* depth_var =
+      Var("depth", ast::StorageClass::kOutput, ty.f32, nullptr,
+          ast::VariableDecorationList{
+              create<ast::BuiltinDecoration>(ast::Builtin::kFragDepth)});
 
   td.RegisterVariableForTesting(depth_var);
-
   mod->AddGlobalVariable(depth_var);
 
   ast::VariableList params;
-  params.push_back(
-      create<ast::Variable>(Source{},                         // source
-                            "param",                          // name
-                            ast::StorageClass::kFunction,     // storage_class
-                            &f32,                             // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{}));  // decorations
+  params.push_back(Var("param", ast::StorageClass::kFunction, ty.f32));
 
-  auto* sub_func = Func(
-      "sub_func", params, &f32,
-      ast::StatementList{
-          create<ast::ReturnStatement>(
-              Source{}, create<ast::IdentifierExpression>(
-                            Source{}, mod->RegisterSymbol("param"), "param")),
-      },
-      ast::FunctionDecorationList{});
+  auto* sub_func = Func("sub_func", params, ty.f32,
+                        ast::StatementList{
+                            create<ast::ReturnStatement>(Expr("param")),
+                        },
+                        ast::FunctionDecorationList{});
 
   mod->AddFunction(sub_func);
 
-  ast::ExpressionList expr;
-  expr.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-
   auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(
-              Source{}, mod->RegisterSymbol("depth"), "depth"),
-          create<ast::CallExpression>(
-              Source{},
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol("sub_func"), "sub_func"),
-              expr)),
-      create<ast::ReturnStatement>(Source{}),
+      create<ast::AssignmentStatement>(Expr("depth"), Call("sub_func", 1.0f)),
+      create<ast::ReturnStatement>(),
   };
 
-  auto* func_1 = Func(
-      "ep_1", ast::VariableList{}, &void_type, body,
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
-      });
+  auto* func_1 =
+      Func("ep_1", ast::VariableList{}, ty.void_, body,
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+           });
 
   mod->AddFunction(func_1);
 
@@ -725,33 +493,15 @@
 TEST_F(
     MslGeneratorImplTest,
     Emit_FunctionDecoration_Called_By_EntryPoints_WithBuiltinGlobals_And_Params) {  // NOLINT
-  ast::type::Void void_type;
-  ast::type::F32 f32;
-  ast::type::Vector vec4(&f32, 4);
+  auto* coord_var =
+      Var("coord", ast::StorageClass::kInput, ty.vec4<f32>(), nullptr,
+          ast::VariableDecorationList{
+              create<ast::BuiltinDecoration>(ast::Builtin::kFragCoord)});
 
-  auto* coord_var = create<ast::Variable>(
-      Source{},                   // source
-      "coord",                    // name
-      ast::StorageClass::kInput,  // storage_class
-      &vec4,                      // type
-      false,                      // is_const
-      nullptr,                    // constructor
-      ast::VariableDecorationList{
-          // decorations
-          create<ast::BuiltinDecoration>(Source{}, ast::Builtin::kFragCoord),
-      });
-
-  auto* depth_var = create<ast::Variable>(
-      Source{},                    // source
-      "depth",                     // name
-      ast::StorageClass::kOutput,  // storage_class
-      &f32,                        // type
-      false,                       // is_const
-      nullptr,                     // constructor
-      ast::VariableDecorationList{
-          // decorations
-          create<ast::BuiltinDecoration>(Source{}, ast::Builtin::kFragDepth),
-      });
+  auto* depth_var =
+      Var("depth", ast::StorageClass::kOutput, ty.f32, nullptr,
+          ast::VariableDecorationList{
+              create<ast::BuiltinDecoration>(ast::Builtin::kFragDepth)});
 
   td.RegisterVariableForTesting(coord_var);
   td.RegisterVariableForTesting(depth_var);
@@ -760,56 +510,27 @@
   mod->AddGlobalVariable(depth_var);
 
   ast::VariableList params;
-  params.push_back(
-      create<ast::Variable>(Source{},                         // source
-                            "param",                          // name
-                            ast::StorageClass::kFunction,     // storage_class
-                            &f32,                             // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{}));  // decorations
+  params.push_back(Var("param", ast::StorageClass::kFunction, ty.f32));
 
   auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(
-              Source{}, mod->RegisterSymbol("depth"), "depth"),
-          create<ast::MemberAccessorExpression>(
-              Source{},
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol("coord"), "coord"),
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol("x"), "x"))),
-      create<ast::ReturnStatement>(
-          Source{}, create<ast::IdentifierExpression>(
-                        Source{}, mod->RegisterSymbol("param"), "param")),
+      create<ast::AssignmentStatement>(Expr("depth"),
+                                       MemberAccessor("coord", "x")),
+      create<ast::ReturnStatement>(Expr("param")),
   };
   auto* sub_func =
-      Func("sub_func", params, &f32, body, ast::FunctionDecorationList{});
+      Func("sub_func", params, ty.f32, body, ast::FunctionDecorationList{});
 
   mod->AddFunction(sub_func);
 
-  ast::ExpressionList expr;
-  expr.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-
   body = ast::StatementList{
-      create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(
-              Source{}, mod->RegisterSymbol("depth"), "depth"),
-          create<ast::CallExpression>(
-              Source{},
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol("sub_func"), "sub_func"),
-              expr)),
-      create<ast::ReturnStatement>(Source{}),
+      create<ast::AssignmentStatement>(Expr("depth"), Call("sub_func", 1.0f)),
+      create<ast::ReturnStatement>(),
   };
-  auto* func_1 = Func(
-      "ep_1", ast::VariableList{}, &void_type, body,
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
-      });
+  auto* func_1 =
+      Func("ep_1", ast::VariableList{}, ty.void_, body,
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+           });
 
   mod->AddFunction(func_1);
 
@@ -837,77 +558,41 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_Called_By_EntryPoint_With_Uniform) {
-  ast::type::Void void_type;
-  ast::type::F32 f32;
-  ast::type::Vector vec4(&f32, 4);
-
   auto* coord_var =
-      create<ast::Variable>(Source{},                     // source
-                            "coord",                      // name
-                            ast::StorageClass::kUniform,  // storage_class
-                            &vec4,                        // type
-                            false,                        // is_const
-                            nullptr,                      // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::BindingDecoration>(Source{}, 0),
-                                create<ast::SetDecoration>(Source{}, 1),
-                            });
+      Var("coord", ast::StorageClass::kUniform, ty.vec4<f32>(), nullptr,
+          ast::VariableDecorationList{create<ast::BindingDecoration>(0),
+                                      create<ast::SetDecoration>(1)});
 
   td.RegisterVariableForTesting(coord_var);
-
   mod->AddGlobalVariable(coord_var);
 
   ast::VariableList params;
-  params.push_back(
-      create<ast::Variable>(Source{},                         // source
-                            "param",                          // name
-                            ast::StorageClass::kFunction,     // storage_class
-                            &f32,                             // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{}));  // decorations
+  params.push_back(Var("param", ast::StorageClass::kFunction, ty.f32));
 
   auto body = ast::StatementList{
-      create<ast::ReturnStatement>(
-          Source{}, create<ast::MemberAccessorExpression>(
-                        Source{},
-                        create<ast::IdentifierExpression>(
-                            Source{}, mod->RegisterSymbol("coord"), "coord"),
-                        create<ast::IdentifierExpression>(
-                            Source{}, mod->RegisterSymbol("x"), "x"))),
+      create<ast::ReturnStatement>(MemberAccessor("coord", "x")),
   };
   auto* sub_func =
-      Func("sub_func", params, &f32, body, ast::FunctionDecorationList{});
+      Func("sub_func", params, ty.f32, body, ast::FunctionDecorationList{});
 
   mod->AddFunction(sub_func);
 
   ast::ExpressionList expr;
   expr.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
+      create<ast::FloatLiteral>(ty.f32, 1.0f)));
 
-  auto* var = create<ast::Variable>(
-      Source{},                      // source
-      "v",                           // name
-      ast::StorageClass::kFunction,  // storage_class
-      &f32,                          // type
-      false,                         // is_const
-      create<ast::CallExpression>(
-          Source{},
-          create<ast::IdentifierExpression>(
-              Source{}, mod->RegisterSymbol("sub_func"), "sub_func"),
-          expr),                       // constructor
-      ast::VariableDecorationList{});  // decorations
+  auto* var = Var("v", ast::StorageClass::kFunction, ty.f32,
+                  Call("sub_func", 1.0f), ast::VariableDecorationList{});
 
-  auto* func = Func(
-      "frag_main", ast::VariableList{}, &void_type,
-      ast::StatementList{
-          create<ast::VariableDeclStatement>(Source{}, var),
-          create<ast::ReturnStatement>(Source{}),
-      },
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
-      });
+  auto* func =
+      Func("frag_main", ast::VariableList{}, ty.void_,
+           ast::StatementList{
+               create<ast::VariableDeclStatement>(var),
+               create<ast::ReturnStatement>(),
+           },
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+           });
 
   mod->AddFunction(func);
 
@@ -929,84 +614,46 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_Called_By_EntryPoint_With_RW_StorageBuffer) {
-  ast::type::Void void_type;
-
   auto* str = create<ast::Struct>(
       ast::StructMemberList{Member("a", ty.i32, {MemberOffset(0)}),
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, &s);
-
-  mod->AddConstructedType(&s);
+  auto* s = ty.struct_("Data", str);
+  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, s);
+  mod->AddConstructedType(s);
 
   auto* coord_var =
-      create<ast::Variable>(Source{},                           // source
-                            "coord",                            // name
-                            ast::StorageClass::kStorageBuffer,  // storage_class
-                            &ac,                                // type
-                            false,                              // is_const
-                            nullptr,                            // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::BindingDecoration>(Source{}, 0),
-                                create<ast::SetDecoration>(Source{}, 1),
-                            });
+      Var("coord", ast::StorageClass::kStorageBuffer, &ac, nullptr,
+          ast::VariableDecorationList{create<ast::BindingDecoration>(0),
+                                      create<ast::SetDecoration>(1)});
 
   td.RegisterVariableForTesting(coord_var);
   mod->AddGlobalVariable(coord_var);
 
   ast::VariableList params;
   params.push_back(
-      create<ast::Variable>(Source{},                         // source
-                            "param",                          // name
-                            ast::StorageClass::kFunction,     // storage_class
-                            ty.f32,                           // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{}));  // decorations
+      Var("param", ast::StorageClass::kFunction, ty.f32));  // decorations
 
   auto body = ast::StatementList{
-      create<ast::ReturnStatement>(
-          Source{}, create<ast::MemberAccessorExpression>(
-                        Source{},
-                        create<ast::IdentifierExpression>(
-                            Source{}, mod->RegisterSymbol("coord"), "coord"),
-                        create<ast::IdentifierExpression>(
-                            Source{}, mod->RegisterSymbol("b"), "b"))),
-  };
+      create<ast::ReturnStatement>(MemberAccessor("coord", "b"))};
   auto* sub_func =
       Func("sub_func", params, ty.f32, body, ast::FunctionDecorationList{});
 
   mod->AddFunction(sub_func);
 
-  ast::ExpressionList expr;
-  expr.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, ty.f32, 1.0f)));
+  auto* var = Var("v", ast::StorageClass::kFunction, ty.f32,
+                  Call("sub_func", 1.0f), ast::VariableDecorationList{});
 
-  auto* var = create<ast::Variable>(
-      Source{},                      // source
-      "v",                           // name
-      ast::StorageClass::kFunction,  // storage_class
-      ty.f32,                        // type
-      false,                         // is_const
-      create<ast::CallExpression>(
-          Source{},
-          create<ast::IdentifierExpression>(
-              Source{}, mod->RegisterSymbol("sub_func"), "sub_func"),
-          expr),                       // constructor
-      ast::VariableDecorationList{});  // decorations
-
-  auto* func = Func(
-      "frag_main", ast::VariableList{}, &void_type,
-      ast::StatementList{
-          create<ast::VariableDeclStatement>(Source{}, var),
-          create<ast::ReturnStatement>(Source{}),
-      },
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
-      });
+  auto* func =
+      Func("frag_main", ast::VariableList{}, ty.void_,
+           ast::StatementList{
+               create<ast::VariableDeclStatement>(var),
+               create<ast::ReturnStatement>(),
+           },
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+           });
 
   mod->AddFunction(func);
 
@@ -1034,53 +681,29 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_Called_By_EntryPoint_With_RO_StorageBuffer) {
-  ast::type::Void void_type;
-
   auto* str = create<ast::Struct>(
       ast::StructMemberList{Member("a", ty.i32, {MemberOffset(0)}),
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  ast::type::AccessControl ac(ast::AccessControl::kReadOnly, &s);
-
-  mod->AddConstructedType(&s);
+  auto* s = ty.struct_("Data", str);
+  ast::type::AccessControl ac(ast::AccessControl::kReadOnly, s);
+  mod->AddConstructedType(s);
 
   auto* coord_var =
-      create<ast::Variable>(Source{},                           // source
-                            "coord",                            // name
-                            ast::StorageClass::kStorageBuffer,  // storage_class
-                            &ac,                                // type
-                            false,                              // is_const
-                            nullptr,                            // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::BindingDecoration>(Source{}, 0),
-                                create<ast::SetDecoration>(Source{}, 1),
-                            });
+      Var("coord", ast::StorageClass::kStorageBuffer, &ac, nullptr,
+          ast::VariableDecorationList{create<ast::BindingDecoration>(0),
+                                      create<ast::SetDecoration>(1)});
 
   td.RegisterVariableForTesting(coord_var);
   mod->AddGlobalVariable(coord_var);
 
   ast::VariableList params;
   params.push_back(
-      create<ast::Variable>(Source{},                         // source
-                            "param",                          // name
-                            ast::StorageClass::kFunction,     // storage_class
-                            ty.f32,                           // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{}));  // decorations
+      Var("param", ast::StorageClass::kFunction, ty.f32));  // decorations
 
   auto body = ast::StatementList{
-      create<ast::ReturnStatement>(
-          Source{}, create<ast::MemberAccessorExpression>(
-                        Source{},
-                        create<ast::IdentifierExpression>(
-                            Source{}, mod->RegisterSymbol("coord"), "coord"),
-                        create<ast::IdentifierExpression>(
-                            Source{}, mod->RegisterSymbol("b"), "b"))),
-  };
+      create<ast::ReturnStatement>(MemberAccessor("coord", "b"))};
   auto* sub_func =
       Func("sub_func", params, ty.f32, body, ast::FunctionDecorationList{});
 
@@ -1088,30 +711,20 @@
 
   ast::ExpressionList expr;
   expr.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, ty.f32, 1.0f)));
+      create<ast::FloatLiteral>(ty.f32, 1.0f)));
 
-  auto* var = create<ast::Variable>(
-      Source{},                      // source
-      "v",                           // name
-      ast::StorageClass::kFunction,  // storage_class
-      ty.f32,                        // type
-      false,                         // is_const
-      create<ast::CallExpression>(
-          Source{},
-          create<ast::IdentifierExpression>(
-              Source{}, mod->RegisterSymbol("sub_func"), "sub_func"),
-          expr),                       // constructor
-      ast::VariableDecorationList{});  // decorations
+  auto* var = Var("v", ast::StorageClass::kFunction, ty.f32,
+                  Call("sub_func", 1.0f), ast::VariableDecorationList{});
 
-  auto* func = Func(
-      "frag_main", ast::VariableList{}, &void_type,
-      ast::StatementList{
-          create<ast::VariableDeclStatement>(Source{}, var),
-          create<ast::ReturnStatement>(Source{}),
-      },
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
-      });
+  auto* func =
+      Func("frag_main", ast::VariableList{}, ty.void_,
+           ast::StatementList{
+               create<ast::VariableDeclStatement>(var),
+               create<ast::ReturnStatement>(),
+           },
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+           });
 
   mod->AddFunction(func);
 
@@ -1139,54 +752,36 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_EntryPoints_WithGlobal_Nested_Return) {
-  ast::type::Void void_type;
-  ast::type::F32 f32;
-  ast::type::I32 i32;
-
   auto* bar_var =
-      create<ast::Variable>(Source{},                    // source
-                            "bar",                       // name
-                            ast::StorageClass::kOutput,  // storage_class
-                            &f32,                        // type
-                            false,                       // is_const
-                            nullptr,                     // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 1),
-                            });
+      Var("bar", ast::StorageClass::kOutput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(1)});
 
   td.RegisterVariableForTesting(bar_var);
   mod->AddGlobalVariable(bar_var);
 
-  auto* list = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}),
-                });
+  auto* list = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::ReturnStatement>(),
+  });
 
   auto body = ast::StatementList{
       create<ast::AssignmentStatement>(
-          Source{},
-          create<ast::IdentifierExpression>(Source{},
-                                            mod->RegisterSymbol("bar"), "bar"),
-          create<ast::ScalarConstructorExpression>(
-              Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f))),
-      create<ast::IfStatement>(
-          Source{},
-          create<ast::BinaryExpression>(
-              Source{}, ast::BinaryOp::kEqual,
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 1)),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 1))),
-          list, ast::ElseStatementList{}),
-      create<ast::ReturnStatement>(Source{}),
+          Expr("bar"), create<ast::ScalarConstructorExpression>(
+                           create<ast::FloatLiteral>(ty.f32, 1.f))),
+      create<ast::IfStatement>(create<ast::BinaryExpression>(
+                                   ast::BinaryOp::kEqual,
+                                   create<ast::ScalarConstructorExpression>(
+                                       create<ast::SintLiteral>(ty.i32, 1)),
+                                   create<ast::ScalarConstructorExpression>(
+                                       create<ast::SintLiteral>(ty.i32, 1))),
+                               list, ast::ElseStatementList{}),
+      create<ast::ReturnStatement>(),
   };
 
-  auto* func_1 = Func(
-      "ep_1", ast::VariableList{}, &void_type, body,
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
-      });
+  auto* func_1 =
+      Func("ep_1", ast::VariableList{}, ty.void_, body,
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+           });
 
   mod->AddFunction(func_1);
 
@@ -1212,13 +807,11 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_EntryPoint_WithNameCollision) {
-  ast::type::Void void_type;
-
-  auto* func = Func(
-      "main", ast::VariableList{}, &void_type, ast::StatementList{},
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kCompute),
-      });
+  auto* func =
+      Func("main", ast::VariableList{}, ty.void_, ast::StatementList{},
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kCompute),
+           });
 
   mod->AddFunction(func);
 
@@ -1232,29 +825,18 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_Function_WithArrayParams) {
-  ast::type::F32 f32;
-  ast::type::Array ary(&f32, 5, ast::ArrayDecorationList{});
+  ast::type::Array ary(ty.f32, 5, ast::ArrayDecorationList{});
 
   ast::VariableList params;
-  params.push_back(
-      create<ast::Variable>(Source{},                         // source
-                            "a",                              // name
-                            ast::StorageClass::kNone,         // storage_class
-                            &ary,                             // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{}));  // decorations
+  params.push_back(Var("a", ast::StorageClass::kNone, &ary));  // decorations
 
-  ast::type::Void void_type;
-
-  auto* func = Func("my_func", params, &void_type,
+  auto* func = Func("my_func", params, ty.void_,
                     ast::StatementList{
                         create<ast::ReturnStatement>(),
                     },
                     ast::FunctionDecorationList{});
 
   mod->AddFunction(func);
-
   gen.increment_indent();
 
   ASSERT_TRUE(gen.Generate()) << gen.error();
@@ -1285,89 +867,51 @@
   //   return;
   // }
 
-  ast::type::Void void_type;
-
   ast::StructDecorationList s_decos;
-  s_decos.push_back(create<ast::StructBlockDecoration>(Source{}));
+  s_decos.push_back(create<ast::StructBlockDecoration>());
 
   auto* str = create<ast::Struct>(
       ast::StructMemberList{Member("d", ty.f32, {MemberOffset(0)})}, s_decos);
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, &s);
+  auto* s = ty.struct_("Data", str);
+  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
   auto* data_var =
-      create<ast::Variable>(Source{},                           // source
-                            "data",                             // name
-                            ast::StorageClass::kStorageBuffer,  // storage_class
-                            &ac,                                // type
-                            false,                              // is_const
-                            nullptr,                            // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::BindingDecoration>(Source{}, 0),
-                                create<ast::SetDecoration>(Source{}, 0),
-                            });
+      Var("data", ast::StorageClass::kStorageBuffer, &ac, nullptr,
+          ast::VariableDecorationList{create<ast::BindingDecoration>(0),
+                                      create<ast::SetDecoration>(0)});
 
-  mod->AddConstructedType(&s);
-
+  mod->AddConstructedType(s);
   td.RegisterVariableForTesting(data_var);
   mod->AddGlobalVariable(data_var);
 
   {
-    auto* var = create<ast::Variable>(
-        Source{},                      // source
-        "v",                           // name
-        ast::StorageClass::kFunction,  // storage_class
-        ty.f32,                        // type
-        false,                         // is_const
-        create<ast::MemberAccessorExpression>(
-            Source{},
-            create<ast::IdentifierExpression>(
-                Source{}, mod->RegisterSymbol("data"), "data"),
-            create<ast::IdentifierExpression>(Source{},
-                                              mod->RegisterSymbol("d"),
-                                              "d")),  // constructor
-        ast::VariableDecorationList{});               // decorations
+    auto* var = Var("v", ast::StorageClass::kFunction, ty.f32,
+                    MemberAccessor("data", "d"), ast::VariableDecorationList{});
 
-    auto* func = Func("a", ast::VariableList{}, &void_type,
-                      ast::StatementList{
-                          create<ast::VariableDeclStatement>(Source{}, var),
-                          create<ast::ReturnStatement>(Source{}),
-                      },
-                      ast::FunctionDecorationList{
-                          create<ast::StageDecoration>(
-                              Source{}, ast::PipelineStage::kCompute),
-                      });
+    auto* func =
+        Func("a", ast::VariableList{}, ty.void_,
+             ast::StatementList{
+                 create<ast::VariableDeclStatement>(var),
+                 create<ast::ReturnStatement>(),
+             },
+             ast::FunctionDecorationList{
+                 create<ast::StageDecoration>(ast::PipelineStage::kCompute),
+             });
 
     mod->AddFunction(func);
   }
 
   {
-    auto* var = create<ast::Variable>(
-        Source{},                      // source
-        "v",                           // name
-        ast::StorageClass::kFunction,  // storage_class
-        ty.f32,                        // type
-        false,                         // is_const
-        create<ast::MemberAccessorExpression>(
-            Source{},
-            create<ast::IdentifierExpression>(
-                Source{}, mod->RegisterSymbol("data"), "data"),
-            create<ast::IdentifierExpression>(Source{},
-                                              mod->RegisterSymbol("d"),
-                                              "d")),  // constructor
-        ast::VariableDecorationList{});               // decorations
+    auto* var = Var("v", ast::StorageClass::kFunction, ty.f32,
+                    MemberAccessor("data", "d"), ast::VariableDecorationList{});
 
-    auto* func = Func("b", ast::VariableList{}, &void_type,
-                      ast::StatementList{
-                          create<ast::VariableDeclStatement>(Source{}, var),
-                          create<ast::ReturnStatement>(Source{}),
-                      },
-                      ast::FunctionDecorationList{
-                          create<ast::StageDecoration>(
-                              Source{}, ast::PipelineStage::kCompute),
-                      });
+    auto* func =
+        Func("b", ast::VariableList{}, ty.void_,
+             ast::StatementList{create<ast::VariableDeclStatement>(var),
+                                create<ast::ReturnStatement>()},
+             ast::FunctionDecorationList{
+                 create<ast::StageDecoration>(ast::PipelineStage::kCompute)});
 
     mod->AddFunction(func);
   }
diff --git a/src/writer/msl/generator_impl_identifier_test.cc b/src/writer/msl/generator_impl_identifier_test.cc
index e203655..ab3279a 100644
--- a/src/writer/msl/generator_impl_identifier_test.cc
+++ b/src/writer/msl/generator_impl_identifier_test.cc
@@ -26,17 +26,14 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitIdentifierExpression) {
-  ast::IdentifierExpression i(Source{}, mod->RegisterSymbol("foo"), "foo");
-
-  ASSERT_TRUE(gen.EmitExpression(&i)) << gen.error();
+  auto* i = Expr("foo");
+  ASSERT_TRUE(gen.EmitExpression(i)) << gen.error();
   EXPECT_EQ(gen.result(), "foo");
 }
 
 TEST_F(MslGeneratorImplTest, EmitIdentifierExpression_Single_WithCollision) {
-  ast::IdentifierExpression i(Source{}, mod->RegisterSymbol("virtual"),
-                              "virtual");
-
-  ASSERT_TRUE(gen.EmitExpression(&i)) << gen.error();
+  auto* i = Expr("virtual");
+  ASSERT_TRUE(gen.EmitExpression(i)) << gen.error();
   EXPECT_EQ(gen.result(), "virtual_tint_0");
 }
 
diff --git a/src/writer/msl/generator_impl_if_test.cc b/src/writer/msl/generator_impl_if_test.cc
index 7da3950..e764782 100644
--- a/src/writer/msl/generator_impl_if_test.cc
+++ b/src/writer/msl/generator_impl_if_test.cc
@@ -29,12 +29,10 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, Emit_If) {
-  auto* cond = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("cond"), "cond");
-  auto* body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}),
-                });
+  auto* cond = Expr("cond");
+  auto* body = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::ReturnStatement>(),
+  });
   ast::IfStatement i(Source{}, cond, body, ast::ElseStatementList{});
 
   gen.increment_indent();
@@ -47,22 +45,17 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_IfWithElseIf) {
-  auto* else_cond = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("else_cond"), "else_cond");
-  auto* else_body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}),
-                });
+  auto* else_cond = Expr("else_cond");
+  auto* else_body = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::ReturnStatement>(),
+  });
 
-  auto* cond = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("cond"), "cond");
-  auto* body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}),
-                });
-  ast::IfStatement i(
-      Source{}, cond, body,
-      {create<ast::ElseStatement>(Source{}, else_cond, else_body)});
+  auto* cond = Expr("cond");
+  auto* body = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::ReturnStatement>(),
+  });
+  ast::IfStatement i(Source{}, cond, body,
+                     {create<ast::ElseStatement>(else_cond, else_body)});
 
   gen.increment_indent();
 
@@ -76,20 +69,16 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_IfWithElse) {
-  auto* else_body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}),
-                });
+  auto* else_body = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::ReturnStatement>(),
+  });
 
-  auto* cond = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("cond"), "cond");
-  auto* body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}),
-                });
-  ast::IfStatement i(
-      Source{}, cond, body,
-      {create<ast::ElseStatement>(Source{}, nullptr, else_body)});
+  auto* cond = Expr("cond");
+  auto* body = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::ReturnStatement>(),
+  });
+  ast::IfStatement i(Source{}, cond, body,
+                     {create<ast::ElseStatement>(nullptr, else_body)});
 
   gen.increment_indent();
 
@@ -103,31 +92,28 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_IfWithMultiple) {
-  auto* else_cond = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("else_cond"), "else_cond");
+  auto* else_cond = Expr("else_cond");
 
-  auto* else_body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}),
-                });
+  auto* else_body =
+      create<ast::BlockStatement>(Source{}, ast::StatementList{
+                                                create<ast::ReturnStatement>(),
+                                            });
 
-  auto* else_body_2 = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}),
-                });
+  auto* else_body_2 =
+      create<ast::BlockStatement>(Source{}, ast::StatementList{
+                                                create<ast::ReturnStatement>(),
+                                            });
 
-  auto* cond = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("cond"), "cond");
-  auto* body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}),
-                });
-  ast::IfStatement i(
-      Source{}, cond, body,
-      {
-          create<ast::ElseStatement>(Source{}, else_cond, else_body),
-          create<ast::ElseStatement>(Source{}, nullptr, else_body_2),
-      });
+  auto* cond = Expr("cond");
+  auto* body =
+      create<ast::BlockStatement>(Source{}, ast::StatementList{
+                                                create<ast::ReturnStatement>(),
+                                            });
+  ast::IfStatement i(Source{}, cond, body,
+                     {
+                         create<ast::ElseStatement>(else_cond, else_body),
+                         create<ast::ElseStatement>(nullptr, else_body_2),
+                     });
 
   gen.increment_indent();
 
diff --git a/src/writer/msl/generator_impl_import_test.cc b/src/writer/msl/generator_impl_import_test.cc
index c27ee73..ef9132d 100644
--- a/src/writer/msl/generator_impl_import_test.cc
+++ b/src/writer/msl/generator_impl_import_test.cc
@@ -50,23 +50,14 @@
 using MslImportData_SingleParamTest = TestParamHelper<MslImportData>;
 TEST_P(MslImportData_SingleParamTest, FloatScalar) {
   auto param = GetParam();
-
-  ast::type::F32 f32;
-
-  ast::ExpressionList params;
-  params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)));
-
-  auto* ident = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol(param.name), param.name);
-
-  ast::CallExpression call(Source{}, ident, params);
+  auto* call = Call(param.name, 1.f);
 
   // The call type determination will set the intrinsic data for the ident
-  ASSERT_TRUE(td.DetermineResultType(&call)) << td.error();
+  ASSERT_TRUE(td.DetermineResultType(call)) << td.error();
 
-  ASSERT_EQ(gen.generate_builtin_name(ident),
-            std::string("metal::") + param.msl_name);
+  ASSERT_EQ(
+      gen.generate_builtin_name(call->func()->As<ast::IdentifierExpression>()),
+      std::string("metal::") + param.msl_name);
 }
 INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
                          MslImportData_SingleParamTest,
@@ -95,43 +86,20 @@
                                          MslImportData{"trunc", "trunc"}));
 
 TEST_F(MslGeneratorImplTest, MslImportData_SingleParamTest_IntScalar) {
-  ast::type::I32 i32;
+  auto* expr = Call("abs", 1);
+  ASSERT_TRUE(td.DetermineResultType(expr)) << td.error();
 
-  ast::ExpressionList params;
-  params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::SintLiteral>(Source{}, &i32, 1)));
-
-  ast::CallExpression expr(Source{},
-                           create<ast::IdentifierExpression>(
-                               Source{}, mod->RegisterSymbol("abs"), "abs"),
-                           params);
-
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-
-  ASSERT_TRUE(gen.EmitCall(&expr)) << gen.error();
+  ASSERT_TRUE(gen.EmitCall(expr)) << gen.error();
   EXPECT_EQ(gen.result(), R"(metal::abs(1))");
 }
 
 using MslImportData_DualParamTest = TestParamHelper<MslImportData>;
 TEST_P(MslImportData_DualParamTest, FloatScalar) {
   auto param = GetParam();
+  auto* expr = Call(param.name, 1.0f, 2.0f);
 
-  ast::type::F32 f32;
-
-  ast::ExpressionList params;
-  params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)));
-  params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 2.f)));
-
-  ast::CallExpression expr(
-      Source{},
-      create<ast::IdentifierExpression>(
-          Source{}, mod->RegisterSymbol(param.name), param.name),
-      params);
-
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-  ASSERT_TRUE(gen.EmitCall(&expr)) << gen.error();
+  ASSERT_TRUE(td.DetermineResultType(expr)) << td.error();
+  ASSERT_TRUE(gen.EmitCall(expr)) << gen.error();
   EXPECT_EQ(gen.result(),
             std::string("metal::") + param.msl_name + "(1.0f, 2.0f)");
 }
@@ -149,42 +117,10 @@
 TEST_P(MslImportData_DualParam_VectorTest, FloatVector) {
   auto param = GetParam();
 
-  ast::type::F32 f32;
-  ast::type::Vector vec(&f32, 3);
-
-  ast::ExpressionList type_params;
-
-  ast::ExpressionList params;
-  params.push_back(create<ast::TypeConstructorExpression>(
-      Source{}, &vec,
-      ast::ExpressionList{
-          create<ast::ScalarConstructorExpression>(
-              Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)),
-          create<ast::ScalarConstructorExpression>(
-              Source{}, create<ast::FloatLiteral>(Source{}, &f32, 2.f)),
-          create<ast::ScalarConstructorExpression>(
-              Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.f)),
-      }));
-
-  params.push_back(create<ast::TypeConstructorExpression>(
-      Source{}, &vec,
-      ast::ExpressionList{
-          create<ast::ScalarConstructorExpression>(
-              Source{}, create<ast::FloatLiteral>(Source{}, &f32, 4.f)),
-          create<ast::ScalarConstructorExpression>(
-              Source{}, create<ast::FloatLiteral>(Source{}, &f32, 5.f)),
-          create<ast::ScalarConstructorExpression>(
-              Source{}, create<ast::FloatLiteral>(Source{}, &f32, 6.f)),
-      }));
-
-  ast::CallExpression expr(
-      Source{},
-      create<ast::IdentifierExpression>(
-          Source{}, mod->RegisterSymbol(param.name), param.name),
-      params);
-
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-  ASSERT_TRUE(gen.EmitCall(&expr)) << gen.error();
+  auto* expr = Call(param.name, Construct(ty.vec3<f32>(), 1.f, 2.f, 3.f),
+                    Construct(ty.vec3<f32>(), 4.f, 5.f, 6.f));
+  ASSERT_TRUE(td.DetermineResultType(expr)) << td.error();
+  ASSERT_TRUE(gen.EmitCall(expr)) << gen.error();
   EXPECT_EQ(gen.result(), std::string("metal::") + param.msl_name +
                               "(float3(1.0f, 2.0f, 3.0f), "
                               "float3(4.0f, 5.0f, 6.0f))");
@@ -197,22 +133,9 @@
 TEST_P(MslImportData_DualParam_Int_Test, IntScalar) {
   auto param = GetParam();
 
-  ast::type::I32 i32;
-
-  ast::ExpressionList params;
-  params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::SintLiteral>(Source{}, &i32, 1)));
-  params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::SintLiteral>(Source{}, &i32, 2)));
-
-  ast::CallExpression expr(
-      Source{},
-      create<ast::IdentifierExpression>(
-          Source{}, mod->RegisterSymbol(param.name), param.name),
-      params);
-
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-  ASSERT_TRUE(gen.EmitCall(&expr)) << gen.error();
+  auto* expr = Call(param.name, 1, 2);
+  ASSERT_TRUE(td.DetermineResultType(expr)) << td.error();
+  ASSERT_TRUE(gen.EmitCall(expr)) << gen.error();
   EXPECT_EQ(gen.result(), std::string("metal::") + param.msl_name + "(1, 2)");
 }
 INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
@@ -224,24 +147,9 @@
 TEST_P(MslImportData_TripleParamTest, FloatScalar) {
   auto param = GetParam();
 
-  ast::type::F32 f32;
-
-  ast::ExpressionList params;
-  params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)));
-  params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 2.f)));
-  params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.f)));
-
-  ast::CallExpression expr(
-      Source{},
-      create<ast::IdentifierExpression>(
-          Source{}, mod->RegisterSymbol(param.name), param.name),
-      params);
-
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-  ASSERT_TRUE(gen.EmitCall(&expr)) << gen.error();
+  auto* expr = Call(param.name, 1.f, 2.f, 3.f);
+  ASSERT_TRUE(td.DetermineResultType(expr)) << td.error();
+  ASSERT_TRUE(gen.EmitCall(expr)) << gen.error();
   EXPECT_EQ(gen.result(),
             std::string("metal::") + param.msl_name + "(1.0f, 2.0f, 3.0f)");
 }
@@ -258,24 +166,9 @@
 TEST_P(MslImportData_TripleParam_Int_Test, IntScalar) {
   auto param = GetParam();
 
-  ast::type::I32 i32;
-
-  ast::ExpressionList params;
-  params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::SintLiteral>(Source{}, &i32, 1)));
-  params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::SintLiteral>(Source{}, &i32, 2)));
-  params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::SintLiteral>(Source{}, &i32, 3)));
-
-  ast::CallExpression expr(
-      Source{},
-      create<ast::IdentifierExpression>(
-          Source{}, mod->RegisterSymbol(param.name), param.name),
-      params);
-
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-  ASSERT_TRUE(gen.EmitCall(&expr)) << gen.error();
+  auto* expr = Call(param.name, 1, 2, 3);
+  ASSERT_TRUE(td.DetermineResultType(expr)) << td.error();
+  ASSERT_TRUE(gen.EmitCall(expr)) << gen.error();
   EXPECT_EQ(gen.result(),
             std::string("metal::") + param.msl_name + "(1, 2, 3)");
 }
@@ -288,31 +181,15 @@
   ast::type::F32 f32;
   ast::type::Matrix mat(&f32, 3, 3);
 
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "var",                           // name
-                            ast::StorageClass::kFunction,    // storage_class
-                            &mat,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-
-  ast::ExpressionList params;
-  params.push_back(create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("var"), "var"));
-
-  ast::CallExpression expr(
-      Source{},
-      create<ast::IdentifierExpression>(
-          Source{}, mod->RegisterSymbol("determinant"), "determinant"),
-      params);
-
+  auto* var = Var("var", ast::StorageClass::kFunction, &mat);
   mod->AddGlobalVariable(var);
 
+  auto* expr = Call("determinant", "var");
+
   // Register the global
   ASSERT_TRUE(td.Determine()) << td.error();
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-  ASSERT_TRUE(gen.EmitCall(&expr)) << gen.error();
+  ASSERT_TRUE(td.DetermineResultType(expr)) << td.error();
+  ASSERT_TRUE(gen.EmitCall(expr)) << gen.error();
   EXPECT_EQ(gen.result(), std::string("metal::determinant(var)"));
 }
 
diff --git a/src/writer/msl/generator_impl_intrinsic_test.cc b/src/writer/msl/generator_impl_intrinsic_test.cc
index ce36062..af661cb 100644
--- a/src/writer/msl/generator_impl_intrinsic_test.cc
+++ b/src/writer/msl/generator_impl_intrinsic_test.cc
@@ -66,39 +66,10 @@
                     IntrinsicData{ast::Intrinsic::kSelect, "select"}));
 
 TEST_F(MslGeneratorImplTest, DISABLED_Intrinsic_OuterProduct) {
-  ast::type::F32 f32;
-  ast::type::Vector vec2(&f32, 2);
-  ast::type::Vector vec3(&f32, 3);
+  auto* a = Var("a", ast::StorageClass::kNone, ty.vec2<f32>());
+  auto* b = Var("b", ast::StorageClass::kNone, ty.vec3<f32>());
 
-  auto* a =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kNone,        // storage_class
-                            &vec2,                           // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* b =
-      create<ast::Variable>(Source{},                        // source
-                            "b",                             // name
-                            ast::StorageClass::kNone,        // storage_class
-                            &vec3,                           // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-
-  ast::ExpressionList params;
-  params.push_back(create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("a"), "a"));
-  params.push_back(create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("b"), "b"));
-
-  ast::CallExpression call(
-      Source{},
-      create<ast::IdentifierExpression>(
-          Source{}, mod->RegisterSymbol("outer_product"), "outer_product"),
-      params);
-
+  auto* call = Call("outer_product", "a", "b");
   td.RegisterVariableForTesting(a);
   td.RegisterVariableForTesting(b);
 
@@ -106,10 +77,10 @@
   mod->AddGlobalVariable(b);
 
   ASSERT_TRUE(td.Determine()) << td.error();
-  ASSERT_TRUE(td.DetermineResultType(&call)) << td.error();
+  ASSERT_TRUE(td.DetermineResultType(call)) << td.error();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(&call)) << gen.error();
+  ASSERT_TRUE(gen.EmitExpression(call)) << gen.error();
   EXPECT_EQ(gen.result(), "  float3x2(a * b[0], a * b[1], a * b[2])");
 }
 
@@ -118,32 +89,18 @@
 }
 
 TEST_F(MslGeneratorImplTest, Intrinsic_Call) {
-  ast::type::F32 f32;
-  ast::type::Vector vec(&f32, 3);
+  auto* call = Call("dot", "param1", "param2");
 
-  ast::ExpressionList params;
-  params.push_back(create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("param1"), "param1"));
-  params.push_back(create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("param2"), "param2"));
+  auto* v1 = Var("param1", ast::StorageClass::kFunction, ty.vec2<f32>());
+  auto* v2 = Var("param2", ast::StorageClass::kFunction, ty.vec2<f32>());
 
-  ast::CallExpression call(Source{},
-                           create<ast::IdentifierExpression>(
-                               Source{}, mod->RegisterSymbol("dot"), "dot"),
-                           params);
+  td.RegisterVariableForTesting(v1);
+  td.RegisterVariableForTesting(v2);
 
-  ast::Variable v1(Source{}, "param1", ast::StorageClass::kFunction, &vec,
-                   false, nullptr, ast::VariableDecorationList{});
-  ast::Variable v2(Source{}, "param2", ast::StorageClass::kFunction, &vec,
-                   false, nullptr, ast::VariableDecorationList{});
-
-  td.RegisterVariableForTesting(&v1);
-  td.RegisterVariableForTesting(&v2);
-
-  ASSERT_TRUE(td.DetermineResultType(&call)) << td.error();
+  ASSERT_TRUE(td.DetermineResultType(call)) << td.error();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(&call)) << gen.error();
+  ASSERT_TRUE(gen.EmitExpression(call)) << gen.error();
   EXPECT_EQ(gen.result(), "  dot(param1, param2)");
 }
 
diff --git a/src/writer/msl/generator_impl_loop_test.cc b/src/writer/msl/generator_impl_loop_test.cc
index 368c3dd..68e7fe2 100644
--- a/src/writer/msl/generator_impl_loop_test.cc
+++ b/src/writer/msl/generator_impl_loop_test.cc
@@ -36,10 +36,9 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, Emit_Loop) {
-  auto* body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::DiscardStatement>(Source{}),
-                });
+  auto* body = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::DiscardStatement>(),
+  });
   ast::LoopStatement l(Source{}, body, {});
 
   gen.increment_indent();
@@ -52,14 +51,12 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_LoopWithContinuing) {
-  auto* body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::DiscardStatement>(Source{}),
-                });
-  auto* continuing = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}),
-                });
+  auto* body = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::DiscardStatement>(),
+  });
+  auto* continuing = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::ReturnStatement>(),
+  });
   ast::LoopStatement l(Source{}, body, continuing);
 
   gen.increment_indent();
@@ -80,31 +77,21 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_LoopNestedWithContinuing) {
-  ast::type::F32 f32;
+  auto* body = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::DiscardStatement>(),
+  });
+  auto* continuing = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::ReturnStatement>(),
+  });
+  auto* inner = create<ast::LoopStatement>(body, continuing);
 
-  auto* body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::DiscardStatement>(Source{}),
-                });
-  auto* continuing = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}),
-                });
-  auto* inner = create<ast::LoopStatement>(Source{}, body, continuing);
+  body = create<ast::BlockStatement>(ast::StatementList{
+      inner,
+  });
 
-  body = create<ast::BlockStatement>(Source{}, ast::StatementList{
-                                                   inner,
-                                               });
-
-  auto* lhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("lhs"), "lhs");
-  auto* rhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("rhs"), "rhs");
-
-  continuing = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::AssignmentStatement>(Source{}, lhs, rhs),
-                });
+  continuing = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::AssignmentStatement>(Expr("lhs"), Expr("rhs")),
+  });
 
   ast::LoopStatement outer(Source{}, body, continuing);
 
@@ -157,43 +144,17 @@
   //   }
   // }
 
-  ast::type::F32 f32;
-
-  auto* var = create<ast::Variable>(
-      Source{},                      // source
-      "lhs",                         // name
-      ast::StorageClass::kFunction,  // storage_class
-      &f32,                          // type
-      false,                         // is_const
-      create<ast::ScalarConstructorExpression>(
-          Source{},
-          create<ast::FloatLiteral>(Source{}, &f32, 2.4)),  // constructor
-      ast::VariableDecorationList{});                       // decorations
+  auto* var = Var("lhs", ast::StorageClass::kFunction, ty.f32, Expr(2.4f),
+                  ast::VariableDecorationList{});
 
   auto* body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::VariableDeclStatement>(Source{}, var),
-          create<ast::VariableDeclStatement>(
-              Source{}, create<ast::Variable>(
-                            Source{},                         // source
-                            "other",                          // name
-                            ast::StorageClass::kFunction,     // storage_class
-                            &f32,                             // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{})),  // decorations
-      });
+      ast::StatementList{create<ast::VariableDeclStatement>(var),
+                         create<ast::VariableDeclStatement>(Var(
+                             "other", ast::StorageClass::kFunction, ty.f32))});
 
-  auto* lhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("lhs"), "lhs");
-  auto* rhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("rhs"), "rhs");
-
-  auto* continuing = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::AssignmentStatement>(Source{}, lhs, rhs),
-                });
+  auto* continuing = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::AssignmentStatement>(Expr("lhs"), Expr("rhs")),
+  });
   gen.increment_indent();
 
   ast::LoopStatement outer(Source{}, body, continuing);
diff --git a/src/writer/msl/generator_impl_member_accessor_test.cc b/src/writer/msl/generator_impl_member_accessor_test.cc
index e6b3d14..f1b8644 100644
--- a/src/writer/msl/generator_impl_member_accessor_test.cc
+++ b/src/writer/msl/generator_impl_member_accessor_test.cc
@@ -29,14 +29,9 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitExpression_MemberAccessor) {
-  auto* str = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("str"), "str");
-  auto* mem = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("mem"), "mem");
+  auto* expr = MemberAccessor("str", "mem");
 
-  ast::MemberAccessorExpression expr(Source{}, str, mem);
-
-  ASSERT_TRUE(gen.EmitExpression(&expr)) << gen.error();
+  ASSERT_TRUE(gen.EmitExpression(expr)) << gen.error();
   EXPECT_EQ(gen.result(), "str.mem");
 }
 
diff --git a/src/writer/msl/generator_impl_return_test.cc b/src/writer/msl/generator_impl_return_test.cc
index 8b8be3e..8d1f7ad 100644
--- a/src/writer/msl/generator_impl_return_test.cc
+++ b/src/writer/msl/generator_impl_return_test.cc
@@ -31,7 +31,6 @@
 
 TEST_F(MslGeneratorImplTest, Emit_Return) {
   ast::ReturnStatement r(Source{});
-
   gen.increment_indent();
 
   ASSERT_TRUE(gen.EmitStatement(&r)) << gen.error();
@@ -39,10 +38,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_ReturnWithValue) {
-  auto* expr = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("expr"), "expr");
-  ast::ReturnStatement r(Source{}, expr);
-
+  ast::ReturnStatement r(Source{}, Expr("expr"));
   gen.increment_indent();
 
   ASSERT_TRUE(gen.EmitStatement(&r)) << gen.error();
diff --git a/src/writer/msl/generator_impl_switch_test.cc b/src/writer/msl/generator_impl_switch_test.cc
index c5bd20b..60b28b6 100644
--- a/src/writer/msl/generator_impl_switch_test.cc
+++ b/src/writer/msl/generator_impl_switch_test.cc
@@ -33,32 +33,25 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, Emit_Switch) {
-  auto* def_body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::BreakStatement>(Source{}),
-                });
-  auto* def =
-      create<ast::CaseStatement>(Source{}, ast::CaseSelectorList{}, def_body);
+  auto* def_body = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::BreakStatement>(),
+  });
+  auto* def = create<ast::CaseStatement>(ast::CaseSelectorList{}, def_body);
 
-  ast::type::I32 i32;
   ast::CaseSelectorList case_val;
-  case_val.push_back(create<ast::SintLiteral>(Source{}, &i32, 5));
+  case_val.push_back(Literal(5));
 
-  auto* case_body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::BreakStatement>(Source{}),
-                });
+  auto* case_body = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::BreakStatement>(),
+  });
 
-  auto* case_stmt = create<ast::CaseStatement>(Source{}, case_val, case_body);
+  auto* case_stmt = create<ast::CaseStatement>(case_val, case_body);
 
   ast::CaseStatementList body;
   body.push_back(case_stmt);
   body.push_back(def);
 
-  auto* cond = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("cond"), "cond");
-  ast::SwitchStatement s(Source{}, cond, body);
-
+  ast::SwitchStatement s(Source{}, Expr("cond"), body);
   gen.increment_indent();
 
   ASSERT_TRUE(gen.EmitStatement(&s)) << gen.error();
diff --git a/src/writer/msl/generator_impl_test.cc b/src/writer/msl/generator_impl_test.cc
index 585f558..d9a63d3 100644
--- a/src/writer/msl/generator_impl_test.cc
+++ b/src/writer/msl/generator_impl_test.cc
@@ -48,13 +48,11 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, Generate) {
-  ast::type::Void void_type;
-
-  auto* func = Func(
-      "my_func", ast::VariableList{}, &void_type, ast::StatementList{},
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, ast::PipelineStage::kCompute),
-      });
+  auto* func =
+      Func("my_func", ast::VariableList{}, ty.void_, ast::StatementList{},
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kCompute),
+           });
   mod->AddFunction(func);
 
   ASSERT_TRUE(gen.Generate()) << gen.error();
@@ -77,10 +75,7 @@
 
 TEST_F(MslGeneratorImplTest, NameConflictWith_InputStructName) {
   ASSERT_EQ(gen.generate_name("func_main_in"), "func_main_in");
-
-  ast::IdentifierExpression ident(Source{}, mod->RegisterSymbol("func_main_in"),
-                                  "func_main_in");
-  ASSERT_TRUE(gen.EmitIdentifier(&ident));
+  ASSERT_TRUE(gen.EmitIdentifier(Expr("func_main_in")));
   EXPECT_EQ(gen.result(), "func_main_in_0");
 }
 
@@ -116,9 +111,8 @@
                                    "thread_position_in_grid"}));
 
 TEST_F(MslGeneratorImplTest, calculate_alignment_size_alias) {
-  ast::type::F32 f32;
-  ast::type::Alias alias(mod->RegisterSymbol("a"), "a", &f32);
-  EXPECT_EQ(4u, gen.calculate_alignment_size(&alias));
+  auto* alias = ty.alias("a", ty.f32);
+  EXPECT_EQ(4u, gen.calculate_alignment_size(alias));
 }
 
 TEST_F(MslGeneratorImplTest, calculate_alignment_size_array) {
@@ -161,9 +155,8 @@
                             Member("c", ty.f32, {MemberOffset(128)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  EXPECT_EQ(132u, gen.calculate_alignment_size(&s));
+  auto* s = ty.struct_("S", str);
+  EXPECT_EQ(132u, gen.calculate_alignment_size(s));
 }
 
 TEST_F(MslGeneratorImplTest, calculate_alignment_size_struct_of_struct) {
@@ -173,17 +166,16 @@
                             Member("c", ty.f32, {MemberOffset(32)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct inner_s(mod->RegisterSymbol("Inner"), "Inner", inner_str);
+  auto* inner_s = ty.struct_("Inner", inner_str);
 
   auto* outer_str = create<ast::Struct>(
       ast::StructMemberList{Member("d", ty.f32, {MemberOffset(0)}),
-                            Member("e", &inner_s, {MemberOffset(32)}),
+                            Member("e", inner_s, {MemberOffset(32)}),
                             Member("f", ty.f32, {MemberOffset(64)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct outer_s(mod->RegisterSymbol("Outer"), "Outer", outer_str);
-
-  EXPECT_EQ(80u, gen.calculate_alignment_size(&outer_s));
+  auto* outer_s = ty.struct_("Outer", outer_str);
+  EXPECT_EQ(80u, gen.calculate_alignment_size(outer_s));
 }
 
 TEST_F(MslGeneratorImplTest, calculate_alignment_size_u32) {
diff --git a/src/writer/msl/generator_impl_type_test.cc b/src/writer/msl/generator_impl_type_test.cc
index c2dd81c..29a44aa 100644
--- a/src/writer/msl/generator_impl_type_test.cc
+++ b/src/writer/msl/generator_impl_type_test.cc
@@ -47,18 +47,14 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitType_Alias) {
-  ast::type::F32 f32;
-  ast::type::Alias alias(mod->RegisterSymbol("alias"), "alias", &f32);
-
-  ASSERT_TRUE(gen.EmitType(&alias, "")) << gen.error();
+  auto* alias = ty.alias("alias", ty.f32);
+  ASSERT_TRUE(gen.EmitType(alias, "")) << gen.error();
   EXPECT_EQ(gen.result(), "alias");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Alias_NameCollision) {
-  ast::type::F32 f32;
-  ast::type::Alias alias(mod->RegisterSymbol("bool"), "bool", &f32);
-
-  ASSERT_TRUE(gen.EmitType(&alias, "")) << gen.error();
+  auto* alias = ty.alias("bool", ty.f32);
+  ASSERT_TRUE(gen.EmitType(alias, "")) << gen.error();
   EXPECT_EQ(gen.result(), "bool_tint_0");
 }
 
@@ -176,9 +172,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  ASSERT_TRUE(gen.EmitType(&s, "")) << gen.error();
+  auto* s = ty.struct_("S", str);
+  ASSERT_TRUE(gen.EmitType(s, "")) << gen.error();
   EXPECT_EQ(gen.result(), "S");
 }
 
@@ -188,9 +183,9 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
+  auto* s = ty.struct_("S", str);
 
-  ASSERT_TRUE(gen.EmitStructType(&s)) << gen.error();
+  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
   EXPECT_EQ(gen.result(), R"(struct S {
   int a;
   float b;
@@ -207,9 +202,8 @@
       },
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  ASSERT_TRUE(gen.EmitStructType(&s)) << gen.error();
+  auto* s = ty.struct_("S", str);
+  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
   EXPECT_EQ(gen.result(), R"(struct S {
   int8_t pad_0[4];
   int a;
@@ -226,9 +220,8 @@
       ast::StructMemberList{Member("main", ty.i32), Member("float", ty.f32)},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  ASSERT_TRUE(gen.EmitStructType(&s)) << gen.error();
+  auto* s = ty.struct_("S", str);
+  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
   EXPECT_EQ(gen.result(), R"(struct S {
   int main_tint_0;
   float float_tint_0;
@@ -239,15 +232,14 @@
 // TODO(dsinclair): How to translate [[block]]
 TEST_F(MslGeneratorImplTest, DISABLED_EmitType_Struct_WithDecoration) {
   ast::StructDecorationList decos;
-  decos.push_back(create<ast::StructBlockDecoration>(Source{}));
+  decos.push_back(create<ast::StructBlockDecoration>());
   auto* str = create<ast::Struct>(
       ast::StructMemberList{Member("a", ty.i32),
                             Member("b", ty.f32, {MemberOffset(4)})},
       decos);
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  ASSERT_TRUE(gen.EmitType(&s, "")) << gen.error();
+  auto* s = ty.struct_("S", str);
+  ASSERT_TRUE(gen.EmitType(s, "")) << gen.error();
   EXPECT_EQ(gen.result(), R"(struct {
   int a;
   float b;
diff --git a/src/writer/msl/generator_impl_unary_op_test.cc b/src/writer/msl/generator_impl_unary_op_test.cc
index 1df4653..991a156 100644
--- a/src/writer/msl/generator_impl_unary_op_test.cc
+++ b/src/writer/msl/generator_impl_unary_op_test.cc
@@ -38,11 +38,7 @@
 using MslUnaryOpTest = TestParamHelper<UnaryOpData>;
 TEST_P(MslUnaryOpTest, Emit) {
   auto params = GetParam();
-
-  auto* expr = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("expr"), "expr");
-  ast::UnaryOpExpression op(Source{}, params.op, expr);
-
+  ast::UnaryOpExpression op(Source{}, params.op, Expr("expr"));
   ASSERT_TRUE(gen.EmitExpression(&op)) << gen.error();
   EXPECT_EQ(gen.result(), std::string(params.name) + "(expr)");
 }
diff --git a/src/writer/msl/generator_impl_variable_decl_statement_test.cc b/src/writer/msl/generator_impl_variable_decl_statement_test.cc
index 9758a34..ad0d1de 100644
--- a/src/writer/msl/generator_impl_variable_decl_statement_test.cc
+++ b/src/writer/msl/generator_impl_variable_decl_statement_test.cc
@@ -41,16 +41,7 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement) {
-  ast::type::F32 f32;
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kNone,        // storage_class
-                            &f32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-
+  auto* var = Var("a", ast::StorageClass::kNone, ty.f32);
   ast::VariableDeclStatement stmt(Source{}, var);
 
   gen.increment_indent();
@@ -60,16 +51,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const) {
-  ast::type::F32 f32;
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kNone,        // storage_class
-                            &f32,                            // type
-                            true,                            // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-
+  auto* var = Const("a", ast::StorageClass::kNone, ty.f32);
   ast::VariableDeclStatement stmt(Source{}, var);
 
   gen.increment_indent();
@@ -79,18 +61,9 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Array) {
-  ast::type::F32 f32;
-  ast::type::Array ary(&f32, 5, ast::ArrayDecorationList{});
+  ast::type::Array ary(ty.f32, 5, ast::ArrayDecorationList{});
 
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kNone,        // storage_class
-                            &ary,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-
+  auto* var = Var("a", ast::StorageClass::kNone, &ary);
   ast::VariableDeclStatement stmt(Source{}, var);
 
   gen.increment_indent();
@@ -105,17 +78,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kNone,        // storage_class
-                            &s,                              // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-
+  auto* s = ty.struct_("S", str);
+  auto* var = Var("a", ast::StorageClass::kNone, s);
   ast::VariableDeclStatement stmt(Source{}, var);
 
   gen.increment_indent();
@@ -126,18 +90,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Vector) {
-  ast::type::F32 f32;
-  ast::type::Vector vec(&f32, 2);
-
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kFunction,    // storage_class
-                            &vec,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-
+  auto* var = Var("a", ast::StorageClass::kFunction, ty.vec2<f32>());
   ast::VariableDeclStatement stmt(Source{}, var);
 
   gen.increment_indent();
@@ -147,16 +100,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Matrix) {
-  ast::type::F32 f32;
-  ast::type::Matrix mat(&f32, 2, 3);
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kFunction,    // storage_class
-                            &mat,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
+  auto* var = Var("a", ast::StorageClass::kFunction, ty.mat3x2<f32>());
 
   ast::VariableDeclStatement stmt(Source{}, var);
 
@@ -167,16 +111,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Private) {
-  ast::type::F32 f32;
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &f32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-
+  auto* var = Var("a", ast::StorageClass::kPrivate, ty.f32);
   ast::VariableDeclStatement stmt(Source{}, var);
 
   gen.increment_indent();
@@ -186,19 +121,8 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Initializer_Private) {
-  auto* ident = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("initializer"), "initializer");
-
-  ast::type::F32 f32;
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kNone,        // storage_class
-                            &f32,                            // type
-                            false,                           // is_const
-                            ident,                           // constructor
-                            ast::VariableDecorationList{});  // decorations
-
+  auto* var = Var("a", ast::StorageClass::kNone, ty.f32, Expr("initializer"),
+                  ast::VariableDecorationList{});
   ast::VariableDeclStatement stmt(Source{}, var);
 
   ASSERT_TRUE(gen.EmitStatement(&stmt)) << gen.error();
@@ -207,22 +131,12 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Initializer_ZeroVec) {
-  ast::type::F32 f32;
-  ast::type::Vector vec(&f32, 3);
-
   ast::ExpressionList values;
   auto* zero_vec =
-      create<ast::TypeConstructorExpression>(Source{}, &vec, values);
+      create<ast::TypeConstructorExpression>(Source{}, ty.vec3<f32>(), values);
 
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kNone,        // storage_class
-                            &vec,                            // type
-                            false,                           // is_const
-                            zero_vec,                        // constructor
-                            ast::VariableDecorationList{});  // decorations
-
+  auto* var = Var("a", ast::StorageClass::kNone, ty.vec3<f32>(), zero_vec,
+                  ast::VariableDecorationList{});
   ast::VariableDeclStatement stmt(Source{}, var);
 
   ASSERT_TRUE(gen.EmitStatement(&stmt)) << gen.error();
diff --git a/src/writer/spirv/builder_accessor_expression_test.cc b/src/writer/spirv/builder_accessor_expression_test.cc
index e3de740..734cae7 100644
--- a/src/writer/spirv/builder_accessor_expression_test.cc
+++ b/src/writer/spirv/builder_accessor_expression_test.cc
@@ -247,9 +247,8 @@
       ast::StructMemberList{Member("a", ty.f32), Member("b", ty.f32)},
       ast::StructDecorationList{});
 
-  ast::type::Struct s_type(mod->RegisterSymbol("my_struct"), "my_struct", s);
-
-  auto* var = Var("ident", ast::StorageClass::kFunction, &s_type);
+  auto* s_type = ty.struct_("my_struct", s);
+  auto* var = Var("ident", ast::StorageClass::kFunction, s_type);
 
   auto* expr = MemberAccessor("ident", "b");
 
@@ -287,19 +286,17 @@
   //
   // var ident : my_struct
   // ident.inner.a
-  ast::type::Struct inner_struct(
-      mod->RegisterSymbol("Inner"), "Inner",
-      create<ast::Struct>(
-          ast::StructMemberList{Member("a", ty.f32), Member("b", ty.f32)},
-          ast::StructDecorationList{}));
+  auto* inner_struct = ty.struct_(
+      "Inner", create<ast::Struct>(ast::StructMemberList{Member("a", ty.f32),
+                                                         Member("b", ty.f32)},
+                                   ast::StructDecorationList{}));
 
-  ast::type::Struct s_type(
-      mod->RegisterSymbol("my_struct"), "my_struct",
-      create<ast::Struct>(ast::StructMemberList{Member("inner", &inner_struct)},
+  auto* s_type = ty.struct_(
+      "my_struct",
+      create<ast::Struct>(ast::StructMemberList{Member("inner", inner_struct)},
                           ast::StructDecorationList{}));
 
-  auto* var = Var("ident", ast::StorageClass::kFunction, &s_type);
-
+  auto* var = Var("ident", ast::StorageClass::kFunction, s_type);
   auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "a");
 
   td.RegisterVariableForTesting(var);
@@ -338,21 +335,18 @@
   //
   // var ident : my_struct
   // ident.inner.a
-  ast::type::Struct inner_struct(
-      mod->RegisterSymbol("Inner"), "Inner",
-      create<ast::Struct>(
-          ast::StructMemberList{Member("a", ty.f32), Member("b", ty.f32)},
-          ast::StructDecorationList{}));
+  auto* inner_struct = ty.struct_(
+      "Inner", create<ast::Struct>(ast::StructMemberList{Member("a", ty.f32),
+                                                         Member("b", ty.f32)},
+                                   ast::StructDecorationList{}));
 
-  ast::type::Alias alias(mod->RegisterSymbol("Inner"), "Inner", &inner_struct);
-
-  ast::type::Struct s_type(
-      mod->RegisterSymbol("Outer"), "Outer",
-      create<ast::Struct>(ast::StructMemberList{Member("inner", &alias)},
+  auto* alias = ty.alias("Inner", inner_struct);
+  auto* s_type = ty.struct_(
+      "Outer",
+      create<ast::Struct>(ast::StructMemberList{Member("inner", alias)},
                           ast::StructDecorationList{}));
 
-  auto* var = Var("ident", ast::StorageClass::kFunction, &s_type);
-
+  auto* var = Var("ident", ast::StorageClass::kFunction, s_type);
   auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "a");
 
   td.RegisterVariableForTesting(var);
@@ -390,24 +384,19 @@
   //
   // var ident : my_struct
   // ident.inner.a = 2.0f;
-  ast::type::Struct inner_struct(
-      mod->RegisterSymbol("Inner"), "Inner",
-      create<ast::Struct>(
-          ast::StructMemberList{Member("a", ty.f32), Member("b", ty.f32)},
-          ast::StructDecorationList{}));
+  auto* inner_struct = ty.struct_(
+      "Inner", create<ast::Struct>(ast::StructMemberList{Member("a", ty.f32),
+                                                         Member("b", ty.f32)},
+                                   ast::StructDecorationList{}));
 
-  ast::type::Struct s_type(
-      mod->RegisterSymbol("my_struct"), "my_struct",
-      create<ast::Struct>(ast::StructMemberList{Member("inner", &inner_struct)},
+  auto* s_type = ty.struct_(
+      "my_struct",
+      create<ast::Struct>(ast::StructMemberList{Member("inner", inner_struct)},
                           ast::StructDecorationList{}));
 
-  auto* var = Var("ident", ast::StorageClass::kFunction, &s_type);
-
-  auto* lhs = MemberAccessor(MemberAccessor("ident", "inner"), "a");
-
-  auto* rhs = Expr(2.0f);
-
-  auto* expr = create<ast::AssignmentStatement>(lhs, rhs);
+  auto* var = Var("ident", ast::StorageClass::kFunction, s_type);
+  auto* expr = create<ast::AssignmentStatement>(
+      MemberAccessor(MemberAccessor("ident", "inner"), "a"), Expr(2.0f));
 
   td.RegisterVariableForTesting(var);
   ASSERT_TRUE(td.DetermineResultType(expr)) << td.error();
@@ -447,24 +436,21 @@
   // var ident : my_struct
   // var store : f32 = ident.inner.a
 
-  ast::type::Struct inner_struct(
-      mod->RegisterSymbol("Inner"), "Inner",
-      create<ast::Struct>(
-          ast::StructMemberList{Member("a", ty.f32), Member("b", ty.f32)},
-          ast::StructDecorationList{}));
+  auto* inner_struct = ty.struct_(
+      "Inner", create<ast::Struct>(ast::StructMemberList{Member("a", ty.f32),
+                                                         Member("b", ty.f32)},
+                                   ast::StructDecorationList{}));
 
-  ast::type::Struct s_type(
-      mod->RegisterSymbol("my_struct"), "my_struct",
-      create<ast::Struct>(ast::StructMemberList{Member("inner", &inner_struct)},
+  auto* s_type = ty.struct_(
+      "my_struct",
+      create<ast::Struct>(ast::StructMemberList{Member("inner", inner_struct)},
                           ast::StructDecorationList{}));
 
-  auto* var = Var("ident", ast::StorageClass::kFunction, &s_type);
+  auto* var = Var("ident", ast::StorageClass::kFunction, s_type);
   auto* store = Var("store", ast::StorageClass::kFunction, ty.f32);
 
-  auto* lhs = Expr("store");
   auto* rhs = MemberAccessor(MemberAccessor("ident", "inner"), "a");
-
-  auto* expr = create<ast::AssignmentStatement>(lhs, rhs);
+  auto* expr = create<ast::AssignmentStatement>(Expr("store"), rhs);
 
   td.RegisterVariableForTesting(var);
   td.RegisterVariableForTesting(store);
@@ -667,22 +653,18 @@
   auto* s =
       create<ast::Struct>(ast::StructMemberList{Member("baz", ty.vec3<f32>())},
                           ast::StructDecorationList{});
-  ast::type::Struct c_type(mod->RegisterSymbol("C"), "C", s);
+  auto* c_type = ty.struct_("C", s);
 
-  s = create<ast::Struct>(ast::StructMemberList{Member("bar", &c_type)},
+  s = create<ast::Struct>(ast::StructMemberList{Member("bar", c_type)},
                           ast::StructDecorationList{});
-  ast::type::Struct b_type(mod->RegisterSymbol("B"), "B", s);
-
-  ast::type::Array b_ary_type(&b_type, 3, ast::ArrayDecorationList{});
-
+  auto* b_type = ty.struct_("B", s);
+  ast::type::Array b_ary_type(b_type, 3, ast::ArrayDecorationList{});
   s = create<ast::Struct>(ast::StructMemberList{Member("foo", &b_ary_type)},
                           ast::StructDecorationList{});
-  ast::type::Struct a_type(mod->RegisterSymbol("A"), "A", s);
+  auto* a_type = ty.struct_("A", s);
 
-  ast::type::Array a_ary_type(&a_type, 2, ast::ArrayDecorationList{});
-
+  ast::type::Array a_ary_type(a_type, 2, ast::ArrayDecorationList{});
   auto* var = Var("index", ast::StorageClass::kFunction, &a_ary_type);
-
   auto* expr = MemberAccessor(
       MemberAccessor(
           MemberAccessor(
diff --git a/src/writer/spirv/builder_assign_test.cc b/src/writer/spirv/builder_assign_test.cc
index c59eb4b..70bb77e 100644
--- a/src/writer/spirv/builder_assign_test.cc
+++ b/src/writer/spirv/builder_assign_test.cc
@@ -42,24 +42,15 @@
 using BuilderTest = TestHelper;
 
 TEST_F(BuilderTest, Assign_Var) {
-  ast::type::F32 f32;
+  auto* v = Var("var", ast::StorageClass::kOutput, ty.f32);
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &f32, false,
-                  nullptr, ast::VariableDecorationList{});
-
-  auto* ident = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("var"), "var");
-  auto* val = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f));
-
-  ast::AssignmentStatement assign(Source{}, ident, val);
-
-  td.RegisterVariableForTesting(&v);
+  ast::AssignmentStatement assign(Source{}, Expr("var"), Expr(1.f));
+  td.RegisterVariableForTesting(v);
 
   ASSERT_TRUE(td.DetermineResultType(&assign)) << td.error();
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_TRUE(b.GenerateAssignStatement(&assign)) << b.error();
@@ -77,23 +68,14 @@
 }
 
 TEST_F(BuilderTest, Assign_Var_OutsideFunction_IsError) {
-  ast::type::F32 f32;
+  auto* v = Var("var", ast::StorageClass::kOutput, ty.f32);
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &f32, false,
-                  nullptr, ast::VariableDecorationList{});
-
-  auto* ident =
-      create<ast::IdentifierExpression>(mod->RegisterSymbol("var"), "var");
-  auto* val = create<ast::ScalarConstructorExpression>(
-      create<ast::FloatLiteral>(&f32, 1.0f));
-
-  ast::AssignmentStatement assign(Source{}, ident, val);
-
-  td.RegisterVariableForTesting(&v);
+  ast::AssignmentStatement assign(Source{}, Expr("var"), Expr(1.f));
+  td.RegisterVariableForTesting(v);
 
   ASSERT_TRUE(td.DetermineResultType(&assign)) << td.error();
 
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_FALSE(b.GenerateAssignStatement(&assign)) << b.error();
@@ -104,25 +86,18 @@
 }
 
 TEST_F(BuilderTest, Assign_Var_ZeroConstructor) {
-  ast::type::F32 f32;
-  ast::type::Vector vec(&f32, 3);
+  auto* v = Var("var", ast::StorageClass::kOutput, ty.vec3<f32>());
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &vec, false,
-                  nullptr, ast::VariableDecorationList{});
+  auto* val = create<ast::TypeConstructorExpression>(Source{}, ty.vec3<f32>(),
+                                                     ast::ExpressionList{});
+  ast::AssignmentStatement assign(Source{}, Expr("var"), val);
 
-  auto* ident = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("var"), "var");
-  ast::ExpressionList vals;
-  auto* val = create<ast::TypeConstructorExpression>(Source{}, &vec, vals);
-
-  ast::AssignmentStatement assign(Source{}, ident, val);
-
-  td.RegisterVariableForTesting(&v);
+  td.RegisterVariableForTesting(v);
 
   ASSERT_TRUE(td.DetermineResultType(&assign)) << td.error();
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_TRUE(b.GenerateAssignStatement(&assign)) << b.error();
@@ -140,41 +115,20 @@
 }
 
 TEST_F(BuilderTest, Assign_Var_Complex_ConstructorWithExtract) {
-  ast::type::F32 f32;
-  ast::type::Vector vec3(&f32, 3);
-  ast::type::Vector vec2(&f32, 2);
-
   auto* first = create<ast::TypeConstructorExpression>(
-      Source{}, &vec2,
-      ast::ExpressionList{
-          create<ast::ScalarConstructorExpression>(
-              Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)),
-          create<ast::ScalarConstructorExpression>(
-              Source{}, create<ast::FloatLiteral>(Source{}, &f32, 2.0f)),
-      });
+      Source{}, ty.vec2<f32>(), ast::ExpressionList{Expr(1.f), Expr(2.f)});
 
   auto* init = create<ast::TypeConstructorExpression>(
-      Source{}, &vec3,
-      ast::ExpressionList{
-          first,
-          create<ast::ScalarConstructorExpression>(
-              Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.0f)),
-      });
+      Source{}, ty.vec3<f32>(), ast::ExpressionList{first, Expr(3.f)});
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &vec3, false,
-                  nullptr, ast::VariableDecorationList{});
+  auto* v = Var("var", ast::StorageClass::kOutput, ty.vec3<f32>());
+  ast::AssignmentStatement assign(Source{}, Expr("var"), init);
 
-  ast::AssignmentStatement assign(
-      Source{},
-      create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol("var"),
-                                        "var"),
-      init);
-
-  td.RegisterVariableForTesting(&v);
+  td.RegisterVariableForTesting(v);
   ASSERT_TRUE(td.DetermineResultType(&assign)) << td.error();
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_TRUE(b.GenerateAssignStatement(&assign)) << b.error();
@@ -200,33 +154,18 @@
 }
 
 TEST_F(BuilderTest, Assign_Var_Complex_Constructor) {
-  ast::type::F32 f32;
-  ast::type::Vector vec3(&f32, 3);
+  auto* init = create<ast::TypeConstructorExpression>(
+      Source{}, ty.vec3<f32>(),
+      ast::ExpressionList{Expr(1.f), Expr(2.f), Expr(3.f)});
 
-  ast::ExpressionList vals;
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 2.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.0f)));
+  auto* v = Var("var", ast::StorageClass::kOutput, ty.vec3<f32>());
+  ast::AssignmentStatement assign(Source{}, Expr("var"), init);
 
-  auto* init = create<ast::TypeConstructorExpression>(Source{}, &vec3, vals);
-
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &vec3, false,
-                  nullptr, ast::VariableDecorationList{});
-
-  ast::AssignmentStatement assign(
-      Source{},
-      create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol("var"),
-                                        "var"),
-      init);
-
-  td.RegisterVariableForTesting(&v);
+  td.RegisterVariableForTesting(v);
   ASSERT_TRUE(td.DetermineResultType(&assign)) << td.error();
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_TRUE(b.GenerateAssignStatement(&assign)) << b.error();
@@ -258,29 +197,17 @@
       ast::StructMemberList{Member("a", ty.f32), Member("b", ty.f32)},
       ast::StructDecorationList{});
 
-  ast::type::Struct s_type(mod->RegisterSymbol("my_struct"), "my_struct", s);
+  auto* s_type = ty.struct_("my_struct", s);
+  auto* v = Var("ident", ast::StorageClass::kFunction, s_type);
 
-  ast::Variable v(Source{}, "ident", ast::StorageClass::kFunction, &s_type,
-                  false, nullptr, ast::VariableDecorationList{});
-
-  auto* ident = create<ast::MemberAccessorExpression>(
-      Source{},
-      create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol("ident"),
-                                        "ident"),
-      create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol("b"),
-                                        "b"));
-
-  auto* val = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, ty.f32, 4.0f));
-
-  ast::AssignmentStatement assign(Source{}, ident, val);
-
-  td.RegisterVariableForTesting(&v);
+  ast::AssignmentStatement assign(Source{}, MemberAccessor("ident", "b"),
+                                  Expr(4.f));
+  td.RegisterVariableForTesting(v);
 
   ASSERT_TRUE(td.DetermineResultType(&assign)) << td.error();
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_TRUE(b.GenerateAssignStatement(&assign)) << b.error();
@@ -303,33 +230,19 @@
 }
 
 TEST_F(BuilderTest, Assign_Vector) {
-  ast::type::F32 f32;
-  ast::type::Vector vec3(&f32, 3);
+  auto* v = Var("var", ast::StorageClass::kOutput, ty.vec3<f32>());
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &vec3, false,
-                  nullptr, ast::VariableDecorationList{});
+  auto* val = create<ast::TypeConstructorExpression>(
+      Source{}, ty.vec3<f32>(),
+      ast::ExpressionList{Expr(1.f), Expr(1.f), Expr(3.f)});
 
-  auto* ident = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("var"), "var");
-
-  ast::ExpressionList vals;
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.0f)));
-
-  auto* val = create<ast::TypeConstructorExpression>(Source{}, &vec3, vals);
-
-  ast::AssignmentStatement assign(Source{}, ident, val);
-
-  td.RegisterVariableForTesting(&v);
+  ast::AssignmentStatement assign(Source{}, Expr("var"), val);
+  td.RegisterVariableForTesting(v);
 
   ASSERT_TRUE(td.DetermineResultType(&assign)) << td.error();
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_TRUE(b.GenerateAssignStatement(&assign)) << b.error();
@@ -350,31 +263,18 @@
 }
 
 TEST_F(BuilderTest, Assign_Vector_MemberByName) {
-  ast::type::F32 f32;
-  ast::type::Vector vec3(&f32, 3);
-
   // var.y = 1
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &vec3, false,
-                  nullptr, ast::VariableDecorationList{});
+  auto* v = Var("var", ast::StorageClass::kOutput, ty.vec3<f32>());
 
-  auto* ident = create<ast::MemberAccessorExpression>(
-      Source{},
-      create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol("var"),
-                                        "var"),
-      create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol("y"),
-                                        "y"));
-  auto* val = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f));
-
-  ast::AssignmentStatement assign(Source{}, ident, val);
-
-  td.RegisterVariableForTesting(&v);
+  ast::AssignmentStatement assign(Source{}, MemberAccessor("var", "y"),
+                                  Expr(1.f));
+  td.RegisterVariableForTesting(v);
 
   ASSERT_TRUE(td.DetermineResultType(&assign)) << td.error();
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_TRUE(b.GenerateAssignStatement(&assign)) << b.error();
@@ -398,32 +298,17 @@
 }
 
 TEST_F(BuilderTest, Assign_Vector_MemberByIndex) {
-  ast::type::I32 i32;
-  ast::type::F32 f32;
-  ast::type::Vector vec3(&f32, 3);
-
   // var[1] = 1
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &vec3, false,
-                  nullptr, ast::VariableDecorationList{});
+  auto* v = Var("var", ast::StorageClass::kOutput, ty.vec3<f32>());
 
-  auto* ident = create<ast::ArrayAccessorExpression>(
-      Source{},
-      create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol("var"),
-                                        "var"),
-      create<ast::ScalarConstructorExpression>(
-          Source{}, create<ast::SintLiteral>(Source{}, &i32, 1)));
-  auto* val = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f));
-
-  ast::AssignmentStatement assign(Source{}, ident, val);
-
-  td.RegisterVariableForTesting(&v);
+  ast::AssignmentStatement assign(Source{}, IndexAccessor("var", 1), Expr(1.f));
+  td.RegisterVariableForTesting(v);
 
   ASSERT_TRUE(td.DetermineResultType(&assign)) << td.error();
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_TRUE(b.GenerateAssignStatement(&assign)) << b.error();
diff --git a/src/writer/spirv/builder_binary_expression_test.cc b/src/writer/spirv/builder_binary_expression_test.cc
index cc52ae7..a6467a6 100644
--- a/src/writer/spirv/builder_binary_expression_test.cc
+++ b/src/writer/spirv/builder_binary_expression_test.cc
@@ -121,23 +121,14 @@
 TEST_P(BinaryArithSignedIntegerTest, Scalar_Loads) {
   auto param = GetParam();
 
-  ast::type::I32 i32;
+  auto* var = Var("param", ast::StorageClass::kFunction, ty.i32);
+  ast::BinaryExpression expr(Source{}, param.op, Expr("param"), Expr("param"));
 
-  ast::Variable var(Source{}, "param", ast::StorageClass::kFunction, &i32,
-                    false, nullptr, ast::VariableDecorationList{});
-
-  auto* lhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("param"), "param");
-  auto* rhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("param"), "param");
-
-  ast::BinaryExpression expr(Source{}, param.op, lhs, rhs);
-
-  td.RegisterVariableForTesting(&var);
+  td.RegisterVariableForTesting(var);
   EXPECT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(&var)) << b.error();
+  EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
   EXPECT_EQ(b.GenerateBinaryExpression(&expr), 7u) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
@@ -646,26 +637,11 @@
 }
 
 TEST_F(BuilderTest, Binary_Multiply_MatrixScalar) {
-  ast::type::F32 f32;
-  ast::type::Matrix mat3(&f32, 3, 3);
-
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "mat",                           // name
-                            ast::StorageClass::kFunction,    // storage_class
-                            &mat3,                           // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* lhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("mat"), "mat");
-  auto* rhs = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f));
-
+  auto* var = Var("mat", ast::StorageClass::kFunction, ty.mat3x3<f32>());
   td.RegisterVariableForTesting(var);
 
-  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kMultiply, lhs, rhs);
-
+  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kMultiply, Expr("mat"),
+                             Expr(1.f));
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
   b.push_function(Function{});
@@ -686,25 +662,11 @@
 }
 
 TEST_F(BuilderTest, Binary_Multiply_ScalarMatrix) {
-  ast::type::F32 f32;
-  ast::type::Matrix mat3(&f32, 3, 3);
-
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "mat",                           // name
-                            ast::StorageClass::kFunction,    // storage_class
-                            &mat3,                           // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* lhs = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f));
-  auto* rhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("mat"), "mat");
-
+  auto* var = Var("mat", ast::StorageClass::kFunction, ty.mat3x3<f32>());
   td.RegisterVariableForTesting(var);
 
-  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kMultiply, lhs, rhs);
+  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kMultiply, Expr(1.f),
+                             Expr("mat"));
 
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
@@ -726,33 +688,15 @@
 }
 
 TEST_F(BuilderTest, Binary_Multiply_MatrixVector) {
-  ast::type::F32 f32;
-  ast::type::Vector vec3(&f32, 3);
-  ast::type::Matrix mat3(&f32, 3, 3);
-
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "mat",                           // name
-                            ast::StorageClass::kFunction,    // storage_class
-                            &mat3,                           // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* lhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("mat"), "mat");
-
-  ast::ExpressionList vals;
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)));
-  auto* rhs = create<ast::TypeConstructorExpression>(Source{}, &vec3, vals);
+  auto* var = Var("mat", ast::StorageClass::kFunction, ty.mat3x3<f32>());
+  auto* rhs = create<ast::TypeConstructorExpression>(
+      Source{}, ty.vec3<f32>(),
+      ast::ExpressionList{Expr(1.f), Expr(1.f), Expr(1.f)});
 
   td.RegisterVariableForTesting(var);
 
-  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kMultiply, lhs, rhs);
+  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kMultiply, Expr("mat"),
+                             rhs);
 
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
@@ -775,34 +719,15 @@
 }
 
 TEST_F(BuilderTest, Binary_Multiply_VectorMatrix) {
-  ast::type::F32 f32;
-  ast::type::Vector vec3(&f32, 3);
-  ast::type::Matrix mat3(&f32, 3, 3);
-
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "mat",                           // name
-                            ast::StorageClass::kFunction,    // storage_class
-                            &mat3,                           // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-
-  ast::ExpressionList vals;
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)));
-  auto* lhs = create<ast::TypeConstructorExpression>(Source{}, &vec3, vals);
-
-  auto* rhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("mat"), "mat");
+  auto* var = Var("mat", ast::StorageClass::kFunction, ty.mat3x3<f32>());
+  auto* lhs = create<ast::TypeConstructorExpression>(
+      Source{}, ty.vec3<f32>(),
+      ast::ExpressionList{Expr(1.f), Expr(1.f), Expr(1.f)});
 
   td.RegisterVariableForTesting(var);
 
-  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kMultiply, lhs, rhs);
+  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kMultiply, lhs,
+                             Expr("mat"));
 
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
@@ -825,26 +750,11 @@
 }
 
 TEST_F(BuilderTest, Binary_Multiply_MatrixMatrix) {
-  ast::type::F32 f32;
-  ast::type::Vector vec3(&f32, 3);
-  ast::type::Matrix mat3(&f32, 3, 3);
-
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "mat",                           // name
-                            ast::StorageClass::kFunction,    // storage_class
-                            &mat3,                           // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* lhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("mat"), "mat");
-  auto* rhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("mat"), "mat");
-
+  auto* var = Var("mat", ast::StorageClass::kFunction, ty.mat3x3<f32>());
   td.RegisterVariableForTesting(var);
 
-  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kMultiply, lhs, rhs);
+  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kMultiply, Expr("mat"),
+                             Expr("mat"));
 
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
@@ -913,36 +823,16 @@
 TEST_F(BuilderTest, Binary_LogicalAnd_WithLoads) {
   ast::type::Bool bool_type;
 
-  auto* a_var = create<ast::Variable>(
-      Source{},                      // source
-      "a",                           // name
-      ast::StorageClass::kFunction,  // storage_class
-      &bool_type,                    // type
-      false,                         // is_const
-      create<ast::ScalarConstructorExpression>(
-          Source{},
-          create<ast::BoolLiteral>(Source{}, &bool_type, true)),  // constructor
-      ast::VariableDecorationList{});                             // decorations
-  auto* b_var = create<ast::Variable>(
-      Source{},                      // source
-      "b",                           // name
-      ast::StorageClass::kFunction,  // storage_class
-      &bool_type,                    // type
-      false,                         // is_const
-      create<ast::ScalarConstructorExpression>(
-          Source{}, create<ast::BoolLiteral>(Source{}, &bool_type,
-                                             false)),  // constructor
-      ast::VariableDecorationList{});                  // decorations
-
-  auto* lhs = create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("a"), "a");
-  auto* rhs = create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("b"), "b");
+  auto* a_var = Var("a", ast::StorageClass::kFunction, ty.bool_, Expr(true),
+                    ast::VariableDecorationList{});
+  auto* b_var = Var("b", ast::StorageClass::kFunction, ty.bool_, Expr(false),
+                    ast::VariableDecorationList{});
 
   td.RegisterVariableForTesting(a_var);
   td.RegisterVariableForTesting(b_var);
 
-  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kLogicalAnd, lhs, rhs);
+  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kLogicalAnd, Expr("a"),
+                             Expr("b"));
 
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
@@ -1115,36 +1005,16 @@
 TEST_F(BuilderTest, Binary_LogicalOr_WithLoads) {
   ast::type::Bool bool_type;
 
-  auto* a_var = create<ast::Variable>(
-      Source{},                      // source
-      "a",                           // name
-      ast::StorageClass::kFunction,  // storage_class
-      &bool_type,                    // type
-      false,                         // is_const
-      create<ast::ScalarConstructorExpression>(
-          Source{},
-          create<ast::BoolLiteral>(Source{}, &bool_type, true)),  // constructor
-      ast::VariableDecorationList{});                             // decorations
-  auto* b_var = create<ast::Variable>(
-      Source{},                      // source
-      "b",                           // name
-      ast::StorageClass::kFunction,  // storage_class
-      &bool_type,                    // type
-      false,                         // is_const
-      create<ast::ScalarConstructorExpression>(
-          Source{}, create<ast::BoolLiteral>(Source{}, &bool_type,
-                                             false)),  // constructor
-      ast::VariableDecorationList{});                  // decorations
-
-  auto* lhs = create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("a"), "a");
-  auto* rhs = create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("b"), "b");
+  auto* a_var = Var("a", ast::StorageClass::kFunction, ty.bool_, Expr(true),
+                    ast::VariableDecorationList{});
+  auto* b_var = Var("b", ast::StorageClass::kFunction, ty.bool_, Expr(false),
+                    ast::VariableDecorationList{});
 
   td.RegisterVariableForTesting(a_var);
   td.RegisterVariableForTesting(b_var);
 
-  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kLogicalOr, lhs, rhs);
+  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kLogicalOr, Expr("a"),
+                             Expr("b"));
 
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
diff --git a/src/writer/spirv/builder_block_test.cc b/src/writer/spirv/builder_block_test.cc
index 3f8e8ae..e3dc0a1 100644
--- a/src/writer/spirv/builder_block_test.cc
+++ b/src/writer/spirv/builder_block_test.cc
@@ -43,47 +43,16 @@
       Source{},
       ast::StatementList{
           create<ast::VariableDeclStatement>(
-              Source{}, create<ast::Variable>(
-                            Source{},                      // source
-                            "var",                         // name
-                            ast::StorageClass::kFunction,  // storage_class
-                            &f32,                          // type
-                            false,                         // is_const
-                            nullptr,                       // constructor
-                            ast::VariableDecorationList{})),
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol("var"), "var"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::FloatLiteral>(Source{}, &f32, 2.0f))),
-      });  // decorations
+              Source{}, Var("var", ast::StorageClass::kFunction, ty.f32)),
+          create<ast::AssignmentStatement>(Source{}, Expr("var"), Expr(2.f))});
   ast::BlockStatement outer(
       Source{},
       ast::StatementList{
           create<ast::VariableDeclStatement>(
-              Source{}, create<ast::Variable>(
-                            Source{},                         // source
-                            "var",                            // name
-                            ast::StorageClass::kFunction,     // storage_class
-                            &f32,                             // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{})),  // decorations
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol("var"), "var"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f))),
+              Source{}, Var("var", ast::StorageClass::kFunction, ty.f32)),
+          create<ast::AssignmentStatement>(Source{}, Expr("var"), Expr(1.f)),
           inner,
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(
-                  Source{}, mod->RegisterSymbol("var"), "var"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.0f))),
-      });
+          create<ast::AssignmentStatement>(Source{}, Expr("var"), Expr(3.f))});
 
   ASSERT_TRUE(td.DetermineResultType(&outer)) << td.error();
 
diff --git a/src/writer/spirv/builder_call_test.cc b/src/writer/spirv/builder_call_test.cc
index 76f4fd6..23de1ae 100644
--- a/src/writer/spirv/builder_call_test.cc
+++ b/src/writer/spirv/builder_call_test.cc
@@ -44,62 +44,26 @@
   ast::type::Void void_type;
 
   ast::VariableList func_params;
-  func_params.push_back(
-      create<ast::Variable>(Source{},                         // source
-                            "a",                              // name
-                            ast::StorageClass::kFunction,     // storage_class
-                            &f32,                             // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{}));  // decorations
-  func_params.push_back(
-      create<ast::Variable>(Source{},                         // source
-                            "b",                              // name
-                            ast::StorageClass::kFunction,     // storage_class
-                            &f32,                             // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{}));  // decorations
+  func_params.push_back(Var("a", ast::StorageClass::kFunction, ty.f32));
+  func_params.push_back(Var("b", ast::StorageClass::kFunction, ty.f32));
 
-  auto* body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::ReturnStatement>(
-              Source{}, create<ast::BinaryExpression>(
-                            Source{}, ast::BinaryOp::kAdd,
-                            create<ast::IdentifierExpression>(
-                                Source{}, mod->RegisterSymbol("a"), "a"),
-                            create<ast::IdentifierExpression>(
-                                Source{}, mod->RegisterSymbol("b"), "b"))),
-      });
-  ast::Function a_func(Source{}, mod->RegisterSymbol("a_func"), "a_func",
-                       func_params, &f32, body, ast::FunctionDecorationList{});
-
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("main"), "main", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
+  auto* a_func = Func(
+      "a_func", func_params, ty.f32,
+      ast::StatementList{create<ast::ReturnStatement>(Source{}, Add("a", "b"))},
       ast::FunctionDecorationList{});
 
-  ast::ExpressionList call_params;
-  call_params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)));
-  call_params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)));
+  auto* func = Func("main", {}, ty.void_, ast::StatementList{},
+                    ast::FunctionDecorationList{});
 
-  ast::CallExpression expr(
-      Source{},
-      create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol("a_func"),
-                                        "a_func"),
-      call_params);
+  auto* expr = Call("a_func", 1.f, 1.f);
+  ASSERT_TRUE(td.DetermineFunction(func)) << td.error();
+  ASSERT_TRUE(td.DetermineFunction(a_func)) << td.error();
+  ASSERT_TRUE(td.DetermineResultType(expr)) << td.error();
 
-  ASSERT_TRUE(td.DetermineFunction(&func)) << td.error();
-  ASSERT_TRUE(td.DetermineFunction(&a_func)) << td.error();
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
+  ASSERT_TRUE(b.GenerateFunction(a_func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
 
-  ASSERT_TRUE(b.GenerateFunction(&a_func)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(&func)) << b.error();
-
-  EXPECT_EQ(b.GenerateCallExpression(&expr), 14u) << b.error();
+  EXPECT_EQ(b.GenerateCallExpression(expr), 14u) << b.error();
   EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
 OpName %4 "a"
 OpName %5 "b"
@@ -127,66 +91,26 @@
 }
 
 TEST_F(BuilderTest, Statement_Call) {
-  ast::type::F32 f32;
-  ast::type::Void void_type;
-
   ast::VariableList func_params;
-  func_params.push_back(
-      create<ast::Variable>(Source{},                         // source
-                            "a",                              // name
-                            ast::StorageClass::kFunction,     // storage_class
-                            &f32,                             // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{}));  // decorations
-  func_params.push_back(
-      create<ast::Variable>(Source{},                         // source
-                            "b",                              // name
-                            ast::StorageClass::kFunction,     // storage_class
-                            &f32,                             // type
-                            false,                            // is_const
-                            nullptr,                          // constructor
-                            ast::VariableDecorationList{}));  // decorations
+  func_params.push_back(Var("a", ast::StorageClass::kFunction, ty.f32));
+  func_params.push_back(Var("b", ast::StorageClass::kFunction, ty.f32));
 
-  auto* body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::ReturnStatement>(
-              Source{}, create<ast::BinaryExpression>(
-                            Source{}, ast::BinaryOp::kAdd,
-                            create<ast::IdentifierExpression>(
-                                Source{}, mod->RegisterSymbol("a"), "a"),
-                            create<ast::IdentifierExpression>(
-                                Source{}, mod->RegisterSymbol("b"), "b"))),
-      });
-  ast::Function a_func(Source{}, mod->RegisterSymbol("a_func"), "a_func",
-                       func_params, &void_type, body,
-                       ast::FunctionDecorationList{});
-
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("main"), "main", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
+  auto* a_func = Func(
+      "a_func", func_params, ty.void_,
+      ast::StatementList{create<ast::ReturnStatement>(Source{}, Add("a", "b"))},
       ast::FunctionDecorationList{});
 
-  ast::ExpressionList call_params;
-  call_params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)));
-  call_params.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.f)));
+  auto* func = Func("main", {}, ty.void_, ast::StatementList{},
+                    ast::FunctionDecorationList{});
 
-  ast::CallStatement expr(
-      Source{}, create<ast::CallExpression>(
-                    Source{},
-                    create<ast::IdentifierExpression>(
-                        Source{}, mod->RegisterSymbol("a_func"), "a_func"),
-                    call_params));
+  ast::CallStatement expr(Source{}, Call("a_func", 1.f, 1.f));
 
-  ASSERT_TRUE(td.DetermineFunction(&func)) << td.error();
-  ASSERT_TRUE(td.DetermineFunction(&a_func)) << td.error();
+  ASSERT_TRUE(td.DetermineFunction(func)) << td.error();
+  ASSERT_TRUE(td.DetermineFunction(a_func)) << td.error();
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
-  ASSERT_TRUE(b.GenerateFunction(&a_func)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(&func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(a_func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
 
   EXPECT_TRUE(b.GenerateStatement(&expr)) << b.error();
   EXPECT_EQ(DumpBuilder(b), R"(OpName %4 "a_func"
diff --git a/src/writer/spirv/builder_constructor_expression_test.cc b/src/writer/spirv/builder_constructor_expression_test.cc
index da71c56..b544ea4 100644
--- a/src/writer/spirv/builder_constructor_expression_test.cc
+++ b/src/writer/spirv/builder_constructor_expression_test.cc
@@ -114,9 +114,8 @@
   // type Int = i32
   // cast<Int>(2.3f)
 
-  ast::type::Alias alias(mod->RegisterSymbol("Int"), "Int", ty.i32);
-
-  ast::TypeConstructorExpression cast(Source{}, &alias, ExprList(2.3f));
+  auto* alias = ty.alias("Int", ty.i32);
+  ast::TypeConstructorExpression cast(Source{}, alias, ExprList(2.3f));
 
   ASSERT_TRUE(td.DetermineResultType(&cast)) << td.error();
 
@@ -959,10 +958,9 @@
           Member("b", ty.vec3<f32>()),
       },
       ast::StructDecorationList{});
-  ast::type::Struct s_type(mod->RegisterSymbol("my_struct"), "my_struct", s);
+  auto* s_type = ty.struct_("my_struct", s);
 
-  auto* t = Construct(&s_type, 2.0f, vec3<f32>(2.0f, 2.0f, 2.0f));
-
+  auto* t = Construct(s_type, 2.0f, vec3<f32>(2.0f, 2.0f, 2.0f));
   EXPECT_TRUE(td.DetermineResultType(t)) << td.error();
 
   b.push_function(Function{});
@@ -1096,10 +1094,8 @@
           Member("a", ty.f32),
       },
       ast::StructDecorationList{});
-  ast::type::Struct s_type(mod->RegisterSymbol("my_struct"), "my_struct", s);
-
-  auto* t = Construct(&s_type);
-
+  auto* s_type = ty.struct_("my_struct", s);
+  auto* t = Construct(s_type);
   EXPECT_TRUE(td.DetermineResultType(t)) << td.error();
 
   b.push_function(Function{});
@@ -1510,10 +1506,8 @@
           Member("b", ty.vec3<f32>()),
       },
       ast::StructDecorationList{});
-  ast::type::Struct s_type(mod->RegisterSymbol("my_struct"), "my_struct", s);
-
-  auto* t = Construct(&s_type, 2.f, vec3<f32>(2.f, 2.f, 2.f));
-
+  auto* s_type = ty.struct_("my_struct", s);
+  auto* t = Construct(s_type, 2.f, vec3<f32>(2.f, 2.f, 2.f));
   ASSERT_TRUE(td.DetermineResultType(t)) << td.error();
 
   EXPECT_TRUE(b.is_constructor_const(t, false));
@@ -1529,9 +1523,8 @@
       },
       ast::StructDecorationList{});
 
-  ast::type::Struct s_type(mod->RegisterSymbol("my_struct"), "my_struct", s);
-
-  auto* t = Construct(&s_type, 2.f, "a", 2.f);
+  auto* s_type = ty.struct_("my_struct", s);
+  auto* t = Construct(s_type, 2.f, "a", 2.f);
 
   Var("a", ast::StorageClass::kPrivate, ty.f32);
   Var("b", ast::StorageClass::kPrivate, ty.f32);
diff --git a/src/writer/spirv/builder_function_decoration_test.cc b/src/writer/spirv/builder_function_decoration_test.cc
index 33fd143..00b64a5 100644
--- a/src/writer/spirv/builder_function_decoration_test.cc
+++ b/src/writer/spirv/builder_function_decoration_test.cc
@@ -39,16 +39,13 @@
 using BuilderTest = TestHelper;
 
 TEST_F(BuilderTest, FunctionDecoration_Stage) {
-  ast::type::Void void_type;
-
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("main"), "main", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
+  auto* func = Func(
+      "main", {}, ty.void_, ast::StatementList{},
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
       });
 
-  ASSERT_TRUE(b.GenerateFunction(&func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
   EXPECT_EQ(DumpInstructions(b.entry_points()),
             R"(OpEntryPoint Vertex %3 "main"
 )");
@@ -66,16 +63,12 @@
 TEST_P(FunctionDecoration_StageTest, Emit) {
   auto params = GetParam();
 
-  ast::type::Void void_type;
+  auto* func = Func("main", {}, ty.void_, ast::StatementList{},
+                    ast::FunctionDecorationList{
+                        create<ast::StageDecoration>(Source{}, params.stage),
+                    });
 
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("main"), "main", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
-      ast::FunctionDecorationList{
-          create<ast::StageDecoration>(Source{}, params.stage),
-      });
-
-  ASSERT_TRUE(b.GenerateFunction(&func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
 
   auto preamble = b.entry_points();
   ASSERT_GE(preamble.size(), 1u);
@@ -98,37 +91,15 @@
   ast::type::F32 f32;
   ast::type::Void void_type;
 
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("main"), "main", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
+  auto* func = Func(
+      "main", {}, ty.void_, ast::StatementList{},
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
       });
 
-  auto* v_in =
-      create<ast::Variable>(Source{},                        // source
-                            "my_in",                         // name
-                            ast::StorageClass::kInput,       // storage_class
-                            &f32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* v_out =
-      create<ast::Variable>(Source{},                        // source
-                            "my_out",                        // name
-                            ast::StorageClass::kOutput,      // storage_class
-                            &f32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* v_wg =
-      create<ast::Variable>(Source{},                        // source
-                            "my_wg",                         // name
-                            ast::StorageClass::kWorkgroup,   // storage_class
-                            &f32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
+  auto* v_in = Var("my_in", ast::StorageClass::kInput, ty.f32);
+  auto* v_out = Var("my_out", ast::StorageClass::kOutput, ty.f32);
+  auto* v_wg = Var("my_wg", ast::StorageClass::kWorkgroup, ty.f32);
 
   EXPECT_TRUE(b.GenerateGlobalVariable(v_in)) << b.error();
   EXPECT_TRUE(b.GenerateGlobalVariable(v_out)) << b.error();
@@ -138,7 +109,7 @@
   mod->AddGlobalVariable(v_out);
   mod->AddGlobalVariable(v_wg);
 
-  ASSERT_TRUE(b.GenerateFunction(&func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "my_in"
 OpName %4 "my_out"
 OpName %7 "my_wg"
@@ -161,69 +132,29 @@
 }
 
 TEST_F(BuilderTest, FunctionDecoration_Stage_WithUsedInterfaceIds) {
-  ast::type::F32 f32;
-  ast::type::Void void_type;
-
-  auto* body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::AssignmentStatement>(
-                        Source{},
-                        create<ast::IdentifierExpression>(
-                            Source{}, mod->RegisterSymbol("my_out"), "my_out"),
-                        create<ast::IdentifierExpression>(
-                            Source{}, mod->RegisterSymbol("my_in"), "my_in")),
-                    create<ast::AssignmentStatement>(
-                        Source{},
-                        create<ast::IdentifierExpression>(
-                            Source{}, mod->RegisterSymbol("my_wg"), "my_wg"),
-                        create<ast::IdentifierExpression>(
-                            Source{}, mod->RegisterSymbol("my_wg"), "my_wg")),
-                    // Add duplicate usages so we show they don't get output
-                    // multiple times.
-                    create<ast::AssignmentStatement>(
-                        Source{},
-                        create<ast::IdentifierExpression>(
-                            Source{}, mod->RegisterSymbol("my_out"), "my_out"),
-                        create<ast::IdentifierExpression>(
-                            Source{}, mod->RegisterSymbol("my_in"), "my_in")),
-                });
-
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("main"), "main", {}, &void_type, body,
+  auto* func = Func(
+      "main", {}, ty.void_,
+      ast::StatementList{create<ast::AssignmentStatement>(
+                             Source{}, Expr("my_out"), Expr("my_in")),
+                         create<ast::AssignmentStatement>(
+                             Source{}, Expr("my_wg"), Expr("my_wg")),
+                         // Add duplicate usages so we show they don't get
+                         // output multiple times.
+                         create<ast::AssignmentStatement>(
+                             Source{}, Expr("my_out"), Expr("my_in"))},
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kVertex),
       });
 
-  auto* v_in =
-      create<ast::Variable>(Source{},                        // source
-                            "my_in",                         // name
-                            ast::StorageClass::kInput,       // storage_class
-                            &f32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* v_out =
-      create<ast::Variable>(Source{},                        // source
-                            "my_out",                        // name
-                            ast::StorageClass::kOutput,      // storage_class
-                            &f32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* v_wg =
-      create<ast::Variable>(Source{},                        // source
-                            "my_wg",                         // name
-                            ast::StorageClass::kWorkgroup,   // storage_class
-                            &f32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
+  auto* v_in = Var("my_in", ast::StorageClass::kInput, ty.f32);
+  auto* v_out = Var("my_out", ast::StorageClass::kOutput, ty.f32);
+  auto* v_wg = Var("my_wg", ast::StorageClass::kWorkgroup, ty.f32);
 
   td.RegisterVariableForTesting(v_in);
   td.RegisterVariableForTesting(v_out);
   td.RegisterVariableForTesting(v_wg);
 
-  ASSERT_TRUE(td.DetermineFunction(&func)) << td.error();
+  ASSERT_TRUE(td.DetermineFunction(func)) << td.error();
 
   EXPECT_TRUE(b.GenerateGlobalVariable(v_in)) << b.error();
   EXPECT_TRUE(b.GenerateGlobalVariable(v_out)) << b.error();
@@ -233,7 +164,7 @@
   mod->AddGlobalVariable(v_out);
   mod->AddGlobalVariable(v_wg);
 
-  ASSERT_TRUE(b.GenerateFunction(&func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "my_in"
 OpName %4 "my_out"
 OpName %7 "my_wg"
@@ -256,73 +187,60 @@
 }
 
 TEST_F(BuilderTest, FunctionDecoration_ExecutionMode_Fragment_OriginUpperLeft) {
-  ast::type::Void void_type;
-
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("main"), "main", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
+  auto* func = Func(
+      "main", {}, ty.void_, ast::StatementList{},
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
       });
 
-  ASSERT_TRUE(b.GenerateExecutionModes(&func, 3)) << b.error();
+  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
   EXPECT_EQ(DumpInstructions(b.execution_modes()),
             R"(OpExecutionMode %3 OriginUpperLeft
 )");
 }
 
 TEST_F(BuilderTest, FunctionDecoration_ExecutionMode_WorkgroupSize_Default) {
-  ast::type::Void void_type;
-
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("main"), "main", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
+  auto* func = Func(
+      "main", {}, ty.void_, ast::StatementList{},
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kCompute),
       });
 
-  ASSERT_TRUE(b.GenerateExecutionModes(&func, 3)) << b.error();
+  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
   EXPECT_EQ(DumpInstructions(b.execution_modes()),
             R"(OpExecutionMode %3 LocalSize 1 1 1
 )");
 }
 
 TEST_F(BuilderTest, FunctionDecoration_ExecutionMode_WorkgroupSize) {
-  ast::type::Void void_type;
-
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("main"), "main", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
+  auto* func = Func(
+      "main", {}, ty.void_, ast::StatementList{},
       ast::FunctionDecorationList{
           create<ast::WorkgroupDecoration>(Source{}, 2u, 4u, 6u),
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kCompute),
       });
 
-  ASSERT_TRUE(b.GenerateExecutionModes(&func, 3)) << b.error();
+  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
   EXPECT_EQ(DumpInstructions(b.execution_modes()),
             R"(OpExecutionMode %3 LocalSize 2 4 6
 )");
 }
 
 TEST_F(BuilderTest, FunctionDecoration_ExecutionMode_MultipleFragment) {
-  ast::type::Void void_type;
-
-  ast::Function func1(
-      Source{}, mod->RegisterSymbol("main1"), "main1", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
+  auto* func1 = Func(
+      "main1", {}, ty.void_, ast::StatementList{},
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
       });
 
-  ast::Function func2(
-      Source{}, mod->RegisterSymbol("main2"), "main2", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
+  auto* func2 = Func(
+      "main2", {}, ty.void_, ast::StatementList{},
       ast::FunctionDecorationList{
           create<ast::StageDecoration>(Source{}, ast::PipelineStage::kFragment),
       });
 
-  ASSERT_TRUE(b.GenerateFunction(&func1)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(&func2)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func1)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func2)) << b.error();
   EXPECT_EQ(DumpBuilder(b),
             R"(OpEntryPoint Fragment %3 "main1"
 OpEntryPoint Fragment %5 "main2"
@@ -345,15 +263,14 @@
 
 TEST_F(BuilderTest, FunctionDecoration_ExecutionMode_FragDepth) {
   auto* fragdepth =
-      Var("fragdepth", ast::StorageClass::kOutput, create<ast::type::F32>(),
-          nullptr,
+      Var("fragdepth", ast::StorageClass::kOutput, ty.f32, nullptr,
           ast::VariableDecorationList{
               create<ast::BuiltinDecoration>(ast::Builtin::kFragDepth),
           });
   mod->AddGlobalVariable(fragdepth);
 
   auto* func =
-      Func("main", ast::VariableList{}, create<ast::type::Void>(),
+      Func("main", ast::VariableList{}, ty.void_,
            ast::StatementList{
                create<ast::AssignmentStatement>(Expr("fragdepth"), Expr(1.f)),
            },
diff --git a/src/writer/spirv/builder_function_test.cc b/src/writer/spirv/builder_function_test.cc
index 600401c..797dcac 100644
--- a/src/writer/spirv/builder_function_test.cc
+++ b/src/writer/spirv/builder_function_test.cc
@@ -46,13 +46,10 @@
 using BuilderTest = TestHelper;
 
 TEST_F(BuilderTest, Function_Empty) {
-  ast::type::Void void_type;
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("a_func"), "a_func", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
-      ast::FunctionDecorationList{});
+  auto* func = Func("a_func", {}, ty.void_, ast::StatementList{},
+                    ast::FunctionDecorationList{});
 
-  ASSERT_TRUE(b.GenerateFunction(&func));
+  ASSERT_TRUE(b.GenerateFunction(func));
   EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
 %2 = OpTypeVoid
 %1 = OpTypeFunction %2
@@ -64,16 +61,13 @@
 }
 
 TEST_F(BuilderTest, Function_Terminator_Return) {
-  ast::type::Void void_type;
+  auto* func = Func("a_func", {}, ty.void_,
+                    ast::StatementList{
+                        create<ast::ReturnStatement>(Source{}),
+                    },
+                    ast::FunctionDecorationList{});
 
-  auto* body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}),
-                });
-  ast::Function func(Source{}, mod->RegisterSymbol("a_func"), "a_func", {},
-                     &void_type, body, ast::FunctionDecorationList{});
-
-  ASSERT_TRUE(b.GenerateFunction(&func));
+  ASSERT_TRUE(b.GenerateFunction(func));
   EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
 %2 = OpTypeVoid
 %1 = OpTypeFunction %2
@@ -85,32 +79,16 @@
 }
 
 TEST_F(BuilderTest, Function_Terminator_ReturnValue) {
-  ast::type::Void void_type;
-  ast::type::F32 f32;
-
-  auto* var_a =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &f32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
+  auto* var_a = Var("a", ast::StorageClass::kPrivate, ty.f32);
   td.RegisterVariableForTesting(var_a);
 
-  auto* body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(
-                        Source{}, create<ast::IdentifierExpression>(
-                                      Source{}, mod->RegisterSymbol("a"), "a")),
-                });
-  ASSERT_TRUE(td.DetermineResultType(body)) << td.error();
+  auto* func = Func("a_func", {}, ty.void_,
+                    ast::StatementList{create<ast::ReturnStatement>(Expr("a"))},
+                    ast::FunctionDecorationList{});
 
-  ast::Function func(Source{}, mod->RegisterSymbol("a_func"), "a_func", {},
-                     &void_type, body, ast::FunctionDecorationList{});
-
+  ASSERT_TRUE(td.DetermineFunction(func)) << td.error();
   ASSERT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(&func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
   EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "a"
 OpName %7 "a_func"
 %3 = OpTypeFloat 32
@@ -128,16 +106,13 @@
 }
 
 TEST_F(BuilderTest, Function_Terminator_Discard) {
-  ast::type::Void void_type;
+  auto* func = Func("a_func", {}, ty.void_,
+                    ast::StatementList{
+                        create<ast::DiscardStatement>(Source{}),
+                    },
+                    ast::FunctionDecorationList{});
 
-  auto* body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::DiscardStatement>(Source{}),
-                });
-  ast::Function func(Source{}, mod->RegisterSymbol("a_func"), "a_func", {},
-                     &void_type, body, ast::FunctionDecorationList{});
-
-  ASSERT_TRUE(b.GenerateFunction(&func));
+  ASSERT_TRUE(b.GenerateFunction(func));
   EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
 %2 = OpTypeVoid
 %1 = OpTypeFunction %2
@@ -149,42 +124,18 @@
 }
 
 TEST_F(BuilderTest, Function_WithParams) {
-  ast::type::Void void_type;
-  ast::type::F32 f32;
-  ast::type::I32 i32;
+  ast::VariableList params = {Var("a", ast::StorageClass::kFunction, ty.f32),
+                              Var("b", ast::StorageClass::kFunction, ty.i32)};
 
-  ast::VariableList params = {
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kFunction,    // storage_class
-                            &f32,                            // type
-                            true,                            // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{}),  // decorations
+  auto* func = Func("a_func", params, ty.f32,
+                    ast::StatementList{create<ast::ReturnStatement>(Expr("a"))},
+                    ast::FunctionDecorationList{});
 
-      create<ast::Variable>(Source{},                        // source
-                            "b",                             // name
-                            ast::StorageClass::kFunction,    // storage_class
-                            &i32,                            // type
-                            true,                            // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{}),  // decorations
-  };
+  td.RegisterVariableForTesting(func->params()[0]);
+  td.RegisterVariableForTesting(func->params()[1]);
+  EXPECT_TRUE(td.DetermineFunction(func));
 
-  auto* body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(
-                        Source{}, create<ast::IdentifierExpression>(
-                                      Source{}, mod->RegisterSymbol("a"), "a")),
-                });
-  ast::Function func(Source{}, mod->RegisterSymbol("a_func"), "a_func", params,
-                     &f32, body, ast::FunctionDecorationList{});
-
-  td.RegisterVariableForTesting(func.params()[0]);
-  td.RegisterVariableForTesting(func.params()[1]);
-  EXPECT_TRUE(td.DetermineFunction(&func));
-
-  ASSERT_TRUE(b.GenerateFunction(&func));
+  ASSERT_TRUE(b.GenerateFunction(func));
   EXPECT_EQ(DumpBuilder(b), R"(OpName %4 "a_func"
 OpName %5 "a"
 OpName %6 "b"
@@ -195,22 +146,20 @@
 %5 = OpFunctionParameter %2
 %6 = OpFunctionParameter %3
 %7 = OpLabel
-OpReturnValue %5
+%8 = OpLoad %2 %5
+OpReturnValue %8
 OpFunctionEnd
 )") << DumpBuilder(b);
 }
 
 TEST_F(BuilderTest, Function_WithBody) {
-  ast::type::Void void_type;
+  auto* func = Func("a_func", {}, ty.void_,
+                    ast::StatementList{
+                        create<ast::ReturnStatement>(Source{}),
+                    },
+                    ast::FunctionDecorationList{});
 
-  auto* body = create<ast::BlockStatement>(
-      Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}),
-                });
-  ast::Function func(Source{}, mod->RegisterSymbol("a_func"), "a_func", {},
-                     &void_type, body, ast::FunctionDecorationList{});
-
-  ASSERT_TRUE(b.GenerateFunction(&func));
+  ASSERT_TRUE(b.GenerateFunction(func));
   EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
 %2 = OpTypeVoid
 %1 = OpTypeFunction %2
@@ -222,31 +171,23 @@
 }
 
 TEST_F(BuilderTest, FunctionType) {
-  ast::type::Void void_type;
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("a_func"), "a_func", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
-      ast::FunctionDecorationList{});
+  auto* func = Func("a_func", {}, ty.void_, ast::StatementList{},
+                    ast::FunctionDecorationList{});
 
-  ASSERT_TRUE(b.GenerateFunction(&func));
+  ASSERT_TRUE(b.GenerateFunction(func));
   EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 )");
 }
 
 TEST_F(BuilderTest, FunctionType_DeDuplicate) {
-  ast::type::Void void_type;
-  ast::Function func1(
-      Source{}, mod->RegisterSymbol("a_func"), "a_func", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
-      ast::FunctionDecorationList{});
-  ast::Function func2(
-      Source{}, mod->RegisterSymbol("b_func"), "b_func", {}, &void_type,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
-      ast::FunctionDecorationList{});
+  auto* func1 = Func("a_func", {}, ty.void_, ast::StatementList{},
+                     ast::FunctionDecorationList{});
+  auto* func2 = Func("b_func", {}, ty.void_, ast::StatementList{},
+                     ast::FunctionDecorationList{});
 
-  ASSERT_TRUE(b.GenerateFunction(&func1));
-  ASSERT_TRUE(b.GenerateFunction(&func2));
+  ASSERT_TRUE(b.GenerateFunction(func1));
+  ASSERT_TRUE(b.GenerateFunction(func2));
   EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 )");
@@ -269,52 +210,31 @@
   //   return;
   // }
 
-  ast::type::Void void_type;
-
   ast::StructDecorationList s_decos;
   s_decos.push_back(create<ast::StructBlockDecoration>(Source{}));
 
   auto* str = create<ast::Struct>(
       ast::StructMemberList{Member("d", ty.f32, {MemberOffset(0)})}, s_decos);
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, &s);
+  auto* s = ty.struct_("Data", str);
+  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
-  auto* data_var =
-      create<ast::Variable>(Source{},                           // source
-                            "data",                             // name
-                            ast::StorageClass::kStorageBuffer,  // storage_class
-                            &ac,                                // type
-                            false,                              // is_const
-                            nullptr,                            // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::BindingDecoration>(Source{}, 0),
-                                create<ast::SetDecoration>(Source{}, 0),
-                            });
+  auto* data_var = Var("data", ast::StorageClass::kStorageBuffer, &ac, nullptr,
+                       ast::VariableDecorationList{
+                           create<ast::BindingDecoration>(Source{}, 0),
+                           create<ast::SetDecoration>(Source{}, 0),
+                       });
 
-  mod->AddConstructedType(&s);
+  mod->AddConstructedType(s);
 
   td.RegisterVariableForTesting(data_var);
   mod->AddGlobalVariable(data_var);
 
   {
-    auto* var = create<ast::Variable>(
-        Source{},                      // source
-        "v",                           // name
-        ast::StorageClass::kFunction,  // storage_class
-        ty.f32,                        // type
-        false,                         // is_const
-        create<ast::MemberAccessorExpression>(
-            Source{},
-            create<ast::IdentifierExpression>(
-                Source{}, mod->RegisterSymbol("data"), "data"),
-            create<ast::IdentifierExpression>(Source{},
-                                              mod->RegisterSymbol("d"),
-                                              "d")),  // constructor
-        ast::VariableDecorationList{});               // decorations
+    auto* var = Var("v", ast::StorageClass::kFunction, ty.f32,
+                    MemberAccessor("data", "d"), ast::VariableDecorationList{});
 
-    auto* func = Func("a", ast::VariableList{}, &void_type,
+    auto* func = Func("a", ast::VariableList{}, ty.void_,
                       ast::StatementList{
                           create<ast::VariableDeclStatement>(Source{}, var),
                           create<ast::ReturnStatement>(Source{}),
@@ -328,22 +248,10 @@
   }
 
   {
-    auto* var = create<ast::Variable>(
-        Source{},                      // source
-        "v",                           // name
-        ast::StorageClass::kFunction,  // storage_class
-        ty.f32,                        // type
-        false,                         // is_const
-        create<ast::MemberAccessorExpression>(
-            Source{},
-            create<ast::IdentifierExpression>(
-                Source{}, mod->RegisterSymbol("data"), "data"),
-            create<ast::IdentifierExpression>(Source{},
-                                              mod->RegisterSymbol("d"),
-                                              "d")),  // constructor
-        ast::VariableDecorationList{});               // decorations
+    auto* var = Var("v", ast::StorageClass::kFunction, ty.f32,
+                    MemberAccessor("data", "d"), ast::VariableDecorationList{});
 
-    auto* func = Func("b", ast::VariableList{}, &void_type,
+    auto* func = Func("b", ast::VariableList{}, ty.void_,
                       ast::StatementList{
                           create<ast::VariableDeclStatement>(Source{}, var),
                           create<ast::ReturnStatement>(Source{}),
diff --git a/src/writer/spirv/builder_function_variable_test.cc b/src/writer/spirv/builder_function_variable_test.cc
index e33a4c0..5c26cff 100644
--- a/src/writer/spirv/builder_function_variable_test.cc
+++ b/src/writer/spirv/builder_function_variable_test.cc
@@ -157,28 +157,20 @@
   // var v : f32 = 1.0;
   // var v2 : f32 = v; // Should generate the load and store automatically.
 
-  ast::type::F32 f32;
+  auto* v = Var("v", ast::StorageClass::kFunction, ty.f32, Expr(1.f),
+                ast::VariableDecorationList{});
+  td.RegisterVariableForTesting(v);
 
-  auto* init = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f));
+  auto* v2 = Var("v2", ast::StorageClass::kFunction, ty.f32, Expr("v"),
+                 ast::VariableDecorationList{});
+  td.RegisterVariableForTesting(v2);
 
-  ASSERT_TRUE(td.DetermineResultType(init)) << td.error();
-
-  ast::Variable v(Source{}, "v", ast::StorageClass::kFunction, &f32, false,
-                  init, ast::VariableDecorationList{});
-  td.RegisterVariableForTesting(&v);
-
-  ast::Variable v2(Source{}, "v2", ast::StorageClass::kFunction, &f32, false,
-                   create<ast::IdentifierExpression>(
-                       Source{}, mod->RegisterSymbol("v"), "v"),
-                   ast::VariableDecorationList{});
-  td.RegisterVariableForTesting(&v2);
-
-  ASSERT_TRUE(td.DetermineResultType(v2.constructor())) << td.error();
+  ASSERT_TRUE(td.DetermineResultType(v->constructor())) << td.error();
+  ASSERT_TRUE(td.DetermineResultType(v2->constructor())) << td.error();
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(&v)) << b.error();
-  EXPECT_TRUE(b.GenerateFunctionVariable(&v2)) << b.error();
+  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
+  EXPECT_TRUE(b.GenerateFunctionVariable(v2)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "v"
@@ -204,31 +196,24 @@
   // var v : f32 = 1.0;
   // const v2 : f32 = v; // Should generate the load
 
-  ast::type::F32 f32;
+  auto* v = Var("v", ast::StorageClass::kFunction, ty.f32, Expr(1.f),
+                ast::VariableDecorationList{});
+  td.RegisterVariableForTesting(v);
 
-  auto* init = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f));
+  auto* v2 = Var("v2", ast::StorageClass::kFunction, ty.f32, Expr("v"),
+                 ast::VariableDecorationList{});
+  td.RegisterVariableForTesting(v2);
 
-  EXPECT_TRUE(td.DetermineResultType(init)) << td.error();
-
-  ast::Variable v(Source{}, "v", ast::StorageClass::kFunction, &f32, false,
-                  init, ast::VariableDecorationList{});
-  td.RegisterVariableForTesting(&v);
-
-  ast::Variable v2(Source{}, "v2", ast::StorageClass::kFunction, &f32, true,
-                   create<ast::IdentifierExpression>(
-                       Source{}, mod->RegisterSymbol("v"), "v"),
-                   ast::VariableDecorationList{});
-  td.RegisterVariableForTesting(&v2);
-
-  ASSERT_TRUE(td.DetermineResultType(v2.constructor())) << td.error();
+  ASSERT_TRUE(td.DetermineResultType(v->constructor())) << td.error();
+  ASSERT_TRUE(td.DetermineResultType(v2->constructor())) << td.error();
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(&v)) << b.error();
-  EXPECT_TRUE(b.GenerateFunctionVariable(&v2)) << b.error();
+  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
+  EXPECT_TRUE(b.GenerateFunctionVariable(v2)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "v"
+OpName %7 "v2"
 )");
   EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 1
@@ -237,35 +222,27 @@
 )");
   EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
             R"(%3 = OpVariable %4 Function %5
+%7 = OpVariable %4 Function %5
 )");
   EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
             R"(OpStore %3 %2
 %6 = OpLoad %1 %3
+OpStore %7 %6
 )");
 }
 
 TEST_F(BuilderTest, FunctionVar_Const) {
-  ast::type::F32 f32;
-  ast::type::Vector vec(&f32, 3);
-
-  ast::ExpressionList vals;
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.0f)));
-
-  auto* init = create<ast::TypeConstructorExpression>(Source{}, &vec, vals);
-
+  auto* init = create<ast::TypeConstructorExpression>(
+      Source{}, ty.vec3<f32>(),
+      ast::ExpressionList{Expr(1.f), Expr(1.f), Expr(3.f)});
   EXPECT_TRUE(td.DetermineResultType(init)) << td.error();
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &f32, true, init,
+  auto* v = Const("var", ast::StorageClass::kOutput, ty.f32, init,
                   ast::VariableDecorationList{});
 
-  td.RegisterVariableForTesting(&v);
+  td.RegisterVariableForTesting(v);
 
-  EXPECT_TRUE(b.GenerateFunctionVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
diff --git a/src/writer/spirv/builder_global_variable_test.cc b/src/writer/spirv/builder_global_variable_test.cc
index f8b3fed..8675194 100644
--- a/src/writer/spirv/builder_global_variable_test.cc
+++ b/src/writer/spirv/builder_global_variable_test.cc
@@ -50,11 +50,8 @@
 using BuilderTest = TestHelper;
 
 TEST_F(BuilderTest, GlobalVar_NoStorageClass) {
-  ast::type::F32 f32;
-  ast::Variable v(Source{}, "var", ast::StorageClass::kNone, &f32, false,
-                  nullptr, ast::VariableDecorationList{});
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  auto* v = Var("var", ast::StorageClass::kNone, ty.f32);
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
 )");
   EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
@@ -65,11 +62,8 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_WithStorageClass) {
-  ast::type::F32 f32;
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &f32, false,
-                  nullptr, ast::VariableDecorationList{});
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  auto* v = Var("var", ast::StorageClass::kOutput, ty.f32);
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
 )");
   EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
@@ -80,11 +74,8 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_WithStorageClass_Input) {
-  ast::type::F32 f32;
-  ast::Variable v(Source{}, "var", ast::StorageClass::kInput, &f32, false,
-                  nullptr, ast::VariableDecorationList{});
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  auto* v = Var("var", ast::StorageClass::kInput, ty.f32);
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
 )");
   EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
@@ -94,27 +85,16 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_WithConstructor) {
-  ast::type::F32 f32;
-  ast::type::Vector vec(&f32, 3);
-
-  ast::ExpressionList vals;
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.0f)));
-
-  auto* init = create<ast::TypeConstructorExpression>(Source{}, &vec, vals);
-
+  auto* init = create<ast::TypeConstructorExpression>(
+      Source{}, ty.vec3<f32>(),
+      ast::ExpressionList{Expr(1.f), Expr(1.f), Expr(3.f)});
   EXPECT_TRUE(td.DetermineResultType(init)) << td.error();
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &f32, false,
-                  init, ast::VariableDecorationList{});
+  auto* v = Var("var", ast::StorageClass::kOutput, ty.f32, init,
+                ast::VariableDecorationList{});
+  td.RegisterVariableForTesting(v);
 
-  td.RegisterVariableForTesting(&v);
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %6 "var"
@@ -130,26 +110,16 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_Const) {
-  ast::type::F32 f32;
-  ast::type::Vector vec(&f32, 3);
-
-  ast::ExpressionList vals;
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.0f)));
-
-  auto* init = create<ast::TypeConstructorExpression>(Source{}, &vec, vals);
-
+  auto* init = create<ast::TypeConstructorExpression>(
+      Source{}, ty.vec3<f32>(),
+      ast::ExpressionList{Expr(1.f), Expr(1.f), Expr(3.f)});
   EXPECT_TRUE(td.DetermineResultType(init)) << td.error();
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &f32, true, init,
+  auto* v = Const("var", ast::StorageClass::kOutput, ty.f32, init,
                   ast::VariableDecorationList{});
-  td.RegisterVariableForTesting(&v);
+  td.RegisterVariableForTesting(v);
 
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %5 "var"
@@ -163,25 +133,16 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_Complex_Constructor) {
-  ast::type::F32 f32;
-  ast::type::Vector vec3(&f32, 3);
-
-  ast::ExpressionList vals;
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 2.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.0f)));
-  auto* init = create<ast::TypeConstructorExpression>(Source{}, &vec3, vals);
-
+  auto* init = create<ast::TypeConstructorExpression>(
+      Source{}, ty.vec3<f32>(),
+      ast::ExpressionList{Expr(1.f), Expr(2.f), Expr(3.f)});
   EXPECT_TRUE(td.DetermineResultType(init)) << td.error();
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &f32, true, init,
+  auto* v = Const("var", ast::StorageClass::kOutput, ty.f32, init,
                   ast::VariableDecorationList{});
-  td.RegisterVariableForTesting(&v);
+  td.RegisterVariableForTesting(v);
 
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
@@ -194,34 +155,19 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_Complex_ConstructorWithExtract) {
-  ast::type::F32 f32;
-  ast::type::Vector vec3(&f32, 3);
-  ast::type::Vector vec2(&f32, 2);
-
   auto* first = create<ast::TypeConstructorExpression>(
-      Source{}, &vec2,
-      ast::ExpressionList{
-          create<ast::ScalarConstructorExpression>(
-              Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)),
-          create<ast::ScalarConstructorExpression>(
-              Source{}, create<ast::FloatLiteral>(Source{}, &f32, 2.0f)),
-      });
+      Source{}, ty.vec2<f32>(), ast::ExpressionList{Expr(1.f), Expr(2.f)});
 
   auto* init = create<ast::TypeConstructorExpression>(
-      Source{}, &vec3,
-      ast::ExpressionList{
-          first,
-          create<ast::ScalarConstructorExpression>(
-              Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.0f)),
-      });
+      Source{}, ty.vec3<f32>(), ast::ExpressionList{first, Expr(3.f)});
 
   EXPECT_TRUE(td.DetermineResultType(init)) << td.error();
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &f32, true, init,
+  auto* v = Const("var", ast::StorageClass::kOutput, ty.f32, init,
                   ast::VariableDecorationList{});
-  td.RegisterVariableForTesting(&v);
+  td.RegisterVariableForTesting(v);
 
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
@@ -241,18 +187,10 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_WithLocation) {
-  ast::type::F32 f32;
-  auto* v =
-      create<ast::Variable>(Source{},                    // source
-                            "var",                       // name
-                            ast::StorageClass::kOutput,  // storage_class
-                            &f32,                        // type
-                            false,                       // is_const
-                            nullptr,                     // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::LocationDecoration>(Source{}, 5),
-                            });
+  auto* v = Var("var", ast::StorageClass::kOutput, ty.f32, nullptr,
+                ast::VariableDecorationList{
+                    create<ast::LocationDecoration>(Source{}, 5),
+                });
 
   EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
@@ -267,19 +205,11 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_WithBindingAndSet) {
-  ast::type::F32 f32;
-  auto* v =
-      create<ast::Variable>(Source{},                    // source
-                            "var",                       // name
-                            ast::StorageClass::kOutput,  // storage_class
-                            &f32,                        // type
-                            false,                       // is_const
-                            nullptr,                     // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::BindingDecoration>(Source{}, 2),
-                                create<ast::SetDecoration>(Source{}, 3),
-                            });
+  auto* v = Var("var", ast::StorageClass::kOutput, ty.f32, nullptr,
+                ast::VariableDecorationList{
+                    create<ast::BindingDecoration>(Source{}, 2),
+                    create<ast::SetDecoration>(Source{}, 3),
+                });
 
   EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
@@ -295,18 +225,11 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_WithBuiltin) {
-  ast::type::F32 f32;
-  auto* v = create<ast::Variable>(
-      Source{},                    // source
-      "var",                       // name
-      ast::StorageClass::kOutput,  // storage_class
-      &f32,                        // type
-      false,                       // is_const
-      nullptr,                     // constructor
-      ast::VariableDecorationList{
-          // decorations
-          create<ast::BuiltinDecoration>(Source{}, ast::Builtin::kPosition),
-      });
+  auto* v =
+      Var("var", ast::StorageClass::kOutput, ty.f32, nullptr,
+          ast::VariableDecorationList{
+              create<ast::BuiltinDecoration>(Source{}, ast::Builtin::kPosition),
+          });
 
   EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
@@ -321,21 +244,10 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_ConstantId_Bool) {
-  ast::type::Bool bool_type;
-
-  auto* v = create<ast::Variable>(
-      Source{},                  // source
-      "var",                     // name
-      ast::StorageClass::kNone,  // storage_class
-      &bool_type,                // type
-      false,                     // is_const
-      create<ast::ScalarConstructorExpression>(
-          Source{},
-          create<ast::BoolLiteral>(Source{}, &bool_type, true)),  // constructor
-      ast::VariableDecorationList{
-          // decorations
-          create<ast::ConstantIdDecoration>(Source{}, 1200),
-      });
+  auto* v = Var("var", ast::StorageClass::kNone, ty.bool_, Expr(true),
+                ast::VariableDecorationList{
+                    create<ast::ConstantIdDecoration>(Source{}, 1200),
+                });
 
   EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "var"
@@ -350,19 +262,10 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_ConstantId_Bool_NoConstructor) {
-  ast::type::Bool bool_type;
-
-  auto* v = create<ast::Variable>(
-      Source{},                  // source
-      "var",                     // name
-      ast::StorageClass::kNone,  // storage_class
-      &bool_type,                // type
-      false,                     // is_const
-      nullptr,                   // constructor
-      ast::VariableDecorationList{
-          // decorations
-          create<ast::ConstantIdDecoration>(Source{}, 1200),
-      });
+  auto* v = Var("var", ast::StorageClass::kNone, ty.bool_, nullptr,
+                ast::VariableDecorationList{
+                    create<ast::ConstantIdDecoration>(Source{}, 1200),
+                });
 
   EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
@@ -377,21 +280,10 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_ConstantId_Scalar) {
-  ast::type::F32 f32;
-
-  auto* v = create<ast::Variable>(
-      Source{},                  // source
-      "var",                     // name
-      ast::StorageClass::kNone,  // storage_class
-      &f32,                      // type
-      false,                     // is_const
-      create<ast::ScalarConstructorExpression>(
-          Source{},
-          create<ast::FloatLiteral>(Source{}, &f32, 2.0)),  // constructor
-      ast::VariableDecorationList{
-          // decorations
-          create<ast::ConstantIdDecoration>(Source{}, 0),
-      });
+  auto* v = Var("var", ast::StorageClass::kNone, ty.f32, Expr(2.f),
+                ast::VariableDecorationList{
+                    create<ast::ConstantIdDecoration>(Source{}, 0),
+                });
 
   EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "var"
@@ -406,19 +298,10 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_ConstantId_Scalar_F32_NoConstructor) {
-  ast::type::F32 f32;
-
-  auto* v =
-      create<ast::Variable>(Source{},                  // source
-                            "var",                     // name
-                            ast::StorageClass::kNone,  // storage_class
-                            &f32,                      // type
-                            false,                     // is_const
-                            nullptr,                   // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::ConstantIdDecoration>(Source{}, 0),
-                            });
+  auto* v = Var("var", ast::StorageClass::kNone, ty.f32, nullptr,
+                ast::VariableDecorationList{
+                    create<ast::ConstantIdDecoration>(Source{}, 0),
+                });
 
   EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
@@ -433,19 +316,10 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_ConstantId_Scalar_I32_NoConstructor) {
-  ast::type::I32 i32;
-
-  auto* v =
-      create<ast::Variable>(Source{},                  // source
-                            "var",                     // name
-                            ast::StorageClass::kNone,  // storage_class
-                            &i32,                      // type
-                            false,                     // is_const
-                            nullptr,                   // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::ConstantIdDecoration>(Source{}, 0),
-                            });
+  auto* v = Var("var", ast::StorageClass::kNone, ty.i32, nullptr,
+                ast::VariableDecorationList{
+                    create<ast::ConstantIdDecoration>(Source{}, 0),
+                });
 
   EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
@@ -460,19 +334,10 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_ConstantId_Scalar_U32_NoConstructor) {
-  ast::type::U32 u32;
-
-  auto* v =
-      create<ast::Variable>(Source{},                  // source
-                            "var",                     // name
-                            ast::StorageClass::kNone,  // storage_class
-                            &u32,                      // type
-                            false,                     // is_const
-                            nullptr,                   // constructor
-                            ast::VariableDecorationList{
-                                // decorations
-                                create<ast::ConstantIdDecoration>(Source{}, 0),
-                            });
+  auto* v = Var("var", ast::StorageClass::kNone, ty.u32, nullptr,
+                ast::VariableDecorationList{
+                    create<ast::ConstantIdDecoration>(Source{}, 0),
+                });
 
   EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
@@ -526,17 +391,14 @@
   // };
   // var b : [[access(read)]] A
 
-  ast::type::Struct A(
-      mod->RegisterSymbol("A"), "A",
-      create<ast::Struct>(
-          ast::StructMemberList{Member("a", ty.i32), Member("b", ty.i32)},
-          ast::StructDecorationList{}));
-  ast::type::AccessControl ac{ast::AccessControl::kReadOnly, &A};
+  auto* A = ty.struct_(
+      "A", create<ast::Struct>(
+               ast::StructMemberList{Member("a", ty.i32), Member("b", ty.i32)},
+               ast::StructDecorationList{}));
+  ast::type::AccessControl ac{ast::AccessControl::kReadOnly, A};
 
-  ast::Variable var(Source{}, "b", ast::StorageClass::kStorageBuffer, &ac,
-                    false, nullptr, ast::VariableDecorationList{});
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(&var)) << b.error();
+  auto* var = Var("b", ast::StorageClass::kStorageBuffer, &ac);
+  EXPECT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 NonWritable
 OpMemberDecorate %3 1 NonWritable
@@ -560,17 +422,13 @@
   // type B = A;
   // var b : [[access(read)]] B
 
-  ast::type::Struct A(
-      mod->RegisterSymbol("A"), "A",
-      create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32)},
-                          ast::StructDecorationList{}));
-  ast::type::Alias B(mod->RegisterSymbol("B"), "B", &A);
-  ast::type::AccessControl ac{ast::AccessControl::kReadOnly, &B};
-
-  ast::Variable var(Source{}, "b", ast::StorageClass::kStorageBuffer, &ac,
-                    false, nullptr, ast::VariableDecorationList{});
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(&var)) << b.error();
+  auto* A = ty.struct_(
+      "A", create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32)},
+                               ast::StructDecorationList{}));
+  auto* B = ty.alias("B", A);
+  ast::type::AccessControl ac{ast::AccessControl::kReadOnly, B};
+  auto* var = Var("b", ast::StorageClass::kStorageBuffer, &ac);
+  EXPECT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 NonWritable
 )");
@@ -592,17 +450,13 @@
   // type B = [[access(read)]] A;
   // var b : B
 
-  ast::type::Struct A(
-      mod->RegisterSymbol("A"), "A",
-      create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32)},
-                          ast::StructDecorationList{}));
-  ast::type::AccessControl ac{ast::AccessControl::kReadOnly, &A};
-  ast::type::Alias B(mod->RegisterSymbol("B"), "B", &ac);
-
-  ast::Variable var(Source{}, "b", ast::StorageClass::kStorageBuffer, &B, false,
-                    nullptr, ast::VariableDecorationList{});
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(&var)) << b.error();
+  auto* A = ty.struct_(
+      "A", create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32)},
+                               ast::StructDecorationList{}));
+  ast::type::AccessControl ac{ast::AccessControl::kReadOnly, A};
+  auto* B = ty.alias("B", &ac);
+  auto* var = Var("b", ast::StorageClass::kStorageBuffer, B);
+  EXPECT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 NonWritable
 )");
@@ -624,20 +478,16 @@
   // var b : [[access(read)]] A
   // var c : [[access(read_write)]] A
 
-  ast::type::Struct A(
-      mod->RegisterSymbol("A"), "A",
-      create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32)},
-                          ast::StructDecorationList{}));
-  ast::type::AccessControl read{ast::AccessControl::kReadOnly, &A};
-  ast::type::AccessControl rw{ast::AccessControl::kReadWrite, &A};
+  auto* A = ty.struct_(
+      "A", create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32)},
+                               ast::StructDecorationList{}));
+  ast::type::AccessControl read{ast::AccessControl::kReadOnly, A};
+  ast::type::AccessControl rw{ast::AccessControl::kReadWrite, A};
 
-  ast::Variable var_b(Source{}, "b", ast::StorageClass::kStorageBuffer, &read,
-                      false, nullptr, ast::VariableDecorationList{});
-  ast::Variable var_c(Source{}, "c", ast::StorageClass::kStorageBuffer, &rw,
-                      false, nullptr, ast::VariableDecorationList{});
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(&var_b)) << b.error();
-  EXPECT_TRUE(b.GenerateGlobalVariable(&var_c)) << b.error();
+  auto* var_b = Var("b", ast::StorageClass::kStorageBuffer, &read);
+  auto* var_c = Var("c", ast::StorageClass::kStorageBuffer, &rw);
+  EXPECT_TRUE(b.GenerateGlobalVariable(var_b)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(var_c)) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 NonWritable
 )");
@@ -660,15 +510,14 @@
 
 TEST_F(BuilderTest, GlobalVar_TextureStorageReadOnly) {
   // var<uniform_constant> a : texture_storage_ro_2d<r32uint>;
+
   ast::type::StorageTexture type(ast::type::TextureDimension::k2d,
                                  ast::AccessControl::kReadOnly,
                                  ast::type::ImageFormat::kR32Uint);
   ASSERT_TRUE(td.DetermineStorageTextureSubtype(&type)) << td.error();
 
-  ast::Variable var_a(Source{}, "a", ast::StorageClass::kUniformConstant, &type,
-                      false, nullptr, ast::VariableDecorationList{});
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(&var_a)) << b.error();
+  auto* var_a = Var("a", ast::StorageClass::kUniformConstant, &type);
+  EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonWritable
 )");
@@ -681,15 +530,14 @@
 
 TEST_F(BuilderTest, GlobalVar_TextureStorageWriteOnly) {
   // var<uniform_constant> a : texture_storage_wo_2d<r32uint>;
+
   ast::type::StorageTexture type(ast::type::TextureDimension::k2d,
                                  ast::AccessControl::kWriteOnly,
                                  ast::type::ImageFormat::kR32Uint);
   ASSERT_TRUE(td.DetermineStorageTextureSubtype(&type)) << td.error();
 
-  ast::Variable var_a(Source{}, "a", ast::StorageClass::kUniformConstant, &type,
-                      false, nullptr, ast::VariableDecorationList{});
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(&var_a)) << b.error();
+  auto* var_a = Var("a", ast::StorageClass::kUniformConstant, &type);
+  EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonReadable
 )");
diff --git a/src/writer/spirv/builder_ident_expression_test.cc b/src/writer/spirv/builder_ident_expression_test.cc
index 48cd6b2..a5f6dbf 100644
--- a/src/writer/spirv/builder_ident_expression_test.cc
+++ b/src/writer/spirv/builder_ident_expression_test.cc
@@ -38,27 +38,16 @@
 using BuilderTest = TestHelper;
 
 TEST_F(BuilderTest, IdentifierExpression_GlobalConst) {
-  ast::type::F32 f32;
-  ast::type::Vector vec(&f32, 3);
-
-  ast::ExpressionList vals;
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.0f)));
-
-  auto* init = create<ast::TypeConstructorExpression>(Source{}, &vec, vals);
-
+  auto* init = create<ast::TypeConstructorExpression>(
+      Source{}, ty.vec3<f32>(),
+      ast::ExpressionList{Expr(1.f), Expr(1.f), Expr(3.f)});
   EXPECT_TRUE(td.DetermineResultType(init)) << td.error();
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &f32, true, init,
+  auto* v = Const("var", ast::StorageClass::kOutput, ty.f32, init,
                   ast::VariableDecorationList{});
+  td.RegisterVariableForTesting(v);
 
-  td.RegisterVariableForTesting(&v);
-
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
@@ -68,21 +57,17 @@
 %5 = OpConstantComposite %1 %3 %3 %4
 )");
 
-  ast::IdentifierExpression expr(Source{}, mod->RegisterSymbol("var"), "var");
-  ASSERT_TRUE(td.DetermineResultType(&expr));
-
-  EXPECT_EQ(b.GenerateIdentifierExpression(&expr), 5u);
+  auto* expr = Expr("var");
+  ASSERT_TRUE(td.DetermineResultType(expr));
+  EXPECT_EQ(b.GenerateIdentifierExpression(expr), 5u);
 }
 
 TEST_F(BuilderTest, IdentifierExpression_GlobalVar) {
-  ast::type::F32 f32;
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &f32, false,
-                  nullptr, ast::VariableDecorationList{});
-
-  td.RegisterVariableForTesting(&v);
+  auto* v = Var("var", ast::StorageClass::kOutput, ty.f32);
+  td.RegisterVariableForTesting(v);
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
 )");
   EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
@@ -91,32 +76,22 @@
 %1 = OpVariable %2 Output %4
 )");
 
-  ast::IdentifierExpression expr(Source{}, mod->RegisterSymbol("var"), "var");
-  ASSERT_TRUE(td.DetermineResultType(&expr));
-  EXPECT_EQ(b.GenerateIdentifierExpression(&expr), 1u);
+  auto* expr = Expr("var");
+  ASSERT_TRUE(td.DetermineResultType(expr));
+  EXPECT_EQ(b.GenerateIdentifierExpression(expr), 1u);
 }
 
 TEST_F(BuilderTest, IdentifierExpression_FunctionConst) {
-  ast::type::F32 f32;
-  ast::type::Vector vec(&f32, 3);
-
-  ast::ExpressionList vals;
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.0f)));
-
-  auto* init = create<ast::TypeConstructorExpression>(Source{}, &vec, vals);
-
+  auto* init = create<ast::TypeConstructorExpression>(
+      Source{}, ty.vec3<f32>(),
+      ast::ExpressionList{Expr(1.f), Expr(1.f), Expr(3.f)});
   EXPECT_TRUE(td.DetermineResultType(init)) << td.error();
 
-  ast::Variable v(Source{}, "var", ast::StorageClass::kOutput, &f32, true, init,
+  auto* v = Const("var", ast::StorageClass::kOutput, ty.f32, init,
                   ast::VariableDecorationList{});
-  td.RegisterVariableForTesting(&v);
+  td.RegisterVariableForTesting(v);
 
-  EXPECT_TRUE(b.GenerateFunctionVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
@@ -126,20 +101,17 @@
 %5 = OpConstantComposite %1 %3 %3 %4
 )");
 
-  ast::IdentifierExpression expr(Source{}, mod->RegisterSymbol("var"), "var");
-  ASSERT_TRUE(td.DetermineResultType(&expr));
-  EXPECT_EQ(b.GenerateIdentifierExpression(&expr), 5u);
+  auto* expr = Expr("var");
+  ASSERT_TRUE(td.DetermineResultType(expr));
+  EXPECT_EQ(b.GenerateIdentifierExpression(expr), 5u);
 }
 
 TEST_F(BuilderTest, IdentifierExpression_FunctionVar) {
-  ast::type::F32 f32;
-  ast::Variable v(Source{}, "var", ast::StorageClass::kNone, &f32, false,
-                  nullptr, ast::VariableDecorationList{});
-
-  td.RegisterVariableForTesting(&v);
+  auto* v = Var("var", ast::StorageClass::kNone, ty.f32);
+  td.RegisterVariableForTesting(v);
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(&v)) << b.error();
+  EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
 )");
   EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
@@ -152,32 +124,23 @@
             R"(%1 = OpVariable %2 Function %4
 )");
 
-  ast::IdentifierExpression expr(Source{}, mod->RegisterSymbol("var"), "var");
-  ASSERT_TRUE(td.DetermineResultType(&expr));
-  EXPECT_EQ(b.GenerateIdentifierExpression(&expr), 1u);
+  auto* expr = Expr("var");
+  ASSERT_TRUE(td.DetermineResultType(expr));
+  EXPECT_EQ(b.GenerateIdentifierExpression(expr), 1u);
 }
 
 TEST_F(BuilderTest, IdentifierExpression_Load) {
-  ast::type::I32 i32;
+  auto* var = Var("var", ast::StorageClass::kPrivate, ty.i32);
+  td.RegisterVariableForTesting(var);
 
-  ast::Variable var(Source{}, "var", ast::StorageClass::kPrivate, &i32, false,
-                    nullptr, ast::VariableDecorationList{});
-
-  td.RegisterVariableForTesting(&var);
-
-  auto* lhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("var"), "var");
-  auto* rhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("var"), "var");
-
-  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kAdd, lhs, rhs);
-
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
+  auto* expr = Add("var", "var");
+  ASSERT_TRUE(td.DetermineResultType(expr)) << td.error();
 
   b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(&var)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
 
-  EXPECT_EQ(b.GenerateBinaryExpression(&expr), 7u) << b.error();
+  EXPECT_EQ(b.GenerateBinaryExpression(expr->As<ast::BinaryExpression>()), 7u)
+      << b.error();
   EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
@@ -191,28 +154,18 @@
 }
 
 TEST_F(BuilderTest, IdentifierExpression_NoLoadConst) {
-  ast::type::I32 i32;
-
-  ast::Variable var(Source{}, "var", ast::StorageClass::kNone, &i32, true,
-                    create<ast::ScalarConstructorExpression>(
-                        Source{}, create<ast::SintLiteral>(Source{}, &i32, 2)),
+  auto* var = Const("var", ast::StorageClass::kNone, ty.i32, Expr(2),
                     ast::VariableDecorationList{});
+  td.RegisterVariableForTesting(var);
 
-  td.RegisterVariableForTesting(&var);
-
-  auto* lhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("var"), "var");
-  auto* rhs = create<ast::IdentifierExpression>(
-      Source{}, mod->RegisterSymbol("var"), "var");
-
-  ast::BinaryExpression expr(Source{}, ast::BinaryOp::kAdd, lhs, rhs);
-
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
+  auto* expr = Add("var", "var");
+  ASSERT_TRUE(td.DetermineResultType(expr)) << td.error();
 
   b.push_function(Function{});
-  ASSERT_TRUE(b.GenerateGlobalVariable(&var)) << b.error();
+  ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
 
-  EXPECT_EQ(b.GenerateBinaryExpression(&expr), 3u) << b.error();
+  EXPECT_EQ(b.GenerateBinaryExpression(expr->As<ast::BinaryExpression>()), 3u)
+      << b.error();
   EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
 %2 = OpConstant %1 2
 )");
diff --git a/src/writer/spirv/builder_if_test.cc b/src/writer/spirv/builder_if_test.cc
index d46b370..a759428 100644
--- a/src/writer/spirv/builder_if_test.cc
+++ b/src/writer/spirv/builder_if_test.cc
@@ -93,35 +93,15 @@
 }
 
 TEST_F(BuilderTest, If_WithStatements) {
-  ast::type::Bool bool_type;
-  ast::type::I32 i32;
-
   // if (true) {
   //   v = 2;
   // }
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "v",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
 
+  auto* var = Var("v", ast::StorageClass::kPrivate, ty.i32);
   auto* body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 2))),
-      });
-  auto* cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, true));
-
-  ast::IfStatement expr(Source{}, cond, body, ast::ElseStatementList{});
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(2))});
+  ast::IfStatement expr(Source{}, Expr(true), body, ast::ElseStatementList{});
 
   td.RegisterVariableForTesting(var);
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
@@ -149,49 +129,22 @@
 }
 
 TEST_F(BuilderTest, If_WithElse) {
-  ast::type::Bool bool_type;
-  ast::type::I32 i32;
-
   // if (true) {
   //   v = 2;
   // } else {
   //   v = 3;
   // }
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "v",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
 
+  auto* var = Var("v", ast::StorageClass::kPrivate, ty.i32);
   auto* body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 2))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(2))});
   auto* else_body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 3))),
-      });
-
-  auto* cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, true));
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(3))});
 
   ast::IfStatement expr(
-      Source{}, cond, body,
+      Source{}, Expr(true), body,
       {create<ast::ElseStatement>(Source{}, nullptr, else_body)});
 
   td.RegisterVariableForTesting(var);
@@ -225,53 +178,23 @@
 }
 
 TEST_F(BuilderTest, If_WithElseIf) {
-  ast::type::Bool bool_type;
-  ast::type::I32 i32;
-
   // if (true) {
   //   v = 2;
   // } elseif (true) {
   //   v = 3;
   // }
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "v",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
 
+  auto* var = Var("v", ast::StorageClass::kPrivate, ty.i32);
   auto* body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 2))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(2))});
   auto* else_body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 3))),
-      });
-
-  auto* else_cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, true));
-
-  auto* cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, true));
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(3))});
 
   ast::IfStatement expr(
-      Source{}, cond, body,
-      {create<ast::ElseStatement>(Source{}, else_cond, else_body)});
+      Source{}, Expr(true), body,
+      {create<ast::ElseStatement>(Source{}, Expr(true), else_body)});
 
   td.RegisterVariableForTesting(var);
 
@@ -309,9 +232,6 @@
 }
 
 TEST_F(BuilderTest, If_WithMultiple) {
-  ast::type::Bool bool_type;
-  ast::type::I32 i32;
-
   // if (true) {
   //   v = 2;
   // } elseif (true) {
@@ -321,69 +241,26 @@
   // } else {
   //   v = 5;
   // }
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "v",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
 
+  auto* var = Var("v", ast::StorageClass::kPrivate, ty.i32);
   auto* body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 2))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(2))});
   auto* elseif_1_body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 3))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(3))});
   auto* elseif_2_body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 4))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(4))});
   auto* else_body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 5))),
-      });
-
-  auto* elseif_1_cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, true));
-  auto* elseif_2_cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, false));
-
-  auto* cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, true));
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(5))});
 
   ast::IfStatement expr(
-      Source{}, cond, body,
+      Source{}, Expr(true), body,
       {
-          create<ast::ElseStatement>(Source{}, elseif_1_cond, elseif_1_body),
-          create<ast::ElseStatement>(Source{}, elseif_2_cond, elseif_2_body),
+          create<ast::ElseStatement>(Source{}, Expr(true), elseif_1_body),
+          create<ast::ElseStatement>(Source{}, Expr(false), elseif_2_body),
           create<ast::ElseStatement>(Source{}, nullptr, else_body),
       });
 
@@ -437,21 +314,18 @@
 }
 
 TEST_F(BuilderTest, If_WithBreak) {
-  ast::type::Bool bool_type;
   // loop {
   //   if (true) {
   //     break;
   //   }
   // }
-  auto* cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, true));
 
   auto* if_body = create<ast::BlockStatement>(
       Source{}, ast::StatementList{
                     create<ast::BreakStatement>(Source{}),
                 });
 
-  auto* if_stmt = create<ast::IfStatement>(Source{}, cond, if_body,
+  auto* if_stmt = create<ast::IfStatement>(Source{}, Expr(true), if_body,
                                            ast::ElseStatementList{});
 
   auto* loop_body = create<ast::BlockStatement>(Source{}, ast::StatementList{
@@ -489,23 +363,19 @@
 }
 
 TEST_F(BuilderTest, If_WithElseBreak) {
-  ast::type::Bool bool_type;
   // loop {
   //   if (true) {
   //   } else {
   //     break;
   //   }
   // }
-  auto* cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, true));
-
   auto* else_body = create<ast::BlockStatement>(
       Source{}, ast::StatementList{
                     create<ast::BreakStatement>(Source{}),
                 });
 
   auto* if_stmt = create<ast::IfStatement>(
-      Source{}, cond,
+      Source{}, Expr(true),
       create<ast::BlockStatement>(Source{}, ast::StatementList{}),
       ast::ElseStatementList{
           create<ast::ElseStatement>(Source{}, nullptr, else_body)});
@@ -547,21 +417,17 @@
 }
 
 TEST_F(BuilderTest, If_WithContinue) {
-  ast::type::Bool bool_type;
   // loop {
   //   if (true) {
   //     continue;
   //   }
   // }
-  auto* cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, true));
-
   auto* if_body = create<ast::BlockStatement>(
       Source{}, ast::StatementList{
                     create<ast::ContinueStatement>(Source{}),
                 });
 
-  auto* if_stmt = create<ast::IfStatement>(Source{}, cond, if_body,
+  auto* if_stmt = create<ast::IfStatement>(Source{}, Expr(true), if_body,
                                            ast::ElseStatementList{});
 
   auto* loop_body = create<ast::BlockStatement>(Source{}, ast::StatementList{
@@ -599,23 +465,19 @@
 }
 
 TEST_F(BuilderTest, If_WithElseContinue) {
-  ast::type::Bool bool_type;
   // loop {
   //   if (true) {
   //   } else {
   //     continue;
   //   }
   // }
-  auto* cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, true));
-
   auto* else_body = create<ast::BlockStatement>(
       Source{}, ast::StatementList{
                     create<ast::ContinueStatement>(Source{}),
                 });
 
   auto* if_stmt = create<ast::IfStatement>(
-      Source{}, cond,
+      Source{}, Expr(true),
       create<ast::BlockStatement>(Source{}, ast::StatementList{}),
       ast::ElseStatementList{
           create<ast::ElseStatement>(Source{}, nullptr, else_body)});
@@ -657,20 +519,16 @@
 }
 
 TEST_F(BuilderTest, If_WithReturn) {
-  ast::type::Bool bool_type;
   // if (true) {
   //   return;
   // }
-  auto* cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, true));
-
   auto* if_body = create<ast::BlockStatement>(
       Source{}, ast::StatementList{
                     create<ast::ReturnStatement>(Source{}),
                 });
 
-  ast::IfStatement expr(Source{}, cond, if_body, ast::ElseStatementList{});
-
+  ast::IfStatement expr(Source{}, Expr(true), if_body,
+                        ast::ElseStatementList{});
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
   b.push_function(Function{});
@@ -689,22 +547,16 @@
 }
 
 TEST_F(BuilderTest, If_WithReturnValue) {
-  ast::type::Bool bool_type;
   // if (true) {
   //   return false;
   // }
-  auto* cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, true));
-  auto* cond2 = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, false));
-
   auto* if_body = create<ast::BlockStatement>(
       Source{}, ast::StatementList{
-                    create<ast::ReturnStatement>(Source{}, cond2),
+                    create<ast::ReturnStatement>(Source{}, Expr(false)),
                 });
 
-  ast::IfStatement expr(Source{}, cond, if_body, ast::ElseStatementList{});
-
+  ast::IfStatement expr(Source{}, Expr(true), if_body,
+                        ast::ElseStatementList{});
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
   b.push_function(Function{});
@@ -728,21 +580,11 @@
   // if (a) {
   // }
 
-  ast::type::Bool bool_type;
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kFunction,    // storage_class
-                            &bool_type,                      // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
+  auto* var = Var("a", ast::StorageClass::kFunction, ty.bool_);
   td.RegisterVariableForTesting(var);
 
   ast::IfStatement expr(
-      Source{},
-      create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol("a"),
-                                        "a"),
+      Source{}, Expr("a"),
       create<ast::BlockStatement>(Source{}, ast::StatementList{}),
       ast::ElseStatementList{});
 
diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc
index e8e2fa7..d927f74 100644
--- a/src/writer/spirv/builder_intrinsic_test.cc
+++ b/src/writer/spirv/builder_intrinsic_test.cc
@@ -1283,10 +1283,8 @@
   auto* s =
       create<ast::Struct>(ast::StructMemberList{Member("a", ty.array<f32>())},
                           ast::StructDecorationList{});
-  ast::type::Struct s_type(mod->RegisterSymbol("my_struct"), "my_struct", s);
-
-  auto* var = Var("b", ast::StorageClass::kPrivate, &s_type);
-
+  auto* s_type = ty.struct_("my_struct", s);
+  auto* var = Var("b", ast::StorageClass::kPrivate, s_type);
   auto* expr = Call("arrayLength", create<ast::MemberAccessorExpression>(
                                        Expr("b"), Expr("a")));
 
@@ -1321,9 +1319,8 @@
       ast::StructMemberList{Member("z", ty.f32), Member("a", ty.array<f32>())},
       ast::StructDecorationList{});
 
-  ast::type::Struct s_type(mod->RegisterSymbol("my_struct"), "my_struct", s);
-
-  auto* var = Var("b", ast::StorageClass::kPrivate, &s_type);
+  auto* s_type = ty.struct_("my_struct", s);
+  auto* var = Var("b", ast::StorageClass::kPrivate, s_type);
   auto* expr = Call("arrayLength", create<ast::MemberAccessorExpression>(
                                        Expr("b"), Expr("a")));
 
@@ -1358,13 +1355,10 @@
   ast::type::Pointer ptr(ty.array<f32>(), ast::StorageClass::kStorageBuffer);
 
   auto* s = create<ast::Struct>(
-      ast::StructMemberList{Member("z", ty.f32), Member("a", ty.array<f32>())
-
-      },
+      ast::StructMemberList{Member("z", ty.f32), Member("a", ty.array<f32>())},
       ast::StructDecorationList{});
-  ast::type::Struct s_type(mod->RegisterSymbol("my_struct"), "my_struct", s);
-
-  auto* var = Var("b", ast::StorageClass::kPrivate, &s_type);
+  auto* s_type = ty.struct_("my_struct", s);
+  auto* var = Var("b", ast::StorageClass::kPrivate, s_type);
 
   Var("ptr_var", ast::StorageClass::kPrivate, &ptr,
       create<ast::MemberAccessorExpression>(Expr("b"), Expr("a")), {});
diff --git a/src/writer/spirv/builder_loop_test.cc b/src/writer/spirv/builder_loop_test.cc
index cf6fa6b..c7e46d2 100644
--- a/src/writer/spirv/builder_loop_test.cc
+++ b/src/writer/spirv/builder_loop_test.cc
@@ -61,30 +61,15 @@
 }
 
 TEST_F(BuilderTest, Loop_WithoutContinuing) {
-  ast::type::I32 i32;
-
   // loop {
   //   v = 2;
   // }
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "v",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
 
+  auto* var = Var("v", ast::StorageClass::kPrivate, ty.i32);
   auto* body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 2))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(2))});
+
   ast::LoopStatement loop(
       Source{}, body,
       create<ast::BlockStatement>(Source{}, ast::StatementList{}));
@@ -125,35 +110,14 @@
   //   }
   // }
 
-  auto* var =
-      create<ast::Variable>(Source{},                        // source
-                            "v",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-
+  auto* var = Var("v", ast::StorageClass::kPrivate, ty.i32);
   auto* body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 2))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(2))});
   auto* continuing = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 3))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(3))});
+
   ast::LoopStatement loop(Source{}, body, continuing);
 
   td.RegisterVariableForTesting(var);
diff --git a/src/writer/spirv/builder_return_test.cc b/src/writer/spirv/builder_return_test.cc
index 8800100..d095562 100644
--- a/src/writer/spirv/builder_return_test.cc
+++ b/src/writer/spirv/builder_return_test.cc
@@ -46,18 +46,9 @@
 }
 
 TEST_F(BuilderTest, Return_WithValue) {
-  ast::type::F32 f32;
-  ast::type::Vector vec(&f32, 3);
-
-  ast::ExpressionList vals;
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1.0f)));
-  vals.push_back(create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::FloatLiteral>(Source{}, &f32, 3.0f)));
-
-  auto* val = create<ast::TypeConstructorExpression>(Source{}, &vec, vals);
+  auto* val = create<ast::TypeConstructorExpression>(
+      Source{}, ty.vec3<f32>(),
+      ast::ExpressionList{Expr(1.f), Expr(1.f), Expr(3.f)});
 
   ast::ReturnStatement ret(Source{}, val);
 
@@ -79,20 +70,15 @@
 }
 
 TEST_F(BuilderTest, Return_WithValue_GeneratesLoad) {
-  ast::type::F32 f32;
+  auto* var = Var("param", ast::StorageClass::kFunction, ty.f32);
 
-  ast::Variable var(Source{}, "param", ast::StorageClass::kFunction, &f32,
-                    false, nullptr, ast::VariableDecorationList{});
+  ast::ReturnStatement ret(Source{}, Expr("param"));
 
-  ast::ReturnStatement ret(
-      Source{}, create<ast::IdentifierExpression>(
-                    Source{}, mod->RegisterSymbol("param"), "param"));
-
-  td.RegisterVariableForTesting(&var);
+  td.RegisterVariableForTesting(var);
   EXPECT_TRUE(td.DetermineResultType(&ret)) << td.error();
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(&var)) << b.error();
+  EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
   EXPECT_TRUE(b.GenerateReturnStatement(&ret)) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
diff --git a/src/writer/spirv/builder_switch_test.cc b/src/writer/spirv/builder_switch_test.cc
index efc1355..d0be541 100644
--- a/src/writer/spirv/builder_switch_test.cc
+++ b/src/writer/spirv/builder_switch_test.cc
@@ -40,15 +40,10 @@
 using BuilderTest = TestHelper;
 
 TEST_F(BuilderTest, Switch_Empty) {
-  ast::type::I32 i32;
-
   // switch (1) {
   // }
-  auto* cond = create<ast::ScalarConstructorExpression>(
-      Source{}, create<ast::SintLiteral>(Source{}, &i32, 1));
 
-  ast::SwitchStatement expr(Source{}, cond, ast::CaseStatementList{});
-
+  ast::SwitchStatement expr(Source{}, Expr(1), ast::CaseStatementList{});
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
   b.push_function(Function{});
@@ -67,8 +62,6 @@
 }
 
 TEST_F(BuilderTest, Switch_WithCase) {
-  ast::type::I32 i32;
-
   // switch(a) {
   //   case 1:
   //     v = 1;
@@ -76,50 +69,22 @@
   //     v = 2;
   // }
 
-  auto* v =
-      create<ast::Variable>(Source{},                        // source
-                            "v",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* a =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
+  auto* v = Var("v", ast::StorageClass::kPrivate, ty.i32);
+  auto* a = Var("a", ast::StorageClass::kPrivate, ty.i32);
 
   auto* case_1_body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 1))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(1))});
 
   auto* case_2_body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 2))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(2))});
 
   ast::CaseSelectorList selector_1;
-  selector_1.push_back(create<ast::SintLiteral>(Source{}, &i32, 1));
+  selector_1.push_back(Literal(1));
 
   ast::CaseSelectorList selector_2;
-  selector_2.push_back(create<ast::SintLiteral>(Source{}, &i32, 2));
+  selector_2.push_back(Literal(2));
 
   ast::CaseStatementList cases;
   cases.push_back(
@@ -127,23 +92,18 @@
   cases.push_back(
       create<ast::CaseStatement>(Source{}, selector_2, case_2_body));
 
-  ast::SwitchStatement expr(Source{},
-                            create<ast::IdentifierExpression>(
-                                Source{}, mod->RegisterSymbol("a"), "a"),
-                            cases);
+  ast::SwitchStatement expr(Source{}, Expr("a"), cases);
 
   td.RegisterVariableForTesting(v);
   td.RegisterVariableForTesting(a);
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("a_func"), "a_func", {}, &i32,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
-      ast::FunctionDecorationList{});
+  auto* func = Func("a_func", {}, ty.i32, ast::StatementList{},
+                    ast::FunctionDecorationList{});
 
   ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(&func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
 
   EXPECT_TRUE(b.GenerateSwitchStatement(&expr)) << b.error();
 
@@ -178,62 +138,34 @@
 }
 
 TEST_F(BuilderTest, Switch_WithDefault) {
-  ast::type::I32 i32;
-
   // switch(true) {
   //   default:
   //     v = 1;
   //  }
 
-  auto* v =
-      create<ast::Variable>(Source{},                        // source
-                            "v",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* a =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
+  auto* v = Var("v", ast::StorageClass::kPrivate, ty.i32);
+  auto* a = Var("a", ast::StorageClass::kPrivate, ty.i32);
 
   auto* default_body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 1))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(1))});
 
   ast::CaseStatementList cases;
   cases.push_back(create<ast::CaseStatement>(Source{}, ast::CaseSelectorList{},
                                              default_body));
 
-  ast::SwitchStatement expr(Source{},
-                            create<ast::IdentifierExpression>(
-                                Source{}, mod->RegisterSymbol("a"), "a"),
-                            cases);
+  ast::SwitchStatement expr(Source{}, Expr("a"), cases);
 
   td.RegisterVariableForTesting(v);
   td.RegisterVariableForTesting(a);
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("a_func"), "a_func", {}, &i32,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
-      ast::FunctionDecorationList{});
+  auto* func = Func("a_func", {}, ty.i32, ast::StatementList{},
+                    ast::FunctionDecorationList{});
 
   ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(&func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
 
   EXPECT_TRUE(b.GenerateSwitchStatement(&expr)) << b.error();
 
@@ -262,8 +194,6 @@
 }
 
 TEST_F(BuilderTest, Switch_WithCaseAndDefault) {
-  ast::type::I32 i32;
-
   // switch(a) {
   //   case 1:
   //      v = 1;
@@ -273,62 +203,27 @@
   //      v = 3;
   //  }
 
-  auto* v =
-      create<ast::Variable>(Source{},                        // source
-                            "v",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* a =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
+  auto* v = Var("v", ast::StorageClass::kPrivate, ty.i32);
+  auto* a = Var("a", ast::StorageClass::kPrivate, ty.i32);
 
   auto* case_1_body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 1))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(1))});
 
   auto* case_2_body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 2))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(2))});
 
   auto* default_body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 3))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(3))});
 
   ast::CaseSelectorList selector_1;
-  selector_1.push_back(create<ast::SintLiteral>(Source{}, &i32, 1));
+  selector_1.push_back(Literal(1));
 
   ast::CaseSelectorList selector_2;
-  selector_2.push_back(create<ast::SintLiteral>(Source{}, &i32, 2));
-  selector_2.push_back(create<ast::SintLiteral>(Source{}, &i32, 3));
+  selector_2.push_back(Literal(2));
+  selector_2.push_back(Literal(3));
 
   ast::CaseStatementList cases;
   cases.push_back(
@@ -338,23 +233,18 @@
   cases.push_back(create<ast::CaseStatement>(Source{}, ast::CaseSelectorList{},
                                              default_body));
 
-  ast::SwitchStatement expr(Source{},
-                            create<ast::IdentifierExpression>(
-                                Source{}, mod->RegisterSymbol("a"), "a"),
-                            cases);
+  ast::SwitchStatement expr(Source{}, Expr("a"), cases);
 
   td.RegisterVariableForTesting(v);
   td.RegisterVariableForTesting(a);
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("a_func"), "a_func", {}, &i32,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
-      ast::FunctionDecorationList{});
+  auto* func = Func("a_func", {}, ty.i32, ast::StatementList{},
+                    ast::FunctionDecorationList{});
 
   ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(&func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
 
   EXPECT_TRUE(b.GenerateSwitchStatement(&expr)) << b.error();
 
@@ -391,8 +281,6 @@
 }
 
 TEST_F(BuilderTest, Switch_CaseWithFallthrough) {
-  ast::type::I32 i32;
-
   // switch(a) {
   //   case 1:
   //      v = 1;
@@ -403,61 +291,28 @@
   //      v = 3;
   //  }
 
-  auto* v =
-      create<ast::Variable>(Source{},                        // source
-                            "v",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* a =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
+  auto* v = Var("v", ast::StorageClass::kPrivate, ty.i32);
+  auto* a = Var("a", ast::StorageClass::kPrivate, ty.i32);
 
   auto* case_1_body = create<ast::BlockStatement>(
       Source{},
       ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 1))),
+          create<ast::AssignmentStatement>(Source{}, Expr("v"), Expr(1)),
           create<ast::FallthroughStatement>(Source{})});
 
   auto* case_2_body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 2))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(2))});
 
   auto* default_body = create<ast::BlockStatement>(
-      Source{},
-      ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 3))),
-      });
+      Source{}, ast::StatementList{create<ast::AssignmentStatement>(
+                    Source{}, Expr("v"), Expr(3))});
 
   ast::CaseSelectorList selector_1;
-  selector_1.push_back(create<ast::SintLiteral>(Source{}, &i32, 1));
+  selector_1.push_back(Literal(1));
 
   ast::CaseSelectorList selector_2;
-  selector_2.push_back(create<ast::SintLiteral>(Source{}, &i32, 2));
+  selector_2.push_back(Literal(2));
 
   ast::CaseStatementList cases;
   cases.push_back(
@@ -467,23 +322,18 @@
   cases.push_back(create<ast::CaseStatement>(Source{}, ast::CaseSelectorList{},
                                              default_body));
 
-  ast::SwitchStatement expr(Source{},
-                            create<ast::IdentifierExpression>(
-                                Source{}, mod->RegisterSymbol("a"), "a"),
-                            cases);
+  ast::SwitchStatement expr(Source{}, Expr("a"), cases);
 
   td.RegisterVariableForTesting(v);
   td.RegisterVariableForTesting(a);
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("a_func"), "a_func", {}, &i32,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
-      ast::FunctionDecorationList{});
+  auto* func = Func("a_func", {}, ty.i32, ast::StatementList{},
+                    ast::FunctionDecorationList{});
 
   ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(&func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
 
   EXPECT_TRUE(b.GenerateSwitchStatement(&expr)) << b.error();
 
@@ -520,75 +370,46 @@
 }
 
 TEST_F(BuilderTest, Switch_CaseFallthroughLastStatement) {
-  ast::type::I32 i32;
-
   // switch(a) {
   //   case 1:
   //      v = 1;
   //      fallthrough;
   //  }
 
-  auto* v =
-      create<ast::Variable>(Source{},                        // source
-                            "v",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* a =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
+  auto* v = Var("v", ast::StorageClass::kPrivate, ty.i32);
+  auto* a = Var("a", ast::StorageClass::kPrivate, ty.i32);
 
   auto* case_1_body = create<ast::BlockStatement>(
       Source{},
       ast::StatementList{
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 1))),
+          create<ast::AssignmentStatement>(Source{}, Expr("v"), Expr(1)),
           create<ast::FallthroughStatement>(Source{})});
 
   ast::CaseSelectorList selector_1;
-  selector_1.push_back(create<ast::SintLiteral>(Source{}, &i32, 1));
+  selector_1.push_back(Literal(1));
 
   ast::CaseStatementList cases;
   cases.push_back(
       create<ast::CaseStatement>(Source{}, selector_1, case_1_body));
 
-  ast::SwitchStatement expr(Source{},
-                            create<ast::IdentifierExpression>(
-                                Source{}, mod->RegisterSymbol("a"), "a"),
-                            cases);
+  ast::SwitchStatement expr(Source{}, Expr("a"), cases);
 
   td.RegisterVariableForTesting(v);
   td.RegisterVariableForTesting(a);
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("a_func"), "a_func", {}, &i32,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
-      ast::FunctionDecorationList{});
+  auto* func = Func("a_func", {}, ty.i32, ast::StatementList{},
+                    ast::FunctionDecorationList{});
 
   ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(&func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
 
   EXPECT_FALSE(b.GenerateSwitchStatement(&expr)) << b.error();
   EXPECT_EQ(b.error(), "fallthrough of last case statement is disallowed");
 }
 
 TEST_F(BuilderTest, Switch_WithNestedBreak) {
-  ast::type::I32 i32;
-  ast::type::Bool bool_type;
-
   // switch (a) {
   //   case 1:
   //     if (true) {
@@ -597,22 +418,8 @@
   //     v = 1;
   //  }
 
-  auto* v =
-      create<ast::Variable>(Source{},                        // source
-                            "v",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
-  auto* a =
-      create<ast::Variable>(Source{},                        // source
-                            "a",                             // name
-                            ast::StorageClass::kPrivate,     // storage_class
-                            &i32,                            // type
-                            false,                           // is_const
-                            nullptr,                         // constructor
-                            ast::VariableDecorationList{});  // decorations
+  auto* v = Var("v", ast::StorageClass::kPrivate, ty.i32);
+  auto* a = Var("a", ast::StorageClass::kPrivate, ty.i32);
 
   auto* if_body = create<ast::BlockStatement>(
       Source{}, ast::StatementList{
@@ -622,43 +429,29 @@
   auto* case_1_body = create<ast::BlockStatement>(
       Source{},
       ast::StatementList{
-          create<ast::IfStatement>(
-              Source{},
-              create<ast::ScalarConstructorExpression>(
-                  Source{},
-                  create<ast::BoolLiteral>(Source{}, &bool_type, true)),
-              if_body, ast::ElseStatementList{}),
-          create<ast::AssignmentStatement>(
-              Source{},
-              create<ast::IdentifierExpression>(Source{},
-                                                mod->RegisterSymbol("v"), "v"),
-              create<ast::ScalarConstructorExpression>(
-                  Source{}, create<ast::SintLiteral>(Source{}, &i32, 1)))});
+          create<ast::IfStatement>(Source{}, Expr(true), if_body,
+                                   ast::ElseStatementList{}),
+          create<ast::AssignmentStatement>(Source{}, Expr("v"), Expr(1))});
 
   ast::CaseSelectorList selector_1;
-  selector_1.push_back(create<ast::SintLiteral>(Source{}, &i32, 1));
+  selector_1.push_back(Literal(1));
 
   ast::CaseStatementList cases;
   cases.push_back(
       create<ast::CaseStatement>(Source{}, selector_1, case_1_body));
 
-  ast::SwitchStatement expr(Source{},
-                            create<ast::IdentifierExpression>(
-                                Source{}, mod->RegisterSymbol("a"), "a"),
-                            cases);
+  ast::SwitchStatement expr(Source{}, Expr("a"), cases);
 
   td.RegisterVariableForTesting(v);
   td.RegisterVariableForTesting(a);
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
-  ast::Function func(
-      Source{}, mod->RegisterSymbol("a_func"), "a_func", {}, &i32,
-      create<ast::BlockStatement>(Source{}, ast::StatementList{}),
-      ast::FunctionDecorationList{});
+  auto* func = Func("a_func", {}, ty.i32, ast::StatementList{},
+                    ast::FunctionDecorationList{});
 
   ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
   ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-  ASSERT_TRUE(b.GenerateFunction(&func)) << b.error();
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
 
   EXPECT_TRUE(b.GenerateSwitchStatement(&expr)) << b.error();
 
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index 76b1da4..ca97704 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -52,10 +52,8 @@
 using BuilderTest_Type = TestHelper;
 
 TEST_F(BuilderTest_Type, GenerateAlias) {
-  ast::type::F32 f32;
-  ast::type::Alias alias_type(mod->RegisterSymbol("my_type"), "my_type", &f32);
-
-  auto id = b.GenerateTypeIfNeeded(&alias_type);
+  auto* alias_type = ty.alias("my_type", ty.f32);
+  auto id = b.GenerateTypeIfNeeded(alias_type);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -65,24 +63,20 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedAlias) {
-  ast::type::I32 i32;
-  ast::type::F32 f32;
-  ast::type::Alias alias_type(mod->RegisterSymbol("my_type"), "my_type", &f32);
+  auto* alias_type = ty.alias("my_type", ty.f32);
 
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&alias_type), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(alias_type), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&alias_type), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(alias_type), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&f32), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.f32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
 TEST_F(BuilderTest_Type, GenerateRuntimeArray) {
-  ast::type::I32 i32;
-  ast::type::Array ary(&i32, 0, ast::ArrayDecorationList{});
-
+  ast::type::Array ary(ty.i32, 0, ast::ArrayDecorationList{});
   auto id = b.GenerateTypeIfNeeded(&ary);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(1u, id);
@@ -93,8 +87,7 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedRuntimeArray) {
-  ast::type::I32 i32;
-  ast::type::Array ary(&i32, 0, ast::ArrayDecorationList{});
+  ast::type::Array ary(ty.i32, 0, ast::ArrayDecorationList{});
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(&ary), 1u);
   EXPECT_EQ(b.GenerateTypeIfNeeded(&ary), 1u);
@@ -106,9 +99,7 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateArray) {
-  ast::type::I32 i32;
-  ast::type::Array ary(&i32, 4, ast::ArrayDecorationList{});
-
+  ast::type::Array ary(ty.i32, 4, ast::ArrayDecorationList{});
   auto id = b.GenerateTypeIfNeeded(&ary);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(1u, id);
@@ -121,9 +112,7 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateArray_WithStride) {
-  ast::type::I32 i32;
-
-  ast::type::Array ary(&i32, 4,
+  ast::type::Array ary(ty.i32, 4,
                        ast::ArrayDecorationList{
                            create<ast::StrideDecoration>(Source{}, 16u),
                        });
@@ -143,8 +132,7 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedArray) {
-  ast::type::I32 i32;
-  ast::type::Array ary(&i32, 4, ast::ArrayDecorationList{});
+  ast::type::Array ary(ty.i32, 4, ast::ArrayDecorationList{});
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(&ary), 1u);
   EXPECT_EQ(b.GenerateTypeIfNeeded(&ary), 1u);
@@ -158,9 +146,7 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateBool) {
-  ast::type::Bool bool_type;
-
-  auto id = b.GenerateTypeIfNeeded(&bool_type);
+  auto id = b.GenerateTypeIfNeeded(ty.bool_);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -170,21 +156,16 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedBool) {
-  ast::type::I32 i32;
-  ast::type::Bool bool_type;
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&bool_type), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.bool_), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&bool_type), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.bool_), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
 TEST_F(BuilderTest_Type, GenerateF32) {
-  ast::type::F32 f32;
-
-  auto id = b.GenerateTypeIfNeeded(&f32);
+  auto id = b.GenerateTypeIfNeeded(ty.f32);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -194,21 +175,16 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedF32) {
-  ast::type::I32 i32;
-  ast::type::F32 f32;
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&f32), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.f32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&f32), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.f32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
 TEST_F(BuilderTest_Type, GenerateI32) {
-  ast::type::I32 i32;
-
-  auto id = b.GenerateTypeIfNeeded(&i32);
+  auto id = b.GenerateTypeIfNeeded(ty.i32);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -218,22 +194,16 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedI32) {
-  ast::type::I32 i32;
-  ast::type::F32 f32;
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&f32), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.f32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
 TEST_F(BuilderTest_Type, GenerateMatrix) {
-  ast::type::F32 f32;
-  ast::type::Matrix mat_type(&f32, 3, 2);
-
-  auto id = b.GenerateTypeIfNeeded(&mat_type);
+  auto id = b.GenerateTypeIfNeeded(ty.mat2x3<f32>());
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -245,21 +215,17 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedMatrix) {
-  ast::type::I32 i32;
-  ast::type::Matrix mat_type(&i32, 3, 4);
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&mat_type), 1u);
+  auto* mat = ty.mat4x3<i32>();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(mat), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 3u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32), 3u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&mat_type), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(mat), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
 TEST_F(BuilderTest_Type, GeneratePtr) {
-  ast::type::I32 i32;
-  ast::type::Pointer ptr(&i32, ast::StorageClass::kOutput);
-
+  ast::type::Pointer ptr(ty.i32, ast::StorageClass::kOutput);
   auto id = b.GenerateTypeIfNeeded(&ptr);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(1u, id);
@@ -270,9 +236,7 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedPtr) {
-  ast::type::I32 i32;
-  ast::type::Pointer ptr(&i32, ast::StorageClass::kOutput);
-
+  ast::type::Pointer ptr(ty.i32, ast::StorageClass::kOutput);
   EXPECT_EQ(b.GenerateTypeIfNeeded(&ptr), 1u);
   EXPECT_EQ(b.GenerateTypeIfNeeded(&ptr), 1u);
 }
@@ -280,9 +244,9 @@
 TEST_F(BuilderTest_Type, GenerateStruct_Empty) {
   auto* s = create<ast::Struct>(Source{}, ast::StructMemberList{},
                                 ast::StructDecorationList{});
-  ast::type::Struct s_type(mod->RegisterSymbol("S"), "S", s);
+  auto* s_type = ty.struct_("S", s);
 
-  auto id = b.GenerateTypeIfNeeded(&s_type);
+  auto id = b.GenerateTypeIfNeeded(s_type);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -296,9 +260,9 @@
 TEST_F(BuilderTest_Type, GenerateStruct) {
   auto* s = create<ast::Struct>(ast::StructMemberList{Member("a", ty.f32)},
                                 ast::StructDecorationList{});
-  ast::type::Struct s_type(mod->RegisterSymbol("my_struct"), "my_struct", s);
+  auto* s_type = ty.struct_("my_struct", s);
 
-  auto id = b.GenerateTypeIfNeeded(&s_type);
+  auto id = b.GenerateTypeIfNeeded(s_type);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -316,9 +280,9 @@
 
   auto* s = create<ast::Struct>(ast::StructMemberList{Member("a", ty.f32)},
                                 struct_decos);
-  ast::type::Struct s_type(mod->RegisterSymbol("my_struct"), "my_struct", s);
+  auto* s_type = ty.struct_("my_struct", s);
 
-  auto id = b.GenerateTypeIfNeeded(&s_type);
+  auto id = b.GenerateTypeIfNeeded(s_type);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -337,9 +301,9 @@
       ast::StructMemberList{Member("a", ty.f32, {MemberOffset(0)}),
                             Member("b", ty.f32, {MemberOffset(8)})},
       ast::StructDecorationList{});
-  ast::type::Struct s_type(mod->RegisterSymbol("S"), "S", s);
+  auto* s_type = ty.struct_("S", s);
 
-  auto id = b.GenerateTypeIfNeeded(&s_type);
+  auto id = b.GenerateTypeIfNeeded(s_type);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -361,9 +325,9 @@
                                                 Member("b", ty.mat2x3<f32>()),
                                                 Member("c", ty.mat4x4<f32>())},
                           ast::StructDecorationList{});
-  ast::type::Struct s_type(mod->RegisterSymbol("S"), "S", s);
+  auto* s_type = ty.struct_("S", s);
 
-  auto id = b.GenerateTypeIfNeeded(&s_type);
+  auto id = b.GenerateTypeIfNeeded(s_type);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -391,9 +355,9 @@
                             Member("b", ty.mat2x3<f32>(), {MemberOffset(16)}),
                             Member("c", ty.mat4x4<f32>(), {MemberOffset(48)})},
       ast::StructDecorationList{});
-  ast::type::Struct s_type(mod->RegisterSymbol("S"), "S", s);
+  auto* s_type = ty.struct_("S", s);
 
-  auto id = b.GenerateTypeIfNeeded(&s_type);
+  auto id = b.GenerateTypeIfNeeded(s_type);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -442,9 +406,9 @@
                             Member("b", &arr_arr_mat2x3, {MemberOffset(16)}),
                             Member("c", &rtarr_mat4x4, {MemberOffset(48)})},
       ast::StructDecorationList{});
-  ast::type::Struct s_type(mod->RegisterSymbol("S"), "S", s);
+  auto* s_type = ty.struct_("S", s);
 
-  auto id = b.GenerateTypeIfNeeded(&s_type);
+  auto id = b.GenerateTypeIfNeeded(s_type);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -480,9 +444,7 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateU32) {
-  ast::type::U32 u32;
-
-  auto id = b.GenerateTypeIfNeeded(&u32);
+  auto id = b.GenerateTypeIfNeeded(ty.u32);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -492,22 +454,16 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedU32) {
-  ast::type::U32 u32;
-  ast::type::F32 f32;
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&u32), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.u32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&f32), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.f32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&u32), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.u32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
 TEST_F(BuilderTest_Type, GenerateVector) {
-  ast::type::F32 f32;
-  ast::type::Vector vec_type(&f32, 3);
-
-  auto id = b.GenerateTypeIfNeeded(&vec_type);
+  auto id = b.GenerateTypeIfNeeded(ty.vec3<f32>());
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -518,21 +474,17 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedVector) {
-  ast::type::I32 i32;
-  ast::type::Vector vec_type(&i32, 3);
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&vec_type), 1u);
+  auto* vec_type = ty.vec3<i32>();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(vec_type), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&vec_type), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(vec_type), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
 TEST_F(BuilderTest_Type, GenerateVoid) {
-  ast::type::Void void_type;
-
-  auto id = b.GenerateTypeIfNeeded(&void_type);
+  auto id = b.GenerateTypeIfNeeded(ty.void_);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -542,14 +494,11 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedVoid) {
-  ast::type::I32 i32;
-  ast::type::Void void_type;
-
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&void_type), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.void_), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&void_type), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.void_), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
@@ -637,8 +586,7 @@
 }
 
 TEST_F(BuilderTest_Type, MultisampledTexture_Generate_2d_i32) {
-  ast::type::I32 i32;
-  ast::type::MultisampledTexture ms(ast::type::TextureDimension::k2d, &i32);
+  ast::type::MultisampledTexture ms(ast::type::TextureDimension::k2d, ty.i32);
 
   EXPECT_EQ(1u, b.GenerateTypeIfNeeded(&ms));
   ASSERT_FALSE(b.has_error()) << b.error();
@@ -648,8 +596,7 @@
 }
 
 TEST_F(BuilderTest_Type, MultisampledTexture_Generate_2d_u32) {
-  ast::type::U32 u32;
-  ast::type::MultisampledTexture ms(ast::type::TextureDimension::k2d, &u32);
+  ast::type::MultisampledTexture ms(ast::type::TextureDimension::k2d, ty.u32);
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(&ms), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
@@ -660,8 +607,7 @@
 }
 
 TEST_F(BuilderTest_Type, MultisampledTexture_Generate_2d_f32) {
-  ast::type::F32 f32;
-  ast::type::MultisampledTexture ms(ast::type::TextureDimension::k2d, &f32);
+  ast::type::MultisampledTexture ms(ast::type::TextureDimension::k2d, ty.f32);
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(&ms), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
@@ -672,8 +618,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_1d_i32) {
-  ast::type::I32 i32;
-  ast::type::SampledTexture s(ast::type::TextureDimension::k1d, &i32);
+  ast::type::SampledTexture s(ast::type::TextureDimension::k1d, ty.i32);
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(&s), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
@@ -688,8 +633,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_1d_u32) {
-  ast::type::U32 u32;
-  ast::type::SampledTexture s(ast::type::TextureDimension::k1d, &u32);
+  ast::type::SampledTexture s(ast::type::TextureDimension::k1d, ty.u32);
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(&s), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
@@ -704,8 +648,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_1d_f32) {
-  ast::type::F32 f32;
-  ast::type::SampledTexture s(ast::type::TextureDimension::k1d, &f32);
+  ast::type::SampledTexture s(ast::type::TextureDimension::k1d, ty.f32);
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(&s), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
@@ -720,8 +663,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_1dArray) {
-  ast::type::F32 f32;
-  ast::type::SampledTexture s(ast::type::TextureDimension::k1dArray, &f32);
+  ast::type::SampledTexture s(ast::type::TextureDimension::k1dArray, ty.f32);
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(&s), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
@@ -736,8 +678,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_2d) {
-  ast::type::F32 f32;
-  ast::type::SampledTexture s(ast::type::TextureDimension::k2d, &f32);
+  ast::type::SampledTexture s(ast::type::TextureDimension::k2d, ty.f32);
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(&s), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
@@ -748,8 +689,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_2d_array) {
-  ast::type::F32 f32;
-  ast::type::SampledTexture s(ast::type::TextureDimension::k2dArray, &f32);
+  ast::type::SampledTexture s(ast::type::TextureDimension::k2dArray, ty.f32);
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(&s), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
@@ -760,8 +700,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_3d) {
-  ast::type::F32 f32;
-  ast::type::SampledTexture s(ast::type::TextureDimension::k3d, &f32);
+  ast::type::SampledTexture s(ast::type::TextureDimension::k3d, ty.f32);
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(&s), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
@@ -772,8 +711,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_Cube) {
-  ast::type::F32 f32;
-  ast::type::SampledTexture s(ast::type::TextureDimension::kCube, &f32);
+  ast::type::SampledTexture s(ast::type::TextureDimension::kCube, ty.f32);
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(&s), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
@@ -785,8 +723,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_CubeArray) {
-  ast::type::F32 f32;
-  ast::type::SampledTexture s(ast::type::TextureDimension::kCubeArray, &f32);
+  ast::type::SampledTexture s(ast::type::TextureDimension::kCubeArray, ty.f32);
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(&s), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
diff --git a/src/writer/spirv/builder_unary_op_expression_test.cc b/src/writer/spirv/builder_unary_op_expression_test.cc
index e633b48..2626f1c 100644
--- a/src/writer/spirv/builder_unary_op_expression_test.cc
+++ b/src/writer/spirv/builder_unary_op_expression_test.cc
@@ -38,13 +38,7 @@
 using BuilderTest = TestHelper;
 
 TEST_F(BuilderTest, UnaryOp_Negation_Integer) {
-  ast::type::I32 i32;
-
-  ast::UnaryOpExpression expr(
-      Source{}, ast::UnaryOp::kNegation,
-      create<ast::ScalarConstructorExpression>(
-          Source{}, create<ast::SintLiteral>(Source{}, &i32, 1)));
-
+  ast::UnaryOpExpression expr(Source{}, ast::UnaryOp::kNegation, Expr(1));
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
   b.push_function(Function{});
@@ -58,13 +52,7 @@
 }
 
 TEST_F(BuilderTest, UnaryOp_Negation_Float) {
-  ast::type::F32 f32;
-
-  ast::UnaryOpExpression expr(
-      Source{}, ast::UnaryOp::kNegation,
-      create<ast::ScalarConstructorExpression>(
-          Source{}, create<ast::FloatLiteral>(Source{}, &f32, 1)));
-
+  ast::UnaryOpExpression expr(Source{}, ast::UnaryOp::kNegation, Expr(1.f));
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
   b.push_function(Function{});
@@ -78,13 +66,7 @@
 }
 
 TEST_F(BuilderTest, UnaryOp_Not) {
-  ast::type::Bool bool_type;
-
-  ast::UnaryOpExpression expr(
-      Source{}, ast::UnaryOp::kNot,
-      create<ast::ScalarConstructorExpression>(
-          Source{}, create<ast::BoolLiteral>(Source{}, &bool_type, false)));
-
+  ast::UnaryOpExpression expr(Source{}, ast::UnaryOp::kNot, Expr(false));
   ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
   b.push_function(Function{});
@@ -98,22 +80,15 @@
 }
 
 TEST_F(BuilderTest, UnaryOp_LoadRequired) {
-  ast::type::F32 f32;
-  ast::type::Vector vec(&f32, 3);
+  auto* var = Var("param", ast::StorageClass::kFunction, ty.vec3<f32>());
 
-  ast::Variable var(Source{}, "param", ast::StorageClass::kFunction, &vec,
-                    false, nullptr, ast::VariableDecorationList{});
+  ast::UnaryOpExpression expr(Source{}, ast::UnaryOp::kNegation, Expr("param"));
 
-  ast::UnaryOpExpression expr(
-      Source{}, ast::UnaryOp::kNegation,
-      create<ast::IdentifierExpression>(Source{}, mod->RegisterSymbol("param"),
-                                        "param"));
-
-  td.RegisterVariableForTesting(&var);
+  td.RegisterVariableForTesting(var);
   EXPECT_TRUE(td.DetermineResultType(&expr)) << td.error();
 
   b.push_function(Function{});
-  EXPECT_TRUE(b.GenerateFunctionVariable(&var)) << b.error();
+  EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
   EXPECT_EQ(b.GenerateUnaryOpExpression(&expr), 6u) << b.error();
   ASSERT_FALSE(b.has_error()) << b.error();
 
diff --git a/src/writer/wgsl/generator_impl_alias_type_test.cc b/src/writer/wgsl/generator_impl_alias_type_test.cc
index 5e654ea..d1813ad 100644
--- a/src/writer/wgsl/generator_impl_alias_type_test.cc
+++ b/src/writer/wgsl/generator_impl_alias_type_test.cc
@@ -28,9 +28,8 @@
 using WgslGeneratorImplTest = TestHelper;
 
 TEST_F(WgslGeneratorImplTest, EmitAlias_F32) {
-  ast::type::Alias alias(mod->RegisterSymbol("a"), "a", ty.f32);
-
-  ASSERT_TRUE(gen.EmitConstructedType(&alias)) << gen.error();
+  auto* alias = ty.alias("a", ty.f32);
+  ASSERT_TRUE(gen.EmitConstructedType(alias)) << gen.error();
   EXPECT_EQ(gen.result(), R"(type a = f32;
 )");
 }
@@ -41,11 +40,11 @@
                             Member("b", ty.i32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("A"), "A", str);
-  ast::type::Alias alias(mod->RegisterSymbol("B"), "B", &s);
+  auto* s = ty.struct_("A", str);
+  auto* alias = ty.alias("B", s);
 
-  ASSERT_TRUE(gen.EmitConstructedType(&s)) << gen.error();
-  ASSERT_TRUE(gen.EmitConstructedType(&alias)) << gen.error();
+  ASSERT_TRUE(gen.EmitConstructedType(s)) << gen.error();
+  ASSERT_TRUE(gen.EmitConstructedType(alias)) << gen.error();
   EXPECT_EQ(gen.result(), R"(struct A {
   a : f32;
   [[offset(4)]]
@@ -61,10 +60,10 @@
                             Member("b", ty.i32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("A"), "A", str);
-  ast::type::Alias alias(mod->RegisterSymbol("B"), "B", &s);
+  auto* s = ty.struct_("A", str);
+  auto* alias = ty.alias("B", s);
 
-  ASSERT_TRUE(gen.EmitConstructedType(&alias)) << gen.error();
+  ASSERT_TRUE(gen.EmitConstructedType(alias)) << gen.error();
   EXPECT_EQ(gen.result(), R"(type B = A;
 )");
 }
diff --git a/src/writer/wgsl/generator_impl_function_test.cc b/src/writer/wgsl/generator_impl_function_test.cc
index 59b7005..2deb562 100644
--- a/src/writer/wgsl/generator_impl_function_test.cc
+++ b/src/writer/wgsl/generator_impl_function_test.cc
@@ -171,8 +171,8 @@
   auto* str = create<ast::Struct>(
       ast::StructMemberList{Member("d", ty.f32, {MemberOffset(0)})}, s_decos);
 
-  ast::type::Struct s(mod->RegisterSymbol("Data"), "Data", str);
-  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, &s);
+  auto* s = ty.struct_("Data", str);
+  ast::type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
   auto* data_var = Var("data", ast::StorageClass::kStorageBuffer, &ac, nullptr,
                        ast::VariableDecorationList{
@@ -181,7 +181,7 @@
                            create<ast::SetDecoration>(0),
                        });
 
-  mod->AddConstructedType(&s);
+  mod->AddConstructedType(s);
 
   td.RegisterVariableForTesting(data_var);
   mod->AddGlobalVariable(data_var);
diff --git a/src/writer/wgsl/generator_impl_if_test.cc b/src/writer/wgsl/generator_impl_if_test.cc
index 2bffb23..c81b5f9 100644
--- a/src/writer/wgsl/generator_impl_if_test.cc
+++ b/src/writer/wgsl/generator_impl_if_test.cc
@@ -44,19 +44,17 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_IfWithElseIf) {
-  auto* else_cond = create<ast::IdentifierExpression>(
-      mod->RegisterSymbol("else_cond"), "else_cond");
   auto* else_body = create<ast::BlockStatement>(ast::StatementList{
       create<ast::DiscardStatement>(),
   });
 
-  auto* cond = Expr("cond");
   auto* body = create<ast::BlockStatement>(ast::StatementList{
       create<ast::DiscardStatement>(),
   });
   auto* i = create<ast::IfStatement>(
-      cond, body,
-      ast::ElseStatementList{create<ast::ElseStatement>(else_cond, else_body)});
+      Expr("cond"), body,
+      ast::ElseStatementList{
+          create<ast::ElseStatement>(Expr("else_cond"), else_body)});
 
   gen.increment_indent();
 
@@ -94,9 +92,6 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_IfWithMultiple) {
-  auto* else_cond = create<ast::IdentifierExpression>(
-      mod->RegisterSymbol("else_cond"), "else_cond");
-
   auto* else_body = create<ast::BlockStatement>(ast::StatementList{
       create<ast::DiscardStatement>(),
   });
@@ -105,14 +100,13 @@
       create<ast::DiscardStatement>(),
   });
 
-  auto* cond = Expr("cond");
   auto* body = create<ast::BlockStatement>(ast::StatementList{
       create<ast::DiscardStatement>(),
   });
   auto* i = create<ast::IfStatement>(
-      cond, body,
+      Expr("cond"), body,
       ast::ElseStatementList{
-          create<ast::ElseStatement>(else_cond, else_body),
+          create<ast::ElseStatement>(Expr("else_cond"), else_body),
           create<ast::ElseStatement>(nullptr, else_body_2),
       });
 
diff --git a/src/writer/wgsl/generator_impl_type_test.cc b/src/writer/wgsl/generator_impl_type_test.cc
index 6faf4a9..4a81c29 100644
--- a/src/writer/wgsl/generator_impl_type_test.cc
+++ b/src/writer/wgsl/generator_impl_type_test.cc
@@ -48,9 +48,9 @@
 using WgslGeneratorImplTest = TestHelper;
 
 TEST_F(WgslGeneratorImplTest, EmitType_Alias) {
-  ast::type::Alias alias(mod->RegisterSymbol("alias"), "alias", ty.f32);
+  auto* alias = ty.alias("alias", ty.f32);
 
-  ASSERT_TRUE(gen.EmitType(&alias)) << gen.error();
+  ASSERT_TRUE(gen.EmitType(alias)) << gen.error();
   EXPECT_EQ(gen.result(), "alias");
 }
 
@@ -66,7 +66,7 @@
 
   auto* str =
       create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32)}, decos);
-  auto* s = create<ast::type::Struct>(mod->RegisterSymbol("S"), "S", str);
+  auto* s = ty.struct_("S", str);
 
   ast::type::AccessControl a(ast::AccessControl::kReadOnly, s);
 
@@ -81,7 +81,7 @@
 
   auto* str =
       create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32)}, decos);
-  auto* s = create<ast::type::Struct>(mod->RegisterSymbol("S"), "S", str);
+  auto* s = ty.struct_("S", str);
 
   ast::type::AccessControl a(ast::AccessControl::kReadWrite, s);
 
@@ -150,9 +150,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  ASSERT_TRUE(gen.EmitType(&s)) << gen.error();
+  auto* s = ty.struct_("S", str);
+  ASSERT_TRUE(gen.EmitType(s)) << gen.error();
   EXPECT_EQ(gen.result(), "S");
 }
 
@@ -162,9 +161,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       ast::StructDecorationList{});
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  ASSERT_TRUE(gen.EmitStructType(&s)) << gen.error();
+  auto* s = ty.struct_("S", str);
+  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
   EXPECT_EQ(gen.result(), R"(struct S {
   a : i32;
   [[offset(4)]]
@@ -182,9 +180,8 @@
                             Member("b", ty.f32, {MemberOffset(4)})},
       decos);
 
-  ast::type::Struct s(mod->RegisterSymbol("S"), "S", str);
-
-  ASSERT_TRUE(gen.EmitStructType(&s)) << gen.error();
+  auto* s = ty.struct_("S", str);
+  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
   EXPECT_EQ(gen.result(), R"([[block]]
 struct S {
   a : i32;
diff --git a/src/writer/wgsl/generator_impl_variable_test.cc b/src/writer/wgsl/generator_impl_variable_test.cc
index cbe77f6..52a7542 100644
--- a/src/writer/wgsl/generator_impl_variable_test.cc
+++ b/src/writer/wgsl/generator_impl_variable_test.cc
@@ -78,10 +78,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitVariable_Constructor) {
-  auto* ident = create<ast::IdentifierExpression>(
-      mod->RegisterSymbol("initializer"), "initializer");
-
-  auto* v = Var("a", ast::StorageClass::kNone, ty.f32, ident,
+  auto* v = Var("a", ast::StorageClass::kNone, ty.f32, Expr("initializer"),
                 ast::VariableDecorationList{});
 
   ASSERT_TRUE(gen.EmitVariable(v)) << gen.error();
@@ -90,10 +87,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitVariable_Const) {
-  auto* ident = create<ast::IdentifierExpression>(
-      mod->RegisterSymbol("initializer"), "initializer");
-
-  auto* v = Const("a", ast::StorageClass::kNone, ty.f32, ident,
+  auto* v = Const("a", ast::StorageClass::kNone, ty.f32, Expr("initializer"),
                   ast::VariableDecorationList{});
 
   ASSERT_TRUE(gen.EmitVariable(v)) << gen.error();