sem: Fold together sem::Struct and sem::StructType

There's now no need to have both.
Removes a whole bunch of Sem().Get() smell, and simplifies the resolver.

Bug: tint:724
Fixed: tint:761
Change-Id: I756a32680ac52441fd6eebf6fc53dd507ef5e538
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/49961
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 80a0c3a..699c066 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -480,8 +480,6 @@
     "sem/sampler_type.h",
     "sem/storage_texture_type.cc",
     "sem/storage_texture_type.h",
-    "sem/struct_type.cc",
-    "sem/struct_type.h",
     "sem/texture_type.cc",
     "sem/texture_type.h",
     "sem/type.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 514a04c..b2a3369 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -313,8 +313,6 @@
   sem/sampler_type.h
   sem/storage_texture_type.cc
   sem/storage_texture_type.h
-  sem/struct_type.cc
-  sem/struct_type.h
   sem/texture_type.cc
   sem/texture_type.h
   sem/type.cc
@@ -581,8 +579,8 @@
     sem/pointer_type_test.cc
     sem/sampled_texture_type_test.cc
     sem/sampler_type_test.cc
+    sem/sem_struct_test.cc
     sem/storage_texture_type_test.cc
-    sem/struct_type_test.cc
     sem/texture_type_test.cc
     sem/type_manager_test.cc
     sem/u32_type_test.cc
@@ -809,7 +807,6 @@
 
   if(${TINT_BUILD_MSL_WRITER})
     list(APPEND TINT_TEST_SRCS
-      writer/msl/generator_impl_alias_type_test.cc
       writer/msl/generator_impl_array_accessor_test.cc
       writer/msl/generator_impl_assign_test.cc
       writer/msl/generator_impl_binary_test.cc
@@ -846,7 +843,6 @@
   if (${TINT_BUILD_HLSL_WRITER})
     list(APPEND TINT_TEST_SRCS
       transform/hlsl_test.cc
-      writer/hlsl/generator_impl_alias_type_test.cc
       writer/hlsl/generator_impl_array_accessor_test.cc
       writer/hlsl/generator_impl_assign_test.cc
       writer/hlsl/generator_impl_binary_test.cc
diff --git a/src/inspector/inspector.cc b/src/inspector/inspector.cc
index 68442e9..c91edc1 100644
--- a/src/inspector/inspector.cc
+++ b/src/inspector/inspector.cc
@@ -33,7 +33,6 @@
 #include "src/sem/sampled_texture_type.h"
 #include "src/sem/storage_texture_type.h"
 #include "src/sem/struct.h"
-#include "src/sem/struct_type.h"
 #include "src/sem/u32_type.h"
 #include "src/sem/variable.h"
 #include "src/sem/vector_type.h"
@@ -389,7 +388,7 @@
     auto binding_info = ruv.second;
 
     auto* unwrapped_type = var->Type()->UnwrapIfNeeded();
-    auto* str = unwrapped_type->As<sem::StructType>();
+    auto* str = unwrapped_type->As<sem::Struct>();
     if (str == nullptr) {
       continue;
     }
@@ -398,19 +397,12 @@
       continue;
     }
 
-    auto* sem = program_->Sem().Get(str);
-    if (!sem) {
-      error_ = "Missing semantic information for structure " +
-               program_->Symbols().NameFor(str->impl()->name());
-      continue;
-    }
-
     ResourceBinding entry;
     entry.resource_type = ResourceBinding::ResourceType::kUniformBuffer;
     entry.bind_group = binding_info.group->value();
     entry.binding = binding_info.binding->value();
-    entry.size = sem->Size();
-    entry.size_no_padding = sem->SizeNoPadding();
+    entry.size = str->Size();
+    entry.size_no_padding = str->SizeNoPadding();
 
     result.push_back(entry);
   }
@@ -554,10 +546,9 @@
 
   auto* unwrapped_type = type->UnwrapAll();
 
-  if (auto* struct_ty = unwrapped_type->As<sem::StructType>()) {
+  if (auto* struct_ty = unwrapped_type->As<sem::Struct>()) {
     // Recurse into members.
-    auto* sem = program_->Sem().Get(struct_ty);
-    for (auto* member : sem->Members()) {
+    for (auto* member : struct_ty->Members()) {
       AddEntryPointInOutVariables(
           name + "." +
               program_->Symbols().NameFor(member->Declaration()->symbol()),
@@ -611,26 +602,19 @@
       continue;
     }
 
-    auto* str = var->Type()->UnwrapIfNeeded()->As<sem::StructType>();
+    auto* str = var->Type()->UnwrapIfNeeded()->As<sem::Struct>();
     if (!str) {
       continue;
     }
 
-    auto* sem = program_->Sem().Get(str);
-    if (!sem) {
-      error_ = "Missing semantic information for structure " +
-               program_->Symbols().NameFor(str->impl()->name());
-      continue;
-    }
-
     ResourceBinding entry;
     entry.resource_type =
         read_only ? ResourceBinding::ResourceType::kReadOnlyStorageBuffer
                   : ResourceBinding::ResourceType::kStorageBuffer;
     entry.bind_group = binding_info.group->value();
     entry.binding = binding_info.binding->value();
-    entry.size = sem->Size();
-    entry.size_no_padding = sem->SizeNoPadding();
+    entry.size = str->Size();
+    entry.size_no_padding = str->SizeNoPadding();
 
     result.push_back(entry);
   }
diff --git a/src/program_builder.cc b/src/program_builder.cc
index d270cc7..a59b03a 100644
--- a/src/program_builder.cc
+++ b/src/program_builder.cc
@@ -80,7 +80,7 @@
 
 void ProgramBuilder::AssertNotMoved() const {
   if (moved_) {
-    TINT_ICE(const_cast<ProgramBuilder*>(this)->Diagnostics())
+    TINT_ICE(const_cast<ProgramBuilder*>(this)->diagnostics_)
         << "Attempting to use ProgramBuilder after it has been moved";
   }
 }
diff --git a/src/program_builder.h b/src/program_builder.h
index 2ab6624..7b0fdce 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -72,7 +72,7 @@
 #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/struct.h"
 #include "src/sem/u32_type.h"
 #include "src/sem/vector_type.h"
 #include "src/sem/void_type.h"
@@ -772,12 +772,6 @@
       return pointer(Of<T>(), storage_class);
     }
 
-    /// @param impl the struct implementation
-    /// @returns a struct pointer
-    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 {
@@ -1544,40 +1538,36 @@
     return create<ast::ReturnStatement>(Expr(std::forward<EXPR>(val)));
   }
 
-  /// Creates a ast::Struct and sem::StructType, registering the
-  /// sem::StructType with the AST().ConstructedTypes().
+  /// Creates a ast::Struct registering it with the AST().ConstructedTypes().
   /// @param source the source information
   /// @param name the struct name
   /// @param members the struct members
   /// @param decorations the optional struct decorations
   /// @returns the struct type
   template <typename NAME>
-  typ::Struct Structure(const Source& source,
-                        NAME&& name,
-                        ast::StructMemberList members,
-                        ast::DecorationList decorations = {}) {
+  ast::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),
+    auto* type = create<ast::Struct>(source, sym, std::move(members),
                                      std::move(decorations));
-    auto type = ty.struct_(impl);
     AST().AddConstructedType(type);
     return type;
   }
 
-  /// Creates a ast::Struct and sem::StructType, registering the
-  /// sem::StructType with the AST().ConstructedTypes().
+  /// Creates a ast::Struct registering it with the AST().ConstructedTypes().
   /// @param name the struct name
   /// @param members the struct members
   /// @param decorations the optional struct decorations
   /// @returns the struct type
   template <typename NAME>
-  typ::Struct Structure(NAME&& name,
-                        ast::StructMemberList members,
-                        ast::DecorationList decorations = {}) {
+  ast::Struct* Structure(NAME&& name,
+                         ast::StructMemberList members,
+                         ast::DecorationList decorations = {}) {
     auto sym = Sym(std::forward<NAME>(name));
-    auto* impl =
+    auto* type =
         create<ast::Struct>(sym, std::move(members), std::move(decorations));
-    auto type = ty.struct_(impl);
     AST().AddConstructedType(type);
     return type;
   }
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index 492d7be..900165b 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -414,14 +414,14 @@
       const spvtools::opt::Instruction& inst,
       ast::Type* first_operand_type);
 
-  /// Returns the given expression, but ensuring it's an unsigned type of the
-  /// same shape as the operand. Wraps the expresison with a bitcast if needed.
+  /// @returns the given expression, but ensuring it's an unsigned type of the
+  /// same shape as the operand. Wraps the expresion with a bitcast if needed.
   /// Assumes the given expresion is a integer scalar or vector.
   /// @param expr an integer scalar or integer vector expression.
   TypedExpression AsUnsigned(TypedExpression expr);
 
-  /// Returns the given expression, but ensuring it's a signed type of the
-  /// same shape as the operand. Wraps the expresison with a bitcast if needed.
+  /// @returns the given expression, but ensuring it's a signed type of the
+  /// same shape as the operand. Wraps the expresion with a bitcast if needed.
   /// Assumes the given expresion is a integer scalar or vector.
   /// @param expr an integer scalar or integer vector expression.
   TypedExpression AsSigned(TypedExpression expr);
diff --git a/src/reader/wgsl/parser_impl_type_alias_test.cc b/src/reader/wgsl/parser_impl_type_alias_test.cc
index fba0e5c..fb2f2d3 100644
--- a/src/reader/wgsl/parser_impl_type_alias_test.cc
+++ b/src/reader/wgsl/parser_impl_type_alias_test.cc
@@ -37,7 +37,7 @@
 TEST_F(ParserImplTest, TypeDecl_ParsesStruct_Ident) {
   auto p = parser("type a = B");
 
-  auto str = p->builder().Structure(p->builder().Symbols().Register("B"), {});
+  auto* str = p->builder().Structure(p->builder().Symbols().Register("B"), {});
   p->register_constructed("B", str);
 
   auto t = p->type_alias();
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 55605b9..32cf335 100644
--- a/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
@@ -113,7 +113,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);
 
@@ -137,7 +137,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);
 
@@ -161,7 +161,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);
 
@@ -182,7 +182,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);
 
@@ -219,7 +219,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/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index be3926e..75ba8a3 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -466,8 +466,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 5516b77..f872844 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/host_shareable_validation_test.cc b/src/resolver/host_shareable_validation_test.cc
index ec04c2e..5bbde0e 100644
--- a/src/resolver/host_shareable_validation_test.cc
+++ b/src/resolver/host_shareable_validation_test.cc
@@ -27,8 +27,8 @@
 using ResolverHostShareableValidationTest = ResolverTest;
 
 TEST_F(ResolverHostShareableValidationTest, BoolMember) {
-  auto s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())},
-                     {create<ast::StructBlockDecoration>()});
+  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);
 
@@ -42,8 +42,8 @@
 }
 
 TEST_F(ResolverHostShareableValidationTest, BoolVectorMember) {
-  auto s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3<bool>())},
-                     {create<ast::StructBlockDecoration>()});
+  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);
 
@@ -59,8 +59,8 @@
 TEST_F(ResolverHostShareableValidationTest, Aliases) {
   auto a1 = ty.alias("a1", ty.bool_());
   AST().AddConstructedType(a1);
-  auto s = Structure("S", {Member(Source{{12, 34}}, "x", a1)},
-                     {create<ast::StructBlockDecoration>()});
+  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);
   AST().AddConstructedType(a2);
@@ -76,12 +76,12 @@
 }
 
 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* 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);
 
@@ -98,7 +98,7 @@
 }
 
 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>()),
@@ -106,21 +106,21 @@
                       });
   auto a1 = ty.alias("a1", i1);
   AST().AddConstructedType(a1);
-  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* 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 a2 = ty.alias("a2", i2);
   AST().AddConstructedType(a2);
-  auto i3 = Structure("I3", {
-                                Member(Source{{4, 1}}, "x3", a1),
-                                Member(Source{{5, 1}}, "y3", i2),
-                                Member(Source{{6, 1}}, "z3", a2),
-                            });
+  auto* i3 = Structure("I3", {
+                                 Member(Source{{4, 1}}, "x3", a1),
+                                 Member(Source{{5, 1}}, "y3", i2),
+                                 Member(Source{{6, 1}}, "z3", a2),
+                             });
 
-  auto s = Structure("S", {Member(Source{{7, 8}}, "m", i3)},
-                     {create<ast::StructBlockDecoration>()});
+  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);
 
diff --git a/src/resolver/intrinsic_test.cc b/src/resolver/intrinsic_test.cc
index 7233f18..f73b19a 100644
--- a/src/resolver/intrinsic_test.cc
+++ b/src/resolver/intrinsic_test.cc
@@ -757,8 +757,8 @@
 
 TEST_F(ResolverIntrinsicDataTest, ArrayLength_Vector) {
   auto ary = ty.array<i32>();
-  auto str = Structure("S", {Member("x", ary)},
-                       {create<ast::StructBlockDecoration>()});
+  auto* str = Structure("S", {Member("x", ary)},
+                        {create<ast::StructBlockDecoration>()});
   auto ac = ty.access(ast::AccessControl::kReadOnly, str);
   Global("a", ac, ast::StorageClass::kStorage);
 
diff --git a/src/resolver/is_host_shareable_test.cc b/src/resolver/is_host_shareable_test.cc
index d2eca75..346a132 100644
--- a/src/resolver/is_host_shareable_test.cc
+++ b/src/resolver/is_host_shareable_test.cc
@@ -105,46 +105,7 @@
   EXPECT_TRUE(r()->IsHostShareable(ty.array<i32>()));
 }
 
-TEST_F(ResolverIsHostShareable, Struct_AllMembersHostShareable) {
-  EXPECT_TRUE(r()->IsHostShareable(Structure("S", {
-                                                      Member("a", ty.i32()),
-                                                      Member("b", ty.f32()),
-                                                  })));
-}
-
-TEST_F(ResolverIsHostShareable, Struct_SomeMembersNonHostShareable) {
-  auto ptr_ty = ty.pointer<i32>(ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->IsHostShareable(Structure("S", {
-                                                       Member("a", ty.i32()),
-                                                       Member("b", ptr_ty),
-                                                   })));
-}
-
-TEST_F(ResolverIsHostShareable, Struct_NestedHostShareable) {
-  auto host_shareable = Structure("S", {
-                                           Member("a", ty.i32()),
-                                           Member("b", ty.f32()),
-                                       });
-  EXPECT_TRUE(
-      r()->IsHostShareable(Structure("S", {
-                                              Member("a", ty.i32()),
-                                              Member("b", host_shareable),
-                                          })));
-}
-
-TEST_F(ResolverIsHostShareable, Struct_NestedNonHostShareable) {
-  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),
-                                      });
-  EXPECT_FALSE(
-      r()->IsHostShareable(Structure("S", {
-                                              Member("a", ty.i32()),
-                                              Member("b", non_host_shareable),
-                                          })));
-}
+// Note: Structure tests covered in host_shareable_validation_test.cc
 
 }  // namespace
 }  // namespace resolver
