ast: Migrate to using ast::Type

Remove all sem::Type references from the AST.
ConstructedTypes are now all AST types.

The parsers will still create semantic types, but these are now disjoint
and ignored.
The parsers will be updated with future changes to stop creating these
semantic types.

Resolver creates semantic types from the AST types. Most downstream
logic continues to use the semantic types, however transforms will now
need to rebuild AST type information instead of reassigning semantic
information, as semantic nodes are fully rebuilt by the Resolver.

Bug: tint:724
Change-Id: I4ce03a075f13c77648cda5c3691bae202752ecc5
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/49747
Commit-Queue: Ben Clayton <bclayton@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/ast/bitcast_expression.cc b/src/ast/bitcast_expression.cc
index 2c23b9d..369d6ae 100644
--- a/src/ast/bitcast_expression.cc
+++ b/src/ast/bitcast_expression.cc
@@ -23,10 +23,10 @@
 
 BitcastExpression::BitcastExpression(ProgramID program_id,
                                      const Source& source,
-                                     typ::Type type,
+                                     ast::Type* type,
                                      Expression* expr)
     : Base(program_id, source), type_(type), expr_(expr) {
-  TINT_ASSERT(type_.ast || type_.sem);
+  TINT_ASSERT(type_);
   TINT_ASSERT(expr_);
   TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(expr, program_id);
 }
@@ -37,7 +37,7 @@
 BitcastExpression* BitcastExpression::Clone(CloneContext* ctx) const {
   // Clone arguments outside of create() call to have deterministic ordering
   auto src = ctx->Clone(source());
-  auto ty = ctx->Clone(type());
+  auto* ty = ctx->Clone(type());
   auto* e = ctx->Clone(expr_);
   return ctx->dst->create<BitcastExpression>(src, ty, e);
 }
@@ -46,9 +46,8 @@
                                std::ostream& out,
                                size_t indent) const {
   make_indent(out, indent);
-  out << "Bitcast[" << result_type_str(sem) << "]<"
-      << (type_.ast ? type_.ast->type_name() : type_.sem->type_name()) << ">{"
-      << std::endl;
+  out << "Bitcast[" << result_type_str(sem) << "]<" << type_->type_name()
+      << ">{" << std::endl;
   expr_->to_str(sem, out, indent + 2);
   make_indent(out, indent);
   out << "}" << std::endl;
diff --git a/src/ast/bitcast_expression.h b/src/ast/bitcast_expression.h
index 5b8baca..6cce249 100644
--- a/src/ast/bitcast_expression.h
+++ b/src/ast/bitcast_expression.h
@@ -30,14 +30,14 @@
   /// @param expr the expr
   BitcastExpression(ProgramID program_id,
                     const Source& source,
-                    typ::Type type,
+                    ast::Type* type,
                     Expression* expr);
   /// Move constructor
   BitcastExpression(BitcastExpression&&);
   ~BitcastExpression() override;
 
   /// @returns the left side expression
-  typ::Type type() const { return type_; }
+  ast::Type* type() const { return type_; }
   /// @returns the expression
   Expression* expr() const { return expr_; }
 
@@ -58,7 +58,7 @@
  private:
   BitcastExpression(const BitcastExpression&) = delete;
 
-  typ::Type const type_;
+  ast::Type* const type_;
   Expression* const expr_;
 };
 
diff --git a/src/ast/bitcast_expression_test.cc b/src/ast/bitcast_expression_test.cc
index 573721c..c058768 100644
--- a/src/ast/bitcast_expression_test.cc
+++ b/src/ast/bitcast_expression_test.cc
@@ -27,7 +27,7 @@
   auto* expr = Expr("expr");
 
   auto* exp = create<BitcastExpression>(ty.f32(), expr);
-  ASSERT_EQ(exp->type(), ty.f32());
+  EXPECT_TRUE(exp->type()->Is<ast::F32>());
   ASSERT_EQ(exp->expr(), expr);
 }
 
diff --git a/src/ast/function.cc b/src/ast/function.cc
index ec7b2f0..24b0227 100644
--- a/src/ast/function.cc
+++ b/src/ast/function.cc
@@ -27,7 +27,7 @@
                    const Source& source,
                    Symbol symbol,
                    VariableList params,
-                   typ::Type return_type,
+                   ast::Type* return_type,
                    BlockStatement* body,
                    DecorationList decorations,
                    DecorationList return_type_decorations)
@@ -45,7 +45,7 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(param, program_id);
   }
   TINT_ASSERT(symbol_.IsValid());
-  TINT_ASSERT(return_type_.ast || return_type_.sem);
+  TINT_ASSERT(return_type_);
   for (auto* deco : decorations_) {
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(deco, program_id);
   }
@@ -81,7 +81,7 @@
   auto src = ctx->Clone(source());
   auto sym = ctx->Clone(symbol());
   auto p = ctx->Clone(params_);
-  auto ret = ctx->Clone(return_type_);
+  auto* ret = ctx->Clone(return_type_);
   auto* b = ctx->Clone(body_);
   auto decos = ctx->Clone(decorations_);
   auto ret_decos = ctx->Clone(return_type_decorations_);
@@ -92,9 +92,7 @@
                       std::ostream& out,
                       size_t indent) const {
   make_indent(out, indent);
-  out << "Function " << symbol_.to_str() << " -> "
-      << (return_type_.ast ? return_type_.ast->type_name()
-                           : return_type_.sem->type_name())
+  out << "Function " << symbol_.to_str() << " -> " << return_type_->type_name()
       << std::endl;
 
   for (auto* deco : decorations()) {
@@ -134,7 +132,7 @@
   for (auto* param : params_) {
     // No need for the sem::Variable here, functions params must have a
     // type
-    out << param->declared_type()->type_name();
+    out << param->type()->type_name();
   }
 
   return out.str();
diff --git a/src/ast/function.h b/src/ast/function.h
index 5a29133..fd91c7c 100644
--- a/src/ast/function.h
+++ b/src/ast/function.h
@@ -49,7 +49,7 @@
            const Source& source,
            Symbol symbol,
            VariableList params,
-           typ::Type return_type,
+           ast::Type* return_type,
            BlockStatement* body,
            DecorationList decorations,
            DecorationList return_type_decorations);
@@ -77,7 +77,7 @@
   bool IsEntryPoint() const { return pipeline_stage() != PipelineStage::kNone; }
 
   /// @returns the function return type.
-  typ::Type return_type() const { return return_type_; }
+  ast::Type* return_type() const { return return_type_; }
 
   /// @returns the decorations attached to the function return type.
   const DecorationList& return_type_decorations() const {
@@ -115,7 +115,7 @@
 
   Symbol const symbol_;
   VariableList const params_;
-  typ::Type const return_type_;
+  ast::Type* const return_type_;
   BlockStatement* const body_;
   DecorationList const decorations_;
   DecorationList const return_type_decorations_;
diff --git a/src/ast/function_test.cc b/src/ast/function_test.cc
index 151c259..60c1525 100644
--- a/src/ast/function_test.cc
+++ b/src/ast/function_test.cc
@@ -32,7 +32,7 @@
   auto* f = Func("func", params, ty.void_(), StatementList{}, DecorationList{});
   EXPECT_EQ(f->symbol(), Symbols().Get("func"));
   ASSERT_EQ(f->params().size(), 1u);
-  EXPECT_EQ(f->return_type(), ty.void_());
+  EXPECT_TRUE(f->return_type()->Is<ast::Void>());
   EXPECT_EQ(f->params()[0], var);
 }
 
diff --git a/src/ast/intrinsic_texture_helper_test.cc b/src/ast/intrinsic_texture_helper_test.cc
index 376182e..bc923ca 100644
--- a/src/ast/intrinsic_texture_helper_test.cc
+++ b/src/ast/intrinsic_texture_helper_test.cc
@@ -132,7 +132,7 @@
   return out;
 }
 
