ast: Replace NamedType with TypeDecl

TypeDecls (alias, structure) are not a types - they declare types.
ast::TypeName is what's used for a ast::Type.

Previously we were trying to automatically convert these to TypeNames in the builder, but having these inherit from ast::Type was extremely error prone.

reader/spirv was actually constructing ast::Structs and using them as types, which is invalid.

Change-Id: I05773ad6d488626606019015b84217a5a55a8e8a
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/53802
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 2696075..89a3f9e 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -382,8 +382,6 @@
     "ast/module.h",
     "ast/multisampled_texture.cc",
     "ast/multisampled_texture.h",
-    "ast/named_type.cc",
-    "ast/named_type.h",
     "ast/node.cc",
     "ast/node.h",
     "ast/override_decoration.cc",
@@ -431,6 +429,8 @@
     "ast/type.h",
     "ast/type_constructor_expression.cc",
     "ast/type_constructor_expression.h",
+    "ast/type_decl.cc",
+    "ast/type_decl.h",
     "ast/type_name.cc",
     "ast/type_name.h",
     "ast/u32.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1d5c6de..25c90ed 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -126,8 +126,6 @@
   ast/module.h
   ast/multisampled_texture.cc
   ast/multisampled_texture.h
-  ast/named_type.cc
-  ast/named_type.h
   ast/node.cc
   ast/node.h
   ast/override_decoration.cc
@@ -178,6 +176,8 @@
   ast/type_name.h
   ast/ast_type.cc  # TODO(bclayton) - rename to type.cc
   ast/type.h
+  ast/type_decl.cc
+  ast/type_decl.h
   ast/type_name.cc
   ast/type_name.h
   ast/u32.cc
diff --git a/src/ast/alias.h b/src/ast/alias.h
index dc2dd8b..6e3dd8d 100644
--- a/src/ast/alias.h
+++ b/src/ast/alias.h
@@ -17,13 +17,13 @@
 
 #include <string>
 