diff --git a/src/resolver/is_storeable_test.cc b/src/resolver/is_storeable_test.cc
index eb7b391..72f9cc6 100644
--- a/src/resolver/is_storeable_test.cc
+++ b/src/resolver/is_storeable_test.cc
@@ -90,41 +90,55 @@
 }
 
 TEST_F(ResolverIsStorableTest, Struct_AllMembersStorable) {
-  EXPECT_TRUE(r()->IsStorable(Structure("S", {
-                                                 Member("a", ty.i32()),
-                                                 Member("b", ty.f32()),
-                                             })));
+  Structure("S", {
+                     Member("a", ty.i32()),
+                     Member("b", ty.f32()),
+                 });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(ResolverIsStorableTest, Struct_SomeMembersNonStorable) {
   auto ptr_ty = ty.pointer<i32>(ast::StorageClass::kPrivate);
-  EXPECT_FALSE(r()->IsStorable(Structure("S", {
-                                                  Member("a", ty.i32()),
-                                                  Member("b", ptr_ty),
-                                              })));
+  Structure("S", {
+                     Member("a", ty.i32()),
+                     Member("b", ptr_ty),
+                 });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(error: ptr<private, i32> cannot be used as the type of a structure member)");
 }
 
 TEST_F(ResolverIsStorableTest, Struct_NestedStorable) {
-  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),
-                                             })));
+  auto* storable = Structure("Storable", {
+                                             Member("a", ty.i32()),
+                                             Member("b", ty.f32()),
+                                         });
+  Structure("S", {
+                     Member("a", ty.i32()),
+                     Member("b", storable),
+                 });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
 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),
-                                               });
-  EXPECT_FALSE(r()->IsStorable(Structure("S", {
-                                                  Member("a", ty.i32()),
-                                                  Member("b", non_storable),
-                                              })));
+  auto* non_storable = Structure("nonstorable", {
+                                                    Member("a", ty.i32()),
+                                                    Member("b", ptr_ty),
+                                                });
+  Structure("S", {
+                     Member("a", ty.i32()),
+                     Member("b", non_storable),
+                 });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(error: ptr<private, i32> cannot be used as the type of a structure member)");
 }
 
 }  // namespace
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index b46e4fe..a155ff4 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -181,15 +181,13 @@
   if (auto* arr = type->As<sem::ArrayType>()) {
     return IsStorable(arr->type());
   }
