ProgramBuilder: Migrate any remaining types to typ::*

Used as a stepping stone to emitting the ast::Types instead.

Bug: tint:724
Change-Id: Ib2d6c150fe8aa7c1e2c502676922b14b1518a4be
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/48686
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/ast/intrinsic_texture_helper_test.cc b/src/ast/intrinsic_texture_helper_test.cc
index c0ea686..376182e 100644
--- a/src/ast/intrinsic_texture_helper_test.cc
+++ b/src/ast/intrinsic_texture_helper_test.cc
@@ -132,7 +132,7 @@
   return out;
 }
 
-sem::Type* TextureOverloadCase::resultVectorComponentType(
+typ::Type TextureOverloadCase::resultVectorComponentType(
     ProgramBuilder* b) const {
   switch (texture_data_type) {
     case ast::intrinsic::test::TextureDataType::kF32:
@@ -144,12 +144,12 @@
   }
 
   TINT_UNREACHABLE(b->Diagnostics());
-  return nullptr;
+  return {};
 }
 
 ast::Variable* TextureOverloadCase::buildTextureVariable(
     ProgramBuilder* b) const {
-  auto* datatype = resultVectorComponentType(b);
+  auto datatype = resultVectorComponentType(b);
 
   DecorationList decos = {
       b->create<ast::GroupDecoration>(0),
@@ -157,27 +157,23 @@
   };
   switch (texture_kind) {
     case ast::intrinsic::test::TextureKind::kRegular:
-      return b->Global(
-          "texture",
-          b->create<sem::SampledTexture>(texture_dimension, datatype),
-          ast::StorageClass::kUniformConstant, nullptr, decos);
+      return b->Global("texture",
+                       b->ty.sampled_texture(texture_dimension, datatype),
+                       ast::StorageClass::kUniformConstant, nullptr, decos);
 
     case ast::intrinsic::test::TextureKind::kDepth:
-      return b->Global("texture",
-                       b->create<sem::DepthTexture>(texture_dimension),
+      return b->Global("texture", b->ty.depth_texture(texture_dimension),
                        ast::StorageClass::kUniformConstant, nullptr, decos);
 
     case ast::intrinsic::test::TextureKind::kMultisampled:
       return b->Global(
           "texture",
-          b->create<sem::MultisampledTexture>(texture_dimension, datatype),
+          b->ty.multisampled_texture(texture_dimension, datatype),
           ast::StorageClass::kUniformConstant, nullptr, decos);
 
     case ast::intrinsic::test::TextureKind::kStorage: {
-      auto* st = b->create<sem::StorageTexture>(texture_dimension, image_format,
-                                                datatype);
-
-      auto* ac = b->create<sem::AccessControl>(access_control, st);
+      auto st = b->ty.storage_texture(texture_dimension, image_format);
+      auto ac = b->ty.access(access_control, st);
       return b->Global("texture", ac, ast::StorageClass::kUniformConstant,
                        nullptr, decos);
     }
@@ -193,7 +189,7 @@
       b->create<ast::GroupDecoration>(0),
       b->create<ast::BindingDecoration>(1),
   };
-  return b->Global("sampler", b->create<sem::Sampler>(sampler_kind),
+  return b->Global("sampler", b->ty.sampler(sampler_kind),
                    ast::StorageClass::kUniformConstant, nullptr, decos);
 }
 
diff --git a/src/ast/intrinsic_texture_helper_test.h b/src/ast/intrinsic_texture_helper_test.h
index d173111..886680b 100644
--- a/src/ast/intrinsic_texture_helper_test.h
+++ b/src/ast/intrinsic_texture_helper_test.h
@@ -209,7 +209,7 @@
 
   /// @param builder the AST builder used for the test
   /// @returns the vector component type of the texture function return value
-  sem::Type* resultVectorComponentType(ProgramBuilder* builder) const;
+  typ::Type resultVectorComponentType(ProgramBuilder* builder) const;
   /// @param builder the AST builder used for the test
   /// @returns a variable holding the test texture, automatically registered as
   /// a global variable.
diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc
index 6d95710..cee8df3 100644
--- a/src/inspector/inspector_test.cc
+++ b/src/inspector/inspector_test.cc
@@ -29,9 +29,7 @@
 
 class InspectorHelper : public ProgramBuilder {
  public:
-  InspectorHelper()
-      : sampler_type_(ast::SamplerKind::kSampler),
-        comparison_sampler_type_(ast::SamplerKind::kComparisonSampler) {}
+  InspectorHelper() {}
 
   /// Generates an empty function
   /// @param name name of the function created
@@ -62,7 +60,7 @@
   /// Generates a struct that contains user-defined IO members
   /// @param name the name of the generated struct
   /// @param inout_vars tuples of {name, loc} that will be the struct members
-  sem::StructType* MakeInOutStruct(
+  typ::Struct MakeInOutStruct(
       std::string name,
       std::vector<std::tuple<std::string, uint32_t>> inout_vars) {
     ast::StructMemberList members;
@@ -146,7 +144,7 @@
   /// @param val value to initialize the variable with, if NULL no initializer
   ///            will be added.
   template <class T>
-  void AddConstantID(std::string name, uint32_t id, sem::Type* type, T* val) {
+  void AddConstantID(std::string name, uint32_t id, typ::Type type, T* val) {
     ast::Expression* constructor = nullptr;
     if (val) {
       constructor =
@@ -161,28 +159,28 @@
   /// @param type AST type of the literal, must resolve to BoolLiteral
   /// @param val scalar value for the literal to contain
   /// @returns a Literal of the expected type and value
-  ast::Literal* MakeLiteral(sem::Type* type, bool* val) {
+  ast::Literal* MakeLiteral(typ::Type type, bool* 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(sem::Type* type, uint32_t* val) {
+  ast::Literal* MakeLiteral(typ::Type type, uint32_t* 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(sem::Type* type, int32_t* val) {
+  ast::Literal* MakeLiteral(typ::Type type, int32_t* 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(sem::Type* type, float* val) {
+  ast::Literal* MakeLiteral(typ::Type type, float* val) {
     return create<ast::FloatLiteral>(type, *val);
   }
 
@@ -203,7 +201,7 @@
   /// @param idx index of member
   /// @param type type of member
   /// @returns a string for the member
-  std::string StructMemberName(size_t idx, sem::Type* type) {
+  std::string StructMemberName(size_t idx, typ::Type type) {
     return std::to_string(idx) + type->type_name();
   }
 
@@ -212,11 +210,11 @@
   /// @param member_types a vector of member types
   /// @param is_block whether or not to decorate as a Block
   /// @returns a struct type
-  sem::StructType* MakeStructType(const std::string& name,
-                                  std::vector<sem::Type*> member_types,
-                                  bool is_block) {
+  typ::Struct MakeStructType(const std::string& name,
+                             std::vector<typ::Type> member_types,
+                             bool is_block) {
     ast::StructMemberList members;
-    for (auto* type : member_types) {
+    for (auto type : member_types) {
       members.push_back(Member(StructMemberName(members.size(), type), type));
     }
 
@@ -232,10 +230,9 @@
   /// @param name name for the type
   /// @param member_types a vector of member types
   /// @returns a struct type that has the layout for an uniform buffer.
-  sem::StructType* MakeUniformBufferType(const std::string& name,
-                                         std::vector<sem::Type*> member_types) {
-    auto* struct_type = MakeStructType(name, member_types, true);
-    return struct_type;
+  typ::Struct MakeUniformBufferType(const std::string& name,
+                                    std::vector<typ::Type> member_types) {
+    return MakeStructType(name, member_types, true);
   }
 
   /// Generates types appropriate for using in a storage buffer
@@ -244,12 +241,11 @@
   /// @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<sem::StructType*, sem::AccessControl*> MakeStorageBufferTypes(
+  std::tuple<typ::Struct, typ::AccessControl> MakeStorageBufferTypes(
       const std::string& name,
-      std::vector<sem::Type*> member_types) {
-    auto* struct_type = MakeStructType(name, member_types, true);
-    auto* access_type =
-        create<sem::AccessControl>(ast::AccessControl::kReadWrite, struct_type);
+      std::vector<typ::Type> member_types) {
+    auto struct_type = MakeStructType(name, member_types, true);
+    auto access_type = ty.access(ast::AccessControl::kReadWrite, struct_type);
     return {struct_type, std::move(access_type)};
   }
 
@@ -259,12 +255,11 @@
   /// @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<sem::StructType*, sem::AccessControl*>
-  MakeReadOnlyStorageBufferTypes(const std::string& name,
-                                 std::vector<sem::Type*> member_types) {
-    auto* struct_type = MakeStructType(name, member_types, true);
-    auto* access_type =
-        create<sem::AccessControl>(ast::AccessControl::kReadOnly, struct_type);
+  std::tuple<typ::Struct, typ::AccessControl> MakeReadOnlyStorageBufferTypes(
+      const std::string& name,
+      std::vector<typ::Type> member_types) {
+    auto struct_type = MakeStructType(name, member_types, true);
+    auto access_type = ty.access(ast::AccessControl::kReadOnly, struct_type);
     return {struct_type, std::move(access_type)};
   }
 
@@ -275,7 +270,7 @@
   /// @param group the binding and group to use for the uniform buffer
   /// @param binding the binding number to use for the uniform buffer
   void AddBinding(const std::string& name,
-                  sem::Type* type,
+                  typ::Type type,
                   ast::StorageClass storage_class,
                   uint32_t group,
                   uint32_t binding) {
@@ -292,7 +287,7 @@
   /// @param group the binding/group/ to use for the uniform buffer
   /// @param binding the binding number to use for the uniform buffer
   void AddUniformBuffer(const std::string& name,
-                        sem::Type* type,
+                        typ::Type type,
                         uint32_t group,
                         uint32_t binding) {
     AddBinding(name, type, ast::StorageClass::kUniform, group, binding);
@@ -304,7 +299,7 @@
   /// @param group the binding/group to use for the storage buffer
   /// @param binding the binding number to use for the storage buffer
   void AddStorageBuffer(const std::string& name,
-                        sem::Type* type,
+                        typ::Type type,
                         uint32_t group,
                         uint32_t binding) {
     AddBinding(name, type, ast::StorageClass::kStorage, group, binding);
@@ -317,11 +312,11 @@
   void MakeStructVariableReferenceBodyFunction(
       std::string func_name,
       std::string struct_name,
-      std::vector<std::tuple<size_t, sem::Type*>> members) {
+      std::vector<std::tuple<size_t, typ::Type>> members) {
     ast::StatementList stmts;
     for (auto member : members) {
       size_t member_idx;
-      sem::Type* member_type;
+      typ::Type member_type;
       std::tie(member_idx, member_type) = member;
       std::string member_name = StructMemberName(member_idx, member_type);
 
@@ -331,7 +326,7 @@
 
     for (auto member : members) {
       size_t member_idx;
-      sem::Type* member_type;
+      typ::Type member_type;
       std::tie(member_idx, member_type) = member;
       std::string member_name = StructMemberName(member_idx, member_type);
 
@@ -369,26 +364,26 @@
   /// @param dim the dimensions of the texture
   /// @param type the data type of the sampled texture
   /// @returns the generated SampleTextureType
-  sem::SampledTexture* MakeSampledTextureType(ast::TextureDimension dim,
-                                              sem::Type* type) {
-    return create<sem::SampledTexture>(dim, type);
+  typ::SampledTexture MakeSampledTextureType(ast::TextureDimension dim,
+                                             typ::Type type) {
+    return ty.sampled_texture(dim, type);
   }
 
   /// Generates a DepthTexture appropriate for the params
   /// @param dim the dimensions of the texture
   /// @returns the generated DepthTexture
-  sem::DepthTexture* MakeDepthTextureType(ast::TextureDimension dim) {
-    return create<sem::DepthTexture>(dim);
+  typ::DepthTexture MakeDepthTextureType(ast::TextureDimension dim) {
+    return ty.depth_texture(dim);
   }
 
   /// Generates a MultisampledTexture appropriate for the params
   /// @param dim the dimensions of the texture
   /// @param type the data type of the sampled texture
   /// @returns the generated SampleTextureType
-  sem::MultisampledTexture* MakeMultisampledTextureType(
+  typ::MultisampledTexture MakeMultisampledTextureType(
       ast::TextureDimension dim,
-      sem::Type* type) {
-    return create<sem::MultisampledTexture>(dim, type);
+      typ::Type type) {
+    return ty.multisampled_texture(dim, type);
   }
 
   /// Adds a sampled texture variable to the program
@@ -397,7 +392,7 @@
   /// @param group the binding/group to use for the sampled texture
   /// @param binding the binding number to use for the sampled texture
   void AddSampledTexture(const std::string& name,
-                         sem::Type* type,
+                         typ::Type type,
                          uint32_t group,
                          uint32_t binding) {
     AddBinding(name, type, ast::StorageClass::kUniformConstant, group, binding);
@@ -409,13 +404,13 @@
   /// @param group the binding/group to use for the multi-sampled texture
   /// @param binding the binding number to use for the multi-sampled texture
   void AddMultisampledTexture(const std::string& name,
-                              sem::Type* type,
+                              typ::Type type,
                               uint32_t group,
                               uint32_t binding) {
     AddBinding(name, type, ast::StorageClass::kUniformConstant, group, binding);
   }
 
-  void AddGlobalVariable(const std::string& name, sem::Type* type) {
+  void AddGlobalVariable(const std::string& name, typ::Type type) {
     Global(name, type, ast::StorageClass::kUniformConstant);
   }
 
@@ -425,7 +420,7 @@
   /// @param group the binding/group to use for the depth texture
   /// @param binding the binding number to use for the depth texture
   void AddDepthTexture(const std::string& name,
-                       sem::Type* type,
+                       typ::Type type,
                        uint32_t group,
                        uint32_t binding) {
     AddBinding(name, type, ast::StorageClass::kUniformConstant, group, binding);
@@ -444,12 +439,12 @@
       const std::string& texture_name,
       const std::string& sampler_name,
       const std::string& coords_name,
-      sem::Type* base_type,
+      typ::Type base_type,
       ast::DecorationList decorations) {
     std::string result_name = "sampler_result";
 
     ast::StatementList stmts;
-    stmts.emplace_back(Decl(Var("sampler_result", vec_type(base_type, 4),
+    stmts.emplace_back(Decl(Var("sampler_result", ty.vec(base_type, 4),
                                 ast::StorageClass::kFunction)));
 
     stmts.emplace_back(
@@ -475,13 +470,13 @@
       const std::string& sampler_name,
       const std::string& coords_name,
       const std::string& array_index,
-      sem::Type* base_type,
+      typ::Type base_type,
       ast::DecorationList decorations) {
     std::string result_name = "sampler_result";
 
     ast::StatementList stmts;
 
-    stmts.emplace_back(Decl(Var("sampler_result", vec_type(base_type, 4),
+    stmts.emplace_back(Decl(Var("sampler_result", ty.vec(base_type, 4),
                                 ast::StorageClass::kFunction)));
 
     stmts.emplace_back(
@@ -508,7 +503,7 @@
       const std::string& sampler_name,
       const std::string& coords_name,
       const std::string& depth_name,
-      sem::Type* base_type,
+      typ::Type base_type,
       ast::DecorationList decorations) {
     std::string result_name = "sampler_result";
 
@@ -527,7 +522,7 @@
   /// Gets an appropriate type for the data in a given texture type.
   /// @param sampled_kind type of in the texture
   /// @returns a pointer to a type appropriate for the coord param
-  sem::Type* GetBaseType(ResourceBinding::SampledKind sampled_kind) {
+  typ::Type GetBaseType(ResourceBinding::SampledKind sampled_kind) {
     switch (sampled_kind) {
       case ResourceBinding::SampledKind::kFloat:
         return ty.f32();
@@ -545,7 +540,7 @@
   /// @param dim dimensionality of the texture being sampled
   /// @param scalar the scalar type
   /// @returns a pointer to a type appropriate for the coord param
-  sem::Type* GetCoordsType(ast::TextureDimension dim, sem::Type* scalar) {
+  typ::Type GetCoordsType(ast::TextureDimension dim, typ::Type scalar) {
     switch (dim) {
       case ast::TextureDimension::k1d:
         return scalar;
@@ -566,11 +561,11 @@
   /// @param dim the texture dimension of the storage texture
   /// @param format the image format of the storage texture
   /// @returns the storage texture type and subtype
-  std::tuple<sem::StorageTexture*, sem::Type*> MakeStorageTextureTypes(
+  std::tuple<typ::StorageTexture, typ::Type> MakeStorageTextureTypes(
       ast::TextureDimension dim,
       ast::ImageFormat format) {
-    sem::Type* subtype = sem::StorageTexture::SubtypeFor(format, Types());
-    return {create<sem::StorageTexture>(dim, format, subtype), subtype};
+    auto tex = ty.storage_texture(dim, format);
+    return {tex, {tex.ast->type(), tex.sem->type()}};
   }
 
   /// Generates appropriate types for a Read-Only StorageTexture
@@ -578,17 +573,16 @@
   /// @param format the image format of the storage texture
   /// @param read_only should the access type be read only, otherwise write only
   /// @returns the storage texture type, subtype & access control type
-  std::tuple<sem::StorageTexture*, sem::Type*, sem::AccessControl*>
+  std::tuple<typ::StorageTexture, typ::Type, typ::AccessControl>
   MakeStorageTextureTypes(ast::TextureDimension dim,
                           ast::ImageFormat format,
                           bool read_only) {
-    sem::StorageTexture* texture_type;
-    sem::Type* subtype;
+    typ::StorageTexture texture_type;
+    typ::Type subtype;
     std::tie(texture_type, subtype) = MakeStorageTextureTypes(dim, format);
-    auto* access_control =
-        create<sem::AccessControl>(read_only ? ast::AccessControl::kReadOnly
-                                             : ast::AccessControl::kWriteOnly,
-                                   texture_type);
+    auto access_control = ty.access(read_only ? ast::AccessControl::kReadOnly
+                                              : ast::AccessControl::kWriteOnly,
+                                    texture_type);
     return {texture_type, subtype, access_control};
   }
 
@@ -598,7 +592,7 @@
   /// @param group the binding/group to use for the sampled texture
   /// @param binding the binding number to use for the sampled texture
   void AddStorageTexture(const std::string& name,
-                         sem::Type* type,
+                         typ::Type type,
                          uint32_t group,
                          uint32_t binding) {
     AddBinding(name, type, ast::StorageClass::kUniformConstant, group, binding);
@@ -613,7 +607,7 @@
   ast::Function* MakeStorageTextureBodyFunction(
       const std::string& func_name,
       const std::string& st_name,
-      sem::Type* dim_type,
+      typ::Type dim_type,
       ast::DecorationList decorations) {
     ast::StatementList stmts;
 
@@ -638,34 +632,14 @@
     return *inspector_;
   }
 
-  sem::ArrayType* u32_array_type(uint32_t count) {
-    if (array_type_memo_.find(count) == array_type_memo_.end()) {
-      array_type_memo_[count] =
-          create<sem::ArrayType>(ty.u32(), count,
-                                 ast::DecorationList{
-                                     create<ast::StrideDecoration>(4),
-                                 });
-    }
-    return array_type_memo_[count];
+  typ::Sampler sampler_type() { return ty.sampler(ast::SamplerKind::kSampler); }
+  typ::Sampler comparison_sampler_type() {
+    return ty.sampler(ast::SamplerKind::kComparisonSampler);
   }
-  sem::Vector* vec_type(sem::Type* type, uint32_t count) {
-    if (vector_type_memo_.find(std::tie(type, count)) ==
-        vector_type_memo_.end()) {
-      vector_type_memo_[std::tie(type, count)] =
-          create<sem::Vector>(type, count);
-    }
-    return vector_type_memo_[std::tie(type, count)];
-  }
-  sem::Sampler* sampler_type() { return &sampler_type_; }
-  sem::Sampler* comparison_sampler_type() { return &comparison_sampler_type_; }
 
  private:
   std::unique_ptr<Program> program_;
   std::unique_ptr<Inspector> inspector_;
-  sem::Sampler sampler_type_;
-  sem::Sampler comparison_sampler_type_;
-  std::map<uint32_t, sem::ArrayType*> array_type_memo_;
-  std::map<std::tuple<sem::Type*, uint32_t>, sem::Vector*> vector_type_memo_;
 };
 
 class InspectorGetEntryPointTest : public InspectorHelper,
@@ -889,7 +863,7 @@
 
 TEST_P(InspectorGetEntryPointTestWithComponentTypeParam, InOutVariables) {
   ComponentType inspector_type = GetParam();
-  sem::Type* tint_type = nullptr;
+  typ::Type tint_type = nullptr;
   switch (inspector_type) {
     case ComponentType::kFloat:
       tint_type = ty.f32();
@@ -1031,7 +1005,7 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, InOutStruct) {
-  auto* interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}});
+  auto interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}});
   Func("foo", {Param("param", interface)}, interface, {Return("param")},
        {Stage(ast::PipelineStage::kFragment)});
   Inspector& inspector = Build();
@@ -1063,7 +1037,7 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutSharedStruct) {
-  auto* interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}});
+  auto interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}});
   Func("foo", {}, interface, {Return(Construct(interface))},
        {Stage(ast::PipelineStage::kFragment)});
   Func("bar", {Param("param", interface)}, ty.void_(), {},
@@ -1101,8 +1075,8 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, MixInOutVariablesAndStruct) {
-  auto* struct_a = MakeInOutStruct("struct_a", {{"a", 0u}, {"b", 1u}});
-  auto* struct_b = MakeInOutStruct("struct_b", {{"a", 2u}});
+  auto struct_a = MakeInOutStruct("struct_a", {{"a", 0u}, {"b", 1u}});
+  auto struct_b = MakeInOutStruct("struct_b", {{"a", 2u}});
   Func("foo",
        {Param("param_a", struct_a), Param("param_b", struct_b),
         Param("param_c", ty.f32(), {Location(3u)}),
@@ -1650,27 +1624,26 @@
 }
 
 TEST_F(InspectorGetResourceBindingsTest, Simple) {
-  sem::StructType* ub_struct_type =
-      MakeUniformBufferType("ub_type", {ty.i32()});
+  typ::Struct ub_struct_type = MakeUniformBufferType("ub_type", {ty.i32()});
   AddUniformBuffer("ub_var", ub_struct_type, 0, 0);
   MakeStructVariableReferenceBodyFunction("ub_func", "ub_var", {{0, ty.i32()}});
 
-  sem::StructType* sb_struct_type;
-  sem::AccessControl* sb_control_type;
+  typ::Struct sb_struct_type;
+  typ::AccessControl sb_control_type;
   std::tie(sb_struct_type, sb_control_type) =
       MakeStorageBufferTypes("sb_type", {ty.i32()});
   AddStorageBuffer("sb_var", sb_control_type, 1, 0);
   MakeStructVariableReferenceBodyFunction("sb_func", "sb_var", {{0, ty.i32()}});
 
-  sem::StructType* rosb_struct_type;
-  sem::AccessControl* rosb_control_type;
+  typ::Struct rosb_struct_type;
+  typ::AccessControl rosb_control_type;
   std::tie(rosb_struct_type, rosb_control_type) =
       MakeReadOnlyStorageBufferTypes("rosb_type", {ty.i32()});
   AddStorageBuffer("rosb_var", rosb_control_type, 1, 1);
   MakeStructVariableReferenceBodyFunction("rosb_func", "rosb_var",
                                           {{0, ty.i32()}});
 
-  auto* s_texture_type =
+  auto s_texture_type =
       MakeSampledTextureType(ast::TextureDimension::k1d, ty.f32());
   AddSampledTexture("s_texture", s_texture_type, 2, 0);
   AddSampler("s_var", 3, 0);
@@ -1678,8 +1651,7 @@
   MakeSamplerReferenceBodyFunction("s_func", "s_texture", "s_var", "s_coords",
                                    ty.f32(), {});
 
-  auto* cs_depth_texture_type =
-      MakeDepthTextureType(ast::TextureDimension::k2d);
+  auto cs_depth_texture_type = MakeDepthTextureType(ast::TextureDimension::k2d);
   AddDepthTexture("cs_texture", cs_depth_texture_type, 3, 1);
   AddComparisonSampler("cs_var", 3, 2);
   AddGlobalVariable("cs_coords", ty.vec2<f32>());
@@ -1687,17 +1659,17 @@
   MakeComparisonSamplerReferenceBodyFunction(
       "cs_func", "cs_texture", "cs_var", "cs_coords", "cs_depth", ty.f32(), {});
 
-  sem::StorageTexture* st_type;
-  sem::Type* st_subtype;
-  sem::AccessControl* st_ac;
+  typ::StorageTexture st_type;
+  typ::Type st_subtype;
+  typ::AccessControl st_ac;
   std::tie(st_type, st_subtype, st_ac) = MakeStorageTextureTypes(
       ast::TextureDimension::k2d, ast::ImageFormat::kR32Uint, false);
   AddStorageTexture("st_var", st_ac, 4, 0);
   MakeStorageTextureBodyFunction("st_func", "st_var", ty.vec2<i32>(), {});
 
-  sem::StorageTexture* rost_type;
-  sem::Type* rost_subtype;
-  sem::AccessControl* rost_ac;
+  typ::StorageTexture rost_type;
+  typ::Type rost_subtype;
+  typ::AccessControl rost_ac;
   std::tie(rost_type, rost_subtype, rost_ac) = MakeStorageTextureTypes(
       ast::TextureDimension::k2d, ast::ImageFormat::kR32Uint, true);
   AddStorageTexture("rost_var", rost_ac, 4, 1);
@@ -1771,8 +1743,7 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, NonEntryPointFunc) {
-  sem::StructType* foo_struct_type =
-      MakeUniformBufferType("foo_type", {ty.i32()});
+  typ::Struct foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()});
   AddUniformBuffer("foo_ub", foo_struct_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
@@ -1790,8 +1761,7 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, Simple) {
-  sem::StructType* foo_struct_type =
-      MakeUniformBufferType("foo_type", {ty.i32()});
+  typ::Struct foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()});
   AddUniformBuffer("foo_ub", foo_struct_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
@@ -1816,7 +1786,7 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleMembers) {
-  sem::StructType* foo_struct_type =
+  typ::Struct foo_struct_type =
       MakeUniformBufferType("foo_type", {ty.i32(), ty.u32(), ty.f32()});
   AddUniformBuffer("foo_ub", foo_struct_type, 0, 0);
 
@@ -1843,7 +1813,7 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, ContainingPadding) {
-  sem::StructType* foo_struct_type =
+  typ::Struct foo_struct_type =
       MakeUniformBufferType("foo_type", {ty.vec3<f32>()});
   AddUniformBuffer("foo_ub", foo_struct_type, 0, 0);
 
@@ -1870,7 +1840,7 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleUniformBuffers) {
-  sem::StructType* ub_struct_type =
+  typ::Struct ub_struct_type =
       MakeUniformBufferType("ub_type", {ty.i32(), ty.u32(), ty.f32()});
   AddUniformBuffer("ub_foo", ub_struct_type, 0, 0);
   AddUniformBuffer("ub_bar", ub_struct_type, 0, 1);
@@ -1928,8 +1898,8 @@
   // TODO(bclayton) - This is not a legal structure layout for uniform buffer
   // usage. Once crbug.com/tint/628 is implemented, this will fail validation
   // and will need to be fixed.
-  sem::StructType* foo_struct_type =
-      MakeUniformBufferType("foo_type", {ty.i32(), u32_array_type(4)});
+  typ::Struct foo_struct_type =
+      MakeUniformBufferType("foo_type", {ty.i32(), ty.array<u32, 4>()});
   AddUniformBuffer("foo_ub", foo_struct_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
@@ -1954,8 +1924,8 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, Simple) {
-  sem::StructType* foo_struct_type;
-  sem::AccessControl* foo_control_type;
+  typ::Struct foo_struct_type;
+  typ::AccessControl foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeStorageBufferTypes("foo_type", {ty.i32()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
@@ -1982,8 +1952,8 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleMembers) {
-  sem::StructType* foo_struct_type;
-  sem::AccessControl* foo_control_type;
+  typ::Struct foo_struct_type;
+  typ::AccessControl foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeStorageBufferTypes("foo_type", {ty.i32(), ty.u32(), ty.f32()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
@@ -2011,8 +1981,8 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleStorageBuffers) {
-  sem::StructType* sb_struct_type;
-  sem::AccessControl* sb_control_type;
+  typ::Struct sb_struct_type;
+  typ::AccessControl sb_control_type;
   std::tie(sb_struct_type, sb_control_type) =
       MakeStorageBufferTypes("sb_type", {ty.i32(), ty.u32(), ty.f32()});
   AddStorageBuffer("sb_foo", sb_control_type, 0, 0);
@@ -2072,10 +2042,10 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingArray) {
-  sem::StructType* foo_struct_type;
-  sem::AccessControl* foo_control_type;
+  typ::Struct foo_struct_type;
+  typ::AccessControl foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
-      MakeStorageBufferTypes("foo_type", {ty.i32(), u32_array_type(4)});
+      MakeStorageBufferTypes("foo_type", {ty.i32(), ty.array<u32, 4>()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
@@ -2100,10 +2070,10 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingRuntimeArray) {
-  sem::StructType* foo_struct_type;
-  sem::AccessControl* foo_control_type;
+  typ::Struct foo_struct_type;
+  typ::AccessControl foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
-      MakeStorageBufferTypes("foo_type", {ty.i32(), u32_array_type(0)});
+      MakeStorageBufferTypes("foo_type", {ty.i32(), ty.array<u32>()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
@@ -2128,8 +2098,8 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingPadding) {
-  sem::StructType* foo_struct_type;
-  sem::AccessControl* foo_control_type;
+  typ::Struct foo_struct_type;
+  typ::AccessControl foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeStorageBufferTypes("foo_type", {ty.vec3<f32>()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
@@ -2157,8 +2127,8 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, SkipReadOnly) {
-  sem::StructType* foo_struct_type;
-  sem::AccessControl* foo_control_type;
+  typ::Struct foo_struct_type;
+  typ::AccessControl foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
@@ -2178,8 +2148,8 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, Simple) {
-  sem::StructType* foo_struct_type;
-  sem::AccessControl* foo_control_type;
+  typ::Struct foo_struct_type;
+  typ::AccessControl foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
@@ -2207,8 +2177,8 @@
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest,
        MultipleStorageBuffers) {
-  sem::StructType* sb_struct_type;
-  sem::AccessControl* sb_control_type;
+  typ::Struct sb_struct_type;
+  typ::AccessControl sb_control_type;
   std::tie(sb_struct_type, sb_control_type) =
       MakeReadOnlyStorageBufferTypes("sb_type", {ty.i32(), ty.u32(), ty.f32()});
   AddStorageBuffer("sb_foo", sb_control_type, 0, 0);
@@ -2268,10 +2238,10 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, ContainingArray) {
-  sem::StructType* foo_struct_type;
-  sem::AccessControl* foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) =
-      MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32(), u32_array_type(4)});
+  typ::Struct foo_struct_type;
+  typ::AccessControl foo_control_type;
+  std::tie(foo_struct_type, foo_control_type) = MakeReadOnlyStorageBufferTypes(
+      "foo_type", {ty.i32(), ty.array<u32, 4>()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
@@ -2297,10 +2267,10 @@
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest,
        ContainingRuntimeArray) {
-  sem::StructType* foo_struct_type;
-  sem::AccessControl* foo_control_type;
+  typ::Struct foo_struct_type;
+  typ::AccessControl foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
-      MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32(), u32_array_type(0)});
+      MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32(), ty.array<u32>()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
@@ -2325,8 +2295,8 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, SkipNonReadOnly) {
-  sem::StructType* foo_struct_type;
-  sem::AccessControl* foo_control_type;
+  typ::Struct foo_struct_type;
+  typ::AccessControl foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
       MakeStorageBufferTypes("foo_type", {ty.i32()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
@@ -2346,7 +2316,7 @@
 }
 
 TEST_F(InspectorGetSamplerResourceBindingsTest, Simple) {
-  auto* sampled_texture_type =
+  auto sampled_texture_type =
       MakeSampledTextureType(ast::TextureDimension::k1d, ty.f32());
   AddSampledTexture("foo_texture", sampled_texture_type, 0, 0);
   AddSampler("foo_sampler", 0, 1);
@@ -2383,7 +2353,7 @@
 }
 
 TEST_F(InspectorGetSamplerResourceBindingsTest, InFunction) {
-  auto* sampled_texture_type =
+  auto sampled_texture_type =
       MakeSampledTextureType(ast::TextureDimension::k1d, ty.f32());
   AddSampledTexture("foo_texture", sampled_texture_type, 0, 0);
   AddSampler("foo_sampler", 0, 1);
@@ -2409,7 +2379,7 @@
 }
 
 TEST_F(InspectorGetSamplerResourceBindingsTest, UnknownEntryPoint) {
-  auto* sampled_texture_type =
+  auto sampled_texture_type =
       MakeSampledTextureType(ast::TextureDimension::k1d, ty.f32());
   AddSampledTexture("foo_texture", sampled_texture_type, 0, 0);
   AddSampler("foo_sampler", 0, 1);
@@ -2428,7 +2398,7 @@
 }
 
 TEST_F(InspectorGetSamplerResourceBindingsTest, SkipsComparisonSamplers) {
-  auto* depth_texture_type = MakeDepthTextureType(ast::TextureDimension::k2d);
+  auto depth_texture_type = MakeDepthTextureType(ast::TextureDimension::k2d);
   AddDepthTexture("foo_texture", depth_texture_type, 0, 0);
   AddComparisonSampler("foo_sampler", 0, 1);
   AddGlobalVariable("foo_coords", ty.vec2<f32>());
@@ -2449,7 +2419,7 @@
 }
 
 TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, Simple) {
-  auto* depth_texture_type = MakeDepthTextureType(ast::TextureDimension::k2d);
+  auto depth_texture_type = MakeDepthTextureType(ast::TextureDimension::k2d);
   AddDepthTexture("foo_texture", depth_texture_type, 0, 0);
   AddComparisonSampler("foo_sampler", 0, 1);
   AddGlobalVariable("foo_coords", ty.vec2<f32>());
@@ -2487,7 +2457,7 @@
 }
 
 TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, InFunction) {
-  auto* depth_texture_type = MakeDepthTextureType(ast::TextureDimension::k2d);
+  auto depth_texture_type = MakeDepthTextureType(ast::TextureDimension::k2d);
   AddDepthTexture("foo_texture", depth_texture_type, 0, 0);
   AddComparisonSampler("foo_sampler", 0, 1);
   AddGlobalVariable("foo_coords", ty.vec2<f32>());
@@ -2515,7 +2485,7 @@
 }
 
 TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, UnknownEntryPoint) {
-  auto* depth_texture_type = MakeDepthTextureType(ast::TextureDimension::k2d);
+  auto depth_texture_type = MakeDepthTextureType(ast::TextureDimension::k2d);
   AddDepthTexture("foo_texture", depth_texture_type, 0, 0);
   AddComparisonSampler("foo_sampler", 0, 1);
   AddGlobalVariable("foo_coords", ty.vec2<f32>());
@@ -2534,7 +2504,7 @@
 }
 
 TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, SkipsSamplers) {
-  auto* sampled_texture_type =
+  auto sampled_texture_type =
       MakeSampledTextureType(ast::TextureDimension::k1d, ty.f32());
   AddSampledTexture("foo_texture", sampled_texture_type, 0, 0);
   AddSampler("foo_sampler", 0, 1);
@@ -2568,11 +2538,11 @@
 }
 
 TEST_P(InspectorGetSampledTextureResourceBindingsTestWithParam, textureSample) {
-  auto* sampled_texture_type = MakeSampledTextureType(
+  auto sampled_texture_type = MakeSampledTextureType(
       GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
   AddSampledTexture("foo_texture", sampled_texture_type, 0, 0);
   AddSampler("foo_sampler", 0, 1);
-  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
+  auto coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
   AddGlobalVariable("foo_coords", coord_type);
 
   MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
@@ -2626,11 +2596,11 @@
 
 TEST_P(InspectorGetSampledArrayTextureResourceBindingsTestWithParam,
        textureSample) {
-  auto* sampled_texture_type = MakeSampledTextureType(
+  auto sampled_texture_type = MakeSampledTextureType(
       GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
   AddSampledTexture("foo_texture", sampled_texture_type, 0, 0);
   AddSampler("foo_sampler", 0, 1);
-  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
+  auto coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
   AddGlobalVariable("foo_coords", coord_type);
   AddGlobalVariable("foo_array_index", ty.i32());
 
@@ -2670,10 +2640,10 @@
 
 TEST_P(InspectorGetMultisampledTextureResourceBindingsTestWithParam,
        textureLoad) {
-  auto* multisampled_texture_type = MakeMultisampledTextureType(
+  auto multisampled_texture_type = MakeMultisampledTextureType(
       GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
   AddMultisampledTexture("foo_texture", multisampled_texture_type, 0, 0);
-  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.i32());
+  auto coord_type = GetCoordsType(GetParam().type_dim, ty.i32());
   AddGlobalVariable("foo_coords", coord_type);
   AddGlobalVariable("foo_sample_index", ty.i32());
 
@@ -2739,11 +2709,11 @@
 
 TEST_P(InspectorGetMultisampledArrayTextureResourceBindingsTestWithParam,
        DISABLED_textureSample) {
-  auto* multisampled_texture_type = MakeMultisampledTextureType(
+  auto multisampled_texture_type = MakeMultisampledTextureType(
       GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
   AddMultisampledTexture("foo_texture", multisampled_texture_type, 0, 0);
   AddSampler("foo_sampler", 0, 1);
-  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
+  auto coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
   AddGlobalVariable("foo_coords", coord_type);
   AddGlobalVariable("foo_array_index", ty.i32());
 
@@ -2816,14 +2786,14 @@
   ResourceBinding::SampledKind expected_kind;
   std::tie(format, expected_format, expected_kind) = format_params;
 
-  sem::StorageTexture* st_type;
-  sem::Type* st_subtype;
-  sem::AccessControl* ac;
+  typ::StorageTexture st_type;
+  typ::Type st_subtype;
+  typ::AccessControl ac;
   std::tie(st_type, st_subtype, ac) =
       MakeStorageTextureTypes(dim, format, read_only);
   AddStorageTexture("st_var", ac, 0, 0);
 
-  sem::Type* dim_type = nullptr;
+  typ::Type dim_type = nullptr;
   switch (dim) {
     case ast::TextureDimension::k1d:
       dim_type = ty.i32();
@@ -2935,7 +2905,7 @@
 
 TEST_P(InspectorGetDepthTextureResourceBindingsTestWithParam,
        textureDimensions) {
-  auto* depth_texture_type = MakeDepthTextureType(GetParam().type_dim);
+  auto depth_texture_type = MakeDepthTextureType(GetParam().type_dim);
   AddDepthTexture("dt", depth_texture_type, 0, 0);
   AddGlobalVariable("dt_level", ty.i32());
 
diff --git a/src/intrinsic_table_test.cc b/src/intrinsic_table_test.cc
index 1e7e51d..a7a9f00 100644
--- a/src/intrinsic_table_test.cc
+++ b/src/intrinsic_table_test.cc
@@ -69,7 +69,7 @@
 }
 
 TEST_F(IntrinsicTableTest, MatchI32) {
-  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k1d, ty.f32());
+  auto tex = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
   auto result = table->Lookup(*this, IntrinsicType::kTextureLoad,
                               {tex, ty.i32(), ty.i32()}, Source{});
   ASSERT_NE(result.intrinsic, nullptr);
@@ -83,7 +83,7 @@
 }
 
 TEST_F(IntrinsicTableTest, MismatchI32) {
-  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k1d, ty.f32());
+  auto tex = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
   auto result = table->Lookup(*this, IntrinsicType::kTextureLoad,
                               {tex, ty.f32()}, Source{});
   ASSERT_EQ(result.intrinsic, nullptr);
@@ -219,8 +219,8 @@
 }
 
 TEST_F(IntrinsicTableTest, MatchSampler) {
-  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, ty.f32());
-  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kSampler);
+  auto tex = ty.sampled_texture(ast::TextureDimension::k2d, ty.f32());
+  auto sampler = ty.sampler(ast::SamplerKind::kSampler);
   auto result = table->Lookup(*this, IntrinsicType::kTextureSample,
                               {tex, sampler, ty.vec2<f32>()}, Source{});
   ASSERT_NE(result.intrinsic, nullptr);
@@ -235,7 +235,7 @@
 }
 
 TEST_F(IntrinsicTableTest, MismatchSampler) {
-  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, ty.f32());
+  auto tex = ty.sampled_texture(ast::TextureDimension::k2d, ty.f32());
   auto result = table->Lookup(*this, IntrinsicType::kTextureSample,
                               {tex, ty.f32(), ty.vec2<f32>()}, Source{});
   ASSERT_EQ(result.intrinsic, nullptr);
@@ -243,7 +243,7 @@
 }
 
 TEST_F(IntrinsicTableTest, MatchSampledTexture) {
-  auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, ty.f32());
+  auto tex = ty.sampled_texture(ast::TextureDimension::k2d, ty.f32());
   auto result = table->Lookup(*this, IntrinsicType::kTextureLoad,
                               {tex, ty.vec2<i32>(), ty.i32()}, Source{});
   ASSERT_NE(result.intrinsic, nullptr);
@@ -272,7 +272,7 @@
 }
 
 TEST_F(IntrinsicTableTest, MatchDepthTexture) {
-  auto* tex = create<sem::DepthTexture>(ast::TextureDimension::k2d);
+  auto tex = ty.depth_texture(ast::TextureDimension::k2d);
   auto result = table->Lookup(*this, IntrinsicType::kTextureLoad,
                               {tex, ty.vec2<i32>(), ty.i32()}, Source{});
   ASSERT_NE(result.intrinsic, nullptr);
@@ -286,10 +286,9 @@
 }
 
 TEST_F(IntrinsicTableTest, MatchROStorageTexture) {
-  auto* tex = create<sem::StorageTexture>(
-      ast::TextureDimension::k2d, ast::ImageFormat::kR16Float,
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR16Float, Types()));
-  auto* tex_ac = create<sem::AccessControl>(ast::AccessControl::kReadOnly, tex);
+  auto tex = ty.storage_texture(ast::TextureDimension::k2d,
+                                ast::ImageFormat::kR16Float);
+  auto tex_ac = ty.access(ast::AccessControl::kReadOnly, tex);
   auto result = table->Lookup(*this, IntrinsicType::kTextureLoad,
                               {tex_ac, ty.vec2<i32>()}, Source{});
   ASSERT_NE(result.intrinsic, nullptr);
@@ -303,11 +302,9 @@
 }
 
 TEST_F(IntrinsicTableTest, MatchWOStorageTexture) {
-  auto* tex = create<sem::StorageTexture>(
-      ast::TextureDimension::k2d, ast::ImageFormat::kR16Float,
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR16Float, Types()));
-  auto* tex_ac =
-      create<sem::AccessControl>(ast::AccessControl::kWriteOnly, tex);
+  auto tex = ty.storage_texture(ast::TextureDimension::k2d,
+                                ast::ImageFormat::kR16Float);
+  auto tex_ac = ty.access(ast::AccessControl::kWriteOnly, tex);
   auto result =
       table->Lookup(*this, IntrinsicType::kTextureStore,
                     {tex_ac, ty.vec2<i32>(), ty.vec4<f32>()}, Source{});
@@ -340,9 +337,9 @@
 }
 
 TEST_F(IntrinsicTableTest, MatchWithAliasUnwrapping) {
-  auto* alias_a = ty.alias("alias_a", ty.f32());
-  auto* alias_b = ty.alias("alias_b", alias_a);
-  auto* alias_c = ty.alias("alias_c", alias_b);
+  auto alias_a = ty.alias("alias_a", ty.f32());
+  auto alias_b = ty.alias("alias_b", alias_a);
+  auto alias_c = ty.alias("alias_c", alias_b);
   auto result = table->Lookup(*this, IntrinsicType::kCos, {alias_c}, Source{});
   ASSERT_NE(result.intrinsic, nullptr);
   ASSERT_EQ(result.diagnostics.str(), "");
@@ -352,12 +349,12 @@
 }
 
 TEST_F(IntrinsicTableTest, MatchWithNestedAliasUnwrapping) {
-  auto* alias_a = ty.alias("alias_a", ty.bool_());
-  auto* alias_b = ty.alias("alias_b", alias_a);
-  auto* alias_c = ty.alias("alias_c", alias_b);
+  auto alias_a = ty.alias("alias_a", ty.bool_());
+  auto alias_b = ty.alias("alias_b", alias_a);
+  auto alias_c = ty.alias("alias_c", alias_b);
   auto vec4_of_c = ty.vec4(alias_c);
-  auto* alias_d = ty.alias("alias_d", vec4_of_c);
-  auto* alias_e = ty.alias("alias_e", alias_d);
+  auto alias_d = ty.alias("alias_d", vec4_of_c);
+  auto alias_e = ty.alias("alias_e", alias_d);
 
   auto result = table->Lookup(*this, IntrinsicType::kAll, {alias_e}, Source{});
   ASSERT_NE(result.intrinsic, nullptr);
@@ -464,7 +461,7 @@
 }
 
 TEST_F(IntrinsicTableTest, OverloadOrderByMatchingParameter) {
-  auto* tex = create<sem::DepthTexture>(ast::TextureDimension::k2d);
+  auto tex = ty.depth_texture(ast::TextureDimension::k2d);
   auto result = table->Lookup(*this, IntrinsicType::kTextureDimensions,
                               {tex, ty.bool_()}, Source{});
   ASSERT_EQ(
diff --git a/src/program_builder.h b/src/program_builder.h
index 5dc5acc..4b00976 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -18,6 +18,7 @@
 #include <string>
 #include <utility>
 
+#include "src/ast/alias.h"
 #include "src/ast/array.h"
 #include "src/ast/array_accessor_expression.h"
 #include "src/ast/assignment_statement.h"
@@ -26,6 +27,7 @@
 #include "src/ast/bool_literal.h"
 #include "src/ast/call_expression.h"
 #include "src/ast/case_statement.h"
+#include "src/ast/depth_texture.h"
 #include "src/ast/f32.h"
 #include "src/ast/float_literal.h"
 #include "src/ast/i32.h"
@@ -34,10 +36,14 @@
 #include "src/ast/matrix.h"
 #include "src/ast/member_accessor_expression.h"
 #include "src/ast/module.h"
+#include "src/ast/multisampled_texture.h"
+#include "src/ast/pointer.h"
 #include "src/ast/return_statement.h"
+#include "src/ast/sampled_texture.h"
 #include "src/ast/scalar_constructor_expression.h"
 #include "src/ast/sint_literal.h"
 #include "src/ast/stage_decoration.h"
+#include "src/ast/storage_texture.h"
 #include "src/ast/stride_decoration.h"
 #include "src/ast/struct_member_align_decoration.h"
 #include "src/ast/struct_member_offset_decoration.h"
@@ -55,10 +61,14 @@
 #include "src/sem/alias_type.h"
 #include "src/sem/array_type.h"
 #include "src/sem/bool_type.h"
+#include "src/sem/depth_texture_type.h"
 #include "src/sem/f32_type.h"
 #include "src/sem/i32_type.h"
 #include "src/sem/matrix_type.h"
+#include "src/sem/multisampled_texture_type.h"
 #include "src/sem/pointer_type.h"
+#include "src/sem/sampled_texture_type.h"
+#include "src/sem/storage_texture_type.h"
 #include "src/sem/struct_type.h"
 #include "src/sem/u32_type.h"
 #include "src/sem/vector_type.h"
@@ -363,24 +373,30 @@
     }
 
     /// @param type vector subtype
-    /// @return the tint AST type for a 2-element vector of `type`.
-    typ::Vector vec2(typ::Type type) const {
-      return {builder->create<ast::Vector>(type, 2u),
-              builder->create<sem::Vector>(type, 2u)};
+    /// @param n vector width in elements
+    /// @return the tint AST type for a `n`-element vector of `type`.
+    typ::Vector vec(typ::Type type, uint32_t n) const {
+      return {builder->create<ast::Vector>(type, n),
+              builder->create<sem::Vector>(type, n)};
     }
 
     /// @param type vector subtype
+    /// @return the tint AST type for a 2-element vector of `type`.
+    typ::Vector vec2(typ::Type type) const { return vec(type, 2u); }
+
+    /// @param type vector subtype
     /// @return the tint AST type for a 3-element vector of `type`.
-    typ::Vector vec3(typ::Type type) const {
-      return {builder->create<ast::Vector>(type, 3u),
-              builder->create<sem::Vector>(type, 3u)};
-    }
+    typ::Vector vec3(typ::Type type) const { return vec(type, 3u); }
 
     /// @param type vector subtype
     /// @return the tint AST type for a 4-element vector of `type`.
-    typ::Vector vec4(typ::Type type) const {
-      return {builder->create<ast::Vector>(type, 4u),
-              builder->create<sem::Vector>(type, 4u)};
+    typ::Vector vec4(typ::Type type) const { return vec(type, 4u); }
+
+    /// @param n vector width in elements
+    /// @return the tint AST type for a `n`-element vector of `type`.
+    template <typename T>
+    typ::Vector vec(uint32_t n) const {
+      return vec(Of<T>(), n);
     }
 
     /// @return the tint AST type for a 2-element vector of the C type `T`.
@@ -402,6 +418,15 @@
     }
 
     /// @param type matrix subtype
+    /// @param columns number of columns for the matrix
+    /// @param rows number of rows for the matrix
+    /// @return the tint AST type for a matrix of `type`
+    typ::Matrix mat(typ::Type type, uint32_t columns, uint32_t rows) const {
+      return {builder->create<ast::Matrix>(type, rows, columns),
+              builder->create<sem::Matrix>(type, rows, columns)};
+    }
+
+    /// @param type matrix subtype
     /// @return the tint AST type for a 2x3 matrix of `type`.
     typ::Matrix mat2x2(typ::Type type) const {
       return {builder->create<ast::Matrix>(type, 2u, 2u),
@@ -464,6 +489,14 @@
               builder->create<sem::Matrix>(type, 4u, 4u)};
     }
 
+    /// @param columns number of columns for the matrix
+    /// @param rows number of rows for the matrix
+    /// @return the tint AST type for a matrix of `type`
+    template <typename T>
+    typ::Matrix mat(uint32_t columns, uint32_t rows) const {
+      return mat(Of<T>(), columns, rows);
+    }
+
     /// @return the tint AST type for a 2x3 matrix of the C type `T`.
     template <typename T>
     typ::Matrix mat2x2() const {
@@ -519,29 +552,23 @@
     }
 
     /// @param subtype the array element type
-    /// @param n the array size. 0 represents a runtime-array.
+    /// @param n the array size. 0 represents a runtime-array
+    /// @param decos the optional decorations for the array
     /// @return the tint AST type for a array of size `n` of type `T`
-    typ::Array array(typ::Type subtype, uint32_t n = 0) const {
-      return {
-          builder->create<ast::Array>(subtype, n, ast::DecorationList{}),
-          builder->create<sem::ArrayType>(subtype, n, ast::DecorationList{})};
+    typ::Array array(typ::Type subtype,
+                     uint32_t n = 0,
+                     ast::DecorationList decos = {}) const {
+      return {builder->create<ast::Array>(subtype, n, decos),
+              builder->create<sem::ArrayType>(subtype, n, decos)};
     }
 
     /// @param subtype the array element type
-    /// @param n the array size. 0 represents a runtime-array.
-    /// @param stride the array stride.
+    /// @param n the array size. 0 represents a runtime-array
+    /// @param stride the array stride
     /// @return the tint AST type for a array of size `n` of type `T`
     typ::Array array(typ::Type subtype, uint32_t n, uint32_t stride) const {
-      return {builder->create<ast::Array>(
-                  subtype, n,
-                  ast::DecorationList{
-                      builder->create<ast::StrideDecoration>(stride),
-                  }),
-              builder->create<sem::ArrayType>(
-                  subtype, n,
-                  ast::DecorationList{
-                      builder->create<ast::StrideDecoration>(stride),
-                  })};
+      return array(subtype, n,
+                   {builder->create<ast::StrideDecoration>(stride)});
     }
 
     /// @return the tint AST type for an array of size `N` of type `T`
@@ -562,40 +589,88 @@
     /// @param type the alias type
     /// @returns the alias pointer
     template <typename NAME>
-    sem::Alias* alias(NAME&& name, sem::Type* type) const {
-      return builder->create<sem::Alias>(builder->Sym(std::forward<NAME>(name)),
-                                         type);
+    typ::Alias alias(NAME&& name, typ::Type type) const {
+      auto sym = builder->Sym(std::forward<NAME>(name));
+      return {
+          builder->create<ast::Alias>(sym, type),
+          builder->create<sem::Alias>(sym, type),
+      };
     }
 
     /// Creates an access control qualifier type
     /// @param access the access control
     /// @param type the inner type
     /// @returns the access control qualifier type
-    sem::AccessControl* access(ast::AccessControl::Access access,
-                               sem::Type* type) const {
-      return builder->create<sem::AccessControl>(access, type);
+    typ::AccessControl access(ast::AccessControl::Access access,
+                              typ::Type type) const {
+      return {builder->create<ast::AccessControl>(access, type),
+              builder->create<sem::AccessControl>(access, type)};
     }
 
-    /// @return the tint AST pointer to `type` with the given ast::StorageClass
     /// @param type the type of the pointer
     /// @param storage_class the storage class of the pointer
-    sem::Pointer* pointer(sem::Type* type,
-                          ast::StorageClass storage_class) const {
-      return builder->create<sem::Pointer>(type, storage_class);
+    /// @return the pointer to `type` with the given ast::StorageClass
+    typ::Pointer pointer(typ::Type type,
+                         ast::StorageClass storage_class) const {
+      return {builder->create<ast::Pointer>(type, storage_class),
+              builder->create<sem::Pointer>(type, storage_class)};
     }
 
-    /// @return the tint AST pointer to type `T` with the given
-    /// ast::StorageClass.
     /// @param storage_class the storage class of the pointer
+    /// @return the pointer to type `T` with the given ast::StorageClass.
     template <typename T>
-    sem::Pointer* pointer(ast::StorageClass storage_class) const {
+    typ::Pointer pointer(ast::StorageClass storage_class) const {
       return pointer(Of<T>(), storage_class);
     }
 
     /// @param impl the struct implementation
     /// @returns a struct pointer
-    sem::StructType* struct_(ast::Struct* impl) const {
-      return builder->create<sem::StructType>(impl);
+    typ::Struct struct_(ast::Struct* impl) const {
+      return {impl, builder->create<sem::StructType>(impl)};
+    }
+
+    /// @param kind the kind of sampler
+    /// @returns the sampler
+    typ::Sampler sampler(ast::SamplerKind kind) const {
+      return {builder->create<ast::Sampler>(kind),
+              builder->create<sem::Sampler>(kind)};
+    }
+
+    /// @param dims the dimensionality of the texture
+    /// @returns the depth texture
+    typ::DepthTexture depth_texture(ast::TextureDimension dims) const {
+      return {builder->create<ast::DepthTexture>(dims),
+              builder->create<sem::DepthTexture>(dims)};
+    }
+
+    /// @param dims the dimensionality of the texture
+    /// @param subtype the texture subtype.
+    /// @returns the sampled texture
+    typ::SampledTexture sampled_texture(ast::TextureDimension dims,
+                                        typ::Type subtype) const {
+      return {builder->create<ast::SampledTexture>(dims, subtype),
+              builder->create<sem::SampledTexture>(dims, subtype)};
+    }
+
+    /// @param dims the dimensionality of the texture
+    /// @param subtype the texture subtype.
+    /// @returns the multisampled texture
+    typ::MultisampledTexture multisampled_texture(ast::TextureDimension dims,
+                                                  typ::Type subtype) const {
+      return {builder->create<ast::MultisampledTexture>(dims, subtype),
+              builder->create<sem::MultisampledTexture>(dims, subtype)};
+    }
+
+    /// @param dims the dimensionality of the texture
+    /// @param format the image format of the texture
+    /// @returns the storage texture
+    typ::StorageTexture storage_texture(ast::TextureDimension dims,
+                                        ast::ImageFormat format) const {
+      auto* ast_subtype = ast::StorageTexture::SubtypeFor(format, *builder);
+      auto* sem_subtype =
+          sem::StorageTexture::SubtypeFor(format, builder->Types());
+      return {builder->create<ast::StorageTexture>(dims, format, ast_subtype),
+              builder->create<sem::StorageTexture>(dims, format, sem_subtype)};
     }
 
    private:
@@ -904,7 +979,7 @@
   /// @return an `ast::TypeConstructorExpression` of an array with element type
   /// `subtype`, constructed with the values `args`.
   template <typename... ARGS>
-  ast::TypeConstructorExpression* array(sem::Type* subtype,
+  ast::TypeConstructorExpression* array(typ::Type subtype,
                                         uint32_t n,
                                         ARGS&&... args) {
     return create<ast::TypeConstructorExpression>(
@@ -1259,14 +1334,14 @@
   /// @param decorations the optional struct decorations
   /// @returns the struct type
   template <typename NAME>
-  sem::StructType* Structure(const Source& source,
-                             NAME&& name,
-                             ast::StructMemberList members,
-                             ast::DecorationList decorations = {}) {
+  typ::Struct Structure(const Source& source,
+                        NAME&& name,
+                        ast::StructMemberList members,
+                        ast::DecorationList decorations = {}) {
     auto sym = Sym(std::forward<NAME>(name));
     auto* impl = create<ast::Struct>(source, sym, std::move(members),
                                      std::move(decorations));
-    auto* type = ty.struct_(impl);
+    auto type = ty.struct_(impl);
     AST().AddConstructedType(type);
     return type;
   }
@@ -1278,13 +1353,13 @@
   /// @param decorations the optional struct decorations
   /// @returns the struct type
   template <typename NAME>
-  sem::StructType* Structure(NAME&& name,
-                             ast::StructMemberList members,
-                             ast::DecorationList decorations = {}) {
+  typ::Struct Structure(NAME&& name,
+                        ast::StructMemberList members,
+                        ast::DecorationList decorations = {}) {
     auto sym = Sym(std::forward<NAME>(name));
     auto* impl =
         create<ast::Struct>(sym, std::move(members), std::move(decorations));
-    auto* type = ty.struct_(impl);
+    auto type = ty.struct_(impl);
     AST().AddConstructedType(type);
     return type;
   }
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index ab1848d..336e18b 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -1897,7 +1897,7 @@
   // Construct the Tint handle type.
   sem::Type* ast_store_type = nullptr;
   if (usage.IsSampler()) {
-    ast_store_type = builder_.create<sem::Sampler>(
+    ast_store_type = builder_.ty.sampler(
         usage.IsComparisonSampler() ? ast::SamplerKind::kComparisonSampler
                                     : ast::SamplerKind::kSampler);
   } else if (usage.IsTexture()) {
diff --git a/src/reader/wgsl/parser_impl_type_alias_test.cc b/src/reader/wgsl/parser_impl_type_alias_test.cc
index f798a4b..82cca4d 100644
--- a/src/reader/wgsl/parser_impl_type_alias_test.cc
+++ b/src/reader/wgsl/parser_impl_type_alias_test.cc
@@ -38,7 +38,7 @@
 TEST_F(ParserImplTest, TypeDecl_ParsesStruct_Ident) {
   auto p = parser("type a = B");
 
-  auto* str = Structure(p->builder().Symbols().Register("B"), {});
+  auto str = Structure(p->builder().Symbols().Register("B"), {});
   p->register_constructed("B", str);
 
   auto t = p->type_alias();
diff --git a/src/reader/wgsl/parser_impl_type_decl_test.cc b/src/reader/wgsl/parser_impl_type_decl_test.cc
index 080b76e..185ac69 100644
--- a/src/reader/wgsl/parser_impl_type_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_type_decl_test.cc
@@ -746,7 +746,7 @@
   auto p = parser("sampler");
 
   auto& builder = p->builder();
-  auto* type = builder.create<sem::Sampler>(ast::SamplerKind::kSampler);
+  auto type = builder.ty.sampler(ast::SamplerKind::kSampler);
 
   auto t = p->type_decl();
   EXPECT_TRUE(t.matched);
diff --git a/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc b/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
index 5f5b4d0..6802fb3 100644
--- a/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
@@ -115,7 +115,7 @@
   ast::DecorationList decos;
   decos.push_back(block_deco);
 
-  auto* s = Structure(Sym("S"), members, decos);
+  auto s = Structure(Sym("S"), members, decos);
 
   p->register_constructed("S", s);
 
@@ -139,7 +139,7 @@
   ast::DecorationList decos;
   decos.push_back(block_deco);
 
-  auto* s = Structure(Sym("S"), members, decos);
+  auto s = Structure(Sym("S"), members, decos);
 
   p->register_constructed("S", s);
 
@@ -163,7 +163,7 @@
   ast::DecorationList decos;
   decos.push_back(block_deco);
 
-  auto* s = Structure(Sym("S"), members, decos);
+  auto s = Structure(Sym("S"), members, decos);
 
   p->register_constructed("S", s);
 
@@ -184,7 +184,7 @@
   ast::DecorationList decos;
   decos.push_back(block_deco);
 
-  auto* s = Structure(Sym("S"), members, decos);
+  auto s = Structure(Sym("S"), members, decos);
 
   p->register_constructed("S", s);
 
@@ -221,7 +221,7 @@
   ast::DecorationList decos;
   decos.push_back(block_deco);
 
-  auto* s = Structure(Sym("S"), members, decos);
+  auto s = Structure(Sym("S"), members, decos);
 
   p->register_constructed("S", s);
 
diff --git a/src/resolver/assignment_validation_test.cc b/src/resolver/assignment_validation_test.cc
index 007a715..2cbdcb9 100644
--- a/src/resolver/assignment_validation_test.cc
+++ b/src/resolver/assignment_validation_test.cc
@@ -166,7 +166,7 @@
   // alias myint = i32;
   // var a :myint = 2;
   // a = 2
-  auto* myint = ty.alias("myint", ty.i32());
+  auto myint = ty.alias("myint", ty.i32());
   auto* var = Var("a", myint, ast::StorageClass::kNone, Expr(2));
 
   auto* lhs = Expr("a");
@@ -237,11 +237,9 @@
   // var b : [[access(read)]] texture_storage_1d<rgba8unorm>;
   // a = b;
 
-  auto* tex_type = create<sem::StorageTexture>(
-      ast::TextureDimension::k1d, ast::ImageFormat::kRgba8Unorm,
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kRgba8Unorm, Types()));
-  auto* tex_ac =
-      create<sem::AccessControl>(ast::AccessControl::kReadOnly, tex_type);
+  auto tex_type = ty.storage_texture(ast::TextureDimension::k1d,
+                                     ast::ImageFormat::kRgba8Unorm);
+  auto tex_ac = ty.access(ast::AccessControl::kReadOnly, tex_type);
 
   auto* var_a = Var("a", tex_ac, ast::StorageClass::kFunction);
   auto* var_b = Var("b", tex_ac, ast::StorageClass::kFunction);
diff --git a/src/resolver/control_block_validation_test.cc b/src/resolver/control_block_validation_test.cc
index f3ec77e..343a72b 100644
--- a/src/resolver/control_block_validation_test.cc
+++ b/src/resolver/control_block_validation_test.cc
@@ -294,7 +294,7 @@
   //   default: {}
   // }
 
-  auto* my_int = ty.alias("MyInt", ty.u32());
+  auto my_int = ty.alias("MyInt", ty.u32());
   auto* var = Var("a", my_int, ast::StorageClass::kNone, Expr(2u));
 
   ast::CaseSelectorList default_csl;
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index 9bae7b9..a6862a0 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -122,11 +122,11 @@
 TEST_P(ArrayDecorationTest, IsValid) {
   auto& params = GetParam();
 
-  auto* arr = create<sem::ArrayType>(
-      ty.f32(), 0,
-      ast::DecorationList{
-          createDecoration(Source{{12, 34}}, *this, params.kind),
-      });
+  auto arr =
+      ty.array(ty.f32(), 0,
+               {
+                   createDecoration(Source{{12, 34}}, *this, params.kind),
+               });
   Structure("mystruct",
             {
                 Member("a", arr),
@@ -318,7 +318,7 @@
 using ArrayStrideTest = TestWithParams;
 TEST_P(ArrayStrideTest, All) {
   auto& params = GetParam();
-  auto* el_ty = params.create_el_type(ty);
+  auto el_ty = params.create_el_type(ty);
 
   std::stringstream ss;
   ss << "el_ty: " << el_ty->FriendlyName(Symbols())
@@ -326,11 +326,7 @@
      << ", should_pass: " << params.should_pass;
   SCOPED_TRACE(ss.str());
 
-  auto* arr =
-      create<sem::ArrayType>(el_ty, 4,
-                             ast::DecorationList{
-                                 create<ast::StrideDecoration>(params.stride),
-                             });
+  auto arr = ty.array(el_ty, 4, params.stride);
 
   Global(Source{{12, 34}}, "myarray", arr, ast::StorageClass::kInput);
 
@@ -415,11 +411,11 @@
         Params{ty_mat4x4<f32>, (default_mat4x4.align - 1) * 7, false}));
 
 TEST_F(ArrayStrideTest, MultipleDecorations) {
-  auto* arr = create<sem::ArrayType>(ty.i32(), 4,
-                                     ast::DecorationList{
-                                         create<ast::StrideDecoration>(4),
-                                         create<ast::StrideDecoration>(4),
-                                     });
+  auto arr = ty.array(ty.i32(), 4,
+                      {
+                          create<ast::StrideDecoration>(4),
+                          create<ast::StrideDecoration>(4),
+                      });
 
   Global(Source{{12, 34}}, "myarray", arr, ast::StorageClass::kInput);
 
@@ -436,8 +432,8 @@
 
 using StructBlockTest = ResolverTest;
 TEST_F(StructBlockTest, StructUsedAsArrayElement) {
-  auto* s = Structure("S", {Member("x", ty.i32())},
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("S", {Member("x", ty.i32())},
+                     {create<ast::StructBlockDecoration>()});
   auto a = ty.array(s, 4);
   Global("G", a, ast::StorageClass::kPrivate);
 
diff --git a/src/resolver/entry_point_validation_test.cc b/src/resolver/entry_point_validation_test.cc
index f872844..5516b77 100644
--- a/src/resolver/entry_point_validation_test.cc
+++ b/src/resolver/entry_point_validation_test.cc
@@ -85,7 +85,7 @@
   // fn main() -> [[location(0)]] Output {
   //   return Output();
   // }
-  auto* output = Structure("Output", {});
+  auto output = Structure("Output", {});
   Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
        {Stage(ast::PipelineStage::kVertex)}, {Location(Source{{13, 43}}, 0)});
 
@@ -105,7 +105,7 @@
   // fn main() -> Output {
   //   return Output();
   // }
-  auto* output = Structure(
+  auto output = Structure(
       "Output", {Member("a", ty.f32(), {Location(0)}),
                  Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
   Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
@@ -123,7 +123,7 @@
   // fn main() -> Output {
   //   return Output();
   // }
-  auto* output = Structure(
+  auto output = Structure(
       "Output",
       {Member("a", ty.f32(),
               {Location(Source{{13, 43}}, 0),
@@ -147,7 +147,7 @@
   // fn main() -> Output {
   //   return Output();
   // }
-  auto* output = Structure(
+  auto output = Structure(
       "Output", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)}),
                  Member(Source{{14, 52}}, "b", ty.f32(), {})});
   Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
@@ -170,9 +170,9 @@
   // fn main() -> Output {
   //   return Output();
   // }
-  auto* inner = Structure(
+  auto inner = Structure(
       "Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
-  auto* output = Structure(
+  auto output = Structure(
       "Output", {Member(Source{{14, 52}}, "a", inner, {Location(0)})});
   Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
        {Stage(ast::PipelineStage::kFragment)});
@@ -193,7 +193,7 @@
   // fn main() -> Output {
   //   return Output();
   // }
-  auto* output = Structure(
+  auto output = Structure(
       "Output",
       {Member(Source{{13, 43}}, "a", ty.array<float>(), {Location(0)})},
       {create<ast::StructBlockDecoration>()});
@@ -216,7 +216,7 @@
   // fn main() -> Output {
   //   return Output();
   // }
-  auto* output = Structure(
+  auto output = Structure(
       "Output", {Member("a", ty.f32(), {Builtin(ast::Builtin::kFragDepth)}),
                  Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
   Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
@@ -238,8 +238,8 @@
   // fn main() -> Output {
   //   return Output();
   // }
-  auto* output = Structure("Output", {Member("a", ty.f32(), {Location(1)}),
-                                      Member("b", ty.f32(), {Location(1)})});
+  auto output = Structure("Output", {Member("a", ty.f32(), {Location(1)}),
+                                     Member("b", ty.f32(), {Location(1)})});
   Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
        {Stage(ast::PipelineStage::kFragment)});
 
@@ -302,7 +302,7 @@
   // };
   // [[stage(fragment)]]
   // fn main([[location(0)]] param : Input) {}
-  auto* input = Structure("Input", {});
+  auto input = Structure("Input", {});
   auto* param = Param("param", input, {Location(Source{{13, 43}}, 0)});
   Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
@@ -321,7 +321,7 @@
   // };
   // [[stage(fragment)]]
   // fn main(param : Input) {}
-  auto* input = Structure(
+  auto input = Structure(
       "Input", {Member("a", ty.f32(), {Location(0)}),
                 Member("b", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
   auto* param = Param("param", input);
@@ -338,7 +338,7 @@
   // };
   // [[stage(fragment)]]
   // fn main(param : Input) {}
-  auto* input = Structure(
+  auto input = Structure(
       "Input",
       {Member("a", ty.f32(),
               {Location(Source{{13, 43}}, 0),
@@ -361,7 +361,7 @@
   // };
   // [[stage(fragment)]]
   // fn main(param : Input) {}
-  auto* input = Structure(
+  auto input = Structure(
       "Input", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)}),
                 Member(Source{{14, 52}}, "b", ty.f32(), {})});
   auto* param = Param("param", input);
@@ -382,9 +382,9 @@
   // };
   // [[stage(fragment)]]
   // fn main(param : Input) {}
-  auto* inner = Structure(
+  auto inner = Structure(
       "Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
-  auto* input =
+  auto input =
       Structure("Input", {Member(Source{{14, 52}}, "a", inner, {Location(0)})});
   auto* param = Param("param", input);
   Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
@@ -404,7 +404,7 @@
   // };
   // [[stage(fragment)]]
   // fn main(param : Input) {}
-  auto* input = Structure(
+  auto input = Structure(
       "Input",
       {Member(Source{{13, 43}}, "a", ty.array<float>(), {Location(0)})},
       {create<ast::StructBlockDecoration>()});
@@ -446,9 +446,9 @@
   // };
   // [[stage(fragment)]]
   // fn main(param_a : InputA, param_b : InputB) {}
-  auto* input_a = Structure(
+  auto input_a = Structure(
       "InputA", {Member("a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
-  auto* input_b = Structure(
+  auto input_b = Structure(
       "InputB", {Member("a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
   auto* param_a = Param("param_a", input_a);
   auto* param_b = Param("param_b", input_b);
@@ -486,8 +486,8 @@
   // };
   // [[stage(fragment)]]
   // fn main(param_a : InputA, param_b : InputB) {}
-  auto* input_a = Structure("InputA", {Member("a", ty.f32(), {Location(1)})});
-  auto* input_b = Structure("InputB", {Member("a", ty.f32(), {Location(1)})});
+  auto input_a = Structure("InputA", {Member("a", ty.f32(), {Location(1)})});
+  auto input_b = Structure("InputB", {Member("a", ty.f32(), {Location(1)})});
   auto* param_a = Param("param_a", input_a);
   auto* param_b = Param("param_b", input_b);
   Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
diff --git a/src/resolver/function_validation_test.cc b/src/resolver/function_validation_test.cc
index 79d0c66..9ffb314 100644
--- a/src/resolver/function_validation_test.cc
+++ b/src/resolver/function_validation_test.cc
@@ -157,7 +157,7 @@
        FunctionTypeMustMatchReturnStatementTypeF32Alias_pass) {
   // type myf32 = f32;
   // fn func -> myf32 { return 2.0; }
-  auto* myf32 = ty.alias("myf32", ty.f32());
+  auto myf32 = ty.alias("myf32", ty.f32());
   Func("func", ast::VariableList{}, myf32,
        ast::StatementList{
            Return(Source{Source::Location{12, 34}}, Expr(2.f)),
@@ -171,7 +171,7 @@
        FunctionTypeMustMatchReturnStatementTypeF32Alias_fail) {
   // type myf32 = f32;
   // fn func -> myf32 { return 2; }
-  auto* myf32 = ty.alias("myf32", ty.f32());
+  auto myf32 = ty.alias("myf32", ty.f32());
   Func("func", ast::VariableList{}, myf32,
        ast::StatementList{
            Return(Source{Source::Location{12, 34}}, Expr(2u)),
diff --git a/src/resolver/host_shareable_validation_test.cc b/src/resolver/host_shareable_validation_test.cc
index 0f6fa06..3990c5e 100644
--- a/src/resolver/host_shareable_validation_test.cc
+++ b/src/resolver/host_shareable_validation_test.cc
@@ -27,9 +27,9 @@
 using ResolverHostShareableValidationTest = ResolverTest;
 
 TEST_F(ResolverHostShareableValidationTest, BoolMember) {
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())},
-                      {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  auto s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())},
+                     {create<ast::StructBlockDecoration>()});
+  auto a = ty.access(ast::AccessControl::kReadOnly, s);
   Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
 
   ASSERT_FALSE(r()->Resolve());
@@ -42,9 +42,9 @@
 }
 
 TEST_F(ResolverHostShareableValidationTest, BoolVectorMember) {
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3<bool>())},
-                      {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  auto s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3<bool>())},
+                     {create<ast::StructBlockDecoration>()});
+  auto a = ty.access(ast::AccessControl::kReadOnly, s);
   Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
 
   ASSERT_FALSE(r()->Resolve());
@@ -57,11 +57,11 @@
 }
 
 TEST_F(ResolverHostShareableValidationTest, Aliases) {
-  auto* a1 = ty.alias("a1", ty.bool_());
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", a1)},
-                      {create<ast::StructBlockDecoration>()});
-  auto* ac = ty.access(ast::AccessControl::kReadOnly, s);
-  auto* a2 = ty.alias("a2", ac);
+  auto a1 = ty.alias("a1", ty.bool_());
+  auto s = Structure("S", {Member(Source{{12, 34}}, "x", a1)},
+                     {create<ast::StructBlockDecoration>()});
+  auto ac = ty.access(ast::AccessControl::kReadOnly, s);
+  auto a2 = ty.alias("a2", ac);
   Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage);
 
   ASSERT_FALSE(r()->Resolve());
@@ -74,13 +74,13 @@
 }
 
 TEST_F(ResolverHostShareableValidationTest, NestedStructures) {
-  auto* i1 = Structure("I1", {Member(Source{{1, 2}}, "x", ty.bool_())});
-  auto* i2 = Structure("I2", {Member(Source{{3, 4}}, "y", i1)});
-  auto* i3 = Structure("I3", {Member(Source{{5, 6}}, "z", i2)});
+  auto i1 = Structure("I1", {Member(Source{{1, 2}}, "x", ty.bool_())});
+  auto i2 = Structure("I2", {Member(Source{{3, 4}}, "y", i1)});
+  auto i3 = Structure("I3", {Member(Source{{5, 6}}, "z", i2)});
 
-  auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)},
-                      {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  auto s = Structure("S", {Member(Source{{7, 8}}, "m", i3)},
+                     {create<ast::StructBlockDecoration>()});
+  auto a = ty.access(ast::AccessControl::kReadOnly, s);
   Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage);
 
   ASSERT_FALSE(r()->Resolve());
@@ -96,27 +96,27 @@
 }
 
 TEST_F(ResolverHostShareableValidationTest, NoError) {
-  auto* i1 =
+  auto i1 =
       Structure("I1", {
                           Member(Source{{1, 1}}, "x1", ty.f32()),
                           Member(Source{{2, 1}}, "y1", ty.vec3<f32>()),
                           Member(Source{{3, 1}}, "z1", ty.array<i32, 4>()),
                       });
-  auto* i2 = Structure("I2", {
-                                 Member(Source{{4, 1}}, "x2", ty.mat2x2<f32>()),
-                                 Member(Source{{5, 1}}, "y2", i1),
-                                 Member(Source{{6, 1}}, "z2", ty.mat3x2<i32>()),
-                             });
-  auto* i3 =
+  auto i2 = Structure("I2", {
+                                Member(Source{{4, 1}}, "x2", ty.mat2x2<f32>()),
+                                Member(Source{{5, 1}}, "y2", i1),
+                                Member(Source{{6, 1}}, "z2", ty.mat3x2<i32>()),
+                            });
+  auto i3 =
       Structure("I3", {
                           Member(Source{{4, 1}}, "x3", ty.alias("a1", i1)),
                           Member(Source{{5, 1}}, "y3", i2),
                           Member(Source{{6, 1}}, "z3", ty.alias("a2", i2)),
                       });
 
-  auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)},
-                      {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  auto s = Structure("S", {Member(Source{{7, 8}}, "m", i3)},
+                     {create<ast::StructBlockDecoration>()});
+  auto a = ty.access(ast::AccessControl::kReadOnly, s);
   Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/resolver/intrinsic_test.cc b/src/resolver/intrinsic_test.cc
index 846461e..ed85faa 100644
--- a/src/resolver/intrinsic_test.cc
+++ b/src/resolver/intrinsic_test.cc
@@ -223,17 +223,17 @@
   /// @param dim dimensionality of the texture being sampled
   /// @param scalar the scalar type
   /// @returns a pointer to a type appropriate for the coord param
-  sem::Type* GetCoordsType(ast::TextureDimension dim, sem::Type* scalar) {
+  typ::Type GetCoordsType(ast::TextureDimension dim, typ::Type scalar) {
     switch (dim) {
       case ast::TextureDimension::k1d:
         return scalar;
       case ast::TextureDimension::k2d:
       case ast::TextureDimension::k2dArray:
-        return create<sem::Vector>(scalar, 2);
+        return ty.vec(scalar, 2);
       case ast::TextureDimension::k3d:
       case ast::TextureDimension::kCube:
       case ast::TextureDimension::kCubeArray:
-        return create<sem::Vector>(scalar, 3);
+        return ty.vec(scalar, 3);
       default:
         [=]() { FAIL() << "Unsupported texture dimension: " << dim; }();
     }
@@ -241,19 +241,19 @@
   }
 
   void add_call_param(std::string name,
-                      sem::Type* type,
+                      typ::Type type,
                       ast::ExpressionList* call_params) {
     Global(name, type, ast::StorageClass::kInput);
     call_params->push_back(Expr(name));
   }
-  sem::Type* subtype(Texture type) {
+  typ::Type subtype(Texture type) {
     if (type == Texture::kF32) {
-      return create<sem::F32>();
+      return ty.f32();
     }
     if (type == Texture::kI32) {
-      return create<sem::I32>();
+      return ty.i32();
     }
-    return create<sem::U32>();
+    return ty.u32();
   }
 };
 
@@ -264,12 +264,9 @@
   auto type = GetParam().type;
   auto format = GetParam().format;
 
-  auto* coords_type = GetCoordsType(dim, ty.i32());
-
-  auto* subtype = sem::StorageTexture::SubtypeFor(format, Types());
-  auto* texture_type = create<sem::StorageTexture>(dim, format, subtype);
-  auto* ro_texture_type =
-      create<sem::AccessControl>(ast::AccessControl::kReadOnly, texture_type);
+  auto coords_type = GetCoordsType(dim, ty.i32());
+  auto texture_type = ty.storage_texture(dim, format);
+  auto ro_texture_type = ty.access(ast::AccessControl::kReadOnly, texture_type);
 
   ast::ExpressionList call_params;
 
@@ -332,9 +329,9 @@
   auto dim = GetParam().dim;
   auto type = GetParam().type;
 
-  sem::Type* s = subtype(type);
-  auto* coords_type = GetCoordsType(dim, ty.i32());
-  auto* texture_type = create<sem::SampledTexture>(dim, s);
+  auto s = subtype(type);
+  auto coords_type = GetCoordsType(dim, ty.i32());
+  auto texture_type = ty.sampled_texture(dim, s);
 
   ast::ExpressionList call_params;
 
@@ -760,9 +757,9 @@
 
 TEST_F(ResolverIntrinsicDataTest, ArrayLength_Vector) {
   auto ary = ty.array<i32>();
-  auto* str = Structure("S", {Member("x", ary)},
-                        {create<ast::StructBlockDecoration>()});
-  auto* ac = ty.access(ast::AccessControl::kReadOnly, str);
+  auto str = Structure("S", {Member("x", ary)},
+                       {create<ast::StructBlockDecoration>()});
+  auto ac = ty.access(ast::AccessControl::kReadOnly, str);
   Global("a", ac, ast::StorageClass::kStorage);
 
   auto* call = Call("arrayLength", MemberAccessor("a", "x"));
@@ -1950,7 +1947,7 @@
       case ast::intrinsic::test::TextureKind::kRegular:
       case ast::intrinsic::test::TextureKind::kMultisampled:
       case ast::intrinsic::test::TextureKind::kStorage: {
-        auto* datatype = param.resultVectorComponentType(this);
+        auto datatype = param.resultVectorComponentType(this);
         ASSERT_TRUE(TypeOf(call)->Is<sem::Vector>());
         EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->type(), datatype);
         break;
diff --git a/src/resolver/is_host_shareable_test.cc b/src/resolver/is_host_shareable_test.cc
index 1d5d53d..d2eca75 100644
--- a/src/resolver/is_host_shareable_test.cc
+++ b/src/resolver/is_host_shareable_test.cc
@@ -89,12 +89,12 @@
 
 TEST_F(ResolverIsHostShareable, AccessControlVoid) {
   EXPECT_FALSE(r()->IsHostShareable(
-      create<sem::AccessControl>(ast::AccessControl::kReadOnly, ty.void_())));
+      ty.access(ast::AccessControl::kReadOnly, ty.void_())));
 }
 
 TEST_F(ResolverIsHostShareable, AccessControlI32) {
-  EXPECT_TRUE(r()->IsHostShareable(
-      create<sem::AccessControl>(ast::AccessControl::kReadOnly, ty.i32())));
+  EXPECT_TRUE(
+      r()->IsHostShareable(ty.access(ast::AccessControl::kReadOnly, ty.i32())));
 }
 
 TEST_F(ResolverIsHostShareable, ArraySizedOfHostShareable) {
@@ -113,7 +113,7 @@
 }
 
 TEST_F(ResolverIsHostShareable, Struct_SomeMembersNonHostShareable) {
-  auto* ptr_ty = ty.pointer<i32>(ast::StorageClass::kPrivate);
+  auto ptr_ty = ty.pointer<i32>(ast::StorageClass::kPrivate);
   EXPECT_FALSE(r()->IsHostShareable(Structure("S", {
                                                        Member("a", ty.i32()),
                                                        Member("b", ptr_ty),
@@ -121,10 +121,10 @@
 }
 
 TEST_F(ResolverIsHostShareable, Struct_NestedHostShareable) {
-  auto* host_shareable = Structure("S", {
-                                            Member("a", ty.i32()),
-                                            Member("b", ty.f32()),
-                                        });
+  auto host_shareable = Structure("S", {
+                                           Member("a", ty.i32()),
+                                           Member("b", ty.f32()),
+                                       });
   EXPECT_TRUE(
       r()->IsHostShareable(Structure("S", {
                                               Member("a", ty.i32()),
@@ -133,8 +133,8 @@
 }
 
 TEST_F(ResolverIsHostShareable, Struct_NestedNonHostShareable) {
-  auto* ptr_ty = ty.pointer<i32>(ast::StorageClass::kPrivate);
-  auto* non_host_shareable =
+  auto ptr_ty = ty.pointer<i32>(ast::StorageClass::kPrivate);
+  auto non_host_shareable =
       Structure("non_host_shareable", {
                                           Member("a", ty.i32()),
                                           Member("b", ptr_ty),
diff --git a/src/resolver/is_storeable_test.cc b/src/resolver/is_storeable_test.cc
index 4e357dc..63afa1f 100644
--- a/src/resolver/is_storeable_test.cc
+++ b/src/resolver/is_storeable_test.cc
@@ -97,7 +97,7 @@
 }
 
 TEST_F(ResolverIsStorableTest, Struct_SomeMembersNonStorable) {
-  auto* ptr_ty = ty.pointer<i32>(ast::StorageClass::kPrivate);
+  auto ptr_ty = ty.pointer<i32>(ast::StorageClass::kPrivate);
   EXPECT_FALSE(r()->IsStorable(Structure("S", {
                                                   Member("a", ty.i32()),
                                                   Member("b", ptr_ty),
@@ -105,10 +105,10 @@
 }
 
 TEST_F(ResolverIsStorableTest, Struct_NestedStorable) {
-  auto* storable = Structure("S", {
-                                      Member("a", ty.i32()),
-                                      Member("b", ty.f32()),
-                                  });
+  auto storable = Structure("S", {
+                                     Member("a", ty.i32()),
+                                     Member("b", ty.f32()),
+                                 });
   EXPECT_TRUE(r()->IsStorable(Structure("S", {
                                                  Member("a", ty.i32()),
                                                  Member("b", storable),
@@ -116,11 +116,11 @@
 }
 
 TEST_F(ResolverIsStorableTest, Struct_NestedNonStorable) {
-  auto* ptr_ty = ty.pointer<i32>(ast::StorageClass::kPrivate);
-  auto* non_storable = Structure("nonstorable", {
-                                                    Member("a", ty.i32()),
-                                                    Member("b", ptr_ty),
-                                                });
+  auto ptr_ty = ty.pointer<i32>(ast::StorageClass::kPrivate);
+  auto non_storable = Structure("nonstorable", {
+                                                   Member("a", ty.i32()),
+                                                   Member("b", ptr_ty),
+                                               });
   EXPECT_FALSE(r()->IsStorable(Structure("S", {
                                                   Member("a", ty.i32()),
                                                   Member("b", non_storable),
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index 935fbb8..79c778d 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -260,7 +260,7 @@
 }
 
 TEST_F(ResolverTest, Stmt_VariableDecl_Alias) {
-  auto* my_int = ty.alias("MyInt", ty.i32());
+  auto my_int = ty.alias("MyInt", ty.i32());
   auto* var = Var("my_var", my_int, ast::StorageClass::kNone, Expr(2));
   auto* init = var->constructor();
 
@@ -407,7 +407,7 @@
 }
 
 TEST_F(ResolverTest, Expr_ArrayAccessor_Alias_Array) {
-  auto* aary = ty.alias("myarrty", ty.array<f32, 3>());
+  auto aary = ty.alias("myarrty", ty.array<f32, 3>());
 
   Global("my_var", aary, ast::StorageClass::kFunction);
 
@@ -761,9 +761,9 @@
 }
 
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
-  auto* s = Structure("S", {Member("m", ty.u32())},
-                      {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  auto s = Structure("S", {Member("m", ty.u32())},
+                     {create<ast::StructBlockDecoration>()});
+  auto a = ty.access(ast::AccessControl::kReadOnly, s);
 
   auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
   auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput);
@@ -796,9 +796,9 @@
 }
 
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) {
-  auto* s = Structure("S", {Member("m", ty.u32())},
-                      {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  auto s = Structure("S", {Member("m", ty.u32())},
+                     {create<ast::StructBlockDecoration>()});
+  auto a = ty.access(ast::AccessControl::kReadOnly, s);
 
   auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
   auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput);
@@ -878,8 +878,8 @@
 }
 
 TEST_F(ResolverTest, Expr_MemberAccessor_Struct) {
-  auto* st = Structure("S", {Member("first_member", ty.i32()),
-                             Member("second_member", ty.f32())});
+  auto st = Structure("S", {Member("first_member", ty.i32()),
+                            Member("second_member", ty.f32())});
   Global("my_struct", st, ast::StorageClass::kInput);
 
   auto* mem = MemberAccessor("my_struct", "second_member");
@@ -903,9 +903,9 @@
 }
 
 TEST_F(ResolverTest, Expr_MemberAccessor_Struct_Alias) {
-  auto* st = Structure("alias", {Member("first_member", ty.i32()),
-                                 Member("second_member", ty.f32())});
-  auto* alias = ty.alias("alias", st);
+  auto st = Structure("alias", {Member("first_member", ty.i32()),
+                                Member("second_member", ty.f32())});
+  auto alias = ty.alias("alias", st);
   Global("my_struct", alias, ast::StorageClass::kInput);
 
   auto* mem = MemberAccessor("my_struct", "second_member");
@@ -981,11 +981,11 @@
   // }
   //
 
-  auto* stB = Structure("B", {Member("foo", ty.vec4<f32>())});
+  auto stB = Structure("B", {Member("foo", ty.vec4<f32>())});
 
   sem::Vector vecB(stB, 3);
 
-  auto* stA = Structure("A", {Member("mem", &vecB)});
+  auto stA = Structure("A", {Member("mem", &vecB)});
   Global("c", stA, ast::StorageClass::kInput);
 
   auto* mem = MemberAccessor(
@@ -1003,8 +1003,8 @@
 }
 
 TEST_F(ResolverTest, Expr_MemberAccessor_InBinaryOp) {
-  auto* st = Structure("S", {Member("first_member", ty.f32()),
-                             Member("second_member", ty.f32())});
+  auto st = Structure("S", {Member("first_member", ty.f32()),
+                            Member("second_member", ty.f32())});
   Global("my_struct", st, ast::StorageClass::kInput);
 
   auto* expr = Add(MemberAccessor("my_struct", "first_member"),
@@ -1181,9 +1181,9 @@
 TEST_P(Expr_Binary_Test_Valid, All) {
   auto& params = GetParam();
 
-  auto* lhs_type = params.create_lhs_type(ty);
-  auto* rhs_type = params.create_rhs_type(ty);
-  auto* result_type = params.create_result_type(ty);
+  auto lhs_type = params.create_lhs_type(ty);
+  auto rhs_type = params.create_rhs_type(ty);
+  auto result_type = params.create_result_type(ty);
 
   std::stringstream ss;
   ss << lhs_type->FriendlyName(Symbols()) << " " << params.op << " "
@@ -1212,8 +1212,8 @@
   const Params& params = std::get<0>(GetParam());
   BinaryExprSide side = std::get<1>(GetParam());
 
-  auto* lhs_type = params.create_lhs_type(ty);
-  auto* rhs_type = params.create_rhs_type(ty);
+  auto lhs_type = params.create_lhs_type(ty);
+  auto rhs_type = params.create_rhs_type(ty);
 
   std::stringstream ss;
   ss << lhs_type->FriendlyName(Symbols()) << " " << params.op << " "
@@ -1287,8 +1287,8 @@
     return;
   }
 
-  auto* lhs_type = params.create_lhs_type(ty);
-  auto* rhs_type = create_type_func(ty);
+  auto lhs_type = params.create_lhs_type(ty);
+  auto rhs_type = create_type_func(ty);
 
   // Skip exceptions: multiplication of f32, vecN<f32>, and matNxN<f32>
   if (params.op == Op::kMultiply &&
@@ -1331,20 +1331,20 @@
   uint32_t mat_rows = std::get<2>(GetParam());
   uint32_t mat_cols = std::get<3>(GetParam());
 
-  sem::Type* lhs_type;
-  sem::Type* rhs_type;
-  sem::Type* result_type;
+  typ::Type lhs_type;
+  typ::Type rhs_type;
+  typ::Type result_type;
   bool is_valid_expr;
 
   if (vec_by_mat) {
-    lhs_type = create<sem::Vector>(ty.f32(), vec_size);
-    rhs_type = create<sem::Matrix>(ty.f32(), mat_rows, mat_cols);
-    result_type = create<sem::Vector>(ty.f32(), mat_cols);
+    lhs_type = ty.vec<f32>(vec_size);
+    rhs_type = ty.mat<f32>(mat_cols, mat_rows);
+    result_type = ty.vec<f32>(mat_cols);
     is_valid_expr = vec_size == mat_rows;
   } else {
-    lhs_type = create<sem::Matrix>(ty.f32(), mat_rows, mat_cols);
-    rhs_type = create<sem::Vector>(ty.f32(), vec_size);
-    result_type = create<sem::Vector>(ty.f32(), mat_rows);
+    lhs_type = ty.mat<f32>(mat_cols, mat_rows);
+    rhs_type = ty.vec<f32>(vec_size);
+    result_type = ty.vec<f32>(mat_rows);
     is_valid_expr = vec_size == mat_cols;
   }
 
@@ -1383,9 +1383,9 @@
   uint32_t rhs_mat_rows = std::get<2>(GetParam());
   uint32_t rhs_mat_cols = std::get<3>(GetParam());
 
-  auto* lhs_type = create<sem::Matrix>(ty.f32(), lhs_mat_rows, lhs_mat_cols);
-  auto* rhs_type = create<sem::Matrix>(ty.f32(), rhs_mat_rows, rhs_mat_cols);
-  auto* result_type = create<sem::Matrix>(ty.f32(), lhs_mat_rows, rhs_mat_cols);
+  auto lhs_type = ty.mat<f32>(lhs_mat_cols, lhs_mat_rows);
+  auto rhs_type = ty.mat<f32>(rhs_mat_cols, rhs_mat_rows);
+  auto result_type = ty.mat<f32>(rhs_mat_cols, lhs_mat_rows);
 
   Global("lhs", lhs_type, ast::StorageClass::kInput);
   Global("rhs", rhs_type, ast::StorageClass::kInput);
diff --git a/src/resolver/resolver_test_helper.h b/src/resolver/resolver_test_helper.h
index 51701ad..a7fd2cf 100644
--- a/src/resolver/resolver_test_helper.h
+++ b/src/resolver/resolver_test_helper.h
@@ -105,97 +105,91 @@
 class ResolverTestWithParam : public TestHelper,
                               public testing::TestWithParam<T> {};
 
-inline sem::Type* ty_bool_(const ProgramBuilder::TypesBuilder& ty) {
+inline typ::Type ty_bool_(const ProgramBuilder::TypesBuilder& ty) {
   return ty.bool_();
 }
-inline sem::Type* ty_i32(const ProgramBuilder::TypesBuilder& ty) {
+inline typ::Type ty_i32(const ProgramBuilder::TypesBuilder& ty) {
   return ty.i32();
 }
-inline sem::Type* ty_u32(const ProgramBuilder::TypesBuilder& ty) {
+inline typ::Type ty_u32(const ProgramBuilder::TypesBuilder& ty) {
   return ty.u32();
 }
-inline sem::Type* ty_f32(const ProgramBuilder::TypesBuilder& ty) {
+inline typ::Type ty_f32(const ProgramBuilder::TypesBuilder& ty) {
   return ty.f32();
 }
 
 using create_type_func_ptr =
-    sem::Type* (*)(const ProgramBuilder::TypesBuilder& ty);
+    typ::Type (*)(const ProgramBuilder::TypesBuilder& ty);
 
 template <typename T>
-sem::Type* ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
+typ::Type ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
   return ty.vec2<T>();
 }
 
 template <create_type_func_ptr create_type>
-sem::Type* ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
-  auto* type = create_type(ty);
-  return ty.vec2(type);
+typ::Type ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.vec2(create_type(ty));
 }
 
 template <typename T>
-sem::Type* ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
+typ::Type ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
   return ty.vec3<T>();
 }
 
 template <create_type_func_ptr create_type>
-sem::Type* ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
-  auto* type = create_type(ty);
-  return ty.vec3(type);
+typ::Type ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.vec3(create_type(ty));
 }
 
 template <typename T>
-sem::Type* ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
+typ::Type ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
   return ty.vec4<T>();
 }
 
 template <create_type_func_ptr create_type>
-sem::Type* ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
-  auto* type = create_type(ty);
-  return ty.vec4(type);
+typ::Type ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.vec4(create_type(ty));
 }
 
 template <typename T>
-sem::Type* ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
+typ::Type ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
   return ty.mat2x2<T>();
 }
 
 template <create_type_func_ptr create_type>
-sem::Type* ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
-  auto* type = create_type(ty);
-  return ty.mat2x2(type);
+typ::Type ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat2x2(create_type(ty));
 }
 
 template <typename T>
-sem::Type* ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
+typ::Type ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
   return ty.mat3x3<T>();
 }
 
 template <create_type_func_ptr create_type>
-sem::Type* ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
-  auto* type = create_type(ty);
-  return ty.mat3x3(type);
+typ::Type ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat3x3(create_type(ty));
 }
 
 template <typename T>
-sem::Type* ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
+typ::Type ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
   return ty.mat4x4<T>();
 }
 
 template <create_type_func_ptr create_type>
-sem::Type* ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
-  auto* type = create_type(ty);
-  return ty.mat4x4(type);
+typ::Type ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.mat4x4(create_type(ty));
 }
 
 template <create_type_func_ptr create_type>
-sem::Type* ty_alias(const ProgramBuilder::TypesBuilder& ty) {
-  auto* type = create_type(ty);
+typ::Type ty_alias(const ProgramBuilder::TypesBuilder& ty) {
+  auto type = create_type(ty);
   return ty.alias("alias_" + type->type_name(), type);
 }
 
 template <create_type_func_ptr create_type>
-sem::Type* ty_access(const ProgramBuilder::TypesBuilder& ty) {
-  auto* type = create_type(ty);
+typ::Type ty_access(const ProgramBuilder::TypesBuilder& ty) {
+  auto type = create_type(ty);
   return ty.access(ast::AccessControl::kReadOnly, type);
 }
 
diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc
index 3642c73..a1c79c4 100644
--- a/src/resolver/storage_class_validation_test.cc
+++ b/src/resolver/storage_class_validation_test.cc
@@ -60,9 +60,9 @@
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferArray) {
   // var<storage> g : [[access(read)]] array<S, 3>;
-  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto s = Structure("S", {Member("a", ty.f32())});
   auto a = ty.array(s, 3);
-  auto* ac = ty.access(ast::AccessControl::kReadOnly, a);
+  auto ac = ty.access(ast::AccessControl::kReadOnly, a);
   Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kStorage);
 
   ASSERT_FALSE(r()->Resolve());
@@ -75,7 +75,7 @@
 TEST_F(ResolverStorageClassValidationTest, StorageBufferBoolAlias) {
   // type a = bool;
   // var<storage> g : [[access(read)]] a;
-  auto* a = ty.alias("a", ty.bool_());
+  auto a = ty.alias("a", ty.bool_());
   Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
 
   ASSERT_FALSE(r()->Resolve());
@@ -87,7 +87,7 @@
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferNoAccessControl) {
   // var<storage> g : S;
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
+  auto s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
   Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage);
 
   ASSERT_FALSE(r()->Resolve());
@@ -100,8 +100,8 @@
 TEST_F(ResolverStorageClassValidationTest, StorageBufferNoBlockDecoration) {
   // struct S { x : i32 };
   // var<storage> g : [[access(read)]] S;
-  auto* s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())});
-  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  auto s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())});
+  auto a = ty.access(ast::AccessControl::kReadOnly, s);
   Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
 
   ASSERT_FALSE(r()->Resolve());
@@ -115,9 +115,9 @@
 TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Basic) {
   // [[block]] struct S { x : i32 };
   // var<storage> g : [[access(read)]] S;
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
-                      {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  auto s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                     {create<ast::StructBlockDecoration>()});
+  auto a = ty.access(ast::AccessControl::kReadOnly, s);
   Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
 
   ASSERT_TRUE(r()->Resolve());
@@ -128,11 +128,11 @@
   // type a1 = S;
   // type a2 = [[access(read)]] a1;
   // var<storage> g : a2;
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
-                      {create<ast::StructBlockDecoration>()});
-  auto* a1 = ty.alias("a1", s);
-  auto* ac = ty.access(ast::AccessControl::kReadOnly, a1);
-  auto* a2 = ty.alias("a2", ac);
+  auto s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                     {create<ast::StructBlockDecoration>()});
+  auto a1 = ty.alias("a1", s);
+  auto ac = ty.access(ast::AccessControl::kReadOnly, a1);
+  auto a2 = ty.alias("a2", ac);
   Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage);
 
   ASSERT_TRUE(r()->Resolve());
@@ -165,9 +165,9 @@
 
 TEST_F(ResolverStorageClassValidationTest, UniformBufferArray) {
   // var<uniform> g : [[access(read)]] array<S, 3>;
-  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto s = Structure("S", {Member("a", ty.f32())});
   auto a = ty.array(s, 3);
-  auto* ac = ty.access(ast::AccessControl::kReadOnly, a);
+  auto ac = ty.access(ast::AccessControl::kReadOnly, a);
   Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kUniform);
 
   ASSERT_FALSE(r()->Resolve());
@@ -180,7 +180,7 @@
 TEST_F(ResolverStorageClassValidationTest, UniformBufferBoolAlias) {
   // type a = bool;
   // var<uniform> g : [[access(read)]] a;
-  auto* a = ty.alias("a", ty.bool_());
+  auto a = ty.alias("a", ty.bool_());
   Global(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform);
 
   ASSERT_FALSE(r()->Resolve());
@@ -193,7 +193,7 @@
 TEST_F(ResolverStorageClassValidationTest, UniformBufferNoBlockDecoration) {
   // struct S { x : i32 };
   // var<uniform> g : S;
-  auto* s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())});
+  auto s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())});
   Global(Source{{56, 78}}, "g", s, ast::StorageClass::kUniform);
 
   ASSERT_FALSE(r()->Resolve());
@@ -207,8 +207,8 @@
 TEST_F(ResolverStorageClassValidationTest, UniformBufferNoError_Basic) {
   // [[block]] struct S { x : i32 };
   // var<uniform> g :  S;
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                     {create<ast::StructBlockDecoration>()});
   Global(Source{{56, 78}}, "g", s, ast::StorageClass::kUniform);
 
   ASSERT_TRUE(r()->Resolve());
@@ -218,9 +218,9 @@
   // [[block]] struct S { x : i32 };
   // type a1 = S;
   // var<uniform> g : a1;
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
-                      {create<ast::StructBlockDecoration>()});
-  auto* a1 = ty.alias("a1", s);
+  auto s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                     {create<ast::StructBlockDecoration>()});
+  auto a1 = ty.alias("a1", s);
   Global(Source{{56, 78}}, "g", a1, ast::StorageClass::kUniform);
 
   ASSERT_TRUE(r()->Resolve());
diff --git a/src/resolver/struct_layout_test.cc b/src/resolver/struct_layout_test.cc
index 782875c..67e1335 100644
--- a/src/resolver/struct_layout_test.cc
+++ b/src/resolver/struct_layout_test.cc
@@ -26,15 +26,15 @@
 using ResolverStructLayoutTest = ResolverTest;
 
 TEST_F(ResolverStructLayoutTest, Scalars) {
-  auto* s = Structure("S", {
-                               Member("a", ty.f32()),
-                               Member("b", ty.u32()),
-                               Member("c", ty.i32()),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.f32()),
+                              Member("b", ty.u32()),
+                              Member("c", ty.i32()),
+                          });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 12u);
   EXPECT_EQ(sem->SizeNoPadding(), 12u);
@@ -52,14 +52,14 @@
 }
 
 TEST_F(ResolverStructLayoutTest, Alias) {
-  auto* s = Structure("S", {
-                               Member("a", ty.alias("a", ty.f32())),
-                               Member("b", ty.alias("b", ty.f32())),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.alias("a", ty.f32())),
+                              Member("b", ty.alias("b", ty.f32())),
+                          });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 8u);
   EXPECT_EQ(sem->SizeNoPadding(), 8u);
@@ -74,15 +74,15 @@
 }
 
 TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayStaticSize) {
-  auto* s = Structure("S", {
-                               Member("a", ty.array<i32, 3>()),
-                               Member("b", ty.array<f32, 5>()),
-                               Member("c", ty.array<f32, 1>()),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.array<i32, 3>()),
+                              Member("b", ty.array<f32, 5>()),
+                              Member("c", ty.array<f32, 1>()),
+                          });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 36u);
   EXPECT_EQ(sem->SizeNoPadding(), 36u);
@@ -100,15 +100,15 @@
 }
 
 TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayStaticSize) {
-  auto* s = Structure("S", {
-                               Member("a", ty.array<i32, 3>(/*stride*/ 8)),
-                               Member("b", ty.array<f32, 5>(/*stride*/ 16)),
-                               Member("c", ty.array<f32, 1>(/*stride*/ 32)),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.array<i32, 3>(/*stride*/ 8)),
+                              Member("b", ty.array<f32, 5>(/*stride*/ 16)),
+                              Member("c", ty.array<f32, 1>(/*stride*/ 32)),
+                          });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 136u);
   EXPECT_EQ(sem->SizeNoPadding(), 136u);
@@ -126,16 +126,15 @@
 }
 
 TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayRuntimeSized) {
-  auto* s =
-      Structure("S",
-                {
-                    Member("c", ty.array<f32>()),
-                },
-                ast::DecorationList{create<ast::StructBlockDecoration>()});
+  auto s = Structure("S",
+                     {
+                         Member("c", ty.array<f32>()),
+                     },
+                     ast::DecorationList{create<ast::StructBlockDecoration>()});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 4u);
   EXPECT_EQ(sem->SizeNoPadding(), 4u);
@@ -147,16 +146,15 @@
 }
 
 TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayRuntimeSized) {
-  auto* s =
-      Structure("S",
-                {
-                    Member("c", ty.array<f32>(/*stride*/ 32)),
-                },
-                ast::DecorationList{create<ast::StructBlockDecoration>()});
+  auto s = Structure("S",
+                     {
+                         Member("c", ty.array<f32>(/*stride*/ 32)),
+                     },
+                     ast::DecorationList{create<ast::StructBlockDecoration>()});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 32u);
   EXPECT_EQ(sem->SizeNoPadding(), 32u);
@@ -170,13 +168,13 @@
 TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfExplicitStrideArray) {
   auto inner = ty.array<i32, 2>(/*stride*/ 16);  // size: 32
   auto outer = ty.array(inner, 12);              // size: 12 * 32
-  auto* s = Structure("S", {
-                               Member("c", outer),
-                           });
+  auto s = Structure("S", {
+                              Member("c", outer),
+                          });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 384u);
   EXPECT_EQ(sem->SizeNoPadding(), 384u);
@@ -188,19 +186,19 @@
 }
 
 TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfStructure) {
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec2<i32>()),
-                                       Member("b", ty.vec3<i32>()),
-                                       Member("c", ty.vec4<i32>()),
-                                   });  // size: 48
+  auto inner = Structure("Inner", {
+                                      Member("a", ty.vec2<i32>()),
+                                      Member("b", ty.vec3<i32>()),
+                                      Member("c", ty.vec4<i32>()),
+                                  });  // size: 48
   auto outer = ty.array(inner, 12);    // size: 12 * 48
-  auto* s = Structure("S", {
-                               Member("c", outer),
-                           });
+  auto s = Structure("S", {
+                              Member("c", outer),
+                          });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 576u);
   EXPECT_EQ(sem->SizeNoPadding(), 576u);
@@ -212,15 +210,15 @@
 }
 
 TEST_F(ResolverStructLayoutTest, Vector) {
-  auto* s = Structure("S", {
-                               Member("a", ty.vec2<i32>()),
-                               Member("b", ty.vec3<i32>()),
-                               Member("c", ty.vec4<i32>()),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.vec2<i32>()),
+                              Member("b", ty.vec3<i32>()),
+                              Member("c", ty.vec4<i32>()),
+                          });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 48u);
   EXPECT_EQ(sem->SizeNoPadding(), 48u);
@@ -238,21 +236,21 @@
 }
 
 TEST_F(ResolverStructLayoutTest, Matrix) {
-  auto* s = Structure("S", {
-                               Member("a", ty.mat2x2<i32>()),
-                               Member("b", ty.mat2x3<i32>()),
-                               Member("c", ty.mat2x4<i32>()),
-                               Member("d", ty.mat3x2<i32>()),
-                               Member("e", ty.mat3x3<i32>()),
-                               Member("f", ty.mat3x4<i32>()),
-                               Member("g", ty.mat4x2<i32>()),
-                               Member("h", ty.mat4x3<i32>()),
-                               Member("i", ty.mat4x4<i32>()),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.mat2x2<i32>()),
+                              Member("b", ty.mat2x3<i32>()),
+                              Member("c", ty.mat2x4<i32>()),
+                              Member("d", ty.mat3x2<i32>()),
+                              Member("e", ty.mat3x3<i32>()),
+                              Member("f", ty.mat3x4<i32>()),
+                              Member("g", ty.mat4x2<i32>()),
+                              Member("h", ty.mat4x3<i32>()),
+                              Member("i", ty.mat4x4<i32>()),
+                          });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 368u);
   EXPECT_EQ(sem->SizeNoPadding(), 368u);
@@ -288,18 +286,18 @@
 }
 
 TEST_F(ResolverStructLayoutTest, NestedStruct) {
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.mat3x3<i32>()),
-                                   });
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", inner),
-                               Member("c", ty.i32()),
-                           });
+  auto inner = Structure("Inner", {
+                                      Member("a", ty.mat3x3<i32>()),
+                                  });
+  auto s = Structure("S", {
+                              Member("a", ty.i32()),
+                              Member("b", inner),
+                              Member("c", ty.i32()),
+                          });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 80u);
   EXPECT_EQ(sem->SizeNoPadding(), 68u);
@@ -317,21 +315,21 @@
 }
 
 TEST_F(ResolverStructLayoutTest, SizeDecorations) {
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.f32(), {MemberSize(8)}),
-                                       Member("b", ty.f32(), {MemberSize(16)}),
-                                       Member("c", ty.f32(), {MemberSize(8)}),
-                                   });
-  auto* s = Structure("S", {
-                               Member("a", ty.f32(), {MemberSize(4)}),
-                               Member("b", ty.u32(), {MemberSize(8)}),
-                               Member("c", inner),
-                               Member("d", ty.i32(), {MemberSize(32)}),
-                           });
+  auto inner = Structure("Inner", {
+                                      Member("a", ty.f32(), {MemberSize(8)}),
+                                      Member("b", ty.f32(), {MemberSize(16)}),
+                                      Member("c", ty.f32(), {MemberSize(8)}),
+                                  });
+  auto s = Structure("S", {
+                              Member("a", ty.f32(), {MemberSize(4)}),
+                              Member("b", ty.u32(), {MemberSize(8)}),
+                              Member("c", inner),
+                              Member("d", ty.i32(), {MemberSize(32)}),
+                          });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 76u);
   EXPECT_EQ(sem->SizeNoPadding(), 76u);
@@ -352,21 +350,21 @@
 }
 
 TEST_F(ResolverStructLayoutTest, AlignDecorations) {
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.f32(), {MemberAlign(8)}),
-                                       Member("b", ty.f32(), {MemberAlign(16)}),
-                                       Member("c", ty.f32(), {MemberAlign(4)}),
-                                   });
-  auto* s = Structure("S", {
-                               Member("a", ty.f32(), {MemberAlign(4)}),
-                               Member("b", ty.u32(), {MemberAlign(8)}),
-                               Member("c", inner),
-                               Member("d", ty.i32(), {MemberAlign(32)}),
-                           });
+  auto inner = Structure("Inner", {
+                                      Member("a", ty.f32(), {MemberAlign(8)}),
+                                      Member("b", ty.f32(), {MemberAlign(16)}),
+                                      Member("c", ty.f32(), {MemberAlign(4)}),
+                                  });
+  auto s = Structure("S", {
+                              Member("a", ty.f32(), {MemberAlign(4)}),
+                              Member("b", ty.u32(), {MemberAlign(8)}),
+                              Member("c", inner),
+                              Member("d", ty.i32(), {MemberAlign(32)}),
+                          });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 96u);
   EXPECT_EQ(sem->SizeNoPadding(), 68u);
@@ -387,13 +385,13 @@
 }
 
 TEST_F(ResolverStructLayoutTest, StructWithLotsOfPadding) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32(), {MemberAlign(1024)}),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.i32(), {MemberAlign(1024)}),
+                          });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 1024u);
   EXPECT_EQ(sem->SizeNoPadding(), 4u);
diff --git a/src/resolver/struct_pipeline_stage_use_test.cc b/src/resolver/struct_pipeline_stage_use_test.cc
index 049b7b1..52e954c 100644
--- a/src/resolver/struct_pipeline_stage_use_test.cc
+++ b/src/resolver/struct_pipeline_stage_use_test.cc
@@ -28,41 +28,41 @@
 using ResolverPipelineStageUseTest = ResolverTest;
 
 TEST_F(ResolverPipelineStageUseTest, UnusedStruct) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+  auto s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_TRUE(sem->PipelineStageUses().empty());
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsNonEntryPointParam) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+  auto s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
 
   Func("foo", {Param("param", s)}, ty.void_(), {}, {});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_TRUE(sem->PipelineStageUses().empty());
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsNonEntryPointReturnType) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+  auto s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
 
   Func("foo", {}, s, {Return(Construct(s, Expr(0.f)))}, {});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_TRUE(sem->PipelineStageUses().empty());
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderParam) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+  auto s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
 
   Func("main", {Param("param", s)}, ty.vec4<f32>(),
        {Return(Construct(ty.vec4<f32>()))},
@@ -71,14 +71,14 @@
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->PipelineStageUses(),
               UnorderedElementsAre(sem::PipelineStageUsage::kVertexInput));
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderReturnType) {
-  auto* s = Structure(
+  auto s = Structure(
       "S", {Member("a", ty.f32(), {Builtin(ast::Builtin::kPosition)})});
 
   Func("main", {}, s, {Return(Construct(s, Expr(0.f)))},
@@ -86,42 +86,42 @@
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->PipelineStageUses(),
               UnorderedElementsAre(sem::PipelineStageUsage::kVertexOutput));
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsFragmentShaderParam) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+  auto s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
 
   Func("main", {Param("param", s)}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->PipelineStageUses(),
               UnorderedElementsAre(sem::PipelineStageUsage::kFragmentInput));
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsFragmentShaderReturnType) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+  auto s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
 
   Func("main", {}, s, {Return(Construct(s, Expr(0.f)))},
        {Stage(ast::PipelineStage::kFragment)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->PipelineStageUses(),
               UnorderedElementsAre(sem::PipelineStageUsage::kFragmentOutput));
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsComputeShaderParam) {
-  auto* s = Structure(
+  auto s = Structure(
       "S",
       {Member("a", ty.u32(), {Builtin(ast::Builtin::kLocalInvocationIndex)})});
 
@@ -130,14 +130,14 @@
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->PipelineStageUses(),
               UnorderedElementsAre(sem::PipelineStageUsage::kComputeInput));
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedMultipleStages) {
-  auto* s = Structure(
+  auto s = Structure(
       "S", {Member("a", ty.f32(), {Builtin(ast::Builtin::kPosition)})});
 
   Func("vert_main", {Param("param", s)}, s, {Return(Construct(s, Expr(0.f)))},
@@ -148,7 +148,7 @@
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->PipelineStageUses(),
               UnorderedElementsAre(sem::PipelineStageUsage::kVertexInput,
@@ -157,30 +157,30 @@
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderParamViaAlias) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
-  auto* s_alias = ty.alias("S_alias", s);
+  auto s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+  auto s_alias = ty.alias("S_alias", s);
 
   Func("main", {Param("param", s_alias)}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->PipelineStageUses(),
               UnorderedElementsAre(sem::PipelineStageUsage::kFragmentInput));
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderReturnTypeViaAlias) {
-  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
-  auto* s_alias = ty.alias("S_alias", s);
+  auto s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+  auto s_alias = ty.alias("S_alias", s);
 
   Func("main", {}, s_alias, {Return(Construct(s_alias, Expr(0.f)))},
        {Stage(ast::PipelineStage::kFragment)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->PipelineStageUses(),
               UnorderedElementsAre(sem::PipelineStageUsage::kFragmentOutput));
diff --git a/src/resolver/struct_storage_class_use_test.cc b/src/resolver/struct_storage_class_use_test.cc
index 4f0eb11..b17f512 100644
--- a/src/resolver/struct_storage_class_use_test.cc
+++ b/src/resolver/struct_storage_class_use_test.cc
@@ -28,156 +28,156 @@
 using ResolverStorageClassUseTest = ResolverTest;
 
 TEST_F(ResolverStorageClassUseTest, UnreachableStruct) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto s = Structure("S", {Member("a", ty.f32())});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_TRUE(sem->StorageClassUsage().empty());
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableFromParameter) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto s = Structure("S", {Member("a", ty.f32())});
 
   Func("f", {Param("param", s)}, ty.void_(), {}, {});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
               UnorderedElementsAre(ast::StorageClass::kNone));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableFromReturnType) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto s = Structure("S", {Member("a", ty.f32())});
 
   Func("f", {}, s, {Return(Construct(s))}, {});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
               UnorderedElementsAre(ast::StorageClass::kNone));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableFromGlobal) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto s = Structure("S", {Member("a", ty.f32())});
 
   Global("g", s, ast::StorageClass::kPrivate);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
               UnorderedElementsAre(ast::StorageClass::kPrivate));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalAlias) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* a = ty.alias("A", s);
+  auto s = Structure("S", {Member("a", ty.f32())});
+  auto a = ty.alias("A", s);
   Global("g", a, ast::StorageClass::kPrivate);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
               UnorderedElementsAre(ast::StorageClass::kPrivate));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalStruct) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* o = Structure("O", {Member("a", s)});
+  auto s = Structure("S", {Member("a", ty.f32())});
+  auto o = Structure("O", {Member("a", s)});
   Global("g", o, ast::StorageClass::kPrivate);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
               UnorderedElementsAre(ast::StorageClass::kPrivate));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalArray) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto s = Structure("S", {Member("a", ty.f32())});
   auto a = ty.array(s, 3);
   Global("g", a, ast::StorageClass::kPrivate);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
               UnorderedElementsAre(ast::StorageClass::kPrivate));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableFromLocal) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto s = Structure("S", {Member("a", ty.f32())});
 
   WrapInFunction(Var("g", s, ast::StorageClass::kFunction));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
               UnorderedElementsAre(ast::StorageClass::kFunction));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalAlias) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* a = ty.alias("A", s);
+  auto s = Structure("S", {Member("a", ty.f32())});
+  auto a = ty.alias("A", s);
   WrapInFunction(Var("g", a, ast::StorageClass::kFunction));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
               UnorderedElementsAre(ast::StorageClass::kFunction));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalStruct) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* o = Structure("O", {Member("a", s)});
+  auto s = Structure("S", {Member("a", ty.f32())});
+  auto o = Structure("O", {Member("a", s)});
   WrapInFunction(Var("g", o, ast::StorageClass::kFunction));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
               UnorderedElementsAre(ast::StorageClass::kFunction));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalArray) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto s = Structure("S", {Member("a", ty.f32())});
   auto a = ty.array(s, 3);
   WrapInFunction(Var("g", a, ast::StorageClass::kFunction));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
               UnorderedElementsAre(ast::StorageClass::kFunction));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructMultipleStorageClassUses) {
-  auto* s = Structure("S", {Member("a", ty.f32())},
-                      {create<ast::StructBlockDecoration>()});
-  auto* ac = ty.access(ast::AccessControl::kReadOnly, s);
+  auto s = Structure("S", {Member("a", ty.f32())},
+                     {create<ast::StructBlockDecoration>()});
+  auto ac = ty.access(ast::AccessControl::kReadOnly, s);
   Global("x", s, ast::StorageClass::kUniform);
   Global("y", ac, ast::StorageClass::kStorage);
   WrapInFunction(Var("g", s, ast::StorageClass::kFunction));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s);
+  auto* sem = Sem().Get(s.sem);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
               UnorderedElementsAre(ast::StorageClass::kUniform,
diff --git a/src/resolver/type_constructor_validation_test.cc b/src/resolver/type_constructor_validation_test.cc
index bf1b84b..ed44623 100644
--- a/src/resolver/type_constructor_validation_test.cc
+++ b/src/resolver/type_constructor_validation_test.cc
@@ -66,7 +66,7 @@
   // }
   auto& params = GetParam();
 
-  auto* rhs_type = params.create_rhs_type(ty);
+  auto rhs_type = params.create_rhs_type(ty);
   auto* constructor_expr = ConstructValueFilledWith(rhs_type, 0);
 
   auto sc = ast::StorageClass::kFunction;
@@ -114,7 +114,7 @@
   // }
   auto& params = GetParam();
 
-  auto* rhs_type = params.create_rhs_type(ty);
+  auto rhs_type = params.create_rhs_type(ty);
 
   auto* arith_lhs_expr = ConstructValueFilledWith(rhs_type, 2);
   auto* arith_rhs_expr = ConstructValueFilledWith(ElementTypeOf(rhs_type), 3);
@@ -159,7 +159,7 @@
   // }
   auto& params = GetParam();
 
-  auto* rhs_type = params.create_rhs_type(ty);
+  auto rhs_type = params.create_rhs_type(ty);
 
   Func("foo", {}, rhs_type, {Return(ConstructValueFilledWith(rhs_type, 0))},
        {});
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index 6c73a40..d9707aa 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -400,7 +400,7 @@
   //  a: u32;
   //}
 
-  auto* alias = ty.alias("RTArr", ty.array<u32>());
+  auto alias = ty.alias("RTArr", ty.array<u32>());
 
   Structure("s",
             {
@@ -426,7 +426,7 @@
   //  b: RTArr;
   //}
 
-  auto* alias = ty.alias("RTArr", ty.array<u32>());
+  auto alias = ty.alias("RTArr", ty.array<u32>());
 
   Structure("s",
             {
@@ -478,8 +478,8 @@
 TEST_P(CanonicalTest, All) {
   auto& params = GetParam();
 
-  auto* type = params.create_type(ty);
-  auto* expected_canonical_type = params.create_canonical_type(ty);
+  auto type = params.create_type(ty);
+  auto expected_canonical_type = params.create_canonical_type(ty);
 
   auto* canonical_type = r()->Canonical(type);
 
@@ -508,7 +508,7 @@
 using MultisampledTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
 TEST_P(MultisampledTextureDimensionTest, All) {
   auto& params = GetParam();
-  Global("a", create<sem::MultisampledTexture>(params.dim, ty.i32()),
+  Global("a", ty.multisampled_texture(params.dim, ty.i32()),
          ast::StorageClass::kUniformConstant, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
@@ -550,14 +550,14 @@
 using MultisampledTextureTypeTest = ResolverTestWithParam<TypeParams>;
 TEST_P(MultisampledTextureTypeTest, All) {
   auto& params = GetParam();
-  Global("a",
-         create<sem::MultisampledTexture>(ast::TextureDimension::k2d,
-                                          params.type_func(ty)),
-         ast::StorageClass::kUniformConstant, nullptr,
-         ast::DecorationList{
-             create<ast::BindingDecoration>(0),
-             create<ast::GroupDecoration>(0),
-         });
+  Global(
+      "a",
+      ty.multisampled_texture(ast::TextureDimension::k2d, params.type_func(ty)),
+      ast::StorageClass::kUniformConstant, nullptr,
+      ast::DecorationList{
+          create<ast::BindingDecoration>(0),
+          create<ast::GroupDecoration>(0),
+      });
 
   if (params.is_valid) {
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -588,10 +588,7 @@
 using StorageTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
 TEST_P(StorageTextureDimensionTest, All) {
   auto& params = GetParam();
-  Global("a",
-         create<sem::StorageTexture>(params.dim, ast::ImageFormat::kR32Uint,
-                                     sem::StorageTexture::SubtypeFor(
-                                         ast::ImageFormat::kR32Uint, Types())),
+  Global("a", ty.storage_texture(params.dim, ast::ImageFormat::kR32Uint),
          ast::StorageClass::kUniformConstant, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
@@ -660,20 +657,14 @@
   // var d : texture_storage_3<*>;
   // }
 
-  Global("a",
-         create<sem::StorageTexture>(
-             ast::TextureDimension::k1d, params.format,
-             sem::StorageTexture::SubtypeFor(params.format, Types())),
+  Global("a", ty.storage_texture(ast::TextureDimension::k1d, params.format),
          ast::StorageClass::kUniformConstant, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
          });
 
-  Global("b",
-         create<sem::StorageTexture>(
-             ast::TextureDimension::k2d, params.format,
-             sem::StorageTexture::SubtypeFor(params.format, Types())),
+  Global("b", ty.storage_texture(ast::TextureDimension::k2d, params.format),
          ast::StorageClass::kUniformConstant, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(1),
@@ -681,19 +672,14 @@
          });
 
   Global("c",
-         create<sem::StorageTexture>(
-             ast::TextureDimension::k2dArray, params.format,
-             sem::StorageTexture::SubtypeFor(params.format, Types())),
+         ty.storage_texture(ast::TextureDimension::k2dArray, params.format),
          ast::StorageClass::kUniformConstant, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(2),
              create<ast::GroupDecoration>(0),
          });
 
-  Global("d",
-         create<sem::StorageTexture>(
-             ast::TextureDimension::k3d, params.format,
-             sem::StorageTexture::SubtypeFor(params.format, Types())),
+  Global("d", ty.storage_texture(ast::TextureDimension::k3d, params.format),
          ast::StorageClass::kUniformConstant, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(3),
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
index 5d941a2..5d213c0 100644
--- a/src/resolver/validation_test.cc
+++ b/src/resolver/validation_test.cc
@@ -164,7 +164,7 @@
 
 TEST_F(ResolverValidationTest,
        Stmt_VariableDecl_MismatchedTypeScalarConstructor_Alias) {
-  auto* my_int = ty.alias("MyInt", ty.i32());
+  auto my_int = ty.alias("MyInt", ty.i32());
   u32 unsigned_value = 2u;  // Type does not match variable type
   auto* var =
       Var("my_var", my_int, ast::StorageClass::kNone, Expr(unsigned_value));
@@ -1684,7 +1684,7 @@
 }
 
 TEST_F(ResolverValidationTest, Expr_Constructor_Vector_Alias_Argument_Error) {
-  auto* alias = ty.alias("UnsignedInt", ty.u32());
+  auto alias = ty.alias("UnsignedInt", ty.u32());
   Global("uint_var", alias, ast::StorageClass::kInput);
 
   auto* tc = vec2<f32>(Expr(Source{{12, 34}}, "uint_var"));
@@ -1697,8 +1697,8 @@
 }
 
 TEST_F(ResolverValidationTest, Expr_Constructor_Vector_Alias_Argument_Success) {
-  auto* f32_alias = ty.alias("Float32", ty.f32());
-  auto* vec2_alias = ty.alias("VectorFloat2", ty.vec2<f32>());
+  auto f32_alias = ty.alias("Float32", ty.f32());
+  auto vec2_alias = ty.alias("VectorFloat2", ty.vec2<f32>());
   Global("my_f32", f32_alias, ast::StorageClass::kInput);
   Global("my_vec2", vec2_alias, ast::StorageClass::kInput);
 
@@ -1708,7 +1708,7 @@
 }
 
 TEST_F(ResolverValidationTest, Expr_Constructor_Vector_ElementTypeAlias_Error) {
-  auto* f32_alias = ty.alias("Float32", ty.f32());
+  auto f32_alias = ty.alias("Float32", ty.f32());
   auto* vec_type = create<sem::Vector>(f32_alias, 2);
 
   // vec2<Float32>(1.0f, 1u)
@@ -1726,7 +1726,7 @@
 
 TEST_F(ResolverValidationTest,
        Expr_Constructor_Vector_ElementTypeAlias_Success) {
-  auto* f32_alias = ty.alias("Float32", ty.f32());
+  auto f32_alias = ty.alias("Float32", ty.f32());
   auto* vec_type = create<sem::Vector>(f32_alias, 2);
 
   // vec2<Float32>(1.0f, 1.0f)
@@ -1739,7 +1739,7 @@
 
 TEST_F(ResolverValidationTest,
        Expr_Constructor_Vector_ArgumentElementTypeAlias_Error) {
-  auto* f32_alias = ty.alias("Float32", ty.f32());
+  auto f32_alias = ty.alias("Float32", ty.f32());
   auto* vec_type = create<sem::Vector>(f32_alias, 2);
 
   // vec3<u32>(vec<Float32>(), 1.0f)
@@ -1756,7 +1756,7 @@
 
 TEST_F(ResolverValidationTest,
        Expr_Constructor_Vector_ArgumentElementTypeAlias_Success) {
-  auto* f32_alias = ty.alias("Float32", ty.f32());
+  auto f32_alias = ty.alias("Float32", ty.f32());
   auto* vec_type = create<sem::Vector>(f32_alias, 2);
 
   // vec3<f32>(vec<Float32>(), 1.0f)
@@ -1789,8 +1789,8 @@
   // matNxM<f32>(vecM<f32>(), ...); with N - 1 arguments
 
   const auto param = GetParam();
-  auto* matrix_type = create<sem::Matrix>(ty.f32(), param.rows, param.columns);
-  auto* vec_type = create<sem::Vector>(ty.f32(), param.rows);
+  auto matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto vec_type = ty.vec<f32>(param.rows);
 
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns - 1; i++) {
@@ -1813,8 +1813,8 @@
   // matNxM<f32>(vecM<f32>(), ...); with N + 1 arguments
 
   const auto param = GetParam();
-  auto* matrix_type = create<sem::Matrix>(ty.f32(), param.rows, param.columns);
-  auto* vec_type = create<sem::Vector>(ty.f32(), param.rows);
+  auto matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto vec_type = ty.vec<f32>(param.rows);
 
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns + 1; i++) {
@@ -1837,7 +1837,7 @@
   // matNxM<f32>(1.0, 1.0, ...); N arguments
 
   const auto param = GetParam();
-  auto* matrix_type = create<sem::Matrix>(ty.f32(), param.rows, param.columns);
+  auto matrix_type = ty.mat<f32>(param.columns, param.rows);
 
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns; i++) {
@@ -1866,9 +1866,9 @@
     return;
   }
 
-  auto* matrix_type = create<sem::Matrix>(ty.f32(), param.rows, param.columns);
-  auto* valid_vec_type = create<sem::Vector>(ty.f32(), param.rows);
-  auto* invalid_vec_type = create<sem::Vector>(ty.f32(), param.rows - 1);
+  auto matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto valid_vec_type = ty.vec<f32>(param.rows);
+  auto invalid_vec_type = ty.vec<f32>(param.rows - 1);
 
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns - 1; i++) {
@@ -1902,9 +1902,9 @@
     return;
   }
 
-  auto* matrix_type = create<sem::Matrix>(ty.f32(), param.rows, param.columns);
-  auto* valid_vec_type = create<sem::Vector>(ty.f32(), param.rows);
-  auto* invalid_vec_type = create<sem::Vector>(ty.f32(), param.rows + 1);
+  auto matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto valid_vec_type = ty.vec<f32>(param.rows);
+  auto invalid_vec_type = ty.vec<f32>(param.rows + 1);
 
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns - 1; i++) {
@@ -1932,7 +1932,7 @@
   // matNxM<f32>(vecM<u32>(), ...); with N arguments
 
   const auto param = GetParam();
-  auto* matrix_type = create<sem::Matrix>(ty.f32(), param.rows, param.columns);
+  auto matrix_type = ty.mat<f32>(param.columns, param.rows);
   auto* vec_type = create<sem::Vector>(ty.u32(), param.rows);
 
   ast::ExpressionList args;
@@ -1956,7 +1956,7 @@
   // matNxM<f32>();
 
   const auto param = GetParam();
-  auto* matrix_type = create<sem::Matrix>(ty.f32(), param.rows, param.columns);
+  auto matrix_type = ty.mat<f32>(param.columns, param.rows);
   auto* tc = create<ast::TypeConstructorExpression>(Source{{12, 40}},
                                                     matrix_type, ExprList());
   WrapInFunction(tc);
@@ -1968,8 +1968,8 @@
   // matNxM<f32>(vecM<f32>(), ...); with N arguments
 
   const auto param = GetParam();
-  auto* matrix_type = create<sem::Matrix>(ty.f32(), param.rows, param.columns);
-  auto* vec_type = create<sem::Vector>(ty.f32(), param.rows);
+  auto matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto vec_type = ty.vec<f32>(param.rows);
 
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns; i++) {
@@ -1988,8 +1988,8 @@
   // matNxM<Float32>(vecM<u32>(), ...); with N arguments
 
   const auto param = GetParam();
-  auto* f32_alias = ty.alias("Float32", ty.f32());
-  auto* matrix_type = create<sem::Matrix>(f32_alias, param.rows, param.columns);
+  auto f32_alias = ty.alias("Float32", ty.f32());
+  auto matrix_type = ty.mat(f32_alias, param.columns, param.rows);
   auto* vec_type = create<sem::Vector>(ty.u32(), param.rows);
 
   ast::ExpressionList args;
@@ -2013,9 +2013,9 @@
   // matNxM<Float32>(vecM<f32>(), ...); with N arguments
 
   const auto param = GetParam();
-  auto* f32_alias = ty.alias("Float32", ty.f32());
-  auto* matrix_type = create<sem::Matrix>(f32_alias, param.rows, param.columns);
-  auto* vec_type = create<sem::Vector>(ty.f32(), param.rows);
+  auto f32_alias = ty.alias("Float32", ty.f32());
+  auto matrix_type = ty.mat(f32_alias, param.columns, param.rows);
+  auto vec_type = ty.vec<f32>(param.rows);
 
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns; i++) {
@@ -2031,7 +2031,7 @@
 }
 
 TEST_F(ResolverValidationTest, Expr_MatrixConstructor_ArgumentTypeAlias_Error) {
-  auto* vec2_alias = ty.alias("VectorUnsigned2", ty.vec2<u32>());
+  auto vec2_alias = ty.alias("VectorUnsigned2", ty.vec2<u32>());
   auto* tc = mat2x2<f32>(create<ast::TypeConstructorExpression>(
                              Source{{12, 34}}, vec2_alias, ExprList()),
                          vec2<f32>());
@@ -2045,9 +2045,9 @@
 
 TEST_P(MatrixConstructorTest, Expr_Constructor_ArgumentTypeAlias_Success) {
   const auto param = GetParam();
-  auto* matrix_type = create<sem::Matrix>(ty.f32(), param.rows, param.columns);
-  auto* vec_type = create<sem::Vector>(ty.f32(), param.rows);
-  auto* vec_alias = ty.alias("VectorFloat2", vec_type);
+  auto matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto vec_type = ty.vec<f32>(param.rows);
+  auto vec_alias = ty.alias("VectorFloat2", vec_type);
 
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns; i++) {
@@ -2064,8 +2064,8 @@
 
 TEST_P(MatrixConstructorTest, Expr_Constructor_ArgumentElementTypeAlias_Error) {
   const auto param = GetParam();
-  auto* matrix_type = create<sem::Matrix>(ty.f32(), param.rows, param.columns);
-  auto* f32_alias = ty.alias("UnsignedInt", ty.u32());
+  auto matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto f32_alias = ty.alias("UnsignedInt", ty.u32());
   auto* vec_type = create<sem::Vector>(f32_alias, param.rows);
 
   ast::ExpressionList args;
@@ -2088,8 +2088,8 @@
 TEST_P(MatrixConstructorTest,
        Expr_Constructor_ArgumentElementTypeAlias_Success) {
   const auto param = GetParam();
-  auto* matrix_type = create<sem::Matrix>(ty.f32(), param.rows, param.columns);
-  auto* f32_alias = ty.alias("Float32", ty.f32());
+  auto matrix_type = ty.mat<f32>(param.columns, param.rows);
+  auto f32_alias = ty.alias("Float32", ty.f32());
   auto* vec_type = create<sem::Vector>(f32_alias, param.rows);
 
   ast::ExpressionList args;
diff --git a/src/sem/alias_type_test.cc b/src/sem/alias_type_test.cc
index e113097..05bb852 100644
--- a/src/sem/alias_type_test.cc
+++ b/src/sem/alias_type_test.cc
@@ -23,13 +23,13 @@
 using AliasTest = TestHelper;
 
 TEST_F(AliasTest, Create) {
-  auto* a = ty.alias("a_type", ty.u32());
+  auto* a = create<Alias>(Sym("a_type"), ty.u32());
   EXPECT_EQ(a->symbol(), Symbol(1, ID()));
   EXPECT_EQ(a->type(), ty.u32());
 }
 
 TEST_F(AliasTest, Is) {
-  auto* at = ty.alias("a", ty.i32());
+  auto* at = create<Alias>(Sym("a"), ty.i32());
   sem::Type* ty = at;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_TRUE(ty->Is<Alias>());
@@ -47,17 +47,17 @@
 }
 
 TEST_F(AliasTest, TypeName) {
-  auto* at = ty.alias("Particle", ty.i32());
+  auto* at = create<Alias>(Sym("Particle"), ty.i32());
   EXPECT_EQ(at->type_name(), "__alias_$1__i32");
 }
 
 TEST_F(AliasTest, FriendlyName) {
-  auto* at = ty.alias("Particle", ty.i32());
+  auto* at = create<Alias>(Sym("Particle"), ty.i32());
   EXPECT_EQ(at->FriendlyName(Symbols()), "Particle");
 }
 
 TEST_F(AliasTest, UnwrapIfNeeded_Alias) {
-  auto* a = ty.alias("a_type", ty.u32());
+  auto* a = create<Alias>(Sym("a_type"), ty.u32());
   EXPECT_EQ(a->symbol(), Symbol(1, ID()));
   EXPECT_EQ(a->type(), ty.u32());
   EXPECT_EQ(a->UnwrapIfNeeded(), ty.u32());
@@ -65,14 +65,14 @@
 }
 
 TEST_F(AliasTest, UnwrapIfNeeded_AccessControl) {
-  AccessControl a{ast::AccessControl::kReadOnly, ty.u32()};
-  EXPECT_EQ(a.type(), ty.u32());
-  EXPECT_EQ(a.UnwrapIfNeeded(), ty.u32());
+  auto* a = create<AccessControl>(ast::AccessControl::kReadOnly, ty.u32());
+  EXPECT_EQ(a->type(), ty.u32());
+  EXPECT_EQ(a->UnwrapIfNeeded(), ty.u32());
 }
 
 TEST_F(AliasTest, UnwrapIfNeeded_MultiLevel) {
-  auto* a = ty.alias("a_type", ty.u32());
-  auto* aa = ty.alias("aa_type", a);
+  auto* a = create<Alias>(Sym("a_type"), ty.u32());
+  auto* aa = create<Alias>(Sym("aa_type"), a);
 
   EXPECT_EQ(aa->symbol(), Symbol(2, ID()));
   EXPECT_EQ(aa->type(), a);
@@ -80,19 +80,19 @@
 }
 
 TEST_F(AliasTest, UnwrapIfNeeded_MultiLevel_AliasAccessControl) {
-  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());
+  auto* a = create<Alias>(Sym("a_type"), ty.u32());
+  auto* aa = create<AccessControl>(ast::AccessControl::kReadWrite, a);
+  EXPECT_EQ(aa->type(), a);
+  EXPECT_EQ(aa->UnwrapIfNeeded(), ty.u32());
 }
 
 TEST_F(AliasTest, UnwrapAll_TwiceAliasPointerTwiceAlias) {
-  auto* a = ty.alias("a_type", ty.u32());
-  auto* aa = ty.alias("aa_type", a);
-  Pointer paa{aa, ast::StorageClass::kUniform};
-  auto* apaa = ty.alias("paa_type", &paa);
-  auto* aapaa = ty.alias("aapaa_type", apaa);
+  auto* u32 = create<U32>();
+  auto* a = create<Alias>(Sym(Sym("a_type")), u32);
+  auto* aa = create<Alias>(Sym("aa_type"), a);
+  auto* paa = create<Pointer>(aa, ast::StorageClass::kUniform);
+  auto* apaa = create<Alias>(Sym("paa_type"), paa);
+  auto* aapaa = create<Alias>(Sym("aapaa_type"), apaa);
 
   EXPECT_EQ(aapaa->symbol(), Symbol(4, ID()));
   EXPECT_EQ(aapaa->type(), apaa);
@@ -100,47 +100,47 @@
 }
 
 TEST_F(AliasTest, UnwrapAll_SecondConsecutivePointerBlocksUnrapping) {
-  auto* a = ty.alias("a_type", ty.u32());
-  auto* aa = ty.alias("aa_type", a);
+  auto* a = create<Alias>(Sym("a_type"), ty.u32());
+  auto* aa = create<Alias>(Sym("aa_type"), a);
 
-  Pointer paa{aa, ast::StorageClass::kUniform};
-  Pointer ppaa{&paa, ast::StorageClass::kUniform};
-  auto* appaa = ty.alias("appaa_type", &ppaa);
-  EXPECT_EQ(appaa->UnwrapAll(), &paa);
+  auto* paa = create<Pointer>(aa, ast::StorageClass::kUniform);
+  auto* ppaa = create<Pointer>(paa, ast::StorageClass::kUniform);
+  auto* appaa = create<Alias>(Sym("appaa_type"), ppaa);
+  EXPECT_EQ(appaa->UnwrapAll(), paa);
 }
 
 TEST_F(AliasTest, UnwrapAll_SecondNonConsecutivePointerBlocksUnrapping) {
-  auto* a = ty.alias("a_type", ty.u32());
-  auto* aa = ty.alias("aa_type", a);
-  Pointer paa{aa, ast::StorageClass::kUniform};
+  auto* a = create<Alias>(Sym("a_type"), ty.u32());
+  auto* aa = create<Alias>(Sym("aa_type"), a);
+  auto* paa = create<Pointer>(aa, ast::StorageClass::kUniform);
 
-  auto* apaa = ty.alias("apaa_type", &paa);
-  auto* aapaa = ty.alias("aapaa_type", apaa);
-  Pointer paapaa{aapaa, ast::StorageClass::kUniform};
-  auto* apaapaa = ty.alias("apaapaa_type", &paapaa);
+  auto* apaa = create<Alias>(Sym("apaa_type"), paa);
+  auto* aapaa = create<Alias>(Sym("aapaa_type"), apaa);
+  auto* paapaa = create<Pointer>(aapaa, ast::StorageClass::kUniform);
+  auto* apaapaa = create<Alias>(Sym("apaapaa_type"), paapaa);
 
-  EXPECT_EQ(apaapaa->UnwrapAll(), &paa);
+  EXPECT_EQ(apaapaa->UnwrapAll(), paa);
 }
 
 TEST_F(AliasTest, UnwrapAll_AccessControlPointer) {
-  AccessControl a{ast::AccessControl::kReadOnly, ty.u32()};
-  Pointer pa{&a, ast::StorageClass::kUniform};
-  EXPECT_EQ(pa.type(), &a);
-  EXPECT_EQ(pa.UnwrapAll(), ty.u32());
+  auto* a = create<AccessControl>(ast::AccessControl::kReadOnly, ty.u32());
+  auto* pa = create<Pointer>(a, ast::StorageClass::kUniform);
+  EXPECT_EQ(pa->type(), a);
+  EXPECT_EQ(pa->UnwrapAll(), ty.u32());
 }
 
 TEST_F(AliasTest, UnwrapAll_PointerAccessControl) {
-  Pointer p{ty.u32(), ast::StorageClass::kUniform};
-  AccessControl a{ast::AccessControl::kReadOnly, &p};
+  auto* p = create<Pointer>(ty.u32(), ast::StorageClass::kUniform);
+  auto* a = create<AccessControl>(ast::AccessControl::kReadOnly, p);
 
-  EXPECT_EQ(a.type(), &p);
-  EXPECT_EQ(a.UnwrapAll(), ty.u32());
+  EXPECT_EQ(a->type(), p);
+  EXPECT_EQ(a->UnwrapAll(), ty.u32());
 }
 
 TEST_F(AliasTest, UnwrapAliasIfNeeded) {
-  auto* alias1 = ty.alias("alias1", ty.f32());
-  auto* alias2 = ty.alias("alias2", alias1);
-  auto* alias3 = ty.alias("alias3", alias2);
+  auto* alias1 = create<Alias>(Sym("alias1"), ty.f32());
+  auto* alias2 = create<Alias>(Sym("alias2"), alias1);
+  auto* alias3 = create<Alias>(Sym("alias3"), alias2);
   EXPECT_EQ(alias3->UnwrapAliasIfNeeded(), ty.f32());
 }
 
diff --git a/src/sem/struct_type_test.cc b/src/sem/struct_type_test.cc
index 1e36821..ab73408 100644
--- a/src/sem/struct_type_test.cc
+++ b/src/sem/struct_type_test.cc
@@ -27,7 +27,7 @@
   auto* impl =
       create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
   auto* ptr = impl;
-  auto* s = ty.struct_(impl);
+  auto s = ty.struct_(impl);
   EXPECT_EQ(s->impl(), ptr);
 }
 
@@ -35,7 +35,7 @@
   auto name = Sym("S");
   auto* impl =
       create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
-  auto* s = ty.struct_(impl);
+  auto s = ty.struct_(impl);
   sem::Type* ty = s;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
@@ -56,7 +56,7 @@
   auto name = Sym("my_struct");
   auto* impl =
       create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
-  auto* s = ty.struct_(impl);
+  auto s = ty.struct_(impl);
   EXPECT_EQ(s->type_name(), "__struct_$1");
 }
 
@@ -64,7 +64,7 @@
   auto name = Sym("my_struct");
   auto* impl =
       create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
-  auto* s = ty.struct_(impl);
+  auto s = ty.struct_(impl);
   EXPECT_EQ(s->FriendlyName(Symbols()), "my_struct");
 }
 
diff --git a/src/transform/first_index_offset.cc b/src/transform/first_index_offset.cc
index 2ee4e1e..c3bb082 100644
--- a/src/transform/first_index_offset.cc
+++ b/src/transform/first_index_offset.cc
@@ -136,7 +136,7 @@
       instance_index_offset = offset;
       offset += 4;
     }
-    auto* struct_type =
+    auto struct_type =
         ctx.dst->Structure(ctx.dst->Symbols().New(), std::move(members),
                            {ctx.dst->create<ast::StructBlockDecoration>()});
 
diff --git a/src/transform/vertex_pulling.cc b/src/transform/vertex_pulling.cc
index 5a289d6..98e4000 100644
--- a/src/transform/vertex_pulling.cc
+++ b/src/transform/vertex_pulling.cc
@@ -198,7 +198,7 @@
 
     // Creating the struct type
     static const char kStructName[] = "TintVertexData";
-    auto* struct_type = ctx.dst->Structure(
+    auto struct_type = ctx.dst->Structure(
         ctx.dst->Symbols().New(kStructName),
         {
             ctx.dst->Member(GetStructBufferName(),
@@ -207,7 +207,7 @@
         {
             ctx.dst->create<ast::StructBlockDecoration>(),
         });
-    auto* access =
+    auto access =
         ctx.dst->ty.access(ast::AccessControl::kReadOnly, struct_type);
     for (uint32_t i = 0; i < cfg.vertex_state.size(); ++i) {
       // The decorated variable with struct type
diff --git a/src/typepair.h b/src/typepair.h
index 89c7587..8f34925 100644
--- a/src/typepair.h
+++ b/src/typepair.h
@@ -22,22 +22,40 @@
 namespace tint {
 
 namespace ast {
+class AccessControl;
+class Alias;
 class Array;
 class Bool;
+class DepthTexture;
 class F32;
 class I32;
 class Matrix;
+class MultisampledTexture;
+class Pointer;
+class Sampler;
+class SampledTexture;
+class StorageTexture;
+class Struct;
 class U32;
 class Vector;
 class Void;
 }  // namespace ast
 
 namespace sem {
+class AccessControl;
+class Alias;
 class ArrayType;
 class Bool;
+class DepthTexture;
 class F32;
 class I32;
 class Matrix;
+class MultisampledTexture;
+class Pointer;
+class Sampler;
+class SampledTexture;
+class StorageTexture;
+class StructType;
 class U32;
 class Vector;
 class Void;
@@ -59,9 +77,9 @@
 template <typename AST, typename SEM>
 struct TypePair {
   /// The ast::Type pointer
-  AST const* const ast = nullptr;
+  AST const* ast = nullptr;
   /// The sem::Type pointer
-  SEM const* const sem = nullptr;
+  SEM const* sem = nullptr;
 
   /// Constructor
   TypePair() = default;
@@ -89,15 +107,42 @@
   operator SEM*() const { return const_cast<SEM*>(sem); }
   /// @returns the sem::Type pointer
   SEM* operator->() const { return const_cast<SEM*>(sem); }
+
+  /// @param ty the semantic type to compare against
+  /// @returns true if the semantic type is equal to `ty`
+  bool operator==(sem::Type* ty) const { return sem == ty; }
+
+  /// @param other the TypePair to compare against
+  /// @returns true if this TypePair is less than `other`
+  template <typename OTHER_AST, typename OTHER_SEM>
+  bool operator<(const TypePair<OTHER_AST, OTHER_SEM>& other) const {
+    if (sem < other.sem) {
+      return true;
+    }
+    if (sem > other.sem) {
+      return false;
+    }
+    return ast < other.ast;
+  }
 };
 
 using Type = TypePair<ast::Type, sem::Type>;
 
+using AccessControl = TypePair<ast::AccessControl, sem::AccessControl>;
+using Alias = TypePair<ast::Alias, sem::Alias>;
 using Array = TypePair<ast::Array, sem::ArrayType>;
 using Bool = TypePair<ast::Bool, sem::Bool>;
+using DepthTexture = TypePair<ast::DepthTexture, sem::DepthTexture>;
 using F32 = TypePair<ast::F32, sem::F32>;
 using I32 = TypePair<ast::I32, sem::I32>;
 using Matrix = TypePair<ast::Matrix, sem::Matrix>;
+using MultisampledTexture =
+    TypePair<ast::MultisampledTexture, sem::MultisampledTexture>;
+using Pointer = TypePair<ast::Pointer, sem::Pointer>;
+using Sampler = TypePair<ast::Sampler, sem::Sampler>;
+using SampledTexture = TypePair<ast::SampledTexture, sem::SampledTexture>;
+using StorageTexture = TypePair<ast::StorageTexture, sem::StorageTexture>;
+using Struct = TypePair<ast::Struct, sem::StructType>;
 using U32 = TypePair<ast::U32, sem::U32>;
 using Vector = TypePair<ast::Vector, sem::Vector>;
 using Void = TypePair<ast::Void, sem::Void>;
diff --git a/src/writer/hlsl/generator_impl_alias_type_test.cc b/src/writer/hlsl/generator_impl_alias_type_test.cc
index 3837cde..f398d8c 100644
--- a/src/writer/hlsl/generator_impl_alias_type_test.cc
+++ b/src/writer/hlsl/generator_impl_alias_type_test.cc
@@ -25,7 +25,7 @@
 using HlslGeneratorImplTest_Alias = TestHelper;
 
 TEST_F(HlslGeneratorImplTest_Alias, EmitAlias_F32) {
-  auto* alias = ty.alias("a", ty.f32());
+  auto alias = ty.alias("a", ty.f32());
 
   GeneratorImpl& gen = Build();
 
@@ -45,11 +45,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Alias, EmitAlias_Struct) {
-  auto* s = Structure("A", {
-                               Member("a", ty.f32()),
-                               Member("b", ty.i32()),
-                           });
-  auto* alias = ty.alias("B", s);
+  auto s = Structure("A", {
+                              Member("a", ty.f32()),
+                              Member("b", ty.i32()),
+                          });
+  auto alias = ty.alias("B", s);
   AST().AddConstructedType(alias);
   Global("g", alias, ast::StorageClass::kPrivate);
 
diff --git a/src/writer/hlsl/generator_impl_constructor_test.cc b/src/writer/hlsl/generator_impl_constructor_test.cc
index 2bee83b..ab0e642 100644
--- a/src/writer/hlsl/generator_impl_constructor_test.cc
+++ b/src/writer/hlsl/generator_impl_constructor_test.cc
@@ -195,11 +195,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct) {
-  auto* str = Structure("S", {
-                                 Member("a", ty.i32()),
-                                 Member("b", ty.f32()),
-                                 Member("c", ty.vec3<i32>()),
-                             });
+  auto str = Structure("S", {
+                                Member("a", ty.i32()),
+                                Member("b", ty.f32()),
+                                Member("c", ty.vec3<i32>()),
+                            });
 
   WrapInFunction(Construct(str, 1, 2.0f, vec3<i32>(3, 4, 5)));
 
@@ -212,11 +212,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct_Empty) {
-  auto* str = Structure("S", {
-                                 Member("a", ty.i32()),
-                                 Member("b", ty.f32()),
-                                 Member("c", ty.vec3<i32>()),
-                             });
+  auto str = Structure("S", {
+                                Member("a", ty.i32()),
+                                Member("b", ty.f32()),
+                                Member("c", ty.vec3<i32>()),
+                            });
 
   WrapInFunction(Construct(str));
 
diff --git a/src/writer/hlsl/generator_impl_function_test.cc b/src/writer/hlsl/generator_impl_function_test.cc
index c59088c..3292320 100644
--- a/src/writer/hlsl/generator_impl_function_test.cc
+++ b/src/writer/hlsl/generator_impl_function_test.cc
@@ -179,7 +179,7 @@
   //   const g = inputs.col2;
   //   const p = inputs.pos;
   // }
-  auto* interface_struct = Structure(
+  auto interface_struct = Structure(
       "Interface",
       {
           Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
@@ -252,7 +252,7 @@
   // fn vert_main2() -> VertexOutput {
   //   return foo(0.25);
   // }
-  auto* vertex_output_struct = Structure(
+  auto vertex_output_struct = Structure(
       "VertexOutput",
       {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
 
@@ -307,8 +307,8 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_Uniform) {
-  auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())},
-                           {create<ast::StructBlockDecoration>()});
+  auto ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())},
+                          {create<ast::StructBlockDecoration>()});
   auto* ubo = Global(
       "ubo", ubo_ty, ast::StorageClass::kUniform, nullptr,
       {create<ast::BindingDecoration>(0), create<ast::GroupDecoration>(1)});
@@ -359,8 +359,8 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_UniformStruct) {
-  auto* s = Structure("Uniforms", {Member("coord", ty.vec4<f32>())},
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("Uniforms", {Member("coord", ty.vec4<f32>())},
+                     {create<ast::StructBlockDecoration>()});
 
   Global("uniforms", s, ast::StorageClass::kUniform, nullptr,
          {
@@ -401,12 +401,12 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_RW_StorageBuffer_Read) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("Data",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", ty.f32()),
+                     },
+                     {create<ast::StructBlockDecoration>()});
 
   sem::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
@@ -447,12 +447,12 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_RO_StorageBuffer_Read) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("Data",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", ty.f32()),
+                     },
+                     {create<ast::StructBlockDecoration>()});
 
   sem::AccessControl ac(ast::AccessControl::kReadOnly, s);
 
@@ -493,12 +493,12 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_WO_StorageBuffer_Store) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("Data",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", ty.f32()),
+                     },
+                     {create<ast::StructBlockDecoration>()});
 
   sem::AccessControl ac(ast::AccessControl::kWriteOnly, s);
 
@@ -536,12 +536,12 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_StorageBuffer_Store) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("Data",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", ty.f32()),
+                     },
+                     {create<ast::StructBlockDecoration>()});
 
   sem::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
@@ -742,8 +742,8 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_Called_By_EntryPoint_With_Uniform) {
-  auto* s = Structure("S", {Member("x", ty.f32())},
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("S", {Member("x", ty.f32())},
+                     {create<ast::StructBlockDecoration>()});
   Global("coord", s, ast::StorageClass::kUniform, nullptr,
          {
              create<ast::BindingDecoration>(0),
@@ -792,9 +792,9 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_Called_By_EntryPoint_With_StorageBuffer) {
-  auto* s = Structure("S", {Member("x", ty.f32())},
-                      {create<ast::StructBlockDecoration>()});
-  auto* ac = ty.access(ast::AccessControl::kReadWrite, s);
+  auto s = Structure("S", {Member("x", ty.f32())},
+                     {create<ast::StructBlockDecoration>()});
+  auto ac = ty.access(ast::AccessControl::kReadWrite, s);
   Global("coord", ac, ast::StorageClass::kStorage, nullptr,
          {
              create<ast::BindingDecoration>(0),
@@ -981,8 +981,8 @@
   //   return;
   // }
 
-  auto* s = Structure("Data", {Member("d", ty.f32())},
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("Data", {Member("d", ty.f32())},
+                     {create<ast::StructBlockDecoration>()});
 
   sem::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
diff --git a/src/writer/hlsl/generator_impl_member_accessor_test.cc b/src/writer/hlsl/generator_impl_member_accessor_test.cc
index 76bedc7..04efb7d 100644
--- a/src/writer/hlsl/generator_impl_member_accessor_test.cc
+++ b/src/writer/hlsl/generator_impl_member_accessor_test.cc
@@ -96,11 +96,10 @@
   void SetupStorageBuffer(ast::StructMemberList members) {
     ProgramBuilder& b = *this;
 
-    auto* s =
+    auto s =
         b.Structure("Data", members, {b.create<ast::StructBlockDecoration>()});
 
-    auto* ac_ty =
-        b.create<sem::AccessControl>(ast::AccessControl::kReadWrite, s);
+    auto ac_ty = b.ty.access(ast::AccessControl::kReadWrite, s);
 
     b.Global("data", ac_ty, ast::StorageClass::kStorage, nullptr,
              ast::DecorationList{
@@ -126,7 +125,7 @@
     HlslGeneratorImplTest_MemberAccessorBase<TestParamHelper<T>>;
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor, EmitExpression_MemberAccessor) {
-  auto* s = Structure("Data", {Member("mem", ty.f32())});
+  auto s = Structure("Data", {Member("mem", ty.f32())});
   Global("str", s, ast::StorageClass::kPrivate);
 
   auto* expr = MemberAccessor("str", "mem");
@@ -520,10 +519,10 @@
   // var<storage> data : Pre;
   // data.c[2].b
 
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
+  auto inner = Structure("Inner", {
+                                      Member("a", ty.vec3<f32>()),
+                                      Member("b", ty.vec3<f32>()),
+                                  });
 
   SetupStorageBuffer({
       Member("c", ty.array(inner, 4, 32)),
@@ -566,10 +565,10 @@
   // var<storage> data : Pre;
   // data.c[2].b.xy
 
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
+  auto inner = Structure("Inner", {
+                                      Member("a", ty.vec3<f32>()),
+                                      Member("b", ty.vec3<f32>()),
+                                  });
 
   SetupStorageBuffer({
       Member("c", ty.array(inner, 4, 32)),
@@ -614,10 +613,10 @@
   // var<storage> data : Pre;
   // data.c[2].b.g
 
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
+  auto inner = Structure("Inner", {
+                                      Member("a", ty.vec3<f32>()),
+                                      Member("b", ty.vec3<f32>()),
+                                  });
 
   SetupStorageBuffer({
       Member("c", ty.array(inner, 4, 32)),
@@ -662,10 +661,10 @@
   // var<storage> data : Pre;
   // data.c[2].b[1]
 
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
+  auto inner = Structure("Inner", {
+                                      Member("a", ty.vec3<f32>()),
+                                      Member("b", ty.vec3<f32>()),
+                                  });
 
   SetupStorageBuffer({
       Member("c", ty.array(inner, 4, 32)),
@@ -709,10 +708,10 @@
   // var<storage> data : Pre;
   // data.c[2].b = vec3<f32>(1.f, 2.f, 3.f);
 
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<f32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
+  auto inner = Structure("Inner", {
+                                      Member("a", ty.vec3<f32>()),
+                                      Member("b", ty.vec3<f32>()),
+                                  });
 
   SetupStorageBuffer({
       Member("c", ty.array(inner, 4, 32)),
@@ -754,10 +753,10 @@
   // var<storage> data : Pre;
   // data.c[2].b.y = 1.f;
 
-  auto* inner = Structure("Inner", {
-                                       Member("a", ty.vec3<i32>()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
+  auto inner = Structure("Inner", {
+                                      Member("a", ty.vec3<i32>()),
+                                      Member("b", ty.vec3<f32>()),
+                                  });
 
   SetupStorageBuffer({
       Member("c", ty.array(inner, 4, 32)),
diff --git a/src/writer/hlsl/generator_impl_sanitizer_test.cc b/src/writer/hlsl/generator_impl_sanitizer_test.cc
index 0cbb2b3..940b087 100644
--- a/src/writer/hlsl/generator_impl_sanitizer_test.cc
+++ b/src/writer/hlsl/generator_impl_sanitizer_test.cc
@@ -26,16 +26,15 @@
 using HlslSanitizerTest = TestHelper;
 
 TEST_F(HlslSanitizerTest, ArrayLength) {
-  auto* sb_ty = Structure("SB",
-                          {
-                              Member("x", ty.f32()),
-                              Member("arr", ty.array(ty.vec4<f32>())),
-                          },
-                          {
-                              create<ast::StructBlockDecoration>(),
-                          });
-  auto* ac_ty =
-      create<sem::AccessControl>(ast::AccessControl::kReadOnly, sb_ty);
+  auto sb_ty = Structure("SB",
+                         {
+                             Member("x", ty.f32()),
+                             Member("arr", ty.array(ty.vec4<f32>())),
+                         },
+                         {
+                             create<ast::StructBlockDecoration>(),
+                         });
+  auto ac_ty = ty.access(ast::AccessControl::kReadOnly, sb_ty);
 
   Global("sb", ac_ty, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{
@@ -101,11 +100,11 @@
 }
 
 TEST_F(HlslSanitizerTest, PromoteStructInitializerToConstVar) {
-  auto* str = Structure("S", {
-                                 Member("a", ty.i32()),
-                                 Member("b", ty.vec3<f32>()),
-                                 Member("c", ty.i32()),
-                             });
+  auto str = Structure("S", {
+                                Member("a", ty.i32()),
+                                Member("b", ty.vec3<f32>()),
+                                Member("c", ty.i32()),
+                            });
   auto* struct_init = Construct(str, 1, vec3<f32>(2.f, 3.f, 4.f), 4);
   auto* struct_access = MemberAccessor(struct_init, "b");
   auto* pos =
diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc
index 68556cc..6a4bdbf 100644
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ b/src/writer/hlsl/generator_impl_type_test.cc
@@ -33,7 +33,7 @@
 using HlslGeneratorImplTest_Type = TestHelper;
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Alias) {
-  auto* alias = ty.alias("alias", ty.f32());
+  auto alias = ty.alias("alias", ty.f32());
 
   GeneratorImpl& gen = Build();
 
@@ -156,10 +156,10 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_StructDecl) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32()),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.i32()),
+                              Member("b", ty.f32()),
+                          });
   Global("g", s, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
@@ -173,12 +173,12 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_StructDecl_OmittedIfStorageBuffer) {
-  auto* s = Structure("S",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("S",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", ty.f32()),
+                     },
+                     {create<ast::StructBlockDecoration>()});
   Global("g", ty.access(ast::AccessControl::kReadWrite, s),
          ast::StorageClass::kStorage);
 
@@ -189,10 +189,10 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32()),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.i32()),
+                              Member("b", ty.f32()),
+                          });
   Global("g", s, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
@@ -205,7 +205,7 @@
 /// TODO(bclayton): Enable this, fix it, add tests for vector, matrix, array and
 /// nested structures.
 TEST_F(HlslGeneratorImplTest_Type, DISABLED_EmitType_Struct_InjectPadding) {
-  auto* s = Structure(
+  auto s = Structure(
       "S", {
                Member("a", ty.i32(), {MemberSize(32)}),
                Member("b", ty.f32()),
@@ -229,10 +229,10 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct_NameCollision) {
-  auto* s = Structure("S", {
-                               Member("double", ty.i32()),
-                               Member("float", ty.f32()),
-                           });
+  auto s = Structure("S", {
+                              Member("double", ty.i32()),
+                              Member("float", ty.f32()),
+                          });
   Global("g", s, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = SanitizeAndBuild();
@@ -247,12 +247,12 @@
 
 // TODO(dsinclair): How to translate [[block]]
 TEST_F(HlslGeneratorImplTest_Type, DISABLED_EmitType_Struct_WithDecoration) {
-  auto* s = Structure("S",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("S",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", ty.f32()),
+                     },
+                     {create<ast::StructBlockDecoration>()});
   Global("g", s, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
@@ -326,7 +326,7 @@
 TEST_P(HlslDepthTexturesTest, Emit) {
   auto params = GetParam();
 
-  auto* t = create<sem::DepthTexture>(params.dim);
+  auto t = ty.depth_texture(params.dim);
 
   Global("tex", t, ast::StorageClass::kUniformConstant, nullptr,
          ast::DecorationList{
@@ -373,7 +373,7 @@
 TEST_P(HlslSampledTexturesTest, Emit) {
   auto params = GetParam();
 
-  sem::Type* datatype = nullptr;
+  typ::Type datatype;
   switch (params.datatype) {
     case TextureDataType::F32:
       datatype = ty.f32();
@@ -385,7 +385,7 @@
       datatype = ty.i32();
       break;
   }
-  auto* t = create<sem::SampledTexture>(params.dim, datatype);
+  auto t = ty.sampled_texture(params.dim, datatype);
 
   Global("tex", t, ast::StorageClass::kUniformConstant, nullptr,
          ast::DecorationList{
@@ -524,12 +524,10 @@
 TEST_P(HlslStorageTexturesTest, Emit) {
   auto params = GetParam();
 
-  auto* subtype = sem::StorageTexture::SubtypeFor(params.imgfmt, Types());
-  auto* t = create<sem::StorageTexture>(params.dim, params.imgfmt, subtype);
-  auto* ac =
-      create<sem::AccessControl>(params.ro ? ast::AccessControl::kReadOnly
-                                           : ast::AccessControl::kWriteOnly,
-                                 t);
+  auto t = ty.storage_texture(params.dim, params.imgfmt);
+  auto ac = ty.access(params.ro ? ast::AccessControl::kReadOnly
+                                : ast::AccessControl::kWriteOnly,
+                      t);
 
   Global("tex", ac, ast::StorageClass::kUniformConstant, nullptr,
          ast::DecorationList{
diff --git a/src/writer/hlsl/generator_impl_workgroup_var_test.cc b/src/writer/hlsl/generator_impl_workgroup_var_test.cc
index d59adef..9fdd5b4 100644
--- a/src/writer/hlsl/generator_impl_workgroup_var_test.cc
+++ b/src/writer/hlsl/generator_impl_workgroup_var_test.cc
@@ -39,7 +39,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_WorkgroupVar, Aliased) {
-  auto* alias = ty.alias("F32", ty.f32());
+  auto alias = ty.alias("F32", ty.f32());
   AST().AddConstructedType(alias);
 
   Global("wg", alias, ast::StorageClass::kWorkgroup);
diff --git a/src/writer/msl/generator_impl_alias_type_test.cc b/src/writer/msl/generator_impl_alias_type_test.cc
index b5ec3df..4d41d2d 100644
--- a/src/writer/msl/generator_impl_alias_type_test.cc
+++ b/src/writer/msl/generator_impl_alias_type_test.cc
@@ -22,7 +22,7 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitConstructedType_F32) {
-  auto* alias = ty.alias("a", ty.f32());
+  auto alias = ty.alias("a", ty.f32());
 
   GeneratorImpl& gen = Build();
 
@@ -32,10 +32,10 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitConstructedType_Struct) {
-  auto* s = Structure("a", {
-                               Member("a", ty.f32()),
-                               Member("b", ty.i32()),
-                           });
+  auto s = Structure("a", {
+                              Member("a", ty.f32()),
+                              Member("b", ty.i32()),
+                          });
 
   GeneratorImpl& gen = Build();
 
@@ -48,12 +48,12 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitConstructedType_AliasStructIdent) {
-  auto* s = Structure("b", {
-                               Member("a", ty.f32()),
-                               Member("b", ty.i32()),
-                           });
+  auto s = Structure("b", {
+                              Member("a", ty.f32()),
+                              Member("b", ty.i32()),
+                          });
 
-  auto* alias = ty.alias("a", s);
+  auto alias = ty.alias("a", s);
 
   GeneratorImpl& gen = Build();
 
diff --git a/src/writer/msl/generator_impl_constructor_test.cc b/src/writer/msl/generator_impl_constructor_test.cc
index 850d2fd..6ce7924 100644
--- a/src/writer/msl/generator_impl_constructor_test.cc
+++ b/src/writer/msl/generator_impl_constructor_test.cc
@@ -145,11 +145,11 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Struct) {
-  auto* str = Structure("S", {
-                                 Member("a", ty.i32()),
-                                 Member("b", ty.f32()),
-                                 Member("c", ty.vec3<i32>()),
-                             });
+  auto str = Structure("S", {
+                                Member("a", ty.i32()),
+                                Member("b", ty.f32()),
+                                Member("c", ty.vec3<i32>()),
+                            });
 
   WrapInFunction(Construct(str, 1, 2.0f, vec3<i32>(3, 4, 5)));
 
diff --git a/src/writer/msl/generator_impl_function_test.cc b/src/writer/msl/generator_impl_function_test.cc
index c4969a6..687ca70 100644
--- a/src/writer/msl/generator_impl_function_test.cc
+++ b/src/writer/msl/generator_impl_function_test.cc
@@ -170,7 +170,7 @@
   //   const r = colors.col1;
   //   const g = colors.col2;
   // }
-  auto* interface_struct = Structure(
+  auto interface_struct = Structure(
       "Interface",
       {
           Member("col1", ty.f32(), {Location(1)}),
@@ -245,7 +245,7 @@
   // fn vert_main2() -> VertexOutput {
   //   return foo(0.25);
   // }
-  auto* vertex_output_struct = Structure(
+  auto vertex_output_struct = Structure(
       "VertexOutput",
       {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
 
@@ -300,12 +300,12 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_EntryPoint_With_RW_StorageBuffer) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("Data",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", ty.f32()),
+                     },
+                     {create<ast::StructBlockDecoration>()});
 
   sem::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
@@ -345,12 +345,12 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_EntryPoint_With_RO_StorageBuffer) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("Data",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", ty.f32()),
+                     },
+                     {create<ast::StructBlockDecoration>()});
 
   sem::AccessControl ac(ast::AccessControl::kReadOnly, s);
 
@@ -550,8 +550,8 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_Decoration_Called_By_EntryPoint_With_Uniform) {
-  auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())},
-                           {create<ast::StructBlockDecoration>()});
+  auto ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())},
+                          {create<ast::StructBlockDecoration>()});
   auto* ubo = Global(
       "ubo", ubo_ty, ast::StorageClass::kUniform, nullptr,
       {create<ast::BindingDecoration>(0), create<ast::GroupDecoration>(1)});
@@ -601,12 +601,12 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_Called_By_EntryPoint_With_RW_StorageBuffer) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("Data",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", ty.f32()),
+                     },
+                     {create<ast::StructBlockDecoration>()});
 
   sem::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
@@ -657,12 +657,12 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_Called_By_EntryPoint_With_RO_StorageBuffer) {
-  auto* s = Structure("Data",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("Data",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", ty.f32()),
+                     },
+                     {create<ast::StructBlockDecoration>()});
 
   sem::AccessControl ac(ast::AccessControl::kReadOnly, s);
 
@@ -796,8 +796,8 @@
   //   return;
   // }
 
-  auto* s = Structure("Data", {Member("d", ty.f32())},
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("Data", {Member("d", ty.f32())},
+                     {create<ast::StructBlockDecoration>()});
 
   sem::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
diff --git a/src/writer/msl/generator_impl_type_test.cc b/src/writer/msl/generator_impl_type_test.cc
index 1d2842a..ff7e71d 100644
--- a/src/writer/msl/generator_impl_type_test.cc
+++ b/src/writer/msl/generator_impl_type_test.cc
@@ -58,7 +58,7 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitType_Alias) {
-  auto* alias = ty.alias("alias", ty.f32());
+  auto alias = ty.alias("alias", ty.f32());
 
   GeneratorImpl& gen = Build();
 
@@ -173,10 +173,10 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Struct) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32()),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.i32()),
+                              Member("b", ty.f32()),
+                          });
 
   GeneratorImpl& gen = Build();
 
@@ -185,10 +185,10 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_StructDecl) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32()),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.i32()),
+                              Member("b", ty.f32()),
+                          });
 
   GeneratorImpl& gen = Build();
 
@@ -201,7 +201,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_NonComposites) {
-  auto* s =
+  auto s =
       Structure("S",
                 {
                     Member("a", ty.i32(), {MemberSize(32)}),
@@ -315,28 +315,28 @@
 
 TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_Structures) {
   // inner_x: size(1024), align(512)
-  auto* inner_x =
+  auto inner_x =
       Structure("inner_x", {
                                Member("a", ty.i32()),
                                Member("b", ty.f32(), {MemberAlign(512)}),
                            });
 
   // inner_y: size(516), align(4)
-  auto* inner_y =
+  auto inner_y =
       Structure("inner_y", {
                                Member("a", ty.i32(), {MemberSize(512)}),
                                Member("b", ty.f32()),
                            });
 
-  auto* s = Structure("S",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", inner_x),
-                          Member("c", ty.f32()),
-                          Member("d", inner_y),
-                          Member("e", ty.f32()),
-                      },
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("S",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", inner_x),
+                         Member("c", ty.f32()),
+                         Member("d", inner_y),
+                         Member("e", ty.f32()),
+                     },
+                     {create<ast::StructBlockDecoration>()});
 
   Global("G", ty.access(ast::AccessControl::kReadOnly, s),
          ast::StorageClass::kStorage);
@@ -401,11 +401,10 @@
 
 TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_ArrayDefaultStride) {
   // inner: size(1024), align(512)
-  auto* inner =
-      Structure("inner", {
-                             Member("a", ty.i32()),
-                             Member("b", ty.f32(), {MemberAlign(512)}),
-                         });
+  auto inner = Structure("inner", {
+                                      Member("a", ty.i32()),
+                                      Member("b", ty.f32(), {MemberAlign(512)}),
+                                  });
 
   // array_x: size(28), align(4)
   auto array_x = ty.array<f32, 7>();
@@ -416,17 +415,16 @@
   // array_z: size(4), align(4)
   auto array_z = ty.array<f32>();
 
-  auto* s =
-      Structure("S",
-                {
-                    Member("a", ty.i32()),
-                    Member("b", array_x),
-                    Member("c", ty.f32()),
-                    Member("d", array_y),
-                    Member("e", ty.f32()),
-                    Member("f", array_z),
-                },
-                ast::DecorationList{create<ast::StructBlockDecoration>()});
+  auto s = Structure("S",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", array_x),
+                         Member("c", ty.f32()),
+                         Member("d", array_y),
+                         Member("e", ty.f32()),
+                         Member("f", array_z),
+                     },
+                     ast::DecorationList{create<ast::StructBlockDecoration>()});
 
   Global("G", ty.access(ast::AccessControl::kReadOnly, s),
          ast::StorageClass::kStorage);
@@ -497,7 +495,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, AttemptTintPadSymbolCollision) {
-  auto* s = Structure(
+  auto s = Structure(
       "S",
       {
           // uses symbols tint_pad_[0..9] and tint_pad_[20..35]
@@ -584,12 +582,12 @@
 
 // TODO(dsinclair): How to translate [[block]]
 TEST_F(MslGeneratorImplTest, DISABLED_EmitType_Struct_WithDecoration) {
-  auto* s = Structure("S",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32()),
-                      },
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("S",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", ty.f32()),
+                     },
+                     {create<ast::StructBlockDecoration>()});
 
   Global("G", ty.access(ast::AccessControl::kReadOnly, s),
          ast::StorageClass::kStorage);
@@ -631,20 +629,20 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Sampler) {
-  sem::Sampler sampler(ast::SamplerKind::kSampler);
+  auto sampler = ty.sampler(ast::SamplerKind::kSampler);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(&sampler, "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(sampler, "")) << gen.error();
   EXPECT_EQ(gen.result(), "sampler");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_SamplerComparison) {
-  sem::Sampler sampler(ast::SamplerKind::kComparisonSampler);
+  auto sampler = ty.sampler(ast::SamplerKind::kComparisonSampler);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(&sampler, "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(sampler, "")) << gen.error();
   EXPECT_EQ(gen.result(), "sampler");
 }
 
@@ -738,14 +736,10 @@
 TEST_P(MslStorageTexturesTest, Emit) {
   auto params = GetParam();
 
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Float, Types());
-  auto* s = create<sem::StorageTexture>(params.dim, ast::ImageFormat::kR32Float,
-                                        subtype);
-  auto* ac =
-      create<sem::AccessControl>(params.ro ? ast::AccessControl::kReadOnly
-                                           : ast::AccessControl::kWriteOnly,
-                                 s);
+  auto s = ty.storage_texture(params.dim, ast::ImageFormat::kR32Float);
+  auto ac = ty.access(params.ro ? ast::AccessControl::kReadOnly
+                                : ast::AccessControl::kWriteOnly,
+                      s);
   Global("test_var", ac, ast::StorageClass::kInput);
 
   GeneratorImpl& gen = Build();
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 7c9fe6d..352ae5a 100644
--- a/src/writer/msl/generator_impl_variable_decl_statement_test.cc
+++ b/src/writer/msl/generator_impl_variable_decl_statement_test.cc
@@ -67,10 +67,10 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Struct) {
-  auto* s = Structure("S", {
-                               Member("a", ty.f32()),
-                               Member("b", ty.f32()),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.f32()),
+                              Member("b", ty.f32()),
+                          });
 
   auto* var = Var("a", s, ast::StorageClass::kNone);
   auto* stmt = Decl(var);
diff --git a/src/writer/spirv/builder_accessor_expression_test.cc b/src/writer/spirv/builder_accessor_expression_test.cc
index 1872c68..4c50c9c 100644
--- a/src/writer/spirv/builder_accessor_expression_test.cc
+++ b/src/writer/spirv/builder_accessor_expression_test.cc
@@ -219,10 +219,10 @@
   // var ident : my_struct
   // ident.b
 
-  auto* s = Structure("my_struct", {
-                                       Member("a", ty.f32()),
-                                       Member("b", ty.f32()),
-                                   });
+  auto s = Structure("my_struct", {
+                                      Member("a", ty.f32()),
+                                      Member("b", ty.f32()),
+                                  });
 
   auto* var = Global("ident", s, ast::StorageClass::kFunction);
 
@@ -263,12 +263,12 @@
   //
   // var ident : my_struct
   // ident.inner.a
-  auto* inner_struct = Structure("Inner", {
-                                              Member("a", ty.f32()),
-                                              Member("b", ty.f32()),
-                                          });
+  auto inner_struct = Structure("Inner", {
+                                             Member("a", ty.f32()),
+                                             Member("b", ty.f32()),
+                                         });
 
-  auto* s_type = Structure("my_struct", {Member("inner", inner_struct)});
+  auto s_type = Structure("my_struct", {Member("inner", inner_struct)});
 
   auto* var = Global("ident", s_type, ast::StorageClass::kFunction);
   auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "b");
@@ -307,10 +307,10 @@
   // let ident : my_struct = my_struct();
   // ident.b
 
-  auto* s = Structure("my_struct", {
-                                       Member("a", ty.f32()),
-                                       Member("b", ty.f32()),
-                                   });
+  auto s = Structure("my_struct", {
+                                      Member("a", ty.f32()),
+                                      Member("b", ty.f32()),
+                                  });
 
   auto* var = GlobalConst("ident", s, Construct(s, 0.f, 0.f));
 
@@ -345,12 +345,12 @@
   //
   // let ident : my_struct = my_struct();
   // ident.inner.a
-  auto* inner_struct = Structure("Inner", {
-                                              Member("a", ty.f32()),
-                                              Member("b", ty.f32()),
-                                          });
+  auto inner_struct = Structure("Inner", {
+                                             Member("a", ty.f32()),
+                                             Member("b", ty.f32()),
+                                         });
 
-  auto* s_type = Structure("my_struct", {Member("inner", inner_struct)});
+  auto s_type = Structure("my_struct", {Member("inner", inner_struct)});
 
   auto* var = GlobalConst("ident", s_type,
                           Construct(s_type, Construct(inner_struct, 0.f, 0.f)));
@@ -388,13 +388,13 @@
   //
   // var ident : my_struct
   // ident.inner.a
-  auto* inner_struct = Structure("Inner", {
-                                              Member("a", ty.f32()),
-                                              Member("b", ty.f32()),
-                                          });
+  auto inner_struct = Structure("Inner", {
+                                             Member("a", ty.f32()),
+                                             Member("b", ty.f32()),
+                                         });
 
-  auto* alias = ty.alias("Inner", inner_struct);
-  auto* s_type = Structure("Outer", {Member("inner", alias)});
+  auto alias = ty.alias("Inner", inner_struct);
+  auto s_type = Structure("Outer", {Member("inner", alias)});
 
   auto* var = Global("ident", s_type, ast::StorageClass::kFunction);
   auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "a");
@@ -434,12 +434,12 @@
   //
   // var ident : my_struct
   // ident.inner.a = 2.0f;
-  auto* inner_struct = Structure("Inner", {
-                                              Member("a", ty.f32()),
-                                              Member("b", ty.f32()),
-                                          });
+  auto inner_struct = Structure("Inner", {
+                                             Member("a", ty.f32()),
+                                             Member("b", ty.f32()),
+                                         });
 
-  auto* s_type = Structure("my_struct", {Member("inner", inner_struct)});
+  auto s_type = Structure("my_struct", {Member("inner", inner_struct)});
 
   auto* var = Global("ident", s_type, ast::StorageClass::kFunction);
   auto* expr =
@@ -483,12 +483,12 @@
   // var ident : my_struct
   // var store : f32 = ident.inner.a
 
-  auto* inner_struct = Structure("Inner", {
-                                              Member("a", ty.f32()),
-                                              Member("b", ty.f32()),
-                                          });
+  auto inner_struct = Structure("Inner", {
+                                             Member("a", ty.f32()),
+                                             Member("b", ty.f32()),
+                                         });
 
-  auto* s_type = Structure("my_struct", {Member("inner", inner_struct)});
+  auto s_type = Structure("my_struct", {Member("inner", inner_struct)});
 
   auto* var = Global("ident", s_type, ast::StorageClass::kFunction);
   auto* store = Global("store", ty.f32(), ast::StorageClass::kFunction);
@@ -693,11 +693,11 @@
   // var index : array<A, 2>
   // index[0].foo[2].bar.baz.yx
 
-  auto* c_type = Structure("C", {Member("baz", ty.vec3<f32>())});
+  auto c_type = Structure("C", {Member("baz", ty.vec3<f32>())});
 
-  auto* b_type = Structure("B", {Member("bar", c_type)});
+  auto b_type = Structure("B", {Member("bar", c_type)});
   auto b_ary_type = ty.array(b_type, 3);
-  auto* a_type = Structure("A", {Member("foo", b_ary_type)});
+  auto a_type = Structure("A", {Member("foo", b_ary_type)});
 
   auto a_ary_type = ty.array(a_type, 2);
   auto* var = Global("index", a_ary_type, ast::StorageClass::kFunction);
diff --git a/src/writer/spirv/builder_assign_test.cc b/src/writer/spirv/builder_assign_test.cc
index c3facfe..dfad65e 100644
--- a/src/writer/spirv/builder_assign_test.cc
+++ b/src/writer/spirv/builder_assign_test.cc
@@ -176,10 +176,10 @@
   // var ident : my_struct
   // ident.b = 4.0;
 
-  auto* s = Structure("my_struct", {
-                                       Member("a", ty.f32()),
-                                       Member("b", ty.f32()),
-                                   });
+  auto s = Structure("my_struct", {
+                                      Member("a", ty.f32()),
+                                      Member("b", ty.f32()),
+                                  });
 
   auto* v = Global("ident", s, ast::StorageClass::kFunction);
 
diff --git a/src/writer/spirv/builder_constructor_expression_test.cc b/src/writer/spirv/builder_constructor_expression_test.cc
index 4037115..6b25bf3 100644
--- a/src/writer/spirv/builder_constructor_expression_test.cc
+++ b/src/writer/spirv/builder_constructor_expression_test.cc
@@ -93,7 +93,7 @@
   // type Int = i32
   // cast<Int>(2.3f)
 
-  auto* alias = ty.alias("Int", ty.i32());
+  auto alias = ty.alias("Int", ty.i32());
   auto* cast = Construct(alias, 2.3f);
   WrapInFunction(cast);
 
@@ -975,10 +975,10 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Struct) {
-  auto* s = Structure("my_struct", {
-                                       Member("a", ty.f32()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
+  auto s = Structure("my_struct", {
+                                      Member("a", ty.f32()),
+                                      Member("b", ty.vec3<f32>()),
+                                  });
 
   auto* t = Construct(s, 2.0f, vec3<f32>(2.0f, 2.0f, 2.0f));
   WrapInFunction(t);
@@ -1125,7 +1125,7 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Struct) {
-  auto* s = Structure("my_struct", {Member("a", ty.f32())});
+  auto s = Structure("my_struct", {Member("a", ty.f32())});
   auto* t = Construct(s);
   WrapInFunction(t);
 
@@ -1545,10 +1545,10 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, IsConstructorConst_Struct) {
-  auto* s = Structure("my_struct", {
-                                       Member("a", ty.f32()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
+  auto s = Structure("my_struct", {
+                                      Member("a", ty.f32()),
+                                      Member("b", ty.vec3<f32>()),
+                                  });
 
   auto* t = Construct(s, 2.f, vec3<f32>(2.f, 2.f, 2.f));
   WrapInFunction(t);
@@ -1561,10 +1561,10 @@
 
 TEST_F(SpvBuilderConstructorTest,
        IsConstructorConst_Struct_WithIdentSubExpression) {
-  auto* s = Structure("my_struct", {
-                                       Member("a", ty.f32()),
-                                       Member("b", ty.vec3<f32>()),
-                                   });
+  auto s = Structure("my_struct", {
+                                      Member("a", ty.f32()),
+                                      Member("b", ty.vec3<f32>()),
+                                  });
 
   Global("a", ty.f32(), ast::StorageClass::kPrivate);
   Global("b", ty.f32(), ast::StorageClass::kPrivate);
diff --git a/src/writer/spirv/builder_entry_point_test.cc b/src/writer/spirv/builder_entry_point_test.cc
index 78f0818..ea34eb1 100644
--- a/src/writer/spirv/builder_entry_point_test.cc
+++ b/src/writer/spirv/builder_entry_point_test.cc
@@ -191,7 +191,7 @@
   //   return inputs.value;
   // }
 
-  auto* interface = Structure(
+  auto interface = Structure(
       "Interface",
       {
           Member("value", ty.f32(), ast::DecorationList{Location(1u)}),
diff --git a/src/writer/spirv/builder_function_test.cc b/src/writer/spirv/builder_function_test.cc
index 055dbe4..77b0866 100644
--- a/src/writer/spirv/builder_function_test.cc
+++ b/src/writer/spirv/builder_function_test.cc
@@ -201,8 +201,8 @@
   //   return;
   // }
 
-  auto* s = Structure("Data", {Member("d", ty.f32())},
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("Data", {Member("d", ty.f32())},
+                     {create<ast::StructBlockDecoration>()});
 
   sem::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
diff --git a/src/writer/spirv/builder_global_variable_test.cc b/src/writer/spirv/builder_global_variable_test.cc
index 1de4d00..91227e0 100644
--- a/src/writer/spirv/builder_global_variable_test.cc
+++ b/src/writer/spirv/builder_global_variable_test.cc
@@ -388,13 +388,13 @@
   // };
   // var b : [[access(read)]] A
 
-  auto* A = Structure("A",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.i32()),
-                      },
-                      {create<ast::StructBlockDecoration>()});
-  auto* ac = create<sem::AccessControl>(ast::AccessControl::kReadOnly, A);
+  auto A = Structure("A",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", ty.i32()),
+                     },
+                     {create<ast::StructBlockDecoration>()});
+  auto ac = ty.access(ast::AccessControl::kReadOnly, A);
 
   auto* var = Global("b", ac, ast::StorageClass::kStorage);
 
@@ -427,10 +427,10 @@
   // type B = A;
   // var b : [[access(read)]] B
 
-  auto* A = Structure("A", {Member("a", ty.i32())},
-                      {create<ast::StructBlockDecoration>()});
-  auto* B = ty.alias("B", A);
-  auto* ac = create<sem::AccessControl>(ast::AccessControl::kReadOnly, B);
+  auto A = Structure("A", {Member("a", ty.i32())},
+                     {create<ast::StructBlockDecoration>()});
+  auto B = ty.alias("B", A);
+  auto ac = ty.access(ast::AccessControl::kReadOnly, B);
   auto* var = Global("b", ac, ast::StorageClass::kStorage);
 
   spirv::Builder& b = Build();
@@ -459,10 +459,10 @@
   // type B = [[access(read)]] A;
   // var b : B
 
-  auto* A = Structure("A", {Member("a", ty.i32())},
-                      {create<ast::StructBlockDecoration>()});
-  auto* ac = create<sem::AccessControl>(ast::AccessControl::kReadOnly, A);
-  auto* B = ty.alias("B", ac);
+  auto A = Structure("A", {Member("a", ty.i32())},
+                     {create<ast::StructBlockDecoration>()});
+  auto ac = ty.access(ast::AccessControl::kReadOnly, A);
+  auto B = ty.alias("B", ac);
   auto* var = Global("b", B, ast::StorageClass::kStorage);
 
   spirv::Builder& b = Build();
@@ -491,8 +491,8 @@
   // var b : [[access(read)]] A
   // var c : [[access(read_write)]] A
 
-  auto* A = Structure("A", {Member("a", ty.i32())},
-                      {create<ast::StructBlockDecoration>()});
+  auto A = Structure("A", {Member("a", ty.i32())},
+                     {create<ast::StructBlockDecoration>()});
   sem::AccessControl read{ast::AccessControl::kReadOnly, A};
   sem::AccessControl rw{ast::AccessControl::kReadWrite, A};
 
@@ -531,12 +531,10 @@
 TEST_F(BuilderTest, GlobalVar_TextureStorageReadOnly) {
   // var<uniform_constant> a : [[access(read)]] texture_storage_2d<r32uint>;
 
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Uint, Types());
-  auto* type = create<sem::StorageTexture>(ast::TextureDimension::k2d,
-                                           ast::ImageFormat::kR32Uint, subtype);
+  auto type = ty.storage_texture(ast::TextureDimension::k2d,
+                                 ast::ImageFormat::kR32Uint);
 
-  auto* ac = create<sem::AccessControl>(ast::AccessControl::kReadOnly, type);
+  auto ac = ty.access(ast::AccessControl::kReadOnly, type);
 
   auto* var_a = Global("a", ac, ast::StorageClass::kUniformConstant);
 
@@ -556,13 +554,11 @@
 TEST_F(BuilderTest, GlobalVar_TextureStorageWriteOnly) {
   // var<uniform_constant> a : [[access(write)]] texture_storage_2d<r32uint>;
 
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Uint, Types());
-  auto* type = create<sem::StorageTexture>(ast::TextureDimension::k2d,
-                                           ast::ImageFormat::kR32Uint, subtype);
+  auto type = ty.storage_texture(ast::TextureDimension::k2d,
+                                 ast::ImageFormat::kR32Uint);
   Global("test_var", type, ast::StorageClass::kInput);
 
-  auto* ac = create<sem::AccessControl>(ast::AccessControl::kWriteOnly, type);
+  auto ac = ty.access(ast::AccessControl::kWriteOnly, type);
 
   auto* var_a = Global("a", ac, ast::StorageClass::kUniformConstant);
 
@@ -585,17 +581,15 @@
   // var<uniform_constant> a : [[access(read)]] texture_storage_2d<r32uint>;
   // var<uniform_constant> b : [[access(write)]] texture_storage_2d<r32uint>;
 
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Uint, Types());
-  auto* st = create<sem::StorageTexture>(ast::TextureDimension::k2d,
-                                         ast::ImageFormat::kR32Uint, subtype);
+  auto st = ty.storage_texture(ast::TextureDimension::k2d,
+                               ast::ImageFormat::kR32Uint);
 
   Global("test_var", st, ast::StorageClass::kInput);
 
-  auto* type_a = create<sem::AccessControl>(ast::AccessControl::kReadOnly, st);
+  auto type_a = ty.access(ast::AccessControl::kReadOnly, st);
   auto* var_a = Global("a", type_a, ast::StorageClass::kUniformConstant);
 
-  auto* type_b = create<sem::AccessControl>(ast::AccessControl::kWriteOnly, st);
+  auto type_b = ty.access(ast::AccessControl::kWriteOnly, st);
   auto* var_b = Global("b", type_b, ast::StorageClass::kUniformConstant);
 
   spirv::Builder& b = Build();
diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc
index d7ea7fe..c5a63d0 100644
--- a/src/writer/spirv/builder_intrinsic_test.cc
+++ b/src/writer/spirv/builder_intrinsic_test.cc
@@ -1379,9 +1379,9 @@
 }
 
 TEST_F(IntrinsicBuilderTest, Call_ArrayLength) {
-  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
-                      {create<ast::StructBlockDecoration>()});
-  auto* ac = ty.access(ast::AccessControl::kReadOnly, s);
+  auto s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
+                     {create<ast::StructBlockDecoration>()});
+  auto ac = ty.access(ast::AccessControl::kReadOnly, s);
   Global("b", ac, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(1),
@@ -1425,13 +1425,13 @@
 }
 
 TEST_F(IntrinsicBuilderTest, Call_ArrayLength_OtherMembersInStruct) {
-  auto* s = Structure("my_struct",
-                      {
-                          Member(0, "z", ty.f32()),
-                          Member(4, "a", ty.array<f32>(4)),
-                      },
-                      {create<ast::StructBlockDecoration>()});
-  auto* ac = ty.access(ast::AccessControl::kReadOnly, s);
+  auto s = Structure("my_struct",
+                     {
+                         Member(0, "z", ty.f32()),
+                         Member(4, "a", ty.array<f32>(4)),
+                     },
+                     {create<ast::StructBlockDecoration>()});
+  auto ac = ty.access(ast::AccessControl::kReadOnly, s);
   Global("b", ac, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(1),
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index 66fadbd..1cf783f 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -27,7 +27,7 @@
 using BuilderTest_Type = TestHelper;
 
 TEST_F(BuilderTest_Type, GenerateAlias) {
-  auto* alias_type = ty.alias("my_type", ty.f32());
+  auto alias_type = ty.alias("my_type", ty.f32());
 
   spirv::Builder& b = Build();
 
@@ -43,7 +43,7 @@
 TEST_F(BuilderTest_Type, ReturnsGeneratedAlias) {
   auto i32 = ty.i32();
   auto f32 = ty.f32();
-  auto* alias_type = ty.alias("my_type", f32);
+  auto alias_type = ty.alias("my_type", f32);
 
   spirv::Builder& b = Build();
 
@@ -59,9 +59,9 @@
 
 TEST_F(BuilderTest_Type, GenerateRuntimeArray) {
   auto ary = ty.array(ty.i32(), 0);
-  auto* str = Structure("S", {Member("x", ary)},
-                        {create<ast::StructBlockDecoration>()});
-  auto* ac = ty.access(ast::AccessControl::kReadOnly, str);
+  auto str = Structure("S", {Member("x", ary)},
+                       {create<ast::StructBlockDecoration>()});
+  auto ac = ty.access(ast::AccessControl::kReadOnly, str);
   Global("a", ac, ast::StorageClass::kStorage);
 
   spirv::Builder& b = Build();
@@ -77,9 +77,9 @@
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedRuntimeArray) {
   auto ary = ty.array(ty.i32(), 0);
-  auto* str = Structure("S", {Member("x", ary)},
-                        {create<ast::StructBlockDecoration>()});
-  auto* ac = ty.access(ast::AccessControl::kReadOnly, str);
+  auto str = Structure("S", {Member("x", ary)},
+                       {create<ast::StructBlockDecoration>()});
+  auto ac = ty.access(ast::AccessControl::kReadOnly, str);
   Global("a", ac, ast::StorageClass::kStorage);
 
   spirv::Builder& b = Build();
@@ -285,7 +285,7 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct_Empty) {
-  auto* s = Structure("S", {});
+  auto s = Structure("S", {});
 
   spirv::Builder& b = Build();
 
@@ -301,7 +301,7 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct) {
-  auto* s = Structure("my_struct", {Member("a", ty.f32())});
+  auto s = Structure("my_struct", {Member("a", ty.f32())});
 
   spirv::Builder& b = Build();
 
@@ -318,8 +318,8 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct_Decorated) {
-  auto* s = Structure("my_struct", {Member("a", ty.f32())},
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("my_struct", {Member("a", ty.f32())},
+                     {create<ast::StructBlockDecoration>()});
 
   spirv::Builder& b = Build();
 
@@ -339,10 +339,10 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers) {
-  auto* s = Structure("S", {
-                               Member("a", ty.f32()),
-                               Member("b", ty.f32(), {MemberAlign(8)}),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.f32()),
+                              Member("b", ty.f32(), {MemberAlign(8)}),
+                          });
 
   spirv::Builder& b = Build();
 
@@ -363,11 +363,11 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct_NonLayout_Matrix) {
-  auto* s = Structure("S", {
-                               Member("a", ty.mat2x2<f32>()),
-                               Member("b", ty.mat2x3<f32>()),
-                               Member("c", ty.mat4x4<f32>()),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.mat2x2<f32>()),
+                              Member("b", ty.mat2x3<f32>()),
+                              Member("c", ty.mat4x4<f32>()),
+                          });
 
   spirv::Builder& b = Build();
 
@@ -403,11 +403,11 @@
 
 TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers_LayoutMatrix) {
   // We have to infer layout for matrix when it also has an offset.
-  auto* s = Structure("S", {
-                               Member("a", ty.mat2x2<f32>()),
-                               Member("b", ty.mat2x3<f32>()),
-                               Member("c", ty.mat4x4<f32>()),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.mat2x2<f32>()),
+                              Member("b", ty.mat2x3<f32>()),
+                              Member("c", ty.mat4x4<f32>()),
+                          });
 
   spirv::Builder& b = Build();
 
@@ -449,14 +449,13 @@
   auto arr_arr_mat2x3 = ty.array(ty.mat2x3<f32>(), 1);  // Doubly nested array
   auto rtarr_mat4x4 = ty.array(ty.mat4x4<f32>(), 0);    // Runtime array
 
-  auto* s =
-      Structure("S",
-                {
-                    Member("a", arr_mat2x2),
-                    Member("b", arr_arr_mat2x3),
-                    Member("c", rtarr_mat4x4),
-                },
-                ast::DecorationList{create<ast::StructBlockDecoration>()});
+  auto s = Structure("S",
+                     {
+                         Member("a", arr_mat2x2),
+                         Member("b", arr_arr_mat2x3),
+                         Member("c", rtarr_mat4x4),
+                     },
+                     ast::DecorationList{create<ast::StructBlockDecoration>()});
 
   spirv::Builder& b = Build();
 
@@ -715,7 +714,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_1d_i32) {
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k1d, ty.i32());
+  auto s = ty.sampled_texture(ast::TextureDimension::k1d, ty.i32());
 
   spirv::Builder& b = Build();
 
@@ -732,7 +731,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_1d_u32) {
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k1d, ty.u32());
+  auto s = ty.sampled_texture(ast::TextureDimension::k1d, ty.u32());
 
   spirv::Builder& b = Build();
 
@@ -749,7 +748,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_1d_f32) {
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k1d, ty.f32());
+  auto s = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
 
   spirv::Builder& b = Build();
 
@@ -766,7 +765,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_2d) {
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k2d, ty.f32());
+  auto s = ty.sampled_texture(ast::TextureDimension::k2d, ty.f32());
 
   spirv::Builder& b = Build();
 
@@ -779,8 +778,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_2d_array) {
-  auto* s =
-      create<sem::SampledTexture>(ast::TextureDimension::k2dArray, ty.f32());
+  auto s = ty.sampled_texture(ast::TextureDimension::k2dArray, ty.f32());
 
   spirv::Builder& b = Build();
 
@@ -793,7 +791,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_3d) {
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::k3d, ty.f32());
+  auto s = ty.sampled_texture(ast::TextureDimension::k3d, ty.f32());
 
   spirv::Builder& b = Build();
 
@@ -806,7 +804,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_Cube) {
-  auto* s = create<sem::SampledTexture>(ast::TextureDimension::kCube, ty.f32());
+  auto s = ty.sampled_texture(ast::TextureDimension::kCube, ty.f32());
 
   spirv::Builder& b = Build();
 
@@ -820,8 +818,7 @@
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_CubeArray) {
-  auto* s =
-      create<sem::SampledTexture>(ast::TextureDimension::kCubeArray, ty.f32());
+  auto s = ty.sampled_texture(ast::TextureDimension::kCubeArray, ty.f32());
 
   spirv::Builder& b = Build();
 
@@ -837,10 +834,8 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_1d) {
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Float, Types());
-  auto* s = create<sem::StorageTexture>(ast::TextureDimension::k1d,
-                                        ast::ImageFormat::kR32Float, subtype);
+  auto s = ty.storage_texture(ast::TextureDimension::k1d,
+                              ast::ImageFormat::kR32Float);
 
   Global("test_var", s, ast::StorageClass::kInput);
 
@@ -854,10 +849,8 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_2d) {
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Float, Types());
-  auto* s = create<sem::StorageTexture>(ast::TextureDimension::k2d,
-                                        ast::ImageFormat::kR32Float, subtype);
+  auto s = ty.storage_texture(ast::TextureDimension::k2d,
+                              ast::ImageFormat::kR32Float);
 
   Global("test_var", s, ast::StorageClass::kInput);
 
@@ -871,10 +864,8 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_2dArray) {
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Float, Types());
-  auto* s = create<sem::StorageTexture>(ast::TextureDimension::k2dArray,
-                                        ast::ImageFormat::kR32Float, subtype);
+  auto s = ty.storage_texture(ast::TextureDimension::k2dArray,
+                              ast::ImageFormat::kR32Float);
 
   Global("test_var", s, ast::StorageClass::kInput);
 
@@ -888,10 +879,8 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_3d) {
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Float, Types());
-  auto* s = create<sem::StorageTexture>(ast::TextureDimension::k3d,
-                                        ast::ImageFormat::kR32Float, subtype);
+  auto s = ty.storage_texture(ast::TextureDimension::k3d,
+                              ast::ImageFormat::kR32Float);
 
   Global("test_var", s, ast::StorageClass::kInput);
 
@@ -906,10 +895,8 @@
 
 TEST_F(BuilderTest_Type,
        StorageTexture_Generate_SampledTypeFloat_Format_r32float) {
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Float, Types());
-  auto* s = create<sem::StorageTexture>(ast::TextureDimension::k2d,
-                                        ast::ImageFormat::kR32Float, subtype);
+  auto s = ty.storage_texture(ast::TextureDimension::k2d,
+                              ast::ImageFormat::kR32Float);
 
   Global("test_var", s, ast::StorageClass::kInput);
 
@@ -924,10 +911,8 @@
 
 TEST_F(BuilderTest_Type,
        StorageTexture_Generate_SampledTypeSint_Format_r32sint) {
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Sint, Types());
-  auto* s = create<sem::StorageTexture>(ast::TextureDimension::k2d,
-                                        ast::ImageFormat::kR32Sint, subtype);
+  auto s = ty.storage_texture(ast::TextureDimension::k2d,
+                              ast::ImageFormat::kR32Sint);
 
   Global("test_var", s, ast::StorageClass::kInput);
 
@@ -942,10 +927,8 @@
 
 TEST_F(BuilderTest_Type,
        StorageTexture_Generate_SampledTypeUint_Format_r32uint) {
-  auto* subtype =
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Uint, Types());
-  auto* s = create<sem::StorageTexture>(ast::TextureDimension::k2d,
-                                        ast::ImageFormat::kR32Uint, subtype);
+  auto s = ty.storage_texture(ast::TextureDimension::k2d,
+                              ast::ImageFormat::kR32Uint);
 
   Global("test_var", s, ast::StorageClass::kInput);
 
diff --git a/src/writer/wgsl/generator_impl_alias_type_test.cc b/src/writer/wgsl/generator_impl_alias_type_test.cc
index a1ecefa..4e0025c 100644
--- a/src/writer/wgsl/generator_impl_alias_type_test.cc
+++ b/src/writer/wgsl/generator_impl_alias_type_test.cc
@@ -22,7 +22,7 @@
 using WgslGeneratorImplTest = TestHelper;
 
 TEST_F(WgslGeneratorImplTest, EmitAlias_F32) {
-  auto* alias = ty.alias("a", ty.f32());
+  auto alias = ty.alias("a", ty.f32());
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.EmitConstructedType(alias)) << gen.error();
@@ -31,12 +31,12 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitConstructedType_Struct) {
-  auto* s = Structure("A", {
-                               Member("a", ty.f32()),
-                               Member("b", ty.i32()),
-                           });
+  auto s = Structure("A", {
+                              Member("a", ty.f32()),
+                              Member("b", ty.i32()),
+                          });
 
-  auto* alias = ty.alias("B", s);
+  auto alias = ty.alias("B", s);
 
   GeneratorImpl& gen = Build();
 
@@ -51,12 +51,12 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitAlias_ToStruct) {
-  auto* s = Structure("A", {
-                               Member("a", ty.f32()),
-                               Member("b", ty.i32()),
-                           });
+  auto s = Structure("A", {
+                              Member("a", ty.f32()),
+                              Member("b", ty.i32()),
+                          });
 
-  auto* alias = ty.alias("B", s);
+  auto alias = ty.alias("B", s);
 
   GeneratorImpl& gen = Build();
 
diff --git a/src/writer/wgsl/generator_impl_entry_point_test.cc b/src/writer/wgsl/generator_impl_entry_point_test.cc
index a47a452..2ff7ff5 100644
--- a/src/writer/wgsl/generator_impl_entry_point_test.cc
+++ b/src/writer/wgsl/generator_impl_entry_point_test.cc
@@ -90,7 +90,7 @@
 TEST_F(WgslGeneratorImplTest, Emit_EntryPoint_GlobalsInterleaved) {
   Global("a0", ty.f32(), ast::StorageClass::kPrivate);
 
-  auto* s0 = Structure("S0", {Member("a", ty.i32())});
+  auto s0 = Structure("S0", {Member("a", ty.i32())});
 
   Func("func", {}, ty.f32(),
        {
@@ -99,7 +99,7 @@
 
   Global("a1", ty.f32(), ast::StorageClass::kOutput);
 
-  auto* s1 = Structure("S1", {Member("a", ty.i32())});
+  auto s1 = Structure("S1", {Member("a", ty.i32())});
 
   Func("main", {}, ty.void_(),
        {
diff --git a/src/writer/wgsl/generator_impl_function_test.cc b/src/writer/wgsl/generator_impl_function_test.cc
index 8edba40..0dffdec 100644
--- a/src/writer/wgsl/generator_impl_function_test.cc
+++ b/src/writer/wgsl/generator_impl_function_test.cc
@@ -202,8 +202,8 @@
   //   return;
   // }
 
-  auto* s = Structure("Data", {Member("d", ty.f32())},
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("Data", {Member("d", ty.f32())},
+                     {create<ast::StructBlockDecoration>()});
 
   sem::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
diff --git a/src/writer/wgsl/generator_impl_global_decl_test.cc b/src/writer/wgsl/generator_impl_global_decl_test.cc
index 1eaed08..53ad8b8 100644
--- a/src/writer/wgsl/generator_impl_global_decl_test.cc
+++ b/src/writer/wgsl/generator_impl_global_decl_test.cc
@@ -48,7 +48,7 @@
 TEST_F(WgslGeneratorImplTest, Emit_GlobalsInterleaved) {
   Global("a0", ty.f32(), ast::StorageClass::kPrivate);
 
-  auto* s0 = Structure("S0", {Member("a", ty.i32())});
+  auto s0 = Structure("S0", {Member("a", ty.i32())});
 
   Func("func", ast::VariableList{}, ty.f32(),
        ast::StatementList{
@@ -58,7 +58,7 @@
 
   Global("a1", ty.f32(), ast::StorageClass::kOutput);
 
-  auto* s1 = Structure("S1", {Member("a", ty.i32())});
+  auto s1 = Structure("S1", {Member("a", ty.i32())});
 
   Func("main", ast::VariableList{}, ty.void_(),
        ast::StatementList{
@@ -101,7 +101,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_Global_Sampler) {
-  Global("s", create<sem::Sampler>(ast::SamplerKind::kSampler),
+  Global("s", ty.sampler(ast::SamplerKind::kSampler),
          ast::StorageClass::kUniformConstant);
 
   GeneratorImpl& gen = Build();
@@ -113,7 +113,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_Global_Texture) {
-  auto* st = create<sem::SampledTexture>(ast::TextureDimension::k1d, ty.f32());
+  auto st = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
   Global("t", ty.access(ast::AccessControl::kReadOnly, st),
          ast::StorageClass::kUniformConstant);
 
diff --git a/src/writer/wgsl/generator_impl_member_accessor_test.cc b/src/writer/wgsl/generator_impl_member_accessor_test.cc
index 1a28011..14aa957 100644
--- a/src/writer/wgsl/generator_impl_member_accessor_test.cc
+++ b/src/writer/wgsl/generator_impl_member_accessor_test.cc
@@ -22,7 +22,7 @@
 using WgslGeneratorImplTest = TestHelper;
 
 TEST_F(WgslGeneratorImplTest, EmitExpression_MemberAccessor) {
-  auto* s = Structure("Data", {Member("mem", ty.f32())});
+  auto s = Structure("Data", {Member("mem", ty.f32())});
   Global("str", s, ast::StorageClass::kPrivate);
 
   auto* expr = MemberAccessor("str", "mem");
diff --git a/src/writer/wgsl/generator_impl_type_test.cc b/src/writer/wgsl/generator_impl_type_test.cc
index 3733af7..edd7e27 100644
--- a/src/writer/wgsl/generator_impl_type_test.cc
+++ b/src/writer/wgsl/generator_impl_type_test.cc
@@ -27,7 +27,7 @@
 using WgslGeneratorImplTest = TestHelper;
 
 TEST_F(WgslGeneratorImplTest, EmitType_Alias) {
-  auto* alias = ty.alias("alias", ty.f32());
+  auto alias = ty.alias("alias", ty.f32());
   AST().AddConstructedType(alias);
 
   GeneratorImpl& gen = Build();
@@ -47,10 +47,10 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_AccessControl_Read) {
-  auto* s = Structure("S", {Member("a", ty.i32())},
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("S", {Member("a", ty.i32())},
+                     {create<ast::StructBlockDecoration>()});
 
-  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  auto a = ty.access(ast::AccessControl::kReadOnly, s);
   AST().AddConstructedType(ty.alias("make_type_reachable", a));
 
   GeneratorImpl& gen = Build();
@@ -60,10 +60,10 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_AccessControl_ReadWrite) {
-  auto* s = Structure("S", {Member("a", ty.i32())},
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("S", {Member("a", ty.i32())},
+                     {create<ast::StructBlockDecoration>()});
 
-  auto* a = ty.access(ast::AccessControl::kReadWrite, s);
+  auto a = ty.access(ast::AccessControl::kReadWrite, s);
   AST().AddConstructedType(ty.alias("make_type_reachable", a));
 
   GeneratorImpl& gen = Build();
@@ -73,10 +73,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Array_Decoration) {
-  auto* a = create<sem::ArrayType>(ty.bool_(), 4,
-                                   ast::DecorationList{
-                                       create<ast::StrideDecoration>(16u),
-                                   });
+  auto a = ty.array(ty.bool_(), 4, 16u);
   AST().AddConstructedType(ty.alias("make_type_reachable", a));
 
   GeneratorImpl& gen = Build();
@@ -85,22 +82,8 @@
   EXPECT_EQ(gen.result(), "[[stride(16)]] array<bool, 4>");
 }
 
-TEST_F(WgslGeneratorImplTest, EmitType_Array_MultipleDecorations) {
-  auto* a = create<sem::ArrayType>(ty.bool_(), 4,
-                                   ast::DecorationList{
-                                       create<ast::StrideDecoration>(16u),
-                                       create<ast::StrideDecoration>(32u),
-                                   });
-  AST().AddConstructedType(ty.alias("make_type_reachable", a));
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitType(a)) << gen.error();
-  EXPECT_EQ(gen.result(), "[[stride(16)]] [[stride(32)]] array<bool, 4>");
-}
-
 TEST_F(WgslGeneratorImplTest, EmitType_RuntimeArray) {
-  auto* a = create<sem::ArrayType>(ty.bool_(), 0, ast::DecorationList{});
+  auto a = ty.array(ty.bool_(), 0);
   AST().AddConstructedType(ty.alias("make_type_reachable", a));
 
   GeneratorImpl& gen = Build();
@@ -150,7 +133,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Pointer) {
-  auto* p = create<sem::Pointer>(ty.f32(), ast::StorageClass::kWorkgroup);
+  auto p = ty.pointer<f32>(ast::StorageClass::kWorkgroup);
   AST().AddConstructedType(ty.alias("make_type_reachable", p));
 
   GeneratorImpl& gen = Build();
@@ -160,10 +143,10 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Struct) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32()),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.i32()),
+                              Member("b", ty.f32()),
+                          });
 
   GeneratorImpl& gen = Build();
 
@@ -172,10 +155,10 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_StructOffsetDecl) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32(), {MemberOffset(8)}),
-                               Member("b", ty.f32(), {MemberOffset(16)}),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.i32(), {MemberOffset(8)}),
+                              Member("b", ty.f32(), {MemberOffset(16)}),
+                          });
 
   GeneratorImpl& gen = Build();
 
@@ -192,7 +175,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_StructOffsetDecl_WithSymbolCollisions) {
-  auto* s =
+  auto s =
       Structure("S", {
                          Member("tint_0_padding", ty.i32(), {MemberOffset(8)}),
                          Member("tint_2_padding", ty.f32(), {MemberOffset(16)}),
@@ -213,10 +196,10 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_StructAlignDecl) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32(), {MemberAlign(8)}),
-                               Member("b", ty.f32(), {MemberAlign(16)}),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.i32(), {MemberAlign(8)}),
+                              Member("b", ty.f32(), {MemberAlign(16)}),
+                          });
 
   GeneratorImpl& gen = Build();
 
@@ -231,10 +214,10 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_StructSizeDecl) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32(), {MemberSize(16)}),
-                               Member("b", ty.f32(), {MemberSize(32)}),
-                           });
+  auto s = Structure("S", {
+                              Member("a", ty.i32(), {MemberSize(16)}),
+                              Member("b", ty.f32(), {MemberSize(32)}),
+                          });
 
   GeneratorImpl& gen = Build();
 
@@ -249,12 +232,12 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Struct_WithDecoration) {
-  auto* s = Structure("S",
-                      {
-                          Member("a", ty.i32()),
-                          Member("b", ty.f32(), {MemberAlign(8)}),
-                      },
-                      {create<ast::StructBlockDecoration>()});
+  auto s = Structure("S",
+                     {
+                         Member("a", ty.i32()),
+                         Member("b", ty.f32(), {MemberAlign(8)}),
+                     },
+                     {create<ast::StructBlockDecoration>()});
 
   GeneratorImpl& gen = Build();
 
@@ -272,7 +255,7 @@
   ast::DecorationList decos;
   decos.push_back(create<ast::StructBlockDecoration>());
 
-  auto* s = Structure(
+  auto s = Structure(
       "S",
       ast::StructMemberList{
           Member("a", ty.u32(), {Builtin(ast::Builtin::kVertexIndex)}),
@@ -325,7 +308,7 @@
 TEST_P(WgslGenerator_DepthTextureTest, EmitType_DepthTexture) {
   auto param = GetParam();
 
-  auto* d = create<sem::DepthTexture>(param.dim);
+  auto d = ty.depth_texture(param.dim);
   AST().AddConstructedType(ty.alias("make_type_reachable", d));
 
   GeneratorImpl& gen = Build();
@@ -347,7 +330,7 @@
 TEST_P(WgslGenerator_SampledTextureTest, EmitType_SampledTexture_F32) {
   auto param = GetParam();
 
-  auto* t = create<sem::SampledTexture>(param.dim, ty.f32());
+  auto t = ty.sampled_texture(param.dim, ty.f32());
   AST().AddConstructedType(ty.alias("make_type_reachable", t));
 
   GeneratorImpl& gen = Build();
@@ -359,7 +342,7 @@
 TEST_P(WgslGenerator_SampledTextureTest, EmitType_SampledTexture_I32) {
   auto param = GetParam();
 
-  auto* t = create<sem::SampledTexture>(param.dim, ty.i32());
+  auto t = ty.sampled_texture(param.dim, ty.i32());
   AST().AddConstructedType(ty.alias("make_type_reachable", t));
 
   GeneratorImpl& gen = Build();
@@ -371,7 +354,7 @@
 TEST_P(WgslGenerator_SampledTextureTest, EmitType_SampledTexture_U32) {
   auto param = GetParam();
 
-  auto* t = create<sem::SampledTexture>(param.dim, ty.u32());
+  auto t = ty.sampled_texture(param.dim, ty.u32());
   AST().AddConstructedType(ty.alias("make_type_reachable", t));
 
   GeneratorImpl& gen = Build();
@@ -394,7 +377,7 @@
 TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_F32) {
   auto param = GetParam();
 
-  auto* t = create<sem::MultisampledTexture>(param.dim, ty.f32());
+  auto t = ty.multisampled_texture(param.dim, ty.f32());
   AST().AddConstructedType(ty.alias("make_type_reachable", t));
 
   GeneratorImpl& gen = Build();
@@ -406,7 +389,7 @@
 TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_I32) {
   auto param = GetParam();
 
-  auto* t = create<sem::MultisampledTexture>(param.dim, ty.i32());
+  auto t = ty.multisampled_texture(param.dim, ty.i32());
   AST().AddConstructedType(ty.alias("make_type_reachable", t));
 
   GeneratorImpl& gen = Build();
@@ -418,7 +401,7 @@
 TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_U32) {
   auto param = GetParam();
 
-  auto* t = create<sem::MultisampledTexture>(param.dim, ty.u32());
+  auto t = ty.multisampled_texture(param.dim, ty.u32());
   AST().AddConstructedType(ty.alias("make_type_reachable", t));
 
   GeneratorImpl& gen = Build();
@@ -446,9 +429,8 @@
 TEST_P(WgslGenerator_StorageTextureTest, EmitType_StorageTexture) {
   auto param = GetParam();
 
-  auto* subtype = sem::StorageTexture::SubtypeFor(param.fmt, Types());
-  auto* t = create<sem::StorageTexture>(param.dim, param.fmt, subtype);
-  auto* ac = ty.access(param.access, t);
+  auto t = ty.storage_texture(param.dim, param.fmt);
+  auto ac = ty.access(param.access, t);
 
   GeneratorImpl& gen = Build();
 
@@ -551,7 +533,7 @@
         ImageFormatData{ast::ImageFormat::kRgba32Float, "rgba32float"}));
 
 TEST_F(WgslGeneratorImplTest, EmitType_Sampler) {
-  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kSampler);
+  auto sampler = ty.sampler(ast::SamplerKind::kSampler);
   AST().AddConstructedType(ty.alias("make_type_reachable", sampler));
 
   GeneratorImpl& gen = Build();
@@ -561,7 +543,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_SamplerComparison) {
-  auto* sampler = create<sem::Sampler>(ast::SamplerKind::kComparisonSampler);
+  auto sampler = ty.sampler(ast::SamplerKind::kComparisonSampler);
   AST().AddConstructedType(ty.alias("make_type_reachable", sampler));
 
   GeneratorImpl& gen = Build();