-#include "src/ast/named_type.h"
+#include "src/ast/type_decl.h"
 
 namespace tint {
 namespace ast {
 
 /// A type alias type. Holds a name and pointer to another type.
-class Alias : public Castable<Alias, NamedType> {
+class Alias : public Castable<Alias, TypeDecl> {
  public:
   /// Constructor
   /// @param program_id the identifier of the program that owns this node
diff --git a/src/ast/alias_test.cc b/src/ast/alias_test.cc
index e55428d..8ae7a63 100644
--- a/src/ast/alias_test.cc
+++ b/src/ast/alias_test.cc
@@ -46,7 +46,7 @@
 TEST_F(AstAliasTest, TypeName_LinearTime) {
   Type* type = ty.i32();
   for (int i = 0; i < 1024; i++) {
-    type = Alias(Symbols().New(), type);
+    type = ty.Of(Alias(Symbols().New(), type));
   }
   for (int i = 0; i < 16384; i++) {
     type->type_name();
@@ -58,24 +58,6 @@
   EXPECT_EQ(at->type_name(), "__alias_$1__i32");
 }
 
-TEST_F(AstAliasTest, FriendlyName) {
-  auto* at = Alias("Particle", create<I32>());
-  EXPECT_EQ(at->FriendlyName(Symbols()), "Particle");
-}
-
-TEST_F(AstAliasTest, UnwrapAll_TwiceAliasPointerTwiceAlias) {
-  auto* u32 = create<U32>();
-  auto* a = create<ast::Alias>(Sym("a_type"), u32);
-  auto* aa = create<ast::Alias>(Sym("aa_type"), a);
-  auto* paa = create<Pointer>(aa, StorageClass::kUniform, Access::kUndefined);
-  auto* apaa = create<ast::Alias>(Sym("paa_type"), paa);
-  auto* aapaa = create<ast::Alias>(Sym("aapaa_type"), apaa);
-
-  EXPECT_EQ(aapaa->symbol(), Symbol(4, ID()));
-  EXPECT_EQ(aapaa->type(), apaa);
-  EXPECT_EQ(aapaa->UnwrapAll(), u32);
-}
-
 }  // namespace
 }  // namespace ast
 }  // namespace tint
diff --git a/src/ast/ast_type.cc b/src/ast/ast_type.cc
index 6702bd9..6dbf38b 100644
--- a/src/ast/ast_type.cc
+++ b/src/ast/ast_type.cc
@@ -40,9 +40,7 @@
 Type* Type::UnwrapAll() {
   auto* type = this;
   while (true) {
-    if (auto* alias = type->As<Alias>()) {
-      type = alias->type();
-    } else if (auto* ptr = type->As<Pointer>()) {
+    if (auto* ptr = type->As<Pointer>()) {
       type = ptr->type();
     } else {
       break;
diff --git a/src/ast/module.cc b/src/ast/module.cc
index 2de195f..fcb7216 100644
--- a/src/ast/module.cc
+++ b/src/ast/module.cc
@@ -16,7 +16,7 @@
 
 #include <utility>
 
-#include "src/ast/named_type.h"
+#include "src/ast/type_decl.h"
 #include "src/program_builder.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::Module);
@@ -36,7 +36,7 @@
       continue;
     }
 
-    if (auto* ty = decl->As<ast::NamedType>()) {
+    if (auto* ty = decl->As<ast::TypeDecl>()) {
       constructed_types_.push_back(ty);
     } else if (auto* func = decl->As<Function>()) {
       functions_.push_back(func);
@@ -51,7 +51,7 @@
 
 Module::~Module() = default;
 
-const ast::NamedType* Module::LookupType(Symbol name) const {
+const ast::TypeDecl* Module::LookupType(Symbol name) const {
   for (auto* ty : ConstructedTypes()) {
     if (ty->name() == name) {
       return ty;
@@ -67,7 +67,7 @@
   global_declarations_.push_back(var);
 }
 
-void Module::AddConstructedType(ast::NamedType* type) {
+void Module::AddConstructedType(ast::TypeDecl* type) {
   TINT_ASSERT(type);
   constructed_types_.push_back(type);
   global_declarations_.push_back(type);
@@ -92,7 +92,7 @@
       TINT_ICE(ctx->dst->Diagnostics()) << "src global declaration was nullptr";
       continue;
     }
-    if (auto* ty = decl->As<ast::NamedType>()) {
+    if (auto* ty = decl->As<ast::TypeDecl>()) {
       AddConstructedType(ty);
     } else if (auto* func = decl->As<Function>()) {
       AddFunction(func);
@@ -115,9 +115,6 @@
     if (auto* alias = ty->As<ast::Alias>()) {
       out << alias->symbol().to_str() << " -> " << alias->type()->type_name()
           << std::endl;
-      if (auto* str = alias->type()->As<ast::Struct>()) {
-        str->to_str(sem, out, indent);
-      }
     } else if (auto* str = ty->As<ast::Struct>()) {
       str->to_str(sem, out, indent);
     }
diff --git a/src/ast/module.h b/src/ast/module.h
index 2ea6219..af502cf 100644
--- a/src/ast/module.h
+++ b/src/ast/module.h
@@ -24,7 +24,7 @@
 namespace tint {
 namespace ast {
 
-class NamedType;
+class TypeDecl;
 
 /// Module holds the top-level AST types, functions and global variables used by
 /// a Program.
@@ -73,16 +73,16 @@
   /// @returns the global variables for the translation unit
   VariableList& GlobalVariables() { return global_variables_; }
 
-  /// Adds a constructed type to the Builder.
-  /// @param type the constructed type to add
-  void AddConstructedType(ast::NamedType* type);
+  /// Adds a type declaration to the Builder.
+  /// @param decl the constructed type declaration to add
+  void AddConstructedType(ast::TypeDecl* decl);
 
-  /// @returns the NamedType registered as a ConstructedType()
+  /// @returns the TypeDecl registered as a ConstructedType()
   /// @param name the name of the type to search for
-  const ast::NamedType* LookupType(Symbol name) const;
+  const ast::TypeDecl* LookupType(Symbol name) const;
 
   /// @returns the constructed types in the translation unit
-  const std::vector<ast::NamedType*>& ConstructedTypes() const {
+  const std::vector<ast::TypeDecl*>& ConstructedTypes() const {
     return constructed_types_;
   }
 
@@ -118,7 +118,7 @@
 
  private:
   std::vector<ast::Node*> global_declarations_;
-  std::vector<ast::NamedType*> constructed_types_;
+  std::vector<ast::TypeDecl*> constructed_types_;
   FunctionList functions_;
   VariableList global_variables_;
 };
diff --git a/src/ast/named_type.h b/src/ast/named_type.h
deleted file mode 100644
index c0c157b..0000000
--- a/src/ast/named_type.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_AST_NAMED_TYPE_H_
-#define SRC_AST_NAMED_TYPE_H_
-
-#include <string>
-
-#include "src/ast/type.h"
-
-namespace tint {
-namespace ast {
-
-/// The base class for user declared, named types.
-class NamedType : public Castable<NamedType, Type> {
- public:
-  /// Create a new struct statement
-  /// @param program_id the identifier of the program that owns this node
-  /// @param source The input source for the import statement
-  /// @param name The name of the structure
-  NamedType(ProgramID program_id, const Source& source, Symbol name);
-  /// Move constructor
-  NamedType(NamedType&&);
-
-  ~NamedType() override;
-
-  /// @returns the name of the structure
-  Symbol name() const { return name_; }
-
-  /// @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:
-  NamedType(const NamedType&) = delete;
-  NamedType& operator=(const NamedType&) = delete;
-
-  Symbol const name_;
-};
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_NAMED_TYPE_H_
diff --git a/src/ast/struct.h b/src/ast/struct.h
index d9fe744..48a7e7e 100644
--- a/src/ast/struct.h
+++ b/src/ast/struct.h
@@ -19,14 +19,14 @@
 #include <utility>
 
 #include "src/ast/decoration.h"
-#include "src/ast/named_type.h"
 #include "src/ast/struct_member.h"
+#include "src/ast/type_decl.h"
 
 namespace tint {
 namespace ast {
 
 /// A struct statement.
-class Struct : public Castable<Struct, NamedType> {
+class Struct : public Castable<Struct, TypeDecl> {
  public:
   /// Create a new struct statement
   /// @param program_id the identifier of the program that owns this node
diff --git a/src/ast/struct_test.cc b/src/ast/struct_test.cc
index 3e37625..1404ce8 100644
--- a/src/ast/struct_test.cc
+++ b/src/ast/struct_test.cc
@@ -144,13 +144,6 @@
   EXPECT_EQ(s->type_name(), "__struct_$1");
 }
 
-TEST_F(AstStructTest, FriendlyName) {
-  auto name = Sym("my_struct");
-  auto* s =
-      create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
-  EXPECT_EQ(s->FriendlyName(Symbols()), "my_struct");
-}
-
 }  // namespace
 }  // namespace ast
 }  // namespace tint
diff --git a/src/ast/named_type.cc b/src/ast/type_decl.cc
similarity index 66%
rename from src/ast/named_type.cc
rename to src/ast/type_decl.cc
index 52d7125..4337d39 100644
--- a/src/ast/named_type.cc
+++ b/src/ast/type_decl.cc
@@ -12,26 +12,29 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/ast/named_type.h"
+#include "src/ast/type_decl.h"
 
 #include "src/program_builder.h"
 
-TINT_INSTANTIATE_TYPEINFO(tint::ast::NamedType);
+TINT_INSTANTIATE_TYPEINFO(tint::ast::TypeDecl);
 
 namespace tint {
 namespace ast {
 
-NamedType::NamedType(ProgramID program_id, const Source& source, Symbol name)
+TypeDecl::TypeDecl(ProgramID program_id, const Source& source, Symbol name)
     : Base(program_id, source), name_(name) {
   TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(name, program_id);
 }
 
-NamedType::NamedType(NamedType&&) = default;
+TypeDecl::TypeDecl(TypeDecl&&) = default;
 
-NamedType::~NamedType() = default;
+TypeDecl::~TypeDecl() = default;
 
-std::string NamedType::FriendlyName(const SymbolTable& symbols) const {
-  return symbols.NameFor(name());
+void TypeDecl::to_str(const sem::Info&,
+                      std::ostream& out,
+                      size_t indent) const {
+  make_indent(out, indent);
+  out << type_name();
 }
 
 }  // namespace ast
diff --git a/src/ast/type_decl.h b/src/ast/type_decl.h
new file mode 100644
index 0000000..c942b0c
--- /dev/null
+++ b/src/ast/type_decl.h
@@ -0,0 +1,62 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_AST_TYPE_DECL_H_
+#define SRC_AST_TYPE_DECL_H_
+
+#include <string>
+
+#include "src/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// The base class for type declarations.
+class TypeDecl : public Castable<TypeDecl, Node> {
+ public:
+  /// Create a new struct statement
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source The input source for the import statement
+  /// @param name The name of the structure
+  TypeDecl(ProgramID program_id, const Source& source, Symbol name);
+  /// Move constructor
+  TypeDecl(TypeDecl&&);
+
+  ~TypeDecl() override;
+
+  /// @returns the name of the type declaration
+  Symbol name() const { return name_; }
+
+  /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
+  /// @param out the stream to write to
+  /// @param indent number of spaces to indent the node when writing
+  void to_str(const sem::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
+
+  /// @returns the name for this type. The type name is unique over all types.
+  virtual std::string type_name() const = 0;
+
+ private:
+  TypeDecl(const TypeDecl&) = delete;
+  TypeDecl& operator=(const TypeDecl&) = delete;
+
+  Symbol const name_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_TYPE_DECL_H_
diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc
index 2dddfbe..9949452 100644
--- a/src/inspector/inspector_test.cc
+++ b/src/inspector/inspector_test.cc
@@ -1006,8 +1006,8 @@
 
 TEST_F(InspectorGetEntryPointTest, InOutStruct) {
   auto* interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}});
-  Func("foo", {Param("param", interface)}, interface, {Return("param")},
-       {Stage(ast::PipelineStage::kFragment)});
+  Func("foo", {Param("param", ty.Of(interface))}, ty.Of(interface),
+       {Return("param")}, {Stage(ast::PipelineStage::kFragment)});
   Inspector& inspector = Build();
 
   auto result = inspector.GetEntryPoints();
@@ -1038,9 +1038,9 @@
 
 TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutSharedStruct) {
   auto* interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}});
-  Func("foo", {}, interface, {Return(Construct(interface))},
+  Func("foo", {}, ty.Of(interface), {Return(Construct(ty.Of(interface)))},
        {Stage(ast::PipelineStage::kFragment)});
-  Func("bar", {Param("param", interface)}, ty.void_(), {},
+  Func("bar", {Param("param", ty.Of(interface))}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
   Inspector& inspector = Build();
 
@@ -1078,10 +1078,11 @@
   auto* struct_a = MakeInOutStruct("struct_a", {{"a", 0u}, {"b", 1u}});
   auto* struct_b = MakeInOutStruct("struct_b", {{"a", 2u}});
   Func("foo",
-       {Param("param_a", struct_a), Param("param_b", struct_b),
+       {Param("param_a", ty.Of(struct_a)), Param("param_b", ty.Of(struct_b)),
         Param("param_c", ty.f32(), {Location(3u)}),
         Param("param_d", ty.f32(), {Location(4u)})},
-       struct_a, {Return("param_a")}, {Stage(ast::PipelineStage::kFragment)});
+       ty.Of(struct_a), {Return("param_a")},
+       {Stage(ast::PipelineStage::kFragment)});
   Inspector& inspector = Build();
 
   auto result = inspector.GetEntryPoints();
@@ -1660,7 +1661,7 @@
 
 TEST_F(InspectorGetResourceBindingsTest, Simple) {
   ast::Struct* ub_struct_type = MakeUniformBufferType("ub_type", {ty.i32()});
-  AddUniformBuffer("ub_var", ub_struct_type, 0, 0);
+  AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0);
   MakeStructVariableReferenceBodyFunction("ub_func", "ub_var", {{0, ty.i32()}});
 
   auto sb = MakeStorageBufferTypes("sb_type", {ty.i32()});
@@ -1768,7 +1769,7 @@
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, NonEntryPointFunc) {
   ast::Struct* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()});
-  AddUniformBuffer("foo_ub", foo_struct_type, 0, 0);
+  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
 
   MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
 
@@ -1786,7 +1787,7 @@
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, Simple) {
   ast::Struct* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()});
-  AddUniformBuffer("foo_ub", foo_struct_type, 0, 0);
+  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
 
   MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
 
@@ -1812,7 +1813,7 @@
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleMembers) {
   ast::Struct* foo_struct_type =
       MakeUniformBufferType("foo_type", {ty.i32(), ty.u32(), ty.f32()});
-  AddUniformBuffer("foo_ub", foo_struct_type, 0, 0);
+  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
 
   MakeStructVariableReferenceBodyFunction(
       "ub_func", "foo_ub", {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
@@ -1839,7 +1840,7 @@
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, ContainingPadding) {
   ast::Struct* foo_struct_type =
       MakeUniformBufferType("foo_type", {ty.vec3<f32>()});
-  AddUniformBuffer("foo_ub", foo_struct_type, 0, 0);
+  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
 
   MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub",
                                           {{0, ty.vec3<f32>()}});
@@ -1866,9 +1867,9 @@
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleUniformBuffers) {
   ast::Struct* ub_struct_type =
       MakeUniformBufferType("ub_type", {ty.i32(), ty.u32(), ty.f32()});
-  AddUniformBuffer("ub_foo", ub_struct_type, 0, 0);
-  AddUniformBuffer("ub_bar", ub_struct_type, 0, 1);
-  AddUniformBuffer("ub_baz", ub_struct_type, 2, 0);
+  AddUniformBuffer("ub_foo", ty.Of(ub_struct_type), 0, 0);
+  AddUniformBuffer("ub_bar", ty.Of(ub_struct_type), 0, 1);
+  AddUniformBuffer("ub_baz", ty.Of(ub_struct_type), 2, 0);
 
   auto AddReferenceFunc = [this](const std::string& func_name,
                                  const std::string& var_name) {
@@ -1924,7 +1925,7 @@
   // and will need to be fixed.
   ast::Struct* foo_struct_type =
       MakeUniformBufferType("foo_type", {ty.i32(), ty.array<u32, 4>()});
-  AddUniformBuffer("foo_ub", foo_struct_type, 0, 0);
+  AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0);
 
   MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
 
diff --git a/src/program.cc b/src/program.cc
index dff5616..7a0135b 100644
--- a/src/program.cc
+++ b/src/program.cc
@@ -110,6 +110,10 @@
   return Sem().Get(type);
 }
 
+const sem::Type* Program::TypeOf(const ast::TypeDecl* type_decl) const {
+  return Sem().Get(type_decl);
+}
+
 std::string Program::to_str(bool demangle) const {
   AssertNotMoved();
   auto str = ast_->to_str(Sem());
diff --git a/src/program.h b/src/program.h
index c321e35..d2c5f82 100644
--- a/src/program.h
+++ b/src/program.h
@@ -132,10 +132,17 @@
   sem::Type* TypeOf(const ast::Expression* expr) const;
 
   /// Helper for returning the resolved semantic type of the AST type `type`.
-  /// @param expr the AST type
+  /// @param type the AST type
   /// @return the resolved semantic type for the type, or nullptr if the type
   /// has no resolved type.
-  const sem::Type* TypeOf(const ast::Type* expr) const;
+  const sem::Type* TypeOf(const ast::Type* type) const;
+
+  /// Helper for returning the resolved semantic type of the AST type
+  /// declaration `type_decl`.
+  /// @param type_decl the AST type declaration
+  /// @return the resolved semantic type for the type declaration, or nullptr if
+  /// the type declaration has no resolved type.
+  const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const;
 
   /// @param demangle whether to automatically demangle the symbols in the
   /// returned string
diff --git a/src/program_builder.cc b/src/program_builder.cc
index 9a2cb09..bdb5726 100644
--- a/src/program_builder.cc
+++ b/src/program_builder.cc
@@ -102,20 +102,17 @@
   return Sem().Get(type);
 }
 
-ast::Type* ProgramBuilder::TypesBuilder::MaybeCreateTypename(
-    ast::Type* type) const {
-  if (auto* nt = As<ast::NamedType>(type)) {
-    return type_name(nt->name());
-  }
-  return type;
+const sem::Type* ProgramBuilder::TypeOf(const ast::TypeDecl* type_decl) const {
+  return Sem().Get(type_decl);
 }
 
-const ast::Type* ProgramBuilder::TypesBuilder::MaybeCreateTypename(
-    const ast::Type* type) const {
-  if (auto* nt = As<ast::NamedType>(type)) {
-    return type_name(nt->name());
-  }
-  return type;
+ast::TypeName* ProgramBuilder::TypesBuilder::Of(ast::TypeDecl* decl) const {
+  return type_name(decl->name());
+}
+
+const ast::TypeName* ProgramBuilder::TypesBuilder::Of(
+    const ast::TypeDecl* decl) const {
+  return type_name(decl->name());
 }
 
 ProgramBuilder::TypesBuilder::TypesBuilder(ProgramBuilder* pb) : builder(pb) {}
diff --git a/src/program_builder.h b/src/program_builder.h
index 4a9c3d0..62c4ee8 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -434,7 +434,6 @@
     /// @param n vector width in elements
     /// @return the tint AST type for a `n`-element vector of `type`.
     ast::Vector* vec(ast::Type* type, uint32_t n) const {
-      type = MaybeCreateTypename(type);
       return builder->create<ast::Vector>(type, n);
     }
 
@@ -443,7 +442,6 @@
     /// @param n vector width in elements
     /// @return the tint AST type for a `n`-element vector of `type`.
     ast::Vector* vec(const Source& source, ast::Type* type, uint32_t n) const {
-      type = MaybeCreateTypename(type);
       return builder->create<ast::Vector>(source, type, n);
     }
 
@@ -489,7 +487,6 @@
     /// @param rows number of rows for the matrix
     /// @return the tint AST type for a matrix of `type`
     ast::Matrix* mat(ast::Type* type, uint32_t columns, uint32_t rows) const {
-      type = MaybeCreateTypename(type);
       return builder->create<ast::Matrix>(type, rows, columns);
     }
 
@@ -610,7 +607,6 @@
     ast::Array* array(ast::Type* subtype,
                       uint32_t n = 0,
                       ast::DecorationList decos = {}) const {
-      subtype = MaybeCreateTypename(subtype);
       return builder->create<ast::Array>(subtype, n, decos);
     }
 
@@ -623,7 +619,6 @@
                       ast::Type* subtype,
                       uint32_t n = 0,
                       ast::DecorationList decos = {}) const {
-      subtype = MaybeCreateTypename(subtype);
       return builder->create<ast::Array>(source, subtype, n, decos);
     }
 
@@ -632,7 +627,6 @@
     /// @param stride the array stride. 0 represents implicit stride
     /// @return the tint AST type for a array of size `n` of type `T`
     ast::Array* array(ast::Type* subtype, uint32_t n, uint32_t stride) const {
-      subtype = MaybeCreateTypename(subtype);
       ast::DecorationList decos;
       if (stride) {
         decos.emplace_back(builder->create<ast::StrideDecoration>(stride));
@@ -649,7 +643,6 @@
                       ast::Type* subtype,
                       uint32_t n,
                       uint32_t stride) const {
-      subtype = MaybeCreateTypename(subtype);
       ast::DecorationList decos;
       if (stride) {
         decos.emplace_back(builder->create<ast::StrideDecoration>(stride));
@@ -695,7 +688,6 @@
     /// @returns the alias pointer
     template <typename NAME>
     ast::Alias* alias(NAME&& name, ast::Type* type) const {
-      type = MaybeCreateTypename(type);
       auto sym = builder->Sym(std::forward<NAME>(name));
       return builder->create<ast::Alias>(sym, type);
     }
@@ -709,7 +701,6 @@
     ast::Alias* alias(const Source& source,
                       NAME&& name,
                       ast::Type* type) const {
-      type = MaybeCreateTypename(type);
       auto sym = builder->Sym(std::forward<NAME>(name));
       return builder->create<ast::Alias>(source, sym, type);
     }
@@ -721,7 +712,6 @@
     ast::Pointer* pointer(ast::Type* type,
                           ast::StorageClass storage_class,
                           ast::Access access = ast::Access::kUndefined) const {
-      type = MaybeCreateTypename(type);
       return builder->create<ast::Pointer>(type, storage_class, access);
     }
 
@@ -734,7 +724,6 @@
                           ast::Type* type,
                           ast::StorageClass storage_class,
                           ast::Access access = ast::Access::kUndefined) const {
-      type = MaybeCreateTypename(type);
       return builder->create<ast::Pointer>(source, type, storage_class, access);
     }
 
@@ -848,17 +837,15 @@
     }
 
     /// [DEPRECATED]: TODO(crbug.com/tint/745): Migrate to const AST pointers.
-    /// If ty is a ast::Struct or ast::Alias, the returned type is an
-    /// ast::TypeName of the given type's name, otherwise  type is returned.
+    /// Constructs a TypeName for the type declaration.
     /// @param type the type
     /// @return either type or a pointer to a new ast::TypeName
-    ast::Type* MaybeCreateTypename(ast::Type* type) const;
+    ast::TypeName* Of(ast::TypeDecl* type) const;
 
-    /// If ty is a ast::Struct or ast::Alias, the returned type is an
-    /// ast::TypeName of the given type's name, otherwise  type is returned.
+    /// Constructs a TypeName for the type declaration.
     /// @param type the type
     /// @return either type or a pointer to a new ast::TypeName
-    const ast::Type* MaybeCreateTypename(const ast::Type* type) const;
+    const ast::TypeName* Of(const ast::TypeDecl* type) const;
 
     /// The ProgramBuilder
     ProgramBuilder* const builder;
@@ -1073,7 +1060,6 @@
   /// values `args`.
   template <typename... ARGS>
   ast::TypeConstructorExpression* Construct(ast::Type* type, ARGS&&... args) {
-    type = ty.MaybeCreateTypename(type);
     return create<ast::TypeConstructorExpression>(
         type, ExprList(std::forward<ARGS>(args)...));
   }
@@ -1210,7 +1196,6 @@
   ast::Variable* Var(NAME&& name,
                      const ast::Type* type,
                      OPTIONAL&&... optional) {
-    type = ty.MaybeCreateTypename(type);
     VarOptionals opts(std::forward<OPTIONAL>(optional)...);
     return create<ast::Variable>(Sym(std::forward<NAME>(name)), opts.storage,
                                  opts.access, type, false, opts.constructor,
@@ -1234,7 +1219,6 @@
                      NAME&& name,
                      const ast::Type* type,
                      OPTIONAL&&... optional) {
-    type = ty.MaybeCreateTypename(type);
     VarOptionals opts(std::forward<OPTIONAL>(optional)...);
     return create<ast::Variable>(source, Sym(std::forward<NAME>(name)),
                                  opts.storage, opts.access, type, false,
@@ -1251,7 +1235,6 @@
                        ast::Type* type,
                        ast::Expression* constructor,
                        ast::DecorationList decorations = {}) {
-    type = ty.MaybeCreateTypename(type);
     return create<ast::Variable>(
         Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
         ast::Access::kUndefined, type, true, constructor, decorations);
@@ -1269,7 +1252,6 @@
                        ast::Type* type,
                        ast::Expression* constructor,
                        ast::DecorationList decorations = {}) {
-    type = ty.MaybeCreateTypename(type);
     return create<ast::Variable>(
         source, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
         ast::Access::kUndefined, type, true, constructor, decorations);
@@ -1283,7 +1265,6 @@
   ast::Variable* Param(NAME&& name,
                        ast::Type* type,
                        ast::DecorationList decorations = {}) {
-    type = ty.MaybeCreateTypename(type);
     return create<ast::Variable>(
         Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
         ast::Access::kUndefined, type, true, nullptr, decorations);
@@ -1299,7 +1280,6 @@
                        NAME&& name,
                        ast::Type* type,
                        ast::DecorationList decorations = {}) {
-    type = ty.MaybeCreateTypename(type);
     return create<ast::Variable>(
         source, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone,
         ast::Access::kUndefined, type, true, nullptr, decorations);
@@ -1559,7 +1539,6 @@
                       ast::StatementList body,
                       ast::DecorationList decorations = {},
                       ast::DecorationList return_type_decorations = {}) {
-    type = ty.MaybeCreateTypename(type);
     auto* func =
         create<ast::Function>(source, Sym(std::forward<NAME>(name)), params,
                               type, create<ast::BlockStatement>(body),
@@ -1584,7 +1563,6 @@
                       ast::StatementList body,
                       ast::DecorationList decorations = {},
                       ast::DecorationList return_type_decorations = {}) {
-    type = ty.MaybeCreateTypename(type);
     auto* func = create<ast::Function>(Sym(std::forward<NAME>(name)), params,
                                        type, create<ast::BlockStatement>(body),
                                        decorations, return_type_decorations);
@@ -1688,7 +1666,6 @@
                             NAME&& name,
                             ast::Type* type,
                             ast::DecorationList decorations = {}) {
-    type = ty.MaybeCreateTypename(type);
     return create<ast::StructMember>(source, Sym(std::forward<NAME>(name)),
                                      type, std::move(decorations));
   }
@@ -1702,7 +1679,6 @@
   ast::StructMember* Member(NAME&& name,
                             ast::Type* type,
                             ast::DecorationList decorations = {}) {
-    type = ty.MaybeCreateTypename(type);
     return create<ast::StructMember>(source_, Sym(std::forward<NAME>(name)),
                                      type, std::move(decorations));
   }
@@ -1714,7 +1690,6 @@
   /// @returns the struct member pointer
   template <typename NAME>
   ast::StructMember* Member(uint32_t offset, NAME&& name, ast::Type* type) {
-    type = ty.MaybeCreateTypename(type);
     return create<ast::StructMember>(
         source_, Sym(std::forward<NAME>(name)), type,
         ast::DecorationList{
@@ -1978,10 +1953,19 @@
   /// Helper for returning the resolved semantic type of the AST type `type`.
   /// @note As the Resolver is run when the Program is built, this will only be
   /// useful for the Resolver itself and tests that use their own Resolver.
-  /// @param expr the AST type
+  /// @param type the AST type
   /// @return the resolved semantic type for the type, or nullptr if the type
   /// has no resolved type.
-  const sem::Type* TypeOf(const ast::Type* expr) const;
+  const sem::Type* TypeOf(const ast::Type* type) const;
+
+  /// Helper for returning the resolved semantic type of the AST type
+  /// declaration `type_decl`.
+  /// @note As the Resolver is run when the Program is built, this will only be
+  /// useful for the Resolver itself and tests that use their own Resolver.
+  /// @param type_decl the AST type declaration
+  /// @return the resolved semantic type for the type declaration, or nullptr if
+  /// the type declaration has no resolved type.
+  const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const;
 
   /// Wraps the ast::Literal in a statement. This is used by tests that
   /// construct a partial AST and require the Resolver to reach these
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 4e7c4fa..132f91f 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -1042,10 +1042,10 @@
     }
 
     // Create and register the result type.
-    return_type = create<ast::Struct>(Source{}, return_struct_sym,
-                                      return_members, ast::DecorationList{});
-    parser_impl_.AddConstructedType(return_struct_sym,
-                                    return_type->As<ast::NamedType>());
+    auto* str = create<ast::Struct>(Source{}, return_struct_sym, return_members,
+                                    ast::DecorationList{});
+    parser_impl_.AddConstructedType(return_struct_sym, str);
+    return_type = builder_.ty.Of(str);
 
     // Add the return-value statement.
     stmts.push_back(create<ast::ReturnStatement>(
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 70dd7eb..f262de9 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -1037,10 +1037,10 @@
   return ty_.Struct(sym, std::move(ast_member_types));
 }
 
-void ParserImpl::AddConstructedType(Symbol name, ast::NamedType* type) {
+void ParserImpl::AddConstructedType(Symbol name, ast::TypeDecl* decl) {
   auto iter = constructed_types_.insert(name);
   if (iter.second) {
-    builder_.AST().AddConstructedType(type);
+    builder_.AST().AddConstructedType(decl);
   }
 }
 
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index 14250fc..d6d2ccf 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -192,8 +192,8 @@
 
   /// Adds `type` as a constructed type if it hasn't been added yet.
   /// @param name the type's unique name
-  /// @param type the type to add
-  void AddConstructedType(Symbol name, ast::NamedType* type);
+  /// @param decl the type declaration to add
+  void AddConstructedType(Symbol name, ast::TypeDecl* decl);
 
   /// @returns the fail stream object
   FailStream& fail_stream() { return fail_stream_; }
diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc
index dcead04..60eb5ef 100644
--- a/src/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/reader/spirv/parser_impl_module_var_test.cc
@@ -4085,7 +4085,7 @@
   {
     Return{}
   }
-  Function main -> __struct_main_out
+  Function main -> __type_name_main_out
   StageDecoration{vertex}
   (
     VariableConst{
@@ -4124,7 +4124,7 @@
     Return{
       {
         TypeConstructor[not set]{
-          __struct_main_out
+          __type_name_main_out
           Identifier[not set]{x_2}
           Identifier[not set]{x_4}
         }
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 6d625fd..aa52558 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -313,11 +313,11 @@
 }
 
 void ParserImpl::register_constructed(const std::string& name,
-                                      const ast::Type* type) {
-  registered_constructs_[name] = type;
+                                      const ast::TypeDecl* type_decl) {
+  registered_constructs_[name] = type_decl;
 }
 
-const ast::Type* ParserImpl::get_constructed(const std::string& name) {
+const ast::TypeDecl* ParserImpl::get_constructed(const std::string& name) {
   if (registered_constructs_.find(name) == registered_constructs_.end()) {
     return nullptr;
   }
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index afbcce1..dce6662 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -381,13 +381,14 @@
   /// Registers a constructed type into the parser
   /// TODO(crbug.com/tint/724): Remove
   /// @param name the constructed name
-  /// @param type the constructed type
-  void register_constructed(const std::string& name, const ast::Type* type);
+  /// @param type_decl the constructed type declaration
+  void register_constructed(const std::string& name,
+                            const ast::TypeDecl* type_decl);
   /// Retrieves a constructed type
   /// TODO(crbug.com/tint/724): Remove
   /// @param name The name to lookup
   /// @returns the constructed type for `name` or `nullptr` if not found
-  const ast::Type* get_constructed(const std::string& name);
+  const ast::TypeDecl* get_constructed(const std::string& name);
 
   /// Parses the `translation_unit` grammar element
   void translation_unit();
@@ -881,7 +882,7 @@
   uint32_t sync_depth_ = 0;
   std::vector<Token::Type> sync_tokens_;
   int silence_errors_ = 0;
-  std::unordered_map<std::string, const ast::Type*> registered_constructs_;
+  std::unordered_map<std::string, const ast::TypeDecl*> registered_constructs_;
   ProgramBuilder builder_;
   size_t max_errors_ = 25;
 };
diff --git a/src/reader/wgsl/parser_impl_test.cc b/src/reader/wgsl/parser_impl_test.cc
index 3c415f6..632d013 100644
--- a/src/reader/wgsl/parser_impl_test.cc
+++ b/src/reader/wgsl/parser_impl_test.cc
@@ -53,11 +53,11 @@
 
 TEST_F(ParserImplTest, GetRegisteredType) {
   auto p = parser("");
-  p->register_constructed("my_alias", ty.i32());
+  auto* alias = create<ast::Alias>(Sym("my_alias"), ty.i32());
+  p->register_constructed("my_alias", alias);
 
-  auto* alias = p->get_constructed("my_alias");
-  ASSERT_NE(alias, nullptr);
-  EXPECT_TRUE(alias->Is<ast::I32>());
+  auto* got = p->get_constructed("my_alias");
+  EXPECT_EQ(got, alias);
 }
 
 TEST_F(ParserImplTest, GetUnregisteredType) {
diff --git a/src/resolver/assignment_validation_test.cc b/src/resolver/assignment_validation_test.cc
index 7c124b4..5879712 100644
--- a/src/resolver/assignment_validation_test.cc
+++ b/src/resolver/assignment_validation_test.cc
@@ -112,7 +112,7 @@
   // var a : myint = 2;
   // a = 2
   auto* myint = Alias("myint", ty.i32());
-  auto* var = Var("a", myint, ast::StorageClass::kNone, Expr(2));
+  auto* var = Var("a", ty.Of(myint), ast::StorageClass::kNone, Expr(2));
   WrapInFunction(var, Assign(Source{{12, 34}}, "a", 2));
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/resolver/control_block_validation_test.cc b/src/resolver/control_block_validation_test.cc
index a7e7847..9a2c716 100644
--- a/src/resolver/control_block_validation_test.cc
+++ b/src/resolver/control_block_validation_test.cc
@@ -295,7 +295,7 @@
   // }
 
   auto* my_int = Alias("MyInt", ty.u32());
-  auto* var = Var("a", my_int, ast::StorageClass::kNone, Expr(2u));
+  auto* var = Var("a", ty.Of(my_int), ast::StorageClass::kNone, Expr(2u));
 
   ast::CaseSelectorList default_csl;
   auto* block_default = Block();
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index 614fec9..a5ca656 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -509,7 +509,7 @@
 TEST_F(StructBlockTest, StructUsedAsArrayElement) {
   auto* s = Structure("S", {Member("x", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* a = ty.array(s, 4);
+  auto* a = ty.array(ty.Of(s), 4);
   Global("G", a, ast::StorageClass::kPrivate);
 
   EXPECT_FALSE(r()->Resolve());
@@ -528,7 +528,7 @@
 TEST_F(ResourceDecorationTest, UniformBufferMissingBinding) {
   auto* s = Structure("S", {Member("x", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  Global(Source{{12, 34}}, "G", s, ast::StorageClass::kUniform);
+  Global(Source{{12, 34}}, "G", ty.Of(s), ast::StorageClass::kUniform);
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
@@ -539,7 +539,7 @@
 TEST_F(ResourceDecorationTest, StorageBufferMissingBinding) {
   auto* s = Structure("S", {Member("x", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  Global(Source{{12, 34}}, "G", s, ast::StorageClass::kStorage,
+  Global(Source{{12, 34}}, "G", ty.Of(s), ast::StorageClass::kStorage,
          ast::Access::kRead);
 
   EXPECT_FALSE(r()->Resolve());
diff --git a/src/resolver/entry_point_validation_test.cc b/src/resolver/entry_point_validation_test.cc
index 80d910b..8f90a22 100644
--- a/src/resolver/entry_point_validation_test.cc
+++ b/src/resolver/entry_point_validation_test.cc
@@ -108,8 +108,9 @@
   //   return Output();
   // }
   auto* output = Structure("Output", {});
-  Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
-       {Stage(ast::PipelineStage::kVertex)}, {Location(Source{{13, 43}}, 0)});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))}, {Stage(ast::PipelineStage::kVertex)},
+       {Location(Source{{13, 43}}, 0)});
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(
@@ -130,7 +131,8 @@
   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))},
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
        {Stage(ast::PipelineStage::kFragment)});
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -150,7 +152,8 @@
       {Member("a", ty.f32(),
               {Location(Source{{13, 43}}, 0),
                Builtin(Source{{14, 52}}, ast::Builtin::kFragDepth)})});
-  Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
        {Stage(ast::PipelineStage::kFragment)});
 
   EXPECT_FALSE(r()->Resolve());
@@ -172,7 +175,8 @@
   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))},
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
        {Stage(ast::PipelineStage::kFragment)});
 
   EXPECT_FALSE(r()->Resolve());
@@ -195,8 +199,9 @@
   auto* inner = Structure(
       "Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
   auto* output = Structure(
-      "Output", {Member(Source{{14, 52}}, "a", inner, {Location(0)})});
-  Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
+      "Output", {Member(Source{{14, 52}}, "a", ty.Of(inner), {Location(0)})});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
        {Stage(ast::PipelineStage::kFragment)});
 
   EXPECT_FALSE(r()->Resolve());
@@ -219,7 +224,8 @@
       "Output",
       {Member(Source{{13, 43}}, "a", ty.array<float>(), {Location(0)})},
       {create<ast::StructBlockDecoration>()});
-  Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
        {Stage(ast::PipelineStage::kFragment)});
 
   EXPECT_FALSE(r()->Resolve());
@@ -241,7 +247,8 @@
   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))},
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
        {Stage(ast::PipelineStage::kFragment)});
 
   EXPECT_FALSE(r()->Resolve());
@@ -262,7 +269,8 @@
   // }
   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))},
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
        {Stage(ast::PipelineStage::kFragment)});
 
   EXPECT_FALSE(r()->Resolve());
@@ -325,7 +333,7 @@
   // [[stage(fragment)]]
   // fn main([[location(0)]] param : Input) {}
   auto* input = Structure("Input", {});
-  auto* param = Param("param", input, {Location(Source{{13, 43}}, 0)});
+  auto* param = Param("param", ty.Of(input), {Location(Source{{13, 43}}, 0)});
   Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
@@ -346,7 +354,7 @@
   auto* input = Structure(
       "Input", {Member("a", ty.f32(), {Location(0)}),
                 Member("b", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
-  auto* param = Param("param", input);
+  auto* param = Param("param", ty.Of(input));
   Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
@@ -365,7 +373,7 @@
       {Member("a", ty.f32(),
               {Location(Source{{13, 43}}, 0),
                Builtin(Source{{14, 52}}, ast::Builtin::kSampleIndex)})});
-  auto* param = Param("param", input);
+  auto* param = Param("param", ty.Of(input));
   Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
@@ -386,7 +394,7 @@
   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);
+  auto* param = Param("param", ty.Of(input));
   Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
@@ -406,9 +414,9 @@
   // fn main(param : Input) {}
   auto* inner = Structure(
       "Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
-  auto* input =
-      Structure("Input", {Member(Source{{14, 52}}, "a", inner, {Location(0)})});
-  auto* param = Param("param", input);
+  auto* input = Structure(
+      "Input", {Member(Source{{14, 52}}, "a", ty.Of(inner), {Location(0)})});
+  auto* param = Param("param", ty.Of(input));
   Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
@@ -430,7 +438,7 @@
       "Input",
       {Member(Source{{13, 43}}, "a", ty.array<float>(), {Location(0)})},
       {create<ast::StructBlockDecoration>()});
-  auto* param = Param("param", input);
+  auto* param = Param("param", ty.Of(input));
   Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
@@ -472,8 +480,8 @@
       "InputA", {Member("a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
   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);
+  auto* param_a = Param("param_a", ty.Of(input_a));
+  auto* param_b = Param("param_b", ty.Of(input_b));
   Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
@@ -510,8 +518,8 @@
   // 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* param_a = Param("param_a", input_a);
-  auto* param_b = Param("param_b", input_b);
+  auto* param_a = Param("param_a", ty.Of(input_a));
+  auto* param_b = Param("param_b", ty.Of(input_b));
   Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
@@ -599,7 +607,7 @@
   auto params = GetParam();
   auto* input = Structure(
       "Input", {Member("a", params.create_ast_type(*this), {Location(0)})});
-  auto* a = Param("a", input, {});
+  auto* a = Param("a", ty.Of(input), {});
   Func(Source{{12, 34}}, "main", {a}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
@@ -638,7 +646,8 @@
   auto params = GetParam();
   auto* output = Structure(
       "Output", {Member("a", params.create_ast_type(*this), {Location(0)})});
-  Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
        {Stage(ast::PipelineStage::kFragment)});
 
   if (params.is_valid) {
diff --git a/src/resolver/function_validation_test.cc b/src/resolver/function_validation_test.cc
index 94e6791..cc72217 100644
--- a/src/resolver/function_validation_test.cc
+++ b/src/resolver/function_validation_test.cc
@@ -231,7 +231,7 @@
   // type myf32 = f32;
   // fn func -> myf32 { return 2.0; }
   auto* myf32 = Alias("myf32", ty.f32());
-  Func("func", ast::VariableList{}, myf32,
+  Func("func", ast::VariableList{}, ty.Of(myf32),
        ast::StatementList{
            Return(Source{Source::Location{12, 34}}, Expr(2.f)),
        },
@@ -245,7 +245,7 @@
   // type myf32 = f32;
   // fn func -> myf32 { return 2; }
   auto* myf32 = Alias("myf32", ty.f32());
-  Func("func", ast::VariableList{}, myf32,
+  Func("func", ast::VariableList{}, ty.Of(myf32),
        ast::StatementList{
            Return(Source{Source::Location{12, 34}}, Expr(2u)),
        },
diff --git a/src/resolver/host_shareable_validation_test.cc b/src/resolver/host_shareable_validation_test.cc
index e783f1d..680a625 100644
--- a/src/resolver/host_shareable_validation_test.cc
+++ b/src/resolver/host_shareable_validation_test.cc
@@ -29,7 +29,7 @@
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())},
                       {create<ast::StructBlockDecoration>()});
 
-  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage,
+  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
          ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
@@ -49,7 +49,7 @@
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3<bool>())},
                       {create<ast::StructBlockDecoration>()});
 
-  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage,
+  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
          ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
@@ -67,10 +67,10 @@
 
 TEST_F(ResolverHostShareableValidationTest, Aliases) {
   auto* a1 = Alias("a1", ty.bool_());
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", a1)},
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.Of(a1))},
                       {create<ast::StructBlockDecoration>()});
-  auto* a2 = Alias("a2", s);
-  Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage,
+  auto* a2 = Alias("a2", ty.Of(s));
+  Global(Source{{56, 78}}, "g", ty.Of(a2), ast::StorageClass::kStorage,
          ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
@@ -88,13 +88,13 @@
 
 TEST_F(ResolverHostShareableValidationTest, NestedStructures) {
   auto* i1 = Structure("I1", {Member(Source{{1, 2}}, "x", ty.bool_())});
-  auto* i2 = Structure("I2", {Member(Source{{3, 4}}, "y", i1)});
-  auto* i3 = Structure("I3", {Member(Source{{5, 6}}, "z", i2)});
+  auto* i2 = Structure("I2", {Member(Source{{3, 4}}, "y", ty.Of(i1))});
+  auto* i3 = Structure("I3", {Member(Source{{5, 6}}, "z", ty.Of(i2))});
 
-  auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)},
+  auto* s = Structure("S", {Member(Source{{7, 8}}, "m", ty.Of(i3))},
                       {create<ast::StructBlockDecoration>()});
 
-  Global(Source{{9, 10}}, "g", s, ast::StorageClass::kStorage,
+  Global(Source{{9, 10}}, "g", ty.Of(s), ast::StorageClass::kStorage,
          ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
@@ -120,22 +120,22 @@
                           Member(Source{{2, 1}}, "y1", ty.vec3<f32>()),
                           Member(Source{{3, 1}}, "z1", ty.array<i32, 4>()),
                       });
-  auto* a1 = Alias("a1", i1);
+  auto* a1 = Alias("a1", ty.Of(i1));
   auto* i2 = Structure("I2", {
                                  Member(Source{{4, 1}}, "x2", ty.mat2x2<f32>()),
-                                 Member(Source{{5, 1}}, "y2", i1),
+                                 Member(Source{{5, 1}}, "y2", ty.Of(i1)),
                              });
-  auto* a2 = Alias("a2", i2);
+  auto* a2 = Alias("a2", ty.Of(i2));
   auto* i3 = Structure("I3", {
-                                 Member(Source{{4, 1}}, "x3", a1),
-                                 Member(Source{{5, 1}}, "y3", i2),
-                                 Member(Source{{6, 1}}, "z3", a2),
+                                 Member(Source{{4, 1}}, "x3", ty.Of(a1)),
+                                 Member(Source{{5, 1}}, "y3", ty.Of(i2)),
+                                 Member(Source{{6, 1}}, "z3", ty.Of(a2)),
                              });
 
-  auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)},
+  auto* s = Structure("S", {Member(Source{{7, 8}}, "m", ty.Of(i3))},
                       {create<ast::StructBlockDecoration>()});
 
-  Global(Source{{9, 10}}, "g", s, ast::StorageClass::kStorage,
+  Global(Source{{9, 10}}, "g", ty.Of(s), ast::StorageClass::kStorage,
          ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
diff --git a/src/resolver/inferred_type_test.cc b/src/resolver/inferred_type_test.cc
index 538850d..bca018e 100644
--- a/src/resolver/inferred_type_test.cc
+++ b/src/resolver/inferred_type_test.cc
@@ -158,15 +158,15 @@
 
 TEST_F(ResolverInferredTypeTest, InferStruct_Pass) {
   auto* member = Member("x", ty.i32());
-  auto* type = Structure("S", {member}, {create<ast::StructBlockDecoration>()});
+  auto* str = Structure("S", {member}, {create<ast::StructBlockDecoration>()});
 
   auto* expected_type =
-      create<sem::Struct>(type,
+      create<sem::Struct>(str,
                           sem::StructMemberList{create<sem::StructMember>(
                               member, create<sem::I32>(), 0, 0, 0, 4)},
                           0, 4, 4);
 
-  auto* ctor_expr = Construct(type);
+  auto* ctor_expr = Construct(ty.Of(str));
 
   auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
   WrapInFunction(var);
diff --git a/src/resolver/intrinsic_test.cc b/src/resolver/intrinsic_test.cc
index 35d1bf0..740824e 100644
--- a/src/resolver/intrinsic_test.cc
+++ b/src/resolver/intrinsic_test.cc
@@ -768,7 +768,7 @@
   auto* ary = ty.array<i32>();
   auto* str = Structure("S", {Member("x", ary)},
                         {create<ast::StructBlockDecoration>()});
-  Global("a", str, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
diff --git a/src/resolver/is_storeable_test.cc b/src/resolver/is_storeable_test.cc
index 209460c..4d3be5c 100644
--- a/src/resolver/is_storeable_test.cc
+++ b/src/resolver/is_storeable_test.cc
@@ -105,7 +105,7 @@
                                          });
   Structure("S", {
                      Member("a", ty.i32()),
-                     Member("b", storable),
+                     Member("b", ty.Of(storable)),
                  });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -120,7 +120,7 @@
                 });
   Structure("S", {
                      Member("a", ty.i32()),
-                     Member("b", non_storable),
+                     Member("b", ty.Of(non_storable)),
                  });
 
   EXPECT_FALSE(r()->Resolve());
diff --git a/src/resolver/ptr_ref_test.cc b/src/resolver/ptr_ref_test.cc
index f70b19b..f842549 100644
--- a/src/resolver/ptr_ref_test.cc
+++ b/src/resolver/ptr_ref_test.cc
@@ -66,12 +66,12 @@
   auto* function = Var("f", ty.i32());
   auto* private_ = Global("p", ty.i32(), ast::StorageClass::kPrivate);
   auto* workgroup = Global("w", ty.i32(), ast::StorageClass::kWorkgroup);
-  auto* uniform = Global("ub", buf, ast::StorageClass::kUniform,
+  auto* uniform = Global("ub", ty.Of(buf), ast::StorageClass::kUniform,
                          ast::DecorationList{
                              create<ast::BindingDecoration>(0),
                              create<ast::GroupDecoration>(0),
                          });
-  auto* storage = Global("sb", buf, ast::StorageClass::kStorage,
+  auto* storage = Global("sb", ty.Of(buf), ast::StorageClass::kStorage,
                          ast::DecorationList{
                              create<ast::BindingDecoration>(1),
                              create<ast::GroupDecoration>(0),
@@ -87,10 +87,10 @@
       Const("w_ptr", ty.pointer(ty.i32(), ast::StorageClass::kWorkgroup),
             AddressOf(workgroup));
   auto* uniform_ptr =
-      Const("ub_ptr", ty.pointer(buf, ast::StorageClass::kUniform),
+      Const("ub_ptr", ty.pointer(ty.Of(buf), ast::StorageClass::kUniform),
             AddressOf(uniform));
   auto* storage_ptr =
-      Const("sb_ptr", ty.pointer(buf, ast::StorageClass::kStorage),
+      Const("sb_ptr", ty.pointer(ty.Of(buf), ast::StorageClass::kStorage),
             AddressOf(storage));
 
   WrapInFunction(function, function_ptr, private_ptr, workgroup_ptr,
diff --git a/src/resolver/ptr_ref_validation_test.cc b/src/resolver/ptr_ref_validation_test.cc
index c734c6a..c693d19 100644
--- a/src/resolver/ptr_ref_validation_test.cc
+++ b/src/resolver/ptr_ref_validation_test.cc
@@ -90,14 +90,14 @@
   //   let p : pointer<storage, i32> = &s.inner.arr[2];
   // }
   auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
-  auto* buf = Structure("S", {Member("inner", inner)},
+  auto* buf = Structure("S", {Member("inner", ty.Of(inner))},
                         {create<ast::StructBlockDecoration>()});
-  auto* storage =
-      Global("s", buf, ast::StorageClass::kStorage, ast::Access::kReadWrite,
-             ast::DecorationList{
-                 create<ast::BindingDecoration>(0),
-                 create<ast::GroupDecoration>(0),
-             });
+  auto* storage = Global("s", ty.Of(buf), ast::StorageClass::kStorage,
+                         ast::Access::kReadWrite,
+                         ast::DecorationList{
+                             create<ast::BindingDecoration>(0),
+                             create<ast::GroupDecoration>(0),
+                         });
 
   auto* expr =
       IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4);
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index fc25d8f..df95064 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -213,9 +213,9 @@
   // Process everything else in the order they appear in the module. This is
   // necessary for validation of use-before-declaration.
   for (auto* decl : builder_->AST().GlobalDeclarations()) {
-    if (auto* ty = decl->As<ast::NamedType>()) {
-      Mark(ty);
-      if (!NamedType(ty)) {
+    if (auto* td = decl->As<ast::TypeDecl>()) {
+      Mark(td);
+      if (!TypeDecl(td)) {
         return false;
       }
     } else if (auto* func = decl->As<ast::Function>()) {
@@ -2418,14 +2418,14 @@
   return true;
 }
 
-sem::Type* Resolver::NamedType(const ast::NamedType* named_type) {
+sem::Type* Resolver::TypeDecl(const ast::TypeDecl* named_type) {
   sem::Type* result = nullptr;
   if (auto* alias = named_type->As<ast::Alias>()) {
     result = Type(alias->type());
   } else if (auto* str = named_type->As<ast::Struct>()) {
     result = Structure(str);
   } else {
-    TINT_UNREACHABLE(diagnostics_) << "Unhandled NamedType";
+    TINT_UNREACHABLE(diagnostics_) << "Unhandled TypeDecl";
   }
 
   if (!result) {
@@ -2433,9 +2433,9 @@
   }
 
   named_type_info_.emplace(named_type->name(),
-                           NamedTypeInfo{named_type, result});
+                           TypeDeclInfo{named_type, result});
 
-  if (!ValidateNamedType(named_type)) {
+  if (!ValidateTypeDecl(named_type)) {
     return nullptr;
   }
 
@@ -2443,10 +2443,10 @@
   return result;
 }
 
-bool Resolver::ValidateNamedType(const ast::NamedType* named_type) const {
+bool Resolver::ValidateTypeDecl(const ast::TypeDecl* named_type) const {
   auto iter = named_type_info_.find(named_type->name());
   if (iter == named_type_info_.end()) {
-    TINT_ICE(diagnostics_) << "ValidateNamedType called() before NamedType()";
+    TINT_ICE(diagnostics_) << "ValidateTypeDecl called() before TypeDecl()";
   }
   if (iter->second.ast != named_type) {
     diagnostics_.add_error("type with the name '" +
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index da9a0a4..de33472 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -184,9 +184,9 @@
     size_t first_continue = kNoContinue;
   };
 
-  // Structure holding information for a NamedType
-  struct NamedTypeInfo {
-    ast::NamedType const* const ast;
+  // Structure holding information for a TypeDecl
+  struct TypeDeclInfo {
+    ast::TypeDecl const* const ast;
     sem::Type* const sem;
   };
 
@@ -270,7 +270,7 @@
                                    const std::string& rhs_type_name);
   bool ValidateVectorConstructor(const ast::TypeConstructorExpression* ctor,
                                  const sem::Vector* vec_type);
-  bool ValidateNamedType(const ast::NamedType* named_type) const;
+  bool ValidateTypeDecl(const ast::TypeDecl* named_type) const;
 
   /// @returns the sem::Type for the ast::Type `ty`, building it if it
   /// hasn't been constructed already. If an error is raised, nullptr is
@@ -280,7 +280,7 @@
 
   /// @param named_type the named type to resolve
   /// @returns the resolved semantic type
-  sem::Type* NamedType(const ast::NamedType* named_type);
+  sem::Type* TypeDecl(const ast::TypeDecl* named_type);
 
   /// Builds and returns the semantic information for the array `arr`.
   /// This method does not mark the ast::Array node, nor attach the generated
@@ -397,7 +397,7 @@
   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<Symbol, NamedTypeInfo> named_type_info_;
+  std::unordered_map<Symbol, TypeDeclInfo> named_type_info_;
 
   std::unordered_set<const ast::Node*> marked_;
   std::unordered_map<uint32_t, const VariableInfo*> constant_ids_;
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index f7ed724..d225fbb 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -294,7 +294,7 @@
 
 TEST_F(ResolverTest, Stmt_VariableDecl_Alias) {
   auto* my_int = Alias("MyInt", ty.i32());
-  auto* var = Var("my_var", my_int, ast::StorageClass::kNone, Expr(2));
+  auto* var = Var("my_var", ty.Of(my_int), ast::StorageClass::kNone, Expr(2));
   auto* init = var->constructor();
 
   auto* decl = Decl(var);
@@ -449,7 +449,7 @@
 TEST_F(ResolverTest, Expr_ArrayAccessor_Alias_Array) {
   auto* aary = Alias("myarrty", ty.array<f32, 3>());
 
-  Global("my_var", aary, ast::StorageClass::kPrivate);
+  Global("my_var", ty.Of(aary), ast::StorageClass::kPrivate);
 
   auto* acc = IndexAccessor("my_var", 2);
   WrapInFunction(acc);
@@ -813,12 +813,12 @@
 
   auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
   auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput);
-  auto* sb_var =
-      Global("sb_var", s, ast::StorageClass::kStorage, ast::Access::kRead,
-             ast::DecorationList{
-                 create<ast::BindingDecoration>(0),
-                 create<ast::GroupDecoration>(0),
-             });
+  auto* sb_var = Global("sb_var", ty.Of(s), ast::StorageClass::kStorage,
+                        ast::Access::kRead,
+                        ast::DecorationList{
+                            create<ast::BindingDecoration>(0),
+                            create<ast::GroupDecoration>(0),
+                        });
   auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
   auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -852,12 +852,12 @@
 
   auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
   auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput);
-  auto* sb_var =
-      Global("sb_var", s, ast::StorageClass::kStorage, ast::Access::kRead,
-             ast::DecorationList{
-                 create<ast::BindingDecoration>(0),
-                 create<ast::GroupDecoration>(0),
-             });
+  auto* sb_var = Global("sb_var", ty.Of(s), ast::StorageClass::kStorage,
+                        ast::Access::kRead,
+                        ast::DecorationList{
+                            create<ast::BindingDecoration>(0),
+                            create<ast::GroupDecoration>(0),
+                        });
   auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
   auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -1097,7 +1097,7 @@
 TEST_F(ResolverTest, Expr_MemberAccessor_Struct) {
   auto* st = Structure("S", {Member("first_member", ty.i32()),
                              Member("second_member", ty.f32())});
-  Global("my_struct", st, ast::StorageClass::kInput);
+  Global("my_struct", ty.Of(st), ast::StorageClass::kInput);
 
   auto* mem = MemberAccessor("my_struct", "second_member");
   WrapInFunction(mem);
@@ -1120,8 +1120,8 @@
 TEST_F(ResolverTest, Expr_MemberAccessor_Struct_Alias) {
   auto* st = Structure("S", {Member("first_member", ty.i32()),
                              Member("second_member", ty.f32())});
-  auto* alias = Alias("alias", st);
-  Global("my_struct", alias, ast::StorageClass::kInput);
+  auto* alias = Alias("alias", ty.Of(st));
+  Global("my_struct", ty.Of(alias), ast::StorageClass::kInput);
 
   auto* mem = MemberAccessor("my_struct", "second_member");
   WrapInFunction(mem);
@@ -1200,8 +1200,8 @@
   //
 
   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* stA = Structure("A", {Member("mem", ty.vec(ty.Of(stB), 3))});
+  Global("c", ty.Of(stA), ast::StorageClass::kInput);
 
   auto* mem = MemberAccessor(
       MemberAccessor(IndexAccessor(MemberAccessor("c", "mem"), 0), "foo"),
@@ -1220,7 +1220,7 @@
 TEST_F(ResolverTest, Expr_MemberAccessor_InBinaryOp) {
   auto* st = Structure("S", {Member("first_member", ty.f32()),
                              Member("second_member", ty.f32())});
-  Global("my_struct", st, ast::StorageClass::kInput);
+  Global("my_struct", ty.Of(st), ast::StorageClass::kInput);
 
   auto* expr = Add(MemberAccessor("my_struct", "first_member"),
                    MemberAccessor("my_struct", "second_member"));
@@ -1819,11 +1819,12 @@
   // var<storage> g : S;
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* var = Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage,
-                     ast::DecorationList{
-                         create<ast::BindingDecoration>(0),
-                         create<ast::GroupDecoration>(0),
-                     });
+  auto* var =
+      Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
+             ast::DecorationList{
+                 create<ast::BindingDecoration>(0),
+                 create<ast::GroupDecoration>(0),
+             });
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
 
diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc
index 033e7f6..d1fdaa8 100644
--- a/src/resolver/storage_class_validation_test.cc
+++ b/src/resolver/storage_class_validation_test.cc
@@ -68,7 +68,7 @@
 TEST_F(ResolverStorageClassValidationTest, StorageBufferArray) {
   // var<storage, read> g : array<S, 3>;
   auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* a = ty.array(s, 3);
+  auto* a = ty.array(ty.Of(s), 3);
   Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage,
          ast::Access::kRead,
          ast::DecorationList{
@@ -87,7 +87,7 @@
   // type a = bool;
   // var<storage, read> g : a;
   auto* a = Alias("a", ty.bool_());
-  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage,
+  Global(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kStorage,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -104,7 +104,7 @@
   // struct S { x : i32 };
   // var<storage, read> g : S;
   auto* s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())});
-  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage,
+  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
          ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
@@ -124,7 +124,7 @@
   // var<storage, read> g : S;
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage,
+  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
          ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
@@ -140,9 +140,9 @@
   // var<storage, read> g : a1;
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* a1 = Alias("a1", s);
-  auto* a2 = Alias("a2", a1);
-  Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage,
+  auto* a1 = Alias("a1", ty.Of(s));
+  auto* a2 = Alias("a2", ty.Of(a1));
+  Global(Source{{56, 78}}, "g", ty.Of(a2), ast::StorageClass::kStorage,
          ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
@@ -188,7 +188,7 @@
 TEST_F(ResolverStorageClassValidationTest, UniformBufferArray) {
   // var<uniform> g : array<S, 3>;
   auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* a = ty.array(s, 3);
+  auto* a = ty.array(ty.Of(s), 3);
   Global(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
@@ -206,7 +206,7 @@
   // type a = bool;
   // var<uniform> g : a;
   auto* a = Alias("a", ty.bool_());
-  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform,
+  Global(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kUniform,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -223,7 +223,7 @@
   // struct S { x : i32 };
   // var<uniform> g : S;
   auto* s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())});
-  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kUniform,
+  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kUniform,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -242,7 +242,7 @@
   // var<uniform> g :  S;
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kUniform,
+  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kUniform,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -257,8 +257,8 @@
   // var<uniform> g : a1;
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* a1 = Alias("a1", s);
-  Global(Source{{56, 78}}, "g", a1, ast::StorageClass::kUniform,
+  auto* a1 = Alias("a1", ty.Of(s));
+  Global(Source{{56, 78}}, "g", ty.Of(a1), ast::StorageClass::kUniform,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
diff --git a/src/resolver/struct_layout_test.cc b/src/resolver/struct_layout_test.cc
index be0e808..f516412 100644
--- a/src/resolver/struct_layout_test.cc
+++ b/src/resolver/struct_layout_test.cc
@@ -56,8 +56,8 @@
   auto* alias_b = Alias("b", ty.f32());
 
   auto* s = Structure("S", {
-                               Member("a", alias_a),
-                               Member("b", alias_b),
+                               Member("a", ty.Of(alias_a)),
+                               Member("b", ty.Of(alias_b)),
                            });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -195,8 +195,8 @@
                                        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
+                                   });       // size: 48
+  auto* outer = ty.array(ty.Of(inner), 12);  // size: 12 * 48
   auto* s = Structure("S", {
                                Member("c", outer),
                            });
@@ -296,7 +296,7 @@
                                    });
   auto* s = Structure("S", {
                                Member("a", ty.i32()),
-                               Member("b", inner),
+                               Member("b", ty.Of(inner)),
                                Member("c", ty.i32()),
                            });
 
@@ -328,7 +328,7 @@
   auto* s = Structure("S", {
                                Member("a", ty.f32(), {MemberSize(4)}),
                                Member("b", ty.u32(), {MemberSize(8)}),
-                               Member("c", inner),
+                               Member("c", ty.Of(inner)),
                                Member("d", ty.i32(), {MemberSize(32)}),
                            });
 
@@ -363,7 +363,7 @@
   auto* s = Structure("S", {
                                Member("a", ty.f32(), {MemberAlign(4)}),
                                Member("b", ty.u32(), {MemberAlign(8)}),
-                               Member("c", inner),
+                               Member("c", ty.Of(inner)),
                                Member("d", ty.i32(), {MemberAlign(32)}),
                            });
 
diff --git a/src/resolver/struct_pipeline_stage_use_test.cc b/src/resolver/struct_pipeline_stage_use_test.cc
index 4c481a5..ae52672 100644
--- a/src/resolver/struct_pipeline_stage_use_test.cc
+++ b/src/resolver/struct_pipeline_stage_use_test.cc
@@ -40,7 +40,7 @@
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsNonEntryPointParam) {
   auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
 
-  Func("foo", {Param("param", s)}, ty.void_(), {}, {});
+  Func("foo", {Param("param", ty.Of(s))}, ty.void_(), {}, {});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -52,7 +52,7 @@
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsNonEntryPointReturnType) {
   auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
 
-  Func("foo", {}, s, {Return(Construct(s, Expr(0.f)))}, {});
+  Func("foo", {}, ty.Of(s), {Return(Construct(ty.Of(s), Expr(0.f)))}, {});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -64,7 +64,7 @@
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderParam) {
   auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
 
-  Func("main", {Param("param", s)}, ty.vec4<f32>(),
+  Func("main", {Param("param", ty.Of(s))}, ty.vec4<f32>(),
        {Return(Construct(ty.vec4<f32>()))},
        {Stage(ast::PipelineStage::kVertex)},
        {Builtin(ast::Builtin::kPosition)});
@@ -81,7 +81,7 @@
   auto* s = Structure(
       "S", {Member("a", ty.f32(), {Builtin(ast::Builtin::kPosition)})});
 
-  Func("main", {}, s, {Return(Construct(s, Expr(0.f)))},
+  Func("main", {}, ty.Of(s), {Return(Construct(ty.Of(s), Expr(0.f)))},
        {Stage(ast::PipelineStage::kVertex)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -95,7 +95,7 @@
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsFragmentShaderParam) {
   auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
 
-  Func("main", {Param("param", s)}, ty.void_(), {},
+  Func("main", {Param("param", ty.Of(s))}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -109,7 +109,7 @@
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsFragmentShaderReturnType) {
   auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
 
-  Func("main", {}, s, {Return(Construct(s, Expr(0.f)))},
+  Func("main", {}, ty.Of(s), {Return(Construct(ty.Of(s), Expr(0.f)))},
        {Stage(ast::PipelineStage::kFragment)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -125,7 +125,7 @@
       "S",
       {Member("a", ty.u32(), {Builtin(ast::Builtin::kLocalInvocationIndex)})});
 
-  Func("main", {Param("param", s)}, ty.void_(), {},
+  Func("main", {Param("param", ty.Of(s))}, ty.void_(), {},
        {Stage(ast::PipelineStage::kCompute)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -140,10 +140,11 @@
   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)))},
+  Func("vert_main", {Param("param", ty.Of(s))}, ty.Of(s),
+       {Return(Construct(ty.Of(s), Expr(0.f)))},
        {Stage(ast::PipelineStage::kVertex)});
 
-  Func("frag_main", {Param("param", s)}, ty.void_(), {},
+  Func("frag_main", {Param("param", ty.Of(s))}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -158,9 +159,9 @@
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderParamViaAlias) {
   auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
-  auto* s_alias = Alias("S_alias", s);
+  auto* s_alias = Alias("S_alias", ty.Of(s));
 
-  Func("main", {Param("param", s_alias)}, ty.void_(), {},
+  Func("main", {Param("param", ty.Of(s_alias))}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -173,9 +174,10 @@
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderReturnTypeViaAlias) {
   auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
-  auto* s_alias = Alias("S_alias", s);
+  auto* s_alias = Alias("S_alias", ty.Of(s));
 
-  Func("main", {}, s_alias, {Return(Construct(s_alias, Expr(0.f)))},
+  Func("main", {}, ty.Of(s_alias),
+       {Return(Construct(ty.Of(s_alias), Expr(0.f)))},
        {Stage(ast::PipelineStage::kFragment)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/resolver/struct_storage_class_use_test.cc b/src/resolver/struct_storage_class_use_test.cc
index 8af99be..598903d 100644
--- a/src/resolver/struct_storage_class_use_test.cc
+++ b/src/resolver/struct_storage_class_use_test.cc
@@ -40,7 +40,7 @@
 TEST_F(ResolverStorageClassUseTest, StructReachableFromParameter) {
   auto* s = Structure("S", {Member("a", ty.f32())});
 
-  Func("f", {Param("param", s)}, ty.void_(), {}, {});
+  Func("f", {Param("param", ty.Of(s))}, ty.void_(), {}, {});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -53,7 +53,7 @@
 TEST_F(ResolverStorageClassUseTest, StructReachableFromReturnType) {
   auto* s = Structure("S", {Member("a", ty.f32())});
 
-  Func("f", {}, s, {Return(Construct(s))}, {});
+  Func("f", {}, ty.Of(s), {Return(Construct(ty.Of(s)))}, {});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -66,7 +66,7 @@
 TEST_F(ResolverStorageClassUseTest, StructReachableFromGlobal) {
   auto* s = Structure("S", {Member("a", ty.f32())});
 
-  Global("g", s, ast::StorageClass::kPrivate);
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -78,8 +78,8 @@
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalAlias) {
   auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* a = Alias("A", s);
-  Global("g", a, ast::StorageClass::kPrivate);
+  auto* a = Alias("A", ty.Of(s));
+  Global("g", ty.Of(a), ast::StorageClass::kPrivate);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -91,8 +91,8 @@
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalStruct) {
   auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* o = Structure("O", {Member("a", s)});
-  Global("g", o, ast::StorageClass::kPrivate);
+  auto* o = Structure("O", {Member("a", ty.Of(s))});
+  Global("g", ty.Of(o), ast::StorageClass::kPrivate);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -104,7 +104,7 @@
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalArray) {
   auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* a = ty.array(s, 3);
+  auto* a = ty.array(ty.Of(s), 3);
   Global("g", a, ast::StorageClass::kPrivate);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -118,7 +118,7 @@
 TEST_F(ResolverStorageClassUseTest, StructReachableFromLocal) {
   auto* s = Structure("S", {Member("a", ty.f32())});
 
-  WrapInFunction(Var("g", s));
+  WrapInFunction(Var("g", ty.Of(s)));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -130,8 +130,8 @@
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalAlias) {
   auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* a = Alias("A", s);
-  WrapInFunction(Var("g", a));
+  auto* a = Alias("A", ty.Of(s));
+  WrapInFunction(Var("g", ty.Of(a)));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -143,8 +143,8 @@
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalStruct) {
   auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* o = Structure("O", {Member("a", s)});
-  WrapInFunction(Var("g", o));
+  auto* o = Structure("O", {Member("a", ty.Of(s))});
+  WrapInFunction(Var("g", ty.Of(o)));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -156,7 +156,7 @@
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalArray) {
   auto* s = Structure("S", {Member("a", ty.f32())});
-  auto* a = ty.array(s, 3);
+  auto* a = ty.array(ty.Of(s), 3);
   WrapInFunction(Var("g", a));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -170,17 +170,17 @@
 TEST_F(ResolverStorageClassUseTest, StructMultipleStorageClassUses) {
   auto* s = Structure("S", {Member("a", ty.f32())},
                       {create<ast::StructBlockDecoration>()});
-  Global("x", s, ast::StorageClass::kUniform,
+  Global("x", ty.Of(s), ast::StorageClass::kUniform,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
          });
-  Global("y", s, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("y", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(1),
              create<ast::GroupDecoration>(0),
          });
-  WrapInFunction(Var("g", s));
+  WrapInFunction(Var("g", ty.Of(s)));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index 2eb0707..d1b1c35 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -341,7 +341,7 @@
   auto* alias = Alias("RTArr", ty.array<u32>());
   Structure("s",
             {
-                Member(Source{{12, 34}}, "b", alias),
+                Member(Source{{12, 34}}, "b", ty.Of(alias)),
                 Member("a", ty.u32()),
             },
             {create<ast::StructBlockDecoration>()});
@@ -367,7 +367,7 @@
   Structure("s",
             {
                 Member("a", ty.u32()),
-                Member("b", alias),
+                Member("b", ty.Of(alias)),
             },
             {create<ast::StructBlockDecoration>()});
 
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
index e271bdc..a0a5f23 100644
--- a/src/resolver/validation_test.cc
+++ b/src/resolver/validation_test.cc
@@ -1709,7 +1709,7 @@
 
 TEST_F(ResolverValidationTest, Expr_Constructor_Vector_Alias_Argument_Error) {
   auto* alias = Alias("UnsignedInt", ty.u32());
-  Global("uint_var", alias, ast::StorageClass::kInput);
+  Global("uint_var", ty.Of(alias), ast::StorageClass::kInput);
 
   auto* tc = vec2<f32>(Expr(Source{{12, 34}}, "uint_var"));
   WrapInFunction(tc);
@@ -1723,8 +1723,8 @@
 TEST_F(ResolverValidationTest, Expr_Constructor_Vector_Alias_Argument_Success) {
   auto* f32_alias = Alias("Float32", ty.f32());
   auto* vec2_alias = Alias("VectorFloat2", ty.vec2<f32>());
-  Global("my_f32", f32_alias, ast::StorageClass::kInput);
-  Global("my_vec2", vec2_alias, ast::StorageClass::kInput);
+  Global("my_f32", ty.Of(f32_alias), ast::StorageClass::kInput);
+  Global("my_vec2", ty.Of(vec2_alias), ast::StorageClass::kInput);
 
   auto* tc = vec3<f32>("my_vec2", "my_f32");
   WrapInFunction(tc);
@@ -1735,7 +1735,7 @@
   auto* f32_alias = Alias("Float32", ty.f32());
 
   // vec2<Float32>(1.0f, 1u)
-  auto* vec_type = ty.vec(f32_alias, 2);
+  auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
   auto* tc = create<ast::TypeConstructorExpression>(
       Source{{12, 34}}, vec_type,
       ExprList(1.0f, create<ast::ScalarConstructorExpression>(Source{{12, 40}},
@@ -1753,7 +1753,7 @@
   auto* f32_alias = Alias("Float32", ty.f32());
 
   // vec2<Float32>(1.0f, 1.0f)
-  auto* vec_type = ty.vec(f32_alias, 2);
+  auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
   auto* tc = create<ast::TypeConstructorExpression>(Source{{12, 34}}, vec_type,
                                                     ExprList(1.0f, 1.0f));
   WrapInFunction(tc);
@@ -1766,7 +1766,7 @@
   auto* f32_alias = Alias("Float32", ty.f32());
 
   // vec3<u32>(vec<Float32>(), 1.0f)
-  auto* vec_type = ty.vec(f32_alias, 2);
+  auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
   auto* tc = vec3<u32>(create<ast::TypeConstructorExpression>(
                            Source{{12, 34}}, vec_type, ExprList()),
                        1.0f);
@@ -1783,7 +1783,7 @@
   auto* f32_alias = Alias("Float32", ty.f32());
 
   // vec3<f32>(vec<Float32>(), 1.0f)
-  auto* vec_type = ty.vec(f32_alias, 2);
+  auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
   auto* tc = vec3<f32>(create<ast::TypeConstructorExpression>(
                            Source{{12, 34}}, vec_type, ExprList()),
                        1.0f);
@@ -2019,7 +2019,7 @@
         Source{{12, i}}, vec_type, ExprList()));
   }
 
-  auto* matrix_type = ty.mat(f32_alias, param.columns, param.rows);
+  auto* matrix_type = ty.mat(ty.Of(f32_alias), param.columns, param.rows);
   auto* tc = create<ast::TypeConstructorExpression>(Source{}, matrix_type,
                                                     std::move(args));
   WrapInFunction(tc);
@@ -2044,7 +2044,7 @@
         Source{{12, i}}, vec_type, ExprList()));
   }
 
-  auto* matrix_type = ty.mat(f32_alias, param.columns, param.rows);
+  auto* matrix_type = ty.mat(ty.Of(f32_alias), param.columns, param.rows);
   auto* tc = create<ast::TypeConstructorExpression>(Source{}, matrix_type,
                                                     std::move(args));
   WrapInFunction(tc);
@@ -2054,10 +2054,9 @@
 
 TEST_F(ResolverValidationTest, Expr_MatrixConstructor_ArgumentTypeAlias_Error) {
   auto* alias = Alias("VectorUnsigned2", ty.vec2<u32>());
-  auto* tc = mat2x2<f32>(
-      create<ast::TypeConstructorExpression>(
-          Source{{12, 34}}, ty.MaybeCreateTypename(alias), ExprList()),
-      vec2<f32>());
+  auto* tc = mat2x2<f32>(create<ast::TypeConstructorExpression>(
+                             Source{{12, 34}}, ty.Of(alias), ExprList()),
+                         vec2<f32>());
   WrapInFunction(tc);
 
   EXPECT_FALSE(r()->Resolve());
@@ -2075,7 +2074,7 @@
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns; i++) {
     args.push_back(create<ast::TypeConstructorExpression>(
-        Source{{12, i}}, ty.MaybeCreateTypename(vec_alias), ExprList()));
+        Source{{12, i}}, ty.Of(vec_alias), ExprList()));
   }
 
   auto* tc = create<ast::TypeConstructorExpression>(Source{}, matrix_type,
@@ -2092,7 +2091,7 @@
 
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns; i++) {
-    auto* vec_type = ty.vec(f32_alias, param.rows);
+    auto* vec_type = ty.vec(ty.Of(f32_alias), param.rows);
     args.push_back(create<ast::TypeConstructorExpression>(
         Source{{12, i}}, vec_type, ExprList()));
   }
@@ -2115,7 +2114,7 @@
 
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns; i++) {
-    auto* vec_type = ty.vec(f32_alias, param.rows);
+    auto* vec_type = ty.vec(ty.Of(f32_alias), param.rows);
     args.push_back(create<ast::TypeConstructorExpression>(
         Source{{12, i}}, vec_type, ExprList()));
   }
diff --git a/src/resolver/var_let_test.cc b/src/resolver/var_let_test.cc
index 84b4ae7..1b45fac 100644
--- a/src/resolver/var_let_test.cc
+++ b/src/resolver/var_let_test.cc
@@ -39,14 +39,14 @@
   // }
 
   auto* S = Structure("S", {Member("i", ty.i32())});
-  auto* A = Alias("A", S);
+  auto* A = Alias("A", ty.Of(S));
 
   auto* i = Var("i", ty.i32(), ast::StorageClass::kNone);
   auto* u = Var("u", ty.u32(), ast::StorageClass::kNone);
   auto* f = Var("f", ty.f32(), ast::StorageClass::kNone);
   auto* b = Var("b", ty.bool_(), ast::StorageClass::kNone);
-  auto* s = Var("s", S, ast::StorageClass::kNone);
-  auto* a = Var("a", A, ast::StorageClass::kNone);
+  auto* s = Var("s", ty.Of(S), ast::StorageClass::kNone);
+  auto* a = Var("a", ty.Of(A), ast::StorageClass::kNone);
 
   Func("F", {}, ty.void_(),
        {
@@ -90,15 +90,15 @@
   // }
 
   auto* S = Structure("S", {Member("i", ty.i32())});
-  auto* A = Alias("A", S);
+  auto* A = Alias("A", ty.Of(S));
 
   auto* v = Var("v", ty.i32(), ast::StorageClass::kNone);
   auto* i = Const("i", ty.i32(), Expr(1));
   auto* u = Const("u", ty.u32(), Expr(1u));
   auto* f = Const("f", ty.f32(), Expr(1.f));
   auto* b = Const("b", ty.bool_(), Expr(true));
-  auto* s = Const("s", S, Construct(S, Expr(1)));
-  auto* a = Const("a", A, Construct(A, Expr(1)));
+  auto* s = Const("s", ty.Of(S), Construct(ty.Of(S), Expr(1)));
+  auto* a = Const("a", ty.Of(A), Construct(ty.Of(A), Expr(1)));
   auto* p =
       Const("p", ty.pointer<i32>(ast::StorageClass::kFunction), AddressOf(v));
 
@@ -135,12 +135,12 @@
   auto* function = Var("f", ty.i32());
   auto* private_ = Global("p", ty.i32(), ast::StorageClass::kPrivate);
   auto* workgroup = Global("w", ty.i32(), ast::StorageClass::kWorkgroup);
-  auto* uniform = Global("ub", buf, ast::StorageClass::kUniform,
+  auto* uniform = Global("ub", ty.Of(buf), ast::StorageClass::kUniform,
                          ast::DecorationList{
                              create<ast::BindingDecoration>(0),
                              create<ast::GroupDecoration>(0),
                          });
-  auto* storage = Global("sb", buf, ast::StorageClass::kStorage,
+  auto* storage = Global("sb", ty.Of(buf), ast::StorageClass::kStorage,
                          ast::DecorationList{
                              create<ast::BindingDecoration>(1),
                              create<ast::GroupDecoration>(0),
@@ -180,12 +180,12 @@
 
   auto* buf = Structure("S", {Member("m", ty.i32())},
                         {create<ast::StructBlockDecoration>()});
-  auto* storage =
-      Global("sb", buf, ast::StorageClass::kStorage, ast::Access::kReadWrite,
-             ast::DecorationList{
-                 create<ast::BindingDecoration>(1),
-                 create<ast::GroupDecoration>(0),
-             });
+  auto* storage = Global("sb", ty.Of(buf), ast::StorageClass::kStorage,
+                         ast::Access::kReadWrite,
+                         ast::DecorationList{
+                             create<ast::BindingDecoration>(1),
+                             create<ast::GroupDecoration>(0),
+                         });
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -207,14 +207,14 @@
   //   let p = &s.inner.arr[2];
   // }
   auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
-  auto* buf = Structure("S", {Member("inner", inner)},
+  auto* buf = Structure("S", {Member("inner", ty.Of(inner))},
                         {create<ast::StructBlockDecoration>()});
-  auto* storage =
-      Global("s", buf, ast::StorageClass::kStorage, ast::Access::kReadWrite,
-             ast::DecorationList{
-                 create<ast::BindingDecoration>(0),
-                 create<ast::GroupDecoration>(0),
-             });
+  auto* storage = Global("s", ty.Of(buf), ast::StorageClass::kStorage,
+                         ast::Access::kReadWrite,
+                         ast::DecorationList{
+                             create<ast::BindingDecoration>(0),
+                             create<ast::GroupDecoration>(0),
+                         });
 
   auto* expr =
       IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4);
diff --git a/src/resolver/var_let_validation_test.cc b/src/resolver/var_let_validation_test.cc
index 0fa67e8..f7fa006 100644
--- a/src/resolver/var_let_validation_test.cc
+++ b/src/resolver/var_let_validation_test.cc
@@ -81,7 +81,7 @@
 
 TEST_F(ResolverVarLetValidationTest, LetConstructorWrongTypeViaAlias) {
   auto* a = Alias("I32", ty.i32());
-  WrapInFunction(Const(Source{{3, 3}}, "v", a, Expr(2u)));
+  WrapInFunction(Const(Source{{3, 3}}, "v", ty.Of(a), Expr(2u)));
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(
@@ -92,7 +92,7 @@
 TEST_F(ResolverVarLetValidationTest, VarConstructorWrongTypeViaAlias) {
   auto* a = Alias("I32", ty.i32());
   WrapInFunction(
-      Var(Source{{3, 3}}, "v", a, ast::StorageClass::kNone, Expr(2u)));
+      Var(Source{{3, 3}}, "v", ty.Of(a), ast::StorageClass::kNone, Expr(2u)));
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(
@@ -228,9 +228,9 @@
   //   let p : pointer<storage, i32, read_write> = &s.inner.arr[2];
   // }
   auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
-  auto* buf = Structure("S", {Member("inner", inner)},
+  auto* buf = Structure("S", {Member("inner", ty.Of(inner))},
                         {create<ast::StructBlockDecoration>()});
-  auto* storage = Global("s", buf, ast::StorageClass::kStorage,
+  auto* storage = Global("s", ty.Of(buf), ast::StorageClass::kStorage,
                          ast::DecorationList{
                              create<ast::BindingDecoration>(0),
                              create<ast::GroupDecoration>(0),
diff --git a/src/sem/struct.cc b/src/sem/struct.cc
index f8eefc1..5365997 100644
--- a/src/sem/struct.cc
+++ b/src/sem/struct.cc
@@ -13,11 +13,13 @@
 // limitations under the License.
 
 #include "src/sem/struct.h"
-#include "src/ast/struct_member.h"
 
 #include <string>
 #include <utility>
 
+#include "src/ast/struct_member.h"
+#include "src/symbol_table.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::sem::Struct);
 TINT_INSTANTIATE_TYPEINFO(tint::sem::StructMember);
 
@@ -51,7 +53,7 @@
 }
 
 std::string Struct::FriendlyName(const SymbolTable& symbols) const {
-  return declaration_->FriendlyName(symbols);
+  return symbols.NameFor(declaration_->name());
 }
 
 StructMember::StructMember(ast::StructMember* declaration,
diff --git a/src/sem/type_mappings.h b/src/sem/type_mappings.h
index 7193547..f4fd7bb 100644
--- a/src/sem/type_mappings.h
+++ b/src/sem/type_mappings.h
@@ -28,6 +28,7 @@
 class Statement;
 class StructMember;
 class Type;
+class TypeDecl;
 class Variable;
 }  // namespace ast
 
@@ -57,6 +58,7 @@
   Statement* operator()(ast::Statement*);
   StructMember* operator()(ast::StructMember*);
   Type* operator()(ast::Type*);
+  Type* operator()(ast::TypeDecl*);
   Variable* operator()(ast::Variable*);
   //! @endcond
 };
diff --git a/src/transform/decompose_storage_access.cc b/src/transform/decompose_storage_access.cc
index c89288c..9de497c 100644
--- a/src/transform/decompose_storage_access.cc
+++ b/src/transform/decompose_storage_access.cc
@@ -332,7 +332,7 @@
 }
 
 /// @returns the unwrapped, user-declared constructed type of ty.
-const ast::NamedType* ConstructedTypeOf(const sem::Type* ty) {
+const ast::TypeDecl* ConstructedTypeOf(const sem::Type* ty) {
   while (true) {
     if (auto* ref = ty->As<sem::Reference>()) {
       ty = ref->StoreType();
@@ -414,7 +414,7 @@
   /// @param var_user the variable user
   /// @return the name of the function that performs the load
   Symbol LoadFunc(CloneContext& ctx,
-                  const ast::NamedType* insert_after,
+                  const ast::TypeDecl* insert_after,
                   const sem::Type* buf_ty,
                   const sem::Type* el_ty,
                   const sem::VariableUser* var_user) {
@@ -490,7 +490,7 @@
   /// @param var_user the variable user
   /// @return the name of the function that performs the store
   Symbol StoreFunc(CloneContext& ctx,
-                   const ast::NamedType* insert_after,
+                   const ast::TypeDecl* insert_after,
                    const sem::Type* buf_ty,
                    const sem::Type* el_ty,
                    const sem::VariableUser* var_user) {
diff --git a/src/transform/first_index_offset.cc b/src/transform/first_index_offset.cc
index b8fb7c2..4a69c63 100644
--- a/src/transform/first_index_offset.cc
+++ b/src/transform/first_index_offset.cc
@@ -133,14 +133,14 @@
       instance_index_offset = offset;
       offset += 4;
     }
-    auto* struct_type =
+    auto* struct_ =
         ctx.dst->Structure(ctx.dst->Sym(), std::move(members),
                            {ctx.dst->create<ast::StructBlockDecoration>()});
 
     // Create a global to hold the uniform buffer
     Symbol buffer_name = ctx.dst->Sym();
-    ctx.dst->Global(buffer_name, struct_type, ast::StorageClass::kUniform,
-                    nullptr,
+    ctx.dst->Global(buffer_name, ctx.dst->ty.Of(struct_),
+                    ast::StorageClass::kUniform, nullptr,
                     ast::DecorationList{
                         ctx.dst->create<ast::BindingDecoration>(ub_binding),
                         ctx.dst->create<ast::GroupDecoration>(ub_group),
diff --git a/src/transform/single_entry_point.cc b/src/transform/single_entry_point.cc
index dec6b09..26f12ec 100644
--- a/src/transform/single_entry_point.cc
+++ b/src/transform/single_entry_point.cc
@@ -69,7 +69,7 @@
   // Clone any module-scope variables, types, and functions that are statically
   // referenced by the target entry point.
   for (auto* decl : in->AST().GlobalDeclarations()) {
-    if (auto* ty = decl->As<ast::NamedType>()) {
+    if (auto* ty = decl->As<ast::TypeDecl>()) {
       // TODO(jrprice): Strip unused types.
       out.AST().AddConstructedType(ctx.Clone(ty));
     } else if (auto* var = decl->As<ast::Variable>()) {
diff --git a/src/transform/vertex_pulling.cc b/src/transform/vertex_pulling.cc
index 9f3b3bf..713c112 100644
--- a/src/transform/vertex_pulling.cc
+++ b/src/transform/vertex_pulling.cc
@@ -216,8 +216,8 @@
     for (uint32_t i = 0; i < cfg.vertex_state.size(); ++i) {
       // The decorated variable with struct type
       ctx.dst->Global(
-          GetVertexBufferName(i), struct_type, ast::StorageClass::kStorage,
-          ast::Access::kRead,
+          GetVertexBufferName(i), ctx.dst->ty.Of(struct_type),
+          ast::StorageClass::kStorage, ast::Access::kRead,
           ast::DecorationList{
               ctx.dst->create<ast::BindingDecoration>(i),
               ctx.dst->create<ast::GroupDecoration>(cfg.pulling_group),
@@ -485,7 +485,8 @@
       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);
+      auto* new_param =
+          ctx.dst->Param(ctx.dst->Sym(), ctx.dst->ty.Of(new_struct));
       new_function_parameters.push_back(new_param);
 
       // Copy values from the new parameter to the function-scope variable.
diff --git a/src/writer/hlsl/generator_impl.h b/src/writer/hlsl/generator_impl.h
index 3aa4958..57de7a3 100644
--- a/src/writer/hlsl/generator_impl.h
+++ b/src/writer/hlsl/generator_impl.h
@@ -392,6 +392,12 @@
     return builder_.TypeOf(type);
   }
 
+  /// @returns the resolved type of the ast::TypeDecl `type_decl`
+  /// @param type_decl the type
+  const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const {
+    return builder_.TypeOf(type_decl);
+  }
+
   /// Emits `prefix`, followed by an opening brace `{`, then calls `cb` to emit
   /// the block body, then finally emits the closing brace `}`.
   /// @param out the output stream
diff --git a/src/writer/hlsl/generator_impl_constructor_test.cc b/src/writer/hlsl/generator_impl_constructor_test.cc
index 2bee83b..8593fff 100644
--- a/src/writer/hlsl/generator_impl_constructor_test.cc
+++ b/src/writer/hlsl/generator_impl_constructor_test.cc
@@ -201,7 +201,7 @@
                                  Member("c", ty.vec3<i32>()),
                              });
 
-  WrapInFunction(Construct(str, 1, 2.0f, vec3<i32>(3, 4, 5)));
+  WrapInFunction(Construct(ty.Of(str), 1, 2.0f, vec3<i32>(3, 4, 5)));
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -218,7 +218,7 @@
                                  Member("c", ty.vec3<i32>()),
                              });
 
-  WrapInFunction(Construct(str));
+  WrapInFunction(Construct(ty.Of(str)));
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
diff --git a/src/writer/hlsl/generator_impl_function_test.cc b/src/writer/hlsl/generator_impl_function_test.cc
index e3b3d37..7c9f54e 100644
--- a/src/writer/hlsl/generator_impl_function_test.cc
+++ b/src/writer/hlsl/generator_impl_function_test.cc
@@ -205,12 +205,12 @@
           Member("col2", ty.f32(), {Location(2)}),
       });
 
-  Func("vert_main", {}, interface_struct,
-       {Return(Construct(interface_struct, Construct(ty.vec4<f32>()),
+  Func("vert_main", {}, ty.Of(interface_struct),
+       {Return(Construct(ty.Of(interface_struct), Construct(ty.vec4<f32>()),
                          Expr(0.5f), Expr(0.25f)))},
        {Stage(ast::PipelineStage::kVertex)});
 
-  Func("frag_main", {Param("inputs", interface_struct)}, ty.void_(),
+  Func("frag_main", {Param("inputs", ty.Of(interface_struct))}, ty.void_(),
        {
            Decl(Const("r", ty.f32(), MemberAccessor("inputs", "col1"))),
            Decl(Const("g", ty.f32(), MemberAccessor("inputs", "col2"))),
@@ -274,19 +274,20 @@
       "VertexOutput",
       {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
 
-  Func("foo", {Param("x", ty.f32())}, vertex_output_struct,
-       {Return(Construct(vertex_output_struct,
+  Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
+       {Return(Construct(ty.Of(vertex_output_struct),
                          Construct(ty.vec4<f32>(), "x", "x", "x", Expr(1.f))))},
        {});
 
-  Func("vert_main1", {}, vertex_output_struct,
-       {Return(Construct(vertex_output_struct, Expr(Call("foo", Expr(0.5f)))))},
+  Func("vert_main1", {}, ty.Of(vertex_output_struct),
+       {Return(Construct(ty.Of(vertex_output_struct),
+                         Expr(Call("foo", Expr(0.5f)))))},
        {Stage(ast::PipelineStage::kVertex)});
 
-  Func(
-      "vert_main2", {}, vertex_output_struct,
-      {Return(Construct(vertex_output_struct, Expr(Call("foo", Expr(0.25f)))))},
-      {Stage(ast::PipelineStage::kVertex)});
+  Func("vert_main2", {}, ty.Of(vertex_output_struct),
+       {Return(Construct(ty.Of(vertex_output_struct),
+                         Expr(Call("foo", Expr(0.25f)))))},
+       {Stage(ast::PipelineStage::kVertex)});
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -327,7 +328,7 @@
        Emit_Decoration_EntryPoint_With_Uniform) {
   auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())},
                            {create<ast::StructBlockDecoration>()});
-  auto* ubo = Global("ubo", ubo_ty, ast::StorageClass::kUniform,
+  auto* ubo = Global("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform,
                      ast::DecorationList{
                          create<ast::BindingDecoration>(0),
                          create<ast::GroupDecoration>(1),
@@ -382,7 +383,7 @@
   auto* s = Structure("Uniforms", {Member("coord", ty.vec4<f32>())},
                       {create<ast::StructBlockDecoration>()});
 
-  Global("uniforms", s, ast::StorageClass::kUniform,
+  Global("uniforms", ty.Of(s), ast::StorageClass::kUniform,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
@@ -428,7 +429,8 @@
                       },
                       {create<ast::StructBlockDecoration>()});
 
-  Global("coord", s, ast::StorageClass::kStorage, ast::Access::kReadWrite,
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
@@ -472,7 +474,7 @@
                       },
                       {create<ast::StructBlockDecoration>()});
 
-  Global("coord", s, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
@@ -516,7 +518,7 @@
                       },
                       {create<ast::StructBlockDecoration>()});
 
-  Global("coord", s, ast::StorageClass::kStorage, ast::Access::kWrite,
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kWrite,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
@@ -557,7 +559,8 @@
                       },
                       {create<ast::StructBlockDecoration>()});
 
-  Global("coord", s, ast::StorageClass::kStorage, ast::Access::kReadWrite,
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
@@ -756,7 +759,7 @@
        Emit_Decoration_Called_By_EntryPoint_With_Uniform) {
   auto* s = Structure("S", {Member("x", ty.f32())},
                       {create<ast::StructBlockDecoration>()});
-  Global("coord", s, ast::StorageClass::kUniform,
+  Global("coord", ty.Of(s), ast::StorageClass::kUniform,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
@@ -806,7 +809,8 @@
        Emit_Decoration_Called_By_EntryPoint_With_StorageBuffer) {
   auto* s = Structure("S", {Member("x", ty.f32())},
                       {create<ast::StructBlockDecoration>()});
-  Global("coord", s, ast::StorageClass::kStorage, ast::Access::kReadWrite,
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
@@ -1055,7 +1059,7 @@
   auto* s = Structure("Data", {Member("d", ty.f32())},
                       {create<ast::StructBlockDecoration>()});
 
-  Global("data", s, ast::StorageClass::kStorage, ast::Access::kReadWrite,
+  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
diff --git a/src/writer/hlsl/generator_impl_member_accessor_test.cc b/src/writer/hlsl/generator_impl_member_accessor_test.cc
index db9d468..bfcfca7 100644
--- a/src/writer/hlsl/generator_impl_member_accessor_test.cc
+++ b/src/writer/hlsl/generator_impl_member_accessor_test.cc
@@ -98,7 +98,8 @@
     auto* s =
         b.Structure("Data", members, {b.create<ast::StructBlockDecoration>()});
 
-    b.Global("data", s, ast::StorageClass::kStorage, ast::Access::kReadWrite,
+    b.Global("data", b.ty.Of(s), ast::StorageClass::kStorage,
+             ast::Access::kReadWrite,
              ast::DecorationList{
                  b.create<ast::BindingDecoration>(0),
                  b.create<ast::GroupDecoration>(1),
@@ -123,7 +124,7 @@
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor, EmitExpression_MemberAccessor) {
   auto* s = Structure("Data", {Member("mem", ty.f32())});
-  Global("str", s, ast::StorageClass::kPrivate);
+  Global("str", ty.Of(s), ast::StorageClass::kPrivate);
 
   auto* expr = MemberAccessor("str", "mem");
   WrapInFunction(Var("expr", ty.f32(), ast::StorageClass::kNone, expr));
@@ -521,7 +522,7 @@
                                    });
 
   SetupStorageBuffer({
-      Member("c", ty.array(inner, 4, 32)),
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
   });
 
   SetupFunction({
@@ -567,7 +568,7 @@
                                    });
 
   SetupStorageBuffer({
-      Member("c", ty.array(inner, 4, 32)),
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
   });
 
   SetupFunction({
@@ -615,7 +616,7 @@
                                    });
 
   SetupStorageBuffer({
-      Member("c", ty.array(inner, 4, 32)),
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
   });
 
   SetupFunction({
@@ -663,7 +664,7 @@
                                    });
 
   SetupStorageBuffer({
-      Member("c", ty.array(inner, 4, 32)),
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
   });
 
   SetupFunction({
@@ -710,7 +711,7 @@
                                    });
 
   SetupStorageBuffer({
-      Member("c", ty.array(inner, 4, 32)),
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
   });
 
   SetupFunction({
@@ -755,7 +756,7 @@
                                    });
 
   SetupStorageBuffer({
-      Member("c", ty.array(inner, 4, 32)),
+      Member("c", ty.array(ty.Of(inner), 4, 32)),
   });
 
   SetupFunction({
diff --git a/src/writer/hlsl/generator_impl_sanitizer_test.cc b/src/writer/hlsl/generator_impl_sanitizer_test.cc
index 25a584a..a848822 100644
--- a/src/writer/hlsl/generator_impl_sanitizer_test.cc
+++ b/src/writer/hlsl/generator_impl_sanitizer_test.cc
@@ -34,7 +34,7 @@
                               create<ast::StructBlockDecoration>(),
                           });
 
-  Global("sb", sb_ty, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("sb", ty.Of(sb_ty), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
@@ -103,7 +103,7 @@
                                  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_init = Construct(ty.Of(str), 1, vec3<f32>(2.f, 3.f, 4.f), 4);
   auto* struct_access = MemberAccessor(struct_init, "b");
   auto* pos =
       Var("pos", ty.vec3<f32>(), ast::StorageClass::kNone, struct_access);
diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc
index 9846c30..24e004d 100644
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ b/src/writer/hlsl/generator_impl_type_test.cc
@@ -170,7 +170,7 @@
                                Member("a", ty.i32()),
                                Member("b", ty.f32()),
                            });
-  Global("g", s, ast::StorageClass::kPrivate);
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
@@ -190,7 +190,7 @@
                           Member("b", ty.f32()),
                       },
                       {create<ast::StructBlockDecoration>()});
-  Global("g", s, ast::StorageClass::kStorage, ast::Access::kReadWrite,
+  Global("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -208,7 +208,7 @@
                                Member("a", ty.i32()),
                                Member("b", ty.f32()),
                            });
-  Global("g", s, ast::StorageClass::kPrivate);
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
@@ -228,7 +228,7 @@
                Member("b", ty.f32()),
                Member("c", ty.f32(), {MemberAlign(128), MemberSize(128)}),
            });
-  Global("g", s, ast::StorageClass::kPrivate);
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
@@ -252,7 +252,7 @@
                                Member("double", ty.i32()),
                                Member("float", ty.f32()),
                            });
-  Global("g", s, ast::StorageClass::kPrivate);
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
@@ -272,7 +272,7 @@
                           Member("b", ty.f32()),
                       },
                       {create<ast::StructBlockDecoration>()});
-  Global("g", s, ast::StorageClass::kPrivate);
+  Global("g", ty.Of(s), ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
diff --git a/src/writer/hlsl/generator_impl_workgroup_var_test.cc b/src/writer/hlsl/generator_impl_workgroup_var_test.cc
index f48920d..19ab36f 100644
--- a/src/writer/hlsl/generator_impl_workgroup_var_test.cc
+++ b/src/writer/hlsl/generator_impl_workgroup_var_test.cc
@@ -41,7 +41,7 @@
 TEST_F(HlslGeneratorImplTest_WorkgroupVar, Aliased) {
   auto* alias = Alias("F32", ty.f32());
 
-  Global("wg", alias, ast::StorageClass::kWorkgroup);
+  Global("wg", ty.Of(alias), ast::StorageClass::kWorkgroup);
 
   Func("main", {}, ty.void_(), {Assign("wg", 1.2f)},
        {Stage(ast::PipelineStage::kCompute)});
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index 90b359c..9ebb145 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -87,9 +87,9 @@
     global_variables_.set(global->symbol(), sem);
   }
 
-  for (auto* const ty : program_->AST().ConstructedTypes()) {
-    if (!ty->Is<ast::Alias>()) {
-      if (!EmitConstructedType(TypeOf(ty))) {
+  for (auto* const type_decl : program_->AST().ConstructedTypes()) {
+    if (!type_decl->Is<ast::Alias>()) {
+      if (!EmitConstructedType(TypeOf(type_decl))) {
         return false;
       }
     }
diff --git a/src/writer/msl/generator_impl.h b/src/writer/msl/generator_impl.h
index e1c4206..72e3739 100644
--- a/src/writer/msl/generator_impl.h
+++ b/src/writer/msl/generator_impl.h
@@ -286,6 +286,12 @@
     return program_->TypeOf(type);
   }
 
+  /// @returns the resolved type of the ast::TypeDecl `type_decl`
+  /// @param type_decl the type declaration
+  const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const {
+    return program_->TypeOf(type_decl);
+  }
+
   // A pair of byte size and alignment `uint32_t`s.
   struct SizeAndAlign {
     uint32_t size;
diff --git a/src/writer/msl/generator_impl_constructor_test.cc b/src/writer/msl/generator_impl_constructor_test.cc
index 9a44da3..c26375c 100644
--- a/src/writer/msl/generator_impl_constructor_test.cc
+++ b/src/writer/msl/generator_impl_constructor_test.cc
@@ -160,7 +160,7 @@
                                  Member("c", ty.vec3<i32>()),
                              });
 
-  WrapInFunction(Construct(str, 1, 2.0f, vec3<i32>(3, 4, 5)));
+  WrapInFunction(Construct(ty.Of(str), 1, 2.0f, vec3<i32>(3, 4, 5)));
 
   GeneratorImpl& gen = Build();
 
@@ -171,7 +171,7 @@
 TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Struct_Empty) {
   auto* str = Structure("S", {});
 
-  WrapInFunction(Construct(str));
+  WrapInFunction(Construct(ty.Of(str)));
 
   GeneratorImpl& gen = Build();
 
diff --git a/src/writer/msl/generator_impl_function_test.cc b/src/writer/msl/generator_impl_function_test.cc
index 66df0a3..a331b4e 100644
--- a/src/writer/msl/generator_impl_function_test.cc
+++ b/src/writer/msl/generator_impl_function_test.cc
@@ -175,12 +175,12 @@
           Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
       });
 
-  Func("vert_main", {}, interface_struct,
-       {Return(Construct(interface_struct, Expr(0.5f), Expr(0.25f),
+  Func("vert_main", {}, ty.Of(interface_struct),
+       {Return(Construct(ty.Of(interface_struct), Expr(0.5f), Expr(0.25f),
                          Construct(ty.vec4<f32>())))},
        {Stage(ast::PipelineStage::kVertex)});
 
-  Func("frag_main", {Param("colors", interface_struct)}, ty.void_(),
+  Func("frag_main", {Param("colors", ty.Of(interface_struct))}, ty.void_(),
        {
            WrapInStatement(
                Const("r", ty.f32(), MemberAccessor("colors", "col1"))),
@@ -246,16 +246,16 @@
       "VertexOutput",
       {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
 
-  Func("foo", {Param("x", ty.f32())}, vertex_output_struct,
-       {Return(Construct(vertex_output_struct,
+  Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
+       {Return(Construct(ty.Of(vertex_output_struct),
                          Construct(ty.vec4<f32>(), "x", "x", "x", Expr(1.f))))},
        {});
 
-  Func("vert_main1", {}, vertex_output_struct,
+  Func("vert_main1", {}, ty.Of(vertex_output_struct),
        {Return(Expr(Call("foo", Expr(0.5f))))},
        {Stage(ast::PipelineStage::kVertex)});
 
-  Func("vert_main2", {}, vertex_output_struct,
+  Func("vert_main2", {}, ty.Of(vertex_output_struct),
        {Return(Expr(Call("foo", Expr(0.25f))))},
        {Stage(ast::PipelineStage::kVertex)});
 
@@ -306,7 +306,8 @@
                       },
                       {create<ast::StructBlockDecoration>()});
 
-  Global("coord", s, ast::StorageClass::kStorage, ast::Access::kReadWrite,
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
@@ -352,7 +353,7 @@
                       },
                       {create<ast::StructBlockDecoration>()});
 
-  Global("coord", s, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
@@ -558,7 +559,7 @@
        Emit_Decoration_Called_By_EntryPoint_With_Uniform) {
   auto* ubo_ty = Structure("UBO", {Member("coord", ty.vec4<f32>())},
                            {create<ast::StructBlockDecoration>()});
-  auto* ubo = Global("ubo", ubo_ty, ast::StorageClass::kUniform,
+  auto* ubo = Global("ubo", ty.Of(ubo_ty), ast::StorageClass::kUniform,
                      ast::DecorationList{
                          create<ast::BindingDecoration>(0),
                          create<ast::GroupDecoration>(1),
@@ -616,7 +617,8 @@
                       },
                       {create<ast::StructBlockDecoration>()});
 
-  Global("coord", s, ast::StorageClass::kStorage, ast::Access::kReadWrite,
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
@@ -673,7 +675,7 @@
                       },
                       {create<ast::StructBlockDecoration>()});
 
-  Global("coord", s, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("coord", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
@@ -816,7 +818,7 @@
   auto* s = Structure("Data", {Member("d", ty.f32())},
                       {create<ast::StructBlockDecoration>()});
 
-  Global("data", s, ast::StorageClass::kStorage, ast::Access::kReadWrite,
+  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
diff --git a/src/writer/msl/generator_impl_member_accessor_test.cc b/src/writer/msl/generator_impl_member_accessor_test.cc
index bb7089d..2047e22 100644
--- a/src/writer/msl/generator_impl_member_accessor_test.cc
+++ b/src/writer/msl/generator_impl_member_accessor_test.cc
@@ -22,7 +22,7 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitExpression_MemberAccessor) {
-  Global("str", Structure("my_str", {Member("mem", ty.f32())}),
+  Global("str", ty.Of(Structure("my_str", {Member("mem", ty.f32())})),
          ast::StorageClass::kPrivate);
   auto* expr = MemberAccessor("str", "mem");
   WrapInFunction(expr);
diff --git a/src/writer/msl/generator_impl_type_test.cc b/src/writer/msl/generator_impl_type_test.cc
index ffd88b6..349995a 100644
--- a/src/writer/msl/generator_impl_type_test.cc
+++ b/src/writer/msl/generator_impl_type_test.cc
@@ -233,7 +233,7 @@
                 },
                 {create<ast::StructBlockDecoration>()});
 
-  Global("G", s, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -335,14 +335,14 @@
   auto* s = Structure("S",
                       {
                           Member("a", ty.i32()),
-                          Member("b", inner_x),
+                          Member("b", ty.Of(inner_x)),
                           Member("c", ty.f32()),
-                          Member("d", inner_y),
+                          Member("d", ty.Of(inner_y)),
                           Member("e", ty.f32()),
                       },
                       {create<ast::StructBlockDecoration>()});
 
-  Global("G", s, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -419,7 +419,7 @@
   auto* array_x = ty.array<f32, 7>();
 
   // array_y: size(4096), align(512)
-  auto* array_y = ty.array(inner, 4);
+  auto* array_y = ty.array(ty.Of(inner), 4);
 
   // array_z: size(4), align(4)
   auto* array_z = ty.array<f32>();
@@ -436,7 +436,7 @@
                 },
                 ast::DecorationList{create<ast::StructBlockDecoration>()});
 
-  Global("G", s, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -549,7 +549,7 @@
       },
       {create<ast::StructBlockDecoration>()});
 
-  Global("G", s, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -614,7 +614,7 @@
                       },
                       {create<ast::StructBlockDecoration>()});
 
-  Global("G", s, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("G", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
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 f403097..68e2983 100644
--- a/src/writer/msl/generator_impl_variable_decl_statement_test.cc
+++ b/src/writer/msl/generator_impl_variable_decl_statement_test.cc
@@ -70,7 +70,7 @@
                                Member("b", ty.f32()),
                            });
 
-  auto* var = Var("a", s, ast::StorageClass::kNone);
+  auto* var = Var("a", ty.Of(s), ast::StorageClass::kNone);
   auto* stmt = Decl(var);
   WrapInFunction(stmt);
 
diff --git a/src/writer/spirv/builder_accessor_expression_test.cc b/src/writer/spirv/builder_accessor_expression_test.cc
index 19da1a4..b77bae2 100644
--- a/src/writer/spirv/builder_accessor_expression_test.cc
+++ b/src/writer/spirv/builder_accessor_expression_test.cc
@@ -224,7 +224,7 @@
                                        Member("b", ty.f32()),
                                    });
 
-  auto* var = Var("ident", s);
+  auto* var = Var("ident", ty.Of(s));
 
   auto* expr = MemberAccessor("ident", "b");
   WrapInFunction(var, expr);
@@ -268,9 +268,9 @@
                                               Member("b", ty.f32()),
                                           });
 
-  auto* s_type = Structure("my_struct", {Member("inner", inner_struct)});
+  auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
 
-  auto* var = Var("ident", s_type);
+  auto* var = Var("ident", ty.Of(s_type));
   auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "b");
   WrapInFunction(var, expr);
 
@@ -312,7 +312,7 @@
                                        Member("b", ty.f32()),
                                    });
 
-  auto* var = Const("ident", s, Construct(s, 0.f, 0.f));
+  auto* var = Const("ident", ty.Of(s), Construct(ty.Of(s), 0.f, 0.f));
 
   auto* expr = MemberAccessor("ident", "b");
   WrapInFunction(var, expr);
@@ -350,10 +350,11 @@
                                               Member("b", ty.f32()),
                                           });
 
-  auto* s_type = Structure("my_struct", {Member("inner", inner_struct)});
+  auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
 
-  auto* var = Const("ident", s_type,
-                    Construct(s_type, Construct(inner_struct, 0.f, 0.f)));
+  auto* var =
+      Const("ident", ty.Of(s_type),
+            Construct(ty.Of(s_type), Construct(ty.Of(inner_struct), 0.f, 0.f)));
   auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "b");
   WrapInFunction(var, expr);
 
@@ -394,10 +395,10 @@
                                               Member("b", ty.f32()),
                                           });
 
-  auto* alias = Alias("Alias", inner_struct);
-  auto* s_type = Structure("Outer", {Member("inner", alias)});
+  auto* alias = Alias("Alias", ty.Of(inner_struct));
+  auto* s_type = Structure("Outer", {Member("inner", ty.Of(alias))});
 
-  auto* var = Var("ident", s_type);
+  auto* var = Var("ident", ty.Of(s_type));
   auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "a");
   WrapInFunction(var, expr);
 
@@ -440,9 +441,9 @@
                                               Member("b", ty.f32()),
                                           });
 
-  auto* s_type = Structure("my_struct", {Member("inner", inner_struct)});
+  auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
 
-  auto* var = Var("ident", s_type);
+  auto* var = Var("ident", ty.Of(s_type));
   auto* expr =
       Assign(MemberAccessor(MemberAccessor("ident", "inner"), "a"), Expr(2.0f));
   WrapInFunction(var, expr);
@@ -489,9 +490,9 @@
                                               Member("b", ty.f32()),
                                           });
 
-  auto* s_type = Structure("my_struct", {Member("inner", inner_struct)});
+  auto* s_type = Structure("my_struct", {Member("inner", ty.Of(inner_struct))});
 
-  auto* var = Var("ident", s_type);
+  auto* var = Var("ident", ty.Of(s_type));
   auto* store = Var("store", ty.f32());
 
   auto* rhs = MemberAccessor(MemberAccessor("ident", "inner"), "a");
@@ -696,11 +697,11 @@
 
   auto* c_type = Structure("C", {Member("baz", ty.vec3<f32>())});
 
-  auto* b_type = Structure("B", {Member("bar", c_type)});
-  auto* b_ary_type = ty.array(b_type, 3);
+  auto* b_type = Structure("B", {Member("bar", ty.Of(c_type))});
+  auto* b_ary_type = ty.array(ty.Of(b_type), 3);
   auto* a_type = Structure("A", {Member("foo", b_ary_type)});
 
-  auto* a_ary_type = ty.array(a_type, 2);
+  auto* a_ary_type = ty.array(ty.Of(a_type), 2);
   auto* var = Var("index", a_ary_type);
   auto* expr = MemberAccessor(
       MemberAccessor(
diff --git a/src/writer/spirv/builder_assign_test.cc b/src/writer/spirv/builder_assign_test.cc
index 4dbf765..c1894b6 100644
--- a/src/writer/spirv/builder_assign_test.cc
+++ b/src/writer/spirv/builder_assign_test.cc
@@ -181,7 +181,7 @@
                                        Member("b", ty.f32()),
                                    });
 
-  auto* v = Var("ident", s);
+  auto* v = Var("ident", ty.Of(s));
 
   auto* assign = Assign(MemberAccessor("ident", "b"), Expr(4.f));
 
diff --git a/src/writer/spirv/builder_constructor_expression_test.cc b/src/writer/spirv/builder_constructor_expression_test.cc
index 5dd4be6..3f7e66b 100644
--- a/src/writer/spirv/builder_constructor_expression_test.cc
+++ b/src/writer/spirv/builder_constructor_expression_test.cc
@@ -94,7 +94,7 @@
   // cast<Int>(2.3f)
 
   auto* alias = Alias("Int", ty.i32());
-  auto* cast = Construct(alias, 2.3f);
+  auto* cast = Construct(ty.Of(alias), 2.3f);
   WrapInFunction(cast);
 
   spirv::Builder& b = Build();
@@ -1056,7 +1056,7 @@
                                        Member("b", ty.vec3<f32>()),
                                    });
 
-  auto* t = Construct(s, 2.0f, vec3<f32>(2.0f, 2.0f, 2.0f));
+  auto* t = Construct(ty.Of(s), 2.0f, vec3<f32>(2.0f, 2.0f, 2.0f));
   WrapInFunction(t);
 
   spirv::Builder& b = Build();
@@ -1202,7 +1202,7 @@
 
 TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Struct) {
   auto* s = Structure("my_struct", {Member("a", ty.f32())});
-  auto* t = Construct(s);
+  auto* t = Construct(ty.Of(s));
   WrapInFunction(t);
 
   spirv::Builder& b = Build();
@@ -1626,7 +1626,7 @@
                                        Member("b", ty.vec3<f32>()),
                                    });
 
-  auto* t = Construct(s, 2.f, vec3<f32>(2.f, 2.f, 2.f));
+  auto* t = Construct(ty.Of(s), 2.f, vec3<f32>(2.f, 2.f, 2.f));
   WrapInFunction(t);
 
   spirv::Builder& b = Build();
@@ -1645,7 +1645,7 @@
   Global("a", ty.f32(), ast::StorageClass::kPrivate);
   Global("b", ty.f32(), ast::StorageClass::kPrivate);
 
-  auto* t = Construct(s, 2.f, "a", 2.f);
+  auto* t = Construct(ty.Of(s), 2.f, "a", 2.f);
   WrapInFunction(t);
 
   spirv::Builder& b = Build();
diff --git a/src/writer/spirv/builder_entry_point_test.cc b/src/writer/spirv/builder_entry_point_test.cc
index fd98a76..0a0720b 100644
--- a/src/writer/spirv/builder_entry_point_test.cc
+++ b/src/writer/spirv/builder_entry_point_test.cc
@@ -196,11 +196,12 @@
                  ast::DecorationList{Builtin(ast::Builtin::kPosition)}),
       });
 
-  auto* vert_retval = Construct(interface, 42.f, Construct(ty.vec4<f32>()));
-  Func("vert_main", ast::VariableList{}, interface, {Return(vert_retval)},
-       {Stage(ast::PipelineStage::kVertex)});
+  auto* vert_retval =
+      Construct(ty.Of(interface), 42.f, Construct(ty.vec4<f32>()));
+  Func("vert_main", ast::VariableList{}, ty.Of(interface),
+       {Return(vert_retval)}, {Stage(ast::PipelineStage::kVertex)});
 
-  auto* frag_inputs = Param("inputs", interface);
+  auto* frag_inputs = Param("inputs", ty.Of(interface));
   Func("frag_main", ast::VariableList{frag_inputs}, ty.f32(),
        {Return(MemberAccessor(Expr("inputs"), "value"))},
        {Stage(ast::PipelineStage::kFragment)},
diff --git a/src/writer/spirv/builder_function_test.cc b/src/writer/spirv/builder_function_test.cc
index d4ed7fb..ad1c8ff 100644
--- a/src/writer/spirv/builder_function_test.cc
+++ b/src/writer/spirv/builder_function_test.cc
@@ -204,7 +204,7 @@
   auto* s = Structure("Data", {Member("d", ty.f32())},
                       {create<ast::StructBlockDecoration>()});
 
-  Global("data", s, ast::StorageClass::kStorage, ast::Access::kReadWrite,
+  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
diff --git a/src/writer/spirv/builder_global_variable_test.cc b/src/writer/spirv/builder_global_variable_test.cc
index 9aa0bca..c11c12c 100644
--- a/src/writer/spirv/builder_global_variable_test.cc
+++ b/src/writer/spirv/builder_global_variable_test.cc
@@ -403,11 +403,12 @@
                       },
                       {create<ast::StructBlockDecoration>()});
 
-  auto* var = Global("b", A, ast::StorageClass::kStorage, ast::Access::kRead,
-                     ast::DecorationList{
-                         create<ast::BindingDecoration>(0),
-                         create<ast::GroupDecoration>(0),
-                     });
+  auto* var =
+      Global("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead,
+             ast::DecorationList{
+                 create<ast::BindingDecoration>(0),
+                 create<ast::GroupDecoration>(0),
+             });
 
   spirv::Builder& b = Build();
 
@@ -441,12 +442,13 @@
 
   auto* A = Structure("A", {Member("a", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* B = Alias("B", A);
-  auto* var = Global("b", B, ast::StorageClass::kStorage, ast::Access::kRead,
-                     ast::DecorationList{
-                         create<ast::BindingDecoration>(0),
-                         create<ast::GroupDecoration>(0),
-                     });
+  auto* B = Alias("B", ty.Of(A));
+  auto* var =
+      Global("b", ty.Of(B), ast::StorageClass::kStorage, ast::Access::kRead,
+             ast::DecorationList{
+                 create<ast::BindingDecoration>(0),
+                 create<ast::GroupDecoration>(0),
+             });
 
   spirv::Builder& b = Build();
 
@@ -478,12 +480,13 @@
 
   auto* A = Structure("A", {Member("a", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* B = Alias("B", A);
-  auto* var = Global("b", B, ast::StorageClass::kStorage, ast::Access::kRead,
-                     ast::DecorationList{
-                         create<ast::BindingDecoration>(0),
-                         create<ast::GroupDecoration>(0),
-                     });
+  auto* B = Alias("B", ty.Of(A));
+  auto* var =
+      Global("b", ty.Of(B), ast::StorageClass::kStorage, ast::Access::kRead,
+             ast::DecorationList{
+                 create<ast::BindingDecoration>(0),
+                 create<ast::GroupDecoration>(0),
+             });
 
   spirv::Builder& b = Build();
 
@@ -515,17 +518,18 @@
 
   auto* A = Structure("A", {Member("a", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* var_b = Global("b", A, ast::StorageClass::kStorage, ast::Access::kRead,
-                       ast::DecorationList{
-                           create<ast::GroupDecoration>(0),
-                           create<ast::BindingDecoration>(0),
-                       });
-  auto* var_c =
-      Global("c", A, ast::StorageClass::kStorage, ast::Access::kReadWrite,
+  auto* var_b =
+      Global("b", ty.Of(A), ast::StorageClass::kStorage, ast::Access::kRead,
              ast::DecorationList{
-                 create<ast::GroupDecoration>(1),
+                 create<ast::GroupDecoration>(0),
                  create<ast::BindingDecoration>(0),
              });
+  auto* var_c = Global("c", ty.Of(A), ast::StorageClass::kStorage,
+                       ast::Access::kReadWrite,
+                       ast::DecorationList{
+                           create<ast::GroupDecoration>(1),
+                           create<ast::BindingDecoration>(0),
+                       });
 
   spirv::Builder& b = Build();
 
diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc
index a42f96c..c1822ec 100644
--- a/src/writer/spirv/builder_intrinsic_test.cc
+++ b/src/writer/spirv/builder_intrinsic_test.cc
@@ -1537,7 +1537,7 @@
 TEST_F(IntrinsicBuilderTest, Call_ArrayLength) {
   auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
                       {create<ast::StructBlockDecoration>()});
-  Global("b", s, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(1),
              create<ast::GroupDecoration>(2),
@@ -1586,7 +1586,7 @@
                           Member(4, "a", ty.array<f32>(4)),
                       },
                       {create<ast::StructBlockDecoration>()});
-  Global("b", s, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("b", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(1),
              create<ast::GroupDecoration>(2),
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index a0fca8b..1bae74f 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -30,7 +30,7 @@
   auto* ary = ty.array(ty.i32(), 0);
   auto* str = Structure("S", {Member("x", ary)},
                         {create<ast::StructBlockDecoration>()});
-  Global("a", str, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -51,7 +51,7 @@
   auto* ary = ty.array(ty.i32(), 0);
   auto* str = Structure("S", {Member("x", ary)},
                         {create<ast::StructBlockDecoration>()});
-  Global("a", str, ast::StorageClass::kStorage, ast::Access::kRead,
+  Global("a", ty.Of(str), ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
diff --git a/src/writer/wgsl/generator_impl.cc b/src/writer/wgsl/generator_impl.cc
index 789bb51..006b799 100644
--- a/src/writer/wgsl/generator_impl.cc
+++ b/src/writer/wgsl/generator_impl.cc
@@ -66,8 +66,8 @@
 bool GeneratorImpl::Generate() {
   // Generate global declarations in the order they appear in the module.
   for (auto* decl : program_->AST().GlobalDeclarations()) {
-    if (auto* ty = decl->As<ast::Type>()) {
-      if (!EmitConstructedType(ty)) {
+    if (auto* td = decl->As<ast::TypeDecl>()) {
+      if (!EmitConstructedType(td)) {
         return false;
       }
     } else if (auto* func = decl->As<ast::Function>()) {
@@ -91,7 +91,7 @@
   return true;
 }
 
-bool GeneratorImpl::EmitConstructedType(const ast::Type* ty) {
+bool GeneratorImpl::EmitConstructedType(const ast::TypeDecl* ty) {
   make_indent();
 
   if (auto* alias = ty->As<ast::Alias>()) {
@@ -105,7 +105,8 @@
       return false;
     }
   } else {
-    diagnostics_.add_error("unknown constructed type: " + ty->type_name());
+    diagnostics_.add_error("unknown constructed type: " +
+                           std::string(ty->TypeInfo().name));
     return false;
   }
   return true;
@@ -374,9 +375,7 @@
 }
 
 bool GeneratorImpl::EmitType(const ast::Type* ty) {
-  if (auto* alias = ty->As<ast::Alias>()) {
-    out_ << program_->Symbols().NameFor(alias->symbol());
-  } else if (auto* ary = ty->As<ast::Array>()) {
+  if (auto* ary = ty->As<ast::Array>()) {
     for (auto* deco : ary->decorations()) {
       if (auto* stride = deco->As<ast::StrideDecoration>()) {
         out_ << "[[stride(" << stride->stride() << ")]] ";
@@ -416,10 +415,6 @@
     if (sampler->IsComparison()) {
       out_ << "_comparison";
     }
-  } else if (auto* str = ty->As<ast::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->name());
   } else if (ty->Is<ast::ExternalTexture>()) {
     out_ << "external_texture";
   } else if (auto* texture = ty->As<ast::Texture>()) {
diff --git a/src/writer/wgsl/generator_impl.h b/src/writer/wgsl/generator_impl.h
index 1017bae..4d48d76 100644
--- a/src/writer/wgsl/generator_impl.h
+++ b/src/writer/wgsl/generator_impl.h
@@ -57,7 +57,7 @@
   /// Handles generating a constructed type
   /// @param ty the constructed to generate
   /// @returns true if the constructed was emitted
-  bool EmitConstructedType(const ast::Type* ty);
+  bool EmitConstructedType(const ast::TypeDecl* ty);
   /// Handles an array accessor expression
   /// @param expr the expression to emit
   /// @returns true if the array accessor was emitted
diff --git a/src/writer/wgsl/generator_impl_alias_type_test.cc b/src/writer/wgsl/generator_impl_alias_type_test.cc
index f71ee45..43c5270 100644
--- a/src/writer/wgsl/generator_impl_alias_type_test.cc
+++ b/src/writer/wgsl/generator_impl_alias_type_test.cc
@@ -23,6 +23,7 @@
 
 TEST_F(WgslGeneratorImplTest, EmitAlias_F32) {
   auto* alias = Alias("a", ty.f32());
+
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.EmitConstructedType(alias)) << gen.error();
@@ -36,7 +37,7 @@
                                Member("b", ty.i32()),
                            });
 
-  auto* alias = Alias("B", s);
+  auto* alias = Alias("B", ty.Of(s));
 
   GeneratorImpl& gen = Build();
 
@@ -56,7 +57,7 @@
                                Member("b", ty.i32()),
                            });
 
-  auto* alias = Alias("B", s);
+  auto* alias = Alias("B", ty.Of(s));
 
   GeneratorImpl& gen = Build();
 
diff --git a/src/writer/wgsl/generator_impl_function_test.cc b/src/writer/wgsl/generator_impl_function_test.cc
index 5bb305a..fc4fc30 100644
--- a/src/writer/wgsl/generator_impl_function_test.cc
+++ b/src/writer/wgsl/generator_impl_function_test.cc
@@ -175,7 +175,7 @@
   auto* s = Structure("Data", {Member("d", ty.f32())},
                       {create<ast::StructBlockDecoration>()});
 
-  Global("data", s, ast::StorageClass::kStorage, ast::Access::kReadWrite,
+  Global("data", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
diff --git a/src/writer/wgsl/generator_impl_global_decl_test.cc b/src/writer/wgsl/generator_impl_global_decl_test.cc
index 5954621..fcb5104 100644
--- a/src/writer/wgsl/generator_impl_global_decl_test.cc
+++ b/src/writer/wgsl/generator_impl_global_decl_test.cc
@@ -61,8 +61,8 @@
 
   Func("main", ast::VariableList{}, ty.void_(),
        ast::StatementList{
-           Decl(Var("s0", s0)),
-           Decl(Var("s1", s1)),
+           Decl(Var("s0", ty.Of(s0))),
+           Decl(Var("s1", ty.Of(s1))),
            Assign("a1", Call("func")),
        },
        ast::DecorationList{
diff --git a/src/writer/wgsl/generator_impl_member_accessor_test.cc b/src/writer/wgsl/generator_impl_member_accessor_test.cc
index 03fccc6..92ad0b8 100644
--- a/src/writer/wgsl/generator_impl_member_accessor_test.cc
+++ b/src/writer/wgsl/generator_impl_member_accessor_test.cc
@@ -23,7 +23,7 @@
 
 TEST_F(WgslGeneratorImplTest, EmitExpression_MemberAccessor) {
   auto* s = Structure("Data", {Member("mem", ty.f32())});
-  Global("str", s, ast::StorageClass::kPrivate);
+  Global("str", ty.Of(s), ast::StorageClass::kPrivate);
 
   auto* expr = MemberAccessor("str", "mem");
   WrapInFunction(expr);
@@ -36,7 +36,7 @@
 
 TEST_F(WgslGeneratorImplTest, EmitExpression_MemberAccessor_OfDref) {
   auto* s = Structure("Data", {Member("mem", ty.f32())});
-  Global("str", s, ast::StorageClass::kPrivate);
+  Global("str", ty.Of(s), ast::StorageClass::kPrivate);
 
   auto* p = Const("p", nullptr, AddressOf("str"));
   auto* expr = MemberAccessor(Deref("p"), "mem");
diff --git a/src/writer/wgsl/generator_impl_type_test.cc b/src/writer/wgsl/generator_impl_type_test.cc
index 9acd634..991dc69 100644
--- a/src/writer/wgsl/generator_impl_type_test.cc
+++ b/src/writer/wgsl/generator_impl_type_test.cc
@@ -27,10 +27,12 @@
 
 TEST_F(WgslGeneratorImplTest, EmitType_Alias) {
   auto* alias = Alias("alias", ty.f32());
+  auto* alias_ty = ty.Of(alias);
+  WrapInFunction(Var("make_reachable", alias_ty));
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(alias)) << gen.error();
+  ASSERT_TRUE(gen.EmitType(alias_ty)) << gen.error();
   EXPECT_EQ(gen.result(), "alias");
 }
 
@@ -119,10 +121,12 @@
                                Member("a", ty.i32()),
                                Member("b", ty.f32()),
                            });
+  auto* s_ty = ty.Of(s);
+  WrapInFunction(Var("make_reachable", s_ty));
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(s)) << gen.error();
+  ASSERT_TRUE(gen.EmitType(s_ty)) << gen.error();
   EXPECT_EQ(gen.result(), "S");
 }
 
diff --git a/src/writer/wgsl/generator_impl_variable_test.cc b/src/writer/wgsl/generator_impl_variable_test.cc
index 8b51c3a..de4cd1d 100644
--- a/src/writer/wgsl/generator_impl_variable_test.cc
+++ b/src/writer/wgsl/generator_impl_variable_test.cc
@@ -46,11 +46,12 @@
 TEST_F(WgslGeneratorImplTest, EmitVariable_Access_Read) {
   auto* s = Structure("S", {Member("a", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* v = Global("a", s, ast::StorageClass::kStorage, ast::Access::kRead,
-                   ast::DecorationList{
-                       create<ast::BindingDecoration>(0),
-                       create<ast::GroupDecoration>(0),
-                   });
+  auto* v =
+      Global("a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
+             ast::DecorationList{
+                 create<ast::BindingDecoration>(0),
+                 create<ast::GroupDecoration>(0),
+             });
 
   GeneratorImpl& gen = Build();
 
@@ -62,11 +63,12 @@
 TEST_F(WgslGeneratorImplTest, EmitVariable_Access_Write) {
   auto* s = Structure("S", {Member("a", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* v = Global("a", s, ast::StorageClass::kStorage, ast::Access::kWrite,
-                   ast::DecorationList{
-                       create<ast::BindingDecoration>(0),
-                       create<ast::GroupDecoration>(0),
-                   });
+  auto* v =
+      Global("a", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kWrite,
+             ast::DecorationList{
+                 create<ast::BindingDecoration>(0),
+                 create<ast::GroupDecoration>(0),
+             });
 
   GeneratorImpl& gen = Build();
 
@@ -78,7 +80,8 @@
 TEST_F(WgslGeneratorImplTest, EmitVariable_Access_ReadWrite) {
   auto* s = Structure("S", {Member("a", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* v = Global("a", s, ast::StorageClass::kStorage, ast::Access::kReadWrite,
+  auto* v = Global("a", ty.Of(s), ast::StorageClass::kStorage,
+                   ast::Access::kReadWrite,
                    ast::DecorationList{
                        create<ast::BindingDecoration>(0),
                        create<ast::GroupDecoration>(0),