-  if (auto* str_ty = type->As<sem::StructType>()) {
-    if (auto* str = Structure(str_ty)) {
-      for (const auto* member : str->members) {
-        if (!IsStorable(member->Type())) {
-          return false;
-        }
+  if (auto* str = type->As<sem::Struct>()) {
+    for (const auto* member : str->Members()) {
+      if (!IsStorable(member->Type())) {
+        return false;
       }
-      return true;
     }
+    return true;
   }
   return false;
 }
@@ -209,12 +207,8 @@
   if (auto* arr = type->As<sem::ArrayType>()) {
     return IsHostShareable(arr->type());
   }
-  if (auto* str = type->As<sem::StructType>()) {
-    auto* info = Structure(str);
-    if (!info) {
-      return false;
-    }
-    for (auto* member : info->members) {
+  if (auto* str = type->As<sem::Struct>()) {
+    for (auto* member : str->Members()) {
       if (!IsHostShareable(member->Type())) {
         return false;
       }
@@ -243,7 +237,7 @@
 bool Resolver::ResolveInternal() {
   Mark(&builder_->AST());
 
-  auto register_named_type = [this](Symbol name, const sem::Type* type,
+  auto register_named_type = [this](Symbol name, sem::Type* type,
                                     const Source& source) {
     auto added = named_types_.emplace(name, type).second;
     if (!added) {
@@ -312,9 +306,9 @@
   return result;
 }
 
-const sem::Type* Resolver::Type(const ast::Type* ty) {
+sem::Type* Resolver::Type(const ast::Type* ty) {
   Mark(ty);
-  auto* s = [&]() -> const sem::Type* {
+  auto* s = [&]() -> sem::Type* {
     if (ty->Is<ast::Void>()) {
       return builder_->create<sem::Void>();
     }
@@ -357,8 +351,11 @@
     }
     if (auto* t = ty->As<ast::Array>()) {
       if (auto* el = Type(t->type())) {
-        return builder_->create<sem::ArrayType>(const_cast<sem::Type*>(el),
-                                                t->size(), t->decorations());
+        auto* sem = builder_->create<sem::ArrayType>(
+            const_cast<sem::Type*>(el), t->size(), t->decorations());
+        if (Array(sem, ty->source())) {
+          return sem;
+        }
       }
       return nullptr;
     }
@@ -370,7 +367,7 @@
       return nullptr;
     }
     if (auto* t = ty->As<ast::Struct>()) {
-      return builder_->create<sem::StructType>(const_cast<ast::Struct*>(t));
+      return Structure(t);
     }
     if (auto* t = ty->As<ast::Sampler>()) {
       return builder_->create<sem::Sampler>(t->kind());
@@ -417,35 +414,15 @@
     return nullptr;
   }();
 
-  if (s == nullptr) {
-    return nullptr;
+  if (s) {
+    builder_->Sem().Add(ty, s);
   }
-  if (!Type(s, ty->source())) {
-    return nullptr;
-  }
-  builder_->Sem().Add(ty, s);
   return s;
 }
 
-// TODO(crbug.com/tint/724): This method should be merged into Type(ast::Type*)
-bool Resolver::Type(const sem::Type* ty, const Source& source /* = {} */) {
-  ty = ty->UnwrapAliasIfNeeded();
-  if (auto* str = ty->As<sem::StructType>()) {
-    if (!Structure(str)) {
-      return false;
-    }
-  } else if (auto* arr = ty->As<sem::ArrayType>()) {
-    if (!Array(arr, source)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-Resolver::VariableInfo* Resolver::Variable(
-    ast::Variable* var,
-    const sem::Type* type, /* = nullptr */
-    std::string type_name /* = "" */) {
+Resolver::VariableInfo* Resolver::Variable(ast::Variable* var,
+                                           sem::Type* type, /* = nullptr */
+                                           std::string type_name /* = "" */) {
   auto it = variable_to_info_.find(var);
   if (it != variable_to_info_.end()) {
     return it->second;
@@ -561,7 +538,7 @@
       // attribute, satisfying the storage class constraints.
 
       auto* access = info->type->As<sem::AccessControl>();
-      auto* str = access ? access->type()->As<sem::StructType>() : nullptr;
+      auto* str = access ? access->type()->As<sem::Struct>() : nullptr;
       if (!str) {
         diagnostics_.add_error(
             "variables declared in the <storage> storage class must be of an "
@@ -574,7 +551,7 @@
         diagnostics_.add_error(
             "structure used as a storage buffer must be declared with the "
             "[[block]] decoration",
-            str->impl()->source());
+            str->Declaration()->source());
         if (info->declaration->source().range.begin.line) {
           diagnostics_.add_note("structure used as storage buffer here",
                                 info->declaration->source());
@@ -588,7 +565,7 @@
       // A variable in the uniform storage class is a uniform buffer variable.
       // Its store type must be a host-shareable structure type with block
       // attribute, satisfying the storage class constraints.
-      auto* str = info->type->As<sem::StructType>();
+      auto* str = info->type->As<sem::Struct>();
       if (!str) {
         diagnostics_.add_error(
             "variables declared in the <uniform> storage class must be of a "
@@ -601,7 +578,7 @@
         diagnostics_.add_error(
             "structure used as a uniform buffer must be declared with the "
             "[[block]] decoration",
-            str->impl()->source());
+            str->Declaration()->source());
         if (info->declaration->source().range.begin.line) {
           diagnostics_.add_note("structure used as uniform buffer here",
                                 info->declaration->source());
@@ -781,7 +758,7 @@
   };
   // Inner lambda that is applied to a type and all of its members.
   auto validate_entry_point_decorations_inner =
-      [&](const ast::DecorationList& decos, const sem::Type* ty, Source source,
+      [&](const ast::DecorationList& decos, sem::Type* ty, Source source,
           ParamOrRetType param_or_ret, bool is_struct_member) {
         // Scan decorations for pipeline IO attributes.
         // Check for overlap with attributes that have been seen previously.
@@ -834,7 +811,7 @@
         }
 
         // Check that we saw a pipeline IO attribute iff we need one.
-        if (Canonical(ty)->Is<sem::StructType>()) {
+        if (Canonical(ty)->Is<sem::Struct>()) {
           if (pipeline_io_attribute) {
             diagnostics_.add_error(
                 "entry point IO attributes must not be used on structure " +
@@ -862,8 +839,7 @@
 
   // Outer lambda for validating the entry point decorations for a type.
   auto validate_entry_point_decorations = [&](const ast::DecorationList& decos,
-                                              const sem::Type* ty,
-                                              Source source,
+                                              sem::Type* ty, Source source,
                                               ParamOrRetType param_or_ret) {
     // Validate the decorations for the type.
     if (!validate_entry_point_decorations_inner(decos, ty, source, param_or_ret,
@@ -871,12 +847,12 @@
       return false;
     }
 
-    if (auto* struct_ty = Canonical(ty)->As<sem::StructType>()) {
+    if (auto* str = Canonical(ty)->As<sem::Struct>()) {
       // Validate the decorations for each struct members, and also check for
       // invalid member types.
-      for (auto* member : Structure(struct_ty)->members) {
+      for (auto* member : str->Members()) {
         auto* member_ty = Canonical(member->Type());
-        if (member_ty->Is<sem::StructType>()) {
+        if (member_ty->Is<sem::Struct>()) {
           diagnostics_.add_error(
               "entry point IO types cannot contain nested structures",
               member->Declaration()->source());
@@ -988,23 +964,16 @@
       return false;
     }
 
-    if (auto* str = param_info->type->As<sem::StructType>()) {
-      auto* str_info = Structure(str);
-      if (!str_info) {
-        return false;
-      }
+    if (auto* str = param_info->type->As<sem::Struct>()) {
       switch (func->pipeline_stage()) {
         case ast::PipelineStage::kVertex:
-          str_info->pipeline_stage_uses.emplace(
-              sem::PipelineStageUsage::kVertexInput);
+          str->AddUsage(sem::PipelineStageUsage::kVertexInput);
           break;
         case ast::PipelineStage::kFragment:
-          str_info->pipeline_stage_uses.emplace(
-              sem::PipelineStageUsage::kFragmentInput);
+          str->AddUsage(sem::PipelineStageUsage::kFragmentInput);
           break;
         case ast::PipelineStage::kCompute:
-          str_info->pipeline_stage_uses.emplace(
-              sem::PipelineStageUsage::kComputeInput);
+          str->AddUsage(sem::PipelineStageUsage::kComputeInput);
           break;
         case ast::PipelineStage::kNone:
           break;
@@ -1026,7 +995,7 @@
 
   info->return_type = Canonical(info->return_type);
 
-  if (auto* str = info->return_type->As<sem::StructType>()) {
+  if (auto* str = info->return_type->As<sem::Struct>()) {
     if (!ApplyStorageClassUsageToType(ast::StorageClass::kNone, str,
                                       func->source())) {
       diagnostics_.add_note("while instantiating return type for " +
@@ -1035,22 +1004,15 @@
       return false;
     }
 
-    auto* str_info = Structure(str);
-    if (!str_info) {
-      return false;
-    }
     switch (func->pipeline_stage()) {
       case ast::PipelineStage::kVertex:
-        str_info->pipeline_stage_uses.emplace(
-            sem::PipelineStageUsage::kVertexOutput);
+        str->AddUsage(sem::PipelineStageUsage::kVertexOutput);
         break;
       case ast::PipelineStage::kFragment:
-        str_info->pipeline_stage_uses.emplace(
-            sem::PipelineStageUsage::kFragmentOutput);
+        str->AddUsage(sem::PipelineStageUsage::kFragmentOutput);
         break;
       case ast::PipelineStage::kCompute:
-        str_info->pipeline_stage_uses.emplace(
-            sem::PipelineStageUsage::kComputeOutput);
+        str->AddUsage(sem::PipelineStageUsage::kComputeOutput);
         break;
       case ast::PipelineStage::kNone:
         break;
@@ -1659,13 +1621,12 @@
   sem::Type* ret = nullptr;
   std::vector<uint32_t> swizzle;
 
-  if (auto* ty = data_type->As<sem::StructType>()) {
+  if (auto* str = data_type->As<sem::Struct>()) {
     Mark(expr->member());
     auto symbol = expr->member()->symbol();
-    auto* str = Structure(ty);
 
     const sem::StructMember* member = nullptr;
-    for (auto* m : str->members) {
+    for (auto* m : str->Members()) {
       if (m->Declaration()->symbol() == symbol) {
         ret = m->Type();
         member = m;
@@ -1689,11 +1650,11 @@
                                   expr, ret, current_statement_, member));
   } else if (auto* vec = data_type->As<sem::Vector>()) {
     Mark(expr->member());
-    std::string str = builder_->Symbols().NameFor(expr->member()->symbol());
-    auto size = str.size();
-    swizzle.reserve(str.size());
+    std::string s = builder_->Symbols().NameFor(expr->member()->symbol());
+    auto size = s.size();
+    swizzle.reserve(s.size());
 
-    for (auto c : str) {
+    for (auto c : s) {
       switch (c) {
         case 'x':
         case 'r':
@@ -1732,8 +1693,8 @@
     auto is_xyzw = [](char c) {
       return c == 'x' || c == 'y' || c == 'z' || c == 'w';
     };
-    if (!std::all_of(str.begin(), str.end(), is_rgba) &&
-        !std::all_of(str.begin(), str.end(), is_xyzw)) {
+    if (!std::all_of(s.begin(), s.end(), is_rgba) &&
+        !std::all_of(s.begin(), s.end(), is_xyzw)) {
       diagnostics_.add_error(
           "invalid mixing of vector swizzle characters rgba with xyzw",
           expr->member()->source());
@@ -2032,7 +1993,7 @@
 
   // If the variable has a declared type, resolve it.
   std::string type_name;
-  const sem::Type* type = nullptr;
+  sem::Type* type = nullptr;
   if (auto* ast_ty = var->type()) {
     type_name = ast_ty->FriendlyName(builder_->Symbols());
     type = Type(ast_ty);
@@ -2122,7 +2083,7 @@
   return true;
 }
 
-const sem::Type* Resolver::TypeOf(const ast::Expression* expr) {
+sem::Type* Resolver::TypeOf(const ast::Expression* expr) {
   auto it = expr_info_.find(expr);
   if (it != expr_info_.end()) {
     return it->second.type;
@@ -2138,7 +2099,7 @@
   return "";
 }
 
-const sem::Type* Resolver::TypeOf(const ast::Literal* lit) {
+sem::Type* Resolver::TypeOf(const ast::Literal* lit) {
   if (lit->Is<ast::SintLiteral>()) {
     return builder_->create<sem::I32>();
   }
@@ -2273,17 +2234,6 @@
             builder_->create<sem::Expression>(
                 const_cast<ast::Expression*>(expr), info.type, info.statement));
   }
-
-  // Create semantic nodes for all structs
-  for (auto it : struct_info_) {
-    auto* str = it.first;
-    auto* info = it.second;
-    builder_->Sem().Add(
-        str, builder_->create<sem::Struct>(
-                 const_cast<sem::StructType*>(str), std::move(info->members),
-                 info->align, info->size, info->size_no_padding,
-                 info->storage_class_usage, info->pipeline_stage_uses));
-  }
 }
 
 bool Resolver::DefaultAlignAndSize(const sem::Type* ty,
@@ -2305,7 +2255,7 @@
       /*vec4*/ 16,
   };
 
-  auto* cty = Canonical(ty);
+  auto* cty = Canonical(const_cast<sem::Type*>(ty));
   if (cty->is_scalar()) {
     // Note: Also captures booleans, but these are not host-shareable.
     align = 4;
@@ -2330,13 +2280,10 @@
     align = vector_align[mat->rows()];
     size = vector_align[mat->rows()] * mat->columns();
     return true;
-  } else if (auto* s = cty->As<sem::StructType>()) {
-    if (auto* si = Structure(s)) {
-      align = si->align;
-      size = si->size;
-      return true;
-    }
-    return false;
+  } else if (auto* s = cty->As<sem::Struct>()) {
+    align = s->Align();
+    size = s->Size();
+    return true;
   } else if (cty->Is<sem::ArrayType>()) {
     if (auto* sem =
             Array(ty->UnwrapAliasIfNeeded()->As<sem::ArrayType>(), source)) {
@@ -2416,8 +2363,8 @@
     return false;
   }
 
-  if (auto* el_str = el_ty->As<sem::StructType>()) {
-    if (el_str->impl()->IsBlockDecorated()) {
+  if (auto* el_str = el_ty->As<sem::Struct>()) {
+    if (el_str->IsBlockDecorated()) {
       // https://gpuweb.github.io/gpuweb/wgsl/#attributes
       // A structure type with the block attribute must not be:
       // * the element type of an array type
@@ -2454,23 +2401,23 @@
   return true;
 }
 
-bool Resolver::ValidateStructure(const StructInfo* st) {
-  for (auto* member : st->members) {
+bool Resolver::ValidateStructure(const sem::Struct* str) {
+  for (auto* member : str->Members()) {
     if (auto* r = member->Type()->UnwrapAll()->As<sem::ArrayType>()) {
       if (r->IsRuntimeArray()) {
-        if (member != st->members.back()) {
+        if (member != str->Members().back()) {
           diagnostics_.add_error(
               "v-0015",
               "runtime arrays may only appear as the last member of a struct",
               member->Declaration()->source());
           return false;
         }
-        if (!st->type->impl()->IsBlockDecorated()) {
+        if (!str->IsBlockDecorated()) {
           diagnostics_.add_error(
               "v-0015",
               "a struct containing a runtime-sized array "
               "requires the [[block]] attribute: '" +
-                  builder_->Symbols().NameFor(st->type->impl()->name()) + "'",
+                  builder_->Symbols().NameFor(str->Declaration()->name()) + "'",
               member->Declaration()->source());
           return false;
         }
@@ -2498,7 +2445,7 @@
     }
   }
 
-  for (auto* deco : st->type->impl()->decorations()) {
+  for (auto* deco : str->Declaration()->decorations()) {
     if (!(deco->Is<ast::StructBlockDecoration>())) {
       diagnostics_.add_error("decoration is not valid for struct declarations",
                              deco->source());
@@ -2509,19 +2456,13 @@
   return true;
 }
 
-Resolver::StructInfo* Resolver::Structure(const sem::StructType* str) {
-  auto info_it = struct_info_.find(str);
-  if (info_it != struct_info_.end()) {
-    // StructInfo already resolved for this structure type
-    return info_it->second;
-  }
-
-  for (auto* deco : str->impl()->decorations()) {
+sem::Struct* Resolver::Structure(const ast::Struct* str) {
+  for (auto* deco : str->decorations()) {
     Mark(deco);
   }
 
   sem::StructMemberList sem_members;
-  sem_members.reserve(str->impl()->members().size());
+  sem_members.reserve(str->members().size());
 
   // Calculate the effective size and alignment of each field, and the overall
   // size of the structure.
@@ -2537,7 +2478,7 @@
   uint32_t struct_size = 0;
   uint32_t struct_align = 1;
 
-  for (auto* member : str->impl()->members()) {
+  for (auto* member : str->members()) {
     Mark(member);
 
     // Resolve member type
@@ -2548,7 +2489,7 @@
 
     // Validate member type
     if (!IsStorable(type)) {
-      builder_->Diagnostics().add_error(
+      diagnostics_.add_error(
           type->FriendlyName(builder_->Symbols()) +
           " cannot be used as the type of a structure member");
       return nullptr;
@@ -2620,19 +2561,14 @@
   auto size_no_padding = struct_size;
   struct_size = utils::RoundUp(struct_align, struct_size);
 
-  auto* info = struct_infos_.Create();
-  info->type = str;
-  info->members = std::move(sem_members);
-  info->align = struct_align;
-  info->size = struct_size;
-  info->size_no_padding = size_no_padding;
-  struct_info_.emplace(str, info);
+  auto* out = builder_->create<sem::Struct>(
+      str, std::move(sem_members), struct_align, struct_size, size_no_padding);
 
-  if (!ValidateStructure(info)) {
+  if (!ValidateStructure(out)) {
     return nullptr;
   }
 
-  return info;
+  return out;
 }
 
 bool Resolver::ValidateReturn(const ast::ReturnStatement* ret) {
@@ -2828,20 +2764,18 @@
 }
 
 bool Resolver::ApplyStorageClassUsageToType(ast::StorageClass sc,
-                                            const sem::Type* ty,
+                                            sem::Type* ty,
                                             const Source& usage) {
   ty = ty->UnwrapIfNeeded();
 
-  if (auto* str = ty->As<sem::StructType>()) {
-    auto* info = Structure(str);
-    if (!info) {
-      return false;
-    }
-    if (info->storage_class_usage.count(sc)) {
+  if (auto* str = ty->As<sem::Struct>()) {
+    if (str->StorageClassUsage().count(sc)) {
       return true;  // Already applied
     }
-    info->storage_class_usage.emplace(sc);
-    for (auto* member : info->members) {
+
+    str->AddUsage(sc);
+
+    for (auto* member : str->Members()) {
       if (!ApplyStorageClassUsageToType(sc, member->Type(), usage)) {
         std::stringstream err;
         err << "while analysing structure member "
@@ -2887,7 +2821,7 @@
   return vec_type.FriendlyName(builder_->Symbols());
 }
 
-const sem::Type* Resolver::Canonical(const sem::Type* type) {
+sem::Type* Resolver::Canonical(sem::Type* type) {
   using AccessControl = sem::AccessControl;
   using Alias = sem::Alias;
   using Matrix = sem::Matrix;
@@ -2899,17 +2833,16 @@
     return nullptr;
   }
 
-  std::function<const Type*(const Type*)> make_canonical;
-  make_canonical = [&](const Type* t) -> const sem::Type* {
+  std::function<Type*(Type*)> make_canonical;
+  make_canonical = [&](Type* t) -> sem::Type* {
     // Unwrap alias sequence
-    const Type* ct = t;
+    Type* ct = t;
     while (auto* p = ct->As<Alias>()) {
       ct = p->type();
     }
 
     if (auto* v = ct->As<Vector>()) {
-      return builder_->create<Vector>(
-          const_cast<sem::Type*>(make_canonical(v->type())), v->size());
+      return builder_->create<Vector>(make_canonical(v->type()), v->size());
     }
     if (auto* m = ct->As<Matrix>()) {
       auto* column_type =
@@ -2943,7 +2876,7 @@
 }
 
 Resolver::VariableInfo::VariableInfo(const ast::Variable* decl,
-                                     const sem::Type* ctype,
+                                     sem::Type* ctype,
                                      const std::string& tn)
     : declaration(decl),
       type(ctype),
@@ -2955,8 +2888,5 @@
 Resolver::FunctionInfo::FunctionInfo(ast::Function* decl) : declaration(decl) {}
 Resolver::FunctionInfo::~FunctionInfo() = default;
 
-Resolver::StructInfo::StructInfo() = default;
-Resolver::StructInfo::~StructInfo() = default;
-
 }  // namespace resolver
 }  // namespace tint
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 4426138..4e94403 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -49,9 +49,6 @@
 class Array;
 class Statement;
 }  // namespace sem
-namespace sem {
-class StructType;
-}  // namespace sem
 
 namespace resolver {
 
@@ -90,19 +87,19 @@
   /// @returns the canonical type for `type`; that is, a type with all aliases
   /// removed. For example, `Canonical(alias<alias<vec3<alias<f32>>>>)` is
   /// `vec3<f32>`.
-  const sem::Type* Canonical(const sem::Type* type);
+  sem::Type* Canonical(sem::Type* type);
 
  private:
   /// Structure holding semantic information about a variable.
   /// Used to build the sem::Variable nodes at the end of resolving.
   struct VariableInfo {
     VariableInfo(const ast::Variable* decl,
-                 const sem::Type* type,
+                 sem::Type* type,
                  const std::string& type_name);
     ~VariableInfo();
 
     ast::Variable const* const declaration;
-    sem::Type const* type;
+    sem::Type* type;
     std::string const type_name;
     ast::StorageClass storage_class;
     std::vector<ast::IdentifierExpression*> users;
@@ -119,7 +116,7 @@
     UniqueVector<VariableInfo*> referenced_module_vars;
     UniqueVector<VariableInfo*> local_referenced_module_vars;
     std::vector<const ast::ReturnStatement*> return_statements;
-    sem::Type const* return_type = nullptr;
+    sem::Type* return_type = nullptr;
     std::string return_type_name;
 
     // List of transitive calls this function makes
@@ -129,7 +126,7 @@
   /// Structure holding semantic information about an expression.
   /// Used to build the sem::Expression nodes at the end of resolving.
   struct ExpressionInfo {
-    sem::Type const* type;
+    sem::Type* type;
     std::string const type_name;  // Declared type name
     sem::Statement* statement;
   };
@@ -142,21 +139,6 @@
     sem::Statement* statement;
   };
 
-  /// Structure holding semantic information about a struct.
-  /// Used to build the sem::Struct nodes at the end of resolving.
-  struct StructInfo {
-    StructInfo();
-    ~StructInfo();
-
-    sem::StructType const* type = nullptr;
-    std::vector<const sem::StructMember*> members;
-    uint32_t align = 0;
-    uint32_t size = 0;
-    uint32_t size_no_padding = 0;
-    std::unordered_set<ast::StorageClass> storage_class_usage;
-    std::unordered_set<sem::PipelineStageUsage> pipeline_stage_uses;
-  };
-
   /// Structure holding semantic information about a block (i.e. scope), such as
   /// parent block and variables declared in the block.
   /// Used to validate variable scoping rules.
@@ -237,7 +219,6 @@
   bool Statement(ast::Statement*);
   bool Statements(const ast::StatementList&);
   bool Switch(ast::SwitchStatement* s);
-  bool Type(const sem::Type* ty, const Source& source = {});
   bool UnaryOp(ast::UnaryOpExpression*);
   bool VariableDeclStatement(const ast::VariableDeclStatement*);
 
@@ -257,7 +238,7 @@
                                  const sem::Matrix* matrix_type);
   bool ValidateParameter(const ast::Variable* param);
   bool ValidateReturn(const ast::ReturnStatement* ret);
-  bool ValidateStructure(const StructInfo* st);
+  bool ValidateStructure(const sem::Struct* str);
   bool ValidateSwitch(const ast::SwitchStatement* s);
   bool ValidateVariable(const ast::Variable* param);
   bool ValidateVectorConstructor(const ast::TypeConstructorExpression* ctor,
@@ -267,7 +248,7 @@
   /// hasn't been constructed already. If an error is raised, nullptr is
   /// returned.
   /// @param ty the ast::Type
-  const sem::Type* Type(const ast::Type* ty);
+  sem::Type* Type(const ast::Type* ty);
 
   /// @returns the semantic information for the array `arr`, building it if it
   /// hasn't been constructed already. If an error is raised, nullptr is
@@ -276,9 +257,9 @@
   /// @param source the Source of the ast node with this array as its type
   const sem::Array* Array(const sem::ArrayType* arr, const Source& source);
 
-  /// @returns the StructInfo for the structure `str`, building it if it hasn't
-  /// been constructed already. If an error is raised, nullptr is returned.
-  StructInfo* Structure(const sem::StructType* str);
+  /// @returns the sem::Struct for the AST structure `str`. If an error is
+  /// raised, nullptr is returned.
+  sem::Struct* Structure(const ast::Struct* str);
 
   /// @returns the VariableInfo for the variable `var`, building it if it hasn't
   /// been constructed already. If an error is raised, nullptr is returned.
@@ -287,7 +268,7 @@
   /// @param type_name optional type name of `var` to use instead of
   /// `var->type()->FriendlyName()`.
   VariableInfo* Variable(ast::Variable* var,
-                         const sem::Type* type = nullptr,
+                         sem::Type* type = nullptr,
                          std::string type_name = "");
 
   /// Records the storage class usage for the given type, and any transient
@@ -299,7 +280,7 @@
   /// given type and storage class. Used for generating sensible error messages.
   /// @returns true on success, false on error
   bool ApplyStorageClassUsageToType(ast::StorageClass sc,
-                                    const sem::Type* ty,
+                                    sem::Type* ty,
                                     const Source& usage);
 
   /// @param align the output default alignment in bytes for the type `ty`
@@ -313,7 +294,7 @@
 
   /// @returns the resolved type of the ast::Expression `expr`
   /// @param expr the expression
-  const sem::Type* TypeOf(const ast::Expression* expr);
+  sem::Type* TypeOf(const ast::Expression* expr);
 
   /// @returns the declared type name of the ast::Expression `expr`
   /// @param expr the type name
@@ -321,7 +302,7 @@
 
   /// @returns the semantic type of the AST literal `lit`
   /// @param lit the literal
-  const sem::Type* TypeOf(const ast::Literal* lit);
+  sem::Type* TypeOf(const ast::Literal* lit);
 
   /// Creates a sem::Expression node with the resolved type `type`, and
   /// assigns this semantic node to the expression `expr`.
@@ -369,15 +350,13 @@
   std::unordered_map<const ast::Variable*, VariableInfo*> variable_to_info_;
   std::unordered_map<ast::CallExpression*, FunctionCallInfo> function_calls_;
   std::unordered_map<const ast::Expression*, ExpressionInfo> expr_info_;
-  std::unordered_map<const sem::StructType*, StructInfo*> struct_info_;
-  std::unordered_map<const sem::Type*, const sem::Type*> type_to_canonical_;
-  std::unordered_map<Symbol, const sem::Type*> named_types_;
+  std::unordered_map<sem::Type*, sem::Type*> type_to_canonical_;
+  std::unordered_map<Symbol, sem::Type*> named_types_;
   std::unordered_set<const ast::Node*> marked_;
   FunctionInfo* current_function_ = nullptr;
   sem::Statement* current_statement_ = nullptr;
   BlockAllocator<VariableInfo> variable_infos_;
   BlockAllocator<FunctionInfo> function_infos_;
-  BlockAllocator<StructInfo> struct_infos_;
 };
 
 }  // namespace resolver
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index f41639e..32a900a 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -764,8 +764,8 @@
 }
 
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
-  auto s = Structure("S", {Member("m", ty.u32())},
-                     {create<ast::StructBlockDecoration>()});
+  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);
@@ -800,8 +800,8 @@
 }
 
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) {
-  auto s = Structure("S", {Member("m", ty.u32())},
-                     {create<ast::StructBlockDecoration>()});
+  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);
@@ -884,8 +884,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");
@@ -906,8 +906,8 @@
 }
 
 TEST_F(ResolverTest, Expr_MemberAccessor_Struct_Alias) {
-  auto st = Structure("S", {Member("first_member", ty.i32()),
-                            Member("second_member", ty.f32())});
+  auto* st = Structure("S", {Member("first_member", ty.i32()),
+                             Member("second_member", ty.f32())});
   auto alias = ty.alias("alias", st);
   AST().AddConstructedType(alias);
   Global("my_struct", alias, ast::StorageClass::kInput);
@@ -987,8 +987,8 @@
   // }
   //
 
-  auto stB = Structure("B", {Member("foo", ty.vec4<f32>())});
-  auto stA = Structure("A", {Member("mem", ty.vec(stB, 3))});
+  auto* stB = Structure("B", {Member("foo", ty.vec4<f32>())});
+  auto* stA = Structure("A", {Member("mem", ty.vec(stB, 3))});
   Global("c", stA, ast::StorageClass::kInput);
 
   auto* mem = MemberAccessor(
@@ -1006,8 +1006,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"),
diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc
index 875cffa..db6be8e 100644
--- a/src/resolver/storage_class_validation_test.cc
+++ b/src/resolver/storage_class_validation_test.cc
@@ -60,7 +60,7 @@
 
 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);
   Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kStorage);
@@ -88,7 +88,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());
@@ -101,7 +101,7 @@
 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* 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);
 
@@ -116,8 +116,8 @@
 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* 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);
 
@@ -129,8 +129,8 @@
   // 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* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                      {create<ast::StructBlockDecoration>()});
   auto a1 = ty.alias("a1", s);
   AST().AddConstructedType(a1);
   auto ac = ty.access(ast::AccessControl::kReadOnly, a1);
@@ -168,7 +168,7 @@
 
 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);
   Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kUniform);
@@ -197,7 +197,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());
@@ -211,8 +211,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());
@@ -222,8 +222,8 @@
   // [[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* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                      {create<ast::StructBlockDecoration>()});
   auto a1 = ty.alias("a1", s);
   AST().AddConstructedType(a1);
   Global(Source{{56, 78}}, "g", a1, ast::StorageClass::kUniform);
diff --git a/src/resolver/struct_layout_test.cc b/src/resolver/struct_layout_test.cc
index 1a17706..55d5d2e 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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 12u);
   EXPECT_EQ(sem->SizeNoPadding(), 12u);
@@ -57,14 +57,14 @@
   auto alias_b = ty.alias("b", ty.f32());
   AST().AddConstructedType(alias_b);
 
-  auto s = Structure("S", {
-                              Member("a", alias_a),
-                              Member("b", alias_b),
-                          });
+  auto* s = Structure("S", {
+                               Member("a", alias_a),
+                               Member("b", alias_b),
+                           });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 8u);
   EXPECT_EQ(sem->SizeNoPadding(), 8u);
@@ -79,15 +79,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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 36u);
   EXPECT_EQ(sem->SizeNoPadding(), 36u);
@@ -105,15 +105,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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 136u);
   EXPECT_EQ(sem->SizeNoPadding(), 136u);
@@ -131,15 +131,16 @@
 }
 
 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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 4u);
   EXPECT_EQ(sem->SizeNoPadding(), 4u);
@@ -151,15 +152,16 @@
 }
 
 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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 32u);
   EXPECT_EQ(sem->SizeNoPadding(), 32u);
@@ -173,13 +175,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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 384u);
   EXPECT_EQ(sem->SizeNoPadding(), 384u);
@@ -191,19 +193,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 outer = ty.array(inner, 12);    // size: 12 * 48
-  auto s = Structure("S", {
-                              Member("c", outer),
-                          });
+  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),
+                           });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 576u);
   EXPECT_EQ(sem->SizeNoPadding(), 576u);
@@ -215,15 +217,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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 48u);
   EXPECT_EQ(sem->SizeNoPadding(), 48u);
@@ -241,21 +243,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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 368u);
   EXPECT_EQ(sem->SizeNoPadding(), 368u);
@@ -291,18 +293,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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 80u);
   EXPECT_EQ(sem->SizeNoPadding(), 68u);
@@ -320,21 +322,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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 76u);
   EXPECT_EQ(sem->SizeNoPadding(), 76u);
@@ -355,21 +357,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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_EQ(sem->Size(), 96u);
   EXPECT_EQ(sem->SizeNoPadding(), 68u);
@@ -390,13 +392,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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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 35e7a53..2930760 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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->PipelineStageUses(),
               UnorderedElementsAre(sem::PipelineStageUsage::kVertexInput,
@@ -157,7 +157,7 @@
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderParamViaAlias) {
-  auto s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
+  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
   auto s_alias = ty.alias("S_alias", s);
   AST().AddConstructedType(s_alias);
 
@@ -166,14 +166,14 @@
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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 = Structure("S", {Member("a", ty.f32(), {Location(0)})});
   auto s_alias = ty.alias("S_alias", s);
   AST().AddConstructedType(s_alias);
 
@@ -182,7 +182,7 @@
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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 9f6d2e4..b41a5e3 100644
--- a/src/resolver/struct_storage_class_use_test.cc
+++ b/src/resolver/struct_storage_class_use_test.cc
@@ -28,150 +28,150 @@
 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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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* s = Structure("S", {Member("a", ty.f32())});
   auto a = ty.alias("A", s);
   AST().AddConstructedType(a);
   Global("g", a, ast::StorageClass::kPrivate);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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* s = Structure("S", {Member("a", ty.f32())});
   auto a = ty.alias("A", s);
   AST().AddConstructedType(a);
   WrapInFunction(Var("g", a, ast::StorageClass::kFunction));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   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* 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);
@@ -179,7 +179,7 @@
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-  auto* sem = Sem().Get(s.sem);
+  auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
               UnorderedElementsAre(ast::StorageClass::kUniform,
diff --git a/src/sem/access_control_type_test.cc b/src/sem/access_control_type_test.cc
index cbc7cfd..3b3138b 100644
--- a/src/sem/access_control_type_test.cc
+++ b/src/sem/access_control_type_test.cc
@@ -44,7 +44,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_FALSE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/alias_type_test.cc b/src/sem/alias_type_test.cc
index 62b96a1..27d5084 100644
--- a/src/sem/alias_type_test.cc
+++ b/src/sem/alias_type_test.cc
@@ -40,7 +40,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_FALSE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/array_type_test.cc b/src/sem/array_type_test.cc
index 6d21249..636520b 100644
--- a/src/sem/array_type_test.cc
+++ b/src/sem/array_type_test.cc
@@ -54,7 +54,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_FALSE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/bool_type_test.cc b/src/sem/bool_type_test.cc
index c11872d..a76f1fb 100644
--- a/src/sem/bool_type_test.cc
+++ b/src/sem/bool_type_test.cc
@@ -34,7 +34,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_FALSE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/depth_texture_type_test.cc b/src/sem/depth_texture_type_test.cc
index da779a5..3f1ddcd 100644
--- a/src/sem/depth_texture_type_test.cc
+++ b/src/sem/depth_texture_type_test.cc
@@ -39,7 +39,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_TRUE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/external_texture_type_test.cc b/src/sem/external_texture_type_test.cc
index 89bfa19..afbb98e 100644
--- a/src/sem/external_texture_type_test.cc
+++ b/src/sem/external_texture_type_test.cc
@@ -40,7 +40,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_TRUE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/f32_type_test.cc b/src/sem/f32_type_test.cc
index 402b953..bc18606 100644
--- a/src/sem/f32_type_test.cc
+++ b/src/sem/f32_type_test.cc
@@ -34,7 +34,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_FALSE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/i32_type_test.cc b/src/sem/i32_type_test.cc
index 1b6e1d3..3d14f35 100644
--- a/src/sem/i32_type_test.cc
+++ b/src/sem/i32_type_test.cc
@@ -34,7 +34,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_FALSE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/matrix_type_test.cc b/src/sem/matrix_type_test.cc
index 9b3df83..4896ca2 100644
--- a/src/sem/matrix_type_test.cc
+++ b/src/sem/matrix_type_test.cc
@@ -45,7 +45,7 @@
   EXPECT_TRUE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_FALSE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/multisampled_texture_type_test.cc b/src/sem/multisampled_texture_type_test.cc
index 4f5a977..e35a2e9 100644
--- a/src/sem/multisampled_texture_type_test.cc
+++ b/src/sem/multisampled_texture_type_test.cc
@@ -40,7 +40,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_TRUE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/pointer_type_test.cc b/src/sem/pointer_type_test.cc
index 71fc47f..00cec4b 100644
--- a/src/sem/pointer_type_test.cc
+++ b/src/sem/pointer_type_test.cc
@@ -42,7 +42,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_TRUE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_FALSE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/sampled_texture_type_test.cc b/src/sem/sampled_texture_type_test.cc
index b3dd053..cb00b03 100644
--- a/src/sem/sampled_texture_type_test.cc
+++ b/src/sem/sampled_texture_type_test.cc
@@ -39,7 +39,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_TRUE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/sampler_type_test.cc b/src/sem/sampler_type_test.cc
index ccac497..c419ee6 100644
--- a/src/sem/sampler_type_test.cc
+++ b/src/sem/sampler_type_test.cc
@@ -45,7 +45,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_TRUE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_FALSE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/struct_type_test.cc b/src/sem/sem_struct_test.cc
similarity index 66%
rename from src/sem/struct_type_test.cc
rename to src/sem/sem_struct_test.cc
index ab73408..0e5742a 100644
--- a/src/sem/struct_type_test.cc
+++ b/src/sem/sem_struct_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "src/sem/access_control_type.h"
+#include "src/sem/struct.h"
 #include "src/sem/test_helper.h"
 #include "src/sem/texture_type.h"
 
@@ -20,22 +21,27 @@
 namespace sem {
 namespace {
 
-using StructTypeTest = TestHelper;
+using StructTest = TestHelper;
 
-TEST_F(StructTypeTest, Creation) {
+TEST_F(StructTest, Creation) {
   auto name = Sym("S");
   auto* impl =
       create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
   auto* ptr = impl;
-  auto s = ty.struct_(impl);
-  EXPECT_EQ(s->impl(), ptr);
+  auto* s = create<sem::Struct>(impl, StructMemberList{}, 4 /* align */,
+                                8 /* size */, 16 /* size_no_padding */);
+  EXPECT_EQ(s->Declaration(), ptr);
+  EXPECT_EQ(s->Align(), 4u);
+  EXPECT_EQ(s->Size(), 8u);
+  EXPECT_EQ(s->SizeNoPadding(), 16u);
 }
 
-TEST_F(StructTypeTest, Is) {
+TEST_F(StructTest, Is) {
   auto name = Sym("S");
   auto* impl =
       create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
-  auto s = ty.struct_(impl);
+  auto* s = create<sem::Struct>(impl, StructMemberList{}, 4 /* align */,
+                                4 /* size */, 4 /* size_no_padding */);
   sem::Type* ty = s;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
@@ -46,25 +52,27 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_TRUE(ty->Is<StructType>());
+  EXPECT_TRUE(ty->Is<Struct>());
   EXPECT_FALSE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
 }
 
-TEST_F(StructTypeTest, TypeName) {
+TEST_F(StructTest, TypeName) {
   auto name = Sym("my_struct");
   auto* impl =
       create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
-  auto s = ty.struct_(impl);
+  auto* s = create<sem::Struct>(impl, StructMemberList{}, 4 /* align */,
+                                4 /* size */, 4 /* size_no_padding */);
   EXPECT_EQ(s->type_name(), "__struct_$1");
 }
 
-TEST_F(StructTypeTest, FriendlyName) {
+TEST_F(StructTest, FriendlyName) {
   auto name = Sym("my_struct");
   auto* impl =
       create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
-  auto s = ty.struct_(impl);
+  auto* s = create<sem::Struct>(impl, StructMemberList{}, 4 /* align */,
+                                4 /* size */, 4 /* size_no_padding */);
   EXPECT_EQ(s->FriendlyName(Symbols()), "my_struct");
 }
 
diff --git a/src/sem/storage_texture_type_test.cc b/src/sem/storage_texture_type_test.cc
index f36706b..77b8944 100644
--- a/src/sem/storage_texture_type_test.cc
+++ b/src/sem/storage_texture_type_test.cc
@@ -41,7 +41,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_TRUE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/struct.cc b/src/sem/struct.cc
index 85afa68..40f3e06 100644
--- a/src/sem/struct.cc
+++ b/src/sem/struct.cc
@@ -15,6 +15,7 @@
 #include "src/sem/struct.h"
 #include "src/ast/struct_member.h"
 
+#include <string>
 #include <utility>
 
 TINT_INSTANTIATE_TYPEINFO(tint::sem::Struct);
@@ -23,20 +24,16 @@
 namespace tint {
 namespace sem {
 
-Struct::Struct(sem::StructType* type,
+Struct::Struct(const ast::Struct* declaration,
                StructMemberList members,
                uint32_t align,
                uint32_t size,
-               uint32_t size_no_padding,
-               std::unordered_set<ast::StorageClass> storage_class_usage,
-               std::unordered_set<PipelineStageUsage> pipeline_stage_uses)
-    : type_(type),
+               uint32_t size_no_padding)
+    : declaration_(declaration),
       members_(std::move(members)),
       align_(align),
       size_(size),
-      size_no_padding_(size_no_padding),
-      storage_class_usage_(std::move(storage_class_usage)),
-      pipeline_stage_uses_(std::move(pipeline_stage_uses)) {}
+      size_no_padding_(size_no_padding) {}
 
 Struct::~Struct() = default;
 
@@ -49,6 +46,14 @@
   return nullptr;
 }
 
+std::string Struct::type_name() const {
+  return declaration_->type_name();
+}
+
+std::string Struct::FriendlyName(const SymbolTable& symbols) const {
+  return declaration_->FriendlyName(symbols);
+}
+
 StructMember::StructMember(ast::StructMember* declaration,
                            sem::Type* type,
                            uint32_t offset,
diff --git a/src/sem/struct.h b/src/sem/struct.h
index 1c1d1a8..fda7b4f 100644
--- a/src/sem/struct.h
+++ b/src/sem/struct.h
@@ -17,11 +17,14 @@
 
 #include <stdint.h>
 
+#include <string>
 #include <unordered_set>
 #include <vector>
 
 #include "src/ast/storage_class.h"
+#include "src/ast/struct.h"
 #include "src/sem/node.h"
+#include "src/sem/type.h"
 #include "src/symbol.h"
 
 namespace tint {
@@ -34,7 +37,6 @@
 namespace sem {
 
 // Forward declarations
-class StructType;
 class StructMember;
 class Type;
 
@@ -52,30 +54,26 @@
 };
 
 /// Struct holds the semantic information for structures.
-class Struct : public Castable<Struct, Node> {
+class Struct : public Castable<Struct, Type> {
  public:
   /// Constructor
-  /// @param type the structure type
+  /// @param declaration the AST structure declaration
   /// @param members the structure members
   /// @param align the byte alignment of the structure
   /// @param size the byte size of the structure
   /// @param size_no_padding size of the members without the end of structure
   /// alignment padding
-  /// @param storage_class_usage a set of all the storage class usages
-  /// @param pipeline_stage_uses a set of all the pipeline stage uses
-  Struct(sem::StructType* type,
+  Struct(const ast::Struct* declaration,
          StructMemberList members,
          uint32_t align,
          uint32_t size,
-         uint32_t size_no_padding,
-         std::unordered_set<ast::StorageClass> storage_class_usage,
-         std::unordered_set<PipelineStageUsage> pipeline_stage_uses);
+         uint32_t size_no_padding);
 
   /// Destructor
   ~Struct() override;
 
-  /// @returns the structure type
-  sem::StructType* Type() const { return type_; }
+  /// @returns the struct
+  const ast::Struct* Declaration() const { return declaration_; }
 
   /// @returns the members of the structure
   const StructMemberList& Members() const { return members_; }
@@ -100,6 +98,12 @@
   /// alignment padding
   uint32_t SizeNoPadding() const { return size_no_padding_; }
 
+  /// Adds the StorageClass usage to the structure.
+  /// @param usage the storage usage
+  void AddUsage(ast::StorageClass usage) {
+    storage_class_usage_.emplace(usage);
+  }
+
   /// @returns the set of storage class uses of this structure
   const std::unordered_set<ast::StorageClass>& StorageClassUsage() const {
     return storage_class_usage_;
@@ -122,19 +126,38 @@
     return false;
   }
 
+  /// Adds the pipeline stage usage to the structure.
+  /// @param usage the storage usage
+  void AddUsage(PipelineStageUsage usage) {
+    pipeline_stage_uses_.emplace(usage);
+  }
+
   /// @returns the set of entry point uses of this structure
   const std::unordered_set<PipelineStageUsage>& PipelineStageUses() const {
     return pipeline_stage_uses_;
   }
 
+  /// @returns true if the struct has a block decoration
+  bool IsBlockDecorated() const { return declaration_->IsBlockDecorated(); }
+
+  /// @returns the name for the type
+  std::string type_name() const override;
+
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
  private:
-  sem::StructType* const type_;
+  uint64_t LargestMemberBaseAlignment(MemoryLayout mem_layout) const;
+
+  ast::Struct const* const declaration_;
   StructMemberList const members_;
   uint32_t const align_;
   uint32_t const size_;
   uint32_t const size_no_padding_;
-  std::unordered_set<ast::StorageClass> const storage_class_usage_;
-  std::unordered_set<PipelineStageUsage> const pipeline_stage_uses_;
+  std::unordered_set<ast::StorageClass> storage_class_usage_;
+  std::unordered_set<PipelineStageUsage> pipeline_stage_uses_;
 };
 
 /// StructMember holds the semantic information for structure members.
diff --git a/src/sem/struct_type.cc b/src/sem/struct_type.cc
deleted file mode 100644
index 21ea67e..0000000
--- a/src/sem/struct_type.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/struct_type.h"
-
-#include <cmath>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::StructType);
-
-namespace tint {
-namespace sem {
-
-StructType::StructType(ast::Struct* impl) : struct_(impl) {}
-
-StructType::StructType(StructType&&) = default;
-
-StructType::~StructType() = default;
-
-std::string StructType::type_name() const {
-  return impl()->type_name();
-}
-
-std::string StructType::FriendlyName(const SymbolTable& symbols) const {
-  return impl()->FriendlyName(symbols);
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/struct_type.h b/src/sem/struct_type.h
deleted file mode 100644
index e39010a..0000000
--- a/src/sem/struct_type.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_STRUCT_TYPE_H_
-#define SRC_SEM_STRUCT_TYPE_H_
-
-#include <string>
-
-#include "src/ast/struct.h"
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// A structure type
-class StructType : public Castable<StructType, Type> {
- public:
-  /// Constructor
-  /// @param impl the struct data
-  explicit StructType(ast::Struct* impl);
-  /// Move constructor
-  StructType(StructType&&);
-  ~StructType() override;
-
-  /// @returns true if the struct has a block decoration
-  bool IsBlockDecorated() const { return struct_->IsBlockDecorated(); }
-
-  /// @returns the struct
-  ast::Struct* impl() const { return struct_; }
-
-  /// @returns the name for the type
-  std::string type_name() const override;
-
-  /// @param symbols the program's symbol table
-  /// @returns the name for this type that closely resembles how it would be
-  /// declared in WGSL.
-  std::string FriendlyName(const SymbolTable& symbols) const override;
-
- private:
-  ast::Struct* const struct_;
-
-  uint64_t LargestMemberBaseAlignment(MemoryLayout mem_layout) const;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_STRUCT_TYPE_H_
diff --git a/src/sem/type_mappings.h b/src/sem/type_mappings.h
index 94bda9b..e963a33 100644
--- a/src/sem/type_mappings.h
+++ b/src/sem/type_mappings.h
@@ -41,7 +41,6 @@
 class MemberAccessorExpression;
 class Statement;
 class Struct;
-class StructType;
 class StructMember;
 class Type;
 class Variable;
@@ -58,7 +57,6 @@
   Function* operator()(ast::Function*);
   MemberAccessorExpression* operator()(ast::MemberAccessorExpression*);
   Statement* operator()(ast::Statement*);
-  Struct* operator()(sem::StructType*);
   StructMember* operator()(ast::StructMember*);
   Type* operator()(ast::Type*);
   Variable* operator()(ast::Variable*);
diff --git a/src/sem/u32_type_test.cc b/src/sem/u32_type_test.cc
index 5e0bcc4..bbb9e08 100644
--- a/src/sem/u32_type_test.cc
+++ b/src/sem/u32_type_test.cc
@@ -34,7 +34,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_FALSE(ty->Is<Texture>());
   EXPECT_TRUE(ty->Is<U32>());
   EXPECT_FALSE(ty->Is<Vector>());
diff --git a/src/sem/vector_type_test.cc b/src/sem/vector_type_test.cc
index c5402a9..6e52afb 100644
--- a/src/sem/vector_type_test.cc
+++ b/src/sem/vector_type_test.cc
@@ -42,7 +42,7 @@
   EXPECT_FALSE(ty->Is<Matrix>());
   EXPECT_FALSE(ty->Is<Pointer>());
   EXPECT_FALSE(ty->Is<Sampler>());
-  EXPECT_FALSE(ty->Is<StructType>());
+  EXPECT_FALSE(ty->Is<Struct>());
   EXPECT_FALSE(ty->Is<Texture>());
   EXPECT_FALSE(ty->Is<U32>());
   EXPECT_TRUE(ty->Is<Vector>());
diff --git a/src/transform/calculate_array_length.cc b/src/transform/calculate_array_length.cc
index 8faad3e..c36acce 100644
--- a/src/transform/calculate_array_length.cc
+++ b/src/transform/calculate_array_length.cc
@@ -77,12 +77,12 @@
   // get_buffer_size_intrinsic() emits the function decorated with
   // BufferSizeIntrinsic that is transformed by the HLSL writer into a call to
   // [RW]ByteAddressBuffer.GetDimensions().
-  std::unordered_map<sem::StructType*, Symbol> buffer_size_intrinsics;
-  auto get_buffer_size_intrinsic = [&](sem::StructType* buffer_type) {
+  std::unordered_map<sem::Struct*, Symbol> buffer_size_intrinsics;
+  auto get_buffer_size_intrinsic = [&](sem::Struct* buffer_type) {
     return utils::GetOrCreate(buffer_size_intrinsics, buffer_type, [&] {
       auto name = ctx.dst->Sym();
       auto* buffer_typename =
-          ctx.dst->ty.type_name(ctx.Clone(buffer_type->impl()->name()));
+          ctx.dst->ty.type_name(ctx.Clone(buffer_type->Declaration()->name()));
       auto* func = ctx.dst->create<ast::Function>(
           name,
           ast::VariableList{
@@ -100,8 +100,8 @@
               ctx.dst->ASTNodes().Create<BufferSizeIntrinsic>(ctx.dst->ID()),
           },
           ast::DecorationList{});
-      ctx.InsertAfter(ctx.src->AST().GlobalDeclarations(), buffer_type->impl(),
-                      func);
+      ctx.InsertAfter(ctx.src->AST().GlobalDeclarations(),
+                      buffer_type->Declaration(), func);
       return name;
     });
   };
@@ -141,7 +141,7 @@
           auto* storage_buffer_expr = accessor->structure();
           auto* storage_buffer_sem = sem.Get(storage_buffer_expr);
           auto* storage_buffer_type =
-              storage_buffer_sem->Type()->UnwrapAll()->As<sem::StructType>();
+              storage_buffer_sem->Type()->UnwrapAll()->As<sem::Struct>();
 
           // Generate BufferSizeIntrinsic for this storage type if we haven't
           // already
@@ -149,7 +149,7 @@
 
           if (!storage_buffer_type) {
             TINT_ICE(ctx.dst->Diagnostics())
-                << "arrayLength(X.Y) expected X to be sem::StructType, got "
+                << "arrayLength(X.Y) expected X to be sem::Struct, got "
                 << storage_buffer_type->FriendlyName(ctx.src->Symbols());
             break;
           }
@@ -176,12 +176,8 @@
                 // First time this array length is used for this block.
                 // Let's calculate it.
 
-                // Semantic info for the storage buffer structure
-                auto* storage_buffer_type_sem =
-                    ctx.src->Sem().Get(storage_buffer_type);
                 // Semantic info for the runtime array structure member
-                auto* array_member_sem =
-                    storage_buffer_type_sem->Members().back();
+                auto* array_member_sem = storage_buffer_type->Members().back();
 
                 // Construct the variable that'll hold the result of
                 // RWByteAddressBuffer.GetDimensions()
diff --git a/src/transform/canonicalize_entry_point_io.cc b/src/transform/canonicalize_entry_point_io.cc
index ee9b73c..3b0c59d 100644
--- a/src/transform/canonicalize_entry_point_io.cc
+++ b/src/transform/canonicalize_entry_point_io.cc
@@ -110,12 +110,11 @@
 
         std::function<ast::Expression*()> func_const_initializer;
 
-        if (auto* struct_ty = param_ty->As<sem::StructType>()) {
-          auto* str = ctx.src->Sem().Get(struct_ty);
+        if (auto* str = param_ty->As<sem::Struct>()) {
           // Pull out all struct members and build initializer list.
           std::vector<Symbol> member_names;
           for (auto* member : str->Members()) {
-            if (member->Type()->UnwrapAll()->Is<sem::StructType>()) {
+            if (member->Type()->UnwrapAll()->Is<sem::Struct>()) {
               TINT_ICE(ctx.dst->Diagnostics()) << "nested pipeline IO struct";
             }
 
@@ -202,11 +201,10 @@
     } else {
       ast::StructMemberList new_struct_members;
 
-      if (auto* struct_ty = ret_type->As<sem::StructType>()) {
-        auto* str = ctx.src->Sem().Get(struct_ty);
+      if (auto* str = ret_type->As<sem::Struct>()) {
         // Rebuild struct with only the entry point IO attributes.
         for (auto* member : str->Members()) {
-          if (member->Type()->UnwrapAll()->Is<sem::StructType>()) {
+          if (member->Type()->UnwrapAll()->Is<sem::Struct>()) {
             TINT_ICE(ctx.dst->Diagnostics()) << "nested pipeline IO struct";
           }
 
@@ -251,7 +249,7 @@
         };
 
         ast::ExpressionList ret_values;
-        if (ret_type->Is<sem::StructType>()) {
+        if (ret_type->Is<sem::Struct>()) {
           if (!ret->value()->Is<ast::IdentifierExpression>()) {
             // Create a const to hold the return value expression to avoid
             // re-evaluating it multiple times.
diff --git a/src/transform/decompose_storage_access.cc b/src/transform/decompose_storage_access.cc
index c3f2893..b4ff19b 100644
--- a/src/transform/decompose_storage_access.cc
+++ b/src/transform/decompose_storage_access.cc
@@ -331,7 +331,7 @@
 }
 
 /// @returns the unwrapped, user-declared constructed type of ty.
-ast::NamedType* ConstructedTypeOf(sem::Type* ty) {
+const ast::NamedType* ConstructedTypeOf(sem::Type* ty) {
   while (true) {
     if (auto* ptr = ty->As<sem::Pointer>()) {
       ty = ptr->type();
@@ -341,8 +341,8 @@
       ty = access->type();
       continue;
     }
-    if (auto* str = ty->As<sem::StructType>()) {
-      return str->impl();
+    if (auto* str = ty->As<sem::Struct>()) {
+      return str->Declaration();
     }
     // Not a constructed type
     return nullptr;
@@ -421,7 +421,7 @@
   /// @param el_ty the storage buffer element type
   /// @return the name of the function that performs the load
   Symbol LoadFunc(CloneContext& ctx,
-                  ast::NamedType* insert_after,
+                  const ast::NamedType* insert_after,
                   sem::Type* buf_ty,
                   sem::Type* el_ty) {
     return utils::GetOrCreate(load_funcs, TypePair{buf_ty, el_ty}, [&] {
@@ -451,9 +451,7 @@
                 ctx.dst->Add("offset", i * MatrixColumnStride(mat_ty));
             values.emplace_back(ctx.dst->Call(load, "buffer", offset));
           }
-        } else if (auto* str_ty = el_ty->As<sem::StructType>()) {
-          auto& sem = ctx.src->Sem();
-          auto* str = sem.Get(str_ty);
+        } else if (auto* str = el_ty->As<sem::Struct>()) {
           for (auto* member : str->Members()) {
             auto* offset = ctx.dst->Add("offset", member->Offset());
             Symbol load = LoadFunc(ctx, insert_after, buf_ty,
@@ -492,7 +490,7 @@
   /// @param el_ty the storage buffer element type
   /// @return the name of the function that performs the store
   Symbol StoreFunc(CloneContext& ctx,
-                   ast::NamedType* insert_after,
+                   const ast::NamedType* insert_after,
                    sem::Type* buf_ty,
                    sem::Type* el_ty) {
     return utils::GetOrCreate(store_funcs, TypePair{buf_ty, el_ty}, [&] {
@@ -525,9 +523,7 @@
             auto* call = ctx.dst->Call(store, "buffer", offset, access);
             body.emplace_back(ctx.dst->create<ast::CallStatement>(call));
           }
-        } else if (auto* str_ty = el_ty->As<sem::StructType>()) {
-          auto& sem = ctx.src->Sem();
-          auto* str = sem.Get(str_ty);
+        } else if (auto* str = el_ty->As<sem::Struct>()) {
           for (auto* member : str->Members()) {
             auto* offset = ctx.dst->Add("offset", member->Offset());
             auto* access = ctx.dst->MemberAccessor(
@@ -676,9 +672,8 @@
         }
       } else {
         if (auto access = state.TakeAccess(accessor->structure())) {
-          auto* str_ty = access.type->As<sem::StructType>();
-          auto* member =
-              sem.Get(str_ty)->FindMember(accessor->member()->symbol());
+          auto* str_ty = access.type->As<sem::Struct>();
+          auto* member = str_ty->FindMember(accessor->member()->symbol());
           auto offset = member->Offset();
           state.AddAccess(accessor,
                           {
diff --git a/src/transform/first_index_offset.cc b/src/transform/first_index_offset.cc
index 1ef89e1..b8fb7c2 100644
--- a/src/transform/first_index_offset.cc
+++ b/src/transform/first_index_offset.cc
@@ -133,7 +133,7 @@
       instance_index_offset = offset;
       offset += 4;
     }
-    auto struct_type =
+    auto* struct_type =
         ctx.dst->Structure(ctx.dst->Sym(), std::move(members),
                            {ctx.dst->create<ast::StructBlockDecoration>()});
 
diff --git a/src/transform/hlsl.cc b/src/transform/hlsl.cc
index cb4f1e3..d3d1656 100644
--- a/src/transform/hlsl.cc
+++ b/src/transform/hlsl.cc
@@ -96,7 +96,7 @@
       }
 
       auto* src_ty = src_sem_expr->Type();
-      if (src_ty->IsAnyOf<sem::ArrayType, sem::StructType>()) {
+      if (src_ty->IsAnyOf<sem::ArrayType, sem::Struct>()) {
         // Create a new symbol for the constant
         auto dst_symbol = ctx.dst->Sym();
         // Clone the type
diff --git a/src/transform/spirv.cc b/src/transform/spirv.cc
index df9461c..0a8d45d 100644
--- a/src/transform/spirv.cc
+++ b/src/transform/spirv.cc
@@ -288,7 +288,7 @@
     sem::Type* ty,
     ast::Type* declared_ty,
     const ast::DecorationList& decorations) const {
-  if (!ty->Is<sem::StructType>()) {
+  if (!ty->Is<sem::Struct>()) {
     // Base case: create a global variable and return.
     ast::DecorationList new_decorations =
         RemoveDecorations(&ctx, decorations, [](const ast::Decoration* deco) {
@@ -305,8 +305,8 @@
 
   // Recurse into struct members and build the initializer list.
   std::vector<Symbol> init_value_names;
-  auto* struct_ty = ty->As<sem::StructType>();
-  for (auto* member : ctx.src->Sem().Get(struct_ty)->Members()) {
+  auto* struct_ty = ty->As<sem::Struct>();
+  for (auto* member : struct_ty->Members()) {
     auto member_var = HoistToInputVariables(
         ctx, func, member->Type(), member->Declaration()->type(),
         member->Declaration()->decorations());
@@ -342,7 +342,7 @@
                                    Symbol store_value,
                                    ast::StatementList& stores) const {
   // Base case.
-  if (!ty->Is<sem::StructType>()) {
+  if (!ty->Is<sem::Struct>()) {
     // Create a global variable.
     ast::DecorationList new_decorations =
         RemoveDecorations(&ctx, decorations, [](const ast::Decoration* deco) {
@@ -366,8 +366,8 @@
   }
 
   // Recurse into struct members.
-  auto* struct_ty = ty->As<sem::StructType>();
-  for (auto* member : ctx.src->Sem().Get(struct_ty)->Members()) {
+  auto* struct_ty = ty->As<sem::Struct>();
+  for (auto* member : struct_ty->Members()) {
     member_accesses.push_back(ctx.Clone(member->Declaration()->symbol()));
     HoistToOutputVariables(ctx, func, member->Type(),
                            member->Declaration()->type(),
diff --git a/src/transform/transform.cc b/src/transform/transform.cc
index def5478..d53cd7a 100644
--- a/src/transform/transform.cc
+++ b/src/transform/transform.cc
@@ -107,8 +107,9 @@
   if (auto* a = ty->As<sem::Alias>()) {
     return ctx->dst->create<ast::TypeName>(ctx->Clone(a->symbol()));
   }
-  if (auto* s = ty->As<sem::StructType>()) {
-    return ctx->dst->create<ast::TypeName>(ctx->Clone(s->impl()->name()));
+  if (auto* s = ty->As<sem::Struct>()) {
+    return ctx->dst->create<ast::TypeName>(
+        ctx->Clone(s->Declaration()->name()));
   }
   TINT_UNREACHABLE(ctx->dst->Diagnostics())
       << "Unhandled type: " << ty->TypeInfo().name;
diff --git a/src/transform/transform_test.cc b/src/transform/transform_test.cc
index b83defc..e36cacd 100644
--- a/src/transform/transform_test.cc
+++ b/src/transform/transform_test.cc
@@ -98,7 +98,10 @@
 
 TEST_F(CreateASTTypeForTest, AccessControl) {
   auto* ac = create([](ProgramBuilder& b) {
-    auto str = b.Structure("S", {}, {});
+    auto* decl = b.Structure("S", {}, {});
+    auto* str =
+        b.create<sem::Struct>(decl, sem::StructMemberList{}, 4 /* align */,
+                              4 /* size */, 4 /* size_no_padding */);
     return b.create<sem::AccessControl>(ast::AccessControl::kReadOnly, str);
   });
   ASSERT_TRUE(ac->Is<ast::AccessControl>());
@@ -109,8 +112,9 @@
 
 TEST_F(CreateASTTypeForTest, Struct) {
   auto* str = create([](ProgramBuilder& b) {
-    auto* impl = b.Structure("S", {}, {}).ast;
-    return b.create<sem::StructType>(const_cast<ast::Struct*>(impl));
+    auto* decl = b.Structure("S", {}, {});
+    return b.create<sem::Struct>(decl, sem::StructMemberList{}, 4 /* align */,
+                                 4 /* size */, 4 /* size_no_padding */);
   });
   ASSERT_TRUE(str->Is<ast::TypeName>());
   EXPECT_EQ(
diff --git a/src/transform/vertex_pulling.cc b/src/transform/vertex_pulling.cc
index 31523b4..fb2a84d 100644
--- a/src/transform/vertex_pulling.cc
+++ b/src/transform/vertex_pulling.cc
@@ -204,7 +204,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(),
@@ -432,7 +432,7 @@
   /// @param struct_ty the structure type
   void ProcessStructParameter(ast::Function* func,
                               ast::Variable* param,
-                              ast::Struct* struct_ty) {
+                              const ast::Struct* struct_ty) {
     auto param_sym = ctx.Clone(param->symbol());
 
     // Process the struct members.
@@ -486,7 +486,7 @@
         new_members.push_back(
             ctx.dst->Member(member_sym, member_type, std::move(member_decos)));
       }
-      auto new_struct = ctx.dst->Structure(ctx.dst->Sym(), new_members);
+      auto* new_struct = ctx.dst->Structure(ctx.dst->Sym(), new_members);
 
       // Create a new function parameter with this struct.
       auto* new_param = ctx.dst->Param(ctx.dst->Sym(), new_struct);
@@ -513,8 +513,8 @@
     // Process entry point parameters.
     for (auto* param : func->params()) {
       auto* sem = ctx.src->Sem().Get(param);
-      if (auto* str = sem->Type()->As<sem::StructType>()) {
-        ProcessStructParameter(func, param, str->impl());
+      if (auto* str = sem->Type()->As<sem::Struct>()) {
+        ProcessStructParameter(func, param, str->Declaration());
       } else {
         ProcessNonStructParameter(func, param);
       }
diff --git a/src/typepair.h b/src/typepair.h
index 422e72f..d258dea 100644
--- a/src/typepair.h
+++ b/src/typepair.h
@@ -69,7 +69,7 @@
 class Sampler;
 class SampledTexture;
 class StorageTexture;
-class StructType;
+class Struct;
 class Texture;
 class Type;
 class U32;
@@ -254,7 +254,7 @@
 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 Struct = TypePair<ast::Struct, sem::Struct>;
 using Texture = TypePair<ast::Texture, sem::Texture>;
 using U32 = TypePair<ast::U32, sem::U32>;
 using Vector = TypePair<ast::Vector, sem::Vector>;
@@ -271,7 +271,6 @@
   return TypePair<AST, SEM>{ast, sem};
 }
 
-
 }  // namespace typ
 
 }  // namespace tint
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index 509d20a..ebd29de 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -210,7 +210,7 @@
   if (auto* alias = ty->As<sem::Alias>()) {
     // HLSL typedef is for intrinsic types only. For an alias'd struct,
     // generate a secondary struct with the new name.
-    if (auto* str = alias->type()->As<sem::StructType>()) {
+    if (auto* str = alias->type()->As<sem::Struct>()) {
       if (!EmitStructType(out, str,
                           builder_.Symbols().NameFor(alias->symbol()))) {
         return false;
@@ -223,9 +223,9 @@
     }
     out << " " << builder_.Symbols().NameFor(alias->symbol()) << ";"
         << std::endl;
-  } else if (auto* str = ty->As<sem::StructType>()) {
-    if (!EmitStructType(out, str,
-                        builder_.Symbols().NameFor(str->impl()->name()))) {
+  } else if (auto* str = ty->As<sem::Struct>()) {
+    if (!EmitStructType(
+            out, str, builder_.Symbols().NameFor(str->Declaration()->name()))) {
       return false;
     }
   } else {
@@ -1325,7 +1325,7 @@
   }
 
   bool brackets =
-      type->UnwrapAliasIfNeeded()->IsAnyOf<sem::ArrayType, sem::StructType>();
+      type->UnwrapAliasIfNeeded()->IsAnyOf<sem::ArrayType, sem::Struct>();
 
   if (brackets) {
     out << "{";
@@ -1715,9 +1715,9 @@
     }
 
     auto* type = var->Type()->UnwrapIfNeeded();
-    if (auto* strct = type->As<sem::StructType>()) {
+    if (auto* strct = type->As<sem::Struct>()) {
       out << "ConstantBuffer<"
-          << builder_.Symbols().NameFor(strct->impl()->name()) << "> "
+          << builder_.Symbols().NameFor(strct->Declaration()->name()) << "> "
           << builder_.Symbols().NameFor(decl->symbol())
           << RegisterAndSpace('b', binding_point) << ";" << std::endl;
     } else {
@@ -2038,7 +2038,7 @@
   for (auto* var : func->params()) {
     auto* sem = builder_.Sem().Get(var);
     auto* type = sem->Type();
-    if (!type->Is<sem::StructType>()) {
+    if (!type->Is<sem::Struct>()) {
       TINT_ICE(diagnostics_) << "Unsupported non-struct entry point parameter";
     }
 
@@ -2140,10 +2140,10 @@
         return false;
       }
     }
-  } else if (auto* str = type->As<sem::StructType>()) {
+  } else if (auto* str = type->As<sem::Struct>()) {
     out << "{";
     bool first = true;
-    for (auto* member : builder_.Sem().Get(str)->Members()) {
+    for (auto* member : str->Members()) {
       if (!first) {
         out << ", ";
       }
@@ -2383,7 +2383,7 @@
 }
 
 bool GeneratorImpl::EmitType(std::ostream& out,
-                             sem::Type* type,
+                             const sem::Type* type,
                              ast::StorageClass storage_class,
                              const std::string& name) {
   auto* access = type->As<sem::AccessControl>();
@@ -2407,7 +2407,7 @@
   if (auto* alias = type->As<sem::Alias>()) {
     out << builder_.Symbols().NameFor(alias->symbol());
   } else if (auto* ary = type->As<sem::ArrayType>()) {
-    sem::Type* base_type = ary;
+    const sem::Type* base_type = ary;
     std::vector<uint32_t> sizes;
     while (auto* arr = base_type->As<sem::ArrayType>()) {
       if (arr->IsRuntimeArray()) {
@@ -2457,8 +2457,8 @@
       out << "Comparison";
     }
     out << "State";
-  } else if (auto* str = type->As<sem::StructType>()) {
-    out << builder_.Symbols().NameFor(str->impl()->name());
+  } else if (auto* str = type->As<sem::Struct>()) {
+    out << builder_.Symbols().NameFor(str->Declaration()->name());
   } else if (auto* tex = type->As<sem::Texture>()) {
     auto* storage = tex->As<sem::StorageTexture>();
     auto* multism = tex->As<sem::MultisampledTexture>();
@@ -2547,11 +2547,9 @@
 }
 
 bool GeneratorImpl::EmitStructType(std::ostream& out,
-                                   const sem::StructType* str,
+                                   const sem::Struct* str,
                                    const std::string& name) {
-  auto* sem_str = builder_.Sem().Get(str);
-
-  auto storage_class_uses = sem_str->StorageClassUsage();
+  auto storage_class_uses = str->StorageClassUsage();
   if (storage_class_uses.size() ==
       storage_class_uses.count(ast::StorageClass::kStorage)) {
     // The only use of the structure is as a storage buffer.
@@ -2563,7 +2561,7 @@
   out << "struct " << name << " {" << std::endl;
 
   increment_indent();
-  for (auto* mem : sem_str->Members()) {
+  for (auto* mem : str->Members()) {
     make_indent(out);
     // TODO(dsinclair): Handle [[offset]] annotation on structs
     // https://bugs.chromium.org/p/tint/issues/detail?id=184
@@ -2579,8 +2577,7 @@
 
     for (auto* deco : mem->Declaration()->decorations()) {
       if (auto* location = deco->As<ast::LocationDecoration>()) {
-        auto& pipeline_stage_uses =
-            builder_.Sem().Get(str)->PipelineStageUses();
+        auto& pipeline_stage_uses = str->PipelineStageUses();
         if (pipeline_stage_uses.size() != 1) {
           TINT_ICE(diagnostics_) << "invalid entry point IO struct uses";
         }
diff --git a/src/writer/hlsl/generator_impl.h b/src/writer/hlsl/generator_impl.h
index 9a919c8..b473ec0 100644
--- a/src/writer/hlsl/generator_impl.h
+++ b/src/writer/hlsl/generator_impl.h
@@ -290,7 +290,7 @@
   /// @param name the name of the variable, only used for array emission
   /// @returns true if the type is emitted
   bool EmitType(std::ostream& out,
-                sem::Type* type,
+                const sem::Type* type,
                 ast::StorageClass storage_class,
                 const std::string& name);
   /// Handles generating a structure declaration
@@ -299,7 +299,7 @@
   /// @param name the struct name
   /// @returns true if the struct is emitted
   bool EmitStructType(std::ostream& out,
-                      const sem::StructType* ty,
+                      const sem::Struct* ty,
                       const std::string& name);
   /// Handles a unary op expression
   /// @param pre the preamble for the expression stream
diff --git a/src/writer/hlsl/generator_impl_alias_type_test.cc b/src/writer/hlsl/generator_impl_alias_type_test.cc
deleted file mode 100644
index b206b89..0000000
--- a/src/writer/hlsl/generator_impl_alias_type_test.cc
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gmock/gmock.h"
-#include "src/writer/hlsl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace hlsl {
-namespace {
-
-using ::testing::HasSubstr;
-
-using HlslGeneratorImplTest_Alias = TestHelper;
-
-TEST_F(HlslGeneratorImplTest_Alias, EmitAlias_F32) {
-  auto alias = ty.alias("a", ty.f32());
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitConstructedType(out, alias)) << gen.error();
-  EXPECT_EQ(result(), R"(typedef float a;
-)");
-}
-
-TEST_F(HlslGeneratorImplTest_Alias, EmitAlias_Struct) {
-  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);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitConstructedType(out, alias)) << gen.error();
-  EXPECT_EQ(result(), R"(struct B {
-  float a;
-  int b;
-};
-)");
-}
-
-}  // namespace
-}  // namespace hlsl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_constructor_test.cc b/src/writer/hlsl/generator_impl_constructor_test.cc
index ab0e642..2bee83b 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 e107792..7551484 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>()});
 
   auto ac = ty.access(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>()});
 
   auto ac = ty.access(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>()});
 
   auto ac = ty.access(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>()});
 
   auto ac = ty.access(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,8 +792,8 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_Called_By_EntryPoint_With_StorageBuffer) {
-  auto s = Structure("S", {Member("x", ty.f32())},
-                     {create<ast::StructBlockDecoration>()});
+  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,
          {
@@ -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>()});
 
   auto ac = ty.access(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 515127b..9343a20 100644
--- a/src/writer/hlsl/generator_impl_member_accessor_test.cc
+++ b/src/writer/hlsl/generator_impl_member_accessor_test.cc
@@ -96,7 +96,7 @@
   void SetupStorageBuffer(ast::StructMemberList members) {
     ProgramBuilder& b = *this;
 
-    auto s =
+    auto* s =
         b.Structure("Data", members, {b.create<ast::StructBlockDecoration>()});
 
     auto ac_ty = b.ty.access(ast::AccessControl::kReadWrite, s);
@@ -125,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");
@@ -522,10 +522,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)),
@@ -568,10 +568,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)),
@@ -616,10 +616,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)),
@@ -664,10 +664,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)),
@@ -711,10 +711,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)),
@@ -756,10 +756,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 940b087..6100f9f 100644
--- a/src/writer/hlsl/generator_impl_sanitizer_test.cc
+++ b/src/writer/hlsl/generator_impl_sanitizer_test.cc
@@ -26,14 +26,14 @@
 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* 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,
@@ -100,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 6a4bdbf..e4cf34e 100644
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ b/src/writer/hlsl/generator_impl_type_test.cc
@@ -156,15 +156,16 @@
 }
 
 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();
 
-  ASSERT_TRUE(gen.EmitStructType(out, s, "S")) << gen.error();
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(out, sem_s, "S")) << gen.error();
   EXPECT_EQ(result(), R"(struct S {
   int a;
   float b;
@@ -173,31 +174,33 @@
 }
 
 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);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitStructType(out, s, "S")) << gen.error();
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(out, sem_s, "S")) << gen.error();
   EXPECT_EQ(result(), "");
 }
 
 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();
 
-  ASSERT_TRUE(gen.EmitType(out, s, ast::StorageClass::kNone, ""))
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitType(out, sem_s, ast::StorageClass::kNone, ""))
       << gen.error();
   EXPECT_EQ(result(), "S");
 }
@@ -205,7 +208,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()),
@@ -215,7 +218,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, s, ast::StorageClass::kNone, ""))
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitType(out, sem_s, ast::StorageClass::kNone, ""))
       << gen.error();
   EXPECT_EQ(gen.result(), R"(struct S {
   int a;
@@ -229,10 +233,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,17 +251,18 @@
 
 // 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();
 
-  ASSERT_TRUE(gen.EmitStructType(out, s, "B")) << gen.error();
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(out, sem_s, "B")) << gen.error();
   EXPECT_EQ(result(), R"(struct B {
   int a;
   float b;
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index 43093c8..a2d4513 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -156,7 +156,7 @@
     }
     out_ << " " << program_->Symbols().NameFor(alias->symbol()) << ";"
          << std::endl;
-  } else if (auto* str = ty->As<sem::StructType>()) {
+  } else if (auto* str = ty->As<sem::Struct>()) {
     if (!EmitStructType(str)) {
       return false;
     }
@@ -889,7 +889,7 @@
 bool GeneratorImpl::EmitTypeConstructor(ast::TypeConstructorExpression* expr) {
   auto* type = TypeOf(expr);
 
-  if (type->IsAnyOf<sem::ArrayType, sem::StructType>()) {
+  if (type->IsAnyOf<sem::ArrayType, sem::Struct>()) {
     out_ << "{";
   } else {
     if (!EmitType(type, "")) {
@@ -918,7 +918,7 @@
     }
   }
 
-  if (type->IsAnyOf<sem::ArrayType, sem::StructType>()) {
+  if (type->IsAnyOf<sem::ArrayType, sem::Struct>()) {
     out_ << "}";
   } else {
     out_ << ")";
@@ -948,7 +948,7 @@
       return false;
     }
     out_ << "}";
-  } else if (type->As<sem::StructType>()) {
+  } else if (type->As<sem::Struct>()) {
     out_ << "{}";
   } else {
     diagnostics_.add_error("Invalid type for zero emission: " +
@@ -1435,7 +1435,7 @@
 
     out_ << " " << program_->Symbols().NameFor(var->symbol());
 
-    if (type->Is<sem::StructType>()) {
+    if (type->Is<sem::Struct>()) {
       out_ << " [[stage_in]]";
     } else {
       auto& decos = var->decorations();
@@ -1957,10 +1957,10 @@
     out_ << "*";
   } else if (type->Is<sem::Sampler>()) {
     out_ << "sampler";
-  } else if (auto* str = type->As<sem::StructType>()) {
+  } else if (auto* str = type->As<sem::Struct>()) {
     // The struct type emits as just the name. The declaration would be emitted
     // as part of emitting the constructed types.
-    out_ << program_->Symbols().NameFor(str->impl()->name());
+    out_ << program_->Symbols().NameFor(str->Declaration()->name());
   } else if (auto* tex = type->As<sem::Texture>()) {
     if (tex->Is<sem::DepthTexture>()) {
       out_ << "depth";
@@ -2056,20 +2056,14 @@
   return EmitType(type, name);
 }
 
-bool GeneratorImpl::EmitStructType(const sem::StructType* str) {
+bool GeneratorImpl::EmitStructType(const sem::Struct* str) {
   // TODO(dsinclair): Block decoration?
   // if (str->impl()->decoration() != ast::Decoration::kNone) {
   // }
-  out_ << "struct " << program_->Symbols().NameFor(str->impl()->name()) << " {"
-       << std::endl;
+  out_ << "struct " << program_->Symbols().NameFor(str->Declaration()->name())
+       << " {" << std::endl;
 
-  auto* sem_str = program_->Sem().Get(str);
-  if (!sem_str) {
-    TINT_ICE(diagnostics_) << "struct  missing semantic info";
-    return false;
-  }
-
-  bool is_host_shareable = sem_str->IsHostShareable();
+  bool is_host_shareable = str->IsHostShareable();
 
   // Emits a `/* 0xnnnn */` byte offset comment for a struct member.
   auto add_byte_offset_comment = [&](uint32_t offset) {
@@ -2084,14 +2078,14 @@
     std::string name;
     do {
       name = "tint_pad_" + std::to_string(pad_count++);
-    } while (sem_str->FindMember(program_->Symbols().Get(name)));
+    } while (str->FindMember(program_->Symbols().Get(name)));
 
     out_ << "int8_t " << name << "[" << size << "];" << std::endl;
   };
 
   increment_indent();
   uint32_t msl_offset = 0;
-  for (auto* mem : sem_str->Members()) {
+  for (auto* mem : str->Members()) {
     make_indent();
 
     auto name = program_->Symbols().NameFor(mem->Declaration()->symbol());
@@ -2142,8 +2136,7 @@
         }
         out_ << " [[" << attr << "]]";
       } else if (auto* loc = deco->As<ast::LocationDecoration>()) {
-        auto& pipeline_stage_uses =
-            program_->Sem().Get(str)->PipelineStageUses();
+        auto& pipeline_stage_uses = str->PipelineStageUses();
         if (pipeline_stage_uses.size() != 1) {
           TINT_ICE(diagnostics_) << "invalid entry point IO struct uses";
         }
@@ -2180,10 +2173,10 @@
     }
   }
 
-  if (is_host_shareable && sem_str->Size() != msl_offset) {
+  if (is_host_shareable && str->Size() != msl_offset) {
     make_indent();
     add_byte_offset_comment(msl_offset);
-    add_padding(sem_str->Size() - msl_offset);
+    add_padding(str->Size() - msl_offset);
   }
 
   decrement_indent();
@@ -2353,15 +2346,10 @@
     return SizeAndAlign{el_size_align.size * num_els, el_size_align.align};
   }
 
-  if (auto* str = ty->As<sem::StructType>()) {
+  if (auto* str = ty->As<sem::Struct>()) {
     // TODO(crbug.com/tint/650): There's an assumption here that MSL's default
     // structure size and alignment matches WGSL's. We need to confirm this.
-    auto* sem = program_->Sem().Get(str);
-    if (!sem) {
-      TINT_ICE(diagnostics_) << "Array missing semantic info";
-      return {};
-    }
-    return SizeAndAlign{sem->Size(), sem->Align()};
+    return SizeAndAlign{str->Size(), str->Align()};
   }
 
   TINT_UNREACHABLE(diagnostics_) << "Unhandled type " << ty->TypeInfo().name;
diff --git a/src/writer/msl/generator_impl.h b/src/writer/msl/generator_impl.h
index dea5bdf..22e5d75 100644
--- a/src/writer/msl/generator_impl.h
+++ b/src/writer/msl/generator_impl.h
@@ -35,7 +35,7 @@
 #include "src/ast/unary_op_expression.h"
 #include "src/program.h"
 #include "src/scope_stack.h"
-#include "src/sem/struct_type.h"
+#include "src/sem/struct.h"
 #include "src/writer/text_generator.h"
 
 namespace tint {
@@ -206,7 +206,7 @@
   /// Handles generating a struct declaration
   /// @param str the struct to generate
   /// @returns true if the struct is emitted
-  bool EmitStructType(const sem::StructType* str);
+  bool EmitStructType(const sem::Struct* str);
   /// Handles emitting a type constructor
   /// @param expr the type constructor expression
   /// @returns true if the constructor is emitted
diff --git a/src/writer/msl/generator_impl_alias_type_test.cc b/src/writer/msl/generator_impl_alias_type_test.cc
deleted file mode 100644
index 4d41d2d..0000000
--- a/src/writer/msl/generator_impl_alias_type_test.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/writer/msl/test_helper.h"
-
-namespace tint {
-namespace writer {
-namespace msl {
-namespace {
-
-using MslGeneratorImplTest = TestHelper;
-
-TEST_F(MslGeneratorImplTest, EmitConstructedType_F32) {
-  auto alias = ty.alias("a", ty.f32());
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitConstructedType(alias)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(typedef float a;
-)");
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructedType_Struct) {
-  auto s = Structure("a", {
-                              Member("a", ty.f32()),
-                              Member("b", ty.i32()),
-                          });
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitConstructedType(s)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(struct a {
-  float a;
-  int b;
-};
-)");
-}
-
-TEST_F(MslGeneratorImplTest, EmitConstructedType_AliasStructIdent) {
-  auto s = Structure("b", {
-                              Member("a", ty.f32()),
-                              Member("b", ty.i32()),
-                          });
-
-  auto alias = ty.alias("a", s);
-
-  GeneratorImpl& gen = Build();
-
-  ASSERT_TRUE(gen.EmitConstructedType(alias)) << gen.error();
-  EXPECT_EQ(gen.result(), R"(typedef b a;
-)");
-}
-
-}  // namespace
-}  // namespace msl
-}  // namespace writer
-}  // namespace tint
diff --git a/src/writer/msl/generator_impl_constructor_test.cc b/src/writer/msl/generator_impl_constructor_test.cc
index 6ce7924..18a4988 100644
--- a/src/writer/msl/generator_impl_constructor_test.cc
+++ b/src/writer/msl/generator_impl_constructor_test.cc
@@ -145,7 +145,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Struct) {
-  auto str = Structure("S", {
+  auto* str = Structure("S", {
                                 Member("a", ty.i32()),
                                 Member("b", ty.f32()),
                                 Member("c", ty.vec3<i32>()),
diff --git a/src/writer/msl/generator_impl_function_test.cc b/src/writer/msl/generator_impl_function_test.cc
index 8a20007..1848625 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>()});
 
   auto ac = ty.access(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>()});
 
   auto ac = ty.access(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>()});
 
   auto ac = ty.access(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>()});
 
   auto ac = ty.access(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>()});
 
   auto ac = ty.access(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 901cfbc..7e28db8 100644
--- a/src/writer/msl/generator_impl_type_test.cc
+++ b/src/writer/msl/generator_impl_type_test.cc
@@ -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,14 +185,15 @@
 }
 
 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();
 
-  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(sem_s)) << gen.error();
   EXPECT_EQ(gen.result(), R"(struct S {
   int a;
   float b;
@@ -201,7 +202,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_NonComposites) {
-  auto s =
+  auto* s =
       Structure("S",
                 {
                     Member("a", ty.i32(), {MemberSize(32)}),
@@ -238,7 +239,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(sem_s)) << gen.error();
 
   // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, NAME, SUFFIX)
   // for each field of the structure s.
@@ -315,35 +317,36 @@
 
 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);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(sem_s)) << gen.error();
 
   // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, NAME, SUFFIX)
   // for each field of the structure s.
@@ -401,10 +404,11 @@
 
 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>();
@@ -415,23 +419,25 @@
   // 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);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(sem_s)) << gen.error();
 
   // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, NAME, SUFFIX)
   // for each field of the structure s.
@@ -495,7 +501,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, AttemptTintPadSymbolCollision) {
-  auto s = Structure(
+  auto* s = Structure(
       "S",
       {
           // uses symbols tint_pad_[0..9] and tint_pad_[20..35]
@@ -533,7 +539,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
+  auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  ASSERT_TRUE(gen.EmitStructType(sem_s)) << gen.error();
   EXPECT_EQ(gen.result(), R"(struct S {
   /* 0x0000 */ int tint_pad_2;
   /* 0x0004 */ int8_t tint_pad_10[124];
@@ -582,12 +589,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);
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 98b2b81..64fa23f 100644
--- a/src/writer/msl/generator_impl_variable_decl_statement_test.cc
+++ b/src/writer/msl/generator_impl_variable_decl_statement_test.cc
@@ -65,10 +65,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.cc b/src/writer/spirv/builder.cc
index 7148a78..27fed86 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -886,13 +886,13 @@
 
   // If the data_type is a structure we're accessing a member, if it's a
   // vector we're accessing a swizzle.
-  if (data_type->Is<sem::StructType>()) {
-    auto* strct = data_type->As<sem::StructType>()->impl();
+  if (auto* str = data_type->As<sem::Struct>()) {
+    auto* impl = str->Declaration();
     auto symbol = expr->member()->symbol();
 
     uint32_t idx = 0;
-    for (; idx < strct->members().size(); ++idx) {
-      auto* member = strct->members()[idx];
+    for (; idx < impl->members().size(); ++idx) {
+      auto* member = impl->members()[idx];
       if (member->symbol() == symbol) {
         break;
       }
@@ -1294,8 +1294,8 @@
       subtype = mat->type()->UnwrapAll();
     } else if (auto* arr = subtype->As<sem::ArrayType>()) {
       subtype = arr->type()->UnwrapAll();
-    } else if (auto* str = subtype->As<sem::StructType>()) {
-      subtype = builder_.Sem().Get(str)->Members()[i]->Type()->UnwrapAll();
+    } else if (auto* str = subtype->As<sem::Struct>()) {
+      subtype = str->Members()[i]->Type()->UnwrapAll();
     }
     if (subtype != TypeOf(sc)->UnwrapAll()) {
       return false;
@@ -1373,8 +1373,7 @@
     // If the result is not a vector then we should have validated that the
     // value type is a correctly sized vector so we can just use it directly.
     if (result_type == value_type || result_type->Is<sem::Matrix>() ||
-        result_type->Is<sem::ArrayType>() ||
-        result_type->Is<sem::StructType>()) {
+        result_type->Is<sem::ArrayType>() || result_type->Is<sem::Struct>()) {
       out << "_" << id;
 
       ops.push_back(Operand::Int(id));
@@ -2024,14 +2023,14 @@
       params.push_back(Operand::Int(struct_id));
 
       auto* type = TypeOf(accessor->structure())->UnwrapAll();
-      if (!type->Is<sem::StructType>()) {
+      if (!type->Is<sem::Struct>()) {
         error_ =
             "invalid type (" + type->type_name() + ") for runtime array length";
         return 0;
       }
       // Runtime array must be the last member in the structure
-      params.push_back(Operand::Int(
-          uint32_t(type->As<sem::StructType>()->impl()->members().size() - 1)));
+      params.push_back(Operand::Int(uint32_t(
+          type->As<sem::Struct>()->Declaration()->members().size() - 1)));
 
       if (!push_function_inst(spv::Op::OpArrayLength, params)) {
         return 0;
@@ -2978,7 +2977,7 @@
     return GenerateTypeIfNeeded(alias->type());
   }
   if (auto* ac = type->As<sem::AccessControl>()) {
-    if (!ac->type()->UnwrapIfNeeded()->Is<sem::StructType>()) {
+    if (!ac->type()->UnwrapIfNeeded()->Is<sem::Struct>()) {
       return GenerateTypeIfNeeded(ac->type());
     }
   }
@@ -2993,8 +2992,8 @@
   if (auto* ac = type->As<sem::AccessControl>()) {
     // The non-struct case was handled above.
     auto* subtype = ac->type()->UnwrapIfNeeded();
-    if (!GenerateStructType(subtype->As<sem::StructType>(),
-                            ac->access_control(), result)) {
+    if (!GenerateStructType(subtype->As<sem::Struct>(), ac->access_control(),
+                            result)) {
       return 0;
     }
   } else if (auto* arr = type->As<sem::ArrayType>()) {
@@ -3015,7 +3014,7 @@
     if (!GeneratePointerType(ptr, result)) {
       return 0;
     }
-  } else if (auto* str = type->As<sem::StructType>()) {
+  } else if (auto* str = type->As<sem::Struct>()) {
     if (!GenerateStructType(str, ast::AccessControl::kReadWrite, result)) {
       return 0;
     }
@@ -3190,16 +3189,16 @@
   return true;
 }
 
-bool Builder::GenerateStructType(const sem::StructType* struct_type,
+bool Builder::GenerateStructType(const sem::Struct* struct_type,
                                  ast::AccessControl::Access access_control,
                                  const Operand& result) {
   auto struct_id = result.to_i();
-  auto* impl = struct_type->impl();
+  auto* impl = struct_type->Declaration();
 
-  if (struct_type->impl()->name().IsValid()) {
-    push_debug(spv::Op::OpName, {Operand::Int(struct_id),
-                                 Operand::String(builder_.Symbols().NameFor(
-                                     struct_type->impl()->name()))});
+  if (impl->name().IsValid()) {
+    push_debug(spv::Op::OpName,
+               {Operand::Int(struct_id),
+                Operand::String(builder_.Symbols().NameFor(impl->name()))});
   }
 
   OperandList ops;
diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h
index e8c2a5d..82d27a3 100644
--- a/src/writer/spirv/builder.h
+++ b/src/writer/spirv/builder.h
@@ -449,7 +449,7 @@
   /// @param access_control the access controls to assign to the struct
   /// @param result the result operand
   /// @returns true if the vector was successfully generated
-  bool GenerateStructType(const sem::StructType* struct_type,
+  bool GenerateStructType(const sem::Struct* struct_type,
                           ast::AccessControl::Access access_control,
                           const Operand& result);
   /// Generates a struct member
diff --git a/src/writer/spirv/builder_accessor_expression_test.cc b/src/writer/spirv/builder_accessor_expression_test.cc
index 80819a0..f1d4a89 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* 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 dfad65e..c3facfe 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 6c2ea8f..1e73535 100644
--- a/src/writer/spirv/builder_constructor_expression_test.cc
+++ b/src/writer/spirv/builder_constructor_expression_test.cc
@@ -1052,10 +1052,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);
@@ -1202,7 +1202,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);
 
@@ -1622,10 +1622,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);
@@ -1638,10 +1638,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 5771b4e..b76d0c5 100644
--- a/src/writer/spirv/builder_entry_point_test.cc
+++ b/src/writer/spirv/builder_entry_point_test.cc
@@ -188,7 +188,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 cf717cc..ac8a5be 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>()});
 
   auto ac = ty.access(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 c264fc5..7b77349 100644
--- a/src/writer/spirv/builder_global_variable_test.cc
+++ b/src/writer/spirv/builder_global_variable_test.cc
@@ -376,12 +376,12 @@
   // };
   // var b : [[access(read)]] A
 
-  auto A = Structure("A",
-                     {
-                         Member("a", ty.i32()),
-                         Member("b", ty.i32()),
-                     },
-                     {create<ast::StructBlockDecoration>()});
+  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);
@@ -415,8 +415,8 @@
   // type B = A;
   // var b : [[access(read)]] B
 
-  auto A = Structure("A", {Member("a", ty.i32())},
-                     {create<ast::StructBlockDecoration>()});
+  auto* A = Structure("A", {Member("a", ty.i32())},
+                      {create<ast::StructBlockDecoration>()});
   auto B = ty.alias("B", A);
   AST().AddConstructedType(B);
   auto ac = ty.access(ast::AccessControl::kReadOnly, B);
@@ -448,8 +448,8 @@
   // type B = [[access(read)]] A;
   // var b : B
 
-  auto A = Structure("A", {Member("a", ty.i32())},
-                     {create<ast::StructBlockDecoration>()});
+  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);
   AST().AddConstructedType(B);
@@ -481,8 +481,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>()});
   auto read = ty.access(ast::AccessControl::kReadOnly, A);
   auto rw = ty.access(ast::AccessControl::kReadWrite, A);
 
diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc
index a020a59..911d243 100644
--- a/src/writer/spirv/builder_intrinsic_test.cc
+++ b/src/writer/spirv/builder_intrinsic_test.cc
@@ -1379,8 +1379,8 @@
 }
 
 TEST_F(IntrinsicBuilderTest, Call_ArrayLength) {
-  auto s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
-                     {create<ast::StructBlockDecoration>()});
+  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{
@@ -1425,12 +1425,12 @@
 }
 
 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* 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{
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index 72ad6b2..3fdf1ad 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -59,8 +59,8 @@
 
 TEST_F(BuilderTest_Type, GenerateRuntimeArray) {
   auto ary = ty.array(ty.i32(), 0);
-  auto str = Structure("S", {Member("x", ary)},
-                       {create<ast::StructBlockDecoration>()});
+  auto* str = Structure("S", {Member("x", ary)},
+                        {create<ast::StructBlockDecoration>()});
   auto ac = ty.access(ast::AccessControl::kReadOnly, str);
   Global("a", ac, ast::StorageClass::kStorage);
 
@@ -77,8 +77,8 @@
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedRuntimeArray) {
   auto ary = ty.array(ty.i32(), 0);
-  auto str = Structure("S", {Member("x", ary)},
-                       {create<ast::StructBlockDecoration>()});
+  auto* str = Structure("S", {Member("x", ary)},
+                        {create<ast::StructBlockDecoration>()});
   auto ac = ty.access(ast::AccessControl::kReadOnly, str);
   Global("a", ac, ast::StorageClass::kStorage);
 
@@ -285,11 +285,11 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct_Empty) {
-  auto s = Structure("S", {});
+  auto* s = Structure("S", {});
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(s);
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -301,11 +301,11 @@
 }
 
 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();
 
-  auto id = b.GenerateTypeIfNeeded(s);
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -318,12 +318,12 @@
 }
 
 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();
 
-  auto id = b.GenerateTypeIfNeeded(s);
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -339,14 +339,14 @@
 }
 
 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();
 
-  auto id = b.GenerateTypeIfNeeded(s);
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -363,15 +363,15 @@
 }
 
 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();
 
-  auto id = b.GenerateTypeIfNeeded(s);
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -403,15 +403,15 @@
 
 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();
 
-  auto id = b.GenerateTypeIfNeeded(s);
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -449,17 +449,18 @@
   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();
 
-  auto id = b.GenerateTypeIfNeeded(s);
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
diff --git a/src/writer/wgsl/generator_impl.cc b/src/writer/wgsl/generator_impl.cc
index 7fe0b2e..46a16e7 100644
--- a/src/writer/wgsl/generator_impl.cc
+++ b/src/writer/wgsl/generator_impl.cc
@@ -147,8 +147,8 @@
         return false;
       }
       out_ << ";" << std::endl;
-    } else if (auto* str = ty->As<sem::StructType>()) {
-      if (!EmitStructType(str->impl())) {
+    } else if (auto* str = ty->As<sem::Struct>()) {
+      if (!EmitStructType(str->Declaration())) {
         return false;
       }
     } else {
@@ -599,10 +599,10 @@
       if (sampler->IsComparison()) {
         out_ << "_comparison";
       }
-    } else if (auto* str = ty->As<sem::StructType>()) {
+    } else if (auto* str = ty->As<sem::Struct>()) {
       // The struct, as a type, is just the name. We should have already emitted
       // the declaration through a call to |EmitStructType| earlier.
-      out_ << program_->Symbols().NameFor(str->impl()->name());
+      out_ << program_->Symbols().NameFor(str->Declaration()->name());
     } else if (auto* texture = ty->As<sem::Texture>()) {
       out_ << "texture_";
       if (texture->Is<sem::DepthTexture>()) {
diff --git a/src/writer/wgsl/generator_impl.h b/src/writer/wgsl/generator_impl.h
index 8e1f5e2..9663612 100644
--- a/src/writer/wgsl/generator_impl.h
+++ b/src/writer/wgsl/generator_impl.h
@@ -35,7 +35,7 @@
 #include "src/ast/unary_op_expression.h"
 #include "src/program.h"
 #include "src/sem/storage_texture_type.h"
-#include "src/sem/struct_type.h"
+#include "src/sem/struct.h"
 #include "src/writer/text_generator.h"
 
 namespace tint {
diff --git a/src/writer/wgsl/generator_impl_alias_type_test.cc b/src/writer/wgsl/generator_impl_alias_type_test.cc
index 4e0025c..2735aa0 100644
--- a/src/writer/wgsl/generator_impl_alias_type_test.cc
+++ b/src/writer/wgsl/generator_impl_alias_type_test.cc
@@ -31,10 +31,10 @@
 }
 
 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);
 
@@ -51,10 +51,10 @@
 }
 
 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);
 
diff --git a/src/writer/wgsl/generator_impl_function_test.cc b/src/writer/wgsl/generator_impl_function_test.cc
index 1e829f0..bf831b0 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>()});
 
   auto ac = ty.access(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 53ad8b8..491cad1 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{
diff --git a/src/writer/wgsl/generator_impl_member_accessor_test.cc b/src/writer/wgsl/generator_impl_member_accessor_test.cc
index 14aa957..1a28011 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 edd7e27..1afdba6 100644
--- a/src/writer/wgsl/generator_impl_type_test.cc
+++ b/src/writer/wgsl/generator_impl_type_test.cc
@@ -47,8 +47,8 @@
 }
 
 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);
   AST().AddConstructedType(ty.alias("make_type_reachable", a));
@@ -60,8 +60,8 @@
 }
 
 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);
   AST().AddConstructedType(ty.alias("make_type_reachable", a));
@@ -143,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();
 
@@ -155,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();
 
@@ -175,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)}),
@@ -196,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();
 
@@ -214,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();
 
@@ -232,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();
 
@@ -255,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)}),
diff --git a/test/BUILD.gn b/test/BUILD.gn
index f53b746..a985eb9 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -281,8 +281,8 @@
     "../src/sem/pointer_type_test.cc",
     "../src/sem/sampled_texture_type_test.cc",
     "../src/sem/sampler_type_test.cc",
+    "../src/sem/sem_struct_test.cc",
     "../src/sem/storage_texture_type_test.cc",
-    "../src/sem/struct_type_test.cc",
     "../src/sem/texture_type_test.cc",
     "../src/sem/type_manager_test.cc",
     "../src/sem/u32_type_test.cc",
@@ -521,7 +521,6 @@
 tint_unittests_source_set("tint_unittests_msl_writer_src") {
   sources = [
     "../src/transform/msl_test.cc",
-    "../src/writer/msl/generator_impl_alias_type_test.cc",
     "../src/writer/msl/generator_impl_array_accessor_test.cc",
     "../src/writer/msl/generator_impl_assign_test.cc",
     "../src/writer/msl/generator_impl_binary_test.cc",
@@ -560,7 +559,6 @@
 tint_unittests_source_set("tint_unittests_hlsl_writer_src") {
   sources = [
     "../src/transform/hlsl_test.cc",
-    "../src/writer/hlsl/generator_impl_alias_type_test.cc",
     "../src/writer/hlsl/generator_impl_array_accessor_test.cc",
     "../src/writer/hlsl/generator_impl_assign_test.cc",
     "../src/writer/hlsl/generator_impl_binary_test.cc",