-typ::Type TextureOverloadCase::resultVectorComponentType(
+ast::Type* TextureOverloadCase::resultVectorComponentType(
     ProgramBuilder* b) const {
   switch (texture_data_type) {
     case ast::intrinsic::test::TextureDataType::kF32:
@@ -149,7 +149,7 @@
 
 ast::Variable* TextureOverloadCase::buildTextureVariable(
     ProgramBuilder* b) const {
-  auto datatype = resultVectorComponentType(b);
+  auto* datatype = resultVectorComponentType(b);
 
   DecorationList decos = {
       b->create<ast::GroupDecoration>(0),
@@ -166,10 +166,9 @@
                        ast::StorageClass::kUniformConstant, nullptr, decos);
 
     case ast::intrinsic::test::TextureKind::kMultisampled:
-      return b->Global(
-          "texture",
-          b->ty.multisampled_texture(texture_dimension, datatype),
-          ast::StorageClass::kUniformConstant, nullptr, decos);
+      return b->Global("texture",
+                       b->ty.multisampled_texture(texture_dimension, datatype),
+                       ast::StorageClass::kUniformConstant, nullptr, decos);
 
     case ast::intrinsic::test::TextureKind::kStorage: {
       auto st = b->ty.storage_texture(texture_dimension, image_format);
diff --git a/src/ast/intrinsic_texture_helper_test.h b/src/ast/intrinsic_texture_helper_test.h
index 886680b..0180efd 100644
--- a/src/ast/intrinsic_texture_helper_test.h
+++ b/src/ast/intrinsic_texture_helper_test.h
@@ -209,7 +209,7 @@
 
   /// @param builder the AST builder used for the test
   /// @returns the vector component type of the texture function return value
-  typ::Type resultVectorComponentType(ProgramBuilder* builder) const;
+  ast::Type* resultVectorComponentType(ProgramBuilder* builder) const;
   /// @param builder the AST builder used for the test
   /// @returns a variable holding the test texture, automatically registered as
   /// a global variable.
diff --git a/src/ast/module.cc b/src/ast/module.cc
index 463b2dd..2de195f 100644
--- a/src/ast/module.cc
+++ b/src/ast/module.cc
@@ -29,14 +29,14 @@
 
 Module::Module(ProgramID program_id,
                const Source& source,
-               std::vector<Cloneable*> global_decls)
+               std::vector<ast::Node*> global_decls)
     : Base(program_id, source), global_declarations_(std::move(global_decls)) {
   for (auto* decl : global_declarations_) {
     if (decl == nullptr) {
       continue;
     }
 
-    if (auto* ty = decl->As<sem::Type>()) {
+    if (auto* ty = decl->As<ast::NamedType>()) {
       constructed_types_.push_back(ty);
     } else if (auto* func = decl->As<Function>()) {
       functions_.push_back(func);
@@ -52,16 +52,34 @@
 Module::~Module() = default;
 
 const ast::NamedType* Module::LookupType(Symbol name) const {
-  for (auto ct : ConstructedTypes()) {
-    if (auto* ty = ct.ast->As<ast::NamedType>()) {
-      if (ty->name() == name) {
-        return ty;
-      }
+  for (auto* ty : ConstructedTypes()) {
+    if (ty->name() == name) {
+      return ty;
     }
   }
   return nullptr;
 }
 
+void Module::AddGlobalVariable(ast::Variable* var) {
+  TINT_ASSERT(var);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(var, program_id());
+  global_variables_.push_back(var);
+  global_declarations_.push_back(var);
+}
+
+void Module::AddConstructedType(ast::NamedType* type) {
+  TINT_ASSERT(type);
+  constructed_types_.push_back(type);
+  global_declarations_.push_back(type);
+}
+
+void Module::AddFunction(ast::Function* func) {
+  TINT_ASSERT(func);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(func, program_id());
+  functions_.push_back(func);
+  global_declarations_.push_back(func);
+}
+
 Module* Module::Clone(CloneContext* ctx) const {
   auto* out = ctx->dst->create<Module>();
   out->Copy(ctx, this);
@@ -74,7 +92,7 @@
       TINT_ICE(ctx->dst->Diagnostics()) << "src global declaration was nullptr";
       continue;
     }
-    if (auto* ty = decl->As<sem::Type>()) {
+    if (auto* ty = decl->As<ast::NamedType>()) {
       AddConstructedType(ty);
     } else if (auto* func = decl->As<Function>()) {
       AddFunction(func);
@@ -92,16 +110,16 @@
   make_indent(out, indent);
   out << "Module{" << std::endl;
   indent += 2;
-  for (auto const ty : constructed_types_) {
+  for (auto* ty : constructed_types_) {
     make_indent(out, indent);
-    if (auto* alias = ty->As<sem::Alias>()) {
+    if (auto* alias = ty->As<ast::Alias>()) {
       out << alias->symbol().to_str() << " -> " << alias->type()->type_name()
           << std::endl;
-      if (auto* str = alias->type()->As<sem::StructType>()) {
-        str->impl()->to_str(sem, out, indent);
+      if (auto* str = alias->type()->As<ast::Struct>()) {
+        str->to_str(sem, out, indent);
       }
-    } else if (auto* str = ty->As<sem::StructType>()) {
-      str->impl()->to_str(sem, out, indent);
+    } else if (auto* str = ty->As<ast::Struct>()) {
+      str->to_str(sem, out, indent);
     }
   }
   for (auto* var : global_variables_) {
diff --git a/src/ast/module.h b/src/ast/module.h
index 2b6b4dd..2ea6219 100644
--- a/src/ast/module.h
+++ b/src/ast/module.h
@@ -42,28 +42,23 @@
   /// the order they were declared in the source program
   Module(ProgramID program_id,
          const Source& source,
-         std::vector<Cloneable*> global_decls);
+         std::vector<ast::Node*> global_decls);
 
   /// Destructor
   ~Module() override;
 
   /// @returns the ordered global declarations for the translation unit
-  const std::vector<Cloneable*>& GlobalDeclarations() const {
+  const std::vector<ast::Node*>& GlobalDeclarations() const {
     return global_declarations_;
   }
 
   /// Add a global variable to the Builder
   /// @param var the variable to add
-  void AddGlobalVariable(ast::Variable* var) {
-    TINT_ASSERT(var);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(var, program_id());
-    global_variables_.push_back(var);
-    global_declarations_.push_back(var);
-  }
+  void AddGlobalVariable(ast::Variable* var);
 
   /// @returns true if the module has the global declaration `decl`
   /// @param decl the declaration to check
-  bool HasGlobalDeclaration(const Cloneable* decl) const {
+  bool HasGlobalDeclaration(ast::Node* decl) const {
     for (auto* d : global_declarations_) {
       if (d == decl) {
         return true;
@@ -79,31 +74,21 @@
   VariableList& GlobalVariables() { return global_variables_; }
 
   /// Adds a constructed type to the Builder.
-  /// The type must be an alias or a struct.
   /// @param type the constructed type to add
-  void AddConstructedType(typ::Type type) {
-    TINT_ASSERT(type);
-    constructed_types_.push_back(type);
-    global_declarations_.push_back(const_cast<sem::Type*>(type.sem));
-  }
+  void AddConstructedType(ast::NamedType* type);
 
   /// @returns the NamedType registered as a ConstructedType()
   /// @param name the name of the type to search for
   const ast::NamedType* LookupType(Symbol name) const;
 
   /// @returns the constructed types in the translation unit
-  const std::vector<typ::Type>& ConstructedTypes() const {
+  const std::vector<ast::NamedType*>& ConstructedTypes() const {
     return constructed_types_;
   }
 
   /// Add a function to the Builder
   /// @param func the function to add
-  void AddFunction(ast::Function* func) {
-    TINT_ASSERT(func);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(func, program_id());
-    functions_.push_back(func);
-    global_declarations_.push_back(func);
-  }
+  void AddFunction(ast::Function* func);
 
   /// @returns the functions declared in the translation unit
   const FunctionList& Functions() const { return functions_; }
@@ -132,8 +117,8 @@
   std::string to_str(const sem::Info& sem) const;
 
  private:
-  std::vector<Cloneable*> global_declarations_;
-  std::vector<typ::Type> constructed_types_;
+  std::vector<ast::Node*> global_declarations_;
+  std::vector<ast::NamedType*> constructed_types_;
   FunctionList functions_;
   VariableList global_variables_;
 };
diff --git a/src/ast/struct_member.cc b/src/ast/struct_member.cc
index ca4bde2..11fad7d 100644
--- a/src/ast/struct_member.cc
+++ b/src/ast/struct_member.cc
@@ -24,13 +24,13 @@
 StructMember::StructMember(ProgramID program_id,
                            const Source& source,
                            const Symbol& sym,
-                           typ::Type type,
+                           ast::Type* type,
                            DecorationList decorations)
     : Base(program_id, source),
       symbol_(sym),
       type_(type),
       decorations_(std::move(decorations)) {
-  TINT_ASSERT(type.ast || type.sem);
+  TINT_ASSERT(type);
   TINT_ASSERT(symbol_.IsValid());
   TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(symbol_, program_id);
   for (auto* deco : decorations_) {
@@ -59,7 +59,7 @@
   // Clone arguments outside of create() call to have deterministic ordering
   auto src = ctx->Clone(source());
   auto sym = ctx->Clone(symbol_);
-  auto ty = ctx->Clone(type_);
+  auto* ty = ctx->Clone(type_);
   auto decos = ctx->Clone(decorations_);
   return ctx->dst->create<StructMember>(src, sym, ty, decos);
 }
@@ -76,9 +76,7 @@
     out << "]] ";
   }
 
-  out << symbol_.to_str() << ": "
-      << (type_.ast ? type_.ast->type_name() : type_.sem->type_name()) << "}"
-      << std::endl;
+  out << symbol_.to_str() << ": " << type_->type_name() << "}" << std::endl;
 }
 
 }  // namespace ast
diff --git a/src/ast/struct_member.h b/src/ast/struct_member.h
index cd8408d..4ed141d 100644
--- a/src/ast/struct_member.h
+++ b/src/ast/struct_member.h
@@ -36,7 +36,7 @@
   StructMember(ProgramID program_id,
                const Source& source,
                const Symbol& sym,
-               typ::Type type,
+               ast::Type* type,
                DecorationList decorations);
   /// Move constructor
   StructMember(StructMember&&);
@@ -47,7 +47,7 @@
   const Symbol& symbol() const { return symbol_; }
 
   /// @returns the type
-  typ::Type type() const { return type_; }
+  ast::Type* type() const { return type_; }
 
   /// @returns the decorations
   const DecorationList& decorations() const { return decorations_; }
@@ -75,7 +75,7 @@
   StructMember(const StructMember&) = delete;
 
   Symbol const symbol_;
-  typ::Type const type_;
+  ast::Type* const type_;
   DecorationList const decorations_;
 };
 
diff --git a/src/ast/struct_member_test.cc b/src/ast/struct_member_test.cc
index d5570a0..c0d3041 100644
--- a/src/ast/struct_member_test.cc
+++ b/src/ast/struct_member_test.cc
@@ -24,7 +24,7 @@
 TEST_F(StructMemberTest, Creation) {
   auto* st = Member("a", ty.i32(), {MemberSize(4)});
   EXPECT_EQ(st->symbol(), Symbol(1, ID()));
-  EXPECT_EQ(st->type(), ty.i32());
+  EXPECT_TRUE(st->type()->Is<ast::I32>());
   EXPECT_EQ(st->decorations().size(), 1u);
   EXPECT_TRUE(st->decorations()[0]->Is<StructMemberSizeDecoration>());
   EXPECT_EQ(st->source().range.begin.line, 0u);
@@ -38,7 +38,7 @@
       Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 8}}},
       "a", ty.i32());
   EXPECT_EQ(st->symbol(), Symbol(1, ID()));
-  EXPECT_EQ(st->type(), ty.i32());
+  EXPECT_TRUE(st->type()->Is<ast::I32>());
   EXPECT_EQ(st->decorations().size(), 0u);
   EXPECT_EQ(st->source().range.begin.line, 27u);
   EXPECT_EQ(st->source().range.begin.column, 4u);
diff --git a/src/ast/type_constructor_expression.cc b/src/ast/type_constructor_expression.cc
index 51deb0f..d0877bf 100644
--- a/src/ast/type_constructor_expression.cc
+++ b/src/ast/type_constructor_expression.cc
@@ -23,10 +23,10 @@
 
 TypeConstructorExpression::TypeConstructorExpression(ProgramID program_id,
                                                      const Source& source,
-                                                     typ::Type type,
+                                                     ast::Type* type,
                                                      ExpressionList values)
     : Base(program_id, source), type_(type), values_(std::move(values)) {
-  TINT_ASSERT(type_.ast || type_.sem);
+  TINT_ASSERT(type_);
   for (auto* val : values_) {
     TINT_ASSERT(val);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(val, program_id);
@@ -42,7 +42,7 @@
     CloneContext* ctx) const {
   // Clone arguments outside of create() call to have deterministic ordering
   auto src = ctx->Clone(source());
-  auto ty = ctx->Clone(type());
+  auto* ty = ctx->Clone(type());
   auto vals = ctx->Clone(values());
   return ctx->dst->create<TypeConstructorExpression>(src, ty, vals);
 }
@@ -53,8 +53,7 @@
   make_indent(out, indent);
   out << "TypeConstructor[" << result_type_str(sem) << "]{" << std::endl;
   make_indent(out, indent + 2);
-  out << (type_.ast ? type_.ast->type_name() : type_.sem->type_name())
-      << std::endl;
+  out << type_->type_name() << std::endl;
 
   for (auto* val : values_) {
     val->to_str(sem, out, indent + 2);
diff --git a/src/ast/type_constructor_expression.h b/src/ast/type_constructor_expression.h
index 6b89a6b..19560a4 100644
--- a/src/ast/type_constructor_expression.h
+++ b/src/ast/type_constructor_expression.h
@@ -33,14 +33,14 @@
   /// @param values the constructor values
   TypeConstructorExpression(ProgramID program_id,
                             const Source& source,
-                            typ::Type type,
+                            ast::Type* type,
                             ExpressionList values);
   /// Move constructor
   TypeConstructorExpression(TypeConstructorExpression&&);
   ~TypeConstructorExpression() override;
 
   /// @returns the type
-  typ::Type type() const { return type_; }
+  ast::Type* type() const { return type_; }
 
   /// @returns the values
   const ExpressionList& values() const { return values_; }
@@ -62,7 +62,7 @@
  private:
   TypeConstructorExpression(const TypeConstructorExpression&) = delete;
 
-  typ::Type const type_;
+  ast::Type* const type_;
   ExpressionList const values_;
 };
 
diff --git a/src/ast/type_constructor_expression_test.cc b/src/ast/type_constructor_expression_test.cc
index 73c61ea..b526fcf 100644
--- a/src/ast/type_constructor_expression_test.cc
+++ b/src/ast/type_constructor_expression_test.cc
@@ -26,7 +26,7 @@
   expr.push_back(Expr("expr"));
 
   auto* t = create<TypeConstructorExpression>(ty.f32(), expr);
-  EXPECT_EQ(t->type(), ty.f32());
+  EXPECT_TRUE(t->type()->Is<ast::F32>());
   ASSERT_EQ(t->values().size(), 1u);
   EXPECT_EQ(t->values()[0], expr[0]);
 }
diff --git a/src/ast/variable.cc b/src/ast/variable.cc
index 9d62164..91a21ba 100644
--- a/src/ast/variable.cc
+++ b/src/ast/variable.cc
@@ -27,7 +27,7 @@
                    const Source& source,
                    const Symbol& sym,
                    StorageClass declared_storage_class,
-                   const typ::Type type,
+                   ast::Type* type,
                    bool is_const,
                    Expression* constructor,
                    DecorationList decorations)
@@ -41,7 +41,7 @@
   TINT_ASSERT(symbol_.IsValid());
   TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(symbol_, program_id);
   // no type means we must have a constructor to infer it
-  TINT_ASSERT(type_.ast || type_.sem || constructor);
+  TINT_ASSERT(type_ || constructor);
   TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(constructor, program_id);
 }
 
@@ -73,7 +73,7 @@
 Variable* Variable::Clone(CloneContext* ctx) const {
   auto src = ctx->Clone(source());
   auto sym = ctx->Clone(symbol());
-  auto ty = ctx->Clone(type());
+  auto* ty = ctx->Clone(type());
   auto* ctor = ctx->Clone(constructor());
   auto decos = ctx->Clone(decorations());
   return ctx->dst->create<Variable>(src, sym, declared_storage_class(), ty,
@@ -90,8 +90,7 @@
   out << (var_sem ? var_sem->StorageClass() : declared_storage_class())
       << std::endl;
   make_indent(out, indent);
-  out << (type_.sem ? type_.sem->type_name() : type_.ast->type_name())
-      << std::endl;
+  out << type_->type_name() << std::endl;
 }
 
 void Variable::constructor_to_str(const sem::Info& sem,
diff --git a/src/ast/variable.h b/src/ast/variable.h
index 66c3cd7..6e8069f 100644
--- a/src/ast/variable.h
+++ b/src/ast/variable.h
@@ -109,7 +109,7 @@
            const Source& source,
            const Symbol& sym,
            StorageClass declared_storage_class,
-           typ::Type type,
+           ast::Type* type,
            bool is_const,
            Expression* constructor,
            DecorationList decorations);
@@ -121,12 +121,8 @@
   /// @returns the variable symbol
   const Symbol& symbol() const { return symbol_; }
 
-  /// @returns the declared type
-  // TODO(crbug.com/tint/697): Remove and use type() instead
-  sem::Type* declared_type() const { return const_cast<sem::Type*>(type_.sem); }
-
   /// @returns the variable type
-  typ::Type type() const { return type_; }
+  ast::Type* type() const { return type_; }
 
   /// @returns the declared storage class
   StorageClass declared_storage_class() const {
@@ -185,7 +181,7 @@
 
   Symbol const symbol_;
   // The value type if a const or formal paramter, and the store type if a var
-  typ::Type const type_;
+  ast::Type* const type_;
   bool const is_const_;
   Expression* const constructor_;
   DecorationList const decorations_;
diff --git a/src/ast/variable_test.cc b/src/ast/variable_test.cc
index a837701..9bb3afb 100644
--- a/src/ast/variable_test.cc
+++ b/src/ast/variable_test.cc
@@ -27,7 +27,7 @@
 
   EXPECT_EQ(v->symbol(), Symbol(1, ID()));
   EXPECT_EQ(v->declared_storage_class(), StorageClass::kFunction);
-  EXPECT_EQ(v->declared_type(), ty.i32());
+  EXPECT_TRUE(v->type()->Is<ast::I32>());
   EXPECT_EQ(v->source().range.begin.line, 0u);
   EXPECT_EQ(v->source().range.begin.column, 0u);
   EXPECT_EQ(v->source().range.end.line, 0u);
@@ -41,7 +41,7 @@
 
   EXPECT_EQ(v->symbol(), Symbol(1, ID()));
   EXPECT_EQ(v->declared_storage_class(), StorageClass::kPrivate);
-  EXPECT_EQ(v->declared_type(), ty.f32());
+  EXPECT_TRUE(v->type()->Is<ast::F32>());
   EXPECT_EQ(v->source().range.begin.line, 27u);
   EXPECT_EQ(v->source().range.begin.column, 4u);
   EXPECT_EQ(v->source().range.end.line, 27u);
@@ -55,7 +55,7 @@
 
   EXPECT_EQ(v->symbol(), Symbol(1, ID()));
   EXPECT_EQ(v->declared_storage_class(), StorageClass::kWorkgroup);
-  EXPECT_EQ(v->declared_type(), ty.i32());
+  EXPECT_TRUE(v->type()->Is<ast::I32>());
   EXPECT_EQ(v->source().range.begin.line, 27u);
   EXPECT_EQ(v->source().range.begin.column, 4u);
   EXPECT_EQ(v->source().range.end.line, 27u);
diff --git a/src/clone_context.h b/src/clone_context.h
index 2623f63..40017a5 100644
--- a/src/clone_context.h
+++ b/src/clone_context.h
@@ -190,12 +190,12 @@
     return CheckedCast<T>(c);
   }
 
-  /// Clones the type pair
+  /// Clones the AST node of the type pair
   /// @param tp the type pair to clone
-  /// @return the cloned type pair
+  /// @return the cloned AST node wrapped in a type pair
   template <typename AST, typename SEM>
   typ::TypePair<AST, SEM> Clone(const typ::TypePair<AST, SEM>& tp) {
-    return Clone(const_cast<sem::Type*>(tp.sem));
+    return Clone(const_cast<ast::Type*>(tp.ast));
   }
 
   /// Clones the Source `s` into #dst
diff --git a/src/inspector/inspector.cc b/src/inspector/inspector.cc
index 578c07f..68442e9 100644
--- a/src/inspector/inspector.cc
+++ b/src/inspector/inspector.cc
@@ -194,6 +194,8 @@
       continue;
     }
 
+    auto* sem = program_->Sem().Get(func);
+
     EntryPoint entry_point;
     entry_point.name = program_->Symbols().NameFor(func->symbol());
     entry_point.remapped_name = program_->Symbols().NameFor(func->symbol());
@@ -201,20 +203,21 @@
     std::tie(entry_point.workgroup_size_x, entry_point.workgroup_size_y,
              entry_point.workgroup_size_z) = func->workgroup_size();
 
-    for (auto* param : func->params()) {
-      AddEntryPointInOutVariables(program_->Symbols().NameFor(param->symbol()),
-                                  param->declared_type(), param->decorations(),
-                                  entry_point.input_variables);
+    for (auto* param : sem->Parameters()) {
+      AddEntryPointInOutVariables(
+          program_->Symbols().NameFor(param->Declaration()->symbol()),
+          param->Type(), param->Declaration()->decorations(),
+          entry_point.input_variables);
     }
 
-    if (!func->return_type()->Is<sem::Void>()) {
-      AddEntryPointInOutVariables("<retval>", func->return_type(),
+    if (!sem->ReturnType()->Is<sem::Void>()) {
+      AddEntryPointInOutVariables("<retval>", sem->ReturnType(),
                                   func->return_type_decorations(),
                                   entry_point.output_variables);
     }
 
     // TODO(crbug.com/tint/697): Remove this.
-    for (auto* var : program_->Sem().Get(func)->ReferencedModuleVariables()) {
+    for (auto* var : sem->ReferencedModuleVariables()) {
       auto* decl = var->Declaration();
 
       auto name = program_->Symbols().NameFor(decl->symbol());
@@ -553,10 +556,12 @@
 
   if (auto* struct_ty = unwrapped_type->As<sem::StructType>()) {
     // Recurse into members.
-    for (auto* member : struct_ty->impl()->members()) {
+    auto* sem = program_->Sem().Get(struct_ty);
+    for (auto* member : sem->Members()) {
       AddEntryPointInOutVariables(
-          name + "." + program_->Symbols().NameFor(member->symbol()),
-          member->type(), member->decorations(), variables);
+          name + "." +
+              program_->Symbols().NameFor(member->Declaration()->symbol()),
+          member->Type(), member->Declaration()->decorations(), variables);
     }
     return;
   }
diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc
index ff4eb84..0d12013 100644
--- a/src/inspector/inspector_test.cc
+++ b/src/inspector/inspector_test.cc
@@ -144,7 +144,7 @@
   /// @param val value to initialize the variable with, if NULL no initializer
   ///            will be added.
   template <class T>
-  void AddConstantID(std::string name, uint32_t id, typ::Type type, T* val) {
+  void AddConstantID(std::string name, uint32_t id, ast::Type* type, T* val) {
     ast::Expression* constructor = nullptr;
     if (val) {
       constructor = Expr(*val);
@@ -172,9 +172,8 @@
   /// @param idx index of member
   /// @param type type of member
   /// @returns a string for the member
-  std::string StructMemberName(size_t idx, typ::Type type) {
-    return std::to_string(idx) +
-           (type.sem ? type.sem->type_name() : type.ast->type_name());
+  std::string StructMemberName(size_t idx, ast::Type* type) {
+    return std::to_string(idx) + type->type_name();
   }
 
   /// Generates a struct type
@@ -182,11 +181,11 @@
   /// @param member_types a vector of member types
   /// @param is_block whether or not to decorate as a Block
   /// @returns a struct type
-  typ::Struct MakeStructType(const std::string& name,
-                             std::vector<typ::Type> member_types,
-                             bool is_block) {
+  ast::Struct* MakeStructType(const std::string& name,
+                              std::vector<ast::Type*> member_types,
+                              bool is_block) {
     ast::StructMemberList members;
-    for (auto type : member_types) {
+    for (auto* type : member_types) {
       members.push_back(Member(StructMemberName(members.size(), type), type));
     }
 
@@ -202,37 +201,37 @@
   /// @param name name for the type
   /// @param member_types a vector of member types
   /// @returns a struct type that has the layout for an uniform buffer.
-  typ::Struct MakeUniformBufferType(const std::string& name,
-                                    std::vector<typ::Type> member_types) {
+  ast::Struct* MakeUniformBufferType(const std::string& name,
+                                     std::vector<ast::Type*> member_types) {
     return MakeStructType(name, member_types, true);
   }
 
   /// Generates types appropriate for using in a storage buffer
   /// @param name name for the type
   /// @param member_types a vector of member types
-  /// @returns a tuple {struct type, access control type}, where the struct has
-  ///          the layout for a storage buffer, and the control type wraps the
-  ///          struct.
-  std::tuple<typ::Struct, typ::AccessControl> MakeStorageBufferTypes(
+  /// @returns a function that returns an ast::AccessControl to the created
+  /// structure.
+  std::function<ast::AccessControl*()> MakeStorageBufferTypes(
       const std::string& name,
-      std::vector<typ::Type> member_types) {
-    auto struct_type = MakeStructType(name, member_types, true);
-    auto access_type = ty.access(ast::AccessControl::kReadWrite, struct_type);
-    return {struct_type, std::move(access_type)};
+      std::vector<ast::Type*> member_types) {
+    MakeStructType(name, member_types, true);
+    return [this, name] {
+      return ty.access(ast::AccessControl::kReadWrite, ty.type_name(name));
+    };
   }
 
   /// Generates types appropriate for using in a read-only storage buffer
   /// @param name name for the type
   /// @param member_types a vector of member types
-  /// @returns a tuple {struct type, access control type}, where the struct has
-  ///          the layout for a read-only storage buffer, and the control type
-  ///          wraps the struct.
-  std::tuple<typ::Struct, typ::AccessControl> MakeReadOnlyStorageBufferTypes(
+  /// @returns a function that returns an ast::AccessControl to the created
+  /// structure.
+  std::function<ast::AccessControl*()> MakeReadOnlyStorageBufferTypes(
       const std::string& name,
-      std::vector<typ::Type> member_types) {
-    auto struct_type = MakeStructType(name, member_types, true);
-    auto access_type = ty.access(ast::AccessControl::kReadOnly, struct_type);
-    return {struct_type, std::move(access_type)};
+      std::vector<ast::Type*> member_types) {
+    MakeStructType(name, member_types, true);
+    return [this, name] {
+      return ty.access(ast::AccessControl::kReadOnly, ty.type_name(name));
+    };
   }
 
   /// Adds a binding variable with a struct type to the program
@@ -242,7 +241,7 @@
   /// @param group the binding and group to use for the uniform buffer
   /// @param binding the binding number to use for the uniform buffer
   void AddBinding(const std::string& name,
-                  typ::Type type,
+                  ast::Type* type,
                   ast::StorageClass storage_class,
                   uint32_t group,
                   uint32_t binding) {
@@ -259,7 +258,7 @@
   /// @param group the binding/group/ to use for the uniform buffer
   /// @param binding the binding number to use for the uniform buffer
   void AddUniformBuffer(const std::string& name,
-                        typ::Type type,
+                        ast::Type* type,
                         uint32_t group,
                         uint32_t binding) {
     AddBinding(name, type, ast::StorageClass::kUniform, group, binding);
@@ -271,7 +270,7 @@
   /// @param group the binding/group to use for the storage buffer
   /// @param binding the binding number to use for the storage buffer
   void AddStorageBuffer(const std::string& name,
-                        typ::Type type,
+                        ast::Type* type,
                         uint32_t group,
                         uint32_t binding) {
     AddBinding(name, type, ast::StorageClass::kStorage, group, binding);
@@ -284,11 +283,11 @@
   void MakeStructVariableReferenceBodyFunction(
       std::string func_name,
       std::string struct_name,
-      std::vector<std::tuple<size_t, typ::Type>> members) {
+      std::vector<std::tuple<size_t, ast::Type*>> members) {
     ast::StatementList stmts;
     for (auto member : members) {
       size_t member_idx;
-      typ::Type member_type;
+      ast::Type* member_type;
       std::tie(member_idx, member_type) = member;
       std::string member_name = StructMemberName(member_idx, member_type);
 
@@ -298,7 +297,7 @@
 
     for (auto member : members) {
       size_t member_idx;
-      typ::Type member_type;
+      ast::Type* member_type;
       std::tie(member_idx, member_type) = member;
       std::string member_name = StructMemberName(member_idx, member_type);
 
@@ -337,7 +336,7 @@
   /// @param type the data type of the sampled texture
   /// @returns the generated SampleTextureType
   typ::SampledTexture MakeSampledTextureType(ast::TextureDimension dim,
-                                             typ::Type type) {
+                                             ast::Type* type) {
     return ty.sampled_texture(dim, type);
   }
 
@@ -354,7 +353,7 @@
   /// @returns the generated SampleTextureType
   typ::MultisampledTexture MakeMultisampledTextureType(
       ast::TextureDimension dim,
-      typ::Type type) {
+      ast::Type* type) {
     return ty.multisampled_texture(dim, type);
   }
 
@@ -364,7 +363,7 @@
   /// @param group the binding/group to use for the sampled texture
   /// @param binding the binding number to use for the sampled texture
   void AddSampledTexture(const std::string& name,
-                         typ::Type type,
+                         ast::Type* type,
                          uint32_t group,
                          uint32_t binding) {
     AddBinding(name, type, ast::StorageClass::kUniformConstant, group, binding);
@@ -376,13 +375,13 @@
   /// @param group the binding/group to use for the multi-sampled texture
   /// @param binding the binding number to use for the multi-sampled texture
   void AddMultisampledTexture(const std::string& name,
-                              typ::Type type,
+                              ast::Type* type,
                               uint32_t group,
                               uint32_t binding) {
     AddBinding(name, type, ast::StorageClass::kUniformConstant, group, binding);
   }
 
-  void AddGlobalVariable(const std::string& name, typ::Type type) {
+  void AddGlobalVariable(const std::string& name, ast::Type* type) {
     Global(name, type, ast::StorageClass::kUniformConstant);
   }
 
@@ -392,7 +391,7 @@
   /// @param group the binding/group to use for the depth texture
   /// @param binding the binding number to use for the depth texture
   void AddDepthTexture(const std::string& name,
-                       typ::Type type,
+                       ast::Type* type,
                        uint32_t group,
                        uint32_t binding) {
     AddBinding(name, type, ast::StorageClass::kUniformConstant, group, binding);
@@ -411,7 +410,7 @@
       const std::string& texture_name,
       const std::string& sampler_name,
       const std::string& coords_name,
-      typ::Type base_type,
+      ast::Type* base_type,
       ast::DecorationList decorations) {
     std::string result_name = "sampler_result";
 
@@ -442,7 +441,7 @@
       const std::string& sampler_name,
       const std::string& coords_name,
       const std::string& array_index,
-      typ::Type base_type,
+      ast::Type* base_type,
       ast::DecorationList decorations) {
     std::string result_name = "sampler_result";
 
@@ -475,7 +474,7 @@
       const std::string& sampler_name,
       const std::string& coords_name,
       const std::string& depth_name,
-      typ::Type base_type,
+      ast::Type* base_type,
       ast::DecorationList decorations) {
     std::string result_name = "sampler_result";
 
@@ -494,7 +493,7 @@
   /// Gets an appropriate type for the data in a given texture type.
   /// @param sampled_kind type of in the texture
   /// @returns a pointer to a type appropriate for the coord param
-  typ::Type GetBaseType(ResourceBinding::SampledKind sampled_kind) {
+  ast::Type* GetBaseType(ResourceBinding::SampledKind sampled_kind) {
     switch (sampled_kind) {
       case ResourceBinding::SampledKind::kFloat:
         return ty.f32();
@@ -512,17 +511,17 @@
   /// @param dim dimensionality of the texture being sampled
   /// @param scalar the scalar type
   /// @returns a pointer to a type appropriate for the coord param
-  typ::Type GetCoordsType(ast::TextureDimension dim, typ::Type scalar) {
+  ast::Type* GetCoordsType(ast::TextureDimension dim, ast::Type* scalar) {
     switch (dim) {
       case ast::TextureDimension::k1d:
         return scalar;
       case ast::TextureDimension::k2d:
       case ast::TextureDimension::k2dArray:
-        return create<sem::Vector>(scalar, 2);
+        return create<ast::Vector>(scalar, 2);
       case ast::TextureDimension::k3d:
       case ast::TextureDimension::kCube:
       case ast::TextureDimension::kCubeArray:
-        return create<sem::Vector>(scalar, 3);
+        return create<ast::Vector>(scalar, 3);
       default:
         [=]() { FAIL() << "Unsupported texture dimension: " << dim; }();
     }
@@ -604,8 +603,10 @@
     return *inspector_;
   }
 
-  typ::Sampler sampler_type() { return ty.sampler(ast::SamplerKind::kSampler); }
-  typ::Sampler comparison_sampler_type() {
+  ast::Sampler* sampler_type() {
+    return ty.sampler(ast::SamplerKind::kSampler);
+  }
+  ast::Sampler* comparison_sampler_type() {
     return ty.sampler(ast::SamplerKind::kComparisonSampler);
   }
 
@@ -835,23 +836,23 @@
 
 TEST_P(InspectorGetEntryPointTestWithComponentTypeParam, InOutVariables) {
   ComponentType inspector_type = GetParam();
-  typ::Type tint_type = nullptr;
+  std::function<typ::Type()> tint_type;
   switch (inspector_type) {
     case ComponentType::kFloat:
-      tint_type = ty.f32();
+      tint_type = [this]() -> typ::Type { return ty.f32(); };
       break;
     case ComponentType::kSInt:
-      tint_type = ty.i32();
+      tint_type = [this]() -> typ::Type { return ty.i32(); };
       break;
     case ComponentType::kUInt:
-      tint_type = ty.u32();
+      tint_type = [this]() -> typ::Type { return ty.u32(); };
       break;
     case ComponentType::kUnknown:
       return;
   }
 
-  auto* in_var = Param("in_var", tint_type, {Location(0u)});
-  Func("foo", {in_var}, tint_type, {Return("in_var")},
+  auto* in_var = Param("in_var", tint_type(), {Location(0u)});
+  Func("foo", {in_var}, tint_type(), {Return("in_var")},
        {Stage(ast::PipelineStage::kFragment)}, {Location(0u)});
   Inspector& inspector = Build();
 
@@ -1600,18 +1601,12 @@
   AddUniformBuffer("ub_var", ub_struct_type, 0, 0);
   MakeStructVariableReferenceBodyFunction("ub_func", "ub_var", {{0, ty.i32()}});
 
-  typ::Struct sb_struct_type;
-  typ::AccessControl sb_control_type;
-  std::tie(sb_struct_type, sb_control_type) =
-      MakeStorageBufferTypes("sb_type", {ty.i32()});
-  AddStorageBuffer("sb_var", sb_control_type, 1, 0);
+  auto sb = MakeStorageBufferTypes("sb_type", {ty.i32()});
+  AddStorageBuffer("sb_var", sb(), 1, 0);
   MakeStructVariableReferenceBodyFunction("sb_func", "sb_var", {{0, ty.i32()}});
 
-  typ::Struct rosb_struct_type;
-  typ::AccessControl rosb_control_type;
-  std::tie(rosb_struct_type, rosb_control_type) =
-      MakeReadOnlyStorageBufferTypes("rosb_type", {ty.i32()});
-  AddStorageBuffer("rosb_var", rosb_control_type, 1, 1);
+  auto ro_sb = MakeReadOnlyStorageBufferTypes("rosb_type", {ty.i32()});
+  AddStorageBuffer("rosb_var", ro_sb(), 1, 1);
   MakeStructVariableReferenceBodyFunction("rosb_func", "rosb_var",
                                           {{0, ty.i32()}});
 
@@ -1896,11 +1891,8 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, Simple) {
-  typ::Struct foo_struct_type;
-  typ::AccessControl foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) =
-      MakeStorageBufferTypes("foo_type", {ty.i32()});
-  AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
+  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
+  AddStorageBuffer("foo_sb", foo_struct_type(), 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
 
@@ -1924,11 +1916,12 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleMembers) {
-  typ::Struct foo_struct_type;
-  typ::AccessControl foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) =
-      MakeStorageBufferTypes("foo_type", {ty.i32(), ty.u32(), ty.f32()});
-  AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
+  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {
+                                                                ty.i32(),
+                                                                ty.u32(),
+                                                                ty.f32(),
+                                                            });
+  AddStorageBuffer("foo_sb", foo_struct_type(), 0, 0);
 
   MakeStructVariableReferenceBodyFunction(
       "sb_func", "foo_sb", {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}});
@@ -1953,13 +1946,14 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleStorageBuffers) {
-  typ::Struct sb_struct_type;
-  typ::AccessControl sb_control_type;
-  std::tie(sb_struct_type, sb_control_type) =
-      MakeStorageBufferTypes("sb_type", {ty.i32(), ty.u32(), ty.f32()});
-  AddStorageBuffer("sb_foo", sb_control_type, 0, 0);
-  AddStorageBuffer("sb_bar", sb_control_type, 0, 1);
-  AddStorageBuffer("sb_baz", sb_control_type, 2, 0);
+  auto sb_struct_type = MakeStorageBufferTypes("sb_type", {
+                                                              ty.i32(),
+                                                              ty.u32(),
+                                                              ty.f32(),
+                                                          });
+  AddStorageBuffer("sb_foo", sb_struct_type(), 0, 0);
+  AddStorageBuffer("sb_bar", sb_struct_type(), 0, 1);
+  AddStorageBuffer("sb_baz", sb_struct_type(), 2, 0);
 
   auto AddReferenceFunc = [this](const std::string& func_name,
                                  const std::string& var_name) {
@@ -2014,11 +2008,9 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingArray) {
-  typ::Struct foo_struct_type;
-  typ::AccessControl foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) =
+  auto foo_struct_type =
       MakeStorageBufferTypes("foo_type", {ty.i32(), ty.array<u32, 4>()});
-  AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
+  AddStorageBuffer("foo_sb", foo_struct_type(), 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
 
@@ -2042,11 +2034,11 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingRuntimeArray) {
-  typ::Struct foo_struct_type;
-  typ::AccessControl foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) =
-      MakeStorageBufferTypes("foo_type", {ty.i32(), ty.array<u32>()});
-  AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
+  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {
+                                                                ty.i32(),
+                                                                ty.array<u32>(),
+                                                            });
+  AddStorageBuffer("foo_sb", foo_struct_type(), 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
 
@@ -2070,11 +2062,8 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingPadding) {
-  typ::Struct foo_struct_type;
-  typ::AccessControl foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) =
-      MakeStorageBufferTypes("foo_type", {ty.vec3<f32>()});
-  AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
+  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.vec3<f32>()});
+  AddStorageBuffer("foo_sb", foo_struct_type(), 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb",
                                           {{0, ty.vec3<f32>()}});
@@ -2099,11 +2088,8 @@
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, SkipReadOnly) {
-  typ::Struct foo_struct_type;
-  typ::AccessControl foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) =
-      MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32()});
-  AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
+  auto foo_struct_type = MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32()});
+  AddStorageBuffer("foo_sb", foo_struct_type(), 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
 
@@ -2120,11 +2106,8 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, Simple) {
-  typ::Struct foo_struct_type;
-  typ::AccessControl foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) =
-      MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32()});
-  AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
+  auto foo_struct_type = MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32()});
+  AddStorageBuffer("foo_sb", foo_struct_type(), 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
 
@@ -2149,13 +2132,14 @@
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest,
        MultipleStorageBuffers) {
-  typ::Struct sb_struct_type;
-  typ::AccessControl sb_control_type;
-  std::tie(sb_struct_type, sb_control_type) =
-      MakeReadOnlyStorageBufferTypes("sb_type", {ty.i32(), ty.u32(), ty.f32()});
-  AddStorageBuffer("sb_foo", sb_control_type, 0, 0);
-  AddStorageBuffer("sb_bar", sb_control_type, 0, 1);
-  AddStorageBuffer("sb_baz", sb_control_type, 2, 0);
+  auto sb_struct_type = MakeReadOnlyStorageBufferTypes("sb_type", {
+                                                                      ty.i32(),
+                                                                      ty.u32(),
+                                                                      ty.f32(),
+                                                                  });
+  AddStorageBuffer("sb_foo", sb_struct_type(), 0, 0);
+  AddStorageBuffer("sb_bar", sb_struct_type(), 0, 1);
+  AddStorageBuffer("sb_baz", sb_struct_type(), 2, 0);
 
   auto AddReferenceFunc = [this](const std::string& func_name,
                                  const std::string& var_name) {
@@ -2210,11 +2194,12 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, ContainingArray) {
-  typ::Struct foo_struct_type;
-  typ::AccessControl foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) = MakeReadOnlyStorageBufferTypes(
-      "foo_type", {ty.i32(), ty.array<u32, 4>()});
-  AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
+  auto foo_struct_type =
+      MakeReadOnlyStorageBufferTypes("foo_type", {
+                                                     ty.i32(),
+                                                     ty.array<u32, 4>(),
+                                                 });
+  AddStorageBuffer("foo_sb", foo_struct_type(), 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
 
@@ -2239,11 +2224,12 @@
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest,
        ContainingRuntimeArray) {
-  typ::Struct foo_struct_type;
-  typ::AccessControl foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) =
-      MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32(), ty.array<u32>()});
-  AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
+  auto foo_struct_type =
+      MakeReadOnlyStorageBufferTypes("foo_type", {
+                                                     ty.i32(),
+                                                     ty.array<u32>(),
+                                                 });
+  AddStorageBuffer("foo_sb", foo_struct_type(), 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
 
@@ -2267,11 +2253,8 @@
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, SkipNonReadOnly) {
-  typ::Struct foo_struct_type;
-  typ::AccessControl foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) =
-      MakeStorageBufferTypes("foo_type", {ty.i32()});
-  AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
+  auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()});
+  AddStorageBuffer("foo_sb", foo_struct_type(), 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
 
@@ -2514,7 +2497,7 @@
       GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
   AddSampledTexture("foo_texture", sampled_texture_type, 0, 0);
   AddSampler("foo_sampler", 0, 1);
-  auto coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
+  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
   AddGlobalVariable("foo_coords", coord_type);
 
   MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler",
@@ -2572,7 +2555,7 @@
       GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
   AddSampledTexture("foo_texture", sampled_texture_type, 0, 0);
   AddSampler("foo_sampler", 0, 1);
-  auto coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
+  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
   AddGlobalVariable("foo_coords", coord_type);
   AddGlobalVariable("foo_array_index", ty.i32());
 
@@ -2615,7 +2598,7 @@
   auto multisampled_texture_type = MakeMultisampledTextureType(
       GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
   AddMultisampledTexture("foo_texture", multisampled_texture_type, 0, 0);
-  auto coord_type = GetCoordsType(GetParam().type_dim, ty.i32());
+  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.i32());
   AddGlobalVariable("foo_coords", coord_type);
   AddGlobalVariable("foo_sample_index", ty.i32());
 
@@ -2633,9 +2616,9 @@
   auto result = inspector.GetMultisampledTextureResourceBindings("ep");
   ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
+  ASSERT_EQ(1u, result.size());
   EXPECT_EQ(ResourceBinding::ResourceType::kMultisampledTexture,
             result[0].resource_type);
-  ASSERT_EQ(1u, result.size());
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
   EXPECT_EQ(GetParam().inspector_dim, result[0].dim);
@@ -2685,7 +2668,7 @@
       GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
   AddMultisampledTexture("foo_texture", multisampled_texture_type, 0, 0);
   AddSampler("foo_sampler", 0, 1);
-  auto coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
+  auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
   AddGlobalVariable("foo_coords", coord_type);
   AddGlobalVariable("foo_array_index", ty.i32());
 
diff --git a/src/program_builder.cc b/src/program_builder.cc
index 47dbd99..d270cc7 100644
--- a/src/program_builder.cc
+++ b/src/program_builder.cc
@@ -16,7 +16,6 @@
 
 #include "src/ast/assignment_statement.h"
 #include "src/ast/call_statement.h"
-#include "src/ast/type_name.h"
 #include "src/ast/variable_decl_statement.h"
 #include "src/debug.h"
 #include "src/demangler.h"
@@ -96,51 +95,62 @@
 }
 
 ast::ConstructorExpression* ProgramBuilder::ConstructValueFilledWith(
-    typ::Type type,
+    const ast::Type* type,
     int elem_value) {
-  auto* unwrapped_type = type->UnwrapAliasIfNeeded();
-  if (unwrapped_type->Is<sem::Bool>()) {
+  CloneContext ctx(this);
+
+  if (type->Is<ast::Bool>()) {
     return create<ast::ScalarConstructorExpression>(
         create<ast::BoolLiteral>(elem_value == 0 ? false : true));
   }
-  if (unwrapped_type->Is<sem::I32>()) {
+  if (type->Is<ast::I32>()) {
     return create<ast::ScalarConstructorExpression>(
         create<ast::SintLiteral>(static_cast<i32>(elem_value)));
   }
-  if (unwrapped_type->Is<sem::U32>()) {
+  if (type->Is<ast::U32>()) {
     return create<ast::ScalarConstructorExpression>(
         create<ast::UintLiteral>(static_cast<u32>(elem_value)));
   }
-  if (unwrapped_type->Is<sem::F32>()) {
+  if (type->Is<ast::F32>()) {
     return create<ast::ScalarConstructorExpression>(
         create<ast::FloatLiteral>(static_cast<f32>(elem_value)));
   }
-  if (auto* v = unwrapped_type->As<sem::Vector>()) {
+  if (auto* v = type->As<ast::Vector>()) {
     ast::ExpressionList el(v->size());
     for (size_t i = 0; i < el.size(); i++) {
-      el[i] = ConstructValueFilledWith(v->type(), elem_value);
+      el[i] = ConstructValueFilledWith(ctx.Clone(v->type()), elem_value);
     }
-    return create<ast::TypeConstructorExpression>(type, std::move(el));
+    return create<ast::TypeConstructorExpression>(const_cast<ast::Type*>(type),
+                                                  std::move(el));
   }
-  if (auto* m = unwrapped_type->As<sem::Matrix>()) {
-    auto* col_vec_type = create<sem::Vector>(m->type(), m->rows());
-    ast::ExpressionList el(col_vec_type->size());
+  if (auto* m = type->As<ast::Matrix>()) {
+    ast::ExpressionList el(m->columns());
     for (size_t i = 0; i < el.size(); i++) {
+      auto* col_vec_type = create<ast::Vector>(ctx.Clone(m->type()), m->rows());
       el[i] = ConstructValueFilledWith(col_vec_type, elem_value);
     }
-    return create<ast::TypeConstructorExpression>(type, std::move(el));
+    return create<ast::TypeConstructorExpression>(const_cast<ast::Type*>(type),
+                                                  std::move(el));
   }
-  TINT_ASSERT(false);
+  if (auto* tn = type->As<ast::TypeName>()) {
+    if (auto* lookup = AST().LookupType(tn->name())) {
+      if (auto* alias = lookup->As<ast::Alias>()) {
+        return ConstructValueFilledWith(ctx.Clone(alias->type()), elem_value);
+      }
+    }
+    TINT_ICE(diagnostics_) << "unable to find NamedType '"
+                           << Symbols().NameFor(tn->name()) << "'";
+    return nullptr;
+  }
+
+  TINT_ICE(diagnostics_) << "unhandled type: " << type->TypeInfo().name;
   return nullptr;
 }
 
 typ::Type ProgramBuilder::TypesBuilder::MaybeCreateTypename(
     typ::Type type) const {
-  if (auto* alias = As<ast::Alias>(type.ast)) {
-    return {builder->create<ast::TypeName>(alias->symbol()), type.sem};
-  }
-  if (auto* str = As<ast::Struct>(type.ast)) {
-    return {builder->create<ast::TypeName>(str->name()), type.sem};
+  if (auto* nt = As<ast::NamedType>(type.ast)) {
+    return {type_name(nt->name()), type.sem};
   }
   return type;
 }
diff --git a/src/program_builder.h b/src/program_builder.h
index 048715f..f9e7a0f 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -1060,7 +1060,7 @@
   /// @return an `ast::TypeConstructorExpression` of `type` constructed with the
   /// values `args`.
   template <typename... ARGS>
-  ast::TypeConstructorExpression* Construct(typ::Type type, ARGS&&... args) {
+  ast::TypeConstructorExpression* Construct(ast::Type* type, ARGS&&... args) {
     type = ty.MaybeCreateTypename(type);
     return create<ast::TypeConstructorExpression>(
         type, ExprList(std::forward<ARGS>(args)...));
@@ -1074,7 +1074,7 @@
   /// @param elem_value the initial or element value (for vec and mat) to
   /// construct with
   /// @return the constructor expression
-  ast::ConstructorExpression* ConstructValueFilledWith(typ::Type type,
+  ast::ConstructorExpression* ConstructValueFilledWith(const ast::Type* type,
                                                        int elem_value = 0);
 
   /// @param args the arguments for the vector constructor
@@ -1187,7 +1187,7 @@
   /// @return an `ast::TypeConstructorExpression` of an array with element type
   /// `subtype`, constructed with the values `args`.
   template <typename... ARGS>
-  ast::TypeConstructorExpression* array(typ::Type subtype,
+  ast::TypeConstructorExpression* array(ast::Type* subtype,
                                         uint32_t n,
                                         ARGS&&... args) {
     return Construct(ty.array(subtype, n), std::forward<ARGS>(args)...);
@@ -1201,7 +1201,7 @@
   /// @returns a `ast::Variable` with the given name, storage and type
   template <typename NAME>
   ast::Variable* Var(NAME&& name,
-                     typ::Type type,
+                     ast::Type* type,
                      ast::StorageClass storage,
                      ast::Expression* constructor = nullptr,
                      ast::DecorationList decorations = {}) {
@@ -1220,7 +1220,7 @@
   template <typename NAME>
   ast::Variable* Var(const Source& source,
                      NAME&& name,
-                     typ::Type type,
+                     ast::Type* type,
                      ast::StorageClass storage,
                      ast::Expression* constructor = nullptr,
                      ast::DecorationList decorations = {}) {
@@ -1236,7 +1236,7 @@
   /// @returns a constant `ast::Variable` with the given name and type
   template <typename NAME>
   ast::Variable* Const(NAME&& name,
-                       typ::Type type,
+                       ast::Type* type,
                        ast::Expression* constructor = nullptr,
                        ast::DecorationList decorations = {}) {
     type = ty.MaybeCreateTypename(type);
@@ -1254,7 +1254,7 @@
   template <typename NAME>
   ast::Variable* Const(const Source& source,
                        NAME&& name,
-                       typ::Type type,
+                       ast::Type* type,
                        ast::Expression* constructor = nullptr,
                        ast::DecorationList decorations = {}) {
     type = ty.MaybeCreateTypename(type);
@@ -1269,7 +1269,7 @@
   /// @returns a constant `ast::Variable` with the given name and type
   template <typename NAME>
   ast::Variable* Param(NAME&& name,
-                       typ::Type type,
+                       ast::Type* type,
                        ast::DecorationList decorations = {}) {
     type = ty.MaybeCreateTypename(type);
     return create<ast::Variable>(Sym(std::forward<NAME>(name)),
@@ -1285,7 +1285,7 @@
   template <typename NAME>
   ast::Variable* Param(const Source& source,
                        NAME&& name,
-                       typ::Type type,
+                       ast::Type* type,
                        ast::DecorationList decorations = {}) {
     type = ty.MaybeCreateTypename(type);
     return create<ast::Variable>(source, Sym(std::forward<NAME>(name)),
@@ -1302,7 +1302,7 @@
   /// global variable with the ast::Module.
   template <typename NAME>
   ast::Variable* Global(NAME&& name,
-                        typ::Type type,
+                        ast::Type* type,
                         ast::StorageClass storage,
                         ast::Expression* constructor = nullptr,
                         ast::DecorationList decorations = {}) {
@@ -1323,7 +1323,7 @@
   template <typename NAME>
   ast::Variable* Global(const Source& source,
                         NAME&& name,
-                        typ::Type type,
+                        ast::Type* type,
                         ast::StorageClass storage,
                         ast::Expression* constructor = nullptr,
                         ast::DecorationList decorations = {}) {
@@ -1476,7 +1476,7 @@
   ast::Function* Func(const Source& source,
                       NAME&& name,
                       ast::VariableList params,
-                      typ::Type type,
+                      ast::Type* type,
                       ast::StatementList body,
                       ast::DecorationList decorations = {},
                       ast::DecorationList return_type_decorations = {}) {
@@ -1501,7 +1501,7 @@
   template <typename NAME>
   ast::Function* Func(NAME&& name,
                       ast::VariableList params,
-                      typ::Type type,
+                      ast::Type* type,
                       ast::StatementList body,
                       ast::DecorationList decorations = {},
                       ast::DecorationList return_type_decorations = {}) {
@@ -1588,7 +1588,7 @@
   template <typename NAME>
   ast::StructMember* Member(const Source& source,
                             NAME&& name,
-                            typ::Type type,
+                            ast::Type* type,
                             ast::DecorationList decorations = {}) {
     type = ty.MaybeCreateTypename(type);
     return create<ast::StructMember>(source, Sym(std::forward<NAME>(name)),
@@ -1602,7 +1602,7 @@
   /// @returns the struct member pointer
   template <typename NAME>
   ast::StructMember* Member(NAME&& name,
-                            typ::Type type,
+                            ast::Type* type,
                             ast::DecorationList decorations = {}) {
     type = ty.MaybeCreateTypename(type);
     return create<ast::StructMember>(source_, Sym(std::forward<NAME>(name)),
@@ -1615,7 +1615,7 @@
   /// @param type the struct member type
   /// @returns the struct member pointer
   template <typename NAME>
-  ast::StructMember* Member(uint32_t offset, NAME&& name, typ::Type type) {
+  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,
diff --git a/src/reader/spirv/function_composite_test.cc b/src/reader/spirv/function_composite_test.cc
index b2db5e7..437e72b 100644
--- a/src/reader/spirv/function_composite_test.cc
+++ b/src/reader/spirv/function_composite_test.cc
@@ -218,7 +218,7 @@
   VariableConst{
     x_1
     none
-    __struct_S
+    __type_name_S
     {
       TypeConstructor[not set]{
         __type_name_S
@@ -835,14 +835,14 @@
   Variable{
     x_35
     function
-    __struct_S
+    __type_name_S
   }
 }
 VariableDeclStatement{
   VariableConst{
     x_1
     none
-    __struct_S
+    __type_name_S
     {
       Identifier[not set]{x_35}
     }
@@ -852,7 +852,7 @@
   Variable{
     x_2_1
     function
-    __struct_S
+    __type_name_S
     {
       Identifier[not set]{x_1}
     }
@@ -869,7 +869,7 @@
   VariableConst{
     x_2
     none
-    __struct_S
+    __type_name_S
     {
       Identifier[not set]{x_2_1}
     }
@@ -909,21 +909,21 @@
   Variable{
     x_40
     function
-    __struct_S_2
+    __type_name_S_2
   }
 }
 VariableDeclStatement{
   Variable{
     x_41
     function
-    __struct_S_2
+    __type_name_S_2
   }
 }
 VariableDeclStatement{
   VariableConst{
     x_1
     none
-    __struct_S_1
+    __type_name_S_1
     {
       Identifier[not set]{x_40}
     }
@@ -933,7 +933,7 @@
   Variable{
     x_2_1
     function
-    __struct_S_1
+    __type_name_S_1
     {
       Identifier[not set]{x_1}
     }
@@ -950,7 +950,7 @@
   VariableConst{
     x_2
     none
-    __struct_S_1
+    __type_name_S_1
     {
       Identifier[not set]{x_2_1}
     }
@@ -960,7 +960,7 @@
   VariableConst{
     x_3
     none
-    __struct_S_2
+    __type_name_S_2
     {
       Identifier[not set]{x_41}
     }
@@ -970,7 +970,7 @@
   Variable{
     x_4_1
     function
-    __struct_S_2
+    __type_name_S_2
     {
       Identifier[not set]{x_3}
     }
@@ -987,7 +987,7 @@
   VariableConst{
     x_4
     none
-    __struct_S_2
+    __type_name_S_2
     {
       Identifier[not set]{x_4_1}
     }
@@ -998,7 +998,7 @@
   Variable{
     x_4_1
     function
-    __struct_S_2
+    __type_name_S_2
     {
       Identifier[not set]{x_3}
     }
@@ -1015,7 +1015,7 @@
   VariableConst{
     x_4
     none
-    __struct_S_2
+    __type_name_S_2
     {
       Identifier[not set]{x_4_1}
     }
@@ -1066,14 +1066,14 @@
   Variable{
     x_37
     function
-    __struct_S_1
+    __type_name_S_1
   }
 }
 VariableDeclStatement{
   VariableConst{
     x_1
     none
-    __struct_S_1
+    __type_name_S_1
     {
       Identifier[not set]{x_37}
     }
@@ -1083,7 +1083,7 @@
   Variable{
     x_2_1
     function
-    __struct_S_1
+    __type_name_S_1
     {
       Identifier[not set]{x_1}
     }
@@ -1109,7 +1109,7 @@
   VariableConst{
     x_2
     none
-    __struct_S_1
+    __type_name_S_1
     {
       Identifier[not set]{x_2_1}
     }
diff --git a/src/reader/spirv/function_memory_test.cc b/src/reader/spirv/function_memory_test.cc
index 85d7d9b..5103cc8 100644
--- a/src/reader/spirv/function_memory_test.cc
+++ b/src/reader/spirv/function_memory_test.cc
@@ -806,7 +806,7 @@
   Variable{
     myvar
     storage
-    __access_control_read_write__struct_S
+    __access_control_read_write__type_name_S
   })"));
 }
 
diff --git a/src/reader/spirv/function_misc_test.cc b/src/reader/spirv/function_misc_test.cc
index 5f4a197..0539563 100644
--- a/src/reader/spirv/function_misc_test.cc
+++ b/src/reader/spirv/function_misc_test.cc
@@ -409,7 +409,7 @@
   VariableConst{
     x_11
     none
-    __struct_S
+    __type_name_S
     {
       TypeConstructor[not set]{
         __type_name_S
diff --git a/src/reader/spirv/function_var_test.cc b/src/reader/spirv/function_var_test.cc
index a66e851..a0c1019 100644
--- a/src/reader/spirv/function_var_test.cc
+++ b/src/reader/spirv/function_var_test.cc
@@ -467,7 +467,7 @@
   Variable{
     x_200
     function
-    __alias_Arr__array__u32_2_stride_16
+    __type_name_Arr
     {
       TypeConstructor[not set]{
         __type_name_Arr
@@ -537,7 +537,7 @@
   Variable{
     x_200
     function
-    __alias_Arr__array__u32_2_stride_16
+    __type_name_Arr
     {
       TypeConstructor[not set]{
         __type_name_Arr
@@ -572,7 +572,7 @@
   Variable{
     x_200
     function
-    __struct_S
+    __type_name_S
     {
       TypeConstructor[not set]{
         __type_name_S
@@ -612,7 +612,7 @@
   Variable{
     x_200
     function
-    __struct_S
+    __type_name_S
     {
       TypeConstructor[not set]{
         __type_name_S
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 6820ba0..9977113 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -233,7 +233,8 @@
 // @param tp the type pair
 // @returns the unwrapped type pair
 typ::Type UnwrapIfNeeded(typ::Type tp) {
-  return typ::Type{tp.ast->UnwrapIfNeeded(), tp.sem->UnwrapIfNeeded()};
+  return typ::Type{tp.ast ? tp.ast->UnwrapIfNeeded() : nullptr,
+                   tp.sem ? tp.sem->UnwrapIfNeeded() : nullptr};
 }
 
 }  // namespace
@@ -1037,7 +1038,7 @@
   return result;
 }
 
-void ParserImpl::AddConstructedType(Symbol name, typ::Type type) {
+void ParserImpl::AddConstructedType(Symbol name, ast::NamedType* type) {
   auto iter = constructed_types_.insert(name);
   if (iter.second) {
     builder_.AST().AddConstructedType(type);
@@ -1539,7 +1540,7 @@
   return {};
 }
 
-ast::Expression* ParserImpl::MakeNullValue(typ::Type type) {
+ast::Expression* ParserImpl::MakeNullValue(ast::Type* type) {
   // TODO(dneto): Use the no-operands constructor syntax when it becomes
   // available in Tint.
   // https://github.com/gpuweb/gpuweb/issues/685
@@ -1550,43 +1551,43 @@
     return nullptr;
   }
 
-  auto original_type = type;
+  auto* original_type = type;
   type = UnwrapIfNeeded(type);
 
-  if (type.ast->Is<ast::Bool>()) {
+  if (type->Is<ast::Bool>()) {
     return create<ast::ScalarConstructorExpression>(
         Source{}, create<ast::BoolLiteral>(Source{}, false));
   }
-  if (type.ast->Is<ast::U32>()) {
+  if (type->Is<ast::U32>()) {
     return create<ast::ScalarConstructorExpression>(
         Source{}, create<ast::UintLiteral>(Source{}, 0u));
   }
-  if (type.ast->Is<ast::I32>()) {
+  if (type->Is<ast::I32>()) {
     return create<ast::ScalarConstructorExpression>(
         Source{}, create<ast::SintLiteral>(Source{}, 0));
   }
-  if (type.ast->Is<ast::F32>()) {
+  if (type->Is<ast::F32>()) {
     return create<ast::ScalarConstructorExpression>(
         Source{}, create<ast::FloatLiteral>(Source{}, 0.0f));
   }
-  if (type.ast->Is<ast::TypeName>()) {
+  if (type->Is<ast::TypeName>()) {
     // TODO(amaiorano): No type constructor for TypeName (yet?)
     ast::ExpressionList ast_components;
     return create<ast::TypeConstructorExpression>(Source{}, original_type,
                                                   std::move(ast_components));
   }
-  if (auto vec_ty = typ::As<typ::Vector>(type)) {
+  if (auto* vec_ty = type->As<ast::Vector>()) {
     ast::ExpressionList ast_components;
     for (size_t i = 0; i < vec_ty->size(); ++i) {
-      ast_components.emplace_back(MakeNullValue(typ::Call_type(vec_ty)));
+      ast_components.emplace_back(MakeNullValue(vec_ty->type()));
     }
     return create<ast::TypeConstructorExpression>(
         Source{}, builder_.ty.MaybeCreateTypename(type),
         std::move(ast_components));
   }
-  if (auto mat_ty = typ::As<typ::Matrix>(type)) {
+  if (auto* mat_ty = type->As<ast::Matrix>()) {
     // Matrix components are columns
-    auto column_ty = builder_.ty.vec(typ::Call_type(mat_ty), mat_ty->rows());
+    auto column_ty = builder_.ty.vec(mat_ty->type(), mat_ty->rows());
     ast::ExpressionList ast_components;
     for (size_t i = 0; i < mat_ty->columns(); ++i) {
       ast_components.emplace_back(MakeNullValue(column_ty));
@@ -1595,18 +1596,18 @@
         Source{}, builder_.ty.MaybeCreateTypename(type),
         std::move(ast_components));
   }
-  if (auto arr_ty = typ::As<typ::Array>(type)) {
+  if (auto* arr_ty = type->As<ast::Array>()) {
     ast::ExpressionList ast_components;
     for (size_t i = 0; i < arr_ty->size(); ++i) {
-      ast_components.emplace_back(MakeNullValue(typ::Call_type(arr_ty)));
+      ast_components.emplace_back(MakeNullValue(arr_ty->type()));
     }
     return create<ast::TypeConstructorExpression>(
         Source{}, builder_.ty.MaybeCreateTypename(original_type),
         std::move(ast_components));
   }
-  if (auto struct_ty = typ::As<typ::Struct>(type)) {
+  if (auto* struct_ty = type->As<ast::Struct>()) {
     ast::ExpressionList ast_components;
-    for (auto* member : struct_ty.ast->members()) {
+    for (auto* member : struct_ty->members()) {
       ast_components.emplace_back(MakeNullValue(member->type()));
     }
     return create<ast::TypeConstructorExpression>(
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index 325c750..be6ea4b 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -334,7 +334,7 @@
   /// Creates an AST expression node for the null value for the given type.
   /// @param type the AST type
   /// @returns a new expression
-  ast::Expression* MakeNullValue(typ::Type type);
+  ast::Expression* MakeNullValue(ast::Type* type);
 
   /// Make a typed expression for the null value for the given type.
   /// @param type the AST type
@@ -598,7 +598,7 @@
   /// 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, typ::Type type);
+  void AddConstructedType(Symbol name, ast::NamedType* type);
 
   /// Creates a new `ast::Node` owned by the ProgramBuilder.
   /// @param args the arguments to pass to the type constructor
diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc
index 03a6801..5d4ff8e 100644
--- a/src/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/reader/spirv/parser_impl_module_var_test.cc
@@ -1408,7 +1408,7 @@
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
-    __struct_S
+    __type_name_S
     {
       TypeConstructor[not set]{
         __type_name_S
@@ -1437,7 +1437,7 @@
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
-    __struct_S
+    __type_name_S
     {
       TypeConstructor[not set]{
         __type_name_S
@@ -1466,7 +1466,7 @@
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
-    __struct_S
+    __type_name_S
     {
       TypeConstructor[not set]{
         __type_name_S
@@ -1553,7 +1553,7 @@
     }
     myvar
     storage
-    __access_control_read_write__struct_S
+    __access_control_read_write__type_name_S
   })"))
       << module_str;
 }
@@ -1607,7 +1607,7 @@
     }
     myvar
     storage
-    __access_control_read_write__struct_S
+    __access_control_read_write__type_name_S
   })"))
       << module_str;
 }
@@ -1664,7 +1664,7 @@
   Variable{
     myvar
     storage
-    __access_control_read_write__struct_S
+    __access_control_read_write__type_name_S
   }
 )")) << module_str;
 }
@@ -1693,7 +1693,7 @@
   Variable{
     myvar
     storage
-    __access_control_read_write__struct_S
+    __access_control_read_write__type_name_S
   }
 })")) << module_str;
 }
@@ -1722,7 +1722,7 @@
   Variable{
     myvar
     storage
-    __access_control_read_write__struct_S
+    __access_control_read_write__type_name_S
   }
 })")) << module_str;
 }
@@ -1772,7 +1772,7 @@
   Variable{
     myvar
     storage
-    __access_control_read_only__struct_S
+    __access_control_read_only__type_name_S
   }
 })")) << module_str;
 }
@@ -1801,7 +1801,7 @@
   Variable{
     myvar
     storage
-    __access_control_read_write__struct_S
+    __access_control_read_write__type_name_S
   }
 })")) << module_str;
 }
@@ -1833,7 +1833,7 @@
   Variable{
     myvar
     storage
-    __access_control_read_write__struct_S
+    __access_control_read_write__type_name_S
   }
 })")) << module_str;
 }
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index a49f475..c595892 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -401,7 +401,7 @@
       if (!expect("type alias", Token::Type::kSemicolon))
         return Failure::kErrored;
 
-      builder_.AST().AddConstructedType(ta.value);
+      builder_.AST().AddConstructedType(const_cast<ast::Alias*>(ta.value.ast));
       return true;
     }
 
@@ -415,7 +415,8 @@
 
       register_constructed(
           builder_.Symbols().NameFor(str.value->impl()->name()), str.value);
-      builder_.AST().AddConstructedType(str.value);
+      builder_.AST().AddConstructedType(
+          const_cast<ast::Struct*>(str.value.ast));
       return true;
     }
 
@@ -561,7 +562,8 @@
   if (decl.errored)
     return Failure::kErrored;
 
-  if (decl->type->UnwrapAll()->is_handle()) {
+  if ((decl->type.sem && decl->type.sem->UnwrapAll()->is_handle()) ||
+      (decl->type.ast && decl->type.ast->UnwrapAll()->is_handle())) {
     // handle types implicitly have the `UniformConstant` storage class.
     if (explicit_sc.matched) {
       return add_error(
@@ -960,7 +962,7 @@
 
 // type_alias
 //   : TYPE IDENT EQUAL type_decl
-Maybe<typ::Type> ParserImpl::type_alias() {
+Maybe<typ::Alias> ParserImpl::type_alias() {
   auto t = peek();
   if (!t.IsType())
     return Failure::kNoMatch;
@@ -1234,7 +1236,7 @@
 
 // struct_decl
 //   : struct_decoration_decl* STRUCT IDENT struct_body_decl
-Maybe<sem::StructType*> ParserImpl::struct_decl(ast::DecorationList& decos) {
+Maybe<typ::Struct> ParserImpl::struct_decl(ast::DecorationList& decos) {
   auto t = peek();
   auto source = t.source();
 
@@ -1250,8 +1252,9 @@
     return Failure::kErrored;
 
   auto sym = builder_.Symbols().Register(name.value);
-  return create<sem::StructType>(create<ast::Struct>(
-      source, sym, std::move(body.value), std::move(decos)));
+  auto* str =
+      create<ast::Struct>(source, sym, std::move(body.value), std::move(decos));
+  return typ::Struct{str, create<sem::StructType>(str)};
 }
 
 // struct_body_decl
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index a18550c..bd3e018 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -398,7 +398,7 @@
   Maybe<ast::StorageClass> variable_storage_decoration();
   /// Parses a `type_alias` grammar element
   /// @returns the type alias or nullptr on error
-  Maybe<typ::Type> type_alias();
+  Maybe<typ::Alias> type_alias();
   /// Parses a `type_decl` grammar element
   /// @returns the parsed Type or nullptr if none matched.
   Maybe<typ::Type> type_decl();
@@ -415,7 +415,7 @@
   /// `struct_decoration_decl*` provided as `decos`.
   /// @returns the struct type or nullptr on error
   /// @param decos the list of decorations for the struct declaration.
-  Maybe<sem::StructType*> struct_decl(ast::DecorationList& decos);
+  Maybe<typ::Struct> struct_decl(ast::DecorationList& decos);
   /// Parses a `struct_body_decl` grammar element, erroring on parse failure.
   /// @returns the struct members
   Expect<ast::StructMemberList> expect_struct_body_decl();
diff --git a/src/reader/wgsl/parser_impl_const_expr_test.cc b/src/reader/wgsl/parser_impl_const_expr_test.cc
index 00a053c..6fc59bd 100644
--- a/src/reader/wgsl/parser_impl_const_expr_test.cc
+++ b/src/reader/wgsl/parser_impl_const_expr_test.cc
@@ -28,8 +28,8 @@
   ASSERT_TRUE(e->Is<ast::TypeConstructorExpression>());
 
   auto* t = e->As<ast::TypeConstructorExpression>();
-  ASSERT_TRUE(t->type()->Is<sem::Vector>());
-  EXPECT_EQ(t->type()->As<sem::Vector>()->size(), 2u);
+  ASSERT_TRUE(t->type()->Is<ast::Vector>());
+  EXPECT_EQ(t->type()->As<ast::Vector>()->size(), 2u);
 
   ASSERT_EQ(t->values().size(), 2u);
   auto& v = t->values();
@@ -56,8 +56,8 @@
   ASSERT_TRUE(e->Is<ast::TypeConstructorExpression>());
 
   auto* t = e->As<ast::TypeConstructorExpression>();
-  ASSERT_TRUE(t->type()->Is<sem::Vector>());
-  EXPECT_EQ(t->type()->As<sem::Vector>()->size(), 2u);
+  ASSERT_TRUE(t->type()->Is<ast::Vector>());
+  EXPECT_EQ(t->type()->As<ast::Vector>()->size(), 2u);
 
   ASSERT_EQ(t->values().size(), 0u);
 }
@@ -71,8 +71,8 @@
   ASSERT_TRUE(e->Is<ast::TypeConstructorExpression>());
 
   auto* t = e->As<ast::TypeConstructorExpression>();
-  ASSERT_TRUE(t->type()->Is<sem::Vector>());
-  EXPECT_EQ(t->type()->As<sem::Vector>()->size(), 2u);
+  ASSERT_TRUE(t->type()->Is<ast::Vector>());
+  EXPECT_EQ(t->type()->As<ast::Vector>()->size(), 2u);
 
   ASSERT_EQ(t->values().size(), 2u);
   ASSERT_TRUE(t->values()[0]->Is<ast::ScalarConstructorExpression>());
diff --git a/src/reader/wgsl/parser_impl_function_decl_test.cc b/src/reader/wgsl/parser_impl_function_decl_test.cc
index 97c99f8..3144df3 100644
--- a/src/reader/wgsl/parser_impl_function_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_function_decl_test.cc
@@ -34,14 +34,14 @@
 
   EXPECT_EQ(f->symbol(), p->builder().Symbols().Get("main"));
   ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->Is<sem::Void>());
+  EXPECT_TRUE(f->return_type()->Is<ast::Void>());
 
   ASSERT_EQ(f->params().size(), 2u);
   EXPECT_EQ(f->params()[0]->symbol(), p->builder().Symbols().Get("a"));
   EXPECT_EQ(f->params()[1]->symbol(), p->builder().Symbols().Get("b"));
 
   ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->Is<sem::Void>());
+  EXPECT_TRUE(f->return_type()->Is<ast::Void>());
 
   auto* body = f->body();
   ASSERT_EQ(body->size(), 1u);
@@ -62,10 +62,8 @@
 
   EXPECT_EQ(f->symbol(), p->builder().Symbols().Get("main"));
   ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->Is<sem::Void>());
+  EXPECT_TRUE(f->return_type()->Is<ast::Void>());
   ASSERT_EQ(f->params().size(), 0u);
-  ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->Is<sem::Void>());
 
   auto& decorations = f->decorations();
   ASSERT_EQ(decorations.size(), 1u);
@@ -100,10 +98,8 @@
 
   EXPECT_EQ(f->symbol(), p->builder().Symbols().Get("main"));
   ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->Is<sem::Void>());
+  EXPECT_TRUE(f->return_type()->Is<ast::Void>());
   ASSERT_EQ(f->params().size(), 0u);
-  ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->Is<sem::Void>());
 
   auto& decorations = f->decorations();
   ASSERT_EQ(decorations.size(), 2u);
@@ -145,10 +141,8 @@
 
   EXPECT_EQ(f->symbol(), p->builder().Symbols().Get("main"));
   ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->Is<sem::Void>());
+  EXPECT_TRUE(f->return_type()->Is<ast::Void>());
   ASSERT_EQ(f->params().size(), 0u);
-  ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->Is<sem::Void>());
 
   auto& decos = f->decorations();
   ASSERT_EQ(decos.size(), 2u);
@@ -187,7 +181,7 @@
 
   EXPECT_EQ(f->symbol(), p->builder().Symbols().Get("main"));
   ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->Is<sem::F32>());
+  EXPECT_TRUE(f->return_type()->Is<ast::F32>());
   ASSERT_EQ(f->params().size(), 0u);
 
   auto& decorations = f->decorations();
diff --git a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc b/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
index f0317b6..f0f6720 100644
--- a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
@@ -33,8 +33,8 @@
 
   EXPECT_TRUE(e->is_const());
   EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
-  ASSERT_NE(e->declared_type(), nullptr);
-  EXPECT_TRUE(e->declared_type()->Is<sem::F32>());
+  ASSERT_NE(e->type(), nullptr);
+  EXPECT_TRUE(e->type()->Is<ast::F32>());
 
   EXPECT_EQ(e->source().range.begin.line, 1u);
   EXPECT_EQ(e->source().range.begin.column, 5u);
@@ -114,8 +114,8 @@
 
   EXPECT_TRUE(e->is_const());
   EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
-  ASSERT_NE(e->declared_type(), nullptr);
-  EXPECT_TRUE(e->declared_type()->Is<sem::F32>());
+  ASSERT_NE(e->type(), nullptr);
+  EXPECT_TRUE(e->type()->Is<ast::F32>());
 
   EXPECT_EQ(e->source().range.begin.line, 1u);
   EXPECT_EQ(e->source().range.begin.column, 21u);
diff --git a/src/reader/wgsl/parser_impl_global_decl_test.cc b/src/reader/wgsl/parser_impl_global_decl_test.cc
index 72a0d49..dcecf15 100644
--- a/src/reader/wgsl/parser_impl_global_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_decl_test.cc
@@ -84,10 +84,10 @@
 
   auto program = p->program();
   ASSERT_EQ(program.AST().ConstructedTypes().size(), 1u);
-  ASSERT_TRUE(program.AST().ConstructedTypes()[0]->Is<sem::Alias>());
+  ASSERT_TRUE(program.AST().ConstructedTypes()[0]->Is<ast::Alias>());
   EXPECT_EQ(
       program.Symbols().NameFor(
-          program.AST().ConstructedTypes()[0]->As<sem::Alias>()->symbol()),
+          program.AST().ConstructedTypes()[0]->As<ast::Alias>()->symbol()),
       "A");
 }
 
@@ -102,14 +102,16 @@
 
   auto program = p->program();
   ASSERT_EQ(program.AST().ConstructedTypes().size(), 2u);
-  ASSERT_TRUE(program.AST().ConstructedTypes()[0]->Is<sem::StructType>());
-  auto* str = program.AST().ConstructedTypes()[0]->As<sem::StructType>();
-  EXPECT_EQ(str->impl()->name(), program.Symbols().Get("A"));
+  ASSERT_TRUE(program.AST().ConstructedTypes()[0]->Is<ast::Struct>());
+  auto* str = program.AST().ConstructedTypes()[0]->As<ast::Struct>();
+  EXPECT_EQ(str->name(), program.Symbols().Get("A"));
 
-  ASSERT_TRUE(program.AST().ConstructedTypes()[1]->Is<sem::Alias>());
-  auto* alias = program.AST().ConstructedTypes()[1]->As<sem::Alias>();
+  ASSERT_TRUE(program.AST().ConstructedTypes()[1]->Is<ast::Alias>());
+  auto* alias = program.AST().ConstructedTypes()[1]->As<ast::Alias>();
   EXPECT_EQ(alias->symbol(), program.Symbols().Get("B"));
-  EXPECT_EQ(alias->type(), str);
+  auto* tn = alias->type()->As<ast::TypeName>();
+  EXPECT_NE(tn, nullptr);
+  EXPECT_EQ(tn->name(), str->name());
 }
 
 TEST_F(ParserImplTest, GlobalDecl_TypeAlias_Invalid) {
@@ -163,13 +165,13 @@
   auto program = p->program();
   ASSERT_EQ(program.AST().ConstructedTypes().size(), 1u);
 
-  auto t = program.AST().ConstructedTypes()[0];
+  auto* t = program.AST().ConstructedTypes()[0];
   ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->Is<sem::StructType>());
+  ASSERT_TRUE(t->Is<ast::Struct>());
 
-  auto* str = t->As<sem::StructType>();
-  EXPECT_EQ(str->impl()->name(), program.Symbols().Get("A"));
-  EXPECT_EQ(str->impl()->members().size(), 2u);
+  auto* str = t->As<ast::Struct>();
+  EXPECT_EQ(str->name(), program.Symbols().Get("A"));
+  EXPECT_EQ(str->members().size(), 2u);
 }
 
 TEST_F(ParserImplTest, GlobalDecl_Struct_WithStride) {
@@ -181,18 +183,18 @@
   auto program = p->program();
   ASSERT_EQ(program.AST().ConstructedTypes().size(), 1u);
 
-  auto t = program.AST().ConstructedTypes()[0];
+  auto* t = program.AST().ConstructedTypes()[0];
   ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->Is<sem::StructType>());
+  ASSERT_TRUE(t->Is<ast::Struct>());
 
-  auto* str = t->As<sem::StructType>();
-  EXPECT_EQ(str->impl()->name(), program.Symbols().Get("A"));
-  EXPECT_EQ(str->impl()->members().size(), 1u);
+  auto* str = t->As<ast::Struct>();
+  EXPECT_EQ(str->name(), program.Symbols().Get("A"));
+  EXPECT_EQ(str->members().size(), 1u);
   EXPECT_FALSE(str->IsBlockDecorated());
 
-  const auto ty = str->impl()->members()[0]->type();
-  ASSERT_TRUE(ty->Is<sem::ArrayType>());
-  const auto* arr = ty->As<sem::ArrayType>();
+  const auto* ty = str->members()[0]->type();
+  ASSERT_TRUE(ty->Is<ast::Array>());
+  const auto* arr = ty->As<ast::Array>();
 
   ASSERT_EQ(arr->decorations().size(), 1u);
   auto* stride = arr->decorations()[0];
@@ -208,13 +210,13 @@
   auto program = p->program();
   ASSERT_EQ(program.AST().ConstructedTypes().size(), 1u);
 
-  auto t = program.AST().ConstructedTypes()[0];
+  auto* t = program.AST().ConstructedTypes()[0];
   ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->Is<sem::StructType>());
+  ASSERT_TRUE(t->Is<ast::Struct>());
 
-  auto* str = t->As<sem::StructType>();
-  EXPECT_EQ(str->impl()->name(), program.Symbols().Get("A"));
-  EXPECT_EQ(str->impl()->members().size(), 1u);
+  auto* str = t->As<ast::Struct>();
+  EXPECT_EQ(str->name(), program.Symbols().Get("A"));
+  EXPECT_EQ(str->members().size(), 1u);
   EXPECT_TRUE(str->IsBlockDecorated());
 }
 
diff --git a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc b/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
index 45cf010..cb469a7 100644
--- a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
@@ -31,7 +31,7 @@
   ASSERT_NE(e.value, nullptr);
 
   EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
-  EXPECT_TRUE(e->declared_type()->Is<sem::F32>());
+  EXPECT_TRUE(e->type()->Is<ast::F32>());
   EXPECT_EQ(e->declared_storage_class(), ast::StorageClass::kPrivate);
 
   EXPECT_EQ(e->source().range.begin.line, 1u);
@@ -54,7 +54,7 @@
   ASSERT_NE(e.value, nullptr);
 
   EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
-  EXPECT_TRUE(e->declared_type()->Is<sem::F32>());
+  EXPECT_TRUE(e->type()->Is<ast::F32>());
   EXPECT_EQ(e->declared_storage_class(), ast::StorageClass::kPrivate);
 
   EXPECT_EQ(e->source().range.begin.line, 1u);
@@ -79,8 +79,8 @@
   ASSERT_NE(e.value, nullptr);
 
   EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
-  ASSERT_NE(e->declared_type(), nullptr);
-  EXPECT_TRUE(e->declared_type()->Is<sem::F32>());
+  ASSERT_NE(e->type(), nullptr);
+  EXPECT_TRUE(e->type()->Is<ast::F32>());
   EXPECT_EQ(e->declared_storage_class(), ast::StorageClass::kUniform);
 
   EXPECT_EQ(e->source().range.begin.line, 1u);
@@ -109,8 +109,8 @@
   ASSERT_NE(e.value, nullptr);
 
   EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
-  ASSERT_NE(e->declared_type(), nullptr);
-  EXPECT_TRUE(e->declared_type()->Is<sem::F32>());
+  ASSERT_NE(e->type(), nullptr);
+  EXPECT_TRUE(e->type()->Is<ast::F32>());
   EXPECT_EQ(e->declared_storage_class(), ast::StorageClass::kUniform);
 
   EXPECT_EQ(e->source().range.begin.line, 1u);
@@ -180,7 +180,7 @@
   ASSERT_NE(e.value, nullptr);
 
   EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("s"));
-  EXPECT_TRUE(e->declared_type()->Is<sem::Sampler>());
+  EXPECT_TRUE(e->type()->Is<ast::Sampler>());
   EXPECT_EQ(e->declared_storage_class(), ast::StorageClass::kUniformConstant);
 }
 
@@ -196,7 +196,7 @@
   ASSERT_NE(e.value, nullptr);
 
   EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("s"));
-  EXPECT_TRUE(e->declared_type()->UnwrapAll()->Is<sem::Texture>());
+  EXPECT_TRUE(e->type()->UnwrapAll()->Is<ast::Texture>());
   EXPECT_EQ(e->declared_storage_class(), ast::StorageClass::kUniformConstant);
 }
 
@@ -210,7 +210,7 @@
   ASSERT_FALSE(p->has_error()) << p->error();
 
   EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
-  EXPECT_TRUE(e->declared_type()->Is<sem::F32>());
+  EXPECT_TRUE(e->type()->Is<ast::F32>());
   EXPECT_EQ(e->declared_storage_class(), ast::StorageClass::kInput);
 
   EXPECT_EQ(
@@ -231,7 +231,7 @@
   ASSERT_FALSE(p->has_error()) << p->error();
 
   EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
-  EXPECT_TRUE(e->declared_type()->Is<sem::F32>());
+  EXPECT_TRUE(e->type()->Is<ast::F32>());
   EXPECT_EQ(e->declared_storage_class(), ast::StorageClass::kOutput);
 
   EXPECT_EQ(
diff --git a/src/reader/wgsl/parser_impl_param_list_test.cc b/src/reader/wgsl/parser_impl_param_list_test.cc
index 60c97d2..4713462 100644
--- a/src/reader/wgsl/parser_impl_param_list_test.cc
+++ b/src/reader/wgsl/parser_impl_param_list_test.cc
@@ -22,15 +22,13 @@
 TEST_F(ParserImplTest, ParamList_Single) {
   auto p = parser("a : i32");
 
-  auto* i32 = p->builder().create<sem::I32>();
-
   auto e = p->expect_param_list();
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_FALSE(e.errored);
   EXPECT_EQ(e.value.size(), 1u);
 
   EXPECT_EQ(e.value[0]->symbol(), p->builder().Symbols().Get("a"));
-  EXPECT_EQ(e.value[0]->declared_type(), i32);
+  EXPECT_TRUE(e.value[0]->type()->Is<ast::I32>());
   EXPECT_TRUE(e.value[0]->is_const());
 
   ASSERT_EQ(e.value[0]->source().range.begin.line, 1u);
@@ -42,17 +40,13 @@
 TEST_F(ParserImplTest, ParamList_Multiple) {
   auto p = parser("a : i32, b: f32, c: vec2<f32>");
 
-  auto* i32 = p->builder().create<sem::I32>();
-  auto* f32 = p->builder().create<sem::F32>();
-  auto* vec2 = p->builder().create<sem::Vector>(f32, 2);
-
   auto e = p->expect_param_list();
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_FALSE(e.errored);
   EXPECT_EQ(e.value.size(), 3u);
 
   EXPECT_EQ(e.value[0]->symbol(), p->builder().Symbols().Get("a"));
-  EXPECT_EQ(e.value[0]->declared_type(), i32);
+  EXPECT_TRUE(e.value[0]->type()->Is<ast::I32>());
   EXPECT_TRUE(e.value[0]->is_const());
 
   ASSERT_EQ(e.value[0]->source().range.begin.line, 1u);
@@ -61,7 +55,7 @@
   ASSERT_EQ(e.value[0]->source().range.end.column, 2u);
 
   EXPECT_EQ(e.value[1]->symbol(), p->builder().Symbols().Get("b"));
-  EXPECT_EQ(e.value[1]->declared_type(), f32);
+  EXPECT_TRUE(e.value[1]->type()->Is<ast::F32>());
   EXPECT_TRUE(e.value[1]->is_const());
 
   ASSERT_EQ(e.value[1]->source().range.begin.line, 1u);
@@ -70,7 +64,9 @@
   ASSERT_EQ(e.value[1]->source().range.end.column, 11u);
 
   EXPECT_EQ(e.value[2]->symbol(), p->builder().Symbols().Get("c"));
-  EXPECT_EQ(e.value[2]->declared_type(), vec2);
+  ASSERT_TRUE(e.value[2]->type()->Is<ast::Vector>());
+  ASSERT_TRUE(e.value[2]->type()->As<ast::Vector>()->type()->Is<ast::F32>());
+  EXPECT_EQ(e.value[2]->type()->As<ast::Vector>()->size(), 2u);
   EXPECT_TRUE(e.value[2]->is_const());
 
   ASSERT_EQ(e.value[2]->source().range.begin.line, 1u);
@@ -100,16 +96,15 @@
       "[[builtin(position)]] coord : vec4<f32>, "
       "[[location(1)]] loc1 : f32");
 
-  auto* f32 = p->builder().create<sem::F32>();
-  auto* vec4 = p->builder().create<sem::Vector>(f32, 4);
-
   auto e = p->expect_param_list();
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_FALSE(e.errored);
   ASSERT_EQ(e.value.size(), 2u);
 
   EXPECT_EQ(e.value[0]->symbol(), p->builder().Symbols().Get("coord"));
-  EXPECT_EQ(e.value[0]->declared_type(), vec4);
+  ASSERT_TRUE(e.value[0]->type()->Is<ast::Vector>());
+  EXPECT_TRUE(e.value[0]->type()->As<ast::Vector>()->type()->Is<ast::F32>());
+  EXPECT_EQ(e.value[0]->type()->As<ast::Vector>()->size(), 4u);
   EXPECT_TRUE(e.value[0]->is_const());
   auto decos0 = e.value[0]->decorations();
   ASSERT_EQ(decos0.size(), 1u);
@@ -123,7 +118,7 @@
   ASSERT_EQ(e.value[0]->source().range.end.column, 28u);
 
   EXPECT_EQ(e.value[1]->symbol(), p->builder().Symbols().Get("loc1"));
-  EXPECT_EQ(e.value[1]->declared_type(), f32);
+  EXPECT_TRUE(e.value[1]->type()->Is<ast::F32>());
   EXPECT_TRUE(e.value[1]->is_const());
   auto decos1 = e.value[1]->decorations();
   ASSERT_EQ(decos1.size(), 1u);
diff --git a/src/reader/wgsl/parser_impl_primary_expression_test.cc b/src/reader/wgsl/parser_impl_primary_expression_test.cc
index dac0e80..2c10397 100644
--- a/src/reader/wgsl/parser_impl_primary_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_primary_expression_test.cc
@@ -141,7 +141,9 @@
   ASSERT_TRUE(e->Is<ast::TypeConstructorExpression>());
 
   auto* constructor = e->As<ast::TypeConstructorExpression>();
-  EXPECT_EQ(constructor->type(), p->get_constructed("S"));
+  ASSERT_TRUE(constructor->type()->Is<ast::TypeName>());
+  EXPECT_EQ(constructor->type()->As<ast::TypeName>()->name(),
+            p->builder().Symbols().Get("S"));
 
   auto values = constructor->values();
   ASSERT_EQ(values.size(), 0u);
@@ -164,7 +166,9 @@
   ASSERT_TRUE(e->Is<ast::TypeConstructorExpression>());
 
   auto* constructor = e->As<ast::TypeConstructorExpression>();
-  EXPECT_EQ(constructor->type(), p->get_constructed("S"));
+  ASSERT_TRUE(constructor->type()->Is<ast::TypeName>());
+  EXPECT_EQ(constructor->type()->As<ast::TypeName>()->name(),
+            p->builder().Symbols().Get("S"));
 
   auto values = constructor->values();
   ASSERT_EQ(values.size(), 2u);
@@ -237,8 +241,6 @@
 TEST_F(ParserImplTest, PrimaryExpression_Cast) {
   auto p = parser("f32(1)");
 
-  auto* f32 = p->builder().create<sem::F32>();
-
   auto e = p->primary_expression();
   EXPECT_TRUE(e.matched);
   EXPECT_FALSE(e.errored);
@@ -248,7 +250,7 @@
   ASSERT_TRUE(e->Is<ast::TypeConstructorExpression>());
 
   auto* c = e->As<ast::TypeConstructorExpression>();
-  ASSERT_EQ(c->type(), f32);
+  ASSERT_TRUE(c->type()->Is<ast::F32>());
   ASSERT_EQ(c->values().size(), 1u);
 
   ASSERT_TRUE(c->values()[0]->Is<ast::ConstructorExpression>());
@@ -258,8 +260,6 @@
 TEST_F(ParserImplTest, PrimaryExpression_Bitcast) {
   auto p = parser("bitcast<f32>(1)");
 
-  auto* f32 = p->builder().create<sem::F32>();
-
   auto e = p->primary_expression();
   EXPECT_TRUE(e.matched);
   EXPECT_FALSE(e.errored);
@@ -268,8 +268,7 @@
   ASSERT_TRUE(e->Is<ast::BitcastExpression>());
 
   auto* c = e->As<ast::BitcastExpression>();
-  ASSERT_EQ(c->type(), f32);
-
+  ASSERT_TRUE(c->type()->Is<ast::F32>());
   ASSERT_TRUE(c->expr()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(c->expr()->Is<ast::ScalarConstructorExpression>());
 }
diff --git a/src/reader/wgsl/parser_impl_struct_body_decl_test.cc b/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
index d55f583..cd0b526 100644
--- a/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
@@ -23,7 +23,6 @@
   auto p = parser("{a : i32;}");
 
   auto& builder = p->builder();
-  auto* i32 = builder.create<sem::I32>();
 
   auto m = p->expect_struct_body_decl();
   ASSERT_FALSE(p->has_error());
@@ -32,7 +31,7 @@
 
   const auto* mem = m.value[0];
   EXPECT_EQ(mem->symbol(), builder.Symbols().Get("a"));
-  EXPECT_EQ(mem->type(), i32);
+  EXPECT_TRUE(mem->type()->Is<ast::I32>());
   EXPECT_EQ(mem->decorations().size(), 0u);
 }
 
diff --git a/src/reader/wgsl/parser_impl_struct_member_test.cc b/src/reader/wgsl/parser_impl_struct_member_test.cc
index d1c8e4b..eb81ae3 100644
--- a/src/reader/wgsl/parser_impl_struct_member_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_member_test.cc
@@ -23,7 +23,6 @@
   auto p = parser("a : i32;");
 
   auto& builder = p->builder();
-  auto i32 = builder.ty.i32();
 
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
@@ -36,18 +35,17 @@
   ASSERT_NE(m.value, nullptr);
 
   EXPECT_EQ(m->symbol(), builder.Symbols().Get("a"));
-  EXPECT_EQ(m->type(), i32);
+  EXPECT_TRUE(m->type()->Is<ast::I32>());
   EXPECT_EQ(m->decorations().size(), 0u);
 
   EXPECT_EQ(m->source().range, (Source::Range{{1u, 1u}, {1u, 2u}}));
-  EXPECT_EQ(m->type().ast->source().range, (Source::Range{{1u, 5u}, {1u, 8u}}));
+  EXPECT_EQ(m->type()->source().range, (Source::Range{{1u, 5u}, {1u, 8u}}));
 }
 
 TEST_F(ParserImplTest, StructMember_ParsesWithOffsetDecoration_DEPRECATED) {
   auto p = parser("[[offset(2)]] a : i32;");
 
   auto& builder = p->builder();
-  auto i32 = builder.ty.i32();
 
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
@@ -60,7 +58,7 @@
   ASSERT_NE(m.value, nullptr);
 
   EXPECT_EQ(m->symbol(), builder.Symbols().Get("a"));
-  EXPECT_EQ(m->type(), i32);
+  EXPECT_TRUE(m->type()->Is<ast::I32>());
   EXPECT_EQ(m->decorations().size(), 1u);
   EXPECT_TRUE(m->decorations()[0]->Is<ast::StructMemberOffsetDecoration>());
   EXPECT_EQ(
@@ -68,15 +66,13 @@
       2u);
 
   EXPECT_EQ(m->source().range, (Source::Range{{1u, 15u}, {1u, 16u}}));
-  EXPECT_EQ(m->type().ast->source().range,
-            (Source::Range{{1u, 19u}, {1u, 22u}}));
+  EXPECT_EQ(m->type()->source().range, (Source::Range{{1u, 19u}, {1u, 22u}}));
 }
 
 TEST_F(ParserImplTest, StructMember_ParsesWithAlignDecoration) {
   auto p = parser("[[align(2)]] a : i32;");
 
   auto& builder = p->builder();
-  auto i32 = builder.ty.i32();
 
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
@@ -89,22 +85,20 @@
   ASSERT_NE(m.value, nullptr);
 
   EXPECT_EQ(m->symbol(), builder.Symbols().Get("a"));
-  EXPECT_EQ(m->type(), i32);
+  EXPECT_TRUE(m->type()->Is<ast::I32>());
   EXPECT_EQ(m->decorations().size(), 1u);
   EXPECT_TRUE(m->decorations()[0]->Is<ast::StructMemberAlignDecoration>());
   EXPECT_EQ(
       m->decorations()[0]->As<ast::StructMemberAlignDecoration>()->align(), 2u);
 
   EXPECT_EQ(m->source().range, (Source::Range{{1u, 14u}, {1u, 15u}}));
-  EXPECT_EQ(m->type().ast->source().range,
-            (Source::Range{{1u, 18u}, {1u, 21u}}));
+  EXPECT_EQ(m->type()->source().range, (Source::Range{{1u, 18u}, {1u, 21u}}));
 }
 
 TEST_F(ParserImplTest, StructMember_ParsesWithSizeDecoration) {
   auto p = parser("[[size(2)]] a : i32;");
 
   auto& builder = p->builder();
-  auto i32 = builder.ty.i32();
 
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
@@ -117,22 +111,20 @@
   ASSERT_NE(m.value, nullptr);
 
   EXPECT_EQ(m->symbol(), builder.Symbols().Get("a"));
-  EXPECT_EQ(m->type(), i32);
+  EXPECT_TRUE(m->type()->Is<ast::I32>());
   EXPECT_EQ(m->decorations().size(), 1u);
   EXPECT_TRUE(m->decorations()[0]->Is<ast::StructMemberSizeDecoration>());
   EXPECT_EQ(m->decorations()[0]->As<ast::StructMemberSizeDecoration>()->size(),
             2u);
 
   EXPECT_EQ(m->source().range, (Source::Range{{1u, 13u}, {1u, 14u}}));
-  EXPECT_EQ(m->type().ast->source().range,
-            (Source::Range{{1u, 17u}, {1u, 20u}}));
+  EXPECT_EQ(m->type()->source().range, (Source::Range{{1u, 17u}, {1u, 20u}}));
 }
 
 TEST_F(ParserImplTest, StructMember_ParsesWithDecoration) {
   auto p = parser("[[size(2)]] a : i32;");
 
   auto& builder = p->builder();
-  auto i32 = builder.ty.i32();
 
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
@@ -145,15 +137,14 @@
   ASSERT_NE(m.value, nullptr);
 
   EXPECT_EQ(m->symbol(), builder.Symbols().Get("a"));
-  EXPECT_EQ(m->type(), i32);
+  EXPECT_TRUE(m->type()->Is<ast::I32>());
   EXPECT_EQ(m->decorations().size(), 1u);
   EXPECT_TRUE(m->decorations()[0]->Is<ast::StructMemberSizeDecoration>());
   EXPECT_EQ(m->decorations()[0]->As<ast::StructMemberSizeDecoration>()->size(),
             2u);
 
   EXPECT_EQ(m->source().range, (Source::Range{{1u, 13u}, {1u, 14u}}));
-  EXPECT_EQ(m->type().ast->source().range,
-            (Source::Range{{1u, 17u}, {1u, 20u}}));
+  EXPECT_EQ(m->type()->source().range, (Source::Range{{1u, 17u}, {1u, 20u}}));
 }
 
 TEST_F(ParserImplTest, StructMember_ParsesWithMultipleDecorations) {
@@ -161,7 +152,6 @@
 [[align(4)]] a : i32;)");
 
   auto& builder = p->builder();
-  auto i32 = builder.ty.i32();
 
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
@@ -174,7 +164,7 @@
   ASSERT_NE(m.value, nullptr);
 
   EXPECT_EQ(m->symbol(), builder.Symbols().Get("a"));
-  EXPECT_EQ(m->type(), i32);
+  EXPECT_TRUE(m->type()->Is<ast::I32>());
   EXPECT_EQ(m->decorations().size(), 2u);
   EXPECT_TRUE(m->decorations()[0]->Is<ast::StructMemberSizeDecoration>());
   EXPECT_EQ(m->decorations()[0]->As<ast::StructMemberSizeDecoration>()->size(),
@@ -184,8 +174,7 @@
       m->decorations()[1]->As<ast::StructMemberAlignDecoration>()->align(), 4u);
 
   EXPECT_EQ(m->source().range, (Source::Range{{2u, 14u}, {2u, 15u}}));
-  EXPECT_EQ(m->type().ast->source().range,
-            (Source::Range{{2u, 18u}, {2u, 21u}}));
+  EXPECT_EQ(m->type()->source().range, (Source::Range{{2u, 18u}, {2u, 21u}}));
 }
 
 TEST_F(ParserImplTest, StructMember_InvalidDecoration) {
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index a482070..1ff15c5 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -308,7 +308,7 @@
 namespace {
 
 struct Params {
-  create_type_func_ptr create_el_type;
+  create_ast_type_func_ptr create_el_type;
   uint32_t stride;
   bool should_pass;
 };
@@ -318,17 +318,16 @@
 using ArrayStrideTest = TestWithParams;
 TEST_P(ArrayStrideTest, All) {
   auto& params = GetParam();
-  auto el_ty = params.create_el_type(ty);
+  auto* el_ty = params.create_el_type(ty);
 
   std::stringstream ss;
-  ss << "el_ty: " << el_ty->FriendlyName(Symbols())
-     << ", stride: " << params.stride
+  ss << "el_ty: " << FriendlyName(el_ty) << ", stride: " << params.stride
      << ", should_pass: " << params.should_pass;
   SCOPED_TRACE(ss.str());
 
-  auto arr = ty.array(el_ty, 4, params.stride);
+  auto arr = ty.array(Source{{12, 34}}, el_ty, 4, params.stride);
 
-  Global(Source{{12, 34}}, "myarray", arr, ast::StorageClass::kInput);
+  Global("myarray", arr, ast::StorageClass::kInput);
 
   if (params.should_pass) {
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -366,58 +365,58 @@
     testing::Values(
         // Succeed because stride >= element size (while being multiple of
         // element alignment)
-        Params{ty_u32, default_u32.size, true},
-        Params{ty_i32, default_i32.size, true},
-        Params{ty_f32, default_f32.size, true},
-        Params{ty_vec2<f32>, default_vec2.size, true},
+        Params{ast_u32, default_u32.size, true},
+        Params{ast_i32, default_i32.size, true},
+        Params{ast_f32, default_f32.size, true},
+        Params{ast_vec2<f32>, default_vec2.size, true},
         // vec3's default size is not a multiple of its alignment
-        // Params{ty_vec3<f32>, default_vec3.size, true},
-        Params{ty_vec4<f32>, default_vec4.size, true},
-        Params{ty_mat2x2<f32>, default_mat2x2.size, true},
-        Params{ty_mat3x3<f32>, default_mat3x3.size, true},
-        Params{ty_mat4x4<f32>, default_mat4x4.size, true},
+        // Params{ast_vec3<f32>, default_vec3.size, true},
+        Params{ast_vec4<f32>, default_vec4.size, true},
+        Params{ast_mat2x2<f32>, default_mat2x2.size, true},
+        Params{ast_mat3x3<f32>, default_mat3x3.size, true},
+        Params{ast_mat4x4<f32>, default_mat4x4.size, true},
 
         // Fail because stride is < element size
-        Params{ty_u32, default_u32.size - 1, false},
-        Params{ty_i32, default_i32.size - 1, false},
-        Params{ty_f32, default_f32.size - 1, false},
-        Params{ty_vec2<f32>, default_vec2.size - 1, false},
-        Params{ty_vec3<f32>, default_vec3.size - 1, false},
-        Params{ty_vec4<f32>, default_vec4.size - 1, false},
-        Params{ty_mat2x2<f32>, default_mat2x2.size - 1, false},
-        Params{ty_mat3x3<f32>, default_mat3x3.size - 1, false},
-        Params{ty_mat4x4<f32>, default_mat4x4.size - 1, false},
+        Params{ast_u32, default_u32.size - 1, false},
+        Params{ast_i32, default_i32.size - 1, false},
+        Params{ast_f32, default_f32.size - 1, false},
+        Params{ast_vec2<f32>, default_vec2.size - 1, false},
+        Params{ast_vec3<f32>, default_vec3.size - 1, false},
+        Params{ast_vec4<f32>, default_vec4.size - 1, false},
+        Params{ast_mat2x2<f32>, default_mat2x2.size - 1, false},
+        Params{ast_mat3x3<f32>, default_mat3x3.size - 1, false},
+        Params{ast_mat4x4<f32>, default_mat4x4.size - 1, false},
 
         // Succeed because stride equals multiple of element alignment
-        Params{ty_u32, default_u32.align * 7, true},
-        Params{ty_i32, default_i32.align * 7, true},
-        Params{ty_f32, default_f32.align * 7, true},
-        Params{ty_vec2<f32>, default_vec2.align * 7, true},
-        Params{ty_vec3<f32>, default_vec3.align * 7, true},
-        Params{ty_vec4<f32>, default_vec4.align * 7, true},
-        Params{ty_mat2x2<f32>, default_mat2x2.align * 7, true},
-        Params{ty_mat3x3<f32>, default_mat3x3.align * 7, true},
-        Params{ty_mat4x4<f32>, default_mat4x4.align * 7, true},
+        Params{ast_u32, default_u32.align * 7, true},
+        Params{ast_i32, default_i32.align * 7, true},
+        Params{ast_f32, default_f32.align * 7, true},
+        Params{ast_vec2<f32>, default_vec2.align * 7, true},
+        Params{ast_vec3<f32>, default_vec3.align * 7, true},
+        Params{ast_vec4<f32>, default_vec4.align * 7, true},
+        Params{ast_mat2x2<f32>, default_mat2x2.align * 7, true},
+        Params{ast_mat3x3<f32>, default_mat3x3.align * 7, true},
+        Params{ast_mat4x4<f32>, default_mat4x4.align * 7, true},
 
         // Fail because stride is not multiple of element alignment
-        Params{ty_u32, (default_u32.align - 1) * 7, false},
-        Params{ty_i32, (default_i32.align - 1) * 7, false},
-        Params{ty_f32, (default_f32.align - 1) * 7, false},
-        Params{ty_vec2<f32>, (default_vec2.align - 1) * 7, false},
-        Params{ty_vec3<f32>, (default_vec3.align - 1) * 7, false},
-        Params{ty_vec4<f32>, (default_vec4.align - 1) * 7, false},
-        Params{ty_mat2x2<f32>, (default_mat2x2.align - 1) * 7, false},
-        Params{ty_mat3x3<f32>, (default_mat3x3.align - 1) * 7, false},
-        Params{ty_mat4x4<f32>, (default_mat4x4.align - 1) * 7, false}));
+        Params{ast_u32, (default_u32.align - 1) * 7, false},
+        Params{ast_i32, (default_i32.align - 1) * 7, false},
+        Params{ast_f32, (default_f32.align - 1) * 7, false},
+        Params{ast_vec2<f32>, (default_vec2.align - 1) * 7, false},
+        Params{ast_vec3<f32>, (default_vec3.align - 1) * 7, false},
+        Params{ast_vec4<f32>, (default_vec4.align - 1) * 7, false},
+        Params{ast_mat2x2<f32>, (default_mat2x2.align - 1) * 7, false},
+        Params{ast_mat3x3<f32>, (default_mat3x3.align - 1) * 7, false},
+        Params{ast_mat4x4<f32>, (default_mat4x4.align - 1) * 7, false}));
 
 TEST_F(ArrayStrideTest, MultipleDecorations) {
-  auto arr = ty.array(ty.i32(), 4,
+  auto arr = ty.array(Source{{12, 34}}, ty.i32(), 4,
                       {
                           create<ast::StrideDecoration>(4),
                           create<ast::StrideDecoration>(4),
                       });
 
-  Global(Source{{12, 34}}, "myarray", arr, ast::StorageClass::kInput);
+  Global("myarray", arr, ast::StorageClass::kInput);
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 0dfea86..cea5eb3 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -40,6 +40,7 @@
 #include "src/ast/storage_texture.h"
 #include "src/ast/struct_block_decoration.h"
 #include "src/ast/switch_statement.h"
+#include "src/ast/type_name.h"
 #include "src/ast/unary_op_expression.h"
 #include "src/ast/variable_decl_statement.h"
 #include "src/ast/vector.h"
@@ -151,8 +152,17 @@
 }
 
 bool Resolver::Resolve() {
+  if (builder_->Diagnostics().contains_errors()) {
+    return false;
+  }
+
   bool result = ResolveInternal();
 
+  if (result && diagnostics_.contains_errors()) {
+    TINT_ICE(diagnostics_) << "resolving failed, but no error was raised";
+    return false;
+  }
+
   // Even if resolving failed, create all the semantic nodes for information we
   // did generate.
   CreateSemanticNodes();
@@ -169,13 +179,15 @@
   if (auto* arr = type->As<sem::ArrayType>()) {
     return IsStorable(arr->type());
   }
-  if (auto* str = type->As<sem::StructType>()) {
-    for (const auto* member : str->impl()->members()) {
-      if (!IsStorable(member->type())) {
-        return false;
+  if (auto* str_ty = type->As<sem::StructType>()) {
+    if (auto* str = Structure(str_ty)) {
+      for (const auto* member : str->members) {
+        if (!IsStorable(member->Type())) {
+          return false;
+        }
       }
+      return true;
     }
-    return true;
   }
   return false;
 }
@@ -196,8 +208,12 @@
     return IsHostShareable(arr->type());
   }
   if (auto* str = type->As<sem::StructType>()) {
-    for (auto* member : str->impl()->members()) {
-      if (!IsHostShareable(member->type())) {
+    auto* info = Structure(str);
+    if (!info) {
+      return false;
+    }
+    for (auto* member : info->members) {
+      if (!IsHostShareable(member->Type())) {
         return false;
       }
     }
@@ -225,11 +241,28 @@
 bool Resolver::ResolveInternal() {
   Mark(&builder_->AST());
 
+  auto register_named_type = [this](Symbol name, const sem::Type* type,
+                                    const Source& source) {
+    auto added = named_types_.emplace(name, type).second;
+    if (!added) {
+      diagnostics_.add_error("type with the name '" +
+                                 builder_->Symbols().NameFor(name) +
+                                 "' was already declared",
+                             source);
+      return false;
+    }
+    return true;
+  };
+
   // 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<sem::Type>()) {
-      if (!Type(ty)) {
+    if (auto* ty = decl->As<ast::NamedType>()) {
+      auto* sem_ty = Type(ty);
+      if (sem_ty == nullptr) {
+        return false;
+      }
+      if (!register_named_type(ty->name(), sem_ty, ty->source())) {
         return false;
       }
     } else if (auto* func = decl->As<ast::Function>()) {
@@ -249,6 +282,8 @@
     }
   }
 
+  bool result = true;
+
   for (auto* node : builder_->ASTNodes().Objects()) {
     if (marked_.count(node) == 0) {
       if (node->IsAnyOf<ast::AccessDecoration, ast::StrideDecoration,
@@ -268,10 +303,11 @@
                              << "At: " << node->source() << "\n"
                              << "Content: " << builder_->str(node) << "\n"
                              << "Pointer: " << node;
+      result = false;
     }
   }
 
-  return true;
+  return result;
 }
 
 const sem::Type* Resolver::Type(const ast::Type* ty) {
@@ -360,6 +396,16 @@
       }
       return nullptr;
     }
+    if (auto* t = ty->As<ast::TypeName>()) {
+      auto it = named_types_.find(t->name());
+      if (it == named_types_.end()) {
+        diagnostics_.add_error(
+            "unknown type '" + builder_->Symbols().NameFor(t->name()) + "'",
+            t->source());
+        return nullptr;
+      }
+      return it->second;
+    }
     TINT_UNREACHABLE(diagnostics_)
         << "Unhandled ast::Type: " << ty->TypeInfo().name;
     return nullptr;
@@ -392,21 +438,26 @@
 
 Resolver::VariableInfo* Resolver::Variable(
     ast::Variable* var,
-    const sem::Type* type /* = nullptr*/) {
+    const sem::Type* type, /* = nullptr */
+    std::string type_name /* = "" */) {
   auto it = variable_to_info_.find(var);
   if (it != variable_to_info_.end()) {
     return it->second;
   }
 
-  if (!type) {
-    type = var->declared_type();
+  if (type == nullptr && var->type()) {
+    type = Type(var->type());
+    type_name = var->type()->FriendlyName(builder_->Symbols());
+  }
+  if (type == nullptr) {
+    return nullptr;
   }
 
-  auto type_name = type->FriendlyName(builder_->Symbols());
   auto* ctype = Canonical(type);
   auto* info = variable_infos_.Create(var, ctype, type_name);
   variable_to_info_.emplace(var, info);
 
+  // TODO(bclayton): Why is this here? Needed?
   // Resolve variable's type
   if (auto* arr = info->type->As<sem::ArrayType>()) {
     if (!Array(arr, var->source())) {
@@ -805,12 +856,12 @@
     if (auto* struct_ty = Canonical(ty)->As<sem::StructType>()) {
       // Validate the decorations for each struct members, and also check for
       // invalid member types.
-      for (auto* member : struct_ty->impl()->members()) {
-        auto* member_ty = Canonical(member->type());
+      for (auto* member : Structure(struct_ty)->members) {
+        auto* member_ty = Canonical(member->Type());
         if (member_ty->Is<sem::StructType>()) {
           diagnostics_.add_error(
               "entry point IO types cannot contain nested structures",
-              member->source());
+              member->Declaration()->source());
           diagnostics_.add_note("while analysing entry point " +
                                     builder_->Symbols().NameFor(func->symbol()),
                                 func->source());
@@ -819,7 +870,7 @@
           if (arr->IsRuntimeArray()) {
             diagnostics_.add_error(
                 "entry point IO types cannot contain runtime sized arrays",
-                member->source());
+                member->Declaration()->source());
             diagnostics_.add_note(
                 "while analysing entry point " +
                     builder_->Symbols().NameFor(func->symbol()),
@@ -828,9 +879,9 @@
           }
         }
 
-        if (!validate_entry_point_decorations_inner(member->decorations(),
-                                                    member_ty, member->source(),
-                                                    param_or_ret, true)) {
+        if (!validate_entry_point_decorations_inner(
+                member->Declaration()->decorations(), member_ty,
+                member->Declaration()->source(), param_or_ret, true)) {
           diagnostics_.add_note("while analysing entry point " +
                                     builder_->Symbols().NameFor(func->symbol()),
                                 func->source());
@@ -842,10 +893,10 @@
     return true;
   };
 
-  for (auto* param : func->params()) {
+  for (auto* param : info->parameters) {
     if (!validate_entry_point_decorations(
-            param->decorations(), param->declared_type(), param->source(),
-            ParamOrRetType::kParameter)) {
+            param->declaration->decorations(), param->type,
+            param->declaration->source(), ParamOrRetType::kParameter)) {
       return false;
     }
   }
@@ -943,19 +994,18 @@
     }
   }
 
-  if (func->return_type().ast || func->return_type().sem) {
-    info->return_type = func->return_type();
-    if (!info->return_type) {
-      info->return_type = Type(func->return_type().ast);
-    }
+  if (auto* ty = func->return_type()) {
+    info->return_type = Type(ty);
+    info->return_type_name = ty->FriendlyName(builder_->Symbols());
     if (!info->return_type) {
       return false;
     }
   } else {
     info->return_type = builder_->create<sem::Void>();
+    info->return_type_name =
+        info->return_type->FriendlyName(builder_->Symbols());
   }
 
-  info->return_type_name = info->return_type->FriendlyName(builder_->Symbols());
   info->return_type = Canonical(info->return_type);
 
   if (auto* str = info->return_type->As<sem::StructType>()) {
@@ -1374,17 +1424,16 @@
 
     SetType(expr, type_ctor->type());
 
+    const sem::Type* type = TypeOf(expr);
+
     // Now that the argument types have been determined, make sure that they
     // obey the constructor type rules laid out in
     // https://gpuweb.github.io/gpuweb/wgsl.html#type-constructor-expr.
-    if (auto* vec_type = type_ctor->type()->As<sem::Vector>()) {
-      return ValidateVectorConstructor(type_ctor, vec_type,
-                                       type_ctor->values());
+    if (auto* vec_type = type->As<sem::Vector>()) {
+      return ValidateVectorConstructor(type_ctor, vec_type);
     }
-    if (auto* mat_type = type_ctor->type()->As<sem::Matrix>()) {
-      auto mat_typename = TypeNameOf(type_ctor);
-      return ValidateMatrixConstructor(type_ctor, mat_type,
-                                       type_ctor->values());
+    if (auto* mat_type = type->As<sem::Matrix>()) {
+      return ValidateMatrixConstructor(type_ctor, mat_type);
     }
     // TODO(crbug.com/tint/634): Validate array constructor
   } else if (auto* scalar_ctor = expr->As<ast::ScalarConstructorExpression>()) {
@@ -1398,8 +1447,8 @@
 
 bool Resolver::ValidateVectorConstructor(
     const ast::TypeConstructorExpression* ctor,
-    const sem::Vector* vec_type,
-    const ast::ExpressionList& values) {
+    const sem::Vector* vec_type) {
+  auto& values = ctor->values();
   auto* elem_type = vec_type->type()->UnwrapAll();
   size_t value_cardinality_sum = 0;
   for (auto* value : values) {
@@ -1467,8 +1516,8 @@
 
 bool Resolver::ValidateMatrixConstructor(
     const ast::TypeConstructorExpression* ctor,
-    const sem::Matrix* matrix_type,
-    const ast::ExpressionList& values) {
+    const sem::Matrix* matrix_type) {
+  auto& values = ctor->values();
   // Zero Value expression
   if (values.empty()) {
     return true;
@@ -1600,7 +1649,7 @@
     const sem::StructMember* member = nullptr;
     for (auto* m : str->members) {
       if (m->Declaration()->symbol() == symbol) {
-        ret = m->Declaration()->type();
+        ret = m->Type();
         member = m;
         break;
       }
@@ -1961,7 +2010,16 @@
   ast::Variable* var = stmt->variable();
   Mark(var);
 
-  const sem::Type* type = var->declared_type();
+  // If the variable has a declared type, resolve it.
+  std::string type_name;
+  const sem::Type* type = nullptr;
+  if (auto* ast_ty = var->type()) {
+    type_name = ast_ty->FriendlyName(builder_->Symbols());
+    type = Type(ast_ty);
+    if (!type) {
+      return false;
+    }
+  }
 
   bool is_global = false;
   if (variable_stack_.get(var->symbol(), nullptr, &is_global)) {
@@ -1982,14 +2040,15 @@
 
     // If the variable has no type, infer it from the rhs
     if (type == nullptr) {
+      type_name = TypeNameOf(ctor);
       type = rhs_type->UnwrapPtrIfNeeded();
     }
 
     if (!IsValidAssignment(type, rhs_type)) {
       diagnostics_.add_error(
-          "variable of type '" + type->FriendlyName(builder_->Symbols()) +
+          "variable of type '" + type_name +
               "' cannot be initialized with a value of type '" +
-              rhs_type->FriendlyName(builder_->Symbols()) + "'",
+              TypeNameOf(ctor) + "'",
           stmt->source());
       return false;
     }
@@ -2000,7 +2059,7 @@
     Mark(deco);
   }
 
-  auto* info = Variable(var, type);
+  auto* info = Variable(var, type, type_name);
   if (!info) {
     return false;
   }
@@ -2071,13 +2130,19 @@
   return nullptr;
 }
 
-void Resolver::SetType(ast::Expression* expr, const sem::Type* type) {
-  SetType(expr, type, type->FriendlyName(builder_->Symbols()));
+void Resolver::SetType(ast::Expression* expr, typ::Type type) {
+  SetType(expr, type,
+          type.sem ? type.sem->FriendlyName(builder_->Symbols())
+                   : type.ast->FriendlyName(builder_->Symbols()));
 }
 
 void Resolver::SetType(ast::Expression* expr,
-                       const sem::Type* type,
+                       typ::Type type,
                        const std::string& type_name) {
+  if (!type.sem) {
+    type.sem = Type(type.ast);
+    TINT_ASSERT(type.sem);
+  }
   if (expr_info_.count(expr)) {
     TINT_ICE(builder_->Diagnostics())
         << "SetType() called twice for the same expression";
@@ -2195,7 +2260,7 @@
   }
 }
 
-bool Resolver::DefaultAlignAndSize(sem::Type* ty,
+bool Resolver::DefaultAlignAndSize(const sem::Type* ty,
                                    uint32_t& align,
                                    uint32_t& size,
                                    const Source& source) {
@@ -2363,24 +2428,24 @@
   return true;
 }
 
-bool Resolver::ValidateStructure(const sem::StructType* st) {
-  for (auto* member : st->impl()->members()) {
-    if (auto* r = member->type()->UnwrapAll()->As<sem::ArrayType>()) {
+bool Resolver::ValidateStructure(const StructInfo* st) {
+  for (auto* member : st->members) {
+    if (auto* r = member->Type()->UnwrapAll()->As<sem::ArrayType>()) {
       if (r->IsRuntimeArray()) {
-        if (member != st->impl()->members().back()) {
+        if (member != st->members.back()) {
           diagnostics_.add_error(
               "v-0015",
               "runtime arrays may only appear as the last member of a struct",
-              member->source());
+              member->Declaration()->source());
           return false;
         }
-        if (!st->IsBlockDecorated()) {
+        if (!st->type->impl()->IsBlockDecorated()) {
           diagnostics_.add_error(
               "v-0015",
               "a struct containing a runtime-sized array "
               "requires the [[block]] attribute: '" +
-                  builder_->Symbols().NameFor(st->impl()->name()) + "'",
-              member->source());
+                  builder_->Symbols().NameFor(st->type->impl()->name()) + "'",
+              member->Declaration()->source());
           return false;
         }
 
@@ -2394,7 +2459,7 @@
       }
     }
 
-    for (auto* deco : member->decorations()) {
+    for (auto* deco : member->Declaration()->decorations()) {
       if (!(deco->Is<ast::BuiltinDecoration>() ||
             deco->Is<ast::LocationDecoration>() ||
             deco->Is<ast::StructMemberOffsetDecoration>() ||
@@ -2407,7 +2472,7 @@
     }
   }
 
-  for (auto* deco : st->impl()->decorations()) {
+  for (auto* deco : st->type->impl()->decorations()) {
     if (!(deco->Is<ast::StructBlockDecoration>())) {
       diagnostics_.add_error("decoration is not valid for struct declarations",
                              deco->source());
@@ -2425,15 +2490,10 @@
     return info_it->second;
   }
 
-  Mark(str->impl());
   for (auto* deco : str->impl()->decorations()) {
     Mark(deco);
   }
 
-  if (!ValidateStructure(str)) {
-    return nullptr;
-  }
-
   sem::StructMemberList sem_members;
   sem_members.reserve(str->impl()->members().size());
 
@@ -2454,12 +2514,16 @@
   for (auto* member : str->impl()->members()) {
     Mark(member);
 
-    auto type = member->type();
+    // Resolve member type
+    auto* type = Type(member->type());
+    if (!type) {
+      return nullptr;
+    }
 
-    // First check the member type is legal
+    // Validate member type
     if (!IsStorable(type)) {
       builder_->Diagnostics().add_error(
-          std::string(type->FriendlyName(builder_->Symbols())) +
+          type->FriendlyName(builder_->Symbols()) +
           " cannot be used as the type of a structure member");
       return nullptr;
     }
@@ -2518,8 +2582,8 @@
 
     offset = utils::RoundUp(align, offset);
 
-    auto* sem_member =
-        builder_->create<sem::StructMember>(member, type, offset, align, size);
+    auto* sem_member = builder_->create<sem::StructMember>(
+        member, const_cast<sem::Type*>(type), offset, align, size);
     builder_->Sem().Add(member, sem_member);
     sem_members.emplace_back(sem_member);
 
@@ -2531,11 +2595,17 @@
   struct_size = utils::RoundUp(struct_align, struct_size);
 
   auto* info = struct_infos_.Create();
+  info->type = str;
   info->members = std::move(sem_members);
   info->align = struct_align;
   info->size = struct_size;
   info->size_no_padding = size_no_padding;
   struct_info_.emplace(str, info);
+
+  if (!ValidateStructure(info)) {
+    return nullptr;
+  }
+
   return info;
 }
 
@@ -2745,13 +2815,13 @@
       return true;  // Already applied
     }
     info->storage_class_usage.emplace(sc);
-    for (auto* member : str->impl()->members()) {
-      if (!ApplyStorageClassUsageToType(sc, member->type(), usage)) {
+    for (auto* member : info->members) {
+      if (!ApplyStorageClassUsageToType(sc, member->Type(), usage)) {
         std::stringstream err;
         err << "while analysing structure member "
             << str->FriendlyName(builder_->Symbols()) << "."
-            << builder_->Symbols().NameFor(member->symbol());
-        diagnostics_.add_note(err.str(), member->source());
+            << builder_->Symbols().NameFor(member->Declaration()->symbol());
+        diagnostics_.add_note(err.str(), member->Declaration()->source());
         return false;
       }
     }
@@ -2798,6 +2868,11 @@
   using Type = sem::Type;
   using Vector = sem::Vector;
 
+  if (!type) {
+    TINT_ICE(diagnostics_) << "Canonical() called with nullptr";
+    return nullptr;
+  }
+
   std::function<const Type*(const Type*)> make_canonical;
   make_canonical = [&](const Type* t) -> const sem::Type* {
     // Unwrap alias sequence
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 823361b..1780cb8 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -73,11 +73,11 @@
 
   /// @param type the given type
   /// @returns true if the given type is storable
-  static bool IsStorable(const sem::Type* type);
+  bool IsStorable(const sem::Type* type);
 
   /// @param type the given type
   /// @returns true if the given type is host-shareable
-  static bool IsHostShareable(const sem::Type* type);
+  bool IsHostShareable(const sem::Type* type);
 
   /// @param lhs the assignment store type (non-pointer)
   /// @param rhs the assignment source type (non-pointer or pointer with
@@ -148,6 +148,7 @@
     StructInfo();
     ~StructInfo();
 
+    sem::StructType const* type = nullptr;
     std::vector<const sem::StructMember*> members;
     uint32_t align = 0;
     uint32_t size = 0;
@@ -253,16 +254,14 @@
   bool ValidateFunction(const ast::Function* func, const FunctionInfo* info);
   bool ValidateGlobalVariable(const VariableInfo* var);
   bool ValidateMatrixConstructor(const ast::TypeConstructorExpression* ctor,
-                                 const sem::Matrix* matrix_type,
-                                 const ast::ExpressionList& values);
+                                 const sem::Matrix* matrix_type);
   bool ValidateParameter(const ast::Variable* param);
   bool ValidateReturn(const ast::ReturnStatement* ret);
-  bool ValidateStructure(const sem::StructType* st);
+  bool ValidateStructure(const StructInfo* st);
   bool ValidateSwitch(const ast::SwitchStatement* s);
   bool ValidateVariable(const ast::Variable* param);
   bool ValidateVectorConstructor(const ast::TypeConstructorExpression* ctor,
-                                 const sem::Vector* vec_type,
-                                 const ast::ExpressionList& values);
+                                 const sem::Vector* vec_type);
 
   /// @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
@@ -284,9 +283,12 @@
   /// @returns the VariableInfo for the variable `var`, building it if it hasn't
   /// been constructed already. If an error is raised, nullptr is returned.
   /// @param var the variable to create or return the `VariableInfo` for
-  /// @param type optional type of `var` to use instead of
-  /// `var->declared_type()`. For type inference.
-  VariableInfo* Variable(ast::Variable* var, const sem::Type* type = nullptr);
+  /// @param type optional type of `var` to use instead of `var->type()`.
+  /// @param type_name optional type name of `var` to use instead of
+  /// `var->type()->FriendlyName()`.
+  VariableInfo* Variable(ast::Variable* var,
+                         const sem::Type* type = nullptr,
+                         std::string type_name = "");
 
   /// Records the storage class usage for the given type, and any transient
   /// dependencies of the type. Validates that the type can be used for the
@@ -304,7 +306,7 @@
   /// @param size the output default size in bytes for the type `ty`
   /// @param source the Source of the variable declaration of type `ty`
   /// @returns true on success, false on error
-  bool DefaultAlignAndSize(sem::Type* ty,
+  bool DefaultAlignAndSize(const sem::Type* ty,
                            uint32_t& align,
                            uint32_t& size,
                            const Source& source);
@@ -325,7 +327,7 @@
   /// assigns this semantic node to the expression `expr`.
   /// @param expr the expression
   /// @param type the resolved type
-  void SetType(ast::Expression* expr, const sem::Type* type);
+  void SetType(ast::Expression* expr, typ::Type type);
 
   /// Creates a sem::Expression node with the resolved type `type`, the declared
   /// type name `type_name` and assigns this semantic node to the expression
@@ -334,7 +336,7 @@
   /// @param type the resolved type
   /// @param type_name the declared type name
   void SetType(ast::Expression* expr,
-               const sem::Type* type,
+               typ::Type type,
                const std::string& type_name);
 
   /// Constructs a new BlockInfo with the given type and with #current_block_ as
@@ -369,6 +371,7 @@
   std::unordered_map<const ast::Expression*, ExpressionInfo> expr_info_;
   std::unordered_map<const sem::StructType*, StructInfo*> struct_info_;
   std::unordered_map<const sem::Type*, const sem::Type*> type_to_canonical_;
+  std::unordered_map<Symbol, const sem::Type*> named_types_;
   std::unordered_set<const ast::Node*> marked_;
   FunctionInfo* current_function_ = nullptr;
   sem::Statement* current_statement_ = nullptr;
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index 3c67637..100bcc2 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -1024,15 +1024,15 @@
 
 struct Params {
   ast::BinaryOp op;
-  create_type_func_ptr create_lhs_type;
-  create_type_func_ptr create_rhs_type;
-  create_type_func_ptr create_result_type;
+  create_ast_type_func_ptr create_lhs_type;
+  create_ast_type_func_ptr create_rhs_type;
+  create_sem_type_func_ptr create_result_type;
 };
 
-static constexpr create_type_func_ptr all_create_type_funcs[] = {
-    ty_bool_,       ty_u32,         ty_i32,        ty_f32,
-    ty_vec3<bool>,  ty_vec3<i32>,   ty_vec3<u32>,  ty_vec3<f32>,
-    ty_mat3x3<i32>, ty_mat3x3<u32>, ty_mat3x3<f32>};
+static constexpr create_ast_type_func_ptr all_create_type_funcs[] = {
+    ast_bool,        ast_u32,         ast_i32,        ast_f32,
+    ast_vec3<bool>,  ast_vec3<i32>,   ast_vec3<u32>,  ast_vec3<f32>,
+    ast_mat3x3<i32>, ast_mat3x3<u32>, ast_mat3x3<f32>};
 
 // A list of all valid test cases for 'lhs op rhs', except that for vecN and
 // matNxN, we only test N=3.
@@ -1041,156 +1041,163 @@
     // https://gpuweb.github.io/gpuweb/wgsl.html#logical-expr
 
     // Binary logical expressions
-    Params{Op::kLogicalAnd, ty_bool_, ty_bool_, ty_bool_},
-    Params{Op::kLogicalOr, ty_bool_, ty_bool_, ty_bool_},
+    Params{Op::kLogicalAnd, ast_bool, ast_bool, sem_bool},
+    Params{Op::kLogicalOr, ast_bool, ast_bool, sem_bool},
 
-    Params{Op::kAnd, ty_bool_, ty_bool_, ty_bool_},
-    Params{Op::kOr, ty_bool_, ty_bool_, ty_bool_},
-    Params{Op::kAnd, ty_vec3<bool>, ty_vec3<bool>, ty_vec3<bool>},
-    Params{Op::kOr, ty_vec3<bool>, ty_vec3<bool>, ty_vec3<bool>},
+    Params{Op::kAnd, ast_bool, ast_bool, sem_bool},
+    Params{Op::kOr, ast_bool, ast_bool, sem_bool},
+    Params{Op::kAnd, ast_vec3<bool>, ast_vec3<bool>, sem_vec3<sem_bool>},
+    Params{Op::kOr, ast_vec3<bool>, ast_vec3<bool>, sem_vec3<sem_bool>},
 
     // Arithmetic expressions
     // https://gpuweb.github.io/gpuweb/wgsl.html#arithmetic-expr
 
     // Binary arithmetic expressions over scalars
-    Params{Op::kAdd, ty_i32, ty_i32, ty_i32},
-    Params{Op::kSubtract, ty_i32, ty_i32, ty_i32},
-    Params{Op::kMultiply, ty_i32, ty_i32, ty_i32},
-    Params{Op::kDivide, ty_i32, ty_i32, ty_i32},
-    Params{Op::kModulo, ty_i32, ty_i32, ty_i32},
+    Params{Op::kAdd, ast_i32, ast_i32, sem_i32},
+    Params{Op::kSubtract, ast_i32, ast_i32, sem_i32},
+    Params{Op::kMultiply, ast_i32, ast_i32, sem_i32},
+    Params{Op::kDivide, ast_i32, ast_i32, sem_i32},
+    Params{Op::kModulo, ast_i32, ast_i32, sem_i32},
 
-    Params{Op::kAdd, ty_u32, ty_u32, ty_u32},
-    Params{Op::kSubtract, ty_u32, ty_u32, ty_u32},
-    Params{Op::kMultiply, ty_u32, ty_u32, ty_u32},
-    Params{Op::kDivide, ty_u32, ty_u32, ty_u32},
-    Params{Op::kModulo, ty_u32, ty_u32, ty_u32},
+    Params{Op::kAdd, ast_u32, ast_u32, sem_u32},
+    Params{Op::kSubtract, ast_u32, ast_u32, sem_u32},
+    Params{Op::kMultiply, ast_u32, ast_u32, sem_u32},
+    Params{Op::kDivide, ast_u32, ast_u32, sem_u32},
+    Params{Op::kModulo, ast_u32, ast_u32, sem_u32},
 
-    Params{Op::kAdd, ty_f32, ty_f32, ty_f32},
-    Params{Op::kSubtract, ty_f32, ty_f32, ty_f32},
-    Params{Op::kMultiply, ty_f32, ty_f32, ty_f32},
-    Params{Op::kDivide, ty_f32, ty_f32, ty_f32},
-    Params{Op::kModulo, ty_f32, ty_f32, ty_f32},
+    Params{Op::kAdd, ast_f32, ast_f32, sem_f32},
+    Params{Op::kSubtract, ast_f32, ast_f32, sem_f32},
+    Params{Op::kMultiply, ast_f32, ast_f32, sem_f32},
+    Params{Op::kDivide, ast_f32, ast_f32, sem_f32},
+    Params{Op::kModulo, ast_f32, ast_f32, sem_f32},
 
     // Binary arithmetic expressions over vectors
-    Params{Op::kAdd, ty_vec3<i32>, ty_vec3<i32>, ty_vec3<i32>},
-    Params{Op::kSubtract, ty_vec3<i32>, ty_vec3<i32>, ty_vec3<i32>},
-    Params{Op::kMultiply, ty_vec3<i32>, ty_vec3<i32>, ty_vec3<i32>},
-    Params{Op::kDivide, ty_vec3<i32>, ty_vec3<i32>, ty_vec3<i32>},
-    Params{Op::kModulo, ty_vec3<i32>, ty_vec3<i32>, ty_vec3<i32>},
+    Params{Op::kAdd, ast_vec3<i32>, ast_vec3<i32>, sem_vec3<sem_i32>},
+    Params{Op::kSubtract, ast_vec3<i32>, ast_vec3<i32>, sem_vec3<sem_i32>},
+    Params{Op::kMultiply, ast_vec3<i32>, ast_vec3<i32>, sem_vec3<sem_i32>},
+    Params{Op::kDivide, ast_vec3<i32>, ast_vec3<i32>, sem_vec3<sem_i32>},
+    Params{Op::kModulo, ast_vec3<i32>, ast_vec3<i32>, sem_vec3<sem_i32>},
 
-    Params{Op::kAdd, ty_vec3<u32>, ty_vec3<u32>, ty_vec3<u32>},
-    Params{Op::kSubtract, ty_vec3<u32>, ty_vec3<u32>, ty_vec3<u32>},
-    Params{Op::kMultiply, ty_vec3<u32>, ty_vec3<u32>, ty_vec3<u32>},
-    Params{Op::kDivide, ty_vec3<u32>, ty_vec3<u32>, ty_vec3<u32>},
-    Params{Op::kModulo, ty_vec3<u32>, ty_vec3<u32>, ty_vec3<u32>},
+    Params{Op::kAdd, ast_vec3<u32>, ast_vec3<u32>, sem_vec3<sem_u32>},
+    Params{Op::kSubtract, ast_vec3<u32>, ast_vec3<u32>, sem_vec3<sem_u32>},
+    Params{Op::kMultiply, ast_vec3<u32>, ast_vec3<u32>, sem_vec3<sem_u32>},
+    Params{Op::kDivide, ast_vec3<u32>, ast_vec3<u32>, sem_vec3<sem_u32>},
+    Params{Op::kModulo, ast_vec3<u32>, ast_vec3<u32>, sem_vec3<sem_u32>},
 
-    Params{Op::kAdd, ty_vec3<f32>, ty_vec3<f32>, ty_vec3<f32>},
-    Params{Op::kSubtract, ty_vec3<f32>, ty_vec3<f32>, ty_vec3<f32>},
-    Params{Op::kMultiply, ty_vec3<f32>, ty_vec3<f32>, ty_vec3<f32>},
-    Params{Op::kDivide, ty_vec3<f32>, ty_vec3<f32>, ty_vec3<f32>},
-    Params{Op::kModulo, ty_vec3<f32>, ty_vec3<f32>, ty_vec3<f32>},
+    Params{Op::kAdd, ast_vec3<f32>, ast_vec3<f32>, sem_vec3<sem_f32>},
+    Params{Op::kSubtract, ast_vec3<f32>, ast_vec3<f32>, sem_vec3<sem_f32>},
+    Params{Op::kMultiply, ast_vec3<f32>, ast_vec3<f32>, sem_vec3<sem_f32>},
+    Params{Op::kDivide, ast_vec3<f32>, ast_vec3<f32>, sem_vec3<sem_f32>},
+    Params{Op::kModulo, ast_vec3<f32>, ast_vec3<f32>, sem_vec3<sem_f32>},
 
     // Binary arithmetic expressions with mixed scalar, vector, and matrix
     // operands
-    Params{Op::kMultiply, ty_vec3<f32>, ty_f32, ty_vec3<f32>},
-    Params{Op::kMultiply, ty_f32, ty_vec3<f32>, ty_vec3<f32>},
+    Params{Op::kMultiply, ast_vec3<f32>, ast_f32, sem_vec3<sem_f32>},
+    Params{Op::kMultiply, ast_f32, ast_vec3<f32>, sem_vec3<sem_f32>},
 
-    Params{Op::kMultiply, ty_mat3x3<f32>, ty_f32, ty_mat3x3<f32>},
-    Params{Op::kMultiply, ty_f32, ty_mat3x3<f32>, ty_mat3x3<f32>},
+    Params{Op::kMultiply, ast_mat3x3<f32>, ast_f32, sem_mat3x3<sem_f32>},
+    Params{Op::kMultiply, ast_f32, ast_mat3x3<f32>, sem_mat3x3<sem_f32>},
 
-    Params{Op::kMultiply, ty_vec3<f32>, ty_mat3x3<f32>, ty_vec3<f32>},
-    Params{Op::kMultiply, ty_mat3x3<f32>, ty_vec3<f32>, ty_vec3<f32>},
-    Params{Op::kMultiply, ty_mat3x3<f32>, ty_mat3x3<f32>, ty_mat3x3<f32>},
+    Params{Op::kMultiply, ast_vec3<f32>, ast_mat3x3<f32>, sem_vec3<sem_f32>},
+    Params{Op::kMultiply, ast_mat3x3<f32>, ast_vec3<f32>, sem_vec3<sem_f32>},
+    Params{Op::kMultiply, ast_mat3x3<f32>, ast_mat3x3<f32>,
+           sem_mat3x3<sem_f32>},
 
     // Comparison expressions
     // https://gpuweb.github.io/gpuweb/wgsl.html#comparison-expr
 
     // Comparisons over scalars
-    Params{Op::kEqual, ty_bool_, ty_bool_, ty_bool_},
-    Params{Op::kNotEqual, ty_bool_, ty_bool_, ty_bool_},
+    Params{Op::kEqual, ast_bool, ast_bool, sem_bool},
+    Params{Op::kNotEqual, ast_bool, ast_bool, sem_bool},
 
-    Params{Op::kEqual, ty_i32, ty_i32, ty_bool_},
-    Params{Op::kNotEqual, ty_i32, ty_i32, ty_bool_},
-    Params{Op::kLessThan, ty_i32, ty_i32, ty_bool_},
-    Params{Op::kLessThanEqual, ty_i32, ty_i32, ty_bool_},
-    Params{Op::kGreaterThan, ty_i32, ty_i32, ty_bool_},
-    Params{Op::kGreaterThanEqual, ty_i32, ty_i32, ty_bool_},
+    Params{Op::kEqual, ast_i32, ast_i32, sem_bool},
+    Params{Op::kNotEqual, ast_i32, ast_i32, sem_bool},
+    Params{Op::kLessThan, ast_i32, ast_i32, sem_bool},
+    Params{Op::kLessThanEqual, ast_i32, ast_i32, sem_bool},
+    Params{Op::kGreaterThan, ast_i32, ast_i32, sem_bool},
+    Params{Op::kGreaterThanEqual, ast_i32, ast_i32, sem_bool},
 
-    Params{Op::kEqual, ty_u32, ty_u32, ty_bool_},
-    Params{Op::kNotEqual, ty_u32, ty_u32, ty_bool_},
-    Params{Op::kLessThan, ty_u32, ty_u32, ty_bool_},
-    Params{Op::kLessThanEqual, ty_u32, ty_u32, ty_bool_},
-    Params{Op::kGreaterThan, ty_u32, ty_u32, ty_bool_},
-    Params{Op::kGreaterThanEqual, ty_u32, ty_u32, ty_bool_},
+    Params{Op::kEqual, ast_u32, ast_u32, sem_bool},
+    Params{Op::kNotEqual, ast_u32, ast_u32, sem_bool},
+    Params{Op::kLessThan, ast_u32, ast_u32, sem_bool},
+    Params{Op::kLessThanEqual, ast_u32, ast_u32, sem_bool},
+    Params{Op::kGreaterThan, ast_u32, ast_u32, sem_bool},
+    Params{Op::kGreaterThanEqual, ast_u32, ast_u32, sem_bool},
 
-    Params{Op::kEqual, ty_f32, ty_f32, ty_bool_},
-    Params{Op::kNotEqual, ty_f32, ty_f32, ty_bool_},
-    Params{Op::kLessThan, ty_f32, ty_f32, ty_bool_},
-    Params{Op::kLessThanEqual, ty_f32, ty_f32, ty_bool_},
-    Params{Op::kGreaterThan, ty_f32, ty_f32, ty_bool_},
-    Params{Op::kGreaterThanEqual, ty_f32, ty_f32, ty_bool_},
+    Params{Op::kEqual, ast_f32, ast_f32, sem_bool},
+    Params{Op::kNotEqual, ast_f32, ast_f32, sem_bool},
+    Params{Op::kLessThan, ast_f32, ast_f32, sem_bool},
+    Params{Op::kLessThanEqual, ast_f32, ast_f32, sem_bool},
+    Params{Op::kGreaterThan, ast_f32, ast_f32, sem_bool},
+    Params{Op::kGreaterThanEqual, ast_f32, ast_f32, sem_bool},
 
     // Comparisons over vectors
-    Params{Op::kEqual, ty_vec3<bool>, ty_vec3<bool>, ty_vec3<bool>},
-    Params{Op::kNotEqual, ty_vec3<bool>, ty_vec3<bool>, ty_vec3<bool>},
+    Params{Op::kEqual, ast_vec3<bool>, ast_vec3<bool>, sem_vec3<sem_bool>},
+    Params{Op::kNotEqual, ast_vec3<bool>, ast_vec3<bool>, sem_vec3<sem_bool>},
 
-    Params{Op::kEqual, ty_vec3<i32>, ty_vec3<i32>, ty_vec3<bool>},
-    Params{Op::kNotEqual, ty_vec3<i32>, ty_vec3<i32>, ty_vec3<bool>},
-    Params{Op::kLessThan, ty_vec3<i32>, ty_vec3<i32>, ty_vec3<bool>},
-    Params{Op::kLessThanEqual, ty_vec3<i32>, ty_vec3<i32>, ty_vec3<bool>},
-    Params{Op::kGreaterThan, ty_vec3<i32>, ty_vec3<i32>, ty_vec3<bool>},
-    Params{Op::kGreaterThanEqual, ty_vec3<i32>, ty_vec3<i32>, ty_vec3<bool>},
+    Params{Op::kEqual, ast_vec3<i32>, ast_vec3<i32>, sem_vec3<sem_bool>},
+    Params{Op::kNotEqual, ast_vec3<i32>, ast_vec3<i32>, sem_vec3<sem_bool>},
+    Params{Op::kLessThan, ast_vec3<i32>, ast_vec3<i32>, sem_vec3<sem_bool>},
+    Params{Op::kLessThanEqual, ast_vec3<i32>, ast_vec3<i32>,
+           sem_vec3<sem_bool>},
+    Params{Op::kGreaterThan, ast_vec3<i32>, ast_vec3<i32>, sem_vec3<sem_bool>},
+    Params{Op::kGreaterThanEqual, ast_vec3<i32>, ast_vec3<i32>,
+           sem_vec3<sem_bool>},
 
-    Params{Op::kEqual, ty_vec3<u32>, ty_vec3<u32>, ty_vec3<bool>},
-    Params{Op::kNotEqual, ty_vec3<u32>, ty_vec3<u32>, ty_vec3<bool>},
-    Params{Op::kLessThan, ty_vec3<u32>, ty_vec3<u32>, ty_vec3<bool>},
-    Params{Op::kLessThanEqual, ty_vec3<u32>, ty_vec3<u32>, ty_vec3<bool>},
-    Params{Op::kGreaterThan, ty_vec3<u32>, ty_vec3<u32>, ty_vec3<bool>},
-    Params{Op::kGreaterThanEqual, ty_vec3<u32>, ty_vec3<u32>, ty_vec3<bool>},
+    Params{Op::kEqual, ast_vec3<u32>, ast_vec3<u32>, sem_vec3<sem_bool>},
+    Params{Op::kNotEqual, ast_vec3<u32>, ast_vec3<u32>, sem_vec3<sem_bool>},
+    Params{Op::kLessThan, ast_vec3<u32>, ast_vec3<u32>, sem_vec3<sem_bool>},
+    Params{Op::kLessThanEqual, ast_vec3<u32>, ast_vec3<u32>,
+           sem_vec3<sem_bool>},
+    Params{Op::kGreaterThan, ast_vec3<u32>, ast_vec3<u32>, sem_vec3<sem_bool>},
+    Params{Op::kGreaterThanEqual, ast_vec3<u32>, ast_vec3<u32>,
+           sem_vec3<sem_bool>},
 
-    Params{Op::kEqual, ty_vec3<f32>, ty_vec3<f32>, ty_vec3<bool>},
-    Params{Op::kNotEqual, ty_vec3<f32>, ty_vec3<f32>, ty_vec3<bool>},
-    Params{Op::kLessThan, ty_vec3<f32>, ty_vec3<f32>, ty_vec3<bool>},
-    Params{Op::kLessThanEqual, ty_vec3<f32>, ty_vec3<f32>, ty_vec3<bool>},
-    Params{Op::kGreaterThan, ty_vec3<f32>, ty_vec3<f32>, ty_vec3<bool>},
-    Params{Op::kGreaterThanEqual, ty_vec3<f32>, ty_vec3<f32>, ty_vec3<bool>},
+    Params{Op::kEqual, ast_vec3<f32>, ast_vec3<f32>, sem_vec3<sem_bool>},
+    Params{Op::kNotEqual, ast_vec3<f32>, ast_vec3<f32>, sem_vec3<sem_bool>},
+    Params{Op::kLessThan, ast_vec3<f32>, ast_vec3<f32>, sem_vec3<sem_bool>},
+    Params{Op::kLessThanEqual, ast_vec3<f32>, ast_vec3<f32>,
+           sem_vec3<sem_bool>},
+    Params{Op::kGreaterThan, ast_vec3<f32>, ast_vec3<f32>, sem_vec3<sem_bool>},
+    Params{Op::kGreaterThanEqual, ast_vec3<f32>, ast_vec3<f32>,
+           sem_vec3<sem_bool>},
 
     // Bit expressions
     // https://gpuweb.github.io/gpuweb/wgsl.html#bit-expr
 
     // Binary bitwise operations
-    Params{Op::kOr, ty_i32, ty_i32, ty_i32},
-    Params{Op::kAnd, ty_i32, ty_i32, ty_i32},
-    Params{Op::kXor, ty_i32, ty_i32, ty_i32},
+    Params{Op::kOr, ast_i32, ast_i32, sem_i32},
+    Params{Op::kAnd, ast_i32, ast_i32, sem_i32},
+    Params{Op::kXor, ast_i32, ast_i32, sem_i32},
 
-    Params{Op::kOr, ty_u32, ty_u32, ty_u32},
-    Params{Op::kAnd, ty_u32, ty_u32, ty_u32},
-    Params{Op::kXor, ty_u32, ty_u32, ty_u32},
+    Params{Op::kOr, ast_u32, ast_u32, sem_u32},
+    Params{Op::kAnd, ast_u32, ast_u32, sem_u32},
+    Params{Op::kXor, ast_u32, ast_u32, sem_u32},
 
     // Bit shift expressions
-    Params{Op::kShiftLeft, ty_i32, ty_u32, ty_i32},
-    Params{Op::kShiftLeft, ty_vec3<i32>, ty_vec3<u32>, ty_vec3<i32>},
+    Params{Op::kShiftLeft, ast_i32, ast_u32, sem_i32},
+    Params{Op::kShiftLeft, ast_vec3<i32>, ast_vec3<u32>, sem_vec3<sem_i32>},
 
-    Params{Op::kShiftLeft, ty_u32, ty_u32, ty_u32},
-    Params{Op::kShiftLeft, ty_vec3<u32>, ty_vec3<u32>, ty_vec3<u32>},
+    Params{Op::kShiftLeft, ast_u32, ast_u32, sem_u32},
+    Params{Op::kShiftLeft, ast_vec3<u32>, ast_vec3<u32>, sem_vec3<sem_u32>},
 
-    Params{Op::kShiftRight, ty_i32, ty_u32, ty_i32},
-    Params{Op::kShiftRight, ty_vec3<i32>, ty_vec3<u32>, ty_vec3<i32>},
+    Params{Op::kShiftRight, ast_i32, ast_u32, sem_i32},
+    Params{Op::kShiftRight, ast_vec3<i32>, ast_vec3<u32>, sem_vec3<sem_i32>},
 
-    Params{Op::kShiftRight, ty_u32, ty_u32, ty_u32},
-    Params{Op::kShiftRight, ty_vec3<u32>, ty_vec3<u32>, ty_vec3<u32>}};
+    Params{Op::kShiftRight, ast_u32, ast_u32, sem_u32},
+    Params{Op::kShiftRight, ast_vec3<u32>, ast_vec3<u32>, sem_vec3<sem_u32>}};
 
 using Expr_Binary_Test_Valid = ResolverTestWithParam<Params>;
 TEST_P(Expr_Binary_Test_Valid, All) {
   auto& params = GetParam();
 
-  auto lhs_type = params.create_lhs_type(ty);
-  auto rhs_type = params.create_rhs_type(ty);
-  auto result_type = params.create_result_type(ty);
+  auto* lhs_type = params.create_lhs_type(ty);
+  auto* rhs_type = params.create_rhs_type(ty);
+  auto* result_type = params.create_result_type(ty);
 
   std::stringstream ss;
-  ss << lhs_type->FriendlyName(Symbols()) << " " << params.op << " "
-     << rhs_type->FriendlyName(Symbols());
+  ss << FriendlyName(lhs_type) << " " << params.op << " "
+     << FriendlyName(rhs_type);
   SCOPED_TRACE(ss.str());
 
   Global("lhs", lhs_type, ast::StorageClass::kInput);
@@ -1215,27 +1222,28 @@
   const Params& params = std::get<0>(GetParam());
   BinaryExprSide side = std::get<1>(GetParam());
 
-  auto lhs_type = params.create_lhs_type(ty);
-  auto rhs_type = params.create_rhs_type(ty);
+  auto* lhs_type = params.create_lhs_type(ty);
+  auto* rhs_type = params.create_rhs_type(ty);
 
   std::stringstream ss;
-  ss << lhs_type->FriendlyName(Symbols()) << " " << params.op << " "
-     << rhs_type->FriendlyName(Symbols());
+  ss << FriendlyName(lhs_type) << " " << params.op << " "
+     << FriendlyName(rhs_type);
 
   // For vectors and matrices, wrap the sub type in an alias
-  auto make_alias = [this](sem::Type* type) -> sem::Type* {
-    sem::Type* result;
-    if (auto* v = type->As<sem::Vector>()) {
-      result = create<sem::Vector>(
-          create<sem::Alias>(Symbols().New(), v->type()), v->size());
-    } else if (auto* m = type->As<sem::Matrix>()) {
-      result =
-          create<sem::Matrix>(create<sem::Alias>(Symbols().New(), m->type()),
-                              m->rows(), m->columns());
-    } else {
-      result = create<sem::Alias>(Symbols().New(), type);
+  auto make_alias = [this](ast::Type* type) -> ast::Type* {
+    if (auto* v = type->As<ast::Vector>()) {
+      auto alias = ty.alias(Symbols().New(), v->type());
+      AST().AddConstructedType(alias);
+      return ty.vec(alias, v->size());
     }
-    return result;
+    if (auto* m = type->As<ast::Matrix>()) {
+      auto alias = ty.alias(Symbols().New(), m->type());
+      AST().AddConstructedType(alias);
+      return ty.mat(alias, m->columns(), m->rows());
+    }
+    auto alias = ty.alias(Symbols().New(), type);
+    AST().AddConstructedType(alias);
+    return ty.type_name(alias.ast->name());
   };
 
   // Wrap in alias
@@ -1246,8 +1254,8 @@
     rhs_type = make_alias(rhs_type);
   }
 
-  ss << ", After aliasing: " << lhs_type->FriendlyName(Symbols()) << " "
-     << params.op << " " << rhs_type->FriendlyName(Symbols());
+  ss << ", After aliasing: " << FriendlyName(lhs_type) << " " << params.op
+     << " " << FriendlyName(rhs_type);
   SCOPED_TRACE(ss.str());
 
   Global("lhs", lhs_type, ast::StorageClass::kInput);
@@ -1261,7 +1269,7 @@
   ASSERT_NE(TypeOf(expr), nullptr);
   // TODO(amaiorano): Bring this back once we have a way to get the canonical
   // type
-  // auto* result_type = params.create_result_type(ty);
+  // auto* *result_type = params.create_result_type(ty);
   // ASSERT_TRUE(TypeOf(expr) == result_type);
 }
 INSTANTIATE_TEST_SUITE_P(
@@ -1273,10 +1281,10 @@
                                      BinaryExprSide::Both)));
 
 using Expr_Binary_Test_Invalid =
-    ResolverTestWithParam<std::tuple<Params, create_type_func_ptr>>;
+    ResolverTestWithParam<std::tuple<Params, create_ast_type_func_ptr>>;
 TEST_P(Expr_Binary_Test_Invalid, All) {
   const Params& params = std::get<0>(GetParam());
-  const create_type_func_ptr& create_type_func = std::get<1>(GetParam());
+  auto& create_type_func = std::get<1>(GetParam());
 
   // Currently, for most operations, for a given lhs type, there is exactly one
   // rhs type allowed.  The only exception is for multiplication, which allows
@@ -1290,8 +1298,8 @@
     return;
   }
 
-  auto lhs_type = params.create_lhs_type(ty);
-  auto rhs_type = create_type_func(ty);
+  auto* lhs_type = params.create_lhs_type(ty);
+  auto* rhs_type = create_type_func(ty);
 
   // Skip exceptions: multiplication of f32, vecN<f32>, and matNxN<f32>
   if (params.op == Op::kMultiply &&
@@ -1301,8 +1309,8 @@
   }
 
   std::stringstream ss;
-  ss << lhs_type->FriendlyName(Symbols()) << " " << params.op << " "
-     << rhs_type->FriendlyName(Symbols());
+  ss << FriendlyName(lhs_type) << " " << params.op << " "
+     << FriendlyName(rhs_type);
   SCOPED_TRACE(ss.str());
 
   Global("lhs", lhs_type, ast::StorageClass::kInput);
@@ -1316,9 +1324,8 @@
   ASSERT_EQ(r()->error(),
             "12:34 error: Binary expression operand types are invalid for "
             "this operation: " +
-                lhs_type->FriendlyName(Symbols()) + " " +
-                FriendlyName(expr->op()) + " " +
-                rhs_type->FriendlyName(Symbols()));
+                FriendlyName(lhs_type) + " " + ast::FriendlyName(expr->op()) +
+                " " + FriendlyName(rhs_type));
 }
 INSTANTIATE_TEST_SUITE_P(
     ResolverTest,
@@ -1365,9 +1372,8 @@
     ASSERT_EQ(r()->error(),
               "12:34 error: Binary expression operand types are invalid for "
               "this operation: " +
-                  lhs_type->FriendlyName(Symbols()) + " " +
-                  FriendlyName(expr->op()) + " " +
-                  rhs_type->FriendlyName(Symbols()));
+                  FriendlyName(lhs_type) + " " + ast::FriendlyName(expr->op()) +
+                  " " + FriendlyName(rhs_type));
   }
 }
 auto all_dimension_values = testing::Values(2u, 3u, 4u);
@@ -1405,9 +1411,8 @@
     ASSERT_EQ(r()->error(),
               "12:34 error: Binary expression operand types are invalid for "
               "this operation: " +
-                  lhs_type->FriendlyName(Symbols()) + " " +
-                  FriendlyName(expr->op()) + " " +
-                  rhs_type->FriendlyName(Symbols()));
+                  FriendlyName(lhs_type) + " " + ast::FriendlyName(expr->op()) +
+                  " " + FriendlyName(rhs_type));
   }
 }
 INSTANTIATE_TEST_SUITE_P(ResolverTest,
diff --git a/src/resolver/resolver_test_helper.h b/src/resolver/resolver_test_helper.h
index a7fd2cf..910a230 100644
--- a/src/resolver/resolver_test_helper.h
+++ b/src/resolver/resolver_test_helper.h
@@ -16,6 +16,7 @@
 #define SRC_RESOLVER_RESOLVER_TEST_HELPER_H_
 
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "gtest/gtest.h"
@@ -95,6 +96,14 @@
     return true;
   }
 
+  /// @param type a type
+  /// @returns the name for `type` that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(typ::Type type) {
+    return type.ast ? type.ast->FriendlyName(Symbols())
+                    : type.sem->FriendlyName(Symbols());
+  }
+
  private:
   std::unique_ptr<Resolver> resolver_;
 };
@@ -105,94 +114,151 @@
 class ResolverTestWithParam : public TestHelper,
                               public testing::TestWithParam<T> {};
 
-inline typ::Type ty_bool_(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type* ast_bool(const ProgramBuilder::TypesBuilder& ty) {
   return ty.bool_();
 }
-inline typ::Type ty_i32(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type* ast_i32(const ProgramBuilder::TypesBuilder& ty) {
   return ty.i32();
 }
-inline typ::Type ty_u32(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type* ast_u32(const ProgramBuilder::TypesBuilder& ty) {
   return ty.u32();
 }
-inline typ::Type ty_f32(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type* ast_f32(const ProgramBuilder::TypesBuilder& ty) {
   return ty.f32();
 }
 
-using create_type_func_ptr =
-    typ::Type (*)(const ProgramBuilder::TypesBuilder& ty);
+using create_ast_type_func_ptr =
+    ast::Type* (*)(const ProgramBuilder::TypesBuilder& ty);
 
 template <typename T>
-typ::Type ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
+ast::Type* ast_vec2(const ProgramBuilder::TypesBuilder& ty) {
   return ty.vec2<T>();
 }
 
-template <create_type_func_ptr create_type>
-typ::Type ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
+template <create_ast_type_func_ptr create_type>
+ast::Type* ast_vec2(const ProgramBuilder::TypesBuilder& ty) {
   return ty.vec2(create_type(ty));
 }
 
 template <typename T>
-typ::Type ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
+ast::Type* ast_vec3(const ProgramBuilder::TypesBuilder& ty) {
   return ty.vec3<T>();
 }
 
-template <create_type_func_ptr create_type>
-typ::Type ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
+template <create_ast_type_func_ptr create_type>
+ast::Type* ast_vec3(const ProgramBuilder::TypesBuilder& ty) {
   return ty.vec3(create_type(ty));
 }
 
 template <typename T>
-typ::Type ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
+ast::Type* ast_vec4(const ProgramBuilder::TypesBuilder& ty) {
   return ty.vec4<T>();
 }
 
-template <create_type_func_ptr create_type>
-typ::Type ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
+template <create_ast_type_func_ptr create_type>
+ast::Type* ast_vec4(const ProgramBuilder::TypesBuilder& ty) {
   return ty.vec4(create_type(ty));
 }
 
 template <typename T>
-typ::Type ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
+ast::Type* ast_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
   return ty.mat2x2<T>();
 }
 
-template <create_type_func_ptr create_type>
-typ::Type ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
+template <create_ast_type_func_ptr create_type>
+ast::Type* ast_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
   return ty.mat2x2(create_type(ty));
 }
 
 template <typename T>
-typ::Type ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
+ast::Type* ast_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
   return ty.mat3x3<T>();
 }
 
-template <create_type_func_ptr create_type>
-typ::Type ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
+template <create_ast_type_func_ptr create_type>
+ast::Type* ast_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
   return ty.mat3x3(create_type(ty));
 }
 
 template <typename T>
-typ::Type ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
+ast::Type* ast_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
   return ty.mat4x4<T>();
 }
 
-template <create_type_func_ptr create_type>
-typ::Type ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
+template <create_ast_type_func_ptr create_type>
+ast::Type* ast_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
   return ty.mat4x4(create_type(ty));
 }
 
-template <create_type_func_ptr create_type>
-typ::Type ty_alias(const ProgramBuilder::TypesBuilder& ty) {
-  auto type = create_type(ty);
-  return ty.alias("alias_" + type->type_name(), type);
+template <create_ast_type_func_ptr create_type>
+ast::Type* ast_alias(const ProgramBuilder::TypesBuilder& ty) {
+  auto* type = create_type(ty);
+  auto name = ty.builder->Symbols().Register("alias_" + type->type_name());
+  if (!ty.builder->AST().LookupType(name)) {
+    ty.builder->AST().AddConstructedType(ty.alias(name, type));
+  }
+  return ty.builder->create<ast::TypeName>(name);
 }
 
-template <create_type_func_ptr create_type>
-typ::Type ty_access(const ProgramBuilder::TypesBuilder& ty) {
-  auto type = create_type(ty);
+template <create_ast_type_func_ptr create_type>
+ast::Type* ast_access(const ProgramBuilder::TypesBuilder& ty) {
+  auto* type = create_type(ty);
   return ty.access(ast::AccessControl::kReadOnly, type);
 }
 
+inline sem::Type* sem_bool(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.builder->create<sem::Bool>();
+}
+inline sem::Type* sem_i32(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.builder->create<sem::I32>();
+}
+inline sem::Type* sem_u32(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.builder->create<sem::U32>();
+}
+inline sem::Type* sem_f32(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.builder->create<sem::F32>();
+}
+
+using create_sem_type_func_ptr =
+    sem::Type* (*)(const ProgramBuilder::TypesBuilder& ty);
+
+template <create_sem_type_func_ptr create_type>
+sem::Type* sem_vec2(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.builder->create<sem::Vector>(create_type(ty), 2);
+}
+
+template <create_sem_type_func_ptr create_type>
+sem::Type* sem_vec3(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.builder->create<sem::Vector>(create_type(ty), 3);
+}
+
+template <create_sem_type_func_ptr create_type>
+sem::Type* sem_vec4(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.builder->create<sem::Vector>(create_type(ty), 4);
+}
+
+template <create_sem_type_func_ptr create_type>
+sem::Type* sem_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.builder->create<sem::Matrix>(create_type(ty), 2, 2);
+}
+
+template <create_sem_type_func_ptr create_type>
+sem::Type* sem_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.builder->create<sem::Matrix>(create_type(ty), 3, 3);
+}
+
+template <create_sem_type_func_ptr create_type>
+sem::Type* sem_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
+  return ty.builder->create<sem::Matrix>(create_type(ty), 4, 4);
+}
+
+template <create_sem_type_func_ptr create_type>
+sem::Type* sem_access(const ProgramBuilder::TypesBuilder& ty) {
+  auto* type = create_type(ty);
+  return ty.builder->create<sem::AccessControl>(ast::AccessControl::kReadOnly,
+                                                type);
+}
+
 }  // namespace resolver
 }  // namespace tint
 
diff --git a/src/resolver/type_constructor_validation_test.cc b/src/resolver/type_constructor_validation_test.cc
index cbbc60d..055464e 100644
--- a/src/resolver/type_constructor_validation_test.cc
+++ b/src/resolver/type_constructor_validation_test.cc
@@ -19,11 +19,11 @@
 namespace {
 
 /// @return the element type of `type` for vec and mat, otherwise `type` itself
-sem::Type* ElementTypeOf(sem::Type* type) {
-  if (auto* v = type->As<sem::Vector>()) {
+ast::Type* ElementTypeOf(ast::Type* type) {
+  if (auto* v = type->As<ast::Vector>()) {
     return v->type();
   }
-  if (auto* m = type->As<sem::Matrix>()) {
+  if (auto* m = type->As<ast::Matrix>()) {
     return m->type();
   }
   return type;
@@ -34,7 +34,8 @@
 
 namespace InferTypeTest {
 struct Params {
-  create_type_func_ptr create_rhs_type;
+  create_ast_type_func_ptr create_rhs_ast_type;
+  create_sem_type_func_ptr create_rhs_sem_type;
 };
 
 // Helpers and typedefs
@@ -66,7 +67,7 @@
   // }
   auto& params = GetParam();
 
-  auto rhs_type = params.create_rhs_type(ty);
+  auto* rhs_type = params.create_rhs_ast_type(ty);
   auto* constructor_expr = ConstructValueFilledWith(rhs_type, 0);
 
   auto sc = ast::StorageClass::kFunction;
@@ -77,30 +78,33 @@
   WrapInFunction(Decl(a), Assign(a_ident, "a"));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_EQ(TypeOf(a_ident), ty.pointer(rhs_type->UnwrapAliasIfNeeded(), sc));
+  auto* got = TypeOf(a_ident);
+  auto* expected = ty.pointer(params.create_rhs_sem_type(ty), sc).sem;
+  ASSERT_EQ(got, expected) << "got:      " << FriendlyName(got) << "\n"
+                           << "expected: " << FriendlyName(expected) << "\n";
 }
 
 static constexpr Params from_constructor_expression_cases[] = {
-    Params{ty_bool_},
-    Params{ty_i32},
-    Params{ty_u32},
-    Params{ty_f32},
-    Params{ty_vec3<i32>},
-    Params{ty_vec3<u32>},
-    Params{ty_vec3<f32>},
-    Params{ty_mat3x3<i32>},
-    Params{ty_mat3x3<u32>},
-    Params{ty_mat3x3<f32>},
-    Params{ty_alias<ty_bool_>},
-    Params{ty_alias<ty_i32>},
-    Params{ty_alias<ty_u32>},
-    Params{ty_alias<ty_f32>},
-    Params{ty_alias<ty_vec3<i32>>},
-    Params{ty_alias<ty_vec3<u32>>},
-    Params{ty_alias<ty_vec3<f32>>},
-    Params{ty_alias<ty_mat3x3<i32>>},
-    Params{ty_alias<ty_mat3x3<u32>>},
-    Params{ty_alias<ty_mat3x3<f32>>},
+    Params{ast_bool, sem_bool},
+    Params{ast_i32, sem_i32},
+    Params{ast_u32, sem_u32},
+    Params{ast_f32, sem_f32},
+    Params{ast_vec3<i32>, sem_vec3<sem_i32>},
+    Params{ast_vec3<u32>, sem_vec3<sem_u32>},
+    Params{ast_vec3<f32>, sem_vec3<sem_f32>},
+    Params{ast_mat3x3<i32>, sem_mat3x3<sem_i32>},
+    Params{ast_mat3x3<u32>, sem_mat3x3<sem_u32>},
+    Params{ast_mat3x3<f32>, sem_mat3x3<sem_f32>},
+    Params{ast_alias<ast_bool>, sem_bool},
+    Params{ast_alias<ast_i32>, sem_i32},
+    Params{ast_alias<ast_u32>, sem_u32},
+    Params{ast_alias<ast_f32>, sem_f32},
+    Params{ast_alias<ast_vec3<i32>>, sem_vec3<sem_i32>},
+    Params{ast_alias<ast_vec3<u32>>, sem_vec3<sem_u32>},
+    Params{ast_alias<ast_vec3<f32>>, sem_vec3<sem_f32>},
+    Params{ast_alias<ast_mat3x3<i32>>, sem_mat3x3<sem_i32>},
+    Params{ast_alias<ast_mat3x3<u32>>, sem_mat3x3<sem_u32>},
+    Params{ast_alias<ast_mat3x3<f32>>, sem_mat3x3<sem_f32>},
 };
 INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
                          InferTypeTest_FromConstructorExpression,
@@ -114,7 +118,7 @@
   // }
   auto& params = GetParam();
 
-  auto rhs_type = params.create_rhs_type(ty);
+  auto* rhs_type = params.create_rhs_ast_type(ty);
 
   auto* arith_lhs_expr = ConstructValueFilledWith(rhs_type, 2);
   auto* arith_rhs_expr = ConstructValueFilledWith(ElementTypeOf(rhs_type), 3);
@@ -128,11 +132,17 @@
   WrapInFunction(Decl(a), Assign(a_ident, "a"));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_EQ(TypeOf(a_ident), ty.pointer(rhs_type, sc));
+  auto* got = TypeOf(a_ident);
+  auto* expected = ty.pointer(params.create_rhs_sem_type(ty), sc).sem;
+  ASSERT_EQ(got, expected) << "got:      " << FriendlyName(got) << "\n"
+                           << "expected: " << FriendlyName(expected) << "\n";
 }
 static constexpr Params from_arithmetic_expression_cases[] = {
-    Params{ty_i32},       Params{ty_u32},         Params{ty_f32},
-    Params{ty_vec3<f32>}, Params{ty_mat3x3<f32>},
+    Params{ast_i32, sem_i32},
+    Params{ast_u32, sem_u32},
+    Params{ast_f32, sem_f32},
+    Params{ast_vec3<f32>, sem_vec3<sem_f32>},
+    Params{ast_mat3x3<f32>, sem_mat3x3<sem_f32>},
 
     // TODO(amaiorano): Uncomment once https://crbug.com/tint/680 is fixed
     // Params{ty_alias<ty_i32>},
@@ -159,43 +169,44 @@
   // }
   auto& params = GetParam();
 
-  auto rhs_type = params.create_rhs_type(ty);
-
-  Func("foo", {}, rhs_type, {Return(ConstructValueFilledWith(rhs_type, 0))},
+  Func("foo", {}, params.create_rhs_ast_type(ty),
+       {Return(ConstructValueFilledWith(params.create_rhs_ast_type(ty), 0))},
        {});
-  auto* constructor_expr = Call(Expr("foo"));
 
   auto sc = ast::StorageClass::kFunction;
-  auto* a = Var("a", nullptr, sc, constructor_expr);
+  auto* a = Var("a", nullptr, sc, Call(Expr("foo")));
   // Self-assign 'a' to force the expression to be resolved so we can test its
   // type below
   auto* a_ident = Expr("a");
   WrapInFunction(Decl(a), Assign(a_ident, "a"));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_EQ(TypeOf(a_ident), ty.pointer(rhs_type->UnwrapAliasIfNeeded(), sc));
+  auto* got = TypeOf(a_ident);
+  auto* expected = ty.pointer(params.create_rhs_sem_type(ty), sc).sem;
+  ASSERT_EQ(got, expected) << "got:      " << FriendlyName(got) << "\n"
+                           << "expected: " << FriendlyName(expected) << "\n";
 }
 static constexpr Params from_call_expression_cases[] = {
-    Params{ty_bool_},
-    Params{ty_i32},
-    Params{ty_u32},
-    Params{ty_f32},
-    Params{ty_vec3<i32>},
-    Params{ty_vec3<u32>},
-    Params{ty_vec3<f32>},
-    Params{ty_mat3x3<i32>},
-    Params{ty_mat3x3<u32>},
-    Params{ty_mat3x3<f32>},
-    Params{ty_alias<ty_bool_>},
-    Params{ty_alias<ty_i32>},
-    Params{ty_alias<ty_u32>},
-    Params{ty_alias<ty_f32>},
-    Params{ty_alias<ty_vec3<i32>>},
-    Params{ty_alias<ty_vec3<u32>>},
-    Params{ty_alias<ty_vec3<f32>>},
-    Params{ty_alias<ty_mat3x3<i32>>},
-    Params{ty_alias<ty_mat3x3<u32>>},
-    Params{ty_alias<ty_mat3x3<f32>>},
+    Params{ast_bool, sem_bool},
+    Params{ast_i32, sem_i32},
+    Params{ast_u32, sem_u32},
+    Params{ast_f32, sem_f32},
+    Params{ast_vec3<i32>, sem_vec3<sem_i32>},
+    Params{ast_vec3<u32>, sem_vec3<sem_u32>},
+    Params{ast_vec3<f32>, sem_vec3<sem_f32>},
+    Params{ast_mat3x3<i32>, sem_mat3x3<sem_i32>},
+    Params{ast_mat3x3<u32>, sem_mat3x3<sem_u32>},
+    Params{ast_mat3x3<f32>, sem_mat3x3<sem_f32>},
+    Params{ast_alias<ast_bool>, sem_bool},
+    Params{ast_alias<ast_i32>, sem_i32},
+    Params{ast_alias<ast_u32>, sem_u32},
+    Params{ast_alias<ast_f32>, sem_f32},
+    Params{ast_alias<ast_vec3<i32>>, sem_vec3<sem_i32>},
+    Params{ast_alias<ast_vec3<u32>>, sem_vec3<sem_u32>},
+    Params{ast_alias<ast_vec3<f32>>, sem_vec3<sem_f32>},
+    Params{ast_alias<ast_mat3x3<i32>>, sem_mat3x3<sem_i32>},
+    Params{ast_alias<ast_mat3x3<u32>>, sem_mat3x3<sem_u32>},
+    Params{ast_alias<ast_mat3x3<f32>>, sem_mat3x3<sem_f32>},
 };
 INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
                          InferTypeTest_FromCallExpression,
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index d4abb28..c7317e3 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -445,48 +445,57 @@
 
 namespace GetCanonicalTests {
 struct Params {
-  create_type_func_ptr create_type;
-  create_type_func_ptr create_canonical_type;
+  create_ast_type_func_ptr create_ast_type;
+  create_sem_type_func_ptr create_sem_type;
 };
 
 static constexpr Params cases[] = {
-    Params{ty_bool_, ty_bool_},
-    Params{ty_alias<ty_bool_>, ty_bool_},
-    Params{ty_alias<ty_alias<ty_bool_>>, ty_bool_},
+    Params{ast_bool, sem_bool},
+    Params{ast_alias<ast_bool>, sem_bool},
+    Params{ast_alias<ast_alias<ast_bool>>, sem_bool},
 
-    Params{ty_vec3<ty_f32>, ty_vec3<ty_f32>},
-    Params{ty_alias<ty_vec3<ty_f32>>, ty_vec3<ty_f32>},
-    Params{ty_alias<ty_alias<ty_vec3<ty_f32>>>, ty_vec3<ty_f32>},
+    Params{ast_vec3<ast_f32>, sem_vec3<sem_f32>},
+    Params{ast_alias<ast_vec3<ast_f32>>, sem_vec3<sem_f32>},
+    Params{ast_alias<ast_alias<ast_vec3<ast_f32>>>, sem_vec3<sem_f32>},
 
-    Params{ty_vec3<ty_alias<ty_f32>>, ty_vec3<ty_f32>},
-    Params{ty_alias<ty_vec3<ty_alias<ty_f32>>>, ty_vec3<ty_f32>},
-    Params{ty_alias<ty_alias<ty_vec3<ty_alias<ty_f32>>>>, ty_vec3<ty_f32>},
-    Params{ty_alias<ty_alias<ty_vec3<ty_alias<ty_alias<ty_f32>>>>>,
-           ty_vec3<ty_f32>},
+    Params{ast_vec3<ast_alias<ast_f32>>, sem_vec3<sem_f32>},
+    Params{ast_alias<ast_vec3<ast_alias<ast_f32>>>, sem_vec3<sem_f32>},
+    Params{ast_alias<ast_alias<ast_vec3<ast_alias<ast_f32>>>>,
+           sem_vec3<sem_f32>},
+    Params{ast_alias<ast_alias<ast_vec3<ast_alias<ast_alias<ast_f32>>>>>,
+           sem_vec3<sem_f32>},
 
-    Params{ty_mat3x3<ty_alias<ty_f32>>, ty_mat3x3<ty_f32>},
-    Params{ty_alias<ty_mat3x3<ty_alias<ty_f32>>>, ty_mat3x3<ty_f32>},
-    Params{ty_alias<ty_alias<ty_mat3x3<ty_alias<ty_f32>>>>, ty_mat3x3<ty_f32>},
-    Params{ty_alias<ty_alias<ty_mat3x3<ty_alias<ty_alias<ty_f32>>>>>,
-           ty_mat3x3<ty_f32>},
+    Params{ast_mat3x3<ast_alias<ast_f32>>, sem_mat3x3<sem_f32>},
+    Params{ast_alias<ast_mat3x3<ast_alias<ast_f32>>>, sem_mat3x3<sem_f32>},
+    Params{ast_alias<ast_alias<ast_mat3x3<ast_alias<ast_f32>>>>,
+           sem_mat3x3<sem_f32>},
+    Params{ast_alias<ast_alias<ast_mat3x3<ast_alias<ast_alias<ast_f32>>>>>,
+           sem_mat3x3<sem_f32>},
 
-    Params{ty_alias<ty_access<ty_alias<ty_bool_>>>, ty_access<ty_bool_>},
-    Params{ty_alias<ty_access<ty_alias<ty_vec3<ty_access<ty_f32>>>>>,
-           ty_access<ty_vec3<ty_access<ty_f32>>>},
-    Params{ty_alias<ty_access<ty_alias<ty_mat3x3<ty_access<ty_f32>>>>>,
-           ty_access<ty_mat3x3<ty_access<ty_f32>>>},
+    Params{ast_alias<ast_access<ast_alias<ast_bool>>>, sem_access<sem_bool>},
+    Params{ast_alias<ast_access<ast_alias<ast_vec3<ast_access<ast_f32>>>>>,
+           sem_access<sem_vec3<sem_access<sem_f32>>>},
+    Params{ast_alias<ast_access<ast_alias<ast_mat3x3<ast_access<ast_f32>>>>>,
+           sem_access<sem_mat3x3<sem_access<sem_f32>>>},
 };
 
 using CanonicalTest = ResolverTestWithParam<Params>;
 TEST_P(CanonicalTest, All) {
   auto& params = GetParam();
 
-  auto type = params.create_type(ty);
-  auto expected_canonical_type = params.create_canonical_type(ty);
+  auto* type = params.create_ast_type(ty);
 
-  auto* canonical_type = r()->Canonical(type);
+  auto* var = Var("v", type, ast::StorageClass::kFunction);
+  auto* expr = Expr("v");
+  WrapInFunction(var, expr);
 
-  EXPECT_EQ(canonical_type, expected_canonical_type);
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* got = TypeOf(expr)->UnwrapPtrIfNeeded();
+  auto* expected = params.create_sem_type(ty);
+
+  EXPECT_EQ(got, expected) << "got:      " << FriendlyName(got) << "\n"
+                           << "expected: " << FriendlyName(expected) << "\n";
 }
 INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
                          CanonicalTest,
@@ -529,26 +538,26 @@
                          testing::ValuesIn(dimension_cases));
 
 struct TypeParams {
-  create_type_func_ptr type_func;
+  create_ast_type_func_ptr type_func;
   bool is_valid;
 };
 
 static constexpr TypeParams type_cases[] = {
-    TypeParams{ty_bool_, false},
-    TypeParams{ty_i32, true},
-    TypeParams{ty_u32, true},
-    TypeParams{ty_f32, true},
+    TypeParams{ast_bool, false},
+    TypeParams{ast_i32, true},
+    TypeParams{ast_u32, true},
+    TypeParams{ast_f32, true},
 
-    TypeParams{ty_alias<ty_bool_>, false},
-    TypeParams{ty_alias<ty_i32>, true},
-    TypeParams{ty_alias<ty_u32>, true},
-    TypeParams{ty_alias<ty_f32>, true},
+    TypeParams{ast_alias<ast_bool>, false},
+    TypeParams{ast_alias<ast_i32>, true},
+    TypeParams{ast_alias<ast_u32>, true},
+    TypeParams{ast_alias<ast_f32>, true},
 
-    TypeParams{ty_vec3<ty_f32>, false},
-    TypeParams{ty_mat3x3<ty_f32>, false},
+    TypeParams{ast_vec3<ast_f32>, false},
+    TypeParams{ast_mat3x3<ast_f32>, false},
 
-    TypeParams{ty_alias<ty_vec3<ty_f32>>, false},
-    TypeParams{ty_alias<ty_mat3x3<ty_f32>>, false}};
+    TypeParams{ast_alias<ast_vec3<ast_f32>>, false},
+    TypeParams{ast_alias<ast_mat3x3<ast_f32>>, false}};
 
 using MultisampledTextureTypeTest = ResolverTestWithParam<TypeParams>;
 TEST_P(MultisampledTextureTypeTest, All) {
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
index c76e396..4ffb660 100644
--- a/src/resolver/validation_test.cc
+++ b/src/resolver/validation_test.cc
@@ -2041,9 +2041,10 @@
 TEST_F(ResolverValidationTest, Expr_MatrixConstructor_ArgumentTypeAlias_Error) {
   auto alias = ty.alias("VectorUnsigned2", ty.vec2<u32>());
   AST().AddConstructedType(alias);
-  auto* tc = mat2x2<f32>(create<ast::TypeConstructorExpression>(
-                             Source{{12, 34}}, alias, ExprList()),
-                         vec2<f32>());
+  auto* tc = mat2x2<f32>(
+      create<ast::TypeConstructorExpression>(
+          Source{{12, 34}}, ty.MaybeCreateTypename(alias), ExprList()),
+      vec2<f32>());
   WrapInFunction(tc);
 
   EXPECT_FALSE(r()->Resolve());
@@ -2062,7 +2063,7 @@
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns; i++) {
     args.push_back(create<ast::TypeConstructorExpression>(
-        Source{{12, i}}, vec_alias, ExprList()));
+        Source{{12, i}}, ty.MaybeCreateTypename(vec_alias), ExprList()));
   }
 
   auto* tc = create<ast::TypeConstructorExpression>(Source{}, matrix_type,
diff --git a/src/sem/function.cc b/src/sem/function.cc
index 528f348..9e31545 100644
--- a/src/sem/function.cc
+++ b/src/sem/function.cc
@@ -28,12 +28,11 @@
 
 namespace {
 
-ParameterList GetParameters(ast::Function* ast) {
+ParameterList GetParameters(const std::vector<const Variable*>& params) {
   ParameterList parameters;
-  parameters.reserve(ast->params().size());
-  for (auto* param : ast->params()) {
-    parameters.emplace_back(
-        Parameter{param->declared_type(), Parameter::Usage::kNone});
+  parameters.reserve(params.size());
+  for (auto* param : params) {
+    parameters.emplace_back(Parameter{param->Type(), Parameter::Usage::kNone});
   }
   return parameters;
 }
@@ -47,7 +46,7 @@
                    std::vector<const Variable*> local_referenced_module_vars,
                    std::vector<const ast::ReturnStatement*> return_statements,
                    std::vector<Symbol> ancestor_entry_points)
-    : Base(return_type, GetParameters(declaration)),
+    : Base(return_type, GetParameters(parameters)),
       declaration_(declaration),
       parameters_(std::move(parameters)),
       referenced_module_vars_(std::move(referenced_module_vars)),
diff --git a/src/sem/variable.cc b/src/sem/variable.cc
index 8db2a70..363785e 100644
--- a/src/sem/variable.cc
+++ b/src/sem/variable.cc
@@ -30,10 +30,6 @@
 
 Variable::~Variable() = default;
 
-sem::Type* Variable::DeclaredType() const {
-  return declaration_->declared_type();
-}
-
 VariableUser::VariableUser(ast::IdentifierExpression* declaration,
                            const sem::Type* type,
                            Statement* statement,
diff --git a/src/sem/variable.h b/src/sem/variable.h
index c6bea3b..2090ee2 100644
--- a/src/sem/variable.h
+++ b/src/sem/variable.h
@@ -54,9 +54,6 @@
   /// @returns the canonical type for the variable
   sem::Type* Type() const { return const_cast<sem::Type*>(type_); }
 
-  /// @returns the AST node's type. May be nullptr.
-  sem::Type* DeclaredType() const;
-
   /// @returns the storage class for the variable
   ast::StorageClass StorageClass() const { return storage_class_; }
 
diff --git a/src/transform/binding_remapper.cc b/src/transform/binding_remapper.cc
index 1a19e78..455a3b3 100644
--- a/src/transform/binding_remapper.cc
+++ b/src/transform/binding_remapper.cc
@@ -65,13 +65,13 @@
       if (ac_it != remappings->access_controls.end()) {
         ast::AccessControl::Access ac = ac_it->second;
         auto* ty = in->Sem().Get(var)->Type();
-        sem::Type* inner_ty = nullptr;
+        ast::Type* inner_ty = nullptr;
         if (auto* old_ac = ty->As<sem::AccessControl>()) {
-          inner_ty = ctx.Clone(old_ac->type());
+          inner_ty = CreateASTTypeFor(&ctx, old_ac->type());
         } else {
-          inner_ty = ctx.Clone(ty);
+          inner_ty = CreateASTTypeFor(&ctx, ty);
         }
-        auto* new_ty = ctx.dst->create<sem::AccessControl>(ac, inner_ty);
+        auto* new_ty = ctx.dst->create<ast::AccessControl>(ac, inner_ty);
         auto* new_var = ctx.dst->create<ast::Variable>(
             ctx.Clone(var->source()), ctx.Clone(var->symbol()),
             var->declared_storage_class(), new_ty, var->is_const(),
diff --git a/src/transform/calculate_array_length.cc b/src/transform/calculate_array_length.cc
index b4e19c3..8faad3e 100644
--- a/src/transform/calculate_array_length.cc
+++ b/src/transform/calculate_array_length.cc
@@ -81,6 +81,8 @@
   auto get_buffer_size_intrinsic = [&](sem::StructType* buffer_type) {
     return utils::GetOrCreate(buffer_size_intrinsics, buffer_type, [&] {
       auto name = ctx.dst->Sym();
+      auto* buffer_typename =
+          ctx.dst->ty.type_name(ctx.Clone(buffer_type->impl()->name()));
       auto* func = ctx.dst->create<ast::Function>(
           name,
           ast::VariableList{
@@ -88,7 +90,7 @@
               // in order for HLSL to emit this as a ByteAddressBuffer.
               ctx.dst->create<ast::Variable>(
                   ctx.dst->Sym("buffer"), ast::StorageClass::kStorage,
-                  ctx.Clone(buffer_type), true, nullptr, ast::DecorationList{}),
+                  buffer_typename, true, nullptr, ast::DecorationList{}),
               ctx.dst->Param("result",
                              ctx.dst->ty.pointer(ctx.dst->ty.u32(),
                                                  ast::StorageClass::kFunction)),
@@ -98,7 +100,8 @@
               ctx.dst->ASTNodes().Create<BufferSizeIntrinsic>(ctx.dst->ID()),
           },
           ast::DecorationList{});
-      ctx.InsertAfter(ctx.src->AST().GlobalDeclarations(), buffer_type, func);
+      ctx.InsertAfter(ctx.src->AST().GlobalDeclarations(), buffer_type->impl(),
+                      func);
       return name;
     });
   };
diff --git a/src/transform/canonicalize_entry_point_io.cc b/src/transform/canonicalize_entry_point_io.cc
index ab09015..ee9b73c 100644
--- a/src/transform/canonicalize_entry_point_io.cc
+++ b/src/transform/canonicalize_entry_point_io.cc
@@ -21,6 +21,7 @@
 #include "src/program_builder.h"
 #include "src/sem/function.h"
 #include "src/sem/statement.h"
+#include "src/sem/struct.h"
 #include "src/sem/variable.h"
 
 namespace tint {
@@ -65,11 +66,11 @@
 
   // Strip entry point IO decorations from struct declarations.
   // TODO(jrprice): This code is duplicated with the SPIR-V transform.
-  for (auto ty : ctx.src->AST().ConstructedTypes()) {
-    if (auto* struct_ty = ty->As<sem::StructType>()) {
+  for (auto* ty : ctx.src->AST().ConstructedTypes()) {
+    if (auto* struct_ty = ty->As<ast::Struct>()) {
       // Build new list of struct members without entry point IO decorations.
       ast::StructMemberList new_struct_members;
-      for (auto* member : struct_ty->impl()->members()) {
+      for (auto* member : struct_ty->members()) {
         ast::DecorationList new_decorations = RemoveDecorations(
             &ctx, member->decorations(), [](const ast::Decoration* deco) {
               return deco
@@ -81,49 +82,53 @@
       }
 
       // Redeclare the struct.
-      auto new_struct_name = ctx.Clone(struct_ty->impl()->name());
+      auto new_struct_name = ctx.Clone(struct_ty->name());
       auto* new_struct =
-          ctx.dst->create<sem::StructType>(ctx.dst->create<ast::Struct>(
-              new_struct_name, new_struct_members,
-              ctx.Clone(struct_ty->impl()->decorations())));
+          ctx.dst->create<ast::Struct>(new_struct_name, new_struct_members,
+                                       ctx.Clone(struct_ty->decorations()));
       ctx.Replace(struct_ty, new_struct);
     }
   }
 
-  for (auto* func : ctx.src->AST().Functions()) {
-    if (!func->IsEntryPoint()) {
+  for (auto* func_ast : ctx.src->AST().Functions()) {
+    if (!func_ast->IsEntryPoint()) {
       continue;
     }
 
+    auto* func = ctx.src->Sem().Get(func_ast);
+
     ast::VariableList new_parameters;
 
-    if (!func->params().empty()) {
+    if (!func->Parameters().empty()) {
       // Collect all parameters and build a list of new struct members.
       auto new_struct_param_symbol = ctx.dst->Sym();
       ast::StructMemberList new_struct_members;
-      for (auto* param : func->params()) {
-        auto param_name = ctx.Clone(param->symbol());
-        auto* param_ty = ctx.src->Sem().Get(param)->Type();
-        auto* param_declared_ty = ctx.src->Sem().Get(param)->DeclaredType();
+      for (auto* param : func->Parameters()) {
+        auto param_name = ctx.Clone(param->Declaration()->symbol());
+        auto* param_ty = param->Type();
+        auto* param_declared_ty = param->Declaration()->type();
 
         std::function<ast::Expression*()> func_const_initializer;
 
         if (auto* struct_ty = param_ty->As<sem::StructType>()) {
+          auto* str = ctx.src->Sem().Get(struct_ty);
           // Pull out all struct members and build initializer list.
           std::vector<Symbol> member_names;
-          for (auto* member : struct_ty->impl()->members()) {
-            if (member->type()->UnwrapAll()->Is<sem::StructType>()) {
+          for (auto* member : str->Members()) {
+            if (member->Type()->UnwrapAll()->Is<sem::StructType>()) {
               TINT_ICE(ctx.dst->Diagnostics()) << "nested pipeline IO struct";
             }
 
             ast::DecorationList new_decorations = RemoveDecorations(
-                &ctx, member->decorations(), [](const ast::Decoration* deco) {
+                &ctx, member->Declaration()->decorations(),
+                [](const ast::Decoration* deco) {
                   return !deco->IsAnyOf<ast::BuiltinDecoration,
                                         ast::LocationDecoration>();
                 });
-            auto member_name = ctx.Clone(member->symbol());
-            new_struct_members.push_back(ctx.dst->Member(
-                member_name, ctx.Clone(member->type()), new_decorations));
+            auto member_name = ctx.Clone(member->Declaration()->symbol());
+            auto* member_type = ctx.Clone(member->Declaration()->type());
+            new_struct_members.push_back(
+                ctx.dst->Member(member_name, member_type, new_decorations));
             member_names.emplace_back(member_name);
           }
 
@@ -139,7 +144,8 @@
           };
         } else {
           ast::DecorationList new_decorations = RemoveDecorations(
-              &ctx, param->decorations(), [](const ast::Decoration* deco) {
+              &ctx, param->Declaration()->decorations(),
+              [](const ast::Decoration* deco) {
                 return !deco->IsAnyOf<ast::BuiltinDecoration,
                                       ast::LocationDecoration>();
               });
@@ -151,7 +157,7 @@
           };
         }
 
-        if (func->body()->empty()) {
+        if (func_ast->body()->empty()) {
           // Don't generate a function-scope const if the function is empty.
           continue;
         }
@@ -160,11 +166,12 @@
         // Initialize it with the value extracted from the new struct parameter.
         auto* func_const = ctx.dst->Const(
             param_name, ctx.Clone(param_declared_ty), func_const_initializer());
-        ctx.InsertBefore(func->body()->statements(), *func->body()->begin(),
+        ctx.InsertBefore(func_ast->body()->statements(),
+                         *func_ast->body()->begin(),
                          ctx.dst->WrapInStatement(func_const));
 
         // Replace all uses of the function parameter with the function const.
-        for (auto* user : ctx.src->Sem().Get(param)->Users()) {
+        for (auto* user : param->Users()) {
           ctx.Replace<ast::Expression>(user->Declaration(),
                                        ctx.dst->Expr(param_name));
         }
@@ -176,44 +183,49 @@
 
       // Create the new struct type.
       auto in_struct_name = ctx.dst->Sym();
-      auto* in_struct =
-          ctx.dst->create<sem::StructType>(ctx.dst->create<ast::Struct>(
-              in_struct_name, new_struct_members, ast::DecorationList{}));
-      ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func, in_struct);
+      auto* in_struct = ctx.dst->create<ast::Struct>(
+          in_struct_name, new_struct_members, ast::DecorationList{});
+      ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func_ast,
+                       in_struct);
 
       // Create a new function parameter using this struct type.
-      auto* struct_param = ctx.dst->Param(new_struct_param_symbol, in_struct);
+      auto* struct_param = ctx.dst->Param(
+          new_struct_param_symbol, ctx.dst->ty.type_name(in_struct_name));
       new_parameters.push_back(struct_param);
     }
 
     // Handle return type.
-    auto* ret_type = func->return_type()->UnwrapAliasIfNeeded();
-    sem::Type* new_ret_type;
+    auto* ret_type = func->ReturnType()->UnwrapAliasIfNeeded();
+    std::function<ast::Type*()> new_ret_type;
     if (ret_type->Is<sem::Void>()) {
-      new_ret_type = ctx.dst->ty.void_();
+      new_ret_type = [&ctx] { return ctx.dst->ty.void_(); };
     } else {
       ast::StructMemberList new_struct_members;
 
       if (auto* struct_ty = ret_type->As<sem::StructType>()) {
+        auto* str = ctx.src->Sem().Get(struct_ty);
         // Rebuild struct with only the entry point IO attributes.
-        for (auto* member : struct_ty->impl()->members()) {
-          if (member->type()->UnwrapAll()->Is<sem::StructType>()) {
+        for (auto* member : str->Members()) {
+          if (member->Type()->UnwrapAll()->Is<sem::StructType>()) {
             TINT_ICE(ctx.dst->Diagnostics()) << "nested pipeline IO struct";
           }
 
           ast::DecorationList new_decorations = RemoveDecorations(
-              &ctx, member->decorations(), [](const ast::Decoration* deco) {
+              &ctx, member->Declaration()->decorations(),
+              [](const ast::Decoration* deco) {
                 return !deco->IsAnyOf<ast::BuiltinDecoration,
                                       ast::LocationDecoration>();
               });
+          auto symbol = ctx.Clone(member->Declaration()->symbol());
+          auto* member_ty = ctx.Clone(member->Declaration()->type());
           new_struct_members.push_back(
-              ctx.dst->Member(ctx.Clone(member->symbol()),
-                              ctx.Clone(member->type()), new_decorations));
+              ctx.dst->Member(symbol, member_ty, new_decorations));
         }
       } else {
+        auto* member_ty = ctx.Clone(func->Declaration()->return_type());
+        auto decos = ctx.Clone(func_ast->return_type_decorations());
         new_struct_members.push_back(
-            ctx.dst->Member("value", ctx.Clone(ret_type),
-                            ctx.Clone(func->return_type_decorations())));
+            ctx.dst->Member("value", member_ty, std::move(decos)));
       }
 
       // Sort struct members to satisfy HLSL interfacing matching rules.
@@ -222,15 +234,16 @@
 
       // Create the new struct type.
       auto out_struct_name = ctx.dst->Sym();
-      auto* out_struct =
-          ctx.dst->create<sem::StructType>(ctx.dst->create<ast::Struct>(
-              out_struct_name, new_struct_members, ast::DecorationList{}));
-      ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func, out_struct);
-      new_ret_type = out_struct;
+      auto* out_struct = ctx.dst->create<ast::Struct>(
+          out_struct_name, new_struct_members, ast::DecorationList{});
+      ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func_ast,
+                       out_struct);
+      new_ret_type = [out_struct_name, &ctx] {
+        return ctx.dst->ty.type_name(out_struct_name);
+      };
 
       // Replace all return statements.
-      auto* sem_func = ctx.src->Sem().Get(func);
-      for (auto* ret : sem_func->ReturnStatements()) {
+      for (auto* ret : func->ReturnStatements()) {
         auto* ret_sem = ctx.src->Sem().Get(ret);
         // Reconstruct the return value using the newly created struct.
         std::function<ast::Expression*()> new_ret_value = [&ctx, ret] {
@@ -243,8 +256,9 @@
             // Create a const to hold the return value expression to avoid
             // re-evaluating it multiple times.
             auto temp = ctx.dst->Sym();
-            auto* temp_var = ctx.dst->Decl(
-                ctx.dst->Const(temp, ctx.Clone(ret_type), new_ret_value()));
+            auto* ty = CreateASTTypeFor(&ctx, ret_type);
+            auto* temp_var =
+                ctx.dst->Decl(ctx.dst->Const(temp, ty, new_ret_value()));
             ctx.InsertBefore(ret_sem->Block()->statements(), ret, temp_var);
             new_ret_value = [&ctx, temp] { return ctx.dst->Expr(temp); };
           }
@@ -258,17 +272,17 @@
         }
 
         auto* new_ret =
-            ctx.dst->Return(ctx.dst->Construct(new_ret_type, ret_values));
+            ctx.dst->Return(ctx.dst->Construct(new_ret_type(), ret_values));
         ctx.Replace(ret, new_ret);
       }
     }
 
     // Rewrite the function header with the new parameters.
     auto* new_func = ctx.dst->create<ast::Function>(
-        func->source(), ctx.Clone(func->symbol()), new_parameters, new_ret_type,
-        ctx.Clone(func->body()), ctx.Clone(func->decorations()),
-        ast::DecorationList{});
-    ctx.Replace(func, new_func);
+        func_ast->source(), ctx.Clone(func_ast->symbol()), new_parameters,
+        new_ret_type(), ctx.Clone(func_ast->body()),
+        ctx.Clone(func_ast->decorations()), ast::DecorationList{});
+    ctx.Replace(func_ast, new_func);
   }
 
   ctx.Clone();
diff --git a/src/transform/decompose_storage_access.cc b/src/transform/decompose_storage_access.cc
index 50b51a5..c9f9ebf 100644
--- a/src/transform/decompose_storage_access.cc
+++ b/src/transform/decompose_storage_access.cc
@@ -23,6 +23,7 @@
 #include "src/ast/assignment_statement.h"
 #include "src/ast/call_statement.h"
 #include "src/ast/scalar_constructor_expression.h"
+#include "src/ast/type_name.h"
 #include "src/program_builder.h"
 #include "src/sem/access_control_type.h"
 #include "src/sem/array.h"
@@ -318,7 +319,9 @@
 /// Inserts `node` before `insert_after` in the global declarations of
 /// `ctx.dst`. If `insert_after` is nullptr, then `node` is inserted at the top
 /// of the module.
-void InsertGlobal(CloneContext& ctx, Cloneable* insert_after, Cloneable* node) {
+void InsertGlobal(CloneContext& ctx,
+                  const Cloneable* insert_after,
+                  Cloneable* node) {
   auto& globals = ctx.src->AST().GlobalDeclarations();
   if (insert_after) {
     ctx.InsertAfter(globals, insert_after, node);
@@ -328,7 +331,7 @@
 }
 
 /// @returns the unwrapped, user-declared constructed type of ty.
-sem::Type* ConstructedTypeOf(sem::Type* ty) {
+ast::NamedType* ConstructedTypeOf(sem::Type* ty) {
   while (true) {
     if (auto* ptr = ty->As<sem::Pointer>()) {
       ty = ptr->type();
@@ -338,11 +341,8 @@
       ty = access->type();
       continue;
     }
-    if (auto* alias = ty->As<sem::Alias>()) {
-      return alias;
-    }
     if (auto* str = ty->As<sem::StructType>()) {
-      return str;
+      return str->impl();
     }
     // Not a constructed type
     return nullptr;
@@ -368,8 +368,10 @@
   StorageBufferAccess target;            // The target for the write
 };
 
+}  // namespace
+
 /// State holds the current transform state
-struct State {
+struct DecomposeStorageAccess::State {
   /// Map of AST expression to storage buffer access
   /// This map has entries added when encountered, and removed when outer
   /// expressions chain the access.
@@ -385,9 +387,12 @@
   /// List of storage buffer writes
   std::vector<Store> stores;
 
-  /// AddAccesss() adds the `expr -> access` map item to #accesses, and `expr`
+  /// AddAccess() adds the `expr -> access` map item to #accesses, and `expr`
   /// to #expression_order.
-  void AddAccesss(ast::Expression* expr, StorageBufferAccess&& access) {
+  /// @param expr the expression that performs the access
+  /// @param access the access
+  void AddAccess(ast::Expression* expr, StorageBufferAccess&& access) {
+    TINT_ASSERT(access.type);
     accesses.emplace(expr, std::move(access));
     expression_order.emplace_back(expr);
   }
@@ -395,6 +400,8 @@
   /// TakeAccess() removes the `node` item from #accesses (if it exists),
   /// returning the StorageBufferAccess. If #accesses does not hold an item for
   /// `node`, an invalid StorageBufferAccess is returned.
+  /// @param node the expression that performed an access
+  /// @return the StorageBufferAccess for the given expression
   StorageBufferAccess TakeAccess(ast::Expression* node) {
     auto lhs_it = accesses.find(node);
     if (lhs_it == accesses.end()) {
@@ -408,24 +415,31 @@
   /// LoadFunc() returns a symbol to an intrinsic function that loads an element
   /// of type `el_ty` from a storage buffer of type `buf_ty`. The function has
   /// the signature: `fn load(buf : buf_ty, offset : u32) -> el_ty`
+  /// @param ctx the CloneContext
+  /// @param insert_after the user-declared type to insert the function after
+  /// @param buf_ty the storage buffer type
+  /// @param el_ty the storage buffer element type
+  /// @return the name of the function that performs the load
   Symbol LoadFunc(CloneContext& ctx,
-                  Cloneable* insert_after,
+                  ast::NamedType* insert_after,
                   sem::Type* buf_ty,
                   sem::Type* el_ty) {
     return utils::GetOrCreate(load_funcs, TypePair{buf_ty, el_ty}, [&] {
+      auto* buf_ast_ty = CreateASTTypeFor(&ctx, buf_ty);
       ast::VariableList params = {
           // Note: The buffer parameter requires the kStorage StorageClass in
           // order for HLSL to emit this as a ByteAddressBuffer.
           ctx.dst->create<ast::Variable>(
-              ctx.dst->Sym("buffer"), ast::StorageClass::kStorage,
-              ctx.Clone(buf_ty), true, nullptr, ast::DecorationList{}),
+              ctx.dst->Sym("buffer"), ast::StorageClass::kStorage, buf_ast_ty,
+              true, nullptr, ast::DecorationList{}),
           ctx.dst->Param("offset", ctx.dst->ty.u32()),
       };
 
       ast::Function* func = nullptr;
       if (auto* intrinsic = IntrinsicLoadFor(ctx.dst, el_ty)) {
+        auto* el_ast_ty = CreateASTTypeFor(&ctx, el_ty);
         func = ctx.dst->create<ast::Function>(
-            ctx.dst->Sym(), params, ctx.Clone(el_ty), nullptr,
+            ctx.dst->Sym(), params, el_ast_ty, nullptr,
             ast::DecorationList{intrinsic}, ast::DecorationList{});
       } else {
         ast::ExpressionList values;
@@ -444,7 +458,7 @@
           for (auto* member : str->Members()) {
             auto* offset = ctx.dst->Add("offset", member->Offset());
             Symbol load = LoadFunc(ctx, insert_after, buf_ty,
-                                   member->Declaration()->type()->UnwrapAll());
+                                   member->Type()->UnwrapAll());
             values.emplace_back(ctx.dst->Call(load, "buffer", offset));
           }
         } else if (auto* arr_ty = el_ty->As<sem::ArrayType>()) {
@@ -457,11 +471,12 @@
             values.emplace_back(ctx.dst->Call(load, "buffer", offset));
           }
         }
+        auto* el_ast_ty = CreateASTTypeFor(&ctx, el_ty);
         func = ctx.dst->create<ast::Function>(
-            ctx.dst->Sym(), params, ctx.Clone(el_ty),
+            ctx.dst->Sym(), params, el_ast_ty,
             ctx.dst->Block(
                 ctx.dst->Return(ctx.dst->create<ast::TypeConstructorExpression>(
-                    ctx.Clone(el_ty), values))),
+                    CreateASTTypeFor(&ctx, el_ty), values))),
             ast::DecorationList{}, ast::DecorationList{});
       }
       InsertGlobal(ctx, insert_after, func);
@@ -472,19 +487,26 @@
   /// StoreFunc() returns a symbol to an intrinsic function that stores an
   /// element of type `el_ty` to a storage buffer of type `buf_ty`. The function
   /// has the signature: `fn store(buf : buf_ty, offset : u32, value : el_ty)`
+  /// @param ctx the CloneContext
+  /// @param insert_after the user-declared type to insert the function after
+  /// @param buf_ty the storage buffer type
+  /// @param el_ty the storage buffer element type
+  /// @return the name of the function that performs the store
   Symbol StoreFunc(CloneContext& ctx,
-                   Cloneable* insert_after,
+                   ast::NamedType* insert_after,
                    sem::Type* buf_ty,
                    sem::Type* el_ty) {
     return utils::GetOrCreate(store_funcs, TypePair{buf_ty, el_ty}, [&] {
+      auto* buf_ast_ty = CreateASTTypeFor(&ctx, buf_ty);
+      auto* el_ast_ty = CreateASTTypeFor(&ctx, el_ty);
       ast::VariableList params{
           // Note: The buffer parameter requires the kStorage StorageClass in
           // order for HLSL to emit this as a ByteAddressBuffer.
           ctx.dst->create<ast::Variable>(
-              ctx.dst->Sym("buffer"), ast::StorageClass::kStorage,
-              ctx.Clone(buf_ty), true, nullptr, ast::DecorationList{}),
+              ctx.dst->Sym("buffer"), ast::StorageClass::kStorage, buf_ast_ty,
+              true, nullptr, ast::DecorationList{}),
           ctx.dst->Param("offset", ctx.dst->ty.u32()),
-          ctx.dst->Param("value", ctx.Clone(el_ty)),
+          ctx.dst->Param("value", el_ast_ty),
       };
       ast::Function* func = nullptr;
       if (auto* intrinsic = IntrinsicStoreFor(ctx.dst, el_ty)) {
@@ -512,9 +534,8 @@
             auto* offset = ctx.dst->Add("offset", member->Offset());
             auto* access = ctx.dst->MemberAccessor(
                 "value", ctx.Clone(member->Declaration()->symbol()));
-            Symbol store =
-                StoreFunc(ctx, insert_after, buf_ty,
-                          member->Declaration()->type()->UnwrapAll());
+            Symbol store = StoreFunc(ctx, insert_after, buf_ty,
+                                     member->Type()->UnwrapAll());
             auto* call = ctx.dst->Call(store, "buffer", offset, access);
             body.emplace_back(ctx.dst->create<ast::CallStatement>(call));
           }
@@ -541,8 +562,6 @@
   }
 };
 
-}  // namespace
-
 DecomposeStorageAccess::Intrinsic::Intrinsic(ProgramID program_id, Type ty)
     : Base(program_id), type(ty) {}
 DecomposeStorageAccess::Intrinsic::~Intrinsic() = default;
@@ -630,11 +649,11 @@
       if (auto* var = sem.Get<sem::VariableUser>(ident)) {
         if (var->Variable()->StorageClass() == ast::StorageClass::kStorage) {
           // Variable to a storage buffer
-          state.AddAccesss(ident, {
-                                      var,
-                                      ToOffset(0u),
-                                      var->Type()->UnwrapAll(),
-                                  });
+          state.AddAccess(ident, {
+                                     var,
+                                     ToOffset(0u),
+                                     var->Type()->UnwrapAll(),
+                                 });
         }
       }
       continue;
@@ -649,7 +668,7 @@
             auto* vec_ty = access.type->As<sem::Vector>();
             auto offset =
                 Mul(ScalarSize(vec_ty->type()), swizzle->Indices()[0]);
-            state.AddAccesss(
+            state.AddAccess(
                 accessor, {
                               access.var,
                               Add(std::move(access.offset), std::move(offset)),
@@ -663,12 +682,12 @@
           auto* member =
               sem.Get(str_ty)->FindMember(accessor->member()->symbol());
           auto offset = member->Offset();
-          state.AddAccesss(accessor,
-                           {
-                               access.var,
-                               Add(std::move(access.offset), std::move(offset)),
-                               member->Declaration()->type()->UnwrapAll(),
-                           });
+          state.AddAccess(accessor,
+                          {
+                              access.var,
+                              Add(std::move(access.offset), std::move(offset)),
+                              member->Type()->UnwrapAll(),
+                          });
         }
       }
       continue;
@@ -680,34 +699,34 @@
         if (auto* arr_ty = access.type->As<sem::ArrayType>()) {
           auto stride = sem.Get(arr_ty)->Stride();
           auto offset = Mul(stride, accessor->idx_expr());
-          state.AddAccesss(accessor,
-                           {
-                               access.var,
-                               Add(std::move(access.offset), std::move(offset)),
-                               arr_ty->type()->UnwrapAll(),
-                           });
+          state.AddAccess(accessor,
+                          {
+                              access.var,
+                              Add(std::move(access.offset), std::move(offset)),
+                              arr_ty->type()->UnwrapAll(),
+                          });
           continue;
         }
         if (auto* vec_ty = access.type->As<sem::Vector>()) {
           auto offset = Mul(ScalarSize(vec_ty->type()), accessor->idx_expr());
-          state.AddAccesss(accessor,
-                           {
-                               access.var,
-                               Add(std::move(access.offset), std::move(offset)),
-                               vec_ty->type()->UnwrapAll(),
-                           });
+          state.AddAccess(accessor,
+                          {
+                              access.var,
+                              Add(std::move(access.offset), std::move(offset)),
+                              vec_ty->type()->UnwrapAll(),
+                          });
           continue;
         }
         if (auto* mat_ty = access.type->As<sem::Matrix>()) {
           auto offset = Mul(MatrixColumnStride(mat_ty), accessor->idx_expr());
           auto* vec_ty = ctx.dst->create<sem::Vector>(
               ctx.Clone(mat_ty->type()->UnwrapAll()), mat_ty->rows());
-          state.AddAccesss(accessor,
-                           {
-                               access.var,
-                               Add(std::move(access.offset), std::move(offset)),
-                               vec_ty,
-                           });
+          state.AddAccess(accessor,
+                          {
+                              access.var,
+                              Add(std::move(access.offset), std::move(offset)),
+                              vec_ty,
+                          });
           continue;
         }
       }
diff --git a/src/transform/decompose_storage_access.h b/src/transform/decompose_storage_access.h
index 3f38035..3da2c1e 100644
--- a/src/transform/decompose_storage_access.h
+++ b/src/transform/decompose_storage_access.h
@@ -95,6 +95,8 @@
   /// @param data optional extra transform-specific data
   /// @returns the transformation result
   Output Run(const Program* program, const DataMap& data = {}) override;
+
+  struct State;
 };
 
 }  // namespace transform
diff --git a/src/transform/hlsl.cc b/src/transform/hlsl.cc
index 6b4887a..cb4f1e3 100644
--- a/src/transform/hlsl.cc
+++ b/src/transform/hlsl.cc
@@ -100,7 +100,7 @@
         // Create a new symbol for the constant
         auto dst_symbol = ctx.dst->Sym();
         // Clone the type
-        auto* dst_ty = ctx.Clone(src_ty);
+        auto* dst_ty = ctx.Clone(src_init->type());
         // Clone the initializer
         auto* dst_init = ctx.Clone(src_init);
         // Construct the constant that holds the hoisted initializer
diff --git a/src/transform/single_entry_point.cc b/src/transform/single_entry_point.cc
index 773fa30..dec6b09 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<sem::Type>()) {
+    if (auto* ty = decl->As<ast::NamedType>()) {
       // TODO(jrprice): Strip unused types.
       out.AST().AddConstructedType(ctx.Clone(ty));
     } else if (auto* var = decl->As<ast::Variable>()) {
diff --git a/src/transform/spirv.cc b/src/transform/spirv.cc
index 29c099e..4676559 100644
--- a/src/transform/spirv.cc
+++ b/src/transform/spirv.cc
@@ -23,6 +23,7 @@
 #include "src/program_builder.h"
 #include "src/sem/function.h"
 #include "src/sem/statement.h"
+#include "src/sem/struct.h"
 #include "src/sem/variable.h"
 
 namespace tint {
@@ -110,11 +111,11 @@
   // ```
 
   // Strip entry point IO decorations from struct declarations.
-  for (auto ty : ctx.src->AST().ConstructedTypes()) {
-    if (auto* struct_ty = ty->As<sem::StructType>()) {
+  for (auto* ty : ctx.src->AST().ConstructedTypes()) {
+    if (auto* struct_ty = ty->As<ast::Struct>()) {
       // Build new list of struct members without entry point IO decorations.
       ast::StructMemberList new_struct_members;
-      for (auto* member : struct_ty->impl()->members()) {
+      for (auto* member : struct_ty->members()) {
         ast::DecorationList new_decorations = RemoveDecorations(
             &ctx, member->decorations(), [](const ast::Decoration* deco) {
               return deco
@@ -126,52 +127,52 @@
       }
 
       // Redeclare the struct.
-      auto new_struct_name = ctx.Clone(struct_ty->impl()->name());
+      auto new_struct_name = ctx.Clone(struct_ty->name());
       auto* new_struct =
-          ctx.dst->create<sem::StructType>(ctx.dst->create<ast::Struct>(
-              new_struct_name, new_struct_members,
-              ctx.Clone(struct_ty->impl()->decorations())));
+          ctx.dst->create<ast::Struct>(new_struct_name, new_struct_members,
+                                       ctx.Clone(struct_ty->decorations()));
       ctx.Replace(struct_ty, new_struct);
     }
   }
 
-  for (auto* func : ctx.src->AST().Functions()) {
-    if (!func->IsEntryPoint()) {
+  for (auto* func_ast : ctx.src->AST().Functions()) {
+    if (!func_ast->IsEntryPoint()) {
       continue;
     }
+    auto* func = ctx.src->Sem().Get(func_ast);
 
-    for (auto* param : func->params()) {
+    for (auto* param : func->Parameters()) {
       Symbol new_var = HoistToInputVariables(
-          ctx, func, ctx.src->Sem().Get(param)->Type(),
-          ctx.src->Sem().Get(param)->DeclaredType(), param->decorations());
+          ctx, func_ast, param->Type(), param->Declaration()->type(),
+          param->Declaration()->decorations());
 
       // Replace all uses of the function parameter with the new variable.
-      for (auto* user : ctx.src->Sem().Get(param)->Users()) {
+      for (auto* user : param->Users()) {
         ctx.Replace<ast::Expression>(user->Declaration(),
                                      ctx.dst->Expr(new_var));
       }
     }
 
-    if (!func->return_type()->Is<sem::Void>()) {
+    if (!func->ReturnType()->Is<sem::Void>()) {
       ast::StatementList stores;
       auto store_value_symbol = ctx.dst->Sym();
       HoistToOutputVariables(
-          ctx, func, func->return_type(), func->return_type(),
-          func->return_type_decorations(), {}, store_value_symbol, stores);
+          ctx, func_ast, func->ReturnType(), func_ast->return_type(),
+          func_ast->return_type_decorations(), {}, store_value_symbol, stores);
 
       // Create a function that writes a return value to all output variables.
-      auto* store_value =
-          ctx.dst->Param(store_value_symbol, ctx.Clone(func->return_type()));
+      auto* store_value = ctx.dst->Param(store_value_symbol,
+                                         ctx.Clone(func_ast->return_type()));
       auto return_func_symbol = ctx.dst->Sym();
       auto* return_func = ctx.dst->create<ast::Function>(
           return_func_symbol, ast::VariableList{store_value},
           ctx.dst->ty.void_(), ctx.dst->create<ast::BlockStatement>(stores),
           ast::DecorationList{}, ast::DecorationList{});
-      ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func, return_func);
+      ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func_ast,
+                       return_func);
 
       // Replace all return statements with calls to the output function.
-      auto* sem_func = ctx.src->Sem().Get(func);
-      for (auto* ret : sem_func->ReturnStatements()) {
+      for (auto* ret : func->ReturnStatements()) {
         auto* ret_sem = ctx.src->Sem().Get(ret);
         auto* call = ctx.dst->Call(return_func_symbol, ctx.Clone(ret->value()));
         ctx.InsertBefore(ret_sem->Block()->statements(), ret,
@@ -181,11 +182,13 @@
     }
 
     // Rewrite the function header to remove the parameters and return value.
+    auto name = ctx.Clone(func_ast->symbol());
+    auto* body = ctx.Clone(func_ast->body());
+    auto decos = ctx.Clone(func_ast->decorations());
     auto* new_func = ctx.dst->create<ast::Function>(
-        func->source(), ctx.Clone(func->symbol()), ast::VariableList{},
-        ctx.dst->ty.void_(), ctx.Clone(func->body()),
-        ctx.Clone(func->decorations()), ast::DecorationList{});
-    ctx.Replace(func, new_func);
+        func_ast->source(), name, ast::VariableList{}, ctx.dst->ty.void_(),
+        body, decos, ast::DecorationList{});
+    ctx.Replace(func_ast, new_func);
   }
 }
 
@@ -253,7 +256,7 @@
     CloneContext& ctx,
     const ast::Function* func,
     sem::Type* ty,
-    sem::Type* declared_ty,
+    ast::Type* declared_ty,
     const ast::DecorationList& decorations) const {
   if (!ty->Is<sem::StructType>()) {
     // Base case: create a global variable and return.
@@ -273,9 +276,10 @@
   // Recurse into struct members and build the initializer list.
   std::vector<Symbol> init_value_names;
   auto* struct_ty = ty->As<sem::StructType>();
-  for (auto* member : struct_ty->impl()->members()) {
+  for (auto* member : ctx.src->Sem().Get(struct_ty)->Members()) {
     auto member_var = HoistToInputVariables(
-        ctx, func, member->type(), member->type(), member->decorations());
+        ctx, func, member->Type(), member->Declaration()->type(),
+        member->Declaration()->decorations());
     init_value_names.emplace_back(member_var);
   }
 
@@ -302,7 +306,7 @@
 void Spirv::HoistToOutputVariables(CloneContext& ctx,
                                    const ast::Function* func,
                                    sem::Type* ty,
-                                   sem::Type* declared_ty,
+                                   ast::Type* declared_ty,
                                    const ast::DecorationList& decorations,
                                    std::vector<Symbol> member_accesses,
                                    Symbol store_value,
@@ -333,11 +337,12 @@
 
   // Recurse into struct members.
   auto* struct_ty = ty->As<sem::StructType>();
-  for (auto* member : struct_ty->impl()->members()) {
-    member_accesses.push_back(ctx.Clone(member->symbol()));
-    HoistToOutputVariables(ctx, func, member->type(), member->type(),
-                           member->decorations(), member_accesses, store_value,
-                           stores);
+  for (auto* member : ctx.src->Sem().Get(struct_ty)->Members()) {
+    member_accesses.push_back(ctx.Clone(member->Declaration()->symbol()));
+    HoistToOutputVariables(ctx, func, member->Type(),
+                           member->Declaration()->type(),
+                           member->Declaration()->decorations(),
+                           member_accesses, store_value, stores);
     member_accesses.pop_back();
   }
 }
diff --git a/src/transform/spirv.h b/src/transform/spirv.h
index 2d60852..3443bb7 100644
--- a/src/transform/spirv.h
+++ b/src/transform/spirv.h
@@ -60,7 +60,7 @@
   Symbol HoistToInputVariables(CloneContext& ctx,
                                const ast::Function* func,
                                sem::Type* ty,
-                               sem::Type* declared_ty,
+                               ast::Type* declared_ty,
                                const ast::DecorationList& decorations) const;
 
   /// Recursively create module-scope output variables for `ty` and build a list
@@ -74,7 +74,7 @@
   void HoistToOutputVariables(CloneContext& ctx,
                               const ast::Function* func,
                               sem::Type* ty,
-                              sem::Type* declared_ty,
+                              ast::Type* declared_ty,
                               const ast::DecorationList& decorations,
                               std::vector<Symbol> member_accesses,
                               Symbol store_value,
diff --git a/src/transform/transform.cc b/src/transform/transform.cc
index d971ab9..def5478 100644
--- a/src/transform/transform.cc
+++ b/src/transform/transform.cc
@@ -49,7 +49,7 @@
   auto source = ctx->Clone(in->source());
   auto symbol = ctx->Clone(in->symbol());
   auto params = ctx->Clone(in->params());
-  auto return_type = ctx->Clone(in->return_type());
+  auto* return_type = ctx->Clone(in->return_type());
   auto* body = ctx->dst->create<ast::BlockStatement>(
       ctx->Clone(in->body()->source()), statements);
   auto decos = ctx->Clone(in->decorations());
diff --git a/src/transform/vertex_pulling.cc b/src/transform/vertex_pulling.cc
index cdcbd4d..88b16a4 100644
--- a/src/transform/vertex_pulling.cc
+++ b/src/transform/vertex_pulling.cc
@@ -184,7 +184,7 @@
           // identifier strings instead of pointers, so we don't need to update
           // any other place in the AST.
           auto name = ctx.Clone(v->symbol());
-          auto* replacement = ctx.dst->Var(name, ctx.Clone(v->declared_type()),
+          auto* replacement = ctx.dst->Var(name, ctx.Clone(v->type()),
                                            ast::StorageClass::kPrivate);
           location_to_expr[location] = [this, name]() {
             return ctx.dst->Expr(name);
@@ -212,9 +212,9 @@
         {
             ctx.dst->create<ast::StructBlockDecoration>(),
         });
-    auto access =
-        ctx.dst->ty.access(ast::AccessControl::kReadOnly, struct_type);
     for (uint32_t i = 0; i < cfg.vertex_state.size(); ++i) {
+      auto access =
+          ctx.dst->ty.access(ast::AccessControl::kReadOnly, struct_type);
       // The decorated variable with struct type
       ctx.dst->Global(
           GetVertexBufferName(i), access, ast::StorageClass::kStorage, nullptr,
@@ -369,7 +369,7 @@
   /// @param count how many elements the vector has
   ast::Expression* AccessVec(uint32_t buffer,
                              uint32_t element_stride,
-                             sem::Type* base_type,
+                             ast::Type* base_type,
                              VertexFormat base_format,
                              uint32_t count) {
     ast::ExpressionList expr_list;
@@ -381,7 +381,7 @@
     }
 
     return ctx.dst->create<ast::TypeConstructorExpression>(
-        ctx.dst->create<sem::Vector>(base_type, count), std::move(expr_list));
+        ctx.dst->create<ast::Vector>(base_type, count), std::move(expr_list));
   }
 
   /// Process a non-struct entry point parameter.
@@ -394,7 +394,7 @@
             ast::GetDecoration<ast::LocationDecoration>(param->decorations())) {
       // Create a function-scope variable to replace the parameter.
       auto func_var_sym = ctx.Clone(param->symbol());
-      auto* func_var_type = ctx.Clone(param->declared_type());
+      auto* func_var_type = ctx.Clone(param->type());
       auto* func_var = ctx.dst->Var(func_var_sym, func_var_type,
                                     ast::StorageClass::kFunction);
       ctx.InsertBefore(func->body()->statements(), *func->body()->begin(),
@@ -428,18 +428,16 @@
   /// instance_index builtins.
   /// @param func the entry point function
   /// @param param the parameter to process
-  void ProcessStructParameter(ast::Function* func, ast::Variable* param) {
-    auto* struct_ty = param->declared_type()->As<sem::StructType>();
-    if (!struct_ty) {
-      TINT_ICE(ctx.dst->Diagnostics()) << "Invalid struct parameter";
-    }
-
+  /// @param struct_ty the structure type
+  void ProcessStructParameter(ast::Function* func,
+                              ast::Variable* param,
+                              ast::Struct* struct_ty) {
     auto param_sym = ctx.Clone(param->symbol());
 
     // Process the struct members.
     bool has_locations = false;
     ast::StructMemberList members_to_clone;
-    for (auto* member : struct_ty->impl()->members()) {
+    for (auto* member : struct_ty->members()) {
       auto member_sym = ctx.Clone(member->symbol());
       std::function<ast::Expression*()> member_expr = [this, param_sym,
                                                        member_sym]() {
@@ -472,7 +470,7 @@
     }
 
     // Create a function-scope variable to replace the parameter.
-    auto* func_var = ctx.dst->Var(param_sym, ctx.Clone(param->declared_type()),
+    auto* func_var = ctx.dst->Var(param_sym, ctx.Clone(param->type()),
                                   ast::StorageClass::kFunction);
     ctx.InsertBefore(func->body()->statements(), *func->body()->begin(),
                      ctx.dst->Decl(func_var));
@@ -482,7 +480,7 @@
       ast::StructMemberList new_members;
       for (auto* member : members_to_clone) {
         auto member_sym = ctx.Clone(member->symbol());
-        auto member_type = ctx.Clone(member->type());
+        auto* member_type = ctx.Clone(member->type());
         auto member_decos = ctx.Clone(member->decorations());
         new_members.push_back(
             ctx.dst->Member(member_sym, member_type, std::move(member_decos)));
@@ -514,8 +512,8 @@
     // Process entry point parameters.
     for (auto* param : func->params()) {
       auto* sem = ctx.src->Sem().Get(param);
-      if (sem->Type()->Is<sem::StructType>()) {
-        ProcessStructParameter(func, param);
+      if (auto* str = sem->Type()->As<sem::StructType>()) {
+        ProcessStructParameter(func, param, str->impl());
       } else {
         ProcessNonStructParameter(func, param);
       }
@@ -553,7 +551,7 @@
 
     // Rewrite the function header with the new parameters.
     auto func_sym = ctx.Clone(func->symbol());
-    auto ret_type = ctx.Clone(func->return_type());
+    auto* ret_type = ctx.Clone(func->return_type());
     auto* body = ctx.Clone(func->body());
     auto decos = ctx.Clone(func->decorations());
     auto ret_decos = ctx.Clone(func->return_type_decorations());
diff --git a/src/writer/append_vector.cc b/src/writer/append_vector.cc
index 1c84d45..7e76346 100644
--- a/src/writer/append_vector.cc
+++ b/src/writer/append_vector.cc
@@ -23,9 +23,10 @@
 
 namespace {
 
-ast::TypeConstructorExpression* AsVectorConstructor(ast::Expression* expr) {
+ast::TypeConstructorExpression* AsVectorConstructor(ProgramBuilder* b,
+                                                    ast::Expression* expr) {
   if (auto* constructor = expr->As<ast::TypeConstructorExpression>()) {
-    if (constructor->type()->Is<sem::Vector>()) {
+    if (b->TypeOf(constructor)->Is<sem::Vector>()) {
       return constructor;
     }
   }
@@ -38,42 +39,52 @@
                                              ast::Expression* vector,
                                              ast::Expression* scalar) {
   uint32_t packed_size;
-  sem::Type* packed_el_ty;  // Currently must be f32.
+  sem::Type* packed_el_sem_ty;
   auto* vector_sem = b->Sem().Get(vector);
   auto* vector_ty = vector_sem->Type()->UnwrapPtrIfNeeded();
   if (auto* vec = vector_ty->As<sem::Vector>()) {
     packed_size = vec->size() + 1;
-    packed_el_ty = vec->type();
+    packed_el_sem_ty = vec->type();
   } else {
     packed_size = 2;
-    packed_el_ty = vector_ty;
+    packed_el_sem_ty = vector_ty;
+  }
+
+  ast::Type* packed_el_ty = nullptr;
+  if (packed_el_sem_ty->Is<sem::I32>()) {
+    packed_el_ty = b->create<ast::I32>();
+  } else if (packed_el_sem_ty->Is<sem::U32>()) {
+    packed_el_ty = b->create<ast::U32>();
+  } else if (packed_el_sem_ty->Is<sem::F32>()) {
+    packed_el_ty = b->create<ast::F32>();
   }
 
   auto* statement = vector_sem->Stmt();
 
-  auto* packed_ty = b->create<sem::Vector>(packed_el_ty, packed_size);
+  auto* packed_ty = b->create<ast::Vector>(packed_el_ty, packed_size);
+  auto* packed_sem_ty = b->create<sem::Vector>(packed_el_sem_ty, packed_size);
 
   // If the coordinates are already passed in a vector constructor, extract
   // the elements into the new vector instead of nesting a vector-in-vector.
   ast::ExpressionList packed;
-  if (auto* vc = AsVectorConstructor(vector)) {
+  if (auto* vc = AsVectorConstructor(b, vector)) {
     packed = vc->values();
   } else {
     packed.emplace_back(vector);
   }
-  if (packed_el_ty != b->Sem().Get(scalar)->Type()->UnwrapPtrIfNeeded()) {
+  if (packed_el_sem_ty != b->TypeOf(scalar)->UnwrapPtrIfNeeded()) {
     // Cast scalar to the vector element type
     auto* scalar_cast = b->Construct(packed_el_ty, scalar);
     b->Sem().Add(scalar_cast, b->create<sem::Expression>(
-                                  scalar_cast, packed_el_ty, statement));
+                                  scalar_cast, packed_el_sem_ty, statement));
     packed.emplace_back(scalar_cast);
   } else {
     packed.emplace_back(scalar);
   }
 
   auto* constructor = b->Construct(packed_ty, std::move(packed));
-  b->Sem().Add(constructor,
-               b->create<sem::Expression>(constructor, packed_ty, statement));
+  b->Sem().Add(constructor, b->create<sem::Expression>(
+                                constructor, packed_sem_ty, statement));
 
   return constructor;
 }
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index defa30f..509d20a 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -119,8 +119,11 @@
     register_global(global);
   }
 
-  for (auto const ty : builder_.AST().ConstructedTypes()) {
-    if (!EmitConstructedType(out, ty)) {
+  for (auto* const ty : builder_.AST().ConstructedTypes()) {
+    if (ty->Is<ast::Alias>()) {
+      continue;
+    }
+    if (!EmitConstructedType(out, TypeOf(ty))) {
       return false;
     }
   }
@@ -2012,7 +2015,7 @@
   bool has_outdata = outdata != ep_sym_to_out_data_.end();
   if (has_outdata) {
     // TODO(crbug.com/tint/697): Remove this.
-    if (!func->return_type()->Is<sem::Void>()) {
+    if (!func->return_type()->Is<ast::Void>()) {
       TINT_ICE(diagnostics_) << "Mixing module-scope variables and return "
                                 "types for shader outputs";
     }
diff --git a/src/writer/hlsl/generator_impl.h b/src/writer/hlsl/generator_impl.h
index 2f7aab9..9a919c8 100644
--- a/src/writer/hlsl/generator_impl.h
+++ b/src/writer/hlsl/generator_impl.h
@@ -381,7 +381,7 @@
 
   /// @returns the resolved type of the ast::Type `type`
   /// @param type the type
-  const sem::Type* TypeOf(ast::Type* type) const {
+  const sem::Type* TypeOf(const ast::Type* type) const {
     return builder_.TypeOf(type);
   }
 
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index f3170bc..43093c8 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -28,6 +28,7 @@
 #include "src/ast/sint_literal.h"
 #include "src/ast/uint_literal.h"
 #include "src/ast/variable_decl_statement.h"
+#include "src/ast/void.h"
 #include "src/sem/access_control_type.h"
 #include "src/sem/alias_type.h"
 #include "src/sem/array.h"
@@ -86,8 +87,8 @@
     global_variables_.set(global->symbol(), sem);
   }
 
-  for (auto const ty : program_->AST().ConstructedTypes()) {
-    if (!EmitConstructedType(ty)) {
+  for (auto* const ty : program_->AST().ConstructedTypes()) {
+    if (!EmitConstructedType(TypeOf(ty))) {
       return false;
     }
   }
@@ -1400,7 +1401,7 @@
   bool has_out_data = out_data != ep_sym_to_out_data_.end();
   if (has_out_data) {
     // TODO(crbug.com/tint/697): Remove this.
-    if (!func->return_type()->Is<sem::Void>()) {
+    if (!func->return_type()->Is<ast::Void>()) {
       TINT_ICE(diagnostics_) << "Mixing module-scope variables and return "
                                 "types for shader outputs";
     }
diff --git a/src/writer/msl/generator_impl.h b/src/writer/msl/generator_impl.h
index 39974c5..dea5bdf 100644
--- a/src/writer/msl/generator_impl.h
+++ b/src/writer/msl/generator_impl.h
@@ -275,7 +275,7 @@
 
   /// @returns the resolved type of the ast::Type `type`
   /// @param type the type
-  const sem::Type* TypeOf(ast::Type* type) const {
+  const sem::Type* TypeOf(const ast::Type* type) const {
     return program_->TypeOf(type);
   }
 
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index 0a75c58..72ad6b2 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -842,9 +842,8 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_1d) {
-  auto* s = create<sem::StorageTexture>(
-      ast::TextureDimension::k1d, ast::ImageFormat::kR32Float,
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Float, Types()));
+  auto s = ty.storage_texture(ast::TextureDimension::k1d,
+                              ast::ImageFormat::kR32Float);
   auto ac = ty.access(ast::AccessControl::kReadOnly, s);
 
   Global("test_var", ac, ast::StorageClass::kInput);
@@ -859,9 +858,8 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_2d) {
-  auto* s = create<sem::StorageTexture>(
-      ast::TextureDimension::k2d, ast::ImageFormat::kR32Float,
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Float, Types()));
+  auto s = ty.storage_texture(ast::TextureDimension::k2d,
+                              ast::ImageFormat::kR32Float);
   auto ac = ty.access(ast::AccessControl::kReadOnly, s);
 
   Global("test_var", ac, ast::StorageClass::kInput);
@@ -876,9 +874,8 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_2dArray) {
-  auto* s = create<sem::StorageTexture>(
-      ast::TextureDimension::k2dArray, ast::ImageFormat::kR32Float,
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Float, Types()));
+  auto s = ty.storage_texture(ast::TextureDimension::k2dArray,
+                              ast::ImageFormat::kR32Float);
   auto ac = ty.access(ast::AccessControl::kReadOnly, s);
 
   Global("test_var", ac, ast::StorageClass::kInput);
@@ -893,9 +890,8 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_3d) {
-  auto* s = create<sem::StorageTexture>(
-      ast::TextureDimension::k3d, ast::ImageFormat::kR32Float,
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Float, Types()));
+  auto s = ty.storage_texture(ast::TextureDimension::k3d,
+                              ast::ImageFormat::kR32Float);
   auto ac = ty.access(ast::AccessControl::kReadOnly, s);
 
   Global("test_var", ac, ast::StorageClass::kInput);
@@ -911,9 +907,8 @@
 
 TEST_F(BuilderTest_Type,
        StorageTexture_Generate_SampledTypeFloat_Format_r32float) {
-  auto* s = create<sem::StorageTexture>(
-      ast::TextureDimension::k2d, ast::ImageFormat::kR32Float,
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Float, Types()));
+  auto s = ty.storage_texture(ast::TextureDimension::k2d,
+                              ast::ImageFormat::kR32Float);
   auto ac = ty.access(ast::AccessControl::kReadOnly, s);
 
   Global("test_var", ac, ast::StorageClass::kInput);
@@ -929,9 +924,8 @@
 
 TEST_F(BuilderTest_Type,
        StorageTexture_Generate_SampledTypeSint_Format_r32sint) {
-  auto* s = create<sem::StorageTexture>(
-      ast::TextureDimension::k2d, ast::ImageFormat::kR32Sint,
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Sint, Types()));
+  auto s = ty.storage_texture(ast::TextureDimension::k2d,
+                              ast::ImageFormat::kR32Sint);
   auto ac = ty.access(ast::AccessControl::kReadOnly, s);
 
   Global("test_var", ac, ast::StorageClass::kInput);
@@ -947,9 +941,8 @@
 
 TEST_F(BuilderTest_Type,
        StorageTexture_Generate_SampledTypeUint_Format_r32uint) {
-  auto* s = create<sem::StorageTexture>(
-      ast::TextureDimension::k2d, ast::ImageFormat::kR32Uint,
-      sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Uint, Types()));
+  auto s = ty.storage_texture(ast::TextureDimension::k2d,
+                              ast::ImageFormat::kR32Uint);
   auto ac = ty.access(ast::AccessControl::kReadOnly, s);
 
   Global("test_var", ac, ast::StorageClass::kInput);
diff --git a/src/writer/wgsl/generator_impl.cc b/src/writer/wgsl/generator_impl.cc
index b704841..7fe0b2e 100644
--- a/src/writer/wgsl/generator_impl.cc
+++ b/src/writer/wgsl/generator_impl.cc
@@ -83,10 +83,6 @@
       if (!EmitConstructedType(ty)) {
         return false;
       }
-    } else if (auto* sem_ty = decl->As<sem::Type>()) {
-      if (!EmitConstructedType(sem_ty)) {
-        return false;
-      }
     } else if (auto* func = decl->As<ast::Function>()) {
       if (entry && func != entry) {
         // Skip functions that are not reachable by the target entry point.
@@ -363,8 +359,7 @@
 
   out_ << ")";
 
-  if (!(Is<ast::Void>(func->return_type().ast) ||
-        Is<sem::Void>(func->return_type().sem)) ||
+  if (!func->return_type()->Is<ast::Void>() ||
       !func->return_type_decorations().empty()) {
     out_ << " -> ";
 
@@ -776,9 +771,9 @@
 
   out_ << " " << program_->Symbols().NameFor(var->symbol());
 
-  if (var->type().ast || var->type().sem) {
+  if (auto* ty = var->type()) {
     out_ << " : ";
-    if (!EmitType(var->type())) {
+    if (!EmitType(ty)) {
       return false;
     }
   }
diff --git a/src/writer/wgsl/generator_impl_unary_op_test.cc b/src/writer/wgsl/generator_impl_unary_op_test.cc
index 1bf93ff..6865234 100644
--- a/src/writer/wgsl/generator_impl_unary_op_test.cc
+++ b/src/writer/wgsl/generator_impl_unary_op_test.cc
@@ -32,8 +32,8 @@
   auto params = GetParam();
 
   auto* type = (params.op == ast::UnaryOp::kNot)
-                   ? static_cast<sem::Type*>(ty.bool_())
-                   : static_cast<sem::Type*>(ty.i32());
+                   ? static_cast<ast::Type*>(ty.bool_())
+                   : static_cast<ast::Type*>(ty.i32());
   Global("expr", type, ast::StorageClass::kPrivate);
 
   auto* op = create<ast::UnaryOpExpression>(params.op, Expr("expr"));