tint: Replace all remaining AST types with ast::Type

This CL removes the following AST nodes:
* ast::Array
* ast::Atomic
* ast::Matrix
* ast::MultisampledTexture
* ast::Pointer
* ast::SampledTexture
* ast::Texture
* ast::TypeName
* ast::Vector

ast::Type, which used to be the base class for all AST types, is now a
thin wrapper around ast::IdentifierExpression. All types are now
referred to using their type name.

The resolver now handles type resolution and validation of the types
listed above based on the TemplateIdentifier arguments.

Other changes:
* ProgramBuilder has undergone substantial refactoring.
* ProgramBuilder helpers for type inferencing is now more explicit.
  Instead of passing 'nullptr', a new 'Infer' template argument is
  passed.
* ast::CheckIdentifier() is used for more tests that check identifiers,
  including types.

Bug: tint:1810
Change-Id: I8e739ef49435dc1c20a462f3ec5ba265661a7edb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/118723
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 59f367c..58d6e95 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -239,9 +239,7 @@
   sources = [
     "ast/accessor_expression.h",
     "ast/alias.h",
-    "ast/array.h",
     "ast/assignment_statement.h",
-    "ast/atomic.h",
     "ast/attribute.h",
     "ast/binary_expression.h",
     "ast/binding_attribute.h",
@@ -286,19 +284,15 @@
     "ast/literal_expression.h",
     "ast/location_attribute.h",
     "ast/loop_statement.h",
-    "ast/matrix.h",
     "ast/member_accessor_expression.h",
     "ast/module.h",
-    "ast/multisampled_texture.h",
     "ast/node.h",
     "ast/node_id.h",
     "ast/override.h",
     "ast/parameter.h",
     "ast/phony_expression.h",
     "ast/pipeline_stage.h",
-    "ast/pointer.h",
     "ast/return_statement.h",
-    "ast/sampled_texture.h",
     "ast/stage_attribute.h",
     "ast/statement.h",
     "ast/stride_attribute.h",
@@ -309,17 +303,14 @@
     "ast/struct_member_size_attribute.h",
     "ast/switch_statement.h",
     "ast/templated_identifier.h",
-    "ast/texture.h",
     "ast/traverse_expressions.h",
     "ast/type.h",
     "ast/type_decl.h",
-    "ast/type_name.h",
     "ast/unary_op.h",
     "ast/unary_op_expression.h",
     "ast/var.h",
     "ast/variable.h",
     "ast/variable_decl_statement.h",
-    "ast/vector.h",
     "ast/while_statement.h",
     "ast/workgroup_attribute.h",
     "clone_context.cc",
@@ -542,12 +533,8 @@
     "ast/accessor_expression.h",
     "ast/alias.cc",
     "ast/alias.h",
-    "ast/array.cc",
-    "ast/array.h",
     "ast/assignment_statement.cc",
     "ast/assignment_statement.h",
-    "ast/atomic.cc",
-    "ast/atomic.h",
     "ast/attribute.cc",
     "ast/attribute.h",
     "ast/binary_expression.cc",
@@ -636,14 +623,10 @@
     "ast/location_attribute.h",
     "ast/loop_statement.cc",
     "ast/loop_statement.h",
-    "ast/matrix.cc",
-    "ast/matrix.h",
     "ast/member_accessor_expression.cc",
     "ast/member_accessor_expression.h",
     "ast/module.cc",
     "ast/module.h",
-    "ast/multisampled_texture.cc",
-    "ast/multisampled_texture.h",
     "ast/node.cc",
     "ast/node.h",
     "ast/node_id.h",
@@ -655,12 +638,8 @@
     "ast/phony_expression.h",
     "ast/pipeline_stage.cc",
     "ast/pipeline_stage.h",
-    "ast/pointer.cc",
-    "ast/pointer.h",
     "ast/return_statement.cc",
     "ast/return_statement.h",
-    "ast/sampled_texture.cc",
-    "ast/sampled_texture.h",
     "ast/stage_attribute.cc",
     "ast/stage_attribute.h",
     "ast/statement.cc",
@@ -681,15 +660,11 @@
     "ast/switch_statement.h",
     "ast/templated_identifier.cc",
     "ast/templated_identifier.h",
-    "ast/texture.cc",
-    "ast/texture.h",
     "ast/traverse_expressions.h",
     "ast/type.cc",
     "ast/type.h",
     "ast/type_decl.cc",
     "ast/type_decl.h",
-    "ast/type_name.cc",
-    "ast/type_name.h",
     "ast/unary_op.cc",
     "ast/unary_op.h",
     "ast/unary_op_expression.cc",
@@ -700,8 +675,6 @@
     "ast/variable.h",
     "ast/variable_decl_statement.cc",
     "ast/variable_decl_statement.h",
-    "ast/vector.cc",
-    "ast/vector.h",
     "ast/while_statement.cc",
     "ast/while_statement.h",
     "ast/workgroup_attribute.cc",
@@ -1294,9 +1267,7 @@
   tint_unittests_source_set("tint_unittests_ast_src") {
     sources = [
       "ast/alias_test.cc",
-      "ast/array_test.cc",
       "ast/assignment_statement_test.cc",
-      "ast/atomic_test.cc",
       "ast/binary_expression_test.cc",
       "ast/binding_attribute_test.cc",
       "ast/bitcast_expression_test.cc",
@@ -1336,14 +1307,10 @@
       "ast/invariant_attribute_test.cc",
       "ast/location_attribute_test.cc",
       "ast/loop_statement_test.cc",
-      "ast/matrix_test.cc",
       "ast/member_accessor_expression_test.cc",
       "ast/module_test.cc",
-      "ast/multisampled_texture_test.cc",
       "ast/phony_expression_test.cc",
-      "ast/pointer_test.cc",
       "ast/return_statement_test.cc",
-      "ast/sampled_texture_test.cc",
       "ast/stage_attribute_test.cc",
       "ast/stride_attribute_test.cc",
       "ast/struct_member_align_attribute_test.cc",
@@ -1353,13 +1320,10 @@
       "ast/struct_test.cc",
       "ast/switch_statement_test.cc",
       "ast/templated_identifier_test.cc",
-      "ast/texture_test.cc",
       "ast/traverse_expressions_test.cc",
-      "ast/type_name_test.cc",
       "ast/unary_op_expression_test.cc",
       "ast/variable_decl_statement_test.cc",
       "ast/variable_test.cc",
-      "ast/vector_test.cc",
       "ast/while_statement_test.cc",
       "ast/workgroup_attribute_test.cc",
     ]
@@ -1489,7 +1453,6 @@
     sources = [
       "type/access_test.cc",
       "type/address_space_test.cc",
-      "type/array_test.cc",
       "type/atomic_test.cc",
       "type/bool_test.cc",
       "type/builtin_test.cc",
@@ -1583,6 +1546,7 @@
     deps = [
       ":libtint_base_src",
       ":libtint_transform_src",
+      ":libtint_unittests_ast_helper",
       ":libtint_wgsl_reader_src",
       ":libtint_wgsl_writer_src",
     ]
@@ -1626,6 +1590,7 @@
     ]
     deps = [
       ":libtint_base_src",
+      ":libtint_unittests_ast_helper",
       ":libtint_writer_src",
     ]
   }
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 92c3bb6..868bce5 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -84,12 +84,8 @@
   ast/accessor_expression.h
   ast/alias.cc
   ast/alias.h
-  ast/array.cc
-  ast/array.h
   ast/assignment_statement.cc
   ast/assignment_statement.h
-  ast/atomic.cc
-  ast/atomic.h
   ast/attribute.cc
   ast/attribute.h
   ast/binary_expression.cc
@@ -172,14 +168,10 @@
   ast/location_attribute.h
   ast/loop_statement.cc
   ast/loop_statement.h
-  ast/matrix.cc
-  ast/matrix.h
   ast/member_accessor_expression.cc
   ast/member_accessor_expression.h
   ast/module.cc
   ast/module.h
-  ast/multisampled_texture.cc
-  ast/multisampled_texture.h
   ast/node_id.h
   ast/node.cc
   ast/node.h
@@ -191,12 +183,8 @@
   ast/phony_expression.h
   ast/pipeline_stage.cc
   ast/pipeline_stage.h
-  ast/pointer.cc
-  ast/pointer.h
   ast/return_statement.cc
   ast/return_statement.h
-  ast/sampled_texture.cc
-  ast/sampled_texture.h
   ast/stage_attribute.cc
   ast/stage_attribute.h
   ast/statement.cc
@@ -217,15 +205,11 @@
   ast/switch_statement.h
   ast/templated_identifier.cc
   ast/templated_identifier.h
-  ast/texture.cc
-  ast/texture.h
   ast/traverse_expressions.h
-  ast/type_decl.cc
-  ast/type_decl.h
-  ast/type_name.cc
-  ast/type_name.h
   ast/type.cc
   ast/type.h
+  ast/type_decl.cc
+  ast/type_decl.h
   ast/unary_op_expression.cc
   ast/unary_op_expression.h
   ast/unary_op.cc
@@ -236,8 +220,6 @@
   ast/variable_decl_statement.h
   ast/variable.cc
   ast/variable.h
-  ast/vector.cc
-  ast/vector.h
   ast/while_statement.cc
   ast/while_statement.h
   ast/workgroup_attribute.cc
@@ -808,9 +790,7 @@
 if(TINT_BUILD_TESTS)
   list(APPEND TINT_TEST_SRCS
     ast/alias_test.cc
-    ast/array_test.cc
     ast/assignment_statement_test.cc
-    ast/atomic_test.cc
     ast/binary_expression_test.cc
     ast/binding_attribute_test.cc
     ast/bitcast_expression_test.cc
@@ -847,14 +827,10 @@
     ast/invariant_attribute_test.cc
     ast/location_attribute_test.cc
     ast/loop_statement_test.cc
-    ast/matrix_test.cc
     ast/member_accessor_expression_test.cc
     ast/module_test.cc
-    ast/multisampled_texture_test.cc
     ast/phony_expression_test.cc
-    ast/pointer_test.cc
     ast/return_statement_test.cc
-    ast/sampled_texture_test.cc
     ast/stage_attribute_test.cc
     ast/stride_attribute_test.cc
     ast/struct_member_align_attribute_test.cc
@@ -866,13 +842,10 @@
     ast/templated_identifier_test.cc
     ast/test_helper.h
     ast/test_helper_test.cc
-    ast/texture_test.cc
     ast/traverse_expressions_test.cc
-    ast/type_name_test.cc
     ast/unary_op_expression_test.cc
     ast/variable_decl_statement_test.cc
     ast/variable_test.cc
-    ast/vector_test.cc
     ast/while_statement_test.cc
     ast/workgroup_attribute_test.cc
     castable_test.cc
diff --git a/src/tint/ast/alias.cc b/src/tint/ast/alias.cc
index 2672667..ec5cf43 100644
--- a/src/tint/ast/alias.cc
+++ b/src/tint/ast/alias.cc
@@ -20,7 +20,7 @@
 
 namespace tint::ast {
 
-Alias::Alias(ProgramID pid, NodeID nid, const Source& src, const Identifier* n, const Type* subtype)
+Alias::Alias(ProgramID pid, NodeID nid, const Source& src, const Identifier* n, Type subtype)
     : Base(pid, nid, src, n), type(subtype) {
     TINT_ASSERT(AST, type);
 }
@@ -33,7 +33,7 @@
     // Clone arguments outside of create() call to have deterministic ordering
     auto src = ctx->Clone(source);
     auto sym = ctx->Clone(name);
-    auto* ty = ctx->Clone(type);
+    auto ty = ctx->Clone(type);
     return ctx->dst->create<Alias>(src, sym, ty);
 }
 
diff --git a/src/tint/ast/alias.h b/src/tint/ast/alias.h
index 971d46b..267a313 100644
--- a/src/tint/ast/alias.h
+++ b/src/tint/ast/alias.h
@@ -17,13 +17,9 @@
 
 #include <string>
 
+#include "src/tint/ast/type.h"
 #include "src/tint/ast/type_decl.h"
 
-// Forward declarations
-namespace tint::ast {
-class Type;
-}  // namespace tint::ast
-
 namespace tint::ast {
 
 /// A type alias type. Holds a name and pointer to another type.
@@ -35,11 +31,7 @@
     /// @param src the source of this node
     /// @param name the symbol for the alias
     /// @param subtype the alias'd type
-    Alias(ProgramID pid,
-          NodeID nid,
-          const Source& src,
-          const Identifier* name,
-          const Type* subtype);
+    Alias(ProgramID pid, NodeID nid, const Source& src, const Identifier* name, Type subtype);
     /// Move constructor
     Alias(Alias&&);
     /// Destructor
@@ -51,7 +43,7 @@
     const Alias* Clone(CloneContext* ctx) const override;
 
     /// the alias type
-    const Type* const type;
+    const Type type;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/alias_test.cc b/src/tint/ast/alias_test.cc
index b35fb1a..6e5d647 100644
--- a/src/tint/ast/alias_test.cc
+++ b/src/tint/ast/alias_test.cc
@@ -13,13 +13,7 @@
 // limitations under the License.
 
 #include "src/tint/ast/alias.h"
-#include "src/tint/ast/array.h"
-#include "src/tint/ast/matrix.h"
-#include "src/tint/ast/pointer.h"
-#include "src/tint/ast/struct.h"
 #include "src/tint/ast/test_helper.h"
-#include "src/tint/ast/texture.h"
-#include "src/tint/ast/vector.h"
 #include "src/tint/type/access.h"
 
 namespace tint::ast {
@@ -28,10 +22,10 @@
 using AstAliasTest = TestHelper;
 
 TEST_F(AstAliasTest, Create) {
-    auto* u32 = ty.u32();
+    auto u32 = ty.u32();
     auto* a = Alias("a_type", u32);
-    EXPECT_EQ(Symbols().NameFor(a->name->symbol), "a_type");
-    EXPECT_EQ(a->type, u32);
+    CheckIdentifier(Symbols(), a->name, "a_type");
+    CheckIdentifier(Symbols(), a->type, "u32");
 }
 
 }  // namespace
diff --git a/src/tint/ast/array.cc b/src/tint/ast/array.cc
deleted file mode 100644
index 6233381..0000000
--- a/src/tint/ast/array.cc
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/array.h"
-
-#include <cmath>
-#include <utility>
-
-#include "src/tint/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Array);
-
-namespace tint::ast {
-
-namespace {
-// Returns the string representation of an array size expression.
-std::string SizeExprToString(const Expression* size, const SymbolTable& symbols) {
-    if (auto* ident = size->As<IdentifierExpression>()) {
-        return symbols.NameFor(ident->identifier->symbol);
-    }
-    if (auto* literal = size->As<IntLiteralExpression>()) {
-        return std::to_string(literal->value);
-    }
-    // This will never be exposed to the user as the Resolver will reject this
-    // expression for array size.
-    return "<invalid>";
-}
-}  // namespace
-
-Array::Array(ProgramID pid,
-             NodeID nid,
-             const Source& src,
-             const Type* subtype,
-             const Expression* cnt,
-             utils::VectorRef<const Attribute*> attrs)
-    : Base(pid, nid, src), type(subtype), count(cnt), attributes(std::move(attrs)) {}
-
-Array::Array(Array&&) = default;
-
-Array::~Array() = default;
-
-std::string Array::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
-    for (auto* attr : attributes) {
-        if (auto* stride = attr->As<ast::StrideAttribute>()) {
-            out << "@stride(" << stride->stride << ") ";
-        }
-    }
-    out << "array";
-    if (type) {
-        out << "<" << type->FriendlyName(symbols);
-        if (count) {
-            out << ", " << SizeExprToString(count, symbols);
-        }
-        out << ">";
-    }
-    return out.str();
-}
-
-const Array* Array::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* cnt = ctx->Clone(count);
-    auto attrs = ctx->Clone(attributes);
-    return ctx->dst->create<Array>(src, ty, cnt, std::move(attrs));
-}
-
-}  // namespace tint::ast
diff --git a/src/tint/ast/array.h b/src/tint/ast/array.h
deleted file mode 100644
index 19e806e..0000000
--- a/src/tint/ast/array.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_AST_ARRAY_H_
-#define SRC_TINT_AST_ARRAY_H_
-
-#include <string>
-
-#include "src/tint/ast/attribute.h"
-#include "src/tint/ast/type.h"
-
-// Forward declarations
-namespace tint::ast {
-class Expression;
-}  // namespace tint::ast
-
-namespace tint::ast {
-
-/// An array type. If size is zero then it is a runtime array.
-class Array final : public Castable<Array, Type> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param src the source of this node
-    /// @param subtype the type of the array elements
-    /// @param count the number of elements in the array
-    /// @param attributes the array attributes
-    /// @note a runtime-sized array is represented by a null count and a non-null type
-    Array(ProgramID pid,
-          NodeID nid,
-          const Source& src,
-          const Type* subtype,
-          const Expression* count,
-          utils::VectorRef<const Attribute*> attributes);
-    /// Move constructor
-    Array(Array&&);
-    ~Array() override;
-
-    /// @returns true if this is a runtime array.
-    /// i.e. the size is determined at runtime
-    bool IsRuntimeArray() const { return type != nullptr && count == nullptr; }
-
-    /// @param symbols the program's symbol table
-    /// @returns the name for this type that closely resembles how it would be
-    /// declared in WGSL.
-    std::string FriendlyName(const SymbolTable& symbols) const override;
-
-    /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned type
-    const Array* Clone(CloneContext* ctx) const override;
-
-    /// the array element type
-    const Type* const type;
-
-    /// the array size in elements, or nullptr for a runtime array
-    const Expression* const count;
-
-    /// the array attributes
-    const utils::Vector<const Attribute*, 1> attributes;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_AST_ARRAY_H_
diff --git a/src/tint/ast/array_test.cc b/src/tint/ast/array_test.cc
deleted file mode 100644
index eb5eddc..0000000
--- a/src/tint/ast/array_test.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/array.h"
-
-#include "src/tint/ast/test_helper.h"
-
-using namespace tint::number_suffixes;  // NOLINT
-
-namespace tint::ast {
-namespace {
-
-using AstArrayTest = TestHelper;
-
-TEST_F(AstArrayTest, CreateSizedArray) {
-    auto* count = Expr(3_u);
-    auto* arr = create<Array>(ty.u32(), count, utils::Empty);
-    ASSERT_TRUE(arr->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(arr->type->As<ast::TypeName>()->name->symbol), "u32");
-    EXPECT_EQ(arr->count, count);
-    EXPECT_TRUE(arr->Is<Array>());
-    EXPECT_FALSE(arr->IsRuntimeArray());
-}
-
-TEST_F(AstArrayTest, CreateRuntimeArray) {
-    auto* arr = create<Array>(ty.u32(), nullptr, utils::Empty);
-    ASSERT_TRUE(arr->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(arr->type->As<ast::TypeName>()->name->symbol), "u32");
-    EXPECT_EQ(arr->count, nullptr);
-    EXPECT_TRUE(arr->Is<Array>());
-    EXPECT_TRUE(arr->IsRuntimeArray());
-}
-
-TEST_F(AstArrayTest, CreateInferredTypeArray) {
-    auto* arr = create<Array>(nullptr, nullptr, utils::Empty);
-    EXPECT_EQ(arr->type, nullptr);
-    EXPECT_EQ(arr->count, nullptr);
-    EXPECT_TRUE(arr->Is<Array>());
-    EXPECT_FALSE(arr->IsRuntimeArray());
-}
-
-TEST_F(AstArrayTest, FriendlyName_RuntimeSized) {
-    auto* arr = create<Array>(ty.i32(), nullptr, utils::Empty);
-    EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32>");
-}
-
-TEST_F(AstArrayTest, FriendlyName_LiteralSized) {
-    auto* arr = create<Array>(ty.i32(), Expr(5_u), utils::Empty);
-    EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, 5>");
-}
-
-TEST_F(AstArrayTest, FriendlyName_ConstantSized) {
-    auto* arr = create<Array>(ty.i32(), Expr("size"), utils::Empty);
-    EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, size>");
-}
-
-TEST_F(AstArrayTest, FriendlyName_WithStride) {
-    auto* arr = create<Array>(ty.i32(), Expr(5_u), utils::Vector{create<StrideAttribute>(32u)});
-    EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(32) array<i32, 5>");
-}
-
-TEST_F(AstArrayTest, FriendlyName_InferredTypeAndCount) {
-    auto* arr = create<Array>(nullptr, nullptr, utils::Empty);
-    EXPECT_EQ(arr->FriendlyName(Symbols()), "array");
-}
-
-TEST_F(AstArrayTest, FriendlyName_InferredTypeAndCount_WithStrize) {
-    auto* arr = create<Array>(nullptr, nullptr, utils::Vector{create<StrideAttribute>(32u)});
-    EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(32) array");
-}
-
-}  // namespace
-}  // namespace tint::ast
diff --git a/src/tint/ast/atomic.cc b/src/tint/ast/atomic.cc
deleted file mode 100644
index 9914c6a..0000000
--- a/src/tint/ast/atomic.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/atomic.h"
-
-#include "src/tint/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Atomic);
-
-namespace tint::ast {
-
-Atomic::Atomic(ProgramID pid, NodeID nid, const Source& src, const Type* const subtype)
-    : Base(pid, nid, src), type(subtype) {}
-
-std::string Atomic::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
-    out << "atomic<" << type->FriendlyName(symbols) << ">";
-    return out.str();
-}
-
-Atomic::Atomic(Atomic&&) = default;
-
-Atomic::~Atomic() = default;
-
-const Atomic* Atomic::Clone(CloneContext* ctx) const {
-    // Clone arguments outside of create() call to have deterministic ordering
-    auto src = ctx->Clone(source);
-    auto* ty = ctx->Clone(type);
-    return ctx->dst->create<Atomic>(src, ty);
-}
-
-}  // namespace tint::ast
diff --git a/src/tint/ast/atomic.h b/src/tint/ast/atomic.h
deleted file mode 100644
index 689871e..0000000
--- a/src/tint/ast/atomic.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_AST_ATOMIC_H_
-#define SRC_TINT_AST_ATOMIC_H_
-
-#include <string>
-
-#include "src/tint/ast/type.h"
-
-namespace tint::ast {
-
-/// An atomic type.
-class Atomic final : public Castable<Atomic, Type> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param src the source of this node
-    /// @param subtype the pointee type
-    Atomic(ProgramID pid, NodeID nid, const Source& src, const Type* const subtype);
-    /// Move constructor
-    Atomic(Atomic&&);
-    ~Atomic() override;
-
-    /// @param symbols the program's symbol table
-    /// @returns the name for this type that closely resembles how it would be
-    /// declared in WGSL.
-    std::string FriendlyName(const SymbolTable& symbols) const override;
-
-    /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned type
-    const Atomic* Clone(CloneContext* ctx) const override;
-
-    /// the pointee type
-    const Type* const type;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_AST_ATOMIC_H_
diff --git a/src/tint/ast/atomic_test.cc b/src/tint/ast/atomic_test.cc
deleted file mode 100644
index abb906e..0000000
--- a/src/tint/ast/atomic_test.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/atomic.h"
-
-#include "src/tint/ast/test_helper.h"
-
-namespace tint::ast {
-namespace {
-
-using AstAtomicTest = TestHelper;
-
-TEST_F(AstAtomicTest, Creation) {
-    auto* i32 = ty.i32();
-    auto* p = create<Atomic>(i32);
-    ASSERT_TRUE(p->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(p->type->As<ast::TypeName>()->name->symbol), "i32");
-}
-
-TEST_F(AstAtomicTest, FriendlyName) {
-    auto* i32 = ty.i32();
-    auto* p = create<Atomic>(i32);
-    EXPECT_EQ(p->FriendlyName(Symbols()), "atomic<i32>");
-}
-
-}  // namespace
-}  // namespace tint::ast
diff --git a/src/tint/ast/bitcast_expression.cc b/src/tint/ast/bitcast_expression.cc
index 5cabf67..4091d73 100644
--- a/src/tint/ast/bitcast_expression.cc
+++ b/src/tint/ast/bitcast_expression.cc
@@ -23,7 +23,7 @@
 BitcastExpression::BitcastExpression(ProgramID pid,
                                      NodeID nid,
                                      const Source& src,
-                                     const Type* t,
+                                     Type t,
                                      const Expression* e)
     : Base(pid, nid, src), type(t), expr(e) {
     TINT_ASSERT(AST, type);
@@ -37,7 +37,7 @@
 const BitcastExpression* BitcastExpression::Clone(CloneContext* ctx) const {
     // Clone arguments outside of create() call to have deterministic ordering
     auto src = ctx->Clone(source);
-    auto* t = ctx->Clone(type);
+    auto t = ctx->Clone(type);
     auto* e = ctx->Clone(expr);
     return ctx->dst->create<BitcastExpression>(src, t, e);
 }
diff --git a/src/tint/ast/bitcast_expression.h b/src/tint/ast/bitcast_expression.h
index 66952b7..82d3bd2 100644
--- a/src/tint/ast/bitcast_expression.h
+++ b/src/tint/ast/bitcast_expression.h
@@ -16,11 +16,7 @@
 #define SRC_TINT_AST_BITCAST_EXPRESSION_H_
 
 #include "src/tint/ast/expression.h"
-
-// Forward declarations
-namespace tint::ast {
-class Type;
-}  // namespace tint::ast
+#include "src/tint/ast/type.h"
 
 namespace tint::ast {
 
@@ -36,7 +32,7 @@
     BitcastExpression(ProgramID pid,
                       NodeID nid,
                       const Source& source,
-                      const Type* type,
+                      Type type,
                       const Expression* expr);
     /// Move constructor
     BitcastExpression(BitcastExpression&&);
@@ -49,7 +45,7 @@
     const BitcastExpression* Clone(CloneContext* ctx) const override;
 
     /// the target cast type
-    const Type* const type;
+    const Type type;
     /// the expression
     const Expression* const expr;
 };
diff --git a/src/tint/ast/bitcast_expression_test.cc b/src/tint/ast/bitcast_expression_test.cc
index 1a446f3..d288a77 100644
--- a/src/tint/ast/bitcast_expression_test.cc
+++ b/src/tint/ast/bitcast_expression_test.cc
@@ -24,17 +24,15 @@
 
 TEST_F(BitcastExpressionTest, Create) {
     auto* expr = Expr("expr");
-
-    auto* exp = create<BitcastExpression>(ty.f32(), expr);
-    ASSERT_TRUE(exp->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(exp->type->As<ast::TypeName>()->name->symbol), "f32");
+    auto* exp = Bitcast(ty.f32(), expr);
+    CheckIdentifier(Symbols(), exp->type, "f32");
     ASSERT_EQ(exp->expr, expr);
 }
 
 TEST_F(BitcastExpressionTest, CreateWithSource) {
     auto* expr = Expr("expr");
 
-    auto* exp = create<BitcastExpression>(Source{Source::Location{20, 2}}, ty.f32(), expr);
+    auto* exp = Bitcast(Source{Source::Location{20, 2}}, ty.f32(), expr);
     auto src = exp->source;
     EXPECT_EQ(src.range.begin.line, 20u);
     EXPECT_EQ(src.range.begin.column, 2u);
@@ -43,7 +41,7 @@
 TEST_F(BitcastExpressionTest, IsBitcast) {
     auto* expr = Expr("expr");
 
-    auto* exp = create<BitcastExpression>(ty.f32(), expr);
+    auto* exp = Bitcast(ty.f32(), expr);
     EXPECT_TRUE(exp->Is<BitcastExpression>());
 }
 
@@ -51,7 +49,7 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.create<BitcastExpression>(nullptr, b.Expr("idx"));
+            b.Bitcast(ast::Type(), "idx");
         },
         "internal compiler error");
 }
@@ -60,7 +58,7 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.create<BitcastExpression>(b.ty.f32(), nullptr);
+            b.Bitcast(b.ty.f32(), nullptr);
         },
         "internal compiler error");
 }
@@ -70,7 +68,7 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<BitcastExpression>(b1.ty.f32(), b2.Expr("idx"));
+            b1.Bitcast(b1.ty.f32(), b2.Expr("idx"));
         },
         "internal compiler error");
 }
diff --git a/src/tint/ast/builtin_texture_helper_test.cc b/src/tint/ast/builtin_texture_helper_test.cc
index df09f11..4aefbc6 100644
--- a/src/tint/ast/builtin_texture_helper_test.cc
+++ b/src/tint/ast/builtin_texture_helper_test.cc
@@ -127,7 +127,7 @@
     return out;
 }
 
-const ast::Type* TextureOverloadCase::BuildResultVectorComponentType(ProgramBuilder* b) const {
+ast::Type TextureOverloadCase::BuildResultVectorComponentType(ProgramBuilder* b) const {
     switch (texture_data_type) {
         case ast::builtin::test::TextureDataType::kF32:
             return b->ty.f32();
@@ -166,7 +166,7 @@
                 attrs);
 
         case ast::builtin::test::TextureKind::kStorage: {
-            auto* st = b->ty.storage_texture(texture_dimension, texel_format, access);
+            auto st = b->ty.storage_texture(texture_dimension, texel_format, access);
             return b->GlobalVar(kTextureName, st, attrs);
         }
     }
diff --git a/src/tint/ast/builtin_texture_helper_test.h b/src/tint/ast/builtin_texture_helper_test.h
index 7130b9a..6fe0193 100644
--- a/src/tint/ast/builtin_texture_helper_test.h
+++ b/src/tint/ast/builtin_texture_helper_test.h
@@ -224,7 +224,7 @@
 
     /// @param builder the AST builder used for the test
     /// @returns the vector component type of the texture function return value
-    const ast::Type* BuildResultVectorComponentType(ProgramBuilder* builder) const;
+    ast::Type BuildResultVectorComponentType(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/tint/ast/call_expression.cc b/src/tint/ast/call_expression.cc
index 145092f..9da7106 100644
--- a/src/tint/ast/call_expression.cc
+++ b/src/tint/ast/call_expression.cc
@@ -22,41 +22,14 @@
 
 namespace tint::ast {
 
-namespace {
-CallExpression::Target ToTarget(const Identifier* name) {
-    CallExpression::Target target;
-    target.name = name;
-    return target;
-}
-CallExpression::Target ToTarget(const Type* type) {
-    CallExpression::Target target;
-    target.type = type;
-    return target;
-}
-}  // namespace
-
 CallExpression::CallExpression(ProgramID pid,
                                NodeID nid,
                                const Source& src,
-                               const Identifier* name,
+                               const IdentifierExpression* t,
                                utils::VectorRef<const Expression*> a)
-    : Base(pid, nid, src), target(ToTarget(name)), args(std::move(a)) {
-    TINT_ASSERT(AST, name);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, name, program_id);
-    for (auto* arg : args) {
-        TINT_ASSERT(AST, arg);
-        TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, arg, program_id);
-    }
-}
-
-CallExpression::CallExpression(ProgramID pid,
-                               NodeID nid,
-                               const Source& src,
-                               const Type* type,
-                               utils::VectorRef<const Expression*> a)
-    : Base(pid, nid, src), target(ToTarget(type)), args(std::move(a)) {
-    TINT_ASSERT(AST, type);
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, type, program_id);
+    : Base(pid, nid, src), target(t), args(std::move(a)) {
+    TINT_ASSERT(AST, target);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, target, program_id);
     for (auto* arg : args) {
         TINT_ASSERT(AST, arg);
         TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, arg, program_id);
@@ -71,9 +44,8 @@
     // Clone arguments outside of create() call to have deterministic ordering
     auto src = ctx->Clone(source);
     auto p = ctx->Clone(args);
-    return target.name
-               ? ctx->dst->create<CallExpression>(src, ctx->Clone(target.name), std::move(p))
-               : ctx->dst->create<CallExpression>(src, ctx->Clone(target.type), std::move(p));
+    auto t = ctx->Clone(target);
+    return ctx->dst->create<CallExpression>(src, t, std::move(p));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/call_expression.h b/src/tint/ast/call_expression.h
index a3b6edc..b22ec11 100644
--- a/src/tint/ast/call_expression.h
+++ b/src/tint/ast/call_expression.h
@@ -19,8 +19,7 @@
 
 // Forward declarations
 namespace tint::ast {
-class Type;
-class Identifier;
+class IdentifierExpression;
 }  // namespace tint::ast
 
 namespace tint::ast {
@@ -36,24 +35,12 @@
     /// @param pid the identifier of the program that owns this node
     /// @param nid the unique node identifier
     /// @param source the call expression source
-    /// @param name the function or type name
+    /// @param target the target of the call
     /// @param args the arguments
     CallExpression(ProgramID pid,
                    NodeID nid,
                    const Source& source,
-                   const Identifier* name,
-                   utils::VectorRef<const Expression*> args);
-
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param source the call expression source
-    /// @param type the type
-    /// @param args the arguments
-    CallExpression(ProgramID pid,
-                   NodeID nid,
-                   const Source& source,
-                   const Type* type,
+                   const IdentifierExpression* target,
                    utils::VectorRef<const Expression*> args);
 
     /// Move constructor
@@ -66,18 +53,8 @@
     /// @return the newly cloned node
     const CallExpression* Clone(CloneContext* ctx) const override;
 
-    /// Target is either an identifier, or a Type.
-    /// One of these must be nullptr and the other a non-nullptr.
-    struct Target {
-        /// name is a function or builtin to call, or type name to construct or
-        /// cast-to
-        const Identifier* name = nullptr;
-        /// type to construct or cast-to
-        const Type* type = nullptr;
-    };
-
-    /// The target function
-    const Target target;
+    /// The target function or type
+    const IdentifierExpression* target;
 
     /// The arguments
     const utils::Vector<const Expression*, 8> args;
diff --git a/src/tint/ast/call_expression_test.cc b/src/tint/ast/call_expression_test.cc
index 12b446a..6a6ee29 100644
--- a/src/tint/ast/call_expression_test.cc
+++ b/src/tint/ast/call_expression_test.cc
@@ -21,15 +21,14 @@
 using CallExpressionTest = TestHelper;
 
 TEST_F(CallExpressionTest, CreationIdentifier) {
-    auto* func = Ident("func");
+    auto* func = Expr("func");
     utils::Vector params{
         Expr("param1"),
         Expr("param2"),
     };
 
     auto* stmt = Call(func, params);
-    EXPECT_EQ(stmt->target.name, func);
-    EXPECT_EQ(stmt->target.type, nullptr);
+    EXPECT_EQ(stmt->target, func);
 
     const auto& vec = stmt->args;
     ASSERT_EQ(vec.Length(), 2u);
@@ -38,10 +37,9 @@
 }
 
 TEST_F(CallExpressionTest, CreationIdentifier_WithSource) {
-    auto* func = Ident("func");
+    auto* func = Expr("func");
     auto* stmt = Call(Source{{20, 2}}, func);
-    EXPECT_EQ(stmt->target.name, func);
-    EXPECT_EQ(stmt->target.type, nullptr);
+    EXPECT_EQ(stmt->target, func);
 
     auto src = stmt->source;
     EXPECT_EQ(src.range.begin.line, 20u);
@@ -49,15 +47,14 @@
 }
 
 TEST_F(CallExpressionTest, CreationType) {
-    auto* type = ty.f32();
+    auto* type = Expr(ty.f32());
     utils::Vector params{
         Expr("param1"),
         Expr("param2"),
     };
 
     auto* stmt = Call(type, params);
-    EXPECT_EQ(stmt->target.name, nullptr);
-    EXPECT_EQ(stmt->target.type, type);
+    EXPECT_EQ(stmt->target, type);
 
     const auto& vec = stmt->args;
     ASSERT_EQ(vec.Length(), 2u);
@@ -66,10 +63,9 @@
 }
 
 TEST_F(CallExpressionTest, CreationType_WithSource) {
-    auto* type = ty.f32();
+    auto* type = Expr(ty.f32());
     auto* stmt = Call(Source{{20, 2}}, type);
-    EXPECT_EQ(stmt->target.name, nullptr);
-    EXPECT_EQ(stmt->target.type, type);
+    EXPECT_EQ(stmt->target, type);
 
     auto src = stmt->source;
     EXPECT_EQ(src.range.begin.line, 20u);
@@ -77,7 +73,7 @@
 }
 
 TEST_F(CallExpressionTest, IsCall) {
-    auto* func = Ident("func");
+    auto* func = Expr("func");
     auto* stmt = Call(func);
     EXPECT_TRUE(stmt->Is<CallExpression>());
 }
@@ -91,15 +87,6 @@
         "internal compiler error");
 }
 
-TEST_F(CallExpressionTest, Assert_Null_Type) {
-    EXPECT_FATAL_FAILURE(
-        {
-            ProgramBuilder b;
-            b.Call(static_cast<Type*>(nullptr));
-        },
-        "internal compiler error");
-}
-
 TEST_F(CallExpressionTest, Assert_Null_Param) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ast/const.cc b/src/tint/ast/const.cc
index 79466c0..60f9bef 100644
--- a/src/tint/ast/const.cc
+++ b/src/tint/ast/const.cc
@@ -26,7 +26,7 @@
              NodeID nid,
              const Source& src,
              const Identifier* n,
-             const ast::Type* ty,
+             Type ty,
              const Expression* init,
              utils::VectorRef<const Attribute*> attrs)
     : Base(pid, nid, src, n, ty, init, std::move(attrs)) {
@@ -44,7 +44,7 @@
 const Const* Const::Clone(CloneContext* ctx) const {
     auto src = ctx->Clone(source);
     auto n = ctx->Clone(name);
-    auto* ty = ctx->Clone(type);
+    auto ty = ctx->Clone(type);
     auto* init = ctx->Clone(initializer);
     auto attrs = ctx->Clone(attributes);
     return ctx->dst->create<Const>(src, n, ty, init, std::move(attrs));
diff --git a/src/tint/ast/const.h b/src/tint/ast/const.h
index aa73d4f..75dc767 100644
--- a/src/tint/ast/const.h
+++ b/src/tint/ast/const.h
@@ -44,7 +44,7 @@
           NodeID nid,
           const Source& source,
           const Identifier* name,
-          const ast::Type* type,
+          Type type,
           const Expression* initializer,
           utils::VectorRef<const Attribute*> attributes);
 
diff --git a/src/tint/ast/function.cc b/src/tint/ast/function.cc
index 5c0597a..3c5446c 100644
--- a/src/tint/ast/function.cc
+++ b/src/tint/ast/function.cc
@@ -27,7 +27,7 @@
                    const Source& src,
                    const Identifier* n,
                    utils::VectorRef<const Parameter*> parameters,
-                   const Type* return_ty,
+                   Type return_ty,
                    const BlockStatement* b,
                    utils::VectorRef<const Attribute*> attrs,
                    utils::VectorRef<const Attribute*> return_type_attrs)
@@ -73,7 +73,7 @@
     auto src = ctx->Clone(source);
     auto n = ctx->Clone(name);
     auto p = ctx->Clone(params);
-    auto* ret = ctx->Clone(return_type);
+    auto ret = ctx->Clone(return_type);
     auto* b = ctx->Clone(body);
     auto attrs = ctx->Clone(attributes);
     auto ret_attrs = ctx->Clone(return_type_attributes);
diff --git a/src/tint/ast/function.h b/src/tint/ast/function.h
index fd39ba3..802f46d 100644
--- a/src/tint/ast/function.h
+++ b/src/tint/ast/function.h
@@ -32,6 +32,7 @@
 // Forward declarations
 namespace tint::ast {
 class Identifier;
+class IdentifierExpression;
 }  // namespace tint::ast
 
 namespace tint::ast {
@@ -54,7 +55,7 @@
              const Source& source,
              const Identifier* name,
              utils::VectorRef<const Parameter*> params,
-             const Type* return_type,
+             Type return_type,
              const BlockStatement* body,
              utils::VectorRef<const Attribute*> attributes,
              utils::VectorRef<const Attribute*> return_type_attributes);
@@ -82,7 +83,7 @@
     const utils::Vector<const Parameter*, 8> params;
 
     /// The function return type
-    const Type* const return_type;
+    const Type return_type;
 
     /// The function body
     const BlockStatement* const body;
diff --git a/src/tint/ast/function_test.cc b/src/tint/ast/function_test.cc
index 90afda1..a9b327e 100644
--- a/src/tint/ast/function_test.cc
+++ b/src/tint/ast/function_test.cc
@@ -27,13 +27,13 @@
 
 TEST_F(FunctionTest, Creation_i32ReturnType) {
     utils::Vector params{Param("var", ty.i32())};
-    auto* i32 = ty.i32();
+    auto i32 = ty.i32();
     auto* var = params[0];
 
     auto* f = Func("func", params, i32, utils::Empty);
     EXPECT_EQ(f->name->symbol, Symbols().Get("func"));
     ASSERT_EQ(f->params.Length(), 1u);
-    EXPECT_EQ(f->return_type, i32);
+    CheckIdentifier(Symbols(), f->return_type, "i32");
     EXPECT_EQ(f->params[0], var);
 }
 
@@ -116,15 +116,6 @@
         "internal compiler error");
 }
 
-TEST_F(FunctionTest, Assert_InvalidName) {
-    EXPECT_FATAL_FAILURE(
-        {
-            ProgramBuilder b;
-            b.Func("", utils::Empty, b.ty.void_(), utils::Empty);
-        },
-        "internal compiler error");
-}
-
 TEST_F(FunctionTest, Assert_NullParam) {
     using ParamList = utils::Vector<const ast::Parameter*, 2>;
     EXPECT_FATAL_FAILURE(
diff --git a/src/tint/ast/identifier_expression_test.cc b/src/tint/ast/identifier_expression_test.cc
index 6eb951c..e31b654 100644
--- a/src/tint/ast/identifier_expression_test.cc
+++ b/src/tint/ast/identifier_expression_test.cc
@@ -44,11 +44,6 @@
     EXPECT_EQ(i->identifier->source.range, (Source::Range{{20, 2}}));
 }
 
-TEST_F(IdentifierExpressionTest, IsIdentifier) {
-    auto* i = Expr("ident");
-    EXPECT_TRUE(i->Is<IdentifierExpression>());
-}
-
 TEST_F(IdentifierExpressionTest, Assert_InvalidSymbol) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ast/let.cc b/src/tint/ast/let.cc
index fd16d68..e899f56 100644
--- a/src/tint/ast/let.cc
+++ b/src/tint/ast/let.cc
@@ -26,7 +26,7 @@
          NodeID nid,
          const Source& src,
          const Identifier* n,
-         const ast::Type* ty,
+         Type ty,
          const Expression* init,
          utils::VectorRef<const Attribute*> attrs)
     : Base(pid, nid, src, n, ty, init, std::move(attrs)) {
@@ -44,7 +44,7 @@
 const Let* Let::Clone(CloneContext* ctx) const {
     auto src = ctx->Clone(source);
     auto* n = ctx->Clone(name);
-    auto* ty = ctx->Clone(type);
+    auto ty = ctx->Clone(type);
     auto* init = ctx->Clone(initializer);
     auto attrs = ctx->Clone(attributes);
     return ctx->dst->create<Let>(src, n, ty, init, std::move(attrs));
diff --git a/src/tint/ast/let.h b/src/tint/ast/let.h
index e0848aa..7ff84b2 100644
--- a/src/tint/ast/let.h
+++ b/src/tint/ast/let.h
@@ -41,7 +41,7 @@
         NodeID nid,
         const Source& source,
         const Identifier* name,
-        const ast::Type* type,
+        Type type,
         const Expression* initializer,
         utils::VectorRef<const Attribute*> attributes);
 
diff --git a/src/tint/ast/matrix.cc b/src/tint/ast/matrix.cc
deleted file mode 100644
index 6937127..0000000
--- a/src/tint/ast/matrix.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/matrix.h"
-
-#include "src/tint/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Matrix);
-
-namespace tint::ast {
-
-Matrix::Matrix(ProgramID pid,
-               NodeID nid,
-               const Source& src,
-               const Type* subtype,
-               uint32_t r,
-               uint32_t c)
-    : Base(pid, nid, src), type(subtype), rows(r), columns(c) {
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, subtype, program_id);
-    TINT_ASSERT(AST, rows > 1);
-    TINT_ASSERT(AST, rows < 5);
-    TINT_ASSERT(AST, columns > 1);
-    TINT_ASSERT(AST, columns < 5);
-}
-
-Matrix::Matrix(Matrix&&) = default;
-
-Matrix::~Matrix() = default;
-
-std::string Matrix::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
-    out << "mat" << columns << "x" << rows;
-    if (type) {
-        out << "<" << type->FriendlyName(symbols) << ">";
-    }
-    return out.str();
-}
-
-const Matrix* Matrix::Clone(CloneContext* ctx) const {
-    // Clone arguments outside of create() call to have deterministic ordering
-    auto src = ctx->Clone(source);
-    auto* ty = ctx->Clone(type);
-    return ctx->dst->create<Matrix>(src, ty, rows, columns);
-}
-
-}  // namespace tint::ast
diff --git a/src/tint/ast/matrix.h b/src/tint/ast/matrix.h
deleted file mode 100644
index 00bf658..0000000
--- a/src/tint/ast/matrix.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_AST_MATRIX_H_
-#define SRC_TINT_AST_MATRIX_H_
-
-#include <string>
-
-#include "src/tint/ast/type.h"
-
-namespace tint::ast {
-
-/// A matrix type
-class Matrix final : public Castable<Matrix, Type> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param src the source of this node
-    /// @param subtype the declared type of the matrix components. May be null for
-    ///        matrix initializers, where the element type will be inferred from
-    ///        the initializer arguments
-    /// @param rows the number of rows in the matrix
-    /// @param columns the number of columns in the matrix
-    Matrix(ProgramID pid,
-           NodeID nid,
-           const Source& src,
-           const Type* subtype,
-           uint32_t rows,
-           uint32_t columns);
-    /// Move constructor
-    Matrix(Matrix&&);
-    ~Matrix() override;
-
-    /// @param symbols the program's symbol table
-    /// @returns the name for this type that closely resembles how it would be
-    /// declared in WGSL.
-    std::string FriendlyName(const SymbolTable& symbols) const override;
-
-    /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned type
-    const Matrix* Clone(CloneContext* ctx) const override;
-
-    /// The declared type of the matrix components. May be null for matrix
-    /// initializers, where the element type will be inferred from the initializer
-    /// arguments
-    const Type* const type;
-
-    /// The number of rows in the matrix
-    const uint32_t rows;
-
-    /// The number of columns in the matrix
-    const uint32_t columns;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_AST_MATRIX_H_
diff --git a/src/tint/ast/matrix_test.cc b/src/tint/ast/matrix_test.cc
deleted file mode 100644
index 2199f78..0000000
--- a/src/tint/ast/matrix_test.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/matrix.h"
-#include "src/tint/ast/alias.h"
-#include "src/tint/ast/array.h"
-#include "src/tint/ast/pointer.h"
-#include "src/tint/ast/struct.h"
-#include "src/tint/ast/test_helper.h"
-#include "src/tint/ast/texture.h"
-#include "src/tint/ast/vector.h"
-#include "src/tint/type/access.h"
-
-namespace tint::ast {
-namespace {
-
-using AstMatrixTest = TestHelper;
-
-TEST_F(AstMatrixTest, Creation) {
-    auto* m = create<Matrix>(ty.i32(), 2u, 4u);
-    ASSERT_TRUE(m->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(m->type->As<ast::TypeName>()->name->symbol), "i32");
-    EXPECT_EQ(m->rows, 2u);
-    EXPECT_EQ(m->columns, 4u);
-}
-
-TEST_F(AstMatrixTest, FriendlyName) {
-    auto* m = create<Matrix>(ty.i32(), 3u, 2u);
-    EXPECT_EQ(m->FriendlyName(Symbols()), "mat2x3<i32>");
-}
-
-TEST_F(AstMatrixTest, FriendlyName_WithoutType) {
-    auto* m = create<Matrix>(nullptr, 3u, 2u);
-    EXPECT_EQ(m->FriendlyName(Symbols()), "mat2x3");
-}
-
-}  // namespace
-}  // namespace tint::ast
diff --git a/src/tint/ast/module.h b/src/tint/ast/module.h
index 6ccfaca..a19f2bd 100644
--- a/src/tint/ast/module.h
+++ b/src/tint/ast/module.h
@@ -21,7 +21,6 @@
 #include "src/tint/ast/diagnostic_directive.h"
 #include "src/tint/ast/enable.h"
 #include "src/tint/ast/function.h"
-#include "src/tint/ast/type.h"
 #include "src/tint/utils/vector.h"
 
 namespace tint::ast {
diff --git a/src/tint/ast/multisampled_texture.cc b/src/tint/ast/multisampled_texture.cc
deleted file mode 100644
index 05aea9d..0000000
--- a/src/tint/ast/multisampled_texture.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/multisampled_texture.h"
-
-#include "src/tint/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::MultisampledTexture);
-
-namespace tint::ast {
-
-MultisampledTexture::MultisampledTexture(ProgramID pid,
-                                         NodeID nid,
-                                         const Source& src,
-                                         type::TextureDimension d,
-                                         const Type* ty)
-    : Base(pid, nid, src, d), type(ty) {
-    TINT_ASSERT(AST, type);
-}
-
-MultisampledTexture::MultisampledTexture(MultisampledTexture&&) = default;
-
-MultisampledTexture::~MultisampledTexture() = default;
-
-std::string MultisampledTexture::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
-    out << "texture_multisampled_" << dim << "<" << type->FriendlyName(symbols) << ">";
-    return out.str();
-}
-
-const MultisampledTexture* MultisampledTexture::Clone(CloneContext* ctx) const {
-    // Clone arguments outside of create() call to have deterministic ordering
-    auto src = ctx->Clone(source);
-    auto* ty = ctx->Clone(type);
-    return ctx->dst->create<MultisampledTexture>(src, dim, ty);
-}
-
-}  // namespace tint::ast
diff --git a/src/tint/ast/multisampled_texture.h b/src/tint/ast/multisampled_texture.h
deleted file mode 100644
index dfa192e..0000000
--- a/src/tint/ast/multisampled_texture.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_AST_MULTISAMPLED_TEXTURE_H_
-#define SRC_TINT_AST_MULTISAMPLED_TEXTURE_H_
-
-#include <string>
-
-#include "src/tint/ast/texture.h"
-#include "src/tint/type/texture_dimension.h"
-
-namespace tint::ast {
-
-/// A multisampled texture type.
-class MultisampledTexture final : public Castable<MultisampledTexture, Texture> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param src the source of this node
-    /// @param dim the dimensionality of the texture
-    /// @param type the data type of the multisampled texture
-    MultisampledTexture(ProgramID pid,
-                        NodeID nid,
-                        const Source& src,
-                        type::TextureDimension dim,
-                        const Type* type);
-    /// Move constructor
-    MultisampledTexture(MultisampledTexture&&);
-    ~MultisampledTexture() override;
-
-    /// @param symbols the program's symbol table
-    /// @returns the name for this type that closely resembles how it would be
-    /// declared in WGSL.
-    std::string FriendlyName(const SymbolTable& symbols) const override;
-
-    /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned type
-    const MultisampledTexture* Clone(CloneContext* ctx) const override;
-
-    /// The subtype of the multisampled texture
-    const Type* const type;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_AST_MULTISAMPLED_TEXTURE_H_
diff --git a/src/tint/ast/multisampled_texture_test.cc b/src/tint/ast/multisampled_texture_test.cc
deleted file mode 100644
index 6b8125c..0000000
--- a/src/tint/ast/multisampled_texture_test.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/multisampled_texture.h"
-
-#include "src/tint/ast/alias.h"
-#include "src/tint/ast/array.h"
-#include "src/tint/ast/matrix.h"
-#include "src/tint/ast/pointer.h"
-#include "src/tint/ast/sampled_texture.h"
-#include "src/tint/ast/struct.h"
-#include "src/tint/ast/test_helper.h"
-#include "src/tint/ast/texture.h"
-#include "src/tint/ast/vector.h"
-#include "src/tint/type/access.h"
-
-namespace tint::ast {
-namespace {
-
-using AstMultisampledTextureTest = TestHelper;
-
-TEST_F(AstMultisampledTextureTest, IsTexture) {
-    Texture* t = create<MultisampledTexture>(type::TextureDimension::kCube, ty.f32());
-    EXPECT_TRUE(t->Is<MultisampledTexture>());
-    EXPECT_FALSE(t->Is<SampledTexture>());
-}
-
-TEST_F(AstMultisampledTextureTest, Dim) {
-    auto* s = create<MultisampledTexture>(type::TextureDimension::k3d, ty.f32());
-    EXPECT_EQ(s->dim, type::TextureDimension::k3d);
-}
-
-TEST_F(AstMultisampledTextureTest, Type) {
-    auto* s = create<MultisampledTexture>(type::TextureDimension::k3d, ty.f32());
-    ASSERT_TRUE(s->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(s->type->As<ast::TypeName>()->name->symbol), "f32");
-}
-
-TEST_F(AstMultisampledTextureTest, FriendlyName) {
-    auto* s = create<MultisampledTexture>(type::TextureDimension::k3d, ty.f32());
-    EXPECT_EQ(s->FriendlyName(Symbols()), "texture_multisampled_3d<f32>");
-}
-
-}  // namespace
-}  // namespace tint::ast
diff --git a/src/tint/ast/override.cc b/src/tint/ast/override.cc
index e7bee05..79f0772 100644
--- a/src/tint/ast/override.cc
+++ b/src/tint/ast/override.cc
@@ -26,7 +26,7 @@
                    NodeID nid,
                    const Source& src,
                    const Identifier* n,
-                   const ast::Type* ty,
+                   Type ty,
                    const Expression* init,
                    utils::VectorRef<const Attribute*> attrs)
     : Base(pid, nid, src, n, ty, init, std::move(attrs)) {}
@@ -42,7 +42,7 @@
 const Override* Override::Clone(CloneContext* ctx) const {
     auto src = ctx->Clone(source);
     auto* n = ctx->Clone(name);
-    auto* ty = ctx->Clone(type);
+    auto ty = ctx->Clone(type);
     auto* init = ctx->Clone(initializer);
     auto attrs = ctx->Clone(attributes);
     return ctx->dst->create<Override>(src, n, ty, init, std::move(attrs));
diff --git a/src/tint/ast/override.h b/src/tint/ast/override.h
index c75035e..531d147 100644
--- a/src/tint/ast/override.h
+++ b/src/tint/ast/override.h
@@ -44,7 +44,7 @@
              NodeID nid,
              const Source& source,
              const Identifier* name,
-             const ast::Type* type,
+             Type type,
              const Expression* initializer,
              utils::VectorRef<const Attribute*> attributes);
 
diff --git a/src/tint/ast/parameter.cc b/src/tint/ast/parameter.cc
index adbeb27..a2c6e5b 100644
--- a/src/tint/ast/parameter.cc
+++ b/src/tint/ast/parameter.cc
@@ -26,7 +26,7 @@
                      NodeID nid,
                      const Source& src,
                      const Identifier* n,
-                     const ast::Type* ty,
+                     Type ty,
                      utils::VectorRef<const Attribute*> attrs)
     : Base(pid, nid, src, n, ty, nullptr, std::move(attrs)) {}
 
@@ -41,7 +41,7 @@
 const Parameter* Parameter::Clone(CloneContext* ctx) const {
     auto src = ctx->Clone(source);
     auto* n = ctx->Clone(name);
-    auto* ty = ctx->Clone(type);
+    auto ty = ctx->Clone(type);
     auto attrs = ctx->Clone(attributes);
     return ctx->dst->create<Parameter>(src, n, ty, std::move(attrs));
 }
diff --git a/src/tint/ast/parameter.h b/src/tint/ast/parameter.h
index 79d1856..7ea49ae 100644
--- a/src/tint/ast/parameter.h
+++ b/src/tint/ast/parameter.h
@@ -44,7 +44,7 @@
               NodeID nid,
               const Source& source,
               const Identifier* name,
-              const ast::Type* type,
+              Type type,
               utils::VectorRef<const Attribute*> attributes);
 
     /// Move constructor
diff --git a/src/tint/ast/pointer.cc b/src/tint/ast/pointer.cc
deleted file mode 100644
index 2216872..0000000
--- a/src/tint/ast/pointer.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/pointer.h"
-
-#include "src/tint/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Pointer);
-
-namespace tint::ast {
-
-Pointer::Pointer(ProgramID pid,
-                 NodeID nid,
-                 const Source& src,
-                 const Type* const subtype,
-                 type::AddressSpace addr_space,
-                 type::Access ac)
-    : Base(pid, nid, src), type(subtype), address_space(addr_space), access(ac) {}
-
-std::string Pointer::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
-    out << "ptr<";
-    if (address_space != type::AddressSpace::kNone) {
-        out << address_space << ", ";
-    }
-    out << type->FriendlyName(symbols);
-    if (access != type::Access::kUndefined) {
-        out << ", " << access;
-    }
-    out << ">";
-    return out.str();
-}
-
-Pointer::Pointer(Pointer&&) = default;
-
-Pointer::~Pointer() = default;
-
-const Pointer* Pointer::Clone(CloneContext* ctx) const {
-    // Clone arguments outside of create() call to have deterministic ordering
-    auto src = ctx->Clone(source);
-    auto* ty = ctx->Clone(type);
-    return ctx->dst->create<Pointer>(src, ty, address_space, access);
-}
-
-}  // namespace tint::ast
diff --git a/src/tint/ast/pointer.h b/src/tint/ast/pointer.h
deleted file mode 100644
index dda146a..0000000
--- a/src/tint/ast/pointer.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_AST_POINTER_H_
-#define SRC_TINT_AST_POINTER_H_
-
-#include <string>
-
-#include "src/tint/ast/type.h"
-#include "src/tint/type/access.h"
-#include "src/tint/type/address_space.h"
-
-namespace tint::ast {
-
-/// A pointer type.
-class Pointer final : public Castable<Pointer, Type> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param src the source of this node
-    /// @param subtype the pointee type
-    /// @param address_space the address space of the pointer
-    /// @param access the access control of the pointer
-    Pointer(ProgramID pid,
-            NodeID nid,
-            const Source& src,
-            const Type* const subtype,
-            type::AddressSpace address_space,
-            type::Access access);
-    /// Move constructor
-    Pointer(Pointer&&);
-    ~Pointer() override;
-
-    /// @param symbols the program's symbol table
-    /// @returns the name for this type that closely resembles how it would be
-    /// declared in WGSL.
-    std::string FriendlyName(const SymbolTable& symbols) const override;
-
-    /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned type
-    const Pointer* Clone(CloneContext* ctx) const override;
-
-    /// The pointee type
-    const Type* const type;
-
-    /// The address space of the pointer
-    type::AddressSpace const address_space;
-
-    /// The access control of the pointer
-    type::Access const access;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_AST_POINTER_H_
diff --git a/src/tint/ast/pointer_test.cc b/src/tint/ast/pointer_test.cc
deleted file mode 100644
index ad2ff15..0000000
--- a/src/tint/ast/pointer_test.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/pointer.h"
-
-#include "src/tint/ast/test_helper.h"
-
-namespace tint::ast {
-namespace {
-
-using AstPointerTest = TestHelper;
-
-TEST_F(AstPointerTest, Creation) {
-    auto* p = create<Pointer>(ty.i32(), type::AddressSpace::kStorage, type::Access::kRead);
-    ASSERT_TRUE(p->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(p->type->As<ast::TypeName>()->name->symbol), "i32");
-    EXPECT_EQ(p->address_space, type::AddressSpace::kStorage);
-    EXPECT_EQ(p->access, type::Access::kRead);
-}
-
-TEST_F(AstPointerTest, FriendlyName) {
-    auto* p = create<Pointer>(ty.i32(), type::AddressSpace::kWorkgroup, type::Access::kUndefined);
-    EXPECT_EQ(p->FriendlyName(Symbols()), "ptr<workgroup, i32>");
-}
-
-TEST_F(AstPointerTest, FriendlyNameWithAccess) {
-    auto* p = create<Pointer>(ty.i32(), type::AddressSpace::kStorage, type::Access::kReadWrite);
-    EXPECT_EQ(p->FriendlyName(Symbols()), "ptr<storage, i32, read_write>");
-}
-
-}  // namespace
-}  // namespace tint::ast
diff --git a/src/tint/ast/sampled_texture.cc b/src/tint/ast/sampled_texture.cc
deleted file mode 100644
index cc33962..0000000
--- a/src/tint/ast/sampled_texture.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/sampled_texture.h"
-
-#include "src/tint/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::SampledTexture);
-
-namespace tint::ast {
-
-SampledTexture::SampledTexture(ProgramID pid,
-                               NodeID nid,
-                               const Source& src,
-                               type::TextureDimension d,
-                               const Type* ty)
-    : Base(pid, nid, src, d), type(ty) {
-    TINT_ASSERT(AST, type);
-}
-
-SampledTexture::SampledTexture(SampledTexture&&) = default;
-
-SampledTexture::~SampledTexture() = default;
-
-std::string SampledTexture::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
-    out << "texture_" << dim << "<" << type->FriendlyName(symbols) << ">";
-    return out.str();
-}
-
-const SampledTexture* SampledTexture::Clone(CloneContext* ctx) const {
-    // Clone arguments outside of create() call to have deterministic ordering
-    auto src = ctx->Clone(source);
-    auto* ty = ctx->Clone(type);
-    return ctx->dst->create<SampledTexture>(src, dim, ty);
-}
-
-}  // namespace tint::ast
diff --git a/src/tint/ast/sampled_texture.h b/src/tint/ast/sampled_texture.h
deleted file mode 100644
index 4ad7aa7..0000000
--- a/src/tint/ast/sampled_texture.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_AST_SAMPLED_TEXTURE_H_
-#define SRC_TINT_AST_SAMPLED_TEXTURE_H_
-
-#include <string>
-
-#include "src/tint/ast/texture.h"
-#include "src/tint/type/texture_dimension.h"
-
-namespace tint::ast {
-
-/// A sampled texture type.
-class SampledTexture final : public Castable<SampledTexture, Texture> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param src the source of this node
-    /// @param dim the dimensionality of the texture
-    /// @param type the data type of the sampled texture
-    SampledTexture(ProgramID pid,
-                   NodeID nid,
-                   const Source& src,
-                   type::TextureDimension dim,
-                   const Type* type);
-    /// Move constructor
-    SampledTexture(SampledTexture&&);
-    ~SampledTexture() override;
-
-    /// @param symbols the program's symbol table
-    /// @returns the name for this type that closely resembles how it would be
-    /// declared in WGSL.
-    std::string FriendlyName(const SymbolTable& symbols) const override;
-
-    /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned type
-    const SampledTexture* Clone(CloneContext* ctx) const override;
-
-    /// The subtype of the sampled texture
-    const Type* const type;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_AST_SAMPLED_TEXTURE_H_
diff --git a/src/tint/ast/sampled_texture_test.cc b/src/tint/ast/sampled_texture_test.cc
deleted file mode 100644
index 01d7b65..0000000
--- a/src/tint/ast/sampled_texture_test.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/sampled_texture.h"
-
-#include "src/tint/ast/test_helper.h"
-
-namespace tint::ast {
-namespace {
-
-using AstSampledTextureTest = TestHelper;
-
-TEST_F(AstSampledTextureTest, IsTexture) {
-    Texture* t = create<SampledTexture>(type::TextureDimension::kCube, ty.f32());
-    EXPECT_TRUE(t->Is<SampledTexture>());
-}
-
-TEST_F(AstSampledTextureTest, Dim) {
-    auto* s = create<SampledTexture>(type::TextureDimension::k3d, ty.f32());
-    EXPECT_EQ(s->dim, type::TextureDimension::k3d);
-}
-
-TEST_F(AstSampledTextureTest, Type) {
-    auto* s = create<SampledTexture>(type::TextureDimension::k3d, ty.f32());
-    ASSERT_TRUE(s->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(s->type->As<ast::TypeName>()->name->symbol), "f32");
-}
-
-TEST_F(AstSampledTextureTest, FriendlyName) {
-    auto* s = create<SampledTexture>(type::TextureDimension::k3d, ty.f32());
-    EXPECT_EQ(s->FriendlyName(Symbols()), "texture_3d<f32>");
-}
-
-}  // namespace
-}  // namespace tint::ast
diff --git a/src/tint/ast/struct_member.cc b/src/tint/ast/struct_member.cc
index 8a3d27a..10f278d 100644
--- a/src/tint/ast/struct_member.cc
+++ b/src/tint/ast/struct_member.cc
@@ -24,7 +24,7 @@
                            NodeID nid,
                            const Source& src,
                            const Identifier* n,
-                           const ast::Type* ty,
+                           Type ty,
                            utils::VectorRef<const Attribute*> attrs)
 
     : Base(pid, nid, src), name(n), type(ty), attributes(std::move(attrs)) {
@@ -47,7 +47,7 @@
     // Clone arguments outside of create() call to have deterministic ordering
     auto src = ctx->Clone(source);
     auto n = ctx->Clone(name);
-    auto* ty = ctx->Clone(type);
+    auto ty = ctx->Clone(type);
     auto attrs = ctx->Clone(attributes);
     return ctx->dst->create<StructMember>(src, n, ty, std::move(attrs));
 }
diff --git a/src/tint/ast/struct_member.h b/src/tint/ast/struct_member.h
index 388d404..fba504a 100644
--- a/src/tint/ast/struct_member.h
+++ b/src/tint/ast/struct_member.h
@@ -18,11 +18,11 @@
 #include <utility>
 
 #include "src/tint/ast/attribute.h"
+#include "src/tint/ast/type.h"
 
 // Forward declarations
 namespace tint::ast {
 class Identifier;
-class Type;
 }  // namespace tint::ast
 
 namespace tint::ast {
@@ -41,7 +41,7 @@
                  NodeID nid,
                  const Source& src,
                  const Identifier* name,
-                 const ast::Type* type,
+                 Type type,
                  utils::VectorRef<const Attribute*> attributes);
     /// Move constructor
     StructMember(StructMember&&);
@@ -58,7 +58,7 @@
     const Identifier* const name;
 
     /// The type
-    const ast::Type* const type;
+    const Type type;
 
     /// The attributes
     const utils::Vector<const Attribute*, 4> attributes;
diff --git a/src/tint/ast/struct_member_test.cc b/src/tint/ast/struct_member_test.cc
index d67b379..94a072f 100644
--- a/src/tint/ast/struct_member_test.cc
+++ b/src/tint/ast/struct_member_test.cc
@@ -23,9 +23,8 @@
 
 TEST_F(StructMemberTest, Creation) {
     auto* st = Member("a", ty.i32(), utils::Vector{MemberSize(4_a)});
-    EXPECT_EQ(Symbols().NameFor(st->name->symbol), "a");
-    ASSERT_TRUE(st->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(st->type->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(Symbols(), st->name, "a");
+    ast::CheckIdentifier(Symbols(), st->type, "i32");
     EXPECT_EQ(st->attributes.Length(), 1u);
     EXPECT_TRUE(st->attributes[0]->Is<StructMemberSizeAttribute>());
     EXPECT_EQ(st->source.range.begin.line, 0u);
@@ -37,9 +36,8 @@
 TEST_F(StructMemberTest, CreationWithSource) {
     auto* st = Member(Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 8}}}, "a",
                       ty.i32());
-    EXPECT_EQ(Symbols().NameFor(st->name->symbol), "a");
-    ASSERT_TRUE(st->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(st->type->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(Symbols(), st->name, "a");
+    ast::CheckIdentifier(Symbols(), st->type, "i32");
     EXPECT_EQ(st->attributes.Length(), 0u);
     EXPECT_EQ(st->source.range.begin.line, 27u);
     EXPECT_EQ(st->source.range.begin.column, 4u);
@@ -60,7 +58,7 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.Member("a", nullptr);
+            b.Member("a", ast::Type{});
         },
         "internal compiler error");
 }
diff --git a/src/tint/ast/struct_test.cc b/src/tint/ast/struct_test.cc
index 072a197..cfb4181 100644
--- a/src/tint/ast/struct_test.cc
+++ b/src/tint/ast/struct_test.cc
@@ -15,12 +15,7 @@
 #include "src/tint/ast/struct.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ast/alias.h"
-#include "src/tint/ast/array.h"
-#include "src/tint/ast/matrix.h"
-#include "src/tint/ast/pointer.h"
 #include "src/tint/ast/test_helper.h"
-#include "src/tint/ast/texture.h"
-#include "src/tint/ast/vector.h"
 #include "src/tint/transform/add_block_attribute.h"
 
 namespace tint::ast {
diff --git a/src/tint/ast/templated_identifier.cc b/src/tint/ast/templated_identifier.cc
index 756b183..3a8b545 100644
--- a/src/tint/ast/templated_identifier.cc
+++ b/src/tint/ast/templated_identifier.cc
@@ -26,10 +26,15 @@
                                          NodeID nid,
                                          const Source& src,
                                          const Symbol& sym,
-                                         utils::VectorRef<const ast::Expression*> args)
-    : Base(pid, nid, src, sym), arguments(std::move(args)) {
+                                         utils::VectorRef<const Expression*> args,
+                                         utils::VectorRef<const Attribute*> attrs)
+    : Base(pid, nid, src, sym), arguments(std::move(args)), attributes(std::move(attrs)) {
+    TINT_ASSERT(AST, !arguments.IsEmpty());  // Should have been an Identifier if this fires.
     for (auto* arg : arguments) {
-        TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, arg, program_id);
+        TINT_ASSERT_PROGRAM_IDS_EQUAL(AST, arg, program_id);
+    }
+    for (auto* attr : attributes) {
+        TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
     }
 }
 
@@ -42,7 +47,8 @@
     auto src = ctx->Clone(source);
     auto sym = ctx->Clone(symbol);
     auto args = ctx->Clone(arguments);
-    return ctx->dst->create<TemplatedIdentifier>(src, sym, args);
+    auto attrs = ctx->Clone(attributes);
+    return ctx->dst->create<TemplatedIdentifier>(src, sym, std::move(args), std::move(attrs));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/templated_identifier.h b/src/tint/ast/templated_identifier.h
index 7a49c05..9f4b17b 100644
--- a/src/tint/ast/templated_identifier.h
+++ b/src/tint/ast/templated_identifier.h
@@ -19,6 +19,7 @@
 
 // Forward declarations
 namespace tint::ast {
+class Attribute;
 class Expression;
 }  // namespace tint::ast
 
@@ -33,11 +34,13 @@
     /// @param src the source of this node
     /// @param sym the symbol for the identifier
     /// @param args the template arguments
+    /// @param attrs the identifier attributes
     TemplatedIdentifier(ProgramID pid,
                         NodeID nid,
                         const Source& src,
                         const Symbol& sym,
-                        utils::VectorRef<const Expression*> args);
+                        utils::VectorRef<const Expression*> args,
+                        utils::VectorRef<const Attribute*> attrs);
     /// Move constructor
     TemplatedIdentifier(TemplatedIdentifier&&);
     ~TemplatedIdentifier() override;
@@ -49,6 +52,9 @@
 
     /// The templated arguments
     const utils::Vector<const Expression*, 3> arguments;
+
+    /// Attributes on the identifier
+    const utils::Vector<const Attribute*, 0> attributes;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/test_helper.h b/src/tint/ast/test_helper.h
index 03ecda8..9d7b689 100644
--- a/src/tint/ast/test_helper.h
+++ b/src/tint/ast/test_helper.h
@@ -71,14 +71,13 @@
     static constexpr bool value = true;
 };
 
-/// A testing utility for checking that an Identifier and any optional templated arguments match the
-/// expected values.
+/// A testing utility for checking that an Identifier matches the expected values.
 /// @param symbols the symbol table
 /// @param got the identifier
 /// @param expected the expected identifier name
 template <typename... ARGS>
 void CheckIdentifier(const SymbolTable& symbols, const Identifier* got, std::string_view expected) {
-    EXPECT_FALSE(got->Is<ast::TemplatedIdentifier>());
+    EXPECT_FALSE(got->Is<TemplatedIdentifier>());
     EXPECT_EQ(symbols.NameFor(got->symbol), expected);
 }
 
@@ -92,8 +91,8 @@
                      const Identifier* ident,
                      const TemplatedIdentifierMatcher<ARGS...>& expected) {
     EXPECT_EQ(symbols.NameFor(ident->symbol), expected.name);
-    ASSERT_TRUE(ident->Is<ast::TemplatedIdentifier>());
-    auto* got = ident->As<ast::TemplatedIdentifier>();
+    ASSERT_TRUE(ident->Is<TemplatedIdentifier>());
+    auto* got = ident->As<TemplatedIdentifier>();
     ASSERT_EQ(got->arguments.Length(), std::tuple_size_v<decltype(expected.args)>);
 
     size_t arg_idx = 0;
@@ -103,8 +102,7 @@
         using T = std::decay_t<decltype(expected_arg)>;
         if constexpr (traits::IsStringLike<T>) {
             ASSERT_TRUE(got_arg->Is<IdentifierExpression>());
-            ast::CheckIdentifier(symbols, got_arg->As<IdentifierExpression>()->identifier,
-                                 expected_arg);
+            CheckIdentifier(symbols, got_arg->As<IdentifierExpression>()->identifier, expected_arg);
         } else if constexpr (IsTemplatedIdentifierMatcher<T>::value) {
             ASSERT_TRUE(got_arg->Is<IdentifierExpression>());
             auto* got_ident = got_arg->As<IdentifierExpression>()->identifier;
@@ -150,6 +148,33 @@
     std::apply([&](auto&&... args) { ((check_arg(args)), ...); }, expected.args);
 }
 
+/// A testing utility for checking that an IdentifierExpression matches the expected values.
+/// @param symbols the symbol table
+/// @param expr the IdentifierExpression
+/// @param expected the expected identifier name
+template <typename... ARGS>
+void CheckIdentifier(const SymbolTable& symbols,
+                     const Expression* expr,
+                     std::string_view expected) {
+    auto* expr_ident = expr->As<IdentifierExpression>();
+    ASSERT_NE(expr_ident, nullptr) << "expression is not a IdentifierExpression";
+    CheckIdentifier(symbols, expr_ident->identifier, expected);
+}
+
+/// A testing utility for checking that an IdentifierExpression matches the expected name and
+/// template arguments.
+/// @param symbols the symbol table
+/// @param expr the IdentifierExpression
+/// @param expected the expected identifier name and arguments
+template <typename... ARGS>
+void CheckIdentifier(const SymbolTable& symbols,
+                     const Expression* expr,
+                     const TemplatedIdentifierMatcher<ARGS...>& expected) {
+    auto* expr_ident = expr->As<IdentifierExpression>();
+    ASSERT_NE(expr_ident, nullptr) << "expression is not a IdentifierExpression";
+    CheckIdentifier(symbols, expr_ident->identifier, expected);
+}
+
 }  // namespace tint::ast
 
 #endif  // SRC_TINT_AST_TEST_HELPER_H_
diff --git a/src/tint/ast/texture.cc b/src/tint/ast/texture.cc
deleted file mode 100644
index bcfbcb5..0000000
--- a/src/tint/ast/texture.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/texture.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Texture);
-
-namespace tint::ast {
-
-bool IsTextureArray(type::TextureDimension dim) {
-    switch (dim) {
-        case type::TextureDimension::k2dArray:
-        case type::TextureDimension::kCubeArray:
-            return true;
-        case type::TextureDimension::k2d:
-        case type::TextureDimension::kNone:
-        case type::TextureDimension::k1d:
-        case type::TextureDimension::k3d:
-        case type::TextureDimension::kCube:
-            return false;
-    }
-    return false;
-}
-
-int NumCoordinateAxes(type::TextureDimension dim) {
-    switch (dim) {
-        case type::TextureDimension::kNone:
-            return 0;
-        case type::TextureDimension::k1d:
-            return 1;
-        case type::TextureDimension::k2d:
-        case type::TextureDimension::k2dArray:
-            return 2;
-        case type::TextureDimension::k3d:
-        case type::TextureDimension::kCube:
-        case type::TextureDimension::kCubeArray:
-            return 3;
-    }
-    return 0;
-}
-
-Texture::Texture(ProgramID pid, NodeID nid, const Source& src, type::TextureDimension d)
-    : Base(pid, nid, src), dim(d) {}
-
-Texture::Texture(Texture&&) = default;
-
-Texture::~Texture() = default;
-
-}  // namespace tint::ast
diff --git a/src/tint/ast/texture.h b/src/tint/ast/texture.h
deleted file mode 100644
index be78ba9..0000000
--- a/src/tint/ast/texture.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_AST_TEXTURE_H_
-#define SRC_TINT_AST_TEXTURE_H_
-
-#include "src/tint/ast/type.h"
-#include "src/tint/type/texture_dimension.h"
-
-namespace tint::ast {
-
-/// @param dim the type::TextureDimension to query
-/// @return true if the given type::TextureDimension is an array texture
-bool IsTextureArray(type::TextureDimension dim);
-
-/// Returns the number of axes in the coordinate used for accessing
-/// the texture, where an access is one of: sampling, fetching, load,
-/// or store.
-///  None -> 0
-///  1D -> 1
-///  2D, 2DArray -> 2
-///  3D, Cube, CubeArray -> 3
-/// Note: To sample a cube texture, the coordinate has 3 dimensions,
-/// but textureDimensions on a cube or cube array returns a 2-element
-/// size, representing the (x,y) size of each cube face, in texels.
-/// @param dim the type::TextureDimension to query
-/// @return number of dimensions in a coordinate for the dimensionality
-int NumCoordinateAxes(type::TextureDimension dim);
-
-/// A texture type.
-class Texture : public Castable<Texture, Type> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param src the source of this node
-    /// @param dim the dimensionality of the texture
-    Texture(ProgramID pid, NodeID nid, const Source& src, type::TextureDimension dim);
-    /// Move constructor
-    Texture(Texture&&);
-    ~Texture() override;
-
-    /// The texture dimension
-    const type::TextureDimension dim;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_AST_TEXTURE_H_
diff --git a/src/tint/ast/texture_test.cc b/src/tint/ast/texture_test.cc
deleted file mode 100644
index e1cd4f6..0000000
--- a/src/tint/ast/texture_test.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/texture.h"
-
-#include "src/tint/ast/alias.h"
-#include "src/tint/ast/array.h"
-#include "src/tint/ast/matrix.h"
-#include "src/tint/ast/pointer.h"
-#include "src/tint/ast/struct.h"
-#include "src/tint/ast/test_helper.h"
-#include "src/tint/ast/vector.h"
-
-namespace tint::ast {
-namespace {
-
-using AstTextureTypeTest = TestHelper;
-
-TEST_F(AstTextureTypeTest, IsTextureArray) {
-    EXPECT_EQ(false, IsTextureArray(type::TextureDimension::kNone));
-    EXPECT_EQ(false, IsTextureArray(type::TextureDimension::k1d));
-    EXPECT_EQ(false, IsTextureArray(type::TextureDimension::k2d));
-    EXPECT_EQ(true, IsTextureArray(type::TextureDimension::k2dArray));
-    EXPECT_EQ(false, IsTextureArray(type::TextureDimension::k3d));
-    EXPECT_EQ(false, IsTextureArray(type::TextureDimension::kCube));
-    EXPECT_EQ(true, IsTextureArray(type::TextureDimension::kCubeArray));
-}
-
-TEST_F(AstTextureTypeTest, NumCoordinateAxes) {
-    EXPECT_EQ(0, NumCoordinateAxes(type::TextureDimension::kNone));
-    EXPECT_EQ(1, NumCoordinateAxes(type::TextureDimension::k1d));
-    EXPECT_EQ(2, NumCoordinateAxes(type::TextureDimension::k2d));
-    EXPECT_EQ(2, NumCoordinateAxes(type::TextureDimension::k2dArray));
-    EXPECT_EQ(3, NumCoordinateAxes(type::TextureDimension::k3d));
-    EXPECT_EQ(3, NumCoordinateAxes(type::TextureDimension::kCube));
-    EXPECT_EQ(3, NumCoordinateAxes(type::TextureDimension::kCubeArray));
-}
-
-}  // namespace
-}  // namespace tint::ast
diff --git a/src/tint/ast/traverse_expressions.h b/src/tint/ast/traverse_expressions.h
index ce7ea52..629e010 100644
--- a/src/tint/ast/traverse_expressions.h
+++ b/src/tint/ast/traverse_expressions.h
@@ -148,7 +148,8 @@
                                                PhonyExpression>()))) {
                     return true;  // Leaf expression
                 }
-                TINT_ICE(AST, diags) << "unhandled expression type: " << expr->TypeInfo().name;
+                TINT_ICE(AST, diags)
+                    << "unhandled expression type: " << (expr ? expr->TypeInfo().name : "<null>");
                 return false;
             });
         if (!ok) {
diff --git a/src/tint/ast/type.cc b/src/tint/ast/type.cc
index 6408596..7267c80 100644
--- a/src/tint/ast/type.cc
+++ b/src/tint/ast/type.cc
@@ -1,4 +1,4 @@
-// Copyright 2020 The Tint Authors.
+// Copyright 2023 The Tint Authors.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -13,22 +13,12 @@
 // limitations under the License.
 
 #include "src/tint/ast/type.h"
+#include "src/tint/ast/identifier_expression.h"
 
-#include "src/tint/ast/alias.h"
-#include "src/tint/ast/matrix.h"
-#include "src/tint/ast/pointer.h"
-#include "src/tint/ast/texture.h"
-#include "src/tint/ast/vector.h"
-#include "src/tint/symbol_table.h"
+namespace tint {
 
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Type);
+ProgramID ProgramIDOf(ast::Type type) {
+    return ProgramIDOf(type.expr);
+}
 
-namespace tint::ast {
-
-Type::Type(ProgramID pid, NodeID nid, const Source& src) : Base(pid, nid, src) {}
-
-Type::Type(Type&&) = default;
-
-Type::~Type() = default;
-
-}  // namespace tint::ast
+}  // namespace tint
diff --git a/src/tint/ast/type.h b/src/tint/ast/type.h
index 4f1f276..e5d626c 100644
--- a/src/tint/ast/type.h
+++ b/src/tint/ast/type.h
@@ -1,4 +1,4 @@
-// Copyright 2020 The Tint Authors.
+// Copyright 2023 The Tint Authors.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,38 +15,38 @@
 #ifndef SRC_TINT_AST_TYPE_H_
 #define SRC_TINT_AST_TYPE_H_
 
-#include <string>
-
-#include "src/tint/ast/node.h"
-#include "src/tint/clone_context.h"
+#include "src/tint/program_id.h"
 
 // Forward declarations
-namespace tint {
-class ProgramBuilder;
-class SymbolTable;
-}  // namespace tint
+namespace tint::ast {
+class IdentifierExpression;
+}  // namespace tint::ast
 
 namespace tint::ast {
-/// Base class for a type in the system
-class Type : public Castable<Type, Node> {
-  public:
-    /// Move constructor
-    Type(Type&&);
-    ~Type() override;
 
-    /// @param symbols the program's symbol table
-    /// @returns the name for this type that closely resembles how it would be
-    /// declared in WGSL.
-    virtual std::string FriendlyName(const SymbolTable& symbols) const = 0;
+/// Type is a thin wrapper around an IdentifierExpression, to help statically disambiguate known
+/// type expressions from other expressions.
+struct Type {
+    /// The type expression
+    const IdentifierExpression* expr = nullptr;
 
-  protected:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param src the source of this node
-    Type(ProgramID pid, NodeID nid, const Source& src);
+    /// Indirection operator for accessing the type's expression
+    /// @return #expr
+    const IdentifierExpression* operator->() const { return expr; }
+
+    /// Implicit conversion operator to the type's expression
+    /// @return #expr
+    operator const IdentifierExpression*() const { return expr; }
 };
 
 }  // namespace tint::ast
 
+namespace tint {
+
+/// @param type an AST type
+/// @returns the ProgramID of the given AST type.
+ProgramID ProgramIDOf(ast::Type type);
+
+}  // namespace tint
+
 #endif  // SRC_TINT_AST_TYPE_H_
diff --git a/src/tint/ast/type_name.cc b/src/tint/ast/type_name.cc
deleted file mode 100644
index 5a02dd9..0000000
--- a/src/tint/ast/type_name.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/type_name.h"
-
-#include "src/tint/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::TypeName);
-
-namespace tint::ast {
-
-TypeName::TypeName(ProgramID pid, NodeID nid, const Source& src, const Identifier* n)
-    : Base(pid, nid, src), name(n) {}
-
-TypeName::~TypeName() = default;
-
-TypeName::TypeName(TypeName&&) = default;
-
-std::string TypeName::FriendlyName(const SymbolTable& symbols) const {
-    return symbols.NameFor(name->symbol);
-}
-
-const TypeName* TypeName::Clone(CloneContext* ctx) const {
-    auto src = ctx->Clone(source);
-    auto n = ctx->Clone(name);
-    return ctx->dst->create<TypeName>(src, n);
-}
-
-}  // namespace tint::ast
diff --git a/src/tint/ast/type_name.h b/src/tint/ast/type_name.h
deleted file mode 100644
index 2c4025c..0000000
--- a/src/tint/ast/type_name.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_AST_TYPE_NAME_H_
-#define SRC_TINT_AST_TYPE_NAME_H_
-
-#include <string>
-
-#include "src/tint/ast/type.h"
-
-// Forward declarations
-namespace tint::ast {
-class Identifier;
-}  // namespace tint::ast
-
-namespace tint::ast {
-
-/// A named type (i.e. struct or alias)
-class TypeName final : public Castable<TypeName, Type> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param src the source of this node
-    /// @param name the type name
-    TypeName(ProgramID pid, NodeID nid, const Source& src, const Identifier* name);
-    /// Move constructor
-    TypeName(TypeName&&);
-    /// Destructor
-    ~TypeName() override;
-
-    /// @param symbols the program's symbol table
-    /// @returns the name for this type that closely resembles how it would be
-    /// declared in WGSL.
-    std::string FriendlyName(const SymbolTable& symbols) const override;
-
-    /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned type
-    const TypeName* Clone(CloneContext* ctx) const override;
-
-    /// The type name
-    Identifier const* const name;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_AST_TYPE_NAME_H_
diff --git a/src/tint/ast/type_name_test.cc b/src/tint/ast/type_name_test.cc
deleted file mode 100644
index f5fe496..0000000
--- a/src/tint/ast/type_name_test.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2023 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gtest/gtest-spi.h"
-
-#include "src/tint/ast/test_helper.h"
-
-namespace tint::ast {
-namespace {
-
-using namespace tint::number_suffixes;  // NOLINT
-using TypeNameTest = TestHelper;
-
-TEST_F(TypeNameTest, Creation_NonTemplated) {
-    auto* t = ty("ty");
-    ASSERT_NE(t->name, nullptr);
-    EXPECT_EQ(t->name->symbol, Symbols().Get("ty"));
-}
-
-TEST_F(TypeNameTest, Creation_Templated) {
-    auto* t = ty("ty", 1_a, 2._a, false);
-    auto* name = As<ast::TemplatedIdentifier>(t->name);
-    ASSERT_NE(name, nullptr);
-    EXPECT_EQ(name->symbol, Symbols().Get("ty"));
-    ASSERT_EQ(name->arguments.Length(), 3u);
-    EXPECT_TRUE(name->arguments[0]->Is<ast::IntLiteralExpression>());
-    EXPECT_TRUE(name->arguments[1]->Is<ast::FloatLiteralExpression>());
-    EXPECT_TRUE(name->arguments[2]->Is<ast::BoolLiteralExpression>());
-}
-
-TEST_F(TypeNameTest, Creation_WithSource) {
-    auto* t = ty(Source{{20, 2}}, "ty");
-    ASSERT_NE(t->name, nullptr);
-    EXPECT_EQ(t->name->symbol, Symbols().Get("ty"));
-
-    auto src = t->source;
-    EXPECT_EQ(src.range.begin.line, 20u);
-    EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(TypeNameTest, Assert_InvalidSymbol) {
-    EXPECT_FATAL_FAILURE(
-        {
-            ProgramBuilder b;
-            b.ty("");
-        },
-        "internal compiler error");
-}
-
-TEST_F(TypeNameTest, Assert_DifferentProgramID_Symbol) {
-    EXPECT_FATAL_FAILURE(
-        {
-            ProgramBuilder b1;
-            ProgramBuilder b2;
-            b1.ty(b2.Sym("b2"));
-        },
-        "internal compiler error");
-}
-
-}  // namespace
-}  // namespace tint::ast
diff --git a/src/tint/ast/var.cc b/src/tint/ast/var.cc
index 87621ae..b748381 100644
--- a/src/tint/ast/var.cc
+++ b/src/tint/ast/var.cc
@@ -24,7 +24,7 @@
          NodeID nid,
          const Source& src,
          const Identifier* n,
-         const ast::Type* ty,
+         Type ty,
          type::AddressSpace address_space,
          type::Access access,
          const Expression* init,
@@ -44,7 +44,7 @@
 const Var* Var::Clone(CloneContext* ctx) const {
     auto src = ctx->Clone(source);
     auto* n = ctx->Clone(name);
-    auto* ty = ctx->Clone(type);
+    auto ty = ctx->Clone(type);
     auto* init = ctx->Clone(initializer);
     auto attrs = ctx->Clone(attributes);
     return ctx->dst->create<Var>(src, n, ty, declared_address_space, declared_access, init,
diff --git a/src/tint/ast/var.h b/src/tint/ast/var.h
index 977b799..e3537d1 100644
--- a/src/tint/ast/var.h
+++ b/src/tint/ast/var.h
@@ -55,7 +55,7 @@
         NodeID nid,
         const Source& source,
         const Identifier* name,
-        const ast::Type* type,
+        Type type,
         type::AddressSpace declared_address_space,
         type::Access declared_access,
         const Expression* initializer,
diff --git a/src/tint/ast/variable.cc b/src/tint/ast/variable.cc
index 1049eff..2aa60bf 100644
--- a/src/tint/ast/variable.cc
+++ b/src/tint/ast/variable.cc
@@ -25,7 +25,7 @@
                    NodeID nid,
                    const Source& src,
                    const Identifier* n,
-                   const ast::Type* ty,
+                   Type ty,
                    const Expression* init,
                    utils::VectorRef<const Attribute*> attrs)
     : Base(pid, nid, src), name(n), type(ty), initializer(init), attributes(std::move(attrs)) {
diff --git a/src/tint/ast/variable.h b/src/tint/ast/variable.h
index d1797fe..7663e4f 100644
--- a/src/tint/ast/variable.h
+++ b/src/tint/ast/variable.h
@@ -22,6 +22,7 @@
 #include "src/tint/ast/binding_attribute.h"
 #include "src/tint/ast/expression.h"
 #include "src/tint/ast/group_attribute.h"
+#include "src/tint/ast/type.h"
 #include "src/tint/type/access.h"
 #include "src/tint/type/address_space.h"
 
@@ -29,7 +30,6 @@
 namespace tint::ast {
 class Identifier;
 class LocationAttribute;
-class Type;
 }  // namespace tint::ast
 
 namespace tint::ast {
@@ -54,7 +54,7 @@
              NodeID nid,
              const Source& src,
              const Identifier* name,
-             const ast::Type* type,
+             Type type,
              const Expression* initializer,
              utils::VectorRef<const Attribute*> attributes);
 
@@ -80,7 +80,7 @@
     /// The declared variable type. This is null if the type is inferred, e.g.:
     ///   let f = 1.0;
     ///   var i = 1;
-    const ast::Type* const type;
+    const Type type;
 
     /// The initializer expression or nullptr if none set
     const Expression* const initializer;
diff --git a/src/tint/ast/variable_test.cc b/src/tint/ast/variable_test.cc
index 29dc754..ff97b2f 100644
--- a/src/tint/ast/variable_test.cc
+++ b/src/tint/ast/variable_test.cc
@@ -27,10 +27,9 @@
 TEST_F(VariableTest, Creation) {
     auto* v = Var("my_var", ty.i32(), type::AddressSpace::kFunction);
 
-    EXPECT_EQ(Symbols().NameFor(v->name->symbol), "my_var");
+    CheckIdentifier(Symbols(), v->name, "my_var");
     EXPECT_EQ(v->declared_address_space, type::AddressSpace::kFunction);
-    ASSERT_TRUE(v->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(v->type->As<ast::TypeName>()->name->symbol), "i32");
+    CheckIdentifier(Symbols(), v->type, "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,10 +40,9 @@
     auto* v = Var(Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 5}}}, "i",
                   ty.f32(), type::AddressSpace::kPrivate, utils::Empty);
 
-    EXPECT_EQ(Symbols().NameFor(v->name->symbol), "i");
+    CheckIdentifier(Symbols(), v->name, "i");
     EXPECT_EQ(v->declared_address_space, type::AddressSpace::kPrivate);
-    ASSERT_TRUE(v->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(v->type->As<ast::TypeName>()->name->symbol), "f32");
+    CheckIdentifier(Symbols(), v->type, "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,10 +53,9 @@
     auto* v = Var(Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 7}}}, "a_var",
                   ty.i32(), type::AddressSpace::kWorkgroup, utils::Empty);
 
-    EXPECT_EQ(Symbols().NameFor(v->name->symbol), "a_var");
+    CheckIdentifier(Symbols(), v->name, "a_var");
     EXPECT_EQ(v->declared_address_space, type::AddressSpace::kWorkgroup);
-    ASSERT_TRUE(v->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(v->type->As<ast::TypeName>()->name->symbol), "i32");
+    CheckIdentifier(Symbols(), v->type, "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/tint/ast/vector.cc b/src/tint/ast/vector.cc
deleted file mode 100644
index d49da33..0000000
--- a/src/tint/ast/vector.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/vector.h"
-
-#include "src/tint/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::Vector);
-
-namespace tint::ast {
-
-Vector::Vector(ProgramID pid, NodeID nid, Source const& src, const Type* subtype, uint32_t w)
-    : Base(pid, nid, src), type(subtype), width(w) {
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, subtype, program_id);
-    TINT_ASSERT(AST, width > 1);
-    TINT_ASSERT(AST, width < 5);
-}
-
-Vector::Vector(Vector&&) = default;
-
-Vector::~Vector() = default;
-
-std::string Vector::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
-    out << "vec" << width;
-    if (type) {
-        out << "<" << type->FriendlyName(symbols) << ">";
-    }
-    return out.str();
-}
-
-const Vector* Vector::Clone(CloneContext* ctx) const {
-    // Clone arguments outside of create() call to have deterministic ordering
-    auto src = ctx->Clone(source);
-    auto* ty = ctx->Clone(type);
-    return ctx->dst->create<Vector>(src, ty, width);
-}
-
-}  // namespace tint::ast
diff --git a/src/tint/ast/vector.h b/src/tint/ast/vector.h
deleted file mode 100644
index 0f24782..0000000
--- a/src/tint/ast/vector.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_AST_VECTOR_H_
-#define SRC_TINT_AST_VECTOR_H_
-
-#include <string>
-
-#include "src/tint/ast/type.h"
-
-namespace tint::ast {
-
-/// A vector type.
-class Vector final : public Castable<Vector, Type> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param src the source of this node
-    /// @param subtype the declared type of the vector components. May be null
-    ///        for vector initializers, where the element type will be inferred
-    ///        from the initializer arguments
-    /// @param width the number of elements in the vector
-    Vector(ProgramID pid, NodeID nid, Source const& src, const Type* subtype, uint32_t width);
-    /// Move constructor
-    Vector(Vector&&);
-    ~Vector() override;
-
-    /// @param symbols the program's symbol table
-    /// @returns the name for this type that closely resembles how it would be
-    /// declared in WGSL.
-    std::string FriendlyName(const SymbolTable& symbols) const override;
-
-    /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned type
-    const Vector* Clone(CloneContext* ctx) const override;
-
-    /// The declared type of the vector components. May be null for vector
-    /// initializers, where the element type will be inferred from the initializer
-    /// arguments
-    const Type* const type;
-
-    /// The number of elements in the vector
-    const uint32_t width;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_AST_VECTOR_H_
diff --git a/src/tint/ast/vector_test.cc b/src/tint/ast/vector_test.cc
deleted file mode 100644
index 81a0075..0000000
--- a/src/tint/ast/vector_test.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/vector.h"
-
-#include "src/tint/ast/test_helper.h"
-
-namespace tint::ast {
-namespace {
-
-using AstVectorTest = TestHelper;
-
-TEST_F(AstVectorTest, Creation) {
-    auto* v = create<Vector>(ty.i32(), 2u);
-    ASSERT_TRUE(v->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(v->type->As<ast::TypeName>()->name->symbol), "i32");
-    EXPECT_EQ(v->width, 2u);
-}
-
-TEST_F(AstVectorTest, FriendlyName) {
-    auto* v = create<Vector>(ty.f32(), 3u);
-    EXPECT_EQ(v->FriendlyName(Symbols()), "vec3<f32>");
-}
-
-}  // namespace
-}  // namespace tint::ast
diff --git a/src/tint/clone_context.cc b/src/tint/clone_context.cc
index fe94a14..83e6d6c 100644
--- a/src/tint/clone_context.cc
+++ b/src/tint/clone_context.cc
@@ -66,6 +66,10 @@
     return out;
 }
 
+ast::Type CloneContext::Clone(const ast::Type& ty) {
+    return {Clone(ty.expr)};
+}
+
 const tint::Cloneable* CloneContext::CloneCloneable(const Cloneable* object) {
     // If the input is nullptr, there's nothing to clone - just return nullptr.
     if (object == nullptr) {
diff --git a/src/tint/clone_context.h b/src/tint/clone_context.h
index 1ec3422..188ee25 100644
--- a/src/tint/clone_context.h
+++ b/src/tint/clone_context.h
@@ -40,6 +40,7 @@
 namespace tint::ast {
 class FunctionList;
 class Node;
+struct Type;
 }  // namespace tint::ast
 
 namespace tint {
@@ -142,6 +143,11 @@
         return CheckedCast<T>(c);
     }
 
+    /// Clones the ast::Type `ty` into the ProgramBuilder #dst
+    /// @param ty the AST type.
+    /// @return the cloned node
+    ast::Type Clone(const ast::Type& ty);
+
     /// Clones the Source `s` into #dst
     /// TODO(bclayton) - Currently this 'clone' is a shallow copy. If/when
     /// `Source.File`s are owned by the Program this should make a copy of the
diff --git a/src/tint/inspector/inspector_test.cc b/src/tint/inspector/inspector_test.cc
index a0263f3..fe4cba1 100644
--- a/src/tint/inspector/inspector_test.cc
+++ b/src/tint/inspector/inspector_test.cc
@@ -286,7 +286,7 @@
     ComponentType component;
     CompositionType composition;
     std::tie(component, composition) = GetParam();
-    std::function<const ast::Type*()> tint_type = GetTypeFunction(component, composition);
+    std::function<ast::Type()> tint_type = GetTypeFunction(component, composition);
 
     if (component == ComponentType::kF16) {
         Enable(ast::Extension::kF16);
@@ -1785,14 +1785,14 @@
                                                 MemberInfo{0, ty.i32()},
                                             });
 
-    auto* s_texture_type = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
+    auto s_texture_type = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
     AddResource("s_texture", s_texture_type, 2, 0);
     AddSampler("s_var", 3, 0);
     AddGlobalVariable("s_coords", ty.f32());
     MakeSamplerReferenceBodyFunction("s_func", "s_texture", "s_var", "s_coords", ty.f32(),
                                      utils::Empty);
 
-    auto* cs_depth_texture_type = ty.depth_texture(type::TextureDimension::k2d);
+    auto cs_depth_texture_type = ty.depth_texture(type::TextureDimension::k2d);
     AddResource("cs_texture", cs_depth_texture_type, 3, 1);
     AddComparisonSampler("cs_var", 3, 2);
     AddGlobalVariable("cs_coords", ty.vec2<f32>());
@@ -1800,14 +1800,14 @@
     MakeComparisonSamplerReferenceBodyFunction("cs_func", "cs_texture", "cs_var", "cs_coords",
                                                "cs_depth", ty.f32(), utils::Empty);
 
-    auto* depth_ms_texture_type = ty.depth_multisampled_texture(type::TextureDimension::k2d);
+    auto depth_ms_texture_type = ty.depth_multisampled_texture(type::TextureDimension::k2d);
     AddResource("depth_ms_texture", depth_ms_texture_type, 3, 3);
     Func("depth_ms_func", utils::Empty, ty.void_(),
          utils::Vector{
              Ignore("depth_ms_texture"),
          });
 
-    auto* st_type =
+    auto st_type =
         MakeStorageTextureTypes(type::TextureDimension::k2d, type::TexelFormat::kR32Uint);
     AddStorageTexture("st_var", st_type, 4, 0);
     MakeStorageTextureBodyFunction("st_func", "st_var", ty.vec2<u32>(), utils::Empty);
@@ -2586,7 +2586,7 @@
 }
 
 TEST_F(InspectorGetSamplerResourceBindingsTest, Simple) {
-    auto* sampled_texture_type = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
+    auto sampled_texture_type = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
     AddResource("foo_texture", sampled_texture_type, 0, 0);
     AddSampler("foo_sampler", 0, 1);
     AddGlobalVariable("foo_coords", ty.f32());
@@ -2621,7 +2621,7 @@
 }
 
 TEST_F(InspectorGetSamplerResourceBindingsTest, InFunction) {
-    auto* sampled_texture_type = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
+    auto sampled_texture_type = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
     AddResource("foo_texture", sampled_texture_type, 0, 0);
     AddSampler("foo_sampler", 0, 1);
     AddGlobalVariable("foo_coords", ty.f32());
@@ -2646,7 +2646,7 @@
 }
 
 TEST_F(InspectorGetSamplerResourceBindingsTest, UnknownEntryPoint) {
-    auto* sampled_texture_type = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
+    auto sampled_texture_type = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
     AddResource("foo_texture", sampled_texture_type, 0, 0);
     AddSampler("foo_sampler", 0, 1);
     AddGlobalVariable("foo_coords", ty.f32());
@@ -2663,7 +2663,7 @@
 }
 
 TEST_F(InspectorGetSamplerResourceBindingsTest, SkipsComparisonSamplers) {
-    auto* depth_texture_type = ty.depth_texture(type::TextureDimension::k2d);
+    auto depth_texture_type = ty.depth_texture(type::TextureDimension::k2d);
     AddResource("foo_texture", depth_texture_type, 0, 0);
     AddComparisonSampler("foo_sampler", 0, 1);
     AddGlobalVariable("foo_coords", ty.vec2<f32>());
@@ -2684,7 +2684,7 @@
 }
 
 TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, Simple) {
-    auto* depth_texture_type = ty.depth_texture(type::TextureDimension::k2d);
+    auto depth_texture_type = ty.depth_texture(type::TextureDimension::k2d);
     AddResource("foo_texture", depth_texture_type, 0, 0);
     AddComparisonSampler("foo_sampler", 0, 1);
     AddGlobalVariable("foo_coords", ty.vec2<f32>());
@@ -2721,7 +2721,7 @@
 }
 
 TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, InFunction) {
-    auto* depth_texture_type = ty.depth_texture(type::TextureDimension::k2d);
+    auto depth_texture_type = ty.depth_texture(type::TextureDimension::k2d);
     AddResource("foo_texture", depth_texture_type, 0, 0);
     AddComparisonSampler("foo_sampler", 0, 1);
     AddGlobalVariable("foo_coords", ty.vec2<f32>());
@@ -2747,7 +2747,7 @@
 }
 
 TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, UnknownEntryPoint) {
-    auto* depth_texture_type = ty.depth_texture(type::TextureDimension::k2d);
+    auto depth_texture_type = ty.depth_texture(type::TextureDimension::k2d);
     AddResource("foo_texture", depth_texture_type, 0, 0);
     AddComparisonSampler("foo_sampler", 0, 1);
     AddGlobalVariable("foo_coords", ty.vec2<f32>());
@@ -2766,7 +2766,7 @@
 }
 
 TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, SkipsSamplers) {
-    auto* sampled_texture_type = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
+    auto sampled_texture_type = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
     AddResource("foo_texture", sampled_texture_type, 0, 0);
     AddSampler("foo_sampler", 0, 1);
     AddGlobalVariable("foo_coords", ty.f32());
@@ -2798,11 +2798,11 @@
 }
 
 TEST_P(InspectorGetSampledTextureResourceBindingsTestWithParam, textureSample) {
-    auto* sampled_texture_type =
+    ast::Type sampled_texture_type =
         ty.sampled_texture(GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
     AddResource("foo_texture", sampled_texture_type, 0, 0);
     AddSampler("foo_sampler", 0, 1);
-    auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
+    ast::Type coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
     AddGlobalVariable("foo_coords", coord_type);
 
     MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", "foo_coords",
@@ -2847,11 +2847,11 @@
                                                 inspector::ResourceBinding::SampledKind::kFloat}));
 
 TEST_P(InspectorGetSampledArrayTextureResourceBindingsTestWithParam, textureSample) {
-    auto* sampled_texture_type =
+    ast::Type sampled_texture_type =
         ty.sampled_texture(GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
     AddResource("foo_texture", sampled_texture_type, 0, 0);
     AddSampler("foo_sampler", 0, 1);
-    auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
+    ast::Type coord_type = GetCoordsType(GetParam().type_dim, ty.f32());
     AddGlobalVariable("foo_coords", coord_type);
     AddGlobalVariable("foo_array_index", ty.i32());
 
@@ -2886,10 +2886,10 @@
                                     inspector::ResourceBinding::SampledKind::kFloat}));
 
 TEST_P(InspectorGetMultisampledTextureResourceBindingsTestWithParam, textureLoad) {
-    auto* multisampled_texture_type =
+    ast::Type multisampled_texture_type =
         ty.multisampled_texture(GetParam().type_dim, GetBaseType(GetParam().sampled_kind));
     AddResource("foo_texture", multisampled_texture_type, 0, 0);
-    auto* coord_type = GetCoordsType(GetParam().type_dim, ty.i32());
+    ast::Type coord_type = GetCoordsType(GetParam().type_dim, ty.i32());
     AddGlobalVariable("foo_coords", coord_type);
     AddGlobalVariable("foo_sample_index", ty.i32());
 
@@ -2973,10 +2973,10 @@
     ResourceBinding::SampledKind expected_kind;
     std::tie(format, expected_format, expected_kind) = format_params;
 
-    auto* st_type = MakeStorageTextureTypes(dim, format);
+    ast::Type st_type = MakeStorageTextureTypes(dim, format);
     AddStorageTexture("st_var", st_type, 0, 0);
 
-    const ast::Type* dim_type = nullptr;
+    ast::Type dim_type;
     switch (dim) {
         case type::TextureDimension::k1d:
             dim_type = ty.u32();
@@ -3074,7 +3074,7 @@
                                                      ResourceBinding::SampledKind::kFloat))));
 
 TEST_P(InspectorGetDepthTextureResourceBindingsTestWithParam, textureDimensions) {
-    auto* depth_texture_type = ty.depth_texture(GetParam().type_dim);
+    auto depth_texture_type = ty.depth_texture(GetParam().type_dim);
     AddResource("dt", depth_texture_type, 0, 0);
 
     Func("ep", utils::Empty, ty.void_(),
@@ -3111,7 +3111,7 @@
                                   inspector::ResourceBinding::TextureDimension::kCubeArray}));
 
 TEST_F(InspectorGetDepthMultisampledTextureResourceBindingsTest, textureDimensions) {
-    auto* depth_ms_texture_type = ty.depth_multisampled_texture(type::TextureDimension::k2d);
+    auto depth_ms_texture_type = ty.depth_multisampled_texture(type::TextureDimension::k2d);
     AddResource("tex", depth_ms_texture_type, 0, 0);
 
     Func("ep", utils::Empty, ty.void_(),
@@ -3135,7 +3135,7 @@
 }
 
 TEST_F(InspectorGetExternalTextureResourceBindingsTest, Simple) {
-    auto* external_texture_type = ty.external_texture();
+    auto external_texture_type = ty.external_texture();
     AddResource("et", external_texture_type, 0, 0);
 
     Func("ep", utils::Empty, ty.void_(),
diff --git a/src/tint/inspector/test_inspector_builder.cc b/src/tint/inspector/test_inspector_builder.cc
index 677f9c1..e73c6f5 100644
--- a/src/tint/inspector/test_inspector_builder.cc
+++ b/src/tint/inspector/test_inspector_builder.cc
@@ -64,7 +64,7 @@
 const ast::Function* InspectorBuilder::MakePlainGlobalReferenceBodyFunction(
     std::string func,
     std::string var,
-    const ast::Type* type,
+    ast::Type type,
     utils::VectorRef<const ast::Attribute*> attributes) {
     utils::Vector<const ast::Statement*, 3> stmts;
     stmts.Push(Decl(Var("local_" + var, type)));
@@ -82,15 +82,14 @@
     return false;
 }
 
-std::string InspectorBuilder::StructMemberName(size_t idx, const ast::Type* type) {
-    return std::to_string(idx) + type->FriendlyName(Symbols());
+std::string InspectorBuilder::StructMemberName(size_t idx, ast::Type type) {
+    return std::to_string(idx) + Symbols().NameFor(type->identifier->symbol);
 }
 
-const ast::Struct* InspectorBuilder::MakeStructType(
-    const std::string& name,
-    utils::VectorRef<const ast::Type*> member_types) {
+const ast::Struct* InspectorBuilder::MakeStructType(const std::string& name,
+                                                    utils::VectorRef<ast::Type> member_types) {
     utils::Vector<const ast::StructMember*, 8> members;
-    for (auto* type : member_types) {
+    for (auto type : member_types) {
         members.Push(MakeStructMember(members.Length(), type, {}));
     }
     return MakeStructTypeFromMembers(name, std::move(members));
@@ -104,37 +103,37 @@
 
 const ast::StructMember* InspectorBuilder::MakeStructMember(
     size_t index,
-    const ast::Type* type,
+    ast::Type type,
     utils::VectorRef<const ast::Attribute*> attributes) {
     return Member(StructMemberName(index, type), type, std::move(attributes));
 }
 
 const ast::Struct* InspectorBuilder::MakeUniformBufferType(
     const std::string& name,
-    utils::VectorRef<const ast::Type*> member_types) {
+    utils::VectorRef<ast::Type> member_types) {
     return MakeStructType(name, member_types);
 }
 
-std::function<const ast::TypeName*()> InspectorBuilder::MakeStorageBufferTypes(
+std::function<ast::Type()> InspectorBuilder::MakeStorageBufferTypes(
     const std::string& name,
-    utils::VectorRef<const ast::Type*> member_types) {
+    utils::VectorRef<ast::Type> member_types) {
     MakeStructType(name, member_types);
     return [this, name] { return ty(name); };
 }
 
 void InspectorBuilder::AddUniformBuffer(const std::string& name,
-                                        const ast::Type* type,
+                                        ast::Type type,
                                         uint32_t group,
                                         uint32_t binding) {
     GlobalVar(name, type, type::AddressSpace::kUniform, Binding(AInt(binding)), Group(AInt(group)));
 }
 
-void InspectorBuilder::AddWorkgroupStorage(const std::string& name, const ast::Type* type) {
+void InspectorBuilder::AddWorkgroupStorage(const std::string& name, ast::Type type) {
     GlobalVar(name, type, type::AddressSpace::kWorkgroup);
 }
 
 void InspectorBuilder::AddStorageBuffer(const std::string& name,
-                                        const ast::Type* type,
+                                        ast::Type type,
                                         type::Access access,
                                         uint32_t group,
                                         uint32_t binding) {
@@ -145,11 +144,11 @@
 void InspectorBuilder::MakeStructVariableReferenceBodyFunction(
     std::string func_name,
     std::string struct_name,
-    utils::VectorRef<std::tuple<size_t, const ast::Type*>> members) {
+    utils::VectorRef<std::tuple<size_t, ast::Type>> members) {
     utils::Vector<const ast::Statement*, 8> stmts;
     for (auto member : members) {
         size_t member_idx;
-        const ast::Type* member_type;
+        ast::Type member_type;
         std::tie(member_idx, member_type) = member;
         std::string member_name = StructMemberName(member_idx, member_type);
 
@@ -158,7 +157,7 @@
 
     for (auto member : members) {
         size_t member_idx;
-        const ast::Type* member_type;
+        ast::Type member_type;
         std::tie(member_idx, member_type) = member;
         std::string member_name = StructMemberName(member_idx, member_type);
 
@@ -183,13 +182,13 @@
 }
 
 void InspectorBuilder::AddResource(const std::string& name,
-                                   const ast::Type* type,
+                                   ast::Type type,
                                    uint32_t group,
                                    uint32_t binding) {
     GlobalVar(name, type, Binding(AInt(binding)), Group(AInt(group)));
 }
 
-void InspectorBuilder::AddGlobalVariable(const std::string& name, const ast::Type* type) {
+void InspectorBuilder::AddGlobalVariable(const std::string& name, ast::Type type) {
     GlobalVar(name, type, type::AddressSpace::kPrivate);
 }
 
@@ -198,7 +197,7 @@
     const std::string& texture_name,
     const std::string& sampler_name,
     const std::string& coords_name,
-    const ast::Type* base_type,
+    ast::Type base_type,
     utils::VectorRef<const ast::Attribute*> attributes) {
     std::string result_name = "sampler_result";
 
@@ -216,7 +215,7 @@
     const std::string& sampler_name,
     const std::string& coords_name,
     const std::string& array_index,
-    const ast::Type* base_type,
+    ast::Type base_type,
     utils::VectorRef<const ast::Attribute*> attributes) {
     std::string result_name = "sampler_result";
 
@@ -235,7 +234,7 @@
     const std::string& sampler_name,
     const std::string& coords_name,
     const std::string& depth_name,
-    const ast::Type* base_type,
+    ast::Type base_type,
     utils::VectorRef<const ast::Attribute*> attributes) {
     std::string result_name = "sampler_result";
 
@@ -248,7 +247,7 @@
     return Func(func_name, utils::Empty, ty.void_(), std::move(stmts), std::move(attributes));
 }
 
-const ast::Type* InspectorBuilder::GetBaseType(ResourceBinding::SampledKind sampled_kind) {
+ast::Type InspectorBuilder::GetBaseType(ResourceBinding::SampledKind sampled_kind) {
     switch (sampled_kind) {
         case ResourceBinding::SampledKind::kFloat:
             return ty.f32();
@@ -257,35 +256,34 @@
         case ResourceBinding::SampledKind::kUInt:
             return ty.u32();
         default:
-            return nullptr;
+            return ast::Type{};
     }
 }
 
-const ast::Type* InspectorBuilder::GetCoordsType(type::TextureDimension dim,
-                                                 const ast::Type* scalar) {
+ast::Type InspectorBuilder::GetCoordsType(type::TextureDimension dim, ast::Type scalar) {
     switch (dim) {
         case type::TextureDimension::k1d:
             return scalar;
         case type::TextureDimension::k2d:
         case type::TextureDimension::k2dArray:
-            return create<ast::Vector>(scalar, 2u);
+            return ty.vec2(scalar);
         case type::TextureDimension::k3d:
         case type::TextureDimension::kCube:
         case type::TextureDimension::kCubeArray:
-            return create<ast::Vector>(scalar, 3u);
+            return ty.vec3(scalar);
         default:
             [=]() { FAIL() << "Unsupported texture dimension: " << dim; }();
     }
-    return nullptr;
+    return ast::Type{};
 }
 
-const ast::Type* InspectorBuilder::MakeStorageTextureTypes(type::TextureDimension dim,
-                                                           type::TexelFormat format) {
+ast::Type InspectorBuilder::MakeStorageTextureTypes(type::TextureDimension dim,
+                                                    type::TexelFormat format) {
     return ty.storage_texture(dim, format, type::Access::kWrite);
 }
 
 void InspectorBuilder::AddStorageTexture(const std::string& name,
-                                         const ast::Type* type,
+                                         ast::Type type,
                                          uint32_t group,
                                          uint32_t binding) {
     GlobalVar(name, type, Binding(AInt(binding)), Group(AInt(group)));
@@ -294,7 +292,7 @@
 const ast::Function* InspectorBuilder::MakeStorageTextureBodyFunction(
     const std::string& func_name,
     const std::string& st_name,
-    const ast::Type* dim_type,
+    ast::Type dim_type,
     utils::VectorRef<const ast::Attribute*> attributes) {
     utils::Vector stmts{
         Decl(Var("dim", dim_type)),
@@ -305,24 +303,24 @@
     return Func(func_name, utils::Empty, ty.void_(), std::move(stmts), std::move(attributes));
 }
 
-std::function<const ast::Type*()> InspectorBuilder::GetTypeFunction(ComponentType component,
-                                                                    CompositionType composition) {
-    std::function<const ast::Type*()> func;
+std::function<ast::Type()> InspectorBuilder::GetTypeFunction(ComponentType component,
+                                                             CompositionType composition) {
+    std::function<ast::Type()> func;
     switch (component) {
         case ComponentType::kF32:
-            func = [this]() -> const ast::Type* { return ty.f32(); };
+            func = [this]() { return ty.f32(); };
             break;
         case ComponentType::kI32:
-            func = [this]() -> const ast::Type* { return ty.i32(); };
+            func = [this]() { return ty.i32(); };
             break;
         case ComponentType::kU32:
-            func = [this]() -> const ast::Type* { return ty.u32(); };
+            func = [this]() { return ty.u32(); };
             break;
         case ComponentType::kF16:
-            func = [this]() -> const ast::Type* { return ty.f16(); };
+            func = [this]() { return ty.f16(); };
             break;
         case ComponentType::kUnknown:
-            return []() -> const ast::Type* { return nullptr; };
+            return []() { return ast::Type{}; };
     }
 
     uint32_t n;
@@ -339,10 +337,10 @@
             n = 4;
             break;
         default:
-            return []() -> ast::Type* { return nullptr; };
+            return []() { return ast::Type{}; };
     }
 
-    return [this, func, n]() -> const ast::Type* { return ty.vec(func(), n); };
+    return [this, func, n]() { return ty.vec(func(), n); };
 }
 
 Inspector& InspectorBuilder::Build() {
diff --git a/src/tint/inspector/test_inspector_builder.h b/src/tint/inspector/test_inspector_builder.h
index 8b32529..1d99863 100644
--- a/src/tint/inspector/test_inspector_builder.h
+++ b/src/tint/inspector/test_inspector_builder.h
@@ -107,7 +107,7 @@
     const ast::Function* MakePlainGlobalReferenceBodyFunction(
         std::string func,
         std::string var,
-        const ast::Type* type,
+        ast::Type type,
         utils::VectorRef<const ast::Attribute*> attributes);
 
     /// @param vec Vector of StageVariable to be searched
@@ -119,14 +119,14 @@
     /// @param idx index of member
     /// @param type type of member
     /// @returns a string for the member
-    std::string StructMemberName(size_t idx, const ast::Type* type);
+    std::string StructMemberName(size_t idx, ast::Type type);
 
     /// Generates a struct type
     /// @param name name for the type
     /// @param member_types a vector of member types
     /// @returns a struct type
     const ast::Struct* MakeStructType(const std::string& name,
-                                      utils::VectorRef<const ast::Type*> member_types);
+                                      utils::VectorRef<ast::Type> member_types);
 
     /// Generates a struct type from a list of member nodes.
     /// @param name name for the struct type
@@ -142,7 +142,7 @@
     /// @param attributes a list of attributes to apply to the member field
     /// @returns a struct member
     const ast::StructMember* MakeStructMember(size_t index,
-                                              const ast::Type* type,
+                                              ast::Type type,
                                               utils::VectorRef<const ast::Attribute*> attributes);
 
     /// Generates types appropriate for using in an uniform buffer
@@ -150,15 +150,14 @@
     /// @param member_types a vector of member types
     /// @returns a struct type that has the layout for an uniform buffer.
     const ast::Struct* MakeUniformBufferType(const std::string& name,
-                                             utils::VectorRef<const ast::Type*> member_types);
+                                             utils::VectorRef<ast::Type> member_types);
 
     /// 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 function that returns the created structure.
-    std::function<const ast::TypeName*()> MakeStorageBufferTypes(
-        const std::string& name,
-        utils::VectorRef<const ast::Type*> member_types);
+    std::function<ast::Type()> MakeStorageBufferTypes(const std::string& name,
+                                                      utils::VectorRef<ast::Type> member_types);
 
     /// Adds an uniform buffer variable to the program
     /// @param name the name of the variable
@@ -166,14 +165,14 @@
     /// @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,
-                          const ast::Type* type,
+                          ast::Type type,
                           uint32_t group,
                           uint32_t binding);
 
     /// Adds a workgroup storage variable to the program
     /// @param name the name of the variable
     /// @param type the type of the variable
-    void AddWorkgroupStorage(const std::string& name, const ast::Type* type);
+    void AddWorkgroupStorage(const std::string& name, ast::Type type);
 
     /// Adds a storage buffer variable to the program
     /// @param name the name of the variable
@@ -182,13 +181,13 @@
     /// @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,
-                          const ast::Type* type,
+                          ast::Type type,
                           type::Access access,
                           uint32_t group,
                           uint32_t binding);
 
     /// MemberInfo is a tuple of member index and type.
-    using MemberInfo = std::tuple<size_t, const ast::Type*>;
+    using MemberInfo = std::tuple<size_t, ast::Type>;
 
     /// Generates a function that references a specific struct variable
     /// @param func_name name of the function created
@@ -215,15 +214,12 @@
     /// @param type the type to use
     /// @param group the binding/group to use for the resource
     /// @param binding the binding number to use for the resource
-    void AddResource(const std::string& name,
-                     const ast::Type* type,
-                     uint32_t group,
-                     uint32_t binding);
+    void AddResource(const std::string& name, ast::Type type, uint32_t group, uint32_t binding);
 
     /// Add a module scope private variable to the progames
     /// @param name the name of the variable
     /// @param type the type to use
-    void AddGlobalVariable(const std::string& name, const ast::Type* type);
+    void AddGlobalVariable(const std::string& name, ast::Type type);
 
     /// Generates a function that references a specific sampler variable
     /// @param func_name name of the function created
@@ -238,7 +234,7 @@
         const std::string& texture_name,
         const std::string& sampler_name,
         const std::string& coords_name,
-        const ast::Type* base_type,
+        ast::Type base_type,
         utils::VectorRef<const ast::Attribute*> attributes);
 
     /// Generates a function that references a specific sampler variable
@@ -256,7 +252,7 @@
         const std::string& sampler_name,
         const std::string& coords_name,
         const std::string& array_index,
-        const ast::Type* base_type,
+        ast::Type base_type,
         utils::VectorRef<const ast::Attribute*> attributes);
 
     /// Generates a function that references a specific comparison sampler
@@ -275,26 +271,26 @@
         const std::string& sampler_name,
         const std::string& coords_name,
         const std::string& depth_name,
-        const ast::Type* base_type,
+        ast::Type base_type,
         utils::VectorRef<const ast::Attribute*> attributes);
 
     /// 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
-    const ast::Type* GetBaseType(ResourceBinding::SampledKind sampled_kind);
+    ast::Type GetBaseType(ResourceBinding::SampledKind sampled_kind);
 
     /// Gets an appropriate type for the coords parameter depending the the
     /// dimensionality of the texture being sampled.
     /// @param dim dimensionality of the texture being sampled
     /// @param scalar the scalar type
     /// @returns a pointer to a type appropriate for the coord param
-    const ast::Type* GetCoordsType(type::TextureDimension dim, const ast::Type* scalar);
+    ast::Type GetCoordsType(type::TextureDimension dim, ast::Type scalar);
 
     /// Generates appropriate types for a Read-Only StorageTexture
     /// @param dim the texture dimension of the storage texture
     /// @param format the texel format of the storage texture
     /// @returns the storage texture type
-    const ast::Type* MakeStorageTextureTypes(type::TextureDimension dim, type::TexelFormat format);
+    ast::Type MakeStorageTextureTypes(type::TextureDimension dim, type::TexelFormat format);
 
     /// Adds a storage texture variable to the program
     /// @param name the name of the variable
@@ -302,7 +298,7 @@
     /// @param group the binding/group to use for the sampled texture
     /// @param binding the binding57 number to use for the sampled texture
     void AddStorageTexture(const std::string& name,
-                           const ast::Type* type,
+                           ast::Type type,
                            uint32_t group,
                            uint32_t binding);
 
@@ -315,7 +311,7 @@
     const ast::Function* MakeStorageTextureBodyFunction(
         const std::string& func_name,
         const std::string& st_name,
-        const ast::Type* dim_type,
+        ast::Type dim_type,
         utils::VectorRef<const ast::Attribute*> attributes);
 
     /// Get a generator function that returns a type appropriate for a stage
@@ -323,8 +319,8 @@
     /// @param component component type of the stage variable
     /// @param composition composition type of the stage variable
     /// @returns a generator function for the stage variable's type.
-    std::function<const ast::Type*()> GetTypeFunction(ComponentType component,
-                                                      CompositionType composition);
+    std::function<ast::Type()> GetTypeFunction(ComponentType component,
+                                               CompositionType composition);
 
     /// Build the Program given all of the previous methods called and return an
     /// Inspector for it.
diff --git a/src/tint/intrinsics.def b/src/tint/intrinsics.def
index c490482..5985921 100644
--- a/src/tint/intrinsics.def
+++ b/src/tint/intrinsics.def
@@ -136,6 +136,15 @@
   i32
   u32
   // https://www.w3.org/TR/WGSL/#matrix-types
+  mat2x2
+  mat2x3
+  mat2x4
+  mat3x2
+  mat3x3
+  mat3x4
+  mat4x2
+  mat4x3
+  mat4x4
   mat2x2f
   mat2x2h
   mat2x3f
@@ -155,6 +164,9 @@
   mat4x4f
   mat4x4h
   // https://www.w3.org/TR/WGSL/#vector-types
+  vec2
+  vec3
+  vec4
   vec2f
   vec2h
   vec2i
@@ -167,6 +179,12 @@
   vec4h
   vec4i
   vec4u
+  // https://www.w3.org/TR/WGSL/#array-types
+  array
+  // https://www.w3.org/TR/WGSL/#atomic-types
+  atomic
+  // https://www.w3.org/TR/WGSL/#ref-ptr-types
+  ptr
   // https://www.w3.org/TR/WGSL/#sampler-type
   sampler
   sampler_comparison
@@ -176,6 +194,15 @@
   texture_depth_cube
   texture_depth_cube_array
   texture_depth_multisampled_2d
+  // https://www.w3.org/TR/WGSL/#sampled-texture-type
+  texture_1d
+  texture_2d
+  texture_2d_array
+  texture_3d
+  texture_cube
+  texture_cube_array
+  // https://www.w3.org/TR/WGSL/#multisampled-texture-type
+  texture_multisampled_2d
   // https://www.w3.org/TR/WGSL/#texture-storage
   texture_storage_1d
   texture_storage_2d
diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h
index c086677..034ab65 100644
--- a/src/tint/ir/builder_impl.h
+++ b/src/tint/ir/builder_impl.h
@@ -19,6 +19,7 @@
 #include <unordered_map>
 #include <utility>
 
+#include "src/tint/ast/type.h"
 #include "src/tint/constant/clone_context.h"
 #include "src/tint/diagnostic/diagnostic.h"
 #include "src/tint/ir/builder.h"
@@ -49,7 +50,6 @@
 class ReturnStatement;
 class Statement;
 class SwitchStatement;
-class Type;
 class WhileStatement;
 class Variable;
 }  // namespace tint::ast
diff --git a/src/tint/program.cc b/src/tint/program.cc
index a5d8c45..378a1ce 100644
--- a/src/tint/program.cc
+++ b/src/tint/program.cc
@@ -18,6 +18,7 @@
 
 #include "src/tint/demangler.h"
 #include "src/tint/resolver/resolver.h"
+#include "src/tint/sem/type_expression.h"
 #include "src/tint/sem/value_expression.h"
 
 namespace tint {
@@ -118,21 +119,24 @@
 }
 
 const type::Type* Program::TypeOf(const ast::Expression* expr) const {
-    auto* sem = Sem().GetVal(expr);
-    return sem ? sem->Type() : nullptr;
+    return tint::Switch(
+        Sem().Get(expr),  //
+        [](const sem::ValueExpression* ty_expr) { return ty_expr->Type(); },
+        [](const sem::TypeExpression* ty_expr) { return ty_expr->Type(); });
 }
 
-const type::Type* Program::TypeOf(const ast::Type* type) const {
-    return Sem().Get(type);
+const type::Type* Program::TypeOf(const ast::Variable* var) const {
+    auto* sem = Sem().Get(var);
+    return sem ? sem->Type() : nullptr;
 }
 
 const type::Type* Program::TypeOf(const ast::TypeDecl* type_decl) const {
     return Sem().Get(type_decl);
 }
 
-std::string Program::FriendlyName(const ast::Type* type) const {
+std::string Program::FriendlyName(ast::Type type) const {
     TINT_ASSERT_PROGRAM_IDS_EQUAL(Program, type, ID());
-    return type ? type->FriendlyName(Symbols()) : "<null>";
+    return type ? Symbols().NameFor(type->identifier->symbol) : "<null>";
 }
 
 std::string Program::FriendlyName(const type::Type* type) const {
diff --git a/src/tint/program.h b/src/tint/program.h
index 873a0c4..1c57fe0 100644
--- a/src/tint/program.h
+++ b/src/tint/program.h
@@ -138,11 +138,11 @@
     /// expression has no resolved type.
     const type::Type* TypeOf(const ast::Expression* expr) const;
 
-    /// Helper for returning the resolved semantic type of the AST type `type`.
-    /// @param type the AST type
-    /// @return the resolved semantic type for the type, or nullptr if the type
-    /// has no resolved type.
-    const type::Type* TypeOf(const ast::Type* type) const;
+    /// Helper for returning the resolved semantic type of the variable `var`.
+    /// @param var the AST variable
+    /// @return the resolved semantic type for the variable, or nullptr if the
+    /// variable has no resolved type.
+    const type::Type* TypeOf(const ast::Variable* var) const;
 
     /// Helper for returning the resolved semantic type of the AST type
     /// declaration `type_decl`.
@@ -152,13 +152,11 @@
     const type::Type* TypeOf(const ast::TypeDecl* type_decl) const;
 
     /// @param type a type
-    /// @returns the name for `type` that closely resembles how it would be
-    /// declared in WGSL.
-    std::string FriendlyName(const ast::Type* type) const;
+    /// @returns the name for `type` that closely resembles how it would be declared in WGSL.
+    std::string FriendlyName(ast::Type type) const;
 
     /// @param type a type
-    /// @returns the name for `type` that closely resembles how it would be
-    /// declared in WGSL.
+    /// @returns the name for `type` that closely resembles how it would be declared in WGSL.
     std::string FriendlyName(const type::Type* type) const;
 
     /// Overload of FriendlyName, which removes an ambiguity when passing nullptr.
diff --git a/src/tint/program_builder.cc b/src/tint/program_builder.cc
index a847e00..aaaa279 100644
--- a/src/tint/program_builder.cc
+++ b/src/tint/program_builder.cc
@@ -19,6 +19,7 @@
 #include "src/tint/ast/variable_decl_statement.h"
 #include "src/tint/debug.h"
 #include "src/tint/demangler.h"
+#include "src/tint/sem/type_expression.h"
 #include "src/tint/sem/value_expression.h"
 #include "src/tint/sem/variable.h"
 #include "src/tint/utils/compiler_macros.h"
@@ -97,8 +98,10 @@
 }
 
 const type::Type* ProgramBuilder::TypeOf(const ast::Expression* expr) const {
-    auto* sem = Sem().GetVal(expr);
-    return sem ? sem->Type() : nullptr;
+    return tint::Switch(
+        Sem().Get(expr),  //
+        [](const sem::ValueExpression* e) { return e->Type(); },
+        [](const sem::TypeExpression* e) { return e->Type(); });
 }
 
 const type::Type* ProgramBuilder::TypeOf(const ast::Variable* var) const {
@@ -106,17 +109,13 @@
     return sem ? sem->Type() : nullptr;
 }
 
-const type::Type* ProgramBuilder::TypeOf(const ast::Type* type) const {
-    return Sem().Get(type);
-}
-
 const type::Type* ProgramBuilder::TypeOf(const ast::TypeDecl* type_decl) const {
     return Sem().Get(type_decl);
 }
 
-std::string ProgramBuilder::FriendlyName(const ast::Type* type) const {
+std::string ProgramBuilder::FriendlyName(ast::Type type) const {
     TINT_ASSERT_PROGRAM_IDS_EQUAL(ProgramBuilder, type, ID());
-    return type ? type->FriendlyName(Symbols()) : "<null>";
+    return type.expr ? Symbols().NameFor(type->identifier->symbol) : "<null>";
 }
 
 std::string ProgramBuilder::FriendlyName(const type::Type* type) const {
@@ -127,10 +126,6 @@
     return "<null>";
 }
 
-const ast::TypeName* ProgramBuilder::TypesBuilder::Of(const ast::TypeDecl* decl) const {
-    return (*this)(decl->name->symbol);
-}
-
 ProgramBuilder::TypesBuilder::TypesBuilder(ProgramBuilder* pb) : builder(pb) {}
 
 const ast::Statement* ProgramBuilder::WrapInStatement(const ast::Expression* expr) {
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index 0541511..265219d 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -22,9 +22,7 @@
 #include "tint/override_id.h"
 
 #include "src/tint/ast/alias.h"
-#include "src/tint/ast/array.h"
 #include "src/tint/ast/assignment_statement.h"
-#include "src/tint/ast/atomic.h"
 #include "src/tint/ast/binary_expression.h"
 #include "src/tint/ast/binding_attribute.h"
 #include "src/tint/ast/bitcast_expression.h"
@@ -57,16 +55,12 @@
 #include "src/tint/ast/invariant_attribute.h"
 #include "src/tint/ast/let.h"
 #include "src/tint/ast/loop_statement.h"
-#include "src/tint/ast/matrix.h"
 #include "src/tint/ast/member_accessor_expression.h"
 #include "src/tint/ast/module.h"
-#include "src/tint/ast/multisampled_texture.h"
 #include "src/tint/ast/override.h"
 #include "src/tint/ast/parameter.h"
 #include "src/tint/ast/phony_expression.h"
-#include "src/tint/ast/pointer.h"
 #include "src/tint/ast/return_statement.h"
-#include "src/tint/ast/sampled_texture.h"
 #include "src/tint/ast/stage_attribute.h"
 #include "src/tint/ast/stride_attribute.h"
 #include "src/tint/ast/struct_member_align_attribute.h"
@@ -74,11 +68,10 @@
 #include "src/tint/ast/struct_member_size_attribute.h"
 #include "src/tint/ast/switch_statement.h"
 #include "src/tint/ast/templated_identifier.h"
-#include "src/tint/ast/type_name.h"
+#include "src/tint/ast/type.h"
 #include "src/tint/ast/unary_op_expression.h"
 #include "src/tint/ast/var.h"
 #include "src/tint/ast/variable_decl_statement.h"
-#include "src/tint/ast/vector.h"
 #include "src/tint/ast/while_statement.h"
 #include "src/tint/ast/workgroup_attribute.h"
 #include "src/tint/constant/composite.h"
@@ -106,6 +99,7 @@
 #include "src/tint/type/u32.h"
 #include "src/tint/type/vector.h"
 #include "src/tint/type/void.h"
+#include "src/tint/utils/string.h"
 
 #ifdef CURRENTLY_IN_TINT_PUBLIC_HEADER
 #error "internal tint header being #included from tint.h"
@@ -145,6 +139,14 @@
 };
 }  // namespace detail
 
+// A sentinel type used by some template arguments to signal that the a type should be inferred.
+struct Infer {};
+
+/// Evaluates to true if T is a Infer, AInt or AFloat.
+template <typename T>
+static constexpr const bool IsInferOrAbstract =
+    std::is_same_v<std::decay_t<T>, Infer> || IsAbstract<std::decay_t<T>>;
+
 // Forward declare metafunction that evaluates to true iff T can be wrapped in a statement.
 template <typename T, typename = void>
 struct CanWrapInStatement;
@@ -163,6 +165,12 @@
         std::is_integral_v<UnwrapNumber<T>> || std::is_floating_point_v<UnwrapNumber<T>> ||
         std::is_same_v<T, bool>;
 
+    /// Evaluates to true if T can be converted to an identifier.
+    template <typename T>
+    static constexpr const bool IsIdentifierLike = std::is_same_v<T, Symbol> ||  // Symbol
+                                                   std::is_enum_v<T> ||          // Enum
+                                                   traits::IsStringLike<T>;      // String
+
     /// A helper used to disable overloads if the first type in `TYPES` is a Source. Used to avoid
     /// ambiguities in overloads that take a Source as the first parameter and those that
     /// perfectly-forward the first argument.
@@ -177,12 +185,36 @@
     using DisableIfScalar =
         traits::EnableIf<!IsScalar<traits::Decay<traits::NthTypeOf<0, TYPES..., void>>>>;
 
+    /// A helper used to enable overloads if the first type in `TYPES` is a scalar type. Used to
+    /// avoid ambiguities in overloads that take a scalar as the first parameter and those that
+    /// perfectly-forward the first argument.
+    template <typename... TYPES>
+    using EnableIfScalar =
+        traits::EnableIf<IsScalar<traits::Decay<traits::NthTypeOf<0, TYPES..., void>>>>;
+
     /// A helper used to disable overloads if the first type in `TYPES` is a utils::Vector,
     /// utils::VectorRef or utils::VectorRef.
     template <typename... TYPES>
     using DisableIfVectorLike = traits::EnableIf<
         !detail::IsVectorLike<traits::Decay<traits::NthTypeOf<0, TYPES..., void>>>::value>;
 
+    /// A helper used to enable overloads if the first type in `TYPES` is identifier-like.
+    template <typename... TYPES>
+    using EnableIfIdentifierLike =
+        traits::EnableIf<IsIdentifierLike<traits::Decay<traits::NthTypeOf<0, TYPES..., void>>>>;
+
+    /// A helper used to disable overloads if the first type in `TYPES` is Infer or an abstract
+    /// numeric.
+    template <typename... TYPES>
+    using DisableIfInferOrAbstract =
+        traits::EnableIf<!IsInferOrAbstract<traits::Decay<traits::NthTypeOf<0, TYPES..., void>>>>;
+
+    /// A helper used to enable overloads if the first type in `TYPES` is Infer or an abstract
+    /// numeric.
+    template <typename... TYPES>
+    using EnableIfInferOrAbstract =
+        traits::EnableIf<IsInferOrAbstract<traits::Decay<traits::NthTypeOf<0, TYPES..., void>>>>;
+
     /// VarOptions is a helper for accepting an arbitrary number of order independent options for
     /// constructing an ast::Var.
     struct VarOptions {
@@ -192,14 +224,14 @@
         }
         ~VarOptions();
 
-        const ast::Type* type = nullptr;
+        ast::Type type;
         type::AddressSpace address_space = type::AddressSpace::kNone;
         type::Access access = type::Access::kUndefined;
         const ast::Expression* initializer = nullptr;
         utils::Vector<const ast::Attribute*, 4> attributes;
 
       private:
-        void Set(const ast::Type* t) { type = t; }
+        void Set(ast::Type t) { type = t; }
         void Set(type::AddressSpace addr_space) { address_space = addr_space; }
         void Set(type::Access ac) { access = ac; }
         void Set(const ast::Expression* c) { initializer = c; }
@@ -219,12 +251,12 @@
         }
         ~LetOptions();
 
-        const ast::Type* type = nullptr;
+        ast::Type type;
         const ast::Expression* initializer = nullptr;
         utils::Vector<const ast::Attribute*, 4> attributes;
 
       private:
-        void Set(const ast::Type* t) { type = t; }
+        void Set(ast::Type t) { type = t; }
         void Set(const ast::Expression* c) { initializer = c; }
         void Set(utils::VectorRef<const ast::Attribute*> l) { attributes = std::move(l); }
         void Set(const ast::Attribute* a) { attributes.Push(a); }
@@ -242,12 +274,12 @@
         }
         ~ConstOptions();
 
-        const ast::Type* type = nullptr;
+        ast::Type type;
         const ast::Expression* initializer = nullptr;
         utils::Vector<const ast::Attribute*, 4> attributes;
 
       private:
-        void Set(const ast::Type* t) { type = t; }
+        void Set(ast::Type t) { type = t; }
         void Set(const ast::Expression* c) { initializer = c; }
         void Set(utils::VectorRef<const ast::Attribute*> l) { attributes = std::move(l); }
         void Set(const ast::Attribute* a) { attributes.Push(a); }
@@ -262,12 +294,12 @@
         }
         ~OverrideOptions();
 
-        const ast::Type* type = nullptr;
+        ast::Type type;
         const ast::Expression* initializer = nullptr;
         utils::Vector<const ast::Attribute*, 4> attributes;
 
       private:
-        void Set(const ast::Type* t) { type = t; }
+        void Set(ast::Type t) { type = t; }
         void Set(const ast::Expression* c) { initializer = c; }
         void Set(utils::VectorRef<const ast::Attribute*> l) { attributes = std::move(l); }
         void Set(const ast::Attribute* a) { attributes.Push(a); }
@@ -551,328 +583,587 @@
         /// @param builder the program builder
         explicit TypesBuilder(ProgramBuilder* builder);
 
-        /// @return the tint AST type for the C type `T`.
+        /// @return the C type `T`.
         template <typename T>
-        const ast::Type* Of() const {
+        ast::Type Of() const {
             return CToAST<T>::get(this);
         }
 
-        /// @returns nullptr ast::Type
-        const ast::Type* void_() const { return nullptr; }
+        /// @param type the type to return
+        /// @return type (passthrough)
+        ast::Type operator()(const ast::Type& type) const { return type; }
 
-        /// @returns a 'bool' typename
-        const ast::TypeName* bool_() const { return (*this)("bool"); }
+        /// Creates a type
+        /// @param name the name
+        /// @param args the optional template arguments
+        /// @returns the type
+        template <typename NAME,
+                  typename... ARGS,
+                  typename = DisableIfSource<NAME>,
+                  typename = std::enable_if_t<!std::is_same_v<std::decay_t<NAME>, ast::Type>>>
+        ast::Type operator()(NAME&& name, ARGS&&... args) const {
+            if constexpr (traits::IsTypeOrDerived<traits::PtrElTy<NAME>, ast::Expression>) {
+                static_assert(sizeof...(ARGS) == 0);
+                return {name};
+            } else {
+                return {builder->Expr(
+                    builder->Ident(std::forward<NAME>(name), std::forward<ARGS>(args)...))};
+            }
+        }
+
+        /// Creates a type
+        /// @param source the Source of the node
+        /// @param name the name
+        /// @param args the optional template arguments
+        /// @returns the type
+        template <typename NAME,
+                  typename... ARGS,
+                  typename = std::enable_if_t<!std::is_same_v<std::decay_t<NAME>, ast::Type>>>
+        ast::Type operator()(const Source& source, NAME&& name, ARGS&&... args) const {
+            return {builder->Expr(
+                builder->Ident(source, std::forward<NAME>(name), std::forward<ARGS>(args)...))};
+        }
+
+        /// @returns a a nullptr expression wrapped in an ast::Type
+        ast::Type void_() const { return ast::Type{}; }
+
+        /// @returns a 'bool' type
+        ast::Type bool_() const { return (*this)("bool"); }
 
         /// @param source the Source of the node
-        /// @returns a 'bool' typename
-        const ast::TypeName* bool_(const Source& source) const { return (*this)(source, "bool"); }
+        /// @returns a 'bool' type
+        ast::Type bool_(const Source& source) const { return (*this)(source, "bool"); }
 
-        /// @returns a 'f16' typename
-        const ast::TypeName* f16() const { return (*this)("f16"); }
+        /// @returns a 'f16' type
+        ast::Type f16() const { return (*this)("f16"); }
 
         /// @param source the Source of the node
-        /// @returns a 'f16' typename
-        const ast::TypeName* f16(const Source& source) const { return (*this)(source, "f16"); }
+        /// @returns a 'f16' type
+        ast::Type f16(const Source& source) const { return (*this)(source, "f16"); }
 
-        /// @returns a 'f32' typename
-        const ast::TypeName* f32() const { return (*this)("f32"); }
+        /// @returns a 'f32' type
+        ast::Type f32() const { return (*this)("f32"); }
 
         /// @param source the Source of the node
-        /// @returns a 'f32' typename
-        const ast::TypeName* f32(const Source& source) const { return (*this)(source, "f32"); }
+        /// @returns a 'f32' type
+        ast::Type f32(const Source& source) const { return (*this)(source, "f32"); }
 
-        /// @returns a 'i32' typename
-        const ast::TypeName* i32() const { return (*this)("i32"); }
+        /// @returns a 'i32' type
+        ast::Type i32() const { return (*this)("i32"); }
 
         /// @param source the Source of the node
-        /// @returns a 'i32' typename
-        const ast::TypeName* i32(const Source& source) const { return (*this)(source, "i32"); }
+        /// @returns a 'i32' type
+        ast::Type i32(const Source& source) const { return (*this)(source, "i32"); }
 
-        /// @returns a 'u32' typename
-        const ast::TypeName* u32() const { return (*this)("u32"); }
+        /// @returns a 'u32' type
+        ast::Type u32() const { return (*this)("u32"); }
 
         /// @param source the Source of the node
-        /// @returns a 'u32' typename
-        const ast::TypeName* u32(const Source& source) const { return (*this)(source, "u32"); }
+        /// @returns a 'u32' type
+        ast::Type u32(const Source& source) const { return (*this)(source, "u32"); }
 
         /// @param type vector subtype
         /// @param n vector width in elements
-        /// @return the tint AST type for a `n`-element vector of `type`.
-        const ast::Vector* vec(const ast::Type* type, uint32_t n) const {
-            return builder->create<ast::Vector>(type, n);
-        }
+        /// @return a @p n element vector of @p type
+        ast::Type vec(ast::Type type, uint32_t n) const { return vec(builder->source_, type, n); }
 
         /// @param source the Source of the node
         /// @param type vector subtype
         /// @param n vector width in elements
-        /// @return the tint AST type for a `n`-element vector of `type`.
-        const ast::Vector* vec(const Source& source, const ast::Type* type, uint32_t n) const {
-            return builder->create<ast::Vector>(source, type, n);
+        /// @return a @p n element vector of @p type
+        ast::Type vec(const Source& source, ast::Type type, uint32_t n) const {
+            switch (n) {
+                case 2:
+                    return vec2(source, type);
+                case 3:
+                    return vec3(source, type);
+                case 4:
+                    return vec4(source, type);
+            }
+            TINT_ICE(ProgramBuilder, builder->Diagnostics()) << "invalid vector width " << n;
+            return ast::Type{};
         }
 
         /// @param type vector subtype
-        /// @return the tint AST type for a 2-element vector of `type`.
-        const ast::Vector* vec2(const ast::Type* type) const { return vec(type, 2u); }
+        /// @return a 2-element vector of @p type
+        ast::Type vec2(ast::Type type) const { return vec2(builder->source_, type); }
 
         /// @param source the vector source
         /// @param type vector subtype
-        /// @return the tint AST type for a 2-element vector of `type`.
-        const ast::Vector* vec2(const Source& source, const ast::Type* type) const {
-            return vec(source, type, 2u);
+        /// @return a 2-element vector of @p type
+        ast::Type vec2(const Source& source, ast::Type type) const {
+            return (*this)(source, "vec2", type);
         }
 
         /// @param type vector subtype
-        /// @return the tint AST type for a 3-element vector of `type`.
-        const ast::Vector* vec3(const ast::Type* type) const { return vec(type, 3u); }
+        /// @return a 3-element vector of @p type
+        ast::Type vec3(ast::Type type) const { return vec3(builder->source_, type); }
 
         /// @param source the vector source
         /// @param type vector subtype
-        /// @return the tint AST type for a 3-element vector of `type`.
-        const ast::Vector* vec3(const Source& source, const ast::Type* type) const {
-            return vec(source, type, 3u);
+        /// @return a 3-element vector of @p type
+        ast::Type vec3(const Source& source, ast::Type type) const {
+            return (*this)(source, "vec3", type);
         }
 
         /// @param type vector subtype
-        /// @return the tint AST type for a 4-element vector of `type`.
-        const ast::Vector* vec4(const ast::Type* type) const { return vec(type, 4u); }
+        /// @return a 4-element vector of @p type
+        ast::Type vec4(ast::Type type) const { return vec4(builder->source_, type); }
 
         /// @param source the vector source
         /// @param type vector subtype
-        /// @return the tint AST type for a 4-element vector of `type`.
-        const ast::Vector* vec4(const Source& source, const ast::Type* type) const {
-            return vec(source, type, 4u);
+        /// @return a 4-element vector of @p type
+        ast::Type vec4(const Source& source, ast::Type type) const {
+            return (*this)(source, "vec4", type);
+        }
+
+        /// @param source the Source of the node
+        /// @return a 2-element vector of the type `T`
+        template <typename T>
+        ast::Type vec2(const Source& source) const {
+            if constexpr (IsInferOrAbstract<T>) {
+                return (*this)(source, "vec2");
+            } else {
+                return (*this)(source, "vec2", Of<T>());
+            }
+        }
+
+        /// @param source the Source of the node
+        /// @return a 3-element vector of the type `T`
+        template <typename T>
+        ast::Type vec3(const Source& source) const {
+            if constexpr (IsInferOrAbstract<T>) {
+                return (*this)(source, "vec3");
+            } else {
+                return (*this)(source, "vec3", Of<T>());
+            }
+        }
+
+        /// @param source the Source of the node
+        /// @return a 4-element vector of the type `T`
+        template <typename T>
+        ast::Type vec4(const Source& source) const {
+            if constexpr (IsInferOrAbstract<T>) {
+                return (*this)(source, "vec4");
+            } else {
+                return (*this)(source, "vec4", Of<T>());
+            }
+        }
+
+        /// @return a 2-element vector of the type `T`
+        template <typename T>
+        ast::Type vec2() const {
+            return vec2<T>(builder->source_);
+        }
+
+        /// @return a 3-element vector of the type `T`
+        template <typename T>
+        ast::Type vec3() const {
+            return vec3<T>(builder->source_);
+        }
+
+        /// @return a 4-element vector of the type `T`
+        template <typename T>
+        ast::Type vec4() const {
+            return vec4<T>(builder->source_);
+        }
+
+        /// @param source the Source of the node
+        /// @param n vector width in elements
+        /// @return a @p n element vector of @p type
+        template <typename T>
+        ast::Type vec(const Source& source, uint32_t n) const {
+            switch (n) {
+                case 2:
+                    return vec2<T>(source);
+                case 3:
+                    return vec3<T>(source);
+                case 4:
+                    return vec4<T>(source);
+            }
+            TINT_ICE(ProgramBuilder, builder->Diagnostics()) << "invalid vector width " << n;
+            return ast::Type{};
+        }
+
+        /// @return a @p N element vector of @p type
+        template <typename T, uint32_t N>
+        ast::Type vec() const {
+            return vec<T>(builder->source_, N);
         }
 
         /// @param n vector width in elements
-        /// @return the tint AST type for a `n`-element vector of `type`.
+        /// @return a @p n element vector of @p type
         template <typename T>
-        const ast::Vector* vec(uint32_t n) const {
-            return vec(Of<T>(), n);
-        }
-
-        /// @return the tint AST type for a 2-element vector of the C type `T`.
-        template <typename T>
-        const ast::Vector* vec2() const {
-            return vec2(Of<T>());
-        }
-
-        /// @param source the Source of the node
-        /// @return the tint AST type for a 2-element vector of the C type `T`.
-        template <typename T>
-        const ast::Vector* vec2(const Source& source) const {
-            return vec2(source, Of<T>());
-        }
-
-        /// @return the tint AST type for a 3-element vector of the C type `T`.
-        template <typename T>
-        const ast::Vector* vec3() const {
-            return vec3(Of<T>());
-        }
-
-        /// @param source the Source of the node
-        /// @return the tint AST type for a 3-element vector of the C type `T`.
-        template <typename T>
-        const ast::Vector* vec3(const Source& source) const {
-            return vec3(source, Of<T>());
-        }
-
-        /// @return the tint AST type for a 4-element vector of the C type `T`.
-        template <typename T>
-        const ast::Vector* vec4() const {
-            return vec4(Of<T>());
-        }
-
-        /// @param source the Source of the node
-        /// @return the tint AST type for a 4-element vector of the C type `T`.
-        template <typename T>
-        const ast::Vector* vec4(const Source& source) const {
-            return vec4(source, Of<T>());
+        ast::Type vec(uint32_t n) const {
+            return vec<T>(builder->source_, n);
         }
 
         /// @param type matrix subtype
         /// @param columns number of columns for the matrix
         /// @param rows number of rows for the matrix
-        /// @return the tint AST type for a matrix of `type`
-        const ast::Matrix* mat(const ast::Type* type, uint32_t columns, uint32_t rows) const {
-            return builder->create<ast::Matrix>(type, rows, columns);
+        /// @return a matrix of @p type
+        ast::Type mat(ast::Type type, uint32_t columns, uint32_t rows) const {
+            return mat(builder->source_, type, columns, rows);
         }
 
         /// @param source the Source of the node
         /// @param type matrix subtype
         /// @param columns number of columns for the matrix
         /// @param rows number of rows for the matrix
-        /// @return the tint AST type for a matrix of `type`
-        const ast::Matrix* mat(const Source& source,
-                               const ast::Type* type,
-                               uint32_t columns,
-                               uint32_t rows) const {
-            return builder->create<ast::Matrix>(source, type, rows, columns);
+        /// @return a matrix of @p type
+        ast::Type mat(const Source& source, ast::Type type, uint32_t columns, uint32_t rows) const {
+            if (TINT_LIKELY(columns >= 2 && columns <= 4 && rows >= 2 && rows <= 4)) {
+                static constexpr const char* names[] = {
+                    "mat2x2", "mat2x3", "mat2x4",  //
+                    "mat3x2", "mat3x3", "mat3x4",  //
+                    "mat4x2", "mat4x3", "mat4x4",  //
+                };
+                auto i = (columns - 2) * 3 + (rows - 2);
+                return (*this)(source, names[i], type);
+            }
+            TINT_ICE(ProgramBuilder, builder->Diagnostics())
+                << "invalid matrix dimensions " << columns << "x" << rows;
+            return ast::Type{};
         }
 
         /// @param type matrix subtype
-        /// @return the tint AST type for a 2x3 matrix of `type`.
-        const ast::Matrix* mat2x2(const ast::Type* type) const { return mat(type, 2u, 2u); }
+        /// @return a 2x3 matrix of @p type.
+        ast::Type mat2x2(ast::Type type) const { return (*this)("mat2x2", type); }
 
         /// @param type matrix subtype
-        /// @return the tint AST type for a 2x3 matrix of `type`.
-        const ast::Matrix* mat2x3(const ast::Type* type) const { return mat(type, 2u, 3u); }
+        /// @return a 2x3 matrix of @p type.
+        ast::Type mat2x3(ast::Type type) const { return (*this)("mat2x3", type); }
 
         /// @param type matrix subtype
-        /// @return the tint AST type for a 2x4 matrix of `type`.
-        const ast::Matrix* mat2x4(const ast::Type* type) const { return mat(type, 2u, 4u); }
+        /// @return a 2x4 matrix of @p type.
+        ast::Type mat2x4(ast::Type type) const { return (*this)("mat2x4", type); }
 
         /// @param type matrix subtype
-        /// @return the tint AST type for a 3x2 matrix of `type`.
-        const ast::Matrix* mat3x2(const ast::Type* type) const { return mat(type, 3u, 2u); }
+        /// @return a 3x2 matrix of @p type.
+        ast::Type mat3x2(ast::Type type) const { return (*this)("mat3x2", type); }
 
         /// @param type matrix subtype
-        /// @return the tint AST type for a 3x3 matrix of `type`.
-        const ast::Matrix* mat3x3(const ast::Type* type) const { return mat(type, 3u, 3u); }
+        /// @return a 3x3 matrix of @p type.
+        ast::Type mat3x3(ast::Type type) const { return (*this)("mat3x3", type); }
 
         /// @param type matrix subtype
-        /// @return the tint AST type for a 3x4 matrix of `type`.
-        const ast::Matrix* mat3x4(const ast::Type* type) const { return mat(type, 3u, 4u); }
+        /// @return a 3x4 matrix of @p type.
+        ast::Type mat3x4(ast::Type type) const { return (*this)("mat3x4", type); }
 
         /// @param type matrix subtype
-        /// @return the tint AST type for a 4x2 matrix of `type`.
-        const ast::Matrix* mat4x2(const ast::Type* type) const { return mat(type, 4u, 2u); }
+        /// @return a 4x2 matrix of @p type.
+        ast::Type mat4x2(ast::Type type) const { return (*this)("mat4x2", type); }
 
         /// @param type matrix subtype
-        /// @return the tint AST type for a 4x3 matrix of `type`.
-        const ast::Matrix* mat4x3(const ast::Type* type) const { return mat(type, 4u, 3u); }
+        /// @return a 4x3 matrix of @p type.
+        ast::Type mat4x3(ast::Type type) const { return (*this)("mat4x3", type); }
 
         /// @param type matrix subtype
-        /// @return the tint AST type for a 4x4 matrix of `type`.
-        const ast::Matrix* mat4x4(const ast::Type* type) const { return mat(type, 4u, 4u); }
+        /// @return a 4x4 matrix of @p type.
+        ast::Type mat4x4(ast::Type type) const { return (*this)("mat4x4", type); }
+
+        /// @param source the source of the type
+        /// @return a 2x2 matrix of the type `T`
+        template <typename T>
+        ast::Type mat2x2(const Source& source) const {
+            if constexpr (IsInferOrAbstract<T>) {
+                return (*this)(source, "mat2x2");
+            } else {
+                return (*this)(source, "mat2x2", Of<T>());
+            }
+        }
+
+        /// @param source the source of the type
+        /// @return a 2x3 matrix of the type `T`
+        template <typename T>
+        ast::Type mat2x3(const Source& source) const {
+            if constexpr (IsInferOrAbstract<T>) {
+                return (*this)(source, "mat2x3");
+            } else {
+                return (*this)(source, "mat2x3", Of<T>());
+            }
+        }
+
+        /// @param source the source of the type
+        /// @return a 2x4 matrix of the type `T`
+        template <typename T>
+        ast::Type mat2x4(const Source& source) const {
+            if constexpr (IsInferOrAbstract<T>) {
+                return (*this)(source, "mat2x4");
+            } else {
+                return (*this)(source, "mat2x4", Of<T>());
+            }
+        }
+
+        /// @param source the source of the type
+        /// @return a 3x2 matrix of the type `T`
+        template <typename T>
+        ast::Type mat3x2(const Source& source) const {
+            if constexpr (IsInferOrAbstract<T>) {
+                return (*this)(source, "mat3x2");
+            } else {
+                return (*this)(source, "mat3x2", Of<T>());
+            }
+        }
+
+        /// @param source the source of the type
+        /// @return a 3x3 matrix of the type `T`
+        template <typename T>
+        ast::Type mat3x3(const Source& source) const {
+            if constexpr (IsInferOrAbstract<T>) {
+                return (*this)(source, "mat3x3");
+            } else {
+                return (*this)(source, "mat3x3", Of<T>());
+            }
+        }
+
+        /// @param source the source of the type
+        /// @return a 3x4 matrix of the type `T`
+        template <typename T>
+        ast::Type mat3x4(const Source& source) const {
+            if constexpr (IsInferOrAbstract<T>) {
+                return (*this)(source, "mat3x4");
+            } else {
+                return (*this)(source, "mat3x4", Of<T>());
+            }
+        }
+
+        /// @param source the source of the type
+        /// @return a 4x2 matrix of the type `T`
+        template <typename T>
+        ast::Type mat4x2(const Source& source) const {
+            if constexpr (IsInferOrAbstract<T>) {
+                return (*this)(source, "mat4x2");
+            } else {
+                return (*this)(source, "mat4x2", Of<T>());
+            }
+        }
+
+        /// @param source the source of the type
+        /// @return a 4x3 matrix of the type `T`
+        template <typename T>
+        ast::Type mat4x3(const Source& source) const {
+            if constexpr (IsInferOrAbstract<T>) {
+                return (*this)(source, "mat4x3");
+            } else {
+                return (*this)(source, "mat4x3", Of<T>());
+            }
+        }
+
+        /// @param source the source of the type
+        /// @return a 4x4 matrix of the type `T`
+        template <typename T>
+        ast::Type mat4x4(const Source& source) const {
+            if constexpr (IsInferOrAbstract<T>) {
+                return (*this)(source, "mat4x4");
+            } else {
+                return (*this)(source, "mat4x4", Of<T>());
+            }
+        }
+
+        /// @return a 2x2 matrix of the type `T`
+        template <typename T>
+        ast::Type mat2x2() const {
+            return mat2x2<T>(builder->source_);
+        }
+
+        /// @return a 2x3 matrix of the type `T`
+        template <typename T>
+        ast::Type mat2x3() const {
+            return mat2x3<T>(builder->source_);
+        }
+
+        /// @return a 2x4 matrix of the type `T`
+        template <typename T>
+        ast::Type mat2x4() const {
+            return mat2x4<T>(builder->source_);
+        }
+
+        /// @return a 3x2 matrix of the type `T`
+        template <typename T>
+        ast::Type mat3x2() const {
+            return mat3x2<T>(builder->source_);
+        }
+
+        /// @return a 3x3 matrix of the type `T`
+        template <typename T>
+        ast::Type mat3x3() const {
+            return mat3x3<T>(builder->source_);
+        }
+
+        /// @return a 3x4 matrix of the type `T`
+        template <typename T>
+        ast::Type mat3x4() const {
+            return mat3x4<T>(builder->source_);
+        }
+
+        /// @return a 4x2 matrix of the type `T`
+        template <typename T>
+        ast::Type mat4x2() const {
+            return mat4x2<T>(builder->source_);
+        }
+
+        /// @return a 4x3 matrix of the type `T`
+        template <typename T>
+        ast::Type mat4x3() const {
+            return mat4x3<T>(builder->source_);
+        }
+
+        /// @return a 4x4 matrix of the type `T`
+        template <typename T>
+        ast::Type mat4x4() const {
+            return mat4x4<T>(builder->source_);
+        }
+
+        /// @param source the Source of the node
+        /// @param columns number of columns for the matrix
+        /// @param rows number of rows for the matrix
+        /// @return a matrix of @p type
+        template <typename T>
+        ast::Type mat(const Source& source, uint32_t columns, uint32_t rows) const {
+            switch ((columns - 2) * 3 + (rows - 2)) {
+                case 0:
+                    return mat2x2<T>(source);
+                case 1:
+                    return mat2x3<T>(source);
+                case 2:
+                    return mat2x4<T>(source);
+                case 3:
+                    return mat3x2<T>(source);
+                case 4:
+                    return mat3x3<T>(source);
+                case 5:
+                    return mat3x4<T>(source);
+                case 6:
+                    return mat4x2<T>(source);
+                case 7:
+                    return mat4x3<T>(source);
+                case 8:
+                    return mat4x4<T>(source);
+                default:
+                    TINT_ICE(ProgramBuilder, builder->Diagnostics())
+                        << "invalid matrix dimensions " << columns << "x" << rows;
+                    return ast::Type{};
+            }
+        }
 
         /// @param columns number of columns for the matrix
         /// @param rows number of rows for the matrix
-        /// @return the tint AST type for a matrix of `type`
+        /// @return a matrix of @p type
         template <typename T>
-        const ast::Matrix* mat(uint32_t columns, uint32_t rows) const {
-            return mat(Of<T>(), columns, rows);
+        ast::Type mat(uint32_t columns, uint32_t rows) const {
+            return mat<T>(builder->source_, columns, rows);
         }
 
-        /// @return the tint AST type for a 2x3 matrix of the C type `T`.
-        template <typename T>
-        const ast::Matrix* mat2x2() const {
-            return mat2x2(Of<T>());
+        /// @return a matrix of @p type
+        template <typename T, uint32_t COLUMNS, uint32_t ROWS>
+        ast::Type mat() const {
+            return mat<T>(builder->source_, COLUMNS, ROWS);
         }
 
-        /// @return the tint AST type for a 2x3 matrix of the C type `T`.
-        template <typename T>
-        const ast::Matrix* mat2x3() const {
-            return mat2x3(Of<T>());
-        }
-
-        /// @return the tint AST type for a 2x4 matrix of the C type `T`.
-        template <typename T>
-        const ast::Matrix* mat2x4() const {
-            return mat2x4(Of<T>());
-        }
-
-        /// @return the tint AST type for a 3x2 matrix of the C type `T`.
-        template <typename T>
-        const ast::Matrix* mat3x2() const {
-            return mat3x2(Of<T>());
-        }
-
-        /// @return the tint AST type for a 3x3 matrix of the C type `T`.
-        template <typename T>
-        const ast::Matrix* mat3x3() const {
-            return mat3x3(Of<T>());
-        }
-
-        /// @return the tint AST type for a 3x4 matrix of the C type `T`.
-        template <typename T>
-        const ast::Matrix* mat3x4() const {
-            return mat3x4(Of<T>());
-        }
-
-        /// @return the tint AST type for a 4x2 matrix of the C type `T`.
-        template <typename T>
-        const ast::Matrix* mat4x2() const {
-            return mat4x2(Of<T>());
-        }
-
-        /// @return the tint AST type for a 4x3 matrix of the C type `T`.
-        template <typename T>
-        const ast::Matrix* mat4x3() const {
-            return mat4x3(Of<T>());
-        }
-
-        /// @return the tint AST type for a 4x4 matrix of the C type `T`.
-        template <typename T>
-        const ast::Matrix* mat4x4() const {
-            return mat4x4(Of<T>());
+        /// @param subtype the array element type
+        /// @param attrs the optional attributes for the array
+        /// @return an array of type `T`
+        ast::Type array(ast::Type subtype,
+                        utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
+            return array(builder->source_, subtype, std::move(attrs));
         }
 
         /// @param subtype the array element type
         /// @param n the array size. nullptr represents a runtime-array
         /// @param attrs the optional attributes for the array
-        /// @return the array of size `n` of type `T`
-        template <typename COUNT = std::nullptr_t>
-        const ast::Array* array(
-            const ast::Type* subtype,
-            COUNT&& n = nullptr,
-            utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
+        /// @return an array of size `n` of type `T`
+        template <typename COUNT, typename = DisableIfVectorLike<COUNT>>
+        ast::Type array(ast::Type subtype,
+                        COUNT&& n,
+                        utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
             return array(builder->source_, subtype, std::forward<COUNT>(n), std::move(attrs));
         }
 
         /// @param source the Source of the node
         /// @param subtype the array element type
+        /// @param attrs the optional attributes for the array
+        /// @return an array of type `T`
+        ast::Type array(const Source& source,
+                        ast::Type subtype,
+                        utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
+            return ast::Type{builder->Expr(
+                builder->create<ast::TemplatedIdentifier>(source, builder->Sym("array"),
+                                                          utils::Vector{
+                                                              subtype.expr,
+                                                          },
+                                                          std::move(attrs)))};
+        }
+
+        /// @param source the Source of the node
+        /// @param subtype the array element type
         /// @param n the array size. nullptr represents a runtime-array
         /// @param attrs the optional attributes for the array
-        /// @return the array of size `n` of type `T`
-        template <typename COUNT = std::nullptr_t>
-        const ast::Array* array(
-            const Source& source,
-            const ast::Type* subtype,
-            COUNT&& n = nullptr,
-            utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
-            if constexpr (std::is_same_v<traits::Decay<COUNT>, std::nullptr_t>) {
-                return builder->create<ast::Array>(source, subtype, nullptr, std::move(attrs));
-            } else {
-                return builder->create<ast::Array>(
-                    source, subtype, builder->Expr(std::forward<COUNT>(n)), std::move(attrs));
-            }
+        /// @return an array of size `n` of type `T`
+        template <typename COUNT, typename = DisableIfVectorLike<COUNT>>
+        ast::Type array(const Source& source,
+                        ast::Type subtype,
+                        COUNT&& n,
+                        utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
+            return ast::Type{builder->Expr(
+                builder->create<ast::TemplatedIdentifier>(source, builder->Sym("array"),
+                                                          utils::Vector{
+                                                              subtype.expr,
+                                                              builder->Expr(std::forward<COUNT>(n)),
+                                                          },
+                                                          std::move(attrs)))};
         }
 
-        /// @param attrs the optional attributes for the array
-        /// @return the runtime-sized array of type `T`
-        template <typename T>
-        const ast::Array* array(
-            utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
-            return array(Of<T>(), nullptr, std::move(attrs));
-        }
-
-        /// @param attrs the optional attributes for the array
-        /// @return the array of size `N` of type `T`
-        template <typename T, int N>
-        const ast::Array* array(
-            utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
-            return array(Of<T>(), tint::u32(N), std::move(attrs));
-        }
-
-        /// Creates a type name
-        /// @param name the name
-        /// @param args the optional template arguments
-        /// @returns the type name
-        template <typename NAME, typename... ARGS, typename _ = DisableIfSource<NAME>>
-        const ast::TypeName* operator()(NAME&& name, ARGS&&... args) const {
-            return (*this)(builder->source_, std::forward<NAME>(name), std::forward<ARGS>(args)...);
-        }
-
-        /// Creates a type name
         /// @param source the Source of the node
-        /// @param name the name
-        /// @param args the optional template arguments
-        /// @returns the type name
-        template <typename NAME, typename... ARGS>
-        const ast::TypeName* operator()(const Source& source, NAME&& name, ARGS&&... args) const {
-            return builder->create<ast::TypeName>(
-                source,
-                builder->Ident(source, std::forward<NAME>(name), std::forward<ARGS>(args)...));
+        /// @return a inferred-size or runtime-sized array of type `T`
+        template <typename T, typename = EnableIfInferOrAbstract<T>>
+        ast::Type array(const Source& source) const {
+            return (*this)(source, "array");
+        }
+
+        /// @return a inferred-size or runtime-sized array of type `T`
+        template <typename T, typename = EnableIfInferOrAbstract<T>>
+        ast::Type array() const {
+            return array<T>(builder->source_);
+        }
+
+        /// @param source the Source of the node
+        /// @param attrs the optional attributes for the array
+        /// @return a inferred-size or runtime-sized array of type `T`
+        template <typename T, typename = DisableIfInferOrAbstract<T>>
+        ast::Type array(const Source& source,
+                        utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
+            return ast::Type{builder->Expr(
+                builder->create<ast::TemplatedIdentifier>(source, builder->Sym("array"),
+                                                          utils::Vector<const ast::Expression*, 1>{
+                                                              Of<T>().expr,
+                                                          },
+                                                          std::move(attrs)))};
+        }
+
+        /// @param attrs the optional attributes for the array
+        /// @return a inferred-size or runtime-sized array of type `T`
+        template <typename T, typename = DisableIfInferOrAbstract<T>>
+        ast::Type array(utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
+            return array<T>(builder->source_, std::move(attrs));
+        }
+
+        /// @param source the Source of the node
+        /// @param attrs the optional attributes for the array
+        /// @return an array of size `N` of type `T`
+        template <typename T, int N>
+        ast::Type array(const Source& source,
+                        utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
+            static_assert(!IsInferOrAbstract<T>, "arrays with a count cannot be inferred");
+            return array(source, Of<T>(), tint::u32(N), std::move(attrs));
+        }
+
+        /// @param attrs the optional attributes for the array
+        /// @return an array of size `N` of type `T`
+        template <typename T, int N>
+        ast::Type array(utils::VectorRef<const ast::Attribute*> attrs = utils::Empty) const {
+            static_assert(!IsInferOrAbstract<T>, "arrays with a count cannot be inferred");
+            return array<T, N>(builder->source_, std::move(attrs));
         }
 
         /// Creates an alias type
@@ -880,7 +1171,7 @@
         /// @param type the alias type
         /// @returns the alias pointer
         template <typename NAME>
-        const ast::Alias* alias(NAME&& name, const ast::Type* type) const {
+        const ast::Alias* alias(NAME&& name, ast::Type type) const {
             return alias(builder->source_, std::forward<NAME>(name), type);
         }
 
@@ -890,7 +1181,7 @@
         /// @param type the alias type
         /// @returns the alias pointer
         template <typename NAME>
-        const ast::Alias* alias(const Source& source, NAME&& name, const ast::Type* type) const {
+        const ast::Alias* alias(const Source& source, NAME&& name, ast::Type type) const {
             return builder->create<ast::Alias>(source, builder->Ident(std::forward<NAME>(name)),
                                                type);
         }
@@ -899,10 +1190,10 @@
         /// @param address_space the address space of the pointer
         /// @param access the optional access control of the pointer
         /// @return the pointer to `type` with the given type::AddressSpace
-        const ast::Pointer* pointer(const ast::Type* type,
-                                    type::AddressSpace address_space,
-                                    type::Access access = type::Access::kUndefined) const {
-            return builder->create<ast::Pointer>(type, address_space, access);
+        ast::Type pointer(ast::Type type,
+                          type::AddressSpace address_space,
+                          type::Access access = type::Access::kUndefined) const {
+            return pointer(builder->source_, type, address_space, access);
         }
 
         /// @param source the Source of the node
@@ -910,20 +1201,24 @@
         /// @param address_space the address space of the pointer
         /// @param access the optional access control of the pointer
         /// @return the pointer to `type` with the given type::AddressSpace
-        const ast::Pointer* pointer(const Source& source,
-                                    const ast::Type* type,
-                                    type::AddressSpace address_space,
-                                    type::Access access = type::Access::kUndefined) const {
-            return builder->create<ast::Pointer>(source, type, address_space, access);
+        ast::Type pointer(const Source& source,
+                          ast::Type type,
+                          type::AddressSpace address_space,
+                          type::Access access = type::Access::kUndefined) const {
+            if (access != type::Access::kUndefined) {
+                return (*this)(source, "ptr", address_space, type, access);
+            } else {
+                return (*this)(source, "ptr", address_space, type);
+            }
         }
 
         /// @param address_space the address space of the pointer
         /// @param access the optional access control of the pointer
         /// @return the pointer to type `T` with the given type::AddressSpace.
         template <typename T>
-        const ast::Pointer* pointer(type::AddressSpace address_space,
-                                    type::Access access = type::Access::kUndefined) const {
-            return pointer(Of<T>(), address_space, access);
+        ast::Type pointer(type::AddressSpace address_space,
+                          type::Access access = type::Access::kUndefined) const {
+            return pointer<T>(builder->source_, address_space, access);
         }
 
         /// @param source the Source of the node
@@ -931,41 +1226,41 @@
         /// @param access the optional access control of the pointer
         /// @return the pointer to type `T` with the given type::AddressSpace.
         template <typename T>
-        const ast::Pointer* pointer(const Source& source,
-                                    type::AddressSpace address_space,
-                                    type::Access access = type::Access::kUndefined) const {
-            return pointer(source, Of<T>(), address_space, access);
+        ast::Type pointer(const Source& source,
+                          type::AddressSpace address_space,
+                          type::Access access = type::Access::kUndefined) const {
+            if (access != type::Access::kUndefined) {
+                return (*this)(source, "ptr", address_space, Of<T>(), access);
+            } else {
+                return (*this)(source, "ptr", address_space, Of<T>());
+            }
         }
 
         /// @param source the Source of the node
         /// @param type the type of the atomic
         /// @return the atomic to `type`
-        const ast::Atomic* atomic(const Source& source, const ast::Type* type) const {
-            return builder->create<ast::Atomic>(source, type);
+        ast::Type atomic(const Source& source, ast::Type type) const {
+            return (*this)(source, "atomic", type);
         }
 
         /// @param type the type of the atomic
         /// @return the atomic to `type`
-        const ast::Atomic* atomic(const ast::Type* type) const {
-            return builder->create<ast::Atomic>(type);
-        }
+        ast::Type atomic(ast::Type type) const { return (*this)("atomic", type); }
 
         /// @return the atomic to type `T`
         template <typename T>
-        const ast::Atomic* atomic() const {
+        ast::Type atomic() const {
             return atomic(Of<T>());
         }
 
         /// @param kind the kind of sampler
-        /// @returns the sampler typename
-        const ast::TypeName* sampler(type::SamplerKind kind) const {
-            return sampler(builder->source_, kind);
-        }
+        /// @returns the sampler
+        ast::Type sampler(type::SamplerKind kind) const { return sampler(builder->source_, kind); }
 
         /// @param source the Source of the node
         /// @param kind the kind of sampler
-        /// @returns the sampler typename
-        const ast::TypeName* sampler(const Source& source, type::SamplerKind kind) const {
+        /// @returns the sampler
+        ast::Type sampler(const Source& source, type::SamplerKind kind) const {
             switch (kind) {
                 case type::SamplerKind::kSampler:
                     return (*this)(source, "sampler");
@@ -973,20 +1268,19 @@
                     return (*this)(source, "sampler_comparison");
             }
             TINT_ICE(ProgramBuilder, builder->Diagnostics()) << "invalid sampler kind " << kind;
-            return nullptr;
+            return ast::Type{};
         }
 
         /// @param dims the dimensionality of the texture
-        /// @returns the depth texture typename
-        const ast::TypeName* depth_texture(type::TextureDimension dims) const {
+        /// @returns the depth texture
+        ast::Type depth_texture(type::TextureDimension dims) const {
             return depth_texture(builder->source_, dims);
         }
 
         /// @param source the Source of the node
         /// @param dims the dimensionality of the texture
-        /// @returns the depth texture typename
-        const ast::TypeName* depth_texture(const Source& source,
-                                           type::TextureDimension dims) const {
+        /// @returns the depth texture
+        ast::Type depth_texture(const Source& source, type::TextureDimension dims) const {
             switch (dims) {
                 case type::TextureDimension::k2d:
                     return (*this)(source, "texture_depth_2d");
@@ -1001,71 +1295,92 @@
             }
             TINT_ICE(ProgramBuilder, builder->Diagnostics())
                 << "invalid depth_texture dimensions: " << dims;
-            return nullptr;
+            return ast::Type{};
         }
 
         /// @param dims the dimensionality of the texture
-        /// @returns the multisampled depth texture typename
-        const ast::TypeName* depth_multisampled_texture(type::TextureDimension dims) const {
+        /// @returns the multisampled depth texture
+        ast::Type depth_multisampled_texture(type::TextureDimension dims) const {
             return depth_multisampled_texture(builder->source_, dims);
         }
 
         /// @param source the Source of the node
         /// @param dims the dimensionality of the texture
-        /// @returns the multisampled depth texture typename
-        const ast::TypeName* depth_multisampled_texture(const Source& source,
-                                                        type::TextureDimension dims) const {
+        /// @returns the multisampled depth texture
+        ast::Type depth_multisampled_texture(const Source& source,
+                                             type::TextureDimension dims) const {
             if (dims == type::TextureDimension::k2d) {
                 return (*this)(source, "texture_depth_multisampled_2d");
             }
             TINT_ICE(ProgramBuilder, builder->Diagnostics())
                 << "invalid depth_multisampled_texture dimensions: " << dims;
-            return nullptr;
+            return ast::Type{};
         }
 
         /// @param dims the dimensionality of the texture
         /// @param subtype the texture subtype.
         /// @returns the sampled texture
-        const ast::SampledTexture* sampled_texture(type::TextureDimension dims,
-                                                   const ast::Type* subtype) const {
-            return builder->create<ast::SampledTexture>(dims, subtype);
+        ast::Type sampled_texture(type::TextureDimension dims, ast::Type subtype) const {
+            return sampled_texture(builder->source_, dims, subtype);
         }
 
         /// @param source the Source of the node
         /// @param dims the dimensionality of the texture
         /// @param subtype the texture subtype.
         /// @returns the sampled texture
-        const ast::SampledTexture* sampled_texture(const Source& source,
-                                                   type::TextureDimension dims,
-                                                   const ast::Type* subtype) const {
-            return builder->create<ast::SampledTexture>(source, dims, subtype);
+        ast::Type sampled_texture(const Source& source,
+                                  type::TextureDimension dims,
+                                  ast::Type subtype) const {
+            switch (dims) {
+                case type::TextureDimension::k1d:
+                    return (*this)(source, "texture_1d", subtype);
+                case type::TextureDimension::k2d:
+                    return (*this)(source, "texture_2d", subtype);
+                case type::TextureDimension::k3d:
+                    return (*this)(source, "texture_3d", subtype);
+                case type::TextureDimension::k2dArray:
+                    return (*this)(source, "texture_2d_array", subtype);
+                case type::TextureDimension::kCube:
+                    return (*this)(source, "texture_cube", subtype);
+                case type::TextureDimension::kCubeArray:
+                    return (*this)(source, "texture_cube_array", subtype);
+                default:
+                    break;
+            }
+            TINT_ICE(ProgramBuilder, builder->Diagnostics())
+                << "invalid sampled_texture dimensions: " << dims;
+            return ast::Type{};
         }
 
         /// @param dims the dimensionality of the texture
         /// @param subtype the texture subtype.
         /// @returns the multisampled texture
-        const ast::MultisampledTexture* multisampled_texture(type::TextureDimension dims,
-                                                             const ast::Type* subtype) const {
-            return builder->create<ast::MultisampledTexture>(dims, subtype);
+        ast::Type multisampled_texture(type::TextureDimension dims, ast::Type subtype) const {
+            return multisampled_texture(builder->source_, dims, subtype);
         }
 
         /// @param source the Source of the node
         /// @param dims the dimensionality of the texture
         /// @param subtype the texture subtype.
         /// @returns the multisampled texture
-        const ast::MultisampledTexture* multisampled_texture(const Source& source,
-                                                             type::TextureDimension dims,
-                                                             const ast::Type* subtype) const {
-            return builder->create<ast::MultisampledTexture>(source, dims, subtype);
+        ast::Type multisampled_texture(const Source& source,
+                                       type::TextureDimension dims,
+                                       ast::Type subtype) const {
+            if (dims == type::TextureDimension::k2d) {
+                return (*this)(source, "texture_multisampled_2d", subtype);
+            }
+            TINT_ICE(ProgramBuilder, builder->Diagnostics())
+                << "invalid multisampled_texture dimensions: " << dims;
+            return ast::Type{};
         }
 
         /// @param dims the dimensionality of the texture
         /// @param format the texel format of the texture
         /// @param access the access control of the texture
-        /// @returns the storage texture typename
-        const ast::TypeName* storage_texture(type::TextureDimension dims,
-                                             type::TexelFormat format,
-                                             type::Access access) const {
+        /// @returns the storage texture
+        ast::Type storage_texture(type::TextureDimension dims,
+                                  type::TexelFormat format,
+                                  type::Access access) const {
             return storage_texture(builder->source_, dims, format, access);
         }
 
@@ -1073,45 +1388,40 @@
         /// @param dims the dimensionality of the texture
         /// @param format the texel format of the texture
         /// @param access the access control of the texture
-        /// @returns the storage texture typename
-        const ast::TypeName* storage_texture(const Source& source,
-                                             type::TextureDimension dims,
-                                             type::TexelFormat format,
-                                             type::Access access) const {
+        /// @returns the storage texture
+        ast::Type storage_texture(const Source& source,
+                                  type::TextureDimension dims,
+                                  type::TexelFormat format,
+                                  type::Access access) const {
             switch (dims) {
                 case type::TextureDimension::k1d:
-                    return (*this)(source, "texture_storage_1d", utils::ToString(format),
-                                   utils::ToString(access));
+                    return (*this)(source, "texture_storage_1d", format, access);
                 case type::TextureDimension::k2d:
-                    return (*this)(source, "texture_storage_2d", utils::ToString(format),
-                                   utils::ToString(access));
+                    return (*this)(source, "texture_storage_2d", format, access);
                 case type::TextureDimension::k2dArray:
-                    return (*this)(source, "texture_storage_2d_array", utils::ToString(format),
-                                   utils::ToString(access));
+                    return (*this)(source, "texture_storage_2d_array", format, access);
                 case type::TextureDimension::k3d:
-                    return (*this)(source, "texture_storage_3d", utils::ToString(format),
-                                   utils::ToString(access));
+                    return (*this)(source, "texture_storage_3d", format, access);
                 default:
                     break;
             }
             TINT_ICE(ProgramBuilder, builder->Diagnostics())
-                << "invalid sampled_texture dimensions: " << dims;
-            return nullptr;
+                << "invalid storage_texture  dimensions: " << dims;
+            return ast::Type{};
         }
 
         /// @returns the external texture
-        const ast::TypeName* external_texture() const { return external_texture(builder->source_); }
+        ast::Type external_texture() const { return (*this)("texture_external"); }
 
         /// @param source the Source of the node
-        /// @returns the external texture typename
-        const ast::TypeName* external_texture(const Source& source) const {
+        /// @returns the external texture
+        ast::Type external_texture(const Source& source) const {
             return (*this)(source, "texture_external");
         }
 
-        /// Constructs a TypeName for the type declaration.
         /// @param type the type
-        /// @return either type or a pointer to a new ast::TypeName
-        const ast::TypeName* Of(const ast::TypeDecl* type) const;
+        /// @return an ast::Type of the type declaration.
+        ast::Type Of(const ast::TypeDecl* type) const { return (*this)(type->name->symbol); }
 
         /// The ProgramBuilder
         ProgramBuilder* const builder;
@@ -1121,7 +1431,7 @@
         /// contains a single static `get()` method for obtaining the corresponding
         /// AST type for the C type `T`.
         /// `get()` has the signature:
-        ///    `static const ast::Type* get(Types* t)`
+        ///    `static ast::Type get(Types* t)`
         template <typename T>
         struct CToAST {};
     };
@@ -1133,91 +1443,107 @@
     /// @return a new unnamed symbol
     Symbol Sym() { return Symbols().New(); }
 
-    /// @param name the symbol string
-    /// @return a Symbol with the given name
-    Symbol Sym(const std::string& name) { return Symbols().Register(name); }
-
+    /// Passthrough
     /// @param sym the symbol
     /// @return `sym`
     Symbol Sym(Symbol sym) { return sym; }
 
-    /// @param source the source information
+    /// @param name the symbol string
+    /// @return a Symbol with the given name
+    Symbol Sym(const std::string& name) { return Symbols().Register(name); }
+
+    /// @param enumerator the enumerator
+    /// @return a Symbol with the given enum value
+    template <typename ENUM, typename = std::enable_if_t<std::is_enum_v<std::decay_t<ENUM>>>>
+    Symbol Sym(ENUM&& enumerator) {
+        return Sym(utils::ToString(enumerator));
+    }
+
+    /// @return nullptr
+    const ast::Identifier* Ident(std::nullptr_t) { return nullptr; }
+
     /// @param identifier the identifier symbol
-    /// @param args optional templated identifier arguments
     /// @return an ast::Identifier with the given symbol
-    template <typename IDENTIFIER, typename... ARGS>
-    const auto* Ident(const Source& source, IDENTIFIER&& identifier, ARGS&&... args) {
-        Symbol sym = Sym(std::forward<IDENTIFIER>(identifier));
-        if constexpr (sizeof...(args) > 0) {
-            return create<ast::TemplatedIdentifier>(source, sym,
-                                                    ExprList(std::forward<ARGS>(args)...));
+    template <typename IDENTIFIER>
+    const ast::Identifier* Ident(IDENTIFIER&& identifier) {
+        if constexpr (traits::IsTypeOrDerived<traits::PtrElTy<IDENTIFIER>, ast::Identifier>) {
+            return identifier;  // Passthrough
         } else {
-            return create<ast::Identifier>(source, sym);
+            return Ident(source_, std::forward<IDENTIFIER>(identifier));
         }
     }
 
+    /// @param source the source information
     /// @param identifier the identifier symbol
-    /// @param args optional templated identifier arguments
     /// @return an ast::Identifier with the given symbol
+    template <typename IDENTIFIER>
+    const ast::Identifier* Ident(const Source& source, IDENTIFIER&& identifier) {
+        return create<ast::Identifier>(source, Sym(std::forward<IDENTIFIER>(identifier)));
+    }
+
+    /// @param identifier the identifier symbol
+    /// @param args the templated identifier arguments
+    /// @return an ast::TemplatedIdentifier with the given symbol and template arguments
     template <typename IDENTIFIER, typename... ARGS, typename = DisableIfSource<IDENTIFIER>>
-    const auto* Ident(IDENTIFIER&& identifier, ARGS&&... args) {
-        if constexpr (traits::IsTypeOrDerived<traits::PtrElTy<IDENTIFIER>, ast::Identifier>) {
-            static_assert(sizeof...(args) == 0);
-            return identifier;  // Pass-through
-        } else {
-            return Ident(source_, std::forward<IDENTIFIER>(identifier),
-                         std::forward<ARGS>(args)...);
-        }
+    const ast::TemplatedIdentifier* Ident(IDENTIFIER&& identifier, ARGS&&... args) {
+        return Ident(source_, std::forward<IDENTIFIER>(identifier), std::forward<ARGS>(args)...);
+    }
+
+    /// @param source the source information
+    /// @param identifier the identifier symbol
+    /// @param args the templated identifier arguments
+    /// @return an ast::TemplatedIdentifier with the given symbol and template arguments
+    template <typename IDENTIFIER, typename... ARGS>
+    const ast::TemplatedIdentifier* Ident(const Source& source,
+                                          IDENTIFIER&& identifier,
+                                          ARGS&&... args) {
+        return create<ast::TemplatedIdentifier>(source, Sym(std::forward<IDENTIFIER>(identifier)),
+                                                ExprList(std::forward<ARGS>(args)...),
+                                                utils::Empty);
     }
 
     /// @param expr the expression
-    /// @return expr
-    template <typename T>
-    traits::EnableIfIsType<T, ast::Expression>* Expr(T* expr) {
+    /// @return expr (passthrough)
+    template <typename T, typename = traits::EnableIfIsType<T, ast::Expression>>
+    const T* Expr(const T* expr) {
         return expr;
     }
 
+    /// @param type an ast::Type
+    /// @return type.expr
+    const ast::IdentifierExpression* Expr(ast::Type type) { return type.expr; }
+
+    /// @param ident the identifier
+    /// @return an ast::IdentifierExpression with the given identifier
+    const ast::IdentifierExpression* Expr(const ast::Identifier* ident) {
+        return ident ? create<ast::IdentifierExpression>(ident->source, ident) : nullptr;
+    }
+
     /// Passthrough for nullptr
     /// @return nullptr
     const ast::IdentifierExpression* Expr(std::nullptr_t) { return nullptr; }
 
-    /// @param source the source information
-    /// @param symbol the identifier symbol
-    /// @return an ast::IdentifierExpression with the given symbol
-    const ast::IdentifierExpression* Expr(const Source& source, Symbol symbol) {
-        return create<ast::IdentifierExpression>(source, Ident(source, symbol));
-    }
-
-    /// @param symbol the identifier symbol
-    /// @return an ast::IdentifierExpression with the given symbol
-    const ast::IdentifierExpression* Expr(Symbol symbol) {
-        return create<ast::IdentifierExpression>(Ident(symbol));
+    /// @param name the identifier name
+    /// @return an ast::IdentifierExpression with the given name
+    template <typename NAME, typename = EnableIfIdentifierLike<NAME>>
+    const ast::IdentifierExpression* Expr(NAME&& name) {
+        auto* ident = Ident(source_, name);
+        return create<ast::IdentifierExpression>(ident->source, ident);
     }
 
     /// @param source the source information
     /// @param name the identifier name
     /// @return an ast::IdentifierExpression with the given name
-    const ast::IdentifierExpression* Expr(const Source& source, const char* name) {
+    template <typename NAME, typename = EnableIfIdentifierLike<NAME>>
+    const ast::IdentifierExpression* Expr(const Source& source, NAME&& name) {
         return create<ast::IdentifierExpression>(source, Ident(source, name));
     }
 
-    /// @param name the identifier name
-    /// @return an ast::IdentifierExpression with the given name
-    const ast::IdentifierExpression* Expr(const char* name) {
-        return create<ast::IdentifierExpression>(Ident(name));
-    }
-
-    /// @param source the source information
-    /// @param name the identifier name
-    /// @return an ast::IdentifierExpression with the given name
-    const ast::IdentifierExpression* Expr(const Source& source, const std::string& name) {
-        return create<ast::IdentifierExpression>(source, Ident(source, name));
-    }
-
-    /// @param name the identifier name
-    /// @return an ast::IdentifierExpression with the given name
-    const ast::IdentifierExpression* Expr(const std::string& name) {
-        return create<ast::IdentifierExpression>(Ident(name));
+    /// @param variable the AST variable
+    /// @return an ast::IdentifierExpression with the variable's symbol
+    const ast::IdentifierExpression* Expr(const ast::Variable* variable) {
+        auto* ident = Ident(variable->source, variable->name->symbol);
+        return create<ast::IdentifierExpression>(ident->source, ident);
     }
 
     /// @param source the source information
@@ -1227,19 +1553,6 @@
         return create<ast::IdentifierExpression>(source, Ident(source, variable->name->symbol));
     }
 
-    /// @param variable the AST variable
-    /// @return an ast::IdentifierExpression with the variable's symbol
-    const ast::IdentifierExpression* Expr(const ast::Variable* variable) {
-        return create<ast::IdentifierExpression>(Ident(variable->name->symbol));
-    }
-
-    /// @param ident the identifier
-    /// @return an ast::IdentifierExpression with the given identifier
-    template <typename IDENTIFIER, typename = traits::EnableIfIsType<IDENTIFIER, ast::Identifier>>
-    const ast::IdentifierExpression* Expr(const IDENTIFIER* ident) {
-        return create<ast::IdentifierExpression>(ident);
-    }
-
     /// @param source the source information
     /// @param value the boolean value
     /// @return a Scalar constructor for the given value
@@ -1250,14 +1563,6 @@
         return create<ast::BoolLiteralExpression>(source, value);
     }
 
-    /// @param value the boolean value
-    /// @return a Scalar constructor for the given value
-    template <typename BOOL>
-    std::enable_if_t<std::is_same_v<BOOL, bool>, const ast::BoolLiteralExpression*> Expr(
-        BOOL value) {
-        return create<ast::BoolLiteralExpression>(value);
-    }
-
     /// @param source the source information
     /// @param value the float value
     /// @return a 'f'-suffixed FloatLiteralExpression for the f32 value
@@ -1266,13 +1571,6 @@
                                                    ast::FloatLiteralExpression::Suffix::kF);
     }
 
-    /// @param value the float value
-    /// @return a 'f'-suffixed FloatLiteralExpression for the f32 value
-    const ast::FloatLiteralExpression* Expr(f32 value) {
-        return create<ast::FloatLiteralExpression>(static_cast<double>(value.value),
-                                                   ast::FloatLiteralExpression::Suffix::kF);
-    }
-
     /// @param source the source information
     /// @param value the float value
     /// @return a 'h'-suffixed FloatLiteralExpression for the f16 value
@@ -1281,13 +1579,6 @@
                                                    ast::FloatLiteralExpression::Suffix::kH);
     }
 
-    /// @param value the float value
-    /// @return a 'h'-suffixed FloatLiteralExpression for the f16 value
-    const ast::FloatLiteralExpression* Expr(f16 value) {
-        return create<ast::FloatLiteralExpression>(static_cast<double>(value.value),
-                                                   ast::FloatLiteralExpression::Suffix::kH);
-    }
-
     /// @param source the source information
     /// @param value the integer value
     /// @return an unsuffixed IntLiteralExpression for the AInt value
@@ -1296,12 +1587,6 @@
                                                  ast::IntLiteralExpression::Suffix::kNone);
     }
 
-    /// @param value the integer value
-    /// @return an unsuffixed IntLiteralExpression for the AInt value
-    const ast::IntLiteralExpression* Expr(AInt value) {
-        return create<ast::IntLiteralExpression>(value, ast::IntLiteralExpression::Suffix::kNone);
-    }
-
     /// @param source the source information
     /// @param value the integer value
     /// @return an unsuffixed FloatLiteralExpression for the AFloat value
@@ -1310,13 +1595,6 @@
                                                    ast::FloatLiteralExpression::Suffix::kNone);
     }
 
-    /// @param value the integer value
-    /// @return an unsuffixed FloatLiteralExpression for the AFloat value
-    const ast::FloatLiteralExpression* Expr(AFloat value) {
-        return create<ast::FloatLiteralExpression>(value.value,
-                                                   ast::FloatLiteralExpression::Suffix::kNone);
-    }
-
     /// @param source the source information
     /// @param value the integer value
     /// @return a signed 'i'-suffixed IntLiteralExpression for the i32 value
@@ -1325,12 +1603,6 @@
                                                  ast::IntLiteralExpression::Suffix::kI);
     }
 
-    /// @param value the integer value
-    /// @return a signed 'i'-suffixed IntLiteralExpression for the i32 value
-    const ast::IntLiteralExpression* Expr(i32 value) {
-        return create<ast::IntLiteralExpression>(value, ast::IntLiteralExpression::Suffix::kI);
-    }
-
     /// @param source the source information
     /// @param value the unsigned int value
     /// @return an unsigned 'u'-suffixed IntLiteralExpression for the u32 value
@@ -1339,10 +1611,11 @@
                                                  ast::IntLiteralExpression::Suffix::kU);
     }
 
-    /// @param value the unsigned int value
-    /// @return an unsigned 'u'-suffixed IntLiteralExpression for the u32 value
-    const ast::IntLiteralExpression* Expr(u32 value) {
-        return create<ast::IntLiteralExpression>(value, ast::IntLiteralExpression::Suffix::kU);
+    /// @param value the scalar value
+    /// @return literal expression of the appropriate type
+    template <typename SCALAR, typename = EnableIfScalar<SCALAR>>
+    const auto* Expr(SCALAR&& value) {
+        return Expr(source_, std::forward<SCALAR>(value));
     }
 
     /// Converts `arg` to an `ast::Expression` using `Expr()`, then appends it to
@@ -1373,9 +1646,7 @@
     /// `Expr()`,
     template <typename... ARGS, typename = DisableIfVectorLike<ARGS...>>
     auto ExprList(ARGS&&... args) {
-        utils::Vector<const ast::Expression*, sizeof...(ARGS)> list;
-        Append(list, std::forward<ARGS>(args)...);
-        return list;
+        return utils::Vector<const ast::Expression*, sizeof...(ARGS)>{Expr(args)...};
     }
 
     /// @param list the list of expressions
@@ -1402,98 +1673,167 @@
 
     /// @param type the type to cast to
     /// @param expr the expression for the bitcast
-    /// @return an `ast::BitcastExpression` of `type` constructed with the values
+    /// @return an `ast::BitcastExpression` of @p type constructed with the values
     /// `expr`.
     template <typename EXPR>
-    const ast::BitcastExpression* Bitcast(const ast::Type* type, EXPR&& expr) {
-        return create<ast::BitcastExpression>(type, Expr(std::forward<EXPR>(expr)));
+    const ast::BitcastExpression* Bitcast(ast::Type type, EXPR&& expr) {
+        return Bitcast(source_, type, Expr(std::forward<EXPR>(expr)));
     }
 
     /// @param source the source information
     /// @param type the type to cast to
     /// @param expr the expression for the bitcast
-    /// @return an `ast::BitcastExpression` of `type` constructed with the values
+    /// @return an `ast::BitcastExpression` of @p type constructed with the values
     /// `expr`.
     template <typename EXPR>
-    const ast::BitcastExpression* Bitcast(const Source& source,
-                                          const ast::Type* type,
-                                          EXPR&& expr) {
+    const ast::BitcastExpression* Bitcast(const Source& source, ast::Type type, EXPR&& expr) {
         return create<ast::BitcastExpression>(source, type, Expr(std::forward<EXPR>(expr)));
     }
 
-    /// @param args the arguments for the vector initializer
     /// @param type the vector type
     /// @param size the vector size
+    /// @param args the arguments for the vector initializer
     /// @return an `ast::CallExpression` of a `size`-element vector of
-    /// type `type`, constructed with the values `args`.
+    /// type `type`, constructed with the values @p args.
     template <typename... ARGS>
-    const ast::CallExpression* vec(const ast::Type* type, uint32_t size, ARGS&&... args) {
-        return Call(ty.vec(type, size), std::forward<ARGS>(args)...);
+    const ast::CallExpression* vec(ast::Type type, uint32_t size, ARGS&&... args) {
+        return vec(source_, type, size, std::forward<ARGS>(args)...);
+    }
+
+    /// @param source the source of the call
+    /// @param type the vector type
+    /// @param size the vector size
+    /// @param args the arguments for the vector initializer
+    /// @return an `ast::CallExpression` of a `size`-element vector of
+    /// type `type`, constructed with the values @p args.
+    template <typename... ARGS>
+    const ast::CallExpression* vec(const Source& source,
+                                   ast::Type type,
+                                   uint32_t size,
+                                   ARGS&&... args) {
+        return Call(source, ty.vec(type, size), std::forward<ARGS>(args)...);
     }
 
     /// @param args the arguments for the vector initializer
-    /// @return an `ast::CallExpression` of a 2-element vector of type
-    /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
+    /// @return an `ast::CallExpression` of a 2-element vector of type `T`, constructed with the
+    /// values @p args.
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
     const ast::CallExpression* vec2(ARGS&&... args) {
-        return Call(ty.vec2<T>(), std::forward<ARGS>(args)...);
+        return vec2<T>(source_, std::forward<ARGS>(args)...);
     }
 
     /// @param source the vector source
     /// @param args the arguments for the vector initializer
-    /// @return an `ast::CallExpression` of a 2-element vector of type
-    /// `T`, constructed with the values `args`.
+    /// @return an `ast::CallExpression` of a 2-element vector of type `T`, constructed with the
+    /// values @p args.
     template <typename T, typename... ARGS>
     const ast::CallExpression* vec2(const Source& source, ARGS&&... args) {
         return Call(source, ty.vec2<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param type the element type of the vector
     /// @param args the arguments for the vector initializer
-    /// @return an `ast::CallExpression` of a 3-element vector of type
-    /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
+    /// @return an `ast::CallExpression` of a 2-element vector of type @p type, constructed with the
+    /// values @p args.
+    template <typename... ARGS>
+    const ast::CallExpression* vec2(ast::Type type, ARGS&&... args) {
+        return vec2(source_, type, std::forward<ARGS>(args)...);
+    }
+
+    /// @param source the vector source
+    /// @param type the element type of the vector
+    /// @param args the arguments for the vector initializer
+    /// @return an `ast::CallExpression` of a 2-element vector of type @p type, constructed with the
+    /// values @p args.
+    template <typename... ARGS>
+    const ast::CallExpression* vec2(const Source& source, ast::Type type, ARGS&&... args) {
+        return Call(source, ty.vec2(type), std::forward<ARGS>(args)...);
+    }
+
+    /// @param args the arguments for the vector initializer
+    /// @return an `ast::CallExpression` of a 3-element vector of type `T`, constructed with the
+    /// values @p args.
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
     const ast::CallExpression* vec3(ARGS&&... args) {
-        return Call(ty.vec3<T>(), std::forward<ARGS>(args)...);
+        return vec3<T>(source_, std::forward<ARGS>(args)...);
     }
 
     /// @param source the vector source
     /// @param args the arguments for the vector initializer
-    /// @return an `ast::CallExpression` of a 3-element vector of type
-    /// `T`, constructed with the values `args`.
+    /// @return an `ast::CallExpression` of a 3-element vector of type `T`, constructed with the
+    /// values @p args.
     template <typename T, typename... ARGS>
     const ast::CallExpression* vec3(const Source& source, ARGS&&... args) {
         return Call(source, ty.vec3<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param type the element type of the vector
     /// @param args the arguments for the vector initializer
-    /// @return an `ast::CallExpression` of a 4-element vector of type
-    /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
+    /// @return an `ast::CallExpression` of a 3-element vector of type @p type, constructed with the
+    /// values @p args.
+    template <typename... ARGS>
+    const ast::CallExpression* vec3(ast::Type type, ARGS&&... args) {
+        return vec3(source_, type, std::forward<ARGS>(args)...);
+    }
+
+    /// @param source the vector source
+    /// @param type the element type of the vector
+    /// @param args the arguments for the vector initializer
+    /// @return an `ast::CallExpression` of a 3-element vector of type @p type, constructed with the
+    /// values @p args.
+    template <typename... ARGS>
+    const ast::CallExpression* vec3(const Source& source, ast::Type type, ARGS&&... args) {
+        return Call(source, ty.vec3(type), std::forward<ARGS>(args)...);
+    }
+
+    /// @param args the arguments for the vector initializer
+    /// @return an `ast::CallExpression` of a 4-element vector of type `T`, constructed with the
+    /// values @p args.
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
     const ast::CallExpression* vec4(ARGS&&... args) {
-        return Call(ty.vec4<T>(), std::forward<ARGS>(args)...);
+        return vec4<T>(source_, std::forward<ARGS>(args)...);
     }
 
     /// @param source the vector source
     /// @param args the arguments for the vector initializer
-    /// @return an `ast::CallExpression` of a 4-element vector of type
-    /// `T`, constructed with the values `args`.
+    /// @return an `ast::CallExpression` of a 4-element vector of type `T`, constructed with the
+    /// values @p args.
     template <typename T, typename... ARGS>
     const ast::CallExpression* vec4(const Source& source, ARGS&&... args) {
         return Call(source, ty.vec4<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param type the element type of the vector
+    /// @param args the arguments for the vector initializer
+    /// @return an `ast::CallExpression` of a 4-element vector of type @p type, constructed with the
+    /// values @p args.
+    template <typename... ARGS>
+    const ast::CallExpression* vec4(ast::Type type, ARGS&&... args) {
+        return vec4(source_, type, std::forward<ARGS>(args)...);
+    }
+
+    /// @param source the vector source
+    /// @param type the element type of the vector
+    /// @param args the arguments for the vector initializer
+    /// @return an `ast::CallExpression` of a 4-element vector of type @p type, constructed with the
+    /// values @p args.
+    template <typename... ARGS>
+    const ast::CallExpression* vec4(const Source& source, ast::Type type, ARGS&&... args) {
+        return Call(source, ty.vec4(type), std::forward<ARGS>(args)...);
+    }
+
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 2x2 matrix of type
-    /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
+    /// `T`, constructed with the values @p args.
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat2x2(ARGS&&... args) {
-        return Call(ty.mat2x2<T>(), std::forward<ARGS>(args)...);
+        return mat2x2<T>(source_, std::forward<ARGS>(args)...);
     }
 
     /// @param source the matrix source
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 2x2 matrix of type
-    /// `T`, constructed with the values `args`.
+    /// `T`, constructed with the values @p args.
     template <typename T, typename... ARGS>
     const ast::CallExpression* mat2x2(const Source& source, ARGS&&... args) {
         return Call(source, ty.mat2x2<T>(), std::forward<ARGS>(args)...);
@@ -1501,16 +1841,16 @@
 
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 2x3 matrix of type
-    /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
+    /// `T`, constructed with the values @p args.
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat2x3(ARGS&&... args) {
-        return Call(ty.mat2x3<T>(), std::forward<ARGS>(args)...);
+        return mat2x3<T>(source_, std::forward<ARGS>(args)...);
     }
 
     /// @param source the matrix source
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 2x3 matrix of type
-    /// `T`, constructed with the values `args`.
+    /// `T`, constructed with the values @p args.
     template <typename T, typename... ARGS>
     const ast::CallExpression* mat2x3(const Source& source, ARGS&&... args) {
         return Call(source, ty.mat2x3<T>(), std::forward<ARGS>(args)...);
@@ -1518,16 +1858,16 @@
 
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 2x4 matrix of type
-    /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
+    /// `T`, constructed with the values @p args.
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat2x4(ARGS&&... args) {
-        return Call(ty.mat2x4<T>(), std::forward<ARGS>(args)...);
+        return mat2x4<T>(source_, std::forward<ARGS>(args)...);
     }
 
     /// @param source the matrix source
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 2x4 matrix of type
-    /// `T`, constructed with the values `args`.
+    /// `T`, constructed with the values @p args.
     template <typename T, typename... ARGS>
     const ast::CallExpression* mat2x4(const Source& source, ARGS&&... args) {
         return Call(source, ty.mat2x4<T>(), std::forward<ARGS>(args)...);
@@ -1535,16 +1875,16 @@
 
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 3x2 matrix of type
-    /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
+    /// `T`, constructed with the values @p args.
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat3x2(ARGS&&... args) {
-        return Call(ty.mat3x2<T>(), std::forward<ARGS>(args)...);
+        return mat3x2<T>(source_, std::forward<ARGS>(args)...);
     }
 
     /// @param source the matrix source
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 3x2 matrix of type
-    /// `T`, constructed with the values `args`.
+    /// `T`, constructed with the values @p args.
     template <typename T, typename... ARGS>
     const ast::CallExpression* mat3x2(const Source& source, ARGS&&... args) {
         return Call(source, ty.mat3x2<T>(), std::forward<ARGS>(args)...);
@@ -1552,16 +1892,16 @@
 
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 3x3 matrix of type
-    /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
+    /// `T`, constructed with the values @p args.
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat3x3(ARGS&&... args) {
-        return Call(ty.mat3x3<T>(), std::forward<ARGS>(args)...);
+        return mat3x3<T>(source_, std::forward<ARGS>(args)...);
     }
 
     /// @param source the matrix source
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 3x3 matrix of type
-    /// `T`, constructed with the values `args`.
+    /// `T`, constructed with the values @p args.
     template <typename T, typename... ARGS>
     const ast::CallExpression* mat3x3(const Source& source, ARGS&&... args) {
         return Call(source, ty.mat3x3<T>(), std::forward<ARGS>(args)...);
@@ -1569,16 +1909,16 @@
 
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 3x4 matrix of type
-    /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
+    /// `T`, constructed with the values @p args.
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat3x4(ARGS&&... args) {
-        return Call(ty.mat3x4<T>(), std::forward<ARGS>(args)...);
+        return mat3x4<T>(source_, std::forward<ARGS>(args)...);
     }
 
     /// @param source the matrix source
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 3x4 matrix of type
-    /// `T`, constructed with the values `args`.
+    /// `T`, constructed with the values @p args.
     template <typename T, typename... ARGS>
     const ast::CallExpression* mat3x4(const Source& source, ARGS&&... args) {
         return Call(source, ty.mat3x4<T>(), std::forward<ARGS>(args)...);
@@ -1586,16 +1926,16 @@
 
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 4x2 matrix of type
-    /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
+    /// `T`, constructed with the values @p args.
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat4x2(ARGS&&... args) {
-        return Call(ty.mat4x2<T>(), std::forward<ARGS>(args)...);
+        return mat4x2<T>(source_, std::forward<ARGS>(args)...);
     }
 
     /// @param source the matrix source
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 4x2 matrix of type
-    /// `T`, constructed with the values `args`.
+    /// `T`, constructed with the values @p args.
     template <typename T, typename... ARGS>
     const ast::CallExpression* mat4x2(const Source& source, ARGS&&... args) {
         return Call(source, ty.mat4x2<T>(), std::forward<ARGS>(args)...);
@@ -1603,16 +1943,16 @@
 
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 4x3 matrix of type
-    /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
+    /// `T`, constructed with the values @p args.
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat4x3(ARGS&&... args) {
-        return Call(ty.mat4x3<T>(), std::forward<ARGS>(args)...);
+        return mat4x3<T>(source_, std::forward<ARGS>(args)...);
     }
 
     /// @param source the matrix source
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 4x3 matrix of type
-    /// `T`, constructed with the values `args`.
+    /// `T`, constructed with the values @p args.
     template <typename T, typename... ARGS>
     const ast::CallExpression* mat4x3(const Source& source, ARGS&&... args) {
         return Call(source, ty.mat4x3<T>(), std::forward<ARGS>(args)...);
@@ -1620,33 +1960,50 @@
 
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 4x4 matrix of type
-    /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
+    /// `T`, constructed with the values @p args.
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat4x4(ARGS&&... args) {
-        return Call(ty.mat4x4<T>(), std::forward<ARGS>(args)...);
+        return mat4x4<T>(source_, std::forward<ARGS>(args)...);
     }
 
     /// @param source the matrix source
     /// @param args the arguments for the matrix initializer
     /// @return an `ast::CallExpression` of a 4x4 matrix of type
-    /// `T`, constructed with the values `args`.
+    /// `T`, constructed with the values @p args.
     template <typename T, typename... ARGS>
     const ast::CallExpression* mat4x4(const Source& source, ARGS&&... args) {
         return Call(source, ty.mat4x4<T>(), std::forward<ARGS>(args)...);
     }
 
     /// @param args the arguments for the array initializer
-    /// @return an `ast::CallExpression` of an array with element type
-    /// `T` and size `N`, constructed with the values `args`.
-    template <typename T, int N, typename... ARGS>
+    /// @return an `ast::CallExpression` of an array with element type `T`, constructed with the
+    /// values @p args.
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
+    const ast::CallExpression* array(ARGS&&... args) {
+        return Call(ty.array<T>(), std::forward<ARGS>(args)...);
+    }
+
+    /// @param source the array source
+    /// @param args the arguments for the array initializer
+    /// @return an `ast::CallExpression` of an array with element type `T`, constructed with the
+    /// values @p args.
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* array(const Source& source, ARGS&&... args) {
+        return Call(source, ty.array<T>(), std::forward<ARGS>(args)...);
+    }
+
+    /// @param args the arguments for the array initializer
+    /// @return an `ast::CallExpression` of an array with element type `T` and size `N`, constructed
+    /// with the values @p args.
+    template <typename T, int N, typename... ARGS, typename = DisableIfSource<ARGS...>>
     const ast::CallExpression* array(ARGS&&... args) {
         return Call(ty.array<T, N>(), std::forward<ARGS>(args)...);
     }
 
     /// @param source the array source
     /// @param args the arguments for the array initializer
-    /// @return an `ast::CallExpression` of an array with element type
-    /// `T` and size `N`, constructed with the values `args`.
+    /// @return an `ast::CallExpression` of an array with element type `T` and size `N`, constructed
+    /// with the values @p args.
     template <typename T, int N, typename... ARGS>
     const ast::CallExpression* array(const Source& source, ARGS&&... args) {
         return Call(source, ty.array<T, N>(), std::forward<ARGS>(args)...);
@@ -1656,9 +2013,9 @@
     /// @param n the array size. nullptr represents a runtime-array.
     /// @param args the arguments for the array initializer
     /// @return an `ast::CallExpression` of an array with element type
-    /// `subtype`, constructed with the values `args`.
+    /// `subtype`, constructed with the values @p args.
     template <typename EXPR, typename... ARGS>
-    const ast::CallExpression* array(const ast::Type* subtype, EXPR&& n, ARGS&&... args) {
+    const ast::CallExpression* array(ast::Type subtype, EXPR&& n, ARGS&&... args) {
         return Call(ty.array(subtype, std::forward<EXPR>(n)), std::forward<ARGS>(args)...);
     }
 
@@ -1667,10 +2024,10 @@
     /// @param n the array size. nullptr represents a runtime-array.
     /// @param args the arguments for the array initializer
     /// @return an `ast::CallExpression` of an array with element type
-    /// `subtype`, constructed with the values `args`.
+    /// `subtype`, constructed with the values @p args.
     template <typename EXPR, typename... ARGS>
     const ast::CallExpression* array(const Source& source,
-                                     const ast::Type* subtype,
+                                     ast::Type subtype,
                                      EXPR&& n,
                                      ARGS&&... args) {
         return Call(source, ty.array(subtype, std::forward<EXPR>(n)), std::forward<ARGS>(args)...);
@@ -1734,6 +2091,7 @@
     /// @param options the extra options passed to the ast::Var initializer
     /// Can be any of the following, in any order:
     ///   * ast::Expression*    - specifies the variable's initializer expression (required)
+    ///   * ast::Identifier*    - specifies the variable type
     ///   * ast::Type*          - specifies the variable type
     ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
     /// Note that non-repeatable arguments of the same type will use the last argument's value.
@@ -1748,6 +2106,7 @@
     /// @param options the extra options passed to the ast::Var initializer
     /// Can be any of the following, in any order:
     ///   * ast::Expression*    - specifies the variable's initializer expression (required)
+    ///   * ast::Identifier*    - specifies the variable type
     ///   * ast::Type*          - specifies the variable type
     ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
     /// Note that non-repeatable arguments of the same type will use the last argument's value.
@@ -1794,7 +2153,7 @@
     /// @returns an `ast::Parameter` with the given name and type
     template <typename NAME>
     const ast::Parameter* Param(NAME&& name,
-                                const ast::Type* type,
+                                ast::Type type,
                                 utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return Param(source_, std::forward<NAME>(name), type, std::move(attributes));
     }
@@ -1807,9 +2166,10 @@
     template <typename NAME>
     const ast::Parameter* Param(const Source& source,
                                 NAME&& name,
-                                const ast::Type* type,
+                                ast::Type type,
                                 utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
-        return create<ast::Parameter>(source, Ident(std::forward<NAME>(name)), type, attributes);
+        return create<ast::Parameter>(source, Ident(std::forward<NAME>(name)), type,
+                                      std::move(attributes));
     }
 
     /// @param name the variable name
@@ -2017,41 +2377,44 @@
                                               Expr(std::forward<EXPR>(expr)));
     }
 
+    /// @param args the arguments for the type constructor
+    /// @returns an ast::CallExpression to the type `T`, with the arguments of @p args converted to
+    /// `ast::Expression`s using Expr().
+    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
+    const ast::CallExpression* Call(ARGS&&... args) {
+        return Call(source_, ty.Of<T>(), std::forward<ARGS>(args)...);
+    }
+
+    /// @param source the source of the call
+    /// @param args the arguments for the type constructor
+    /// @returns an ast::CallExpression to the type `T` with the arguments of @p args converted to
+    /// `ast::Expression`s using Expr().
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* Call(const Source& source, ARGS&&... args) {
+        return Call(source, ty.Of<T>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param target the call target
     /// @param args the function call arguments
-    /// @returns a `ast::CallExpression` to the function `func`, with the
-    /// arguments of `args` converted to `ast::Expression`s using `Expr()`.
+    /// @returns an ast::CallExpression to the target @p target, with the arguments of @p args
+    /// converted to `ast::Expression`s using Expr().
     template <typename TARGET,
               typename... ARGS,
               typename = DisableIfSource<TARGET>,
               typename = DisableIfScalar<TARGET>>
     const ast::CallExpression* Call(TARGET&& target, ARGS&&... args) {
-        return Call(source_, std::forward<TARGET>(target), std::forward<ARGS>(args)...);
+        return Call(source_, Expr(std::forward<TARGET>(target)), std::forward<ARGS>(args)...);
     }
 
-    /// @param source the source information
-    /// @param target the call target. Can be an ast::Type or ast::Identifier, or string-like.
+    /// @param source the source of the call
+    /// @param target the call target
     /// @param args the function call arguments
-    /// @returns a `ast::CallExpression` to the target @p target, with the arguments of @p args
+    /// @returns an ast::CallExpression to the target @p target, with the arguments of @p args
     /// converted to `ast::Expression`s using Expr().
     template <typename TARGET, typename... ARGS, typename = DisableIfScalar<TARGET>>
     const ast::CallExpression* Call(const Source& source, TARGET&& target, ARGS&&... args) {
-        if constexpr (traits::IsTypeOrDerived<traits::PtrElTy<TARGET>, ast::Type>) {
-            return create<ast::CallExpression>(source, target,
-                                               ExprList(std::forward<ARGS>(args)...));
-
-        } else {
-            return create<ast::CallExpression>(source, Ident(target),
-                                               ExprList(std::forward<ARGS>(args)...));
-        }
-    }
-
-    /// @param args the arguments for the type constructor
-    /// @return an `ast::CallExpression` of type `ty`, with the values
-    /// of `args` converted to `ast::Expression`s using `Expr()`
-    template <typename T, typename... ARGS, typename = DisableIfSource<ARGS...>>
-    const ast::CallExpression* Call(ARGS&&... args) {
-        return Call(ty.Of<T>(), std::forward<ARGS>(args)...);
+        return create<ast::CallExpression>(source, Expr(std::forward<TARGET>(target)),
+                                           ExprList(std::forward<ARGS>(args)...));
     }
 
     /// @param source the source information
@@ -2470,13 +2833,12 @@
     /// be automatically placed into a block, or nullptr for a stub function.
     /// @param attributes the optional function attributes
     /// @param return_type_attributes the optional function return type attributes
-    /// attributes
     /// @returns the function pointer
     template <typename NAME, typename BODY = utils::VectorRef<const ast::Statement*>>
     const ast::Function* Func(
         NAME&& name,
         utils::VectorRef<const ast::Parameter*> params,
-        const ast::Type* type,
+        ast::Type type,
         BODY&& body,
         utils::VectorRef<const ast::Attribute*> attributes = utils::Empty,
         utils::VectorRef<const ast::Attribute*> return_type_attributes = utils::Empty) {
@@ -2494,14 +2856,13 @@
     /// be automatically placed into a block, or nullptr for a stub function.
     /// @param attributes the optional function attributes
     /// @param return_type_attributes the optional function return type attributes
-    /// attributes
     /// @returns the function pointer
     template <typename NAME, typename BODY = utils::VectorRef<const ast::Statement*>>
     const ast::Function* Func(
         const Source& source,
         NAME&& name,
         utils::VectorRef<const ast::Parameter*> params,
-        const ast::Type* type,
+        ast::Type type,
         BODY&& body,
         utils::VectorRef<const ast::Attribute*> attributes = utils::Empty,
         utils::VectorRef<const ast::Attribute*> return_type_attributes = utils::Empty) {
@@ -2603,7 +2964,7 @@
     /// @param type the alias target type
     /// @returns the alias type
     template <typename NAME>
-    const ast::Alias* Alias(NAME&& name, const ast::Type* type) {
+    const ast::Alias* Alias(NAME&& name, ast::Type type) {
         return Alias(source_, std::forward<NAME>(name), type);
     }
 
@@ -2613,8 +2974,8 @@
     /// @param type the alias target type
     /// @returns the alias type
     template <typename NAME>
-    const ast::Alias* Alias(const Source& source, NAME&& name, const ast::Type* type) {
-        auto* out = ty.alias(source, std::forward<NAME>(name), type);
+    const ast::Alias* Alias(const Source& source, NAME&& name, ast::Type type) {
+        auto out = ty.alias(source, std::forward<NAME>(name), type);
         AST().AddTypeDecl(out);
         return out;
     }
@@ -2656,16 +3017,16 @@
     /// @param type the struct member type
     /// @param attributes the optional struct member attributes
     /// @returns the struct member pointer
-    template <typename NAME>
+    template <typename NAME, typename = DisableIfSource<NAME>>
     const ast::StructMember* Member(
         NAME&& name,
-        const ast::Type* type,
+        ast::Type type,
         utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return Member(source_, std::forward<NAME>(name), type, std::move(attributes));
     }
 
     /// Creates a ast::StructMember
-    /// @param source the source information
+    /// @param source the struct member source
     /// @param name the struct member name
     /// @param type the struct member type
     /// @param attributes the optional struct member attributes
@@ -2674,7 +3035,7 @@
     const ast::StructMember* Member(
         const Source& source,
         NAME&& name,
-        const ast::Type* type,
+        ast::Type type,
         utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return create<ast::StructMember>(source, Ident(std::forward<NAME>(name)), type,
                                          std::move(attributes));
@@ -2686,7 +3047,7 @@
     /// @param type the struct member type
     /// @returns the struct member pointer
     template <typename NAME>
-    const ast::StructMember* Member(uint32_t offset, NAME&& name, const ast::Type* type) {
+    const ast::StructMember* Member(uint32_t offset, NAME&& name, ast::Type type) {
         return create<ast::StructMember>(source_, Ident(std::forward<NAME>(name)), type,
                                          utils::Vector<const ast::Attribute*, 1>{
                                              MemberOffset(AInt(offset)),
@@ -2999,6 +3360,15 @@
     }
 
     /// Creates a ast::CaseStatement with input list of selectors, and body
+    /// @param selectors list of selectors
+    /// @param body the case body
+    /// @returns the case statement pointer
+    const ast::CaseStatement* Case(utils::VectorRef<const ast::CaseSelector*> selectors,
+                                   const ast::BlockStatement* body = nullptr) {
+        return Case(source_, std::move(selectors), body);
+    }
+
+    /// Creates a ast::CaseStatement with input list of selectors, and body
     /// @param source the source information
     /// @param selectors list of selectors
     /// @param body the case body
@@ -3009,22 +3379,20 @@
         return create<ast::CaseStatement>(source, std::move(selectors), body ? body : Block());
     }
 
-    /// Creates a ast::CaseStatement with input list of selectors, and body
-    /// @param selectors list of selectors
-    /// @param body the case body
-    /// @returns the case statement pointer
-    const ast::CaseStatement* Case(utils::VectorRef<const ast::CaseSelector*> selectors,
-                                   const ast::BlockStatement* body = nullptr) {
-        return create<ast::CaseStatement>(std::move(selectors), body ? body : Block());
-    }
-
     /// Convenient overload that takes a single selector
     /// @param selector a single case selector
     /// @param body the case body
     /// @returns the case statement pointer
     const ast::CaseStatement* Case(const ast::CaseSelector* selector,
                                    const ast::BlockStatement* body = nullptr) {
-        return Case(utils::Vector{selector}, body);
+        return Case(utils::Vector{selector}, body ? body : Block());
+    }
+
+    /// Convenience function that creates a 'default' ast::CaseStatement
+    /// @param body the case body
+    /// @returns the case statement pointer
+    const ast::CaseStatement* DefaultCase(const ast::BlockStatement* body = nullptr) {
+        return DefaultCase(source_, body);
     }
 
     /// Convenience function that creates a 'default' ast::CaseStatement
@@ -3036,13 +3404,6 @@
         return Case(source, utils::Vector{DefaultCaseSelector(source)}, body);
     }
 
-    /// Convenience function that creates a 'default' ast::CaseStatement
-    /// @param body the case body
-    /// @returns the case statement pointer
-    const ast::CaseStatement* DefaultCase(const ast::BlockStatement* body = nullptr) {
-        return Case(utils::Vector{DefaultCaseSelector()}, body);
-    }
-
     /// Convenience function that creates a case selector
     /// @param source the source information
     /// @param expr the selector expression
@@ -3350,14 +3711,6 @@
     /// variable has no resolved type.
     const type::Type* TypeOf(const ast::Variable* var) const;
 
-    /// Helper for returning the resolved semantic type of the AST type `type`.
-    /// @note As the Resolver is run when the Program is built, this will only be
-    /// useful for the Resolver itself and tests that use their own Resolver.
-    /// @param type the AST type
-    /// @return the resolved semantic type for the type, or nullptr if the type
-    /// has no resolved type.
-    const type::Type* TypeOf(const ast::Type* type) const;
-
     /// Helper for returning the resolved semantic type of the AST type
     /// declaration `type_decl`.
     /// @note As the Resolver is run when the Program is built, this will only be
@@ -3368,13 +3721,11 @@
     const type::Type* TypeOf(const ast::TypeDecl* type_decl) const;
 
     /// @param type a type
-    /// @returns the name for `type` that closely resembles how it would be
-    /// declared in WGSL.
-    std::string FriendlyName(const ast::Type* type) const;
+    /// @returns the name for `type` that closely resembles how it would be declared in WGSL.
+    std::string FriendlyName(ast::Type type) const;
 
     /// @param type a type
-    /// @returns the name for `type` that closely resembles how it would be
-    /// declared in WGSL.
+    /// @returns the name for `type` that closely resembles how it would be declared in WGSL.
     std::string FriendlyName(const type::Type* type) const;
 
     /// Overload of FriendlyName, which removes an ambiguity when passing nullptr.
@@ -3455,35 +3806,31 @@
 // Various template specializations for ProgramBuilder::TypesBuilder::CToAST.
 template <>
 struct ProgramBuilder::TypesBuilder::CToAST<AInt> {
-    static const ast::Type* get(const ProgramBuilder::TypesBuilder*) { return nullptr; }
+    static ast::Type get(const ProgramBuilder::TypesBuilder*) { return ast::Type{}; }
 };
 template <>
 struct ProgramBuilder::TypesBuilder::CToAST<AFloat> {
-    static const ast::Type* get(const ProgramBuilder::TypesBuilder*) { return nullptr; }
+    static ast::Type get(const ProgramBuilder::TypesBuilder*) { return ast::Type{}; }
 };
 template <>
 struct ProgramBuilder::TypesBuilder::CToAST<i32> {
-    static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) { return t->i32(); }
+    static ast::Type get(const ProgramBuilder::TypesBuilder* t) { return t->i32(); }
 };
 template <>
 struct ProgramBuilder::TypesBuilder::CToAST<u32> {
-    static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) { return t->u32(); }
+    static ast::Type get(const ProgramBuilder::TypesBuilder* t) { return t->u32(); }
 };
 template <>
 struct ProgramBuilder::TypesBuilder::CToAST<f32> {
-    static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) { return t->f32(); }
+    static ast::Type get(const ProgramBuilder::TypesBuilder* t) { return t->f32(); }
 };
 template <>
 struct ProgramBuilder::TypesBuilder::CToAST<f16> {
-    static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) { return t->f16(); }
+    static ast::Type get(const ProgramBuilder::TypesBuilder* t) { return t->f16(); }
 };
 template <>
 struct ProgramBuilder::TypesBuilder::CToAST<bool> {
-    static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) { return t->bool_(); }
-};
-template <>
-struct ProgramBuilder::TypesBuilder::CToAST<void> {
-    static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) { return t->void_(); }
+    static ast::Type get(const ProgramBuilder::TypesBuilder* t) { return t->bool_(); }
 };
 //! @endcond
 
diff --git a/src/tint/program_builder_test.cc b/src/tint/program_builder_test.cc
index 02e3ba2..2733816 100644
--- a/src/tint/program_builder_test.cc
+++ b/src/tint/program_builder_test.cc
@@ -33,7 +33,7 @@
 TEST_F(ProgramBuilderTest, WrapDoesntAffectInner) {
     Program inner([] {
         ProgramBuilder builder;
-        auto* ty = builder.ty.f32();
+        auto ty = builder.ty.f32();
         builder.Func("a", {}, ty, {}, {});
         return builder;
     }());
@@ -54,7 +54,7 @@
     EXPECT_FALSE(inner.Symbols().Get("b").IsValid());
     EXPECT_FALSE(outer.Symbols().Get("b").IsValid());
 
-    auto* ty = outer.ty.f32();
+    auto ty = outer.ty.f32();
     outer.Func("b", {}, ty, {}, {});
 
     ASSERT_EQ(inner.AST().Functions().Length(), 1u);
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
index c5eb5a4..a23ca20 100644
--- a/src/tint/reader/spirv/function.cc
+++ b/src/tint/reader/spirv/function.cc
@@ -1259,7 +1259,7 @@
     FunctionDeclaration decl;
     decl.source = source;
     decl.name = ep_info_->name;
-    const ast::Type* return_type = nullptr;  // Populated below.
+    ast::Type return_type;  // Populated below.
 
     // Pipeline inputs become parameters to the wrapper function, and
     // their values are saved into the corresponding private variables that
@@ -3825,7 +3825,7 @@
     if (unary_builtin_name != nullptr) {
         ExpressionList params;
         params.Push(MakeOperand(inst, 0).expr);
-        return {ast_type, builder_.Call(Source{}, unary_builtin_name, std::move(params))};
+        return {ast_type, builder_.Call(unary_builtin_name, std::move(params))};
     }
 
     const auto builtin = GetBuiltin(op);
@@ -3906,7 +3906,7 @@
             }
             operands.Push(operand.expr);
         }
-        return {ast_type, builder_.Call(Source{}, ast_type->Build(builder_), std::move(operands))};
+        return {ast_type, builder_.Call(ast_type->Build(builder_), std::move(operands))};
     }
 
     if (op == spv::Op::OpCompositeExtract) {
@@ -3971,7 +3971,7 @@
         auto e1 = MakeOperand(inst, 2);
         auto e2 = ToSignedIfUnsigned(MakeOperand(inst, 3));
 
-        return {e1.type, builder_.Call(Source{}, "ldexp", utils::Vector{e1.expr, e2.expr})};
+        return {e1.type, builder_.Call("ldexp", utils::Vector{e1.expr, e2.expr})};
     }
 
     auto* result_type = parser_impl_.ConvertType(inst.type_id());
@@ -3983,7 +3983,7 @@
             case GLSLstd450Determinant: {
                 auto m = MakeOperand(inst, 2);
                 TINT_ASSERT(Reader, m.type->Is<Matrix>());
-                return {ty_.F32(), builder_.Call(Source{}, "determinant", m.expr)};
+                return {ty_.F32(), builder_.Call("determinant", m.expr)};
             }
 
             case GLSLstd450Normalize:
@@ -4049,7 +4049,7 @@
                 return {
                     f32,
                     builder_.MemberAccessor(
-                        builder_.Call(Source{}, "refract",
+                        builder_.Call("refract",
                                       utils::Vector{
                                           builder_.vec2<tint::f32>(incident.expr, 0_f),
                                           builder_.vec2<tint::f32>(normal.expr, 0_f),
@@ -4084,7 +4084,7 @@
         }
         operands.Push(operand.expr);
     }
-    auto* call = builder_.Call(Source{}, name, std::move(operands));
+    auto* call = builder_.Call(name, std::move(operands));
     TypedExpression call_expr{result_type, call};
     return parser_impl_.RectifyForcedResultType(call_expr, inst, first_operand_type);
 }
@@ -5199,7 +5199,7 @@
     if (failed()) {
         return false;
     }
-    auto* call_expr = create<ast::CallExpression>(Source{}, function, std::move(args));
+    auto* call_expr = builder_.Call(function, std::move(args));
     auto* result_type = parser_impl_.ConvertType(inst.type_id());
     if (!result_type) {
         return Fail() << "internal error: no mapped type result of call: " << inst.PrettyPrint();
@@ -5270,7 +5270,7 @@
         }
         params.Push(operand.expr);
     }
-    auto* call_expr = create<ast::CallExpression>(Source{}, ident, std::move(params));
+    auto* call_expr = builder_.Call(ident, std::move(params));
     auto* result_type = parser_impl_.ConvertType(inst.type_id());
     if (!result_type) {
         Fail() << "internal error: no mapped type result of call: " << inst.PrettyPrint();
@@ -5298,10 +5298,7 @@
         params.Push(true_value.expr);
         // The condition goes last.
         params.Push(condition.expr);
-        return {op_ty, create<ast::CallExpression>(
-                           Source{},
-                           create<ast::Identifier>(Source{}, builder_.Symbols().Register("select")),
-                           std::move(params))};
+        return {op_ty, builder_.Call("select", std::move(params))};
     }
     return {};
 }
@@ -5604,7 +5601,7 @@
         return false;
     }
 
-    auto* call_expr = builder_.Call(Source{}, builtin_name, std::move(args));
+    auto* call_expr = builder_.Call(builtin_name, std::move(args));
 
     if (inst.type_id() != 0) {
         // It returns a value.
@@ -5697,7 +5694,7 @@
                 dims_args.Push(MakeOperand(inst, 1).expr);
             }
             const ast::Expression* dims_call =
-                builder_.Call(Source{}, "textureDimensions", std::move(dims_args));
+                builder_.Call("textureDimensions", std::move(dims_args));
             auto dims = texture_type->dims;
             if ((dims == type::TextureDimension::kCube) ||
                 (dims == type::TextureDimension::kCubeArray)) {
@@ -5706,9 +5703,8 @@
                     create<ast::MemberAccessorExpression>(Source{}, dims_call, PrefixSwizzle(2));
             }
             exprs.Push(dims_call);
-            if (ast::IsTextureArray(dims)) {
-                auto num_layers =
-                    builder_.Call(Source{}, "textureNumLayers", GetImageExpression(inst));
+            if (type::IsTextureArray(dims)) {
+                auto num_layers = builder_.Call("textureNumLayers", GetImageExpression(inst));
                 exprs.Push(num_layers);
             }
             auto* result_type = parser_impl_.ConvertType(inst.type_id());
@@ -5720,7 +5716,7 @@
                 // vector initializer - otherwise, just emit the single expression to omit an
                 // unnecessary cast.
                 (exprs.Length() > 1)
-                    ? builder_.Call(Source{}, unsigned_type->Build(builder_), std::move(exprs))
+                    ? builder_.Call(unsigned_type->Build(builder_), std::move(exprs))
                     : exprs[0],
             };
 
@@ -5735,15 +5731,13 @@
         case spv::Op::OpImageQuerySamples: {
             const auto* name =
                 (op == spv::Op::OpImageQueryLevels) ? "textureNumLevels" : "textureNumSamples";
-            const ast::Expression* ast_expr =
-                builder_.Call(Source{}, name, GetImageExpression(inst));
+            const ast::Expression* ast_expr = builder_.Call(name, GetImageExpression(inst));
             auto* result_type = parser_impl_.ConvertType(inst.type_id());
             // The SPIR-V result type must be integer scalar.
             // The WGSL bulitin returns u32.
             // If they aren't the same then convert the result.
             if (!result_type->Is<U32>()) {
-                ast_expr =
-                    builder_.Call(Source{}, result_type->Build(builder_), utils::Vector{ast_expr});
+                ast_expr = builder_.Call(result_type->Build(builder_), utils::Vector{ast_expr});
             }
             TypedExpression expr{result_type, ast_expr};
             return EmitConstDefOrWriteToHoistedVar(inst, expr);
@@ -5768,7 +5762,7 @@
         }
 
         // Function return type
-        const ast::Type* ret_type = nullptr;
+        ast::Type ret_type;
         if (inst.type_id() != 0) {
             ret_type = parser_impl_.ConvertType(inst.type_id())->Build(builder_);
         } else {
@@ -5787,7 +5781,7 @@
             });
 
         // Emit call to stub, will be replaced with call to atomic builtin by transform::SpirvAtomic
-        auto* call = builder_.Call(Source{}, stub->name->symbol, std::move(exprs));
+        auto* call = builder_.Call(stub->name->symbol, std::move(exprs));
         if (inst.type_id() != 0) {
             auto* result_type = parser_impl_.ConvertType(inst.type_id());
             TypedExpression expr{result_type, call};
@@ -5897,8 +5891,8 @@
     }
     type::TextureDimension dim = texture_type->dims;
     // Number of regular coordinates.
-    uint32_t num_axes = static_cast<uint32_t>(ast::NumCoordinateAxes(dim));
-    bool is_arrayed = ast::IsTextureArray(dim);
+    uint32_t num_axes = static_cast<uint32_t>(type::NumCoordinateAxes(dim));
+    bool is_arrayed = type::IsTextureArray(dim);
     if ((num_axes == 0) || (num_axes > 3)) {
         Fail() << "unsupported image dimensionality for " << texture_type->TypeInfo().name
                << " prompted by " << inst.PrettyPrint();
@@ -6052,7 +6046,7 @@
         for (auto i = src_count; i < dest_count; i++) {
             exprs.Push(parser_impl_.MakeNullExpression(component_type).expr);
         }
-        texel.expr = builder_.Call(Source{}, src_type->Build(builder_), std::move(exprs));
+        texel.expr = builder_.Call(src_type->Build(builder_), std::move(exprs));
     }
 
     return texel.expr;
@@ -6062,7 +6056,7 @@
     if (!value || value.type->Is<I32>()) {
         return value;
     }
-    return {ty_.I32(), builder_.Call(Source{}, builder_.ty.i32(), utils::Vector{value.expr})};
+    return {ty_.I32(), builder_.Call(builder_.ty.i32(), utils::Vector{value.expr})};
 }
 
 TypedExpression FunctionEmitter::ToSignedIfUnsigned(TypedExpression value) {
@@ -6103,7 +6097,7 @@
     auto* member_access = builder_.MemberAccessor(Source{}, member_expr.expr, field_name);
 
     // Generate the builtin function call.
-    auto* call_expr = builder_.Call(Source{}, "arrayLength", builder_.AddressOf(member_access));
+    auto* call_expr = builder_.Call("arrayLength", builder_.AddressOf(member_access));
 
     return {parser_impl_.ConvertType(inst.type_id()), call_expr};
 }
@@ -6142,11 +6136,9 @@
                                                        row_factor, column_factor);
             result_row.Push(elem);
         }
-        result_columns.Push(
-            builder_.Call(Source{}, col_ty->Build(builder_), std::move(result_row)));
+        result_columns.Push(builder_.Call(col_ty->Build(builder_), std::move(result_row)));
     }
-    return {result_ty,
-            builder_.Call(Source{}, result_ty->Build(builder_), std::move(result_columns))};
+    return {result_ty, builder_.Call(result_ty->Build(builder_), std::move(result_columns))};
 }
 
 bool FunctionEmitter::MakeVectorInsertDynamic(const spvtools::opt::Instruction& inst) {
diff --git a/src/tint/reader/spirv/parser_impl.cc b/src/tint/reader/spirv/parser_impl.cc
index 0b7d30e..2589d8e 100644
--- a/src/tint/reader/spirv/parser_impl.cc
+++ b/src/tint/reader/spirv/parser_impl.cc
@@ -24,7 +24,6 @@
 #include "src/tint/ast/disable_validation_attribute.h"
 #include "src/tint/ast/id_attribute.h"
 #include "src/tint/ast/interpolate_attribute.h"
-#include "src/tint/ast/type_name.h"
 #include "src/tint/ast/unary_op_expression.h"
 #include "src/tint/reader/spirv/function.h"
 #include "src/tint/type/depth_texture.h"
diff --git a/src/tint/reader/spirv/parser_impl_convert_type_test.cc b/src/tint/reader/spirv/parser_impl_convert_type_test.cc
index a471632..0dc8ddc 100644
--- a/src/tint/reader/spirv/parser_impl_convert_type_test.cc
+++ b/src/tint/reader/spirv/parser_impl_convert_type_test.cc
@@ -546,7 +546,7 @@
     ASSERT_NE(type, nullptr);
     EXPECT_TRUE(type->Is<Struct>());
 
-    auto* str = type->Build(p->builder());
+    auto str = type->Build(p->builder());
     Program program = p->program();
     EXPECT_EQ(test::ToString(program, str), "S");
 }
@@ -564,7 +564,7 @@
     ASSERT_NE(type, nullptr);
     EXPECT_TRUE(type->Is<Struct>());
 
-    auto* str = type->Build(p->builder());
+    auto str = type->Build(p->builder());
     Program program = p->program();
     EXPECT_EQ(test::ToString(program, str), "S");
 }
@@ -586,7 +586,7 @@
     ASSERT_NE(type, nullptr);
     EXPECT_TRUE(type->Is<Struct>());
 
-    auto* str = type->Build(p->builder());
+    auto str = type->Build(p->builder());
     Program program = p->program();
     EXPECT_EQ(test::ToString(program, str), "S");
 }
diff --git a/src/tint/reader/spirv/parser_impl_test_helper.cc b/src/tint/reader/spirv/parser_impl_test_helper.cc
index 2a6dd06..d0b6ef7 100644
--- a/src/tint/reader/spirv/parser_impl_test_helper.cc
+++ b/src/tint/reader/spirv/parser_impl_test_helper.cc
@@ -66,13 +66,6 @@
             }
             return writer.result();
         },
-        [&](const ast::Type* ty) {
-            std::stringstream out;
-            if (!writer.EmitType(out, ty)) {
-                return "WGSL writer error: " + writer.error();
-            }
-            return out.str();
-        },
         [&](const ast::Identifier* ident) { return program.Symbols().NameFor(ident->symbol); },
         [&](Default) {
             return "<unhandled AST node type " + std::string(node->TypeInfo().name) + ">";
diff --git a/src/tint/reader/spirv/parser_type.cc b/src/tint/reader/spirv/parser_type.cc
index 77b9df4..9051b78 100644
--- a/src/tint/reader/spirv/parser_type.cc
+++ b/src/tint/reader/spirv/parser_type.cc
@@ -149,23 +149,23 @@
 }
 //! @endcond
 
-const ast::Type* Void::Build(ProgramBuilder& b) const {
+ast::Type Void::Build(ProgramBuilder& b) const {
     return b.ty.void_();
 }
 
-const ast::Type* Bool::Build(ProgramBuilder& b) const {
+ast::Type Bool::Build(ProgramBuilder& b) const {
     return b.ty.bool_();
 }
 
-const ast::Type* U32::Build(ProgramBuilder& b) const {
+ast::Type U32::Build(ProgramBuilder& b) const {
     return b.ty.u32();
 }
 
-const ast::Type* F32::Build(ProgramBuilder& b) const {
+ast::Type F32::Build(ProgramBuilder& b) const {
     return b.ty.f32();
 }
 
-const ast::Type* I32::Build(ProgramBuilder& b) const {
+ast::Type I32::Build(ProgramBuilder& b) const {
     return b.ty.i32();
 }
 
@@ -179,7 +179,7 @@
     : type(t), address_space(s), access(a) {}
 Pointer::Pointer(const Pointer&) = default;
 
-const ast::Type* Pointer::Build(ProgramBuilder& b) const {
+ast::Type Pointer::Build(ProgramBuilder& b) const {
     auto store_type = type->Build(b);
     if (!store_type) {
         // TODO(crbug.com/tint/1838): We should not be constructing pointers with 'void' store
@@ -193,28 +193,28 @@
     : type(t), address_space(s), access(a) {}
 Reference::Reference(const Reference&) = default;
 
-const ast::Type* Reference::Build(ProgramBuilder& b) const {
+ast::Type Reference::Build(ProgramBuilder& b) const {
     return type->Build(b);
 }
 
 Vector::Vector(const Type* t, uint32_t s) : type(t), size(s) {}
 Vector::Vector(const Vector&) = default;
 
-const ast::Type* Vector::Build(ProgramBuilder& b) const {
+ast::Type Vector::Build(ProgramBuilder& b) const {
     return b.ty.vec(type->Build(b), size);
 }
 
 Matrix::Matrix(const Type* t, uint32_t c, uint32_t r) : type(t), columns(c), rows(r) {}
 Matrix::Matrix(const Matrix&) = default;
 
-const ast::Type* Matrix::Build(ProgramBuilder& b) const {
+ast::Type Matrix::Build(ProgramBuilder& b) const {
     return b.ty.mat(type->Build(b), columns, rows);
 }
 
 Array::Array(const Type* t, uint32_t sz, uint32_t st) : type(t), size(sz), stride(st) {}
 Array::Array(const Array&) = default;
 
-const ast::Type* Array::Build(ProgramBuilder& b) const {
+ast::Type Array::Build(ProgramBuilder& b) const {
     if (size > 0) {
         if (stride > 0) {
             return b.ty.array(type->Build(b), u32(size), utils::Vector{b.Stride(stride)});
@@ -223,9 +223,9 @@
         }
     } else {
         if (stride > 0) {
-            return b.ty.array(type->Build(b), nullptr, utils::Vector{b.Stride(stride)});
+            return b.ty.array(type->Build(b), utils::Vector{b.Stride(stride)});
         } else {
-            return b.ty.array(type->Build(b), nullptr);
+            return b.ty.array(type->Build(b));
         }
     }
 }
@@ -233,7 +233,7 @@
 Sampler::Sampler(type::SamplerKind k) : kind(k) {}
 Sampler::Sampler(const Sampler&) = default;
 
-const ast::Type* Sampler::Build(ProgramBuilder& b) const {
+ast::Type Sampler::Build(ProgramBuilder& b) const {
     return b.ty.sampler(kind);
 }
 
@@ -243,14 +243,14 @@
 DepthTexture::DepthTexture(type::TextureDimension d) : Base(d) {}
 DepthTexture::DepthTexture(const DepthTexture&) = default;
 
-const ast::Type* DepthTexture::Build(ProgramBuilder& b) const {
+ast::Type DepthTexture::Build(ProgramBuilder& b) const {
     return b.ty.depth_texture(dims);
 }
 
 DepthMultisampledTexture::DepthMultisampledTexture(type::TextureDimension d) : Base(d) {}
 DepthMultisampledTexture::DepthMultisampledTexture(const DepthMultisampledTexture&) = default;
 
-const ast::Type* DepthMultisampledTexture::Build(ProgramBuilder& b) const {
+ast::Type DepthMultisampledTexture::Build(ProgramBuilder& b) const {
     return b.ty.depth_multisampled_texture(dims);
 }
 
@@ -258,14 +258,14 @@
     : Base(d), type(t) {}
 MultisampledTexture::MultisampledTexture(const MultisampledTexture&) = default;
 
-const ast::Type* MultisampledTexture::Build(ProgramBuilder& b) const {
+ast::Type MultisampledTexture::Build(ProgramBuilder& b) const {
     return b.ty.multisampled_texture(dims, type->Build(b));
 }
 
 SampledTexture::SampledTexture(type::TextureDimension d, const Type* t) : Base(d), type(t) {}
 SampledTexture::SampledTexture(const SampledTexture&) = default;
 
-const ast::Type* SampledTexture::Build(ProgramBuilder& b) const {
+ast::Type SampledTexture::Build(ProgramBuilder& b) const {
     return b.ty.sampled_texture(dims, type->Build(b));
 }
 
@@ -273,7 +273,7 @@
     : Base(d), format(f), access(a) {}
 StorageTexture::StorageTexture(const StorageTexture&) = default;
 
-const ast::Type* StorageTexture::Build(ProgramBuilder& b) const {
+ast::Type StorageTexture::Build(ProgramBuilder& b) const {
     return b.ty.storage_texture(dims, format, access);
 }
 
@@ -284,7 +284,7 @@
 Alias::Alias(Symbol n, const Type* ty) : Base(n), type(ty) {}
 Alias::Alias(const Alias&) = default;
 
-const ast::Type* Alias::Build(ProgramBuilder& b) const {
+ast::Type Alias::Build(ProgramBuilder& b) const {
     return b.ty(name);
 }
 
@@ -292,7 +292,7 @@
 Struct::Struct(const Struct&) = default;
 Struct::~Struct() = default;
 
-const ast::Type* Struct::Build(ProgramBuilder& b) const {
+ast::Type Struct::Build(ProgramBuilder& b) const {
     return b.ty(name);
 }
 
diff --git a/src/tint/reader/spirv/parser_type.h b/src/tint/reader/spirv/parser_type.h
index 04c8c67..9805607 100644
--- a/src/tint/reader/spirv/parser_type.h
+++ b/src/tint/reader/spirv/parser_type.h
@@ -19,6 +19,7 @@
 #include <string>
 #include <vector>
 
+#include "src/tint/ast/type.h"
 #include "src/tint/castable.h"
 #include "src/tint/symbol.h"
 #include "src/tint/type/access.h"
@@ -32,9 +33,6 @@
 namespace tint {
 class ProgramBuilder;
 }  // namespace tint
-namespace tint::ast {
-class Type;
-}  // namespace tint::ast
 
 namespace tint::reader::spirv {
 
@@ -50,7 +48,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    virtual const ast::Type* Build(ProgramBuilder& b) const = 0;
+    virtual ast::Type Build(ProgramBuilder& b) const = 0;
 
     /// @returns the inner most store type if this is a pointer, `this` otherwise
     const Type* UnwrapPtr() const;
@@ -102,7 +100,7 @@
 struct Void final : public Castable<Void, Type> {
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -114,7 +112,7 @@
 struct Bool final : public Castable<Bool, Type> {
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -126,7 +124,7 @@
 struct U32 final : public Castable<U32, Type> {
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -138,7 +136,7 @@
 struct F32 final : public Castable<F32, Type> {
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -150,7 +148,7 @@
 struct I32 final : public Castable<I32, Type> {
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -172,7 +170,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -203,7 +201,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -231,7 +229,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -258,7 +256,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -288,7 +286,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -315,7 +313,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -354,7 +352,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -374,7 +372,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -395,7 +393,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -419,7 +417,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -444,7 +442,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
 #ifndef NDEBUG
     /// @returns a string representation of the type, for debug purposes only
@@ -493,7 +491,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
     /// the aliased type
     Type const* const type;
@@ -515,7 +513,7 @@
 
     /// @param b the ProgramBuilder used to construct the AST types
     /// @returns the constructed ast::Type node for the given type
-    const ast::Type* Build(ProgramBuilder& b) const override;
+    ast::Type Build(ProgramBuilder& b) const override;
 
     /// the member types
     const TypeList members;
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index 3021bf6..0fbb29f 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -16,7 +16,6 @@
 
 #include <limits>
 
-#include "src/tint/ast/array.h"
 #include "src/tint/ast/assignment_statement.h"
 #include "src/tint/ast/bitcast_expression.h"
 #include "src/tint/ast/break_if_statement.h"
@@ -32,10 +31,8 @@
 #include "src/tint/ast/return_statement.h"
 #include "src/tint/ast/stage_attribute.h"
 #include "src/tint/ast/switch_statement.h"
-#include "src/tint/ast/type_name.h"
 #include "src/tint/ast/unary_op_expression.h"
 #include "src/tint/ast/variable_decl_statement.h"
-#include "src/tint/ast/vector.h"
 #include "src/tint/ast/workgroup_attribute.h"
 #include "src/tint/reader/wgsl/classify_template_args.h"
 #include "src/tint/reader/wgsl/lexer.h"
@@ -180,7 +177,7 @@
 
 ParserImpl::TypedIdentifier::TypedIdentifier(const TypedIdentifier&) = default;
 
-ParserImpl::TypedIdentifier::TypedIdentifier(const ast::Type* type_in,
+ParserImpl::TypedIdentifier::TypedIdentifier(ast::Type type_in,
                                              std::string name_in,
                                              Source source_in)
     : type(type_in), name(std::move(name_in)), source(std::move(source_in)) {}
@@ -194,7 +191,7 @@
 ParserImpl::FunctionHeader::FunctionHeader(Source src,
                                            std::string n,
                                            utils::VectorRef<const ast::Parameter*> p,
-                                           const ast::Type* ret_ty,
+                                           ast::Type ret_ty,
                                            utils::VectorRef<const ast::Attribute*> ret_attrs)
     : source(src),
       name(n),
@@ -215,7 +212,7 @@
                                      std::string name_in,
                                      type::AddressSpace address_space_in,
                                      type::Access access_in,
-                                     const ast::Type* type_in)
+                                     ast::Type type_in)
     : source(std::move(source_in)),
       name(std::move(name_in)),
       address_space(address_space_in),
@@ -733,7 +730,7 @@
 //  | multisampled_texture_type LESS_THAN type_specifier GREATER_THAN
 //  | storage_texture_type LESS_THAN texel_format
 //                         COMMA access_mode GREATER_THAN
-Maybe<const ast::Type*> ParserImpl::texture_and_sampler_types() {
+Maybe<ast::Type> ParserImpl::texture_and_sampler_types() {
     auto type = sampler_type();
     if (type.matched) {
         return type;
@@ -811,7 +808,7 @@
 // sampler_type
 //  : SAMPLER
 //  | SAMPLER_COMPARISON
-Maybe<const ast::Type*> ParserImpl::sampler_type() {
+Maybe<ast::Type> ParserImpl::sampler_type() {
     Source source;
     if (match(Token::Type::kSampler, &source)) {
         return builder_.ty.sampler(source, type::SamplerKind::kSampler);
@@ -861,7 +858,7 @@
 
 // external_texture
 //  : TEXTURE_EXTERNAL
-Maybe<const ast::Type*> ParserImpl::external_texture() {
+Maybe<ast::Type> ParserImpl::external_texture() {
     Source source;
     if (match(Token::Type::kTextureExternal, &source)) {
         return builder_.ty.external_texture(source);
@@ -908,7 +905,7 @@
 //  | TEXTURE_DEPTH_CUBE
 //  | TEXTURE_DEPTH_CUBE_ARRAY
 //  | TEXTURE_DEPTH_MULTISAMPLED_2D
-Maybe<const ast::Type*> ParserImpl::depth_texture_type() {
+Maybe<ast::Type> ParserImpl::depth_texture_type() {
     Source source;
     if (match(Token::Type::kTextureDepth2d, &source)) {
         return builder_.ty.depth_texture(source, type::TextureDimension::k2d);
@@ -958,7 +955,7 @@
     }
 
     if (allow_inferred && !peek_is(Token::Type::kColon)) {
-        return TypedIdentifier{nullptr, ident.value, ident.source};
+        return TypedIdentifier{ast::Type{}, ident.value, ident.source};
     }
 
     if (!expect(use, Token::Type::kColon)) {
@@ -1130,7 +1127,7 @@
 //   | mat_prefix LESS_THAN type_specifier GREATER_THAN
 //   | vec_prefix LESS_THAN type_specifier GREATER_THAN
 //   | texture_and_sampler_types
-Maybe<const ast::Type*> ParserImpl::type_specifier_without_ident() {
+Maybe<ast::Type> ParserImpl::type_specifier_without_ident() {
     auto& t = peek();
 
     if (match(Token::Type::kBool)) {
@@ -1198,7 +1195,7 @@
 // type_specifier
 //   : IDENTIFIER
 //   | type_specifier_without_ident
-Maybe<const ast::Type*> ParserImpl::type_specifier() {
+Maybe<ast::Type> ParserImpl::type_specifier() {
     auto& t = peek();
     Source source;
     if (match(Token::Type::kIdentifier, &source)) {
@@ -1243,7 +1240,7 @@
     return add_error(t.source(), err.str());
 }
 
-Expect<const ast::Type*> ParserImpl::expect_type(std::string_view use) {
+Expect<ast::Type> ParserImpl::expect_type(std::string_view use) {
     auto type = type_specifier();
     if (type.errored) {
         return Failure::kErrored;
@@ -1255,13 +1252,13 @@
 }
 
 // LESS_THAN address_space COMMA type_specifier ( COMMA access_mode )? GREATER_THAN
-Expect<const ast::Type*> ParserImpl::expect_type_specifier_pointer(const Source& s) {
+Expect<ast::Type> ParserImpl::expect_type_specifier_pointer(const Source& s) {
     const char* use = "ptr declaration";
 
     auto address_space = type::AddressSpace::kNone;
     auto access = type::Access::kUndefined;
 
-    auto subtype = expect_template_arg_block(use, [&]() -> Expect<const ast::Type*> {
+    auto subtype = expect_template_arg_block(use, [&]() -> Expect<ast::Type> {
         auto sc = expect_address_space(use);
         if (sc.errored) {
             return Failure::kErrored;
@@ -1296,7 +1293,7 @@
 }
 
 // LESS_THAN type_specifier GREATER_THAN
-Expect<const ast::Type*> ParserImpl::expect_type_specifier_atomic(const Source& s) {
+Expect<ast::Type> ParserImpl::expect_type_specifier_atomic(const Source& s) {
     const char* use = "atomic declaration";
 
     auto subtype = expect_template_arg_block(use, [&] { return expect_type(use); });
@@ -1308,7 +1305,7 @@
 }
 
 // LESS_THAN type_specifier GREATER_THAN
-Expect<const ast::Type*> ParserImpl::expect_type_specifier_vector(const Source& s, uint32_t count) {
+Expect<ast::Type> ParserImpl::expect_type_specifier_vector(const Source& s, uint32_t count) {
     const char* use = "vector";
     auto ty = expect_template_arg_block(use, [&] { return expect_type(use); });
     if (ty.errored) {
@@ -1319,11 +1316,11 @@
 }
 
 // LESS_THAN type_specifier ( COMMA element_count_expression )? GREATER_THAN
-Expect<const ast::Type*> ParserImpl::expect_type_specifier_array(const Source& s) {
+Expect<ast::Type> ParserImpl::expect_type_specifier_array(const Source& s) {
     const char* use = "array declaration";
 
     struct TypeAndSize {
-        const ast::Type* type = nullptr;
+        ast::Type type;
         const ast::Expression* size = nullptr;
     };
 
@@ -1352,12 +1349,16 @@
         return Failure::kErrored;
     }
 
-    return builder_.ty.array(make_source_range_from(s), type_size->type, type_size->size);
+    if (type_size->size) {
+        return builder_.ty.array(make_source_range_from(s), type_size->type, type_size->size);
+    } else {
+        return builder_.ty.array(make_source_range_from(s), type_size->type);
+    }
 }
 
 // LESS_THAN type_specifier GREATER_THAN
-Expect<const ast::Type*> ParserImpl::expect_type_specifier_matrix(const Source& s,
-                                                                  const MatrixDimensions& dims) {
+Expect<ast::Type> ParserImpl::expect_type_specifier_matrix(const Source& s,
+                                                           const MatrixDimensions& dims) {
     const char* use = "matrix";
     auto ty = expect_template_arg_block(use, [&] { return expect_type(use); });
     if (ty.errored) {
@@ -1542,7 +1543,7 @@
         }
     }
 
-    const ast::Type* return_type = nullptr;
+    ast::Type return_type;
     AttributeList return_attributes;
 
     if (match(Token::Type::kArrow)) {
@@ -2417,10 +2418,7 @@
 
     return builder_.CallStmt(
         t.source(),
-        create<ast::CallExpression>(
-            t.source(),
-            create<ast::Identifier>(t.source(), builder_.Symbols().Register(t.to_str())),
-            std::move(params.value)));
+        builder_.Call(t.source(), builder_.Expr(t.source(), t.to_str()), std::move(params.value)));
 }
 
 // break_statement
@@ -2530,7 +2528,7 @@
 //  Note, `ident` is pulled out to `primary_expression` as it's the only one that
 //  doesn't create a `type`. Then we can just return a `type` from here on match and
 //  deal with `ident` in `primary_expression.
-Maybe<const ast::Type*> ParserImpl::callable() {
+Maybe<const ast::IdentifierExpression*> ParserImpl::callable() {
     auto& t = peek();
 
     //  This _must_ match `type_specifier_without_ident` before any of the other types as they're
@@ -2541,22 +2539,22 @@
         return Failure::kErrored;
     }
     if (ty.matched) {
-        return ty.value;
+        return ty->expr;
     }
 
     if (match(Token::Type::kArray)) {
-        return builder_.ty.array(make_source_range_from(t.source()), nullptr, nullptr);
+        return builder_.ty.array<Infer>(make_source_range_from(t.source()));
     }
 
     auto vec = vec_prefix();
     if (vec.matched) {
-        return builder_.ty.vec(make_source_range_from(t.source()), nullptr, vec.value);
+        return builder_.ty.vec<Infer>(make_source_range_from(t.source()), vec.value);
     }
 
     auto mat = mat_prefix();
     if (mat.matched) {
-        return builder_.ty.mat(make_source_range_from(t.source()), nullptr, mat.value.columns,
-                               mat.value.rows);
+        return builder_.ty.mat<Infer>(make_source_range_from(t.source()), mat.value.columns,
+                                      mat.value.rows);
     }
 
     return Failure::kNoMatch;
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h
index 933611b..455300d 100644
--- a/src/tint/reader/wgsl/parser_impl.h
+++ b/src/tint/reader/wgsl/parser_impl.h
@@ -220,12 +220,12 @@
         /// @param type_in parsed type
         /// @param name_in parsed identifier
         /// @param source_in source to the identifier
-        TypedIdentifier(const ast::Type* type_in, std::string name_in, Source source_in);
+        TypedIdentifier(ast::Type type_in, std::string name_in, Source source_in);
         /// Destructor
         ~TypedIdentifier();
 
-        /// Parsed type. May be nullptr for inferred types.
-        const ast::Type* type = nullptr;
+        /// Parsed type. type.expr be nullptr for inferred types.
+        ast::Type type;
         /// Parsed identifier.
         std::string name;
         /// Source to the identifier.
@@ -248,7 +248,7 @@
         FunctionHeader(Source src,
                        std::string n,
                        utils::VectorRef<const ast::Parameter*> p,
-                       const ast::Type* ret_ty,
+                       ast::Type ret_ty,
                        utils::VectorRef<const ast::Attribute*> ret_attrs);
         /// Destructor
         ~FunctionHeader();
@@ -264,7 +264,7 @@
         /// Function parameters
         utils::Vector<const ast::Parameter*, 8> params;
         /// Function return type
-        const ast::Type* return_type = nullptr;
+        ast::Type return_type;
         /// Function return type attributes
         AttributeList return_type_attributes;
     };
@@ -286,7 +286,7 @@
                     std::string name_in,
                     type::AddressSpace address_space_in,
                     type::Access access_in,
-                    const ast::Type* type_in);
+                    ast::Type type_in);
         /// Destructor
         ~VarDeclInfo();
 
@@ -299,7 +299,7 @@
         /// Variable access control
         type::Access access = type::Access::kUndefined;
         /// Variable type
-        const ast::Type* type = nullptr;
+        ast::Type type;
     };
 
     /// VariableQualifier contains the parsed information for a variable qualifier
@@ -449,7 +449,7 @@
     Maybe<const ast::Alias*> type_alias_decl();
     /// Parses a `callable` grammar element
     /// @returns the type or nullptr
-    Maybe<const ast::Type*> callable();
+    Maybe<const ast::IdentifierExpression*> callable();
     /// Parses a `vec_prefix` grammar element
     /// @returns the vector size or nullptr
     Maybe<uint32_t> vec_prefix();
@@ -458,10 +458,10 @@
     Maybe<MatrixDimensions> mat_prefix();
     /// Parses a `type_specifier_without_ident` grammar element
     /// @returns the parsed Type or nullptr if none matched.
-    Maybe<const ast::Type*> type_specifier_without_ident();
+    Maybe<ast::Type> type_specifier_without_ident();
     /// Parses a `type_specifier` grammar element
     /// @returns the parsed Type or nullptr if none matched.
-    Maybe<const ast::Type*> type_specifier();
+    Maybe<ast::Type> type_specifier();
     /// Parses an `address_space` grammar element, erroring on parse failure.
     /// @param use a description of what was being parsed if an error was raised.
     /// @returns the address space or type::AddressSpace::kNone if none matched
@@ -483,10 +483,10 @@
     Maybe<const ast::Function*> function_decl(AttributeList& attrs);
     /// Parses a `texture_and_sampler_types` grammar element
     /// @returns the parsed Type or nullptr if none matched.
-    Maybe<const ast::Type*> texture_and_sampler_types();
+    Maybe<ast::Type> texture_and_sampler_types();
     /// Parses a `sampler_type` grammar element
     /// @returns the parsed Type or nullptr if none matched.
-    Maybe<const ast::Type*> sampler_type();
+    Maybe<ast::Type> sampler_type();
     /// Parses a `multisampled_texture_type` grammar element
     /// @returns returns the multisample texture dimension or kNone if none
     /// matched.
@@ -500,10 +500,10 @@
     Maybe<const type::TextureDimension> storage_texture_type();
     /// Parses a `depth_texture_type` grammar element
     /// @returns the parsed Type or nullptr if none matched.
-    Maybe<const ast::Type*> depth_texture_type();
+    Maybe<ast::Type> depth_texture_type();
     /// Parses a 'texture_external_type' grammar element
     /// @returns the parsed Type or nullptr if none matched
-    Maybe<const ast::Type*> external_texture();
+    Maybe<ast::Type> external_texture();
     /// Parses a `texel_format` grammar element
     /// @param use a description of what was being parsed if an error was raised
     /// @returns returns the texel format or kNone if none matched.
@@ -891,12 +891,11 @@
     /// Used to ensure that all attributes are consumed.
     bool expect_attributes_consumed(utils::VectorRef<const ast::Attribute*> list);
 
-    Expect<const ast::Type*> expect_type_specifier_pointer(const Source& s);
-    Expect<const ast::Type*> expect_type_specifier_atomic(const Source& s);
-    Expect<const ast::Type*> expect_type_specifier_vector(const Source& s, uint32_t count);
-    Expect<const ast::Type*> expect_type_specifier_array(const Source& s);
-    Expect<const ast::Type*> expect_type_specifier_matrix(const Source& s,
-                                                          const MatrixDimensions& dims);
+    Expect<ast::Type> expect_type_specifier_pointer(const Source& s);
+    Expect<ast::Type> expect_type_specifier_atomic(const Source& s);
+    Expect<ast::Type> expect_type_specifier_vector(const Source& s, uint32_t count);
+    Expect<ast::Type> expect_type_specifier_array(const Source& s);
+    Expect<ast::Type> expect_type_specifier_matrix(const Source& s, const MatrixDimensions& dims);
 
     /// Parses the given enum, providing sensible error messages if the next token does not match
     /// any of the enum values.
@@ -910,7 +909,7 @@
                              const char* const (&strings)[N],
                              std::string_view use = "");
 
-    Expect<const ast::Type*> expect_type(std::string_view use);
+    Expect<ast::Type> expect_type(std::string_view use);
 
     Maybe<const ast::Statement*> non_block_statement();
     Maybe<const ast::Statement*> for_header_initializer();
diff --git a/src/tint/reader/wgsl/parser_impl_call_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_call_stmt_test.cc
index 59aeb3c..63f9574 100644
--- a/src/tint/reader/wgsl/parser_impl_call_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_call_stmt_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -34,7 +35,7 @@
     ASSERT_TRUE(e->Is<ast::CallStatement>());
     auto* c = e->As<ast::CallStatement>()->expr;
 
-    EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
+    ast::CheckIdentifier(p->builder().Symbols(), c->target, "a");
 
     EXPECT_EQ(c->args.Length(), 0u);
 }
@@ -50,7 +51,7 @@
     ASSERT_TRUE(e->Is<ast::CallStatement>());
     auto* c = e->As<ast::CallStatement>()->expr;
 
-    EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
+    ast::CheckIdentifier(p->builder().Symbols(), c->target, "a");
 
     EXPECT_EQ(c->args.Length(), 3u);
     EXPECT_TRUE(c->args[0]->Is<ast::IntLiteralExpression>());
@@ -69,7 +70,7 @@
     ASSERT_TRUE(e->Is<ast::CallStatement>());
     auto* c = e->As<ast::CallStatement>()->expr;
 
-    EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
+    ast::CheckIdentifier(p->builder().Symbols(), c->target, "a");
 
     EXPECT_EQ(c->args.Length(), 2u);
     EXPECT_TRUE(c->args[0]->Is<ast::IntLiteralExpression>());
diff --git a/src/tint/reader/wgsl/parser_impl_callable_test.cc b/src/tint/reader/wgsl/parser_impl_callable_test.cc
index 408a36f..647e2b2 100644
--- a/src/tint/reader/wgsl/parser_impl_callable_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_callable_test.cc
@@ -12,11 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
 namespace {
 
+using namespace tint::number_suffixes;  // NOLINT
+
 TEST_F(ParserImplTest, Callable_Array) {
     auto p = parser("array");
     auto t = p->callable();
@@ -25,12 +28,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(t.value, nullptr);
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    EXPECT_FALSE(a->IsRuntimeArray());
-    EXPECT_EQ(a->type, nullptr);
-    EXPECT_EQ(a->count, nullptr);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "array");
 }
 
 TEST_F(ParserImplTest, Callable_VecPrefix) {
@@ -41,11 +40,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(t.value, nullptr);
-    ASSERT_TRUE(t.value->Is<ast::Vector>());
 
-    auto* v = t.value->As<ast::Vector>();
-    EXPECT_EQ(v->type, nullptr);
-    EXPECT_EQ(v->width, 3u);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "vec3");
 }
 
 TEST_F(ParserImplTest, Callable_MatPrefix) {
@@ -56,12 +52,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(t.value, nullptr);
-    ASSERT_TRUE(t.value->Is<ast::Matrix>());
 
-    auto* m = t.value->As<ast::Matrix>();
-    EXPECT_EQ(m->type, nullptr);
-    EXPECT_EQ(m->columns, 3u);
-    EXPECT_EQ(m->rows, 2u);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "mat3x2");
 }
 
 TEST_F(ParserImplTest, Callable_TypeDecl_Array) {
@@ -72,18 +64,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(t.value, nullptr);
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    EXPECT_FALSE(a->IsRuntimeArray());
-
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "f32");
-
-    auto* size = a->count->As<ast::IntLiteralExpression>();
-    ASSERT_NE(size, nullptr);
-    EXPECT_EQ(size->value, 2);
-    EXPECT_EQ(size->suffix, ast::IntLiteralExpression::Suffix::kNone);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("array", "f32", 2_a));
 }
 
 TEST_F(ParserImplTest, Callable_TypeDecl_Array_Runtime) {
@@ -94,15 +76,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(t.value, nullptr);
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    EXPECT_TRUE(a->IsRuntimeArray());
-
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "f32");
-
-    ASSERT_EQ(a->count, nullptr);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("array", "f32"));
 }
 
 TEST_F(ParserImplTest, Callable_TypeDecl_VecPrefix) {
@@ -113,14 +88,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(t.value, nullptr);
-    ASSERT_TRUE(t.value->Is<ast::Vector>());
 
-    auto* v = t.value->As<ast::Vector>();
-
-    ASSERT_TRUE(v->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(v->type->As<ast::TypeName>()->name->symbol), "f32");
-
-    EXPECT_EQ(v->width, 3u);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("vec3", "f32"));
 }
 
 TEST_F(ParserImplTest, Callable_TypeDecl_MatPrefix) {
@@ -131,15 +100,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(t.value, nullptr);
-    ASSERT_TRUE(t.value->Is<ast::Matrix>());
 
-    auto* m = t.value->As<ast::Matrix>();
-
-    ASSERT_TRUE(m->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(m->type->As<ast::TypeName>()->name->symbol), "f32");
-
-    EXPECT_EQ(m->columns, 3u);
-    EXPECT_EQ(m->rows, 2u);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("mat3x2", "f32"));
 }
 
 TEST_F(ParserImplTest, Callable_NoMatch) {
diff --git a/src/tint/reader/wgsl/parser_impl_depth_texture_test.cc b/src/tint/reader/wgsl/parser_impl_depth_texture_test.cc
index e04bed3..f807943 100644
--- a/src/tint/reader/wgsl/parser_impl_depth_texture_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_depth_texture_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 #include "src/tint/type/depth_texture.h"
 #include "src/tint/type/texture_dimension.h"
@@ -33,9 +34,8 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol),
-              "texture_depth_2d");
-    EXPECT_FALSE(p->has_error());
+
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "texture_depth_2d");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
 }
 
@@ -45,9 +45,8 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol),
-              "texture_depth_2d_array");
-    EXPECT_FALSE(p->has_error());
+
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "texture_depth_2d_array");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 23u}}));
 }
 
@@ -57,9 +56,8 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol),
-              "texture_depth_cube");
-    EXPECT_FALSE(p->has_error());
+
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "texture_depth_cube");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
 }
 
@@ -69,9 +67,8 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol),
-              "texture_depth_cube_array");
-    EXPECT_FALSE(p->has_error());
+
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "texture_depth_cube_array");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 25u}}));
 }
 
@@ -81,9 +78,8 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol),
-              "texture_depth_multisampled_2d");
-    EXPECT_FALSE(p->has_error());
+
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "texture_depth_multisampled_2d");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 30u}}));
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_diagnostic_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_diagnostic_attribute_test.cc
index c383c4d..6c5e6f6 100644
--- a/src/tint/reader/wgsl/parser_impl_diagnostic_attribute_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_diagnostic_attribute_test.cc
@@ -15,6 +15,7 @@
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 #include "src/tint/ast/diagnostic_control.h"
+#include "src/tint/ast/test_helper.h"
 
 namespace tint::reader::wgsl {
 namespace {
@@ -29,7 +30,7 @@
     EXPECT_EQ(d->control.severity, ast::DiagnosticSeverity::kOff);
     auto* r = d->control.rule_name;
     ASSERT_NE(r, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(r->symbol), "foo");
+    ast::CheckIdentifier(p->builder().Symbols(), r, "foo");
 }
 
 }  // namespace
diff --git a/src/tint/reader/wgsl/parser_impl_diagnostic_control_test.cc b/src/tint/reader/wgsl/parser_impl_diagnostic_control_test.cc
index 7c4b417..4454a9d 100644
--- a/src/tint/reader/wgsl/parser_impl_diagnostic_control_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_diagnostic_control_test.cc
@@ -15,6 +15,7 @@
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 #include "src/tint/ast/diagnostic_control.h"
+#include "src/tint/ast/test_helper.h"
 
 namespace tint::reader::wgsl {
 namespace {
@@ -32,7 +33,7 @@
 
     auto* r = e->rule_name;
     ASSERT_NE(r, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(r->symbol), "foo");
+    ast::CheckIdentifier(p->builder().Symbols(), r, "foo");
 }
 INSTANTIATE_TEST_SUITE_P(DiagnosticControlParserTest,
                          DiagnosticControlParserTest,
@@ -50,7 +51,7 @@
 
     auto* r = e->rule_name;
     ASSERT_NE(r, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(r->symbol), "foo");
+    ast::CheckIdentifier(p->builder().Symbols(), r, "foo");
 }
 
 TEST_F(ParserImplTest, DiagnosticControl_MissingOpenParen) {
diff --git a/src/tint/reader/wgsl/parser_impl_diagnostic_directive_test.cc b/src/tint/reader/wgsl/parser_impl_diagnostic_directive_test.cc
index e3e767d..ab50f34 100644
--- a/src/tint/reader/wgsl/parser_impl_diagnostic_directive_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_diagnostic_directive_test.cc
@@ -15,6 +15,7 @@
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 #include "src/tint/ast/diagnostic_control.h"
+#include "src/tint/ast/test_helper.h"
 
 namespace tint::reader::wgsl {
 namespace {
@@ -32,7 +33,7 @@
 
     auto* r = directive->control.rule_name;
     ASSERT_NE(r, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(r->symbol), "foo");
+    ast::CheckIdentifier(p->builder().Symbols(), r, "foo");
 }
 
 TEST_F(ParserImplTest, DiagnosticDirective_MissingSemicolon) {
diff --git a/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc
index a9b9818..05151d0 100644
--- a/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/ast/workgroup_attribute.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
@@ -310,9 +311,7 @@
               ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_NE(values[1], nullptr);
-    auto* y_ident = values[1]->As<ast::IdentifierExpression>();
-    ASSERT_NE(y_ident, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(y_ident->identifier->symbol), "height");
+    ast::CheckIdentifier(p->builder().Symbols(), values[1], "height");
 
     ASSERT_EQ(values[2], nullptr);
 }
diff --git a/src/tint/reader/wgsl/parser_impl_function_decl_test.cc b/src/tint/reader/wgsl/parser_impl_function_decl_test.cc
index dad9b55..2c26bac 100644
--- a/src/tint/reader/wgsl/parser_impl_function_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_function_decl_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/ast/workgroup_attribute.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 #include "src/tint/utils/string.h"
@@ -240,9 +241,7 @@
     EXPECT_EQ(f->name->symbol, p->builder().Symbols().Get("main"));
     ASSERT_NE(f->return_type, nullptr);
 
-    ASSERT_TRUE(f->return_type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(f->return_type->As<ast::TypeName>()->name->symbol),
-              "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), f->return_type, "f32");
 
     ASSERT_EQ(f->params.Length(), 0u);
 
diff --git a/src/tint/reader/wgsl/parser_impl_function_header_test.cc b/src/tint/reader/wgsl/parser_impl_function_header_test.cc
index 7007c98..f68ea69 100644
--- a/src/tint/reader/wgsl/parser_impl_function_header_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_function_header_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -52,9 +53,7 @@
 
     EXPECT_EQ(f->name, "main");
     EXPECT_EQ(f->params.Length(), 0u);
-    ASSERT_TRUE(f->return_type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(f->return_type->As<ast::TypeName>()->name->symbol),
-              "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), f->return_type, "f32");
     ASSERT_EQ(f->return_type_attributes.Length(), 1u);
 
     auto* loc = f->return_type_attributes[0]->As<ast::LocationAttribute>();
@@ -73,9 +72,7 @@
 
     EXPECT_EQ(f->name, "main");
     EXPECT_EQ(f->params.Length(), 0u);
-    ASSERT_TRUE(f->return_type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(f->return_type->As<ast::TypeName>()->name->symbol),
-              "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), f->return_type, "f32");
     ASSERT_EQ(f->return_type_attributes.Length(), 1u);
     EXPECT_TRUE(f->return_type_attributes[0]->Is<ast::InvariantAttribute>());
 }
diff --git a/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc b/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc
index 7bb7a6f..98f3de1 100644
--- a/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -44,8 +45,7 @@
 
     EXPECT_EQ(c->name->symbol, p->builder().Symbols().Get("a"));
     ASSERT_NE(c->type, nullptr);
-    ASSERT_TRUE(c->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(c->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), c->type, "f32");
 
     EXPECT_EQ(c->source.range.begin.line, 1u);
     EXPECT_EQ(c->source.range.begin.column, 7u);
@@ -121,9 +121,7 @@
 
     EXPECT_EQ(override->name->symbol, p->builder().Symbols().Get("a"));
     ASSERT_NE(override->type, nullptr);
-    ASSERT_TRUE(override->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(override->type->As<ast::TypeName>()->name->symbol),
-              "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), override->type, "f32");
 
     EXPECT_EQ(override->source.range.begin.line, 1u);
     EXPECT_EQ(override->source.range.begin.column, 17u);
@@ -153,9 +151,7 @@
 
     EXPECT_EQ(override->name->symbol, p->builder().Symbols().Get("a"));
     ASSERT_NE(override->type, nullptr);
-    ASSERT_TRUE(override->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(override->type->As<ast::TypeName>()->name->symbol),
-              "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), override->type, "f32");
 
     EXPECT_EQ(override->source.range.begin.line, 1u);
     EXPECT_EQ(override->source.range.begin.column, 18u);
@@ -185,9 +181,7 @@
 
     EXPECT_EQ(override->name->symbol, p->builder().Symbols().Get("a"));
     ASSERT_NE(override->type, nullptr);
-    ASSERT_TRUE(override->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(override->type->As<ast::TypeName>()->name->symbol),
-              "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), override->type, "f32");
 
     EXPECT_EQ(override->source.range.begin.line, 1u);
     EXPECT_EQ(override->source.range.begin.column, 10u);
diff --git a/src/tint/reader/wgsl/parser_impl_global_decl_test.cc b/src/tint/reader/wgsl/parser_impl_global_decl_test.cc
index db5db00..a14507c 100644
--- a/src/tint/reader/wgsl/parser_impl_global_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_global_decl_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -33,7 +34,7 @@
 
     auto* v = program.AST().GlobalVariables()[0];
     EXPECT_EQ(v->name->symbol, program.Symbols().Get("a"));
-    EXPECT_TRUE(Is<ast::Vector>(v->type));
+    ast::CheckIdentifier(program.Symbols(), v->type, ast::Template("vec2", "i32"));
 }
 
 TEST_F(ParserImplTest, GlobalDecl_GlobalVariable_Inferred) {
@@ -106,9 +107,8 @@
     auto program = p->program();
     ASSERT_EQ(program.AST().TypeDecls().Length(), 1u);
     ASSERT_TRUE(program.AST().TypeDecls()[0]->Is<ast::Alias>());
-    EXPECT_EQ(
-        program.Symbols().NameFor(program.AST().TypeDecls()[0]->As<ast::Alias>()->name->symbol),
-        "A");
+    ast::CheckIdentifier(program.Symbols(), program.AST().TypeDecls()[0]->As<ast::Alias>()->name,
+                         "A");
 }
 
 TEST_F(ParserImplTest, GlobalDecl_TypeAlias_StructIdent) {
@@ -129,9 +129,7 @@
     ASSERT_TRUE(program.AST().TypeDecls()[1]->Is<ast::Alias>());
     auto* alias = program.AST().TypeDecls()[1]->As<ast::Alias>();
     EXPECT_EQ(alias->name->symbol, program.Symbols().Get("B"));
-    auto* tn = alias->type->As<ast::TypeName>();
-    EXPECT_NE(tn, nullptr);
-    EXPECT_EQ(tn->name->symbol, str->name->symbol);
+    ast::CheckIdentifier(program.Symbols(), alias->type, "A");
 }
 
 // TODO(crbug.com/tint/1812): DEPRECATED
@@ -143,9 +141,8 @@
     auto program = p->program();
     ASSERT_EQ(program.AST().TypeDecls().Length(), 1u);
     ASSERT_TRUE(program.AST().TypeDecls()[0]->Is<ast::Alias>());
-    EXPECT_EQ(
-        program.Symbols().NameFor(program.AST().TypeDecls()[0]->As<ast::Alias>()->name->symbol),
-        "A");
+    ast::CheckIdentifier(program.Symbols(), program.AST().TypeDecls()[0]->As<ast::Alias>()->name,
+                         "A");
 }
 
 // TODO(crbug.com/tint/1812): DEPRECATED
@@ -167,9 +164,7 @@
     ASSERT_TRUE(program.AST().TypeDecls()[1]->Is<ast::Alias>());
     auto* alias = program.AST().TypeDecls()[1]->As<ast::Alias>();
     EXPECT_EQ(alias->name->symbol, program.Symbols().Get("B"));
-    auto* tn = alias->type->As<ast::TypeName>();
-    EXPECT_NE(tn, nullptr);
-    EXPECT_EQ(tn->name->symbol, str->name->symbol);
+    ast::CheckIdentifier(program.Symbols(), alias->type, "A");
 }
 
 TEST_F(ParserImplTest, GlobalDecl_TypeAlias_MissingSemicolon) {
@@ -196,7 +191,7 @@
 
     auto program = p->program();
     ASSERT_EQ(program.AST().Functions().Length(), 1u);
-    EXPECT_EQ(program.Symbols().NameFor(program.AST().Functions()[0]->name->symbol), "main");
+    ast::CheckIdentifier(program.Symbols(), program.AST().Functions()[0]->name, "main");
 }
 
 TEST_F(ParserImplTest, GlobalDecl_Function_WithAttribute) {
@@ -206,7 +201,7 @@
 
     auto program = p->program();
     ASSERT_EQ(program.AST().Functions().Length(), 1u);
-    EXPECT_EQ(program.Symbols().NameFor(program.AST().Functions()[0]->name->symbol), "main");
+    ast::CheckIdentifier(program.Symbols(), program.AST().Functions()[0]->name, "main");
 }
 
 TEST_F(ParserImplTest, GlobalDecl_Function_Invalid) {
diff --git a/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc b/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc
index 867e700..92272cc 100644
--- a/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -29,10 +30,9 @@
     auto* var = e.value->As<ast::Var>();
     ASSERT_NE(var, nullptr);
 
-    EXPECT_EQ(var->name->symbol, p->builder().Symbols().Get("a"));
+    ast::CheckIdentifier(p->builder().Symbols(), var->name, "a");
 
-    ASSERT_TRUE(var->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(var->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), var->type, "f32");
 
     EXPECT_EQ(var->declared_address_space, type::AddressSpace::kPrivate);
 
@@ -56,10 +56,8 @@
     auto* var = e.value->As<ast::Var>();
     ASSERT_NE(var, nullptr);
 
-    EXPECT_EQ(var->name->symbol, p->builder().Symbols().Get("a"));
-
-    ASSERT_TRUE(var->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(var->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), var->name, "a");
+    ast::CheckIdentifier(p->builder().Symbols(), var->type, "f32");
 
     EXPECT_EQ(var->declared_address_space, type::AddressSpace::kPrivate);
 
@@ -84,11 +82,10 @@
     auto* var = e.value->As<ast::Var>();
     ASSERT_NE(var, nullptr);
 
-    EXPECT_EQ(var->name->symbol, p->builder().Symbols().Get("a"));
+    ast::CheckIdentifier(p->builder().Symbols(), var->name, "a");
     ASSERT_NE(var->type, nullptr);
 
-    ASSERT_TRUE(var->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(var->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), var->type, "f32");
 
     EXPECT_EQ(var->declared_address_space, type::AddressSpace::kUniform);
 
@@ -118,11 +115,9 @@
     auto* var = e.value->As<ast::Var>();
     ASSERT_NE(var, nullptr);
 
-    EXPECT_EQ(var->name->symbol, p->builder().Symbols().Get("a"));
+    ast::CheckIdentifier(p->builder().Symbols(), var->name, "a");
     ASSERT_NE(var->type, nullptr);
-
-    ASSERT_TRUE(var->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(var->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), var->type, "f32");
 
     EXPECT_EQ(var->declared_address_space, type::AddressSpace::kUniform);
 
diff --git a/src/tint/reader/wgsl/parser_impl_param_list_test.cc b/src/tint/reader/wgsl/parser_impl_param_list_test.cc
index 14d2363..4829294 100644
--- a/src/tint/reader/wgsl/parser_impl_param_list_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_param_list_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -26,9 +27,7 @@
     EXPECT_EQ(e.value.Length(), 1u);
 
     EXPECT_EQ(e.value[0]->name->symbol, p->builder().Symbols().Get("a"));
-    ASSERT_TRUE(e.value[0]->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(e.value[0]->type->As<ast::TypeName>()->name->symbol),
-              "i32");
+    ast::CheckIdentifier(p->builder().Symbols(), e.value[0]->type, "i32");
     EXPECT_TRUE(e.value[0]->Is<ast::Parameter>());
 
     ASSERT_EQ(e.value[0]->source.range.begin.line, 1u);
@@ -46,9 +45,7 @@
     EXPECT_EQ(e.value.Length(), 3u);
 
     EXPECT_EQ(e.value[0]->name->symbol, p->builder().Symbols().Get("a"));
-    ASSERT_TRUE(e.value[0]->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(e.value[0]->type->As<ast::TypeName>()->name->symbol),
-              "i32");
+    ast::CheckIdentifier(p->builder().Symbols(), e.value[0]->type, "i32");
     EXPECT_TRUE(e.value[0]->Is<ast::Parameter>());
 
     ASSERT_EQ(e.value[0]->source.range.begin.line, 1u);
@@ -57,9 +54,7 @@
     ASSERT_EQ(e.value[0]->source.range.end.column, 2u);
 
     EXPECT_EQ(e.value[1]->name->symbol, p->builder().Symbols().Get("b"));
-    ASSERT_TRUE(e.value[1]->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(e.value[1]->type->As<ast::TypeName>()->name->symbol),
-              "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), e.value[1]->type, "f32");
     EXPECT_TRUE(e.value[1]->Is<ast::Parameter>());
 
     ASSERT_EQ(e.value[1]->source.range.begin.line, 1u);
@@ -68,12 +63,7 @@
     ASSERT_EQ(e.value[1]->source.range.end.column, 11u);
 
     EXPECT_EQ(e.value[2]->name->symbol, p->builder().Symbols().Get("c"));
-    ASSERT_TRUE(e.value[2]->type->Is<ast::Vector>());
-    ASSERT_TRUE(e.value[2]->type->As<ast::Vector>()->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(
-                  e.value[2]->type->As<ast::Vector>()->type->As<ast::TypeName>()->name->symbol),
-              "f32");
-    EXPECT_EQ(e.value[2]->type->As<ast::Vector>()->width, 2u);
+    ast::CheckIdentifier(p->builder().Symbols(), e.value[2]->type, ast::Template("vec2", "f32"));
     EXPECT_TRUE(e.value[2]->Is<ast::Parameter>());
 
     ASSERT_EQ(e.value[2]->source.range.begin.line, 1u);
@@ -107,12 +97,7 @@
     ASSERT_EQ(e.value.Length(), 2u);
 
     EXPECT_EQ(e.value[0]->name->symbol, p->builder().Symbols().Get("coord"));
-    ASSERT_TRUE(e.value[0]->type->Is<ast::Vector>());
-    ASSERT_TRUE(e.value[0]->type->As<ast::Vector>()->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(
-                  e.value[0]->type->As<ast::Vector>()->type->As<ast::TypeName>()->name->symbol),
-              "f32");
-    EXPECT_EQ(e.value[0]->type->As<ast::Vector>()->width, 4u);
+    ast::CheckIdentifier(p->builder().Symbols(), e.value[0]->type, ast::Template("vec4", "f32"));
     EXPECT_TRUE(e.value[0]->Is<ast::Parameter>());
     auto attrs_0 = e.value[0]->attributes;
     ASSERT_EQ(attrs_0.Length(), 1u);
@@ -125,9 +110,7 @@
     ASSERT_EQ(e.value[0]->source.range.end.column, 25u);
 
     EXPECT_EQ(e.value[1]->name->symbol, p->builder().Symbols().Get("loc1"));
-    ASSERT_TRUE(e.value[1]->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(e.value[1]->type->As<ast::TypeName>()->name->symbol),
-              "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), e.value[1]->type, "f32");
     EXPECT_TRUE(e.value[1]->Is<ast::Parameter>());
     auto attrs_1 = e.value[1]->attributes;
     ASSERT_EQ(attrs_1.Length(), 1u);
diff --git a/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc b/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc
index 517a7b6..b7b0d85 100644
--- a/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -26,8 +27,7 @@
     EXPECT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(e.value, nullptr);
     ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
-    auto* ident_expr = e->As<ast::IdentifierExpression>();
-    EXPECT_EQ(ident_expr->identifier->symbol, p->builder().Symbols().Get("a"));
+    ast::CheckIdentifier(p->builder().Symbols(), e.value, "a");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_TypeDecl) {
@@ -40,8 +40,6 @@
     ASSERT_TRUE(e->Is<ast::CallExpression>());
     auto* call = e->As<ast::CallExpression>();
 
-    EXPECT_NE(call->target.type, nullptr);
-
     ASSERT_EQ(call->args.Length(), 4u);
     const auto& val = call->args;
     ASSERT_TRUE(val[0]->Is<ast::IntLiteralExpression>());
@@ -137,8 +135,8 @@
     ASSERT_TRUE(e->Is<ast::CallExpression>());
     auto* call = e->As<ast::CallExpression>();
 
-    ASSERT_NE(call->target.name, nullptr);
-    EXPECT_EQ(call->target.name->symbol, p->builder().Symbols().Get("S"));
+    ASSERT_NE(call->target, nullptr);
+    ast::CheckIdentifier(p->builder().Symbols(), call->target, "S");
 
     ASSERT_EQ(call->args.Length(), 0u);
 }
@@ -161,8 +159,8 @@
     ASSERT_TRUE(e->Is<ast::CallExpression>());
     auto* call = e->As<ast::CallExpression>();
 
-    ASSERT_NE(call->target.name, nullptr);
-    EXPECT_EQ(call->target.name->symbol, p->builder().Symbols().Get("S"));
+    ASSERT_NE(call->target, nullptr);
+    ast::CheckIdentifier(p->builder().Symbols(), call->target, "S");
 
     ASSERT_EQ(call->args.Length(), 2u);
 
@@ -237,10 +235,7 @@
 
     ASSERT_TRUE(e->Is<ast::CallExpression>());
     auto* call = e->As<ast::CallExpression>();
-
-    auto* type_name = As<ast::TypeName>(call->target.type);
-    ASSERT_NE(type_name, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(type_name->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), call->target, "f32");
 
     ASSERT_EQ(call->args.Length(), 1u);
     ASSERT_TRUE(call->args[0]->Is<ast::IntLiteralExpression>());
@@ -258,8 +253,7 @@
 
     auto* c = e->As<ast::BitcastExpression>();
 
-    ASSERT_TRUE(c->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(c->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), c->type, "f32");
 
     ASSERT_TRUE(c->expr->Is<ast::IntLiteralExpression>());
 }
diff --git a/src/tint/reader/wgsl/parser_impl_sampler_test.cc b/src/tint/reader/wgsl/parser_impl_sampler_test.cc
index c1869ea..b88ac2d 100644
--- a/src/tint/reader/wgsl/parser_impl_sampler_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_sampler_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -32,8 +33,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol),
-              "sampler");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "sampler");
     EXPECT_FALSE(p->has_error());
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 8u}}));
 }
@@ -44,8 +44,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol),
-              "sampler_comparison");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "sampler_comparison");
     EXPECT_FALSE(p->has_error());
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
 }
diff --git a/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc b/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc
index 4bfc5a8..dfde6fa 100644
--- a/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -97,7 +98,7 @@
     ASSERT_TRUE(e->Is<ast::CallExpression>());
     auto* c = e->As<ast::CallExpression>();
 
-    EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("a"));
+    ast::CheckIdentifier(p->builder().Symbols(), c->target, "a");
 
     EXPECT_EQ(c->args.Length(), 0u);
 }
@@ -113,7 +114,7 @@
     ASSERT_TRUE(e->Is<ast::CallExpression>());
     auto* c = e->As<ast::CallExpression>();
 
-    EXPECT_EQ(c->target.name->symbol, p->builder().Symbols().Get("test"));
+    ast::CheckIdentifier(p->builder().Symbols(), c->target, "test");
 
     EXPECT_EQ(c->args.Length(), 3u);
     EXPECT_TRUE(c->args[0]->Is<ast::IntLiteralExpression>());
diff --git a/src/tint/reader/wgsl/parser_impl_struct_body_decl_test.cc b/src/tint/reader/wgsl/parser_impl_struct_body_decl_test.cc
index 1ae5adf..d01c266 100644
--- a/src/tint/reader/wgsl/parser_impl_struct_body_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_struct_body_decl_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -29,8 +30,7 @@
 
     const auto* mem = m.value[0];
     EXPECT_EQ(mem->name->symbol, builder.Symbols().Get("a"));
-    ASSERT_TRUE(mem->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(mem->type->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(p->builder().Symbols(), mem->type, "i32");
     EXPECT_EQ(mem->attributes.Length(), 0u);
 }
 
@@ -46,8 +46,7 @@
 
     const auto* mem = m.value[0];
     EXPECT_EQ(mem->name->symbol, builder.Symbols().Get("a"));
-    ASSERT_TRUE(mem->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(mem->type->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(p->builder().Symbols(), mem->type, "i32");
     EXPECT_EQ(mem->attributes.Length(), 0u);
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_struct_member_test.cc b/src/tint/reader/wgsl/parser_impl_struct_member_test.cc
index 09f25b8..3c27a9f 100644
--- a/src/tint/reader/wgsl/parser_impl_struct_member_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_struct_member_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -20,16 +21,13 @@
 TEST_F(ParserImplTest, StructMember_Parses) {
     auto p = parser("a : i32,");
 
-    auto& builder = p->builder();
-
     auto m = p->expect_struct_member();
     ASSERT_FALSE(p->has_error());
     ASSERT_FALSE(m.errored);
     ASSERT_NE(m.value, nullptr);
 
-    EXPECT_EQ(m->name->symbol, builder.Symbols().Get("a"));
-    ASSERT_TRUE(m->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(m->type->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(p->builder().Symbols(), m->name, "a");
+    ast::CheckIdentifier(p->builder().Symbols(), m->type, "i32");
     EXPECT_EQ(m->attributes.Length(), 0u);
 
     EXPECT_EQ(m->source.range, (Source::Range{{1u, 1u}, {1u, 2u}}));
@@ -39,16 +37,13 @@
 TEST_F(ParserImplTest, StructMember_ParsesWithAlignAttribute) {
     auto p = parser("@align(2) a : i32,");
 
-    auto& builder = p->builder();
-
     auto m = p->expect_struct_member();
     ASSERT_FALSE(p->has_error());
     ASSERT_FALSE(m.errored);
     ASSERT_NE(m.value, nullptr);
 
-    EXPECT_EQ(m->name->symbol, builder.Symbols().Get("a"));
-    ASSERT_TRUE(m->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(m->type->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(p->builder().Symbols(), m->name, "a");
+    ast::CheckIdentifier(p->builder().Symbols(), m->type, "i32");
     EXPECT_EQ(m->attributes.Length(), 1u);
     EXPECT_TRUE(m->attributes[0]->Is<ast::StructMemberAlignAttribute>());
 
@@ -65,16 +60,13 @@
 TEST_F(ParserImplTest, StructMember_ParsesWithSizeAttribute) {
     auto p = parser("@size(2) a : i32,");
 
-    auto& builder = p->builder();
-
     auto m = p->expect_struct_member();
     ASSERT_FALSE(p->has_error());
     ASSERT_FALSE(m.errored);
     ASSERT_NE(m.value, nullptr);
 
-    EXPECT_EQ(m->name->symbol, builder.Symbols().Get("a"));
-    ASSERT_TRUE(m->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(m->type->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(p->builder().Symbols(), m->name, "a");
+    ast::CheckIdentifier(p->builder().Symbols(), m->type, "i32");
     EXPECT_EQ(m->attributes.Length(), 1u);
     ASSERT_TRUE(m->attributes[0]->Is<ast::StructMemberSizeAttribute>());
     auto* s = m->attributes[0]->As<ast::StructMemberSizeAttribute>();
@@ -90,16 +82,13 @@
     auto p = parser(R"(@size(2)
 @align(4) a : i32,)");
 
-    auto& builder = p->builder();
-
     auto m = p->expect_struct_member();
     ASSERT_FALSE(p->has_error());
     ASSERT_FALSE(m.errored);
     ASSERT_NE(m.value, nullptr);
 
-    EXPECT_EQ(m->name->symbol, builder.Symbols().Get("a"));
-    ASSERT_TRUE(m->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(m->type->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(p->builder().Symbols(), m->name, "a");
+    ast::CheckIdentifier(p->builder().Symbols(), m->type, "i32");
     EXPECT_EQ(m->attributes.Length(), 2u);
     ASSERT_TRUE(m->attributes[0]->Is<ast::StructMemberSizeAttribute>());
     auto* size_attr = m->attributes[0]->As<ast::StructMemberSizeAttribute>();
diff --git a/src/tint/reader/wgsl/parser_impl_texture_sampler_test.cc b/src/tint/reader/wgsl/parser_impl_texture_sampler_test.cc
index 37dbd97..3163377 100644
--- a/src/tint/reader/wgsl/parser_impl_texture_sampler_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_texture_sampler_test.cc
@@ -38,9 +38,8 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol),
-              "sampler");
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 8u}}));
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "sampler");
+    EXPECT_EQ(t->expr->source.range, (Source::Range{{1u, 1u}, {1u, 8u}}));
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SamplerComparison) {
@@ -49,10 +48,8 @@
     ASSERT_FALSE(p->has_error()) << p->error();
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
-    ASSERT_NE(t.value, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol),
-              "sampler_comparison");
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "sampler_comparison");
+    EXPECT_EQ(t->expr->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_DepthTexture) {
@@ -62,9 +59,8 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol),
-              "texture_depth_2d");
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "texture_depth_2d");
+    EXPECT_EQ(t->expr->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_F32) {
@@ -74,13 +70,8 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    ASSERT_TRUE(t->Is<ast::SampledTexture>());
-    ASSERT_TRUE(t->As<ast::SampledTexture>()->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(
-                  t->As<ast::SampledTexture>()->type->As<ast::TypeName>()->name->symbol),
-              "f32");
-    EXPECT_EQ(t->As<ast::Texture>()->dim, type::TextureDimension::k1d);
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 16u}}));
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("texture_1d", "f32"));
+    EXPECT_EQ(t->expr->source.range, (Source::Range{{1u, 1u}, {1u, 16u}}));
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_I32) {
@@ -90,13 +81,8 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    ASSERT_TRUE(t->Is<ast::SampledTexture>());
-    ASSERT_TRUE(t->As<ast::SampledTexture>()->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(
-                  t->As<ast::SampledTexture>()->type->As<ast::TypeName>()->name->symbol),
-              "i32");
-    EXPECT_EQ(t->As<ast::Texture>()->dim, type::TextureDimension::k2d);
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 16u}}));
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("texture_2d", "i32"));
+    EXPECT_EQ(t->expr->source.range, (Source::Range{{1u, 1u}, {1u, 16u}}));
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_U32) {
@@ -106,42 +92,8 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    ASSERT_TRUE(t->Is<ast::SampledTexture>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(
-                  t->As<ast::SampledTexture>()->type->As<ast::TypeName>()->name->symbol),
-              "u32");
-    EXPECT_EQ(t->As<ast::Texture>()->dim, type::TextureDimension::k3d);
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 16u}}));
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingType) {
-    auto p = parser("texture_1d<>");
-    auto t = p->texture_and_sampler_types();
-    ASSERT_TRUE(p->has_error());
-    EXPECT_EQ(t.value, nullptr);
-    EXPECT_FALSE(t.matched);
-    EXPECT_TRUE(t.errored);
-    EXPECT_EQ(p->error(), "1:12: invalid type for sampled texture type");
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingLessThan) {
-    auto p = parser("texture_1d");
-    auto t = p->texture_and_sampler_types();
-    ASSERT_TRUE(p->has_error());
-    EXPECT_EQ(t.value, nullptr);
-    EXPECT_FALSE(t.matched);
-    EXPECT_TRUE(t.errored);
-    EXPECT_EQ(p->error(), "1:11: expected '<' for sampled texture type");
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingGreaterThan) {
-    auto p = parser("texture_1d<u32");
-    auto t = p->texture_and_sampler_types();
-    ASSERT_TRUE(p->has_error());
-    EXPECT_EQ(t.value, nullptr);
-    EXPECT_FALSE(t.matched);
-    EXPECT_TRUE(t.errored);
-    EXPECT_EQ(p->error(), "1:11: missing closing '>' for sampled texture type");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("texture_3d", "u32"));
+    EXPECT_EQ(t->expr->source.range, (Source::Range{{1u, 1u}, {1u, 16u}}));
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_I32) {
@@ -151,13 +103,9 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    ASSERT_TRUE(t->Is<ast::MultisampledTexture>());
-    ASSERT_TRUE(t->As<ast::MultisampledTexture>()->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(
-                  t->As<ast::MultisampledTexture>()->type->As<ast::TypeName>()->name->symbol),
-              "i32");
-    EXPECT_EQ(t->As<ast::Texture>()->dim, type::TextureDimension::k2d);
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 29u}}));
+    ast::CheckIdentifier(p->builder().Symbols(), t.value,
+                         ast::Template("texture_multisampled_2d", "i32"));
+    EXPECT_EQ(t->expr->source.range, (Source::Range{{1u, 1u}, {1u, 29u}}));
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_MissingType) {
@@ -196,9 +144,9 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
 
-    ast::CheckIdentifier(p->builder().Symbols(), t->As<ast::TypeName>()->name,
+    ast::CheckIdentifier(p->builder().Symbols(), t.value,
                          ast::Template("texture_storage_1d", "rg32float", "read"));
-    EXPECT_EQ(t->source.range, (Source::Range{{1u, 1u}, {1u, 36u}}));
+    EXPECT_EQ(t->expr->source.range, (Source::Range{{1u, 1u}, {1u, 36u}}));
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_Writeonly2dR32Uint) {
@@ -209,43 +157,9 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
 
-    ast::CheckIdentifier(p->builder().Symbols(), t->As<ast::TypeName>()->name,
+    ast::CheckIdentifier(p->builder().Symbols(), t.value,
                          ast::Template("texture_storage_2d", "r32uint", "write"));
-    EXPECT_EQ(t->source.range, (Source::Range{{1u, 1u}, {1u, 35u}}));
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidType) {
-    auto p = parser("texture_storage_1d<abc, read>");
-    auto t = p->texture_and_sampler_types();
-    EXPECT_EQ(t.value, nullptr);
-    EXPECT_FALSE(t.matched);
-    EXPECT_TRUE(t.errored);
-    EXPECT_EQ(p->error(), R"(1:20: expected texel format for storage texture type
-Possible values: 'bgra8unorm', 'r32float', 'r32sint', 'r32uint', 'rg32float', 'rg32sint', 'rg32uint', 'rgba16float', 'rgba16sint', 'rgba16uint', 'rgba32float', 'rgba32sint', 'rgba32uint', 'rgba8sint', 'rgba8snorm', 'rgba8uint', 'rgba8unorm')");
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidTypeSuggest) {
-    auto p = parser("texture_storage_1d<rg32_float, read>");
-    auto t = p->texture_and_sampler_types();
-    EXPECT_EQ(t.value, nullptr);
-    EXPECT_FALSE(t.matched);
-    EXPECT_TRUE(t.errored);
-    EXPECT_EQ(p->error(),
-              R"(1:20: expected texel format for storage texture type
-Did you mean 'rg32float'?
-Possible values: 'bgra8unorm', 'r32float', 'r32sint', 'r32uint', 'rg32float', 'rg32sint', 'rg32uint', 'rgba16float', 'rgba16sint', 'rgba16uint', 'rgba32float', 'rgba32sint', 'rgba32uint', 'rgba8sint', 'rgba8snorm', 'rgba8uint', 'rgba8unorm')");
-}
-
-TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidAccess) {
-    auto p = parser("texture_storage_1d<r32float, abc>");
-    auto t = p->texture_and_sampler_types();
-    EXPECT_EQ(t.value, nullptr);
-    EXPECT_FALSE(t.matched);
-    EXPECT_TRUE(t.errored);
-    EXPECT_EQ(p->error(),
-              R"(1:30: expected access control for storage texture type
-Did you mean 'read'?
-Possible values: 'read', 'read_write', 'write')");
+    EXPECT_EQ(t->expr->source.range, (Source::Range{{1u, 1u}, {1u, 35u}}));
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingType) {
@@ -258,15 +172,6 @@
 Possible values: 'bgra8unorm', 'r32float', 'r32sint', 'r32uint', 'rg32float', 'rg32sint', 'rg32uint', 'rgba16float', 'rgba16sint', 'rgba16uint', 'rgba32float', 'rgba32sint', 'rgba32uint', 'rgba8sint', 'rgba8snorm', 'rgba8uint', 'rgba8unorm')");
 }
 
-TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingLessThan) {
-    auto p = parser("texture_storage_1d");
-    auto t = p->texture_and_sampler_types();
-    EXPECT_EQ(t.value, nullptr);
-    EXPECT_FALSE(t.matched);
-    EXPECT_TRUE(t.errored);
-    EXPECT_EQ(p->error(), "1:19: expected '<' for storage texture type");
-}
-
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingGreaterThan) {
     auto p = parser("texture_storage_1d<r32uint, read");
     auto t = p->texture_and_sampler_types();
diff --git a/src/tint/reader/wgsl/parser_impl_type_alias_test.cc b/src/tint/reader/wgsl/parser_impl_type_alias_test.cc
index de85b2c..1c2b4fe 100644
--- a/src/tint/reader/wgsl/parser_impl_type_alias_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_type_alias_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -27,9 +28,7 @@
     ASSERT_NE(t.value, nullptr);
     ASSERT_TRUE(t->Is<ast::Alias>());
     auto* alias = t->As<ast::Alias>();
-    ASSERT_TRUE(alias->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(alias->type->As<ast::TypeName>()->name->symbol),
-              "i32");
+    ast::CheckIdentifier(p->builder().Symbols(), alias->type, "i32");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 13u}}));
 }
 
@@ -43,8 +42,8 @@
     ASSERT_NE(t.value, nullptr);
     ASSERT_TRUE(t.value->Is<ast::Alias>());
     auto* alias = t.value->As<ast::Alias>();
-    EXPECT_EQ(p->builder().Symbols().NameFor(alias->name->symbol), "a");
-    EXPECT_TRUE(alias->type->Is<ast::TypeName>());
+    ast::CheckIdentifier(p->builder().Symbols(), alias->name, "a");
+    ast::CheckIdentifier(p->builder().Symbols(), alias->type, "B");
     EXPECT_EQ(alias->source.range, (Source::Range{{1u, 1u}, {1u, 11u}}));
 }
 
@@ -62,10 +61,8 @@
     ASSERT_NE(t.value, nullptr);
     ASSERT_TRUE(t.value->Is<ast::Alias>());
     auto* alias = t.value->As<ast::Alias>();
-    EXPECT_EQ(p->builder().Symbols().NameFor(alias->name->symbol), ident);
-    ASSERT_TRUE(alias->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(alias->type->As<ast::TypeName>()->name->symbol),
-              "i32");
+    ast::CheckIdentifier(p->builder().Symbols(), alias->name, ident);
+    ast::CheckIdentifier(p->builder().Symbols(), alias->type, "i32");
     EXPECT_EQ(alias->source.range, (Source::Range{{1u, 1u}, {1u, 37u}}));
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
index 612e102..d1adde2 100644
--- a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
@@ -13,14 +13,15 @@
 // limitations under the License.
 
 #include "src/tint/ast/alias.h"
-#include "src/tint/ast/array.h"
-#include "src/tint/ast/matrix.h"
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 #include "src/tint/type/sampled_texture.h"
 
 namespace tint::reader::wgsl {
 namespace {
 
+using namespace tint::number_suffixes;  // NOLINT
+
 TEST_F(ParserImplTest, TypeDecl_Invalid) {
     auto p = parser("1234");
     auto t = p->type_specifier();
@@ -37,10 +38,8 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
-    auto* type_name = t.value->As<ast::TypeName>();
-    ASSERT_NE(type_name, nullptr);
-    EXPECT_EQ(p->builder().Symbols().Get("A"), type_name->name->symbol);
-    EXPECT_EQ(type_name->source.range, (Source::Range{{1u, 1u}, {1u, 2u}}));
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "A");
+    EXPECT_EQ(t->expr->source.range, (Source::Range{{1u, 1u}, {1u, 2u}}));
 }
 
 TEST_F(ParserImplTest, TypeDecl_Bool) {
@@ -50,8 +49,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
-    ASSERT_TRUE(t.value->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol), "bool");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "bool");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 5u}}));
 }
 
@@ -62,8 +60,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
-    ASSERT_TRUE(t.value->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol), "f16");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "f16");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
 }
 
@@ -74,8 +71,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
-    ASSERT_TRUE(t.value->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "f32");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
 }
 
@@ -86,8 +82,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
-    ASSERT_TRUE(t.value->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "i32");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
 }
 
@@ -98,8 +93,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
-    ASSERT_TRUE(t.value->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol), "u32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "u32");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
 }
 
@@ -123,8 +117,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    EXPECT_TRUE(t.value->Is<ast::Vector>());
-    EXPECT_EQ(t.value->As<ast::Vector>()->width, params.count);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value,
+                         ast::Template("vec" + std::to_string(params.count), "f32"));
     EXPECT_EQ(t.value->source.range, params.range);
 }
 INSTANTIATE_TEST_SUITE_P(ParserImplTest,
@@ -176,12 +170,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Pointer>());
 
-    auto* ptr = t.value->As<ast::Pointer>();
-    ASSERT_TRUE(ptr->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(ptr->type->As<ast::TypeName>()->name->symbol), "f32");
-    ASSERT_EQ(ptr->address_space, type::AddressSpace::kFunction);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("ptr", "function", "f32"));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
 }
 
@@ -192,13 +182,9 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Pointer>());
 
-    auto* ptr = t.value->As<ast::Pointer>();
-    ASSERT_TRUE(ptr->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(ptr->type->As<ast::TypeName>()->name->symbol), "f32");
-    ASSERT_EQ(ptr->address_space, type::AddressSpace::kFunction);
-    ASSERT_EQ(ptr->access, type::Access::kRead);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value,
+                         ast::Template("ptr", "function", "f32", "read"));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 25u}}));
 }
 
@@ -209,16 +195,10 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Pointer>());
 
-    auto* ptr = t.value->As<ast::Pointer>();
-    ASSERT_TRUE(ptr->type->Is<ast::Vector>());
-    ASSERT_EQ(ptr->address_space, type::AddressSpace::kFunction);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value,
+                         ast::Template("ptr", "function", ast::Template("vec2", "f32")));
 
-    auto* vec = ptr->type->As<ast::Vector>();
-    ASSERT_EQ(vec->width, 2u);
-    ASSERT_TRUE(vec->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(vec->type->As<ast::TypeName>()->name->symbol), "f32");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 25}}));
 }
 
@@ -346,12 +326,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Atomic>());
 
-    auto* atomic = t.value->As<ast::Atomic>();
-    ASSERT_TRUE(atomic->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(atomic->type->As<ast::TypeName>()->name->symbol),
-              "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("atomic", "f32"));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 12u}}));
 }
 
@@ -362,15 +338,9 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Atomic>());
 
-    auto* atomic = t.value->As<ast::Atomic>();
-    ASSERT_TRUE(atomic->type->Is<ast::Vector>());
-
-    auto* vec = atomic->type->As<ast::Vector>();
-    ASSERT_EQ(vec->width, 2u);
-    ASSERT_TRUE(vec->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(vec->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value,
+                         ast::Template("atomic", ast::Template("vec2", "f32")));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 18u}}));
 }
 
@@ -411,19 +381,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_FALSE(a->IsRuntimeArray());
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "f32");
-    EXPECT_EQ(a->attributes.Length(), 0u);
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 14u}}));
-
-    auto* size = a->count->As<ast::IntLiteralExpression>();
-    ASSERT_NE(size, nullptr);
-    EXPECT_EQ(size->value, 5);
-    EXPECT_EQ(size->suffix, ast::IntLiteralExpression::Suffix::kNone);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("array", "f32", 5_a));
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_SintLiteralSize) {
@@ -433,19 +392,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_FALSE(a->IsRuntimeArray());
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "f32");
-    EXPECT_EQ(a->attributes.Length(), 0u);
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 15u}}));
-
-    auto* size = a->count->As<ast::IntLiteralExpression>();
-    ASSERT_NE(size, nullptr);
-    EXPECT_EQ(size->value, 5);
-    EXPECT_EQ(size->suffix, ast::IntLiteralExpression::Suffix::kI);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("array", "f32", 5_i));
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_UintLiteralSize) {
@@ -455,18 +403,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_FALSE(a->IsRuntimeArray());
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "f32");
-    EXPECT_EQ(a->attributes.Length(), 0u);
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 15u}}));
-
-    auto* size = a->count->As<ast::IntLiteralExpression>();
-    ASSERT_NE(size, nullptr);
-    EXPECT_EQ(size->suffix, ast::IntLiteralExpression::Suffix::kU);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("array", "f32", 5_u));
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_ConstantSize) {
@@ -476,18 +414,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_FALSE(a->IsRuntimeArray());
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "f32");
-    EXPECT_EQ(a->attributes.Length(), 0u);
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
-
-    auto* count_expr = a->count->As<ast::IdentifierExpression>();
-    ASSERT_NE(count_expr, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(count_expr->identifier->symbol), "size");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("array", "f32", "size"));
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_ExpressionSize) {
@@ -497,25 +425,30 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_FALSE(a->IsRuntimeArray());
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "f32");
-    EXPECT_EQ(a->attributes.Length(), 0u);
+    auto name_for = [&](const Symbol& sym) { return p->builder().Symbols().NameFor(sym); };
 
-    ASSERT_TRUE(a->count->Is<ast::BinaryExpression>());
-    auto* count_expr = a->count->As<ast::BinaryExpression>();
-    EXPECT_EQ(ast::BinaryOp::kAdd, count_expr->op);
+    auto* arr = t->expr->identifier->As<ast::TemplatedIdentifier>();
+    EXPECT_EQ(name_for(arr->symbol), "array");
+    EXPECT_TRUE(arr->attributes.IsEmpty());
 
-    ASSERT_TRUE(count_expr->lhs->Is<ast::IdentifierExpression>());
-    auto* ident_expr = count_expr->lhs->As<ast::IdentifierExpression>();
-    EXPECT_EQ(p->builder().Symbols().NameFor(ident_expr->identifier->symbol), "size");
+    ASSERT_EQ(arr->arguments.Length(), 2u);
 
-    ASSERT_TRUE(count_expr->rhs->Is<ast::IntLiteralExpression>());
-    auto* val = count_expr->rhs->As<ast::IntLiteralExpression>();
-    EXPECT_EQ(2, static_cast<int32_t>(val->value));
+    auto* ty = As<ast::IdentifierExpression>(arr->arguments[0]);
+    ASSERT_NE(ty, nullptr);
+    EXPECT_EQ(name_for(ty->identifier->symbol), "f32");
+
+    auto* count = As<ast::BinaryExpression>(arr->arguments[1]);
+    ASSERT_NE(count, nullptr);
+    EXPECT_EQ(ast::BinaryOp::kAdd, count->op);
+
+    auto* count_lhs = As<ast::IdentifierExpression>(count->lhs);
+    ASSERT_NE(count_lhs, nullptr);
+    EXPECT_EQ(name_for(count_lhs->identifier->symbol), "size");
+
+    auto* count_rhs = As<ast::IntLiteralExpression>(count->rhs);
+    ASSERT_NE(count_rhs, nullptr);
+    EXPECT_EQ(count_rhs->value, static_cast<int64_t>(2));
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_Runtime) {
@@ -525,12 +458,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_TRUE(a->IsRuntimeArray());
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "u32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("array", "u32"));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 11u}}));
 }
 
@@ -541,16 +470,9 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_TRUE(a->IsRuntimeArray());
-    ASSERT_TRUE(a->type->Is<ast::Vector>());
-    EXPECT_EQ(a->type->As<ast::Vector>()->width, 4u);
-    ASSERT_TRUE(a->type->As<ast::Vector>()->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(
-                  a->type->As<ast::Vector>()->type->As<ast::TypeName>()->name->symbol),
-              "u32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value,
+                         ast::Template("array", ast::Template("vec4", "u32")));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
 }
 
@@ -615,10 +537,11 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    EXPECT_TRUE(t.value->Is<ast::Matrix>());
-    auto* mat = t.value->As<ast::Matrix>();
-    EXPECT_EQ(mat->rows, params.rows);
-    EXPECT_EQ(mat->columns, params.columns);
+
+    std::string expected_name =
+        "mat" + std::to_string(GetParam().columns) + "x" + std::to_string(GetParam().rows);
+
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template(expected_name, "f32"));
     EXPECT_EQ(t.value->source.range, params.range);
 }
 INSTANTIATE_TEST_SUITE_P(ParserImplTest,
@@ -688,8 +611,8 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol),
-              "sampler");
+
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "sampler");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 8u}}));
 }
 
@@ -700,13 +623,8 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    ASSERT_TRUE(t.value->Is<ast::Texture>());
-    ASSERT_TRUE(t.value->Is<ast::SampledTexture>());
-    ASSERT_TRUE(t.value->As<ast::SampledTexture>()->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(
-                  t.value->As<ast::SampledTexture>()->type->As<ast::TypeName>()->name->symbol),
-              "f32");
 
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("texture_cube", "f32"));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 18u}}));
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_type_decl_without_ident_test.cc b/src/tint/reader/wgsl/parser_impl_type_decl_without_ident_test.cc
index ba2fe61..bd385a6 100644
--- a/src/tint/reader/wgsl/parser_impl_type_decl_without_ident_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_type_decl_without_ident_test.cc
@@ -13,14 +13,15 @@
 // limitations under the License.
 
 #include "src/tint/ast/alias.h"
-#include "src/tint/ast/array.h"
-#include "src/tint/ast/matrix.h"
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 #include "src/tint/type/sampled_texture.h"
 
 namespace tint::reader::wgsl {
 namespace {
 
+using namespace tint::number_suffixes;  // NOLINT
+
 TEST_F(ParserImplTest, TypeDeclWithoutIdent_Invalid) {
     auto p = parser("1234");
     auto t = p->type_specifier_without_ident();
@@ -45,8 +46,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
-    ASSERT_TRUE(t.value->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol), "bool");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "bool");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 5u}}));
 }
 
@@ -56,8 +56,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
-    ASSERT_TRUE(t.value->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol), "f16");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "f16");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
 }
 
@@ -67,8 +66,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
-    ASSERT_TRUE(t.value->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "f32");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
 }
 
@@ -78,8 +76,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
-    ASSERT_TRUE(t.value->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "i32");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
 }
 
@@ -89,8 +86,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
-    ASSERT_TRUE(t.value->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol), "u32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "u32");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
 }
 
@@ -114,8 +110,9 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    EXPECT_TRUE(t.value->Is<ast::Vector>());
-    EXPECT_EQ(t.value->As<ast::Vector>()->width, params.count);
+
+    ast::CheckIdentifier(p->builder().Symbols(), t.value,
+                         ast::Template("vec" + std::to_string(params.count), "f32"));
     EXPECT_EQ(t.value->source.range, params.range);
 }
 INSTANTIATE_TEST_SUITE_P(ParserImplTest,
@@ -167,12 +164,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Pointer>());
 
-    auto* ptr = t.value->As<ast::Pointer>();
-    ASSERT_TRUE(ptr->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(ptr->type->As<ast::TypeName>()->name->symbol), "f32");
-    ASSERT_EQ(ptr->address_space, type::AddressSpace::kFunction);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("ptr", "function", "f32"));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 19u}}));
 }
 
@@ -183,13 +176,9 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Pointer>());
 
-    auto* ptr = t.value->As<ast::Pointer>();
-    ASSERT_TRUE(ptr->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(ptr->type->As<ast::TypeName>()->name->symbol), "f32");
-    ASSERT_EQ(ptr->address_space, type::AddressSpace::kFunction);
-    ASSERT_EQ(ptr->access, type::Access::kRead);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value,
+                         ast::Template("ptr", "function", "f32", "read"));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 25u}}));
 }
 
@@ -200,16 +189,9 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Pointer>());
 
-    auto* ptr = t.value->As<ast::Pointer>();
-    ASSERT_TRUE(ptr->type->Is<ast::Vector>());
-    ASSERT_EQ(ptr->address_space, type::AddressSpace::kFunction);
-
-    auto* vec = ptr->type->As<ast::Vector>();
-    ASSERT_EQ(vec->width, 2u);
-    ASSERT_TRUE(vec->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(vec->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value,
+                         ast::Template("ptr", "function", ast::Template("vec2", "f32")));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 25}}));
 }
 
@@ -337,12 +319,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Atomic>());
 
-    auto* atomic = t.value->As<ast::Atomic>();
-    ASSERT_TRUE(atomic->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(atomic->type->As<ast::TypeName>()->name->symbol),
-              "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("atomic", "f32"));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 12u}}));
 }
 
@@ -353,15 +331,9 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Atomic>());
 
-    auto* atomic = t.value->As<ast::Atomic>();
-    ASSERT_TRUE(atomic->type->Is<ast::Vector>());
-
-    auto* vec = atomic->type->As<ast::Vector>();
-    ASSERT_EQ(vec->width, 2u);
-    ASSERT_TRUE(vec->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(vec->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value,
+                         ast::Template("atomic", ast::Template("vec2", "f32")));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 18u}}));
 }
 
@@ -402,19 +374,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_FALSE(a->IsRuntimeArray());
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "f32");
-    EXPECT_EQ(a->attributes.Length(), 0u);
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 14u}}));
-
-    auto* size = a->count->As<ast::IntLiteralExpression>();
-    ASSERT_NE(size, nullptr);
-    EXPECT_EQ(size->value, 5);
-    EXPECT_EQ(size->suffix, ast::IntLiteralExpression::Suffix::kNone);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("array", "f32", 5_a));
 }
 
 TEST_F(ParserImplTest, TypeDeclWithoutIdent_Array_SintLiteralSize) {
@@ -424,19 +385,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_FALSE(a->IsRuntimeArray());
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "f32");
-    EXPECT_EQ(a->attributes.Length(), 0u);
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 15u}}));
-
-    auto* size = a->count->As<ast::IntLiteralExpression>();
-    ASSERT_NE(size, nullptr);
-    EXPECT_EQ(size->value, 5);
-    EXPECT_EQ(size->suffix, ast::IntLiteralExpression::Suffix::kI);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("array", "f32", 5_i));
 }
 
 TEST_F(ParserImplTest, TypeDeclWithoutIdent_Array_UintLiteralSize) {
@@ -446,18 +396,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_FALSE(a->IsRuntimeArray());
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "f32");
-    EXPECT_EQ(a->attributes.Length(), 0u);
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 15u}}));
-
-    auto* size = a->count->As<ast::IntLiteralExpression>();
-    ASSERT_NE(size, nullptr);
-    EXPECT_EQ(size->suffix, ast::IntLiteralExpression::Suffix::kU);
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("array", "f32", 5_u));
 }
 
 TEST_F(ParserImplTest, TypeDeclWithoutIdent_Array_ConstantSize) {
@@ -467,18 +407,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_FALSE(a->IsRuntimeArray());
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "f32");
-    EXPECT_EQ(a->attributes.Length(), 0u);
-    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
-
-    auto* count_expr = a->count->As<ast::IdentifierExpression>();
-    ASSERT_NE(count_expr, nullptr);
-    EXPECT_EQ(p->builder().Symbols().NameFor(count_expr->identifier->symbol), "size");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("array", "f32", "size"));
 }
 
 TEST_F(ParserImplTest, TypeDeclWithoutIdent_Array_ExpressionSize) {
@@ -488,25 +418,30 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_FALSE(a->IsRuntimeArray());
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "f32");
-    EXPECT_EQ(a->attributes.Length(), 0u);
+    auto name_for = [&](const Symbol& sym) { return p->builder().Symbols().NameFor(sym); };
 
-    ASSERT_TRUE(a->count->Is<ast::BinaryExpression>());
-    auto* count_expr = a->count->As<ast::BinaryExpression>();
-    EXPECT_EQ(ast::BinaryOp::kAdd, count_expr->op);
+    auto* arr = t->expr->identifier->As<ast::TemplatedIdentifier>();
+    EXPECT_EQ(name_for(arr->symbol), "array");
+    EXPECT_TRUE(arr->attributes.IsEmpty());
 
-    ASSERT_TRUE(count_expr->lhs->Is<ast::IdentifierExpression>());
-    auto* ident = count_expr->lhs->As<ast::IdentifierExpression>();
-    EXPECT_EQ(p->builder().Symbols().NameFor(ident->identifier->symbol), "size");
+    ASSERT_EQ(arr->arguments.Length(), 2u);
 
-    ASSERT_TRUE(count_expr->rhs->Is<ast::IntLiteralExpression>());
-    auto* val = count_expr->rhs->As<ast::IntLiteralExpression>();
-    EXPECT_EQ(2, static_cast<int32_t>(val->value));
+    auto* ty = As<ast::IdentifierExpression>(arr->arguments[0]);
+    ASSERT_NE(ty, nullptr);
+    EXPECT_EQ(name_for(ty->identifier->symbol), "f32");
+
+    auto* count = As<ast::BinaryExpression>(arr->arguments[1]);
+    ASSERT_NE(count, nullptr);
+    EXPECT_EQ(ast::BinaryOp::kAdd, count->op);
+
+    auto* count_lhs = As<ast::IdentifierExpression>(count->lhs);
+    ASSERT_NE(count_lhs, nullptr);
+    EXPECT_EQ(name_for(count_lhs->identifier->symbol), "size");
+
+    auto* count_rhs = As<ast::IntLiteralExpression>(count->rhs);
+    ASSERT_NE(count_rhs, nullptr);
+    EXPECT_EQ(count_rhs->value, static_cast<int64_t>(2));
 }
 
 TEST_F(ParserImplTest, TypeDeclWithoutIdent_Array_Runtime) {
@@ -516,13 +451,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_TRUE(a->IsRuntimeArray());
-
-    ASSERT_TRUE(a->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(a->type->As<ast::TypeName>()->name->symbol), "u32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("array", "u32"));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 11u}}));
 }
 
@@ -533,16 +463,9 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    ASSERT_TRUE(t.value->Is<ast::Array>());
 
-    auto* a = t.value->As<ast::Array>();
-    ASSERT_TRUE(a->IsRuntimeArray());
-    ASSERT_TRUE(a->type->Is<ast::Vector>());
-    EXPECT_EQ(a->type->As<ast::Vector>()->width, 4u);
-    ASSERT_TRUE(a->type->As<ast::Vector>()->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(
-                  a->type->As<ast::Vector>()->type->As<ast::TypeName>()->name->symbol),
-              "u32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value,
+                         ast::Template("array", ast::Template("vec4", "u32")));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
 }
 
@@ -607,10 +530,12 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
     ASSERT_FALSE(p->has_error());
-    EXPECT_TRUE(t.value->Is<ast::Matrix>());
-    auto* mat = t.value->As<ast::Matrix>();
-    EXPECT_EQ(mat->rows, params.rows);
-    EXPECT_EQ(mat->columns, params.columns);
+
+    std::string expected_name =
+        "mat" + std::to_string(GetParam().columns) + "x" + std::to_string(GetParam().rows);
+
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template(expected_name, "f32"));
+
     EXPECT_EQ(t.value->source.range, params.range);
 }
 INSTANTIATE_TEST_SUITE_P(ParserImplTest,
@@ -681,8 +606,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr) << p->error();
-    EXPECT_EQ(p->builder().Symbols().NameFor(t.value->As<ast::TypeName>()->name->symbol),
-              "sampler");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, "sampler");
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 8u}}));
 }
 
@@ -693,12 +617,7 @@
     EXPECT_TRUE(t.matched);
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
-    ASSERT_TRUE(t.value->Is<ast::Texture>());
-    ASSERT_TRUE(t.value->Is<ast::SampledTexture>());
-    ASSERT_TRUE(t.value->As<ast::SampledTexture>()->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(
-                  t.value->As<ast::SampledTexture>()->type->As<ast::TypeName>()->name->symbol),
-              "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), t.value, ast::Template("texture_cube", "f32"));
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 18u}}));
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc b/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc
index 13f9005..9138d55 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -25,8 +26,7 @@
     EXPECT_EQ(v->name, "my_var");
     EXPECT_NE(v->type, nullptr);
 
-    ASSERT_TRUE(v->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(v->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), v->type, "f32");
 
     EXPECT_EQ(v->source.range, (Source::Range{{1u, 5u}, {1u, 11u}}));
     EXPECT_EQ(v->type->source.range, (Source::Range{{1u, 14u}, {1u, 17u}}));
@@ -46,8 +46,7 @@
     EXPECT_EQ(v->name, ident);
     EXPECT_NE(v->type, nullptr);
 
-    ASSERT_TRUE(v->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(v->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), v->type, "f32");
 
     EXPECT_EQ(v->source.range, (Source::Range{{1u, 5u}, {1u, 48u}}));
     EXPECT_EQ(v->type->source.range, (Source::Range{{1u, 51u}, {1u, 54u}}));
@@ -84,8 +83,7 @@
     EXPECT_FALSE(p->has_error());
     EXPECT_EQ(v->name, "my_var");
 
-    ASSERT_TRUE(v->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(v->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), v->type, "f32");
 
     EXPECT_EQ(v->address_space, type::AddressSpace::kPrivate);
 
@@ -103,8 +101,7 @@
     EXPECT_FALSE(p->has_error());
     EXPECT_EQ(v->name, "my_var");
 
-    ASSERT_TRUE(v->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(v->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), v->type, "f32");
 
     EXPECT_EQ(v->address_space, type::AddressSpace::kPushConstant);
 }
diff --git a/src/tint/reader/wgsl/parser_impl_variable_ident_decl_test.cc b/src/tint/reader/wgsl/parser_impl_variable_ident_decl_test.cc
index 373eb75..467fa80 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_ident_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_ident_decl_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -24,8 +25,7 @@
     ASSERT_FALSE(decl.errored);
     ASSERT_EQ(decl->name, "my_var");
     ASSERT_NE(decl->type, nullptr);
-    ASSERT_TRUE(decl->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(decl->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), decl->type, "f32");
 
     EXPECT_EQ(decl->source.range, (Source::Range{{1u, 1u}, {1u, 7u}}));
     EXPECT_EQ(decl->type->source.range, (Source::Range{{1u, 10u}, {1u, 13u}}));
@@ -38,8 +38,7 @@
     ASSERT_FALSE(decl.errored);
     ASSERT_EQ(decl->name, "my_var");
     ASSERT_NE(decl->type, nullptr);
-    ASSERT_TRUE(decl->type->Is<ast::TypeName>());
-    EXPECT_EQ(p->builder().Symbols().NameFor(decl->type->As<ast::TypeName>()->name->symbol), "f32");
+    ast::CheckIdentifier(p->builder().Symbols(), decl->type, "f32");
 
     EXPECT_EQ(decl->source.range, (Source::Range{{1u, 1u}, {1u, 7u}}));
     EXPECT_EQ(decl->type->source.range, (Source::Range{{1u, 10u}, {1u, 13u}}));
diff --git a/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc
index 6233dc5..bd3ee19 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 
 namespace tint::reader::wgsl {
@@ -80,8 +81,7 @@
     ASSERT_NE(e->variable->initializer, nullptr);
     auto* call = e->variable->initializer->As<ast::CallExpression>();
     ASSERT_NE(call, nullptr);
-    EXPECT_EQ(call->target.name, nullptr);
-    EXPECT_NE(call->target.type, nullptr);
+    ast::CheckIdentifier(p->builder().Symbols(), call->target, ast::Template("array", "i32"));
 }
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_ArrayInit_NoSpace) {
@@ -98,8 +98,7 @@
     ASSERT_NE(e->variable->initializer, nullptr);
     auto* call = e->variable->initializer->As<ast::CallExpression>();
     ASSERT_NE(call, nullptr);
-    EXPECT_EQ(call->target.name, nullptr);
-    EXPECT_NE(call->target.type, nullptr);
+    ast::CheckIdentifier(p->builder().Symbols(), call->target, ast::Template("array", "i32"));
 }
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_VecInit) {
@@ -115,9 +114,7 @@
 
     ASSERT_NE(e->variable->initializer, nullptr);
     auto* call = e->variable->initializer->As<ast::CallExpression>();
-    ASSERT_NE(call, nullptr);
-    EXPECT_EQ(call->target.name, nullptr);
-    EXPECT_NE(call->target.type, nullptr);
+    ast::CheckIdentifier(p->builder().Symbols(), call->target, ast::Template("vec2", "i32"));
 }
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_VecInit_NoSpace) {
@@ -134,8 +131,7 @@
     ASSERT_NE(e->variable->initializer, nullptr);
     auto* call = e->variable->initializer->As<ast::CallExpression>();
     ASSERT_NE(call, nullptr);
-    EXPECT_EQ(call->target.name, nullptr);
-    EXPECT_NE(call->target.type, nullptr);
+    ast::CheckIdentifier(p->builder().Symbols(), call->target, ast::Template("vec2", "i32"));
 }
 
 TEST_F(ParserImplTest, VariableStmt_Let) {
@@ -172,11 +168,11 @@
 
     ASSERT_TRUE(expr->lhs->Is<ast::IdentifierExpression>());
     auto* ident_expr = expr->lhs->As<ast::IdentifierExpression>();
-    EXPECT_EQ(ident_expr->identifier->symbol, p->builder().Symbols().Get("collide"));
+    ast::CheckIdentifier(p->builder().Symbols(), ident_expr->identifier, "collide");
 
     ASSERT_TRUE(expr->rhs->Is<ast::IdentifierExpression>());
     ident_expr = expr->rhs->As<ast::IdentifierExpression>();
-    EXPECT_EQ(ident_expr->identifier->symbol, p->builder().Symbols().Get("collide_1"));
+    ast::CheckIdentifier(p->builder().Symbols(), ident_expr->identifier, "collide_1");
 }
 
 TEST_F(ParserImplTest, VariableStmt_Let_MissingEqual) {
diff --git a/src/tint/resolver/address_space_validation_test.cc b/src/tint/resolver/address_space_validation_test.cc
index fb4981e..f96b29f 100644
--- a/src/tint/resolver/address_space_validation_test.cc
+++ b/src/tint/resolver/address_space_validation_test.cc
@@ -38,10 +38,10 @@
 
 TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_NoAddressSpace_Fail) {
     // type g = ptr<f32>;
-    Alias("g", ty.pointer(Source{{12, 34}}, ty.f32(), type::AddressSpace::kUndefined));
+    Alias("g", ty(Source{{12, 34}}, "ptr", ty.f32()));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: ptr missing address space");
+    EXPECT_EQ(r()->error(), "12:34 error: 'ptr' requires at least 2 template arguments");
 }
 
 TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_FunctionAddressSpace_Fail) {
@@ -473,7 +473,7 @@
 }
 
 TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_NotStorage_AccessMode) {
-    // type t = ptr<private, read, a>;
+    // type t = ptr<private, i32, read>;
     Alias("t", ty.pointer(Source{{12, 34}}, ty.i32(), type::AddressSpace::kPrivate,
                           type::Access::kRead));
 
diff --git a/src/tint/resolver/alias_analysis_test.cc b/src/tint/resolver/alias_analysis_test.cc
index 44db995..cc55f0b 100644
--- a/src/tint/resolver/alias_analysis_test.cc
+++ b/src/tint/resolver/alias_analysis_test.cc
@@ -759,7 +759,7 @@
          ty.void_(),
          utils::Vector{
              Assign(Phony(), MemberAccessor(Deref("p2"), "a")),
-             Assign(Deref("p1"), Call(ty("S"))),
+             Assign(Deref("p1"), Call("S")),
          });
     Func("f1", utils::Empty, ty.void_(),
          utils::Vector{
diff --git a/src/tint/resolver/assignment_validation_test.cc b/src/tint/resolver/assignment_validation_test.cc
index 642340b..cf8b505 100644
--- a/src/tint/resolver/assignment_validation_test.cc
+++ b/src/tint/resolver/assignment_validation_test.cc
@@ -373,8 +373,8 @@
                    Assign(Phony(), 3_f),                                    //
                    Assign(Phony(), 4_a),                                    //
                    Assign(Phony(), 5.0_a),                                  //
-                   Assign(Phony(), vec(nullptr, 2u, 6_a)),                  //
-                   Assign(Phony(), vec(nullptr, 3u, 7.0_a)),                //
+                   Assign(Phony(), vec2<Infer>(6_a)),                       //
+                   Assign(Phony(), vec3<Infer>(7.0_a)),                     //
                    Assign(Phony(), vec4<bool>()),                           //
                    Assign(Phony(), "tex"),                                  //
                    Assign(Phony(), "smp"),                                  //
diff --git a/src/tint/resolver/atomics_validation_test.cc b/src/tint/resolver/atomics_validation_test.cc
index f57cf0b..bd0c5cc 100644
--- a/src/tint/resolver/atomics_validation_test.cc
+++ b/src/tint/resolver/atomics_validation_test.cc
@@ -173,9 +173,9 @@
     // var<private> g : S0;
 
     auto* atomic_array = Alias("AtomicArray", ty.atomic(ty.i32()));
-    auto* array_i32_4 = ty.array<i32, 4>();
-    auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8_u);
-    auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4_u);
+    auto array_i32_4 = ty.array<i32, 4>();
+    auto array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8_u);
+    auto array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4_u);
 
     auto* s6 = Structure("S6", utils::Vector{Member("x", array_i32_4)});
     auto* s5 = Structure("S5", utils::Vector{Member("x", ty.Of(s6)),                              //
@@ -272,9 +272,9 @@
     // var<storage, read> g : S0;
 
     auto* atomic_array = Alias("AtomicArray", ty.atomic(ty.i32()));
-    auto* array_i32_4 = ty.array<i32, 4>();
-    auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8_u);
-    auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4_u);
+    auto array_i32_4 = ty.array<i32, 4>();
+    auto array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8_u);
+    auto array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4_u);
 
     auto* s6 = Structure("S6", utils::Vector{Member("x", array_i32_4)});
     auto* s5 = Structure("S5", utils::Vector{Member("x", ty.Of(s6)),                              //
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc
index 196796f..0f09306 100644
--- a/src/tint/resolver/attribute_validation_test.cc
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -849,7 +849,7 @@
 TEST_P(ArrayAttributeTest, IsValid) {
     auto& params = GetParam();
 
-    auto* arr = ty.array(ty.f32(), nullptr, createAttributes(Source{{12, 34}}, *this, params.kind));
+    auto arr = ty.array(ty.f32(), createAttributes(Source{{12, 34}}, *this, params.kind));
     Structure("mystruct", utils::Vector{
                               Member("a", arr),
                           });
@@ -1145,17 +1145,17 @@
 using ArrayStrideTest = TestWithParams;
 TEST_P(ArrayStrideTest, All) {
     auto& params = GetParam();
-    auto* el_ty = params.create_el_type(*this);
+    ast::Type el_ty = params.create_el_type(*this);
 
     std::stringstream ss;
     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_u,
-                         utils::Vector{
-                             create<ast::StrideAttribute>(Source{{12, 34}}, params.stride),
-                         });
+    auto arr = ty.array(el_ty, 4_u,
+                        utils::Vector{
+                            create<ast::StrideAttribute>(Source{{12, 34}}, params.stride),
+                        });
 
     GlobalVar("myarray", arr, type::AddressSpace::kPrivate);
 
@@ -1234,11 +1234,11 @@
                              ParamsFor<mat4x4<f32>>((default_mat4x4.align - 1) * 7, false)));
 
 TEST_F(ArrayStrideTest, DuplicateAttribute) {
-    auto* arr = ty.array(Source{{12, 34}}, ty.i32(), 4_u,
-                         utils::Vector{
-                             create<ast::StrideAttribute>(Source{{12, 34}}, 4u),
-                             create<ast::StrideAttribute>(Source{{56, 78}}, 4u),
-                         });
+    auto arr = ty.array(Source{{12, 34}}, ty.i32(), 4_u,
+                        utils::Vector{
+                            create<ast::StrideAttribute>(Source{{12, 34}}, 4u),
+                            create<ast::StrideAttribute>(Source{{56, 78}}, 4u),
+                        });
 
     GlobalVar("myarray", arr, type::AddressSpace::kPrivate);
 
diff --git a/src/tint/resolver/builtin_test.cc b/src/tint/resolver/builtin_test.cc
index 74c1d60..dc49f3a 100644
--- a/src/tint/resolver/builtin_test.cc
+++ b/src/tint/resolver/builtin_test.cc
@@ -213,7 +213,7 @@
 using ResolverBuiltinArrayTest = ResolverTest;
 
 TEST_F(ResolverBuiltinArrayTest, ArrayLength_Vector) {
-    auto* ary = ty.array<i32>();
+    auto ary = ty.array<i32>();
     auto* str = Structure("S", utils::Vector{Member("x", ary)});
     GlobalVar("a", ty.Of(str), type::AddressSpace::kStorage, type::Access::kRead, Binding(0_a),
               Group(0_a));
@@ -2097,33 +2097,26 @@
     /// @param dim dimensionality of the texture being sampled
     /// @param scalar the scalar type
     /// @returns a pointer to a type appropriate for the coord param
-    const ast::Type* GetCoordsType(type::TextureDimension dim, const ast::Type* scalar) {
+    ast::Type GetCoordsType(type::TextureDimension dim, ast::Type scalar) {
         switch (dim) {
             case type::TextureDimension::k1d:
-                return scalar;
+                return ty(scalar);
             case type::TextureDimension::k2d:
             case type::TextureDimension::k2dArray:
-                return ty.vec(scalar, 2);
+                return ty.vec2(scalar);
             case type::TextureDimension::k3d:
             case type::TextureDimension::kCube:
             case type::TextureDimension::kCubeArray:
-                return ty.vec(scalar, 3);
+                return ty.vec3(scalar);
             default:
                 [=]() { FAIL() << "Unsupported texture dimension: " << dim; }();
         }
-        return nullptr;
+        return ast::Type{};
     }
 
-    void add_call_param(std::string name, const ast::Type* type, ExpressionList* call_params) {
-        if (auto* type_name = type->As<ast::TypeName>()) {
-            auto n = Symbols().NameFor(type_name->name->symbol);
-            if (utils::HasPrefix(n, "texture") || utils::HasPrefix(n, "sampler")) {
-                GlobalVar(name, type, Binding(0_a), Group(0_a));
-                return;
-            }
-        }
-
-        if (type->Is<ast::Texture>()) {
+    void add_call_param(std::string name, ast::Type type, ExpressionList* call_params) {
+        std::string type_name = Symbols().NameFor(type->identifier->symbol);
+        if (utils::HasPrefix(type_name, "texture") || utils::HasPrefix(type_name, "sampler")) {
             GlobalVar(name, type, Binding(0_a), Group(0_a));
         } else {
             GlobalVar(name, type, type::AddressSpace::kPrivate);
@@ -2131,7 +2124,7 @@
 
         call_params->Push(Expr(name));
     }
-    const ast::Type* subtype(Texture type) {
+    ast::Type subtype(Texture type) {
         if (type == Texture::kF32) {
             return ty.f32();
         }
@@ -2147,9 +2140,9 @@
     auto dim = GetParam().dim;
     auto type = GetParam().type;
 
-    auto* s = subtype(type);
-    auto* coords_type = GetCoordsType(dim, ty.i32());
-    auto* texture_type = ty.sampled_texture(dim, s);
+    ast::Type s = subtype(type);
+    ast::Type coords_type = GetCoordsType(dim, ty.i32());
+    auto texture_type = ty.sampled_texture(dim, s);
 
     ExpressionList call_params;
 
diff --git a/src/tint/resolver/builtin_validation_test.cc b/src/tint/resolver/builtin_validation_test.cc
index 2750d97..b9c31c9 100644
--- a/src/tint/resolver/builtin_validation_test.cc
+++ b/src/tint/resolver/builtin_validation_test.cc
@@ -115,7 +115,8 @@
     WrapInFunction(Decl(Var("v", Expr(Source{{56, 78}}, "mix"))));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), R"(56:78 error: missing '(' for function call)");
+    EXPECT_EQ(r()->error(), R"(56:78 error: cannot use function 'mix' as value
+12:34 note: function 'mix' declared here)");
 }
 
 TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsGlobalConstUsedAsVariable) {
@@ -159,7 +160,7 @@
 
 TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsAliasUsedAsType) {
     auto* mix = Alias(Source{{12, 34}}, "mix", ty.i32());
-    auto* use = Call(ty("mix"));
+    auto* use = Call("mix");
     WrapInFunction(use);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -183,7 +184,7 @@
     auto* mix = Structure("mix", utils::Vector{
                                      Member("m", ty.i32()),
                                  });
-    auto* use = Call(ty("mix"));
+    auto* use = Call("mix");
     WrapInFunction(use);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/tint/resolver/const_eval_binary_op_test.cc b/src/tint/resolver/const_eval_binary_op_test.cc
index 09b1247..e7f6f14 100644
--- a/src/tint/resolver/const_eval_binary_op_test.cc
+++ b/src/tint/resolver/const_eval_binary_op_test.cc
@@ -1953,8 +1953,7 @@
     Structure("S", utils::Vector{Member("a", ty.i32()), Member("b", ty.f32())});
     GlobalConst("one", Expr(1_a));
     auto* lhs = Equal("one", 0_a);
-    auto* rhs =
-        Equal(MemberAccessor(Call(ty("S"), Expr(1_a), Expr(Source{{12, 34}}, true)), "a"), 0_a);
+    auto* rhs = Equal(MemberAccessor(Call("S", Expr(1_a), Expr(Source{{12, 34}}, true)), "a"), 0_a);
     GlobalConst("result", LogicalAnd(lhs, rhs));
 
     EXPECT_FALSE(r()->Resolve());
@@ -1973,8 +1972,7 @@
     Structure("S", utils::Vector{Member("a", ty.i32()), Member("b", ty.f32())});
     GlobalConst("one", Expr(1_a));
     auto* lhs = Equal("one", 1_a);
-    auto* rhs =
-        Equal(MemberAccessor(Call(ty("S"), Expr(1_a), Expr(Source{{12, 34}}, true)), "a"), 0_a);
+    auto* rhs = Equal(MemberAccessor(Call("S", Expr(1_a), Expr(Source{{12, 34}}, true)), "a"), 0_a);
     GlobalConst("result", LogicalOr(lhs, rhs));
 
     EXPECT_FALSE(r()->Resolve());
@@ -2146,7 +2144,7 @@
     // const one = 1;
     // const result = (one == 0) && (s.c == 0);
     Structure("S", utils::Vector{Member("a", ty.i32()), Member("b", ty.f32())});
-    GlobalConst("s", Call(ty("S"), Expr(1_a), Expr(2.0_a)));
+    GlobalConst("s", Call("S", Expr(1_a), Expr(2.0_a)));
     GlobalConst("one", Expr(1_a));
     auto* lhs = Equal("one", 0_a);
     auto* rhs = Equal(MemberAccessor(Source{{12, 34}}, "s", "c"), 0_a);
@@ -2165,7 +2163,7 @@
     // const one = 1;
     // const result = (one == 1) || (s.c == 0);
     Structure("S", utils::Vector{Member("a", ty.i32()), Member("b", ty.f32())});
-    GlobalConst("s", Call(ty("S"), Expr(1_a), Expr(2.0_a)));
+    GlobalConst("s", Call("S", Expr(1_a), Expr(2.0_a)));
     GlobalConst("one", Expr(1_a));
     auto* lhs = Equal("one", 1_a);
     auto* rhs = Equal(MemberAccessor(Source{{12, 34}}, "s", "c"), 0_a);
@@ -2187,7 +2185,7 @@
     // const result = (one == 0) && (vec2(1, 2).z == 0);
     GlobalConst("one", Expr(1_a));
     auto* lhs = Equal("one", 0_a);
-    auto* rhs = Equal(MemberAccessor(vec2<AInt>(1_a, 2_a), Ident(Source{{12, 34}}, "z")), 0_a);
+    auto* rhs = Equal(MemberAccessor(vec2<Infer>(1_a, 2_a), Ident(Source{{12, 34}}, "z")), 0_a);
     GlobalConst("result", LogicalAnd(lhs, rhs));
 
     EXPECT_FALSE(r()->Resolve());
@@ -2199,7 +2197,7 @@
     // const result = (one == 1) || (vec2(1, 2).z == 0);
     GlobalConst("one", Expr(1_a));
     auto* lhs = Equal("one", 1_a);
-    auto* rhs = Equal(MemberAccessor(vec2<AInt>(1_a, 2_a), Ident(Source{{12, 34}}, "z")), 0_a);
+    auto* rhs = Equal(MemberAccessor(vec2<Infer>(1_a, 2_a), Ident(Source{{12, 34}}, "z")), 0_a);
     GlobalConst("result", LogicalOr(lhs, rhs));
 
     EXPECT_FALSE(r()->Resolve());
diff --git a/src/tint/resolver/const_eval_bitcast_test.cc b/src/tint/resolver/const_eval_bitcast_test.cc
index 32c1382..cbb09d5 100644
--- a/src/tint/resolver/const_eval_bitcast_test.cc
+++ b/src/tint/resolver/const_eval_bitcast_test.cc
@@ -64,7 +64,7 @@
         target_create_ptrs = expected.Failure().create_ptrs;
     }
 
-    auto* target_ty = target_create_ptrs.ast(*this);
+    auto target_ty = target_create_ptrs.ast(*this);
     ASSERT_NE(target_ty, nullptr);
     auto* input_val = input.Expr(*this);
     const ast::Expression* expr = Bitcast(Source{{12, 34}}, target_ty, input_val);
diff --git a/src/tint/resolver/const_eval_construction_test.cc b/src/tint/resolver/const_eval_construction_test.cc
index 80f0c11..fbf7515 100644
--- a/src/tint/resolver/const_eval_construction_test.cc
+++ b/src/tint/resolver/const_eval_construction_test.cc
@@ -148,7 +148,7 @@
 TEST_P(ResolverConstEvalZeroInitTest, Test) {
     Enable(ast::Extension::kF16);
     auto& param = GetParam();
-    auto* ty = param.type(*this);
+    auto ty = param.type(*this);
     auto* expr = Call(ty);
     auto* a = Const("a", expr);
     WrapInFunction(a);
@@ -552,7 +552,7 @@
 }
 
 TEST_F(ResolverConstEvalTest, Vec3_FullConstruct_AInt) {
-    auto* expr = vec3<AInt>(1_a, 2_a, 3_a);
+    auto* expr = vec3<Infer>(1_a, 2_a, 3_a);
     auto* a = Const("a", expr);
     WrapInFunction(a);
 
@@ -586,7 +586,7 @@
 }
 
 TEST_F(ResolverConstEvalTest, Vec3_FullConstruct_AFloat) {
-    auto* expr = vec3<AFloat>(1.0_a, 2.0_a, 3.0_a);
+    auto* expr = vec3<Infer>(1.0_a, 2.0_a, 3.0_a);
     auto* a = Const("a", expr);
     WrapInFunction(a);
 
@@ -1392,7 +1392,7 @@
 }
 
 TEST_F(ResolverConstEvalTest, Mat3x2_Construct_Scalars_af) {
-    auto* expr = Call(ty.mat(nullptr, 3, 2), 1.0_a, 2.0_a, 3.0_a, 4.0_a, 5.0_a, 6.0_a);
+    auto* expr = Call(ty.mat3x2<Infer>(), 1.0_a, 2.0_a, 3.0_a, 4.0_a, 5.0_a, 6.0_a);
     WrapInFunction(expr);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -1441,10 +1441,10 @@
 }
 
 TEST_F(ResolverConstEvalTest, Mat3x2_Construct_Columns_af) {
-    auto* expr = Call(ty.mat(nullptr, 3, 2),           //
-                      vec(nullptr, 2u, 1.0_a, 2.0_a),  //
-                      vec(nullptr, 2u, 3.0_a, 4.0_a),  //
-                      vec(nullptr, 2u, 5.0_a, 6.0_a));
+    auto* expr = Call(ty.mat<Infer>(3, 2),        //
+                      vec2<Infer>(1.0_a, 2.0_a),  //
+                      vec2<Infer>(3.0_a, 4.0_a),  //
+                      vec2<Infer>(5.0_a, 6.0_a));
     WrapInFunction(expr);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -1853,9 +1853,9 @@
                        Member("m1", ty.f32()),
                        Member("m2", ty.f32()),
                    });
-    auto* expr = Call(ty.array(ty("S"), 2_u),   //
-                      Call(ty("S"), 1_f, 2_f),  //
-                      Call(ty("S"), 3_f, 4_f));
+    auto* expr = Call(ty.array(ty("S"), 2_u),  //
+                      Call("S", 1_f, 2_f),     //
+                      Call("S", 3_f, 4_f));
     WrapInFunction(expr);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -1992,7 +1992,7 @@
 TEST_F(ResolverConstEvalTest, Struct_I32s_ZeroInit) {
     Structure(
         "S", utils::Vector{Member("m1", ty.i32()), Member("m2", ty.i32()), Member("m3", ty.i32())});
-    auto* expr = Call(ty("S"));
+    auto* expr = Call("S");
     WrapInFunction(expr);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -2037,7 +2037,7 @@
                        Member("m4", ty.f16()),
                        Member("m5", ty.bool_()),
                    });
-    auto* expr = Call(ty("S"));
+    auto* expr = Call("S");
     WrapInFunction(expr);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -2090,7 +2090,7 @@
                        Member("m2", ty.vec3<f32>()),
                        Member("m3", ty.vec3<f32>()),
                    });
-    auto* expr = Call(ty("S"));
+    auto* expr = Call("S");
     WrapInFunction(expr);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -2147,7 +2147,7 @@
                        Member("m4", ty.vec3<f16>()),
                        Member("m5", ty.vec2<bool>()),
                    });
-    auto* expr = Call(ty("S"));
+    auto* expr = Call("S");
     WrapInFunction(expr);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -2224,7 +2224,7 @@
                            Member("m1", ty("Inner")),
                            Member("m2", ty("Inner")),
                        });
-    auto* expr = Call(ty("Outer"));
+    auto* expr = Call("Outer");
     WrapInFunction(expr);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -2267,7 +2267,7 @@
                        Member("m4", ty.f16()),
                        Member("m5", ty.bool_()),
                    });
-    auto* expr = Call(ty("S"), 1_i, 2_u, 3_f, 4_h, false);
+    auto* expr = Call("S", 1_i, 2_u, 3_f, 4_h, false);
     WrapInFunction(expr);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -2324,7 +2324,7 @@
                        Member("m4", ty.vec3<f16>()),
                        Member("m5", ty.vec2<bool>()),
                    });
-    auto* expr = Call(ty("S"), vec2<i32>(1_i), vec3<u32>(2_u), vec4<f32>(3_f), vec3<f16>(4_h),
+    auto* expr = Call("S", vec2<i32>(1_i), vec3<u32>(2_u), vec4<f32>(3_f), vec3<f16>(4_h),
                       vec2<bool>(false));
     WrapInFunction(expr);
 
@@ -2402,8 +2402,8 @@
                            Member("m1", ty("Inner")),
                            Member("m2", ty("Inner")),
                        });
-    auto* expr = Call(ty("Outer"),  //
-                      Call(ty("Inner"), 1_i, 2_u, 3_f), Call(ty("Inner"), 4_i, 0_u, 6_f));
+    auto* expr = Call("Outer",  //
+                      Call("Inner", 1_i, 2_u, 3_f), Call("Inner", 4_i, 0_u, 6_f));
     WrapInFunction(expr);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -2441,7 +2441,7 @@
                        Member("m1", ty.array<i32, 2>()),
                        Member("m2", ty.array<f32, 3>()),
                    });
-    auto* expr = Call(ty("S"),  //
+    auto* expr = Call("S",  //
                       Call(ty.array<i32, 2>(), 1_i, 2_i), Call(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
     WrapInFunction(expr);
 
diff --git a/src/tint/resolver/const_eval_conversion_test.cc b/src/tint/resolver/const_eval_conversion_test.cc
index 739294d..e159b0e 100644
--- a/src/tint/resolver/const_eval_conversion_test.cc
+++ b/src/tint/resolver/const_eval_conversion_test.cc
@@ -73,7 +73,7 @@
     auto* input_val = input.Expr(*this);
     auto* expr = Call(type.ast(*this), input_val);
     if (kind == Kind::kVector) {
-        expr = Call(ty.vec(nullptr, 3), expr);
+        expr = Call(ty.vec<Infer>(3), expr);
     }
     WrapInFunction(expr);
 
diff --git a/src/tint/resolver/const_eval_member_access_test.cc b/src/tint/resolver/const_eval_member_access_test.cc
index c1fcfa1..e93d63b 100644
--- a/src/tint/resolver/const_eval_member_access_test.cc
+++ b/src/tint/resolver/const_eval_member_access_test.cc
@@ -31,8 +31,8 @@
                            Member("o1", ty("Inner")),
                            Member("o2", ty("Inner")),
                        });
-    auto* outer_expr = Call(ty("Outer"),  //
-                            Call(ty("Inner"), 1_i, 2_u, 3_f, true), Call(ty("Inner")));
+    auto* outer_expr = Call("Outer",  //
+                            Call("Inner", 1_i, 2_u, 3_f, true), Call("Inner"));
     auto* o1_expr = MemberAccessor(outer_expr, "o1");
     auto* i2_expr = MemberAccessor(o1_expr, "i2");
     WrapInFunction(i2_expr);
@@ -71,9 +71,9 @@
 }
 
 TEST_F(ResolverConstEvalTest, Matrix_AFloat_Construct_From_AInt_Vectors) {
-    auto* c = Const("a", Call(ty.mat(nullptr, 2, 2),  //
-                              Call(ty.vec(nullptr, 2), Expr(1_a), Expr(2_a)),
-                              Call(ty.vec(nullptr, 2), Expr(3_a), Expr(4_a))));
+    auto* c = Const("a", Call(ty.mat2x2<Infer>(),  //
+                              Call(ty.vec<Infer>(2), Expr(1_a), Expr(2_a)),
+                              Call(ty.vec<Infer>(2), Expr(3_a), Expr(4_a))));
     WrapInFunction(c);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -97,9 +97,9 @@
 }
 
 TEST_F(ResolverConstEvalTest, MatrixMemberAccess_AFloat) {
-    auto* c = Const("a", Call(ty.mat(nullptr, 2, 3),  //
-                              Call(ty.vec(nullptr, 3), Expr(1.0_a), Expr(2.0_a), Expr(3.0_a)),
-                              Call(ty.vec(nullptr, 3), Expr(4.0_a), Expr(5.0_a), Expr(6.0_a))));
+    auto* c = Const("a", Call(ty.mat2x3<Infer>(),  //
+                              Call(ty.vec3<Infer>(), Expr(1.0_a), Expr(2.0_a), Expr(3.0_a)),
+                              Call(ty.vec3<Infer>(), Expr(4.0_a), Expr(5.0_a), Expr(6.0_a))));
 
     auto* col_0 = Const("col_0", IndexAccessor("a", Expr(0_i)));
     auto* col_1 = Const("col_1", IndexAccessor("a", Expr(1_i)));
@@ -174,9 +174,9 @@
 }
 
 TEST_F(ResolverConstEvalTest, MatrixMemberAccess_f32) {
-    auto* c = Const("a", Call(ty.mat(nullptr, 2, 3),  //
-                              Call(ty.vec(nullptr, 3), Expr(1.0_f), Expr(2.0_f), Expr(3.0_f)),
-                              Call(ty.vec(nullptr, 3), Expr(4.0_f), Expr(5.0_f), Expr(6.0_f))));
+    auto* c = Const("a", Call(ty.mat2x3<Infer>(),  //
+                              Call(ty.vec3<Infer>(), Expr(1.0_f), Expr(2.0_f), Expr(3.0_f)),
+                              Call(ty.vec3<Infer>(), Expr(4.0_f), Expr(5.0_f), Expr(6.0_f))));
 
     auto* col_0 = Const("col_0", IndexAccessor("a", Expr(0_i)));
     auto* col_1 = Const("col_1", IndexAccessor("a", Expr(1_i)));
diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc
index e581b9a..16a3f8e 100644
--- a/src/tint/resolver/dependency_graph.cc
+++ b/src/tint/resolver/dependency_graph.cc
@@ -19,9 +19,7 @@
 #include <vector>
 
 #include "src/tint/ast/alias.h"
-#include "src/tint/ast/array.h"
 #include "src/tint/ast/assignment_statement.h"
-#include "src/tint/ast/atomic.h"
 #include "src/tint/ast/block_statement.h"
 #include "src/tint/ast/break_if_statement.h"
 #include "src/tint/ast/break_statement.h"
@@ -41,12 +39,8 @@
 #include "src/tint/ast/invariant_attribute.h"
 #include "src/tint/ast/location_attribute.h"
 #include "src/tint/ast/loop_statement.h"
-#include "src/tint/ast/matrix.h"
-#include "src/tint/ast/multisampled_texture.h"
 #include "src/tint/ast/override.h"
-#include "src/tint/ast/pointer.h"
 #include "src/tint/ast/return_statement.h"
-#include "src/tint/ast/sampled_texture.h"
 #include "src/tint/ast/stage_attribute.h"
 #include "src/tint/ast/stride_attribute.h"
 #include "src/tint/ast/struct.h"
@@ -56,10 +50,8 @@
 #include "src/tint/ast/switch_statement.h"
 #include "src/tint/ast/templated_identifier.h"
 #include "src/tint/ast/traverse_expressions.h"
-#include "src/tint/ast/type_name.h"
 #include "src/tint/ast/var.h"
 #include "src/tint/ast/variable_decl_statement.h"
-#include "src/tint/ast/vector.h"
 #include "src/tint/ast/while_statement.h"
 #include "src/tint/ast/workgroup_attribute.h"
 #include "src/tint/scope_stack.h"
@@ -180,12 +172,12 @@
                 Declare(str->name->symbol, str);
                 for (auto* member : str->members) {
                     TraverseAttributes(member->attributes);
-                    TraverseType(member->type);
+                    TraverseTypeExpression(member->type);
                 }
             },
             [&](const ast::Alias* alias) {
                 Declare(alias->name->symbol, alias);
-                TraverseType(alias->type);
+                TraverseTypeExpression(alias->type);
             },
             [&](const ast::Function* func) {
                 Declare(func->name->symbol, func);
@@ -193,10 +185,10 @@
             },
             [&](const ast::Variable* var) {
                 Declare(var->name->symbol, var);
-                TraverseType(var->type);
+                TraverseTypeExpression(var->type);
                 TraverseAttributes(var->attributes);
                 if (var->initializer) {
-                    TraverseExpression(var->initializer);
+                    TraverseValueExpression(var->initializer);
                 }
             },
             [&](const ast::DiagnosticDirective*) {
@@ -205,7 +197,9 @@
             [&](const ast::Enable*) {
                 // Enable directives do not affect the dependency graph.
             },
-            [&](const ast::ConstAssert* assertion) { TraverseExpression(assertion->condition); },
+            [&](const ast::ConstAssert* assertion) {
+                TraverseValueExpression(assertion->condition);
+            },
             [&](Default) { UnhandledNode(diagnostics_, global->node); });
     }
 
@@ -220,10 +214,10 @@
         // with the same identifier as its type.
         for (auto* param : func->params) {
             TraverseAttributes(param->attributes);
-            TraverseType(param->type);
+            TraverseTypeExpression(param->type);
         }
         // Resolve the return type
-        TraverseType(func->return_type);
+        TraverseTypeExpression(func->return_type);
 
         // Push the scope stack for the parameters and function body.
         scope_stack_.Push();
@@ -257,29 +251,29 @@
         Switch(
             stmt,  //
             [&](const ast::AssignmentStatement* a) {
-                TraverseExpression(a->lhs);
-                TraverseExpression(a->rhs);
+                TraverseValueExpression(a->lhs);
+                TraverseValueExpression(a->rhs);
             },
             [&](const ast::BlockStatement* b) {
                 scope_stack_.Push();
                 TINT_DEFER(scope_stack_.Pop());
                 TraverseStatements(b->statements);
             },
-            [&](const ast::BreakIfStatement* b) { TraverseExpression(b->condition); },
-            [&](const ast::CallStatement* r) { TraverseExpression(r->expr); },
+            [&](const ast::BreakIfStatement* b) { TraverseValueExpression(b->condition); },
+            [&](const ast::CallStatement* r) { TraverseValueExpression(r->expr); },
             [&](const ast::CompoundAssignmentStatement* a) {
-                TraverseExpression(a->lhs);
-                TraverseExpression(a->rhs);
+                TraverseValueExpression(a->lhs);
+                TraverseValueExpression(a->rhs);
             },
             [&](const ast::ForLoopStatement* l) {
                 scope_stack_.Push();
                 TINT_DEFER(scope_stack_.Pop());
                 TraverseStatement(l->initializer);
-                TraverseExpression(l->condition);
+                TraverseValueExpression(l->condition);
                 TraverseStatement(l->continuing);
                 TraverseStatement(l->body);
             },
-            [&](const ast::IncrementDecrementStatement* i) { TraverseExpression(i->lhs); },
+            [&](const ast::IncrementDecrementStatement* i) { TraverseValueExpression(i->lhs); },
             [&](const ast::LoopStatement* l) {
                 scope_stack_.Push();
                 TINT_DEFER(scope_stack_.Pop());
@@ -287,18 +281,18 @@
                 TraverseStatement(l->continuing);
             },
             [&](const ast::IfStatement* i) {
-                TraverseExpression(i->condition);
+                TraverseValueExpression(i->condition);
                 TraverseStatement(i->body);
                 if (i->else_statement) {
                     TraverseStatement(i->else_statement);
                 }
             },
-            [&](const ast::ReturnStatement* r) { TraverseExpression(r->value); },
+            [&](const ast::ReturnStatement* r) { TraverseValueExpression(r->value); },
             [&](const ast::SwitchStatement* s) {
-                TraverseExpression(s->condition);
+                TraverseValueExpression(s->condition);
                 for (auto* c : s->body) {
                     for (auto* sel : c->selectors) {
-                        TraverseExpression(sel->expr);
+                        TraverseValueExpression(sel->expr);
                     }
                     TraverseStatement(c->body);
                 }
@@ -307,17 +301,19 @@
                 if (auto* shadows = scope_stack_.Get(v->variable->name->symbol)) {
                     graph_.shadows.Add(v->variable, shadows);
                 }
-                TraverseType(v->variable->type);
-                TraverseExpression(v->variable->initializer);
+                TraverseTypeExpression(v->variable->type);
+                TraverseValueExpression(v->variable->initializer);
                 Declare(v->variable->name->symbol, v->variable);
             },
             [&](const ast::WhileStatement* w) {
                 scope_stack_.Push();
                 TINT_DEFER(scope_stack_.Pop());
-                TraverseExpression(w->condition);
+                TraverseValueExpression(w->condition);
                 TraverseStatement(w->body);
             },
-            [&](const ast::ConstAssert* assertion) { TraverseExpression(assertion->condition); },
+            [&](const ast::ConstAssert* assertion) {
+                TraverseValueExpression(assertion->condition);
+            },
             [&](Default) {
                 if (TINT_UNLIKELY((!stmt->IsAnyOf<ast::BreakStatement, ast::ContinueStatement,
                                                   ast::DiscardStatement>()))) {
@@ -337,87 +333,63 @@
         }
     }
 
-    /// Traverses the expression, performing symbol resolution and determining global dependencies.
-    void TraverseExpression(const ast::Expression* root) {
-        if (!root) {
+    /// Traverses the expression @p root_expr for the intended use as a value, performing symbol
+    /// resolution and determining global dependencies.
+    void TraverseValueExpression(const ast::Expression* root) {
+        TraverseExpression(root, "identifier", "references");
+    }
+
+    /// Traverses the expression @p root_expr for the intended use as a type, performing symbol
+    /// resolution and determining global dependencies.
+    void TraverseTypeExpression(const ast::Expression* root) {
+        TraverseExpression(root, "type", "references");
+    }
+
+    /// Traverses the expression @p root_expr for the intended use as a call target, performing
+    /// symbol resolution and determining global dependencies.
+    void TraverseCallableExpression(const ast::Expression* root) {
+        TraverseExpression(root, "function", "calls");
+    }
+
+    /// Traverses the expression @p root_expr, performing symbol resolution and determining global
+    /// dependencies.
+    void TraverseExpression(const ast::Expression* root_expr,
+                            const char* root_use,
+                            const char* root_action) {
+        if (!root_expr) {
             return;
         }
-        utils::Vector<const ast::Expression*, 8> pending{root};
+
+        struct Pending {
+            const ast::Expression* expr;
+            const char* use;
+            const char* action;
+        };
+        utils::Vector<Pending, 8> pending{{root_expr, root_use, root_action}};
         while (!pending.IsEmpty()) {
-            ast::TraverseExpressions(pending.Pop(), diagnostics_, [&](const ast::Expression* expr) {
+            auto next = pending.Pop();
+            ast::TraverseExpressions(next.expr, diagnostics_, [&](const ast::Expression* expr) {
                 Switch(
                     expr,
                     [&](const ast::IdentifierExpression* e) {
-                        AddDependency(e->identifier, e->identifier->symbol, "identifier",
-                                      "references");
+                        AddDependency(e->identifier, e->identifier->symbol, next.use, next.action);
                         if (auto* tmpl_ident = e->identifier->As<ast::TemplatedIdentifier>()) {
                             for (auto* arg : tmpl_ident->arguments) {
-                                pending.Push(arg);
+                                pending.Push({arg, "identifier", "references"});
                             }
                         }
                     },
                     [&](const ast::CallExpression* call) {
-                        if (call->target.name) {
-                            AddDependency(call->target.name, call->target.name->symbol, "function",
-                                          "calls");
-                            if (auto* tmpl_ident =
-                                    call->target.name->As<ast::TemplatedIdentifier>()) {
-                                for (auto* arg : tmpl_ident->arguments) {
-                                    pending.Push(arg);
-                                }
-                            }
-                        }
-                        if (call->target.type) {
-                            TraverseType(call->target.type);
-                        }
+                        TraverseCallableExpression(call->target);
                     },
-                    [&](const ast::BitcastExpression* cast) { TraverseType(cast->type); });
+                    [&](const ast::BitcastExpression* cast) {
+                        TraverseTypeExpression(cast->type);
+                    });
                 return ast::TraverseAction::Descend;
             });
         }
     }
 
-    /// Traverses the type node, performing symbol resolution and determining
-    /// global dependencies.
-    void TraverseType(const ast::Type* ty) {
-        if (!ty) {
-            return;
-        }
-        Switch(
-            ty,  //
-            [&](const ast::Array* arr) {
-                TraverseType(arr->type);  //
-                TraverseExpression(arr->count);
-            },
-            [&](const ast::Atomic* atomic) {  //
-                TraverseType(atomic->type);
-            },
-            [&](const ast::Matrix* mat) {  //
-                TraverseType(mat->type);
-            },
-            [&](const ast::Pointer* ptr) {  //
-                TraverseType(ptr->type);
-            },
-            [&](const ast::TypeName* tn) {  //
-                AddDependency(tn->name, tn->name->symbol, "type", "references");
-                if (auto* tmpl_ident = tn->name->As<ast::TemplatedIdentifier>()) {
-                    for (auto* arg : tmpl_ident->arguments) {
-                        TraverseExpression(arg);
-                    }
-                }
-            },
-            [&](const ast::Vector* vec) {  //
-                TraverseType(vec->type);
-            },
-            [&](const ast::SampledTexture* tex) {  //
-                TraverseType(tex->type);
-            },
-            [&](const ast::MultisampledTexture* tex) {  //
-                TraverseType(tex->type);
-            },
-            [&](Default) { UnhandledNode(diagnostics_, ty); });
-    }
-
     /// Traverses the attribute list, performing symbol resolution and
     /// determining global dependencies.
     void TraverseAttributes(utils::VectorRef<const ast::Attribute*> attrs) {
@@ -432,33 +404,33 @@
         bool handled = Switch(
             attr,
             [&](const ast::BindingAttribute* binding) {
-                TraverseExpression(binding->expr);
+                TraverseValueExpression(binding->expr);
                 return true;
             },
             [&](const ast::GroupAttribute* group) {
-                TraverseExpression(group->expr);
+                TraverseValueExpression(group->expr);
                 return true;
             },
             [&](const ast::IdAttribute* id) {
-                TraverseExpression(id->expr);
+                TraverseValueExpression(id->expr);
                 return true;
             },
             [&](const ast::LocationAttribute* loc) {
-                TraverseExpression(loc->expr);
+                TraverseValueExpression(loc->expr);
                 return true;
             },
             [&](const ast::StructMemberAlignAttribute* align) {
-                TraverseExpression(align->expr);
+                TraverseValueExpression(align->expr);
                 return true;
             },
             [&](const ast::StructMemberSizeAttribute* size) {
-                TraverseExpression(size->expr);
+                TraverseValueExpression(size->expr);
                 return true;
             },
             [&](const ast::WorkgroupAttribute* wg) {
-                TraverseExpression(wg->x);
-                TraverseExpression(wg->y);
-                TraverseExpression(wg->z);
+                TraverseValueExpression(wg->x);
+                TraverseValueExpression(wg->y);
+                TraverseValueExpression(wg->z);
                 return true;
             });
         if (handled) {
@@ -517,8 +489,7 @@
         graph_.resolved_identifiers.Add(from, ResolvedIdentifier(resolved));
     }
 
-    /// Appends an error to the diagnostics that the given symbol cannot be
-    /// resolved.
+    /// Appends an error to the diagnostics that the given symbol cannot be resolved.
     void UnknownSymbol(Symbol name, Source source, const char* use) {
         AddError(diagnostics_, "unknown " + std::string(use) + ": '" + symbols_.NameFor(name) + "'",
                  source);
diff --git a/src/tint/resolver/dependency_graph.h b/src/tint/resolver/dependency_graph.h
index 8b2e668..7819b52 100644
--- a/src/tint/resolver/dependency_graph.h
+++ b/src/tint/resolver/dependency_graph.h
@@ -21,6 +21,7 @@
 #include "src/tint/ast/module.h"
 #include "src/tint/diagnostic/diagnostic.h"
 #include "src/tint/sem/builtin_type.h"
+#include "src/tint/symbol_table.h"
 #include "src/tint/type/access.h"
 #include "src/tint/type/builtin.h"
 #include "src/tint/type/texel_format.h"
diff --git a/src/tint/resolver/dependency_graph_test.cc b/src/tint/resolver/dependency_graph_test.cc
index 7a98b2f..8d61599 100644
--- a/src/tint/resolver/dependency_graph_test.cc
+++ b/src/tint/resolver/dependency_graph_test.cc
@@ -277,26 +277,26 @@
 std::string DiagString(SymbolUseKind kind) {
     switch (kind) {
         case SymbolUseKind::GlobalVarType:
+        case SymbolUseKind::GlobalConstType:
+        case SymbolUseKind::AliasType:
+        case SymbolUseKind::StructMemberType:
+        case SymbolUseKind::ParameterType:
+        case SymbolUseKind::LocalVarType:
+        case SymbolUseKind::LocalLetType:
+        case SymbolUseKind::NestedLocalVarType:
+        case SymbolUseKind::NestedLocalLetType:
+            return "type";
         case SymbolUseKind::GlobalVarArrayElemType:
         case SymbolUseKind::GlobalVarVectorElemType:
         case SymbolUseKind::GlobalVarMatrixElemType:
         case SymbolUseKind::GlobalVarSampledTexElemType:
         case SymbolUseKind::GlobalVarMultisampledTexElemType:
-        case SymbolUseKind::GlobalConstType:
         case SymbolUseKind::GlobalConstArrayElemType:
         case SymbolUseKind::GlobalConstVectorElemType:
         case SymbolUseKind::GlobalConstMatrixElemType:
-        case SymbolUseKind::AliasType:
-        case SymbolUseKind::StructMemberType:
-        case SymbolUseKind::ParameterType:
-        case SymbolUseKind::LocalVarType:
         case SymbolUseKind::LocalVarArrayElemType:
         case SymbolUseKind::LocalVarVectorElemType:
         case SymbolUseKind::LocalVarMatrixElemType:
-        case SymbolUseKind::LocalLetType:
-        case SymbolUseKind::NestedLocalVarType:
-        case SymbolUseKind::NestedLocalLetType:
-            return "type";
         case SymbolUseKind::GlobalVarValue:
         case SymbolUseKind::GlobalVarArraySizeValue:
         case SymbolUseKind::GlobalConstValue:
@@ -469,14 +469,14 @@
     auto& b = *builder;
     switch (kind) {
         case SymbolUseKind::GlobalVarType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             b.GlobalVar(b.Sym(), node, type::AddressSpace::kPrivate);
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::GlobalVarArrayElemType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             b.GlobalVar(b.Sym(), b.ty.array(node, 4_i), type::AddressSpace::kPrivate);
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::GlobalVarArraySizeValue: {
             auto* node = b.Expr(source, symbol);
@@ -484,24 +484,24 @@
             return node->identifier;
         }
         case SymbolUseKind::GlobalVarVectorElemType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             b.GlobalVar(b.Sym(), b.ty.vec3(node), type::AddressSpace::kPrivate);
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::GlobalVarMatrixElemType: {
-            auto* node = b.ty(source, symbol);
+            ast::Type node = b.ty(source, symbol);
             b.GlobalVar(b.Sym(), b.ty.mat3x4(node), type::AddressSpace::kPrivate);
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::GlobalVarSampledTexElemType: {
-            auto* node = b.ty(source, symbol);
+            ast::Type node = b.ty(source, symbol);
             b.GlobalVar(b.Sym(), b.ty.sampled_texture(type::TextureDimension::k2d, node));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::GlobalVarMultisampledTexElemType: {
-            auto* node = b.ty(source, symbol);
+            ast::Type node = b.ty(source, symbol);
             b.GlobalVar(b.Sym(), b.ty.multisampled_texture(type::TextureDimension::k2d, node));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::GlobalVarValue: {
             auto* node = b.Expr(source, symbol);
@@ -509,14 +509,14 @@
             return node->identifier;
         }
         case SymbolUseKind::GlobalConstType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             b.GlobalConst(b.Sym(), node, b.Expr(1_i));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::GlobalConstArrayElemType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             b.GlobalConst(b.Sym(), b.ty.array(node, 4_i), b.Expr(1_i));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::GlobalConstArraySizeValue: {
             auto* node = b.Expr(source, symbol);
@@ -524,14 +524,14 @@
             return node->identifier;
         }
         case SymbolUseKind::GlobalConstVectorElemType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             b.GlobalConst(b.Sym(), b.ty.vec3(node), b.Expr(1_i));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::GlobalConstMatrixElemType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             b.GlobalConst(b.Sym(), b.ty.mat3x4(node), b.Expr(1_i));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::GlobalConstValue: {
             auto* node = b.Expr(source, symbol);
@@ -539,14 +539,14 @@
             return node->identifier;
         }
         case SymbolUseKind::AliasType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             b.Alias(b.Sym(), node);
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::StructMemberType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             b.Structure(b.Sym(), utils::Vector{b.Member("m", node)});
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::CallFunction: {
             auto* node = b.Ident(source, symbol);
@@ -554,19 +554,19 @@
             return node;
         }
         case SymbolUseKind::ParameterType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             parameters.Push(b.Param(b.Sym(), node));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::LocalVarType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             statements.Push(b.Decl(b.Var(b.Sym(), node)));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::LocalVarArrayElemType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             statements.Push(b.Decl(b.Var(b.Sym(), b.ty.array(node, 4_u), b.Expr(1_i))));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::LocalVarArraySizeValue: {
             auto* node = b.Expr(source, symbol);
@@ -574,14 +574,14 @@
             return node->identifier;
         }
         case SymbolUseKind::LocalVarVectorElemType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             statements.Push(b.Decl(b.Var(b.Sym(), b.ty.vec3(node))));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::LocalVarMatrixElemType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             statements.Push(b.Decl(b.Var(b.Sym(), b.ty.mat3x4(node))));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::LocalVarValue: {
             auto* node = b.Expr(source, symbol);
@@ -589,9 +589,9 @@
             return node->identifier;
         }
         case SymbolUseKind::LocalLetType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             statements.Push(b.Decl(b.Let(b.Sym(), node, b.Expr(1_i))));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::LocalLetValue: {
             auto* node = b.Expr(source, symbol);
@@ -599,9 +599,9 @@
             return node->identifier;
         }
         case SymbolUseKind::NestedLocalVarType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             nested_statements.Push(b.Decl(b.Var(b.Sym(), node)));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::NestedLocalVarValue: {
             auto* node = b.Expr(source, symbol);
@@ -609,9 +609,9 @@
             return node->identifier;
         }
         case SymbolUseKind::NestedLocalLetType: {
-            auto* node = b.ty(source, symbol);
+            auto node = b.ty(source, symbol);
             nested_statements.Push(b.Decl(b.Let(b.Sym(), node, b.Expr(1_i))));
-            return node->name;
+            return node->identifier;
         }
         case SymbolUseKind::NestedLocalLetValue: {
             auto* node = b.Expr(source, symbol);
@@ -666,7 +666,7 @@
     // type T = i32;
 
     Func("F", utils::Empty, ty.void_(),
-         utils::Vector{Block(Ignore(Call(ty(Source{{12, 34}}, "T"))))});
+         utils::Vector{Block(Ignore(Call(Ident(Source{{12, 34}}, "T"))))});
     Alias(Source{{56, 78}}, "T", ty.i32());
 
     Build();
@@ -1638,10 +1638,6 @@
     return expr->identifier;
 }
 
-static const ast::Identifier* IdentifierOf(const ast::TypeName* ty) {
-    return ty->name;
-}
-
 static const ast::Identifier* IdentifierOf(const ast::Identifier* ident) {
     return ident;
 }
@@ -1665,7 +1661,7 @@
 
     utils::Vector<SymbolUse, 64> symbol_uses;
 
-    auto add_use = [&](const ast::Node* decl, auto* use, int line, const char* kind) {
+    auto add_use = [&](const ast::Node* decl, auto use, int line, const char* kind) {
         symbol_uses.Push(
             SymbolUse{decl, IdentifierOf(use),
                       std::string(__FILE__) + ":" + std::to_string(line) + ": " + kind});
@@ -1776,7 +1772,7 @@
     Structure("B", utils::Vector{Member("b", ty.i32())});
     Func("f", utils::Vector{Param("a", ty("A"))}, ty("B"),
          utils::Vector{
-             Return(Call(ty("B"))),
+             Return(Call("B")),
          });
     Build();
 }
diff --git a/src/tint/resolver/evaluation_stage_test.cc b/src/tint/resolver/evaluation_stage_test.cc
index 28c8a99..2e148e5 100644
--- a/src/tint/resolver/evaluation_stage_test.cc
+++ b/src/tint/resolver/evaluation_stage_test.cc
@@ -270,7 +270,7 @@
     // const str = S();
     // str.m
     Structure("S", utils::Vector{Member("m", ty.i32())});
-    auto* str = Const("str", Call(ty("S")));
+    auto* str = Const("str", Call("S"));
     auto* expr = MemberAccessor(str, "m");
     WrapInFunction(str, expr);
 
@@ -284,7 +284,7 @@
     // var str = S();
     // str.m
     Structure("S", utils::Vector{Member("m", ty.i32())});
-    auto* str = Var("str", Call(ty("S")));
+    auto* str = Var("str", Call("S"));
     auto* expr = MemberAccessor(str, "m");
     WrapInFunction(str, expr);
 
diff --git a/src/tint/resolver/expression_kind_test.cc b/src/tint/resolver/expression_kind_test.cc
index 7737654..c8cea54 100644
--- a/src/tint/resolver/expression_kind_test.cc
+++ b/src/tint/resolver/expression_kind_test.cc
@@ -119,78 +119,144 @@
 
 TEST_P(ResolverExpressionKindTest, Test) {
     Symbol sym;
+    std::function<void(const sem::Expression*)> check_expr;
     switch (GetParam().def) {
-        case Def::kAccess:
+        case Def::kAccess: {
             sym = Sym("write");
+            check_expr = [](const sem::Expression* expr) {
+                ASSERT_NE(expr, nullptr);
+                auto* enum_expr = expr->As<sem::BuiltinEnumExpression<type::Access>>();
+                ASSERT_NE(enum_expr, nullptr);
+                EXPECT_EQ(enum_expr->Value(), type::Access::kWrite);
+            };
             break;
-        case Def::kAddressSpace:
+        }
+        case Def::kAddressSpace: {
             sym = Sym("workgroup");
+            check_expr = [](const sem::Expression* expr) {
+                ASSERT_NE(expr, nullptr);
+                auto* enum_expr = expr->As<sem::BuiltinEnumExpression<type::AddressSpace>>();
+                ASSERT_NE(enum_expr, nullptr);
+                EXPECT_EQ(enum_expr->Value(), type::AddressSpace::kWorkgroup);
+            };
             break;
-        case Def::kBuiltinFunction:
+        }
+        case Def::kBuiltinFunction: {
             sym = Sym("workgroupBarrier");
+            check_expr = [](const sem::Expression* expr) { EXPECT_EQ(expr, nullptr); };
             break;
-        case Def::kBuiltinType:
+        }
+        case Def::kBuiltinType: {
             sym = Sym("vec4f");
+            check_expr = [](const sem::Expression* expr) {
+                ASSERT_NE(expr, nullptr);
+                auto* ty_expr = expr->As<sem::TypeExpression>();
+                ASSERT_NE(ty_expr, nullptr);
+                EXPECT_TRUE(ty_expr->Type()->Is<type::Vector>());
+            };
             break;
-        case Def::kFunction:
-            Func(kDefSource, "FUNCTION", utils::Empty, ty.i32(), Return(1_i));
+        }
+        case Def::kFunction: {
+            auto* fn = Func(kDefSource, "FUNCTION", utils::Empty, ty.i32(), Return(1_i));
             sym = Sym("FUNCTION");
+            check_expr = [fn](const sem::Expression* expr) {
+                ASSERT_NE(expr, nullptr);
+                auto* fn_expr = expr->As<sem::FunctionExpression>();
+                ASSERT_NE(fn_expr, nullptr);
+                EXPECT_EQ(fn_expr->Function()->Declaration(), fn);
+            };
             break;
-        case Def::kStruct:
-            Structure(kDefSource, "STRUCT", utils::Vector{Member("m", ty.i32())});
+        }
+        case Def::kStruct: {
+            auto* s = Structure(kDefSource, "STRUCT", utils::Vector{Member("m", ty.i32())});
             sym = Sym("STRUCT");
+            check_expr = [s](const sem::Expression* expr) {
+                ASSERT_NE(expr, nullptr);
+                auto* ty_expr = expr->As<sem::TypeExpression>();
+                ASSERT_NE(ty_expr, nullptr);
+                auto* got = ty_expr->Type()->As<sem::Struct>();
+                ASSERT_NE(got, nullptr);
+                EXPECT_EQ(got->Declaration(), s);
+            };
             break;
-        case Def::kTexelFormat:
+        }
+        case Def::kTexelFormat: {
             sym = Sym("rgba8unorm");
+            check_expr = [](const sem::Expression* expr) {
+                ASSERT_NE(expr, nullptr);
+                auto* enum_expr = expr->As<sem::BuiltinEnumExpression<type::TexelFormat>>();
+                ASSERT_NE(enum_expr, nullptr);
+                EXPECT_EQ(enum_expr->Value(), type::TexelFormat::kRgba8Unorm);
+            };
             break;
-        case Def::kTypeAlias:
+        }
+        case Def::kTypeAlias: {
             Alias(kDefSource, "ALIAS", ty.i32());
             sym = Sym("ALIAS");
+            check_expr = [](const sem::Expression* expr) {
+                ASSERT_NE(expr, nullptr);
+                auto* ty_expr = expr->As<sem::TypeExpression>();
+                ASSERT_NE(ty_expr, nullptr);
+                EXPECT_TRUE(ty_expr->Type()->Is<type::I32>());
+            };
             break;
-        case Def::kVariable:
-            GlobalConst(kDefSource, "VARIABLE", Expr(1_i));
+        }
+        case Def::kVariable: {
+            auto* c = GlobalConst(kDefSource, "VARIABLE", Expr(1_i));
             sym = Sym("VARIABLE");
+            check_expr = [c](const sem::Expression* expr) {
+                ASSERT_NE(expr, nullptr);
+                auto* var_expr = expr->As<sem::VariableUser>();
+                ASSERT_NE(var_expr, nullptr);
+                EXPECT_EQ(var_expr->Variable()->Declaration(), c);
+            };
             break;
+        }
     }
 
+    auto* expr = Expr(Ident(kUseSource, sym));
     switch (GetParam().use) {
         case Use::kAccess:
-            GlobalVar("v", ty("texture_storage_2d", "rgba8unorm", sym), Group(0_u), Binding(0_u));
+            GlobalVar("v", ty("texture_storage_2d", "rgba8unorm", expr), Group(0_u), Binding(0_u));
             break;
         case Use::kAddressSpace:
-            return;  // TODO(crbug.com/tint/1810)
+            Enable(ast::Extension::kChromiumExperimentalFullPtrParameters);
+            Func("f", utils::Vector{Param("p", ty("ptr", expr, ty.f32()))}, ty.void_(),
+                 utils::Empty);
+            break;
         case Use::kCallExpr:
-            Func("f", utils::Empty, ty.void_(), Decl(Var("v", Call(Ident(kUseSource, sym)))));
+            Func("f", utils::Empty, ty.void_(), Decl(Var("v", Call(expr))));
             break;
         case Use::kCallStmt:
-            Func("f", utils::Empty, ty.void_(), CallStmt(Call(Ident(kUseSource, sym))));
+            Func("f", utils::Empty, ty.void_(), CallStmt(Call(expr)));
             break;
         case Use::kBinaryOp:
-            GlobalVar("v", type::AddressSpace::kPrivate, Mul(1_a, Expr(kUseSource, sym)));
+            GlobalVar("v", type::AddressSpace::kPrivate, Mul(1_a, expr));
             break;
         case Use::kFunctionReturnType:
-            Func("f", utils::Empty, ty(kUseSource, sym), Return(Call(sym)));
+            Func("f", utils::Empty, ty(expr), Return(Call(sym)));
             break;
         case Use::kMemberType:
-            Structure("s", utils::Vector{Member("m", ty(kUseSource, sym))});
+            Structure("s", utils::Vector{Member("m", ty(expr))});
             break;
         case Use::kTexelFormat:
-            GlobalVar("v", ty("texture_storage_2d", sym, "write"), Group(0_u), Binding(0_u));
+            GlobalVar("v", ty("texture_storage_2d", ty(expr), "write"), Group(0_u), Binding(0_u));
             break;
         case Use::kValueExpression:
-            GlobalVar("v", type::AddressSpace::kPrivate, Expr(kUseSource, sym));
+            GlobalVar("v", type::AddressSpace::kPrivate, expr);
             break;
         case Use::kVariableType:
-            GlobalVar("v", type::AddressSpace::kPrivate, ty(kUseSource, sym));
+            GlobalVar("v", type::AddressSpace::kPrivate, ty(expr));
             break;
         case Use::kUnaryOp:
-            GlobalVar("v", type::AddressSpace::kPrivate, Negation(Expr(kUseSource, sym)));
+            GlobalVar("v", type::AddressSpace::kPrivate, Negation(expr));
             break;
     }
 
     if (GetParam().error == kPass) {
         EXPECT_TRUE(r()->Resolve());
         EXPECT_EQ(r()->error(), "");
+        check_expr(Sem().Get(expr));
     } else {
         EXPECT_FALSE(r()->Resolve());
         EXPECT_EQ(r()->error(), GetParam().error);
@@ -202,19 +268,21 @@
     ResolverExpressionKindTest,
     testing::ValuesIn(std::vector<Case>{
         {Def::kAccess, Use::kAccess, kPass},
-        {Def::kAccess, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kAccess, Use::kAddressSpace,
+         R"(5:6 error: cannot use access 'write' as address space)"},
         {Def::kAccess, Use::kBinaryOp, R"(5:6 error: cannot use access 'write' as value)"},
         {Def::kAccess, Use::kCallExpr, R"(5:6 error: cannot use access 'write' as call target)"},
         {Def::kAccess, Use::kCallStmt, R"(5:6 error: cannot use access 'write' as call target)"},
         {Def::kAccess, Use::kFunctionReturnType, R"(5:6 error: cannot use access 'write' as type)"},
         {Def::kAccess, Use::kMemberType, R"(5:6 error: cannot use access 'write' as type)"},
-        {Def::kAccess, Use::kTexelFormat, R"(error: cannot use access 'write' as texel format)"},
+        {Def::kAccess, Use::kTexelFormat,
+         R"(5:6 error: cannot use access 'write' as texel format)"},
         {Def::kAccess, Use::kValueExpression, R"(5:6 error: cannot use access 'write' as value)"},
         {Def::kAccess, Use::kVariableType, R"(5:6 error: cannot use access 'write' as type)"},
         {Def::kAccess, Use::kUnaryOp, R"(5:6 error: cannot use access 'write' as value)"},
 
         {Def::kAddressSpace, Use::kAccess,
-         R"(error: cannot use address space 'workgroup' as access)"},
+         R"(5:6 error: cannot use address space 'workgroup' as access)"},
         {Def::kAddressSpace, Use::kAddressSpace, kPass},
         {Def::kAddressSpace, Use::kBinaryOp,
          R"(5:6 error: cannot use address space 'workgroup' as value)"},
@@ -227,7 +295,7 @@
         {Def::kAddressSpace, Use::kMemberType,
          R"(5:6 error: cannot use address space 'workgroup' as type)"},
         {Def::kAddressSpace, Use::kTexelFormat,
-         R"(error: cannot use address space 'workgroup' as texel format)"},
+         R"(5:6 error: cannot use address space 'workgroup' as texel format)"},
         {Def::kAddressSpace, Use::kValueExpression,
          R"(5:6 error: cannot use address space 'workgroup' as value)"},
         {Def::kAddressSpace, Use::kVariableType,
@@ -235,26 +303,29 @@
         {Def::kAddressSpace, Use::kUnaryOp,
          R"(5:6 error: cannot use address space 'workgroup' as value)"},
 
-        {Def::kBuiltinFunction, Use::kAccess, R"(error: missing '(' for builtin function call)"},
-        {Def::kBuiltinFunction, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kBuiltinFunction, Use::kAccess,
+         R"(7:8 error: missing '(' for builtin function call)"},
+        {Def::kBuiltinFunction, Use::kAddressSpace,
+         R"(7:8 error: missing '(' for builtin function call)"},
         {Def::kBuiltinFunction, Use::kBinaryOp,
          R"(7:8 error: missing '(' for builtin function call)"},
         {Def::kBuiltinFunction, Use::kCallStmt, kPass},
         {Def::kBuiltinFunction, Use::kFunctionReturnType,
-         R"(5:6 error: cannot use builtin function 'workgroupBarrier' as type)"},
+         R"(7:8 error: missing '(' for builtin function call)"},
         {Def::kBuiltinFunction, Use::kMemberType,
-         R"(5:6 error: cannot use builtin function 'workgroupBarrier' as type)"},
+         R"(7:8 error: missing '(' for builtin function call)"},
         {Def::kBuiltinFunction, Use::kTexelFormat,
-         R"(error: missing '(' for builtin function call)"},
+         R"(7:8 error: missing '(' for builtin function call)"},
         {Def::kBuiltinFunction, Use::kValueExpression,
          R"(7:8 error: missing '(' for builtin function call)"},
         {Def::kBuiltinFunction, Use::kVariableType,
-         R"(5:6 error: cannot use builtin function 'workgroupBarrier' as type)"},
+         R"(7:8 error: missing '(' for builtin function call)"},
         {Def::kBuiltinFunction, Use::kUnaryOp,
          R"(7:8 error: missing '(' for builtin function call)"},
 
-        {Def::kBuiltinType, Use::kAccess, R"(error: cannot use type 'vec4<f32>' as access)"},
-        {Def::kBuiltinType, Use::kAddressSpace, kPass},
+        {Def::kBuiltinType, Use::kAccess, R"(5:6 error: cannot use type 'vec4<f32>' as access)"},
+        {Def::kBuiltinType, Use::kAddressSpace,
+         R"(5:6 error: cannot use type 'vec4<f32>' as address space)"},
         {Def::kBuiltinType, Use::kBinaryOp,
          R"(5:6 error: cannot use type 'vec4<f32>' as value
 7:8 note: are you missing '()' for type initializer?)"},
@@ -262,7 +333,7 @@
         {Def::kBuiltinType, Use::kFunctionReturnType, kPass},
         {Def::kBuiltinType, Use::kMemberType, kPass},
         {Def::kBuiltinType, Use::kTexelFormat,
-         R"(error: cannot use type 'vec4<f32>' as texel format)"},
+         R"(5:6 error: cannot use type 'vec4<f32>' as texel format)"},
         {Def::kBuiltinType, Use::kValueExpression,
          R"(5:6 error: cannot use type 'vec4<f32>' as value
 7:8 note: are you missing '()' for type initializer?)"},
@@ -271,9 +342,13 @@
          R"(5:6 error: cannot use type 'vec4<f32>' as value
 7:8 note: are you missing '()' for type initializer?)"},
 
-        {Def::kFunction, Use::kAccess, R"(error: missing '(' for function call)"},
-        {Def::kFunction, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
-        {Def::kFunction, Use::kBinaryOp, R"(7:8 error: missing '(' for function call)"},
+        {Def::kFunction, Use::kAccess, R"(5:6 error: cannot use function 'FUNCTION' as access
+1:2 note: function 'FUNCTION' declared here)"},
+        {Def::kFunction, Use::kAddressSpace,
+         R"(5:6 error: cannot use function 'FUNCTION' as address space
+1:2 note: function 'FUNCTION' declared here)"},
+        {Def::kFunction, Use::kBinaryOp, R"(5:6 error: cannot use function 'FUNCTION' as value
+1:2 note: function 'FUNCTION' declared here)"},
         {Def::kFunction, Use::kCallExpr, kPass},
         {Def::kFunction, Use::kCallStmt, kPass},
         {Def::kFunction, Use::kFunctionReturnType,
@@ -282,21 +357,27 @@
         {Def::kFunction, Use::kMemberType,
          R"(5:6 error: cannot use function 'FUNCTION' as type
 1:2 note: function 'FUNCTION' declared here)"},
-        {Def::kFunction, Use::kTexelFormat, R"(error: missing '(' for function call)"},
-        {Def::kFunction, Use::kValueExpression, R"(7:8 error: missing '(' for function call)"},
+        {Def::kFunction, Use::kTexelFormat,
+         R"(5:6 error: cannot use function 'FUNCTION' as texel format
+1:2 note: function 'FUNCTION' declared here)"},
+        {Def::kFunction, Use::kValueExpression,
+         R"(5:6 error: cannot use function 'FUNCTION' as value
+1:2 note: function 'FUNCTION' declared here)"},
         {Def::kFunction, Use::kVariableType,
          R"(5:6 error: cannot use function 'FUNCTION' as type
 1:2 note: function 'FUNCTION' declared here)"},
-        {Def::kFunction, Use::kUnaryOp, R"(7:8 error: missing '(' for function call)"},
+        {Def::kFunction, Use::kUnaryOp, R"(5:6 error: cannot use function 'FUNCTION' as value
+1:2 note: function 'FUNCTION' declared here)"},
 
-        {Def::kStruct, Use::kAccess, R"(error: cannot use type 'STRUCT' as access)"},
-        {Def::kStruct, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kStruct, Use::kAccess, R"(5:6 error: cannot use type 'STRUCT' as access)"},
+        {Def::kStruct, Use::kAddressSpace,
+         R"(5:6 error: cannot use type 'STRUCT' as address space)"},
         {Def::kStruct, Use::kBinaryOp, R"(5:6 error: cannot use type 'STRUCT' as value
 7:8 note: are you missing '()' for type initializer?
 1:2 note: struct 'STRUCT' declared here)"},
         {Def::kStruct, Use::kFunctionReturnType, kPass},
         {Def::kStruct, Use::kMemberType, kPass},
-        {Def::kStruct, Use::kTexelFormat, R"(error: cannot use type 'STRUCT' as texel format)"},
+        {Def::kStruct, Use::kTexelFormat, R"(5:6 error: cannot use type 'STRUCT' as texel format)"},
         {Def::kStruct, Use::kValueExpression,
          R"(5:6 error: cannot use type 'STRUCT' as value
 7:8 note: are you missing '()' for type initializer?
@@ -308,8 +389,9 @@
 1:2 note: struct 'STRUCT' declared here)"},
 
         {Def::kTexelFormat, Use::kAccess,
-         R"(error: cannot use texel format 'rgba8unorm' as access)"},
-        {Def::kTexelFormat, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
+         R"(5:6 error: cannot use texel format 'rgba8unorm' as access)"},
+        {Def::kTexelFormat, Use::kAddressSpace,
+         R"(5:6 error: cannot use texel format 'rgba8unorm' as address space)"},
         {Def::kTexelFormat, Use::kBinaryOp,
          R"(5:6 error: cannot use texel format 'rgba8unorm' as value)"},
         {Def::kTexelFormat, Use::kCallExpr,
@@ -328,15 +410,16 @@
         {Def::kTexelFormat, Use::kUnaryOp,
          R"(5:6 error: cannot use texel format 'rgba8unorm' as value)"},
 
-        {Def::kTypeAlias, Use::kAccess, R"(error: cannot use type 'i32' as access)"},
-        {Def::kTypeAlias, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kTypeAlias, Use::kAccess, R"(5:6 error: cannot use type 'i32' as access)"},
+        {Def::kTypeAlias, Use::kAddressSpace,
+         R"(5:6 error: cannot use type 'i32' as address space)"},
         {Def::kTypeAlias, Use::kBinaryOp,
          R"(5:6 error: cannot use type 'i32' as value
 7:8 note: are you missing '()' for type initializer?)"},
         {Def::kTypeAlias, Use::kCallExpr, kPass},
         {Def::kTypeAlias, Use::kFunctionReturnType, kPass},
         {Def::kTypeAlias, Use::kMemberType, kPass},
-        {Def::kTypeAlias, Use::kTexelFormat, R"(error: cannot use type 'i32' as texel format)"},
+        {Def::kTypeAlias, Use::kTexelFormat, R"(5:6 error: cannot use type 'i32' as texel format)"},
         {Def::kTypeAlias, Use::kValueExpression,
          R"(5:6 error: cannot use type 'i32' as value
 7:8 note: are you missing '()' for type initializer?)"},
@@ -345,8 +428,11 @@
          R"(5:6 error: cannot use type 'i32' as value
 7:8 note: are you missing '()' for type initializer?)"},
 
-        {Def::kVariable, Use::kAccess, R"(error: cannot use 'VARIABLE' of type 'i32' as access)"},
-        {Def::kVariable, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kVariable, Use::kAccess, R"(5:6 error: cannot use const 'VARIABLE' as access
+1:2 note: const 'VARIABLE' declared here)"},
+        {Def::kVariable, Use::kAddressSpace,
+         R"(5:6 error: cannot use const 'VARIABLE' as address space
+1:2 note: const 'VARIABLE' declared here)"},
         {Def::kVariable, Use::kBinaryOp, kPass},
         {Def::kVariable, Use::kCallStmt,
          R"(5:6 error: cannot use const 'VARIABLE' as call target
@@ -361,7 +447,8 @@
          R"(5:6 error: cannot use const 'VARIABLE' as type
 1:2 note: const 'VARIABLE' declared here)"},
         {Def::kVariable, Use::kTexelFormat,
-         R"(error: cannot use 'VARIABLE' of type 'i32' as texel format)"},
+         R"(5:6 error: cannot use const 'VARIABLE' as texel format
+1:2 note: const 'VARIABLE' declared here)"},
         {Def::kVariable, Use::kValueExpression, kPass},
         {Def::kVariable, Use::kVariableType,
          R"(5:6 error: cannot use const 'VARIABLE' as type
diff --git a/src/tint/resolver/f16_extension_test.cc b/src/tint/resolver/f16_extension_test.cc
index 1d327e5..f0d515f 100644
--- a/src/tint/resolver/f16_extension_test.cc
+++ b/src/tint/resolver/f16_extension_test.cc
@@ -65,7 +65,7 @@
     // var<private> v = vec2<f16>();
     Enable(ast::Extension::kF16);
 
-    GlobalVar("v", Call(ty.vec2<f16>()), type::AddressSpace::kPrivate);
+    GlobalVar("v", vec2<f16>(), type::AddressSpace::kPrivate);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -83,15 +83,14 @@
     // var<private> v = vec2<f16>(vec2<f32>());
     Enable(ast::Extension::kF16);
 
-    GlobalVar("v", Call(ty.vec2<f16>(), Call(ty.vec2<f32>())), type::AddressSpace::kPrivate);
+    GlobalVar("v", vec2<f16>(vec2<f32>()), type::AddressSpace::kPrivate);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(ResolverF16ExtensionTest, Vec2TypeConvUsedWithoutExtension) {
     // var<private> v = vec2<f16>(vec2<f32>());
-    GlobalVar("v", Call(ty.vec2(ty.f16(Source{{12, 34}})), Call(ty.vec2<f32>())),
-              type::AddressSpace::kPrivate);
+    GlobalVar("v", vec2(ty.f16(Source{{12, 34}}), vec2<f32>()), type::AddressSpace::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: f16 type used without 'f16' extension enabled");
diff --git a/src/tint/resolver/function_validation_test.cc b/src/tint/resolver/function_validation_test.cc
index 65aff49..511d379 100644
--- a/src/tint/resolver/function_validation_test.cc
+++ b/src/tint/resolver/function_validation_test.cc
@@ -936,7 +936,7 @@
 }
 
 TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_NonPlain) {
-    auto* ret_type = ty.pointer(Source{{12, 34}}, ty.i32(), type::AddressSpace::kFunction);
+    auto ret_type = ty.pointer(Source{{12, 34}}, ty.i32(), type::AddressSpace::kFunction);
     Func("f", utils::Empty, ret_type, utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
@@ -944,7 +944,7 @@
 }
 
 TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_AtomicInt) {
-    auto* ret_type = ty.atomic(Source{{12, 34}}, ty.i32());
+    auto ret_type = ty.atomic(Source{{12, 34}}, ty.i32());
     Func("f", utils::Empty, ret_type, utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
@@ -952,7 +952,7 @@
 }
 
 TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_ArrayOfAtomic) {
-    auto* ret_type = ty.array(Source{{12, 34}}, ty.atomic(ty.i32()), 10_u);
+    auto ret_type = ty.array(Source{{12, 34}}, ty.atomic(ty.i32()), 10_u);
     Func("f", utils::Empty, ret_type, utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
@@ -963,7 +963,7 @@
     Structure("S", utils::Vector{
                        Member("m", ty.atomic(ty.i32())),
                    });
-    auto* ret_type = ty(Source{{12, 34}}, "S");
+    auto ret_type = ty(Source{{12, 34}}, "S");
     Func("f", utils::Empty, ret_type, utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
@@ -971,7 +971,7 @@
 }
 
 TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_RuntimeArray) {
-    auto* ret_type = ty.array(Source{{12, 34}}, ty.i32());
+    auto ret_type = ty.array(Source{{12, 34}}, ty.i32());
     Func("f", utils::Empty, ret_type, utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
@@ -982,7 +982,7 @@
     Structure("S", utils::Vector{
                        Member("m", ty.atomic(ty.i32())),
                    });
-    auto* ret_type = ty(Source{{12, 34}}, "S");
+    auto ret_type = ty(Source{{12, 34}}, "S");
     auto* bar = Param("bar", ret_type);
     Func("f", utils::Vector{bar}, ty.void_(), utils::Empty);
 
@@ -994,7 +994,7 @@
     Structure("S", utils::Vector{
                        Member("m", ty.i32()),
                    });
-    auto* ret_type = ty(Source{{12, 34}}, "S");
+    auto ret_type = ty(Source{{12, 34}}, "S");
     auto* bar = Param(Source{{12, 34}}, "bar", ret_type);
     Func("f", utils::Vector{bar}, ty.void_(), utils::Empty);
 
@@ -1025,29 +1025,28 @@
 TEST_F(ResolverFunctionValidationTest, ParameterVectorNoType) {
     // fn f(p : vec3) {}
 
-    Func(Source{{12, 34}}, "f",
-         utils::Vector{Param("p", create<ast::Vector>(Source{{12, 34}}, nullptr, 3u))}, ty.void_(),
-         utils::Empty);
-
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
-}
-
-TEST_F(ResolverFunctionValidationTest, ParameterMatrixNoType) {
-    // fn f(p : vec3) {}
-
-    Func(Source{{12, 34}}, "f",
-         utils::Vector{Param("p", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3u, 3u))},
+    Func(Source{{12, 34}}, "f", utils::Vector{Param("p", ty.vec3<Infer>(Source{{12, 34}}))},
          ty.void_(), utils::Empty);
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
+    EXPECT_EQ(r()->error(), "12:34 error: expected '<' for 'vec3'");
+}
+
+TEST_F(ResolverFunctionValidationTest, ParameterMatrixNoType) {
+    // fn f(p : mat3x3) {}
+
+    Func(Source{{12, 34}}, "f", utils::Vector{Param("p", ty.mat3x3<Infer>(Source{{12, 34}}))},
+         ty.void_(), utils::Empty);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "12:34 error: expected '<' for 'mat3x3'");
 }
 
 enum class Expectation {
     kAlwaysPass,
     kPassWithFullPtrParameterExtension,
     kAlwaysFail,
+    kInvalid,
 };
 struct TestParams {
     type::AddressSpace address_space;
@@ -1059,7 +1058,7 @@
 using ResolverFunctionParameterValidationTest = TestWithParams;
 TEST_P(ResolverFunctionParameterValidationTest, AddressSpaceNoExtension) {
     auto& param = GetParam();
-    auto* ptr_type = ty.pointer(Source{{12, 34}}, ty.i32(), param.address_space);
+    auto ptr_type = ty("ptr", Ident(Source{{12, 34}}, param.address_space), ty.i32());
     auto* arg = Param(Source{{12, 34}}, "p", ptr_type);
     Func("f", utils::Vector{arg}, ty.void_(), utils::Empty);
 
@@ -1069,37 +1068,48 @@
         std::stringstream ss;
         ss << param.address_space;
         EXPECT_FALSE(r()->Resolve());
-        EXPECT_EQ(r()->error(), "12:34 error: function parameter of pointer type cannot be in '" +
-                                    ss.str() + "' address space");
+        if (param.expectation == Expectation::kInvalid) {
+            EXPECT_EQ(r()->error(), "12:34 error: unknown identifier: '" + ss.str() + "'");
+        } else {
+            EXPECT_EQ(r()->error(),
+                      "12:34 error: function parameter of pointer type cannot be in '" + ss.str() +
+                          "' address space");
+        }
     }
 }
 TEST_P(ResolverFunctionParameterValidationTest, AddressSpaceWithExtension) {
     auto& param = GetParam();
-    auto* ptr_type = ty.pointer(Source{{12, 34}}, ty.i32(), param.address_space);
+    auto ptr_type = ty("ptr", Ident(Source{{12, 34}}, param.address_space), ty.i32());
     auto* arg = Param(Source{{12, 34}}, "p", ptr_type);
     Enable(ast::Extension::kChromiumExperimentalFullPtrParameters);
     Func("f", utils::Vector{arg}, ty.void_(), utils::Empty);
 
-    if (param.expectation != Expectation::kAlwaysFail) {
+    if (param.expectation == Expectation::kAlwaysPass ||
+        param.expectation == Expectation::kPassWithFullPtrParameterExtension) {
         ASSERT_TRUE(r()->Resolve()) << r()->error();
     } else {
         std::stringstream ss;
         ss << param.address_space;
         EXPECT_FALSE(r()->Resolve());
-        EXPECT_EQ(r()->error(), "12:34 error: function parameter of pointer type cannot be in '" +
-                                    ss.str() + "' address space");
+        if (param.expectation == Expectation::kInvalid) {
+            EXPECT_EQ(r()->error(), "12:34 error: unknown identifier: '" + ss.str() + "'");
+        } else {
+            EXPECT_EQ(r()->error(),
+                      "12:34 error: function parameter of pointer type cannot be in '" + ss.str() +
+                          "' address space");
+        }
     }
 }
 INSTANTIATE_TEST_SUITE_P(
     ResolverTest,
     ResolverFunctionParameterValidationTest,
     testing::Values(
-        TestParams{type::AddressSpace::kNone, Expectation::kAlwaysFail},
-        TestParams{type::AddressSpace::kIn, Expectation::kAlwaysFail},
-        TestParams{type::AddressSpace::kOut, Expectation::kAlwaysFail},
+        TestParams{type::AddressSpace::kNone, Expectation::kInvalid},
+        TestParams{type::AddressSpace::kIn, Expectation::kInvalid},
+        TestParams{type::AddressSpace::kOut, Expectation::kInvalid},
         TestParams{type::AddressSpace::kUniform, Expectation::kPassWithFullPtrParameterExtension},
         TestParams{type::AddressSpace::kWorkgroup, Expectation::kPassWithFullPtrParameterExtension},
-        TestParams{type::AddressSpace::kHandle, Expectation::kAlwaysFail},
+        TestParams{type::AddressSpace::kHandle, Expectation::kInvalid},
         TestParams{type::AddressSpace::kStorage, Expectation::kPassWithFullPtrParameterExtension},
         TestParams{type::AddressSpace::kPrivate, Expectation::kAlwaysPass},
         TestParams{type::AddressSpace::kFunction, Expectation::kAlwaysPass}));
diff --git a/src/tint/resolver/inferred_type_test.cc b/src/tint/resolver/inferred_type_test.cc
index eba38ff..8a609d2 100644
--- a/src/tint/resolver/inferred_type_test.cc
+++ b/src/tint/resolver/inferred_type_test.cc
@@ -134,7 +134,7 @@
 INSTANTIATE_TEST_SUITE_P(ResolverTest, ResolverInferredTypeParamTest, testing::ValuesIn(all_cases));
 
 TEST_F(ResolverInferredTypeTest, InferArray_Pass) {
-    auto* type = ty.array<u32, 10>();
+    auto type = ty.array<u32, 10>();
     auto* expected_type = create<type::Array>(
         create<type::U32>(), create<type::ConstantArrayCount>(10u), 4u, 4u * 10u, 4u, 4u);
 
diff --git a/src/tint/resolver/load_test.cc b/src/tint/resolver/load_test.cc
index 7a74224..2334227 100644
--- a/src/tint/resolver/load_test.cc
+++ b/src/tint/resolver/load_test.cc
@@ -154,7 +154,7 @@
     // var ref = vec4(1);
     // var v = ref.xyz;
     auto* ident = Expr("ref");
-    WrapInFunction(Var("ref", Call(ty.vec4<i32>(), 1_i)),  //
+    WrapInFunction(Var("ref", vec4<i32>(1_i)),  //
                    Var("v", MemberAccessor(ident, "xyz")));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -230,7 +230,7 @@
          },
          ty.vec4<f32>(),
          utils::Vector{
-             Return(Call("textureSampleLevel", "tp", "sp", Call(ty.vec2<f32>()), 0_a)),
+             Return(Call("textureSampleLevel", "tp", "sp", vec2<f32>(), 0_a)),
          });
     auto* t_ident = Expr("t");
     auto* s_ident = Expr("s");
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index 366fbc3..acd69a8 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -350,7 +350,7 @@
             break;
         case Method::kStruct:
             Structure("S", utils::Vector{Member("v", target_ty())});
-            WrapInFunction(Call(ty("S"), abstract_expr));
+            WrapInFunction(Call("S", abstract_expr));
             break;
         case Method::kBinaryOp: {
             // Add 0 to ensure no overflow with max float values
@@ -1262,7 +1262,7 @@
 
 TEST_F(MaterializeAbstractStructure, Modf_Vector_DefaultType) {
     // var v = modf(vec2(1));
-    auto* call = Call("modf", Call(ty.vec2(nullptr), 1_a));
+    auto* call = Call("modf", Call(ty.vec2<Infer>(), 1_a));
     WrapInFunction(Decl(Var("v", call)));
     ASSERT_TRUE(r()->Resolve()) << r()->error();
     auto* sem = Sem().Get(call);
@@ -1302,8 +1302,8 @@
     // var v = modf(vec2(1_h)); // v is __modf_result_vec2_f16
     // v = modf(vec2(1));       // __modf_result_vec2_f16 <- __modf_result_vec2_abstract
     Enable(ast::Extension::kF16);
-    auto* call = Call("modf", Call(ty.vec2(nullptr), 1_a));
-    WrapInFunction(Decl(Var("v", Call("modf", Call(ty.vec2(nullptr), 1_h)))), Assign("v", call));
+    auto* call = Call("modf", Call(ty.vec2<Infer>(), 1_a));
+    WrapInFunction(Decl(Var("v", Call("modf", Call(ty.vec2<Infer>(), 1_h)))), Assign("v", call));
     ASSERT_TRUE(r()->Resolve()) << r()->error();
     auto* sem = Sem().Get(call);
     ASSERT_TRUE(sem->Is<sem::Materialize>());
@@ -1339,7 +1339,7 @@
 
 TEST_F(MaterializeAbstractStructure, Frexp_Vector_DefaultType) {
     // var v = frexp(vec2(1));
-    auto* call = Call("frexp", Call(ty.vec2(nullptr), 1_a));
+    auto* call = Call("frexp", Call(ty.vec2<Infer>(), 1_a));
     WrapInFunction(Decl(Var("v", call)));
     ASSERT_TRUE(r()->Resolve()) << r()->error();
     auto* sem = Sem().Get(call);
@@ -1385,8 +1385,8 @@
     // var v = frexp(vec2(1_h)); // v is __frexp_result_vec2_f16
     // v = frexp(vec2(1));       // __frexp_result_vec2_f16 <- __frexp_result_vec2_abstract
     Enable(ast::Extension::kF16);
-    auto* call = Call("frexp", Call(ty.vec2(nullptr), 1_a));
-    WrapInFunction(Decl(Var("v", Call("frexp", Call(ty.vec2(nullptr), 1_h)))), Assign("v", call));
+    auto* call = Call("frexp", Call(ty.vec2<Infer>(), 1_a));
+    WrapInFunction(Decl(Var("v", Call("frexp", Call(ty.vec2<Infer>(), 1_h)))), Assign("v", call));
     ASSERT_TRUE(r()->Resolve()) << r()->error();
     auto* sem = Sem().Get(call);
     ASSERT_TRUE(sem->Is<sem::Materialize>());
diff --git a/src/tint/resolver/override_test.cc b/src/tint/resolver/override_test.cc
index 813d4de..2fd48ed 100644
--- a/src/tint/resolver/override_test.cc
+++ b/src/tint/resolver/override_test.cc
@@ -208,8 +208,8 @@
 TEST_F(ResolverOverrideTest, TransitiveReferences_ViaArraySize) {
     auto* a = Override("a", ty.i32());
     auto* b = Override("b", ty.i32(), Mul(2_a, "a"));
-    auto* arr_ty = ty.array(ty.i32(), Mul(2_a, "b"));
-    auto* arr = GlobalVar("arr", type::AddressSpace::kWorkgroup, arr_ty);
+    auto* arr = GlobalVar("arr", type::AddressSpace::kWorkgroup, ty.array(ty.i32(), Mul(2_a, "b")));
+    auto arr_ty = arr->type;
     Override("unused", ty.i32(), Expr(1_a));
     auto* func = Func("foo", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -219,7 +219,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     {
-        auto* r = Sem().TransitivelyReferencedOverrides(Sem().Get(arr_ty));
+        auto* r = Sem().TransitivelyReferencedOverrides(TypeOf(arr_ty));
         ASSERT_NE(r, nullptr);
         auto& refs = *r;
         ASSERT_EQ(refs.Length(), 2u);
@@ -248,8 +248,9 @@
 TEST_F(ResolverOverrideTest, TransitiveReferences_ViaArraySize_Alias) {
     auto* a = Override("a", ty.i32());
     auto* b = Override("b", ty.i32(), Mul(2_a, "a"));
-    auto* arr_ty = Alias("arr_ty", ty.array(ty.i32(), Mul(2_a, "b")));
+    Alias("arr_ty", ty.array(ty.i32(), Mul(2_a, "b")));
     auto* arr = GlobalVar("arr", type::AddressSpace::kWorkgroup, ty("arr_ty"));
+    auto arr_ty = arr->type;
     Override("unused", ty.i32(), Expr(1_a));
     auto* func = Func("foo", utils::Empty, ty.void_(),
                       utils::Vector{
@@ -259,7 +260,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     {
-        auto* r = Sem().TransitivelyReferencedOverrides(Sem().Get<type::Array>(arr_ty->type));
+        auto* r = Sem().TransitivelyReferencedOverrides(TypeOf(arr_ty));
         ASSERT_NE(r, nullptr);
         auto& refs = *r;
         ASSERT_EQ(refs.Length(), 2u);
diff --git a/src/tint/resolver/ptr_ref_test.cc b/src/tint/resolver/ptr_ref_test.cc
index 64b21ae..692bd3b 100644
--- a/src/tint/resolver/ptr_ref_test.cc
+++ b/src/tint/resolver/ptr_ref_test.cc
@@ -88,7 +88,7 @@
 
     WrapInFunction(function, function_ptr, private_ptr, workgroup_ptr, uniform_ptr, storage_ptr);
 
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
 
     ASSERT_TRUE(TypeOf(function_ptr)->Is<type::Pointer>())
         << "function_ptr is " << TypeOf(function_ptr)->TypeInfo().name;
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index ab1970a..352c5e4 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -21,7 +21,6 @@
 #include <utility>
 
 #include "src/tint/ast/alias.h"
-#include "src/tint/ast/array.h"
 #include "src/tint/ast/assignment_statement.h"
 #include "src/tint/ast/attribute.h"
 #include "src/tint/ast/bitcast_expression.h"
@@ -36,16 +35,11 @@
 #include "src/tint/ast/internal_attribute.h"
 #include "src/tint/ast/interpolate_attribute.h"
 #include "src/tint/ast/loop_statement.h"
-#include "src/tint/ast/matrix.h"
-#include "src/tint/ast/pointer.h"
 #include "src/tint/ast/return_statement.h"
-#include "src/tint/ast/sampled_texture.h"
 #include "src/tint/ast/switch_statement.h"
 #include "src/tint/ast/traverse_expressions.h"
-#include "src/tint/ast/type_name.h"
 #include "src/tint/ast/unary_op_expression.h"
 #include "src/tint/ast/variable_decl_statement.h"
-#include "src/tint/ast/vector.h"
 #include "src/tint/ast/while_statement.h"
 #include "src/tint/ast/workgroup_attribute.h"
 #include "src/tint/resolver/uniformity.h"
@@ -54,6 +48,7 @@
 #include "src/tint/sem/call.h"
 #include "src/tint/sem/for_loop_statement.h"
 #include "src/tint/sem/function.h"
+#include "src/tint/sem/function_expression.h"
 #include "src/tint/sem/if_statement.h"
 #include "src/tint/sem/index_accessor_expression.h"
 #include "src/tint/sem/load.h"
@@ -217,138 +212,6 @@
     return result;
 }
 
-type::Type* Resolver::Type(const ast::Type* ty) {
-    if (ty == nullptr) {
-        return builder_->create<type::Void>();
-    }
-
-    Mark(ty);
-    auto* s = Switch(
-        ty,  //
-        [&](const ast::Vector* t) -> type::Vector* {
-            if (!t->type) {
-                AddError("missing vector element type", t->source.End());
-                return nullptr;
-            }
-            if (auto* el = Type(t->type)) {
-                if (auto* vector = builder_->create<type::Vector>(el, t->width)) {
-                    if (validator_.Vector(vector, t->source)) {
-                        return vector;
-                    }
-                }
-            }
-            return nullptr;
-        },
-        [&](const ast::Matrix* t) -> type::Matrix* {
-            if (!t->type) {
-                AddError("missing matrix element type", t->source.End());
-                return nullptr;
-            }
-            if (auto* el = Type(t->type)) {
-                if (auto* column_type = builder_->create<type::Vector>(el, t->rows)) {
-                    if (auto* matrix = builder_->create<type::Matrix>(column_type, t->columns)) {
-                        if (validator_.Matrix(matrix, t->source)) {
-                            return matrix;
-                        }
-                    }
-                }
-            }
-            return nullptr;
-        },
-        [&](const ast::Array* t) { return Array(t); },
-        [&](const ast::Atomic* t) -> type::Atomic* {
-            if (auto* el = Type(t->type)) {
-                auto* a = builder_->create<type::Atomic>(el);
-                if (!validator_.Atomic(t, a)) {
-                    return nullptr;
-                }
-                return a;
-            }
-            return nullptr;
-        },
-        [&](const ast::Pointer* t) -> type::Pointer* {
-            if (auto* el = Type(t->type)) {
-                auto access = t->access;
-                if (access == type::Access::kUndefined) {
-                    access = DefaultAccessForAddressSpace(t->address_space);
-                }
-                auto ptr = builder_->create<type::Pointer>(el, t->address_space, access);
-                if (!ptr) {
-                    return nullptr;
-                }
-                if (!validator_.Pointer(t, ptr)) {
-                    return nullptr;
-                }
-                if (!ApplyAddressSpaceUsageToType(t->address_space, el, t->type->source)) {
-                    AddNote("while instantiating " + builder_->FriendlyName(ptr), t->source);
-                    return nullptr;
-                }
-                return ptr;
-            }
-            return nullptr;
-        },
-        [&](const ast::SampledTexture* t) -> type::SampledTexture* {
-            if (auto* el = Type(t->type)) {
-                auto* sem = builder_->create<type::SampledTexture>(t->dim, el);
-                if (!validator_.SampledTexture(sem, t->source)) {
-                    return nullptr;
-                }
-                return sem;
-            }
-            return nullptr;
-        },
-        [&](const ast::MultisampledTexture* t) -> type::MultisampledTexture* {
-            if (auto* el = Type(t->type)) {
-                auto* sem = builder_->create<type::MultisampledTexture>(t->dim, el);
-                if (!validator_.MultisampledTexture(sem, t->source)) {
-                    return nullptr;
-                }
-                return sem;
-            }
-            return nullptr;
-        },
-        [&](const ast::TypeName* t) -> type::Type* {
-            Mark(t->name);
-
-            auto resolved = dependencies_.resolved_identifiers.Get(t->name);
-            if (!resolved) {
-                TINT_ICE(Resolver, diagnostics_)
-                    << "identifier '" << builder_->Symbols().NameFor(t->name->symbol)
-                    << "' was not resolved";
-                return nullptr;
-            }
-
-            if (auto* ast_node = resolved->Node()) {
-                auto* type = sem_.Get<type::Type>(ast_node);
-                if (TINT_UNLIKELY(!type)) {
-                    ErrorMismatchedResolvedIdentifier(t->source, *resolved, "type");
-                    return nullptr;
-                }
-
-                if (TINT_UNLIKELY(t->name->Is<ast::TemplatedIdentifier>())) {
-                    AddError("type '" + builder_->Symbols().NameFor(t->name->symbol) +
-                                 "' does not take template arguments",
-                             t->source);
-                    NoteDeclarationSource(ast_node);
-                    return nullptr;
-                }
-
-                return type;
-            }
-            if (auto b = resolved->BuiltinType(); b != type::Builtin::kUndefined) {
-                return BuiltinType(b, t->name);
-            }
-
-            ErrorMismatchedResolvedIdentifier(t->source, *resolved, "type");
-            return nullptr;
-        });
-
-    if (s) {
-        builder_->Sem().Add(ty, s);
-    }
-    return s;
-}
-
 sem::Variable* Resolver::Variable(const ast::Variable* v, bool is_global) {
     Mark(v->name);
 
@@ -576,7 +439,7 @@
     const type::Type* storage_ty = nullptr;
 
     // If the variable has a declared type, resolve it.
-    if (auto* ty = var->type) {
+    if (auto ty = var->type) {
         storage_ty = Type(ty);
         if (!storage_ty) {
             return nullptr;
@@ -1032,7 +895,7 @@
 
     // Resolve the return type
     type::Type* return_type = nullptr;
-    if (auto* ty = decl->return_type) {
+    if (auto ty = decl->return_type) {
         return_type = Type(ty);
         if (!return_type) {
             return nullptr;
@@ -1601,7 +1464,37 @@
 }
 
 sem::ValueExpression* Resolver::ValueExpression(const ast::Expression* expr) {
-    return sem_.AsValue(Expression(expr));
+    return sem_.AsValueExpression(Expression(expr));
+}
+
+sem::TypeExpression* Resolver::TypeExpression(const ast::Expression* expr) {
+    return sem_.AsTypeExpression(Expression(expr));
+}
+
+sem::FunctionExpression* Resolver::FunctionExpression(const ast::Expression* expr) {
+    return sem_.AsFunctionExpression(Expression(expr));
+}
+
+type::Type* Resolver::Type(const ast::Expression* ast) {
+    auto* type_expr = TypeExpression(ast);
+    if (!type_expr) {
+        return nullptr;
+    }
+    return const_cast<type::Type*>(type_expr->Type());
+}
+
+sem::BuiltinEnumExpression<type::AddressSpace>* Resolver::AddressSpaceExpression(
+    const ast::Expression* expr) {
+    return sem_.AsAddressSpace(Expression(expr));
+}
+
+sem::BuiltinEnumExpression<type::TexelFormat>* Resolver::TexelFormatExpression(
+    const ast::Expression* expr) {
+    return sem_.AsTexelFormat(Expression(expr));
+}
+
+sem::BuiltinEnumExpression<type::Access>* Resolver::AccessExpression(const ast::Expression* expr) {
+    return sem_.AsAccess(Expression(expr));
 }
 
 void Resolver::RegisterStore(const sem::ValueExpression* expr) {
@@ -2002,6 +1895,11 @@
     // * A builtin call.
     // * A type initializer.
     // * A type conversion.
+    auto* target = expr->target;
+    Mark(target);
+
+    auto* ident = target->identifier;
+    Mark(ident);
 
     // Resolve all of the arguments, their types and the set of behaviors.
     utils::Vector<const sem::ValueExpression*, 8> args;
@@ -2023,9 +1921,9 @@
     bool has_side_effects =
         std::any_of(args.begin(), args.end(), [](auto* e) { return e->HasSideEffects(); });
 
-    // ct_init_or_conv is a helper for building either a sem::TypeInitializer or
+    // init_or_conv is a helper for building either a sem::TypeInitializer or
     // sem::TypeConversion call for a InitConvIntrinsic with an optional template argument type.
-    auto ct_init_or_conv = [&](InitConvIntrinsic ty, const type::Type* template_arg) -> sem::Call* {
+    auto init_or_conv = [&](InitConvIntrinsic ty, const type::Type* template_arg) -> sem::Call* {
         auto arg_tys = utils::Transform(args, [](auto* arg) { return arg->Type(); });
         auto ctor_or_conv =
             intrinsic_table_->Lookup(ty, template_arg, arg_tys, args_stage, expr->source);
@@ -2087,26 +1985,24 @@
                                            current_statement_, value, has_side_effects);
     };
 
-    // ty_init_or_conv is a helper for building either a sem::TypeInitializer or
-    // sem::TypeConversion call for the given semantic type.
-    auto ty_init_or_conv = [&](const type::Type* ty) {
+    auto ty_init_or_conv = [&](const type::Type* type) {
         return Switch(
-            ty,  //
-            [&](const type::Vector* v) {
-                return ct_init_or_conv(VectorInitConvIntrinsic(v->Width()), v->type());
-            },
-            [&](const type::Matrix* m) {
-                return ct_init_or_conv(MatrixInitConvIntrinsic(m->columns(), m->rows()), m->type());
-            },
-            [&](const type::I32*) { return ct_init_or_conv(InitConvIntrinsic::kI32, nullptr); },
-            [&](const type::U32*) { return ct_init_or_conv(InitConvIntrinsic::kU32, nullptr); },
+            type,  //
+            [&](const type::I32*) { return init_or_conv(InitConvIntrinsic::kI32, nullptr); },
+            [&](const type::U32*) { return init_or_conv(InitConvIntrinsic::kU32, nullptr); },
             [&](const type::F16*) {
                 return validator_.CheckF16Enabled(expr->source)
-                           ? ct_init_or_conv(InitConvIntrinsic::kF16, nullptr)
+                           ? init_or_conv(InitConvIntrinsic::kF16, nullptr)
                            : nullptr;
             },
-            [&](const type::F32*) { return ct_init_or_conv(InitConvIntrinsic::kF32, nullptr); },
-            [&](const type::Bool*) { return ct_init_or_conv(InitConvIntrinsic::kBool, nullptr); },
+            [&](const type::F32*) { return init_or_conv(InitConvIntrinsic::kF32, nullptr); },
+            [&](const type::Bool*) { return init_or_conv(InitConvIntrinsic::kBool, nullptr); },
+            [&](const type::Vector* v) {
+                return init_or_conv(VectorInitConvIntrinsic(v->Width()), v->type());
+            },
+            [&](const type::Matrix* m) {
+                return init_or_conv(MatrixInitConvIntrinsic(m->columns(), m->rows()), m->type());
+            },
             [&](const type::Array* arr) -> sem::Call* {
                 auto* call_target = array_inits_.GetOrCreate(
                     ArrayInitializerSig{{arr, args.Length(), args_stage}},
@@ -2169,170 +2065,117 @@
             });
     };
 
-    // ast::CallExpression has a target which is either an ast::Type or an
-    // ast::IdentifierExpression
+    auto inferred_array = [&]() -> tint::sem::Call* {
+        auto el_count =
+            builder_->create<type::ConstantArrayCount>(static_cast<uint32_t>(args.Length()));
+        auto arg_tys = utils::Transform(args, [](auto* arg) { return arg->Type()->UnwrapRef(); });
+        auto el_ty = type::Type::Common(arg_tys);
+        if (!el_ty) {
+            AddError("cannot infer common array element type from initializer arguments",
+                     expr->source);
+            utils::Hashset<const type::Type*, 8> types;
+            for (size_t i = 0; i < args.Length(); i++) {
+                if (types.Add(args[i]->Type())) {
+                    AddNote("argument " + std::to_string(i) + " is of type '" +
+                                sem_.TypeNameOf(args[i]->Type()) + "'",
+                            args[i]->Declaration()->source);
+                }
+            }
+            return nullptr;
+        }
+        auto* arr = Array(expr->source, expr->source, el_ty, el_count, /* explicit_stride */ 0);
+        if (!arr) {
+            return nullptr;
+        }
+        return ty_init_or_conv(arr);
+    };
+
     auto call = [&]() -> sem::Call* {
-        if (expr->target.type) {
-            // ast::CallExpression has an ast::Type as the target.
-            // This call is either a type initializer or type conversion.
+        auto resolved = dependencies_.resolved_identifiers.Get(ident);
+        if (!resolved) {
+            TINT_ICE(Resolver, diagnostics_)
+                << "identifier '" << builder_->Symbols().NameFor(ident->symbol)
+                << "' was not resolved";
+            return nullptr;
+        }
+
+        if (auto* ast_node = resolved->Node()) {
             return Switch(
-                expr->target.type,
-                [&](const ast::Vector* v) -> sem::Call* {
-                    Mark(v);
-                    // vector element type must be inferred if it was not specified.
-                    type::Type* template_arg = nullptr;
-                    if (v->type) {
-                        template_arg = Type(v->type);
-                        if (!template_arg) {
-                            return nullptr;
-                        }
-                    }
-                    if (auto* c =
-                            ct_init_or_conv(VectorInitConvIntrinsic(v->width), template_arg)) {
-                        builder_->Sem().Add(expr->target.type, c->Target()->ReturnType());
-                        return c;
-                    }
-                    return nullptr;
-                },
-                [&](const ast::Matrix* m) -> sem::Call* {
-                    Mark(m);
-                    // matrix element type must be inferred if it was not specified.
-                    type::Type* template_arg = nullptr;
-                    if (m->type) {
-                        template_arg = Type(m->type);
-                        if (!template_arg) {
-                            return nullptr;
-                        }
-                    }
-                    if (auto* c = ct_init_or_conv(MatrixInitConvIntrinsic(m->columns, m->rows),
-                                                  template_arg)) {
-                        builder_->Sem().Add(expr->target.type, c->Target()->ReturnType());
-                        return c;
-                    }
-                    return nullptr;
-                },
-                [&](const ast::Array* a) -> sem::Call* {
-                    Mark(a);
-                    // array element type must be inferred if it was not specified.
-                    const type::ArrayCount* el_count = nullptr;
-                    const type::Type* el_ty = nullptr;
-                    if (a->type) {
-                        el_ty = Type(a->type);
-                        if (!el_ty) {
-                            return nullptr;
-                        }
-                        if (!a->count) {
-                            AddError("cannot construct a runtime-sized array", expr->source);
-                            return nullptr;
-                        }
-                        el_count = ArrayCount(a->count);
-                        if (!el_count) {
-                            return nullptr;
-                        }
-                        // Note: validation later will detect any mismatches between explicit array
-                        // size and number of initializer expressions.
-                    } else {
-                        el_count = builder_->create<type::ConstantArrayCount>(
-                            static_cast<uint32_t>(args.Length()));
-                        auto arg_tys = utils::Transform(
-                            args, [](auto* arg) { return arg->Type()->UnwrapRef(); });
-                        el_ty = type::Type::Common(arg_tys);
-                        if (!el_ty) {
-                            AddError(
-                                "cannot infer common array element type from initializer arguments",
-                                expr->source);
-                            utils::Hashset<const type::Type*, 8> types;
-                            for (size_t i = 0; i < args.Length(); i++) {
-                                if (types.Add(args[i]->Type())) {
-                                    AddNote("argument " + std::to_string(i) + " is of type '" +
-                                                sem_.TypeNameOf(args[i]->Type()) + "'",
-                                            args[i]->Declaration()->source);
-                                }
-                            }
-                            return nullptr;
-                        }
-                    }
-                    uint32_t explicit_stride = 0;
-                    if (!ArrayAttributes(a->attributes, el_ty, explicit_stride)) {
-                        return nullptr;
-                    }
-
-                    auto* arr = Array(a->type ? a->type->source : a->source,
-                                      a->count ? a->count->source : a->source,  //
-                                      el_ty, el_count, explicit_stride);
-                    if (!arr) {
-                        return nullptr;
-                    }
-                    builder_->Sem().Add(a, arr);
-
-                    return ty_init_or_conv(arr);
-                },
-                [&](const ast::Type* ast) -> sem::Call* {
-                    // Handler for AST types that do not have an optional element type.
-                    if (auto* ty = Type(ast)) {
-                        return ty_init_or_conv(ty);
-                    }
+                sem_.Get(ast_node),  //
+                [&](type::Type* t) { return ty_init_or_conv(t); },
+                [&](sem::Function* f) { return FunctionCall(expr, f, args, arg_behaviors); },
+                [&](sem::Expression* e) {
+                    sem_.ErrorUnexpectedExprKind(e, "call target");
                     return nullptr;
                 },
                 [&](Default) {
-                    TINT_ICE(Resolver, diagnostics_)
-                        << expr->source << " unhandled CallExpression target:\n"
-                        << "type: "
-                        << (expr->target.type ? expr->target.type->TypeInfo().name : "<null>");
+                    ErrorMismatchedResolvedIdentifier(ident->source, *resolved, "call target");
                     return nullptr;
                 });
-        } else {
-            // ast::CallExpression has an ast::IdentifierExpression as the target.
-            // This call is either a function call, builtin call, type initializer or type
-            // conversion.
-            auto* ident = expr->target.name;
-            Mark(ident);
+        }
 
-            auto resolved = dependencies_.resolved_identifiers.Get(ident);
-            if (!resolved) {
-                TINT_ICE(Resolver, diagnostics_)
-                    << "identifier '" << builder_->Symbols().NameFor(ident->symbol)
-                    << "' was not resolved";
+        if (auto f = resolved->BuiltinFunction(); f != sem::BuiltinType::kNone) {
+            return BuiltinCall(expr, f, args);
+        }
+
+        if (auto b = resolved->BuiltinType(); b != type::Builtin::kUndefined) {
+            if (!ident->Is<ast::TemplatedIdentifier>()) {
+                // No template arguments provided.
+                // Check to see if this is an inferred-element-type call.
+                switch (b) {
+                    case type::Builtin::kArray:
+                        return inferred_array();
+                    case type::Builtin::kVec2:
+                        return init_or_conv(InitConvIntrinsic::kVec2, nullptr);
+                    case type::Builtin::kVec3:
+                        return init_or_conv(InitConvIntrinsic::kVec3, nullptr);
+                    case type::Builtin::kVec4:
+                        return init_or_conv(InitConvIntrinsic::kVec4, nullptr);
+                    case type::Builtin::kMat2X2:
+                        return init_or_conv(InitConvIntrinsic::kMat2x2, nullptr);
+                    case type::Builtin::kMat2X3:
+                        return init_or_conv(InitConvIntrinsic::kMat2x3, nullptr);
+                    case type::Builtin::kMat2X4:
+                        return init_or_conv(InitConvIntrinsic::kMat2x4, nullptr);
+                    case type::Builtin::kMat3X2:
+                        return init_or_conv(InitConvIntrinsic::kMat3x2, nullptr);
+                    case type::Builtin::kMat3X3:
+                        return init_or_conv(InitConvIntrinsic::kMat3x3, nullptr);
+                    case type::Builtin::kMat3X4:
+                        return init_or_conv(InitConvIntrinsic::kMat3x4, nullptr);
+                    case type::Builtin::kMat4X2:
+                        return init_or_conv(InitConvIntrinsic::kMat4x2, nullptr);
+                    case type::Builtin::kMat4X3:
+                        return init_or_conv(InitConvIntrinsic::kMat4x3, nullptr);
+                    case type::Builtin::kMat4X4:
+                        return init_or_conv(InitConvIntrinsic::kMat4x4, nullptr);
+                    default:
+                        break;
+                }
+            }
+            auto* ty = BuiltinType(b, ident);
+            if (TINT_UNLIKELY(!ty)) {
                 return nullptr;
             }
-
-            if (auto* ast_node = resolved->Node()) {
-                return Switch(
-                    sem_.Get(ast_node),  //
-                    [&](const type::Type* ty) {
-                        // A type initializer or conversions.
-                        // Note: Unlike the code path where we're resolving the call target from an
-                        // ast::Type, all types must already have the element type explicitly
-                        // specified, so there's no need to infer element types.
-                        return ty_init_or_conv(ty);
-                    },
-                    [&](sem::Function* func) {
-                        return FunctionCall(expr, func, args, arg_behaviors);
-                    },
-                    [&](Default) {
-                        ErrorMismatchedResolvedIdentifier(ident->source, *resolved, "call target");
-                        return nullptr;
-                    });
-            }
-
-            if (auto f = resolved->BuiltinFunction(); f != sem::BuiltinType::kNone) {
-                return BuiltinCall(expr, f, args);
-            }
-
-            if (auto b = resolved->BuiltinType(); b != type::Builtin::kUndefined) {
-                auto* ty = BuiltinType(b, expr->target.name);
-                return ty ? ty_init_or_conv(ty) : nullptr;
-            }
-
-            ErrorMismatchedResolvedIdentifier(ident->source, *resolved, "call target");
-            return nullptr;
+            return ty_init_or_conv(ty);
         }
+
+        ErrorMismatchedResolvedIdentifier(ident->source, *resolved, "call target");
+        return nullptr;
     }();
 
     if (!call) {
         return nullptr;
     }
 
+    if (call->Target()->IsAnyOf<sem::TypeInitializer, sem::TypeConversion>()) {
+        // The target of the call was a type.
+        // Associate the target identifier expression with the resolved type.
+        auto* ty_expr =
+            builder_->create<sem::TypeExpression>(target, current_statement_, call->Type());
+        builder_->Sem().Add(target, ty_expr);
+    }
+
     return validator_.Call(call, current_statement_) ? call : nullptr;
 }
 
@@ -2444,32 +2287,224 @@
     auto f16 = [&] {
         return validator_.CheckF16Enabled(ident->source) ? b.create<type::F16>() : nullptr;
     };
-    auto vec = [&](type::Type* el, uint32_t n) {
-        return el ? b.create<type::Vector>(el, n) : nullptr;
-    };
-    auto mat = [&](type::Type* el, uint32_t num_columns, uint32_t num_rows) {
-        return el ? b.create<type::Matrix>(vec(el, num_rows), num_columns) : nullptr;
-    };
-    auto templated_identifier = [&](size_t num_args) -> const ast::TemplatedIdentifier* {
+    auto templated_identifier =
+        [&](size_t min_args, size_t max_args = /* use min */ 0) -> const ast::TemplatedIdentifier* {
+        if (max_args == 0) {
+            max_args = min_args;
+        }
         auto* tmpl_ident = ident->As<ast::TemplatedIdentifier>();
-        if (TINT_UNLIKELY(!tmpl_ident)) {
-            AddError("expected '<' for '" + b.Symbols().NameFor(ident->symbol) + "'",
-                     Source{ident->source.range.end});
+        if (!tmpl_ident) {
+            if (TINT_UNLIKELY(min_args != 0)) {
+                AddError("expected '<' for '" + b.Symbols().NameFor(ident->symbol) + "'",
+                         Source{ident->source.range.end});
+            }
             return nullptr;
         }
-        if (TINT_UNLIKELY(tmpl_ident->arguments.Length() != num_args)) {
-            AddError("'" + b.Symbols().NameFor(ident->symbol) + "' requires " +
-                         std::to_string(num_args) + " template arguments",
-                     ident->source);
-            return nullptr;
+        if (min_args == max_args) {
+            if (TINT_UNLIKELY(tmpl_ident->arguments.Length() != min_args)) {
+                AddError("'" + b.Symbols().NameFor(ident->symbol) + "' requires " +
+                             std::to_string(min_args) + " template arguments",
+                         ident->source);
+                return nullptr;
+            }
+        } else {
+            if (TINT_UNLIKELY(tmpl_ident->arguments.Length() < min_args)) {
+                AddError("'" + b.Symbols().NameFor(ident->symbol) + "' requires at least " +
+                             std::to_string(min_args) + " template arguments",
+                         ident->source);
+                return nullptr;
+            }
+            if (TINT_UNLIKELY(tmpl_ident->arguments.Length() > max_args)) {
+                AddError("'" + b.Symbols().NameFor(ident->symbol) + "' requires at most " +
+                             std::to_string(max_args) + " template arguments",
+                         ident->source);
+                return nullptr;
+            }
         }
         return tmpl_ident;
     };
+    auto vec = [&](type::Type* el, uint32_t n) -> type::Vector* {
+        if (TINT_UNLIKELY(!el)) {
+            return nullptr;
+        }
+        if (TINT_UNLIKELY(!validator_.Vector(el, ident->source))) {
+            return nullptr;
+        }
+        return b.create<type::Vector>(el, n);
+    };
+    auto mat = [&](type::Type* el, uint32_t num_columns, uint32_t num_rows) -> type::Matrix* {
+        if (TINT_UNLIKELY(!el)) {
+            return nullptr;
+        }
+        if (TINT_UNLIKELY(!validator_.Matrix(el, ident->source))) {
+            return nullptr;
+        }
+        auto* column = vec(el, num_rows);
+        if (!column) {
+            return nullptr;
+        }
+        return b.create<type::Matrix>(column, num_columns);
+    };
+    auto vec_t = [&](uint32_t n) -> type::Vector* {
+        auto* tmpl_ident = templated_identifier(1);
+        if (TINT_UNLIKELY(!tmpl_ident)) {
+            return nullptr;
+        }
+        auto* ty = Type(tmpl_ident->arguments[0]);
+        if (TINT_UNLIKELY(!ty)) {
+            return nullptr;
+        }
+        return vec(const_cast<type::Type*>(ty), n);
+    };
+    auto mat_t = [&](uint32_t num_columns, uint32_t num_rows) -> type::Matrix* {
+        auto* tmpl_ident = templated_identifier(1);
+        if (TINT_UNLIKELY(!tmpl_ident)) {
+            return nullptr;
+        }
+        auto* ty = Type(tmpl_ident->arguments[0]);
+        if (TINT_UNLIKELY(!ty)) {
+            return nullptr;
+        }
+        return mat(const_cast<type::Type*>(ty), num_columns, num_rows);
+    };
+    auto array = [&]() -> type::Array* {
+        utils::UniqueVector<const sem::GlobalVariable*, 4> transitively_referenced_overrides;
+        TINT_SCOPED_ASSIGNMENT(resolved_overrides_, &transitively_referenced_overrides);
+
+        auto* tmpl_ident = templated_identifier(1, 2);
+        if (TINT_UNLIKELY(!tmpl_ident)) {
+            return nullptr;
+        }
+        auto* ast_el_ty = tmpl_ident->arguments[0];
+        auto* ast_count = (tmpl_ident->arguments.Length() > 1) ? tmpl_ident->arguments[1] : nullptr;
+
+        auto* el_ty = Type(ast_el_ty);
+        if (!el_ty) {
+            return nullptr;
+        }
+
+        const type::ArrayCount* el_count =
+            ast_count ? ArrayCount(ast_count) : builder_->create<type::RuntimeArrayCount>();
+        if (!el_count) {
+            return nullptr;
+        }
+
+        // Look for explicit stride via @stride(n) attribute
+        uint32_t explicit_stride = 0;
+        if (!ArrayAttributes(tmpl_ident->attributes, el_ty, explicit_stride)) {
+            return nullptr;
+        }
+
+        auto* out = Array(ast_el_ty->source,                              //
+                          ast_count ? ast_count->source : ident->source,  //
+                          el_ty, el_count, explicit_stride);
+        if (!out) {
+            return nullptr;
+        }
+
+        if (el_ty->Is<type::Atomic>()) {
+            atomic_composite_info_.Add(out, &ast_el_ty->source);
+        } else {
+            if (auto found = atomic_composite_info_.Get(el_ty)) {
+                atomic_composite_info_.Add(out, *found);
+            }
+        }
+
+        // Track the pipeline-overridable constants that are transitively referenced by this
+        // array type.
+        for (auto* var : transitively_referenced_overrides) {
+            builder_->Sem().AddTransitivelyReferencedOverride(out, var);
+        }
+        return out;
+    };
+    auto atomic = [&]() -> type::Atomic* {
+        auto* tmpl_ident = templated_identifier(1);  // atomic<type>
+        if (TINT_UNLIKELY(!tmpl_ident)) {
+            return nullptr;
+        }
+
+        auto* ty_expr = TypeExpression(tmpl_ident->arguments[0]);
+        if (TINT_UNLIKELY(!ty_expr)) {
+            return nullptr;
+        }
+        auto* ty = ty_expr->Type();
+
+        auto* out = builder_->create<type::Atomic>(ty);
+        if (!validator_.Atomic(tmpl_ident, out)) {
+            return nullptr;
+        }
+        return out;
+    };
+    auto ptr = [&]() -> type::Pointer* {
+        auto* tmpl_ident = templated_identifier(2, 3);  // ptr<address, type [, access]>
+        if (TINT_UNLIKELY(!tmpl_ident)) {
+            return nullptr;
+        }
+
+        auto* address_space_expr = AddressSpaceExpression(tmpl_ident->arguments[0]);
+        if (TINT_UNLIKELY(!address_space_expr)) {
+            return nullptr;
+        }
+        auto address_space = address_space_expr->Value();
+
+        auto* store_ty_expr = TypeExpression(tmpl_ident->arguments[1]);
+        if (TINT_UNLIKELY(!store_ty_expr)) {
+            return nullptr;
+        }
+        auto* store_ty = const_cast<type::Type*>(store_ty_expr->Type());
+
+        auto access = DefaultAccessForAddressSpace(address_space);
+        if (tmpl_ident->arguments.Length() > 2) {
+            auto* access_expr = AccessExpression(tmpl_ident->arguments[2]);
+            if (TINT_UNLIKELY(!access_expr)) {
+                return nullptr;
+            }
+            access = access_expr->Value();
+        }
+
+        auto* out = b.create<type::Pointer>(store_ty, address_space, access);
+        if (!validator_.Pointer(tmpl_ident, out)) {
+            return nullptr;
+        }
+        if (!ApplyAddressSpaceUsageToType(address_space, store_ty,
+                                          store_ty_expr->Declaration()->source)) {
+            AddNote("while instantiating " + builder_->FriendlyName(out), ident->source);
+            return nullptr;
+        }
+        return out;
+    };
+    auto sampled_texture = [&](type::TextureDimension dim) -> type::SampledTexture* {
+        auto* tmpl_ident = templated_identifier(1);
+        if (TINT_UNLIKELY(!tmpl_ident)) {
+            return nullptr;
+        }
+
+        auto* ty_expr = TypeExpression(tmpl_ident->arguments[0]);
+        if (TINT_UNLIKELY(!ty_expr)) {
+            return nullptr;
+        }
+        auto* out = b.create<type::SampledTexture>(dim, ty_expr->Type());
+        return validator_.SampledTexture(out, ident->source) ? out : nullptr;
+    };
+    auto multisampled_texture = [&](type::TextureDimension dim) -> type::MultisampledTexture* {
+        auto* tmpl_ident = templated_identifier(1);
+        if (TINT_UNLIKELY(!tmpl_ident)) {
+            return nullptr;
+        }
+
+        auto* ty_expr = TypeExpression(tmpl_ident->arguments[0]);
+        if (TINT_UNLIKELY(!ty_expr)) {
+            return nullptr;
+        }
+        auto* out = b.create<type::MultisampledTexture>(dim, ty_expr->Type());
+        return validator_.MultisampledTexture(out, ident->source) ? out : nullptr;
+    };
     auto storage_texture = [&](type::TextureDimension dim) -> type::StorageTexture* {
         auto* tmpl_ident = templated_identifier(2);
         if (TINT_UNLIKELY(!tmpl_ident)) {
             return nullptr;
         }
+
         auto* format = sem_.AsTexelFormat(Expression(tmpl_ident->arguments[0]));
         if (TINT_UNLIKELY(!format)) {
             return nullptr;
@@ -2497,6 +2532,30 @@
             return check_no_tmpl_args(f16());
         case type::Builtin::kF32:
             return check_no_tmpl_args(b.create<type::F32>());
+        case type::Builtin::kVec2:
+            return vec_t(2);
+        case type::Builtin::kVec3:
+            return vec_t(3);
+        case type::Builtin::kVec4:
+            return vec_t(4);
+        case type::Builtin::kMat2X2:
+            return mat_t(2, 2);
+        case type::Builtin::kMat2X3:
+            return mat_t(2, 3);
+        case type::Builtin::kMat2X4:
+            return mat_t(2, 4);
+        case type::Builtin::kMat3X2:
+            return mat_t(3, 2);
+        case type::Builtin::kMat3X3:
+            return mat_t(3, 3);
+        case type::Builtin::kMat3X4:
+            return mat_t(3, 4);
+        case type::Builtin::kMat4X2:
+            return mat_t(4, 2);
+        case type::Builtin::kMat4X3:
+            return mat_t(4, 3);
+        case type::Builtin::kMat4X4:
+            return mat_t(4, 4);
         case type::Builtin::kMat2X2F:
             return check_no_tmpl_args(mat(f32(), 2u, 2u));
         case type::Builtin::kMat2X3F:
@@ -2557,11 +2616,29 @@
             return check_no_tmpl_args(vec(u32(), 3u));
         case type::Builtin::kVec4U:
             return check_no_tmpl_args(vec(u32(), 4u));
+        case type::Builtin::kArray:
+            return array();
+        case type::Builtin::kAtomic:
+            return atomic();
+        case type::Builtin::kPtr:
+            return ptr();
         case type::Builtin::kSampler:
             return check_no_tmpl_args(builder_->create<type::Sampler>(type::SamplerKind::kSampler));
         case type::Builtin::kSamplerComparison:
             return check_no_tmpl_args(
                 builder_->create<type::Sampler>(type::SamplerKind::kComparisonSampler));
+        case type::Builtin::kTexture1D:
+            return sampled_texture(type::TextureDimension::k1d);
+        case type::Builtin::kTexture2D:
+            return sampled_texture(type::TextureDimension::k2d);
+        case type::Builtin::kTexture2DArray:
+            return sampled_texture(type::TextureDimension::k2dArray);
+        case type::Builtin::kTexture3D:
+            return sampled_texture(type::TextureDimension::k3d);
+        case type::Builtin::kTextureCube:
+            return sampled_texture(type::TextureDimension::kCube);
+        case type::Builtin::kTextureCubeArray:
+            return sampled_texture(type::TextureDimension::kCubeArray);
         case type::Builtin::kTextureDepth2D:
             return check_no_tmpl_args(
                 builder_->create<type::DepthTexture>(type::TextureDimension::k2d));
@@ -2579,6 +2656,8 @@
                 builder_->create<type::DepthMultisampledTexture>(type::TextureDimension::k2d));
         case type::Builtin::kTextureExternal:
             return check_no_tmpl_args(builder_->create<type::ExternalTexture>());
+        case type::Builtin::kTextureMultisampled2D:
+            return multisampled_texture(type::TextureDimension::k2d);
         case type::Builtin::kTextureStorage1D:
             return storage_texture(type::TextureDimension::k1d);
         case type::Builtin::kTextureStorage2D:
@@ -2626,9 +2705,6 @@
                                   sem::Function* target,
                                   utils::Vector<const sem::ValueExpression*, N>& args,
                                   sem::Behaviors arg_behaviors) {
-    auto sym = expr->target.name->symbol;
-    auto name = builder_->Symbols().NameFor(sym);
-
     if (!MaybeMaterializeAndLoadArguments(args, target)) {
         return nullptr;
     }
@@ -2671,6 +2747,11 @@
         CollectTextureSamplerPairs(target, call->Arguments());
     }
 
+    // Associate the target identifier expression with the resolved function.
+    auto* fn_expr =
+        builder_->create<sem::FunctionExpression>(expr->target, current_statement_, target);
+    builder_->Sem().Add(expr->target, fn_expr);
+
     return call;
 }
 
@@ -2758,13 +2839,13 @@
 }
 
 sem::Expression* Resolver::Identifier(const ast::IdentifierExpression* expr) {
-    Mark(expr->identifier);
+    auto* ident = expr->identifier;
+    Mark(ident);
 
-    auto resolved = dependencies_.resolved_identifiers.Get(expr->identifier);
+    auto resolved = dependencies_.resolved_identifiers.Get(ident);
     if (!resolved) {
         TINT_ICE(Resolver, diagnostics_)
-            << "identifier '" << builder_->Symbols().NameFor(expr->identifier->symbol)
-            << "' was not resolved";
+            << "identifier '" << builder_->Symbols().NameFor(ident->symbol) << "' was not resolved";
         return nullptr;
     }
 
@@ -2773,7 +2854,7 @@
         return Switch(
             resolved_node,  //
             [&](sem::Variable* variable) -> sem::VariableUser* {
-                auto symbol = expr->identifier->symbol;
+                auto symbol = ident->symbol;
                 auto* user =
                     builder_->create<sem::VariableUser>(expr, current_statement_, variable);
 
@@ -2844,17 +2925,32 @@
                 variable->AddUser(user);
                 return user;
             },
-            [&](const type::Type* ty) {
+            [&](const type::Type* ty) -> sem::TypeExpression* {
+                if (TINT_UNLIKELY(ident->Is<ast::TemplatedIdentifier>())) {
+                    AddError("type '" + builder_->Symbols().NameFor(ident->symbol) +
+                                 "' does not take template arguments",
+                             ident->source);
+                    sem_.NoteDeclarationSource(ast_node);
+                    return nullptr;
+                }
+
                 return builder_->create<sem::TypeExpression>(expr, current_statement_, ty);
             },
-            [&](const sem::Function*) {
-                AddError("missing '(' for function call", expr->source.End());
-                return nullptr;
+            [&](const sem::Function* fn) -> sem::FunctionExpression* {
+                if (TINT_UNLIKELY(ident->Is<ast::TemplatedIdentifier>())) {
+                    AddError("function '" + builder_->Symbols().NameFor(ident->symbol) +
+                                 "' does not take template arguments",
+                             ident->source);
+                    sem_.NoteDeclarationSource(ast_node);
+                    return nullptr;
+                }
+
+                return builder_->create<sem::FunctionExpression>(expr, current_statement_, fn);
             });
     }
 
     if (auto builtin_ty = resolved->BuiltinType(); builtin_ty != type::Builtin::kUndefined) {
-        auto* ty = BuiltinType(builtin_ty, expr->identifier);
+        auto* ty = BuiltinType(builtin_ty, ident);
         if (!ty) {
             return nullptr;
         }
@@ -3237,62 +3333,6 @@
     return result;
 }
 
-type::Array* Resolver::Array(const ast::Array* arr) {
-    if (!arr->type) {
-        AddError("missing array element type", arr->source.End());
-        return nullptr;
-    }
-
-    utils::UniqueVector<const sem::GlobalVariable*, 4> transitively_referenced_overrides;
-    TINT_SCOPED_ASSIGNMENT(resolved_overrides_, &transitively_referenced_overrides);
-
-    auto* el_ty = Type(arr->type);
-    if (!el_ty) {
-        return nullptr;
-    }
-
-    // Look for explicit stride via @stride(n) attribute
-    uint32_t explicit_stride = 0;
-    if (!ArrayAttributes(arr->attributes, el_ty, explicit_stride)) {
-        return nullptr;
-    }
-
-    const type::ArrayCount* el_count = nullptr;
-
-    // Evaluate the constant array count expression.
-    if (auto* count_expr = arr->count) {
-        el_count = ArrayCount(count_expr);
-        if (!el_count) {
-            return nullptr;
-        }
-    } else {
-        el_count = builder_->create<type::RuntimeArrayCount>();
-    }
-
-    auto* out = Array(arr->type->source,                              //
-                      arr->count ? arr->count->source : arr->source,  //
-                      el_ty, el_count, explicit_stride);
-    if (out == nullptr) {
-        return nullptr;
-    }
-
-    if (el_ty->Is<type::Atomic>()) {
-        atomic_composite_info_.Add(out, &arr->type->source);
-    } else {
-        if (auto found = atomic_composite_info_.Get(el_ty)) {
-            atomic_composite_info_.Add(out, *found);
-        }
-    }
-
-    // Track the pipeline-overridable constants that are transitively referenced by this array
-    // type.
-    for (auto* var : transitively_referenced_overrides) {
-        builder_->Sem().AddTransitivelyReferencedOverride(out, var);
-    }
-
-    return out;
-}
-
 const type::ArrayCount* Resolver::ArrayCount(const ast::Expression* count_expr) {
     // Evaluate the constant array count expression.
     const auto* count_sem = Materialize(ValueExpression(count_expr));
@@ -3442,7 +3482,7 @@
         }
 
         // Resolve member type
-        auto* type = Type(member->type);
+        auto type = Type(member->type);
         if (!type) {
             return nullptr;
         }
@@ -4074,45 +4114,7 @@
     AddError("cannot use " + resolved.String(builder_->Symbols(), diagnostics_) + " as " +
                  std::string(wanted),
              source);
-    NoteDeclarationSource(resolved.Node());
-}
-
-void Resolver::NoteDeclarationSource(const ast::Node* node) {
-    Switch(
-        node,
-        [&](const ast::Struct* n) {
-            AddNote("struct '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
-                    n->source);
-        },
-        [&](const ast::Alias* n) {
-            AddNote("alias '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
-                    n->source);
-        },
-        [&](const ast::Var* n) {
-            AddNote("var '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
-                    n->source);
-        },
-        [&](const ast::Let* n) {
-            AddNote("let '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
-                    n->source);
-        },
-        [&](const ast::Override* n) {
-            AddNote("override '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
-                    n->source);
-        },
-        [&](const ast::Const* n) {
-            AddNote("const '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
-                    n->source);
-        },
-        [&](const ast::Parameter* n) {
-            AddNote(
-                "parameter '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
-                n->source);
-        },
-        [&](const ast::Function* n) {
-            AddNote("function '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
-                    n->source);
-        });
+    sem_.NoteDeclarationSource(resolved.Node());
 }
 
 void Resolver::AddError(const std::string& msg, const Source& source) const {
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index bc8821a..c5223db 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -126,7 +126,36 @@
     /// not a sem::ValueExpression, then an error diagnostic is raised and nullptr is returned.
     sem::ValueExpression* ValueExpression(const ast::Expression* expr);
 
-    /// Expression traverses the graph of expressions starting at `expr`, building a postordered
+    /// @returns the call of Expression() cast to a sem::TypeExpression. If the sem::Expression is
+    /// not a sem::TypeExpression, then an error diagnostic is raised and nullptr is returned.
+    sem::TypeExpression* TypeExpression(const ast::Expression* expr);
+
+    /// @returns the call of Expression() cast to a sem::FunctionExpression. If the sem::Expression
+    /// is not a sem::FunctionExpression, then an error diagnostic is raised and nullptr is
+    /// returned.
+    sem::FunctionExpression* FunctionExpression(const ast::Expression* expr);
+
+    /// @returns the resolved type from an expression, or nullptr on error
+    type::Type* Type(const ast::Expression* ast);
+
+    /// @returns the call of Expression() cast to a sem::BuiltinEnumExpression<type::AddressSpace>.
+    /// If the sem::Expression is not a sem::BuiltinEnumExpression<type::AddressSpace>, then an
+    /// error diagnostic is raised and nullptr is returned.
+    sem::BuiltinEnumExpression<type::AddressSpace>* AddressSpaceExpression(
+        const ast::Expression* expr);
+
+    /// @returns the call of Expression() cast to a sem::BuiltinEnumExpression<type::TexelFormat>.
+    /// If the sem::Expression is not a sem::BuiltinEnumExpression<type::TexelFormat>, then an error
+    /// diagnostic is raised and nullptr is returned.
+    sem::BuiltinEnumExpression<type::TexelFormat>* TexelFormatExpression(
+        const ast::Expression* expr);
+
+    /// @returns the call of Expression() cast to a sem::BuiltinEnumExpression<type::Access>*.
+    /// If the sem::Expression is not a sem::BuiltinEnumExpression<type::Access>*, then an error
+    /// diagnostic is raised and nullptr is returned.
+    sem::BuiltinEnumExpression<type::Access>* AccessExpression(const ast::Expression* expr);
+
+    /// Expression traverses the graph of expressions starting at `expr`, building a post-ordered
     /// list (leaf-first) of all the expression nodes. Each of the expressions are then resolved by
     /// dispatching to the appropriate expression handlers below.
     /// @returns the resolved semantic node for the expression `expr`, or nullptr on failure.
@@ -259,12 +288,6 @@
     /// current_function_
     bool WorkgroupSize(const ast::Function*);
 
-    /// @returns the type::Type for the ast::Type `ty`, building it if it
-    /// hasn't been constructed already. If an error is raised, nullptr is
-    /// returned.
-    /// @param ty the ast::Type
-    type::Type* Type(const ast::Type* ty);
-
     /// @param control the diagnostic control
     /// @returns true on success, false on failure
     bool DiagnosticControl(const ast::DiagnosticControl& control);
@@ -277,13 +300,6 @@
     /// @returns the resolved semantic type
     type::Type* TypeDecl(const ast::TypeDecl* named_type);
 
-    /// Builds and returns the semantic information for the AST array `arr`.
-    /// This method does not mark the ast::Array node, nor attach the generated semantic information
-    /// to the AST node.
-    /// @returns the semantic Array information, or nullptr if an error is raised.
-    /// @param arr the Array to get semantic information for
-    type::Array* Array(const ast::Array* arr);
-
     /// Resolves and validates the expression used as the count parameter of an array.
     /// @param count_expr the expression used as the second template parameter to an array<>.
     /// @returns the number of elements in the array.
@@ -432,11 +448,6 @@
                                            const ResolvedIdentifier& resolved,
                                            std::string_view wanted);
 
-    /// If @p node is a module-scope type, variable or function declaration, then appends a note
-    /// diagnostic where this declaration was declared, otherwise the function does nothing.
-    /// @param node the AST node.
-    void NoteDeclarationSource(const ast::Node* node);
-
     /// Adds the given error message to the diagnostics
     void AddError(const std::string& msg, const Source& source) const;
 
diff --git a/src/tint/resolver/resolver_behavior_test.cc b/src/tint/resolver/resolver_behavior_test.cc
index af69c3c..bc4af42 100644
--- a/src/tint/resolver/resolver_behavior_test.cc
+++ b/src/tint/resolver/resolver_behavior_test.cc
@@ -82,7 +82,7 @@
     Func("ArrayDiscardOrNext", utils::Empty, ty.array<i32, 4>(),
          utils::Vector{
              If(true, Block(Discard())),
-             Return(Call(ty.array<i32, 4>())),
+             Return(array<i32, 4>()),
          });
 
     auto* stmt = Decl(Var("lhs", ty.i32(), IndexAccessor(Call("ArrayDiscardOrNext"), 1_i)));
diff --git a/src/tint/resolver/resolver_test.cc b/src/tint/resolver/resolver_test.cc
index 0630eb9..3d32799 100644
--- a/src/tint/resolver/resolver_test.cc
+++ b/src/tint/resolver/resolver_test.cc
@@ -1641,8 +1641,8 @@
 TEST_P(Expr_Binary_Test_Valid, All) {
     auto& params = GetParam();
 
-    auto* lhs_type = params.create_lhs_type(*this);
-    auto* rhs_type = params.create_rhs_type(*this);
+    ast::Type lhs_type = params.create_lhs_type(*this);
+    ast::Type rhs_type = params.create_rhs_type(*this);
     auto* result_type = params.create_result_type(*this);
 
     std::stringstream ss;
@@ -1674,8 +1674,8 @@
                                 ? params.create_rhs_alias_type
                                 : params.create_rhs_type;
 
-    auto* lhs_type = create_lhs_type(*this);
-    auto* rhs_type = create_rhs_type(*this);
+    ast::Type lhs_type = create_lhs_type(*this);
+    ast::Type rhs_type = create_rhs_type(*this);
 
     std::stringstream ss;
     ss << FriendlyName(lhs_type) << " " << params.op << " " << FriendlyName(rhs_type);
@@ -1723,8 +1723,8 @@
         }
     }
 
-    auto* lhs_type = lhs_create_type_func(*this);
-    auto* rhs_type = rhs_create_type_func(*this);
+    ast::Type lhs_type = lhs_create_type_func(*this);
+    ast::Type rhs_type = rhs_create_type_func(*this);
 
     std::stringstream ss;
     ss << FriendlyName(lhs_type) << " " << op << " " << FriendlyName(rhs_type);
@@ -1753,8 +1753,8 @@
     uint32_t mat_rows = std::get<2>(GetParam());
     uint32_t mat_cols = std::get<3>(GetParam());
 
-    const ast::Type* lhs_type = nullptr;
-    const ast::Type* rhs_type = nullptr;
+    ast::Type lhs_type;
+    ast::Type rhs_type;
     const type::Type* result_type = nullptr;
     bool is_valid_expr;
 
@@ -1800,8 +1800,8 @@
     uint32_t rhs_mat_rows = std::get<2>(GetParam());
     uint32_t rhs_mat_cols = std::get<3>(GetParam());
 
-    auto* lhs_type = ty.mat<f32>(lhs_mat_cols, lhs_mat_rows);
-    auto* rhs_type = ty.mat<f32>(rhs_mat_cols, rhs_mat_rows);
+    auto lhs_type = ty.mat<f32>(lhs_mat_cols, lhs_mat_rows);
+    auto rhs_type = ty.mat<f32>(rhs_mat_cols, rhs_mat_rows);
 
     auto* f32 = create<type::F32>();
     auto* col = create<type::Vector>(f32, lhs_mat_rows);
@@ -1876,7 +1876,7 @@
 }
 
 TEST_F(ResolverTest, AddressSpace_SetForSampler) {
-    auto* t = ty.sampler(type::SamplerKind::kSampler);
+    auto t = ty.sampler(type::SamplerKind::kSampler);
     auto* var = GlobalVar("var", t, Binding(0_a), Group(0_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -1885,7 +1885,7 @@
 }
 
 TEST_F(ResolverTest, AddressSpace_SetForTexture) {
-    auto* t = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
+    auto t = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
     auto* var = GlobalVar("var", t, Binding(0_a), Group(0_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/tint/resolver/resolver_test_helper.h b/src/tint/resolver/resolver_test_helper.h
index ee913f4..67d71b8 100644
--- a/src/tint/resolver/resolver_test_helper.h
+++ b/src/tint/resolver/resolver_test_helper.h
@@ -112,7 +112,7 @@
     /// @param type a type
     /// @returns the name for `type` that closely resembles how it would be
     /// declared in WGSL.
-    std::string FriendlyName(const ast::Type* type) { return type->FriendlyName(Symbols()); }
+    std::string FriendlyName(ast::Type type) { return Symbols().NameFor(type->identifier->symbol); }
 
     /// @param type a type
     /// @returns the name for `type` that closely resembles how it would be
@@ -199,7 +199,7 @@
     return std::visit([](auto&& v) { return static_cast<T>(v); }, s);
 }
 
-using ast_type_func_ptr = const ast::Type* (*)(ProgramBuilder& b);
+using ast_type_func_ptr = ast::Type (*)(ProgramBuilder& b);
 using ast_expr_func_ptr = const ast::Expression* (*)(ProgramBuilder& b,
                                                      utils::VectorRef<Scalar> args);
 using ast_expr_from_double_func_ptr = const ast::Expression* (*)(ProgramBuilder& b, double v);
@@ -222,7 +222,7 @@
     using ElementType = void;
 
     /// @return nullptr
-    static inline const ast::Type* AST(ProgramBuilder&) { return nullptr; }
+    static inline ast::Type AST(ProgramBuilder&) { return {}; }
     /// @return nullptr
     static inline const type::Type* Sem(ProgramBuilder&) { return nullptr; }
 };
@@ -238,7 +238,7 @@
 
     /// @param b the ProgramBuilder
     /// @return a new AST bool type
-    static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.bool_(); }
+    static inline ast::Type AST(ProgramBuilder& b) { return b.ty.bool_(); }
     /// @param b the ProgramBuilder
     /// @return the semantic bool type
     static inline const type::Type* Sem(ProgramBuilder& b) { return b.create<type::Bool>(); }
@@ -269,7 +269,7 @@
 
     /// @param b the ProgramBuilder
     /// @return a new AST i32 type
-    static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.i32(); }
+    static inline ast::Type AST(ProgramBuilder& b) { return b.ty.i32(); }
     /// @param b the ProgramBuilder
     /// @return the semantic i32 type
     static inline const type::Type* Sem(ProgramBuilder& b) { return b.create<type::I32>(); }
@@ -300,7 +300,7 @@
 
     /// @param b the ProgramBuilder
     /// @return a new AST u32 type
-    static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.u32(); }
+    static inline ast::Type AST(ProgramBuilder& b) { return b.ty.u32(); }
     /// @param b the ProgramBuilder
     /// @return the semantic u32 type
     static inline const type::Type* Sem(ProgramBuilder& b) { return b.create<type::U32>(); }
@@ -331,7 +331,7 @@
 
     /// @param b the ProgramBuilder
     /// @return a new AST f32 type
-    static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.f32(); }
+    static inline ast::Type AST(ProgramBuilder& b) { return b.ty.f32(); }
     /// @param b the ProgramBuilder
     /// @return the semantic f32 type
     static inline const type::Type* Sem(ProgramBuilder& b) { return b.create<type::F32>(); }
@@ -362,7 +362,7 @@
 
     /// @param b the ProgramBuilder
     /// @return a new AST f16 type
-    static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.f16(); }
+    static inline ast::Type AST(ProgramBuilder& b) { return b.ty.f16(); }
     /// @param b the ProgramBuilder
     /// @return the semantic f16 type
     static inline const type::Type* Sem(ProgramBuilder& b) { return b.create<type::F16>(); }
@@ -392,7 +392,7 @@
     static constexpr bool is_composite = false;
 
     /// @returns nullptr, as abstract floats are un-typeable
-    static inline const ast::Type* AST(ProgramBuilder&) { return nullptr; }
+    static inline ast::Type AST(ProgramBuilder&) { return {}; }
     /// @param b the ProgramBuilder
     /// @return the semantic abstract-float type
     static inline const type::Type* Sem(ProgramBuilder& b) {
@@ -424,7 +424,7 @@
     static constexpr bool is_composite = false;
 
     /// @returns nullptr, as abstract integers are un-typeable
-    static inline const ast::Type* AST(ProgramBuilder&) { return nullptr; }
+    static inline ast::Type AST(ProgramBuilder&) { return {}; }
     /// @param b the ProgramBuilder
     /// @return the semantic abstract-int type
     static inline const type::Type* Sem(ProgramBuilder& b) { return b.create<type::AbstractInt>(); }
@@ -455,8 +455,12 @@
 
     /// @param b the ProgramBuilder
     /// @return a new AST vector type
-    static inline const ast::Type* AST(ProgramBuilder& b) {
-        return b.ty.vec(DataType<T>::AST(b), N);
+    static inline ast::Type AST(ProgramBuilder& b) {
+        if (IsInferOrAbstract<T>) {
+            return b.ty.vec<Infer, N>();
+        } else {
+            return b.ty.vec(DataType<T>::AST(b), N);
+        }
     }
     /// @param b the ProgramBuilder
     /// @return the semantic vector type
@@ -503,8 +507,12 @@
 
     /// @param b the ProgramBuilder
     /// @return a new AST matrix type
-    static inline const ast::Type* AST(ProgramBuilder& b) {
-        return b.ty.mat(DataType<T>::AST(b), N, M);
+    static inline ast::Type AST(ProgramBuilder& b) {
+        if (IsInferOrAbstract<T>) {
+            return b.ty.mat<Infer, N, M>();
+        } else {
+            return b.ty.mat(DataType<T>::AST(b), N, M);
+        }
     }
     /// @param b the ProgramBuilder
     /// @return the semantic matrix type
@@ -562,14 +570,15 @@
 
     /// @param b the ProgramBuilder
     /// @return a new AST alias type
-    static inline const ast::Type* AST(ProgramBuilder& b) {
+    static inline ast::Type AST(ProgramBuilder& b) {
         auto name = b.Symbols().Register("alias_" + std::to_string(ID));
         if (!b.AST().LookupType(name)) {
-            auto* type = DataType<T>::AST(b);
+            auto type = DataType<T>::AST(b);
             b.AST().AddTypeDecl(b.ty.alias(name, type));
         }
         return b.ty(name);
     }
+
     /// @param b the ProgramBuilder
     /// @return the semantic aliased type
     static inline const type::Type* Sem(ProgramBuilder& b) { return DataType<T>::Sem(b); }
@@ -618,9 +627,9 @@
 
     /// @param b the ProgramBuilder
     /// @return a new AST alias type
-    static inline const ast::Type* AST(ProgramBuilder& b) {
-        return b.create<ast::Pointer>(DataType<T>::AST(b), type::AddressSpace::kPrivate,
-                                      type::Access::kUndefined);
+    static inline ast::Type AST(ProgramBuilder& b) {
+        return b.ty.pointer(DataType<T>::AST(b), type::AddressSpace::kPrivate,
+                            type::Access::kUndefined);
     }
     /// @param b the ProgramBuilder
     /// @return the semantic aliased type
@@ -660,11 +669,11 @@
 
     /// @param b the ProgramBuilder
     /// @return a new AST array type
-    static inline const ast::Type* AST(ProgramBuilder& b) {
-        if (auto* ast = DataType<T>::AST(b)) {
+    static inline ast::Type AST(ProgramBuilder& b) {
+        if (auto ast = DataType<T>::AST(b)) {
             return b.ty.array(ast, u32(N));
         }
-        return b.ty.array(nullptr, nullptr);
+        return b.ty.array<Infer>();
     }
     /// @param b the ProgramBuilder
     /// @return the semantic array type
diff --git a/src/tint/resolver/sem_helper.cc b/src/tint/resolver/sem_helper.cc
index e288707..01a737c 100644
--- a/src/tint/resolver/sem_helper.cc
+++ b/src/tint/resolver/sem_helper.cc
@@ -15,6 +15,8 @@
 #include "src/tint/resolver/sem_helper.h"
 
 #include "src/tint/sem/builtin_enum_expression.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/function_expression.h"
 #include "src/tint/sem/type_expression.h"
 #include "src/tint/sem/value_expression.h"
 
@@ -42,11 +44,18 @@
     Switch(
         expr,  //
         [&](const sem::VariableUser* var_expr) {
-            auto name =
-                builder_->Symbols().NameFor(var_expr->Variable()->Declaration()->name->symbol);
-            auto type = var_expr->Type()->FriendlyName(builder_->Symbols());
-            AddError("cannot use '" + name + "' of type '" + type + "' as " + std::string(wanted),
+            auto* variable = var_expr->Variable()->Declaration();
+            auto name = builder_->Symbols().NameFor(variable->name->symbol);
+            std::string kind = Switch(
+                variable,                                            //
+                [&](const ast::Var*) { return "var"; },              //
+                [&](const ast::Const*) { return "const"; },          //
+                [&](const ast::Parameter*) { return "parameter"; },  //
+                [&](const ast::Override*) { return "override"; },    //
+                [&](Default) { return "variable"; });
+            AddError("cannot use " + kind + " '" + name + "' as " + std::string(wanted),
                      var_expr->Declaration()->source);
+            NoteDeclarationSource(variable);
         },
         [&](const sem::ValueExpression* val_expr) {
             auto type = val_expr->Type()->FriendlyName(builder_->Symbols());
@@ -58,6 +67,13 @@
             AddError("cannot use type '" + name + "' as " + std::string(wanted),
                      ty_expr->Declaration()->source);
         },
+        [&](const sem::FunctionExpression* fn_expr) {
+            auto* fn = fn_expr->Function()->Declaration();
+            auto name = builder_->Symbols().NameFor(fn->name->symbol);
+            AddError("cannot use function '" + name + "' as " + std::string(wanted),
+                     fn_expr->Declaration()->source);
+            NoteDeclarationSource(fn);
+        },
         [&](const sem::BuiltinEnumExpression<type::Access>* access) {
             AddError("cannot use access '" + utils::ToString(access->Value()) + "' as " +
                          std::string(wanted),
@@ -93,6 +109,44 @@
     }
 }
 
+void SemHelper::NoteDeclarationSource(const ast::Node* node) const {
+    Switch(
+        node,
+        [&](const ast::Struct* n) {
+            AddNote("struct '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+                    n->source);
+        },
+        [&](const ast::Alias* n) {
+            AddNote("alias '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+                    n->source);
+        },
+        [&](const ast::Var* n) {
+            AddNote("var '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+                    n->source);
+        },
+        [&](const ast::Let* n) {
+            AddNote("let '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+                    n->source);
+        },
+        [&](const ast::Override* n) {
+            AddNote("override '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+                    n->source);
+        },
+        [&](const ast::Const* n) {
+            AddNote("const '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+                    n->source);
+        },
+        [&](const ast::Parameter* n) {
+            AddNote(
+                "parameter '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+                n->source);
+        },
+        [&](const ast::Function* n) {
+            AddNote("function '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+                    n->source);
+        });
+}
+
 void SemHelper::AddError(const std::string& msg, const Source& source) const {
     builder_->Diagnostics().add_error(diag::System::Resolver, msg, source);
 }
diff --git a/src/tint/resolver/sem_helper.h b/src/tint/resolver/sem_helper.h
index db3eda4..dd752d3 100644
--- a/src/tint/resolver/sem_helper.h
+++ b/src/tint/resolver/sem_helper.h
@@ -21,6 +21,8 @@
 #include "src/tint/program_builder.h"
 #include "src/tint/resolver/dependency_graph.h"
 #include "src/tint/sem/builtin_enum_expression.h"
+#include "src/tint/sem/function_expression.h"
+#include "src/tint/sem/type_expression.h"
 #include "src/tint/utils/map.h"
 
 namespace tint::resolver {
@@ -58,16 +60,16 @@
     /// @returns the sem node for @p ast
     template <typename AST = ast::Node>
     auto* GetVal(const AST* ast) const {
-        return AsValue(Get(ast));
+        return AsValueExpression(Get(ast));
     }
 
     /// @param expr the semantic node
     /// @returns nullptr if @p expr is nullptr, or @p expr cast to sem::ValueExpression if the cast
     /// is successful, otherwise an error diagnostic is raised.
-    sem::ValueExpression* AsValue(sem::Expression* expr) const {
+    sem::ValueExpression* AsValueExpression(sem::Expression* expr) const {
         if (TINT_LIKELY(expr)) {
-            if (auto* val = expr->As<sem::ValueExpression>(); TINT_LIKELY(val)) {
-                return val;
+            if (auto* val_expr = expr->As<sem::ValueExpression>(); TINT_LIKELY(val_expr)) {
+                return val_expr;
             }
             ErrorExpectedValueExpr(expr);
         }
@@ -75,14 +77,56 @@
     }
 
     /// @param expr the semantic node
+    /// @returns nullptr if @p expr is nullptr, or @p expr cast to type::Type if the cast is
+    /// successful, otherwise an error diagnostic is raised.
+    sem::TypeExpression* AsTypeExpression(sem::Expression* expr) const {
+        if (TINT_LIKELY(expr)) {
+            if (auto* ty_expr = expr->As<sem::TypeExpression>(); TINT_LIKELY(ty_expr)) {
+                return ty_expr;
+            }
+            ErrorUnexpectedExprKind(expr, "type");
+        }
+        return nullptr;
+    }
+
+    /// @param expr the semantic node
+    /// @returns nullptr if @p expr is nullptr, or @p expr cast to sem::Function if the cast is
+    /// successful, otherwise an error diagnostic is raised.
+    sem::FunctionExpression* AsFunctionExpression(sem::Expression* expr) const {
+        if (TINT_LIKELY(expr)) {
+            auto* fn_expr = expr->As<sem::FunctionExpression>();
+            if (TINT_LIKELY(fn_expr)) {
+                return fn_expr;
+            }
+            ErrorUnexpectedExprKind(expr, "function");
+        }
+        return nullptr;
+    }
+
+    /// @param expr the semantic node
+    /// @returns nullptr if @p expr is nullptr, or @p expr cast to
+    /// sem::BuiltinEnumExpression<type::AddressSpace> if the cast is successful, otherwise an error
+    /// diagnostic is raised.
+    sem::BuiltinEnumExpression<type::AddressSpace>* AsAddressSpace(sem::Expression* expr) const {
+        if (TINT_LIKELY(expr)) {
+            auto* enum_expr = expr->As<sem::BuiltinEnumExpression<type::AddressSpace>>();
+            if (TINT_LIKELY(enum_expr)) {
+                return enum_expr;
+            }
+            ErrorUnexpectedExprKind(expr, "address space");
+        }
+        return nullptr;
+    }
+
+    /// @param expr the semantic node
     /// @returns nullptr if @p expr is nullptr, or @p expr cast to
     /// sem::BuiltinEnumExpression<type::TexelFormat> if the cast is successful, otherwise an error
     /// diagnostic is raised.
     sem::BuiltinEnumExpression<type::TexelFormat>* AsTexelFormat(sem::Expression* expr) const {
         if (TINT_LIKELY(expr)) {
-            if (auto* val = expr->As<sem::BuiltinEnumExpression<type::TexelFormat>>();
-                TINT_LIKELY(val)) {
-                return val;
+            auto* enum_expr = expr->As<sem::BuiltinEnumExpression<type::TexelFormat>>();
+            if (TINT_LIKELY(enum_expr)) {
+                return enum_expr;
             }
             ErrorUnexpectedExprKind(expr, "texel format");
         }
@@ -95,9 +139,9 @@
     /// diagnostic is raised.
     sem::BuiltinEnumExpression<type::Access>* AsAccess(sem::Expression* expr) const {
         if (TINT_LIKELY(expr)) {
-            if (auto* val = expr->As<sem::BuiltinEnumExpression<type::Access>>();
-                TINT_LIKELY(val)) {
-                return val;
+            auto* enum_expr = expr->As<sem::BuiltinEnumExpression<type::Access>>();
+            if (TINT_LIKELY(enum_expr)) {
+                return enum_expr;
             }
             ErrorUnexpectedExprKind(expr, "access");
         }
@@ -121,11 +165,17 @@
     /// @param expr the expression
     void ErrorExpectedValueExpr(const sem::Expression* expr) const;
 
-  private:
     /// Raises an error diagnostic that the expression @p got was not of the kind @p wanted.
     /// @param expr the expression
+    /// @param wanted the expected expression kind
     void ErrorUnexpectedExprKind(const sem::Expression* expr, std::string_view wanted) const;
 
+    /// If @p node is a module-scope type, variable or function declaration, then appends a note
+    /// diagnostic where this declaration was declared, otherwise the function does nothing.
+    /// @param node the AST node.
+    void NoteDeclarationSource(const ast::Node* node) const;
+
+  private:
     /// Adds the given error message to the diagnostics
     void AddError(const std::string& msg, const Source& source) const;
 
diff --git a/src/tint/resolver/struct_address_space_use_test.cc b/src/tint/resolver/struct_address_space_use_test.cc
index 6500158..2d011bb 100644
--- a/src/tint/resolver/struct_address_space_use_test.cc
+++ b/src/tint/resolver/struct_address_space_use_test.cc
@@ -99,7 +99,7 @@
 
 TEST_F(ResolverAddressSpaceUseTest, StructReachableViaGlobalArray) {
     auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
-    auto* a = ty.array(ty.Of(s), 3_u);
+    auto a = ty.array(ty.Of(s), 3_u);
     GlobalVar("g", a, type::AddressSpace::kPrivate);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -147,7 +147,7 @@
 
 TEST_F(ResolverAddressSpaceUseTest, StructReachableViaLocalArray) {
     auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
-    auto* a = ty.array(ty.Of(s), 3_u);
+    auto a = ty.array(ty.Of(s), 3_u);
     WrapInFunction(Var("g", a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/tint/resolver/struct_layout_test.cc b/src/tint/resolver/struct_layout_test.cc
index 8d9dee9..7288449 100644
--- a/src/tint/resolver/struct_layout_test.cc
+++ b/src/tint/resolver/struct_layout_test.cc
@@ -256,8 +256,8 @@
 }
 
 TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfExplicitStrideArray) {
-    auto* inner = ty.array<i32, 2>(utils::Vector{Stride(16)});  // size: 32
-    auto* outer = ty.array(inner, 12_u);                        // size: 12 * 32
+    auto inner = ty.array<i32, 2>(utils::Vector{Stride(16)});  // size: 32
+    auto outer = ty.array(inner, 12_u);                        // size: 12 * 32
     auto* s = Structure("S", utils::Vector{
                                  Member("c", outer),
                              });
@@ -283,8 +283,8 @@
                                          Member("a", ty.vec2<i32>()),
                                          Member("b", ty.vec3<i32>()),
                                          Member("c", ty.vec4<i32>()),
-                                     });         // size: 48
-    auto* outer = ty.array(ty.Of(inner), 12_u);  // size: 12 * 48
+                                     });        // size: 48
+    auto outer = ty.array(ty.Of(inner), 12_u);  // size: 12 * 48
     auto* s = Structure("S", utils::Vector{
                                  Member("c", outer),
                              });
diff --git a/src/tint/resolver/type_initializer_validation_test.cc b/src/tint/resolver/type_initializer_validation_test.cc
index c1212ec..0f7144b 100644
--- a/src/tint/resolver/type_initializer_validation_test.cc
+++ b/src/tint/resolver/type_initializer_validation_test.cc
@@ -346,9 +346,9 @@
     Enable(ast::Extension::kF16);
 
     // var a : <lhs_type1> = <lhs_type2>(<rhs_type>(<rhs_value_expr>));
-    auto* lhs_type1 = params.lhs_type(*this);
-    auto* lhs_type2 = params.lhs_type(*this);
-    auto* rhs_type = params.rhs_type(*this);
+    auto lhs_type1 = params.lhs_type(*this);
+    auto lhs_type2 = params.lhs_type(*this);
+    auto rhs_type = params.rhs_type(*this);
     auto* rhs_value_expr = params.rhs_value_expr(*this, 0);
 
     std::stringstream ss;
@@ -439,9 +439,9 @@
     }
 
     // var a : <lhs_type1> = <lhs_type2>(<rhs_type>(<rhs_value_expr>));
-    auto* lhs_type1 = lhs_params.ast(*this);
-    auto* lhs_type2 = lhs_params.ast(*this);
-    auto* rhs_type = rhs_params.ast(*this);
+    auto lhs_type1 = lhs_params.ast(*this);
+    auto lhs_type2 = lhs_params.ast(*this);
+    auto rhs_type = rhs_params.ast(*this);
     auto* rhs_value_expr = rhs_params.expr_from_double(*this, 0);
 
     std::stringstream ss;
@@ -523,7 +523,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArray_U32U32U32) {
     // array(0u, 10u, 20u);
-    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 0_u, 10_u, 20_u);
+    auto* tc = array<Infer>(Source{{12, 34}}, 0_u, 10_u, 20_u);
     WrapInFunction(tc);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -561,7 +561,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArray_U32AIU32) {
     // array(0u, 10u, 20u);
-    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 0_u, 10_a, 20_u);
+    auto* tc = array<Infer>(Source{{12, 34}}, 0_u, 10_a, 20_u);
     WrapInFunction(tc);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -599,7 +599,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArray_AIAIAI) {
     // const c = array(0, 10, 20);
-    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 0_a, 10_a, 20_a);
+    auto* tc = array<Infer>(Source{{12, 34}}, 0_a, 10_a, 20_a);
     WrapInFunction(Decl(Const("C", tc)));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -618,9 +618,9 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArrayU32_VecI32_VecAI) {
     // array(vec2(10i), vec2(20));
-    auto* tc = array(Source{{12, 34}}, nullptr, nullptr,  //
-                     Call(ty.vec(nullptr, 2), 20_i),      //
-                     Call(ty.vec(nullptr, 2), 20_a));
+    auto* tc = array<Infer>(Source{{12, 34}},              //
+                            Call(ty.vec<Infer>(2), 20_i),  //
+                            Call(ty.vec<Infer>(2), 20_a));
     WrapInFunction(tc);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -640,9 +640,9 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArrayU32_VecAI_VecF32) {
     // array(vec2(20), vec2(10f));
-    auto* tc = array(Source{{12, 34}}, nullptr, nullptr,  //
-                     Call(ty.vec(nullptr, 2), 20_a),      //
-                     Call(ty.vec(nullptr, 2), 20_f));
+    auto* tc = array<Infer>(Source{{12, 34}},              //
+                            Call(ty.vec<Infer>(2), 20_a),  //
+                            Call(ty.vec<Infer>(2), 20_f));
     WrapInFunction(tc);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -671,7 +671,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArrayArgumentTypeMismatch_U32F32) {
     // array(0u, 1.0f, 20u);
-    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 0_u, 1_f, 20_u);
+    auto* tc = array<Infer>(Source{{12, 34}}, 0_u, 1_f, 20_u);
     WrapInFunction(tc);
 
     EXPECT_FALSE(r()->Resolve());
@@ -692,7 +692,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArrayArgumentTypeMismatch_F32I32) {
     // array(1f, 1i);
-    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 1_f, 1_i);
+    auto* tc = array<Infer>(Source{{12, 34}}, 1_f, 1_i);
     WrapInFunction(tc);
 
     EXPECT_FALSE(r()->Resolve());
@@ -713,7 +713,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArrayArgumentTypeMismatch_U32I32) {
     // array(1i, 0u, 0u, 0u, 0u, 0u);
-    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 1_i, 0_u, 0_u, 0_u, 0_u);
+    auto* tc = array<Infer>(Source{{12, 34}}, 1_i, 0_u, 0_u, 0_u, 0_u);
     WrapInFunction(tc);
 
     EXPECT_FALSE(r()->Resolve());
@@ -734,7 +734,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArrayArgumentTypeMismatch_I32Vec2) {
     // array(1i, vec2<i32>());
-    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 1_i, vec2<i32>());
+    auto* tc = array<Infer>(Source{{12, 34}}, 1_i, vec2<i32>());
     WrapInFunction(tc);
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -755,7 +755,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArrayArgumentTypeMismatch_Vec3i32_Vec3u32) {
     // array(vec3<i32>(), vec3<u32>());
-    auto* t = array(Source{{12, 34}}, nullptr, nullptr, vec3<i32>(), vec3<u32>());
+    auto* t = array<Infer>(Source{{12, 34}}, vec3<i32>(), vec3<u32>());
     WrapInFunction(t);
 
     EXPECT_FALSE(r()->Resolve());
@@ -767,7 +767,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArrayArgumentTypeMismatch_Vec3i32_Vec3AF) {
     // array(vec3<i32>(), vec3(1.0));
-    auto* t = array(Source{{12, 34}}, nullptr, nullptr, vec3<i32>(), Call(ty.vec3(nullptr), 1._a));
+    auto* t = array<Infer>(Source{{12, 34}}, vec3<i32>(), Call("vec3", 1._a));
     WrapInFunction(t);
 
     EXPECT_FALSE(r()->Resolve());
@@ -789,7 +789,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArrayArgumentTypeMismatch_Vec3i32_Vec3bool) {
     // array(vec3<i32>(), vec3<bool>());
-    auto* t = array(Source{{12, 34}}, nullptr, nullptr, vec3<i32>(), vec3<bool>());
+    auto* t = array<Infer>(Source{{12, 34}}, vec3<i32>(), vec3<bool>());
     WrapInFunction(t);
 
     EXPECT_FALSE(r()->Resolve());
@@ -811,7 +811,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArrayOfArray_SubElemSizeMismatch) {
     // array<array<i32, 2u>, 2u>(array<i32, 3u>(), array<i32, 2u>());
-    auto* t = array(Source{{12, 34}}, nullptr, nullptr, array<i32, 3>(), array<i32, 2>());
+    auto* t = array<Infer>(Source{{12, 34}}, array<i32, 3>(), array<i32, 2>());
     WrapInFunction(t);
 
     EXPECT_FALSE(r()->Resolve());
@@ -833,7 +833,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, InferredArrayOfArray_SubElemTypeMismatch) {
     // array<array<i32, 2u>, 2u>(array<i32, 2u>(), array<u32, 2u>());
-    auto* t = array(Source{{12, 34}}, nullptr, nullptr, array<i32, 2>(), array<u32, 2>());
+    auto* t = array<Infer>(Source{{12, 34}}, array<i32, 2>(), array<u32, 2>());
     WrapInFunction(t);
 
     EXPECT_FALSE(r()->Resolve());
@@ -869,7 +869,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, Array_Runtime) {
     // array<i32>(1i);
-    auto* tc = array(Source{{12, 34}}, ty.i32(), nullptr, Expr(1_i));
+    auto* tc = array<i32>(Source{{12, 34}}, Expr(1_i));
     WrapInFunction(tc);
 
     EXPECT_FALSE(r()->Resolve());
@@ -878,7 +878,7 @@
 
 TEST_F(ResolverTypeInitializerValidationTest, Array_RuntimeZeroValue) {
     // array<i32>();
-    auto* tc = array(Source{{12, 34}}, ty.i32(), nullptr);
+    auto* tc = array<i32>(Source{{12, 34}});
     WrapInFunction(tc);
 
     EXPECT_FALSE(r()->Resolve());
@@ -1992,7 +1992,7 @@
     auto* f32_alias = Alias("Float32", ty.f32());
 
     // vec2<Float32>(1.0f, 1u)
-    auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
+    auto vec_type = ty.vec(ty.Of(f32_alias), 2);
     WrapInFunction(Call(Source{{12, 34}}, vec_type, 1_f, 1_u));
 
     EXPECT_FALSE(r()->Resolve());
@@ -2004,7 +2004,7 @@
     auto* f32_alias = Alias("Float32", ty.f32());
 
     // vec2<Float32>(1.0f, 1.0f)
-    auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
+    auto vec_type = ty.vec(ty.Of(f32_alias), 2);
     auto* tc = Call(Source{{12, 34}}, vec_type, 1_f, 1_f);
     WrapInFunction(tc);
 
@@ -2015,7 +2015,7 @@
     auto* f32_alias = Alias("Float32", ty.f32());
 
     // vec3<u32>(vec<Float32>(), 1.0f)
-    auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
+    auto vec_type = ty.vec(ty.Of(f32_alias), 2);
     WrapInFunction(vec3<u32>(Source{{12, 34}}, Call(vec_type), 1_f));
 
     EXPECT_FALSE(r()->Resolve());
@@ -2027,7 +2027,7 @@
     auto* f32_alias = Alias("Float32", ty.f32());
 
     // vec3<f32>(vec<Float32>(), 1.0f)
-    auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
+    auto vec_type = ty.vec(ty.Of(f32_alias), 2);
     auto* tc = vec3<f32>(Call(Source{{12, 34}}, vec_type), 1_f);
     WrapInFunction(tc);
 
@@ -2037,11 +2037,11 @@
 TEST_F(ResolverTypeInitializerValidationTest, InferVec2ElementTypeFromScalars) {
     Enable(ast::Extension::kF16);
 
-    auto* vec2_bool = Call(create<ast::Vector>(nullptr, 2u), Expr(true), Expr(false));
-    auto* vec2_i32 = Call(create<ast::Vector>(nullptr, 2u), Expr(1_i), Expr(2_i));
-    auto* vec2_u32 = Call(create<ast::Vector>(nullptr, 2u), Expr(1_u), Expr(2_u));
-    auto* vec2_f32 = Call(create<ast::Vector>(nullptr, 2u), Expr(1_f), Expr(2_f));
-    auto* vec2_f16 = Call(create<ast::Vector>(nullptr, 2u), Expr(1_h), Expr(2_h));
+    auto* vec2_bool = vec2<Infer>(true, false);
+    auto* vec2_i32 = vec2<Infer>(1_i, 2_i);
+    auto* vec2_u32 = vec2<Infer>(1_u, 2_u);
+    auto* vec2_f32 = vec2<Infer>(1_f, 2_f);
+    auto* vec2_f16 = vec2<Infer>(1_h, 2_h);
     WrapInFunction(vec2_bool, vec2_i32, vec2_u32, vec2_f32, vec2_f16);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -2061,21 +2061,16 @@
     EXPECT_EQ(TypeOf(vec2_u32)->As<type::Vector>()->Width(), 2u);
     EXPECT_EQ(TypeOf(vec2_f32)->As<type::Vector>()->Width(), 2u);
     EXPECT_EQ(TypeOf(vec2_f16)->As<type::Vector>()->Width(), 2u);
-    EXPECT_EQ(TypeOf(vec2_bool), TypeOf(vec2_bool->target.type));
-    EXPECT_EQ(TypeOf(vec2_i32), TypeOf(vec2_i32->target.type));
-    EXPECT_EQ(TypeOf(vec2_u32), TypeOf(vec2_u32->target.type));
-    EXPECT_EQ(TypeOf(vec2_f32), TypeOf(vec2_f32->target.type));
-    EXPECT_EQ(TypeOf(vec2_f16), TypeOf(vec2_f16->target.type));
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, InferVec2ElementTypeFromVec2) {
     Enable(ast::Extension::kF16);
 
-    auto* vec2_bool = Call(create<ast::Vector>(nullptr, 2u), vec2<bool>(true, false));
-    auto* vec2_i32 = Call(create<ast::Vector>(nullptr, 2u), vec2<i32>(1_i, 2_i));
-    auto* vec2_u32 = Call(create<ast::Vector>(nullptr, 2u), vec2<u32>(1_u, 2_u));
-    auto* vec2_f32 = Call(create<ast::Vector>(nullptr, 2u), vec2<f32>(1_f, 2_f));
-    auto* vec2_f16 = Call(create<ast::Vector>(nullptr, 2u), vec2<f16>(1_h, 2_h));
+    auto* vec2_bool = vec2<Infer>(vec2<bool>(true, false));
+    auto* vec2_i32 = vec2<Infer>(vec2<i32>(1_i, 2_i));
+    auto* vec2_u32 = vec2<Infer>(vec2<u32>(1_u, 2_u));
+    auto* vec2_f32 = vec2<Infer>(vec2<f32>(1_f, 2_f));
+    auto* vec2_f16 = vec2<Infer>(vec2<f16>(1_h, 2_h));
     WrapInFunction(vec2_bool, vec2_i32, vec2_u32, vec2_f32, vec2_f16);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -2095,21 +2090,16 @@
     EXPECT_EQ(TypeOf(vec2_u32)->As<type::Vector>()->Width(), 2u);
     EXPECT_EQ(TypeOf(vec2_f32)->As<type::Vector>()->Width(), 2u);
     EXPECT_EQ(TypeOf(vec2_f16)->As<type::Vector>()->Width(), 2u);
-    EXPECT_EQ(TypeOf(vec2_bool), TypeOf(vec2_bool->target.type));
-    EXPECT_EQ(TypeOf(vec2_i32), TypeOf(vec2_i32->target.type));
-    EXPECT_EQ(TypeOf(vec2_u32), TypeOf(vec2_u32->target.type));
-    EXPECT_EQ(TypeOf(vec2_f32), TypeOf(vec2_f32->target.type));
-    EXPECT_EQ(TypeOf(vec2_f16), TypeOf(vec2_f16->target.type));
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, InferVec3ElementTypeFromScalars) {
     Enable(ast::Extension::kF16);
 
-    auto* vec3_bool = Call(create<ast::Vector>(nullptr, 3u), Expr(true), Expr(false), Expr(true));
-    auto* vec3_i32 = Call(create<ast::Vector>(nullptr, 3u), Expr(1_i), Expr(2_i), Expr(3_i));
-    auto* vec3_u32 = Call(create<ast::Vector>(nullptr, 3u), Expr(1_u), Expr(2_u), Expr(3_u));
-    auto* vec3_f32 = Call(create<ast::Vector>(nullptr, 3u), Expr(1_f), Expr(2_f), Expr(3_f));
-    auto* vec3_f16 = Call(create<ast::Vector>(nullptr, 3u), Expr(1_h), Expr(2_h), Expr(3_h));
+    auto* vec3_bool = vec3<Infer>(Expr(true), Expr(false), Expr(true));
+    auto* vec3_i32 = vec3<Infer>(Expr(1_i), Expr(2_i), Expr(3_i));
+    auto* vec3_u32 = vec3<Infer>(Expr(1_u), Expr(2_u), Expr(3_u));
+    auto* vec3_f32 = vec3<Infer>(Expr(1_f), Expr(2_f), Expr(3_f));
+    auto* vec3_f16 = vec3<Infer>(Expr(1_h), Expr(2_h), Expr(3_h));
     WrapInFunction(vec3_bool, vec3_i32, vec3_u32, vec3_f32, vec3_f16);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -2129,21 +2119,16 @@
     EXPECT_EQ(TypeOf(vec3_u32)->As<type::Vector>()->Width(), 3u);
     EXPECT_EQ(TypeOf(vec3_f32)->As<type::Vector>()->Width(), 3u);
     EXPECT_EQ(TypeOf(vec3_f16)->As<type::Vector>()->Width(), 3u);
-    EXPECT_EQ(TypeOf(vec3_bool), TypeOf(vec3_bool->target.type));
-    EXPECT_EQ(TypeOf(vec3_i32), TypeOf(vec3_i32->target.type));
-    EXPECT_EQ(TypeOf(vec3_u32), TypeOf(vec3_u32->target.type));
-    EXPECT_EQ(TypeOf(vec3_f32), TypeOf(vec3_f32->target.type));
-    EXPECT_EQ(TypeOf(vec3_f16), TypeOf(vec3_f16->target.type));
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, InferVec3ElementTypeFromVec3) {
     Enable(ast::Extension::kF16);
 
-    auto* vec3_bool = Call(create<ast::Vector>(nullptr, 3u), vec3<bool>(true, false, true));
-    auto* vec3_i32 = Call(create<ast::Vector>(nullptr, 3u), vec3<i32>(1_i, 2_i, 3_i));
-    auto* vec3_u32 = Call(create<ast::Vector>(nullptr, 3u), vec3<u32>(1_u, 2_u, 3_u));
-    auto* vec3_f32 = Call(create<ast::Vector>(nullptr, 3u), vec3<f32>(1_f, 2_f, 3_f));
-    auto* vec3_f16 = Call(create<ast::Vector>(nullptr, 3u), vec3<f16>(1_h, 2_h, 3_h));
+    auto* vec3_bool = vec3<Infer>(vec3<bool>(true, false, true));
+    auto* vec3_i32 = vec3<Infer>(vec3<i32>(1_i, 2_i, 3_i));
+    auto* vec3_u32 = vec3<Infer>(vec3<u32>(1_u, 2_u, 3_u));
+    auto* vec3_f32 = vec3<Infer>(vec3<f32>(1_f, 2_f, 3_f));
+    auto* vec3_f16 = vec3<Infer>(vec3<f16>(1_h, 2_h, 3_h));
     WrapInFunction(vec3_bool, vec3_i32, vec3_u32, vec3_f32, vec3_f16);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -2163,21 +2148,16 @@
     EXPECT_EQ(TypeOf(vec3_u32)->As<type::Vector>()->Width(), 3u);
     EXPECT_EQ(TypeOf(vec3_f32)->As<type::Vector>()->Width(), 3u);
     EXPECT_EQ(TypeOf(vec3_f16)->As<type::Vector>()->Width(), 3u);
-    EXPECT_EQ(TypeOf(vec3_bool), TypeOf(vec3_bool->target.type));
-    EXPECT_EQ(TypeOf(vec3_i32), TypeOf(vec3_i32->target.type));
-    EXPECT_EQ(TypeOf(vec3_u32), TypeOf(vec3_u32->target.type));
-    EXPECT_EQ(TypeOf(vec3_f32), TypeOf(vec3_f32->target.type));
-    EXPECT_EQ(TypeOf(vec3_f16), TypeOf(vec3_f16->target.type));
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, InferVec3ElementTypeFromScalarAndVec2) {
     Enable(ast::Extension::kF16);
 
-    auto* vec3_bool = Call(create<ast::Vector>(nullptr, 3u), Expr(true), vec2<bool>(false, true));
-    auto* vec3_i32 = Call(create<ast::Vector>(nullptr, 3u), Expr(1_i), vec2<i32>(2_i, 3_i));
-    auto* vec3_u32 = Call(create<ast::Vector>(nullptr, 3u), Expr(1_u), vec2<u32>(2_u, 3_u));
-    auto* vec3_f32 = Call(create<ast::Vector>(nullptr, 3u), Expr(1_f), vec2<f32>(2_f, 3_f));
-    auto* vec3_f16 = Call(create<ast::Vector>(nullptr, 3u), Expr(1_h), vec2<f16>(2_h, 3_h));
+    auto* vec3_bool = vec3<Infer>(Expr(true), vec2<bool>(false, true));
+    auto* vec3_i32 = vec3<Infer>(Expr(1_i), vec2<i32>(2_i, 3_i));
+    auto* vec3_u32 = vec3<Infer>(Expr(1_u), vec2<u32>(2_u, 3_u));
+    auto* vec3_f32 = vec3<Infer>(Expr(1_f), vec2<f32>(2_f, 3_f));
+    auto* vec3_f16 = vec3<Infer>(Expr(1_h), vec2<f16>(2_h, 3_h));
     WrapInFunction(vec3_bool, vec3_i32, vec3_u32, vec3_f32, vec3_f16);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -2197,26 +2177,16 @@
     EXPECT_EQ(TypeOf(vec3_u32)->As<type::Vector>()->Width(), 3u);
     EXPECT_EQ(TypeOf(vec3_f32)->As<type::Vector>()->Width(), 3u);
     EXPECT_EQ(TypeOf(vec3_f16)->As<type::Vector>()->Width(), 3u);
-    EXPECT_EQ(TypeOf(vec3_bool), TypeOf(vec3_bool->target.type));
-    EXPECT_EQ(TypeOf(vec3_i32), TypeOf(vec3_i32->target.type));
-    EXPECT_EQ(TypeOf(vec3_u32), TypeOf(vec3_u32->target.type));
-    EXPECT_EQ(TypeOf(vec3_f32), TypeOf(vec3_f32->target.type));
-    EXPECT_EQ(TypeOf(vec3_f16), TypeOf(vec3_f16->target.type));
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, InferVec4ElementTypeFromScalars) {
     Enable(ast::Extension::kF16);
 
-    auto* vec4_bool =
-        Call(create<ast::Vector>(nullptr, 4u), Expr(true), Expr(false), Expr(true), Expr(false));
-    auto* vec4_i32 =
-        Call(create<ast::Vector>(nullptr, 4u), Expr(1_i), Expr(2_i), Expr(3_i), Expr(4_i));
-    auto* vec4_u32 =
-        Call(create<ast::Vector>(nullptr, 4u), Expr(1_u), Expr(2_u), Expr(3_u), Expr(4_u));
-    auto* vec4_f32 =
-        Call(create<ast::Vector>(nullptr, 4u), Expr(1_f), Expr(2_f), Expr(3_f), Expr(4_f));
-    auto* vec4_f16 =
-        Call(create<ast::Vector>(nullptr, 4u), Expr(1_h), Expr(2_h), Expr(3_h), Expr(4_h));
+    auto* vec4_bool = vec4<Infer>(Expr(true), Expr(false), Expr(true), Expr(false));
+    auto* vec4_i32 = vec4<Infer>(Expr(1_i), Expr(2_i), Expr(3_i), Expr(4_i));
+    auto* vec4_u32 = vec4<Infer>(Expr(1_u), Expr(2_u), Expr(3_u), Expr(4_u));
+    auto* vec4_f32 = vec4<Infer>(Expr(1_f), Expr(2_f), Expr(3_f), Expr(4_f));
+    auto* vec4_f16 = vec4<Infer>(Expr(1_h), Expr(2_h), Expr(3_h), Expr(4_h));
     WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32, vec4_f16);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -2236,21 +2206,16 @@
     EXPECT_EQ(TypeOf(vec4_u32)->As<type::Vector>()->Width(), 4u);
     EXPECT_EQ(TypeOf(vec4_f32)->As<type::Vector>()->Width(), 4u);
     EXPECT_EQ(TypeOf(vec4_f16)->As<type::Vector>()->Width(), 4u);
-    EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
-    EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
-    EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
-    EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
-    EXPECT_EQ(TypeOf(vec4_f16), TypeOf(vec4_f16->target.type));
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, InferVec4ElementTypeFromVec4) {
     Enable(ast::Extension::kF16);
 
-    auto* vec4_bool = Call(create<ast::Vector>(nullptr, 4u), vec4<bool>(true, false, true, false));
-    auto* vec4_i32 = Call(create<ast::Vector>(nullptr, 4u), vec4<i32>(1_i, 2_i, 3_i, 4_i));
-    auto* vec4_u32 = Call(create<ast::Vector>(nullptr, 4u), vec4<u32>(1_u, 2_u, 3_u, 4_u));
-    auto* vec4_f32 = Call(create<ast::Vector>(nullptr, 4u), vec4<f32>(1_f, 2_f, 3_f, 4_f));
-    auto* vec4_f16 = Call(create<ast::Vector>(nullptr, 4u), vec4<f16>(1_h, 2_h, 3_h, 4_h));
+    auto* vec4_bool = vec4<Infer>(vec4<bool>(true, false, true, false));
+    auto* vec4_i32 = vec4<Infer>(vec4<i32>(1_i, 2_i, 3_i, 4_i));
+    auto* vec4_u32 = vec4<Infer>(vec4<u32>(1_u, 2_u, 3_u, 4_u));
+    auto* vec4_f32 = vec4<Infer>(vec4<f32>(1_f, 2_f, 3_f, 4_f));
+    auto* vec4_f16 = vec4<Infer>(vec4<f16>(1_h, 2_h, 3_h, 4_h));
     WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32, vec4_f16);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -2270,22 +2235,16 @@
     EXPECT_EQ(TypeOf(vec4_u32)->As<type::Vector>()->Width(), 4u);
     EXPECT_EQ(TypeOf(vec4_f32)->As<type::Vector>()->Width(), 4u);
     EXPECT_EQ(TypeOf(vec4_f16)->As<type::Vector>()->Width(), 4u);
-    EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
-    EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
-    EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
-    EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
-    EXPECT_EQ(TypeOf(vec4_f16), TypeOf(vec4_f16->target.type));
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, InferVec4ElementTypeFromScalarAndVec3) {
     Enable(ast::Extension::kF16);
 
-    auto* vec4_bool =
-        Call(create<ast::Vector>(nullptr, 4u), Expr(true), vec3<bool>(false, true, false));
-    auto* vec4_i32 = Call(create<ast::Vector>(nullptr, 4u), Expr(1_i), vec3<i32>(2_i, 3_i, 4_i));
-    auto* vec4_u32 = Call(create<ast::Vector>(nullptr, 4u), Expr(1_u), vec3<u32>(2_u, 3_u, 4_u));
-    auto* vec4_f32 = Call(create<ast::Vector>(nullptr, 4u), Expr(1_f), vec3<f32>(2_f, 3_f, 4_f));
-    auto* vec4_f16 = Call(create<ast::Vector>(nullptr, 4u), Expr(1_h), vec3<f16>(2_h, 3_h, 4_h));
+    auto* vec4_bool = vec4<Infer>(Expr(true), vec3<bool>(false, true, false));
+    auto* vec4_i32 = vec4<Infer>(Expr(1_i), vec3<i32>(2_i, 3_i, 4_i));
+    auto* vec4_u32 = vec4<Infer>(Expr(1_u), vec3<u32>(2_u, 3_u, 4_u));
+    auto* vec4_f32 = vec4<Infer>(Expr(1_f), vec3<f32>(2_f, 3_f, 4_f));
+    auto* vec4_f16 = vec4<Infer>(Expr(1_h), vec3<f16>(2_h, 3_h, 4_h));
     WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32, vec4_f16);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -2305,26 +2264,16 @@
     EXPECT_EQ(TypeOf(vec4_u32)->As<type::Vector>()->Width(), 4u);
     EXPECT_EQ(TypeOf(vec4_f32)->As<type::Vector>()->Width(), 4u);
     EXPECT_EQ(TypeOf(vec4_f16)->As<type::Vector>()->Width(), 4u);
-    EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
-    EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
-    EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
-    EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
-    EXPECT_EQ(TypeOf(vec4_f16), TypeOf(vec4_f16->target.type));
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, InferVec4ElementTypeFromVec2AndVec2) {
     Enable(ast::Extension::kF16);
 
-    auto* vec4_bool =
-        Call(create<ast::Vector>(nullptr, 4u), vec2<bool>(true, false), vec2<bool>(true, false));
-    auto* vec4_i32 =
-        Call(create<ast::Vector>(nullptr, 4u), vec2<i32>(1_i, 2_i), vec2<i32>(3_i, 4_i));
-    auto* vec4_u32 =
-        Call(create<ast::Vector>(nullptr, 4u), vec2<u32>(1_u, 2_u), vec2<u32>(3_u, 4_u));
-    auto* vec4_f32 =
-        Call(create<ast::Vector>(nullptr, 4u), vec2<f32>(1_f, 2_f), vec2<f32>(3_f, 4_f));
-    auto* vec4_f16 =
-        Call(create<ast::Vector>(nullptr, 4u), vec2<f16>(1_h, 2_h), vec2<f16>(3_h, 4_h));
+    auto* vec4_bool = vec4<Infer>(vec2<bool>(true, false), vec2<bool>(true, false));
+    auto* vec4_i32 = vec4<Infer>(vec2<i32>(1_i, 2_i), vec2<i32>(3_i, 4_i));
+    auto* vec4_u32 = vec4<Infer>(vec2<u32>(1_u, 2_u), vec2<u32>(3_u, 4_u));
+    auto* vec4_f32 = vec4<Infer>(vec2<f32>(1_f, 2_f), vec2<f32>(3_f, 4_f));
+    auto* vec4_f16 = vec4<Infer>(vec2<f16>(1_h, 2_h), vec2<f16>(3_h, 4_h));
     WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32, vec4_f16);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -2344,22 +2293,17 @@
     EXPECT_EQ(TypeOf(vec4_u32)->As<type::Vector>()->Width(), 4u);
     EXPECT_EQ(TypeOf(vec4_f32)->As<type::Vector>()->Width(), 4u);
     EXPECT_EQ(TypeOf(vec4_f16)->As<type::Vector>()->Width(), 4u);
-    EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
-    EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
-    EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
-    EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
-    EXPECT_EQ(TypeOf(vec4_f16), TypeOf(vec4_f16->target.type));
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, CannotInferVectorElementTypeWithoutArgs) {
-    WrapInFunction(Call(Source{{12, 34}}, create<ast::Vector>(nullptr, 3u)));
+    WrapInFunction(Call(Source{{12, 34}}, "vec3"));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(), HasSubstr("12:34 error: no matching initializer for vec3()"));
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, CannotInferVec2ElementTypeFromScalarsMismatch) {
-    WrapInFunction(Call(Source{{1, 1}}, create<ast::Vector>(nullptr, 2u),
+    WrapInFunction(Call(Source{{1, 1}}, "vec2",     //
                         Expr(Source{{1, 2}}, 1_i),  //
                         Expr(Source{{1, 3}}, 2_u)));
 
@@ -2368,7 +2312,7 @@
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, CannotInferVec3ElementTypeFromScalarsMismatch) {
-    WrapInFunction(Call(Source{{1, 1}}, create<ast::Vector>(nullptr, 3u),
+    WrapInFunction(Call(Source{{1, 1}}, "vec3",     //
                         Expr(Source{{1, 2}}, 1_i),  //
                         Expr(Source{{1, 3}}, 2_u),  //
                         Expr(Source{{1, 4}}, 3_i)));
@@ -2379,7 +2323,7 @@
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, CannotInferVec3ElementTypeFromScalarAndVec2Mismatch) {
-    WrapInFunction(Call(Source{{1, 1}}, create<ast::Vector>(nullptr, 3u),
+    WrapInFunction(Call(Source{{1, 1}}, "vec3",     //
                         Expr(Source{{1, 2}}, 1_i),  //
                         Call(Source{{1, 3}}, ty.vec2<f32>(), 2_f, 3_f)));
 
@@ -2389,7 +2333,7 @@
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, CannotInferVec4ElementTypeFromScalarsMismatch) {
-    WrapInFunction(Call(Source{{1, 1}}, create<ast::Vector>(nullptr, 4u),
+    WrapInFunction(Call(Source{{1, 1}}, "vec4",     //
                         Expr(Source{{1, 2}}, 1_i),  //
                         Expr(Source{{1, 3}}, 2_i),  //
                         Expr(Source{{1, 4}}, 3_f),  //
@@ -2401,7 +2345,7 @@
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, CannotInferVec4ElementTypeFromScalarAndVec3Mismatch) {
-    WrapInFunction(Call(Source{{1, 1}}, create<ast::Vector>(nullptr, 4u),
+    WrapInFunction(Call(Source{{1, 1}}, "vec4",     //
                         Expr(Source{{1, 2}}, 1_i),  //
                         Call(Source{{1, 3}}, ty.vec3<u32>(), 2_u, 3_u, 4_u)));
 
@@ -2411,7 +2355,7 @@
 }
 
 TEST_F(ResolverTypeInitializerValidationTest, CannotInferVec4ElementTypeFromVec2AndVec2Mismatch) {
-    WrapInFunction(Call(Source{{1, 1}}, create<ast::Vector>(nullptr, 4u),
+    WrapInFunction(Call(Source{{1, 1}}, "vec4",                          //
                         Call(Source{{1, 2}}, ty.vec2<i32>(), 3_i, 4_i),  //
                         Call(Source{{1, 3}}, ty.vec2<u32>(), 3_u, 4_u)));
 
@@ -2468,7 +2412,7 @@
     std::stringstream args_tys;
     utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns - 1; i++) {
-        auto* vec_type = param.create_column_ast_type(*this);
+        ast::Type vec_type = param.create_column_ast_type(*this);
         args.Push(Call(vec_type));
         if (i > 0) {
             args_tys << ", ";
@@ -2476,7 +2420,7 @@
         args_tys << "vec" << param.rows << "<" + element_type_name + ">";
     }
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
     auto* tc = Call(Source{{12, 34}}, matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2504,7 +2448,7 @@
         args_tys << element_type_name;
     }
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
     auto* tc = Call(Source{{12, 34}}, matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2525,7 +2469,7 @@
     std::stringstream args_tys;
     utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns + 1; i++) {
-        auto* vec_type = param.create_column_ast_type(*this);
+        ast::Type vec_type = param.create_column_ast_type(*this);
         args.Push(Call(vec_type));
         if (i > 0) {
             args_tys << ", ";
@@ -2533,7 +2477,7 @@
         args_tys << "vec" << param.rows << "<" + element_type_name + ">";
     }
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
     auto* tc = Call(Source{{12, 34}}, matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2561,7 +2505,7 @@
         args_tys << element_type_name;
     }
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
     auto* tc = Call(Source{{12, 34}}, matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2581,7 +2525,7 @@
     std::stringstream args_tys;
     utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns; i++) {
-        auto* vec_type = ty.vec<u32>(param.rows);
+        auto vec_type = ty.vec<u32>(param.rows);
         args.Push(Call(vec_type));
         if (i > 0) {
             args_tys << ", ";
@@ -2589,7 +2533,7 @@
         args_tys << "vec" << param.rows << "<u32>";
     }
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
     auto* tc = Call(Source{{12, 34}}, matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2616,7 +2560,7 @@
         args_tys << "u32";
     }
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
     auto* tc = Call(Source{{12, 34}}, matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2642,7 +2586,7 @@
     std::stringstream args_tys;
     utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns; i++) {
-        auto* valid_vec_type = param.create_column_ast_type(*this);
+        ast::Type valid_vec_type = param.create_column_ast_type(*this);
         args.Push(Call(valid_vec_type));
         if (i > 0) {
             args_tys << ", ";
@@ -2650,11 +2594,11 @@
         args_tys << "vec" << param.rows << "<" + element_type_name + ">";
     }
     const size_t kInvalidLoc = 2 * (param.columns - 1);
-    auto* invalid_vec_type = ty.vec(param.create_element_ast_type(*this), param.rows - 1);
+    auto invalid_vec_type = ty.vec(param.create_element_ast_type(*this), param.rows - 1);
     args.Push(Call(Source{{12, kInvalidLoc}}, invalid_vec_type));
     args_tys << ", vec" << (param.rows - 1) << "<" + element_type_name + ">";
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
     auto* tc = Call(Source{{12, 34}}, matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2680,18 +2624,18 @@
     std::stringstream args_tys;
     utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns; i++) {
-        auto* valid_vec_type = param.create_column_ast_type(*this);
+        ast::Type valid_vec_type = param.create_column_ast_type(*this);
         args.Push(Call(valid_vec_type));
         if (i > 0) {
             args_tys << ", ";
         }
         args_tys << "vec" << param.rows << "<" + element_type_name + ">";
     }
-    auto* invalid_vec_type = ty.vec(param.create_element_ast_type(*this), param.rows + 1);
+    auto invalid_vec_type = ty.vec(param.create_element_ast_type(*this), param.rows + 1);
     args.Push(Call(invalid_vec_type));
     args_tys << ", vec" << (param.rows + 1) << "<" + element_type_name + ">";
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
     auto* tc = Call(Source{{12, 34}}, matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2708,7 +2652,7 @@
 
     Enable(ast::Extension::kF16);
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
     auto* tc = Call(Source{{12, 40}}, matrix_type);
     WrapInFunction(tc);
 
@@ -2725,11 +2669,11 @@
 
     utils::Vector<const ast::Expression*, 4> args;
     for (uint32_t i = 0; i < param.columns; i++) {
-        auto* vec_type = param.create_column_ast_type(*this);
+        ast::Type vec_type = param.create_column_ast_type(*this);
         args.Push(Call(vec_type));
     }
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
     auto* tc = Call(matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2749,7 +2693,7 @@
         args.Push(Call(param.create_element_ast_type(*this)));
     }
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
     auto* tc = Call(matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2769,7 +2713,7 @@
     std::stringstream args_tys;
     utils::Vector<const ast::Expression*, 4> args;
     for (uint32_t i = 0; i < param.columns; i++) {
-        auto* vec_type = ty.vec(ty.u32(), param.rows);
+        auto vec_type = ty.vec(ty.u32(), param.rows);
         args.Push(Call(vec_type));
         if (i > 0) {
             args_tys << ", ";
@@ -2777,7 +2721,7 @@
         args_tys << "vec" << param.rows << "<u32>";
     }
 
-    auto* matrix_type = ty.mat(ty.Of(elem_type_alias), param.columns, param.rows);
+    auto matrix_type = ty.mat(ty.Of(elem_type_alias), param.columns, param.rows);
     auto* tc = Call(Source{{12, 34}}, matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2798,11 +2742,11 @@
 
     utils::Vector<const ast::Expression*, 8> args;
     for (uint32_t i = 0; i < param.columns; i++) {
-        auto* vec_type = param.create_column_ast_type(*this);
+        ast::Type vec_type = param.create_column_ast_type(*this);
         args.Push(Call(vec_type));
     }
 
-    auto* matrix_type = ty.mat(ty.Of(elem_type_alias), param.columns, param.rows);
+    auto matrix_type = ty.mat(ty.Of(elem_type_alias), param.columns, param.rows);
     auto* tc = Call(Source{}, matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2825,8 +2769,8 @@
 
     Enable(ast::Extension::kF16);
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
-    auto* vec_type = param.create_column_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
+    ast::Type vec_type = param.create_column_ast_type(*this);
     auto* vec_alias = Alias("ColVectorAlias", vec_type);
 
     utils::Vector<const ast::Expression*, 4> args;
@@ -2845,13 +2789,13 @@
 
     Enable(ast::Extension::kF16);
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
     auto* u32_type_alias = Alias("UnsignedInt", ty.u32());
 
     std::stringstream args_tys;
     utils::Vector<const ast::Expression*, 4> args;
     for (uint32_t i = 0; i < param.columns; i++) {
-        auto* vec_type = ty.vec(ty.Of(u32_type_alias), param.rows);
+        auto vec_type = ty.vec(ty.Of(u32_type_alias), param.rows);
         args.Push(Call(vec_type));
         if (i > 0) {
             args_tys << ", ";
@@ -2876,11 +2820,11 @@
 
     utils::Vector<const ast::Expression*, 4> args;
     for (uint32_t i = 0; i < param.columns; i++) {
-        auto* vec_type = ty.vec(ty.Of(elem_type_alias), param.rows);
+        auto vec_type = ty.vec(ty.Of(elem_type_alias), param.rows);
         args.Push(Call(vec_type));
     }
 
-    auto* matrix_type = param.create_mat_ast_type(*this);
+    ast::Type matrix_type = param.create_mat_ast_type(*this);
     auto* tc = Call(Source{}, matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2897,7 +2841,7 @@
         args.Push(Call(param.create_column_ast_type(*this)));
     }
 
-    auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
+    auto matrix_type = ty.mat<Infer>(param.columns, param.rows);
     auto* tc = Call(Source{}, matrix_type, std::move(args));
     WrapInFunction(tc);
 
@@ -2914,7 +2858,7 @@
         args.Push(param.create_element_ast_value(*this, static_cast<double>(i)));
     }
 
-    auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
+    auto matrix_type = ty.mat<Infer>(param.columns, param.rows);
     WrapInFunction(Call(Source{{12, 34}}, matrix_type, std::move(args)));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -2944,7 +2888,7 @@
         }
     }
 
-    auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
+    auto matrix_type = ty.mat<Infer>(param.columns, param.rows);
     WrapInFunction(Call(Source{{12, 34}}, matrix_type, std::move(args)));
 
     EXPECT_FALSE(r()->Resolve());
@@ -2976,7 +2920,7 @@
 
     err << ")";
 
-    auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
+    auto matrix_type = ty.mat<Infer>(param.columns, param.rows);
     WrapInFunction(Call(Source{{12, 34}}, matrix_type, std::move(args)));
 
     EXPECT_FALSE(r()->Resolve());
@@ -3049,7 +2993,7 @@
     utils::Vector<const ast::StructMember*, 16> members;
     utils::Vector<const ast::Expression*, 16> values;
     for (uint32_t i = 0; i < N; i++) {
-        auto* struct_type = str_params.ast(*this);
+        ast::Type struct_type = str_params.ast(*this);
         members.Push(Member("member_" + std::to_string(i), struct_type));
         if (i < N - 1) {
             auto* ctor_value_expr = str_params.expr_from_double(*this, 0);
@@ -3075,7 +3019,7 @@
     utils::Vector<const ast::Expression*, 8> values;
     for (uint32_t i = 0; i < N + 1; i++) {
         if (i < N) {
-            auto* struct_type = str_params.ast(*this);
+            ast::Type struct_type = str_params.ast(*this);
             members.Push(Member("member_" + std::to_string(i), struct_type));
         }
         auto* ctor_value_expr = str_params.expr_from_double(*this, 0);
@@ -3113,7 +3057,7 @@
     // make the last value of the initializer to have a different type
     uint32_t initializer_value_with_different_type = N - 1;
     for (uint32_t i = 0; i < N; i++) {
-        auto* struct_type = str_params.ast(*this);
+        ast::Type struct_type = str_params.ast(*this);
         members.Push(Member("member_" + std::to_string(i), struct_type));
         auto* ctor_value_expr = (i == initializer_value_with_different_type)
                                     ? ctor_params.expr_from_double(*this, 0)
@@ -3124,11 +3068,9 @@
     auto* tc = Call(ty.Of(s), values);
     WrapInFunction(tc);
 
-    std::string found = FriendlyName(ctor_params.ast(*this));
-    std::string expected = FriendlyName(str_params.ast(*this));
     std::stringstream err;
     err << "error: type in struct initializer does not match struct member ";
-    err << "type: expected '" << expected << "', found '" << found << "'";
+    err << "type: expected '" << str_params.name() << "', found '" << ctor_params.name() << "'";
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), err.str());
 }
diff --git a/src/tint/resolver/type_validation_test.cc b/src/tint/resolver/type_validation_test.cc
index 70b3837..0e69da8 100644
--- a/src/tint/resolver/type_validation_test.cc
+++ b/src/tint/resolver/type_validation_test.cc
@@ -612,11 +612,11 @@
     // };
 
     Structure("S", utils::Vector{
-                       Member("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3u)),
+                       Member("a", ty.vec3<Infer>(Source{{12, 34}})),
                    });
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+    EXPECT_EQ(r()->error(), "12:34 error: expected '<' for 'vec3'");
 }
 
 TEST_F(ResolverTypeValidationTest, Struct_Member_MatrixNoType) {
@@ -624,11 +624,11 @@
     //   a: mat3x3;
     // };
     Structure("S", utils::Vector{
-                       Member("a", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3u, 3u)),
+                       Member("a", ty.mat3x3<Infer>(Source{{12, 34}})),
                    });
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
+    EXPECT_EQ(r()->error(), "12:34 error: expected '<' for 'mat3x3'");
 }
 
 TEST_F(ResolverTypeValidationTest, Struct_TooBig) {
@@ -641,11 +641,9 @@
     // }
 
     Structure(Source{{10, 34}}, "Bar", utils::Vector{Member("a", ty.array<f32, 10000>())});
-    Structure(
-        Source{{12, 34}}, "Foo",
-        utils::Vector{
-            Member("a", ty.array(ty(Source{{12, 30}}, "Bar"), Expr(Source{{12, 34}}, 65535_a))),
-            Member("b", ty.array(ty(Source{{12, 30}}, "Bar"), Expr(Source{{12, 34}}, 65535_a)))});
+    Structure(Source{{12, 34}}, "Foo",
+              utils::Vector{Member("a", ty.array(ty(Source{{12, 30}}, "Bar"), Expr(65535_a))),
+                            Member("b", ty.array(ty("Bar"), Expr(65535_a)))});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
@@ -865,7 +863,7 @@
 }
 
 TEST_F(ResolverTypeValidationTest, ArrayOfNonStorableType) {
-    auto* tex_ty = ty.sampled_texture(Source{{12, 34}}, type::TextureDimension::k2d, ty.f32());
+    auto tex_ty = ty.sampled_texture(Source{{12, 34}}, type::TextureDimension::k2d, ty.f32());
     GlobalVar("arr", ty.array(tex_ty, 4_i), type::AddressSpace::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
@@ -874,7 +872,7 @@
 }
 
 TEST_F(ResolverTypeValidationTest, ArrayOfNonStorableTypeWithStride) {
-    auto* ptr_ty = ty.pointer<u32>(Source{{12, 34}}, type::AddressSpace::kUniform);
+    auto ptr_ty = ty.pointer<u32>(Source{{12, 34}}, type::AddressSpace::kUniform);
     GlobalVar("arr", ty.array(ptr_ty, 4_i, utils::Vector{Stride(16)}),
               type::AddressSpace::kPrivate);
 
@@ -922,7 +920,7 @@
 TEST_P(CanonicalTest, All) {
     auto& params = GetParam();
 
-    auto* type = params.create_ast_type(*this);
+    ast::Type type = params.create_ast_type(*this);
 
     auto* var = Var("v", type);
     auto* expr = Expr("v");
@@ -941,15 +939,11 @@
 }  // namespace GetCanonicalTests
 
 namespace SampledTextureTests {
-struct DimensionParams {
-    type::TextureDimension dim;
-    bool is_valid;
-};
 
-using SampledTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
+using SampledTextureDimensionTest = ResolverTestWithParam<type::TextureDimension>;
 TEST_P(SampledTextureDimensionTest, All) {
     auto& params = GetParam();
-    GlobalVar(Source{{12, 34}}, "a", ty.sampled_texture(params.dim, ty.i32()), Group(0_a),
+    GlobalVar(Source{{12, 34}}, "a", ty.sampled_texture(params, ty.i32()), Group(0_a),
               Binding(0_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -957,35 +951,24 @@
 INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
                          SampledTextureDimensionTest,
                          testing::Values(  //
-                             DimensionParams{type::TextureDimension::k1d, true},
-                             DimensionParams{type::TextureDimension::k2d, true},
-                             DimensionParams{type::TextureDimension::k2dArray, true},
-                             DimensionParams{type::TextureDimension::k3d, true},
-                             DimensionParams{type::TextureDimension::kCube, true},
-                             DimensionParams{type::TextureDimension::kCubeArray, true}));
+                             type::TextureDimension::k1d,
+                             type::TextureDimension::k2d,
+                             type::TextureDimension::k2dArray,
+                             type::TextureDimension::k3d,
+                             type::TextureDimension::kCube,
+                             type::TextureDimension::kCubeArray));
 
-using MultisampledTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
+using MultisampledTextureDimensionTest = ResolverTestWithParam<type::TextureDimension>;
 TEST_P(MultisampledTextureDimensionTest, All) {
     auto& params = GetParam();
-    GlobalVar("a", ty.multisampled_texture(Source{{12, 34}}, params.dim, ty.i32()), Group(0_a),
+    GlobalVar("a", ty.multisampled_texture(Source{{12, 34}}, params, ty.i32()), Group(0_a),
               Binding(0_a));
 
-    if (params.is_valid) {
-        EXPECT_TRUE(r()->Resolve()) << r()->error();
-    } else {
-        EXPECT_FALSE(r()->Resolve());
-        EXPECT_EQ(r()->error(), "12:34 error: only 2d multisampled textures are supported");
-    }
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
                          MultisampledTextureDimensionTest,
-                         testing::Values(  //
-                             DimensionParams{type::TextureDimension::k1d, false},
-                             DimensionParams{type::TextureDimension::k2d, true},
-                             DimensionParams{type::TextureDimension::k2dArray, false},
-                             DimensionParams{type::TextureDimension::k3d, false},
-                             DimensionParams{type::TextureDimension::kCube, false},
-                             DimensionParams{type::TextureDimension::kCubeArray, false}));
+                         testing::Values(type::TextureDimension::k2d));
 
 struct TypeParams {
     builder::ast_type_func_ptr type_func;
@@ -1082,8 +1065,8 @@
     // var a : texture_storage_*<r32uint, write>;
     auto& params = GetParam();
 
-    auto* st = ty(Source{{12, 34}}, params.name, utils::ToString(type::TexelFormat::kR32Uint),
-                  utils::ToString(type::Access::kWrite));
+    auto st = ty(Source{{12, 34}}, params.name, utils::ToString(type::TexelFormat::kR32Uint),
+                 utils::ToString(type::Access::kWrite));
 
     GlobalVar("a", st, Group(0_a), Binding(0_a));
 
@@ -1133,19 +1116,19 @@
     // @group(0) @binding(3)
     // var d : texture_storage_3d<*, write>;
 
-    auto* st_a = ty.storage_texture(Source{{12, 34}}, type::TextureDimension::k1d, params.format,
-                                    type::Access::kWrite);
+    auto st_a = ty.storage_texture(Source{{12, 34}}, type::TextureDimension::k1d, params.format,
+                                   type::Access::kWrite);
     GlobalVar("a", st_a, Group(0_a), Binding(0_a));
 
-    auto* st_b =
+    ast::Type st_b =
         ty.storage_texture(type::TextureDimension::k2d, params.format, type::Access::kWrite);
     GlobalVar("b", st_b, Group(0_a), Binding(1_a));
 
-    auto* st_c =
+    ast::Type st_c =
         ty.storage_texture(type::TextureDimension::k2dArray, params.format, type::Access::kWrite);
     GlobalVar("c", st_c, Group(0_a), Binding(2_a));
 
-    auto* st_d =
+    ast::Type st_d =
         ty.storage_texture(type::TextureDimension::k3d, params.format, type::Access::kWrite);
     GlobalVar("d", st_d, Group(0_a), Binding(3_a));
 
@@ -1155,7 +1138,7 @@
         EXPECT_FALSE(r()->Resolve());
         EXPECT_EQ(r()->error(),
                   "12:34 error: image format must be one of the texel formats specified for "
-                  "storage textues in https://gpuweb.github.io/gpuweb/wgsl/#texel-formats");
+                  "storage textures in https://gpuweb.github.io/gpuweb/wgsl/#texel-formats");
     }
 }
 INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
@@ -1168,7 +1151,7 @@
     // @group(0) @binding(0)
     // var a : texture_storage_1d<r32uint>;
 
-    auto* st = ty(Source{{12, 34}}, "texture_storage_1d");
+    auto st = ty(Source{{12, 34}}, "texture_storage_1d");
 
     GlobalVar("a", st, Group(0_a), Binding(0_a));
 
@@ -1180,7 +1163,7 @@
     // @group(0) @binding(0)
     // var a : texture_storage_1d<r32uint>;
 
-    auto* st = ty(Source{{12, 34}}, "texture_storage_1d", "r32uint");
+    auto st = ty(Source{{12, 34}}, "texture_storage_1d", "r32uint");
 
     GlobalVar("a", st, Group(0_a), Binding(0_a));
 
@@ -1192,8 +1175,8 @@
     // @group(0) @binding(0)
     // var a : texture_storage_1d<r32uint, read_write>;
 
-    auto* st = ty.storage_texture(Source{{12, 34}}, type::TextureDimension::k1d,
-                                  type::TexelFormat::kR32Uint, type::Access::kReadWrite);
+    auto st = ty.storage_texture(Source{{12, 34}}, type::TextureDimension::k1d,
+                                 type::TexelFormat::kR32Uint, type::Access::kReadWrite);
 
     GlobalVar("a", st, Group(0_a), Binding(0_a));
 
@@ -1206,8 +1189,8 @@
     // @group(0) @binding(0)
     // var a : texture_storage_1d<r32uint, read>;
 
-    auto* st = ty.storage_texture(Source{{12, 34}}, type::TextureDimension::k1d,
-                                  type::TexelFormat::kR32Uint, type::Access::kRead);
+    auto st = ty.storage_texture(Source{{12, 34}}, type::TextureDimension::k1d,
+                                 type::TexelFormat::kR32Uint, type::Access::kRead);
 
     GlobalVar("a", st, Group(0_a), Binding(0_a));
 
@@ -1220,8 +1203,8 @@
     // @group(0) @binding(0)
     // var a : texture_storage_1d<r32uint, write>;
 
-    auto* st = ty.storage_texture(type::TextureDimension::k1d, type::TexelFormat::kR32Uint,
-                                  type::Access::kWrite);
+    auto st = ty.storage_texture(type::TextureDimension::k1d, type::TexelFormat::kR32Uint,
+                                 type::Access::kWrite);
 
     GlobalVar("a", st, Group(0_a), Binding(0_a));
 
@@ -1250,8 +1233,9 @@
 
     Enable(ast::Extension::kF16);
 
-    GlobalVar("a", ty.mat(params.elem_ty(*this), params.columns, params.rows),
-              type::AddressSpace::kPrivate);
+    ast::Type el_ty = params.elem_ty(*this);
+
+    GlobalVar("a", ty.mat(el_ty, params.columns, params.rows), type::AddressSpace::kPrivate);
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
@@ -1289,7 +1273,9 @@
 
     Enable(ast::Extension::kF16);
 
-    GlobalVar("a", ty.mat(Source{{12, 34}}, params.elem_ty(*this), params.columns, params.rows),
+    ast::Type el_ty = params.elem_ty(*this);
+
+    GlobalVar("a", ty.mat(Source{{12, 34}}, el_ty, params.columns, params.rows),
               type::AddressSpace::kPrivate);
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: matrix element type must be 'f32' or 'f16'");
@@ -1417,7 +1403,7 @@
 
     Enable(ast::Extension::kF16);
 
-    WrapInFunction(Decl(Var("v", params.type(*this), Call(ty(params.alias)))));
+    WrapInFunction(Decl(Var("v", params.type(*this), Call(params.alias))));
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
@@ -1474,7 +1460,7 @@
 TEST_P(ResolverUntemplatedTypeUsedWithTemplateArgs, BuiltinAlias_UseWithTemplateArgs) {
     // enable f16;
     // alias A = f32;
-    // var<private> v : S<true>;
+    // var<private> v : A<true>;
 
     Enable(ast::Extension::kF16);
     Alias(Source{{56, 78}}, "A", ty(GetParam()));
diff --git a/src/tint/resolver/uniformity.cc b/src/tint/resolver/uniformity.cc
index 403ac53..0b8fd7f 100644
--- a/src/tint/resolver/uniformity.cc
+++ b/src/tint/resolver/uniformity.cc
@@ -386,12 +386,27 @@
         return current_function_->CreateNode(std::move(tag_list), ast);
     }
 
-    /// Get the symbol name of an AST node.
-    /// @param ast the AST node to get the symbol name of
+    /// Get the symbol name of an AST expression.
+    /// @param expr the expression to get the symbol name of
     /// @returns the symbol name
-    template <typename T>
-    inline std::string NameFor(const T* ast) {
-        return builder_->Symbols().NameFor(ast->symbol);
+    inline std::string NameFor(const ast::IdentifierExpression* expr) {
+        return builder_->Symbols().NameFor(expr->identifier->symbol);
+    }
+
+    /// @param var the variable to get the name of
+    /// @returns the name of the variable @p var
+    inline std::string NameFor(const ast::Variable* var) {
+        return builder_->Symbols().NameFor(var->name->symbol);
+    }
+
+    /// @param var the variable to get the name of
+    /// @returns the name of the variable @p var
+    inline std::string NameFor(const sem::Variable* var) { return NameFor(var->Declaration()); }
+
+    /// @param fn the function to get the name of
+    /// @returns the name of the function @p fn
+    inline std::string NameFor(const sem::Function* fn) {
+        return builder_->Symbols().NameFor(fn->Declaration()->name->symbol);
     }
 
     /// Process a function.
@@ -621,7 +636,7 @@
 
                     // Add an edge from the variable exit node to its value at this point.
                     auto* exit_node = info.var_exit_nodes.GetOrCreate(var, [&]() {
-                        auto name = NameFor(var->Declaration()->name);
+                        auto name = NameFor(var);
                         return CreateNode({name, "_value_", info.type, "_exit"});
                     });
                     exit_node->AddEdge(current_function_->variables.Get(var));
@@ -657,7 +672,7 @@
 
                         // Add an edge from the variable exit node to its value at this point.
                         auto* exit_node = info.var_exit_nodes.GetOrCreate(var, [&]() {
-                            auto name = NameFor(var->Declaration()->name);
+                            auto name = NameFor(var);
                             return CreateNode({name, "_value_", info.type, "_exit"});
                         });
 
@@ -730,8 +745,7 @@
 
                 // Create input nodes for any variables declared before this loop.
                 for (auto* v : current_function_->local_var_decls) {
-                    auto* in_node =
-                        CreateNode({NameFor(v->Declaration()->name), "_value_forloop_in"});
+                    auto* in_node = CreateNode({NameFor(v), "_value_forloop_in"});
                     in_node->AddEdge(current_function_->variables.Get(v));
                     info.var_in_nodes.Replace(v, in_node);
                     current_function_->variables.Set(v, in_node);
@@ -748,7 +762,7 @@
                     // Propagate assignments to the loop exit nodes.
                     for (auto* var : current_function_->local_var_decls) {
                         auto* exit_node = info.var_exit_nodes.GetOrCreate(var, [&]() {
-                            auto name = NameFor(var->Declaration()->name);
+                            auto name = NameFor(var);
                             return CreateNode({name, "_value_", info.type, "_exit"});
                         });
                         exit_node->AddEdge(current_function_->variables.Get(var));
@@ -806,8 +820,7 @@
 
                 // Create input nodes for any variables declared before this loop.
                 for (auto* v : current_function_->local_var_decls) {
-                    auto* in_node =
-                        CreateNode({NameFor(v->Declaration()->name), "_value_forloop_in"});
+                    auto* in_node = CreateNode({NameFor(v), "_value_forloop_in"});
                     in_node->AddEdge(current_function_->variables.Get(v));
                     info.var_in_nodes.Replace(v, in_node);
                     current_function_->variables.Set(v, in_node);
@@ -825,7 +838,7 @@
                 // Propagate assignments to the loop exit nodes.
                 for (auto* var : current_function_->local_var_decls) {
                     auto* exit_node = info.var_exit_nodes.GetOrCreate(var, [&]() {
-                        auto name = NameFor(var->Declaration()->name);
+                        auto name = NameFor(var);
                         return CreateNode({name, "_value_", info.type, "_exit"});
                     });
                     exit_node->AddEdge(current_function_->variables.Get(var));
@@ -909,8 +922,7 @@
                     }
 
                     // Create an exit node for the variable.
-                    auto* out_node =
-                        CreateNode({NameFor(var->Declaration()->name), "_value_if_exit"});
+                    auto* out_node = CreateNode({NameFor(var), "_value_if_exit"});
 
                     // Add edges to the assigned value or the initial value.
                     // Only add edges if the behavior for that block contains 'Next'.
@@ -966,7 +978,7 @@
 
                 // Create input nodes for any variables declared before this loop.
                 for (auto* v : current_function_->local_var_decls) {
-                    auto name = NameFor(v->Declaration()->name);
+                    auto name = NameFor(v);
                     auto* in_node = CreateNode({name, "_value_loop_in"}, v->Declaration());
                     in_node->AddEdge(current_function_->variables.Get(v));
                     info.var_in_nodes.Replace(v, in_node);
@@ -1065,7 +1077,7 @@
 
                             // Add an edge from the variable exit node to its new value.
                             auto* exit_node = info.var_exit_nodes.GetOrCreate(var, [&]() {
-                                auto name = NameFor(var->Declaration()->name);
+                                auto name = NameFor(var);
                                 return CreateNode({name, "_value_", info.type, "_exit"});
                             });
                             exit_node->AddEdge(current_function_->variables.Get(var));
@@ -1144,7 +1156,7 @@
             return true;
         };
 
-        auto* node = CreateNode({NameFor(ident->identifier), "_ident_expr"}, ident);
+        auto* node = CreateNode({NameFor(ident), "_ident_expr"}, ident);
         auto* sem_ident = sem_.GetVal(ident);
         TINT_ASSERT(Resolver, sem_ident);
         auto* var_user = sem_ident->Unwrap()->As<sem::VariableUser>();
@@ -1367,7 +1379,7 @@
                     return std::make_pair(cf, current_function_->may_be_non_uniform);
                 } else if (auto* local = sem->Variable()->As<sem::LocalVariable>()) {
                     // Create a new value node for this variable.
-                    auto* value = CreateNode({NameFor(i->identifier), "_lvalue"});
+                    auto* value = CreateNode({NameFor(i), "_lvalue"});
                     auto* old_value = current_function_->variables.Set(local, value);
 
                     // If i is part of an expression that is a partial reference to a variable (e.g.
@@ -1404,7 +1416,7 @@
                     // Cut the analysis short, since we only need to know the originating variable
                     // that is being written to.
                     auto* root_ident = sem_.Get(u)->RootIdentifier();
-                    auto* deref = CreateNode({NameFor(root_ident->Declaration()->name), "_deref"});
+                    auto* deref = CreateNode({NameFor(root_ident), "_deref"});
                     auto* old_value = current_function_->variables.Set(root_ident, deref);
 
                     if (old_value) {
@@ -1432,12 +1444,7 @@
     /// @param call the function call to process
     /// @returns a pair of (control flow node, value node)
     std::pair<Node*, Node*> ProcessCall(Node* cf, const ast::CallExpression* call) {
-        std::string name;
-        if (call->target.name) {
-            name = NameFor(call->target.name);
-        } else {
-            name = call->target.type->FriendlyName(builder_->Symbols());
-        }
+        std::string name = NameFor(call->target);
 
         // Process call arguments
         Node* cf_last_arg = cf;
@@ -1771,10 +1778,10 @@
                 std::ostringstream ss;
                 if (auto* param = var->As<sem::Parameter>()) {
                     auto* func = param->Owner()->As<sem::Function>();
-                    ss << param_type(param) << "'" << NameFor(ident->identifier) << "' of '"
-                       << NameFor(func->Declaration()->name) << "' may be non-uniform";
+                    ss << param_type(param) << "'" << NameFor(ident) << "' of '" << NameFor(func)
+                       << "' may be non-uniform";
                 } else {
-                    ss << "reading from " << var_type(var) << "'" << NameFor(ident->identifier)
+                    ss << "reading from " << var_type(var) << "'" << NameFor(ident)
                        << "' may result in a non-uniform value";
                 }
                 diagnostics_.add_note(diag::System::Resolver, ss.str(), ident->source);
@@ -1782,12 +1789,12 @@
             [&](const ast::Variable* v) {
                 auto* var = sem_.Get(v);
                 std::ostringstream ss;
-                ss << "reading from " << var_type(var) << "'" << NameFor(v->name)
+                ss << "reading from " << var_type(var) << "'" << NameFor(v)
                    << "' may result in a non-uniform value";
                 diagnostics_.add_note(diag::System::Resolver, ss.str(), v->source);
             },
             [&](const ast::CallExpression* c) {
-                auto target_name = NameFor(c->target.name);
+                auto target_name = NameFor(c->target);
                 switch (non_uniform_source->type) {
                     case Node::kFunctionCallReturnValue: {
                         diagnostics_.add_note(
@@ -1799,8 +1806,7 @@
                         auto* arg = c->args[non_uniform_source->arg_index];
                         auto* var = sem_.GetVal(arg)->RootIdentifier();
                         std::ostringstream ss;
-                        ss << "reading from " << var_type(var) << "'"
-                           << NameFor(var->Declaration()->name)
+                        ss << "reading from " << var_type(var) << "'" << NameFor(var)
                            << "' may result in a non-uniform value";
                         diagnostics_.add_note(diag::System::Resolver, ss.str(),
                                               var->Declaration()->source);
@@ -1867,7 +1873,7 @@
         auto* call = cause->ast->As<ast::CallExpression>();
         TINT_ASSERT(Resolver, call);
         auto* target = SemCall(call)->Target();
-        auto func_name = NameFor(call->target.name);
+        auto func_name = NameFor(call->target);
 
         if (cause->type == Node::kFunctionCallArgumentValue ||
             cause->type == Node::kFunctionCallArgumentContents) {
@@ -1897,7 +1903,7 @@
                 // Show a builtin was reachable from this call (which may be the call itself).
                 // This will be the trigger location for the failure.
                 std::ostringstream ss;
-                ss << "'" << NameFor(builtin_call->target.name)
+                ss << "'" << NameFor(builtin_call->target)
                    << "' must only be called from uniform control flow";
                 report(builtin_call->source, ss.str(), /* note */ false);
             }
diff --git a/src/tint/resolver/uniformity_test.cc b/src/tint/resolver/uniformity_test.cc
index 719db08..1cf996d 100644
--- a/src/tint/resolver/uniformity_test.cc
+++ b/src/tint/resolver/uniformity_test.cc
@@ -5330,7 +5330,7 @@
         args.Push(b.AddressOf(name));
     }
     main_body.Push(b.Assign("v0", "non_uniform_global"));
-    main_body.Push(b.CallStmt(b.create<ast::CallExpression>(b.Ident("foo"), args)));
+    main_body.Push(b.CallStmt(b.Call("foo", args)));
     main_body.Push(b.If(b.Equal("v254", 0_i), b.Block(b.CallStmt(b.Call("workgroupBarrier")))));
     b.Func("main", utils::Empty, ty.void_(), main_body);
 
diff --git a/src/tint/resolver/validation_test.cc b/src/tint/resolver/validation_test.cc
index bbfd4aa..634e3a2 100644
--- a/src/tint/resolver/validation_test.cc
+++ b/src/tint/resolver/validation_test.cc
@@ -294,7 +294,7 @@
 }
 
 TEST_F(ResolverValidationTest, AddressSpace_SamplerExplicitAddressSpace) {
-    auto* t = ty.sampler(type::SamplerKind::kSampler);
+    auto t = ty.sampler(type::SamplerKind::kSampler);
     GlobalVar(Source{{12, 34}}, "var", t, type::AddressSpace::kHandle, Binding(0_a), Group(0_a));
 
     EXPECT_FALSE(r()->Resolve());
@@ -304,7 +304,7 @@
 }
 
 TEST_F(ResolverValidationTest, AddressSpace_TextureExplicitAddressSpace) {
-    auto* t = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
+    auto t = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
     GlobalVar(Source{{12, 34}}, "var", t, type::AddressSpace::kHandle, Binding(0_a), Group(0_a));
 
     EXPECT_FALSE(r()->Resolve()) << r()->error();
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 71e39af..0a7abcf 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -19,7 +19,6 @@
 #include <utility>
 
 #include "src/tint/ast/alias.h"
-#include "src/tint/ast/array.h"
 #include "src/tint/ast/assignment_statement.h"
 #include "src/tint/ast/bitcast_expression.h"
 #include "src/tint/ast/break_statement.h"
@@ -33,16 +32,11 @@
 #include "src/tint/ast/internal_attribute.h"
 #include "src/tint/ast/interpolate_attribute.h"
 #include "src/tint/ast/loop_statement.h"
-#include "src/tint/ast/matrix.h"
-#include "src/tint/ast/pointer.h"
 #include "src/tint/ast/return_statement.h"
-#include "src/tint/ast/sampled_texture.h"
 #include "src/tint/ast/switch_statement.h"
 #include "src/tint/ast/traverse_expressions.h"
-#include "src/tint/ast/type_name.h"
 #include "src/tint/ast/unary_op_expression.h"
 #include "src/tint/ast/variable_decl_statement.h"
-#include "src/tint/ast/vector.h"
 #include "src/tint/ast/workgroup_attribute.h"
 #include "src/tint/sem/break_if_statement.h"
 #include "src/tint/sem/call.h"
@@ -283,28 +277,28 @@
     return nullptr;
 }
 
-bool Validator::Atomic(const ast::Atomic* a, const type::Atomic* s) const {
+bool Validator::Atomic(const ast::TemplatedIdentifier* a, const type::Atomic* s) const {
     // https://gpuweb.github.io/gpuweb/wgsl/#atomic-types
     // T must be either u32 or i32.
     if (!s->Type()->IsAnyOf<type::U32, type::I32>()) {
-        AddError("atomic only supports i32 or u32 types", a->type ? a->type->source : a->source);
+        AddError("atomic only supports i32 or u32 types", a->arguments[0]->source);
         return false;
     }
     return true;
 }
 
-bool Validator::Pointer(const ast::Pointer* a, const type::Pointer* s) const {
+bool Validator::Pointer(const ast::TemplatedIdentifier* a, const type::Pointer* s) const {
     if (s->AddressSpace() == type::AddressSpace::kUndefined) {
         AddError("ptr missing address space", a->source);
         return false;
     }
 
-    if (a->access != type::Access::kUndefined) {
+    if (a->arguments.Length() > 2) {  // ptr<address-space, type [, access]>
         // https://www.w3.org/TR/WGSL/#access-mode-defaults
         // When writing a variable declaration or a pointer type in WGSL source:
         // * For the storage address space, the access mode is optional, and defaults to read.
         // * For other address spaces, the access mode must not be written.
-        if (a->address_space != type::AddressSpace::kStorage) {
+        if (s->AddressSpace() != type::AddressSpace::kStorage) {
             AddError("only pointers in <storage> address space may declare an access mode",
                      a->source);
             return false;
@@ -1567,10 +1561,10 @@
         }
         if (!is_call_statement) {
             // https://gpuweb.github.io/gpuweb/wgsl/#function-call-expr
-            // If the called function does not return a value, a function call
-            // statement should be used instead.
-            auto* ident = call->Declaration()->target.name;
-            auto name = symbols_.NameFor(ident->symbol);
+            // If the called function does not return a value, a function call statement should be
+            // used instead.
+            auto* builtin = call->Target()->As<sem::Builtin>();
+            auto name = utils::ToString(builtin->Type());
             AddError("builtin '" + name + "' does not return a value", call->Declaration()->source);
             return false;
         }
@@ -1685,7 +1679,7 @@
 bool Validator::FunctionCall(const sem::Call* call, sem::Statement* current_statement) const {
     auto* decl = call->Declaration();
     auto* target = call->Target()->As<sem::Function>();
-    auto sym = decl->target.name->symbol;
+    auto sym = target->Declaration()->name->symbol;
     auto name = symbols_.NameFor(sym);
 
     if (!current_statement) {  // Function call at module-scope.
@@ -1852,16 +1846,16 @@
     return true;
 }
 
-bool Validator::Vector(const type::Vector* ty, const Source& source) const {
-    if (!ty->type()->is_scalar()) {
+bool Validator::Vector(const type::Type* el_ty, const Source& source) const {
+    if (!el_ty->is_scalar()) {
         AddError("vector element type must be 'bool', 'f32', 'f16', 'i32' or 'u32'", source);
         return false;
     }
     return true;
 }
 
-bool Validator::Matrix(const type::Matrix* ty, const Source& source) const {
-    if (!ty->is_float_matrix()) {
+bool Validator::Matrix(const type::Type* el_ty, const Source& source) const {
+    if (!el_ty->is_float_scalar()) {
         AddError("matrix element type must be 'f32' or 'f16'", source);
         return false;
     }
diff --git a/src/tint/resolver/validator.h b/src/tint/resolver/validator.h
index 9e11f50..dc1bea6 100644
--- a/src/tint/resolver/validator.h
+++ b/src/tint/resolver/validator.h
@@ -185,13 +185,13 @@
     /// @param a the atomic ast node
     /// @param s the atomic sem node
     /// @returns true on success, false otherwise.
-    bool Atomic(const ast::Atomic* a, const type::Atomic* s) const;
+    bool Atomic(const ast::TemplatedIdentifier* a, const type::Atomic* s) const;
 
     /// Validates a pointer type
     /// @param a the pointer ast node
     /// @param s the pointer sem node
     /// @returns true on success, false otherwise.
-    bool Pointer(const ast::Pointer* a, const type::Pointer* s) const;
+    bool Pointer(const ast::TemplatedIdentifier* a, const type::Pointer* s) const;
 
     /// Validates an assignment
     /// @param a the assignment statement
@@ -343,10 +343,10 @@
     bool Materialize(const type::Type* to, const type::Type* from, const Source& source) const;
 
     /// Validates a matrix
-    /// @param ty the matrix to validate
+    /// @param el_ty the matrix element type to validate
     /// @param source the source of the matrix
     /// @returns true on success, false otherwise
-    bool Matrix(const type::Matrix* ty, const Source& source) const;
+    bool Matrix(const type::Type* el_ty, const Source& source) const;
 
     /// Validates a function parameter
     /// @param func the function the variable is for
@@ -440,10 +440,10 @@
                              const sem::ValueExpression* initializer) const;
 
     /// Validates a vector
-    /// @param ty the vector to validate
+    /// @param el_ty the vector element type to validate
     /// @param source the source of the vector
     /// @returns true on success, false otherwise
-    bool Vector(const type::Vector* ty, const Source& source) const;
+    bool Vector(const type::Type* el_ty, const Source& source) const;
 
     /// Validates an array initializer
     /// @param ctor the call expresion to validate
diff --git a/src/tint/resolver/variable_test.cc b/src/tint/resolver/variable_test.cc
index 9d00a5a..c5af49c 100644
--- a/src/tint/resolver/variable_test.cc
+++ b/src/tint/resolver/variable_test.cc
@@ -902,7 +902,7 @@
     auto* c_vu32 = Const("e", ty.vec3<u32>(), vec3<u32>());
     auto* c_vf32 = Const("f", ty.vec3<f32>(), vec3<f32>());
     auto* c_mf32 = Const("g", ty.mat3x3<f32>(), mat3x3<f32>());
-    auto* c_s = Const("h", ty("S"), Call(ty("S")));
+    auto* c_s = Const("h", ty("S"), Call("S"));
 
     WrapInFunction(c_i32, c_u32, c_f32, c_vi32, c_vu32, c_vf32, c_mf32, c_s);
 
@@ -947,14 +947,14 @@
     auto* c_vi32 = Const("f", vec3<i32>());
     auto* c_vu32 = Const("g", vec3<u32>());
     auto* c_vf32 = Const("h", vec3<f32>());
-    auto* c_vai = Const("i", Call(ty.vec(nullptr, 3), Expr(0_a)));
-    auto* c_vaf = Const("j", Call(ty.vec(nullptr, 3), Expr(0._a)));
+    auto* c_vai = Const("i", Call(ty.vec<Infer>(3), Expr(0_a)));
+    auto* c_vaf = Const("j", Call(ty.vec<Infer>(3), Expr(0._a)));
     auto* c_mf32 = Const("k", mat3x3<f32>());
     auto* c_maf32 =
-        Const("l", Call(ty.mat(nullptr, 3, 3),  //
-                        Call(ty.vec(nullptr, 3), Expr(0._a)), Call(ty.vec(nullptr, 3), Expr(0._a)),
-                        Call(ty.vec(nullptr, 3), Expr(0._a))));
-    auto* c_s = Const("m", Call(ty("S")));
+        Const("l", Call(ty.mat3x3<Infer>(),  //
+                        Call(ty.vec<Infer>(3), Expr(0._a)), Call(ty.vec<Infer>(3), Expr(0._a)),
+                        Call(ty.vec<Infer>(3), Expr(0._a))));
+    auto* c_s = Const("m", Call("S"));
 
     WrapInFunction(c_i32, c_u32, c_f32, c_ai, c_af, c_vi32, c_vu32, c_vf32, c_vai, c_vaf, c_mf32,
                    c_maf32, c_s);
@@ -1123,13 +1123,13 @@
     auto* c_vi32 = GlobalConst("f", vec3<i32>());
     auto* c_vu32 = GlobalConst("g", vec3<u32>());
     auto* c_vf32 = GlobalConst("h", vec3<f32>());
-    auto* c_vai = GlobalConst("i", Call(ty.vec(nullptr, 3), Expr(0_a)));
-    auto* c_vaf = GlobalConst("j", Call(ty.vec(nullptr, 3), Expr(0._a)));
+    auto* c_vai = GlobalConst("i", Call(ty.vec<Infer>(3), Expr(0_a)));
+    auto* c_vaf = GlobalConst("j", Call(ty.vec<Infer>(3), Expr(0._a)));
     auto* c_mf32 = GlobalConst("k", mat3x3<f32>());
     auto* c_maf32 = GlobalConst(
-        "l", Call(ty.mat(nullptr, 3, 3),  //
-                  Call(ty.vec(nullptr, 3), Expr(0._a)), Call(ty.vec(nullptr, 3), Expr(0._a)),
-                  Call(ty.vec(nullptr, 3), Expr(0._a))));
+        "l", Call(ty.mat3x3<Infer>(),  //
+                  Call(ty.vec<Infer>(3), Expr(0._a)), Call(ty.vec<Infer>(3), Expr(0._a)),
+                  Call(ty.vec<Infer>(3), Expr(0._a))));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
diff --git a/src/tint/resolver/variable_validation_test.cc b/src/tint/resolver/variable_validation_test.cc
index 5434042..2649562 100644
--- a/src/tint/resolver/variable_validation_test.cc
+++ b/src/tint/resolver/variable_validation_test.cc
@@ -369,52 +369,51 @@
 }
 
 TEST_F(ResolverVariableValidationTest, VectorConstNoType) {
-    // const a : mat3x3 = mat3x3<f32>();
-    WrapInFunction(Const("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3u), vec3<f32>()));
+    // const a vec3 = vec3<f32>();
+    WrapInFunction(Const("a", ty.vec3<Infer>(Source{{12, 34}}), vec3<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+    EXPECT_EQ(r()->error(), "12:34 error: expected '<' for 'vec3'");
 }
 
 TEST_F(ResolverVariableValidationTest, VectorLetNoType) {
-    // let a : mat3x3 = mat3x3<f32>();
-    WrapInFunction(Let("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3u), vec3<f32>()));
+    // let a : vec3 = vec3<f32>();
+    WrapInFunction(Let("a", ty.vec3<Infer>(Source{{12, 34}}), vec3<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+    EXPECT_EQ(r()->error(), "12:34 error: expected '<' for 'vec3'");
 }
 
 TEST_F(ResolverVariableValidationTest, VectorVarNoType) {
-    // var a : mat3x3;
-    WrapInFunction(Var("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3u)));
+    // var a : vec3;
+    WrapInFunction(Var("a", ty.vec3<Infer>(Source{{12, 34}})));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+    EXPECT_EQ(r()->error(), "12:34 error: expected '<' for 'vec3'");
 }
 
 TEST_F(ResolverVariableValidationTest, MatrixConstNoType) {
     // const a : mat3x3 = mat3x3<f32>();
-    WrapInFunction(
-        Const("a", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3u, 3u), mat3x3<f32>()));
+    WrapInFunction(Const("a", ty.mat3x3<Infer>(Source{{12, 34}}), mat3x3<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
+    EXPECT_EQ(r()->error(), "12:34 error: expected '<' for 'mat3x3'");
 }
 
 TEST_F(ResolverVariableValidationTest, MatrixLetNoType) {
     // let a : mat3x3 = mat3x3<f32>();
-    WrapInFunction(Let("a", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3u, 3u), mat3x3<f32>()));
+    WrapInFunction(Let("a", ty.mat3x3<Infer>(Source{{12, 34}}), mat3x3<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
+    EXPECT_EQ(r()->error(), "12:34 error: expected '<' for 'mat3x3'");
 }
 
 TEST_F(ResolverVariableValidationTest, MatrixVarNoType) {
     // var a : mat3x3;
-    WrapInFunction(Var("a", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3u, 3u)));
+    WrapInFunction(Var("a", ty.mat3x3<Infer>(Source{{12, 34}})));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
+    EXPECT_EQ(r()->error(), "12:34 error: expected '<' for 'mat3x3'");
 }
 
 TEST_F(ResolverVariableValidationTest, GlobalConstWithRuntimeExpression) {
diff --git a/src/tint/sem/type_mappings.h b/src/tint/sem/type_mappings.h
index 419a9c2..fa7d8c6 100644
--- a/src/tint/sem/type_mappings.h
+++ b/src/tint/sem/type_mappings.h
@@ -23,7 +23,6 @@
 }  // namespace tint
 namespace tint::ast {
 class AccessorExpression;
-class Array;
 class BinaryExpression;
 class BitcastExpression;
 class CallExpression;
@@ -39,7 +38,6 @@
 class Struct;
 class StructMember;
 class SwitchStatement;
-class Type;
 class TypeDecl;
 class Variable;
 class WhileStatement;
@@ -73,7 +71,6 @@
 /// rules will be used to infer the return type based on the argument type.
 struct TypeMappings {
     //! @cond Doxygen_Suppress
-    type::Array* operator()(ast::Array*);
     ForLoopStatement* operator()(ast::ForLoopStatement*);
     Function* operator()(ast::Function*);
     IfStatement* operator()(ast::IfStatement*);
@@ -83,7 +80,6 @@
     Struct* operator()(ast::Struct*);
     StructMember* operator()(ast::StructMember*);
     SwitchStatement* operator()(ast::SwitchStatement*);
-    type::Type* operator()(ast::Type*);
     type::Type* operator()(ast::TypeDecl*);
     Expression* operator()(ast::Expression*);
     ValueExpression* operator()(ast::AccessorExpression*);
diff --git a/src/tint/traits.h b/src/tint/traits.h
index 1d0c2f6..8d74bf7 100644
--- a/src/tint/traits.h
+++ b/src/tint/traits.h
@@ -110,11 +110,6 @@
 template <typename T, typename BASE>
 using EnableIfIsType = EnableIf<IsTypeOrDerived<T, BASE>, T>;
 
-/// If `T` is not of type `BASE`, or does not derive from `BASE`, then
-/// EnableIfIsNotType resolves to type `T`, otherwise an invalid type.
-template <typename T, typename BASE>
-using EnableIfIsNotType = EnableIf<!IsTypeOrDerived<T, BASE>, T>;
-
 /// @returns the std::index_sequence with all the indices shifted by OFFSET.
 template <std::size_t OFFSET, std::size_t... INDICES>
 constexpr auto Shift(std::index_sequence<INDICES...>) {
diff --git a/src/tint/transform/add_block_attribute.cc b/src/tint/transform/add_block_attribute.cc
index e2a0c00..2b1ec31 100644
--- a/src/tint/transform/add_block_attribute.cc
+++ b/src/tint/transform/add_block_attribute.cc
@@ -78,7 +78,7 @@
                 ctx.InsertBefore(src->AST().GlobalDeclarations(), global, ret);
                 return ret;
             });
-            ctx.Replace(global->type, b.ty.Of(wrapper));
+            ctx.Replace(global->type.expr, b.Expr(wrapper->name->symbol));
 
             // Insert a member accessor to get the original type from the wrapper at
             // any usage of the original variable.
diff --git a/src/tint/transform/binding_remapper.cc b/src/tint/transform/binding_remapper.cc
index 470826d..b657980 100644
--- a/src/tint/transform/binding_remapper.cc
+++ b/src/tint/transform/binding_remapper.cc
@@ -138,7 +138,7 @@
                     return Program(std::move(b));
                 }
                 auto* ty = sem->Type()->UnwrapRef();
-                const ast::Type* inner_ty = CreateASTTypeFor(ctx, ty);
+                auto inner_ty = CreateASTTypeFor(ctx, ty);
                 auto* new_var = b.Var(ctx.Clone(var->source), ctx.Clone(var->name->symbol),
                                       inner_ty, var->declared_address_space, ac,
                                       ctx.Clone(var->initializer), ctx.Clone(var->attributes));
diff --git a/src/tint/transform/builtin_polyfill.cc b/src/tint/transform/builtin_polyfill.cc
index 3d647a7..5baef8a 100644
--- a/src/tint/transform/builtin_polyfill.cc
+++ b/src/tint/transform/builtin_polyfill.cc
@@ -176,7 +176,7 @@
         uint32_t width = WidthOf(ty);
 
         // Returns either u32 or vecN<u32>
-        auto U = [&]() -> const ast::Type* {
+        auto U = [&]() {
             if (width == 1) {
                 return b.ty.u32();
             }
@@ -234,7 +234,7 @@
         uint32_t width = WidthOf(ty);
 
         // Returns either u32 or vecN<u32>
-        auto U = [&]() -> const ast::Type* {
+        auto U = [&]() {
             if (width == 1) {
                 return b.ty.u32();
             }
@@ -351,7 +351,7 @@
         uint32_t width = WidthOf(ty);
 
         // Returns either u32 or vecN<u32>
-        auto U = [&]() -> const ast::Type* {
+        auto U = [&]() {
             if (width == 1) {
                 return b.ty.u32();
             }
@@ -423,7 +423,7 @@
         uint32_t width = WidthOf(ty);
 
         // Returns either u32 or vecN<u32>
-        auto U = [&]() -> const ast::Type* {
+        auto U = [&]() {
             if (width == 1) {
                 return b.ty.u32();
             }
@@ -825,7 +825,7 @@
     bool has_full_ptr_params;
 
     /// @returns the AST type for the given sem type
-    const ast::Type* T(const type::Type* ty) const { return CreateASTTypeFor(ctx, ty); }
+    ast::Type T(const type::Type* ty) const { return CreateASTTypeFor(ctx, ty); }
 
     /// @returns 1 if `ty` is not a vector, otherwise the vector width
     uint32_t WidthOf(const type::Type* ty) const {
@@ -1067,15 +1067,17 @@
                         break;
                 }
             },
-            [&](const ast::TypeName* type_name) {
+            [&](const ast::Expression* expr) {
                 if (polyfill.bgra8unorm) {
-                    if (auto* tex = src->Sem().Get<type::StorageTexture>(type_name)) {
-                        if (tex->texel_format() == type::TexelFormat::kBgra8Unorm) {
-                            ctx.Replace(type_name, [&ctx, tex] {
-                                return ctx.dst->ty.storage_texture(
-                                    tex->dim(), type::TexelFormat::kRgba8Unorm, tex->access());
-                            });
-                            made_changes = true;
+                    if (auto* ty_expr = src->Sem().Get<sem::TypeExpression>(expr)) {
+                        if (auto* tex = ty_expr->Type()->As<type::StorageTexture>()) {
+                            if (tex->texel_format() == type::TexelFormat::kBgra8Unorm) {
+                                ctx.Replace(expr, [&ctx, tex] {
+                                    return ctx.dst->Expr(ctx.dst->ty.storage_texture(
+                                        tex->dim(), type::TexelFormat::kRgba8Unorm, tex->access()));
+                                });
+                                made_changes = true;
+                            }
                         }
                     }
                 }
diff --git a/src/tint/transform/calculate_array_length.cc b/src/tint/transform/calculate_array_length.cc
index 028f8c5..5ed6547 100644
--- a/src/tint/transform/calculate_array_length.cc
+++ b/src/tint/transform/calculate_array_length.cc
@@ -104,10 +104,10 @@
     auto get_buffer_size_intrinsic = [&](const type::Reference* buffer_type) {
         return utils::GetOrCreate(buffer_size_intrinsics, buffer_type, [&] {
             auto name = b.Sym();
-            auto* type = CreateASTTypeFor(ctx, buffer_type);
+            auto type = CreateASTTypeFor(ctx, buffer_type);
             auto* disable_validation = b.Disable(ast::DisabledValidation::kFunctionParameter);
-            b.AST().AddFunction(b.create<ast::Function>(
-                b.Ident(name),
+            b.Func(
+                name,
                 utils::Vector{
                     b.Param("buffer",
                             b.ty.pointer(type, buffer_type->AddressSpace(), buffer_type->Access()),
@@ -117,8 +117,7 @@
                 b.ty.void_(), nullptr,
                 utils::Vector{
                     b.ASTNodes().Create<BufferSizeIntrinsic>(b.ID(), b.AllocateNodeID()),
-                },
-                utils::Empty));
+                });
 
             return name;
         });
diff --git a/src/tint/transform/canonicalize_entry_point_io.cc b/src/tint/transform/canonicalize_entry_point_io.cc
index f51d4cc..81c2821 100644
--- a/src/tint/transform/canonicalize_entry_point_io.cc
+++ b/src/tint/transform/canonicalize_entry_point_io.cc
@@ -130,7 +130,7 @@
         /// The name of the output value.
         std::string name;
         /// The type of the output value.
-        const ast::Type* type;
+        ast::Type type;
         /// The shader IO attributes.
         utils::Vector<const ast::Attribute*, 2> attributes;
         /// The value itself.
@@ -210,7 +210,7 @@
                                     const type::Type* type,
                                     std::optional<uint32_t> location,
                                     utils::Vector<const ast::Attribute*, 8> attributes) {
-        auto* ast_type = CreateASTTypeFor(ctx, type);
+        auto ast_type = CreateASTTypeFor(ctx, type);
         if (cfg.shader_style == ShaderStyle::kSpirv || cfg.shader_style == ShaderStyle::kGlsl) {
             // Vulkan requires that integer user-defined fragment inputs are always decorated with
             // `Flat`. See:
@@ -524,7 +524,7 @@
 
             // Create the global variable and assign it the output value.
             auto name = ctx.dst->Symbols().New(outval.name);
-            auto* type = outval.type;
+            ast::Type type = outval.type;
             const ast::Expression* lhs = ctx.dst->Expr(name);
             if (HasSampleMask(attributes)) {
                 // Vulkan requires the type of a SampleMask builtin to be an array.
@@ -606,7 +606,7 @@
         auto* call_inner = CallInnerFunction();
 
         // Process the return type, and start building the wrapper function body.
-        std::function<const ast::Type*()> wrapper_ret_type = [&] { return ctx.dst->ty.void_(); };
+        std::function<ast::Type()> wrapper_ret_type = [&] { return ctx.dst->ty.void_(); };
         if (func_sem->ReturnType()->Is<type::Void>()) {
             // The function call is just a statement with no result.
             wrapper_body.Push(ctx.dst->CallStmt(call_inner));
@@ -665,7 +665,7 @@
         }
 
         auto* wrapper_func = ctx.dst->create<ast::Function>(
-            ctx.dst->Ident(name), wrapper_ep_parameters, wrapper_ret_type(),
+            ctx.dst->Ident(name), wrapper_ep_parameters, ctx.dst->ty(wrapper_ret_type()),
             ctx.dst->Block(wrapper_body), ctx.Clone(func_ast->attributes), utils::Empty);
         ctx.InsertAfter(ctx.src->AST().GlobalDeclarations(), func_ast, wrapper_func);
     }
@@ -727,7 +727,7 @@
     /// WGSL expects
     const ast::Expression* FromGLSLBuiltin(ast::BuiltinValue builtin,
                                            const ast::Expression* value,
-                                           const ast::Type*& ast_type) {
+                                           ast::Type& ast_type) {
         switch (builtin) {
             case ast::BuiltinValue::kVertexIndex:
             case ast::BuiltinValue::kInstanceIndex:
diff --git a/src/tint/transform/clamp_frag_depth.cc b/src/tint/transform/clamp_frag_depth.cc
index 2e6ef2d..0b8d8f7 100644
--- a/src/tint/transform/clamp_frag_depth.cc
+++ b/src/tint/transform/clamp_frag_depth.cc
@@ -22,7 +22,6 @@
 #include "src/tint/ast/function.h"
 #include "src/tint/ast/module.h"
 #include "src/tint/ast/struct.h"
-#include "src/tint/ast/type.h"
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/function.h"
 #include "src/tint/sem/statement.h"
@@ -163,10 +162,9 @@
             //   }
             auto* struct_ty = sem.Get(fn)->ReturnType()->As<sem::Struct>()->Declaration();
             auto helper = io_structs_clamp_helpers.GetOrCreate(struct_ty, [&] {
-                auto* return_ty = fn->return_type;
+                auto return_ty = fn->return_type;
                 auto fn_sym =
-                    b.Symbols().New("clamp_frag_depth_" +
-                                    sym.NameFor(return_ty->As<ast::TypeName>()->name->symbol));
+                    b.Symbols().New("clamp_frag_depth_" + sym.NameFor(struct_ty->name->symbol));
 
                 utils::Vector<const ast::Expression*, 8u> initializer_args;
                 for (auto* member : struct_ty->members) {
diff --git a/src/tint/transform/combine_samplers.cc b/src/tint/transform/combine_samplers.cc
index c3593ef..7b9f9fd 100644
--- a/src/tint/transform/combine_samplers.cc
+++ b/src/tint/transform/combine_samplers.cc
@@ -115,7 +115,7 @@
         if (it != binding_info->binding_map.end()) {
             name = it->second;
         }
-        const ast::Type* type = CreateCombinedASTTypeFor(texture_var, sampler_var);
+        ast::Type type = CreateCombinedASTTypeFor(texture_var, sampler_var);
         Symbol symbol = ctx.dst->Symbols().New(name);
         return ctx.dst->GlobalVar(symbol, type, Attributes());
     }
@@ -124,7 +124,7 @@
     /// @param kind the sampler kind to create for
     /// @returns the newly-created global variable
     const ast::Variable* CreatePlaceholder(type::SamplerKind kind) {
-        const ast::Type* type = ctx.dst->ty.sampler(kind);
+        ast::Type type = ctx.dst->ty.sampler(kind);
         const char* name = kind == type::SamplerKind::kComparisonSampler
                                ? "placeholder_comparison_sampler"
                                : "placeholder_sampler";
@@ -132,18 +132,17 @@
         return ctx.dst->GlobalVar(symbol, type, Attributes());
     }
 
-    /// Creates ast::Type for a given texture and sampler variable pair.
+    /// Creates ast::Identifier for a given texture and sampler variable pair.
     /// Depth textures with no samplers are turned into the corresponding
     /// f32 texture (e.g., texture_depth_2d -> texture_2d<f32>).
     /// @param texture the texture variable of interest
     /// @param sampler the texture variable of interest
     /// @returns the newly-created type
-    const ast::Type* CreateCombinedASTTypeFor(const sem::Variable* texture,
-                                              const sem::Variable* sampler) {
+    ast::Type CreateCombinedASTTypeFor(const sem::Variable* texture, const sem::Variable* sampler) {
         const type::Type* texture_type = texture->Type()->UnwrapRef();
         const type::DepthTexture* depth = texture_type->As<type::DepthTexture>();
         if (depth && !sampler) {
-            return ctx.dst->create<ast::SampledTexture>(depth->dim(), ctx.dst->ty.f32());
+            return ctx.dst->ty.sampled_texture(depth->dim(), ctx.dst->ty.f32());
         } else {
             return CreateASTTypeFor(ctx, texture_type);
         }
@@ -158,7 +157,7 @@
         // by combined samplers.
         for (auto* global : ctx.src->AST().GlobalVariables()) {
             auto* global_sem = sem.Get(global)->As<sem::GlobalVariable>();
-            auto* type = sem.Get(global->type);
+            auto* type = ctx.src->TypeOf(global->type);
             if (tint::IsAnyOf<type::Texture, type::Sampler>(type) &&
                 !type->Is<type::StorageTexture>()) {
                 ctx.Remove(ctx.src->AST().GlobalDeclarations(), global);
@@ -199,7 +198,7 @@
                     } else {
                         // Either texture or sampler (or both) is a function parameter;
                         // add a new function parameter to represent the combined sampler.
-                        auto* type = CreateCombinedASTTypeFor(texture_var, sampler_var);
+                        ast::Type type = CreateCombinedASTTypeFor(texture_var, sampler_var);
                         auto* var = ctx.dst->Param(ctx.dst->Symbols().New(name), type);
                         params.Push(var);
                         function_combined_texture_samplers_[fn][pair] = var;
@@ -215,7 +214,7 @@
                 // Create a new function signature that differs only in the parameter
                 // list.
                 auto name = ctx.Clone(ast_fn->name);
-                auto* return_type = ctx.Clone(ast_fn->return_type);
+                auto return_type = ctx.Clone(ast_fn->return_type);
                 auto* body = ctx.Clone(ast_fn->body);
                 auto attributes = ctx.Clone(ast_fn->attributes);
                 auto return_type_attributes = ctx.Clone(ast_fn->return_type_attributes);
@@ -276,8 +275,7 @@
                             args.Push(ctx.Clone(arg));
                         }
                     }
-                    const ast::Expression* value =
-                        ctx.dst->Call(ctx.Clone(expr->target.name), args);
+                    const ast::Expression* value = ctx.dst->Call(ctx.Clone(expr->target), args);
                     if (builtin->Type() == sem::BuiltinType::kTextureLoad &&
                         texture_var->Type()->UnwrapRef()->Is<type::DepthTexture>() &&
                         !call->Stmt()->Declaration()->Is<ast::CallStatement>()) {
@@ -329,7 +327,7 @@
                             args.Push(ctx.Clone(arg));
                         }
                     }
-                    return ctx.dst->Call(ctx.Clone(expr->target.name), args);
+                    return ctx.dst->Call(ctx.Clone(expr->target), args);
                 }
             }
             return nullptr;
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index fc2003c..072c9c6 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -23,7 +23,6 @@
 #include "src/tint/ast/assignment_statement.h"
 #include "src/tint/ast/call_statement.h"
 #include "src/tint/ast/disable_validation_attribute.h"
-#include "src/tint/ast/type_name.h"
 #include "src/tint/ast/unary_op.h"
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/call.h"
@@ -481,15 +480,12 @@
                 auto name = b.Sym();
 
                 if (auto* intrinsic = IntrinsicLoadFor(ctx.dst, address_space, el_ty)) {
-                    auto* el_ast_ty = CreateASTTypeFor(ctx, el_ty);
-                    auto* func = b.create<ast::Function>(
-                        b.Ident(name), params, el_ast_ty, nullptr,
-                        utils::Vector{
-                            intrinsic,
-                            b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
-                        },
-                        utils::Empty);
-                    b.AST().AddFunction(func);
+                    auto el_ast_ty = CreateASTTypeFor(ctx, el_ty);
+                    b.Func(name, params, el_ast_ty, nullptr,
+                           utils::Vector{
+                               intrinsic,
+                               b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
+                           });
                 } else if (auto* arr_ty = el_ty->As<type::Array>()) {
                     // fn load_func(buffer : buf_ty, offset : u32) -> array<T, N> {
                     //   var arr : array<T, N>;
@@ -581,14 +577,11 @@
                 auto name = b.Sym();
 
                 if (auto* intrinsic = IntrinsicStoreFor(ctx.dst, address_space, el_ty)) {
-                    auto* func = b.create<ast::Function>(
-                        b.Ident(name), params, b.ty.void_(), nullptr,
-                        utils::Vector{
-                            intrinsic,
-                            b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
-                        },
-                        utils::Empty);
-                    b.AST().AddFunction(func);
+                    b.Func(name, params, b.ty.void_(), nullptr,
+                           utils::Vector{
+                               intrinsic,
+                               b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
+                           });
                 } else {
                     auto body = Switch<utils::Vector<const ast::Statement*, 8>>(
                         el_ty,  //
@@ -695,7 +688,7 @@
             // Other parameters are copied as-is:
             for (size_t i = 1; i < intrinsic->Parameters().Length(); i++) {
                 auto* param = intrinsic->Parameters()[i];
-                auto* ty = CreateASTTypeFor(ctx, param->Type());
+                auto ty = CreateASTTypeFor(ctx, param->Type());
                 params.Push(b.Param("param_" + std::to_string(i), ty));
             }
 
@@ -706,7 +699,7 @@
                     << el_ty->TypeInfo().name;
             }
 
-            const ast::Type* ret_ty = nullptr;
+            ast::Type ret_ty;
 
             // For intrinsics that return a struct, there is no AST node for it, so create one now.
             if (intrinsic->Type() == sem::BuiltinType::kAtomicCompareExchangeWeak) {
@@ -727,17 +720,13 @@
                 ret_ty = CreateASTTypeFor(ctx, intrinsic->ReturnType());
             }
 
-            auto* func = b.create<ast::Function>(
-                b.Ident(b.Symbols().New(std::string{"tint_"} + intrinsic->str())), params, ret_ty,
-                nullptr,
-                utils::Vector{
-                    atomic,
-                    b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
-                },
-                utils::Empty);
-
-            b.AST().AddFunction(func);
-            return func->name->symbol;
+            auto name = b.Symbols().New(std::string{"tint_"} + intrinsic->str());
+            b.Func(name, std::move(params), ret_ty, nullptr,
+                   utils::Vector{
+                       atomic,
+                       b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
+                   });
+            return name;
         });
     }
 };
diff --git a/src/tint/transform/decompose_memory_access_test.cc b/src/tint/transform/decompose_memory_access_test.cc
index b58d340..f0f441f 100644
--- a/src/tint/transform/decompose_memory_access_test.cc
+++ b/src/tint/transform/decompose_memory_access_test.cc
@@ -1584,16 +1584,16 @@
 }
 
 fn tint_symbol_34(@internal(disable_validation__function_parameter) buffer : ptr<storage, SB, read_write>, offset : u32, value : array<vec3<f32>, 2u>) {
-  var array = value;
+  var array_1 = value;
   for(var i = 0u; (i < 2u); i = (i + 1u)) {
-    tint_symbol_8(buffer, (offset + (i * 16u)), array[i]);
+    tint_symbol_8(buffer, (offset + (i * 16u)), array_1[i]);
   }
 }
 
 fn tint_symbol_35(@internal(disable_validation__function_parameter) buffer : ptr<storage, SB, read_write>, offset : u32, value : array<mat4x2<f16>, 2u>) {
-  var array_1 = value;
+  var array_2 = value;
   for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    tint_symbol_31(buffer, (offset + (i_1 * 16u)), array_1[i_1]);
+    tint_symbol_31(buffer, (offset + (i_1 * 16u)), array_2[i_1]);
   }
 }
 
@@ -1889,16 +1889,16 @@
 }
 
 fn tint_symbol_34(@internal(disable_validation__function_parameter) buffer : ptr<storage, SB, read_write>, offset : u32, value : array<vec3<f32>, 2u>) {
-  var array = value;
+  var array_1 = value;
   for(var i = 0u; (i < 2u); i = (i + 1u)) {
-    tint_symbol_8(buffer, (offset + (i * 16u)), array[i]);
+    tint_symbol_8(buffer, (offset + (i * 16u)), array_1[i]);
   }
 }
 
 fn tint_symbol_35(@internal(disable_validation__function_parameter) buffer : ptr<storage, SB, read_write>, offset : u32, value : array<mat4x2<f16>, 2u>) {
-  var array_1 = value;
+  var array_2 = value;
   for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    tint_symbol_31(buffer, (offset + (i_1 * 16u)), array_1[i_1]);
+    tint_symbol_31(buffer, (offset + (i_1 * 16u)), array_2[i_1]);
   }
 }
 
@@ -2733,16 +2733,16 @@
 }
 
 fn tint_symbol_35(@internal(disable_validation__function_parameter) buffer : ptr<storage, SB, read_write>, offset : u32, value : array<vec3<f32>, 2u>) {
-  var array = value;
+  var array_1 = value;
   for(var i = 0u; (i < 2u); i = (i + 1u)) {
-    tint_symbol_9(buffer, (offset + (i * 16u)), array[i]);
+    tint_symbol_9(buffer, (offset + (i * 16u)), array_1[i]);
   }
 }
 
 fn tint_symbol_36(@internal(disable_validation__function_parameter) buffer : ptr<storage, SB, read_write>, offset : u32, value : array<mat4x2<f16>, 2u>) {
-  var array_1 = value;
+  var array_2 = value;
   for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    tint_symbol_32(buffer, (offset + (i_1 * 16u)), array_1[i_1]);
+    tint_symbol_32(buffer, (offset + (i_1 * 16u)), array_2[i_1]);
   }
 }
 
@@ -3007,16 +3007,16 @@
 }
 
 fn tint_symbol_35(@internal(disable_validation__function_parameter) buffer : ptr<storage, SB, read_write>, offset : u32, value : array<vec3<f32>, 2u>) {
-  var array = value;
+  var array_1 = value;
   for(var i = 0u; (i < 2u); i = (i + 1u)) {
-    tint_symbol_9(buffer, (offset + (i * 16u)), array[i]);
+    tint_symbol_9(buffer, (offset + (i * 16u)), array_1[i]);
   }
 }
 
 fn tint_symbol_36(@internal(disable_validation__function_parameter) buffer : ptr<storage, SB, read_write>, offset : u32, value : array<mat4x2<f16>, 2u>) {
-  var array_1 = value;
+  var array_2 = value;
   for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-    tint_symbol_32(buffer, (offset + (i_1 * 16u)), array_1[i_1]);
+    tint_symbol_32(buffer, (offset + (i_1 * 16u)), array_2[i_1]);
   }
 }
 
diff --git a/src/tint/transform/decompose_strided_array.cc b/src/tint/transform/decompose_strided_array.cc
index 8602f14..e92b85e 100644
--- a/src/tint/transform/decompose_strided_array.cc
+++ b/src/tint/transform/decompose_strided_array.cc
@@ -21,6 +21,7 @@
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/call.h"
 #include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/type_expression.h"
 #include "src/tint/sem/type_initializer.h"
 #include "src/tint/sem/value_expression.h"
 #include "src/tint/transform/simplify_pointers.h"
@@ -36,8 +37,8 @@
 
 bool ShouldRun(const Program* program) {
     for (auto* node : program->ASTNodes().Objects()) {
-        if (auto* ast = node->As<ast::Array>()) {
-            if (ast::GetAttribute<ast::StrideAttribute>(ast->attributes)) {
+        if (auto* ident = node->As<ast::TemplatedIdentifier>()) {
+            if (ast::GetAttribute<ast::StrideAttribute>(ident->attributes)) {
                 return true;
             }
         }
@@ -73,27 +74,45 @@
     // stride for the array element type, then replace the array element type with
     // a structure, holding a single field with a @size attribute equal to the
     // array stride.
-    ctx.ReplaceAll([&](const ast::Array* ast) -> const ast::Array* {
-        if (auto* arr = sem.Get(ast)) {
-            if (!arr->IsStrideImplicit()) {
-                auto el_ty = utils::GetOrCreate(decomposed, arr, [&] {
-                    auto name = b.Symbols().New("strided_arr");
-                    auto* member_ty = ctx.Clone(ast->type);
-                    auto* member = b.Member(kMemberName, member_ty,
-                                            utils::Vector{
-                                                b.MemberSize(AInt(arr->Stride())),
-                                            });
-                    b.Structure(name, utils::Vector{member});
-                    return name;
-                });
-                auto* count = ctx.Clone(ast->count);
-                return b.ty.array(b.ty(el_ty), count);
+    ctx.ReplaceAll([&](const ast::IdentifierExpression* expr) -> const ast::IdentifierExpression* {
+        auto* ident = expr->identifier->As<ast::TemplatedIdentifier>();
+        if (!ident) {
+            return nullptr;
+        }
+        auto* type_expr = sem.Get<sem::TypeExpression>(expr);
+        if (!type_expr) {
+            return nullptr;
+        }
+        auto* arr = type_expr->Type()->As<type::Array>();
+        if (!arr) {
+            return nullptr;
+        }
+        if (!arr->IsStrideImplicit()) {
+            auto el_ty = utils::GetOrCreate(decomposed, arr, [&] {
+                auto name = b.Symbols().New("strided_arr");
+                auto* member_ty = ctx.Clone(ident->arguments[0]->As<ast::IdentifierExpression>());
+                auto* member = b.Member(kMemberName, ast::Type{member_ty},
+                                        utils::Vector{
+                                            b.MemberSize(AInt(arr->Stride())),
+                                        });
+                b.Structure(name, utils::Vector{member});
+                return name;
+            });
+            if (ident->arguments.Length() > 1) {
+                auto* count = ctx.Clone(ident->arguments[1]);
+                return b.Expr(b.ty.array(b.ty(el_ty), count));
+            } else {
+                return b.Expr(b.ty.array(b.ty(el_ty)));
             }
-            if (ast::GetAttribute<ast::StrideAttribute>(ast->attributes)) {
-                // Strip the @stride attribute
-                auto* ty = ctx.Clone(ast->type);
-                auto* count = ctx.Clone(ast->count);
-                return b.ty.array(ty, count);
+        }
+        if (ast::GetAttribute<ast::StrideAttribute>(ident->attributes)) {
+            // Strip the @stride attribute
+            auto* ty = ctx.Clone(ident->arguments[0]->As<ast::IdentifierExpression>());
+            if (ident->arguments.Length() > 1) {
+                auto* count = ctx.Clone(ident->arguments[1]);
+                return b.Expr(b.ty.array(ast::Type{ty}, count));
+            } else {
+                return b.Expr(b.ty.array(ast::Type{ty}));
             }
         }
         return nullptr;
@@ -133,12 +152,8 @@
                         // decomposed.
                         // If this is an aliased array, decomposed should already be
                         // populated with any strided aliases.
-                        ast::CallExpression::Target target;
-                        if (expr->target.type) {
-                            target.type = ctx.Clone(expr->target.type);
-                        } else {
-                            target.name = ctx.Clone(expr->target.name);
-                        }
+
+                        auto* target = ctx.Clone(expr->target);
 
                         utils::Vector<const ast::Expression*, 8> args;
                         if (auto it = decomposed.find(arr); it != decomposed.end()) {
@@ -150,8 +165,7 @@
                             args = ctx.Clone(expr->args);
                         }
 
-                        return target.type ? b.Call(target.type, std::move(args))
-                                           : b.Call(target.name, std::move(args));
+                        return b.Call(target, std::move(args));
                     }
                 }
             }
diff --git a/src/tint/transform/decompose_strided_matrix.cc b/src/tint/transform/decompose_strided_matrix.cc
index 8b72ac2..4642c57 100644
--- a/src/tint/transform/decompose_strided_matrix.cc
+++ b/src/tint/transform/decompose_strided_matrix.cc
@@ -37,9 +37,8 @@
     /// The type of the matrix
     const type::Matrix* matrix = nullptr;
 
-    /// @returns a new ast::Array that holds an vector column for each row of the
-    /// matrix.
-    const ast::Array* array(ProgramBuilder* b) const {
+    /// @returns the identifier of an array that holds an vector column for each row of the matrix.
+    ast::Type array(ProgramBuilder* b) const {
         return b->ty.array(b->ty.vec<f32>(matrix->rows()), u32(matrix->columns()),
                            utils::Vector{
                                b->Stride(stride),
diff --git a/src/tint/transform/demote_to_helper.cc b/src/tint/transform/demote_to_helper.cc
index 128894d..dc5384b 100644
--- a/src/tint/transform/demote_to_helper.cc
+++ b/src/tint/transform/demote_to_helper.cc
@@ -177,7 +177,7 @@
                         //   }
                         //   let y = x + tmp;
                         auto result = b.Sym();
-                        const ast::Type* result_ty = nullptr;
+                        ast::Type result_ty;
                         const ast::Statement* masked_call = nullptr;
                         if (builtin->Type() == sem::BuiltinType::kAtomicCompareExchangeWeak) {
                             // Special case for atomicCompareExchangeWeak as we cannot name its
diff --git a/src/tint/transform/direct_variable_access.cc b/src/tint/transform/direct_variable_access.cc
index 3e89ca3..a8a1272 100644
--- a/src/tint/transform/direct_variable_access.cc
+++ b/src/tint/transform/direct_variable_access.cc
@@ -831,14 +831,14 @@
                     if (auto incoming_shape = variant_sig.Find(param)) {
                         auto& symbols = *variant.ptr_param_symbols.Find(param);
                         if (symbols.base_ptr.IsValid()) {
-                            auto* base_ptr_ty =
+                            auto base_ptr_ty =
                                 b.ty.pointer(CreateASTTypeFor(ctx, incoming_shape->root.type),
                                              incoming_shape->root.address_space);
                             params.Push(b.Param(symbols.base_ptr, base_ptr_ty));
                         }
                         if (symbols.indices.IsValid()) {
                             // Variant has dynamic indices for this variant, replace it.
-                            auto* dyn_idx_arr_type = DynamicIndexArrayType(*incoming_shape);
+                            auto dyn_idx_arr_type = DynamicIndexArrayType(*incoming_shape);
                             params.Push(b.Param(symbols.indices, dyn_idx_arr_type));
                         }
                     } else {
@@ -850,7 +850,7 @@
                 // Build the variant by cloning the source function. The other clone callbacks will
                 // use clone_state->current_variant and clone_state->current_variant_sig to produce
                 // the variant.
-                auto* ret_ty = ctx.Clone(fn->Declaration()->return_type);
+                auto ret_ty = ctx.Clone(fn->Declaration()->return_type);
                 auto body = ctx.Clone(fn->Declaration()->body);
                 auto attrs = ctx.Clone(fn->Declaration()->attributes);
                 auto ret_attrs = ctx.Clone(fn->Declaration()->return_type_attributes);
@@ -912,7 +912,7 @@
                 }
 
                 // Get or create the dynamic indices array.
-                if (auto* dyn_idx_arr_ty = DynamicIndexArrayType(full_indices)) {
+                if (auto dyn_idx_arr_ty = DynamicIndexArrayType(full_indices)) {
                     // Build an array of dynamic indices to pass as the replacement for the pointer.
                     utils::Vector<const ast::Expression*, 8> dyn_idx_args;
                     if (auto* root_param = chain->root.variable->As<sem::Parameter>()) {
@@ -1064,7 +1064,7 @@
 
     /// @returns the type alias used to hold the dynamic indices for @p shape, declaring a new alias
     /// if this is the first call for the given shape.
-    const ast::TypeName* DynamicIndexArrayType(const AccessShape& shape) {
+    ast::Type DynamicIndexArrayType(const AccessShape& shape) {
         auto name = dynamic_index_array_aliases.GetOrCreate(shape, [&] {
             // Count the number of dynamic indices
             uint32_t num_dyn_indices = shape.NumDynamicIndices();
@@ -1075,7 +1075,7 @@
             b.Alias(symbol, b.ty.array(b.ty.u32(), u32(num_dyn_indices)));
             return symbol;
         });
-        return name.IsValid() ? b.ty(name) : nullptr;
+        return name.IsValid() ? b.ty(name) : ast::Type{};
     }
 
     /// @returns a name describing the given shape
diff --git a/src/tint/transform/module_scope_var_to_entry_point_param.cc b/src/tint/transform/module_scope_var_to_entry_point_param.cc
index 285b95c..364087b 100644
--- a/src/tint/transform/module_scope_var_to_entry_point_param.cc
+++ b/src/tint/transform/module_scope_var_to_entry_point_param.cc
@@ -145,7 +145,7 @@
                 attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kEntryPointParameter));
                 attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kIgnoreAddressSpace));
 
-                auto* param_type = store_type();
+                auto param_type = store_type();
                 if (auto* arr = ty->As<type::Array>();
                     arr && arr->Count()->Is<type::RuntimeArrayCount>()) {
                     // Wrap runtime-sized arrays in structures, so that we can declare pointers to
@@ -230,7 +230,7 @@
                                        bool& is_pointer) {
         auto* var_ast = var->Declaration()->As<ast::Var>();
         auto* ty = var->Type()->UnwrapRef();
-        auto* param_type = CreateASTTypeFor(ctx, ty);
+        auto param_type = CreateASTTypeFor(ctx, ty);
         auto sc = var->AddressSpace();
         switch (sc) {
             case type::AddressSpace::kPrivate:
@@ -450,7 +450,7 @@
                 // The parameter is a struct that contains members for each workgroup variable.
                 auto* str =
                     ctx.dst->Structure(ctx.dst->Sym(), std::move(workgroup_parameter_members));
-                auto* param_type =
+                auto param_type =
                     ctx.dst->ty.pointer(ctx.dst->ty.Of(str), type::AddressSpace::kWorkgroup);
                 auto* param = ctx.dst->Param(
                     workgroup_param(), param_type,
@@ -463,8 +463,8 @@
 
             // Pass the variables as pointers to any functions that need them.
             for (auto* call : calls_to_replace[func_ast]) {
-                auto* target = ctx.src->AST().Functions().Find(call->target.name->symbol);
-                auto* target_sem = ctx.src->Sem().Get(target);
+                auto* call_sem = ctx.src->Sem().Get(call)->Unwrap()->As<sem::Call>();
+                auto* target_sem = call_sem->Target()->As<sem::Function>();
 
                 // Add new arguments for any variables that are needed by the callee.
                 // For entry points, pass non-handle types as pointers.
diff --git a/src/tint/transform/multiplanar_external_texture.cc b/src/tint/transform/multiplanar_external_texture.cc
index 1bbbe22..06dfba4 100644
--- a/src/tint/transform/multiplanar_external_texture.cc
+++ b/src/tint/transform/multiplanar_external_texture.cc
@@ -34,8 +34,8 @@
 
 bool ShouldRun(const Program* program) {
     for (auto* node : program->ASTNodes().Objects()) {
-        if (auto* ty = node->As<ast::Type>()) {
-            if (program->Sem().Get<type::ExternalTexture>(ty)) {
+        if (auto* expr = node->As<ast::Expression>()) {
+            if (Is<type::ExternalTexture>(program->TypeOf(expr))) {
                 return true;
             }
         }
@@ -263,7 +263,8 @@
             b.Member("gammaDecodeParams", b.ty("GammaTransferParams")),
             b.Member("gammaEncodeParams", b.ty("GammaTransferParams")),
             b.Member("gamutConversionMatrix", b.ty.mat3x3<f32>()),
-            b.Member("coordTransformationMatrix", b.ty.mat3x2<f32>())};
+            b.Member("coordTransformationMatrix", b.ty.mat3x2<f32>()),
+        };
 
         params_struct_sym = b.Symbols().New("ExternalTextureParams");
 
diff --git a/src/tint/transform/pad_structs.cc b/src/tint/transform/pad_structs.cc
index 7d9e7cd..954069c 100644
--- a/src/tint/transform/pad_structs.cc
+++ b/src/tint/transform/pad_structs.cc
@@ -75,7 +75,7 @@
             }
 
             auto* ty = mem->Type();
-            const ast::Type* type = CreateASTTypeFor(ctx, ty);
+            auto type = CreateASTTypeFor(ctx, ty);
 
             new_members.Push(b.Member(name, type));
 
diff --git a/src/tint/transform/remove_phonies.cc b/src/tint/transform/remove_phonies.cc
index 3b0dabd..6131892 100644
--- a/src/tint/transform/remove_phonies.cc
+++ b/src/tint/transform/remove_phonies.cc
@@ -108,7 +108,7 @@
                             auto name = b.Symbols().New("phony_sink");
                             utils::Vector<const ast::Parameter*, 8> params;
                             for (auto* ty : sig) {
-                                auto* ast_ty = CreateASTTypeFor(ctx, ty);
+                                auto ast_ty = CreateASTTypeFor(ctx, ty);
                                 params.Push(b.Param("p" + std::to_string(params.Length()), ast_ty));
                             }
                             b.Func(name, params, b.ty.void_(), {});
diff --git a/src/tint/transform/renamer.cc b/src/tint/transform/renamer.cc
index ec8dcee..f2aea29 100644
--- a/src/tint/transform/renamer.cc
+++ b/src/tint/transform/renamer.cc
@@ -22,6 +22,7 @@
 #include "src/tint/sem/call.h"
 #include "src/tint/sem/member_accessor_expression.h"
 #include "src/tint/sem/type_conversion.h"
+#include "src/tint/sem/type_expression.h"
 #include "src/tint/sem/type_initializer.h"
 #include "src/tint/text/unicode.h"
 
@@ -1292,20 +1293,28 @@
             [&](const ast::DiagnosticDirective* diagnostic) {
                 preserved_identifiers.Add(diagnostic->control.rule_name);
             },
-            [&](const ast::TypeName* ty) { preserve_if_builtin_type(ty->name); },
             [&](const ast::IdentifierExpression* expr) {
-                if (src->Sem().Get<sem::BuiltinEnumExpressionBase>(expr)) {
-                    preserved_identifiers.Add(expr->identifier);
-                }
+                Switch(
+                    src->Sem().Get(expr),  //
+                    [&](const sem::BuiltinEnumExpressionBase*) {
+                        preserved_identifiers.Add(expr->identifier);
+                    },
+                    [&](const sem::TypeExpression*) {
+                        preserve_if_builtin_type(expr->identifier);
+                    });
             },
             [&](const ast::CallExpression* call) {
-                if (auto* ident = call->target.name) {
-                    Switch(
-                        src->Sem().Get(call)->UnwrapMaterialize()->As<sem::Call>()->Target(),
-                        [&](const sem::Builtin*) { preserved_identifiers.Add(ident); },
-                        [&](const sem::TypeConversion*) { preserve_if_builtin_type(ident); },
-                        [&](const sem::TypeInitializer*) { preserve_if_builtin_type(ident); });
-                }
+                Switch(
+                    src->Sem().Get(call)->UnwrapMaterialize()->As<sem::Call>()->Target(),
+                    [&](const sem::Builtin*) {
+                        preserved_identifiers.Add(call->target->identifier);
+                    },
+                    [&](const sem::TypeConversion*) {
+                        preserve_if_builtin_type(call->target->identifier);
+                    },
+                    [&](const sem::TypeInitializer*) {
+                        preserve_if_builtin_type(call->target->identifier);
+                    });
             });
     }
 
@@ -1369,7 +1378,7 @@
         if (auto* tmpl_ident = ident->As<ast::TemplatedIdentifier>()) {
             auto args = ctx.Clone(tmpl_ident->arguments);
             return ctx.dst->create<ast::TemplatedIdentifier>(ctx.Clone(ident->source), replacement,
-                                                             std::move(args));
+                                                             std::move(args), utils::Empty);
         }
         return ctx.dst->create<ast::Identifier>(ctx.Clone(ident->source), replacement);
     });
diff --git a/src/tint/transform/renamer_test.cc b/src/tint/transform/renamer_test.cc
index 982a9a2..71cf2a2 100644
--- a/src/tint/transform/renamer_test.cc
+++ b/src/tint/transform/renamer_test.cc
@@ -1667,23 +1667,45 @@
 /// @return all the identifiers parsed as keywords
 std::unordered_set<std::string> Keywords() {
     return {
+        "array",
+        "atomic",
         "bool",
         "f16",
         "f32",
         "i32",
+        "mat2x2",
+        "mat2x3",
+        "mat2x4",
+        "mat3x2",
+        "mat3x3",
+        "mat3x4",
+        "mat4x2",
+        "mat4x3",
+        "mat4x4",
+        "ptr",
         "sampler_comparison",
         "sampler",
+        "texture_1d",
+        "texture_2d_array",
+        "texture_2d",
+        "texture_3d",
+        "texture_cube_array",
+        "texture_cube",
         "texture_depth_2d_array",
         "texture_depth_2d",
         "texture_depth_cube_array",
         "texture_depth_cube",
         "texture_depth_multisampled_2d",
         "texture_external",
+        "texture_multisampled_2d",
         "texture_storage_1d",
-        "texture_storage_2d",
         "texture_storage_2d_array",
+        "texture_storage_2d",
         "texture_storage_3d",
         "u32",
+        "vec2",
+        "vec3",
+        "vec4",
     };
 }
 
@@ -1802,6 +1824,30 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(RenamerBuiltinTypeTest, PreserveTypeExpression) {
+    auto src = R"(
+enable f16;
+
+@fragment
+fn f() {
+  var v : array<f32, 2> = array<f32, 2>();
+}
+)";
+
+    auto expect = R"(
+enable f16;
+
+@fragment
+fn tint_symbol() {
+  var tint_symbol_1 : array<f32, 2> = array<f32, 2>();
+}
+)";
+
+    auto got = Run<Renamer>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
 TEST_P(RenamerBuiltinTypeTest, RenameShadowedByAlias) {
     auto expand = [&](const char* source) {
         auto out = utils::ReplaceAll(source, "$name", GetParam());
diff --git a/src/tint/transform/robustness.cc b/src/tint/transform/robustness.cc
index 87403e8..579d684 100644
--- a/src/tint/transform/robustness.cc
+++ b/src/tint/transform/robustness.cc
@@ -182,7 +182,7 @@
             }
             return 1u;
         };
-        auto scalar_or_vec_ty = [&](const ast::Type* scalar, uint32_t width) -> const ast::Type* {
+        auto scalar_or_vec_ty = [&](ast::Type scalar, uint32_t width) {
             if (width > 1) {
                 return b.ty.vec(scalar, width);
             }
@@ -191,7 +191,7 @@
         auto scalar_or_vec = [&](const ast::Expression* scalar,
                                  uint32_t width) -> const ast::Expression* {
             if (width > 1) {
-                return b.Call(b.ty.vec(nullptr, width), scalar);
+                return b.Call(b.ty.vec<Infer>(width), scalar);
             }
             return scalar;
         };
diff --git a/src/tint/transform/spirv_atomic.cc b/src/tint/transform/spirv_atomic.cc
index afbc5cb..b3924cc 100644
--- a/src/tint/transform/spirv_atomic.cc
+++ b/src/tint/transform/spirv_atomic.cc
@@ -131,7 +131,7 @@
                     for (size_t i = 0; i < str->members.Length(); i++) {
                         auto* member = str->members[i];
                         if (forked.atomic_members.count(i)) {
-                            auto* type = AtomicTypeFor(ctx.src->Sem().Get(member)->Type());
+                            auto type = AtomicTypeFor(ctx.src->Sem().Get(member)->Type());
                             auto name = ctx.src->Symbols().NameFor(member->name->symbol);
                             members.Push(b.Member(name, type, ctx.Clone(member->attributes)));
                         } else {
@@ -169,7 +169,7 @@
                 [&](const sem::VariableUser* user) {
                     auto* v = user->Variable()->Declaration();
                     if (v->type && atomic_variables.emplace(user->Variable()).second) {
-                        ctx.Replace(v->type, AtomicTypeFor(user->Variable()->Type()));
+                        ctx.Replace(v->type.expr, b.Expr(AtomicTypeFor(user->Variable()->Type())));
                     }
                     if (auto* ctor = user->Variable()->Initializer()) {
                         atomic_expressions.Add(ctor);
@@ -193,13 +193,13 @@
         }
     }
 
-    const ast::Type* AtomicTypeFor(const type::Type* ty) {
+    ast::Type AtomicTypeFor(const type::Type* ty) {
         return Switch(
             ty,  //
             [&](const type::I32*) { return b.ty.atomic(CreateASTTypeFor(ctx, ty)); },
             [&](const type::U32*) { return b.ty.atomic(CreateASTTypeFor(ctx, ty)); },
             [&](const sem::Struct* str) { return b.ty(Fork(str->Declaration()).name); },
-            [&](const type::Array* arr) -> const ast::Type* {
+            [&](const type::Array* arr) {
                 if (arr->Count()->Is<type::RuntimeArrayCount>()) {
                     return b.ty.array(AtomicTypeFor(arr->ElemType()));
                 }
@@ -221,7 +221,7 @@
             [&](Default) {
                 TINT_ICE(Transform, b.Diagnostics())
                     << "unhandled type: " << ty->FriendlyName(ctx.src->Symbols());
-                return nullptr;
+                return ast::Type{};
             });
     }
 
diff --git a/src/tint/transform/std140.cc b/src/tint/transform/std140.cc
index 825685e..4c487d0 100644
--- a/src/tint/transform/std140.cc
+++ b/src/tint/transform/std140.cc
@@ -308,7 +308,7 @@
 
                             continue;  // Next member
                         }
-                    } else if (auto* std140_ty = Std140Type(member->Type())) {
+                    } else if (auto std140_ty = Std140Type(member->Type())) {
                         // Member is of a type that requires forking for std140-layout
                         fork_std140 = true;
                         auto attrs = ctx.Clone(member->Declaration()->attributes);
@@ -352,8 +352,8 @@
             if (auto* var = global->As<ast::Var>()) {
                 if (var->declared_address_space == type::AddressSpace::kUniform) {
                     auto* v = sem.Get(var);
-                    if (auto* std140_ty = Std140Type(v->Type()->UnwrapRef())) {
-                        ctx.Replace(global->type, std140_ty);
+                    if (auto std140_ty = Std140Type(v->Type()->UnwrapRef())) {
+                        ctx.Replace(global->type.expr, b.Expr(std140_ty));
                         std140_uniforms.Add(v);
                     }
                 }
@@ -400,16 +400,16 @@
     ///          If the semantic type is not split for std140-layout, then nullptr is returned.
     /// @note will construct new std140 structures to hold decomposed matrices, populating
     ///       #std140_mats.
-    const ast::Type* Std140Type(const type::Type* ty) {
+    ast::Type Std140Type(const type::Type* ty) {
         return Switch(
             ty,  //
-            [&](const sem::Struct* str) -> const ast::Type* {
+            [&](const sem::Struct* str) {
                 if (auto std140 = std140_structs.Find(str)) {
                     return b.ty(*std140);
                 }
-                return nullptr;
+                return ast::Type{};
             },
-            [&](const type::Matrix* mat) -> const ast::Type* {
+            [&](const type::Matrix* mat) {
                 if (MatrixNeedsDecomposing(mat)) {
                     auto std140_mat = std140_mats.GetOrCreate(mat, [&] {
                         auto name = b.Symbols().New("mat" + std::to_string(mat->columns()) + "x" +
@@ -426,10 +426,10 @@
                     });
                     return b.ty(std140_mat.name);
                 }
-                return nullptr;
+                return ast::Type{};
             },
-            [&](const type::Array* arr) -> const ast::Type* {
-                if (auto* std140 = Std140Type(arr->ElemType())) {
+            [&](const type::Array* arr) {
+                if (auto std140 = Std140Type(arr->ElemType())) {
                     utils::Vector<const ast::Attribute*, 1> attrs;
                     if (!arr->IsStrideImplicit()) {
                         attrs.Push(b.create<ast::StrideAttribute>(arr->Stride()));
@@ -444,10 +444,9 @@
                             << "unexpected non-constant array count";
                         count = 1;
                     }
-                    return b.create<ast::Array>(std140, b.Expr(u32(count.value())),
-                                                std::move(attrs));
+                    return b.ty.array(std140, b.Expr(u32(count.value())), std::move(attrs));
                 }
-                return nullptr;
+                return ast::Type{};
             });
     }
 
@@ -483,7 +482,7 @@
 
             // Build the member
             const auto col_name = name_prefix + std::to_string(i);
-            const auto* col_ty = CreateASTTypeFor(ctx, mat->ColumnType());
+            const auto col_ty = CreateASTTypeFor(ctx, mat->ColumnType());
             const auto* col_member = b.Member(col_name, col_ty, std::move(attributes));
             // Record the member for std140_mat_members
             out.Push(col_member);
@@ -702,7 +701,7 @@
                     for (auto* member : str->Members()) {
                         if (auto col_members = std140_mat_members.Find(member)) {
                             // std140 decomposed matrix. Reassemble.
-                            auto* mat_ty = CreateASTTypeFor(ctx, member->Type());
+                            auto mat_ty = CreateASTTypeFor(ctx, member->Type());
                             auto mat_args =
                                 utils::Transform(*col_members, [&](const ast::StructMember* m) {
                                     return b.MemberAccessor(param, m->name->symbol);
@@ -723,7 +722,7 @@
                     if (TINT_LIKELY(std140_mat)) {
                         utils::Vector<const ast::Expression*, 8> args;
                         // std140 decomposed matrix. Reassemble.
-                        auto* mat_ty = CreateASTTypeFor(ctx, mat);
+                        auto mat_ty = CreateASTTypeFor(ctx, mat);
                         auto mat_args = utils::Transform(std140_mat->columns, [&](Symbol name) {
                             return b.MemberAccessor(param, name);
                         });
@@ -764,7 +763,7 @@
                 });
 
             // Generate the function
-            auto* ret_ty = CreateASTTypeFor(ctx, ty);
+            auto ret_ty = CreateASTTypeFor(ctx, ty);
             auto fn_sym = b.Symbols().New("conv_" + ConvertSuffix(ty));
             b.Func(fn_sym, utils::Vector{param}, ret_ty, std::move(stmts));
             return fn_sym;
@@ -1046,7 +1045,7 @@
         stmts.Push(b.Return(expr));
 
         // Build the function
-        auto* ret_ty = CreateASTTypeFor(ctx, ty);
+        auto ret_ty = CreateASTTypeFor(ctx, ty);
         auto fn_sym = b.Symbols().New(name);
         b.Func(fn_sym, std::move(dynamic_index_params), ret_ty, std::move(stmts));
         return fn_sym;
diff --git a/src/tint/transform/substitute_override.cc b/src/tint/transform/substitute_override.cc
index f5a859b..2d6b995 100644
--- a/src/tint/transform/substitute_override.cc
+++ b/src/tint/transform/substitute_override.cc
@@ -64,7 +64,7 @@
 
         auto source = ctx.Clone(w->source);
         auto sym = ctx.Clone(w->name->symbol);
-        auto* ty = ctx.Clone(w->type);
+        ast::Type ty = w->type ? ctx.Clone(w->type) : ast::Type{};
 
         // No replacement provided, just clone the override node as a const.
         auto iter = data->map.find(sem->OverrideId());
diff --git a/src/tint/transform/texture_1d_to_2d.cc b/src/tint/transform/texture_1d_to_2d.cc
index 84d480a..d1caaab 100644
--- a/src/tint/transform/texture_1d_to_2d.cc
+++ b/src/tint/transform/texture_1d_to_2d.cc
@@ -19,6 +19,7 @@
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/function.h"
 #include "src/tint/sem/statement.h"
+#include "src/tint/sem/type_expression.h"
 #include "src/tint/type/texture_dimension.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::Texture1DTo2D);
@@ -46,7 +47,7 @@
     }
     for (auto* var : program->AST().GlobalVariables()) {
         if (Switch(
-                program->Sem().Get(var->type),
+                program->Sem().Get(var)->Type()->UnwrapRef(),
                 [&](const type::SampledTexture* tex) {
                     return tex->dim() == type::TextureDimension::k1d;
                 },
@@ -83,8 +84,7 @@
             return SkipTransform;
         }
 
-        auto create_var = [&](const ast::Variable* v,
-                              const ast::Type* type) -> const ast::Variable* {
+        auto create_var = [&](const ast::Variable* v, ast::Type type) -> const ast::Variable* {
             if (v->As<ast::Parameter>()) {
                 return ctx.dst->Param(ctx.Clone(v->name->symbol), type, ctx.Clone(v->attributes));
             } else {
@@ -94,11 +94,11 @@
 
         ctx.ReplaceAll([&](const ast::Variable* v) -> const ast::Variable* {
             const ast::Variable* r = Switch(
-                sem.Get(v->type),
+                sem.Get(v)->Type()->UnwrapRef(),
                 [&](const type::SampledTexture* tex) -> const ast::Variable* {
                     if (tex->dim() == type::TextureDimension::k1d) {
-                        auto* type = ctx.dst->create<ast::SampledTexture>(
-                            type::TextureDimension::k2d, CreateASTTypeFor(ctx, tex->type()));
+                        auto type = ctx.dst->ty.sampled_texture(type::TextureDimension::k2d,
+                                                                CreateASTTypeFor(ctx, tex->type()));
                         return create_var(v, type);
                     } else {
                         return nullptr;
@@ -106,9 +106,9 @@
                 },
                 [&](const type::StorageTexture* storage_tex) -> const ast::Variable* {
                     if (storage_tex->dim() == type::TextureDimension::k1d) {
-                        auto* type = ctx.dst->ty.storage_texture(type::TextureDimension::k2d,
-                                                                 storage_tex->texel_format(),
-                                                                 storage_tex->access());
+                        auto type = ctx.dst->ty.storage_texture(type::TextureDimension::k2d,
+                                                                storage_tex->texel_format(),
+                                                                storage_tex->access());
                         return create_var(v, type);
                     } else {
                         return nullptr;
@@ -172,7 +172,7 @@
                 }
                 index++;
             }
-            return ctx.dst->Call(ctx.Clone(c->target.name), args);
+            return ctx.dst->Call(ctx.Clone(c->target), args);
         });
 
         ctx.Clone();
diff --git a/src/tint/transform/transform.cc b/src/tint/transform/transform.cc
index a2ae237..cc09e6c 100644
--- a/src/tint/transform/transform.cc
+++ b/src/tint/transform/transform.cc
@@ -73,9 +73,9 @@
         << "unable to remove statement from parent of type " << sem->TypeInfo().name;
 }
 
-const ast::Type* Transform::CreateASTTypeFor(CloneContext& ctx, const type::Type* ty) {
+ast::Type Transform::CreateASTTypeFor(CloneContext& ctx, const type::Type* ty) {
     if (ty->Is<type::Void>()) {
-        return nullptr;
+        return ast::Type{};
     }
     if (ty->Is<type::I32>()) {
         return ctx.dst->ty.i32();
@@ -93,21 +93,21 @@
         return ctx.dst->ty.bool_();
     }
     if (auto* m = ty->As<type::Matrix>()) {
-        auto* el = CreateASTTypeFor(ctx, m->type());
-        return ctx.dst->create<ast::Matrix>(el, m->rows(), m->columns());
+        auto el = CreateASTTypeFor(ctx, m->type());
+        return ctx.dst->ty.mat(el, m->columns(), m->rows());
     }
     if (auto* v = ty->As<type::Vector>()) {
-        auto* el = CreateASTTypeFor(ctx, v->type());
-        return ctx.dst->create<ast::Vector>(el, v->Width());
+        auto el = CreateASTTypeFor(ctx, v->type());
+        return ctx.dst->ty.vec(el, v->Width());
     }
     if (auto* a = ty->As<type::Array>()) {
-        auto* el = CreateASTTypeFor(ctx, a->ElemType());
+        auto el = CreateASTTypeFor(ctx, a->ElemType());
         utils::Vector<const ast::Attribute*, 1> attrs;
         if (!a->IsStrideImplicit()) {
             attrs.Push(ctx.dst->create<ast::StrideAttribute>(a->Stride()));
         }
         if (a->Count()->Is<type::RuntimeArrayCount>()) {
-            return ctx.dst->ty.array(el, nullptr, std::move(attrs));
+            return ctx.dst->ty.array(el, std::move(attrs));
         }
         if (auto* override = a->Count()->As<sem::NamedOverrideArrayCount>()) {
             auto* count = ctx.Clone(override->variable->Declaration());
@@ -144,7 +144,7 @@
         return CreateASTTypeFor(ctx, s->StoreType());
     }
     if (auto* a = ty->As<type::Atomic>()) {
-        return ctx.dst->create<ast::Atomic>(CreateASTTypeFor(ctx, a->Type()));
+        return ctx.dst->ty.atomic(CreateASTTypeFor(ctx, a->Type()));
     }
     if (auto* t = ty->As<type::DepthTexture>()) {
         return ctx.dst->ty.depth_texture(t->dim());
@@ -156,11 +156,10 @@
         return ctx.dst->ty.external_texture();
     }
     if (auto* t = ty->As<type::MultisampledTexture>()) {
-        return ctx.dst->create<ast::MultisampledTexture>(t->dim(),
-                                                         CreateASTTypeFor(ctx, t->type()));
+        return ctx.dst->ty.multisampled_texture(t->dim(), CreateASTTypeFor(ctx, t->type()));
     }
     if (auto* t = ty->As<type::SampledTexture>()) {
-        return ctx.dst->create<ast::SampledTexture>(t->dim(), CreateASTTypeFor(ctx, t->type()));
+        return ctx.dst->ty.sampled_texture(t->dim(), CreateASTTypeFor(ctx, t->type()));
     }
     if (auto* t = ty->As<type::StorageTexture>()) {
         return ctx.dst->ty.storage_texture(t->dim(), t->texel_format(), t->access());
@@ -170,7 +169,7 @@
     }
     TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics())
         << "Unhandled type: " << ty->TypeInfo().name;
-    return nullptr;
+    return ast::Type{};
 }
 
 }  // namespace tint::transform
diff --git a/src/tint/transform/transform.h b/src/tint/transform/transform.h
index cc2f782..dfb9188 100644
--- a/src/tint/transform/transform.h
+++ b/src/tint/transform/transform.h
@@ -189,13 +189,11 @@
     /// @param stmt the statement to remove when the program is cloned
     static void RemoveStatement(CloneContext& ctx, const ast::Statement* stmt);
 
-    /// CreateASTTypeFor constructs new ast::Type nodes that reconstructs the
-    /// semantic type `ty`.
+    /// CreateASTTypeFor constructs new ast::Type that reconstructs the semantic type `ty`.
     /// @param ctx the clone context
     /// @param ty the semantic type to reconstruct
-    /// @returns a ast::Type that when resolved, will produce the semantic type
-    /// `ty`.
-    static const ast::Type* CreateASTTypeFor(CloneContext& ctx, const type::Type* ty);
+    /// @returns an ast::Type that when resolved, will produce the semantic type `ty`.
+    static ast::Type CreateASTTypeFor(CloneContext& ctx, const type::Type* ty);
 };
 
 }  // namespace tint::transform
diff --git a/src/tint/transform/transform_test.cc b/src/tint/transform/transform_test.cc
index 6d97614..f27a990 100644
--- a/src/tint/transform/transform_test.cc
+++ b/src/tint/transform/transform_test.cc
@@ -14,6 +14,7 @@
 
 #include <string>
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/clone_context.h"
 #include "src/tint/program_builder.h"
 #include "src/tint/transform/transform.h"
@@ -31,7 +32,7 @@
         return SkipTransform;
     }
 
-    const ast::Type* create(std::function<type::Type*(ProgramBuilder&)> create_sem_type) {
+    ast::Type create(std::function<type::Type*(ProgramBuilder&)> create_sem_type) {
         ProgramBuilder sem_type_builder;
         auto* sem_type = create_sem_type(sem_type_builder);
         Program program(std::move(sem_type_builder));
@@ -39,71 +40,60 @@
         return CreateASTTypeFor(ctx, sem_type);
     }
 
-    std::string TypeNameOf(const ast::Type* ty) const {
-        if (auto* type_name = ty->As<ast::TypeName>()) {
-            return ast_type_builder.Symbols().NameFor(type_name->name->symbol);
-        }
-        return "<not-a-typename>";
-    }
-
     ProgramBuilder ast_type_builder;
 };
 
 TEST_F(CreateASTTypeForTest, Basic) {
-    EXPECT_EQ(TypeNameOf(create([](ProgramBuilder& b) { return b.create<type::I32>(); })), "i32");
-    EXPECT_EQ(TypeNameOf(create([](ProgramBuilder& b) { return b.create<type::U32>(); })), "u32");
-    EXPECT_EQ(TypeNameOf(create([](ProgramBuilder& b) { return b.create<type::F32>(); })), "f32");
-    EXPECT_EQ(TypeNameOf(create([](ProgramBuilder& b) { return b.create<type::Bool>(); })), "bool");
+    auto check = [&](ast::Type ty, const char* expect) {
+        ast::CheckIdentifier(ast_type_builder.Symbols(), ty->identifier, expect);
+    };
+
+    check(create([](ProgramBuilder& b) { return b.create<type::I32>(); }), "i32");
+    check(create([](ProgramBuilder& b) { return b.create<type::U32>(); }), "u32");
+    check(create([](ProgramBuilder& b) { return b.create<type::F32>(); }), "f32");
+    check(create([](ProgramBuilder& b) { return b.create<type::Bool>(); }), "bool");
     EXPECT_EQ(create([](ProgramBuilder& b) { return b.create<type::Void>(); }), nullptr);
 }
 
 TEST_F(CreateASTTypeForTest, Matrix) {
-    auto* mat = create([](ProgramBuilder& b) {
+    auto mat = create([](ProgramBuilder& b) {
         auto* column_type = b.create<type::Vector>(b.create<type::F32>(), 2u);
         return b.create<type::Matrix>(column_type, 3u);
     });
-    ASSERT_TRUE(mat->Is<ast::Matrix>());
-    EXPECT_EQ(TypeNameOf(mat->As<ast::Matrix>()->type), "f32");
-    ASSERT_EQ(mat->As<ast::Matrix>()->columns, 3u);
-    ASSERT_EQ(mat->As<ast::Matrix>()->rows, 2u);
+
+    ast::CheckIdentifier(ast_type_builder.Symbols(), mat, ast::Template("mat3x2", "f32"));
 }
 
 TEST_F(CreateASTTypeForTest, Vector) {
-    auto* vec =
+    auto vec =
         create([](ProgramBuilder& b) { return b.create<type::Vector>(b.create<type::F32>(), 2u); });
-    ASSERT_TRUE(vec->Is<ast::Vector>());
-    EXPECT_EQ(TypeNameOf(vec->As<ast::Vector>()->type), "f32");
-    ASSERT_EQ(vec->As<ast::Vector>()->width, 2u);
+
+    ast::CheckIdentifier(ast_type_builder.Symbols(), vec, ast::Template("vec2", "f32"));
 }
 
 TEST_F(CreateASTTypeForTest, ArrayImplicitStride) {
-    auto* arr = create([](ProgramBuilder& b) {
+    auto arr = create([](ProgramBuilder& b) {
         return b.create<type::Array>(b.create<type::F32>(), b.create<type::ConstantArrayCount>(2u),
                                      4u, 4u, 32u, 32u);
     });
-    ASSERT_TRUE(arr->Is<ast::Array>());
-    EXPECT_EQ(TypeNameOf(arr->As<ast::Array>()->type), "f32");
-    ASSERT_EQ(arr->As<ast::Array>()->attributes.Length(), 0u);
 
-    auto* size = arr->As<ast::Array>()->count->As<ast::IntLiteralExpression>();
-    ASSERT_NE(size, nullptr);
-    EXPECT_EQ(size->value, 2);
+    ast::CheckIdentifier(ast_type_builder.Symbols(), arr, ast::Template("array", "f32", 2_u));
+    auto* tmpl_attr = arr->identifier->As<ast::TemplatedIdentifier>();
+    ASSERT_NE(tmpl_attr, nullptr);
+    EXPECT_TRUE(tmpl_attr->attributes.IsEmpty());
 }
 
 TEST_F(CreateASTTypeForTest, ArrayNonImplicitStride) {
-    auto* arr = create([](ProgramBuilder& b) {
+    auto arr = create([](ProgramBuilder& b) {
         return b.create<type::Array>(b.create<type::F32>(), b.create<type::ConstantArrayCount>(2u),
                                      4u, 4u, 64u, 32u);
     });
-    ASSERT_TRUE(arr->Is<ast::Array>());
-    EXPECT_EQ(TypeNameOf(arr->As<ast::Array>()->type), "f32");
-    ASSERT_EQ(arr->As<ast::Array>()->attributes.Length(), 1u);
-    ASSERT_TRUE(arr->As<ast::Array>()->attributes[0]->Is<ast::StrideAttribute>());
-    ASSERT_EQ(arr->As<ast::Array>()->attributes[0]->As<ast::StrideAttribute>()->stride, 64u);
-
-    auto* size = arr->As<ast::Array>()->count->As<ast::IntLiteralExpression>();
-    ASSERT_NE(size, nullptr);
-    EXPECT_EQ(size->value, 2);
+    ast::CheckIdentifier(ast_type_builder.Symbols(), arr, ast::Template("array", "f32", 2_u));
+    auto* tmpl_attr = arr->identifier->As<ast::TemplatedIdentifier>();
+    ASSERT_NE(tmpl_attr, nullptr);
+    ASSERT_EQ(tmpl_attr->attributes.Length(), 1u);
+    ASSERT_TRUE(tmpl_attr->attributes[0]->Is<ast::StrideAttribute>());
+    ASSERT_EQ(tmpl_attr->attributes[0]->As<ast::StrideAttribute>()->stride, 64u);
 }
 
 // crbug.com/tint/1764
@@ -123,19 +113,18 @@
     auto* arr_ty = program.Sem().Get(alias);
 
     CloneContext ctx(&ast_type_builder, &program, false);
-    auto* ast_ty = tint::As<ast::TypeName>(CreateASTTypeFor(ctx, arr_ty));
-    ASSERT_NE(ast_ty, nullptr);
-    EXPECT_EQ(ast_type_builder.Symbols().NameFor(ast_ty->name->symbol), "A");
+    auto ast_ty = CreateASTTypeFor(ctx, arr_ty);
+    ast::CheckIdentifier(ast_type_builder.Symbols(), ast_ty, "A");
 }
 
 TEST_F(CreateASTTypeForTest, Struct) {
-    auto* str = create([](ProgramBuilder& b) {
+    auto str = create([](ProgramBuilder& b) {
         auto* decl = b.Structure("S", {});
         return b.create<sem::Struct>(decl, decl->source, decl->name->symbol, utils::Empty,
                                      4u /* align */, 4u /* size */, 4u /* size_no_padding */);
     });
-    ASSERT_TRUE(str->Is<ast::TypeName>());
-    EXPECT_EQ(ast_type_builder.Symbols().NameFor(str->As<ast::TypeName>()->name->symbol), "S");
+
+    ast::CheckIdentifier(ast_type_builder.Symbols(), str, "S");
 }
 
 }  // namespace
diff --git a/src/tint/transform/truncate_interstage_variables.cc b/src/tint/transform/truncate_interstage_variables.cc
index cf15a38..b4ae24e 100644
--- a/src/tint/transform/truncate_interstage_variables.cc
+++ b/src/tint/transform/truncate_interstage_variables.cc
@@ -143,11 +143,12 @@
                        utils::Vector{b.Param("io", ctx.Clone(func_ast->return_type))},
                        b.ty(new_struct_sym),
                        utils::Vector{
-                           b.Return(b.Call(b.ty(new_struct_sym), std::move(initializer_exprs)))});
+                           b.Return(b.Call(new_struct_sym, std::move(initializer_exprs))),
+                       });
                 return TruncatedStructAndConverter{new_struct_sym, mapping_fn_sym};
             });
 
-        ctx.Replace(func_ast->return_type, b.ty(entry.truncated_struct));
+        ctx.Replace(func_ast->return_type.expr, b.Expr(entry.truncated_struct));
 
         entry_point_functions_to_truncate_functions.Add(func_sem, entry.truncate_fn);
     }
diff --git a/src/tint/transform/unshadow.cc b/src/tint/transform/unshadow.cc
index ac00a39..8f79544 100644
--- a/src/tint/transform/unshadow.cc
+++ b/src/tint/transform/unshadow.cc
@@ -56,7 +56,7 @@
             renamed_to.Add(v, symbol);
 
             auto source = ctx.Clone(decl->source);
-            auto* type = ctx.Clone(decl->type);
+            auto type = decl->type ? ctx.Clone(decl->type) : ast::Type{};
             auto* initializer = ctx.Clone(decl->initializer);
             auto attributes = ctx.Clone(decl->attributes);
             return Switch(
diff --git a/src/tint/transform/vertex_pulling.cc b/src/tint/transform/vertex_pulling.cc
index 438ede8..11984cd 100644
--- a/src/tint/transform/vertex_pulling.cc
+++ b/src/tint/transform/vertex_pulling.cc
@@ -395,7 +395,7 @@
                 // Convert the fetched scalar/vector if WGSL variable is of `f16` types
                 if (var_dt.base_type == BaseWGSLType::kF16) {
                     // The type of the same element number of base type of target WGSL variable
-                    const ast::Type* loaded_data_target_type;
+                    ast::Type loaded_data_target_type;
                     if (fmt_dt.width == 1) {
                         loaded_data_target_type = b.ty.f16();
                     } else {
@@ -443,8 +443,7 @@
                         }
                     }
 
-                    const ast::Type* target_ty = CreateASTTypeFor(ctx, var.type);
-                    value = b.Call(target_ty, values);
+                    value = b.Call(CreateASTTypeFor(ctx, var.type), values);
                 }
 
                 // Assign the value to the WGSL variable
@@ -735,7 +734,7 @@
                                    uint32_t offset,
                                    uint32_t buffer,
                                    uint32_t element_stride,
-                                   const ast::Type* base_type,
+                                   ast::Type base_type,
                                    VertexFormat base_format,
                                    uint32_t count) {
         utils::Vector<const ast::Expression*, 8> expr_list;
@@ -745,7 +744,7 @@
             expr_list.Push(LoadPrimitive(array_base, primitive_offset, buffer, base_format));
         }
 
-        return b.Call(b.create<ast::Vector>(base_type, count), std::move(expr_list));
+        return b.Call(b.ty.vec(base_type, count), std::move(expr_list));
     }
 
     /// Process a non-struct entry point parameter.
@@ -757,7 +756,7 @@
         if (ast::HasAttribute<ast::LocationAttribute>(param->attributes)) {
             // Create a function-scope variable to replace the parameter.
             auto func_var_sym = ctx.Clone(param->name->symbol);
-            auto* func_var_type = ctx.Clone(param->type);
+            auto func_var_type = ctx.Clone(param->type);
             auto* func_var = b.Var(func_var_sym, func_var_type);
             ctx.InsertFront(func->body->statements, b.Decl(func_var));
             // Capture mapping from location to the new variable.
@@ -856,7 +855,7 @@
             utils::Vector<const ast::StructMember*, 8> new_members;
             for (auto* member : members_to_clone) {
                 auto member_name = ctx.Clone(member->name);
-                auto* member_type = ctx.Clone(member->type);
+                auto member_type = ctx.Clone(member->type);
                 auto member_attrs = ctx.Clone(member->attributes);
                 new_members.Push(b.Member(member_name, member_type, std::move(member_attrs)));
             }
@@ -926,7 +925,7 @@
 
         // Rewrite the function header with the new parameters.
         auto func_sym = ctx.Clone(func->name->symbol);
-        auto* ret_type = ctx.Clone(func->return_type);
+        auto ret_type = ctx.Clone(func->return_type);
         auto* body = ctx.Clone(func->body);
         auto attrs = ctx.Clone(func->attributes);
         auto ret_attrs = ctx.Clone(func->return_type_attributes);
diff --git a/src/tint/type/builtin.cc b/src/tint/type/builtin.cc
index b400ccb..828bf57 100644
--- a/src/tint/type/builtin.cc
+++ b/src/tint/type/builtin.cc
@@ -28,6 +28,12 @@
 /// @param str the string to parse
 /// @returns the parsed enum, or Builtin::kUndefined if the string could not be parsed.
 Builtin ParseBuiltin(std::string_view str) {
+    if (str == "array") {
+        return Builtin::kArray;
+    }
+    if (str == "atomic") {
+        return Builtin::kAtomic;
+    }
     if (str == "bool") {
         return Builtin::kBool;
     }
@@ -40,66 +46,114 @@
     if (str == "i32") {
         return Builtin::kI32;
     }
+    if (str == "mat2x2") {
+        return Builtin::kMat2X2;
+    }
     if (str == "mat2x2f") {
         return Builtin::kMat2X2F;
     }
     if (str == "mat2x2h") {
         return Builtin::kMat2X2H;
     }
+    if (str == "mat2x3") {
+        return Builtin::kMat2X3;
+    }
     if (str == "mat2x3f") {
         return Builtin::kMat2X3F;
     }
     if (str == "mat2x3h") {
         return Builtin::kMat2X3H;
     }
+    if (str == "mat2x4") {
+        return Builtin::kMat2X4;
+    }
     if (str == "mat2x4f") {
         return Builtin::kMat2X4F;
     }
     if (str == "mat2x4h") {
         return Builtin::kMat2X4H;
     }
+    if (str == "mat3x2") {
+        return Builtin::kMat3X2;
+    }
     if (str == "mat3x2f") {
         return Builtin::kMat3X2F;
     }
     if (str == "mat3x2h") {
         return Builtin::kMat3X2H;
     }
+    if (str == "mat3x3") {
+        return Builtin::kMat3X3;
+    }
     if (str == "mat3x3f") {
         return Builtin::kMat3X3F;
     }
     if (str == "mat3x3h") {
         return Builtin::kMat3X3H;
     }
+    if (str == "mat3x4") {
+        return Builtin::kMat3X4;
+    }
     if (str == "mat3x4f") {
         return Builtin::kMat3X4F;
     }
     if (str == "mat3x4h") {
         return Builtin::kMat3X4H;
     }
+    if (str == "mat4x2") {
+        return Builtin::kMat4X2;
+    }
     if (str == "mat4x2f") {
         return Builtin::kMat4X2F;
     }
     if (str == "mat4x2h") {
         return Builtin::kMat4X2H;
     }
+    if (str == "mat4x3") {
+        return Builtin::kMat4X3;
+    }
     if (str == "mat4x3f") {
         return Builtin::kMat4X3F;
     }
     if (str == "mat4x3h") {
         return Builtin::kMat4X3H;
     }
+    if (str == "mat4x4") {
+        return Builtin::kMat4X4;
+    }
     if (str == "mat4x4f") {
         return Builtin::kMat4X4F;
     }
     if (str == "mat4x4h") {
         return Builtin::kMat4X4H;
     }
+    if (str == "ptr") {
+        return Builtin::kPtr;
+    }
     if (str == "sampler") {
         return Builtin::kSampler;
     }
     if (str == "sampler_comparison") {
         return Builtin::kSamplerComparison;
     }
+    if (str == "texture_1d") {
+        return Builtin::kTexture1D;
+    }
+    if (str == "texture_2d") {
+        return Builtin::kTexture2D;
+    }
+    if (str == "texture_2d_array") {
+        return Builtin::kTexture2DArray;
+    }
+    if (str == "texture_3d") {
+        return Builtin::kTexture3D;
+    }
+    if (str == "texture_cube") {
+        return Builtin::kTextureCube;
+    }
+    if (str == "texture_cube_array") {
+        return Builtin::kTextureCubeArray;
+    }
     if (str == "texture_depth_2d") {
         return Builtin::kTextureDepth2D;
     }
@@ -118,6 +172,9 @@
     if (str == "texture_external") {
         return Builtin::kTextureExternal;
     }
+    if (str == "texture_multisampled_2d") {
+        return Builtin::kTextureMultisampled2D;
+    }
     if (str == "texture_storage_1d") {
         return Builtin::kTextureStorage1D;
     }
@@ -133,6 +190,9 @@
     if (str == "u32") {
         return Builtin::kU32;
     }
+    if (str == "vec2") {
+        return Builtin::kVec2;
+    }
     if (str == "vec2f") {
         return Builtin::kVec2F;
     }
@@ -145,6 +205,9 @@
     if (str == "vec2u") {
         return Builtin::kVec2U;
     }
+    if (str == "vec3") {
+        return Builtin::kVec3;
+    }
     if (str == "vec3f") {
         return Builtin::kVec3F;
     }
@@ -157,6 +220,9 @@
     if (str == "vec3u") {
         return Builtin::kVec3U;
     }
+    if (str == "vec4") {
+        return Builtin::kVec4;
+    }
     if (str == "vec4f") {
         return Builtin::kVec4F;
     }
@@ -176,6 +242,10 @@
     switch (value) {
         case Builtin::kUndefined:
             return out << "undefined";
+        case Builtin::kArray:
+            return out << "array";
+        case Builtin::kAtomic:
+            return out << "atomic";
         case Builtin::kBool:
             return out << "bool";
         case Builtin::kF16:
@@ -184,46 +254,78 @@
             return out << "f32";
         case Builtin::kI32:
             return out << "i32";
+        case Builtin::kMat2X2:
+            return out << "mat2x2";
         case Builtin::kMat2X2F:
             return out << "mat2x2f";
         case Builtin::kMat2X2H:
             return out << "mat2x2h";
+        case Builtin::kMat2X3:
+            return out << "mat2x3";
         case Builtin::kMat2X3F:
             return out << "mat2x3f";
         case Builtin::kMat2X3H:
             return out << "mat2x3h";
+        case Builtin::kMat2X4:
+            return out << "mat2x4";
         case Builtin::kMat2X4F:
             return out << "mat2x4f";
         case Builtin::kMat2X4H:
             return out << "mat2x4h";
+        case Builtin::kMat3X2:
+            return out << "mat3x2";
         case Builtin::kMat3X2F:
             return out << "mat3x2f";
         case Builtin::kMat3X2H:
             return out << "mat3x2h";
+        case Builtin::kMat3X3:
+            return out << "mat3x3";
         case Builtin::kMat3X3F:
             return out << "mat3x3f";
         case Builtin::kMat3X3H:
             return out << "mat3x3h";
+        case Builtin::kMat3X4:
+            return out << "mat3x4";
         case Builtin::kMat3X4F:
             return out << "mat3x4f";
         case Builtin::kMat3X4H:
             return out << "mat3x4h";
+        case Builtin::kMat4X2:
+            return out << "mat4x2";
         case Builtin::kMat4X2F:
             return out << "mat4x2f";
         case Builtin::kMat4X2H:
             return out << "mat4x2h";
+        case Builtin::kMat4X3:
+            return out << "mat4x3";
         case Builtin::kMat4X3F:
             return out << "mat4x3f";
         case Builtin::kMat4X3H:
             return out << "mat4x3h";
+        case Builtin::kMat4X4:
+            return out << "mat4x4";
         case Builtin::kMat4X4F:
             return out << "mat4x4f";
         case Builtin::kMat4X4H:
             return out << "mat4x4h";
+        case Builtin::kPtr:
+            return out << "ptr";
         case Builtin::kSampler:
             return out << "sampler";
         case Builtin::kSamplerComparison:
             return out << "sampler_comparison";
+        case Builtin::kTexture1D:
+            return out << "texture_1d";
+        case Builtin::kTexture2D:
+            return out << "texture_2d";
+        case Builtin::kTexture2DArray:
+            return out << "texture_2d_array";
+        case Builtin::kTexture3D:
+            return out << "texture_3d";
+        case Builtin::kTextureCube:
+            return out << "texture_cube";
+        case Builtin::kTextureCubeArray:
+            return out << "texture_cube_array";
         case Builtin::kTextureDepth2D:
             return out << "texture_depth_2d";
         case Builtin::kTextureDepth2DArray:
@@ -236,6 +338,8 @@
             return out << "texture_depth_multisampled_2d";
         case Builtin::kTextureExternal:
             return out << "texture_external";
+        case Builtin::kTextureMultisampled2D:
+            return out << "texture_multisampled_2d";
         case Builtin::kTextureStorage1D:
             return out << "texture_storage_1d";
         case Builtin::kTextureStorage2D:
@@ -246,6 +350,8 @@
             return out << "texture_storage_3d";
         case Builtin::kU32:
             return out << "u32";
+        case Builtin::kVec2:
+            return out << "vec2";
         case Builtin::kVec2F:
             return out << "vec2f";
         case Builtin::kVec2H:
@@ -254,6 +360,8 @@
             return out << "vec2i";
         case Builtin::kVec2U:
             return out << "vec2u";
+        case Builtin::kVec3:
+            return out << "vec3";
         case Builtin::kVec3F:
             return out << "vec3f";
         case Builtin::kVec3H:
@@ -262,6 +370,8 @@
             return out << "vec3i";
         case Builtin::kVec3U:
             return out << "vec3u";
+        case Builtin::kVec4:
+            return out << "vec4";
         case Builtin::kVec4F:
             return out << "vec4f";
         case Builtin::kVec4H:
diff --git a/src/tint/type/builtin.h b/src/tint/type/builtin.h
index 3b57489..78e63c2 100644
--- a/src/tint/type/builtin.h
+++ b/src/tint/type/builtin.h
@@ -30,49 +30,71 @@
 /// An enumerator of builtin types.
 enum class Builtin {
     kUndefined,
+    kArray,
+    kAtomic,
     kBool,
     kF16,
     kF32,
     kI32,
+    kMat2X2,
     kMat2X2F,
     kMat2X2H,
+    kMat2X3,
     kMat2X3F,
     kMat2X3H,
+    kMat2X4,
     kMat2X4F,
     kMat2X4H,
+    kMat3X2,
     kMat3X2F,
     kMat3X2H,
+    kMat3X3,
     kMat3X3F,
     kMat3X3H,
+    kMat3X4,
     kMat3X4F,
     kMat3X4H,
+    kMat4X2,
     kMat4X2F,
     kMat4X2H,
+    kMat4X3,
     kMat4X3F,
     kMat4X3H,
+    kMat4X4,
     kMat4X4F,
     kMat4X4H,
+    kPtr,
     kSampler,
     kSamplerComparison,
+    kTexture1D,
+    kTexture2D,
+    kTexture2DArray,
+    kTexture3D,
+    kTextureCube,
+    kTextureCubeArray,
     kTextureDepth2D,
     kTextureDepth2DArray,
     kTextureDepthCube,
     kTextureDepthCubeArray,
     kTextureDepthMultisampled2D,
     kTextureExternal,
+    kTextureMultisampled2D,
     kTextureStorage1D,
     kTextureStorage2D,
     kTextureStorage2DArray,
     kTextureStorage3D,
     kU32,
+    kVec2,
     kVec2F,
     kVec2H,
     kVec2I,
     kVec2U,
+    kVec3,
     kVec3F,
     kVec3H,
     kVec3I,
     kVec3U,
+    kVec4,
     kVec4F,
     kVec4H,
     kVec4I,
@@ -90,49 +112,71 @@
 Builtin ParseBuiltin(std::string_view str);
 
 constexpr const char* kBuiltinStrings[] = {
+    "array",
+    "atomic",
     "bool",
     "f16",
     "f32",
     "i32",
+    "mat2x2",
     "mat2x2f",
     "mat2x2h",
+    "mat2x3",
     "mat2x3f",
     "mat2x3h",
+    "mat2x4",
     "mat2x4f",
     "mat2x4h",
+    "mat3x2",
     "mat3x2f",
     "mat3x2h",
+    "mat3x3",
     "mat3x3f",
     "mat3x3h",
+    "mat3x4",
     "mat3x4f",
     "mat3x4h",
+    "mat4x2",
     "mat4x2f",
     "mat4x2h",
+    "mat4x3",
     "mat4x3f",
     "mat4x3h",
+    "mat4x4",
     "mat4x4f",
     "mat4x4h",
+    "ptr",
     "sampler",
     "sampler_comparison",
+    "texture_1d",
+    "texture_2d",
+    "texture_2d_array",
+    "texture_3d",
+    "texture_cube",
+    "texture_cube_array",
     "texture_depth_2d",
     "texture_depth_2d_array",
     "texture_depth_cube",
     "texture_depth_cube_array",
     "texture_depth_multisampled_2d",
     "texture_external",
+    "texture_multisampled_2d",
     "texture_storage_1d",
     "texture_storage_2d",
     "texture_storage_2d_array",
     "texture_storage_3d",
     "u32",
+    "vec2",
     "vec2f",
     "vec2h",
     "vec2i",
     "vec2u",
+    "vec3",
     "vec3f",
     "vec3h",
     "vec3i",
     "vec3u",
+    "vec4",
     "vec4f",
     "vec4h",
     "vec4i",
diff --git a/src/tint/type/builtin_bench.cc b/src/tint/type/builtin_bench.cc
index aea57f5..f8b86cf 100644
--- a/src/tint/type/builtin_bench.cc
+++ b/src/tint/type/builtin_bench.cc
@@ -31,335 +31,489 @@
 
 void BuiltinParser(::benchmark::State& state) {
     const char* kStrings[] = {
-        "ccol",
-        "3",
-        "bVol",
+        "arccy",
+        "3a",
+        "aVray",
+        "array",
+        "arra1",
+        "qqrJy",
+        "arrll7y",
+        "atppmHHc",
+        "cto",
+        "abGmi",
+        "atomic",
+        "atvmiii",
+        "atWWm8c",
+        "xxtomc",
+        "bXgg",
+        "Xu",
+        "b3ol",
         "bool",
-        "1ool",
-        "bqoJ",
-        "bllo77",
-        "fppqH",
-        "",
-        "Gb",
-        "f16",
-        "f1vi",
-        "f8WW",
-        "fxx",
-        "fgg",
-        "X",
-        "332",
-        "f32",
-        "fE2",
-        "fPTT",
-        "dxx2",
-        "4432",
-        "iSVV2",
+        "booE",
+        "TTPol",
+        "xxool",
+        "4416",
+        "fSVV6",
         "RR2",
+        "f16",
+        "96",
+        "f1",
+        "VOR6",
+        "y3",
+        "l77rrn2",
+        "4032",
+        "f32",
+        "5",
+        "u377",
+        "kk2",
+        "ii",
+        "i3XX",
+        "55399II",
         "i32",
-        "92",
-        "i3",
-        "VOR2",
-        "ma2xyf",
-        "llnarr2772f",
-        "mat24200",
-        "mat2x2f",
-        "a2oof",
-        "zz2x2f",
-        "miitppx1",
-        "mat2xXXh",
-        "9II5ann2x2h",
-        "mataSSrHHYh",
-        "mat2x2h",
-        "makkh",
-        "jatgRx",
-        "mb2x2",
+        "irSSHHa",
+        "U",
+        "jV3",
+        "ax2",
+        "t2SGG",
+        "q2x2",
+        "mat2x2",
+        "at2",
+        "majjx",
+        "a2xrf",
         "mat2xjf",
-        "at2x3f",
-        "q2x3f",
+        "mNNw2x28",
+        "matx2f",
+        "mat2x2f",
+        "mrrt2x2f",
+        "Gat2x2f",
+        "mat2x2FF",
+        "at2h",
+        "marrx2h",
+        "t2x2h",
+        "mat2x2h",
+        "Da2xJJh",
+        "ma82",
+        "m11k2",
+        "matx3",
+        "maJx3",
+        "cat2x3",
+        "mat2x3",
+        "mat2O3",
+        "ttKavv2x__",
+        "5txxx8",
+        "__qatF3",
+        "matqx3f",
+        "33atOx3f",
         "mat2x3f",
-        "matNN3f",
-        "at23vv",
-        "QQt2x3f",
-        "maffxr",
-        "mat2xjh",
-        "mNNw2x38",
-        "mat2x3h",
-        "matx3h",
-        "mrrt2x3h",
-        "Gat2x3h",
-        "mat2x4FF",
-        "at2f",
-        "marrx4f",
-        "mat2x4f",
-        "t2x4f",
-        "Da2xJJf",
-        "ma84",
-        "m11k4",
-        "matx4h",
-        "maJx4h",
-        "mat2x4h",
-        "mat2c4h",
-        "mat2x4O",
-        "KK_atvvtt4h",
-        "xx83x2f",
-        "__qatF2",
-        "matqx2f",
-        "mat3x2f",
-        "33atOx2f",
-        "mtt63x9oQQ",
-        "ma3x66f",
+        "mtt62x9oQQ",
+        "ma2x66f",
         "mtOxzz66",
-        "mat3yy2h",
-        "ZaHH2Z",
-        "mat3x2h",
-        "4WWt3q2h",
-        "mOO3x2h",
-        "oatY2h",
+        "mat2yy3h",
+        "ZaHH3Z",
+        "4WWt2q3h",
+        "mat2x3h",
+        "mOO2x3h",
+        "oatY3h",
         "matx",
-        "ma3xFf",
-        "at3x3w",
-        "mat3x3f",
-        "fGtxKf",
-        "matqKx3f",
-        "matmmxFf",
-        "at3x3h",
-        "mt3x3q",
-        "mat3xbb",
-        "mat3x3h",
-        "mi3x3h",
-        "maOO3xq",
-        "matTvvx3h",
-        "maFF3x4f",
-        "Pa00xQf",
-        "mPt3x4f",
-        "mat3x4f",
-        "ma773xss",
-        "RRCbb3x4f",
-        "mXXt3x4f",
-        "qaCC3xOOh",
-        "ma3s4L",
-        "mXt3x4h",
-        "mat3x4h",
-        "mat34h",
-        "qa3O4",
-        "mat3x22h",
-        "myzz40XX",
+        "ma2x4",
+        "matw4",
+        "ma2Gf",
+        "mat2x4",
+        "qatKKx4",
+        "mmmt2x4",
+        "at2x4",
+        "mt2x4q",
+        "mat2xbb",
+        "mi2x4f",
+        "mat2x4f",
+        "maOO2xq",
+        "matTvvx4f",
+        "maFF2x4f",
+        "Pa00xQh",
+        "mPt2x4h",
+        "ma772xss",
+        "mat2x4h",
+        "RRCbb2x4h",
+        "mXXt2x4h",
+        "qaCC2xOOh",
+        "mtsuL",
+        "mat3xX",
+        "mat3x",
+        "mat3x2",
+        "qqt2",
+        "mat3x22",
+        "mzzyt3x",
         "matVViP",
         "mannC2f",
-        "mat4x2f",
         "atx2AHHq",
+        "mat3x2f",
+        "may3x2",
+        "aOOOZZf",
+        "Vt12f",
+        "mff__3x2h",
+        "qaTMMl4h",
+        "mNNt3xg",
+        "mat3x2h",
+        "uub3XX2h",
+        "matx2h",
+        "Qt882h",
+        "maqx3",
+        "mat3113",
+        "Ft3xi22",
+        "mat3x3",
+        "m7t3x3",
+        "NNa323",
+        "VVat3x3",
+        "FaWW3w11f",
+        "mawwx3f",
+        "Dat3x3f",
+        "mat3x3f",
+        "mt3x3K",
+        "mat31PPhf",
+        "mat33f",
+        "mYYt3x3h",
+        "mttHH3kk",
+        "mat3rr3h",
+        "mat3x3h",
+        "WWas3x3h",
+        "Yt3x3h",
+        "mt3qfh",
+        "vvafu224",
+        "mt34",
+        "maY34",
+        "mat3x4",
+        "YYa7y3E4",
+        "Moatd4",
+        "mt3xMM",
+        "mat3x55f",
+        "maN34",
+        "ma3Ox33",
+        "mat3x4f",
+        "m3t3x4f",
+        "mam3xI",
+        "mnnt3r4K",
+        "m3XX",
+        "LatIx4h",
+        "at3fh",
+        "mat3x4h",
+        "mYtURD4",
+        "mah3x4h",
+        "uuIqt3x",
+        "mat4xH",
+        "at4Qvv",
+        "66ate",
         "mat4x2",
-        "fatK2f",
-        "ltgg2h",
-        "mat4xh",
-        "NTTtcx4h",
+        "mat7x",
+        "m0t55DD2",
+        "IIaH4x2",
+        "at4x2",
+        "rat4x299",
+        "mGtt41W2f",
+        "mat4x2f",
+        "yatx2",
+        "mt4x2f",
+        "IIaBB4x2f",
+        "TTat4x833",
+        "ddUUnntYYx2h",
+        "m5CCxxdZ",
         "mat4x2h",
-        "ma7ppl2h",
-        "mNNt4xg",
-        "uub4XX2h",
-        "matx3f",
-        "Qt883f",
-        "mt9q3f",
+        "matkkq2h",
+        "005itpxh",
+        "maIInnx2h",
+        "ccaKx",
+        "mtKK",
+        "ma664x3",
+        "mat4x3",
+        "mKKtPx",
+        "maxx43",
+        "matqx3",
+        "MMayySrxf",
+        "mat3f",
+        "tx3f",
         "mat4x3f",
-        "m11t4x3f",
-        "22at4iif",
-        "at4x377",
-        "m2t4xNh",
-        "mVVt4x3h",
-        "FaWW4w11h",
+        "ma5F4x3f",
+        "rra444z3f",
+        "matWW",
+        "CatZJXx3h",
+        "maPPx3h",
+        "mat4c3h",
         "mat4x3h",
-        "mawwx3h",
-        "Dat4x3h",
-        "mt4x3K",
-        "mat41PPhf",
-        "mat44f",
-        "mYYt4x4f",
+        "matPPll6h",
+        "mat993yy",
+        "mat4JKKh",
+        "ma_x4",
+        "a4K4",
+        "kVt4xz",
+        "mat4x4",
+        "qaSKx4",
+        "mat44",
+        "ma4xVV",
+        "AAatIxUf",
+        "mbj4f",
+        "YY444x",
         "mat4x4f",
-        "mttHH4kk",
-        "mat4rr4f",
-        "WWas4x4f",
-        "Yt4x4h",
-        "mt4qfh",
-        "mav224xuh",
+        "mao4x4",
+        "mtx114f",
+        "mtmxccf",
+        "aJJ4x4h",
+        "fCCDD4x4U",
+        "mgt4x4h",
         "mat4x4h",
-        "t4x4h",
-        "YYat4h",
-        "may4x4EYY",
-        "daplMor",
-        "samMMle",
-        "sampl55r",
-        "sampler",
-        "saNpe",
-        "sa3Ol33",
-        "s3mpler",
-        "Iamplercomparismn",
-        "sampleKrcompannison",
-        "samlr_copXXison",
-        "sampler_comparison",
-        "samplpLL_comparisI",
-        "smplerfomparison",
-        "sYmpURDr_comprison",
-        "texturh_depth_2d",
-        "teqtureuIIdep_2d",
-        "texture_depth_2H",
-        "texture_depth_2d",
-        "texre_depth_2Qvv",
-        "te66ue_depeh_2d",
-        "textue_d7pOh_2d",
-        "textureDDde0th_255_array",
-        "texture_IIepth_Hd_array",
-        "txture_depth_2d_array",
-        "texture_depth_2d_array",
-        "txture_depth_2r_array",
-        "tlxture_depth_2d_array",
-        "ttexturGdeth_2d_arrJJy",
-        "yexture_depth_cbe",
-        "texturedepth_cube",
-        "texture_IIeptBB_cube",
-        "texture_depth_cube",
-        "textKre_depth_c83TTe",
-        "texSnnYUUure_depth_cube",
-        "textuxe_5eptCCdZube",
-        "texturekkdepth_cube_arraq",
-        "exture_dppt00iicube5array",
-        "texIIurenndepth_cube_array",
-        "texture_depth_cube_array",
-        "ccextue_depth_cube_aKWa",
-        "texture_epth_cube_raKK",
-        "texture_depth_cube_a66ray",
-        "textEPPeKKdept_multisampled_2",
-        "texture_depth_mutisampledxx2d",
-        "texture_depth_qultisampled_2d",
-        "texture_depth_multisampled_2d",
-        "textureyydMMptr_mutisampleSS_2d",
-        "txture_depth_muluisampled2d",
-        "texSure_ept_mutisampled_2d",
-        "textu5e_externFFl",
-        "text44rrr_exterzal",
-        "texue_eWWtenal",
-        "texture_external",
-        "textuXe_ZZxtJJrnal",
-        "textuPPe_eternal",
-        "texturc_external",
-        "tllxture_storage_P6d",
-        "tex99ure_yytorag_1d",
-        "textuKKe_storage_1d",
-        "texture_storage_1d",
-        "texture__xorage_d",
-        "yxKur_storage_1d",
-        "textureVstorkge_1z",
-        "texKure_Storqge_2d",
-        "texture_storage_d",
-        "teture_storage_VVd",
-        "texture_storage_2d",
-        "textureIstoraAUe_2d",
-        "jextre_storaR_2d",
-        "extue44storYYge_2",
-        "textre_storage_2d_array",
-        "tex9ur_stor11ge_d_xxrray",
-        "tmmxture_storJe_2d_arrcc",
-        "texture_storage_2d_array",
-        "tJJxture_storage_2_array",
-        "DDCCltufe_storaUe_2d_array",
-        "tegture_storage_2d_array",
-        "exture_srageCC3d",
-        "txture_storage_3d",
-        "textuIe_sto__age_3d",
-        "texture_storage_3d",
-        "texttte_PPorage_3d",
-        "texture_stora3de_3d",
-        "exture_Ktoragyy_3d",
+        "CCx4h",
+        "mat4x66",
+        "maN4M4h",
+        "pt",
+        "KW",
+        "pzzee",
+        "ptr",
         "",
-        "03nn",
-        "uCnuu",
+        "w9",
+        "4tnn",
+        "sllDler",
+        "oamp4er",
+        "wEaggler",
+        "sampler",
+        "gamler",
+        "spleS",
+        "aampl",
+        "sampZcRTr_comparison",
+        "sampler_88TmparisOn",
+        "sampler_comparim00n",
+        "sampler_comparison",
+        "sampler_Bmomparison",
+        "Mamper_ppomarison",
+        "samper_compOOrison",
+        "teGtGre_1d",
+        "tex11ureHH1d",
+        "6exeeur_1FF",
+        "texture_1d",
+        "texure_1",
+        "tKiilure_1d",
+        "exture_1d",
+        "99etvIIre_2d",
+        "texture_d",
+        "texture_hd",
+        "texture_2d",
+        "llxzzure_PPd",
+        "exue2d",
+        "tffqqtre_2d",
+        "texJJre_2dd_arWay",
+        "teXXzzre_2darray",
+        "textu2_2d_array",
+        "texture_2d_array",
+        "tNyyture_2d_array",
+        "txture_2d_rOOa",
+        "textureErduaZPay",
+        "22lxtredd3ee",
+        "texVVe93d",
+        "teture_I1d",
+        "texture_3d",
+        "tebture_3d",
+        "ie7ure3d",
+        "teotiire_3d",
+        "entre_cube",
+        "texturScube",
+        "tex22r_cube",
+        "texture_cube",
+        "teC711recuGe",
+        "texture8cffbe",
+        "textue_cue",
+        "tJJxture_SSube_array",
+        "texture_9ue_arry",
+        "TbbJJxture_cube_array",
+        "texture_cube_array",
+        "t66ture_cube_aray",
+        "textur66_cubu_arra",
+        "textureWubeyarray",
+        "texture_deth_d",
+        "texture_epth_2d",
+        "texture_derth_2d",
+        "texture_depth_2d",
+        "tex2ure_depth_2B",
+        "texture_dpBBh_2d",
+        "texture_dpth_RRd",
+        "tLLxture_deptVV0darray",
+        "textuOOe_dethKK2d_arra",
+        "textuwe_ggepth_2d_rray",
+        "texture_depth_2d_array",
+        "textue_depthLh2d_arpay",
+        "texture_depEh2diiKrray",
+        "texture_dept_2d_array",
+        "textuUUe88dept_cbe",
+        "texrrure_depvvh_cube",
+        "texure_wepmmh_ube",
+        "texture_depth_cube",
+        "tjture_d44pth_cube",
+        "texture_depth_cXbe",
+        "t8xture_depth_cube",
+        "textre_depth_cubeEEarrvvy",
+        "tzzture_d9pth_cuie_array",
+        "teAture_depth_QQube_GGrrJJy",
+        "texture_depth_cube_array",
+        "texture_depth_cusse_array",
+        "texture_Pepth_cKbe_array",
+        "texture_dppp_cube_attray",
+        "texture_depth_multisample_2",
+        "texture_depth_multisamplMMd_2d",
+        "texJJure_de0th_multisampled_2d",
+        "texture_depth_multisampled_2d",
+        "textu8_dpth_mulisampled_2V",
+        "texture_dhhpth_mKltisggmpled_2d",
+        "texture_depth_multisampledf2d",
+        "tex77ure_exQernal",
+        "tYYxture_externa",
+        "tektur_exterSal",
+        "texture_external",
+        "txturn_ext2rnal",
+        "txture_FFternal",
+        "texUPPIre_GGxuernal",
+        "txtuEEe_mulaisFmpledv2d",
+        "ddexBBure_mltDDeampled_2d",
+        "teMture_EEulccisam55led_2",
+        "texture_multisampled_2d",
+        "texturemuKKtisample_d",
+        "texture_multisRmpled_2d",
+        "texturemulDisampl9d_2d",
+        "texturestorage_1d",
+        "textIre_storaa_1d",
+        "texture_sto77age_1d",
+        "texture_storage_1d",
+        "texIure_storage_1d",
+        "texture_storagedd",
+        "texture_storae_1d",
+        "texture_strate_d",
+        "texture33stoXXcge_2d",
+        "texturestorage_2E",
+        "texture_storage_2d",
+        "textuXXestorage_2d",
+        "texture_stoBaxxe_2d",
+        "texte_storWge_2G",
+        "texture_storage_2d_ar66ay",
+        "t0xTTr_storave_2d_array",
+        "kexure_orage_2d_rray",
+        "texture_storage_2d_array",
+        "textppre_stoae_2d_array",
+        "textre_stora11e_d_array",
+        "textureystorBEgeJ2d_array",
+        "textqreIImtxrage_3d",
+        "texture_toFage_3d",
+        "exture_Ytorage_3d",
+        "texture_storage_3d",
+        "heDture_sHHorage_3d",
+        "texturstorage23H",
+        "teture_strage_3d",
+        "u2",
+        "u2",
+        "dd32",
         "u32",
-        "3Xl",
-        "pp3o",
-        "uww",
-        "veuug",
-        "vaac",
-        "TRZcccf",
+        "uPO",
+        "ba",
+        "u02",
+        "veh2",
+        "vgY2",
+        "Oec2",
+        "vec2",
+        "eh",
+        "ppfe2",
+        "vev",
+        "vc2zz",
+        "vaac2",
+        "Ouuicf",
         "vec2f",
-        "vTc2O8",
-        "vem02f",
-        "meBB2f",
-        "Mpp2",
-        "OOe2h",
-        "veG2G",
+        "vGc2f",
+        "22ecTTf",
+        "dlc2f",
+        "vecbh",
+        "ec2BB",
+        "IIScXPP",
         "vec2h",
-        "11eHH2h",
-        "veFFe6",
-        "ve2",
-        "vKii2l",
-        "ec2i",
-        "v992IIv",
+        "jjec2h",
+        "cc_c2h",
+        "zz6xx2h",
+        "c2",
+        "4xx2N",
+        "p0AAei",
         "vec2i",
-        "veci",
-        "vechi",
-        "vczllPi",
-        "u",
-        "vffqq2",
-        "vJdd2u",
+        "vey2",
+        "vbWW0i",
+        "meMMtti",
+        "du",
+        "vvc_",
+        "VEEc2u",
         "vec2u",
-        "vecXX",
-        "ve22",
-        "Nyyc2u",
-        "vO3",
-        "PEruZ",
-        "vlc2edd",
-        "vec3f",
-        "ec9f",
-        "ve1II",
-        "veb3f",
-        "vi7",
-        "oec3ii",
-        "ec3",
-        "vec3h",
-        "veci",
-        "22ec",
-        "vGc3C",
-        "ffec38",
-        "c3i",
-        "JJecSSi",
-        "vec3i",
-        "93i",
-        "vbbJJ3TT",
-        "e66i",
-        "u663u",
-        "vW3u",
-        "v3u",
-        "vec3u",
-        "vecu",
-        "rec3u",
-        "2ec3B",
-        "vcBBf",
-        "vRc4f",
-        "v4LL0",
-        "vec4f",
-        "vKOOf",
-        "vgwcf",
-        "vLphf",
-        "eiiEh",
-        "ec4h",
-        "UU884",
-        "vec4h",
-        "rrecvvh",
-        "ecmm",
-        "vec4j",
-        "vec4X",
-        "vec48",
-        "vecvEE",
-        "vec4i",
-        "z99ci",
-        "GGeJJA4i",
-        "vess4i",
-        "vPcKu",
-        "tpc4u",
+        "vec24",
+        "VVeX2u",
+        "veVou",
         "vec",
+        "KKc3",
+        "G",
+        "vec3",
+        "ea3",
+        "OOc",
+        "G",
+        "v5c3f",
+        "99jcfff",
+        "XXvYY3R",
+        "vec3f",
+        "ccf",
+        "v8XX5",
+        "ec3",
+        "ppc3cc",
+        "vecvh",
+        "eEE3SS",
+        "vec3h",
+        "vec",
+        "eh",
+        "ec3ww",
+        "vecd99i",
+        "ve99P",
+        "KKec3",
+        "vec3i",
+        "ooMcDD",
+        "vei",
+        "vqi",
+        "veL30",
+        "vncvv66",
+        "vrrn3",
+        "vec3u",
+        "vxxce",
+        "NCCOc3u",
+        "vc3u",
+        "veca",
+        "veNNN",
+        "vec",
+        "vec4",
+        "vc",
+        "vAYS4",
+        "vec0",
+        "vecaaf",
+        "vmm4f",
+        "ec4f",
+        "vec4f",
+        "vE4U",
+        "veKD4",
+        "v0t4__",
+        "cpA",
+        "ec4h",
+        "vBBc4h",
+        "vec4h",
+        "vbnn99",
+        "EEcAAh",
+        "v5c66h",
+        "vHc4i",
+        "vecxi",
+        "vzyn40",
+        "vec4i",
+        "ve4i",
+        "kH4i",
+        "veci",
+        "oo4rr",
+        "JJc4",
+        "vcCC0",
         "vec4u",
-        "MMec4u",
-        "vJJc40",
-        "8c",
+        "xAA99F",
+        "veccu",
+        "vec4S",
     };
     for (auto _ : state) {
         for (auto* str : kStrings) {
diff --git a/src/tint/type/builtin_test.cc b/src/tint/type/builtin_test.cc
index ebd2cea..345a214 100644
--- a/src/tint/type/builtin_test.cc
+++ b/src/tint/type/builtin_test.cc
@@ -43,49 +43,71 @@
 }
 
 static constexpr Case kValidCases[] = {
+    {"array", Builtin::kArray},
+    {"atomic", Builtin::kAtomic},
     {"bool", Builtin::kBool},
     {"f16", Builtin::kF16},
     {"f32", Builtin::kF32},
     {"i32", Builtin::kI32},
+    {"mat2x2", Builtin::kMat2X2},
     {"mat2x2f", Builtin::kMat2X2F},
     {"mat2x2h", Builtin::kMat2X2H},
+    {"mat2x3", Builtin::kMat2X3},
     {"mat2x3f", Builtin::kMat2X3F},
     {"mat2x3h", Builtin::kMat2X3H},
+    {"mat2x4", Builtin::kMat2X4},
     {"mat2x4f", Builtin::kMat2X4F},
     {"mat2x4h", Builtin::kMat2X4H},
+    {"mat3x2", Builtin::kMat3X2},
     {"mat3x2f", Builtin::kMat3X2F},
     {"mat3x2h", Builtin::kMat3X2H},
+    {"mat3x3", Builtin::kMat3X3},
     {"mat3x3f", Builtin::kMat3X3F},
     {"mat3x3h", Builtin::kMat3X3H},
+    {"mat3x4", Builtin::kMat3X4},
     {"mat3x4f", Builtin::kMat3X4F},
     {"mat3x4h", Builtin::kMat3X4H},
+    {"mat4x2", Builtin::kMat4X2},
     {"mat4x2f", Builtin::kMat4X2F},
     {"mat4x2h", Builtin::kMat4X2H},
+    {"mat4x3", Builtin::kMat4X3},
     {"mat4x3f", Builtin::kMat4X3F},
     {"mat4x3h", Builtin::kMat4X3H},
+    {"mat4x4", Builtin::kMat4X4},
     {"mat4x4f", Builtin::kMat4X4F},
     {"mat4x4h", Builtin::kMat4X4H},
+    {"ptr", Builtin::kPtr},
     {"sampler", Builtin::kSampler},
     {"sampler_comparison", Builtin::kSamplerComparison},
+    {"texture_1d", Builtin::kTexture1D},
+    {"texture_2d", Builtin::kTexture2D},
+    {"texture_2d_array", Builtin::kTexture2DArray},
+    {"texture_3d", Builtin::kTexture3D},
+    {"texture_cube", Builtin::kTextureCube},
+    {"texture_cube_array", Builtin::kTextureCubeArray},
     {"texture_depth_2d", Builtin::kTextureDepth2D},
     {"texture_depth_2d_array", Builtin::kTextureDepth2DArray},
     {"texture_depth_cube", Builtin::kTextureDepthCube},
     {"texture_depth_cube_array", Builtin::kTextureDepthCubeArray},
     {"texture_depth_multisampled_2d", Builtin::kTextureDepthMultisampled2D},
     {"texture_external", Builtin::kTextureExternal},
+    {"texture_multisampled_2d", Builtin::kTextureMultisampled2D},
     {"texture_storage_1d", Builtin::kTextureStorage1D},
     {"texture_storage_2d", Builtin::kTextureStorage2D},
     {"texture_storage_2d_array", Builtin::kTextureStorage2DArray},
     {"texture_storage_3d", Builtin::kTextureStorage3D},
     {"u32", Builtin::kU32},
+    {"vec2", Builtin::kVec2},
     {"vec2f", Builtin::kVec2F},
     {"vec2h", Builtin::kVec2H},
     {"vec2i", Builtin::kVec2I},
     {"vec2u", Builtin::kVec2U},
+    {"vec3", Builtin::kVec3},
     {"vec3f", Builtin::kVec3F},
     {"vec3h", Builtin::kVec3H},
     {"vec3i", Builtin::kVec3I},
     {"vec3u", Builtin::kVec3U},
+    {"vec4", Builtin::kVec4},
     {"vec4f", Builtin::kVec4F},
     {"vec4h", Builtin::kVec4H},
     {"vec4i", Builtin::kVec4I},
@@ -93,147 +115,213 @@
 };
 
 static constexpr Case kInvalidCases[] = {
-    {"ccol", Builtin::kUndefined},
-    {"3", Builtin::kUndefined},
-    {"bVol", Builtin::kUndefined},
-    {"116", Builtin::kUndefined},
-    {"qJ6", Builtin::kUndefined},
-    {"f17ll", Builtin::kUndefined},
-    {"fppqH", Builtin::kUndefined},
-    {"", Builtin::kUndefined},
-    {"Gb", Builtin::kUndefined},
-    {"i3vi", Builtin::kUndefined},
-    {"i8WW", Builtin::kUndefined},
-    {"ixx", Builtin::kUndefined},
-    {"mX2x2gg", Builtin::kUndefined},
-    {"a2xXf", Builtin::kUndefined},
-    {"mat232f", Builtin::kUndefined},
-    {"Eat2x2h", Builtin::kUndefined},
-    {"mPTT2x2h", Builtin::kUndefined},
-    {"mat2xdxx", Builtin::kUndefined},
-    {"m44t2x3f", Builtin::kUndefined},
-    {"maSS2xVVf", Builtin::kUndefined},
-    {"RatR22f", Builtin::kUndefined},
-    {"mF2x9h", Builtin::kUndefined},
-    {"matx3h", Builtin::kUndefined},
-    {"VOORRH3h", Builtin::kUndefined},
-    {"ma2xyf", Builtin::kUndefined},
-    {"llnarr2774f", Builtin::kUndefined},
-    {"mat24400", Builtin::kUndefined},
-    {"a2ooh", Builtin::kUndefined},
-    {"zz2x4h", Builtin::kUndefined},
-    {"miitppx1", Builtin::kUndefined},
-    {"mat3xXXf", Builtin::kUndefined},
-    {"9II5ann3x2f", Builtin::kUndefined},
+    {"arccy", Builtin::kUndefined},
+    {"3a", Builtin::kUndefined},
+    {"aVray", Builtin::kUndefined},
+    {"1tomic", Builtin::kUndefined},
+    {"aoqqic", Builtin::kUndefined},
+    {"atomll77", Builtin::kUndefined},
+    {"ppqooH", Builtin::kUndefined},
+    {"c", Builtin::kUndefined},
+    {"bGo", Builtin::kUndefined},
+    {"f1vi", Builtin::kUndefined},
+    {"f8WW", Builtin::kUndefined},
+    {"fxx", Builtin::kUndefined},
+    {"fgg", Builtin::kUndefined},
+    {"X", Builtin::kUndefined},
+    {"332", Builtin::kUndefined},
+    {"iE2", Builtin::kUndefined},
+    {"iPTT", Builtin::kUndefined},
+    {"dxx2", Builtin::kUndefined},
+    {"44at2x2", Builtin::kUndefined},
+    {"mSSVV2x2", Builtin::kUndefined},
+    {"mat2R2", Builtin::kUndefined},
+    {"mF2x9f", Builtin::kUndefined},
+    {"matx2f", Builtin::kUndefined},
+    {"VOORRH2f", Builtin::kUndefined},
+    {"ma2xyh", Builtin::kUndefined},
+    {"llnarr2772h", Builtin::kUndefined},
+    {"mat24200", Builtin::kUndefined},
+    {"m2oo", Builtin::kUndefined},
+    {"atzz3", Builtin::kUndefined},
+    {"1it2xpp", Builtin::kUndefined},
+    {"mat2xXXf", Builtin::kUndefined},
+    {"9II5ann2x3f", Builtin::kUndefined},
     {"mataSSrHHYf", Builtin::kUndefined},
     {"makkh", Builtin::kUndefined},
     {"jatgRx", Builtin::kUndefined},
-    {"mb3x2", Builtin::kUndefined},
-    {"mat3xjf", Builtin::kUndefined},
-    {"at3x3f", Builtin::kUndefined},
-    {"q3x3f", Builtin::kUndefined},
-    {"matNN3h", Builtin::kUndefined},
-    {"at33vv", Builtin::kUndefined},
-    {"QQt3x3h", Builtin::kUndefined},
+    {"mb2x3", Builtin::kUndefined},
+    {"mat2j4", Builtin::kUndefined},
+    {"mt2x4", Builtin::kUndefined},
+    {"m2q4", Builtin::kUndefined},
+    {"matNN4f", Builtin::kUndefined},
+    {"at24vv", Builtin::kUndefined},
+    {"QQt2x4f", Builtin::kUndefined},
     {"maffxr", Builtin::kUndefined},
-    {"mat3xjf", Builtin::kUndefined},
-    {"mNNw3x48", Builtin::kUndefined},
-    {"matx4h", Builtin::kUndefined},
-    {"mrrt3x4h", Builtin::kUndefined},
-    {"Gat3x4h", Builtin::kUndefined},
-    {"mat4x2FF", Builtin::kUndefined},
-    {"at4f", Builtin::kUndefined},
+    {"mat2xjh", Builtin::kUndefined},
+    {"mNNw2x48", Builtin::kUndefined},
+    {"mt3x2", Builtin::kUndefined},
+    {"rrat3x2", Builtin::kUndefined},
+    {"mGt3x2", Builtin::kUndefined},
+    {"mat3x2FF", Builtin::kUndefined},
+    {"at3f", Builtin::kUndefined},
     {"marrx2f", Builtin::kUndefined},
-    {"t4x2h", Builtin::kUndefined},
-    {"Da4xJJh", Builtin::kUndefined},
+    {"t3x2h", Builtin::kUndefined},
+    {"Da3xJJh", Builtin::kUndefined},
     {"ma82", Builtin::kUndefined},
-    {"m11k3", Builtin::kUndefined},
-    {"matx3f", Builtin::kUndefined},
-    {"maJx3f", Builtin::kUndefined},
-    {"mat4c3h", Builtin::kUndefined},
-    {"mat4x3O", Builtin::kUndefined},
-    {"KK_atvvtt3h", Builtin::kUndefined},
-    {"xx84x4f", Builtin::kUndefined},
-    {"__qatF4", Builtin::kUndefined},
-    {"matqx4f", Builtin::kUndefined},
-    {"33atOx4h", Builtin::kUndefined},
-    {"mtt64x9oQQ", Builtin::kUndefined},
-    {"ma4x66h", Builtin::kUndefined},
-    {"smOlzz66", Builtin::kUndefined},
-    {"sampyyer", Builtin::kUndefined},
-    {"ZaHHeZ", Builtin::kUndefined},
-    {"sWWpleq_compari44on", Builtin::kUndefined},
-    {"sampler_compaisoOO", Builtin::kUndefined},
-    {"smpeoo_coYparison", Builtin::kUndefined},
-    {"eture_dpth_2d", Builtin::kUndefined},
-    {"texture_detF_2d", Builtin::kUndefined},
-    {"texturedwpth_2d", Builtin::kUndefined},
-    {"teKuffe_Gepth_2d_arry", Builtin::kUndefined},
-    {"texture_dKKptq_2d_array", Builtin::kUndefined},
-    {"texture_depmmh32d_arraF", Builtin::kUndefined},
-    {"textur_depth_cube", Builtin::kUndefined},
-    {"texure_depqh_cube", Builtin::kUndefined},
-    {"texture_debth_cube", Builtin::kUndefined},
-    {"txture_deptii_cube_arry", Builtin::kUndefined},
-    {"textureqdepth_OOube_arry", Builtin::kUndefined},
-    {"texture_deTvvth_cube_array", Builtin::kUndefined},
-    {"texture_depth_multiFFampled_2d", Builtin::kUndefined},
-    {"textue_depthPmfl00isampled_Qd", Builtin::kUndefined},
-    {"textuPe_depth_multisampled_2d", Builtin::kUndefined},
-    {"texture_exernss77", Builtin::kUndefined},
-    {"texture_bbxternRRl", Builtin::kUndefined},
-    {"textureXXexternal", Builtin::kUndefined},
-    {"CCextOOre_stoage_qOd", Builtin::kUndefined},
-    {"txtsre_sturage_1L", Builtin::kUndefined},
-    {"texture_stoXage_1d", Builtin::kUndefined},
+    {"1k33", Builtin::kUndefined},
+    {"matx3", Builtin::kUndefined},
+    {"maJx3", Builtin::kUndefined},
+    {"mat3c3f", Builtin::kUndefined},
+    {"mat3x3O", Builtin::kUndefined},
+    {"KK_atvvtt3f", Builtin::kUndefined},
+    {"xx83x3h", Builtin::kUndefined},
+    {"__qatF3", Builtin::kUndefined},
+    {"matqx3h", Builtin::kUndefined},
+    {"ma33x66", Builtin::kUndefined},
+    {"mttQQo3x4", Builtin::kUndefined},
+    {"mat66x", Builtin::kUndefined},
+    {"mtOxzz66", Builtin::kUndefined},
+    {"mat3yy4f", Builtin::kUndefined},
+    {"ZaHH4Z", Builtin::kUndefined},
+    {"4WWt3q4h", Builtin::kUndefined},
+    {"mOO3x4h", Builtin::kUndefined},
+    {"oatY4h", Builtin::kUndefined},
+    {"ax2", Builtin::kUndefined},
+    {"ma4x2", Builtin::kUndefined},
+    {"matw2", Builtin::kUndefined},
+    {"fGtxKf", Builtin::kUndefined},
+    {"matqKx2f", Builtin::kUndefined},
+    {"matmmxFf", Builtin::kUndefined},
+    {"at4x2h", Builtin::kUndefined},
+    {"mt4x2q", Builtin::kUndefined},
+    {"mat4xbb", Builtin::kUndefined},
+    {"it4x3", Builtin::kUndefined},
+    {"mOO4xq", Builtin::kUndefined},
+    {"mat4Tvv3", Builtin::kUndefined},
+    {"maFF4x3f", Builtin::kUndefined},
+    {"Pa00xQf", Builtin::kUndefined},
+    {"mPt4x3f", Builtin::kUndefined},
+    {"ma774xss", Builtin::kUndefined},
+    {"RRCbb4x3h", Builtin::kUndefined},
+    {"mXXt4x3h", Builtin::kUndefined},
+    {"CCt4OOOO", Builtin::kUndefined},
+    {"mtsuL", Builtin::kUndefined},
+    {"mat4xX", Builtin::kUndefined},
+    {"mat44f", Builtin::kUndefined},
+    {"qa4O4", Builtin::kUndefined},
+    {"mat4x22f", Builtin::kUndefined},
+    {"myzz40XX", Builtin::kUndefined},
+    {"matVViP", Builtin::kUndefined},
+    {"mannC4h", Builtin::kUndefined},
+    {"pHAq", Builtin::kUndefined},
+    {"tr", Builtin::kUndefined},
+    {"Kf", Builtin::kUndefined},
+    {"lmgger", Builtin::kUndefined},
+    {"samplr", Builtin::kUndefined},
+    {"NTTmcl4r", Builtin::kUndefined},
+    {"sampler_clmppri77on", Builtin::kUndefined},
+    {"samplg_czzmparNNso", Builtin::kUndefined},
+    {"smpleuuXXomparibbon", Builtin::kUndefined},
+    {"texture_1", Builtin::kUndefined},
+    {"t88tueQ1K", Builtin::kUndefined},
+    {"texturq9d", Builtin::kUndefined},
+    {"text11re_2d", Builtin::kUndefined},
+    {"teiiu22eF2d", Builtin::kUndefined},
+    {"tex77ur_2d", Builtin::kUndefined},
+    {"textNNr2_d_array", Builtin::kUndefined},
+    {"textVVre_2d_array", Builtin::kUndefined},
+    {"texwure_WWF_11rray", Builtin::kUndefined},
+    {"txture_3ww", Builtin::kUndefined},
+    {"texturD_3d", Builtin::kUndefined},
+    {"teKture_d", Builtin::kUndefined},
+    {"11exPPufe_cubh", Builtin::kUndefined},
+    {"textue_cube", Builtin::kUndefined},
+    {"texture_cubYY", Builtin::kUndefined},
+    {"texttr_cube_HHkkVay", Builtin::kUndefined},
+    {"texture_crrbe_array", Builtin::kUndefined},
+    {"texturesscubeWWaray", Builtin::kUndefined},
+    {"texture_deptY_d", Builtin::kUndefined},
+    {"teLturq_defh_2d", Builtin::kUndefined},
+    {"texvvre_duu22th_2d", Builtin::kUndefined},
+    {"texure_deth_2d_array", Builtin::kUndefined},
+    {"texturYY_depth_2daray", Builtin::kUndefined},
+    {"texturE_77epth_2d_aryYay", Builtin::kUndefined},
+    {"Mexdoore_depth_cue", Builtin::kUndefined},
+    {"texturedepMMh_cube", Builtin::kUndefined},
+    {"texture55depth_cube", Builtin::kUndefined},
+    {"textue_depth_cbe_aNray", Builtin::kUndefined},
+    {"texture_dpth_c33be_array", Builtin::kUndefined},
+    {"texture_depth_cub3_array", Builtin::kUndefined},
+    {"texIure_mepth_mulisampled_2d", Builtin::kUndefined},
+    {"texture_depthrmKltisampled_2nn", Builtin::kUndefined},
+    {"textur_depth_multismXld_2d", Builtin::kUndefined},
+    {"texpure_exLLeIna", Builtin::kUndefined},
+    {"txture_exfrnal", Builtin::kUndefined},
+    {"teUture_extYRRDl", Builtin::kUndefined},
+    {"texturehmultisampled_2d", Builtin::kUndefined},
+    {"texturqmultsIImuuled_2d", Builtin::kUndefined},
+    {"Hexture_multisampled_2d", Builtin::kUndefined},
+    {"texQQur_storge_vvd", Builtin::kUndefined},
+    {"texeure_66oage_1d", Builtin::kUndefined},
+    {"texture_stoage71d", Builtin::kUndefined},
+    {"texture_s55or0ge_2DD", Builtin::kUndefined},
+    {"teHture_storIIge_2d", Builtin::kUndefined},
     {"textue_storage_2d", Builtin::kUndefined},
-    {"teuresOorageqq2d", Builtin::kUndefined},
-    {"texture_sto22age_2d", Builtin::kUndefined},
-    {"exture_syora0e_2d_Xzzrray", Builtin::kUndefined},
-    {"texiVVr_storageP2d_array", Builtin::kUndefined},
-    {"texturestorage_2nn_arCay", Builtin::kUndefined},
-    {"texturHHstorAAe_qqd", Builtin::kUndefined},
-    {"textur_storage_3d", Builtin::kUndefined},
-    {"texure_sfKorage3d", Builtin::kUndefined},
-    {"gg", Builtin::kUndefined},
-    {"u3", Builtin::kUndefined},
-    {"NT42", Builtin::kUndefined},
-    {"ppec7l", Builtin::kUndefined},
-    {"zNe2f", Builtin::kUndefined},
-    {"uXXb2f", Builtin::kUndefined},
-    {"vec2", Builtin::kUndefined},
-    {"882K", Builtin::kUndefined},
-    {"vq9h", Builtin::kUndefined},
-    {"vec211", Builtin::kUndefined},
-    {"22ciii", Builtin::kUndefined},
-    {"ec77i", Builtin::kUndefined},
-    {"NN22u", Builtin::kUndefined},
-    {"vVVc2u", Builtin::kUndefined},
-    {"WW11w2u", Builtin::kUndefined},
-    {"vcwwf", Builtin::kUndefined},
-    {"vDc3f", Builtin::kUndefined},
-    {"vecK", Builtin::kUndefined},
-    {"f11r3PP", Builtin::kUndefined},
-    {"ve3h", Builtin::kUndefined},
-    {"vec3YY", Builtin::kUndefined},
-    {"vkktHH", Builtin::kUndefined},
-    {"rrec3i", Builtin::kUndefined},
-    {"vWWssi", Builtin::kUndefined},
-    {"veYu", Builtin::kUndefined},
-    {"eq3f", Builtin::kUndefined},
-    {"u22ec3u", Builtin::kUndefined},
-    {"c4f", Builtin::kUndefined},
-    {"vec4", Builtin::kUndefined},
-    {"vYyc47E", Builtin::kUndefined},
-    {"veMoh", Builtin::kUndefined},
-    {"ve4MM", Builtin::kUndefined},
-    {"55ec4h", Builtin::kUndefined},
-    {"N4i", Builtin::kUndefined},
-    {"ve33i", Builtin::kUndefined},
-    {"3ec4i", Builtin::kUndefined},
-    {"mecI", Builtin::kUndefined},
-    {"vrnK4u", Builtin::kUndefined},
-    {"v4", Builtin::kUndefined},
+    {"texturestorage_2d_rrray", Builtin::kUndefined},
+    {"textule_storage_2d_array", Builtin::kUndefined},
+    {"tetture_JJtorage_Gd_arra", Builtin::kUndefined},
+    {"yexture_storage3d", Builtin::kUndefined},
+    {"texturestorage_3d", Builtin::kUndefined},
+    {"texture_IItorBBge_3d", Builtin::kUndefined},
+    {"TTK33", Builtin::kUndefined},
+    {"nnUYdSS2", Builtin::kUndefined},
+    {"x5dZ", Builtin::kUndefined},
+    {"veckq", Builtin::kUndefined},
+    {"ii500", Builtin::kUndefined},
+    {"vecIIn", Builtin::kUndefined},
+    {"cceW", Builtin::kUndefined},
+    {"cKK", Builtin::kUndefined},
+    {"vec66f", Builtin::kUndefined},
+    {"vePPK", Builtin::kUndefined},
+    {"vexxh", Builtin::kUndefined},
+    {"qec2h", Builtin::kUndefined},
+    {"veSyMMr", Builtin::kUndefined},
+    {"v2u", Builtin::kUndefined},
+    {"ec", Builtin::kUndefined},
+    {"5eFF2u", Builtin::kUndefined},
+    {"rrecz44", Builtin::kUndefined},
+    {"vWW", Builtin::kUndefined},
+    {"ZJJCcX", Builtin::kUndefined},
+    {"vcPP", Builtin::kUndefined},
+    {"vec", Builtin::kUndefined},
+    {"3Le003f", Builtin::kUndefined},
+    {"MMec3RR", Builtin::kUndefined},
+    {"vec39K", Builtin::kUndefined},
+    {"yyecm", Builtin::kUndefined},
+    {"v__cD", Builtin::kUndefined},
+    {"vec3U", Builtin::kUndefined},
+    {"ze333i", Builtin::kUndefined},
+    {"eKti", Builtin::kUndefined},
+    {"ve3V", Builtin::kUndefined},
+    {"jbR3K", Builtin::kUndefined},
+    {"e44344", Builtin::kUndefined},
+    {"00u", Builtin::kUndefined},
+    {"WK4", Builtin::kUndefined},
+    {"m", Builtin::kUndefined},
+    {"vJJ", Builtin::kUndefined},
+    {"lDDcUfC", Builtin::kUndefined},
+    {"vec4g", Builtin::kUndefined},
+    {"CCe", Builtin::kUndefined},
+    {"ec4h", Builtin::kUndefined},
+    {"vIc__h", Builtin::kUndefined},
+    {"ePPtt", Builtin::kUndefined},
+    {"v3dc4i", Builtin::kUndefined},
+    {"vcyyi", Builtin::kUndefined},
+    {"u4", Builtin::kUndefined},
+    {"v03nnu", Builtin::kUndefined},
+    {"Cuuecnv", Builtin::kUndefined},
+    {"vX4ll", Builtin::kUndefined},
 };
 
 using BuiltinParseTest = testing::TestWithParam<Case>;
diff --git a/src/tint/type/storage_texture_test.cc b/src/tint/type/storage_texture_test.cc
index ad5ad6e..2ffc0b7 100644
--- a/src/tint/type/storage_texture_test.cc
+++ b/src/tint/type/storage_texture_test.cc
@@ -96,7 +96,7 @@
 }
 
 TEST_F(StorageTextureTest, F32) {
-    Type* s = Create(TextureDimension::k2dArray, type::TexelFormat::kRgba32Float,
+    auto* s = Create(TextureDimension::k2dArray, type::TexelFormat::kRgba32Float,
                      type::Access::kReadWrite);
 
     auto program = Build();
@@ -109,7 +109,7 @@
 
 TEST_F(StorageTextureTest, U32) {
     auto* subtype = StorageTexture::SubtypeFor(type::TexelFormat::kRg32Uint, Types());
-    Type* s = create<StorageTexture>(TextureDimension::k2dArray, type::TexelFormat::kRg32Uint,
+    auto* s = create<StorageTexture>(TextureDimension::k2dArray, type::TexelFormat::kRg32Uint,
                                      type::Access::kReadWrite, subtype);
 
     auto program = Build();
@@ -122,7 +122,7 @@
 
 TEST_F(StorageTextureTest, I32) {
     auto* subtype = StorageTexture::SubtypeFor(type::TexelFormat::kRgba32Sint, Types());
-    Type* s = create<StorageTexture>(TextureDimension::k2dArray, type::TexelFormat::kRgba32Sint,
+    auto* s = create<StorageTexture>(TextureDimension::k2dArray, type::TexelFormat::kRgba32Sint,
                                      type::Access::kReadWrite, subtype);
 
     auto program = Build();
diff --git a/src/tint/type/texture.cc b/src/tint/type/texture.cc
index 717d63ae5..357af56 100644
--- a/src/tint/type/texture.cc
+++ b/src/tint/type/texture.cc
@@ -22,4 +22,36 @@
 
 Texture::~Texture() = default;
 
+bool IsTextureArray(type::TextureDimension dim) {
+    switch (dim) {
+        case type::TextureDimension::k2dArray:
+        case type::TextureDimension::kCubeArray:
+            return true;
+        case type::TextureDimension::k2d:
+        case type::TextureDimension::kNone:
+        case type::TextureDimension::k1d:
+        case type::TextureDimension::k3d:
+        case type::TextureDimension::kCube:
+            return false;
+    }
+    return false;
+}
+
+int NumCoordinateAxes(type::TextureDimension dim) {
+    switch (dim) {
+        case type::TextureDimension::kNone:
+            return 0;
+        case type::TextureDimension::k1d:
+            return 1;
+        case type::TextureDimension::k2d:
+        case type::TextureDimension::k2dArray:
+            return 2;
+        case type::TextureDimension::k3d:
+        case type::TextureDimension::kCube:
+        case type::TextureDimension::kCubeArray:
+            return 3;
+    }
+    return 0;
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/texture.h b/src/tint/type/texture.h
index 4fab761..e7cbe81 100644
--- a/src/tint/type/texture.h
+++ b/src/tint/type/texture.h
@@ -37,6 +37,24 @@
     TextureDimension const dim_;
 };
 
+/// @param dim the type::TextureDimension to query
+/// @return true if the given type::TextureDimension is an array texture
+bool IsTextureArray(type::TextureDimension dim);
+
+/// Returns the number of axes in the coordinate used for accessing
+/// the texture, where an access is one of: sampling, fetching, load,
+/// or store.
+///  None -> 0
+///  1D -> 1
+///  2D, 2DArray -> 2
+///  3D, Cube, CubeArray -> 3
+/// Note: To sample a cube texture, the coordinate has 3 dimensions,
+/// but textureDimensions on a cube or cube array returns a 2-element
+/// size, representing the (x,y) size of each cube face, in texels.
+/// @param dim the type::TextureDimension to query
+/// @return number of dimensions in a coordinate for the dimensionality
+int NumCoordinateAxes(type::TextureDimension dim);
+
 }  // namespace tint::type
 
 #endif  // SRC_TINT_TYPE_TEXTURE_H_
diff --git a/src/tint/writer/append_vector.cc b/src/tint/writer/append_vector.cc
index ab18461..3d38c7f 100644
--- a/src/tint/writer/append_vector.cc
+++ b/src/tint/writer/append_vector.cc
@@ -86,7 +86,7 @@
         packed_el_sem_ty = vector_ty;
     }
 
-    const ast::Type* packed_el_ast_ty = Switch(
+    auto packed_el_ast_ty = Switch(
         packed_el_sem_ty,  //
         [&](const type::I32*) { return b->ty.i32(); },
         [&](const type::U32*) { return b->ty.u32(); },
@@ -95,12 +95,12 @@
         [&](Default) {
             TINT_UNREACHABLE(Writer, b->Diagnostics())
                 << "unsupported vector element type: " << packed_el_sem_ty->TypeInfo().name;
-            return nullptr;
+            return ast::Type{};
         });
 
     auto* statement = vector_sem->Stmt();
 
-    auto* packed_ast_ty = b->create<ast::Vector>(packed_el_ast_ty, packed_size);
+    auto packed_ast_ty = b->ty.vec(packed_el_ast_ty, packed_size);
     auto* packed_sem_ty = b->create<type::Vector>(packed_el_sem_ty, packed_size);
 
     // If the coordinates are already passed in a vector initializer, with only
diff --git a/src/tint/writer/append_vector_test.cc b/src/tint/writer/append_vector_test.cc
index c8c6428..feded0c 100644
--- a/src/tint/writer/append_vector_test.cc
+++ b/src/tint/writer/append_vector_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "src/tint/writer/append_vector.h"
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/program_builder.h"
 #include "src/tint/resolver/resolver.h"
 #include "src/tint/sem/type_initializer.h"
@@ -86,8 +87,7 @@
     EXPECT_EQ(vec_123->args[1], scalar_2);
     auto* u32_to_i32 = vec_123->args[2]->As<ast::CallExpression>();
     ASSERT_NE(u32_to_i32, nullptr);
-    ASSERT_TRUE(u32_to_i32->target.type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(u32_to_i32->target.type->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(Symbols(), u32_to_i32->target, "i32");
 
     ASSERT_EQ(u32_to_i32->args.Length(), 1u);
     EXPECT_EQ(u32_to_i32->args[0], scalar_3);
@@ -132,20 +132,14 @@
     ASSERT_EQ(vec_123->args.Length(), 2u);
     auto* v2u32_to_v2i32 = vec_123->args[0]->As<ast::CallExpression>();
     ASSERT_NE(v2u32_to_v2i32, nullptr);
-    ASSERT_TRUE(v2u32_to_v2i32->target.type->Is<ast::Vector>());
-    EXPECT_EQ(v2u32_to_v2i32->target.type->As<ast::Vector>()->width, 2u);
-    ASSERT_TRUE(v2u32_to_v2i32->target.type->As<ast::Vector>()->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(v2u32_to_v2i32->target.type->As<ast::Vector>()
-                                    ->type->As<ast::TypeName>()
-                                    ->name->symbol),
-              "i32");
+
+    ast::CheckIdentifier(Symbols(), v2u32_to_v2i32->target, ast::Template("vec2", "i32"));
     EXPECT_EQ(v2u32_to_v2i32->args.Length(), 1u);
     EXPECT_EQ(v2u32_to_v2i32->args[0], uvec_12);
 
     auto* u32_to_i32 = vec_123->args[1]->As<ast::CallExpression>();
     ASSERT_NE(u32_to_i32, nullptr);
-    ASSERT_TRUE(u32_to_i32->target.type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(u32_to_i32->target.type->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(Symbols(), u32_to_i32->target, "i32");
     ASSERT_EQ(u32_to_i32->args.Length(), 1u);
     EXPECT_EQ(u32_to_i32->args[0], scalar_3);
 
@@ -157,6 +151,7 @@
 
     auto* ctor = call->Target()->As<sem::TypeInitializer>();
     ASSERT_NE(ctor, nullptr);
+
     ASSERT_TRUE(ctor->ReturnType()->Is<type::Vector>());
     EXPECT_EQ(ctor->ReturnType()->As<type::Vector>()->Width(), 3u);
     EXPECT_TRUE(ctor->ReturnType()->As<type::Vector>()->type()->Is<type::I32>());
@@ -187,8 +182,7 @@
     EXPECT_EQ(vec_123->args[1], scalar_2);
     auto* f32_to_i32 = vec_123->args[2]->As<ast::CallExpression>();
     ASSERT_NE(f32_to_i32, nullptr);
-    ASSERT_TRUE(f32_to_i32->target.type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(f32_to_i32->target.type->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(Symbols(), f32_to_i32->target, "i32");
     ASSERT_EQ(f32_to_i32->args.Length(), 1u);
     EXPECT_EQ(f32_to_i32->args[0], scalar_3);
 
@@ -389,8 +383,7 @@
     EXPECT_EQ(vec_123->args[0], vec_12);
     auto* f32_to_i32 = vec_123->args[1]->As<ast::CallExpression>();
     ASSERT_NE(f32_to_i32, nullptr);
-    ASSERT_TRUE(f32_to_i32->target.type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(f32_to_i32->target.type->As<ast::TypeName>()->name->symbol), "i32");
+    ast::CheckIdentifier(Symbols(), f32_to_i32->target, "i32");
     ASSERT_EQ(f32_to_i32->args.Length(), 1u);
     EXPECT_EQ(f32_to_i32->args[0], scalar_3);
 
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index cb1850a..5847923 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -706,7 +706,7 @@
     auto* call = builder_.Sem().Get<sem::Call>(expr);
     return Switch(
         call->Target(),  //
-        [&](const sem::Function*) { return EmitFunctionCall(out, call); },
+        [&](const sem::Function* fn) { return EmitFunctionCall(out, call, fn); },
         [&](const sem::Builtin* builtin) { return EmitBuiltinCall(out, call, builtin); },
         [&](const sem::TypeConversion* conv) { return EmitTypeConversion(out, call, conv); },
         [&](const sem::TypeInitializer* init) { return EmitTypeInitializer(out, call, init); },
@@ -717,15 +717,13 @@
         });
 }
 
-bool GeneratorImpl::EmitFunctionCall(std::ostream& out, const sem::Call* call) {
+bool GeneratorImpl::EmitFunctionCall(std::ostream& out,
+                                     const sem::Call* call,
+                                     const sem::Function* fn) {
     const auto& args = call->Arguments();
-    auto* decl = call->Declaration();
-    auto* ident = decl->target.name;
+    auto* ident = fn->Declaration()->name;
 
-    auto name = builder_.Symbols().NameFor(ident->symbol);
-    auto caller_sym = ident->symbol;
-
-    out << name;
+    out << builder_.Symbols().NameFor(ident->symbol);
     ScopedParen sp(out);
 
     bool first = true;
diff --git a/src/tint/writer/glsl/generator_impl.h b/src/tint/writer/glsl/generator_impl.h
index 7a623ed..e972b12 100644
--- a/src/tint/writer/glsl/generator_impl.h
+++ b/src/tint/writer/glsl/generator_impl.h
@@ -150,8 +150,9 @@
     /// Handles generating a function call expression
     /// @param out the output of the expression stream
     /// @param call the call expression
+    /// @param fn the function being called
     /// @returns true if the expression is emitted
-    bool EmitFunctionCall(std::ostream& out, const sem::Call* call);
+    bool EmitFunctionCall(std::ostream& out, const sem::Call* call, const sem::Function* fn);
     /// Handles generating a builtin call expression
     /// @param out the output of the expression stream
     /// @param call the call expression
diff --git a/src/tint/writer/glsl/generator_impl_member_accessor_test.cc b/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
index ebccf27..1023ad0 100644
--- a/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
+++ b/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
@@ -23,63 +23,63 @@
 
 using ::testing::HasSubstr;
 
-using create_type_func_ptr = const ast::Type* (*)(const ProgramBuilder::TypesBuilder& ty);
+using create_type_func_ptr = ast::Type (*)(const ProgramBuilder::TypesBuilder& ty);
 
-inline const ast::Type* ty_i32(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_i32(const ProgramBuilder::TypesBuilder& ty) {
     return ty.i32();
 }
-inline const ast::Type* ty_u32(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_u32(const ProgramBuilder::TypesBuilder& ty) {
     return ty.u32();
 }
-inline const ast::Type* ty_f32(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_f32(const ProgramBuilder::TypesBuilder& ty) {
     return ty.f32();
 }
 template <typename T>
-inline const ast::Type* ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
     return ty.vec2<T>();
 }
 template <typename T>
-inline const ast::Type* ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
     return ty.vec3<T>();
 }
 template <typename T>
-inline const ast::Type* ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
     return ty.vec4<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat2x2<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat2x3(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat2x3(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat2x3<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat2x4(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat2x4(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat2x4<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat3x2(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat3x2(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat3x2<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat3x3<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat3x4(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat3x4(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat3x4<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat4x2(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat4x2(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat4x2<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat4x3(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat4x3(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat4x3<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat4x4<T>();
 }
 
@@ -144,10 +144,7 @@
     std::string expected;
 };
 inline std::ostream& operator<<(std::ostream& out, TypeCase c) {
-    ProgramBuilder b;
-    auto* ty = c.member_type(b.ty);
-    out << ty->FriendlyName(b.Symbols());
-    return out;
+    return out << c.expected;
 }
 
 using GlslGeneratorImplTest_MemberAccessor_StorageBufferLoad =
diff --git a/src/tint/writer/glsl/generator_impl_module_constant_test.cc b/src/tint/writer/glsl/generator_impl_module_constant_test.cc
index c5d594f..9f0985d 100644
--- a/src/tint/writer/glsl/generator_impl_module_constant_test.cc
+++ b/src/tint/writer/glsl/generator_impl_module_constant_test.cc
@@ -156,7 +156,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_AInt) {
-    auto* var = GlobalConst("G", Call(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    auto* var = GlobalConst("G", Call(ty.vec3<Infer>(), 1_a, 2_a, 3_a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(Let("l", Expr(var))),
@@ -176,7 +176,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_AFloat) {
-    auto* var = GlobalConst("G", Call(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    auto* var = GlobalConst("G", Call(ty.vec3<Infer>(), 1._a, 2._a, 3._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(Let("l", Expr(var))),
@@ -239,7 +239,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_mat2x3_AFloat) {
-    auto* var = GlobalConst("G", Call(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    auto* var = GlobalConst("G", Call(ty.mat2x3<Infer>(), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(Let("l", Expr(var))),
diff --git a/src/tint/writer/glsl/generator_impl_type_test.cc b/src/tint/writer/glsl/generator_impl_type_test.cc
index 5a63fcb..625c7d3 100644
--- a/src/tint/writer/glsl/generator_impl_type_test.cc
+++ b/src/tint/writer/glsl/generator_impl_type_test.cc
@@ -33,52 +33,52 @@
 using GlslGeneratorImplTest_Type = TestHelper;
 
 TEST_F(GlslGeneratorImplTest_Type, EmitType_Array) {
-    auto* arr = ty.array<bool, 4>();
-    GlobalVar("G", arr, type::AddressSpace::kPrivate);
+    auto arr = ty.array<bool, 4>();
+    ast::Type ty = GlobalVar("G", arr, type::AddressSpace::kPrivate)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), type::AddressSpace::kNone,
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(ty), type::AddressSpace::kNone,
                              type::Access::kReadWrite, "ary"))
         << gen.error();
     EXPECT_EQ(out.str(), "bool ary[4]");
 }
 
 TEST_F(GlslGeneratorImplTest_Type, EmitType_ArrayOfArray) {
-    auto* arr = ty.array(ty.array<bool, 4>(), 5_u);
-    GlobalVar("G", arr, type::AddressSpace::kPrivate);
+    auto arr = ty.array(ty.array<bool, 4>(), 5_u);
+    ast::Type ty = GlobalVar("G", arr, type::AddressSpace::kPrivate)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), type::AddressSpace::kNone,
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(ty), type::AddressSpace::kNone,
                              type::Access::kReadWrite, "ary"))
         << gen.error();
     EXPECT_EQ(out.str(), "bool ary[5][4]");
 }
 
 TEST_F(GlslGeneratorImplTest_Type, EmitType_ArrayOfArrayOfArray) {
-    auto* arr = ty.array(ty.array(ty.array<bool, 4>(), 5_u), 6_u);
-    GlobalVar("G", arr, type::AddressSpace::kPrivate);
+    auto arr = ty.array(ty.array(ty.array<bool, 4>(), 5_u), 6_u);
+    ast::Type ty = GlobalVar("G", arr, type::AddressSpace::kPrivate)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), type::AddressSpace::kNone,
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(ty), type::AddressSpace::kNone,
                              type::Access::kReadWrite, "ary"))
         << gen.error();
     EXPECT_EQ(out.str(), "bool ary[6][5][4]");
 }
 
 TEST_F(GlslGeneratorImplTest_Type, EmitType_Array_WithoutName) {
-    auto* arr = ty.array<bool, 4>();
-    GlobalVar("G", arr, type::AddressSpace::kPrivate);
+    auto arr = ty.array<bool, 4>();
+    ast::Type ty = GlobalVar("G", arr, type::AddressSpace::kPrivate)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), type::AddressSpace::kNone,
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(ty), type::AddressSpace::kNone,
                              type::Access::kReadWrite, ""))
         << gen.error();
     EXPECT_EQ(out.str(), "bool[4]");
@@ -313,7 +313,7 @@
 TEST_P(GlslDepthTexturesTest, Emit) {
     auto params = GetParam();
 
-    auto* t = ty.depth_texture(params.dim);
+    auto t = ty.depth_texture(params.dim);
 
     GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
@@ -341,7 +341,7 @@
 
 using GlslDepthMultisampledTexturesTest = TestHelper;
 TEST_F(GlslDepthMultisampledTexturesTest, Emit) {
-    auto* t = ty.depth_multisampled_texture(type::TextureDimension::k2d);
+    auto t = ty.depth_multisampled_texture(type::TextureDimension::k2d);
 
     GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
@@ -373,7 +373,7 @@
 TEST_P(GlslSampledTexturesTest, Emit) {
     auto params = GetParam();
 
-    const ast::Type* datatype = nullptr;
+    ast::Type datatype;
     switch (params.datatype) {
         case TextureDataType::F32:
             datatype = ty.f32();
@@ -385,7 +385,7 @@
             datatype = ty.i32();
             break;
     }
-    auto* t = ty.sampled_texture(params.dim, datatype);
+    ast::Type t = ty.sampled_texture(params.dim, datatype);
 
     GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
@@ -520,7 +520,7 @@
 TEST_P(GlslStorageTexturesTest, Emit) {
     auto params = GetParam();
 
-    auto* t = ty.storage_texture(params.dim, params.imgfmt, type::Access::kWrite);
+    auto t = ty.storage_texture(params.dim, params.imgfmt, type::Access::kWrite);
 
     GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
diff --git a/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc b/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc
index 562dabe..6511179 100644
--- a/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc
+++ b/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc
@@ -194,7 +194,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_AInt) {
-    auto* C = Const("C", Call(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    auto* C = Const("C", Call(ty.vec3<Infer>(), 1_a, 2_a, 3_a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
@@ -215,7 +215,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_AFloat) {
-    auto* C = Const("C", Call(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    auto* C = Const("C", Call(ty.vec3<Infer>(), 1._a, 2._a, 3._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
@@ -281,7 +281,7 @@
 }
 
 TEST_F(GlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_AFloat) {
-    auto* C = Const("C", Call(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    auto* C = Const("C", Call(ty.mat2x3<Infer>(), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
diff --git a/src/tint/writer/hlsl/generator_impl_member_accessor_test.cc b/src/tint/writer/hlsl/generator_impl_member_accessor_test.cc
index 94f96a8..0bd7ae6 100644
--- a/src/tint/writer/hlsl/generator_impl_member_accessor_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_member_accessor_test.cc
@@ -23,66 +23,66 @@
 namespace tint::writer::hlsl {
 namespace {
 
-using create_type_func_ptr = const ast::Type* (*)(const ProgramBuilder::TypesBuilder& ty);
+using create_type_func_ptr = ast::Type (*)(const ProgramBuilder::TypesBuilder& ty);
 
-inline const ast::Type* ty_i32(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_i32(const ProgramBuilder::TypesBuilder& ty) {
     return ty.i32();
 }
-inline const ast::Type* ty_u32(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_u32(const ProgramBuilder::TypesBuilder& ty) {
     return ty.u32();
 }
-inline const ast::Type* ty_f32(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_f32(const ProgramBuilder::TypesBuilder& ty) {
     return ty.f32();
 }
-inline const ast::Type* ty_f16(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_f16(const ProgramBuilder::TypesBuilder& ty) {
     return ty.f16();
 }
 template <typename T>
-inline const ast::Type* ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_vec2(const ProgramBuilder::TypesBuilder& ty) {
     return ty.vec2<T>();
 }
 template <typename T>
-inline const ast::Type* ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_vec3(const ProgramBuilder::TypesBuilder& ty) {
     return ty.vec3<T>();
 }
 template <typename T>
-inline const ast::Type* ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_vec4(const ProgramBuilder::TypesBuilder& ty) {
     return ty.vec4<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat2x2<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat2x3(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat2x3(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat2x3<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat2x4(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat2x4(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat2x4<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat3x2(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat3x2(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat3x2<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat3x3<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat3x4(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat3x4(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat3x4<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat4x2(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat4x2(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat4x2<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat4x3(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat4x3(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat4x3<T>();
 }
 template <typename T>
-inline const ast::Type* ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
+inline ast::Type ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) {
     return ty.mat4x4<T>();
 }
 
@@ -149,10 +149,7 @@
     std::string expected;
 };
 inline std::ostream& operator<<(std::ostream& out, TypeCase c) {
-    ProgramBuilder b;
-    auto* ty = c.member_type(b.ty);
-    out << ty->FriendlyName(b.Symbols());
-    return out;
+    return out << c.expected;
 }
 
 using HlslGeneratorImplTest_MemberAccessor_StorageBufferLoad_ConstantOffset =
diff --git a/src/tint/writer/hlsl/generator_impl_module_constant_test.cc b/src/tint/writer/hlsl/generator_impl_module_constant_test.cc
index 212a5d5..62f1d8ee 100644
--- a/src/tint/writer/hlsl/generator_impl_module_constant_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_module_constant_test.cc
@@ -109,7 +109,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_AInt) {
-    auto* var = GlobalConst("G", Call(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    auto* var = GlobalConst("G", Call(ty.vec3<Infer>(), 1_a, 2_a, 3_a));
     Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
@@ -123,7 +123,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_vec3_AFloat) {
-    auto* var = GlobalConst("G", Call(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    auto* var = GlobalConst("G", Call(ty.vec3<Infer>(), 1._a, 2._a, 3._a));
     Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
@@ -167,7 +167,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_GlobalConst_mat2x3_AFloat) {
-    auto* var = GlobalConst("G", Call(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    auto* var = GlobalConst("G", Call(ty.mat2x3<Infer>(), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
     Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
diff --git a/src/tint/writer/hlsl/generator_impl_type_test.cc b/src/tint/writer/hlsl/generator_impl_type_test.cc
index 9f5d012..be5a900 100644
--- a/src/tint/writer/hlsl/generator_impl_type_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_type_test.cc
@@ -33,52 +33,52 @@
 using HlslGeneratorImplTest_Type = TestHelper;
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Array) {
-    auto* arr = ty.array<bool, 4>();
-    GlobalVar("G", arr, type::AddressSpace::kPrivate);
+    auto arr = ty.array<bool, 4>();
+    ast::Type ty = GlobalVar("G", arr, type::AddressSpace::kPrivate)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), type::AddressSpace::kNone,
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(ty), type::AddressSpace::kNone,
                              type::Access::kReadWrite, "ary"))
         << gen.error();
     EXPECT_EQ(out.str(), "bool ary[4]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_ArrayOfArray) {
-    auto* arr = ty.array(ty.array<bool, 4>(), 5_u);
-    GlobalVar("G", arr, type::AddressSpace::kPrivate);
+    auto arr = ty.array(ty.array<bool, 4>(), 5_u);
+    ast::Type ty = GlobalVar("G", arr, type::AddressSpace::kPrivate)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), type::AddressSpace::kNone,
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(ty), type::AddressSpace::kNone,
                              type::Access::kReadWrite, "ary"))
         << gen.error();
     EXPECT_EQ(out.str(), "bool ary[5][4]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_ArrayOfArrayOfArray) {
-    auto* arr = ty.array(ty.array(ty.array<bool, 4>(), 5_u), 6_u);
-    GlobalVar("G", arr, type::AddressSpace::kPrivate);
+    auto arr = ty.array(ty.array(ty.array<bool, 4>(), 5_u), 6_u);
+    ast::Type ty = GlobalVar("G", arr, type::AddressSpace::kPrivate)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), type::AddressSpace::kNone,
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(ty), type::AddressSpace::kNone,
                              type::Access::kReadWrite, "ary"))
         << gen.error();
     EXPECT_EQ(out.str(), "bool ary[6][5][4]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Array_WithoutName) {
-    auto* arr = ty.array<bool, 4>();
-    GlobalVar("G", arr, type::AddressSpace::kPrivate);
+    auto arr = ty.array<bool, 4>();
+    ast::Type ty = GlobalVar("G", arr, type::AddressSpace::kPrivate)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), type::AddressSpace::kNone,
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(ty), type::AddressSpace::kNone,
                              type::Access::kReadWrite, ""))
         << gen.error();
     EXPECT_EQ(out.str(), "bool[4]");
@@ -307,7 +307,7 @@
 TEST_P(HlslDepthTexturesTest, Emit) {
     auto params = GetParam();
 
-    auto* t = ty.depth_texture(params.dim);
+    auto t = ty.depth_texture(params.dim);
 
     GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
@@ -338,7 +338,7 @@
 
 using HlslDepthMultisampledTexturesTest = TestHelper;
 TEST_F(HlslDepthMultisampledTexturesTest, Emit) {
-    auto* t = ty.depth_multisampled_texture(type::TextureDimension::k2d);
+    auto t = ty.depth_multisampled_texture(type::TextureDimension::k2d);
 
     GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
@@ -370,7 +370,7 @@
 TEST_P(HlslSampledTexturesTest, Emit) {
     auto params = GetParam();
 
-    const ast::Type* datatype = nullptr;
+    ast::Type datatype;
     switch (params.datatype) {
         case TextureDataType::F32:
             datatype = ty.f32();
@@ -382,7 +382,7 @@
             datatype = ty.i32();
             break;
     }
-    auto* t = ty.sampled_texture(params.dim, datatype);
+    ast::Type t = ty.sampled_texture(params.dim, datatype);
 
     GlobalVar("tex", t, Binding(1_a), Group(2_a));
 
@@ -518,7 +518,7 @@
 TEST_P(HlslStorageTexturesTest, Emit) {
     auto params = GetParam();
 
-    auto* t = ty.storage_texture(params.dim, params.imgfmt, type::Access::kWrite);
+    auto t = ty.storage_texture(params.dim, params.imgfmt, type::Access::kWrite);
 
     GlobalVar("tex", t,
               utils::Vector{
diff --git a/src/tint/writer/hlsl/generator_impl_variable_decl_statement_test.cc b/src/tint/writer/hlsl/generator_impl_variable_decl_statement_test.cc
index 47a7db3..170d993 100644
--- a/src/tint/writer/hlsl/generator_impl_variable_decl_statement_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_variable_decl_statement_test.cc
@@ -175,7 +175,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_AInt) {
-    auto* C = Const("C", Call(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    auto* C = Const("C", Call(ty.vec3<Infer>(), 1_a, 2_a, 3_a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
@@ -193,7 +193,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_AFloat) {
-    auto* C = Const("C", Call(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    auto* C = Const("C", Call(ty.vec3<Infer>(), 1._a, 2._a, 3._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
@@ -249,7 +249,7 @@
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_AFloat) {
-    auto* C = Const("C", Call(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    auto* C = Const("C", Call(ty.mat2x3<Infer>(), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 7100321..febde27 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -650,9 +650,8 @@
 
 bool GeneratorImpl::EmitFunctionCall(std::ostream& out,
                                      const sem::Call* call,
-                                     const sem::Function*) {
-    auto* ident = call->Declaration()->target.name;
-    out << program_->Symbols().NameFor(ident->symbol) << "(";
+                                     const sem::Function* fn) {
+    out << program_->Symbols().NameFor(fn->Declaration()->name->symbol) << "(";
 
     bool first = true;
     for (auto* arg : call->Arguments()) {
@@ -2097,7 +2096,6 @@
                     }
                     return true;
                 });
-
             if (!ok) {
                 return false;
             }
diff --git a/src/tint/writer/msl/generator_impl_binary_test.cc b/src/tint/writer/msl/generator_impl_binary_test.cc
index fc03507..4916799 100644
--- a/src/tint/writer/msl/generator_impl_binary_test.cc
+++ b/src/tint/writer/msl/generator_impl_binary_test.cc
@@ -32,8 +32,8 @@
     auto type = [&] {
         return ((params.op == ast::BinaryOp::kLogicalAnd) ||
                 (params.op == ast::BinaryOp::kLogicalOr))
-                   ? static_cast<const ast::Type*>(ty.bool_())
-                   : static_cast<const ast::Type*>(ty.u32());
+                   ? ty.bool_()
+                   : ty.u32();
     };
 
     auto* left = Var("left", type());
@@ -74,10 +74,10 @@
 TEST_P(MslBinaryTest_SignedOverflowDefinedBehaviour, Emit) {
     auto params = GetParam();
 
-    auto* a_type = ty.i32();
-    auto* b_type =
+    auto a_type = ty.i32();
+    auto b_type =
         (params.op == ast::BinaryOp::kShiftLeft || params.op == ast::BinaryOp::kShiftRight)
-            ? static_cast<const ast::Type*>(ty.u32())
+            ? ty.u32()
             : ty.i32();
 
     auto* a = Var("a", a_type);
@@ -107,10 +107,10 @@
 TEST_P(MslBinaryTest_SignedOverflowDefinedBehaviour_Chained, Emit) {
     auto params = GetParam();
 
-    auto* a_type = ty.i32();
-    auto* b_type =
+    auto a_type = ty.i32();
+    auto b_type =
         (params.op == ast::BinaryOp::kShiftLeft || params.op == ast::BinaryOp::kShiftRight)
-            ? static_cast<const ast::Type*>(ty.u32())
+            ? ty.u32()
             : ty.i32();
 
     auto* a = Var("a", a_type);
diff --git a/src/tint/writer/msl/generator_impl_builtin_texture_test.cc b/src/tint/writer/msl/generator_impl_builtin_texture_test.cc
index 028f851..16688ef 100644
--- a/src/tint/writer/msl/generator_impl_builtin_texture_test.cc
+++ b/src/tint/writer/msl/generator_impl_builtin_texture_test.cc
@@ -276,7 +276,7 @@
     param.BuildTextureVariable(this);
     param.BuildSamplerVariable(this);
 
-    auto* call = Call(Ident(param.function), param.args(this));
+    auto* call = Call(param.function, param.args(this));
     auto* stmt = CallStmt(call);
 
     Func("main", utils::Empty, ty.void_(), utils::Vector{stmt},
diff --git a/src/tint/writer/msl/generator_impl_function_test.cc b/src/tint/writer/msl/generator_impl_function_test.cc
index a1e91fe..a93d262 100644
--- a/src/tint/writer/msl/generator_impl_function_test.cc
+++ b/src/tint/writer/msl/generator_impl_function_test.cc
@@ -179,7 +179,7 @@
     //   @builtin(position) pos : vec4<f32>;
     // };
     // fn vert_main() -> Interface {
-    //   return Interface(0.4, 0.6, vec4<f32>());
+    //   return Interface(0.5, 0.25, vec4<f32>());
     // }
     // fn frag_main(colors : Interface) {
     //   const r = colors.col1;
@@ -194,8 +194,7 @@
         });
 
     Func("vert_main", utils::Empty, ty.Of(interface_struct),
-         utils::Vector{Return(
-             Call(ty.Of(interface_struct), Expr(0.5_f), Expr(0.25_f), Call(ty.vec4<f32>())))},
+         utils::Vector{Return(Call(ty.Of(interface_struct), 0.5_f, 0.25_f, vec4<f32>()))},
          utils::Vector{Stage(ast::PipelineStage::kVertex)});
 
     Func("frag_main", utils::Vector{Param("colors", ty.Of(interface_struct))}, ty.void_(),
diff --git a/src/tint/writer/msl/generator_impl_module_constant_test.cc b/src/tint/writer/msl/generator_impl_module_constant_test.cc
index 6de50d7..e798364 100644
--- a/src/tint/writer/msl/generator_impl_module_constant_test.cc
+++ b/src/tint/writer/msl/generator_impl_module_constant_test.cc
@@ -133,7 +133,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_vec3_AInt) {
-    auto* var = GlobalConst("G", Call(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    auto* var = GlobalConst("G", Call(ty.vec3<Infer>(), 1_a, 2_a, 3_a));
     Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
@@ -151,7 +151,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_vec3_AFloat) {
-    auto* var = GlobalConst("G", Call(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    auto* var = GlobalConst("G", Call(ty.vec3<Infer>(), 1._a, 2._a, 3._a));
     Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
@@ -207,7 +207,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_GlobalConst_mat2x3_AFloat) {
-    auto* var = GlobalConst("G", Call(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    auto* var = GlobalConst("G", Call(ty.mat2x3<Infer>(), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
     Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(Let("l", Expr(var)))});
 
     GeneratorImpl& gen = Build();
diff --git a/src/tint/writer/msl/generator_impl_sanitizer_test.cc b/src/tint/writer/msl/generator_impl_sanitizer_test.cc
index fda5296..3b35e6d 100644
--- a/src/tint/writer/msl/generator_impl_sanitizer_test.cc
+++ b/src/tint/writer/msl/generator_impl_sanitizer_test.cc
@@ -27,9 +27,7 @@
 using MslSanitizerTest = TestHelper;
 
 TEST_F(MslSanitizerTest, Call_ArrayLength) {
-    auto* s = Structure("my_struct", utils::Vector{
-                                         Member(0, "a", ty.array<f32>()),
-                                     });
+    auto* s = Structure("my_struct", utils::Vector{Member(0, "a", ty.array<f32>())});
     GlobalVar("b", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead, Binding(1_a),
               Group(2_a));
 
@@ -136,9 +134,7 @@
 }
 
 TEST_F(MslSanitizerTest, Call_ArrayLength_ViaLets) {
-    auto* s = Structure("my_struct", utils::Vector{
-                                         Member(0, "a", ty.array<f32>()),
-                                     });
+    auto* s = Structure("my_struct", utils::Vector{Member(0, "a", ty.array<f32>())});
     GlobalVar("b", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead, Binding(1_a),
               Group(2_a));
 
@@ -195,9 +191,7 @@
 }
 
 TEST_F(MslSanitizerTest, Call_ArrayLength_ArrayLengthFromUniform) {
-    auto* s = Structure("my_struct", utils::Vector{
-                                         Member(0, "a", ty.array<f32>()),
-                                     });
+    auto* s = Structure("my_struct", utils::Vector{Member(0, "a", ty.array<f32>())});
     GlobalVar("b", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead, Binding(1_a),
               Group(0_a));
     GlobalVar("c", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead, Binding(2_a),
@@ -256,9 +250,7 @@
 }
 
 TEST_F(MslSanitizerTest, Call_ArrayLength_ArrayLengthFromUniformMissingBinding) {
-    auto* s = Structure("my_struct", utils::Vector{
-                                         Member(0, "a", ty.array<f32>()),
-                                     });
+    auto* s = Structure("my_struct", utils::Vector{Member(0, "a", ty.array<f32>())});
     GlobalVar("b", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead, Binding(1_a),
               Group(0_a));
     GlobalVar("c", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead, Binding(2_a),
diff --git a/src/tint/writer/msl/generator_impl_type_test.cc b/src/tint/writer/msl/generator_impl_type_test.cc
index c116e87..971d2a6 100644
--- a/src/tint/writer/msl/generator_impl_type_test.cc
+++ b/src/tint/writer/msl/generator_impl_type_test.cc
@@ -89,60 +89,60 @@
 using MslGeneratorImplTest = TestHelper;
 
 TEST_F(MslGeneratorImplTest, EmitType_Array) {
-    auto* arr = ty.array<bool, 4>();
-    GlobalVar("G", arr, type::AddressSpace::kPrivate);
+    auto arr = ty.array<bool, 4>();
+    ast::Type type = GlobalVar("G", arr, type::AddressSpace::kPrivate)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), "ary")) << gen.error();
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type), "ary")) << gen.error();
     EXPECT_EQ(out.str(), "tint_array<bool, 4>");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_ArrayOfArray) {
-    auto* a = ty.array<bool, 4>();
-    auto* b = ty.array(a, 5_u);
-    GlobalVar("G", b, type::AddressSpace::kPrivate);
+    auto a = ty.array<bool, 4>();
+    auto b = ty.array(a, 5_u);
+    ast::Type type = GlobalVar("G", b, type::AddressSpace::kPrivate)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(b), "ary")) << gen.error();
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type), "ary")) << gen.error();
     EXPECT_EQ(out.str(), "tint_array<tint_array<bool, 4>, 5>");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_ArrayOfArrayOfArray) {
-    auto* a = ty.array<bool, 4>();
-    auto* b = ty.array(a, 5_u);
-    auto* c = ty.array(b, 6_u);
-    GlobalVar("G", c, type::AddressSpace::kPrivate);
+    auto a = ty.array<bool, 4>();
+    auto b = ty.array(a, 5_u);
+    auto c = ty.array(b, 6_u);
+    ast::Type type = GlobalVar("G", c, type::AddressSpace::kPrivate)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(c), "ary")) << gen.error();
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type), "ary")) << gen.error();
     EXPECT_EQ(out.str(), "tint_array<tint_array<tint_array<bool, 4>, 5>, 6>");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Array_WithoutName) {
-    auto* arr = ty.array<bool, 4>();
-    GlobalVar("G", arr, type::AddressSpace::kPrivate);
+    auto arr = ty.array<bool, 4>();
+    ast::Type type = GlobalVar("G", arr, type::AddressSpace::kPrivate)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), "")) << gen.error();
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type), "")) << gen.error();
     EXPECT_EQ(out.str(), "tint_array<bool, 4>");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_RuntimeArray) {
-    auto* arr = ty.array<bool, 1>();
-    GlobalVar("G", arr, type::AddressSpace::kPrivate);
+    auto arr = ty.array<bool, 1>();
+    ast::Type type = GlobalVar("G", arr, type::AddressSpace::kPrivate)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), "ary")) << gen.error();
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type), "ary")) << gen.error();
     EXPECT_EQ(out.str(), "tint_array<bool, 1>");
 }
 
@@ -283,13 +283,14 @@
                  Member("z", ty.f32()),
              });
 
-    GlobalVar("G", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead, Binding(0_a),
-              Group(0_a));
+    ast::Type type = GlobalVar("G", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead,
+                               Binding(0_a), Group(0_a))
+                         ->type;
 
     GeneratorImpl& gen = Build();
 
     TextGenerator::TextBuffer buf;
-    auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+    auto* sem_s = program->TypeOf(type)->As<sem::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
 
     // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
@@ -391,13 +392,14 @@
                                  Member("e", ty.f32()),
                              });
 
-    GlobalVar("G", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead, Binding(0_a),
-              Group(0_a));
+    ast::Type type = GlobalVar("G", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead,
+                               Binding(0_a), Group(0_a))
+                         ->type;
 
     GeneratorImpl& gen = Build();
 
     TextGenerator::TextBuffer buf;
-    auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+    auto* sem_s = program->TypeOf(type)->As<sem::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
 
     // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
@@ -465,13 +467,13 @@
                                      });
 
     // array_x: size(28), align(4)
-    auto* array_x = ty.array<f32, 7>();
+    auto array_x = ty.array<f32, 7>();
 
     // array_y: size(4096), align(512)
-    auto* array_y = ty.array(ty.Of(inner), 4_u);
+    auto array_y = ty.array(ty.Of(inner), 4_u);
 
     // array_z: size(4), align(4)
-    auto* array_z = ty.array<f32>();
+    auto array_z = ty.array<f32>();
 
     auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
@@ -482,13 +484,14 @@
                                  Member("f", array_z),
                              });
 
-    GlobalVar("G", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead, Binding(0_a),
-              Group(0_a));
+    ast::Type type = GlobalVar("G", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead,
+                               Binding(0_a), Group(0_a))
+                         ->type;
 
     GeneratorImpl& gen = Build();
 
     TextGenerator::TextBuffer buf;
-    auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+    auto* sem_s = program->TypeOf(type)->As<sem::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
 
     // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
@@ -557,7 +560,7 @@
 
 TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_ArrayVec3DefaultStride) {
     // array: size(64), align(16)
-    auto* array = ty.array(ty.vec3<f32>(), 4_u);
+    auto array = ty.array(ty.vec3<f32>(), 4_u);
 
     auto* s = Structure("S", utils::Vector{
                                  Member("a", ty.i32()),
@@ -565,13 +568,14 @@
                                  Member("c", ty.i32()),
                              });
 
-    GlobalVar("G", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead, Binding(0_a),
-              Group(0_a));
+    ast::Type type = GlobalVar("G", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead,
+                               Binding(0_a), Group(0_a))
+                         ->type;
 
     GeneratorImpl& gen = Build();
 
     TextGenerator::TextBuffer buf;
-    auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+    auto* sem_s = program->TypeOf(type)->As<sem::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
 
     // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
@@ -626,13 +630,14 @@
                                  Member("tint_pad_21", ty.f32()),
                              });
 
-    GlobalVar("G", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead, Binding(0_a),
-              Group(0_a));
+    ast::Type type = GlobalVar("G", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead,
+                               Binding(0_a), Group(0_a))
+                         ->type;
 
     GeneratorImpl& gen = Build();
 
     TextGenerator::TextBuffer buf;
-    auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+    auto* sem_s = program->TypeOf(type)->As<sem::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
     EXPECT_EQ(buf.String(), R"(struct S {
   /* 0x0000 */ int tint_pad_2;
@@ -684,13 +689,14 @@
                                  Member("b", ty.f32()),
                              });
 
-    GlobalVar("G", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead, Binding(0_a),
-              Group(0_a));
+    ast::Type type = GlobalVar("G", ty.Of(s), type::AddressSpace::kStorage, type::Access::kRead,
+                               Binding(0_a), Group(0_a))
+                         ->type;
 
     GeneratorImpl& gen = Build();
 
     TextGenerator::TextBuffer buf;
-    auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+    auto* sem_s = program->TypeOf(type)->As<sem::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, sem_s)) << gen.error();
     EXPECT_EQ(buf.String(), R"(struct S {
   /* 0x0000 */ int a;
@@ -847,13 +853,13 @@
 TEST_P(MslStorageTexturesTest, Emit) {
     auto params = GetParam();
 
-    auto* s = ty.storage_texture(params.dim, type::TexelFormat::kR32Float, type::Access::kWrite);
-    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
+    auto s = ty.storage_texture(params.dim, type::TexelFormat::kR32Float, type::Access::kWrite);
+    ast::Type type = GlobalVar("test_var", s, Binding(0_a), Group(0_a))->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(s), "")) << gen.error();
+    ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type), "")) << gen.error();
     EXPECT_EQ(out.str(), params.result);
 }
 INSTANTIATE_TEST_SUITE_P(
diff --git a/src/tint/writer/msl/generator_impl_variable_decl_statement_test.cc b/src/tint/writer/msl/generator_impl_variable_decl_statement_test.cc
index 5631b4d..927ae26 100644
--- a/src/tint/writer/msl/generator_impl_variable_decl_statement_test.cc
+++ b/src/tint/writer/msl/generator_impl_variable_decl_statement_test.cc
@@ -175,7 +175,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_AInt) {
-    auto* C = Const("C", Call(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    auto* C = Const("C", vec3<Infer>(1_a, 2_a, 3_a));
     Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
@@ -193,7 +193,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_AFloat) {
-    auto* C = Const("C", Call(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    auto* C = Const("C", vec3<Infer>(1._a, 2._a, 3._a));
     Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
@@ -249,7 +249,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_mat2x3_AFloat) {
-    auto* C = Const("C", Call(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    auto* C = Const("C", Call(ty.mat2x3<Infer>(), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
     Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
@@ -305,7 +305,7 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Const_arr_f32) {
-    auto* C = Const("C", Call(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
+    auto* C = Const("C", array<f32, 3>(1_f, 2_f, 3_f));
     Func("f", utils::Empty, ty.void_(), utils::Vector{Decl(C), Decl(Let("l", Expr(C)))});
 
     GeneratorImpl& gen = Build();
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index 590004c..008cf41 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -2243,9 +2243,9 @@
         });
 }
 
-uint32_t Builder::GenerateFunctionCall(const sem::Call* call, const sem::Function*) {
+uint32_t Builder::GenerateFunctionCall(const sem::Call* call, const sem::Function* fn) {
     auto* expr = call->Declaration();
-    auto* ident = expr->target.name;
+    auto* ident = fn->Declaration()->name;
 
     auto type_id = GenerateTypeIfNeeded(call->Type());
     if (type_id == 0) {
diff --git a/src/tint/writer/spirv/builder_accessor_expression_test.cc b/src/tint/writer/spirv/builder_accessor_expression_test.cc
index ccbcad6..e6692ab 100644
--- a/src/tint/writer/spirv/builder_accessor_expression_test.cc
+++ b/src/tint/writer/spirv/builder_accessor_expression_test.cc
@@ -1384,10 +1384,10 @@
     auto* c_type = Structure("C", utils::Vector{Member("baz", ty.vec3<f32>())});
 
     auto* b_type = Structure("B", utils::Vector{Member("bar", ty.Of(c_type))});
-    auto* b_ary_type = ty.array(ty.Of(b_type), 3_u);
+    auto b_ary_type = ty.array(ty.Of(b_type), 3_u);
     auto* a_type = Structure("A", utils::Vector{Member("foo", b_ary_type)});
 
-    auto* a_ary_type = ty.array(ty.Of(a_type), 2_u);
+    auto a_ary_type = ty.array(ty.Of(a_type), 2_u);
     auto* var = Var("index", a_ary_type);
     auto* expr = MemberAccessor(
         MemberAccessor(
diff --git a/src/tint/writer/spirv/builder_builtin_test.cc b/src/tint/writer/spirv/builder_builtin_test.cc
index 63c3990..0bab161 100644
--- a/src/tint/writer/spirv/builder_builtin_test.cc
+++ b/src/tint/writer/spirv/builder_builtin_test.cc
@@ -41,8 +41,8 @@
 
 // This tests that we do not push OpTypeSampledImage and float_0 type twice.
 TEST_F(BuiltinBuilderTest, Call_TextureSampleCompare_Twice) {
-    auto* s = ty.sampler(type::SamplerKind::kComparisonSampler);
-    auto* t = ty.depth_texture(type::TextureDimension::k2d);
+    auto s = ty.sampler(type::SamplerKind::kComparisonSampler);
+    auto t = ty.depth_texture(type::TextureDimension::k2d);
 
     auto* tex = GlobalVar("texture", t, Binding(0_a), Group(0_a));
     auto* sampler = GlobalVar("sampler", s, Binding(1_a), Group(0_a));
diff --git a/src/tint/writer/spirv/builder_entry_point_test.cc b/src/tint/writer/spirv/builder_entry_point_test.cc
index 51995dd..7b55804 100644
--- a/src/tint/writer/spirv/builder_entry_point_test.cc
+++ b/src/tint/writer/spirv/builder_entry_point_test.cc
@@ -215,7 +215,7 @@
             Member("pos", ty.vec4<f32>(), utils::Vector{Builtin(ast::BuiltinValue::kPosition)}),
         });
 
-    auto* vert_retval = Call(ty.Of(interface), 42_f, Call(ty.vec4<f32>()));
+    auto* vert_retval = Call(ty.Of(interface), 42_f, vec4<f32>());
     Func("vert_main", utils::Empty, ty.Of(interface), utils::Vector{Return(vert_retval)},
          utils::Vector{
              Stage(ast::PipelineStage::kVertex),
diff --git a/src/tint/writer/spirv/builder_function_attribute_test.cc b/src/tint/writer/spirv/builder_function_attribute_test.cc
index 9bba88f..8776842 100644
--- a/src/tint/writer/spirv/builder_function_attribute_test.cc
+++ b/src/tint/writer/spirv/builder_function_attribute_test.cc
@@ -51,15 +51,13 @@
     auto params = GetParam();
 
     const ast::Variable* var = nullptr;
-    const ast::Type* ret_type = nullptr;
+    ast::Type ret_type;
     utils::Vector<const ast::Attribute*, 2> ret_type_attrs;
     utils::Vector<const ast::Statement*, 2> body;
     if (params.stage == ast::PipelineStage::kVertex) {
         ret_type = ty.vec4<f32>();
         ret_type_attrs.Push(Builtin(ast::BuiltinValue::kPosition));
         body.Push(Return(Call(ty.vec4<f32>())));
-    } else {
-        ret_type = ty.void_();
     }
 
     utils::Vector<const ast::Attribute*, 2> deco_list{Stage(params.stage)};
diff --git a/src/tint/writer/spirv/builder_global_variable_test.cc b/src/tint/writer/spirv/builder_global_variable_test.cc
index c74def5..a246cbd 100644
--- a/src/tint/writer/spirv/builder_global_variable_test.cc
+++ b/src/tint/writer/spirv/builder_global_variable_test.cc
@@ -150,7 +150,7 @@
     // const c = vec3(1, 2, 3);
     // var v = c;
 
-    auto* c = GlobalConst("c", Call(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    auto* c = GlobalConst("c", Call(ty.vec3<Infer>(), 1_a, 2_a, 3_a));
     GlobalVar("v", type::AddressSpace::kPrivate, Expr(c));
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -179,7 +179,7 @@
     // const c = vec3(1.0, 2.0, 3.0);
     // var v = c;
 
-    auto* c = GlobalConst("c", Call(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    auto* c = GlobalConst("c", Call(ty.vec3<Infer>(), 1._a, 2._a, 3._a));
     GlobalVar("v", type::AddressSpace::kPrivate, Expr(c));
 
     spirv::Builder& b = SanitizeAndBuild();
@@ -472,8 +472,8 @@
 TEST_F(BuilderTest, GlobalVar_TextureStorageWriteOnly) {
     // var<uniform_constant> a : texture_storage_2d<r32uint, write>;
 
-    auto* type = ty.storage_texture(type::TextureDimension::k2d, type::TexelFormat::kR32Uint,
-                                    type::Access::kWrite);
+    auto type = ty.storage_texture(type::TextureDimension::k2d, type::TexelFormat::kR32Uint,
+                                   type::Access::kWrite);
 
     auto* var_a = GlobalVar("a", type, Binding(0_a), Group(0_a));
 
@@ -493,10 +493,10 @@
 }
 
 TEST_F(BuilderTest, GlobalVar_WorkgroupWithZeroInit) {
-    auto* type_scalar = ty.i32();
+    auto type_scalar = ty.i32();
     auto* var_scalar = GlobalVar("a", type_scalar, type::AddressSpace::kWorkgroup);
 
-    auto* type_array = ty.array<f32, 16>();
+    auto type_array = ty.array<f32, 16>();
     auto* var_array = GlobalVar("b", type_array, type::AddressSpace::kWorkgroup);
 
     auto* type_struct = Structure("C", utils::Vector{
diff --git a/src/tint/writer/spirv/builder_type_test.cc b/src/tint/writer/spirv/builder_type_test.cc
index 81c51a2..ad13e96 100644
--- a/src/tint/writer/spirv/builder_type_test.cc
+++ b/src/tint/writer/spirv/builder_type_test.cc
@@ -27,14 +27,15 @@
 using BuilderTest_Type = TestHelper;
 
 TEST_F(BuilderTest_Type, GenerateRuntimeArray) {
-    auto* ary = ty.array(ty.i32());
+    auto ary = ty.array(ty.i32());
     auto* str = Structure("S", utils::Vector{Member("x", ary)});
     GlobalVar("a", ty.Of(str), type::AddressSpace::kStorage, type::Access::kRead, Binding(0_a),
               Group(0_a));
+    ast::Type type = str->members[0]->type;
 
     spirv::Builder& b = Build();
 
-    auto id = b.GenerateTypeIfNeeded(program->TypeOf(ary));
+    auto id = b.GenerateTypeIfNeeded(program->TypeOf(type));
     ASSERT_FALSE(b.has_error()) << b.error();
     EXPECT_EQ(1u, id);
 
@@ -44,15 +45,16 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedRuntimeArray) {
-    auto* ary = ty.array(ty.i32());
+    auto ary = ty.array(ty.i32());
     auto* str = Structure("S", utils::Vector{Member("x", ary)});
     GlobalVar("a", ty.Of(str), type::AddressSpace::kStorage, type::Access::kRead, Binding(0_a),
               Group(0_a));
+    ast::Type type = str->members[0]->type;
 
     spirv::Builder& b = Build();
 
-    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
-    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
+    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(type)), 1u);
+    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(type)), 1u);
     ASSERT_FALSE(b.has_error()) << b.error();
 
     EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
@@ -61,12 +63,12 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateArray) {
-    auto* ary = ty.array<i32, 4>();
-    GlobalVar("a", ary, type::AddressSpace::kPrivate);
+    auto ary = ty.array<i32, 4>();
+    ast::Type type = GlobalVar("a", ary, type::AddressSpace::kPrivate)->type;
 
     spirv::Builder& b = Build();
 
-    auto id = b.GenerateTypeIfNeeded(program->TypeOf(ary));
+    auto id = b.GenerateTypeIfNeeded(program->TypeOf(type));
     ASSERT_FALSE(b.has_error()) << b.error();
     EXPECT_EQ(1u, id);
 
@@ -78,12 +80,12 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateArray_WithStride) {
-    auto* ary = ty.array<i32, 4>(utils::Vector{Stride(16)});
-    GlobalVar("a", ary, type::AddressSpace::kPrivate);
+    auto ary = ty.array<i32, 4>(utils::Vector{Stride(16)});
+    ast::Type ty = GlobalVar("a", ary, type::AddressSpace::kPrivate)->type;
 
     spirv::Builder& b = Build();
 
-    auto id = b.GenerateTypeIfNeeded(program->TypeOf(ary));
+    auto id = b.GenerateTypeIfNeeded(program->TypeOf(ty));
     ASSERT_FALSE(b.has_error()) << b.error();
     EXPECT_EQ(1u, id);
 
@@ -98,13 +100,13 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedArray) {
-    auto* ary = ty.array<i32, 4>();
-    GlobalVar("a", ary, type::AddressSpace::kPrivate);
+    auto ary = ty.array<i32, 4>();
+    ast::Type ty = GlobalVar("a", ary, type::AddressSpace::kPrivate)->type;
 
     spirv::Builder& b = Build();
 
-    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
-    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
+    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
+    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
     ASSERT_FALSE(b.has_error()) << b.error();
 
     EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
@@ -438,13 +440,13 @@
 TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers_ArraysOfMatrix) {
     Enable(ast::Extension::kF16);
 
-    auto* arr_mat2x2_f32 = ty.array(ty.mat2x2<f32>(), 1_u);  // Singly nested array
-    auto* arr_mat2x2_f16 = ty.array(ty.mat2x2<f16>(), 1_u);  // Singly nested array
-    auto* arr_arr_mat2x3_f32 =
+    auto arr_mat2x2_f32 = ty.array(ty.mat2x2<f32>(), 1_u);  // Singly nested array
+    auto arr_mat2x2_f16 = ty.array(ty.mat2x2<f16>(), 1_u);  // Singly nested array
+    ast::Type arr_arr_mat2x3_f32 =
         ty.array(ty.array(ty.mat2x3<f32>(), 1_u), 2_u);  // Doubly nested array
-    auto* arr_arr_mat2x3_f16 =
+    ast::Type arr_arr_mat2x3_f16 =
         ty.array(ty.array(ty.mat2x3<f16>(), 1_u), 2_u);  // Doubly nested array
-    auto* rtarr_mat4x4 = ty.array(ty.mat4x4<f32>());     // Runtime array
+    auto rtarr_mat4x4 = ty.array(ty.mat4x4<f32>());      // Runtime array
 
     auto* s = Structure(
         "S", utils::Vector{
@@ -860,14 +862,14 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_1d) {
-    auto* s = ty.storage_texture(type::TextureDimension::k1d, type::TexelFormat::kR32Float,
-                                 type::Access::kWrite);
+    auto s = ty.storage_texture(type::TextureDimension::k1d, type::TexelFormat::kR32Float,
+                                type::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
+    ast::Type ty = GlobalVar("test_var", s, Binding(0_a), Group(0_a))->type;
 
     spirv::Builder& b = Build();
 
-    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
     ASSERT_FALSE(b.has_error()) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 1D 0 0 0 2 R32f
@@ -875,14 +877,14 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_2d) {
-    auto* s = ty.storage_texture(type::TextureDimension::k2d, type::TexelFormat::kR32Float,
-                                 type::Access::kWrite);
+    auto s = ty.storage_texture(type::TextureDimension::k2d, type::TexelFormat::kR32Float,
+                                type::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
+    ast::Type ty = GlobalVar("test_var", s, Binding(0_a), Group(0_a))->type;
 
     spirv::Builder& b = Build();
 
-    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
     ASSERT_FALSE(b.has_error()) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 2D 0 0 0 2 R32f
@@ -890,14 +892,14 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_2dArray) {
-    auto* s = ty.storage_texture(type::TextureDimension::k2dArray, type::TexelFormat::kR32Float,
-                                 type::Access::kWrite);
+    auto s = ty.storage_texture(type::TextureDimension::k2dArray, type::TexelFormat::kR32Float,
+                                type::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
+    ast::Type ty = GlobalVar("test_var", s, Binding(0_a), Group(0_a))->type;
 
     spirv::Builder& b = Build();
 
-    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
     ASSERT_FALSE(b.has_error()) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 2D 0 1 0 2 R32f
@@ -905,14 +907,14 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_3d) {
-    auto* s = ty.storage_texture(type::TextureDimension::k3d, type::TexelFormat::kR32Float,
-                                 type::Access::kWrite);
+    auto s = ty.storage_texture(type::TextureDimension::k3d, type::TexelFormat::kR32Float,
+                                type::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
+    ast::Type ty = GlobalVar("test_var", s, Binding(0_a), Group(0_a))->type;
 
     spirv::Builder& b = Build();
 
-    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
     ASSERT_FALSE(b.has_error()) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 3D 0 0 0 2 R32f
@@ -920,14 +922,14 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_SampledTypeFloat_Format_r32float) {
-    auto* s = ty.storage_texture(type::TextureDimension::k2d, type::TexelFormat::kR32Float,
-                                 type::Access::kWrite);
+    auto s = ty.storage_texture(type::TextureDimension::k2d, type::TexelFormat::kR32Float,
+                                type::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
+    ast::Type ty = GlobalVar("test_var", s, Binding(0_a), Group(0_a))->type;
 
     spirv::Builder& b = Build();
 
-    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
     ASSERT_FALSE(b.has_error()) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 2D 0 0 0 2 R32f
@@ -935,14 +937,14 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_SampledTypeSint_Format_r32sint) {
-    auto* s = ty.storage_texture(type::TextureDimension::k2d, type::TexelFormat::kR32Sint,
-                                 type::Access::kWrite);
+    auto s = ty.storage_texture(type::TextureDimension::k2d, type::TexelFormat::kR32Sint,
+                                type::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
+    ast::Type ty = GlobalVar("test_var", s, Binding(0_a), Group(0_a))->type;
 
     spirv::Builder& b = Build();
 
-    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
     ASSERT_FALSE(b.has_error()) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
 %1 = OpTypeImage %2 2D 0 0 0 2 R32i
@@ -950,14 +952,14 @@
 }
 
 TEST_F(BuilderTest_Type, StorageTexture_Generate_SampledTypeUint_Format_r32uint) {
-    auto* s = ty.storage_texture(type::TextureDimension::k2d, type::TexelFormat::kR32Uint,
-                                 type::Access::kWrite);
+    auto s = ty.storage_texture(type::TextureDimension::k2d, type::TexelFormat::kR32Uint,
+                                type::Access::kWrite);
 
-    GlobalVar("test_var", s, Binding(0_a), Group(0_a));
+    ast::Type ty = GlobalVar("test_var", s, Binding(0_a), Group(0_a))->type;
 
     spirv::Builder& b = Build();
 
-    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(s)), 1u);
+    EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
     ASSERT_FALSE(b.has_error()) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
 %1 = OpTypeImage %2 2D 0 0 0 2 R32ui
diff --git a/src/tint/writer/text_generator.h b/src/tint/writer/text_generator.h
index 248809a..1c22522 100644
--- a/src/tint/writer/text_generator.h
+++ b/src/tint/writer/text_generator.h
@@ -196,10 +196,6 @@
     /// @param expr the expression
     const type::Type* TypeOf(const ast::Expression* expr) const { return builder_.TypeOf(expr); }
 
-    /// @returns the resolved type of the ast::Type `type`
-    /// @param type the type
-    const type::Type* TypeOf(const ast::Type* type) const { return builder_.TypeOf(type); }
-
     /// @returns the resolved type of the ast::TypeDecl `type_decl`
     /// @param type_decl the type
     const type::Type* TypeOf(const ast::TypeDecl* type_decl) const {
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index 19345d0..10bd9d8 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -17,8 +17,6 @@
 #include <algorithm>
 
 #include "src/tint/ast/alias.h"
-#include "src/tint/ast/array.h"
-#include "src/tint/ast/atomic.h"
 #include "src/tint/ast/bool_literal_expression.h"
 #include "src/tint/ast/call_statement.h"
 #include "src/tint/ast/float_literal_expression.h"
@@ -26,19 +24,13 @@
 #include "src/tint/ast/internal_attribute.h"
 #include "src/tint/ast/interpolate_attribute.h"
 #include "src/tint/ast/invariant_attribute.h"
-#include "src/tint/ast/matrix.h"
 #include "src/tint/ast/module.h"
-#include "src/tint/ast/multisampled_texture.h"
-#include "src/tint/ast/pointer.h"
-#include "src/tint/ast/sampled_texture.h"
 #include "src/tint/ast/stage_attribute.h"
 #include "src/tint/ast/stride_attribute.h"
 #include "src/tint/ast/struct_member_align_attribute.h"
 #include "src/tint/ast/struct_member_offset_attribute.h"
 #include "src/tint/ast/struct_member_size_attribute.h"
-#include "src/tint/ast/type_name.h"
 #include "src/tint/ast/variable_decl_statement.h"
-#include "src/tint/ast/vector.h"
 #include "src/tint/ast/workgroup_attribute.h"
 #include "src/tint/sem/struct.h"
 #include "src/tint/sem/switch_statement.h"
@@ -118,7 +110,7 @@
         [&](const ast::Alias* alias) {  //
             auto out = line();
             out << "alias " << program_->Symbols().NameFor(alias->name->symbol) << " = ";
-            if (!EmitType(out, alias->type)) {
+            if (!EmitExpression(out, alias->type)) {
                 return false;
             }
             out << ";";
@@ -215,7 +207,7 @@
 
 bool GeneratorImpl::EmitBitcast(std::ostream& out, const ast::BitcastExpression* expr) {
     out << "bitcast<";
-    if (!EmitType(out, expr->type)) {
+    if (!EmitExpression(out, expr->type)) {
         return false;
     }
 
@@ -229,14 +221,7 @@
 }
 
 bool GeneratorImpl::EmitCall(std::ostream& out, const ast::CallExpression* expr) {
-    if (expr->target.name) {
-        out << program_->Symbols().NameFor(expr->target.name->symbol);
-    } else if (TINT_LIKELY(expr->target.type)) {
-        if (!EmitType(out, expr->target.type)) {
-            return false;
-        }
-    } else {
-        TINT_ICE(Writer, diagnostics_) << "CallExpression target had neither a name or type";
+    if (!EmitExpression(out, expr->target)) {
         return false;
     }
     out << "(";
@@ -293,9 +278,12 @@
 }
 
 bool GeneratorImpl::EmitIdentifier(std::ostream& out, const ast::Identifier* ident) {
-    out << program_->Symbols().NameFor(ident->symbol);
     if (auto* tmpl_ident = ident->As<ast::TemplatedIdentifier>()) {
-        out << "<";
+        if (!tmpl_ident->attributes.IsEmpty()) {
+            EmitAttributes(out, tmpl_ident->attributes);
+            out << " ";
+        }
+        out << program_->Symbols().NameFor(ident->symbol) << "<";
         TINT_DEFER(out << ">");
         for (auto* expr : tmpl_ident->arguments) {
             if (expr != tmpl_ident->arguments.Front()) {
@@ -305,6 +293,8 @@
                 return false;
             }
         }
+    } else {
+        out << program_->Symbols().NameFor(ident->symbol);
     }
     return true;
 }
@@ -335,7 +325,7 @@
 
             out << program_->Symbols().NameFor(v->name->symbol) << " : ";
 
-            if (!EmitType(out, v->type)) {
+            if (!EmitExpression(out, v->type)) {
                 return false;
             }
         }
@@ -352,7 +342,7 @@
                 out << " ";
             }
 
-            if (!EmitType(out, func->return_type)) {
+            if (!EmitExpression(out, func->return_type)) {
                 return false;
             }
         }
@@ -404,152 +394,6 @@
     return false;
 }
 
-bool GeneratorImpl::EmitType(std::ostream& out, const ast::Type* ty) {
-    return Switch(
-        ty,
-        [&](const ast::Array* ary) {
-            for (auto* attr : ary->attributes) {
-                if (auto* stride = attr->As<ast::StrideAttribute>()) {
-                    out << "@stride(" << stride->stride << ") ";
-                }
-            }
-
-            out << "array";
-            if (ary->type) {
-                out << "<";
-                TINT_DEFER(out << ">");
-
-                if (!EmitType(out, ary->type)) {
-                    return false;
-                }
-
-                if (!ary->IsRuntimeArray()) {
-                    out << ", ";
-                    if (!EmitExpression(out, ary->count)) {
-                        return false;
-                    }
-                }
-            }
-            return true;
-        },
-        [&](const ast::Matrix* mat) {
-            out << "mat" << mat->columns << "x" << mat->rows;
-            if (auto* el_ty = mat->type) {
-                out << "<";
-                if (!EmitType(out, el_ty)) {
-                    return false;
-                }
-                out << ">";
-            }
-            return true;
-        },
-        [&](const ast::Pointer* ptr) {
-            out << "ptr<" << ptr->address_space << ", ";
-            if (!EmitType(out, ptr->type)) {
-                return false;
-            }
-            if (ptr->access != type::Access::kUndefined) {
-                out << ", ";
-                if (!EmitAccess(out, ptr->access)) {
-                    return false;
-                }
-            }
-            out << ">";
-            return true;
-        },
-        [&](const ast::Atomic* atomic) {
-            out << "atomic<";
-            if (!EmitType(out, atomic->type)) {
-                return false;
-            }
-            out << ">";
-            return true;
-        },
-        [&](const ast::Texture* texture) {
-            out << "texture_";
-            bool ok = Switch(
-                texture,
-                [&](const ast::SampledTexture*) {  //
-                    /* nothing to emit */
-                    return true;
-                },
-                [&](const ast::MultisampledTexture*) {  //
-                    out << "multisampled_";
-                    return true;
-                },
-                [&](Default) {  //
-                    diagnostics_.add_error(diag::System::Writer, "unknown texture type");
-                    return false;
-                });
-            if (!ok) {
-                return false;
-            }
-
-            switch (texture->dim) {
-                case type::TextureDimension::k1d:
-                    out << "1d";
-                    break;
-                case type::TextureDimension::k2d:
-                    out << "2d";
-                    break;
-                case type::TextureDimension::k2dArray:
-                    out << "2d_array";
-                    break;
-                case type::TextureDimension::k3d:
-                    out << "3d";
-                    break;
-                case type::TextureDimension::kCube:
-                    out << "cube";
-                    break;
-                case type::TextureDimension::kCubeArray:
-                    out << "cube_array";
-                    break;
-                default:
-                    diagnostics_.add_error(diag::System::Writer, "unknown texture dimension");
-                    return false;
-            }
-
-            return Switch(
-                texture,
-                [&](const ast::SampledTexture* sampled) {  //
-                    out << "<";
-                    if (!EmitType(out, sampled->type)) {
-                        return false;
-                    }
-                    out << ">";
-                    return true;
-                },
-                [&](const ast::MultisampledTexture* ms) {  //
-                    out << "<";
-                    if (!EmitType(out, ms->type)) {
-                        return false;
-                    }
-                    out << ">";
-                    return true;
-                },
-                [&](Default) {  //
-                    return true;
-                });
-        },
-        [&](const ast::Vector* vec) {
-            out << "vec" << vec->width;
-            if (auto* el_ty = vec->type) {
-                out << "<";
-                if (!EmitType(out, el_ty)) {
-                    return false;
-                }
-                out << ">";
-            }
-            return true;
-        },
-        [&](const ast::TypeName* tn) { return EmitIdentifier(out, tn->name); },
-        [&](Default) {
-            diagnostics_.add_error(diag::System::Writer,
-                                   "unknown type in EmitType: " + std::string(ty->TypeInfo().name));
-            return false;
-        });
-}
-
 bool GeneratorImpl::EmitStructType(const ast::Struct* str) {
     if (str->attributes.Length()) {
         if (!EmitAttributes(line(), str->attributes)) {
@@ -606,7 +450,7 @@
 
         auto out = line();
         out << program_->Symbols().NameFor(mem->name->symbol) << " : ";
-        if (!EmitType(out, mem->type)) {
+        if (!EmitExpression(out, mem->type)) {
             return false;
         }
         out << ",";
@@ -665,9 +509,9 @@
 
     out << " " << program_->Symbols().NameFor(v->name->symbol);
 
-    if (auto* ty = v->type) {
+    if (auto ty = v->type) {
         out << " : ";
-        if (!EmitType(out, ty)) {
+        if (!EmitExpression(out, ty)) {
             return false;
         }
     }
diff --git a/src/tint/writer/wgsl/generator_impl.h b/src/tint/writer/wgsl/generator_impl.h
index cf5d270..56c0dfb 100644
--- a/src/tint/writer/wgsl/generator_impl.h
+++ b/src/tint/writer/wgsl/generator_impl.h
@@ -53,7 +53,7 @@
     bool Generate();
 
     /// Handles generating a diagnostic control
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param diagnostic the diagnostic control node
     /// @returns true if the diagnostic control was emitted
     bool EmitDiagnosticControl(std::ostream& out, const ast::DiagnosticControl& diagnostic);
@@ -66,7 +66,7 @@
     /// @returns true if the declared type was emitted
     bool EmitTypeDecl(const ast::TypeDecl* ty);
     /// Handles an index accessor expression
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param expr the expression to emit
     /// @returns true if the index accessor was emitted
     bool EmitIndexAccessor(std::ostream& out, const ast::IndexAccessorExpression* expr);
@@ -75,17 +75,17 @@
     /// @returns true if the statement was emitted successfully
     bool EmitAssign(const ast::AssignmentStatement* stmt);
     /// Handles generating a binary expression
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param expr the binary expression
     /// @returns true if the expression was emitted, false otherwise
     bool EmitBinary(std::ostream& out, const ast::BinaryExpression* expr);
     /// Handles generating a binary operator
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param op the binary operator
     /// @returns true if the operator was emitted, false otherwise
     bool EmitBinaryOp(std::ostream& out, const ast::BinaryOp op);
     /// Handles generating a bitcast expression
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param expr the bitcast expression
     /// @returns true if the bitcast was emitted
     bool EmitBitcast(std::ostream& out, const ast::BitcastExpression* expr);
@@ -107,7 +107,7 @@
     /// @returns true if the statement was emitted successfully
     bool EmitBreakIf(const ast::BreakIfStatement* stmt);
     /// Handles generating a call expression
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param expr the call expression
     /// @returns true if the call expression is emitted
     bool EmitCall(std::ostream& out, const ast::CallExpression* expr);
@@ -120,7 +120,7 @@
     /// @returns true if the statement was emitted successfully
     bool EmitCompoundAssign(const ast::CompoundAssignmentStatement* stmt);
     /// Handles generating a literal expression
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param expr the literal expression expression
     /// @returns true if the literal expression is emitted
     bool EmitLiteral(std::ostream& out, const ast::LiteralExpression* expr);
@@ -129,7 +129,7 @@
     /// @returns true if the statement was emitted successfully
     bool EmitContinue(const ast::ContinueStatement* stmt);
     /// Handles generate an Expression
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param expr the expression
     /// @returns true if the expression was emitted
     bool EmitExpression(std::ostream& out, const ast::Expression* expr);
@@ -138,7 +138,7 @@
     /// @returns true if the function was emitted
     bool EmitFunction(const ast::Function* func);
     /// Handles generating an identifier expression
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param expr the identifier expression
     /// @returns true if the identifier was emitted
     bool EmitIdentifier(std::ostream& out, const ast::IdentifierExpression* expr);
@@ -172,7 +172,7 @@
     /// @returns true if the statement was emtited
     bool EmitWhile(const ast::WhileStatement* stmt);
     /// Handles a member accessor expression
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param expr the member accessor expression
     /// @returns true if the member accessor was emitted
     bool EmitMemberAccessor(std::ostream& out, const ast::MemberAccessorExpression* expr);
@@ -200,37 +200,32 @@
     /// @param stmt the statement to emit
     /// @returns true if the statement was emitted
     bool EmitSwitch(const ast::SwitchStatement* stmt);
-    /// Handles generating type
-    /// @param out the output of the expression stream
-    /// @param type the type to generate
-    /// @returns true if the type is emitted
-    bool EmitType(std::ostream& out, const ast::Type* type);
     /// Handles generating a struct declaration
     /// @param str the struct
     /// @returns true if the struct is emitted
     bool EmitStructType(const ast::Struct* str);
     /// Handles emitting an image format
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param fmt the format to generate
     /// @returns true if the format is emitted
     bool EmitImageFormat(std::ostream& out, const type::TexelFormat fmt);
     /// Handles emitting an access control
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param access the access to generate
     /// @returns true if the access is emitted
     bool EmitAccess(std::ostream& out, const type::Access access);
     /// Handles a unary op expression
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param expr the expression to emit
     /// @returns true if the expression was emitted
     bool EmitUnaryOp(std::ostream& out, const ast::UnaryOpExpression* expr);
     /// Handles generating a variable
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param var the variable to generate
     /// @returns true if the variable was emitted
     bool EmitVariable(std::ostream& out, const ast::Variable* var);
     /// Handles generating a attribute list
-    /// @param out the output of the expression stream
+    /// @param out the output stream
     /// @param attrs the attribute list
     /// @returns true if the attributes were emitted
     bool EmitAttributes(std::ostream& out, utils::VectorRef<const ast::Attribute*> attrs);
diff --git a/src/tint/writer/wgsl/generator_impl_binary_test.cc b/src/tint/writer/wgsl/generator_impl_binary_test.cc
index 8368ad8..2770e5c 100644
--- a/src/tint/writer/wgsl/generator_impl_binary_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_binary_test.cc
@@ -29,7 +29,7 @@
 TEST_P(WgslBinaryTest, Emit) {
     auto params = GetParam();
 
-    auto op_ty = [&]() -> const ast::Type* {
+    auto op_ty = [&]() {
         if (params.op == ast::BinaryOp::kLogicalAnd || params.op == ast::BinaryOp::kLogicalOr) {
             return ty.bool_();
         } else {
diff --git a/src/tint/writer/wgsl/generator_impl_function_test.cc b/src/tint/writer/wgsl/generator_impl_function_test.cc
index 6550b6a..42e0184 100644
--- a/src/tint/writer/wgsl/generator_impl_function_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_function_test.cc
@@ -109,7 +109,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_Function_EntryPoint_Parameters) {
-    auto* vec4 = ty.vec4<f32>();
+    auto vec4 = ty.vec4<f32>();
     auto* coord = Param("coord", vec4,
                         utils::Vector{
                             Builtin(ast::BuiltinValue::kPosition),
diff --git a/src/tint/writer/wgsl/generator_impl_global_decl_test.cc b/src/tint/writer/wgsl/generator_impl_global_decl_test.cc
index 294b2d6..5654ebe 100644
--- a/src/tint/writer/wgsl/generator_impl_global_decl_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_global_decl_test.cc
@@ -117,7 +117,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_Global_Texture) {
-    auto* st = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
+    auto st = ty.sampled_texture(type::TextureDimension::k1d, ty.f32());
     GlobalVar("t", st, Group(0_a), Binding(0_a));
 
     GeneratorImpl& gen = Build();
diff --git a/src/tint/writer/wgsl/generator_impl_initializer_test.cc b/src/tint/writer/wgsl/generator_impl_initializer_test.cc
index 9bc62c1..87dddca 100644
--- a/src/tint/writer/wgsl/generator_impl_initializer_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_initializer_test.cc
@@ -175,8 +175,8 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitInitializer_Type_ImplicitArray) {
-    WrapInFunction(Call(ty.array(nullptr, nullptr), vec3<f32>(1_f, 2_f, 3_f),
-                        vec3<f32>(4_f, 5_f, 6_f), vec3<f32>(7_f, 8_f, 9_f)));
+    WrapInFunction(Call(ty.array<Infer>(), vec3<f32>(1_f, 2_f, 3_f), vec3<f32>(4_f, 5_f, 6_f),
+                        vec3<f32>(7_f, 8_f, 9_f)));
 
     GeneratorImpl& gen = Build();
 
diff --git a/src/tint/writer/wgsl/generator_impl_type_test.cc b/src/tint/writer/wgsl/generator_impl_type_test.cc
index ee798b8..bad1440 100644
--- a/src/tint/writer/wgsl/generator_impl_type_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_type_test.cc
@@ -27,138 +27,129 @@
 
 TEST_F(WgslGeneratorImplTest, EmitType_Alias) {
     auto* alias = Alias("alias", ty.f32());
-    auto* alias_ty = ty.Of(alias);
-    WrapInFunction(Var("make_reachable", alias_ty));
+    auto type = Alias("make_type_reachable", ty.Of(alias))->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, alias_ty)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "alias");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Array) {
-    auto* arr = ty.array<bool, 4u>();
-    Alias("make_type_reachable", arr);
+    auto type = Alias("make_type_reachable", ty.array<bool, 4u>())->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, arr)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "array<bool, 4u>");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Array_Attribute) {
-    auto* a = ty.array(ty.bool_(), 4_u, utils::Vector{Stride(16)});
-    Alias("make_type_reachable", a);
+    auto type =
+        Alias("make_type_reachable", ty.array(ty.bool_(), 4_u, utils::Vector{Stride(16)}))->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, a)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "@stride(16) array<bool, 4u>");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_RuntimeArray) {
-    auto* a = ty.array(ty.bool_());
-    Alias("make_type_reachable", a);
+    auto type = Alias("make_type_reachable", ty.array(ty.bool_()))->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, a)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "array<bool>");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Bool) {
-    auto* bool_ = ty.bool_();
-    Alias("make_type_reachable", bool_);
+    auto type = Alias("make_type_reachable", ty.bool_())->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, bool_)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "bool");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_F32) {
-    auto* f32 = ty.f32();
-    Alias("make_type_reachable", f32);
+    auto type = Alias("make_type_reachable", ty.f32())->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, f32)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "f32");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_F16) {
     Enable(ast::Extension::kF16);
 
-    auto* f16 = ty.f16();
-    Alias("make_type_reachable", f16);
+    auto type = Alias("make_type_reachable", ty.f16())->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, f16)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "f16");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_I32) {
-    auto* i32 = ty.i32();
-    Alias("make_type_reachable", i32);
+    auto type = Alias("make_type_reachable", ty.i32())->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, i32)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "i32");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Matrix_F32) {
-    auto* mat2x3 = ty.mat2x3<f32>();
-    Alias("make_type_reachable", mat2x3);
+    auto type = Alias("make_type_reachable", ty.mat2x3<f32>())->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, mat2x3)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "mat2x3<f32>");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Matrix_F16) {
     Enable(ast::Extension::kF16);
 
-    auto* mat2x3 = ty.mat2x3<f16>();
-    Alias("make_type_reachable", mat2x3);
+    auto type = Alias("make_type_reachable", ty.mat2x3<f16>())->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, mat2x3)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "mat2x3<f16>");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Pointer) {
-    auto* p = ty.pointer<f32>(type::AddressSpace::kWorkgroup);
-    Alias("make_type_reachable", p);
+    auto type = Alias("make_type_reachable", ty.pointer<f32>(type::AddressSpace::kWorkgroup))->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, p)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "ptr<workgroup, f32>");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_PointerAccessMode) {
-    auto* p = ty.pointer<f32>(type::AddressSpace::kStorage, type::Access::kReadWrite);
-    Alias("make_type_reachable", p);
+    auto type = Alias("make_type_reachable",
+                      ty.pointer<f32>(type::AddressSpace::kStorage, type::Access::kReadWrite))
+                    ->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, p)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "ptr<storage, f32, read_write>");
 }
 
@@ -167,13 +158,12 @@
                                  Member("a", ty.i32()),
                                  Member("b", ty.f32()),
                              });
-    auto* s_ty = ty.Of(s);
-    WrapInFunction(Var("make_reachable", s_ty));
+    auto type = Alias("make_reachable", ty.Of(s))->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, s_ty)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "S");
 }
 
@@ -295,37 +285,34 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_U32) {
-    auto* u32 = ty.u32();
-    Alias("make_type_reachable", u32);
+    auto type = Alias("make_type_reachable", ty.u32())->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, u32)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "u32");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Vector_F32) {
-    auto* vec3 = ty.vec3<f32>();
-    Alias("make_type_reachable", vec3);
+    auto type = Alias("make_type_reachable", ty.vec3<f32>())->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, vec3)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "vec3<f32>");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Vector_F16) {
     Enable(ast::Extension::kF16);
 
-    auto* vec3 = ty.vec3<f16>();
-    Alias("make_type_reachable", vec3);
+    auto type = Alias("make_type_reachable", ty.vec3<f16>())->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, vec3)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "vec3<f16>");
 }
 
@@ -342,13 +329,12 @@
 TEST_P(WgslGenerator_DepthTextureTest, EmitType_DepthTexture) {
     auto param = GetParam();
 
-    auto* d = ty.depth_texture(param.dim);
-    Alias("make_type_reachable", d);
+    auto type = Alias("make_type_reachable", ty.depth_texture(param.dim))->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, d)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), param.name);
 }
 INSTANTIATE_TEST_SUITE_P(
@@ -363,39 +349,39 @@
 TEST_P(WgslGenerator_SampledTextureTest, EmitType_SampledTexture_F32) {
     auto param = GetParam();
 
-    auto* t = ty.sampled_texture(param.dim, ty.f32());
-    Alias("make_type_reachable", t);
+    auto t = ty.sampled_texture(param.dim, ty.f32());
+    auto type = Alias("make_type_reachable", t)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), std::string(param.name) + "<f32>");
 }
 
 TEST_P(WgslGenerator_SampledTextureTest, EmitType_SampledTexture_I32) {
     auto param = GetParam();
 
-    auto* t = ty.sampled_texture(param.dim, ty.i32());
-    Alias("make_type_reachable", t);
+    auto t = ty.sampled_texture(param.dim, ty.i32());
+    auto type = Alias("make_type_reachable", t)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), std::string(param.name) + "<i32>");
 }
 
 TEST_P(WgslGenerator_SampledTextureTest, EmitType_SampledTexture_U32) {
     auto param = GetParam();
 
-    auto* t = ty.sampled_texture(param.dim, ty.u32());
-    Alias("make_type_reachable", t);
+    auto t = ty.sampled_texture(param.dim, ty.u32());
+    auto type = Alias("make_type_reachable", t)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), std::string(param.name) + "<u32>");
 }
 INSTANTIATE_TEST_SUITE_P(
@@ -412,39 +398,39 @@
 TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_F32) {
     auto param = GetParam();
 
-    auto* t = ty.multisampled_texture(param.dim, ty.f32());
-    Alias("make_type_reachable", t);
+    auto t = ty.multisampled_texture(param.dim, ty.f32());
+    auto type = Alias("make_type_reachable", t)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), std::string(param.name) + "<f32>");
 }
 
 TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_I32) {
     auto param = GetParam();
 
-    auto* t = ty.multisampled_texture(param.dim, ty.i32());
-    Alias("make_type_reachable", t);
+    auto t = ty.multisampled_texture(param.dim, ty.i32());
+    auto type = Alias("make_type_reachable", t)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), std::string(param.name) + "<i32>");
 }
 
 TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_U32) {
     auto param = GetParam();
 
-    auto* t = ty.multisampled_texture(param.dim, ty.u32());
-    Alias("make_type_reachable", t);
+    auto t = ty.multisampled_texture(param.dim, ty.u32());
+    auto type = Alias("make_type_reachable", t)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), std::string(param.name) + "<u32>");
 }
 INSTANTIATE_TEST_SUITE_P(WgslGeneratorImplTest,
@@ -466,13 +452,13 @@
 TEST_P(WgslGenerator_StorageTextureTest, EmitType_StorageTexture) {
     auto param = GetParam();
 
-    auto* t = ty.storage_texture(param.dim, param.fmt, param.access);
-    GlobalVar("g", t, Binding(1_a), Group(2_a));
+    auto s = ty.storage_texture(param.dim, param.fmt, param.access);
+    auto type = GlobalVar("g", s, Binding(1_a), Group(2_a))->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, t)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), param.name);
 }
 INSTANTIATE_TEST_SUITE_P(
@@ -528,24 +514,24 @@
                     ImageFormatData{type::TexelFormat::kRgba32Float, "rgba32float"}));
 
 TEST_F(WgslGeneratorImplTest, EmitType_Sampler) {
-    auto* sampler = ty.sampler(type::SamplerKind::kSampler);
-    Alias("make_type_reachable", sampler);
+    auto sampler = ty.sampler(type::SamplerKind::kSampler);
+    auto type = Alias("make_type_reachable", sampler)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, sampler)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "sampler");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_SamplerComparison) {
-    auto* sampler = ty.sampler(type::SamplerKind::kComparisonSampler);
-    Alias("make_type_reachable", sampler);
+    auto sampler = ty.sampler(type::SamplerKind::kComparisonSampler);
+    auto type = Alias("make_type_reachable", sampler)->type;
 
     GeneratorImpl& gen = Build();
 
     std::stringstream out;
-    ASSERT_TRUE(gen.EmitType(out, sampler)) << gen.error();
+    ASSERT_TRUE(gen.EmitExpression(out, type)) << gen.error();
     EXPECT_EQ(out.str(), "sampler_comparison");
 }
 
diff --git a/src/tint/writer/wgsl/generator_impl_variable_decl_statement_test.cc b/src/tint/writer/wgsl/generator_impl_variable_decl_statement_test.cc
index 51b8683..bf68420 100644
--- a/src/tint/writer/wgsl/generator_impl_variable_decl_statement_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_variable_decl_statement_test.cc
@@ -169,7 +169,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_AInt) {
-    auto* C = Const("C", Call(ty.vec3(nullptr), 1_a, 2_a, 3_a));
+    auto* C = Const("C", Call(ty.vec3<Infer>(), 1_a, 2_a, 3_a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
@@ -188,7 +188,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_vec3_AFloat) {
-    auto* C = Const("C", Call(ty.vec3(nullptr), 1._a, 2._a, 3._a));
+    auto* C = Const("C", Call(ty.vec3<Infer>(), 1._a, 2._a, 3._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
@@ -249,7 +249,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_mat2x3_AFloat) {
-    auto* C = Const("C", Call(ty.mat(nullptr, 2, 3), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
+    auto* C = Const("C", Call(ty.mat2x3<Infer>(), 1._a, 2._a, 3._a, 4._a, 5._a, 6._a));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
@@ -310,7 +310,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, Emit_VariableDeclStatement_Const_arr_f32) {
-    auto* C = Const("C", Call(ty.array<f32, 3>(), 1_f, 2_f, 3_f));
+    auto* C = Const("C", array<f32, 3>(1_f, 2_f, 3_f));
     Func("f", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(C),
diff --git a/test/tint/array/assign_to_storage_var.wgsl.expected.dxc.hlsl b/test/tint/array/assign_to_storage_var.wgsl.expected.dxc.hlsl
index 8529fd0..2ce38dc 100644
--- a/test/tint/array/assign_to_storage_var.wgsl.expected.dxc.hlsl
+++ b/test/tint/array/assign_to_storage_var.wgsl.expected.dxc.hlsl
@@ -28,10 +28,10 @@
 }
 
 void tint_symbol_3(RWByteAddressBuffer buffer, uint offset, int4 value[4]) {
-  int4 array[4] = value;
+  int4 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      buffer.Store4((offset + (i * 16u)), asuint(array[i]));
+      buffer.Store4((offset + (i * 16u)), asuint(array_1[i]));
     }
   }
 }
@@ -60,28 +60,28 @@
 }
 
 void tint_symbol_11(RWByteAddressBuffer buffer, uint offset, int value[2]) {
-  int array_3[2] = value;
+  int array_4[2] = value;
   {
     for(uint i_3 = 0u; (i_3 < 2u); i_3 = (i_3 + 1u)) {
-      buffer.Store((offset + (i_3 * 4u)), asuint(array_3[i_3]));
+      buffer.Store((offset + (i_3 * 4u)), asuint(array_4[i_3]));
     }
   }
 }
 
 void tint_symbol_10(RWByteAddressBuffer buffer, uint offset, int value[3][2]) {
-  int array_2[3][2] = value;
+  int array_3[3][2] = value;
   {
     for(uint i_4 = 0u; (i_4 < 3u); i_4 = (i_4 + 1u)) {
-      tint_symbol_11(buffer, (offset + (i_4 * 8u)), array_2[i_4]);
+      tint_symbol_11(buffer, (offset + (i_4 * 8u)), array_3[i_4]);
     }
   }
 }
 
 void tint_symbol_9(RWByteAddressBuffer buffer, uint offset, int value[4][3][2]) {
-  int array_1[4][3][2] = value;
+  int array_2[4][3][2] = value;
   {
     for(uint i_5 = 0u; (i_5 < 4u); i_5 = (i_5 + 1u)) {
-      tint_symbol_10(buffer, (offset + (i_5 * 24u)), array_1[i_5]);
+      tint_symbol_10(buffer, (offset + (i_5 * 24u)), array_2[i_5]);
     }
   }
 }
diff --git a/test/tint/array/assign_to_storage_var.wgsl.expected.fxc.hlsl b/test/tint/array/assign_to_storage_var.wgsl.expected.fxc.hlsl
index 8529fd0..2ce38dc 100644
--- a/test/tint/array/assign_to_storage_var.wgsl.expected.fxc.hlsl
+++ b/test/tint/array/assign_to_storage_var.wgsl.expected.fxc.hlsl
@@ -28,10 +28,10 @@
 }
 
 void tint_symbol_3(RWByteAddressBuffer buffer, uint offset, int4 value[4]) {
-  int4 array[4] = value;
+  int4 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      buffer.Store4((offset + (i * 16u)), asuint(array[i]));
+      buffer.Store4((offset + (i * 16u)), asuint(array_1[i]));
     }
   }
 }
@@ -60,28 +60,28 @@
 }
 
 void tint_symbol_11(RWByteAddressBuffer buffer, uint offset, int value[2]) {
-  int array_3[2] = value;
+  int array_4[2] = value;
   {
     for(uint i_3 = 0u; (i_3 < 2u); i_3 = (i_3 + 1u)) {
-      buffer.Store((offset + (i_3 * 4u)), asuint(array_3[i_3]));
+      buffer.Store((offset + (i_3 * 4u)), asuint(array_4[i_3]));
     }
   }
 }
 
 void tint_symbol_10(RWByteAddressBuffer buffer, uint offset, int value[3][2]) {
-  int array_2[3][2] = value;
+  int array_3[3][2] = value;
   {
     for(uint i_4 = 0u; (i_4 < 3u); i_4 = (i_4 + 1u)) {
-      tint_symbol_11(buffer, (offset + (i_4 * 8u)), array_2[i_4]);
+      tint_symbol_11(buffer, (offset + (i_4 * 8u)), array_3[i_4]);
     }
   }
 }
 
 void tint_symbol_9(RWByteAddressBuffer buffer, uint offset, int value[4][3][2]) {
-  int array_1[4][3][2] = value;
+  int array_2[4][3][2] = value;
   {
     for(uint i_5 = 0u; (i_5 < 4u); i_5 = (i_5 + 1u)) {
-      tint_symbol_10(buffer, (offset + (i_5 * 24u)), array_1[i_5]);
+      tint_symbol_10(buffer, (offset + (i_5 * 24u)), array_2[i_5]);
     }
   }
 }
diff --git a/test/tint/array/strides.spvasm.expected.dxc.hlsl b/test/tint/array/strides.spvasm.expected.dxc.hlsl
index ddd8ded4..72a5a23 100644
--- a/test/tint/array/strides.spvasm.expected.dxc.hlsl
+++ b/test/tint/array/strides.spvasm.expected.dxc.hlsl
@@ -55,19 +55,19 @@
 }
 
 void tint_symbol_9(RWByteAddressBuffer buffer, uint offset, strided_arr value[2]) {
-  strided_arr array_2[2] = value;
+  strided_arr array_3[2] = value;
   {
     for(uint i_3 = 0u; (i_3 < 2u); i_3 = (i_3 + 1u)) {
-      tint_symbol_10(buffer, (offset + (i_3 * 8u)), array_2[i_3]);
+      tint_symbol_10(buffer, (offset + (i_3 * 8u)), array_3[i_3]);
     }
   }
 }
 
 void tint_symbol_8(RWByteAddressBuffer buffer, uint offset, strided_arr value[3][2]) {
-  strided_arr array_1[3][2] = value;
+  strided_arr array_2[3][2] = value;
   {
     for(uint i_4 = 0u; (i_4 < 3u); i_4 = (i_4 + 1u)) {
-      tint_symbol_9(buffer, (offset + (i_4 * 16u)), array_1[i_4]);
+      tint_symbol_9(buffer, (offset + (i_4 * 16u)), array_2[i_4]);
     }
   }
 }
@@ -77,10 +77,10 @@
 }
 
 void tint_symbol_6(RWByteAddressBuffer buffer, uint offset, strided_arr_1 value[4]) {
-  strided_arr_1 array[4] = value;
+  strided_arr_1 array_1[4] = value;
   {
     for(uint i_5 = 0u; (i_5 < 4u); i_5 = (i_5 + 1u)) {
-      tint_symbol_7(buffer, (offset + (i_5 * 128u)), array[i_5]);
+      tint_symbol_7(buffer, (offset + (i_5 * 128u)), array_1[i_5]);
     }
   }
 }
diff --git a/test/tint/array/strides.spvasm.expected.fxc.hlsl b/test/tint/array/strides.spvasm.expected.fxc.hlsl
index ddd8ded4..72a5a23 100644
--- a/test/tint/array/strides.spvasm.expected.fxc.hlsl
+++ b/test/tint/array/strides.spvasm.expected.fxc.hlsl
@@ -55,19 +55,19 @@
 }
 
 void tint_symbol_9(RWByteAddressBuffer buffer, uint offset, strided_arr value[2]) {
-  strided_arr array_2[2] = value;
+  strided_arr array_3[2] = value;
   {
     for(uint i_3 = 0u; (i_3 < 2u); i_3 = (i_3 + 1u)) {
-      tint_symbol_10(buffer, (offset + (i_3 * 8u)), array_2[i_3]);
+      tint_symbol_10(buffer, (offset + (i_3 * 8u)), array_3[i_3]);
     }
   }
 }
 
 void tint_symbol_8(RWByteAddressBuffer buffer, uint offset, strided_arr value[3][2]) {
-  strided_arr array_1[3][2] = value;
+  strided_arr array_2[3][2] = value;
   {
     for(uint i_4 = 0u; (i_4 < 3u); i_4 = (i_4 + 1u)) {
-      tint_symbol_9(buffer, (offset + (i_4 * 16u)), array_1[i_4]);
+      tint_symbol_9(buffer, (offset + (i_4 * 16u)), array_2[i_4]);
     }
   }
 }
@@ -77,10 +77,10 @@
 }
 
 void tint_symbol_6(RWByteAddressBuffer buffer, uint offset, strided_arr_1 value[4]) {
-  strided_arr_1 array[4] = value;
+  strided_arr_1 array_1[4] = value;
   {
     for(uint i_5 = 0u; (i_5 < 4u); i_5 = (i_5 + 1u)) {
-      tint_symbol_7(buffer, (offset + (i_5 * 128u)), array[i_5]);
+      tint_symbol_7(buffer, (offset + (i_5 * 128u)), array_1[i_5]);
     }
   }
 }
diff --git a/test/tint/buffer/storage/dynamic_index/write.wgsl.expected.dxc.hlsl b/test/tint/buffer/storage/dynamic_index/write.wgsl.expected.dxc.hlsl
index 4bcc56d..508e269 100644
--- a/test/tint/buffer/storage/dynamic_index/write.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/storage/dynamic_index/write.wgsl.expected.dxc.hlsl
@@ -59,10 +59,10 @@
 }
 
 void tint_symbol_23(RWByteAddressBuffer buffer, uint offset, float3 value[2]) {
-  float3 array[2] = value;
+  float3 array_1[2] = value;
   {
     for(uint i = 0u; (i < 2u); i = (i + 1u)) {
-      buffer.Store3((offset + (i * 16u)), asuint(array[i]));
+      buffer.Store3((offset + (i * 16u)), asuint(array_1[i]));
     }
   }
 }
diff --git a/test/tint/buffer/storage/dynamic_index/write.wgsl.expected.fxc.hlsl b/test/tint/buffer/storage/dynamic_index/write.wgsl.expected.fxc.hlsl
index 4bcc56d..508e269 100644
--- a/test/tint/buffer/storage/dynamic_index/write.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/storage/dynamic_index/write.wgsl.expected.fxc.hlsl
@@ -59,10 +59,10 @@
 }
 
 void tint_symbol_23(RWByteAddressBuffer buffer, uint offset, float3 value[2]) {
-  float3 array[2] = value;
+  float3 array_1[2] = value;
   {
     for(uint i = 0u; (i < 2u); i = (i + 1u)) {
-      buffer.Store3((offset + (i * 16u)), asuint(array[i]));
+      buffer.Store3((offset + (i * 16u)), asuint(array_1[i]));
     }
   }
 }
diff --git a/test/tint/buffer/storage/dynamic_index/write_f16.wgsl.expected.dxc.hlsl b/test/tint/buffer/storage/dynamic_index/write_f16.wgsl.expected.dxc.hlsl
index fe9d9f0..2688819 100644
--- a/test/tint/buffer/storage/dynamic_index/write_f16.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/storage/dynamic_index/write_f16.wgsl.expected.dxc.hlsl
@@ -113,19 +113,19 @@
 }
 
 void tint_symbol_36(RWByteAddressBuffer buffer, uint offset, float3 value[2]) {
-  float3 array[2] = value;
+  float3 array_1[2] = value;
   {
     for(uint i = 0u; (i < 2u); i = (i + 1u)) {
-      buffer.Store3((offset + (i * 16u)), asuint(array[i]));
+      buffer.Store3((offset + (i * 16u)), asuint(array_1[i]));
     }
   }
 }
 
 void tint_symbol_37(RWByteAddressBuffer buffer, uint offset, matrix<float16_t, 4, 2> value[2]) {
-  matrix<float16_t, 4, 2> array_1[2] = value;
+  matrix<float16_t, 4, 2> array_2[2] = value;
   {
     for(uint i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-      tint_symbol_33(buffer, (offset + (i_1 * 16u)), array_1[i_1]);
+      tint_symbol_33(buffer, (offset + (i_1 * 16u)), array_2[i_1]);
     }
   }
 }
diff --git a/test/tint/buffer/storage/static_index/write.wgsl.expected.dxc.hlsl b/test/tint/buffer/storage/static_index/write.wgsl.expected.dxc.hlsl
index 1fcb362..9bf75ff 100644
--- a/test/tint/buffer/storage/static_index/write.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/storage/static_index/write.wgsl.expected.dxc.hlsl
@@ -60,10 +60,10 @@
 }
 
 void tint_symbol_21(RWByteAddressBuffer buffer, uint offset, float3 value[2]) {
-  float3 array[2] = value;
+  float3 array_1[2] = value;
   {
     for(uint i = 0u; (i < 2u); i = (i + 1u)) {
-      buffer.Store3((offset + (i * 16u)), asuint(array[i]));
+      buffer.Store3((offset + (i * 16u)), asuint(array_1[i]));
     }
   }
 }
@@ -74,10 +74,10 @@
 }
 
 void tint_symbol_23(RWByteAddressBuffer buffer, uint offset, Inner value[4]) {
-  Inner array_1[4] = value;
+  Inner array_2[4] = value;
   {
     for(uint i_1 = 0u; (i_1 < 4u); i_1 = (i_1 + 1u)) {
-      tint_symbol_22(buffer, (offset + (i_1 * 8u)), array_1[i_1]);
+      tint_symbol_22(buffer, (offset + (i_1 * 8u)), array_2[i_1]);
     }
   }
 }
diff --git a/test/tint/buffer/storage/static_index/write.wgsl.expected.fxc.hlsl b/test/tint/buffer/storage/static_index/write.wgsl.expected.fxc.hlsl
index 1fcb362..9bf75ff 100644
--- a/test/tint/buffer/storage/static_index/write.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/storage/static_index/write.wgsl.expected.fxc.hlsl
@@ -60,10 +60,10 @@
 }
 
 void tint_symbol_21(RWByteAddressBuffer buffer, uint offset, float3 value[2]) {
-  float3 array[2] = value;
+  float3 array_1[2] = value;
   {
     for(uint i = 0u; (i < 2u); i = (i + 1u)) {
-      buffer.Store3((offset + (i * 16u)), asuint(array[i]));
+      buffer.Store3((offset + (i * 16u)), asuint(array_1[i]));
     }
   }
 }
@@ -74,10 +74,10 @@
 }
 
 void tint_symbol_23(RWByteAddressBuffer buffer, uint offset, Inner value[4]) {
-  Inner array_1[4] = value;
+  Inner array_2[4] = value;
   {
     for(uint i_1 = 0u; (i_1 < 4u); i_1 = (i_1 + 1u)) {
-      tint_symbol_22(buffer, (offset + (i_1 * 8u)), array_1[i_1]);
+      tint_symbol_22(buffer, (offset + (i_1 * 8u)), array_2[i_1]);
     }
   }
 }
diff --git a/test/tint/buffer/storage/static_index/write_f16.wgsl.expected.dxc.hlsl b/test/tint/buffer/storage/static_index/write_f16.wgsl.expected.dxc.hlsl
index 526b6f5..121700a 100644
--- a/test/tint/buffer/storage/static_index/write_f16.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/storage/static_index/write_f16.wgsl.expected.dxc.hlsl
@@ -115,19 +115,19 @@
 }
 
 void tint_symbol_34(RWByteAddressBuffer buffer, uint offset, float3 value[2]) {
-  float3 array[2] = value;
+  float3 array_1[2] = value;
   {
     for(uint i = 0u; (i < 2u); i = (i + 1u)) {
-      buffer.Store3((offset + (i * 16u)), asuint(array[i]));
+      buffer.Store3((offset + (i * 16u)), asuint(array_1[i]));
     }
   }
 }
 
 void tint_symbol_35(RWByteAddressBuffer buffer, uint offset, matrix<float16_t, 4, 2> value[2]) {
-  matrix<float16_t, 4, 2> array_1[2] = value;
+  matrix<float16_t, 4, 2> array_2[2] = value;
   {
     for(uint i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-      tint_symbol_31(buffer, (offset + (i_1 * 16u)), array_1[i_1]);
+      tint_symbol_31(buffer, (offset + (i_1 * 16u)), array_2[i_1]);
     }
   }
 }
@@ -139,10 +139,10 @@
 }
 
 void tint_symbol_37(RWByteAddressBuffer buffer, uint offset, Inner value[4]) {
-  Inner array_2[4] = value;
+  Inner array_3[4] = value;
   {
     for(uint i_2 = 0u; (i_2 < 4u); i_2 = (i_2 + 1u)) {
-      tint_symbol_36(buffer, (offset + (i_2 * 12u)), array_2[i_2]);
+      tint_symbol_36(buffer, (offset + (i_2 * 12u)), array_3[i_2]);
     }
   }
 }
diff --git a/test/tint/buffer/storage/types/array4_f16.wgsl.expected.dxc.hlsl b/test/tint/buffer/storage/types/array4_f16.wgsl.expected.dxc.hlsl
index 15fa0ab..ac93313 100644
--- a/test/tint/buffer/storage/types/array4_f16.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/storage/types/array4_f16.wgsl.expected.dxc.hlsl
@@ -2,10 +2,10 @@
 RWByteAddressBuffer tint_symbol_1 : register(u1, space0);
 
 void tint_symbol_2(RWByteAddressBuffer buffer, uint offset, float16_t value[4]) {
-  float16_t array[4] = value;
+  float16_t array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      buffer.Store<float16_t>((offset + (i * 2u)), array[i]);
+      buffer.Store<float16_t>((offset + (i * 2u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/storage/types/array4_f32.wgsl.expected.dxc.hlsl b/test/tint/buffer/storage/types/array4_f32.wgsl.expected.dxc.hlsl
index efaf35e..b1f25b2 100644
--- a/test/tint/buffer/storage/types/array4_f32.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/storage/types/array4_f32.wgsl.expected.dxc.hlsl
@@ -2,10 +2,10 @@
 RWByteAddressBuffer tint_symbol_1 : register(u1, space0);
 
 void tint_symbol_2(RWByteAddressBuffer buffer, uint offset, float value[4]) {
-  float array[4] = value;
+  float array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      buffer.Store((offset + (i * 4u)), asuint(array[i]));
+      buffer.Store((offset + (i * 4u)), asuint(array_1[i]));
     }
   }
 }
diff --git a/test/tint/buffer/storage/types/array4_f32.wgsl.expected.fxc.hlsl b/test/tint/buffer/storage/types/array4_f32.wgsl.expected.fxc.hlsl
index efaf35e..b1f25b2 100644
--- a/test/tint/buffer/storage/types/array4_f32.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/storage/types/array4_f32.wgsl.expected.fxc.hlsl
@@ -2,10 +2,10 @@
 RWByteAddressBuffer tint_symbol_1 : register(u1, space0);
 
 void tint_symbol_2(RWByteAddressBuffer buffer, uint offset, float value[4]) {
-  float array[4] = value;
+  float array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      buffer.Store((offset + (i * 4u)), asuint(array[i]));
+      buffer.Store((offset + (i * 4u)), asuint(array_1[i]));
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat2x2_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/array/mat2x2_f32/to_storage.wgsl.expected.dxc.hlsl
index b5a3774..280d978 100644
--- a/test/tint/buffer/uniform/std140/array/mat2x2_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat2x2_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -9,10 +9,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float2x2 value[4]) {
-  float2x2 array[4] = value;
+  float2x2 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 16u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 16u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat2x2_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/array/mat2x2_f32/to_storage.wgsl.expected.fxc.hlsl
index b5a3774..280d978 100644
--- a/test/tint/buffer/uniform/std140/array/mat2x2_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat2x2_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -9,10 +9,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float2x2 value[4]) {
-  float2x2 array[4] = value;
+  float2x2 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 16u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 16u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat2x3_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/array/mat2x3_f16/to_storage.wgsl.expected.dxc.hlsl
index 26f9723..b406594 100644
--- a/test/tint/buffer/uniform/std140/array/mat2x3_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat2x3_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -9,10 +9,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, matrix<float16_t, 2, 3> value[4]) {
-  matrix<float16_t, 2, 3> array[4] = value;
+  matrix<float16_t, 2, 3> array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 16u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 16u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat2x3_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/array/mat2x3_f32/to_storage.wgsl.expected.dxc.hlsl
index 9d1bbf8..93021a5 100644
--- a/test/tint/buffer/uniform/std140/array/mat2x3_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat2x3_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -9,10 +9,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float2x3 value[4]) {
-  float2x3 array[4] = value;
+  float2x3 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 32u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 32u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat2x3_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/array/mat2x3_f32/to_storage.wgsl.expected.fxc.hlsl
index 9d1bbf8..93021a5 100644
--- a/test/tint/buffer/uniform/std140/array/mat2x3_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat2x3_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -9,10 +9,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float2x3 value[4]) {
-  float2x3 array[4] = value;
+  float2x3 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 32u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 32u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat2x4_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/array/mat2x4_f16/to_storage.wgsl.expected.dxc.hlsl
index d519b2e..28c564e 100644
--- a/test/tint/buffer/uniform/std140/array/mat2x4_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat2x4_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -9,10 +9,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, matrix<float16_t, 2, 4> value[4]) {
-  matrix<float16_t, 2, 4> array[4] = value;
+  matrix<float16_t, 2, 4> array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 16u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 16u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat2x4_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/array/mat2x4_f32/to_storage.wgsl.expected.dxc.hlsl
index 747c20a..5b44d2d 100644
--- a/test/tint/buffer/uniform/std140/array/mat2x4_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat2x4_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -9,10 +9,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float2x4 value[4]) {
-  float2x4 array[4] = value;
+  float2x4 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 32u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 32u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat2x4_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/array/mat2x4_f32/to_storage.wgsl.expected.fxc.hlsl
index 747c20a..5b44d2d 100644
--- a/test/tint/buffer/uniform/std140/array/mat2x4_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat2x4_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -9,10 +9,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float2x4 value[4]) {
-  float2x4 array[4] = value;
+  float2x4 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 32u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 32u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat3x3_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/array/mat3x3_f32/to_storage.wgsl.expected.dxc.hlsl
index 6dc59a8..563ec2e 100644
--- a/test/tint/buffer/uniform/std140/array/mat3x3_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat3x3_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -10,10 +10,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float3x3 value[4]) {
-  float3x3 array[4] = value;
+  float3x3 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 48u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 48u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat3x3_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/array/mat3x3_f32/to_storage.wgsl.expected.fxc.hlsl
index 6dc59a8..563ec2e 100644
--- a/test/tint/buffer/uniform/std140/array/mat3x3_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat3x3_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -10,10 +10,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float3x3 value[4]) {
-  float3x3 array[4] = value;
+  float3x3 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 48u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 48u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat3x4_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/array/mat3x4_f32/to_storage.wgsl.expected.dxc.hlsl
index b82dc9c..b6ecb13 100644
--- a/test/tint/buffer/uniform/std140/array/mat3x4_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat3x4_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -10,10 +10,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float3x4 value[4]) {
-  float3x4 array[4] = value;
+  float3x4 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 48u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 48u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat3x4_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/array/mat3x4_f32/to_storage.wgsl.expected.fxc.hlsl
index b82dc9c..b6ecb13 100644
--- a/test/tint/buffer/uniform/std140/array/mat3x4_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat3x4_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -10,10 +10,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float3x4 value[4]) {
-  float3x4 array[4] = value;
+  float3x4 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 48u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 48u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat4x2_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/array/mat4x2_f16/to_storage.wgsl.expected.dxc.hlsl
index 8097133..4580024 100644
--- a/test/tint/buffer/uniform/std140/array/mat4x2_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat4x2_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -11,10 +11,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, matrix<float16_t, 4, 2> value[4]) {
-  matrix<float16_t, 4, 2> array[4] = value;
+  matrix<float16_t, 4, 2> array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 16u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 16u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat4x2_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/array/mat4x2_f32/to_storage.wgsl.expected.dxc.hlsl
index f264391..22f1ffe 100644
--- a/test/tint/buffer/uniform/std140/array/mat4x2_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat4x2_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -11,10 +11,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float4x2 value[4]) {
-  float4x2 array[4] = value;
+  float4x2 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 32u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 32u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat4x2_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/array/mat4x2_f32/to_storage.wgsl.expected.fxc.hlsl
index f264391..22f1ffe 100644
--- a/test/tint/buffer/uniform/std140/array/mat4x2_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat4x2_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -11,10 +11,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float4x2 value[4]) {
-  float4x2 array[4] = value;
+  float4x2 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 32u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 32u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat4x3_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/array/mat4x3_f16/to_storage.wgsl.expected.dxc.hlsl
index a101585..8039a70 100644
--- a/test/tint/buffer/uniform/std140/array/mat4x3_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat4x3_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -11,10 +11,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, matrix<float16_t, 4, 3> value[4]) {
-  matrix<float16_t, 4, 3> array[4] = value;
+  matrix<float16_t, 4, 3> array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 32u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 32u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat4x3_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/array/mat4x3_f32/to_storage.wgsl.expected.dxc.hlsl
index befc57d..6d83315 100644
--- a/test/tint/buffer/uniform/std140/array/mat4x3_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat4x3_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -11,10 +11,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float4x3 value[4]) {
-  float4x3 array[4] = value;
+  float4x3 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 64u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 64u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat4x3_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/array/mat4x3_f32/to_storage.wgsl.expected.fxc.hlsl
index befc57d..6d83315 100644
--- a/test/tint/buffer/uniform/std140/array/mat4x3_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat4x3_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -11,10 +11,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float4x3 value[4]) {
-  float4x3 array[4] = value;
+  float4x3 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 64u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 64u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat4x4_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/array/mat4x4_f16/to_storage.wgsl.expected.dxc.hlsl
index 8a3aa03..9e8d1ec 100644
--- a/test/tint/buffer/uniform/std140/array/mat4x4_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat4x4_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -11,10 +11,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, matrix<float16_t, 4, 4> value[4]) {
-  matrix<float16_t, 4, 4> array[4] = value;
+  matrix<float16_t, 4, 4> array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 32u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 32u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat4x4_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/array/mat4x4_f32/to_storage.wgsl.expected.dxc.hlsl
index 9fd7977..fce4827 100644
--- a/test/tint/buffer/uniform/std140/array/mat4x4_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat4x4_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -11,10 +11,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float4x4 value[4]) {
-  float4x4 array[4] = value;
+  float4x4 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 64u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 64u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/array/mat4x4_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/array/mat4x4_f32/to_storage.wgsl.expected.fxc.hlsl
index 9fd7977..fce4827 100644
--- a/test/tint/buffer/uniform/std140/array/mat4x4_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/array/mat4x4_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -11,10 +11,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, float4x4 value[4]) {
-  float4x4 array[4] = value;
+  float4x4 array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 64u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 64u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat2x2_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat2x2_f16/to_storage.wgsl.expected.dxc.hlsl
index e217232..c2951f1 100644
--- a/test/tint/buffer/uniform/std140/struct/mat2x2_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat2x2_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -21,10 +21,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat2x2_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat2x2_f32/to_storage.wgsl.expected.dxc.hlsl
index bfcca34..de4e979 100644
--- a/test/tint/buffer/uniform/std140/struct/mat2x2_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat2x2_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -21,10 +21,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat2x2_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat2x2_f32/to_storage.wgsl.expected.fxc.hlsl
index bfcca34..de4e979 100644
--- a/test/tint/buffer/uniform/std140/struct/mat2x2_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat2x2_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -21,10 +21,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat2x3_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat2x3_f16/to_storage.wgsl.expected.dxc.hlsl
index ba15d73..6f680d2 100644
--- a/test/tint/buffer/uniform/std140/struct/mat2x3_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat2x3_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -21,10 +21,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat2x3_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat2x3_f32/to_storage.wgsl.expected.dxc.hlsl
index 89eabee..257e965 100644
--- a/test/tint/buffer/uniform/std140/struct/mat2x3_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat2x3_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -21,10 +21,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat2x3_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat2x3_f32/to_storage.wgsl.expected.fxc.hlsl
index 89eabee..257e965 100644
--- a/test/tint/buffer/uniform/std140/struct/mat2x3_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat2x3_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -21,10 +21,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat2x4_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat2x4_f16/to_storage.wgsl.expected.dxc.hlsl
index 10a3807..daa5fd7 100644
--- a/test/tint/buffer/uniform/std140/struct/mat2x4_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat2x4_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -21,10 +21,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat2x4_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat2x4_f32/to_storage.wgsl.expected.dxc.hlsl
index 2146713..425d179 100644
--- a/test/tint/buffer/uniform/std140/struct/mat2x4_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat2x4_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -21,10 +21,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat2x4_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat2x4_f32/to_storage.wgsl.expected.fxc.hlsl
index 2146713..425d179 100644
--- a/test/tint/buffer/uniform/std140/struct/mat2x4_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat2x4_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -21,10 +21,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat3x2_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat3x2_f16/to_storage.wgsl.expected.dxc.hlsl
index 0a5e75f..2a90973 100644
--- a/test/tint/buffer/uniform/std140/struct/mat3x2_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat3x2_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -22,10 +22,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat3x2_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat3x2_f32/to_storage.wgsl.expected.dxc.hlsl
index 2869d03..50eb6e7 100644
--- a/test/tint/buffer/uniform/std140/struct/mat3x2_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat3x2_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -22,10 +22,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat3x2_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat3x2_f32/to_storage.wgsl.expected.fxc.hlsl
index 2869d03..50eb6e7 100644
--- a/test/tint/buffer/uniform/std140/struct/mat3x2_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat3x2_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -22,10 +22,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat3x3_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat3x3_f16/to_storage.wgsl.expected.dxc.hlsl
index 403b78d..35a1a17 100644
--- a/test/tint/buffer/uniform/std140/struct/mat3x3_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat3x3_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -22,10 +22,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat3x3_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat3x3_f32/to_storage.wgsl.expected.dxc.hlsl
index 51fb0e8..c27c393 100644
--- a/test/tint/buffer/uniform/std140/struct/mat3x3_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat3x3_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -22,10 +22,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat3x3_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat3x3_f32/to_storage.wgsl.expected.fxc.hlsl
index 51fb0e8..c27c393 100644
--- a/test/tint/buffer/uniform/std140/struct/mat3x3_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat3x3_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -22,10 +22,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat3x4_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat3x4_f16/to_storage.wgsl.expected.dxc.hlsl
index c236690..994f4fc 100644
--- a/test/tint/buffer/uniform/std140/struct/mat3x4_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat3x4_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -22,10 +22,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat3x4_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat3x4_f32/to_storage.wgsl.expected.dxc.hlsl
index 629f8ff..ce77d82 100644
--- a/test/tint/buffer/uniform/std140/struct/mat3x4_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat3x4_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -22,10 +22,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat3x4_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat3x4_f32/to_storage.wgsl.expected.fxc.hlsl
index 629f8ff..ce77d82 100644
--- a/test/tint/buffer/uniform/std140/struct/mat3x4_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat3x4_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -22,10 +22,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat4x2_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat4x2_f16/to_storage.wgsl.expected.dxc.hlsl
index 180ae47..651a533 100644
--- a/test/tint/buffer/uniform/std140/struct/mat4x2_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat4x2_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -23,10 +23,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat4x2_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat4x2_f32/to_storage.wgsl.expected.dxc.hlsl
index 5dc514f..bfc91ab 100644
--- a/test/tint/buffer/uniform/std140/struct/mat4x2_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat4x2_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -23,10 +23,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat4x2_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat4x2_f32/to_storage.wgsl.expected.fxc.hlsl
index 5dc514f..bfc91ab 100644
--- a/test/tint/buffer/uniform/std140/struct/mat4x2_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat4x2_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -23,10 +23,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat4x3_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat4x3_f16/to_storage.wgsl.expected.dxc.hlsl
index 5b9dfa8..f4e10ac 100644
--- a/test/tint/buffer/uniform/std140/struct/mat4x3_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat4x3_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -23,10 +23,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat4x3_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat4x3_f32/to_storage.wgsl.expected.dxc.hlsl
index f910578..9f86bd8 100644
--- a/test/tint/buffer/uniform/std140/struct/mat4x3_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat4x3_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -23,10 +23,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 192u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 192u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat4x3_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat4x3_f32/to_storage.wgsl.expected.fxc.hlsl
index f910578..9f86bd8 100644
--- a/test/tint/buffer/uniform/std140/struct/mat4x3_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat4x3_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -23,10 +23,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 192u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 192u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat4x4_f16/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat4x4_f16/to_storage.wgsl.expected.dxc.hlsl
index 212b921..4ec616b 100644
--- a/test/tint/buffer/uniform/std140/struct/mat4x4_f16/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat4x4_f16/to_storage.wgsl.expected.dxc.hlsl
@@ -23,10 +23,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 128u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 128u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat4x4_f32/to_storage.wgsl.expected.dxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat4x4_f32/to_storage.wgsl.expected.dxc.hlsl
index cc21007..9f194ab 100644
--- a/test/tint/buffer/uniform/std140/struct/mat4x4_f32/to_storage.wgsl.expected.dxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat4x4_f32/to_storage.wgsl.expected.dxc.hlsl
@@ -23,10 +23,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 192u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 192u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/buffer/uniform/std140/struct/mat4x4_f32/to_storage.wgsl.expected.fxc.hlsl b/test/tint/buffer/uniform/std140/struct/mat4x4_f32/to_storage.wgsl.expected.fxc.hlsl
index cc21007..9f194ab 100644
--- a/test/tint/buffer/uniform/std140/struct/mat4x4_f32/to_storage.wgsl.expected.fxc.hlsl
+++ b/test/tint/buffer/uniform/std140/struct/mat4x4_f32/to_storage.wgsl.expected.fxc.hlsl
@@ -23,10 +23,10 @@
 }
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, S value[4]) {
-  S array[4] = value;
+  S array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      tint_symbol_1(buffer, (offset + (i * 192u)), array[i]);
+      tint_symbol_1(buffer, (offset + (i * 192u)), array_1[i]);
     }
   }
 }
diff --git a/test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.dxc.hlsl b/test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.dxc.hlsl
index dd1dcb6..0c20131 100644
--- a/test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.dxc.hlsl
+++ b/test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.dxc.hlsl
@@ -37,10 +37,10 @@
 }
 
 void tint_symbol_4(RWByteAddressBuffer buffer, uint offset, strided_arr value[2]) {
-  strided_arr array[2] = value;
+  strided_arr array_1[2] = value;
   {
     for(uint i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-      tint_symbol_5(buffer, (offset + (i_1 * 16u)), array[i_1]);
+      tint_symbol_5(buffer, (offset + (i_1 * 16u)), array_1[i_1]);
     }
   }
 }
diff --git a/test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.fxc.hlsl b/test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.fxc.hlsl
index dd1dcb6..0c20131 100644
--- a/test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.fxc.hlsl
+++ b/test/tint/layout/storage/mat2x2/stride/16.spvasm.expected.fxc.hlsl
@@ -37,10 +37,10 @@
 }
 
 void tint_symbol_4(RWByteAddressBuffer buffer, uint offset, strided_arr value[2]) {
-  strided_arr array[2] = value;
+  strided_arr array_1[2] = value;
   {
     for(uint i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
-      tint_symbol_5(buffer, (offset + (i_1 * 16u)), array[i_1]);
+      tint_symbol_5(buffer, (offset + (i_1 * 16u)), array_1[i_1]);
     }
   }
 }
diff --git a/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.dxc.hlsl
index 30148d5..517d789 100644
--- a/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.dxc.hlsl
+++ b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.dxc.hlsl
@@ -1,10 +1,10 @@
 RWByteAddressBuffer S : register(u0, space0);
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, int value[4]) {
-  int array[4] = value;
+  int array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      buffer.Store((offset + (i * 4u)), asuint(array[i]));
+      buffer.Store((offset + (i * 4u)), asuint(array_1[i]));
     }
   }
 }
diff --git a/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.fxc.hlsl
index 30148d5..517d789 100644
--- a/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.fxc.hlsl
+++ b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.fxc.hlsl
@@ -1,10 +1,10 @@
 RWByteAddressBuffer S : register(u0, space0);
 
 void tint_symbol(RWByteAddressBuffer buffer, uint offset, int value[4]) {
-  int array[4] = value;
+  int array_1[4] = value;
   {
     for(uint i = 0u; (i < 4u); i = (i + 1u)) {
-      buffer.Store((offset + (i * 4u)), asuint(array[i]));
+      buffer.Store((offset + (i * 4u)), asuint(array_1[i]));
     }
   }
 }