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/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,