Import Tint changes from Dawn

Changes:
  - 5f764d8527a44ba6657fd99e55a1930553efe3a1 Move type base classes into type/ folder. by dan sinclair <dsinclair@chromium.org>
  - 7017ec264ca31610c68b3bfcbcff7c8d86da9f62 tint: Implement signed-int overloads of sign() by Ben Clayton <bclayton@google.com>
  - d839931a377c04dd877b8ac2c74f7d9f5c09e2c7 tint/resolver: Clean up const eval builtin case functions by Ben Clayton <bclayton@google.com>
  - 6c337aa18ec88556be6c94f5f47275318d1c1e86 tint/sem: Rename [un]signed Type helper methods by Ben Clayton <bclayton@google.com>
  - 4c8f5a1ac2a0224605bdcf7deeb7aabb6be7fb52 tint: const eval of logical AND and OR by Antonio Maiorano <amaiorano@google.com>
GitOrigin-RevId: 5f764d8527a44ba6657fd99e55a1930553efe3a1
Change-Id: I251e9490a197f9241af0ff6949e073e2e30d7a37
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/113380
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index cbcd804..8889605 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -458,7 +458,6 @@
     "sem/storage_texture.h",
     "sem/switch_statement.h",
     "sem/texture.h",
-    "sem/type.h",
     "sem/type_conversion.h",
     "sem/type_initializer.h",
     "sem/type_manager.h",
@@ -577,6 +576,9 @@
     "transform/while_to_loop.h",
     "transform/zero_init_workgroup_memory.cc",
     "transform/zero_init_workgroup_memory.h",
+    "type/array_count.h",
+    "type/node.h",
+    "type/type.h",
     "utils/bitcast.h",
     "utils/bitset.h",
     "utils/block_allocator.h",
@@ -716,8 +718,6 @@
     "sem/switch_statement.h",
     "sem/texture.cc",
     "sem/texture.h",
-    "sem/type.cc",
-    "sem/type.h",
     "sem/type_conversion.cc",
     "sem/type_conversion.h",
     "sem/type_initializer.cc",
@@ -739,10 +739,24 @@
   public_deps = [ ":libtint_core_all_src" ]
 }
 
+libtint_source_set("libtint_type_src") {
+  sources = [
+    "type/array_count.cc",
+    "type/array_count.h",
+    "type/node.cc",
+    "type/node.h",
+    "type/type.cc",
+    "type/type.h",
+  ]
+
+  public_deps = [ ":libtint_core_all_src" ]
+}
+
 libtint_source_set("libtint_core_src") {
   public_deps = [
     ":libtint_core_all_src",
     ":libtint_sem_src",
+    ":libtint_type_src",
   ]
 }
 
@@ -1209,12 +1223,15 @@
       "sem/struct_test.cc",
       "sem/texture_test.cc",
       "sem/type_manager_test.cc",
-      "sem/type_test.cc",
       "sem/u32_test.cc",
       "sem/vector_test.cc",
     ]
   }
 
+  tint_unittests_source_set("tint_unittests_type_src") {
+    sources = [ "type/type_test.cc" ]
+  }
+
   tint_unittests_source_set("tint_unittests_text_src") {
     sources = [ "text/unicode_test.cc" ]
   }
@@ -1690,6 +1707,7 @@
       ":tint_unittests_sem_src",
       ":tint_unittests_text_src",
       ":tint_unittests_transform_src",
+      ":tint_unittests_type_src",
       ":tint_unittests_utils_src",
       ":tint_unittests_writer_src",
     ]
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 9156cb0..a22ab9a 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -382,8 +382,6 @@
   sem/type_manager.cc
   sem/type_manager.h
   sem/type_mappings.h
-  sem/type.cc
-  sem/type.h
   sem/u32.cc
   sem/u32.h
   sem/variable.cc
@@ -501,6 +499,12 @@
   transform/while_to_loop.h
   transform/zero_init_workgroup_memory.cc
   transform/zero_init_workgroup_memory.h
+  type/array_count.cc
+  type/array_count.h
+  type/node.cc
+  type/node.h
+  type/type.cc
+  type/type.h
   utils/bitcast.h
   utils/bitset.h
   utils/block_allocator.h
@@ -937,7 +941,6 @@
     sem/struct_test.cc
     sem/texture_test.cc
     sem/type_manager_test.cc
-    sem/type_test.cc
     sem/u32_test.cc
     sem/vector_test.cc
     source_test.cc
@@ -947,6 +950,7 @@
     text/unicode_test.cc
     traits_test.cc
     transform/transform_test.cc
+    type/type_test.cc
     utils/bitcast_test.cc
     utils/bitset_test.cc
     utils/block_allocator_test.cc
diff --git a/src/tint/ast/expression.h b/src/tint/ast/expression.h
index 8e0718b..d851cb1 100644
--- a/src/tint/ast/expression.h
+++ b/src/tint/ast/expression.h
@@ -19,7 +19,6 @@
 #include <vector>
 
 #include "src/tint/ast/node.h"
-#include "src/tint/sem/type.h"
 
 namespace tint::ast {
 
diff --git a/src/tint/ast/module_clone_test.cc b/src/tint/ast/module_clone_test.cc
index a79ef0e..09dced5 100644
--- a/src/tint/ast/module_clone_test.cc
+++ b/src/tint/ast/module_clone_test.cc
@@ -136,7 +136,7 @@
     for (auto* src_node : src.ASTNodes().Objects()) {
         src_nodes.emplace(src_node);
     }
-    std::unordered_set<const sem::Type*> src_types;
+    std::unordered_set<const type::Type*> src_types;
     for (auto* src_type : src.Types()) {
         src_types.emplace(src_type);
     }
diff --git a/src/tint/clone_context.h b/src/tint/clone_context.h
index f1db617..508932a 100644
--- a/src/tint/clone_context.h
+++ b/src/tint/clone_context.h
@@ -93,14 +93,14 @@
     /// Destructor
     ~CloneContext();
 
-    /// Clones the Node or sem::Type `a` into the ProgramBuilder #dst if `a` is
+    /// Clones the Node or type::Type `a` into the ProgramBuilder #dst if `a` is
     /// not null. If `a` is null, then Clone() returns null.
     ///
     /// Clone() may use a function registered with ReplaceAll() to create a
     /// transformed version of the object. See ReplaceAll() for more information.
     ///
     /// If the CloneContext is cloning from a Program to a ProgramBuilder, then
-    /// the Node or sem::Type `a` must be owned by the Program #src.
+    /// the Node or type::Type `a` must be owned by the Program #src.
     ///
     /// @param object the type deriving from Cloneable to clone
     /// @return the cloned node
@@ -117,14 +117,14 @@
         return nullptr;
     }
 
-    /// Clones the Node or sem::Type `a` into the ProgramBuilder #dst if `a` is
+    /// Clones the Node or type::Type `a` into the ProgramBuilder #dst if `a` is
     /// not null. If `a` is null, then Clone() returns null.
     ///
     /// Unlike Clone(), this method does not invoke or use any transformations
     /// registered by ReplaceAll().
     ///
     /// If the CloneContext is cloning from a Program to a ProgramBuilder, then
-    /// the Node or sem::Type `a` must be owned by the Program #src.
+    /// the Node or type::Type `a` must be owned by the Program #src.
     ///
     /// @param a the type deriving from Cloneable to clone
     /// @return the cloned node
diff --git a/src/tint/diagnostic/diagnostic.h b/src/tint/diagnostic/diagnostic.h
index 3ea8a0d..f66cb62 100644
--- a/src/tint/diagnostic/diagnostic.h
+++ b/src/tint/diagnostic/diagnostic.h
@@ -47,6 +47,7 @@
     Symbol,
     Test,
     Transform,
+    Type,
     Utils,
     Writer,
 };
diff --git a/src/tint/fuzzers/tint_ast_clone_fuzzer.cc b/src/tint/fuzzers/tint_ast_clone_fuzzer.cc
index 33db8e2..c2a47cd 100644
--- a/src/tint/fuzzers/tint_ast_clone_fuzzer.cc
+++ b/src/tint/fuzzers/tint_ast_clone_fuzzer.cc
@@ -75,7 +75,7 @@
     for (auto* src_node : src.ASTNodes().Objects()) {
         src_nodes.emplace(src_node);
     }
-    std::unordered_set<const tint::sem::Type*> src_types;
+    std::unordered_set<const tint::type::Type*> src_types;
     for (auto* src_type : src.Types()) {
         src_types.emplace(src_type);
     }
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.cc
index c8dff2c..16e0ab3 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.cc
@@ -46,7 +46,7 @@
             type->Is<sem::Reference>() ? type->As<sem::Reference>()->StoreType() : type;
 
         // Only signed integer or vector of signed integer can be mutated.
-        if (!basic_type->is_signed_scalar_or_vector()) {
+        if (!basic_type->is_signed_integer_scalar_or_vector()) {
             continue;
         }
 
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc
index 6e78cb5..15af58d 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc
@@ -24,7 +24,7 @@
 
 namespace {
 
-bool IsSuitableForShift(const sem::Type* lhs_type, const sem::Type* rhs_type) {
+bool IsSuitableForShift(const type::Type* lhs_type, const type::Type* rhs_type) {
     // `a << b` requires b to be an unsigned scalar or vector, and `a` to be an
     // integer scalar or vector with the same width as `b`. Similar for `a >> b`.
 
@@ -37,8 +37,8 @@
     return false;
 }
 
-bool CanReplaceAddSubtractWith(const sem::Type* lhs_type,
-                               const sem::Type* rhs_type,
+bool CanReplaceAddSubtractWith(const type::Type* lhs_type,
+                               const type::Type* rhs_type,
                                ast::BinaryOp new_operator) {
     // The program is assumed to be well-typed, so this method determines when
     // 'new_operator' can be used as a type-preserving replacement in an '+' or
@@ -71,8 +71,8 @@
     }
 }
 
-bool CanReplaceMultiplyWith(const sem::Type* lhs_type,
-                            const sem::Type* rhs_type,
+bool CanReplaceMultiplyWith(const type::Type* lhs_type,
+                            const type::Type* rhs_type,
                             ast::BinaryOp new_operator) {
     // The program is assumed to be well-typed, so this method determines when
     // 'new_operator' can be used as a type-preserving replacement in a '*'
@@ -107,8 +107,8 @@
     }
 }
 
-bool CanReplaceDivideOrModuloWith(const sem::Type* lhs_type,
-                                  const sem::Type* rhs_type,
+bool CanReplaceDivideOrModuloWith(const type::Type* lhs_type,
+                                  const type::Type* rhs_type,
                                   ast::BinaryOp new_operator) {
     // The program is assumed to be well-typed, so this method determines when
     // 'new_operator' can be used as a type-preserving replacement in a '/'
@@ -149,8 +149,8 @@
     }
 }
 
-bool CanReplaceAndOrWith(const sem::Type* lhs_type,
-                         const sem::Type* rhs_type,
+bool CanReplaceAndOrWith(const type::Type* lhs_type,
+                         const type::Type* rhs_type,
                          ast::BinaryOp new_operator) {
     switch (new_operator) {
         case ast::BinaryOp::kAnd:
@@ -184,8 +184,8 @@
     }
 }
 
-bool CanReplaceXorWith(const sem::Type* lhs_type,
-                       const sem::Type* rhs_type,
+bool CanReplaceXorWith(const type::Type* lhs_type,
+                       const type::Type* rhs_type,
                        ast::BinaryOp new_operator) {
     switch (new_operator) {
         case ast::BinaryOp::kAdd:
@@ -207,8 +207,8 @@
     }
 }
 
-bool CanReplaceShiftLeftShiftRightWith(const sem::Type* lhs_type,
-                                       const sem::Type* rhs_type,
+bool CanReplaceShiftLeftShiftRightWith(const type::Type* lhs_type,
+                                       const type::Type* rhs_type,
                                        ast::BinaryOp new_operator) {
     switch (new_operator) {
         case ast::BinaryOp::kShiftLeft:
@@ -232,7 +232,7 @@
     }
 }
 
-bool CanReplaceEqualNotEqualWith(const sem::Type* lhs_type, ast::BinaryOp new_operator) {
+bool CanReplaceEqualNotEqualWith(const type::Type* lhs_type, ast::BinaryOp new_operator) {
     switch (new_operator) {
         case ast::BinaryOp::kEqual:
         case ast::BinaryOp::kNotEqual:
@@ -301,9 +301,9 @@
     const auto* rhs_type = program.Sem().Get(binary_expr.rhs)->Type();
 
     // If these are reference types, unwrap them to get the pointee type.
-    const sem::Type* lhs_basic_type =
+    const type::Type* lhs_basic_type =
         lhs_type->Is<sem::Reference>() ? lhs_type->As<sem::Reference>()->StoreType() : lhs_type;
-    const sem::Type* rhs_basic_type =
+    const type::Type* rhs_basic_type =
         rhs_type->Is<sem::Reference>() ? rhs_type->As<sem::Reference>()->StoreType() : rhs_type;
 
     switch (binary_expr.op) {
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator.cc
index d32d569..c9bed06 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator.cc
@@ -52,7 +52,7 @@
 
     // Only signed integer or vector of signed integer has more than 1
     // unary operators to change between.
-    if (!basic_type->is_signed_scalar_or_vector()) {
+    if (!basic_type->is_signed_integer_scalar_or_vector()) {
         return false;
     }
 
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc
index d6612f5..499a23f 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc
@@ -100,12 +100,12 @@
         return {ast::UnaryOp::kNot};
     }
 
-    if (expr_type->is_signed_scalar_or_vector() ||
+    if (expr_type->is_signed_integer_scalar_or_vector() ||
         expr_type->is_abstract_integer_scalar_or_vector()) {
         return {ast::UnaryOp::kNegation, ast::UnaryOp::kComplement};
     }
 
-    if (expr_type->is_unsigned_scalar_or_vector()) {
+    if (expr_type->is_unsigned_integer_scalar_or_vector()) {
         return {ast::UnaryOp::kComplement};
     }
 
diff --git a/src/tint/inspector/inspector.cc b/src/tint/inspector/inspector.cc
index b893ed1..303f4b6 100644
--- a/src/tint/inspector/inspector.cc
+++ b/src/tint/inspector/inspector.cc
@@ -68,12 +68,13 @@
     dest->insert(dest->end(), orig.begin(), orig.end());
 }
 
-std::tuple<ComponentType, CompositionType> CalculateComponentAndComposition(const sem::Type* type) {
+std::tuple<ComponentType, CompositionType> CalculateComponentAndComposition(
+    const type::Type* type) {
     // entry point in/out variables must of numeric scalar or vector types.
     TINT_ASSERT(Inspector, type->is_numeric_scalar_or_vector());
 
     ComponentType componentType = Switch(
-        sem::Type::DeepestElementOf(type),  //
+        type::Type::DeepestElementOf(type),  //
         [&](const sem::F32*) { return ComponentType::kF32; },
         [&](const sem::F16*) { return ComponentType::kF16; },
         [&](const sem::I32*) { return ComponentType::kI32; },
@@ -114,7 +115,7 @@
 }
 
 std::tuple<InterpolationType, InterpolationSampling> CalculateInterpolationData(
-    const sem::Type* type,
+    const type::Type* type,
     utils::VectorRef<const ast::Attribute*> attributes) {
     auto* interpolation_attribute = ast::GetAttribute<ast::InterpolateAttribute>(attributes);
     if (type->is_integer_scalar_or_vector()) {
@@ -641,7 +642,7 @@
 }
 
 void Inspector::AddEntryPointInOutVariables(std::string name,
-                                            const sem::Type* type,
+                                            const type::Type* type,
                                             utils::VectorRef<const ast::Attribute*> attributes,
                                             std::optional<uint32_t> location,
                                             std::vector<StageVariable>& variables) const {
@@ -680,7 +681,7 @@
 }
 
 bool Inspector::ContainsBuiltin(ast::BuiltinValue builtin,
-                                const sem::Type* type,
+                                const type::Type* type,
                                 utils::VectorRef<const ast::Attribute*> attributes) const {
     auto* unwrapped_type = type->UnwrapRef();
 
@@ -768,7 +769,7 @@
         auto* texture_type = var->Type()->UnwrapRef()->As<sem::Texture>();
         entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(texture_type->dim());
 
-        const sem::Type* base_type = nullptr;
+        const type::Type* base_type = nullptr;
         if (multisampled_only) {
             base_type = texture_type->As<sem::MultisampledTexture>()->type();
         } else {
diff --git a/src/tint/inspector/inspector.h b/src/tint/inspector/inspector.h
index 62a1c73..b16fadf 100644
--- a/src/tint/inspector/inspector.h
+++ b/src/tint/inspector/inspector.h
@@ -175,7 +175,7 @@
     /// @param location the location value if provided
     /// @param variables the list to add the variables to
     void AddEntryPointInOutVariables(std::string name,
-                                     const sem::Type* type,
+                                     const type::Type* type,
                                      utils::VectorRef<const ast::Attribute*> attributes,
                                      std::optional<uint32_t> location,
                                      std::vector<StageVariable>& variables) const;
@@ -184,7 +184,7 @@
     /// If `type` is a struct, recurse into members to check for the attribute.
     /// Otherwise, check `attributes` for the attribute.
     bool ContainsBuiltin(ast::BuiltinValue builtin,
-                         const sem::Type* type,
+                         const type::Type* type,
                          utils::VectorRef<const ast::Attribute*> attributes) const;
 
     /// Gathers all the texture resource bindings of the given type for the given
diff --git a/src/tint/inspector/resource_binding.cc b/src/tint/inspector/resource_binding.cc
index a0e89e2..225e739 100644
--- a/src/tint/inspector/resource_binding.cc
+++ b/src/tint/inspector/resource_binding.cc
@@ -18,9 +18,9 @@
 #include "src/tint/sem/f32.h"
 #include "src/tint/sem/i32.h"
 #include "src/tint/sem/matrix.h"
-#include "src/tint/sem/type.h"
 #include "src/tint/sem/u32.h"
 #include "src/tint/sem/vector.h"
+#include "src/tint/type/type.h"
 
 namespace tint::inspector {
 
@@ -45,7 +45,7 @@
     return ResourceBinding::TextureDimension::kNone;
 }
 
-ResourceBinding::SampledKind BaseTypeToSampledKind(const sem::Type* base_type) {
+ResourceBinding::SampledKind BaseTypeToSampledKind(const type::Type* base_type) {
     if (!base_type) {
         return ResourceBinding::SampledKind::kUnknown;
     }
diff --git a/src/tint/inspector/resource_binding.h b/src/tint/inspector/resource_binding.h
index 6adab5a..76b5024 100644
--- a/src/tint/inspector/resource_binding.h
+++ b/src/tint/inspector/resource_binding.h
@@ -19,11 +19,7 @@
 
 #include "src/tint/ast/storage_texture.h"
 #include "src/tint/ast/texture.h"
-
-// Forward declarations
-namespace tint::sem {
-class Type;
-}  // namespace tint::sem
+#include "src/tint/type/type.h"
 
 namespace tint::inspector {
 
@@ -114,10 +110,10 @@
 ResourceBinding::TextureDimension TypeTextureDimensionToResourceBindingTextureDimension(
     const ast::TextureDimension& type_dim);
 
-/// Infer ResourceBinding::SampledKind for a given sem::Type
+/// Infer ResourceBinding::SampledKind for a given type::Type
 /// @param base_type internal type to infer from
 /// @returns the publicly visible equivalent
-ResourceBinding::SampledKind BaseTypeToSampledKind(const sem::Type* base_type);
+ResourceBinding::SampledKind BaseTypeToSampledKind(const type::Type* base_type);
 
 /// Convert from internal ast::TexelFormat to public
 /// ResourceBinding::TexelFormat
diff --git a/src/tint/intrinsics.def b/src/tint/intrinsics.def
index d9188ed..52a30ea 100644
--- a/src/tint/intrinsics.def
+++ b/src/tint/intrinsics.def
@@ -51,7 +51,7 @@
   chromium_disable_uniformity_analysis
   // A Chromium-specific extension for push constants
   chromium_experimental_push_constant
-  // A Chromium-specific extension that enables passing of uniform, storage and workgroup 
+  // A Chromium-specific extension that enables passing of uniform, storage and workgroup
   // address-spaced pointers as parameters, as well as pointers into sub-objects.
   chromium_experimental_full_ptr_parameters
 }
@@ -548,8 +548,8 @@
 @const("select_bool") fn select<T: scalar>(T, T, bool) -> T
 @const("select_bool") fn select<T: scalar, N: num>(vec<N, T>, vec<N, T>, bool) -> vec<N, T>
 @const("select_boolvec") fn select<N: num, T: scalar>(vec<N, T>, vec<N, T>, vec<N, bool>) -> vec<N, T>
-@const fn sign<T: fa_f32_f16>(T) -> T
-@const fn sign<N: num, T: fa_f32_f16>(vec<N, T>) -> vec<N, T>
+@const fn sign<T: fia_fi32_f16>(T) -> T
+@const fn sign<N: num, T: fia_fi32_f16>(vec<N, T>) -> vec<N, T>
 @const fn sin<T: fa_f32_f16>(T) -> T
 @const fn sin<N: num, T: fa_f32_f16>(vec<N, T>) -> vec<N, T>
 @const fn sinh<T: fa_f32_f16>(T) -> T
@@ -969,8 +969,8 @@
 @const op | <T: ia_iu32>(T, T) -> T
 @const op | <T: ia_iu32, N: num> (vec<N, T>, vec<N, T>) -> vec<N, T>
 
-op && (bool, bool) -> bool
-op || (bool, bool) -> bool
+@const op && (bool, bool) -> bool
+@const op || (bool, bool) -> bool
 
 @const op == <T: scalar>(T, T) -> bool
 @const op == <T: scalar, N: num> (vec<N, T>, vec<N, T>) -> vec<N, bool>
diff --git a/src/tint/program.cc b/src/tint/program.cc
index e4caca1..5003caa 100644
--- a/src/tint/program.cc
+++ b/src/tint/program.cc
@@ -117,16 +117,16 @@
     return is_valid_;
 }
 
-const sem::Type* Program::TypeOf(const ast::Expression* expr) const {
+const type::Type* Program::TypeOf(const ast::Expression* expr) const {
     auto* sem = Sem().Get(expr);
     return sem ? sem->Type() : nullptr;
 }
 
-const sem::Type* Program::TypeOf(const ast::Type* type) const {
+const type::Type* Program::TypeOf(const ast::Type* type) const {
     return Sem().Get(type);
 }
 
-const sem::Type* Program::TypeOf(const ast::TypeDecl* type_decl) const {
+const type::Type* Program::TypeOf(const ast::TypeDecl* type_decl) const {
     return Sem().Get(type_decl);
 }
 
@@ -135,7 +135,7 @@
     return type ? type->FriendlyName(Symbols()) : "<null>";
 }
 
-std::string Program::FriendlyName(const sem::Type* type) const {
+std::string Program::FriendlyName(const type::Type* type) const {
     return type ? type->FriendlyName(Symbols()) : "<null>";
 }
 
diff --git a/src/tint/program.h b/src/tint/program.h
index 3390920..b1a9576 100644
--- a/src/tint/program.h
+++ b/src/tint/program.h
@@ -136,20 +136,20 @@
     /// @param expr the AST expression
     /// @return the resolved semantic type for the expression, or nullptr if the
     /// expression has no resolved type.
-    const sem::Type* TypeOf(const ast::Expression* expr) const;
+    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 sem::Type* TypeOf(const ast::Type* type) const;
+    const type::Type* TypeOf(const ast::Type* type) const;
 
     /// Helper for returning the resolved semantic type of the AST type
     /// declaration `type_decl`.
     /// @param type_decl the AST type declaration
     /// @return the resolved semantic type for the type declaration, or nullptr if
     /// the type declaration has no resolved type.
-    const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const;
+    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
@@ -159,7 +159,7 @@
     /// @param type a type
     /// @returns the name for `type` that closely resembles how it would be
     /// declared in WGSL.
-    std::string FriendlyName(const sem::Type* type) const;
+    std::string FriendlyName(const type::Type* type) const;
 
     /// Overload of FriendlyName, which removes an ambiguity when passing nullptr.
     /// Simplifies test code.
diff --git a/src/tint/program_builder.cc b/src/tint/program_builder.cc
index a45d805..02f9a13 100644
--- a/src/tint/program_builder.cc
+++ b/src/tint/program_builder.cc
@@ -95,21 +95,21 @@
     }
 }
 
-const sem::Type* ProgramBuilder::TypeOf(const ast::Expression* expr) const {
+const type::Type* ProgramBuilder::TypeOf(const ast::Expression* expr) const {
     auto* sem = Sem().Get(expr);
     return sem ? sem->Type() : nullptr;
 }
 
-const sem::Type* ProgramBuilder::TypeOf(const ast::Variable* var) const {
+const type::Type* ProgramBuilder::TypeOf(const ast::Variable* var) const {
     auto* sem = Sem().Get(var);
     return sem ? sem->Type() : nullptr;
 }
 
-const sem::Type* ProgramBuilder::TypeOf(const ast::Type* type) const {
+const type::Type* ProgramBuilder::TypeOf(const ast::Type* type) const {
     return Sem().Get(type);
 }
 
-const sem::Type* ProgramBuilder::TypeOf(const ast::TypeDecl* type_decl) const {
+const type::Type* ProgramBuilder::TypeOf(const ast::TypeDecl* type_decl) const {
     return Sem().Get(type_decl);
 }
 
@@ -118,7 +118,7 @@
     return type ? type->FriendlyName(Symbols()) : "<null>";
 }
 
-std::string ProgramBuilder::FriendlyName(const sem::Type* type) const {
+std::string ProgramBuilder::FriendlyName(const type::Type* type) const {
     return type ? type->FriendlyName(Symbols()) : "<null>";
 }
 
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index 4677009..e8b27d4 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -458,8 +458,7 @@
     /// @returns the node pointer
     template <typename T, typename... ARGS>
     traits::EnableIf<traits::IsTypeOrDerived<T, sem::Node> &&
-                         !traits::IsTypeOrDerived<T, sem::Type> &&
-                         !traits::IsTypeOrDerived<T, sem::ArrayCount>,
+                         !traits::IsTypeOrDerived<T, type::Node>,
                      T>*
     create(ARGS&&... args) {
         AssertNotMoved();
@@ -476,7 +475,7 @@
         return constant_nodes_.Create<T>(std::forward<ARGS>(args)...);
     }
 
-    /// Creates a new sem::Type owned by the ProgramBuilder.
+    /// Creates a new type::Type owned by the ProgramBuilder.
     /// When the ProgramBuilder is destructed, owned ProgramBuilder and the
     /// returned `Type` will also be destructed.
     /// Types are unique (de-aliased), and so calling create() for the same `T`
@@ -484,11 +483,12 @@
     /// @param args the arguments to pass to the type constructor
     /// @returns the de-aliased type pointer
     template <typename T, typename... ARGS>
-    traits::EnableIfIsType<T, sem::Type>* create(ARGS&&... args) {
+    traits::EnableIfIsType<T, type::Type>* create(ARGS&&... args) {
         AssertNotMoved();
         return types_.Get<T>(std::forward<ARGS>(args)...);
     }
-    /// Creates a new sem::ArrayCount owned by the ProgramBuilder.
+
+    /// Creates a new type::ArrayCount owned by the ProgramBuilder.
     /// When the ProgramBuilder is destructed, owned ProgramBuilder and the
     /// returned `ArrayCount` will also be destructed.
     /// ArrayCounts are unique (de-aliased), and so calling create() for the same `T`
@@ -496,9 +496,12 @@
     /// @param args the arguments to pass to the array count constructor
     /// @returns the de-aliased array count pointer
     template <typename T, typename... ARGS>
-    traits::EnableIfIsType<T, sem::ArrayCount>* create(ARGS&&... args) {
+    traits::EnableIf<traits::IsTypeOrDerived<T, type::ArrayCount> ||
+                         traits::IsTypeOrDerived<T, sem::StructMemberBase>,
+                     T>*
+    create(ARGS&&... args) {
         AssertNotMoved();
-        return types_.GetArrayCount<T>(std::forward<ARGS>(args)...);
+        return types_.GetNode<T>(std::forward<ARGS>(args)...);
     }
 
     /// Marks this builder as moved, preventing any further use of the builder.
@@ -3159,7 +3162,7 @@
     /// @param expr the AST expression
     /// @return the resolved semantic type for the expression, or nullptr if the
     /// expression has no resolved type.
-    const sem::Type* TypeOf(const ast::Expression* expr) const;
+    const type::Type* TypeOf(const ast::Expression* expr) const;
 
     /// Helper for returning the resolved semantic type of the variable `var`.
     /// @note As the Resolver is run when the Program is built, this will only be
@@ -3167,7 +3170,7 @@
     /// @param var the AST variable
     /// @return the resolved semantic type for the variable, or nullptr if the
     /// variable has no resolved type.
-    const sem::Type* TypeOf(const ast::Variable* var) const;
+    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
@@ -3175,7 +3178,7 @@
     /// @param type the AST type
     /// @return the resolved semantic type for the type, or nullptr if the type
     /// has no resolved type.
-    const sem::Type* TypeOf(const ast::Type* type) const;
+    const type::Type* TypeOf(const ast::Type* type) const;
 
     /// Helper for returning the resolved semantic type of the AST type
     /// declaration `type_decl`.
@@ -3184,7 +3187,7 @@
     /// @param type_decl the AST type declaration
     /// @return the resolved semantic type for the type declaration, or nullptr if
     /// the type declaration has no resolved type.
-    const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const;
+    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
@@ -3194,7 +3197,7 @@
     /// @param type a type
     /// @returns the name for `type` that closely resembles how it would be
     /// declared in WGSL.
-    std::string FriendlyName(const sem::Type* type) const;
+    std::string FriendlyName(const type::Type* type) const;
 
     /// Overload of FriendlyName, which removes an ambiguity when passing nullptr.
     /// Simplifies test code.
diff --git a/src/tint/resolver/builtin_test.cc b/src/tint/resolver/builtin_test.cc
index 4b33064..c5cd274 100644
--- a/src/tint/resolver/builtin_test.cc
+++ b/src/tint/resolver/builtin_test.cc
@@ -34,8 +34,8 @@
 #include "src/tint/sem/member_accessor_expression.h"
 #include "src/tint/sem/sampled_texture.h"
 #include "src/tint/sem/statement.h"
-#include "src/tint/sem/test_helper.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/type/test_helper.h"
 
 using ::testing::ElementsAre;
 using ::testing::HasSubstr;
diff --git a/src/tint/resolver/const_eval.cc b/src/tint/resolver/const_eval.cc
index ce38b1d..14841fe 100644
--- a/src/tint/resolver/const_eval.cc
+++ b/src/tint/resolver/const_eval.cc
@@ -147,14 +147,14 @@
 }
 
 /// ZeroTypeDispatch is a helper for calling the function `f`, passing a single zero-value argument
-/// of the C++ type that corresponds to the sem::Type `type`. For example, calling
+/// of the C++ type that corresponds to the type::Type `type`. For example, calling
 /// `ZeroTypeDispatch()` with a type of `sem::I32*` will call the function f with a single argument
 /// of `i32(0)`.
 /// @returns the value returned by calling `f`.
 /// @note `type` must be a scalar or abstract numeric type. Other types will not call `f`, and will
 /// return the zero-initialized value of the return type for `f`.
 template <typename F>
-auto ZeroTypeDispatch(const sem::Type* type, F&& f) {
+auto ZeroTypeDispatch(const type::Type* type, F&& f) {
     return Switch(
         type,                                                     //
         [&](const sem::AbstractInt*) { return f(AInt(0)); },      //
@@ -250,7 +250,7 @@
     /// Convert attempts to convert the constant value to the given type. On error, Convert()
     /// creates a new diagnostic message and returns a Failure.
     virtual utils::Result<const ImplConstant*> Convert(ProgramBuilder& builder,
-                                                       const sem::Type* target_ty,
+                                                       const type::Type* target_ty,
                                                        const Source& source) const = 0;
 };
 
@@ -259,7 +259,7 @@
 
 // Forward declaration
 const ImplConstant* CreateComposite(ProgramBuilder& builder,
-                                    const sem::Type* type,
+                                    const type::Type* type,
                                     utils::VectorRef<const sem::Constant*> elements);
 
 /// Element holds a single scalar or abstract-numeric value.
@@ -269,13 +269,13 @@
     static_assert(!std::is_same_v<UnwrapNumber<T>, T> || std::is_same_v<T, bool>,
                   "T must be a Number or bool");
 
-    Element(const sem::Type* t, T v) : type(t), value(v) {
+    Element(const type::Type* t, T v) : type(t), value(v) {
         if constexpr (IsFloatingPoint<T>) {
             TINT_ASSERT(Resolver, std::isfinite(v.value));
         }
     }
     ~Element() override = default;
-    const sem::Type* Type() const override { return type; }
+    const type::Type* Type() const override { return type; }
     std::variant<std::monostate, AInt, AFloat> Value() const override {
         if constexpr (IsFloatingPoint<UnwrapNumber<T>>) {
             return static_cast<AFloat>(value);
@@ -290,7 +290,7 @@
     size_t Hash() const override { return utils::Hash(type, ValueOf(value)); }
 
     ImplResult Convert(ProgramBuilder& builder,
-                       const sem::Type* target_ty,
+                       const type::Type* target_ty,
                        const Source& source) const override {
         TINT_BEGIN_DISABLE_WARNING(UNREACHABLE_CODE);
         if (target_ty == type) {
@@ -345,7 +345,7 @@
         TINT_END_DISABLE_WARNING(UNREACHABLE_CODE);
     }
 
-    sem::Type const* const type;
+    type::Type const* const type;
     const T value;
 };
 
@@ -354,9 +354,9 @@
 /// identical. Splat may be of a vector, matrix or array type.
 /// Splat implements the Constant interface.
 struct Splat : ImplConstant {
-    Splat(const sem::Type* t, const sem::Constant* e, size_t n) : type(t), el(e), count(n) {}
+    Splat(const type::Type* t, const sem::Constant* e, size_t n) : type(t), el(e), count(n) {}
     ~Splat() override = default;
-    const sem::Type* Type() const override { return type; }
+    const type::Type* Type() const override { return type; }
     std::variant<std::monostate, AInt, AFloat> Value() const override { return {}; }
     const sem::Constant* Index(size_t i) const override { return i < count ? el : nullptr; }
     bool AllZero() const override { return el->AllZero(); }
@@ -365,13 +365,13 @@
     size_t Hash() const override { return utils::Hash(type, el->Hash(), count); }
 
     ImplResult Convert(ProgramBuilder& builder,
-                       const sem::Type* target_ty,
+                       const type::Type* target_ty,
                        const Source& source) const override {
         // Convert the single splatted element type.
         // Note: This file is the only place where `sem::Constant`s are created, so this static_cast
         // is safe.
         auto conv_el = static_cast<const ImplConstant*>(el)->Convert(
-            builder, sem::Type::ElementOf(target_ty), source);
+            builder, type::Type::ElementOf(target_ty), source);
         if (!conv_el) {
             return utils::Failure;
         }
@@ -381,7 +381,7 @@
         return builder.create<Splat>(target_ty, conv_el.Get(), count);
     }
 
-    sem::Type const* const type;
+    type::Type const* const type;
     const sem::Constant* el;
     const size_t count;
 };
@@ -392,13 +392,13 @@
 /// implementation. Use CreateComposite() to create the appropriate Constant type.
 /// Composite implements the Constant interface.
 struct Composite : ImplConstant {
-    Composite(const sem::Type* t,
+    Composite(const type::Type* t,
               utils::VectorRef<const sem::Constant*> els,
               bool all_0,
               bool any_0)
         : type(t), elements(std::move(els)), all_zero(all_0), any_zero(any_0), hash(CalcHash()) {}
     ~Composite() override = default;
-    const sem::Type* Type() const override { return type; }
+    const type::Type* Type() const override { return type; }
     std::variant<std::monostate, AInt, AFloat> Value() const override { return {}; }
     const sem::Constant* Index(size_t i) const override {
         return i < elements.Length() ? elements[i] : nullptr;
@@ -409,12 +409,12 @@
     size_t Hash() const override { return hash; }
 
     ImplResult Convert(ProgramBuilder& builder,
-                       const sem::Type* target_ty,
+                       const type::Type* target_ty,
                        const Source& source) const override {
         // Convert each of the composite element types.
         utils::Vector<const sem::Constant*, 4> conv_els;
         conv_els.Reserve(elements.Length());
-        std::function<const sem::Type*(size_t idx)> target_el_ty;
+        std::function<const type::Type*(size_t idx)> target_el_ty;
         if (auto* str = target_ty->As<sem::Struct>()) {
             if (str->Members().Length() != elements.Length()) {
                 TINT_ICE(Resolver, builder.Diagnostics())
@@ -423,7 +423,7 @@
             }
             target_el_ty = [str](size_t idx) { return str->Members()[idx]->Type(); };
         } else {
-            auto* el_ty = sem::Type::ElementOf(target_ty);
+            auto* el_ty = type::Type::ElementOf(target_ty);
             target_el_ty = [el_ty](size_t) { return el_ty; };
         }
 
@@ -451,7 +451,7 @@
         return h;
     }
 
-    sem::Type const* const type;
+    type::Type const* const type;
     const utils::Vector<const sem::Constant*, 8> elements;
     const bool all_zero;
     const bool any_zero;
@@ -460,7 +460,7 @@
 
 /// CreateElement constructs and returns an Element<T>.
 template <typename T>
-ImplResult CreateElement(ProgramBuilder& builder, const Source& source, const sem::Type* t, T v) {
+ImplResult CreateElement(ProgramBuilder& builder, const Source& source, const type::Type* t, T v) {
     TINT_ASSERT(Resolver, t->is_scalar());
 
     if constexpr (IsFloatingPoint<T>) {
@@ -474,7 +474,7 @@
 }
 
 /// ZeroValue returns a Constant for the zero-value of the type `type`.
-const ImplConstant* ZeroValue(ProgramBuilder& builder, const sem::Type* type) {
+const ImplConstant* ZeroValue(ProgramBuilder& builder, const type::Type* type) {
     return Switch(
         type,  //
         [&](const sem::Vector* v) -> const ImplConstant* {
@@ -494,7 +494,7 @@
             return nullptr;
         },
         [&](const sem::Struct* s) -> const ImplConstant* {
-            utils::Hashmap<const sem::Type*, const ImplConstant*, 8> zero_by_type;
+            utils::Hashmap<const type::Type*, const ImplConstant*, 8> zero_by_type;
             utils::Vector<const sem::Constant*, 4> zeros;
             zeros.Reserve(s->Members().Length());
             for (auto* member : s->Members()) {
@@ -565,7 +565,7 @@
 /// CreateComposite examines the element values and will return either a Composite or a Splat,
 /// depending on the element types and values.
 const ImplConstant* CreateComposite(ProgramBuilder& builder,
-                                    const sem::Type* type,
+                                    const type::Type* type,
                                     utils::VectorRef<const sem::Constant*> elements) {
     if (elements.IsEmpty()) {
         return nullptr;
@@ -601,13 +601,13 @@
 /// Implementation of TransformElements
 template <typename F, typename... CONSTANTS>
 ImplResult TransformElements(ProgramBuilder& builder,
-                             const sem::Type* composite_ty,
+                             const type::Type* composite_ty,
                              F&& f,
                              size_t index,
                              CONSTANTS&&... cs) {
     uint32_t n = 0;
     auto* ty = First(cs...)->Type();
-    auto* el_ty = sem::Type::ElementOf(ty, &n);
+    auto* el_ty = type::Type::ElementOf(ty, &n);
     if (el_ty == ty) {
         constexpr bool kHasIndexParam = traits::IsType<size_t, traits::LastParameterType<F>>;
         if constexpr (kHasIndexParam) {
@@ -619,7 +619,7 @@
     utils::Vector<const sem::Constant*, 8> els;
     els.Reserve(n);
     for (uint32_t i = 0; i < n; i++) {
-        if (auto el = detail::TransformElements(builder, sem::Type::ElementOf(composite_ty),
+        if (auto el = detail::TransformElements(builder, type::Type::ElementOf(composite_ty),
                                                 std::forward<F>(f), index + i, cs->Index(i)...)) {
             els.Push(el.Get());
 
@@ -638,7 +638,7 @@
 /// the most deeply nested aggregate type will be passed in.
 template <typename F, typename... CONSTANTS>
 ImplResult TransformElements(ProgramBuilder& builder,
-                             const sem::Type* composite_ty,
+                             const type::Type* composite_ty,
                              F&& f,
                              CONSTANTS&&... cs) {
     return detail::TransformElements(builder, composite_ty, f, 0, cs...);
@@ -650,14 +650,14 @@
 /// vector-scalar, scalar-vector.
 template <typename F>
 ImplResult TransformBinaryElements(ProgramBuilder& builder,
-                                   const sem::Type* composite_ty,
+                                   const type::Type* composite_ty,
                                    F&& f,
                                    const sem::Constant* c0,
                                    const sem::Constant* c1) {
     uint32_t n0 = 0;
-    sem::Type::ElementOf(c0->Type(), &n0);
+    type::Type::ElementOf(c0->Type(), &n0);
     uint32_t n1 = 0;
-    sem::Type::ElementOf(c1->Type(), &n1);
+    type::Type::ElementOf(c1->Type(), &n1);
     uint32_t max_n = std::max(n0, n1);
     // If arity of both constants is 1, invoke callback
     if (max_n == 1u) {
@@ -673,7 +673,7 @@
             }
             return c->Index(i);
         };
-        if (auto el = TransformBinaryElements(builder, sem::Type::ElementOf(composite_ty),
+        if (auto el = TransformBinaryElements(builder, type::Type::ElementOf(composite_ty),
                                               std::forward<F>(f), nested_or_self(c0, n0),
                                               nested_or_self(c1, n1))) {
             els.Push(el.Get());
@@ -1082,7 +1082,7 @@
     return NumberT{std::sqrt(v)};
 }
 
-auto ConstEval::SqrtFunc(const Source& source, const sem::Type* elem_ty) {
+auto ConstEval::SqrtFunc(const Source& source, const type::Type* elem_ty) {
     return [=](auto v) -> ImplResult {
         if (auto r = Sqrt(source, v)) {
             return CreateElement(builder, source, elem_ty, r.Get());
@@ -1096,7 +1096,7 @@
     return NumberT{std::min(std::max(e, low), high)};
 }
 
-auto ConstEval::ClampFunc(const Source& source, const sem::Type* elem_ty) {
+auto ConstEval::ClampFunc(const Source& source, const type::Type* elem_ty) {
     return [=](auto e, auto low, auto high) -> ImplResult {
         if (auto r = Clamp(source, e, low, high)) {
             return CreateElement(builder, source, elem_ty, r.Get());
@@ -1105,7 +1105,7 @@
     };
 }
 
-auto ConstEval::AddFunc(const Source& source, const sem::Type* elem_ty) {
+auto ConstEval::AddFunc(const Source& source, const type::Type* elem_ty) {
     return [=](auto a1, auto a2) -> ImplResult {
         if (auto r = Add(source, a1, a2)) {
             return CreateElement(builder, source, elem_ty, r.Get());
@@ -1114,7 +1114,7 @@
     };
 }
 
-auto ConstEval::SubFunc(const Source& source, const sem::Type* elem_ty) {
+auto ConstEval::SubFunc(const Source& source, const type::Type* elem_ty) {
     return [=](auto a1, auto a2) -> ImplResult {
         if (auto r = Sub(source, a1, a2)) {
             return CreateElement(builder, source, elem_ty, r.Get());
@@ -1123,7 +1123,7 @@
     };
 }
 
-auto ConstEval::MulFunc(const Source& source, const sem::Type* elem_ty) {
+auto ConstEval::MulFunc(const Source& source, const type::Type* elem_ty) {
     return [=](auto a1, auto a2) -> ImplResult {
         if (auto r = Mul(source, a1, a2)) {
             return CreateElement(builder, source, elem_ty, r.Get());
@@ -1132,7 +1132,7 @@
     };
 }
 
-auto ConstEval::DivFunc(const Source& source, const sem::Type* elem_ty) {
+auto ConstEval::DivFunc(const Source& source, const type::Type* elem_ty) {
     return [=](auto a1, auto a2) -> ImplResult {
         if (auto r = Div(source, a1, a2)) {
             return CreateElement(builder, source, elem_ty, r.Get());
@@ -1141,7 +1141,7 @@
     };
 }
 
-auto ConstEval::ModFunc(const Source& source, const sem::Type* elem_ty) {
+auto ConstEval::ModFunc(const Source& source, const type::Type* elem_ty) {
     return [=](auto a1, auto a2) -> ImplResult {
         if (auto r = Mod(source, a1, a2)) {
             return CreateElement(builder, source, elem_ty, r.Get());
@@ -1150,7 +1150,7 @@
     };
 }
 
-auto ConstEval::Dot2Func(const Source& source, const sem::Type* elem_ty) {
+auto ConstEval::Dot2Func(const Source& source, const type::Type* elem_ty) {
     return [=](auto a1, auto a2, auto b1, auto b2) -> ImplResult {
         if (auto r = Dot2(source, a1, a2, b1, b2)) {
             return CreateElement(builder, source, elem_ty, r.Get());
@@ -1159,7 +1159,7 @@
     };
 }
 
-auto ConstEval::Dot3Func(const Source& source, const sem::Type* elem_ty) {
+auto ConstEval::Dot3Func(const Source& source, const type::Type* elem_ty) {
     return [=](auto a1, auto a2, auto a3, auto b1, auto b2, auto b3) -> ImplResult {
         if (auto r = Dot3(source, a1, a2, a3, b1, b2, b3)) {
             return CreateElement(builder, source, elem_ty, r.Get());
@@ -1168,7 +1168,7 @@
     };
 }
 
-auto ConstEval::Dot4Func(const Source& source, const sem::Type* elem_ty) {
+auto ConstEval::Dot4Func(const Source& source, const type::Type* elem_ty) {
     return
         [=](auto a1, auto a2, auto a3, auto a4, auto b1, auto b2, auto b3, auto b4) -> ImplResult {
             if (auto r = Dot4(source, a1, a2, a3, a4, b1, b2, b3, b4)) {
@@ -1206,7 +1206,7 @@
 }
 
 ConstEval::Result ConstEval::Length(const Source& source,
-                                    const sem::Type* ty,
+                                    const type::Type* ty,
                                     const sem::Constant* c0) {
     auto* vec_ty = c0->Type()->As<sem::Vector>();
     // Evaluates to the absolute value of e if T is scalar.
@@ -1227,7 +1227,7 @@
 }
 
 ConstEval::Result ConstEval::Mul(const Source& source,
-                                 const sem::Type* ty,
+                                 const type::Type* ty,
                                  const sem::Constant* v1,
                                  const sem::Constant* v2) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -1237,7 +1237,7 @@
 }
 
 ConstEval::Result ConstEval::Sub(const Source& source,
-                                 const sem::Type* ty,
+                                 const type::Type* ty,
                                  const sem::Constant* v1,
                                  const sem::Constant* v2) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -1246,7 +1246,7 @@
     return TransformBinaryElements(builder, ty, transform, v1, v2);
 }
 
-auto ConstEval::Det2Func(const Source& source, const sem::Type* elem_ty) {
+auto ConstEval::Det2Func(const Source& source, const type::Type* elem_ty) {
     return [=](auto a, auto b, auto c, auto d) -> ImplResult {
         if (auto r = Det2(source, a, b, c, d)) {
             return CreateElement(builder, source, elem_ty, r.Get());
@@ -1255,7 +1255,7 @@
     };
 }
 
-auto ConstEval::Det3Func(const Source& source, const sem::Type* elem_ty) {
+auto ConstEval::Det3Func(const Source& source, const type::Type* elem_ty) {
     return
         [=](auto a, auto b, auto c, auto d, auto e, auto f, auto g, auto h, auto i) -> ImplResult {
             if (auto r = Det3(source, a, b, c, d, e, f, g, h, i)) {
@@ -1265,7 +1265,7 @@
         };
 }
 
-auto ConstEval::Det4Func(const Source& source, const sem::Type* elem_ty) {
+auto ConstEval::Det4Func(const Source& source, const type::Type* elem_ty) {
     return [=](auto a, auto b, auto c, auto d, auto e, auto f, auto g, auto h, auto i, auto j,
                auto k, auto l, auto m, auto n, auto o, auto p) -> ImplResult {
         if (auto r = Det4(source, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)) {
@@ -1275,7 +1275,7 @@
     };
 }
 
-ConstEval::Result ConstEval::Literal(const sem::Type* ty, const ast::LiteralExpression* literal) {
+ConstEval::Result ConstEval::Literal(const type::Type* ty, const ast::LiteralExpression* literal) {
     auto& source = literal->source;
     return Switch(
         literal,
@@ -1306,7 +1306,7 @@
         });
 }
 
-ConstEval::Result ConstEval::ArrayOrStructInit(const sem::Type* ty,
+ConstEval::Result ConstEval::ArrayOrStructInit(const type::Type* ty,
                                                utils::VectorRef<const sem::Expression*> args) {
     if (args.IsEmpty()) {
         return ZeroValue(builder, ty);
@@ -1326,11 +1326,11 @@
     return CreateComposite(builder, ty, std::move(els));
 }
 
-ConstEval::Result ConstEval::Conv(const sem::Type* ty,
+ConstEval::Result ConstEval::Conv(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     uint32_t el_count = 0;
-    auto* el_ty = sem::Type::ElementOf(ty, &el_count);
+    auto* el_ty = type::Type::ElementOf(ty, &el_count);
     if (!el_ty) {
         return nullptr;
     }
@@ -1342,19 +1342,19 @@
     return Convert(ty, args[0], source);
 }
 
-ConstEval::Result ConstEval::Zero(const sem::Type* ty,
+ConstEval::Result ConstEval::Zero(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*>,
                                   const Source&) {
     return ZeroValue(builder, ty);
 }
 
-ConstEval::Result ConstEval::Identity(const sem::Type*,
+ConstEval::Result ConstEval::Identity(const type::Type*,
                                       utils::VectorRef<const sem::Constant*> args,
                                       const Source&) {
     return args[0];
 }
 
-ConstEval::Result ConstEval::VecSplat(const sem::Type* ty,
+ConstEval::Result ConstEval::VecSplat(const type::Type* ty,
                                       utils::VectorRef<const sem::Constant*> args,
                                       const Source&) {
     if (auto* arg = args[0]) {
@@ -1363,13 +1363,13 @@
     return nullptr;
 }
 
-ConstEval::Result ConstEval::VecInitS(const sem::Type* ty,
+ConstEval::Result ConstEval::VecInitS(const type::Type* ty,
                                       utils::VectorRef<const sem::Constant*> args,
                                       const Source&) {
     return CreateComposite(builder, ty, args);
 }
 
-ConstEval::Result ConstEval::VecInitM(const sem::Type* ty,
+ConstEval::Result ConstEval::VecInitM(const type::Type* ty,
                                       utils::VectorRef<const sem::Constant*> args,
                                       const Source&) {
     utils::Vector<const sem::Constant*, 4> els;
@@ -1395,7 +1395,7 @@
     return CreateComposite(builder, ty, std::move(els));
 }
 
-ConstEval::Result ConstEval::MatInitS(const sem::Type* ty,
+ConstEval::Result ConstEval::MatInitS(const type::Type* ty,
                                       utils::VectorRef<const sem::Constant*> args,
                                       const Source&) {
     auto* m = static_cast<const sem::Matrix*>(ty);
@@ -1412,7 +1412,7 @@
     return CreateComposite(builder, ty, std::move(els));
 }
 
-ConstEval::Result ConstEval::MatInitV(const sem::Type* ty,
+ConstEval::Result ConstEval::MatInitV(const type::Type* ty,
                                       utils::VectorRef<const sem::Constant*> args,
                                       const Source&) {
     return CreateComposite(builder, ty, args);
@@ -1426,7 +1426,7 @@
     }
 
     uint32_t el_count = 0;
-    sem::Type::ElementOf(obj_expr->Type()->UnwrapRef(), &el_count);
+    type::Type::ElementOf(obj_expr->Type()->UnwrapRef(), &el_count);
 
     AInt idx = idx_val->As<AInt>();
     if (idx < 0 || (el_count > 0 && idx >= el_count)) {
@@ -1456,7 +1456,7 @@
     return obj_val->Index(static_cast<size_t>(member->Index()));
 }
 
-ConstEval::Result ConstEval::Swizzle(const sem::Type* ty,
+ConstEval::Result ConstEval::Swizzle(const type::Type* ty,
                                      const sem::Expression* vec_expr,
                                      utils::VectorRef<uint32_t> indices) {
     auto* vec_val = vec_expr->ConstantValue();
@@ -1471,12 +1471,12 @@
     return CreateComposite(builder, ty, std::move(values));
 }
 
-ConstEval::Result ConstEval::Bitcast(const sem::Type*, const sem::Expression*) {
+ConstEval::Result ConstEval::Bitcast(const type::Type*, const sem::Expression*) {
     // TODO(crbug.com/tint/1581): Implement @const intrinsics
     return nullptr;
 }
 
-ConstEval::Result ConstEval::OpComplement(const sem::Type* ty,
+ConstEval::Result ConstEval::OpComplement(const type::Type* ty,
                                           utils::VectorRef<const sem::Constant*> args,
                                           const Source& source) {
     auto transform = [&](const sem::Constant* c) {
@@ -1488,7 +1488,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::OpUnaryMinus(const sem::Type* ty,
+ConstEval::Result ConstEval::OpUnaryMinus(const type::Type* ty,
                                           utils::VectorRef<const sem::Constant*> args,
                                           const Source& source) {
     auto transform = [&](const sem::Constant* c) {
@@ -1513,7 +1513,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::OpNot(const sem::Type* ty,
+ConstEval::Result ConstEval::OpNot(const type::Type* ty,
                                    utils::VectorRef<const sem::Constant*> args,
                                    const Source& source) {
     auto transform = [&](const sem::Constant* c) {
@@ -1525,7 +1525,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::OpPlus(const sem::Type* ty,
+ConstEval::Result ConstEval::OpPlus(const type::Type* ty,
                                     utils::VectorRef<const sem::Constant*> args,
                                     const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -1535,19 +1535,19 @@
     return TransformBinaryElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpMinus(const sem::Type* ty,
+ConstEval::Result ConstEval::OpMinus(const type::Type* ty,
                                      utils::VectorRef<const sem::Constant*> args,
                                      const Source& source) {
     return Sub(source, ty, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpMultiply(const sem::Type* ty,
+ConstEval::Result ConstEval::OpMultiply(const type::Type* ty,
                                         utils::VectorRef<const sem::Constant*> args,
                                         const Source& source) {
     return Mul(source, ty, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpMultiplyMatVec(const sem::Type* ty,
+ConstEval::Result ConstEval::OpMultiplyMatVec(const type::Type* ty,
                                               utils::VectorRef<const sem::Constant*> args,
                                               const Source& source) {
     auto* mat_ty = args[0]->Type()->As<sem::Matrix>();
@@ -1597,7 +1597,7 @@
     }
     return CreateComposite(builder, ty, result);
 }
-ConstEval::Result ConstEval::OpMultiplyVecMat(const sem::Type* ty,
+ConstEval::Result ConstEval::OpMultiplyVecMat(const type::Type* ty,
                                               utils::VectorRef<const sem::Constant*> args,
                                               const Source& source) {
     auto* vec_ty = args[0]->Type()->As<sem::Vector>();
@@ -1648,7 +1648,7 @@
     return CreateComposite(builder, ty, result);
 }
 
-ConstEval::Result ConstEval::OpMultiplyMatMat(const sem::Type* ty,
+ConstEval::Result ConstEval::OpMultiplyMatMat(const type::Type* ty,
                                               utils::VectorRef<const sem::Constant*> args,
                                               const Source& source) {
     auto* mat1 = args[0];
@@ -1712,7 +1712,7 @@
     return CreateComposite(builder, ty, result_mat);
 }
 
-ConstEval::Result ConstEval::OpDivide(const sem::Type* ty,
+ConstEval::Result ConstEval::OpDivide(const type::Type* ty,
                                       utils::VectorRef<const sem::Constant*> args,
                                       const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -1722,7 +1722,7 @@
     return TransformBinaryElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpModulo(const sem::Type* ty,
+ConstEval::Result ConstEval::OpModulo(const type::Type* ty,
                                       utils::VectorRef<const sem::Constant*> args,
                                       const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -1732,12 +1732,12 @@
     return TransformBinaryElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpEqual(const sem::Type* ty,
+ConstEval::Result ConstEval::OpEqual(const type::Type* ty,
                                      utils::VectorRef<const sem::Constant*> args,
                                      const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
         auto create = [&](auto i, auto j) -> ImplResult {
-            return CreateElement(builder, source, sem::Type::DeepestElementOf(ty), i == j);
+            return CreateElement(builder, source, type::Type::DeepestElementOf(ty), i == j);
         };
         return Dispatch_fia_fiu32_f16_bool(create, c0, c1);
     };
@@ -1745,12 +1745,12 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpNotEqual(const sem::Type* ty,
+ConstEval::Result ConstEval::OpNotEqual(const type::Type* ty,
                                         utils::VectorRef<const sem::Constant*> args,
                                         const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
         auto create = [&](auto i, auto j) -> ImplResult {
-            return CreateElement(builder, source, sem::Type::DeepestElementOf(ty), i != j);
+            return CreateElement(builder, source, type::Type::DeepestElementOf(ty), i != j);
         };
         return Dispatch_fia_fiu32_f16_bool(create, c0, c1);
     };
@@ -1758,12 +1758,12 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpLessThan(const sem::Type* ty,
+ConstEval::Result ConstEval::OpLessThan(const type::Type* ty,
                                         utils::VectorRef<const sem::Constant*> args,
                                         const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
         auto create = [&](auto i, auto j) -> ImplResult {
-            return CreateElement(builder, source, sem::Type::DeepestElementOf(ty), i < j);
+            return CreateElement(builder, source, type::Type::DeepestElementOf(ty), i < j);
         };
         return Dispatch_fia_fiu32_f16(create, c0, c1);
     };
@@ -1771,12 +1771,12 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpGreaterThan(const sem::Type* ty,
+ConstEval::Result ConstEval::OpGreaterThan(const type::Type* ty,
                                            utils::VectorRef<const sem::Constant*> args,
                                            const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
         auto create = [&](auto i, auto j) -> ImplResult {
-            return CreateElement(builder, source, sem::Type::DeepestElementOf(ty), i > j);
+            return CreateElement(builder, source, type::Type::DeepestElementOf(ty), i > j);
         };
         return Dispatch_fia_fiu32_f16(create, c0, c1);
     };
@@ -1784,12 +1784,12 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpLessThanEqual(const sem::Type* ty,
+ConstEval::Result ConstEval::OpLessThanEqual(const type::Type* ty,
                                              utils::VectorRef<const sem::Constant*> args,
                                              const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
         auto create = [&](auto i, auto j) -> ImplResult {
-            return CreateElement(builder, source, sem::Type::DeepestElementOf(ty), i <= j);
+            return CreateElement(builder, source, type::Type::DeepestElementOf(ty), i <= j);
         };
         return Dispatch_fia_fiu32_f16(create, c0, c1);
     };
@@ -1797,12 +1797,12 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpGreaterThanEqual(const sem::Type* ty,
+ConstEval::Result ConstEval::OpGreaterThanEqual(const type::Type* ty,
                                                 utils::VectorRef<const sem::Constant*> args,
                                                 const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
         auto create = [&](auto i, auto j) -> ImplResult {
-            return CreateElement(builder, source, sem::Type::DeepestElementOf(ty), i >= j);
+            return CreateElement(builder, source, type::Type::DeepestElementOf(ty), i >= j);
         };
         return Dispatch_fia_fiu32_f16(create, c0, c1);
     };
@@ -1810,7 +1810,19 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpAnd(const sem::Type* ty,
+ConstEval::Result ConstEval::OpLogicalAnd(const type::Type* ty,
+                                          utils::VectorRef<const sem::Constant*> args,
+                                          const Source& source) {
+    return CreateElement(builder, source, ty, args[0]->As<bool>() && args[1]->As<bool>());
+}
+
+ConstEval::Result ConstEval::OpLogicalOr(const type::Type* ty,
+                                         utils::VectorRef<const sem::Constant*> args,
+                                         const Source& source) {
+    return CreateElement(builder, source, ty, args[0]->As<bool>() || args[1]->As<bool>());
+}
+
+ConstEval::Result ConstEval::OpAnd(const type::Type* ty,
                                    utils::VectorRef<const sem::Constant*> args,
                                    const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -1822,7 +1834,7 @@
             } else {  // integral
                 result = i & j;
             }
-            return CreateElement(builder, source, sem::Type::DeepestElementOf(ty), result);
+            return CreateElement(builder, source, type::Type::DeepestElementOf(ty), result);
         };
         return Dispatch_ia_iu32_bool(create, c0, c1);
     };
@@ -1830,7 +1842,7 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpOr(const sem::Type* ty,
+ConstEval::Result ConstEval::OpOr(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -1842,7 +1854,7 @@
             } else {  // integral
                 result = i | j;
             }
-            return CreateElement(builder, source, sem::Type::DeepestElementOf(ty), result);
+            return CreateElement(builder, source, type::Type::DeepestElementOf(ty), result);
         };
         return Dispatch_ia_iu32_bool(create, c0, c1);
     };
@@ -1850,12 +1862,12 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpXor(const sem::Type* ty,
+ConstEval::Result ConstEval::OpXor(const type::Type* ty,
                                    utils::VectorRef<const sem::Constant*> args,
                                    const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
         auto create = [&](auto i, auto j) -> ImplResult {
-            return CreateElement(builder, source, sem::Type::DeepestElementOf(ty),
+            return CreateElement(builder, source, type::Type::DeepestElementOf(ty),
                                  decltype(i){i ^ j});
         };
         return Dispatch_ia_iu32(create, c0, c1);
@@ -1864,7 +1876,7 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpShiftLeft(const sem::Type* ty,
+ConstEval::Result ConstEval::OpShiftLeft(const type::Type* ty,
                                          utils::VectorRef<const sem::Constant*> args,
                                          const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -1936,12 +1948,13 @@
 
             // Avoid UB by left shifting as unsigned value
             auto result = static_cast<T>(static_cast<UT>(e1) << e2);
-            return CreateElement(builder, source, sem::Type::DeepestElementOf(ty), NumberT{result});
+            return CreateElement(builder, source, type::Type::DeepestElementOf(ty),
+                                 NumberT{result});
         };
         return Dispatch_ia_iu32(create, c0, c1);
     };
 
-    if (!sem::Type::DeepestElementOf(args[1]->Type())->Is<sem::U32>()) {
+    if (!type::Type::DeepestElementOf(args[1]->Type())->Is<sem::U32>()) {
         TINT_ICE(Resolver, builder.Diagnostics())
             << "Element type of rhs of ShiftLeft must be a u32";
         return utils::Failure;
@@ -1950,7 +1963,7 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::OpShiftRight(const sem::Type* ty,
+ConstEval::Result ConstEval::OpShiftRight(const type::Type* ty,
                                           utils::VectorRef<const sem::Constant*> args,
                                           const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -2000,12 +2013,13 @@
                     result = e1 >> e2;
                 }
             }
-            return CreateElement(builder, source, sem::Type::DeepestElementOf(ty), NumberT{result});
+            return CreateElement(builder, source, type::Type::DeepestElementOf(ty),
+                                 NumberT{result});
         };
         return Dispatch_ia_iu32(create, c0, c1);
     };
 
-    if (!sem::Type::DeepestElementOf(args[1]->Type())->Is<sem::U32>()) {
+    if (!type::Type::DeepestElementOf(args[1]->Type())->Is<sem::U32>()) {
         TINT_ICE(Resolver, builder.Diagnostics())
             << "Element type of rhs of ShiftLeft must be a u32";
         return utils::Failure;
@@ -2014,7 +2028,7 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::abs(const sem::Type* ty,
+ConstEval::Result ConstEval::abs(const type::Type* ty,
                                  utils::VectorRef<const sem::Constant*> args,
                                  const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2039,7 +2053,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::acos(const sem::Type* ty,
+ConstEval::Result ConstEval::acos(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2057,7 +2071,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::acosh(const sem::Type* ty,
+ConstEval::Result ConstEval::acosh(const type::Type* ty,
                                    utils::VectorRef<const sem::Constant*> args,
                                    const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2075,19 +2089,19 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::all(const sem::Type* ty,
+ConstEval::Result ConstEval::all(const type::Type* ty,
                                  utils::VectorRef<const sem::Constant*> args,
                                  const Source& source) {
     return CreateElement(builder, source, ty, !args[0]->AnyZero());
 }
 
-ConstEval::Result ConstEval::any(const sem::Type* ty,
+ConstEval::Result ConstEval::any(const type::Type* ty,
                                  utils::VectorRef<const sem::Constant*> args,
                                  const Source& source) {
     return CreateElement(builder, source, ty, !args[0]->AllZero());
 }
 
-ConstEval::Result ConstEval::asin(const sem::Type* ty,
+ConstEval::Result ConstEval::asin(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2105,7 +2119,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::asinh(const sem::Type* ty,
+ConstEval::Result ConstEval::asinh(const type::Type* ty,
                                    utils::VectorRef<const sem::Constant*> args,
                                    const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2118,7 +2132,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::atan(const sem::Type* ty,
+ConstEval::Result ConstEval::atan(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2130,7 +2144,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::atanh(const sem::Type* ty,
+ConstEval::Result ConstEval::atanh(const type::Type* ty,
                                    utils::VectorRef<const sem::Constant*> args,
                                    const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2149,7 +2163,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::atan2(const sem::Type* ty,
+ConstEval::Result ConstEval::atan2(const type::Type* ty,
                                    utils::VectorRef<const sem::Constant*> args,
                                    const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -2162,7 +2176,7 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::ceil(const sem::Type* ty,
+ConstEval::Result ConstEval::ceil(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2174,7 +2188,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::clamp(const sem::Type* ty,
+ConstEval::Result ConstEval::clamp(const type::Type* ty,
                                    utils::VectorRef<const sem::Constant*> args,
                                    const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1,
@@ -2184,7 +2198,7 @@
     return TransformElements(builder, ty, transform, args[0], args[1], args[2]);
 }
 
-ConstEval::Result ConstEval::cos(const sem::Type* ty,
+ConstEval::Result ConstEval::cos(const type::Type* ty,
                                  utils::VectorRef<const sem::Constant*> args,
                                  const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2197,7 +2211,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::cosh(const sem::Type* ty,
+ConstEval::Result ConstEval::cosh(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2210,7 +2224,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::countLeadingZeros(const sem::Type* ty,
+ConstEval::Result ConstEval::countLeadingZeros(const type::Type* ty,
                                                utils::VectorRef<const sem::Constant*> args,
                                                const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2225,7 +2239,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::countOneBits(const sem::Type* ty,
+ConstEval::Result ConstEval::countOneBits(const type::Type* ty,
                                           utils::VectorRef<const sem::Constant*> args,
                                           const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2249,7 +2263,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::countTrailingZeros(const sem::Type* ty,
+ConstEval::Result ConstEval::countTrailingZeros(const type::Type* ty,
                                                 utils::VectorRef<const sem::Constant*> args,
                                                 const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2264,7 +2278,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::cross(const sem::Type* ty,
+ConstEval::Result ConstEval::cross(const type::Type* ty,
                                    utils::VectorRef<const sem::Constant*> args,
                                    const Source& source) {
     auto* u = args[0];
@@ -2307,7 +2321,7 @@
                            utils::Vector<const sem::Constant*, 3>{x.Get(), y.Get(), z.Get()});
 }
 
-ConstEval::Result ConstEval::degrees(const sem::Type* ty,
+ConstEval::Result ConstEval::degrees(const type::Type* ty,
                                      utils::VectorRef<const sem::Constant*> args,
                                      const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2333,7 +2347,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::determinant(const sem::Type* ty,
+ConstEval::Result ConstEval::determinant(const type::Type* ty,
                                          utils::VectorRef<const sem::Constant*> args,
                                          const Source& source) {
     auto calculate = [&]() -> ConstEval::Result {
@@ -2369,7 +2383,7 @@
     return r;
 }
 
-ConstEval::Result ConstEval::distance(const sem::Type* ty,
+ConstEval::Result ConstEval::distance(const type::Type* ty,
                                       utils::VectorRef<const sem::Constant*> args,
                                       const Source& source) {
     auto err = [&]() -> ImplResult {
@@ -2389,7 +2403,7 @@
     return len;
 }
 
-ConstEval::Result ConstEval::dot(const sem::Type*,
+ConstEval::Result ConstEval::dot(const type::Type*,
                                  utils::VectorRef<const sem::Constant*> args,
                                  const Source& source) {
     auto r = Dot(source, args[0], args[1]);
@@ -2399,7 +2413,7 @@
     return r;
 }
 
-ConstEval::Result ConstEval::exp(const sem::Type* ty,
+ConstEval::Result ConstEval::exp(const type::Type* ty,
                                  utils::VectorRef<const sem::Constant*> args,
                                  const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2417,7 +2431,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::exp2(const sem::Type* ty,
+ConstEval::Result ConstEval::exp2(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2435,7 +2449,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::extractBits(const sem::Type* ty,
+ConstEval::Result ConstEval::extractBits(const type::Type* ty,
                                          utils::VectorRef<const sem::Constant*> args,
                                          const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2490,7 +2504,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::faceForward(const sem::Type* ty,
+ConstEval::Result ConstEval::faceForward(const type::Type* ty,
                                          utils::VectorRef<const sem::Constant*> args,
                                          const Source& source) {
     // Returns e1 if dot(e2, e3) is negative, and -e1 otherwise.
@@ -2509,7 +2523,7 @@
     return OpUnaryMinus(ty, utils::Vector{e1}, source);
 }
 
-ConstEval::Result ConstEval::firstLeadingBit(const sem::Type* ty,
+ConstEval::Result ConstEval::firstLeadingBit(const type::Type* ty,
                                              utils::VectorRef<const sem::Constant*> args,
                                              const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2553,7 +2567,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::firstTrailingBit(const sem::Type* ty,
+ConstEval::Result ConstEval::firstTrailingBit(const type::Type* ty,
                                               utils::VectorRef<const sem::Constant*> args,
                                               const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2579,7 +2593,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::floor(const sem::Type* ty,
+ConstEval::Result ConstEval::floor(const type::Type* ty,
                                    utils::VectorRef<const sem::Constant*> args,
                                    const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2591,7 +2605,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::fma(const sem::Type* ty,
+ConstEval::Result ConstEval::fma(const type::Type* ty,
                                  utils::VectorRef<const sem::Constant*> args,
                                  const Source& source) {
     auto transform = [&](const sem::Constant* c1, const sem::Constant* c2,
@@ -2618,7 +2632,7 @@
     return TransformElements(builder, ty, transform, args[0], args[1], args[2]);
 }
 
-ConstEval::Result ConstEval::frexp(const sem::Type* ty,
+ConstEval::Result ConstEval::frexp(const type::Type* ty,
                                    utils::VectorRef<const sem::Constant*> args,
                                    const Source& source) {
     auto* arg = args[0];
@@ -2691,7 +2705,7 @@
     }
 }
 
-ConstEval::Result ConstEval::insertBits(const sem::Type* ty,
+ConstEval::Result ConstEval::insertBits(const type::Type* ty,
                                         utils::VectorRef<const sem::Constant*> args,
                                         const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -2743,7 +2757,7 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::inverseSqrt(const sem::Type* ty,
+ConstEval::Result ConstEval::inverseSqrt(const type::Type* ty,
                                          utils::VectorRef<const sem::Constant*> args,
                                          const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2777,7 +2791,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::length(const sem::Type* ty,
+ConstEval::Result ConstEval::length(const type::Type* ty,
                                     utils::VectorRef<const sem::Constant*> args,
                                     const Source& source) {
     auto r = Length(source, ty, args[0]);
@@ -2787,7 +2801,7 @@
     return r;
 }
 
-ConstEval::Result ConstEval::log(const sem::Type* ty,
+ConstEval::Result ConstEval::log(const type::Type* ty,
                                  utils::VectorRef<const sem::Constant*> args,
                                  const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2804,7 +2818,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::log2(const sem::Type* ty,
+ConstEval::Result ConstEval::log2(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -2821,7 +2835,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::max(const sem::Type* ty,
+ConstEval::Result ConstEval::max(const type::Type* ty,
                                  utils::VectorRef<const sem::Constant*> args,
                                  const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -2833,7 +2847,7 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::min(const sem::Type* ty,
+ConstEval::Result ConstEval::min(const type::Type* ty,
                                  utils::VectorRef<const sem::Constant*> args,
                                  const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -2845,7 +2859,7 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::modf(const sem::Type* ty,
+ConstEval::Result ConstEval::modf(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform_fract = [&](const sem::Constant* c) {
@@ -2879,10 +2893,10 @@
     return CreateComposite(builder, ty, std::move(fields));
 }
 
-ConstEval::Result ConstEval::normalize(const sem::Type* ty,
+ConstEval::Result ConstEval::normalize(const type::Type* ty,
                                        utils::VectorRef<const sem::Constant*> args,
                                        const Source& source) {
-    auto* len_ty = sem::Type::DeepestElementOf(ty);
+    auto* len_ty = type::Type::DeepestElementOf(ty);
     auto len = Length(source, len_ty, args[0]);
     if (!len) {
         AddNote("when calculating normalize", source);
@@ -2896,7 +2910,7 @@
     return OpDivide(ty, utils::Vector{args[0], v}, source);
 }
 
-ConstEval::Result ConstEval::pack2x16float(const sem::Type* ty,
+ConstEval::Result ConstEval::pack2x16float(const type::Type* ty,
                                            utils::VectorRef<const sem::Constant*> args,
                                            const Source& source) {
     auto convert = [&](f32 val) -> utils::Result<uint32_t> {
@@ -2924,7 +2938,7 @@
     return CreateElement(builder, source, ty, ret);
 }
 
-ConstEval::Result ConstEval::pack2x16snorm(const sem::Type* ty,
+ConstEval::Result ConstEval::pack2x16snorm(const type::Type* ty,
                                            utils::VectorRef<const sem::Constant*> args,
                                            const Source& source) {
     auto calc = [&](f32 val) -> u32 {
@@ -2941,7 +2955,7 @@
     return CreateElement(builder, source, ty, ret);
 }
 
-ConstEval::Result ConstEval::pack2x16unorm(const sem::Type* ty,
+ConstEval::Result ConstEval::pack2x16unorm(const type::Type* ty,
                                            utils::VectorRef<const sem::Constant*> args,
                                            const Source& source) {
     auto calc = [&](f32 val) -> u32 {
@@ -2957,7 +2971,7 @@
     return CreateElement(builder, source, ty, ret);
 }
 
-ConstEval::Result ConstEval::pack4x8snorm(const sem::Type* ty,
+ConstEval::Result ConstEval::pack4x8snorm(const type::Type* ty,
                                           utils::VectorRef<const sem::Constant*> args,
                                           const Source& source) {
     auto calc = [&](f32 val) -> u32 {
@@ -2977,7 +2991,7 @@
     return CreateElement(builder, source, ty, ret);
 }
 
-ConstEval::Result ConstEval::pack4x8unorm(const sem::Type* ty,
+ConstEval::Result ConstEval::pack4x8unorm(const type::Type* ty,
                                           utils::VectorRef<const sem::Constant*> args,
                                           const Source& source) {
     auto calc = [&](f32 val) -> u32 {
@@ -2996,7 +3010,7 @@
     return CreateElement(builder, source, ty, ret);
 }
 
-ConstEval::Result ConstEval::radians(const sem::Type* ty,
+ConstEval::Result ConstEval::radians(const type::Type* ty,
                                      utils::VectorRef<const sem::Constant*> args,
                                      const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -3022,7 +3036,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::reflect(const sem::Type* ty,
+ConstEval::Result ConstEval::reflect(const type::Type* ty,
                                      utils::VectorRef<const sem::Constant*> args,
                                      const Source& source) {
     auto calculate = [&]() -> ConstEval::Result {
@@ -3065,7 +3079,7 @@
     return r;
 }
 
-ConstEval::Result ConstEval::refract(const sem::Type* ty,
+ConstEval::Result ConstEval::refract(const type::Type* ty,
                                      utils::VectorRef<const sem::Constant*> args,
                                      const Source& source) {
     auto* vec_ty = ty->As<sem::Vector>();
@@ -3163,7 +3177,7 @@
     return r;
 }
 
-ConstEval::Result ConstEval::reverseBits(const sem::Type* ty,
+ConstEval::Result ConstEval::reverseBits(const type::Type* ty,
                                          utils::VectorRef<const sem::Constant*> args,
                                          const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -3190,7 +3204,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::round(const sem::Type* ty,
+ConstEval::Result ConstEval::round(const type::Type* ty,
                                    utils::VectorRef<const sem::Constant*> args,
                                    const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -3226,7 +3240,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::saturate(const sem::Type* ty,
+ConstEval::Result ConstEval::saturate(const type::Type* ty,
                                       utils::VectorRef<const sem::Constant*> args,
                                       const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -3240,13 +3254,13 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::select_bool(const sem::Type* ty,
+ConstEval::Result ConstEval::select_bool(const type::Type* ty,
                                          utils::VectorRef<const sem::Constant*> args,
                                          const Source& source) {
     auto cond = args[2]->As<bool>();
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
         auto create = [&](auto f, auto t) -> ImplResult {
-            return CreateElement(builder, source, sem::Type::DeepestElementOf(ty), cond ? t : f);
+            return CreateElement(builder, source, type::Type::DeepestElementOf(ty), cond ? t : f);
         };
         return Dispatch_fia_fiu32_f16_bool(create, c0, c1);
     };
@@ -3254,14 +3268,14 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::select_boolvec(const sem::Type* ty,
+ConstEval::Result ConstEval::select_boolvec(const type::Type* ty,
                                             utils::VectorRef<const sem::Constant*> args,
                                             const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1, size_t index) {
         auto create = [&](auto f, auto t) -> ImplResult {
             // Get corresponding bool value at the current vector value index
             auto cond = args[2]->Index(index)->As<bool>();
-            return CreateElement(builder, source, sem::Type::DeepestElementOf(ty), cond ? t : f);
+            return CreateElement(builder, source, type::Type::DeepestElementOf(ty), cond ? t : f);
         };
         return Dispatch_fia_fiu32_f16_bool(create, c0, c1);
     };
@@ -3269,7 +3283,7 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::sign(const sem::Type* ty,
+ConstEval::Result ConstEval::sign(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -3286,12 +3300,12 @@
             }
             return CreateElement(builder, source, c0->Type(), result);
         };
-        return Dispatch_fa_f32_f16(create, c0);
+        return Dispatch_fia_fi32_f16(create, c0);
     };
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::sin(const sem::Type* ty,
+ConstEval::Result ConstEval::sin(const type::Type* ty,
                                  utils::VectorRef<const sem::Constant*> args,
                                  const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -3304,7 +3318,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::sinh(const sem::Type* ty,
+ConstEval::Result ConstEval::sinh(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -3317,7 +3331,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::smoothstep(const sem::Type* ty,
+ConstEval::Result ConstEval::smoothstep(const type::Type* ty,
                                         utils::VectorRef<const sem::Constant*> args,
                                         const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1,
@@ -3368,7 +3382,7 @@
     return TransformElements(builder, ty, transform, args[0], args[1], args[2]);
 }
 
-ConstEval::Result ConstEval::step(const sem::Type* ty,
+ConstEval::Result ConstEval::step(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
@@ -3382,7 +3396,7 @@
     return TransformElements(builder, ty, transform, args[0], args[1]);
 }
 
-ConstEval::Result ConstEval::sqrt(const sem::Type* ty,
+ConstEval::Result ConstEval::sqrt(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -3392,7 +3406,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::tan(const sem::Type* ty,
+ConstEval::Result ConstEval::tan(const type::Type* ty,
                                  utils::VectorRef<const sem::Constant*> args,
                                  const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -3405,7 +3419,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::tanh(const sem::Type* ty,
+ConstEval::Result ConstEval::tanh(const type::Type* ty,
                                   utils::VectorRef<const sem::Constant*> args,
                                   const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -3418,7 +3432,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::transpose(const sem::Type* ty,
+ConstEval::Result ConstEval::transpose(const type::Type* ty,
                                        utils::VectorRef<const sem::Constant*> args,
                                        const Source&) {
     auto* m = args[0];
@@ -3438,7 +3452,7 @@
     return CreateComposite(builder, ty, result_mat);
 }
 
-ConstEval::Result ConstEval::trunc(const sem::Type* ty,
+ConstEval::Result ConstEval::trunc(const type::Type* ty,
                                    utils::VectorRef<const sem::Constant*> args,
                                    const Source& source) {
     auto transform = [&](const sem::Constant* c0) {
@@ -3450,10 +3464,10 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::unpack2x16float(const sem::Type* ty,
+ConstEval::Result ConstEval::unpack2x16float(const type::Type* ty,
                                              utils::VectorRef<const sem::Constant*> args,
                                              const Source& source) {
-    auto* inner_ty = sem::Type::DeepestElementOf(ty);
+    auto* inner_ty = type::Type::DeepestElementOf(ty);
     auto e = args[0]->As<u32>().value;
 
     utils::Vector<const sem::Constant*, 2> els;
@@ -3474,10 +3488,10 @@
     return CreateComposite(builder, ty, std::move(els));
 }
 
-ConstEval::Result ConstEval::unpack2x16snorm(const sem::Type* ty,
+ConstEval::Result ConstEval::unpack2x16snorm(const type::Type* ty,
                                              utils::VectorRef<const sem::Constant*> args,
                                              const Source& source) {
-    auto* inner_ty = sem::Type::DeepestElementOf(ty);
+    auto* inner_ty = type::Type::DeepestElementOf(ty);
     auto e = args[0]->As<u32>().value;
 
     utils::Vector<const sem::Constant*, 2> els;
@@ -3494,10 +3508,10 @@
     return CreateComposite(builder, ty, std::move(els));
 }
 
-ConstEval::Result ConstEval::unpack2x16unorm(const sem::Type* ty,
+ConstEval::Result ConstEval::unpack2x16unorm(const type::Type* ty,
                                              utils::VectorRef<const sem::Constant*> args,
                                              const Source& source) {
-    auto* inner_ty = sem::Type::DeepestElementOf(ty);
+    auto* inner_ty = type::Type::DeepestElementOf(ty);
     auto e = args[0]->As<u32>().value;
 
     utils::Vector<const sem::Constant*, 2> els;
@@ -3513,10 +3527,10 @@
     return CreateComposite(builder, ty, std::move(els));
 }
 
-ConstEval::Result ConstEval::unpack4x8snorm(const sem::Type* ty,
+ConstEval::Result ConstEval::unpack4x8snorm(const type::Type* ty,
                                             utils::VectorRef<const sem::Constant*> args,
                                             const Source& source) {
-    auto* inner_ty = sem::Type::DeepestElementOf(ty);
+    auto* inner_ty = type::Type::DeepestElementOf(ty);
     auto e = args[0]->As<u32>().value;
 
     utils::Vector<const sem::Constant*, 4> els;
@@ -3533,10 +3547,10 @@
     return CreateComposite(builder, ty, std::move(els));
 }
 
-ConstEval::Result ConstEval::unpack4x8unorm(const sem::Type* ty,
+ConstEval::Result ConstEval::unpack4x8unorm(const type::Type* ty,
                                             utils::VectorRef<const sem::Constant*> args,
                                             const Source& source) {
-    auto* inner_ty = sem::Type::DeepestElementOf(ty);
+    auto* inner_ty = type::Type::DeepestElementOf(ty);
     auto e = args[0]->As<u32>().value;
 
     utils::Vector<const sem::Constant*, 4> els;
@@ -3552,7 +3566,7 @@
     return CreateComposite(builder, ty, std::move(els));
 }
 
-ConstEval::Result ConstEval::quantizeToF16(const sem::Type* ty,
+ConstEval::Result ConstEval::quantizeToF16(const type::Type* ty,
                                            utils::VectorRef<const sem::Constant*> args,
                                            const Source& source) {
     auto transform = [&](const sem::Constant* c) -> ImplResult {
@@ -3567,7 +3581,7 @@
     return TransformElements(builder, ty, transform, args[0]);
 }
 
-ConstEval::Result ConstEval::Convert(const sem::Type* target_ty,
+ConstEval::Result ConstEval::Convert(const type::Type* target_ty,
                                      const sem::Constant* value,
                                      const Source& source) {
     if (value->Type() == target_ty) {
diff --git a/src/tint/resolver/const_eval.h b/src/tint/resolver/const_eval.h
index 3e6c626..81e4575 100644
--- a/src/tint/resolver/const_eval.h
+++ b/src/tint/resolver/const_eval.h
@@ -18,6 +18,7 @@
 #include <stddef.h>
 #include <string>
 
+#include "src/tint/type/type.h"
 #include "src/tint/utils/result.h"
 #include "src/tint/utils/vector.h"
 
@@ -33,7 +34,6 @@
 class Constant;
 class Expression;
 class StructMember;
-class Type;
 }  // namespace tint::sem
 
 namespace tint::resolver {
@@ -56,7 +56,7 @@
     using Result = utils::Result<const sem::Constant*>;
 
     /// Typedef for a constant evaluation function
-    using Function = Result (ConstEval::*)(const sem::Type* result_ty,
+    using Function = Result (ConstEval::*)(const type::Type* result_ty,
                                            utils::VectorRef<const sem::Constant*>,
                                            const Source&);
 
@@ -71,13 +71,13 @@
     /// @param ty the target type - must be an array or initializer
     /// @param args the input arguments
     /// @return the constructed value, or null if the value cannot be calculated
-    Result ArrayOrStructInit(const sem::Type* ty, utils::VectorRef<const sem::Expression*> args);
+    Result ArrayOrStructInit(const type::Type* ty, utils::VectorRef<const sem::Expression*> args);
 
     /// @param ty the target type
     /// @param expr the input expression
     /// @return the bit-cast of the given expression to the given type, or null if the value cannot
     ///         be calculated
-    Result Bitcast(const sem::Type* ty, const sem::Expression* expr);
+    Result Bitcast(const type::Type* ty, const sem::Expression* expr);
 
     /// @param obj the object being indexed
     /// @param idx the index expression
@@ -87,7 +87,7 @@
     /// @param ty the result type
     /// @param lit the literal AST node
     /// @return the constant value of the literal
-    Result Literal(const sem::Type* ty, const ast::LiteralExpression* lit);
+    Result Literal(const type::Type* ty, const ast::LiteralExpression* lit);
 
     /// @param obj the object being accessed
     /// @param member the member
@@ -98,7 +98,7 @@
     /// @param vector the vector being swizzled
     /// @param indices the swizzle indices
     /// @return the result of the swizzle, or null if the value cannot be calculated
-    Result Swizzle(const sem::Type* ty,
+    Result Swizzle(const type::Type* ty,
                    const sem::Expression* vector,
                    utils::VectorRef<uint32_t> indices);
 
@@ -107,7 +107,7 @@
     /// @param value the value being converted
     /// @param source the source location
     /// @return the converted value, or null if the value cannot be calculated
-    Result Convert(const sem::Type* ty, const sem::Constant* value, const Source& source);
+    Result Convert(const type::Type* ty, const sem::Constant* value, const Source& source);
 
     ////////////////////////////////////////////////////////////////////////////////////////////////
     // Constant value evaluation methods, to be indirectly called via the intrinsic table
@@ -118,7 +118,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the converted value, or null if the value cannot be calculated
-    Result Conv(const sem::Type* ty,
+    Result Conv(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -127,7 +127,7 @@
     /// @param args the input arguments (no arguments provided)
     /// @param source the source location
     /// @return the constructed value, or null if the value cannot be calculated
-    Result Zero(const sem::Type* ty,
+    Result Zero(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -136,7 +136,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the constructed value, or null if the value cannot be calculated
-    Result Identity(const sem::Type* ty,
+    Result Identity(const type::Type* ty,
                     utils::VectorRef<const sem::Constant*> args,
                     const Source& source);
 
@@ -145,7 +145,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the constructed value, or null if the value cannot be calculated
-    Result VecSplat(const sem::Type* ty,
+    Result VecSplat(const type::Type* ty,
                     utils::VectorRef<const sem::Constant*> args,
                     const Source& source);
 
@@ -154,7 +154,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the constructed value, or null if the value cannot be calculated
-    Result VecInitS(const sem::Type* ty,
+    Result VecInitS(const type::Type* ty,
                     utils::VectorRef<const sem::Constant*> args,
                     const Source& source);
 
@@ -163,7 +163,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the constructed value, or null if the value cannot be calculated
-    Result VecInitM(const sem::Type* ty,
+    Result VecInitM(const type::Type* ty,
                     utils::VectorRef<const sem::Constant*> args,
                     const Source& source);
 
@@ -172,7 +172,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the constructed value, or null if the value cannot be calculated
-    Result MatInitS(const sem::Type* ty,
+    Result MatInitS(const type::Type* ty,
                     utils::VectorRef<const sem::Constant*> args,
                     const Source& source);
 
@@ -181,7 +181,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the constructed value, or null if the value cannot be calculated
-    Result MatInitV(const sem::Type* ty,
+    Result MatInitV(const type::Type* ty,
                     utils::VectorRef<const sem::Constant*> args,
                     const Source& source);
 
@@ -194,7 +194,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpComplement(const sem::Type* ty,
+    Result OpComplement(const type::Type* ty,
                         utils::VectorRef<const sem::Constant*> args,
                         const Source& source);
 
@@ -203,7 +203,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpUnaryMinus(const sem::Type* ty,
+    Result OpUnaryMinus(const type::Type* ty,
                         utils::VectorRef<const sem::Constant*> args,
                         const Source& source);
 
@@ -212,7 +212,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpNot(const sem::Type* ty,
+    Result OpNot(const type::Type* ty,
                  utils::VectorRef<const sem::Constant*> args,
                  const Source& source);
 
@@ -225,7 +225,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpPlus(const sem::Type* ty,
+    Result OpPlus(const type::Type* ty,
                   utils::VectorRef<const sem::Constant*> args,
                   const Source& source);
 
@@ -234,7 +234,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpMinus(const sem::Type* ty,
+    Result OpMinus(const type::Type* ty,
                    utils::VectorRef<const sem::Constant*> args,
                    const Source& source);
 
@@ -243,7 +243,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpMultiply(const sem::Type* ty,
+    Result OpMultiply(const type::Type* ty,
                       utils::VectorRef<const sem::Constant*> args,
                       const Source& source);
 
@@ -252,7 +252,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpMultiplyMatVec(const sem::Type* ty,
+    Result OpMultiplyMatVec(const type::Type* ty,
                             utils::VectorRef<const sem::Constant*> args,
                             const Source& source);
 
@@ -261,7 +261,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpMultiplyVecMat(const sem::Type* ty,
+    Result OpMultiplyVecMat(const type::Type* ty,
                             utils::VectorRef<const sem::Constant*> args,
                             const Source& source);
 
@@ -270,7 +270,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpMultiplyMatMat(const sem::Type* ty,
+    Result OpMultiplyMatMat(const type::Type* ty,
                             utils::VectorRef<const sem::Constant*> args,
                             const Source& source);
 
@@ -279,7 +279,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpDivide(const sem::Type* ty,
+    Result OpDivide(const type::Type* ty,
                     utils::VectorRef<const sem::Constant*> args,
                     const Source& source);
 
@@ -288,7 +288,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpModulo(const sem::Type* ty,
+    Result OpModulo(const type::Type* ty,
                     utils::VectorRef<const sem::Constant*> args,
                     const Source& source);
 
@@ -297,7 +297,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpEqual(const sem::Type* ty,
+    Result OpEqual(const type::Type* ty,
                    utils::VectorRef<const sem::Constant*> args,
                    const Source& source);
 
@@ -306,7 +306,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpNotEqual(const sem::Type* ty,
+    Result OpNotEqual(const type::Type* ty,
                       utils::VectorRef<const sem::Constant*> args,
                       const Source& source);
 
@@ -315,7 +315,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpLessThan(const sem::Type* ty,
+    Result OpLessThan(const type::Type* ty,
                       utils::VectorRef<const sem::Constant*> args,
                       const Source& source);
 
@@ -324,7 +324,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpGreaterThan(const sem::Type* ty,
+    Result OpGreaterThan(const type::Type* ty,
                          utils::VectorRef<const sem::Constant*> args,
                          const Source& source);
 
@@ -333,7 +333,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpLessThanEqual(const sem::Type* ty,
+    Result OpLessThanEqual(const type::Type* ty,
                            utils::VectorRef<const sem::Constant*> args,
                            const Source& source);
 
@@ -342,16 +342,34 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpGreaterThanEqual(const sem::Type* ty,
+    Result OpGreaterThanEqual(const type::Type* ty,
                               utils::VectorRef<const sem::Constant*> args,
                               const Source& source);
 
+    /// Logical and operator '&&'
+    /// @param ty the expression type
+    /// @param args the input arguments
+    /// @param source the source location
+    /// @return the result value, or null if the value cannot be calculated
+    Result OpLogicalAnd(const type::Type* ty,
+                        utils::VectorRef<const sem::Constant*> args,
+                        const Source& source);
+
+    /// Logical or operator '||'
+    /// @param ty the expression type
+    /// @param args the input arguments
+    /// @param source the source location
+    /// @return the result value, or null if the value cannot be calculated
+    Result OpLogicalOr(const type::Type* ty,
+                       utils::VectorRef<const sem::Constant*> args,
+                       const Source& source);
+
     /// Bitwise and operator '&'
     /// @param ty the expression type
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpAnd(const sem::Type* ty,
+    Result OpAnd(const type::Type* ty,
                  utils::VectorRef<const sem::Constant*> args,
                  const Source& source);
 
@@ -360,7 +378,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpOr(const sem::Type* ty,
+    Result OpOr(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -369,7 +387,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpXor(const sem::Type* ty,
+    Result OpXor(const type::Type* ty,
                  utils::VectorRef<const sem::Constant*> args,
                  const Source& source);
 
@@ -378,7 +396,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpShiftLeft(const sem::Type* ty,
+    Result OpShiftLeft(const type::Type* ty,
                        utils::VectorRef<const sem::Constant*> args,
                        const Source& source);
 
@@ -387,7 +405,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result OpShiftRight(const sem::Type* ty,
+    Result OpShiftRight(const type::Type* ty,
                         utils::VectorRef<const sem::Constant*> args,
                         const Source& source);
 
@@ -400,7 +418,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result abs(const sem::Type* ty,
+    Result abs(const type::Type* ty,
                utils::VectorRef<const sem::Constant*> args,
                const Source& source);
 
@@ -409,7 +427,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result acos(const sem::Type* ty,
+    Result acos(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -418,7 +436,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result acosh(const sem::Type* ty,
+    Result acosh(const type::Type* ty,
                  utils::VectorRef<const sem::Constant*> args,
                  const Source& source);
 
@@ -427,7 +445,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result all(const sem::Type* ty,
+    Result all(const type::Type* ty,
                utils::VectorRef<const sem::Constant*> args,
                const Source& source);
 
@@ -436,7 +454,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result any(const sem::Type* ty,
+    Result any(const type::Type* ty,
                utils::VectorRef<const sem::Constant*> args,
                const Source& source);
 
@@ -445,7 +463,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result asin(const sem::Type* ty,
+    Result asin(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -454,7 +472,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result asinh(const sem::Type* ty,
+    Result asinh(const type::Type* ty,
                  utils::VectorRef<const sem::Constant*> args,
                  const Source& source);
 
@@ -463,7 +481,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result atan(const sem::Type* ty,
+    Result atan(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -472,7 +490,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result atanh(const sem::Type* ty,
+    Result atanh(const type::Type* ty,
                  utils::VectorRef<const sem::Constant*> args,
                  const Source& source);
 
@@ -481,7 +499,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result atan2(const sem::Type* ty,
+    Result atan2(const type::Type* ty,
                  utils::VectorRef<const sem::Constant*> args,
                  const Source& source);
 
@@ -490,7 +508,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result ceil(const sem::Type* ty,
+    Result ceil(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -499,7 +517,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result clamp(const sem::Type* ty,
+    Result clamp(const type::Type* ty,
                  utils::VectorRef<const sem::Constant*> args,
                  const Source& source);
 
@@ -508,7 +526,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result cos(const sem::Type* ty,
+    Result cos(const type::Type* ty,
                utils::VectorRef<const sem::Constant*> args,
                const Source& source);
 
@@ -517,7 +535,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result cosh(const sem::Type* ty,
+    Result cosh(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -526,7 +544,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result countLeadingZeros(const sem::Type* ty,
+    Result countLeadingZeros(const type::Type* ty,
                              utils::VectorRef<const sem::Constant*> args,
                              const Source& source);
 
@@ -535,7 +553,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result countOneBits(const sem::Type* ty,
+    Result countOneBits(const type::Type* ty,
                         utils::VectorRef<const sem::Constant*> args,
                         const Source& source);
 
@@ -544,7 +562,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result countTrailingZeros(const sem::Type* ty,
+    Result countTrailingZeros(const type::Type* ty,
                               utils::VectorRef<const sem::Constant*> args,
                               const Source& source);
 
@@ -553,7 +571,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result cross(const sem::Type* ty,
+    Result cross(const type::Type* ty,
                  utils::VectorRef<const sem::Constant*> args,
                  const Source& source);
 
@@ -562,7 +580,7 @@
     /// @param args the input arguments
     /// @param source the source location of the conversion
     /// @return the result value, or null if the value cannot be calculated
-    Result degrees(const sem::Type* ty,
+    Result degrees(const type::Type* ty,
                    utils::VectorRef<const sem::Constant*> args,
                    const Source& source);
 
@@ -571,7 +589,7 @@
     /// @param args the input arguments
     /// @param source the source location of the conversion
     /// @return the result value, or null if the value cannot be calculated
-    Result determinant(const sem::Type* ty,
+    Result determinant(const type::Type* ty,
                        utils::VectorRef<const sem::Constant*> args,
                        const Source& source);
 
@@ -580,7 +598,7 @@
     /// @param args the input arguments
     /// @param source the source location of the conversion
     /// @return the result value, or null if the value cannot be calculated
-    Result distance(const sem::Type* ty,
+    Result distance(const type::Type* ty,
                     utils::VectorRef<const sem::Constant*> args,
                     const Source& source);
 
@@ -589,7 +607,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result dot(const sem::Type* ty,
+    Result dot(const type::Type* ty,
                utils::VectorRef<const sem::Constant*> args,
                const Source& source);
 
@@ -598,7 +616,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result exp(const sem::Type* ty,
+    Result exp(const type::Type* ty,
                utils::VectorRef<const sem::Constant*> args,
                const Source& source);
 
@@ -607,7 +625,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result exp2(const sem::Type* ty,
+    Result exp2(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -616,7 +634,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result extractBits(const sem::Type* ty,
+    Result extractBits(const type::Type* ty,
                        utils::VectorRef<const sem::Constant*> args,
                        const Source& source);
 
@@ -625,7 +643,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result faceForward(const sem::Type* ty,
+    Result faceForward(const type::Type* ty,
                        utils::VectorRef<const sem::Constant*> args,
                        const Source& source);
 
@@ -634,7 +652,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result firstLeadingBit(const sem::Type* ty,
+    Result firstLeadingBit(const type::Type* ty,
                            utils::VectorRef<const sem::Constant*> args,
                            const Source& source);
 
@@ -643,7 +661,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result firstTrailingBit(const sem::Type* ty,
+    Result firstTrailingBit(const type::Type* ty,
                             utils::VectorRef<const sem::Constant*> args,
                             const Source& source);
 
@@ -652,7 +670,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result floor(const sem::Type* ty,
+    Result floor(const type::Type* ty,
                  utils::VectorRef<const sem::Constant*> args,
                  const Source& source);
 
@@ -661,7 +679,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result fma(const sem::Type* ty,
+    Result fma(const type::Type* ty,
                utils::VectorRef<const sem::Constant*> args,
                const Source& source);
 
@@ -670,7 +688,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result frexp(const sem::Type* ty,
+    Result frexp(const type::Type* ty,
                  utils::VectorRef<const sem::Constant*> args,
                  const Source& source);
 
@@ -679,7 +697,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result insertBits(const sem::Type* ty,
+    Result insertBits(const type::Type* ty,
                       utils::VectorRef<const sem::Constant*> args,
                       const Source& source);
 
@@ -688,7 +706,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result inverseSqrt(const sem::Type* ty,
+    Result inverseSqrt(const type::Type* ty,
                        utils::VectorRef<const sem::Constant*> args,
                        const Source& source);
 
@@ -697,7 +715,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result length(const sem::Type* ty,
+    Result length(const type::Type* ty,
                   utils::VectorRef<const sem::Constant*> args,
                   const Source& source);
 
@@ -706,7 +724,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result log(const sem::Type* ty,
+    Result log(const type::Type* ty,
                utils::VectorRef<const sem::Constant*> args,
                const Source& source);
 
@@ -715,7 +733,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result log2(const sem::Type* ty,
+    Result log2(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -724,7 +742,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result max(const sem::Type* ty,
+    Result max(const type::Type* ty,
                utils::VectorRef<const sem::Constant*> args,
                const Source& source);
 
@@ -733,7 +751,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result min(const sem::Type* ty,  // NOLINT(build/include_what_you_use)  -- confused by min
+    Result min(const type::Type* ty,  // NOLINT(build/include_what_you_use)  -- confused by min
                utils::VectorRef<const sem::Constant*> args,
                const Source& source);
 
@@ -742,7 +760,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result modf(const sem::Type* ty,
+    Result modf(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -751,7 +769,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result normalize(const sem::Type* ty,
+    Result normalize(const type::Type* ty,
                      utils::VectorRef<const sem::Constant*> args,
                      const Source& source);
 
@@ -760,7 +778,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result pack2x16float(const sem::Type* ty,
+    Result pack2x16float(const type::Type* ty,
                          utils::VectorRef<const sem::Constant*> args,
                          const Source& source);
 
@@ -769,7 +787,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result pack2x16snorm(const sem::Type* ty,
+    Result pack2x16snorm(const type::Type* ty,
                          utils::VectorRef<const sem::Constant*> args,
                          const Source& source);
 
@@ -778,7 +796,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result pack2x16unorm(const sem::Type* ty,
+    Result pack2x16unorm(const type::Type* ty,
                          utils::VectorRef<const sem::Constant*> args,
                          const Source& source);
 
@@ -787,7 +805,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result pack4x8snorm(const sem::Type* ty,
+    Result pack4x8snorm(const type::Type* ty,
                         utils::VectorRef<const sem::Constant*> args,
                         const Source& source);
 
@@ -796,7 +814,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result pack4x8unorm(const sem::Type* ty,
+    Result pack4x8unorm(const type::Type* ty,
                         utils::VectorRef<const sem::Constant*> args,
                         const Source& source);
 
@@ -805,7 +823,7 @@
     /// @param args the input arguments
     /// @param source the source location of the conversion
     /// @return the result value, or null if the value cannot be calculated
-    Result radians(const sem::Type* ty,
+    Result radians(const type::Type* ty,
                    utils::VectorRef<const sem::Constant*> args,
                    const Source& source);
 
@@ -814,7 +832,7 @@
     /// @param args the input arguments
     /// @param source the source location of the conversion
     /// @return the result value, or null if the value cannot be calculated
-    Result reflect(const sem::Type* ty,
+    Result reflect(const type::Type* ty,
                    utils::VectorRef<const sem::Constant*> args,
                    const Source& source);
 
@@ -823,7 +841,7 @@
     /// @param args the input arguments
     /// @param source the source location of the conversion
     /// @return the result value, or null if the value cannot be calculated
-    Result refract(const sem::Type* ty,
+    Result refract(const type::Type* ty,
                    utils::VectorRef<const sem::Constant*> args,
                    const Source& source);
 
@@ -832,7 +850,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result reverseBits(const sem::Type* ty,
+    Result reverseBits(const type::Type* ty,
                        utils::VectorRef<const sem::Constant*> args,
                        const Source& source);
 
@@ -841,7 +859,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result round(const sem::Type* ty,
+    Result round(const type::Type* ty,
                  utils::VectorRef<const sem::Constant*> args,
                  const Source& source);
 
@@ -850,7 +868,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result saturate(const sem::Type* ty,
+    Result saturate(const type::Type* ty,
                     utils::VectorRef<const sem::Constant*> args,
                     const Source& source);
 
@@ -859,7 +877,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result select_bool(const sem::Type* ty,
+    Result select_bool(const type::Type* ty,
                        utils::VectorRef<const sem::Constant*> args,
                        const Source& source);
 
@@ -868,7 +886,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result select_boolvec(const sem::Type* ty,
+    Result select_boolvec(const type::Type* ty,
                           utils::VectorRef<const sem::Constant*> args,
                           const Source& source);
 
@@ -877,7 +895,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result sign(const sem::Type* ty,
+    Result sign(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -886,7 +904,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result sin(const sem::Type* ty,
+    Result sin(const type::Type* ty,
                utils::VectorRef<const sem::Constant*> args,
                const Source& source);
 
@@ -895,7 +913,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result sinh(const sem::Type* ty,
+    Result sinh(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -904,7 +922,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result smoothstep(const sem::Type* ty,
+    Result smoothstep(const type::Type* ty,
                       utils::VectorRef<const sem::Constant*> args,
                       const Source& source);
 
@@ -913,7 +931,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result step(const sem::Type* ty,
+    Result step(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -922,7 +940,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result sqrt(const sem::Type* ty,
+    Result sqrt(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -931,7 +949,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result tan(const sem::Type* ty,
+    Result tan(const type::Type* ty,
                utils::VectorRef<const sem::Constant*> args,
                const Source& source);
 
@@ -940,7 +958,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result tanh(const sem::Type* ty,
+    Result tanh(const type::Type* ty,
                 utils::VectorRef<const sem::Constant*> args,
                 const Source& source);
 
@@ -949,7 +967,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result transpose(const sem::Type* ty,
+    Result transpose(const type::Type* ty,
                      utils::VectorRef<const sem::Constant*> args,
                      const Source& source);
 
@@ -958,7 +976,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result trunc(const sem::Type* ty,
+    Result trunc(const type::Type* ty,
                  utils::VectorRef<const sem::Constant*> args,
                  const Source& source);
 
@@ -967,7 +985,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result unpack2x16float(const sem::Type* ty,
+    Result unpack2x16float(const type::Type* ty,
                            utils::VectorRef<const sem::Constant*> args,
                            const Source& source);
 
@@ -976,7 +994,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result unpack2x16snorm(const sem::Type* ty,
+    Result unpack2x16snorm(const type::Type* ty,
                            utils::VectorRef<const sem::Constant*> args,
                            const Source& source);
 
@@ -985,7 +1003,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result unpack2x16unorm(const sem::Type* ty,
+    Result unpack2x16unorm(const type::Type* ty,
                            utils::VectorRef<const sem::Constant*> args,
                            const Source& source);
 
@@ -994,7 +1012,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result unpack4x8snorm(const sem::Type* ty,
+    Result unpack4x8snorm(const type::Type* ty,
                           utils::VectorRef<const sem::Constant*> args,
                           const Source& source);
 
@@ -1003,7 +1021,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result unpack4x8unorm(const sem::Type* ty,
+    Result unpack4x8unorm(const type::Type* ty,
                           utils::VectorRef<const sem::Constant*> args,
                           const Source& source);
 
@@ -1012,7 +1030,7 @@
     /// @param args the input arguments
     /// @param source the source location
     /// @return the result value, or null if the value cannot be calculated
-    Result quantizeToF16(const sem::Type* ty,
+    Result quantizeToF16(const type::Type* ty,
                          utils::VectorRef<const sem::Constant*> args,
                          const Source& source);
 
@@ -1219,91 +1237,91 @@
     /// @param source the source location
     /// @param elem_ty the element type of the Constant to create on success
     /// @returns the callable function
-    auto AddFunc(const Source& source, const sem::Type* elem_ty);
+    auto AddFunc(const Source& source, const type::Type* elem_ty);
 
     /// Returns a callable that calls Sub, and creates a Constant with its result of type `elem_ty`
     /// if successful, or returns Failure otherwise.
     /// @param source the source location
     /// @param elem_ty the element type of the Constant to create on success
     /// @returns the callable function
-    auto SubFunc(const Source& source, const sem::Type* elem_ty);
+    auto SubFunc(const Source& source, const type::Type* elem_ty);
 
     /// Returns a callable that calls Mul, and creates a Constant with its result of type `elem_ty`
     /// if successful, or returns Failure otherwise.
     /// @param source the source location
     /// @param elem_ty the element type of the Constant to create on success
     /// @returns the callable function
-    auto MulFunc(const Source& source, const sem::Type* elem_ty);
+    auto MulFunc(const Source& source, const type::Type* elem_ty);
 
     /// Returns a callable that calls Div, and creates a Constant with its result of type `elem_ty`
     /// if successful, or returns Failure otherwise.
     /// @param source the source location
     /// @param elem_ty the element type of the Constant to create on success
     /// @returns the callable function
-    auto DivFunc(const Source& source, const sem::Type* elem_ty);
+    auto DivFunc(const Source& source, const type::Type* elem_ty);
 
     /// Returns a callable that calls Mod, and creates a Constant with its result of type `elem_ty`
     /// if successful, or returns Failure otherwise.
     /// @param source the source location
     /// @param elem_ty the element type of the Constant to create on success
     /// @returns the callable function
-    auto ModFunc(const Source& source, const sem::Type* elem_ty);
+    auto ModFunc(const Source& source, const type::Type* elem_ty);
 
     /// Returns a callable that calls Dot2, and creates a Constant with its result of type `elem_ty`
     /// if successful, or returns Failure otherwise.
     /// @param source the source location
     /// @param elem_ty the element type of the Constant to create on success
     /// @returns the callable function
-    auto Dot2Func(const Source& source, const sem::Type* elem_ty);
+    auto Dot2Func(const Source& source, const type::Type* elem_ty);
 
     /// Returns a callable that calls Dot3, and creates a Constant with its result of type `elem_ty`
     /// if successful, or returns Failure otherwise.
     /// @param source the source location
     /// @param elem_ty the element type of the Constant to create on success
     /// @returns the callable function
-    auto Dot3Func(const Source& source, const sem::Type* elem_ty);
+    auto Dot3Func(const Source& source, const type::Type* elem_ty);
 
     /// Returns a callable that calls Dot4, and creates a Constant with its result of type `elem_ty`
     /// if successful, or returns Failure otherwise.
     /// @param source the source location
     /// @param elem_ty the element type of the Constant to create on success
     /// @returns the callable function
-    auto Dot4Func(const Source& source, const sem::Type* elem_ty);
+    auto Dot4Func(const Source& source, const type::Type* elem_ty);
 
     /// Returns a callable that calls Det2, and creates a Constant with its result of type `elem_ty`
     /// if successful, or returns Failure otherwise.
     /// @param source the source location
     /// @param elem_ty the element type of the Constant to create on success
     /// @returns the callable function
-    auto Det2Func(const Source& source, const sem::Type* elem_ty);
+    auto Det2Func(const Source& source, const type::Type* elem_ty);
 
     /// Returns a callable that calls Det3, and creates a Constant with its result of type `elem_ty`
     /// if successful, or returns Failure otherwise.
     /// @param source the source location
     /// @param elem_ty the element type of the Constant to create on success
     /// @returns the callable function
-    auto Det3Func(const Source& source, const sem::Type* elem_ty);
+    auto Det3Func(const Source& source, const type::Type* elem_ty);
 
     /// Returns a callable that calls Det4, and creates a Constant with its result of type `elem_ty`
     /// if successful, or returns Failure otherwise.
     /// @param source the source location
     /// @param elem_ty the element type of the Constant to create on success
     /// @returns the callable function
-    auto Det4Func(const Source& source, const sem::Type* elem_ty);
+    auto Det4Func(const Source& source, const type::Type* elem_ty);
 
     /// Returns a callable that calls Clamp, and creates a Constant with its result of type
     /// `elem_ty` if successful, or returns Failure otherwise.
     /// @param source the source location
     /// @param elem_ty the element type of the Constant to create on success
     /// @returns the callable function
-    auto ClampFunc(const Source& source, const sem::Type* elem_ty);
+    auto ClampFunc(const Source& source, const type::Type* elem_ty);
 
     /// Returns a callable that calls SqrtFunc, and creates a Constant with its
     /// result of type `elem_ty` if successful, or returns Failure otherwise.
     /// @param source the source location
     /// @param elem_ty the element type of the Constant to create on success
     /// @returns the callable function
-    auto SqrtFunc(const Source& source, const sem::Type* elem_ty);
+    auto SqrtFunc(const Source& source, const type::Type* elem_ty);
 
     /// Returns the dot product of v1 and v2.
     /// @param source the source location
@@ -1317,7 +1335,7 @@
     /// @param ty the return type
     /// @param c0 the constant to calculate the length of
     /// @returns the length of c0
-    Result Length(const Source& source, const sem::Type* ty, const sem::Constant* c0);
+    Result Length(const Source& source, const type::Type* ty, const sem::Constant* c0);
 
     /// Returns the product of v1 and v2
     /// @param source the source location
@@ -1326,7 +1344,7 @@
     /// @param v2 rhs value
     /// @returns the product of v1 and v2
     Result Mul(const Source& source,
-               const sem::Type* ty,
+               const type::Type* ty,
                const sem::Constant* v1,
                const sem::Constant* v2);
 
@@ -1337,7 +1355,7 @@
     /// @param v2 rhs value
     /// @returns the difference between v2 and v1
     Result Sub(const Source& source,
-               const sem::Type* ty,
+               const type::Type* ty,
                const sem::Constant* v1,
                const sem::Constant* v2);
 
diff --git a/src/tint/resolver/const_eval_binary_op_test.cc b/src/tint/resolver/const_eval_binary_op_test.cc
index a370285..e5a19ff 100644
--- a/src/tint/resolver/const_eval_binary_op_test.cc
+++ b/src/tint/resolver/const_eval_binary_op_test.cc
@@ -749,6 +749,34 @@
                                  OpGreaterThanCases<f32, false>(),
                                  OpGreaterThanCases<f16, false>()))));
 
+static std::vector<Case> OpLogicalAndCases() {
+    return {
+        C(true, true, true),
+        C(true, false, false),
+        C(false, true, false),
+        C(false, false, false),
+    };
+}
+INSTANTIATE_TEST_SUITE_P(LogicalAnd,
+                         ResolverConstEvalBinaryOpTest,
+                         testing::Combine(  //
+                             testing::Values(ast::BinaryOp::kLogicalAnd),
+                             testing::ValuesIn(OpLogicalAndCases())));
+
+static std::vector<Case> OpLogicalOrCases() {
+    return {
+        C(true, true, true),
+        C(true, false, true),
+        C(false, true, true),
+        C(false, false, false),
+    };
+}
+INSTANTIATE_TEST_SUITE_P(LogicalOr,
+                         ResolverConstEvalBinaryOpTest,
+                         testing::Combine(  //
+                             testing::Values(ast::BinaryOp::kLogicalOr),
+                             testing::ValuesIn(OpLogicalOrCases())));
+
 static std::vector<Case> OpAndBoolCases() {
     return {
         C(true, true, true),
diff --git a/src/tint/resolver/const_eval_builtin_test.cc b/src/tint/resolver/const_eval_builtin_test.cc
index b7f506c..8da5af4 100644
--- a/src/tint/resolver/const_eval_builtin_test.cc
+++ b/src/tint/resolver/const_eval_builtin_test.cc
@@ -522,7 +522,7 @@
 
 template <typename T>
 std::vector<Case> CosCases() {
-    std::vector<Case> cases = {
+    return {
         C({-T(0)}, T(1)),
         C({T(0)}, T(1)),
 
@@ -531,8 +531,6 @@
         // Vector test
         C({Vec(T(0), -T(0), T(0.75))}, Vec(T(1), T(1), T(0.7316888689))).FloatComp(),
     };
-
-    return cases;
 }
 INSTANTIATE_TEST_SUITE_P(  //
     Cos,
@@ -547,7 +545,7 @@
     auto error_msg = [](auto a) {
         return "12:34 error: " + OverflowErrorMessage(a, FriendlyName<decltype(a)>());
     };
-    std::vector<Case> cases = {
+    return {
         C({T(0)}, T(1)),
         C({-T(0)}, T(1)),
         C({T(1)}, T(1.5430806348)).FloatComp(),
@@ -559,7 +557,6 @@
 
         E({T(10000)}, error_msg(T::Inf())),
     };
-    return cases;
 }
 INSTANTIATE_TEST_SUITE_P(  //
     Cosh,
@@ -805,7 +802,7 @@
 12:34 note: when calculating distance)";
     };
 
-    return std::vector<Case>{
+    return {
         C({T(0), T(0)}, T(0)),
         // length(-5) -> 5
         C({T(30), T(35)}, T(5)),
@@ -1180,7 +1177,7 @@
     using F = T;                                                         // fract type
     using E = std::conditional_t<std::is_same_v<T, AFloat>, AInt, i32>;  // exp type
 
-    auto cases = std::vector<Case>{
+    std::vector<Case> cases = {
         // Scalar tests
         //  in         fract     exp
         C({T(-3.5)}, {F(-0.875), E(2)}),  //
@@ -1315,7 +1312,7 @@
 
 template <typename T>
 std::vector<Case> InverseSqrtCases() {
-    std::vector<Case> cases = {
+    return {
         C({T(25)}, T(.2)),
 
         // Vector tests
@@ -1325,7 +1322,6 @@
         E({-T(0)}, "12:34 error: inverseSqrt must be called with a value > 0"),
         E({-T(25)}, "12:34 error: inverseSqrt must be called with a value > 0"),
     };
-    return cases;
 }
 INSTANTIATE_TEST_SUITE_P(  //
     InverseSqrt,
@@ -1337,7 +1333,7 @@
 
 template <typename T>
 std::vector<Case> DegreesAFloatCases() {
-    return std::vector<Case>{
+    return {
         C({T(0)}, T(0)),                             //
         C({-T(0)}, -T(0)),                           //
         C({T(0.698132)}, T(40)).FloatComp(),         //
@@ -1354,7 +1350,7 @@
 
 template <typename T>
 std::vector<Case> DegreesF32Cases() {
-    return std::vector<Case>{
+    return {
         C({T(0)}, T(0)),                             //
         C({-T(0)}, -T(0)),                           //
         C({T(0.698132)}, T(40)).FloatComp(),         //
@@ -1371,7 +1367,7 @@
 
 template <typename T>
 std::vector<Case> DegreesF16Cases() {
-    return std::vector<Case>{
+    return {
         C({T(0)}, T(0)),                            //
         C({-T(0)}, -T(0)),                          //
         C({T(0.698132)}, T(39.96875)).FloatComp(),  //
@@ -1389,13 +1385,13 @@
 template <typename T>
 std::vector<Case> ExpCases() {
     auto error_msg = [](auto a) { return "12:34 error: " + OverflowExpErrorMessage("e", a); };
-    return std::vector<Case>{C({T(0)}, T(1)),   //
-                             C({-T(0)}, T(1)),  //
-                             C({T(2)}, T(7.3890562)).FloatComp(),
-                             C({-T(2)}, T(0.13533528)).FloatComp(),  //
-                             C({T::Lowest()}, T(0)),
+    return {C({T(0)}, T(1)),   //
+            C({-T(0)}, T(1)),  //
+            C({T(2)}, T(7.3890562)).FloatComp(),
+            C({-T(2)}, T(0.13533528)).FloatComp(),  //
+            C({T::Lowest()}, T(0)),
 
-                             E({T::Highest()}, error_msg(T::Highest()))};
+            E({T::Highest()}, error_msg(T::Highest()))};
 }
 INSTANTIATE_TEST_SUITE_P(  //
     Exp,
@@ -1408,7 +1404,7 @@
 template <typename T>
 std::vector<Case> Exp2Cases() {
     auto error_msg = [](auto a) { return "12:34 error: " + OverflowExpErrorMessage("2", a); };
-    return std::vector<Case>{
+    return {
         C({T(0)}, T(1)),   //
         C({-T(0)}, T(1)),  //
         C({T(2)}, T(4.0)),
@@ -1584,11 +1580,10 @@
 template <typename T>
 std::vector<Case> LogCases() {
     auto error_msg = [] { return "12:34 error: log must be called with a value > 0"; };
-    return std::vector<Case>{C({T(1)}, T(0)),                              //
-                             C({T(54.598150033)}, T(4)).FloatComp(0.002),  //
+    return {C({T(1)}, T(0)),                              //
+            C({T(54.598150033)}, T(4)).FloatComp(0.002),  //
 
-                             E({T::Lowest()}, error_msg()), E({T(0)}, error_msg()),
-                             E({-T(0)}, error_msg())};
+            E({T::Lowest()}, error_msg()), E({T(0)}, error_msg()), E({-T(0)}, error_msg())};
 }
 INSTANTIATE_TEST_SUITE_P(  //
     Log,
@@ -1599,7 +1594,7 @@
                                               LogCases<f16>()))));
 template <typename T>
 std::vector<Case> LogF16Cases() {
-    return std::vector<Case>{
+    return {
         C({T::Highest()}, T(11.085938)).FloatComp(),
     };
 }
@@ -1610,7 +1605,7 @@
                      testing::ValuesIn(LogF16Cases<f16>())));
 template <typename T>
 std::vector<Case> LogF32Cases() {
-    return std::vector<Case>{
+    return {
         C({T::Highest()}, T(88.722839)).FloatComp(),
     };
 }
@@ -1622,7 +1617,7 @@
 
 template <typename T>
 std::vector<Case> LogAbstractCases() {
-    return std::vector<Case>{
+    return {
         C({T::Highest()}, T(709.78271)).FloatComp(),
     };
 }
@@ -1635,7 +1630,7 @@
 template <typename T>
 std::vector<Case> Log2Cases() {
     auto error_msg = [] { return "12:34 error: log2 must be called with a value > 0"; };
-    return std::vector<Case>{
+    return {
         C({T(1)}, T(0)),  //
         C({T(4)}, T(2)),  //
 
@@ -1653,7 +1648,7 @@
                                               Log2Cases<f16>()))));
 template <typename T>
 std::vector<Case> Log2F16Cases() {
-    return std::vector<Case>{
+    return {
         C({T::Highest()}, T(15.9922)).FloatComp(),
     };
 }
@@ -1664,7 +1659,7 @@
                      testing::ValuesIn(Log2F16Cases<f16>())));
 template <typename T>
 std::vector<Case> Log2F32Cases() {
-    return std::vector<Case>{
+    return {
         C({T::Highest()}, T(128)).FloatComp(),
     };
 }
@@ -1675,7 +1670,7 @@
                      testing::ValuesIn(Log2F32Cases<f32>())));
 template <typename T>
 std::vector<Case> Log2AbstractCases() {
-    return std::vector<Case>{
+    return {
         C({T::Highest()}, T(1024)).FloatComp(),
     };
 }
@@ -2071,7 +2066,7 @@
 
 template <typename T>
 std::vector<Case> RadiansCases() {
-    return std::vector<Case>{
+    return {
         C({T(0)}, T(0)),                         //
         C({-T(0)}, -T(0)),                       //
         C({T(40)}, T(0.69813168)).FloatComp(),   //
@@ -2089,7 +2084,7 @@
 
 template <typename T>
 std::vector<Case> RadiansF16Cases() {
-    return std::vector<Case>{
+    return {
         C({T(0)}, T(0)),                         //
         C({-T(0)}, -T(0)),                       //
         C({T(40)}, T(0.69726562)).FloatComp(),   //
@@ -2106,7 +2101,7 @@
 
 template <typename T>
 std::vector<Case> RoundCases() {
-    std::vector<Case> cases = {
+    return {
         C({T(0.0)}, T(0.0)),      //
         C({-T(0.0)}, -T(0.0)),    //
         C({T(1.5)}, T(2.0)),      //
@@ -2123,8 +2118,6 @@
         // Vector tests
         C({Vec(T(0.0), T(1.5), T(2.5))}, Vec(T(0.0), T(2.0), T(2.0))),
     };
-
-    return cases;
 }
 INSTANTIATE_TEST_SUITE_P(  //
     Round,
@@ -2207,33 +2200,46 @@
 
 template <typename T>
 std::vector<Case> SignCases() {
-    return {
-        C({-T(1)}, -T(1)),
-        C({-T(0.5)}, -T(1)),
+    std::vector<Case> cases = {
         C({T(0)}, T(0)),
         C({-T(0)}, T(0)),
-        C({T(0.5)}, T(1)),
+
+        C({-T(1)}, -T(1)),
+        C({-T(10)}, -T(1)),
+        C({-T(100)}, -T(1)),
         C({T(1)}, T(1)),
+        C({T(10)}, T(1)),
+        C({T(100)}, T(1)),
 
         C({T::Highest()}, T(1.0)),
         C({T::Lowest()}, -T(1.0)),
 
         // Vector tests
-        C({Vec(-T(0.5), T(0), T(0.5))}, Vec(-T(1.0), T(0.0), T(1.0))),
         C({Vec(T::Highest(), T::Lowest())}, Vec(T(1.0), -T(1.0))),
     };
+
+    ConcatIntoIf<IsFloatingPoint<T>>(
+        cases, std::vector<Case>{
+                   C({-T(0.5)}, -T(1)),
+                   C({T(0.5)}, T(1)),
+                   C({Vec(-T(0.5), T(0), T(0.5))}, Vec(-T(1.0), T(0.0), T(1.0))),
+               });
+
+    return cases;
 }
 INSTANTIATE_TEST_SUITE_P(  //
     Sign,
     ResolverConstEvalBuiltinTest,
     testing::Combine(testing::Values(sem::BuiltinType::kSign),
-                     testing::ValuesIn(Concat(SignCases<AFloat>(),  //
+                     testing::ValuesIn(Concat(SignCases<AInt>(),  //
+                                              SignCases<i32>(),
+                                              SignCases<AFloat>(),
                                               SignCases<f32>(),
                                               SignCases<f16>()))));
 
 template <typename T>
 std::vector<Case> SinCases() {
-    std::vector<Case> cases = {
+    return {
         C({-T(0)}, -T(0)),
         C({T(0)}, T(0)),
         C({T(0.75)}, T(0.68163876)).FloatComp(),
@@ -2242,8 +2248,6 @@
         // Vector test
         C({Vec(T(0), -T(0), T(0.75))}, Vec(T(0), -T(0), T(0.68163876))).FloatComp(),
     };
-
-    return cases;
 }
 INSTANTIATE_TEST_SUITE_P(  //
     Sin,
@@ -2258,7 +2262,7 @@
     auto error_msg = [](auto a) {
         return "12:34 error: " + OverflowErrorMessage(a, FriendlyName<decltype(a)>());
     };
-    std::vector<Case> cases = {
+    return {
         C({T(0)}, T(0)),
         C({-T(0)}, -T(0)),
         C({T(1)}, T(1.1752012)).FloatComp(),
@@ -2269,7 +2273,6 @@
 
         E({T(10000)}, error_msg(T::Inf())),
     };
-    return cases;
 }
 INSTANTIATE_TEST_SUITE_P(  //
     Sinh,
@@ -2346,7 +2349,7 @@
 
 template <typename T>
 std::vector<Case> SqrtCases() {
-    std::vector<Case> cases = {
+    return {
         C({-T(0)}, -T(0)),  //
         C({T(0)}, T(0)),    //
         C({T(25)}, T(5)),
@@ -2356,7 +2359,6 @@
 
         E({-T(25)}, "12:34 error: sqrt must be called with a value >= 0"),
     };
-    return cases;
 }
 INSTANTIATE_TEST_SUITE_P(  //
     Sqrt,
@@ -2368,7 +2370,7 @@
 
 template <typename T>
 std::vector<Case> TanCases() {
-    std::vector<Case> cases = {
+    return {
         C({-T(0)}, -T(0)),
         C({T(0)}, T(0)),
         C({T(.75)}, T(0.9315964599)).FloatComp(),
@@ -2376,8 +2378,6 @@
         // Vector test
         C({Vec(T(0), -T(0), T(.75))}, Vec(T(0), -T(0), T(0.9315964599))).FloatComp(),
     };
-
-    return cases;
 }
 INSTANTIATE_TEST_SUITE_P(  //
     Tan,
@@ -2389,7 +2389,7 @@
 
 template <typename T>
 std::vector<Case> TanhCases() {
-    std::vector<Case> cases = {
+    return {
         C({T(0)}, T(0)),
         C({-T(0)}, -T(0)),
         C({T(1)}, T(0.761594156)).FloatComp(),
@@ -2398,8 +2398,6 @@
         // Vector tests
         C({Vec(T(0), -T(0), T(1))}, Vec(T(0), -T(0), T(0.761594156))).FloatComp(),
     };
-
-    return cases;
 }
 INSTANTIATE_TEST_SUITE_P(  //
     Tanh,
@@ -2477,15 +2475,13 @@
 
 template <typename T>
 std::vector<Case> TruncCases() {
-    std::vector<Case> cases = {C({T(0)}, T(0)),    //
-                               C({-T(0)}, -T(0)),  //
-                               C({T(1.5)}, T(1)),  //
-                               C({-T(1.5)}, -T(1)),
+    return {C({T(0)}, T(0)),    //
+            C({-T(0)}, -T(0)),  //
+            C({T(1.5)}, T(1)),  //
+            C({-T(1.5)}, -T(1)),
 
-                               // Vector tests
-                               C({Vec(T(0.0), T(1.5), -T(2.2))}, Vec(T(0), T(1), -T(2)))};
-
-    return cases;
+            // Vector tests
+            C({Vec(T(0.0), T(1.5), -T(2.2))}, Vec(T(0), T(1), -T(2)))};
 }
 INSTANTIATE_TEST_SUITE_P(  //
     Trunc,
diff --git a/src/tint/resolver/const_eval_test.h b/src/tint/resolver/const_eval_test.h
index 42b6f92..27b5e54 100644
--- a/src/tint/resolver/const_eval_test.h
+++ b/src/tint/resolver/const_eval_test.h
@@ -23,7 +23,7 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "src/tint/resolver/resolver_test_helper.h"
-#include "src/tint/sem/test_helper.h"
+#include "src/tint/type/test_helper.h"
 
 namespace tint::resolver {
 
diff --git a/src/tint/resolver/inferred_type_test.cc b/src/tint/resolver/inferred_type_test.cc
index 025ea95..4b3100b 100644
--- a/src/tint/resolver/inferred_type_test.cc
+++ b/src/tint/resolver/inferred_type_test.cc
@@ -136,7 +136,7 @@
 TEST_F(ResolverInferredTypeTest, InferArray_Pass) {
     auto* type = ty.array(ty.u32(), 10_u);
     auto* expected_type = create<sem::Array>(
-        create<sem::U32>(), create<sem::ConstantArrayCount>(10u), 4u, 4u * 10u, 4u, 4u);
+        create<sem::U32>(), create<type::ConstantArrayCount>(10u), 4u, 4u * 10u, 4u, 4u);
 
     auto* ctor_expr = Construct(type);
     auto* var = Var("a", ast::AddressSpace::kFunction, ctor_expr);
diff --git a/src/tint/resolver/intrinsic_table.cc b/src/tint/resolver/intrinsic_table.cc
index 2578881..f2fb2ce 100644
--- a/src/tint/resolver/intrinsic_table.cc
+++ b/src/tint/resolver/intrinsic_table.cc
@@ -55,14 +55,14 @@
 constexpr static const size_t kNumFixedCandidates = 8;
 
 /// A special type that matches all TypeMatchers
-class Any final : public Castable<Any, sem::Type> {
+class Any final : public Castable<Any, type::Type> {
   public:
-    Any() : Base(sem::TypeFlags{}) {}
+    Any() : Base(type::TypeFlags{}) {}
     ~Any() override = default;
 
-    // Stub implementations for sem::Type conformance.
+    // Stub implementations for type::Type conformance.
     size_t Hash() const override { return 0; }
-    bool Equals(const sem::Type&) const override { return false; }
+    bool Equals(const type::Type&) const override { return false; }
     std::string FriendlyName(const SymbolTable&) const override { return "<any>"; }
 };
 
@@ -122,7 +122,7 @@
     /// template type is replaced with `ty`, and `ty` is returned.
     /// If none of the above applies, then `ty` is a type mismatch for the template type, and
     /// nullptr is returned.
-    const sem::Type* Type(size_t idx, const sem::Type* ty) {
+    const type::Type* Type(size_t idx, const type::Type* ty) {
         if (idx >= types_.Length()) {
             types_.Resize(idx + 1);
         }
@@ -131,7 +131,7 @@
             t = ty;
             return ty;
         }
-        ty = sem::Type::Common(utils::Vector{t, ty});
+        ty = type::Type::Common(utils::Vector{t, ty});
         if (ty) {
             t = ty;
         }
@@ -154,7 +154,7 @@
     }
 
     /// Type returns the template type with index `idx`, or nullptr if the type was not defined.
-    const sem::Type* Type(size_t idx) const {
+    const type::Type* Type(size_t idx) const {
         if (idx >= types_.Length()) {
             return nullptr;
         }
@@ -162,7 +162,7 @@
     }
 
     /// SetType replaces the template type with index `idx` with type `ty`.
-    void SetType(size_t idx, const sem::Type* ty) {
+    void SetType(size_t idx, const type::Type* ty) {
         if (idx >= types_.Length()) {
             types_.Resize(idx + 1);
         }
@@ -178,7 +178,7 @@
     }
 
   private:
-    utils::Vector<const sem::Type*, 4> types_;
+    utils::Vector<const type::Type*, 4> types_;
     utils::Vector<Number, 2> numbers_;
 };
 
@@ -219,7 +219,7 @@
     /// `ty`. If the type matches, the canonical expected type is returned. If the
     /// type `ty` does not match, then nullptr is returned.
     /// @note: The matcher indices are progressed on calling.
-    const sem::Type* Type(const sem::Type* ty);
+    const type::Type* Type(const type::Type* ty);
 
     /// Num uses the next NumMatcher from the matcher indices to match the number
     /// `num`. If the number matches, the canonical expected number is returned.
@@ -253,7 +253,7 @@
     /// Match may define and refine the template types and numbers in state.
     /// @param type the type to match
     /// @returns the canonicalized type on match, otherwise nullptr
-    virtual const sem::Type* Match(MatchState& state, const sem::Type* type) const = 0;
+    virtual const type::Type* Match(MatchState& state, const type::Type* type) const = 0;
 
     /// @return a string representation of the matcher. Used for printing error
     /// messages when no overload is found.
@@ -286,7 +286,7 @@
     /// Constructor
     explicit TemplateTypeMatcher(size_t index) : index_(index) {}
 
-    const sem::Type* Match(MatchState& state, const sem::Type* type) const override {
+    const type::Type* Match(MatchState& state, const type::Type* type) const override {
         if (type->Is<Any>()) {
             return state.templates.Type(index_);
         }
@@ -348,7 +348,7 @@
 // An enum set of OverloadFlag, used by OperatorInfo
 using OverloadFlags = utils::EnumSet<OverloadFlag>;
 
-bool match_bool(MatchState&, const sem::Type* ty) {
+bool match_bool(MatchState&, const type::Type* ty) {
     return ty->IsAnyOf<Any, sem::Bool>();
 }
 
@@ -356,7 +356,7 @@
     return state.builder.create<sem::AbstractFloat>();
 }
 
-bool match_fa(MatchState& state, const sem::Type* ty) {
+bool match_fa(MatchState& state, const type::Type* ty) {
     return (state.earliest_eval_stage == sem::EvaluationStage::kConstant) &&
            ty->IsAnyOf<Any, sem::AbstractNumeric>();
 }
@@ -365,7 +365,7 @@
     return state.builder.create<sem::AbstractInt>();
 }
 
-bool match_ia(MatchState& state, const sem::Type* ty) {
+bool match_ia(MatchState& state, const type::Type* ty) {
     return (state.earliest_eval_stage == sem::EvaluationStage::kConstant) &&
            ty->IsAnyOf<Any, sem::AbstractInt>();
 }
@@ -378,7 +378,7 @@
     return state.builder.create<sem::F16>();
 }
 
-bool match_f16(MatchState&, const sem::Type* ty) {
+bool match_f16(MatchState&, const type::Type* ty) {
     return ty->IsAnyOf<Any, sem::F16, sem::AbstractNumeric>();
 }
 
@@ -386,7 +386,7 @@
     return state.builder.create<sem::F32>();
 }
 
-bool match_f32(MatchState&, const sem::Type* ty) {
+bool match_f32(MatchState&, const type::Type* ty) {
     return ty->IsAnyOf<Any, sem::F32, sem::AbstractNumeric>();
 }
 
@@ -394,7 +394,7 @@
     return state.builder.create<sem::I32>();
 }
 
-bool match_i32(MatchState&, const sem::Type* ty) {
+bool match_i32(MatchState&, const type::Type* ty) {
     return ty->IsAnyOf<Any, sem::I32, sem::AbstractInt>();
 }
 
@@ -402,11 +402,11 @@
     return state.builder.create<sem::U32>();
 }
 
-bool match_u32(MatchState&, const sem::Type* ty) {
+bool match_u32(MatchState&, const type::Type* ty) {
     return ty->IsAnyOf<Any, sem::U32, sem::AbstractInt>();
 }
 
-bool match_vec(MatchState&, const sem::Type* ty, Number& N, const sem::Type*& T) {
+bool match_vec(MatchState&, const type::Type* ty, Number& N, const type::Type*& T) {
     if (ty->Is<Any>()) {
         N = Number::any;
         T = ty;
@@ -422,7 +422,7 @@
 }
 
 template <uint32_t N>
-bool match_vec(MatchState&, const sem::Type* ty, const sem::Type*& T) {
+bool match_vec(MatchState&, const type::Type* ty, const type::Type*& T) {
     if (ty->Is<Any>()) {
         T = ty;
         return true;
@@ -437,12 +437,12 @@
     return false;
 }
 
-const sem::Vector* build_vec(MatchState& state, Number N, const sem::Type* el) {
+const sem::Vector* build_vec(MatchState& state, Number N, const type::Type* el) {
     return state.builder.create<sem::Vector>(el, N.Value());
 }
 
 template <uint32_t N>
-const sem::Vector* build_vec(MatchState& state, const sem::Type* el) {
+const sem::Vector* build_vec(MatchState& state, const type::Type* el) {
     return state.builder.create<sem::Vector>(el, N);
 }
 
@@ -454,7 +454,7 @@
 constexpr auto build_vec3 = build_vec<3>;
 constexpr auto build_vec4 = build_vec<4>;
 
-bool match_mat(MatchState&, const sem::Type* ty, Number& M, Number& N, const sem::Type*& T) {
+bool match_mat(MatchState&, const type::Type* ty, Number& M, Number& N, const type::Type*& T) {
     if (ty->Is<Any>()) {
         M = Number::any;
         N = Number::any;
@@ -471,7 +471,7 @@
 }
 
 template <uint32_t C, uint32_t R>
-bool match_mat(MatchState&, const sem::Type* ty, const sem::Type*& T) {
+bool match_mat(MatchState&, const type::Type* ty, const type::Type*& T) {
     if (ty->Is<Any>()) {
         T = ty;
         return true;
@@ -485,13 +485,13 @@
     return false;
 }
 
-const sem::Matrix* build_mat(MatchState& state, Number C, Number R, const sem::Type* T) {
+const sem::Matrix* build_mat(MatchState& state, Number C, Number R, const type::Type* T) {
     auto* column_type = state.builder.create<sem::Vector>(T, R.Value());
     return state.builder.create<sem::Matrix>(column_type, C.Value());
 }
 
 template <uint32_t C, uint32_t R>
-const sem::Matrix* build_mat(MatchState& state, const sem::Type* T) {
+const sem::Matrix* build_mat(MatchState& state, const type::Type* T) {
     auto* column_type = state.builder.create<sem::Vector>(T, R);
     return state.builder.create<sem::Matrix>(column_type, C);
 }
@@ -516,14 +516,14 @@
 constexpr auto match_mat4x3 = match_mat<4, 3>;
 constexpr auto match_mat4x4 = match_mat<4, 4>;
 
-bool match_array(MatchState&, const sem::Type* ty, const sem::Type*& T) {
+bool match_array(MatchState&, const type::Type* ty, const type::Type*& T) {
     if (ty->Is<Any>()) {
         T = ty;
         return true;
     }
 
     if (auto* a = ty->As<sem::Array>()) {
-        if (a->Count()->Is<sem::RuntimeArrayCount>()) {
+        if (a->Count()->Is<type::RuntimeArrayCount>()) {
             T = a->ElemType();
             return true;
         }
@@ -531,17 +531,17 @@
     return false;
 }
 
-const sem::Array* build_array(MatchState& state, const sem::Type* el) {
+const sem::Array* build_array(MatchState& state, const type::Type* el) {
     return state.builder.create<sem::Array>(
         el,
-        /* count */ state.builder.create<sem::RuntimeArrayCount>(),
+        /* count */ state.builder.create<type::RuntimeArrayCount>(),
         /* align */ 0u,
         /* size */ 0u,
         /* stride */ 0u,
         /* stride_implicit */ 0u);
 }
 
-bool match_ptr(MatchState&, const sem::Type* ty, Number& S, const sem::Type*& T, Number& A) {
+bool match_ptr(MatchState&, const type::Type* ty, Number& S, const type::Type*& T, Number& A) {
     if (ty->Is<Any>()) {
         S = Number::any;
         T = ty;
@@ -558,12 +558,12 @@
     return false;
 }
 
-const sem::Pointer* build_ptr(MatchState& state, Number S, const sem::Type* T, Number& A) {
+const sem::Pointer* build_ptr(MatchState& state, Number S, const type::Type* T, Number& A) {
     return state.builder.create<sem::Pointer>(T, static_cast<ast::AddressSpace>(S.Value()),
                                               static_cast<ast::Access>(A.Value()));
 }
 
-bool match_atomic(MatchState&, const sem::Type* ty, const sem::Type*& T) {
+bool match_atomic(MatchState&, const type::Type* ty, const type::Type*& T) {
     if (ty->Is<Any>()) {
         T = ty;
         return true;
@@ -576,11 +576,11 @@
     return false;
 }
 
-const sem::Atomic* build_atomic(MatchState& state, const sem::Type* T) {
+const sem::Atomic* build_atomic(MatchState& state, const type::Type* T) {
     return state.builder.create<sem::Atomic>(T);
 }
 
-bool match_sampler(MatchState&, const sem::Type* ty) {
+bool match_sampler(MatchState&, const type::Type* ty) {
     if (ty->Is<Any>()) {
         return true;
     }
@@ -591,7 +591,7 @@
     return state.builder.create<sem::Sampler>(ast::SamplerKind::kSampler);
 }
 
-bool match_sampler_comparison(MatchState&, const sem::Type* ty) {
+bool match_sampler_comparison(MatchState&, const type::Type* ty) {
     if (ty->Is<Any>()) {
         return true;
     }
@@ -604,9 +604,9 @@
 }
 
 bool match_texture(MatchState&,
-                   const sem::Type* ty,
+                   const type::Type* ty,
                    ast::TextureDimension dim,
-                   const sem::Type*& T) {
+                   const type::Type*& T) {
     if (ty->Is<Any>()) {
         T = ty;
         return true;
@@ -622,14 +622,14 @@
 
 #define JOIN(a, b) a##b
 
-#define DECLARE_SAMPLED_TEXTURE(suffix, dim)                                      \
-    bool JOIN(match_texture_, suffix)(MatchState & state, const sem::Type* ty,    \
-                                      const sem::Type*& T) {                      \
-        return match_texture(state, ty, dim, T);                                  \
-    }                                                                             \
-    const sem::SampledTexture* JOIN(build_texture_, suffix)(MatchState & state,   \
-                                                            const sem::Type* T) { \
-        return state.builder.create<sem::SampledTexture>(dim, T);                 \
+#define DECLARE_SAMPLED_TEXTURE(suffix, dim)                                       \
+    bool JOIN(match_texture_, suffix)(MatchState & state, const type::Type* ty,    \
+                                      const type::Type*& T) {                      \
+        return match_texture(state, ty, dim, T);                                   \
+    }                                                                              \
+    const sem::SampledTexture* JOIN(build_texture_, suffix)(MatchState & state,    \
+                                                            const type::Type* T) { \
+        return state.builder.create<sem::SampledTexture>(dim, T);                  \
     }
 
 DECLARE_SAMPLED_TEXTURE(1d, ast::TextureDimension::k1d)
@@ -641,9 +641,9 @@
 #undef DECLARE_SAMPLED_TEXTURE
 
 bool match_texture_multisampled(MatchState&,
-                                const sem::Type* ty,
+                                const type::Type* ty,
                                 ast::TextureDimension dim,
-                                const sem::Type*& T) {
+                                const type::Type*& T) {
     if (ty->Is<Any>()) {
         T = ty;
         return true;
@@ -657,32 +657,32 @@
     return false;
 }
 
-#define DECLARE_MULTISAMPLED_TEXTURE(suffix, dim)                                           \
-    bool JOIN(match_texture_multisampled_, suffix)(MatchState & state, const sem::Type* ty, \
-                                                   const sem::Type*& T) {                   \
-        return match_texture_multisampled(state, ty, dim, T);                               \
-    }                                                                                       \
-    const sem::MultisampledTexture* JOIN(build_texture_multisampled_, suffix)(              \
-        MatchState & state, const sem::Type* T) {                                           \
-        return state.builder.create<sem::MultisampledTexture>(dim, T);                      \
+#define DECLARE_MULTISAMPLED_TEXTURE(suffix, dim)                                            \
+    bool JOIN(match_texture_multisampled_, suffix)(MatchState & state, const type::Type* ty, \
+                                                   const type::Type*& T) {                   \
+        return match_texture_multisampled(state, ty, dim, T);                                \
+    }                                                                                        \
+    const sem::MultisampledTexture* JOIN(build_texture_multisampled_, suffix)(               \
+        MatchState & state, const type::Type* T) {                                           \
+        return state.builder.create<sem::MultisampledTexture>(dim, T);                       \
     }
 
 DECLARE_MULTISAMPLED_TEXTURE(2d, ast::TextureDimension::k2d)
 #undef DECLARE_MULTISAMPLED_TEXTURE
 
-bool match_texture_depth(MatchState&, const sem::Type* ty, ast::TextureDimension dim) {
+bool match_texture_depth(MatchState&, const type::Type* ty, ast::TextureDimension dim) {
     if (ty->Is<Any>()) {
         return true;
     }
     return ty->Is([&](const sem::DepthTexture* t) { return t->dim() == dim; });
 }
 
-#define DECLARE_DEPTH_TEXTURE(suffix, dim)                                             \
-    bool JOIN(match_texture_depth_, suffix)(MatchState & state, const sem::Type* ty) { \
-        return match_texture_depth(state, ty, dim);                                    \
-    }                                                                                  \
-    const sem::DepthTexture* JOIN(build_texture_depth_, suffix)(MatchState & state) {  \
-        return state.builder.create<sem::DepthTexture>(dim);                           \
+#define DECLARE_DEPTH_TEXTURE(suffix, dim)                                              \
+    bool JOIN(match_texture_depth_, suffix)(MatchState & state, const type::Type* ty) { \
+        return match_texture_depth(state, ty, dim);                                     \
+    }                                                                                   \
+    const sem::DepthTexture* JOIN(build_texture_depth_, suffix)(MatchState & state) {   \
+        return state.builder.create<sem::DepthTexture>(dim);                            \
     }
 
 DECLARE_DEPTH_TEXTURE(2d, ast::TextureDimension::k2d)
@@ -691,7 +691,7 @@
 DECLARE_DEPTH_TEXTURE(cube_array, ast::TextureDimension::kCubeArray)
 #undef DECLARE_DEPTH_TEXTURE
 
-bool match_texture_depth_multisampled_2d(MatchState&, const sem::Type* ty) {
+bool match_texture_depth_multisampled_2d(MatchState&, const type::Type* ty) {
     if (ty->Is<Any>()) {
         return true;
     }
@@ -705,7 +705,7 @@
 }
 
 bool match_texture_storage(MatchState&,
-                           const sem::Type* ty,
+                           const type::Type* ty,
                            ast::TextureDimension dim,
                            Number& F,
                            Number& A) {
@@ -724,17 +724,17 @@
     return false;
 }
 
-#define DECLARE_STORAGE_TEXTURE(suffix, dim)                                                      \
-    bool JOIN(match_texture_storage_, suffix)(MatchState & state, const sem::Type* ty, Number& F, \
-                                              Number& A) {                                        \
-        return match_texture_storage(state, ty, dim, F, A);                                       \
-    }                                                                                             \
-    const sem::StorageTexture* JOIN(build_texture_storage_, suffix)(MatchState & state, Number F, \
-                                                                    Number A) {                   \
-        auto format = static_cast<TexelFormat>(F.Value());                                        \
-        auto access = static_cast<Access>(A.Value());                                             \
-        auto* T = sem::StorageTexture::SubtypeFor(format, state.builder.Types());                 \
-        return state.builder.create<sem::StorageTexture>(dim, format, access, T);                 \
+#define DECLARE_STORAGE_TEXTURE(suffix, dim)                                                       \
+    bool JOIN(match_texture_storage_, suffix)(MatchState & state, const type::Type* ty, Number& F, \
+                                              Number& A) {                                         \
+        return match_texture_storage(state, ty, dim, F, A);                                        \
+    }                                                                                              \
+    const sem::StorageTexture* JOIN(build_texture_storage_, suffix)(MatchState & state, Number F,  \
+                                                                    Number A) {                    \
+        auto format = static_cast<TexelFormat>(F.Value());                                         \
+        auto access = static_cast<Access>(A.Value());                                              \
+        auto* T = sem::StorageTexture::SubtypeFor(format, state.builder.Types());                  \
+        return state.builder.create<sem::StorageTexture>(dim, format, access, T);                  \
     }
 
 DECLARE_STORAGE_TEXTURE(1d, ast::TextureDimension::k1d)
@@ -743,7 +743,7 @@
 DECLARE_STORAGE_TEXTURE(3d, ast::TextureDimension::k3d)
 #undef DECLARE_STORAGE_TEXTURE
 
-bool match_texture_external(MatchState&, const sem::Type* ty) {
+bool match_texture_external(MatchState&, const type::Type* ty) {
     return ty->IsAnyOf<Any, sem::ExternalTexture>();
 }
 
@@ -754,14 +754,14 @@
 // Builtin types starting with a _ prefix cannot be declared in WGSL, so they
 // can only be used as return types. Because of this, they must only match Any,
 // which is used as the return type matcher.
-bool match_modf_result(MatchState&, const sem::Type* ty, const sem::Type*& T) {
+bool match_modf_result(MatchState&, const type::Type* ty, const type::Type*& T) {
     if (!ty->Is<Any>()) {
         return false;
     }
     T = ty;
     return true;
 }
-bool match_modf_result_vec(MatchState&, const sem::Type* ty, Number& N, const sem::Type*& T) {
+bool match_modf_result_vec(MatchState&, const type::Type* ty, Number& N, const type::Type*& T) {
     if (!ty->Is<Any>()) {
         return false;
     }
@@ -769,14 +769,14 @@
     T = ty;
     return true;
 }
-bool match_frexp_result(MatchState&, const sem::Type* ty, const sem::Type*& T) {
+bool match_frexp_result(MatchState&, const type::Type* ty, const type::Type*& T) {
     if (!ty->Is<Any>()) {
         return false;
     }
     T = ty;
     return true;
 }
-bool match_frexp_result_vec(MatchState&, const sem::Type* ty, Number& N, const sem::Type*& T) {
+bool match_frexp_result_vec(MatchState&, const type::Type* ty, Number& N, const type::Type*& T) {
     if (!ty->Is<Any>()) {
         return false;
     }
@@ -785,7 +785,7 @@
     return true;
 }
 
-bool match_atomic_compare_exchange_result(MatchState&, const sem::Type* ty, const sem::Type*& T) {
+bool match_atomic_compare_exchange_result(MatchState&, const type::Type* ty, const type::Type*& T) {
     if (ty->Is<Any>()) {
         T = ty;
         return true;
@@ -795,7 +795,7 @@
 
 struct NameAndType {
     std::string name;
-    const sem::Type* type;
+    const type::Type* type;
 };
 sem::Struct* build_struct(ProgramBuilder& b,
                           std::string name,
@@ -832,7 +832,7 @@
         /* size_no_padding */ size_without_padding);
 }
 
-const sem::Struct* build_modf_result(MatchState& state, const sem::Type* el) {
+const sem::Struct* build_modf_result(MatchState& state, const type::Type* el) {
     auto build_f32 = [&] {
         auto* ty = state.builder.create<sem::F32>();
         return build_struct(state.builder, "__modf_result_f32", {{"fract", ty}, {"whole", ty}});
@@ -859,7 +859,7 @@
         });
 }
 
-const sem::Struct* build_modf_result_vec(MatchState& state, Number& n, const sem::Type* el) {
+const sem::Struct* build_modf_result_vec(MatchState& state, Number& n, const type::Type* el) {
     auto prefix = "__modf_result_vec" + std::to_string(n.Value());
     auto build_f32 = [&] {
         auto* vec = state.builder.create<sem::Vector>(state.builder.create<sem::F32>(), n.Value());
@@ -888,7 +888,7 @@
         });
 }
 
-const sem::Struct* build_frexp_result(MatchState& state, const sem::Type* el) {
+const sem::Struct* build_frexp_result(MatchState& state, const type::Type* el) {
     auto build_f32 = [&] {
         auto* f = state.builder.create<sem::F32>();
         auto* i = state.builder.create<sem::I32>();
@@ -918,7 +918,7 @@
         });
 }
 
-const sem::Struct* build_frexp_result_vec(MatchState& state, Number& n, const sem::Type* el) {
+const sem::Struct* build_frexp_result_vec(MatchState& state, Number& n, const type::Type* el) {
     auto prefix = "__frexp_result_vec" + std::to_string(n.Value());
     auto build_f32 = [&] {
         auto* f = state.builder.create<sem::Vector>(state.builder.create<sem::F32>(), n.Value());
@@ -951,11 +951,11 @@
         });
 }
 
-const sem::Struct* build_atomic_compare_exchange_result(MatchState& state, const sem::Type* ty) {
+const sem::Struct* build_atomic_compare_exchange_result(MatchState& state, const type::Type* ty) {
     return build_struct(
         state.builder,
         "__atomic_compare_exchange_result" + ty->FriendlyName(state.builder.Symbols()),
-        {{"old_value", const_cast<sem::Type*>(ty)},
+        {{"old_value", const_cast<type::Type*>(ty)},
          {"exchanged", state.builder.create<sem::Bool>()}});
 }
 
@@ -1028,7 +1028,7 @@
     /// Parameter describes a single parameter
     struct Parameter {
         /// Parameter type
-        const sem::Type* const type;
+        const type::Type* const type;
         /// Parameter usage
         ParameterUsage const usage = ParameterUsage::kNone;
     };
@@ -1047,7 +1047,7 @@
     };
 
     const OverloadInfo* overload = nullptr;
-    sem::Type const* return_type = nullptr;
+    type::Type const* return_type = nullptr;
     utils::Vector<Parameter, kNumFixedParams> parameters;
 };
 
@@ -1073,25 +1073,25 @@
     explicit Impl(ProgramBuilder& builder);
 
     Builtin Lookup(sem::BuiltinType builtin_type,
-                   utils::VectorRef<const sem::Type*> args,
+                   utils::VectorRef<const type::Type*> args,
                    sem::EvaluationStage earliest_eval_stage,
                    const Source& source) override;
 
     UnaryOperator Lookup(ast::UnaryOp op,
-                         const sem::Type* arg,
+                         const type::Type* arg,
                          sem::EvaluationStage earliest_eval_stage,
                          const Source& source) override;
 
     BinaryOperator Lookup(ast::BinaryOp op,
-                          const sem::Type* lhs,
-                          const sem::Type* rhs,
+                          const type::Type* lhs,
+                          const type::Type* rhs,
                           sem::EvaluationStage earliest_eval_stage,
                           const Source& source,
                           bool is_compound) override;
 
     InitOrConv Lookup(InitConvIntrinsic type,
-                      const sem::Type* template_arg,
-                      utils::VectorRef<const sem::Type*> args,
+                      const type::Type* template_arg,
+                      utils::VectorRef<const type::Type*> args,
                       sem::EvaluationStage earliest_eval_stage,
                       const Source& source) override;
 
@@ -1137,7 +1137,7 @@
     ///          IntrinsicPrototype::return_type.
     IntrinsicPrototype MatchIntrinsic(const IntrinsicInfo& intrinsic,
                                       const char* intrinsic_name,
-                                      utils::VectorRef<const sem::Type*> args,
+                                      utils::VectorRef<const type::Type*> args,
                                       sem::EvaluationStage earliest_eval_stage,
                                       TemplateState templates,
                                       OnNoMatch on_no_match) const;
@@ -1150,7 +1150,7 @@
     ///                  template as `f32`.
     /// @returns the evaluated Candidate information.
     Candidate ScoreOverload(const OverloadInfo* overload,
-                            utils::VectorRef<const sem::Type*> args,
+                            utils::VectorRef<const type::Type*> args,
                             sem::EvaluationStage earliest_eval_stage,
                             TemplateState templates) const;
 
@@ -1166,7 +1166,7 @@
     /// @returns the resolved Candidate.
     Candidate ResolveCandidate(Candidates&& candidates,
                                const char* intrinsic_name,
-                               utils::VectorRef<const sem::Type*> args,
+                               utils::VectorRef<const type::Type*> args,
                                TemplateState templates) const;
 
     /// Match constructs a new MatchState
@@ -1190,7 +1190,7 @@
 
     /// Raises an error when no overload is a clear winner of overload resolution
     void ErrAmbiguousOverload(const char* intrinsic_name,
-                              utils::VectorRef<const sem::Type*> args,
+                              utils::VectorRef<const type::Type*> args,
                               TemplateState templates,
                               utils::VectorRef<Candidate> candidates) const;
 
@@ -1207,8 +1207,8 @@
 /// types.
 std::string CallSignature(ProgramBuilder& builder,
                           const char* intrinsic_name,
-                          utils::VectorRef<const sem::Type*> args,
-                          const sem::Type* template_arg = nullptr) {
+                          utils::VectorRef<const type::Type*> args,
+                          const type::Type* template_arg = nullptr) {
     std::stringstream ss;
     ss << intrinsic_name;
     if (template_arg) {
@@ -1241,7 +1241,7 @@
 Impl::Impl(ProgramBuilder& b) : builder(b) {}
 
 Impl::Builtin Impl::Lookup(sem::BuiltinType builtin_type,
-                           utils::VectorRef<const sem::Type*> args,
+                           utils::VectorRef<const type::Type*> args,
                            sem::EvaluationStage earliest_eval_stage,
                            const Source& source) {
     const char* intrinsic_name = sem::str(builtin_type);
@@ -1295,7 +1295,7 @@
 }
 
 IntrinsicTable::UnaryOperator Impl::Lookup(ast::UnaryOp op,
-                                           const sem::Type* arg,
+                                           const type::Type* arg,
                                            sem::EvaluationStage earliest_eval_stage,
                                            const Source& source) {
     auto [intrinsic_index, intrinsic_name] = [&]() -> std::pair<size_t, const char*> {
@@ -1341,8 +1341,8 @@
 }
 
 IntrinsicTable::BinaryOperator Impl::Lookup(ast::BinaryOp op,
-                                            const sem::Type* lhs,
-                                            const sem::Type* rhs,
+                                            const type::Type* lhs,
+                                            const type::Type* rhs,
                                             sem::EvaluationStage earliest_eval_stage,
                                             const Source& source,
                                             bool is_compound) {
@@ -1420,8 +1420,8 @@
 }
 
 IntrinsicTable::InitOrConv Impl::Lookup(InitConvIntrinsic type,
-                                        const sem::Type* template_arg,
-                                        utils::VectorRef<const sem::Type*> args,
+                                        const type::Type* template_arg,
+                                        utils::VectorRef<const type::Type*> args,
                                         sem::EvaluationStage earliest_eval_stage,
                                         const Source& source) {
     auto name = str(type);
@@ -1499,7 +1499,7 @@
 
 IntrinsicPrototype Impl::MatchIntrinsic(const IntrinsicInfo& intrinsic,
                                         const char* intrinsic_name,
-                                        utils::VectorRef<const sem::Type*> args,
+                                        utils::VectorRef<const type::Type*> args,
                                         sem::EvaluationStage earliest_eval_stage,
                                         TemplateState templates,
                                         OnNoMatch on_no_match) const {
@@ -1539,7 +1539,7 @@
     }
 
     // Build the return type
-    const sem::Type* return_type = nullptr;
+    const type::Type* return_type = nullptr;
     if (auto* indices = match.overload->return_matcher_indices) {
         Any any;
         return_type =
@@ -1556,7 +1556,7 @@
 }
 
 Impl::Candidate Impl::ScoreOverload(const OverloadInfo* overload,
-                                    utils::VectorRef<const sem::Type*> args,
+                                    utils::VectorRef<const type::Type*> args,
                                     sem::EvaluationStage earliest_eval_stage,
                                     TemplateState templates) const {
     // Penalty weights for overload mismatching.
@@ -1656,7 +1656,7 @@
 
 Impl::Candidate Impl::ResolveCandidate(Impl::Candidates&& candidates,
                                        const char* intrinsic_name,
-                                       utils::VectorRef<const sem::Type*> args,
+                                       utils::VectorRef<const type::Type*> args,
                                        TemplateState templates) const {
     utils::Vector<uint32_t, kNumFixedParams> best_ranks;
     best_ranks.Resize(args.Length(), 0xffffffff);
@@ -1669,7 +1669,7 @@
         bool some_won = false;   // An argument ranked less than the 'best' overload's argument
         bool some_lost = false;  // An argument ranked more than the 'best' overload's argument
         for (size_t i = 0; i < args.Length(); i++) {
-            auto rank = sem::Type::ConversionRank(args[i], candidate.parameters[i].type);
+            auto rank = type::Type::ConversionRank(args[i], candidate.parameters[i].type);
             if (best_ranks[i] > rank) {
                 best_ranks[i] = rank;
                 some_won = true;
@@ -1808,7 +1808,7 @@
     }
 }
 
-const sem::Type* MatchState::Type(const sem::Type* ty) {
+const type::Type* MatchState::Type(const type::Type* ty) {
     MatcherIndex matcher_index = *matcher_indices_++;
     auto* matcher = matchers.type[matcher_index];
     return matcher->Match(*this, ty);
@@ -1833,7 +1833,7 @@
 }
 
 void Impl::ErrAmbiguousOverload(const char* intrinsic_name,
-                                utils::VectorRef<const sem::Type*> args,
+                                utils::VectorRef<const type::Type*> args,
                                 TemplateState templates,
                                 utils::VectorRef<Candidate> candidates) const {
     std::stringstream ss;
diff --git a/src/tint/resolver/intrinsic_table.h b/src/tint/resolver/intrinsic_table.h
index 05ca8d2..c49ed50 100644
--- a/src/tint/resolver/intrinsic_table.h
+++ b/src/tint/resolver/intrinsic_table.h
@@ -53,9 +53,9 @@
     /// UnaryOperator describes a resolved unary operator
     struct UnaryOperator {
         /// The result type of the unary operator
-        const sem::Type* result = nullptr;
+        const type::Type* result = nullptr;
         /// The type of the parameter of the unary operator
-        const sem::Type* parameter = nullptr;
+        const type::Type* parameter = nullptr;
         /// The constant evaluation function
         ConstEval::Function const_eval_fn = nullptr;
     };
@@ -63,11 +63,11 @@
     /// BinaryOperator describes a resolved binary operator
     struct BinaryOperator {
         /// The result type of the binary operator
-        const sem::Type* result = nullptr;
+        const type::Type* result = nullptr;
         /// The type of LHS parameter of the binary operator
-        const sem::Type* lhs = nullptr;
+        const type::Type* lhs = nullptr;
         /// The type of RHS parameter of the binary operator
-        const sem::Type* rhs = nullptr;
+        const type::Type* rhs = nullptr;
         /// The constant evaluation function
         ConstEval::Function const_eval_fn = nullptr;
     };
@@ -93,7 +93,7 @@
     /// @param source the source of the builtin call
     /// @return the semantic builtin if found, otherwise nullptr
     virtual Builtin Lookup(sem::BuiltinType type,
-                           utils::VectorRef<const sem::Type*> args,
+                           utils::VectorRef<const type::Type*> args,
                            sem::EvaluationStage earliest_eval_stage,
                            const Source& source) = 0;
 
@@ -111,7 +111,7 @@
     /// @return the operator call target signature. If the operator was not found
     ///         UnaryOperator::result will be nullptr.
     virtual UnaryOperator Lookup(ast::UnaryOp op,
-                                 const sem::Type* arg,
+                                 const type::Type* arg,
                                  sem::EvaluationStage earliest_eval_stage,
                                  const Source& source) = 0;
 
@@ -131,8 +131,8 @@
     /// @return the operator call target signature. If the operator was not found
     ///         BinaryOperator::result will be nullptr.
     virtual BinaryOperator Lookup(ast::BinaryOp op,
-                                  const sem::Type* lhs,
-                                  const sem::Type* rhs,
+                                  const type::Type* lhs,
+                                  const type::Type* rhs,
                                   sem::EvaluationStage earliest_eval_stage,
                                   const Source& source,
                                   bool is_compound) = 0;
@@ -151,8 +151,8 @@
     /// @param source the source of the call
     /// @return a sem::TypeInitializer, sem::TypeConversion or nullptr if nothing matched
     virtual InitOrConv Lookup(InitConvIntrinsic type,
-                              const sem::Type* template_arg,
-                              utils::VectorRef<const sem::Type*> args,
+                              const type::Type* template_arg,
+                              utils::VectorRef<const type::Type*> args,
                               sem::EvaluationStage earliest_eval_stage,
                               const Source& source) = 0;
 };
diff --git a/src/tint/resolver/intrinsic_table.inl b/src/tint/resolver/intrinsic_table.inl
index 3fedcd0..3ca1d07 100644
--- a/src/tint/resolver/intrinsic_table.inl
+++ b/src/tint/resolver/intrinsic_table.inl
@@ -30,14 +30,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Bool::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* Bool::Match(MatchState& state, const type::Type* ty) const {
   if (!match_bool(state, ty)) {
     return nullptr;
   }
@@ -56,14 +56,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Ia::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* Ia::Match(MatchState& state, const type::Type* ty) const {
   if (!match_ia(state, ty)) {
     return nullptr;
   }
@@ -84,14 +84,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Fa::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* Fa::Match(MatchState& state, const type::Type* ty) const {
   if (!match_fa(state, ty)) {
     return nullptr;
   }
@@ -112,14 +112,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* I32::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* I32::Match(MatchState& state, const type::Type* ty) const {
   if (!match_i32(state, ty)) {
     return nullptr;
   }
@@ -138,14 +138,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* U32::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* U32::Match(MatchState& state, const type::Type* ty) const {
   if (!match_u32(state, ty)) {
     return nullptr;
   }
@@ -164,14 +164,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* F32::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* F32::Match(MatchState& state, const type::Type* ty) const {
   if (!match_f32(state, ty)) {
     return nullptr;
   }
@@ -190,14 +190,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* F16::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* F16::Match(MatchState& state, const type::Type* ty) const {
   if (!match_f16(state, ty)) {
     return nullptr;
   }
@@ -216,15 +216,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Vec2::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Vec2::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_vec2(state, ty, T)) {
     return nullptr;
   }
@@ -248,15 +248,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Vec3::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Vec3::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_vec3(state, ty, T)) {
     return nullptr;
   }
@@ -280,15 +280,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Vec4::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Vec4::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_vec4(state, ty, T)) {
     return nullptr;
   }
@@ -312,15 +312,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Mat2X2::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Mat2X2::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_mat2x2(state, ty, T)) {
     return nullptr;
   }
@@ -344,15 +344,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Mat2X3::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Mat2X3::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_mat2x3(state, ty, T)) {
     return nullptr;
   }
@@ -376,15 +376,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Mat2X4::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Mat2X4::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_mat2x4(state, ty, T)) {
     return nullptr;
   }
@@ -408,15 +408,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Mat3X2::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Mat3X2::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_mat3x2(state, ty, T)) {
     return nullptr;
   }
@@ -440,15 +440,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Mat3X3::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Mat3X3::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_mat3x3(state, ty, T)) {
     return nullptr;
   }
@@ -472,15 +472,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Mat3X4::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Mat3X4::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_mat3x4(state, ty, T)) {
     return nullptr;
   }
@@ -504,15 +504,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Mat4X2::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Mat4X2::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_mat4x2(state, ty, T)) {
     return nullptr;
   }
@@ -536,15 +536,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Mat4X3::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Mat4X3::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_mat4x3(state, ty, T)) {
     return nullptr;
   }
@@ -568,15 +568,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Mat4X4::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Mat4X4::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_mat4x4(state, ty, T)) {
     return nullptr;
   }
@@ -600,16 +600,16 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Vec::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* Vec::Match(MatchState& state, const type::Type* ty) const {
   Number N = Number::invalid;
-  const sem::Type* T = nullptr;
+  const type::Type* T = nullptr;
   if (!match_vec(state, ty, N, T)) {
     return nullptr;
   }
@@ -640,17 +640,17 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Mat::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* Mat::Match(MatchState& state, const type::Type* ty) const {
   Number N = Number::invalid;
   Number M = Number::invalid;
-  const sem::Type* T = nullptr;
+  const type::Type* T = nullptr;
   if (!match_mat(state, ty, N, M, T)) {
     return nullptr;
   }
@@ -686,16 +686,16 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Ptr::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* Ptr::Match(MatchState& state, const type::Type* ty) const {
   Number S = Number::invalid;
-  const sem::Type* T = nullptr;
+  const type::Type* T = nullptr;
   Number A = Number::invalid;
   if (!match_ptr(state, ty, S, T, A)) {
     return nullptr;
@@ -730,15 +730,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Atomic::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Atomic::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_atomic(state, ty, T)) {
     return nullptr;
   }
@@ -762,15 +762,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Array::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Array::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_array(state, ty, T)) {
     return nullptr;
   }
@@ -794,14 +794,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Sampler::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* Sampler::Match(MatchState& state, const type::Type* ty) const {
   if (!match_sampler(state, ty)) {
     return nullptr;
   }
@@ -820,14 +820,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* SamplerComparison::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* SamplerComparison::Match(MatchState& state, const type::Type* ty) const {
   if (!match_sampler_comparison(state, ty)) {
     return nullptr;
   }
@@ -846,15 +846,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Texture1D::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Texture1D::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_texture_1d(state, ty, T)) {
     return nullptr;
   }
@@ -878,15 +878,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Texture2D::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Texture2D::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_texture_2d(state, ty, T)) {
     return nullptr;
   }
@@ -910,15 +910,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Texture2DArray::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Texture2DArray::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_texture_2d_array(state, ty, T)) {
     return nullptr;
   }
@@ -942,15 +942,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Texture3D::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* Texture3D::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_texture_3d(state, ty, T)) {
     return nullptr;
   }
@@ -974,15 +974,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* TextureCube::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* TextureCube::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_texture_cube(state, ty, T)) {
     return nullptr;
   }
@@ -1006,15 +1006,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* TextureCubeArray::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* TextureCubeArray::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_texture_cube_array(state, ty, T)) {
     return nullptr;
   }
@@ -1038,15 +1038,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* TextureMultisampled2D::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* TextureMultisampled2D::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_texture_multisampled_2d(state, ty, T)) {
     return nullptr;
   }
@@ -1070,14 +1070,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* TextureDepth2D::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* TextureDepth2D::Match(MatchState& state, const type::Type* ty) const {
   if (!match_texture_depth_2d(state, ty)) {
     return nullptr;
   }
@@ -1096,14 +1096,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* TextureDepth2DArray::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* TextureDepth2DArray::Match(MatchState& state, const type::Type* ty) const {
   if (!match_texture_depth_2d_array(state, ty)) {
     return nullptr;
   }
@@ -1122,14 +1122,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* TextureDepthCube::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* TextureDepthCube::Match(MatchState& state, const type::Type* ty) const {
   if (!match_texture_depth_cube(state, ty)) {
     return nullptr;
   }
@@ -1148,14 +1148,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* TextureDepthCubeArray::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* TextureDepthCubeArray::Match(MatchState& state, const type::Type* ty) const {
   if (!match_texture_depth_cube_array(state, ty)) {
     return nullptr;
   }
@@ -1174,14 +1174,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* TextureDepthMultisampled2D::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* TextureDepthMultisampled2D::Match(MatchState& state, const type::Type* ty) const {
   if (!match_texture_depth_multisampled_2d(state, ty)) {
     return nullptr;
   }
@@ -1200,14 +1200,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* TextureStorage1D::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* TextureStorage1D::Match(MatchState& state, const type::Type* ty) const {
   Number F = Number::invalid;
   Number A = Number::invalid;
   if (!match_texture_storage_1d(state, ty, F, A)) {
@@ -1238,14 +1238,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* TextureStorage2D::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* TextureStorage2D::Match(MatchState& state, const type::Type* ty) const {
   Number F = Number::invalid;
   Number A = Number::invalid;
   if (!match_texture_storage_2d(state, ty, F, A)) {
@@ -1276,14 +1276,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* TextureStorage2DArray::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* TextureStorage2DArray::Match(MatchState& state, const type::Type* ty) const {
   Number F = Number::invalid;
   Number A = Number::invalid;
   if (!match_texture_storage_2d_array(state, ty, F, A)) {
@@ -1314,14 +1314,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* TextureStorage3D::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* TextureStorage3D::Match(MatchState& state, const type::Type* ty) const {
   Number F = Number::invalid;
   Number A = Number::invalid;
   if (!match_texture_storage_3d(state, ty, F, A)) {
@@ -1352,14 +1352,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* TextureExternal::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* TextureExternal::Match(MatchState& state, const type::Type* ty) const {
   if (!match_texture_external(state, ty)) {
     return nullptr;
   }
@@ -1378,15 +1378,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* ModfResult::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* ModfResult::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_modf_result(state, ty, T)) {
     return nullptr;
   }
@@ -1412,16 +1412,16 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* ModfResultVec::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* ModfResultVec::Match(MatchState& state, const type::Type* ty) const {
   Number N = Number::invalid;
-  const sem::Type* T = nullptr;
+  const type::Type* T = nullptr;
   if (!match_modf_result_vec(state, ty, N, T)) {
     return nullptr;
   }
@@ -1452,15 +1452,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* FrexpResult::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* FrexpResult::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_frexp_result(state, ty, T)) {
     return nullptr;
   }
@@ -1486,16 +1486,16 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* FrexpResultVec::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* FrexpResultVec::Match(MatchState& state, const type::Type* ty) const {
   Number N = Number::invalid;
-  const sem::Type* T = nullptr;
+  const type::Type* T = nullptr;
   if (!match_frexp_result_vec(state, ty, N, T)) {
     return nullptr;
   }
@@ -1526,15 +1526,15 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* AtomicCompareExchangeResult::Match(MatchState& state, const sem::Type* ty) const {
-  const sem::Type* T = nullptr;
+const type::Type* AtomicCompareExchangeResult::Match(MatchState& state, const type::Type* ty) const {
+  const type::Type* T = nullptr;
   if (!match_atomic_compare_exchange_result(state, ty, T)) {
     return nullptr;
   }
@@ -1559,14 +1559,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Scalar::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* Scalar::Match(MatchState& state, const type::Type* ty) const {
   if (match_ia(state, ty)) {
     return build_ia(state);
   }
@@ -1608,14 +1608,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* ConcreteScalar::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* ConcreteScalar::Match(MatchState& state, const type::Type* ty) const {
   if (match_i32(state, ty)) {
     return build_i32(state);
   }
@@ -1651,14 +1651,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* ScalarNoF32::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* ScalarNoF32::Match(MatchState& state, const type::Type* ty) const {
   if (match_ia(state, ty)) {
     return build_ia(state);
   }
@@ -1697,14 +1697,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* ScalarNoF16::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* ScalarNoF16::Match(MatchState& state, const type::Type* ty) const {
   if (match_ia(state, ty)) {
     return build_ia(state);
   }
@@ -1743,14 +1743,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* ScalarNoI32::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* ScalarNoI32::Match(MatchState& state, const type::Type* ty) const {
   if (match_ia(state, ty)) {
     return build_ia(state);
   }
@@ -1789,14 +1789,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* ScalarNoU32::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* ScalarNoU32::Match(MatchState& state, const type::Type* ty) const {
   if (match_ia(state, ty)) {
     return build_ia(state);
   }
@@ -1835,14 +1835,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* ScalarNoBool::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* ScalarNoBool::Match(MatchState& state, const type::Type* ty) const {
   if (match_ia(state, ty)) {
     return build_ia(state);
   }
@@ -1881,14 +1881,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* FiaFiu32F16::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* FiaFiu32F16::Match(MatchState& state, const type::Type* ty) const {
   if (match_ia(state, ty)) {
     return build_ia(state);
   }
@@ -1927,14 +1927,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* FiaFi32F16::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* FiaFi32F16::Match(MatchState& state, const type::Type* ty) const {
   if (match_ia(state, ty)) {
     return build_ia(state);
   }
@@ -1970,14 +1970,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* FiaFiu32::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* FiaFiu32::Match(MatchState& state, const type::Type* ty) const {
   if (match_ia(state, ty)) {
     return build_ia(state);
   }
@@ -2013,14 +2013,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* FaF32::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* FaF32::Match(MatchState& state, const type::Type* ty) const {
   if (match_fa(state, ty)) {
     return build_fa(state);
   }
@@ -2047,14 +2047,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* FaF32F16::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* FaF32F16::Match(MatchState& state, const type::Type* ty) const {
   if (match_fa(state, ty)) {
     return build_fa(state);
   }
@@ -2084,14 +2084,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* IaIu32::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* IaIu32::Match(MatchState& state, const type::Type* ty) const {
   if (match_ia(state, ty)) {
     return build_ia(state);
   }
@@ -2121,14 +2121,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Fiu32F16::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* Fiu32F16::Match(MatchState& state, const type::Type* ty) const {
   if (match_i32(state, ty)) {
     return build_i32(state);
   }
@@ -2161,14 +2161,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Fiu32::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* Fiu32::Match(MatchState& state, const type::Type* ty) const {
   if (match_i32(state, ty)) {
     return build_i32(state);
   }
@@ -2198,14 +2198,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Fi32F16::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* Fi32F16::Match(MatchState& state, const type::Type* ty) const {
   if (match_i32(state, ty)) {
     return build_i32(state);
   }
@@ -2235,14 +2235,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Fi32::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* Fi32::Match(MatchState& state, const type::Type* ty) const {
   if (match_i32(state, ty)) {
     return build_i32(state);
   }
@@ -2269,14 +2269,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* F32F16::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* F32F16::Match(MatchState& state, const type::Type* ty) const {
   if (match_f32(state, ty)) {
     return build_f32(state);
   }
@@ -2303,14 +2303,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* Iu32::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* Iu32::Match(MatchState& state, const type::Type* ty) const {
   if (match_i32(state, ty)) {
     return build_i32(state);
   }
@@ -8194,12 +8194,12 @@
   {
     /* [28] */
     /* name */ "T",
-    /* matcher index */ 64,
+    /* matcher index */ 60,
   },
   {
     /* [29] */
     /* name */ "T",
-    /* matcher index */ 60,
+    /* matcher index */ 64,
   },
   {
     /* [30] */
@@ -11369,7 +11369,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[734],
     /* return matcher indices */ &kMatcherIndices[3],
@@ -11381,7 +11381,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[736],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -11417,7 +11417,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[742],
     /* return matcher indices */ &kMatcherIndices[3],
@@ -11429,7 +11429,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[744],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -12941,7 +12941,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
+    /* template types */ &kTemplateTypes[28],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[892],
     /* return matcher indices */ &kMatcherIndices[3],
@@ -12953,7 +12953,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
+    /* template types */ &kTemplateTypes[28],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[893],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -13229,7 +13229,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[950],
     /* return matcher indices */ &kMatcherIndices[3],
@@ -13241,7 +13241,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[951],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -13253,7 +13253,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[29],
+    /* template types */ &kTemplateTypes[28],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[952],
     /* return matcher indices */ &kMatcherIndices[3],
@@ -13265,7 +13265,7 @@
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[29],
+    /* template types */ &kTemplateTypes[28],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[953],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -13277,7 +13277,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[726],
     /* return matcher indices */ &kMatcherIndices[3],
@@ -13289,7 +13289,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[728],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -13445,7 +13445,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[774],
     /* return matcher indices */ &kMatcherIndices[3],
@@ -13457,7 +13457,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[776],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -13469,7 +13469,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[778],
     /* return matcher indices */ &kMatcherIndices[3],
@@ -13481,7 +13481,7 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[780],
     /* return matcher indices */ &kMatcherIndices[30],
@@ -13918,7 +13918,7 @@
     /* parameters */ &kParameters[746],
     /* return matcher indices */ &kMatcherIndices[35],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
+    /* const eval */ &ConstEval::OpLogicalAnd,
   },
   {
     /* [469] */
@@ -13930,7 +13930,7 @@
     /* parameters */ &kParameters[748],
     /* return matcher indices */ &kMatcherIndices[35],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
+    /* const eval */ &ConstEval::OpLogicalOr,
   },
 };
 
@@ -14407,8 +14407,8 @@
   },
   {
     /* [69] */
-    /* fn sign<T : fa_f32_f16>(T) -> T */
-    /* fn sign<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
+    /* fn sign<T : fia_fi32_f16>(T) -> T */
+    /* fn sign<N : num, T : fia_fi32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
     /* overloads */ &kOverloads[387],
   },
diff --git a/src/tint/resolver/intrinsic_table.inl.tmpl b/src/tint/resolver/intrinsic_table.inl.tmpl
index 834d300..e8f87b8 100644
--- a/src/tint/resolver/intrinsic_table.inl.tmpl
+++ b/src/tint/resolver/intrinsic_table.inl.tmpl
@@ -190,14 +190,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* {{$class}}::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* {{$class}}::Match(MatchState& state, const type::Type* ty) const {
 {{- range .TemplateParams }}
 {{-   template "DeclareLocalTemplateParam" . }}
 {{- end  }}
@@ -245,14 +245,14 @@
   /// @param state the MatchState
   /// @param type the type to match
   /// @returns the canonicalized type on match, otherwise nullptr
-  const sem::Type* Match(MatchState& state,
-                         const sem::Type* type) const override;
+  const type::Type* Match(MatchState& state,
+                         const type::Type* type) const override;
   /// @param state the MatchState
   /// @return a string representation of the matcher.
   std::string String(MatchState* state) const override;
 };
 
-const sem::Type* {{$class}}::Match(MatchState& state, const sem::Type* ty) const {
+const type::Type* {{$class}}::Match(MatchState& state, const type::Type* ty) const {
 {{- range .PrecedenceSortedTypes }}
   if (match_{{.Name}}(state, ty)) {
     return build_{{.Name}}(state);
@@ -400,7 +400,7 @@
 {{-                     define "DeclareLocalTemplateParam"                   -}}
 {{- /* ------------------------------------------------------------------ */ -}}
 {{-   if      IsTemplateTypeParam . }}
-  const sem::Type* {{.Name}} = nullptr;
+  const type::Type* {{.Name}} = nullptr;
 {{-   else if IsTemplateNumberParam . }}
   Number {{.Name}} = Number::invalid;
 {{-   else if IsTemplateEnumParam . }}
diff --git a/src/tint/resolver/intrinsic_table_test.cc b/src/tint/resolver/intrinsic_table_test.cc
index 03255df..d96fcd7 100644
--- a/src/tint/resolver/intrinsic_table_test.cc
+++ b/src/tint/resolver/intrinsic_table_test.cc
@@ -27,9 +27,9 @@
 #include "src/tint/sem/reference.h"
 #include "src/tint/sem/sampled_texture.h"
 #include "src/tint/sem/storage_texture.h"
-#include "src/tint/sem/test_helper.h"
 #include "src/tint/sem/type_conversion.h"
 #include "src/tint/sem/type_initializer.h"
+#include "src/tint/type/test_helper.h"
 
 namespace tint::resolver {
 namespace {
@@ -253,7 +253,7 @@
 
 TEST_F(IntrinsicTableTest, MatchArray) {
     auto* arr =
-        create<sem::Array>(create<sem::U32>(), create<sem::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
+        create<sem::Array>(create<sem::U32>(), create<type::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
     auto* arr_ptr = create<sem::Pointer>(arr, ast::AddressSpace::kStorage, ast::Access::kReadWrite);
     auto result = table->Lookup(BuiltinType::kArrayLength, utils::Vector{arr_ptr},
                                 sem::EvaluationStage::kConstant, Source{});
@@ -957,7 +957,7 @@
 
 TEST_F(IntrinsicTableTest, MismatchTypeConversion) {
     auto* arr =
-        create<sem::Array>(create<sem::U32>(), create<sem::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
+        create<sem::Array>(create<sem::U32>(), create<type::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
     auto* f32 = create<sem::F32>();
     auto result = table->Lookup(InitConvIntrinsic::kVec3, f32, utils::Vector{arr},
                                 sem::EvaluationStage::kConstant, Source{{12, 34}});
@@ -1017,7 +1017,7 @@
 
 TEST_F(IntrinsicTableTest, Err257Arguments) {  // crbug.com/1323605
     auto* f32 = create<sem::F32>();
-    utils::Vector<const sem::Type*, 0> arg_tys;
+    utils::Vector<const type::Type*, 0> arg_tys;
     arg_tys.Resize(257, f32);
     auto result = table->Lookup(BuiltinType::kAbs, std::move(arg_tys),
                                 sem::EvaluationStage::kConstant, Source{});
diff --git a/src/tint/resolver/is_host_shareable_test.cc b/src/tint/resolver/is_host_shareable_test.cc
index 5e1555b..c106020 100644
--- a/src/tint/resolver/is_host_shareable_test.cc
+++ b/src/tint/resolver/is_host_shareable_test.cc
@@ -106,14 +106,14 @@
 }
 
 TEST_F(ResolverIsHostShareable, ArraySizedOfHostShareable) {
-    auto* arr = create<sem::Array>(create<sem::I32>(), create<sem::ConstantArrayCount>(5u), 4u, 20u,
-                                   4u, 4u);
+    auto* arr = create<sem::Array>(create<sem::I32>(), create<type::ConstantArrayCount>(5u), 4u,
+                                   20u, 4u, 4u);
     EXPECT_TRUE(r()->IsHostShareable(arr));
 }
 
 TEST_F(ResolverIsHostShareable, ArrayUnsizedOfHostShareable) {
     auto* arr =
-        create<sem::Array>(create<sem::I32>(), create<sem::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
+        create<sem::Array>(create<sem::I32>(), create<type::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
     EXPECT_TRUE(r()->IsHostShareable(arr));
 }
 
diff --git a/src/tint/resolver/is_storeable_test.cc b/src/tint/resolver/is_storeable_test.cc
index 6618199..9c80e0a 100644
--- a/src/tint/resolver/is_storeable_test.cc
+++ b/src/tint/resolver/is_storeable_test.cc
@@ -89,14 +89,14 @@
 }
 
 TEST_F(ResolverIsStorableTest, ArraySizedOfStorable) {
-    auto* arr = create<sem::Array>(create<sem::I32>(), create<sem::ConstantArrayCount>(5u), 4u, 20u,
-                                   4u, 4u);
+    auto* arr = create<sem::Array>(create<sem::I32>(), create<type::ConstantArrayCount>(5u), 4u,
+                                   20u, 4u, 4u);
     EXPECT_TRUE(r()->IsStorable(arr));
 }
 
 TEST_F(ResolverIsStorableTest, ArrayUnsizedOfStorable) {
     auto* arr =
-        create<sem::Array>(create<sem::I32>(), create<sem::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
+        create<sem::Array>(create<sem::I32>(), create<type::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
     EXPECT_TRUE(r()->IsStorable(arr));
 }
 
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index be8d400..24d9999 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -16,7 +16,7 @@
 
 #include "src/tint/resolver/resolver.h"
 #include "src/tint/resolver/resolver_test_helper.h"
-#include "src/tint/sem/test_helper.h"
+#include "src/tint/type/test_helper.h"
 
 #include "gmock/gmock.h"
 
@@ -77,7 +77,7 @@
     using ProgramBuilder::FriendlyName;
 
     void CheckTypesAndValues(const sem::Expression* expr,
-                             const tint::sem::Type* expected_sem_ty,
+                             const tint::type::Type* expected_sem_ty,
                              const std::variant<AInt, AFloat>& expected_value) {
         std::visit([&](auto v) { CheckTypesAndValuesImpl(expr, expected_sem_ty, v); },
                    expected_value);
@@ -86,7 +86,7 @@
   private:
     template <typename T>
     void CheckTypesAndValuesImpl(const sem::Expression* expr,
-                                 const tint::sem::Type* expected_sem_ty,
+                                 const tint::type::Type* expected_sem_ty,
                                  T expected_value) {
         EXPECT_TYPE(expr->Type(), expected_sem_ty);
 
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index e47f83c..ee4f628 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -199,7 +199,7 @@
     return result;
 }
 
-sem::Type* Resolver::Type(const ast::Type* ty) {
+type::Type* Resolver::Type(const ast::Type* ty) {
     Mark(ty);
     auto* s = Switch(
         ty,  //
@@ -317,7 +317,7 @@
             auto* resolved = sem_.ResolvedSymbol(ty);
             return Switch(
                 resolved,  //
-                [&](sem::Type* type) { return type; },
+                [&](type::Type* type) { return type; },
                 [&](sem::Variable* var) {
                     auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
                     AddError("cannot use variable '" + name + "' as type", ty->source);
@@ -330,7 +330,7 @@
                     AddNote("'" + name + "' declared here", func->Declaration()->source);
                     return nullptr;
                 },
-                [&](Default) -> sem::Type* {
+                [&](Default) -> type::Type* {
                     if (auto* tn = ty->As<ast::TypeName>()) {
                         if (IsBuiltin(tn->name)) {
                             auto name = builder_->Symbols().NameFor(tn->name);
@@ -371,7 +371,7 @@
 }
 
 sem::Variable* Resolver::Let(const ast::Let* v, bool is_global) {
-    const sem::Type* ty = nullptr;
+    const type::Type* ty = nullptr;
 
     // If the variable has a declared type, resolve it.
     if (v->type) {
@@ -402,7 +402,7 @@
         return nullptr;
     }
 
-    if (!ApplyAddressSpaceUsageToType(ast::AddressSpace::kNone, const_cast<sem::Type*>(ty),
+    if (!ApplyAddressSpaceUsageToType(ast::AddressSpace::kNone, const_cast<type::Type*>(ty),
                                       v->source)) {
         AddNote("while instantiating 'let' " + builder_->Symbols().NameFor(v->symbol), v->source);
         return nullptr;
@@ -427,7 +427,7 @@
 }
 
 sem::Variable* Resolver::Override(const ast::Override* v) {
-    const sem::Type* ty = nullptr;
+    const type::Type* ty = nullptr;
 
     // If the variable has a declared type, resolve it.
     if (v->type) {
@@ -461,7 +461,7 @@
         return nullptr;
     }
 
-    if (!ApplyAddressSpaceUsageToType(ast::AddressSpace::kNone, const_cast<sem::Type*>(ty),
+    if (!ApplyAddressSpaceUsageToType(ast::AddressSpace::kNone, const_cast<type::Type*>(ty),
                                       v->source)) {
         AddNote("while instantiating 'override' " + builder_->Symbols().NameFor(v->symbol),
                 v->source);
@@ -511,7 +511,7 @@
 }
 
 sem::Variable* Resolver::Const(const ast::Const* c, bool is_global) {
-    const sem::Type* ty = nullptr;
+    const type::Type* ty = nullptr;
 
     // If the variable has a declared type, resolve it.
     if (c->type) {
@@ -551,7 +551,7 @@
         return nullptr;
     }
 
-    if (!ApplyAddressSpaceUsageToType(ast::AddressSpace::kNone, const_cast<sem::Type*>(ty),
+    if (!ApplyAddressSpaceUsageToType(ast::AddressSpace::kNone, const_cast<type::Type*>(ty),
                                       c->source)) {
         AddNote("while instantiating 'const' " + builder_->Symbols().NameFor(c->symbol), c->source);
         return nullptr;
@@ -571,7 +571,7 @@
 }
 
 sem::Variable* Resolver::Var(const ast::Var* var, bool is_global) {
-    const sem::Type* storage_ty = nullptr;
+    const type::Type* storage_ty = nullptr;
 
     // If the variable has a declared type, resolve it.
     if (auto* ty = var->type) {
@@ -738,7 +738,7 @@
         return nullptr;
     }
 
-    sem::Type* ty = Type(param->type);
+    type::Type* ty = Type(param->type);
     if (!ty) {
         return nullptr;
     }
@@ -752,7 +752,7 @@
         // For MSL, we push module-scope variables into the entry point as pointer
         // parameters, so we also need to handle their store type.
         if (!ApplyAddressSpaceUsageToType(
-                ptr->AddressSpace(), const_cast<sem::Type*>(ptr->StoreType()), param->source)) {
+                ptr->AddressSpace(), const_cast<type::Type*>(ptr->StoreType()), param->source)) {
             add_note();
             return nullptr;
         }
@@ -887,10 +887,18 @@
 
 void Resolver::SetShadows() {
     for (auto it : dependencies_.shadows) {
+        CastableBase* b = sem_.Get(it.value);
+        if (!b) {
+            TINT_ICE(Resolver, builder_->Diagnostics())
+                << "AST node '" << it.value->TypeInfo().name << "' had no semantic info\n"
+                << "At: " << it.value->source << "\n"
+                << "Pointer: " << it.value;
+        }
+
         Switch(
             sem_.Get(it.key),  //
-            [&](sem::LocalVariable* local) { local->SetShadows(sem_.Get(it.value)); },
-            [&](sem::Parameter* param) { param->SetShadows(sem_.Get(it.value)); });
+            [&](sem::LocalVariable* local) { local->SetShadows(b); },
+            [&](sem::Parameter* param) { param->SetShadows(b); });
     }
 }
 
@@ -984,7 +992,7 @@
 
         parameters.Push(p);
 
-        auto* p_ty = const_cast<sem::Type*>(p->Type());
+        auto* p_ty = const_cast<type::Type*>(p->Type());
         if (auto* str = p_ty->As<sem::Struct>()) {
             switch (decl->PipelineStage()) {
                 case ast::PipelineStage::kVertex:
@@ -1003,7 +1011,7 @@
     }
 
     // Resolve the return type
-    sem::Type* return_type = nullptr;
+    type::Type* return_type = nullptr;
     if (auto* ty = decl->return_type) {
         return_type = Type(ty);
         if (!return_type) {
@@ -1129,7 +1137,7 @@
 
     auto values = attr->Values();
     utils::Vector<const sem::Expression*, 3> args;
-    utils::Vector<const sem::Type*, 3> arg_tys;
+    utils::Vector<const type::Type*, 3> arg_tys;
 
     constexpr const char* kErrBadExpr =
         "workgroup_size argument must be a constant or override-expression of type "
@@ -1162,7 +1170,7 @@
         arg_tys.Push(ty);
     }
 
-    auto* common_ty = sem::Type::Common(arg_tys);
+    auto* common_ty = type::Type::Common(arg_tys);
     if (!common_ty) {
         AddError("workgroup_size arguments must be of the same type, either i32 or u32",
                  attr->source);
@@ -1266,7 +1274,7 @@
         });
 }
 
-sem::CaseStatement* Resolver::CaseStatement(const ast::CaseStatement* stmt, const sem::Type* ty) {
+sem::CaseStatement* Resolver::CaseStatement(const ast::CaseStatement* stmt, const type::Type* ty) {
     auto* sem =
         builder_->create<sem::CaseStatement>(stmt, current_compound_statement_, current_function_);
     return StatementScope(stmt, sem, [&] {
@@ -1705,9 +1713,9 @@
     return true;
 }
 
-const sem::Type* Resolver::ConcreteType(const sem::Type* ty,
-                                        const sem::Type* target_ty,
-                                        const Source& source) {
+const type::Type* Resolver::ConcreteType(const type::Type* ty,
+                                         const type::Type* target_ty,
+                                         const Source& source) {
     auto i32 = [&] { return builder_->create<sem::I32>(); };
     auto f32 = [&] { return builder_->create<sem::F32>(); };
     auto i32v = [&](uint32_t width) { return builder_->create<sem::Vector>(i32(), width); };
@@ -1734,8 +1742,8 @@
                               return target_ty ? target_ty : f32m(m->columns(), m->rows());
                           });
         },
-        [&](const sem::Array* a) -> const sem::Type* {
-            const sem::Type* target_el_ty = nullptr;
+        [&](const sem::Array* a) -> const type::Type* {
+            const type::Type* target_el_ty = nullptr;
             if (auto* target_arr_ty = As<sem::Array>(target_ty)) {
                 target_el_ty = target_arr_ty->ElemType();
             }
@@ -1744,7 +1752,7 @@
             }
             return nullptr;
         },
-        [&](const sem::Struct* s) -> const sem::Type* {
+        [&](const sem::Struct* s) -> const type::Type* {
             if (auto tys = s->ConcreteTypes(); !tys.IsEmpty()) {
                 return target_ty ? target_ty : tys[0];
             }
@@ -1753,7 +1761,7 @@
 }
 
 const sem::Expression* Resolver::Materialize(const sem::Expression* expr,
-                                             const sem::Type* target_type /* = nullptr */) {
+                                             const type::Type* target_type /* = nullptr */) {
     if (!expr) {
         // Allow for Materialize(Expression(blah)), where failures pass through Materialize()
         return nullptr;
@@ -1813,12 +1821,12 @@
     return true;
 }
 
-bool Resolver::ShouldMaterializeArgument(const sem::Type* parameter_ty) const {
-    const auto* param_el_ty = sem::Type::DeepestElementOf(parameter_ty);
+bool Resolver::ShouldMaterializeArgument(const type::Type* parameter_ty) const {
+    const auto* param_el_ty = type::Type::DeepestElementOf(parameter_ty);
     return param_el_ty && !param_el_ty->Is<sem::AbstractNumeric>();
 }
 
-bool Resolver::Convert(const sem::Constant*& c, const sem::Type* target_ty, const Source& source) {
+bool Resolver::Convert(const sem::Constant*& c, const type::Type* target_ty, const Source& source) {
     auto r = const_eval_.Convert(target_ty, c, source);
     if (!r) {
         return false;
@@ -1963,7 +1971,7 @@
 
     // ct_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 sem::Type* template_arg) -> sem::Call* {
+    auto ct_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);
@@ -1993,7 +2001,7 @@
 
     // arr_or_str_init is a helper for building a sem::TypeInitializer for an array or structure
     // initializer call target.
-    auto arr_or_str_init = [&](const sem::Type* ty,
+    auto arr_or_str_init = [&](const type::Type* ty,
                                const sem::CallTarget* call_target) -> sem::Call* {
         if (!MaybeMaterializeArguments(args, call_target)) {
             return nullptr;
@@ -2023,7 +2031,7 @@
 
     // 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 sem::Type* ty) {
+    auto ty_init_or_conv = [&](const type::Type* ty) {
         return Switch(
             ty,  //
             [&](const sem::Vector* v) {
@@ -2110,7 +2118,7 @@
             [&](const ast::Vector* v) -> sem::Call* {
                 Mark(v);
                 // vector element type must be inferred if it was not specified.
-                sem::Type* template_arg = nullptr;
+                type::Type* template_arg = nullptr;
                 if (v->type) {
                     template_arg = Type(v->type);
                     if (!template_arg) {
@@ -2126,7 +2134,7 @@
             [&](const ast::Matrix* m) -> sem::Call* {
                 Mark(m);
                 // matrix element type must be inferred if it was not specified.
-                sem::Type* template_arg = nullptr;
+                type::Type* template_arg = nullptr;
                 if (m->type) {
                     template_arg = Type(m->type);
                     if (!template_arg) {
@@ -2143,8 +2151,8 @@
             [&](const ast::Array* a) -> sem::Call* {
                 Mark(a);
                 // array element type must be inferred if it was not specified.
-                const sem::ArrayCount* el_count = nullptr;
-                const sem::Type* el_ty = nullptr;
+                const type::ArrayCount* el_count = nullptr;
+                const type::Type* el_ty = nullptr;
                 if (a->type) {
                     el_ty = Type(a->type);
                     if (!el_ty) {
@@ -2161,16 +2169,16 @@
                     // Note: validation later will detect any mismatches between explicit array
                     // size and number of initializer expressions.
                 } else {
-                    el_count = builder_->create<sem::ConstantArrayCount>(
+                    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 = sem::Type::Common(arg_tys);
+                    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 sem::Type*, 8> types;
+                        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 '" +
@@ -2216,16 +2224,17 @@
         // conversion.
         auto* ident = expr->target.name;
         Mark(ident);
-        auto* resolved = sem_.ResolvedSymbol(ident);
+        if (auto* resolved = sem_.ResolvedSymbol<type::Type>(ident)) {
+            // 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(resolved);
+        }
+
+        auto* resolved = sem_.ResolvedSymbol<sem::Node>(ident);
         call = Switch<sem::Call*>(
             resolved,  //
-            [&](sem::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); },
             [&](sem::Variable* var) {
                 auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
@@ -2337,7 +2346,7 @@
     return call;
 }
 
-sem::Type* Resolver::BuiltinTypeAlias(Symbol sym) const {
+type::Type* Resolver::BuiltinTypeAlias(Symbol sym) const {
     auto name = builder_->Symbols().NameFor(sym);
     auto& b = *builder_;
     switch (ParseTypeAlias(name)) {
@@ -2471,7 +2480,7 @@
 sem::Expression* Resolver::Literal(const ast::LiteralExpression* literal) {
     auto* ty = Switch(
         literal,
-        [&](const ast::IntLiteralExpression* i) -> sem::Type* {
+        [&](const ast::IntLiteralExpression* i) -> type::Type* {
             switch (i->suffix) {
                 case ast::IntLiteralExpression::Suffix::kNone:
                     return builder_->create<sem::AbstractInt>();
@@ -2482,7 +2491,7 @@
             }
             return nullptr;
         },
-        [&](const ast::FloatLiteralExpression* f) -> sem::Type* {
+        [&](const ast::FloatLiteralExpression* f) -> type::Type* {
             switch (f->suffix) {
                 case ast::FloatLiteralExpression::Suffix::kNone:
                     return builder_->create<sem::AbstractFloat>();
@@ -2520,8 +2529,8 @@
 
 sem::Expression* Resolver::Identifier(const ast::IdentifierExpression* expr) {
     auto symbol = expr->symbol;
-    auto* resolved = sem_.ResolvedSymbol(expr);
-    if (auto* variable = As<sem::Variable>(resolved)) {
+    auto* sem_resolved = sem_.ResolvedSymbol<sem::Node>(expr);
+    if (auto* variable = As<sem::Variable>(sem_resolved)) {
         auto* user = builder_->create<sem::VariableUser>(expr, current_statement_, variable);
 
         if (current_statement_) {
@@ -2589,7 +2598,7 @@
         return user;
     }
 
-    if (Is<sem::Function>(resolved)) {
+    if (Is<sem::Function>(sem_resolved)) {
         AddError("missing '(' for function call", expr->source.End());
         return nullptr;
     }
@@ -2599,14 +2608,14 @@
         return nullptr;
     }
 
-    if (resolved->Is<sem::Type>() || BuiltinTypeAlias(symbol)) {
+    if (sem_.ResolvedSymbol<type::Type>(expr)) {
         AddError("missing '(' for type initializer or cast", expr->source.End());
         return nullptr;
     }
 
     TINT_ICE(Resolver, diagnostics_)
         << expr->source << " unresolved identifier:\n"
-        << "resolved: " << (resolved ? resolved->TypeInfo().name : "<null>") << "\n"
+        << "resolved: " << (sem_resolved ? sem_resolved->TypeInfo().name : "<null>") << "\n"
         << "name: " << builder_->Symbols().NameFor(symbol);
     return nullptr;
 }
@@ -2617,7 +2626,7 @@
     auto* object = sem_.Get(expr->structure);
     auto* root_ident = object->RootIdentifier();
 
-    const sem::Type* ty = nullptr;
+    const type::Type* ty = nullptr;
 
     // Object may be a side-effecting expression (e.g. function call).
     bool has_side_effects = object && object->HasSideEffects();
@@ -2812,7 +2821,7 @@
         return nullptr;
     }
 
-    const sem::Type* ty = nullptr;
+    const type::Type* ty = nullptr;
     const sem::Variable* root_ident = nullptr;
     const sem::Constant* value = nullptr;
     auto stage = sem::EvaluationStage::kRuntime;
@@ -2898,8 +2907,8 @@
     return true;
 }
 
-sem::Type* Resolver::TypeDecl(const ast::TypeDecl* named_type) {
-    sem::Type* result = nullptr;
+type::Type* Resolver::TypeDecl(const ast::TypeDecl* named_type) {
+    type::Type* result = nullptr;
     if (auto* alias = named_type->As<ast::Alias>()) {
         result = Alias(alias);
     } else if (auto* str = named_type->As<ast::Struct>()) {
@@ -2936,7 +2945,7 @@
         return nullptr;
     }
 
-    const sem::ArrayCount* el_count = nullptr;
+    const type::ArrayCount* el_count = nullptr;
 
     // Evaluate the constant array count expression.
     if (auto* count_expr = arr->count) {
@@ -2945,7 +2954,7 @@
             return nullptr;
         }
     } else {
-        el_count = builder_->create<sem::RuntimeArrayCount>();
+        el_count = builder_->create<type::RuntimeArrayCount>();
     }
 
     auto* out = Array(arr->type->source,                              //
@@ -2972,7 +2981,7 @@
     return out;
 }
 
-const sem::ArrayCount* Resolver::ArrayCount(const ast::Expression* count_expr) {
+const type::ArrayCount* Resolver::ArrayCount(const ast::Expression* count_expr) {
     // Evaluate the constant array count expression.
     const auto* count_sem = Materialize(Expression(count_expr));
     if (!count_sem) {
@@ -3011,11 +3020,11 @@
         return nullptr;
     }
 
-    return builder_->create<sem::ConstantArrayCount>(static_cast<uint32_t>(count));
+    return builder_->create<type::ConstantArrayCount>(static_cast<uint32_t>(count));
 }
 
 bool Resolver::ArrayAttributes(utils::VectorRef<const ast::Attribute*> attributes,
-                               const sem::Type* el_ty,
+                               const type::Type* el_ty,
                                uint32_t& explicit_stride) {
     if (!validator_.NoDuplicateAttributes(attributes)) {
         return false;
@@ -3046,8 +3055,8 @@
 
 sem::Array* Resolver::Array(const Source& el_source,
                             const Source& count_source,
-                            const sem::Type* el_ty,
-                            const sem::ArrayCount* el_count,
+                            const type::Type* el_ty,
+                            const type::ArrayCount* el_count,
                             uint32_t explicit_stride) {
     uint32_t el_align = el_ty->Align();
     uint32_t el_size = el_ty->Size();
@@ -3055,7 +3064,7 @@
     uint64_t stride = explicit_stride ? explicit_stride : implicit_stride;
     uint64_t size = 0;
 
-    if (auto const_count = el_count->As<sem::ConstantArrayCount>()) {
+    if (auto const_count = el_count->As<type::ConstantArrayCount>()) {
         size = const_count->value * stride;
         if (size > std::numeric_limits<uint32_t>::max()) {
             std::stringstream msg;
@@ -3064,7 +3073,7 @@
             AddError(msg.str(), count_source);
             return nullptr;
         }
-    } else if (el_count->Is<sem::RuntimeArrayCount>()) {
+    } else if (el_count->Is<type::RuntimeArrayCount>()) {
         size = stride;
     }
     auto* out = builder_->create<sem::Array>(el_ty, el_count, el_align, static_cast<uint32_t>(size),
@@ -3078,7 +3087,7 @@
     return out;
 }
 
-sem::Type* Resolver::Alias(const ast::Alias* alias) {
+type::Type* Resolver::Alias(const ast::Alias* alias) {
     auto* ty = Type(alias->type);
     if (!ty) {
         return nullptr;
@@ -3329,7 +3338,7 @@
         auto& behaviors = current_statement_->Behaviors();
         behaviors = sem::Behavior::kReturn;
 
-        const sem::Type* value_ty = nullptr;
+        const type::Type* value_ty = nullptr;
         if (auto* value = stmt->value) {
             const auto* expr = Expression(value);
             if (!expr) {
@@ -3374,7 +3383,7 @@
 
         // Determine the common type across all selectors and the switch expression
         // This must materialize to an integer scalar (non-abstract).
-        utils::Vector<const sem::Type*, 8> types;
+        utils::Vector<const type::Type*, 8> types;
         types.Push(cond_ty);
         for (auto* case_stmt : stmt->body) {
             for (auto* sel : case_stmt->selectors) {
@@ -3388,7 +3397,7 @@
                 types.Push(sem_expr->Type()->UnwrapRef());
             }
         }
-        auto* common_ty = sem::Type::Common(types);
+        auto* common_ty = type::Type::Common(types);
         if (!common_ty || !common_ty->is_integer_scalar()) {
             // No common type found or the common type was abstract.
             // Pick i32 and let validation deal with any mismatches.
@@ -3607,9 +3616,9 @@
 }
 
 bool Resolver::ApplyAddressSpaceUsageToType(ast::AddressSpace address_space,
-                                            sem::Type* ty,
+                                            type::Type* ty,
                                             const Source& usage) {
-    ty = const_cast<sem::Type*>(ty->UnwrapRef());
+    ty = const_cast<type::Type*>(ty->UnwrapRef());
 
     if (auto* str = ty->As<sem::Struct>()) {
         if (str->AddressSpaceUsage().count(address_space)) {
@@ -3621,8 +3630,8 @@
         for (auto* member : str->Members()) {
             auto decl = member->Declaration();
             if (decl &&
-                !ApplyAddressSpaceUsageToType(address_space, const_cast<sem::Type*>(member->Type()),
-                                              decl->type->source)) {
+                !ApplyAddressSpaceUsageToType(
+                    address_space, const_cast<type::Type*>(member->Type()), decl->type->source)) {
                 std::stringstream err;
                 err << "while analyzing structure member " << sem_.TypeNameOf(str) << "."
                     << builder_->Symbols().NameFor(member->Name());
@@ -3635,7 +3644,7 @@
 
     if (auto* arr = ty->As<sem::Array>()) {
         if (address_space != ast::AddressSpace::kStorage) {
-            if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
+            if (arr->Count()->Is<type::RuntimeArrayCount>()) {
                 AddError("runtime-sized arrays can only be used in the <storage> address space",
                          usage);
                 return false;
@@ -3649,7 +3658,7 @@
                 return false;
             }
         }
-        return ApplyAddressSpaceUsageToType(address_space, const_cast<sem::Type*>(arr->ElemType()),
+        return ApplyAddressSpaceUsageToType(address_space, const_cast<type::Type*>(arr->ElemType()),
                                             usage);
     }
 
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index 0deef5f..5592405 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -93,19 +93,21 @@
 
     /// @param type the given type
     /// @returns true if the given type is a plain type
-    bool IsPlain(const sem::Type* type) const { return validator_.IsPlain(type); }
+    bool IsPlain(const type::Type* type) const { return validator_.IsPlain(type); }
 
     /// @param type the given type
     /// @returns true if the given type is a fixed-footprint type
-    bool IsFixedFootprint(const sem::Type* type) const { return validator_.IsFixedFootprint(type); }
+    bool IsFixedFootprint(const type::Type* type) const {
+        return validator_.IsFixedFootprint(type);
+    }
 
     /// @param type the given type
     /// @returns true if the given type is storable
-    bool IsStorable(const sem::Type* type) const { return validator_.IsStorable(type); }
+    bool IsStorable(const type::Type* type) const { return validator_.IsStorable(type); }
 
     /// @param type the given type
     /// @returns true if the given type is host-shareable
-    bool IsHostShareable(const sem::Type* type) const { return validator_.IsHostShareable(type); }
+    bool IsHostShareable(const type::Type* type) const { return validator_.IsHostShareable(type); }
 
     /// @returns the validator for testing
     const Validator* GetValidatorForTesting() const { return &validator_; }
@@ -179,7 +181,7 @@
     ///   materialized type.
     /// If `expr` is nullptr, then Materialize() will also return nullptr.
     const sem::Expression* Materialize(const sem::Expression* expr,
-                                       const sem::Type* target_type = nullptr);
+                                       const type::Type* target_type = nullptr);
 
     /// Materializes all the arguments in `args` to the parameter types of `target`.
     /// @returns true on success, false on failure.
@@ -189,11 +191,11 @@
 
     /// @returns true if an argument of an abstract numeric type, passed to a parameter of type
     /// `parameter_ty` should be materialized.
-    bool ShouldMaterializeArgument(const sem::Type* parameter_ty) const;
+    bool ShouldMaterializeArgument(const type::Type* parameter_ty) const;
 
     /// Converts `c` to `target_ty`
     /// @returns true on success, false on failure.
-    bool Convert(const sem::Constant*& c, const sem::Type* target_ty, const Source& source);
+    bool Convert(const sem::Constant*& c, const type::Type* target_ty, const Source& source);
 
     /// Transforms `args` to a vector of constants, and converts each constant to the call target's
     /// parameter type.
@@ -209,9 +211,9 @@
     /// @param source the source of the expression requiring materialization
     /// @returns the concrete (materialized) type for the given type, or nullptr if the type is
     ///          already concrete.
-    const sem::Type* ConcreteType(const sem::Type* ty,
-                                  const sem::Type* target_ty,
-                                  const Source& source);
+    const type::Type* ConcreteType(const type::Type* ty,
+                                   const type::Type* target_ty,
+                                   const Source& source);
 
     // Statement resolving methods
     // Each return true on success, false on failure.
@@ -220,7 +222,7 @@
     sem::Statement* BreakStatement(const ast::BreakStatement*);
     sem::Statement* BreakIfStatement(const ast::BreakIfStatement*);
     sem::Statement* CallStatement(const ast::CallStatement*);
-    sem::CaseStatement* CaseStatement(const ast::CaseStatement*, const sem::Type*);
+    sem::CaseStatement* CaseStatement(const ast::CaseStatement*, const type::Type*);
     sem::Statement* CompoundAssignmentStatement(const ast::CompoundAssignmentStatement*);
     sem::Statement* ContinueStatement(const ast::ContinueStatement*);
     sem::Statement* DiscardStatement(const ast::DiscardStatement*);
@@ -249,11 +251,11 @@
     /// current_function_
     bool WorkgroupSize(const ast::Function*);
 
-    /// @returns the sem::Type for the ast::Type `ty`, building it if it
+    /// @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
-    sem::Type* Type(const ast::Type* ty);
+    type::Type* Type(const ast::Type* ty);
 
     /// @param enable the enable declaration
     /// @returns the resolved extension
@@ -261,7 +263,7 @@
 
     /// @param named_type the named type to resolve
     /// @returns the resolved semantic type
-    sem::Type* TypeDecl(const ast::TypeDecl* named_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
@@ -273,7 +275,7 @@
     /// 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.
-    const sem::ArrayCount* ArrayCount(const ast::Expression* count_expr);
+    const type::ArrayCount* ArrayCount(const ast::Expression* count_expr);
 
     /// Resolves and validates the attributes on an array.
     /// @param attributes the attributes on the array type.
@@ -281,7 +283,7 @@
     /// @param explicit_stride assigned the specified stride of the array in bytes.
     /// @returns true on success, false on failure
     bool ArrayAttributes(utils::VectorRef<const ast::Attribute*> attributes,
-                         const sem::Type* el_ty,
+                         const type::Type* el_ty,
                          uint32_t& explicit_stride);
 
     /// Builds and returns the semantic information for an array.
@@ -295,15 +297,15 @@
     /// @param explicit_stride the explicit byte stride of the array. Zero means implicit stride.
     sem::Array* Array(const Source& el_source,
                       const Source& count_source,
-                      const sem::Type* el_ty,
-                      const sem::ArrayCount* el_count,
+                      const type::Type* el_ty,
+                      const type::ArrayCount* el_count,
                       uint32_t explicit_stride);
 
     /// Builds and returns the semantic information for the alias `alias`.
     /// This method does not mark the ast::Alias node, nor attach the generated
     /// semantic information to the AST node.
     /// @returns the aliased type, or nullptr if an error is raised.
-    sem::Type* Alias(const ast::Alias* alias);
+    type::Type* Alias(const ast::Alias* alias);
 
     /// Builds and returns the semantic information for the structure `str`.
     /// This method does not mark the ast::Struct node, nor attach the generated
@@ -371,7 +373,7 @@
     /// given type and address space. Used for generating sensible error
     /// messages.
     /// @returns true on success, false on error
-    bool ApplyAddressSpaceUsageToType(ast::AddressSpace sc, sem::Type* ty, const Source& usage);
+    bool ApplyAddressSpaceUsageToType(ast::AddressSpace sc, type::Type* ty, const Source& usage);
 
     /// @param address_space the address space
     /// @returns the default access control for the given address space
@@ -417,7 +419,7 @@
     bool IsBuiltin(Symbol) const;
 
     /// @returns the builtin type alias for the given symbol
-    sem::Type* BuiltinTypeAlias(Symbol) const;
+    type::Type* BuiltinTypeAlias(Symbol) const;
 
     // ArrayInitializerSig represents a unique array initializer signature.
     // It is a tuple of the array type, number of arguments provided and earliest evaluation stage.
@@ -461,7 +463,7 @@
     Validator validator_;
     ast::Extensions enabled_extensions_;
     utils::Vector<sem::Function*, 8> entry_points_;
-    utils::Hashmap<const sem::Type*, const Source*, 8> atomic_composite_info_;
+    utils::Hashmap<const type::Type*, const Source*, 8> atomic_composite_info_;
     utils::Bitset<0> marked_;
     ExprEvalStageConstraint expr_eval_stage_constraint_;
     std::unordered_map<const sem::Function*, AliasAnalysisInfo> alias_analysis_infos_;
diff --git a/src/tint/resolver/resolver_test.cc b/src/tint/resolver/resolver_test.cc
index 2a88249..1ac1369 100644
--- a/src/tint/resolver/resolver_test.cc
+++ b/src/tint/resolver/resolver_test.cc
@@ -440,7 +440,7 @@
     auto* ref = TypeOf(a)->As<sem::Reference>();
     ASSERT_NE(ref, nullptr);
     auto* ary = ref->StoreType()->As<sem::Array>();
-    EXPECT_EQ(ary->Count(), create<sem::ConstantArrayCount>(10u));
+    EXPECT_EQ(ary->Count(), create<type::ConstantArrayCount>(10u));
 }
 
 TEST_F(ResolverTest, ArraySize_SignedLiteral) {
@@ -453,7 +453,7 @@
     auto* ref = TypeOf(a)->As<sem::Reference>();
     ASSERT_NE(ref, nullptr);
     auto* ary = ref->StoreType()->As<sem::Array>();
-    EXPECT_EQ(ary->Count(), create<sem::ConstantArrayCount>(10u));
+    EXPECT_EQ(ary->Count(), create<type::ConstantArrayCount>(10u));
 }
 
 TEST_F(ResolverTest, ArraySize_UnsignedConst) {
@@ -468,7 +468,7 @@
     auto* ref = TypeOf(a)->As<sem::Reference>();
     ASSERT_NE(ref, nullptr);
     auto* ary = ref->StoreType()->As<sem::Array>();
-    EXPECT_EQ(ary->Count(), create<sem::ConstantArrayCount>(10u));
+    EXPECT_EQ(ary->Count(), create<type::ConstantArrayCount>(10u));
 }
 
 TEST_F(ResolverTest, ArraySize_SignedConst) {
@@ -483,7 +483,7 @@
     auto* ref = TypeOf(a)->As<sem::Reference>();
     ASSERT_NE(ref, nullptr);
     auto* ary = ref->StoreType()->As<sem::Array>();
-    EXPECT_EQ(ary->Count(), create<sem::ConstantArrayCount>(10u));
+    EXPECT_EQ(ary->Count(), create<type::ConstantArrayCount>(10u));
 }
 
 TEST_F(ResolverTest, ArraySize_NamedOverride) {
@@ -1767,7 +1767,7 @@
 
     const ast::Type* lhs_type = nullptr;
     const ast::Type* rhs_type = nullptr;
-    const sem::Type* result_type = nullptr;
+    const type::Type* result_type = nullptr;
     bool is_valid_expr;
 
     if (vec_by_mat) {
diff --git a/src/tint/resolver/resolver_test_helper.h b/src/tint/resolver/resolver_test_helper.h
index f2567c8..f47719f 100644
--- a/src/tint/resolver/resolver_test_helper.h
+++ b/src/tint/resolver/resolver_test_helper.h
@@ -114,7 +114,7 @@
     /// @param type a type
     /// @returns the name for `type` that closely resembles how it would be
     /// declared in WGSL.
-    std::string FriendlyName(const sem::Type* type) { return type->FriendlyName(Symbols()); }
+    std::string FriendlyName(const type::Type* type) { return type->FriendlyName(Symbols()); }
 
   private:
     std::unique_ptr<Resolver> resolver_;
@@ -194,7 +194,7 @@
 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);
-using sem_type_func_ptr = const sem::Type* (*)(ProgramBuilder& b);
+using sem_type_func_ptr = const type::Type* (*)(ProgramBuilder& b);
 using type_name_func_ptr = std::string (*)();
 
 struct UnspecializedElementType {};
@@ -215,7 +215,7 @@
     /// @return nullptr
     static inline const ast::Type* AST(ProgramBuilder&) { return nullptr; }
     /// @return nullptr
-    static inline const sem::Type* Sem(ProgramBuilder&) { return nullptr; }
+    static inline const type::Type* Sem(ProgramBuilder&) { return nullptr; }
 };
 
 /// Helper for building bool types and expressions
@@ -232,7 +232,7 @@
     static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.bool_(); }
     /// @param b the ProgramBuilder
     /// @return the semantic bool type
-    static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::Bool>(); }
+    static inline const type::Type* Sem(ProgramBuilder& b) { return b.create<sem::Bool>(); }
     /// @param b the ProgramBuilder
     /// @param args args of size 1 with the boolean value to init with
     /// @return a new AST expression of the bool type
@@ -263,7 +263,7 @@
     static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.i32(); }
     /// @param b the ProgramBuilder
     /// @return the semantic i32 type
-    static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::I32>(); }
+    static inline const type::Type* Sem(ProgramBuilder& b) { return b.create<sem::I32>(); }
     /// @param b the ProgramBuilder
     /// @param args args of size 1 with the i32 value to init with
     /// @return a new AST i32 literal value expression
@@ -294,7 +294,7 @@
     static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.u32(); }
     /// @param b the ProgramBuilder
     /// @return the semantic u32 type
-    static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::U32>(); }
+    static inline const type::Type* Sem(ProgramBuilder& b) { return b.create<sem::U32>(); }
     /// @param b the ProgramBuilder
     /// @param args args of size 1 with the u32 value to init with
     /// @return a new AST u32 literal value expression
@@ -325,7 +325,7 @@
     static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.f32(); }
     /// @param b the ProgramBuilder
     /// @return the semantic f32 type
-    static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::F32>(); }
+    static inline const type::Type* Sem(ProgramBuilder& b) { return b.create<sem::F32>(); }
     /// @param b the ProgramBuilder
     /// @param args args of size 1 with the f32 value to init with
     /// @return a new AST f32 literal value expression
@@ -356,7 +356,7 @@
     static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.f16(); }
     /// @param b the ProgramBuilder
     /// @return the semantic f16 type
-    static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::F16>(); }
+    static inline const type::Type* Sem(ProgramBuilder& b) { return b.create<sem::F16>(); }
     /// @param b the ProgramBuilder
     /// @param args args of size 1 with the f16 value to init with
     /// @return a new AST f16 literal value expression
@@ -386,7 +386,9 @@
     static inline const ast::Type* AST(ProgramBuilder&) { return nullptr; }
     /// @param b the ProgramBuilder
     /// @return the semantic abstract-float type
-    static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::AbstractFloat>(); }
+    static inline const type::Type* Sem(ProgramBuilder& b) {
+        return b.create<sem::AbstractFloat>();
+    }
     /// @param b the ProgramBuilder
     /// @param args args of size 1 with the abstract-float value to init with
     /// @return a new AST abstract-float literal value expression
@@ -416,7 +418,7 @@
     static inline const ast::Type* AST(ProgramBuilder&) { return nullptr; }
     /// @param b the ProgramBuilder
     /// @return the semantic abstract-int type
-    static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::AbstractInt>(); }
+    static inline const type::Type* Sem(ProgramBuilder& b) { return b.create<sem::AbstractInt>(); }
     /// @param b the ProgramBuilder
     /// @param args args of size 1 with the abstract-int value to init with
     /// @return a new AST abstract-int literal value expression
@@ -449,7 +451,7 @@
     }
     /// @param b the ProgramBuilder
     /// @return the semantic vector type
-    static inline const sem::Type* Sem(ProgramBuilder& b) {
+    static inline const type::Type* Sem(ProgramBuilder& b) {
         return b.create<sem::Vector>(DataType<T>::Sem(b), N);
     }
     /// @param b the ProgramBuilder
@@ -497,7 +499,7 @@
     }
     /// @param b the ProgramBuilder
     /// @return the semantic matrix type
-    static inline const sem::Type* Sem(ProgramBuilder& b) {
+    static inline const type::Type* Sem(ProgramBuilder& b) {
         auto* column_type = b.create<sem::Vector>(DataType<T>::Sem(b), M);
         return b.create<sem::Matrix>(column_type, N);
     }
@@ -561,7 +563,7 @@
     }
     /// @param b the ProgramBuilder
     /// @return the semantic aliased type
-    static inline const sem::Type* Sem(ProgramBuilder& b) { return DataType<T>::Sem(b); }
+    static inline const type::Type* Sem(ProgramBuilder& b) { return DataType<T>::Sem(b); }
 
     /// @param b the ProgramBuilder
     /// @param args the value nested elements will be initialized with
@@ -613,7 +615,7 @@
     }
     /// @param b the ProgramBuilder
     /// @return the semantic aliased type
-    static inline const sem::Type* Sem(ProgramBuilder& b) {
+    static inline const type::Type* Sem(ProgramBuilder& b) {
         return b.create<sem::Pointer>(DataType<T>::Sem(b), ast::AddressSpace::kPrivate,
                                       ast::Access::kReadWrite);
     }
@@ -657,13 +659,13 @@
     }
     /// @param b the ProgramBuilder
     /// @return the semantic array type
-    static inline const sem::Type* Sem(ProgramBuilder& b) {
+    static inline const type::Type* Sem(ProgramBuilder& b) {
         auto* el = DataType<T>::Sem(b);
-        const sem::ArrayCount* count = nullptr;
+        const type::ArrayCount* count = nullptr;
         if (N == 0) {
-            count = b.create<sem::RuntimeArrayCount>();
+            count = b.create<type::RuntimeArrayCount>();
         } else {
-            count = b.create<sem::ConstantArrayCount>(N);
+            count = b.create<type::ConstantArrayCount>(N);
         }
         return b.create<sem::Array>(
             /* element */ el,
diff --git a/src/tint/resolver/sem_helper.cc b/src/tint/resolver/sem_helper.cc
index 01a5480..db253a7 100644
--- a/src/tint/resolver/sem_helper.cc
+++ b/src/tint/resolver/sem_helper.cc
@@ -23,17 +23,17 @@
 
 SemHelper::~SemHelper() = default;
 
-std::string SemHelper::TypeNameOf(const sem::Type* ty) const {
+std::string SemHelper::TypeNameOf(const type::Type* ty) const {
     return RawTypeNameOf(ty->UnwrapRef());
 }
 
-std::string SemHelper::RawTypeNameOf(const sem::Type* ty) const {
+std::string SemHelper::RawTypeNameOf(const type::Type* ty) const {
     return ty->FriendlyName(builder_->Symbols());
 }
 
-sem::Type* SemHelper::TypeOf(const ast::Expression* expr) const {
+type::Type* SemHelper::TypeOf(const ast::Expression* expr) const {
     auto* sem = Get(expr);
-    return sem ? const_cast<sem::Type*>(sem->Type()) : nullptr;
+    return sem ? const_cast<type::Type*>(sem->Type()) : nullptr;
 }
 
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/sem_helper.h b/src/tint/resolver/sem_helper.h
index 2e557d9..8e4a5b6 100644
--- a/src/tint/resolver/sem_helper.h
+++ b/src/tint/resolver/sem_helper.h
@@ -49,10 +49,10 @@
         return const_cast<T*>(As<T>(sem));
     }
 
-    /// @returns the resolved symbol (function, type or variable) for the given
-    /// ast::Identifier or ast::TypeName cast to the given semantic type.
+    /// @returns the resolved symbol (function, type or variable) for the given ast::Identifier or
+    /// ast::TypeName cast to the given semantic type.
     /// @param node the node to retrieve
-    template <typename SEM = sem::Node>
+    template <typename SEM = CastableBase>
     SEM* ResolvedSymbol(const ast::Node* node) const {
         auto resolved = dependencies_.resolved_symbols.Find(node);
         return resolved ? const_cast<SEM*>(builder_->Sem().Get<SEM>(*resolved)) : nullptr;
@@ -60,17 +60,17 @@
 
     /// @returns the resolved type of the ast::Expression `expr`
     /// @param expr the expression
-    sem::Type* TypeOf(const ast::Expression* expr) const;
+    type::Type* TypeOf(const ast::Expression* expr) const;
 
     /// @returns the type name of the given semantic type, unwrapping
     /// references.
     /// @param ty the type to look up
-    std::string TypeNameOf(const sem::Type* ty) const;
+    std::string TypeNameOf(const type::Type* ty) const;
 
     /// @returns the type name of the given semantic type, without unwrapping
     /// references.
     /// @param ty the type to look up
-    std::string RawTypeNameOf(const sem::Type* ty) const;
+    std::string RawTypeNameOf(const type::Type* ty) const;
 
   private:
     ProgramBuilder* builder_;
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 558217d..7d1f94b 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -157,7 +157,7 @@
     ProgramBuilder* builder,
     SemHelper& sem,
     const ast::Extensions& enabled_extensions,
-    const utils::Hashmap<const sem::Type*, const Source*, 8>& atomic_composite_info,
+    const utils::Hashmap<const type::Type*, const Source*, 8>& atomic_composite_info,
     utils::Hashset<TypeAndAddressSpace, 8>& valid_type_storage_layouts)
     : symbols_(builder->Symbols()),
       diagnostics_(builder->Diagnostics()),
@@ -181,20 +181,21 @@
 }
 
 // https://gpuweb.github.io/gpuweb/wgsl/#plain-types-section
-bool Validator::IsPlain(const sem::Type* type) const {
+bool Validator::IsPlain(const type::Type* type) const {
     return type->is_scalar() ||
            type->IsAnyOf<sem::Atomic, sem::Vector, sem::Matrix, sem::Array, sem::Struct>();
 }
 
 // https://gpuweb.github.io/gpuweb/wgsl/#fixed-footprint-types
-bool Validator::IsFixedFootprint(const sem::Type* type) const {
+bool Validator::IsFixedFootprint(const type::Type* type) const {
     return Switch(
         type,                                      //
         [&](const sem::Vector*) { return true; },  //
         [&](const sem::Matrix*) { return true; },  //
         [&](const sem::Atomic*) { return true; },
         [&](const sem::Array* arr) {
-            return !arr->Count()->Is<sem::RuntimeArrayCount>() && IsFixedFootprint(arr->ElemType());
+            return !arr->Count()->Is<type::RuntimeArrayCount>() &&
+                   IsFixedFootprint(arr->ElemType());
         },
         [&](const sem::Struct* str) {
             for (auto* member : str->Members()) {
@@ -208,7 +209,7 @@
 }
 
 // https://gpuweb.github.io/gpuweb/wgsl.html#host-shareable-types
-bool Validator::IsHostShareable(const sem::Type* type) const {
+bool Validator::IsHostShareable(const type::Type* type) const {
     if (type->IsAnyOf<sem::I32, sem::U32, sem::F32, sem::F16>()) {
         return true;
     }
@@ -229,7 +230,7 @@
 }
 
 // https://gpuweb.github.io/gpuweb/wgsl.html#storable-types
-bool Validator::IsStorable(const sem::Type* type) const {
+bool Validator::IsStorable(const type::Type* type) const {
     return IsPlain(type) || type->IsAnyOf<sem::Texture, sem::Sampler>();
 }
 
@@ -341,10 +342,10 @@
     return true;
 }
 
-bool Validator::Materialize(const sem::Type* to,
-                            const sem::Type* from,
+bool Validator::Materialize(const type::Type* to,
+                            const type::Type* from,
                             const Source& source) const {
-    if (sem::Type::ConversionRank(from, to) == sem::Type::kNoConversion) {
+    if (type::Type::ConversionRank(from, to) == type::Type::kNoConversion) {
         AddError("cannot convert value of type '" + sem_.TypeNameOf(from) + "' to type '" +
                      sem_.TypeNameOf(to) + "'",
                  source);
@@ -355,7 +356,7 @@
 
 bool Validator::VariableInitializer(const ast::Variable* v,
                                     ast::AddressSpace address_space,
-                                    const sem::Type* storage_ty,
+                                    const type::Type* storage_ty,
                                     const sem::Expression* initializer) const {
     auto* initializer_ty = initializer->Type();
     auto* value_type = initializer_ty->UnwrapRef();  // Implicit load of RHS
@@ -389,21 +390,21 @@
     return true;
 }
 
-bool Validator::AddressSpaceLayout(const sem::Type* store_ty,
+bool Validator::AddressSpaceLayout(const type::Type* store_ty,
                                    ast::AddressSpace address_space,
                                    Source source) const {
     // https://gpuweb.github.io/gpuweb/wgsl/#storage-class-layout-constraints
 
-    auto is_uniform_struct_or_array = [address_space](const sem::Type* ty) {
+    auto is_uniform_struct_or_array = [address_space](const type::Type* ty) {
         return address_space == ast::AddressSpace::kUniform &&
                ty->IsAnyOf<sem::Array, sem::Struct>();
     };
 
-    auto is_uniform_struct = [address_space](const sem::Type* ty) {
+    auto is_uniform_struct = [address_space](const type::Type* ty) {
         return address_space == ast::AddressSpace::kUniform && ty->Is<sem::Struct>();
     };
 
-    auto required_alignment_of = [&](const sem::Type* ty) {
+    auto required_alignment_of = [&](const type::Type* ty) {
         uint32_t actual_align = ty->Align();
         uint32_t required_align = actual_align;
         if (is_uniform_struct_or_array(ty)) {
@@ -433,7 +434,7 @@
 
     // Among three host-shareable address spaces, f16 is supported in "uniform" and
     // "storage" address space, but not "push_constant" address space yet.
-    if (Is<sem::F16>(sem::Type::DeepestElementOf(store_ty)) &&
+    if (Is<sem::F16>(type::Type::DeepestElementOf(store_ty)) &&
         address_space == ast::AddressSpace::kPushConstant) {
         AddError("using f16 types in 'push_constant' address space is not implemented yet", source);
         return false;
@@ -840,7 +841,7 @@
 }
 
 bool Validator::BuiltinAttribute(const ast::BuiltinAttribute* attr,
-                                 const sem::Type* storage_ty,
+                                 const type::Type* storage_ty,
                                  ast::PipelineStage stage,
                                  const bool is_input) const {
     auto* type = storage_ty->UnwrapRef();
@@ -950,7 +951,7 @@
 }
 
 bool Validator::InterpolateAttribute(const ast::InterpolateAttribute* attr,
-                                     const sem::Type* storage_ty) const {
+                                     const type::Type* storage_ty) const {
     auto* type = storage_ty->UnwrapRef();
 
     if (type->is_integer_scalar_or_vector() && attr->type != ast::InterpolationType::kFlat) {
@@ -1064,7 +1065,7 @@
 
     // Inner lambda that is applied to a type and all of its members.
     auto validate_entry_point_attributes_inner = [&](utils::VectorRef<const ast::Attribute*> attrs,
-                                                     const sem::Type* ty, Source source,
+                                                     const type::Type* ty, Source source,
                                                      ParamOrRetType param_or_ret,
                                                      bool is_struct_member,
                                                      std::optional<uint32_t> location) {
@@ -1207,7 +1208,7 @@
 
     // Outer lambda for validating the entry point attributes for a type.
     auto validate_entry_point_attributes = [&](utils::VectorRef<const ast::Attribute*> attrs,
-                                               const sem::Type* ty, Source source,
+                                               const type::Type* ty, Source source,
                                                ParamOrRetType param_or_ret,
                                                std::optional<uint32_t> location) {
         if (!validate_entry_point_attributes_inner(attrs, ty, source, param_or_ret,
@@ -1357,7 +1358,7 @@
     return true;
 }
 
-bool Validator::Bitcast(const ast::BitcastExpression* cast, const sem::Type* to) const {
+bool Validator::Bitcast(const ast::BitcastExpression* cast, const type::Type* to) const {
     auto* from = sem_.TypeOf(cast->expr)->UnwrapRef();
     if (!from->is_numeric_scalar_or_vector()) {
         AddError("'" + sem_.TypeNameOf(from) + "' cannot be bitcast", cast->expr->source);
@@ -1368,7 +1369,7 @@
         return false;
     }
 
-    auto width = [&](const sem::Type* ty) {
+    auto width = [&](const type::Type* ty) {
         if (auto* vec = ty->As<sem::Vector>()) {
             return vec->Width();
         }
@@ -1672,7 +1673,7 @@
             auto* root_ptr_ty = root->Type()->As<sem::Pointer>();
             auto* root_ref_ty = root->Type()->As<sem::Reference>();
             TINT_ASSERT(Resolver, root_ptr_ty || root_ref_ty);
-            const sem::Type* root_store_type;
+            const type::Type* root_store_type;
             if (root_ptr_ty) {
                 root_store_type = root_ptr_ty->StoreType();
             } else {
@@ -1747,7 +1748,7 @@
     auto* elem_ty = array_type->ElemType();
     for (auto* value : values) {
         auto* value_ty = sem_.TypeOf(value)->UnwrapRef();
-        if (sem::Type::ConversionRank(value_ty, elem_ty) == sem::Type::kNoConversion) {
+        if (type::Type::ConversionRank(value_ty, elem_ty) == type::Type::kNoConversion) {
             AddError("'" + sem_.TypeNameOf(value_ty) +
                          "' cannot be used to construct an array of '" + sem_.TypeNameOf(elem_ty) +
                          "'",
@@ -1757,7 +1758,7 @@
     }
 
     auto* c = array_type->Count();
-    if (c->Is<sem::RuntimeArrayCount>()) {
+    if (c->Is<type::RuntimeArrayCount>()) {
         AddError("cannot construct a runtime-sized array", ctor->source);
         return false;
     }
@@ -1772,12 +1773,12 @@
         return false;
     }
 
-    if (!c->Is<sem::ConstantArrayCount>()) {
+    if (!c->Is<type::ConstantArrayCount>()) {
         TINT_ICE(Resolver, diagnostics_) << "Invalid ArrayCount found";
         return false;
     }
 
-    const auto count = c->As<sem::ConstantArrayCount>()->value;
+    const auto count = c->As<type::ConstantArrayCount>()->value;
     if (!values.IsEmpty() && (values.Length() != count)) {
         std::string fm = values.Length() < count ? "few" : "many";
         AddError("array initializer has too " + fm + " elements: expected " +
@@ -2020,7 +2021,7 @@
     utils::Hashset<uint32_t, 8> locations;
     for (auto* member : str->Members()) {
         if (auto* r = member->Type()->As<sem::Array>()) {
-            if (r->Count()->Is<sem::RuntimeArrayCount>()) {
+            if (r->Count()->Is<type::RuntimeArrayCount>()) {
                 if (member != str->Members().Back()) {
                     AddError("runtime arrays may only appear as the last member of a struct",
                              member->Source());
@@ -2134,7 +2135,7 @@
 
 bool Validator::LocationAttribute(const ast::LocationAttribute* loc_attr,
                                   uint32_t location,
-                                  const sem::Type* type,
+                                  const type::Type* type,
                                   utils::Hashset<uint32_t, 8>& locations,
                                   ast::PipelineStage stage,
                                   const Source& source,
@@ -2166,8 +2167,8 @@
 }
 
 bool Validator::Return(const ast::ReturnStatement* ret,
-                       const sem::Type* func_type,
-                       const sem::Type* ret_type,
+                       const type::Type* func_type,
+                       const type::Type* ret_type,
                        sem::Statement* current_statement) const {
     if (func_type->UnwrapRef() != ret_type) {
         AddError("return statement type must match its function return type, returned '" +
@@ -2246,7 +2247,7 @@
     return true;
 }
 
-bool Validator::Assignment(const ast::Statement* a, const sem::Type* rhs_ty) const {
+bool Validator::Assignment(const ast::Statement* a, const type::Type* rhs_ty) const {
     const ast::Expression* lhs;
     const ast::Expression* rhs;
     if (auto* assign = a->As<ast::AssignmentStatement>()) {
@@ -2390,7 +2391,7 @@
     return !IsValidationDisabled(attributes, validation);
 }
 
-bool Validator::IsArrayWithOverrideCount(const sem::Type* ty) const {
+bool Validator::IsArrayWithOverrideCount(const type::Type* ty) const {
     if (auto* arr = ty->UnwrapRef()->As<sem::Array>()) {
         if (arr->Count()->IsAnyOf<sem::NamedOverrideArrayCount, sem::UnnamedOverrideArrayCount>()) {
             return true;
@@ -2406,13 +2407,13 @@
         source);
 }
 
-std::string Validator::VectorPretty(uint32_t size, const sem::Type* element_type) const {
+std::string Validator::VectorPretty(uint32_t size, const type::Type* element_type) const {
     sem::Vector vec_type(element_type, size);
     return vec_type.FriendlyName(symbols_);
 }
 
 bool Validator::CheckTypeAccessAddressSpace(
-    const sem::Type* store_ty,
+    const type::Type* store_ty,
     ast::Access access,
     ast::AddressSpace address_space,
     utils::VectorRef<const tint::ast::Attribute*> attributes,
diff --git a/src/tint/resolver/validator.h b/src/tint/resolver/validator.h
index 9f0664c..99db201 100644
--- a/src/tint/resolver/validator.h
+++ b/src/tint/resolver/validator.h
@@ -70,7 +70,7 @@
 /// TypeAndAddressSpace is a pair of type and address space
 struct TypeAndAddressSpace {
     /// The type
-    const sem::Type* type;
+    const type::Type* type;
     /// The address space
     ast::AddressSpace address_space;
 
@@ -97,7 +97,7 @@
     Validator(ProgramBuilder* builder,
               SemHelper& helper,
               const ast::Extensions& enabled_extensions,
-              const utils::Hashmap<const sem::Type*, const Source*, 8>& atomic_composite_info,
+              const utils::Hashmap<const type::Type*, const Source*, 8>& atomic_composite_info,
               utils::Hashset<TypeAndAddressSpace, 8>& valid_type_storage_layouts);
     ~Validator();
 
@@ -118,19 +118,19 @@
 
     /// @param type the given type
     /// @returns true if the given type is a plain type
-    bool IsPlain(const sem::Type* type) const;
+    bool IsPlain(const type::Type* type) const;
 
     /// @param type the given type
     /// @returns true if the given type is a fixed-footprint type
-    bool IsFixedFootprint(const sem::Type* type) const;
+    bool IsFixedFootprint(const type::Type* type) const;
 
     /// @param type the given type
     /// @returns true if the given type is storable
-    bool IsStorable(const sem::Type* type) const;
+    bool IsStorable(const type::Type* type) const;
 
     /// @param type the given type
     /// @returns true if the given type is host-shareable
-    bool IsHostShareable(const sem::Type* type) const;
+    bool IsHostShareable(const type::Type* type) const;
 
     /// Validates pipeline stages
     /// @param entry_points the entry points to the module
@@ -179,13 +179,13 @@
     /// @param a the assignment statement
     /// @param rhs_ty the type of the right hand side
     /// @returns true on success, false otherwise.
-    bool Assignment(const ast::Statement* a, const sem::Type* rhs_ty) const;
+    bool Assignment(const ast::Statement* a, const type::Type* rhs_ty) const;
 
     /// Validates a bitcase
     /// @param cast the bitcast expression
     /// @param to the destination type
     /// @returns true on success, false otherwise
-    bool Bitcast(const ast::BitcastExpression* cast, const sem::Type* to) const;
+    bool Bitcast(const ast::BitcastExpression* cast, const type::Type* to) const;
 
     /// Validates a break statement
     /// @param stmt the break statement to validate
@@ -200,7 +200,7 @@
     /// @param is_input true if this is an input attribute
     /// @returns true on success, false otherwise.
     bool BuiltinAttribute(const ast::BuiltinAttribute* attr,
-                          const sem::Type* storage_type,
+                          const type::Type* storage_type,
                           ast::PipelineStage stage,
                           const bool is_input) const;
 
@@ -283,7 +283,7 @@
     /// @param storage_type the storage type of the attached variable
     /// @returns true on succes, false otherwise
     bool InterpolateAttribute(const ast::InterpolateAttribute* attr,
-                              const sem::Type* storage_type) const;
+                              const type::Type* storage_type) const;
 
     /// Validates a builtin call
     /// @param call the builtin call to validate
@@ -306,7 +306,7 @@
     /// @returns true on success, false otherwise.
     bool LocationAttribute(const ast::LocationAttribute* loc_attr,
                            uint32_t location,
-                           const sem::Type* type,
+                           const type::Type* type,
                            utils::Hashset<uint32_t, 8>& locations,
                            ast::PipelineStage stage,
                            const Source& source,
@@ -322,7 +322,7 @@
     /// @param from the abstract numeric type
     /// @param source the source of the materialization
     /// @returns true on success, false otherwise
-    bool Materialize(const sem::Type* to, const sem::Type* from, const Source& source) const;
+    bool Materialize(const type::Type* to, const type::Type* from, const Source& source) const;
 
     /// Validates a matrix
     /// @param ty the matrix to validate
@@ -343,8 +343,8 @@
     /// @param current_statement the current statement being resolved
     /// @returns true on success, false otherwise
     bool Return(const ast::ReturnStatement* ret,
-                const sem::Type* func_type,
-                const sem::Type* ret_type,
+                const type::Type* func_type,
+                const type::Type* ret_type,
                 sem::Statement* current_statement) const;
 
     /// Validates a list of statements
@@ -417,7 +417,7 @@
     /// @returns true on succes, false otherwise
     bool VariableInitializer(const ast::Variable* v,
                              ast::AddressSpace address_space,
-                             const sem::Type* storage_type,
+                             const type::Type* storage_type,
                              const sem::Expression* initializer) const;
 
     /// Validates a vector
@@ -452,7 +452,7 @@
     /// @param sc the address space
     /// @param source the source of the type
     /// @returns true on success, false otherwise
-    bool AddressSpaceLayout(const sem::Type* type, ast::AddressSpace sc, Source source) const;
+    bool AddressSpaceLayout(const type::Type* type, ast::AddressSpace sc, Source source) const;
 
     /// @returns true if the attribute list contains a
     /// ast::DisableValidationAttribute with the validation mode equal to
@@ -474,7 +474,7 @@
     /// @param ty the type to check
     /// @returns true if @p ty is an array with an `override` expression element count, otherwise
     ///          false.
-    bool IsArrayWithOverrideCount(const sem::Type* ty) const;
+    bool IsArrayWithOverrideCount(const type::Type* ty) const;
 
     /// Raises an error about an array type using an `override` expression element count, outside
     /// the single allowed use of a `var<workgroup>`.
@@ -496,7 +496,7 @@
     /// @param size the vector dimension
     /// @param element_type scalar vector sub-element type
     /// @return pretty string representation
-    std::string VectorPretty(uint32_t size, const sem::Type* element_type) const;
+    std::string VectorPretty(uint32_t size, const type::Type* element_type) const;
 
     /// Raises an error if combination of @p store_ty, @p access and @p address_space are not valid
     /// for a `var` or `ptr` declaration.
@@ -505,7 +505,7 @@
     /// @param address_space the var or pointer address space
     /// @param source the source for the error
     /// @returns true on success, false if an error was raised.
-    bool CheckTypeAccessAddressSpace(const sem::Type* store_ty,
+    bool CheckTypeAccessAddressSpace(const type::Type* store_ty,
                                      ast::Access access,
                                      ast::AddressSpace address_space,
                                      utils::VectorRef<const tint::ast::Attribute*> attributes,
@@ -514,7 +514,7 @@
     diag::List& diagnostics_;
     SemHelper& sem_;
     const ast::Extensions& enabled_extensions_;
-    const utils::Hashmap<const sem::Type*, const Source*, 8>& atomic_composite_info_;
+    const utils::Hashmap<const type::Type*, const Source*, 8>& atomic_composite_info_;
     utils::Hashset<TypeAndAddressSpace, 8>& valid_type_storage_layouts_;
 };
 
diff --git a/src/tint/resolver/validator_is_storeable_test.cc b/src/tint/resolver/validator_is_storeable_test.cc
index cd079ce..3dd144c 100644
--- a/src/tint/resolver/validator_is_storeable_test.cc
+++ b/src/tint/resolver/validator_is_storeable_test.cc
@@ -89,14 +89,14 @@
 }
 
 TEST_F(ValidatorIsStorableTest, ArraySizedOfStorable) {
-    auto* arr = create<sem::Array>(create<sem::I32>(), create<sem::ConstantArrayCount>(5u), 4u, 20u,
-                                   4u, 4u);
+    auto* arr = create<sem::Array>(create<sem::I32>(), create<type::ConstantArrayCount>(5u), 4u,
+                                   20u, 4u, 4u);
     EXPECT_TRUE(v()->IsStorable(arr));
 }
 
 TEST_F(ValidatorIsStorableTest, ArrayUnsizedOfStorable) {
     auto* arr =
-        create<sem::Array>(create<sem::I32>(), create<sem::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
+        create<sem::Array>(create<sem::I32>(), create<type::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
     EXPECT_TRUE(v()->IsStorable(arr));
 }
 
diff --git a/src/tint/sem/abstract_float.cc b/src/tint/sem/abstract_float.cc
index 6f32e99..50ddfd3 100644
--- a/src/tint/sem/abstract_float.cc
+++ b/src/tint/sem/abstract_float.cc
@@ -29,7 +29,7 @@
     return utils::Hash(TypeInfo::Of<AbstractFloat>().full_hashcode);
 }
 
-bool AbstractFloat::Equals(const sem::Type& other) const {
+bool AbstractFloat::Equals(const type::Type& other) const {
     return other.Is<AbstractFloat>();
 }
 
diff --git a/src/tint/sem/abstract_int.cc b/src/tint/sem/abstract_int.cc
index 682c50a..9ab7a0a 100644
--- a/src/tint/sem/abstract_int.cc
+++ b/src/tint/sem/abstract_int.cc
@@ -29,7 +29,7 @@
     return utils::Hash(TypeInfo::Of<AbstractInt>().full_hashcode);
 }
 
-bool AbstractInt::Equals(const sem::Type& other) const {
+bool AbstractInt::Equals(const type::Type& other) const {
     return other.Is<AbstractInt>();
 }
 
diff --git a/src/tint/sem/abstract_numeric.cc b/src/tint/sem/abstract_numeric.cc
index eea9abe..e19f133 100644
--- a/src/tint/sem/abstract_numeric.cc
+++ b/src/tint/sem/abstract_numeric.cc
@@ -19,7 +19,7 @@
 namespace tint::sem {
 
 AbstractNumeric::AbstractNumeric()
-    : Base(TypeFlags{
+    : Base(type::TypeFlags{
           Flag::kConstructable,
           Flag::kCreationFixedFootprint,
           Flag::kFixedFootprint,
diff --git a/src/tint/sem/abstract_numeric.h b/src/tint/sem/abstract_numeric.h
index 68f21ae..665044c 100644
--- a/src/tint/sem/abstract_numeric.h
+++ b/src/tint/sem/abstract_numeric.h
@@ -17,13 +17,13 @@
 
 #include <string>
 
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 namespace tint::sem {
 
 /// The base class for abstract-int and abstract-float types.
 /// @see https://www.w3.org/TR/WGSL/#types-for-creation-time-constants
-class AbstractNumeric : public Castable<AbstractNumeric, Type> {
+class AbstractNumeric : public Castable<AbstractNumeric, type::Type> {
   public:
     /// Constructor
     AbstractNumeric();
diff --git a/src/tint/sem/array.cc b/src/tint/sem/array.cc
index 207a627..d936c94 100644
--- a/src/tint/sem/array.cc
+++ b/src/tint/sem/array.cc
@@ -28,20 +28,21 @@
 
 namespace {
 
-TypeFlags FlagsFrom(const Type* element, const ArrayCount* count) {
-    TypeFlags flags;
+type::TypeFlags FlagsFrom(const type::Type* element, const type::ArrayCount* count) {
+    type::TypeFlags flags;
     // Only constant-expression sized arrays are constructible
-    if (count->Is<ConstantArrayCount>()) {
+    if (count->Is<type::ConstantArrayCount>()) {
         if (element->IsConstructible()) {
-            flags.Add(TypeFlag::kConstructable);
+            flags.Add(type::TypeFlag::kConstructable);
         }
         if (element->HasCreationFixedFootprint()) {
-            flags.Add(TypeFlag::kCreationFixedFootprint);
+            flags.Add(type::TypeFlag::kCreationFixedFootprint);
         }
     }
-    if (count->IsAnyOf<ConstantArrayCount, NamedOverrideArrayCount, UnnamedOverrideArrayCount>()) {
+    if (count->IsAnyOf<type::ConstantArrayCount, sem::NamedOverrideArrayCount,
+                       sem::UnnamedOverrideArrayCount>()) {
         if (element->HasFixedFootprint()) {
-            flags.Add(TypeFlag::kFixedFootprint);
+            flags.Add(type::TypeFlag::kFixedFootprint);
         }
     }
     return flags;
@@ -53,8 +54,8 @@
     "array size is an override-expression, when expected a constant-expression.\n"
     "Was the SubstituteOverride transform run?";
 
-Array::Array(const Type* element,
-             const ArrayCount* count,
+Array::Array(const type::Type* element,
+             const type::ArrayCount* count,
              uint32_t align,
              uint32_t size,
              uint32_t stride,
@@ -73,7 +74,7 @@
     return utils::Hash(TypeInfo::Of<Array>().full_hashcode, count_, align_, size_, stride_);
 }
 
-bool Array::Equals(const sem::Type& other) const {
+bool Array::Equals(const type::Type& other) const {
     if (auto* o = other.As<Array>()) {
         // Note: implicit_stride is not part of the type_name string as this is
         // derived from the element type
@@ -89,11 +90,11 @@
         out << "@stride(" << stride_ << ") ";
     }
     out << "array<" << element_->FriendlyName(symbols);
-    if (auto* const_count = count_->As<ConstantArrayCount>()) {
+    if (auto* const_count = count_->As<type::ConstantArrayCount>()) {
         out << ", " << const_count->value;
-    } else if (auto* named_override_count = count_->As<NamedOverrideArrayCount>()) {
+    } else if (auto* named_override_count = count_->As<sem::NamedOverrideArrayCount>()) {
         out << ", " << symbols.NameFor(named_override_count->variable->Declaration()->symbol);
-    } else if (count_->Is<UnnamedOverrideArrayCount>()) {
+    } else if (count_->Is<sem::UnnamedOverrideArrayCount>()) {
         out << ", [unnamed override-expression]";
     }
     out << ">";
diff --git a/src/tint/sem/array.h b/src/tint/sem/array.h
index 7a484e5..93b9fa6 100644
--- a/src/tint/sem/array.h
+++ b/src/tint/sem/array.h
@@ -22,7 +22,7 @@
 
 #include "src/tint/sem/array_count.h"
 #include "src/tint/sem/node.h"
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 #include "src/tint/utils/compiler_macros.h"
 #include "src/tint/utils/unique_vector.h"
 
@@ -35,7 +35,7 @@
 namespace tint::sem {
 
 /// Array holds the semantic information for Array nodes.
-class Array final : public Castable<Array, Type> {
+class Array final : public Castable<Array, type::Type> {
   public:
     /// An error message string stating that the array count was expected to be a constant
     /// expression. Used by multiple writers and transforms.
@@ -52,8 +52,8 @@
     /// @param implicit_stride the number of bytes from the start of one element
     /// of the array to the start of the next element, if there was no `@stride`
     /// attribute applied.
-    Array(Type const* element,
-          const ArrayCount* count,
+    Array(type::Type const* element,
+          const type::ArrayCount* count,
           uint32_t align,
           uint32_t size,
           uint32_t stride,
@@ -64,17 +64,17 @@
 
     /// @param other the other type to compare against
     /// @returns true if the this type is equal to the given type
-    bool Equals(const Type& other) const override;
+    bool Equals(const type::Type& other) const override;
 
     /// @return the array element type
-    Type const* ElemType() const { return element_; }
+    type::Type const* ElemType() const { return element_; }
 
     /// @returns the number of elements in the array.
-    const ArrayCount* Count() const { return count_; }
+    const type::ArrayCount* Count() const { return count_; }
 
     /// @returns the array count if the count is a const-expression, otherwise returns nullopt.
     inline std::optional<uint32_t> ConstantCount() const {
-        if (auto* count = count_->As<ConstantArrayCount>()) {
+        if (auto* count = count_->As<type::ConstantArrayCount>()) {
             return count->value;
         }
         return std::nullopt;
@@ -109,8 +109,8 @@
     std::string FriendlyName(const SymbolTable& symbols) const override;
 
   private:
-    Type const* const element_;
-    const ArrayCount* count_;
+    type::Type const* const element_;
+    const type::ArrayCount* count_;
     const uint32_t align_;
     const uint32_t size_;
     const uint32_t stride_;
diff --git a/src/tint/sem/array_count.cc b/src/tint/sem/array_count.cc
index fa16639..8bb09bc 100644
--- a/src/tint/sem/array_count.cc
+++ b/src/tint/sem/array_count.cc
@@ -14,42 +14,11 @@
 
 #include "src/tint/sem/array_count.h"
 
-TINT_INSTANTIATE_TYPEINFO(tint::sem::ArrayCount);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::ConstantArrayCount);
-TINT_INSTANTIATE_TYPEINFO(tint::sem::RuntimeArrayCount);
 TINT_INSTANTIATE_TYPEINFO(tint::sem::NamedOverrideArrayCount);
 TINT_INSTANTIATE_TYPEINFO(tint::sem::UnnamedOverrideArrayCount);
 
 namespace tint::sem {
 
-ArrayCount::ArrayCount() : Base() {}
-ArrayCount::~ArrayCount() = default;
-
-ConstantArrayCount::ConstantArrayCount(uint32_t val) : Base(), value(val) {}
-ConstantArrayCount::~ConstantArrayCount() = default;
-
-size_t ConstantArrayCount::Hash() const {
-    return static_cast<size_t>(TypeInfo::Of<ConstantArrayCount>().full_hashcode);
-}
-
-bool ConstantArrayCount::Equals(const ArrayCount& other) const {
-    if (auto* v = other.As<ConstantArrayCount>()) {
-        return value == v->value;
-    }
-    return false;
-}
-
-RuntimeArrayCount::RuntimeArrayCount() : Base() {}
-RuntimeArrayCount::~RuntimeArrayCount() = default;
-
-size_t RuntimeArrayCount::Hash() const {
-    return static_cast<size_t>(TypeInfo::Of<RuntimeArrayCount>().full_hashcode);
-}
-
-bool RuntimeArrayCount::Equals(const ArrayCount& other) const {
-    return other.Is<RuntimeArrayCount>();
-}
-
 NamedOverrideArrayCount::NamedOverrideArrayCount(const GlobalVariable* var)
     : Base(), variable(var) {}
 NamedOverrideArrayCount::~NamedOverrideArrayCount() = default;
diff --git a/src/tint/sem/array_count.h b/src/tint/sem/array_count.h
index eb1a001..0a64cf3 100644
--- a/src/tint/sem/array_count.h
+++ b/src/tint/sem/array_count.h
@@ -15,81 +15,19 @@
 #ifndef SRC_TINT_SEM_ARRAY_COUNT_H_
 #define SRC_TINT_SEM_ARRAY_COUNT_H_
 
-#include <functional>
-#include <string>
-
 #include "src/tint/sem/expression.h"
-#include "src/tint/sem/node.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/type/array_count.h"
 
 namespace tint::sem {
 
-/// An array count
-class ArrayCount : public Castable<ArrayCount, Node> {
-  public:
-    ~ArrayCount() override;
-
-    /// @returns a hash of the array count.
-    virtual size_t Hash() const = 0;
-
-    /// @param t other array count
-    /// @returns true if this array count is equal to the given array count
-    virtual bool Equals(const ArrayCount& t) const = 0;
-
-  protected:
-    ArrayCount();
-};
-
-/// The variant of an ArrayCount when the array is a const-expression.
-/// Example:
-/// ```
-/// const N = 123;
-/// type arr = array<i32, N>
-/// ```
-class ConstantArrayCount final : public Castable<ConstantArrayCount, ArrayCount> {
-  public:
-    /// Constructor
-    /// @param val the constant-expression value
-    explicit ConstantArrayCount(uint32_t val);
-    ~ConstantArrayCount() override;
-
-    /// @returns a hash of the array count.
-    size_t Hash() const override;
-
-    /// @param t other array count
-    /// @returns true if this array count is equal to the given array count
-    bool Equals(const ArrayCount& t) const override;
-
-    /// The array count constant-expression value.
-    uint32_t value;
-};
-
-/// The variant of an ArrayCount when the array is is runtime-sized.
-/// Example:
-/// ```
-/// type arr = array<i32>
-/// ```
-class RuntimeArrayCount final : public Castable<RuntimeArrayCount, ArrayCount> {
-  public:
-    /// Constructor
-    RuntimeArrayCount();
-    ~RuntimeArrayCount() override;
-
-    /// @returns a hash of the array count.
-    size_t Hash() const override;
-
-    /// @param t other array count
-    /// @returns true if this array count is equal to the given array count
-    bool Equals(const ArrayCount& t) const override;
-};
-
 /// The variant of an ArrayCount when the count is a named override variable.
 /// Example:
 /// ```
 /// override N : i32;
 /// type arr = array<i32, N>
 /// ```
-class NamedOverrideArrayCount final : public Castable<NamedOverrideArrayCount, ArrayCount> {
+class NamedOverrideArrayCount final : public Castable<NamedOverrideArrayCount, type::ArrayCount> {
   public:
     /// Constructor
     /// @param var the `override` variable
@@ -101,7 +39,7 @@
 
     /// @param t other array count
     /// @returns true if this array count is equal to the given array count
-    bool Equals(const ArrayCount& t) const override;
+    bool Equals(const type::ArrayCount& t) const override;
 
     /// The `override` variable.
     const GlobalVariable* variable;
@@ -113,7 +51,8 @@
 /// override N : i32;
 /// type arr = array<i32, N*2>
 /// ```
-class UnnamedOverrideArrayCount final : public Castable<UnnamedOverrideArrayCount, ArrayCount> {
+class UnnamedOverrideArrayCount final
+    : public Castable<UnnamedOverrideArrayCount, type::ArrayCount> {
   public:
     /// Constructor
     /// @param e the override expression
@@ -125,7 +64,7 @@
 
     /// @param t other array count
     /// @returns true if this array count is equal to the given array count
-    bool Equals(const ArrayCount& t) const override;
+    bool Equals(const type::ArrayCount& t) const override;
 
     /// The unnamed override expression.
     /// Note: Each AST expression gets a unique semantic expression node, so two equivalent AST
@@ -144,27 +83,4 @@
 
 }  // namespace tint::sem
 
-namespace std {
-
-/// std::hash specialization for tint::sem::ArrayCount
-template <>
-struct hash<tint::sem::ArrayCount> {
-    /// @param a the array count to obtain a hash from
-    /// @returns the hash of the array count
-    size_t operator()(const tint::sem::ArrayCount& a) const { return a.Hash(); }
-};
-
-/// std::equal_to specialization for tint::sem::ArrayCount
-template <>
-struct equal_to<tint::sem::ArrayCount> {
-    /// @param a the first array count to compare
-    /// @param b the second array count to compare
-    /// @returns true if the two array counts are equal
-    bool operator()(const tint::sem::ArrayCount& a, const tint::sem::ArrayCount& b) const {
-        return a.Equals(b);
-    }
-};
-
-}  // namespace std
-
 #endif  // SRC_TINT_SEM_ARRAY_COUNT_H_
diff --git a/src/tint/sem/array_test.cc b/src/tint/sem/array_test.cc
index a2bc3d3..05788a5 100644
--- a/src/tint/sem/array_test.cc
+++ b/src/tint/sem/array_test.cc
@@ -21,22 +21,22 @@
 using ArrayTest = TestHelper;
 
 TEST_F(ArrayTest, CreateSizedArray) {
-    auto* a = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
-    auto* b = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
-    auto* c = create<Array>(create<U32>(), create<ConstantArrayCount>(3u), 4u, 8u, 32u, 16u);
-    auto* d = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 5u, 8u, 32u, 16u);
-    auto* e = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 9u, 32u, 16u);
-    auto* f = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 33u, 16u);
-    auto* g = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 33u, 17u);
+    auto* a = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* b = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* c = create<Array>(create<U32>(), create<type::ConstantArrayCount>(3u), 4u, 8u, 32u, 16u);
+    auto* d = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 5u, 8u, 32u, 16u);
+    auto* e = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 9u, 32u, 16u);
+    auto* f = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 33u, 16u);
+    auto* g = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 33u, 17u);
 
     EXPECT_EQ(a->ElemType(), create<U32>());
-    EXPECT_EQ(a->Count(), create<ConstantArrayCount>(2u));
+    EXPECT_EQ(a->Count(), create<type::ConstantArrayCount>(2u));
     EXPECT_EQ(a->Align(), 4u);
     EXPECT_EQ(a->Size(), 8u);
     EXPECT_EQ(a->Stride(), 32u);
     EXPECT_EQ(a->ImplicitStride(), 16u);
     EXPECT_FALSE(a->IsStrideImplicit());
-    EXPECT_FALSE(a->Count()->Is<RuntimeArrayCount>());
+    EXPECT_FALSE(a->Count()->Is<type::RuntimeArrayCount>());
 
     EXPECT_EQ(a, b);
     EXPECT_NE(a, c);
@@ -47,21 +47,21 @@
 }
 
 TEST_F(ArrayTest, CreateRuntimeArray) {
-    auto* a = create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 32u, 32u);
-    auto* b = create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 32u, 32u);
-    auto* c = create<Array>(create<U32>(), create<RuntimeArrayCount>(), 5u, 8u, 32u, 32u);
-    auto* d = create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 9u, 32u, 32u);
-    auto* e = create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 33u, 32u);
-    auto* f = create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 33u, 17u);
+    auto* a = create<Array>(create<U32>(), create<type::RuntimeArrayCount>(), 4u, 8u, 32u, 32u);
+    auto* b = create<Array>(create<U32>(), create<type::RuntimeArrayCount>(), 4u, 8u, 32u, 32u);
+    auto* c = create<Array>(create<U32>(), create<type::RuntimeArrayCount>(), 5u, 8u, 32u, 32u);
+    auto* d = create<Array>(create<U32>(), create<type::RuntimeArrayCount>(), 4u, 9u, 32u, 32u);
+    auto* e = create<Array>(create<U32>(), create<type::RuntimeArrayCount>(), 4u, 8u, 33u, 32u);
+    auto* f = create<Array>(create<U32>(), create<type::RuntimeArrayCount>(), 4u, 8u, 33u, 17u);
 
     EXPECT_EQ(a->ElemType(), create<U32>());
-    EXPECT_EQ(a->Count(), create<sem::RuntimeArrayCount>());
+    EXPECT_EQ(a->Count(), create<type::RuntimeArrayCount>());
     EXPECT_EQ(a->Align(), 4u);
     EXPECT_EQ(a->Size(), 8u);
     EXPECT_EQ(a->Stride(), 32u);
     EXPECT_EQ(a->ImplicitStride(), 32u);
     EXPECT_TRUE(a->IsStrideImplicit());
-    EXPECT_TRUE(a->Count()->Is<RuntimeArrayCount>());
+    EXPECT_TRUE(a->Count()->Is<type::RuntimeArrayCount>());
 
     EXPECT_EQ(a, b);
     EXPECT_NE(a, c);
@@ -71,13 +71,13 @@
 }
 
 TEST_F(ArrayTest, Hash) {
-    auto* a = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
-    auto* b = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
-    auto* c = create<Array>(create<U32>(), create<ConstantArrayCount>(3u), 4u, 8u, 32u, 16u);
-    auto* d = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 5u, 8u, 32u, 16u);
-    auto* e = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 9u, 32u, 16u);
-    auto* f = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 33u, 16u);
-    auto* g = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 33u, 17u);
+    auto* a = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* b = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* c = create<Array>(create<U32>(), create<type::ConstantArrayCount>(3u), 4u, 8u, 32u, 16u);
+    auto* d = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 5u, 8u, 32u, 16u);
+    auto* e = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 9u, 32u, 16u);
+    auto* f = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 33u, 16u);
+    auto* g = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 33u, 17u);
 
     EXPECT_EQ(a->Hash(), b->Hash());
     EXPECT_NE(a->Hash(), c->Hash());
@@ -88,13 +88,13 @@
 }
 
 TEST_F(ArrayTest, Equals) {
-    auto* a = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
-    auto* b = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
-    auto* c = create<Array>(create<U32>(), create<ConstantArrayCount>(3u), 4u, 8u, 32u, 16u);
-    auto* d = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 5u, 8u, 32u, 16u);
-    auto* e = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 9u, 32u, 16u);
-    auto* f = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 33u, 16u);
-    auto* g = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 33u, 17u);
+    auto* a = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* b = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* c = create<Array>(create<U32>(), create<type::ConstantArrayCount>(3u), 4u, 8u, 32u, 16u);
+    auto* d = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 5u, 8u, 32u, 16u);
+    auto* e = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 9u, 32u, 16u);
+    auto* f = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 33u, 16u);
+    auto* g = create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 33u, 17u);
 
     EXPECT_TRUE(a->Equals(*b));
     EXPECT_FALSE(a->Equals(*c));
@@ -106,34 +106,34 @@
 }
 
 TEST_F(ArrayTest, FriendlyNameRuntimeSized) {
-    auto* arr = create<Array>(create<I32>(), create<RuntimeArrayCount>(), 0u, 4u, 4u, 4u);
+    auto* arr = create<Array>(create<I32>(), create<type::RuntimeArrayCount>(), 0u, 4u, 4u, 4u);
     EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32>");
 }
 
 TEST_F(ArrayTest, FriendlyNameStaticSized) {
-    auto* arr = create<Array>(create<I32>(), create<ConstantArrayCount>(5u), 4u, 20u, 4u, 4u);
+    auto* arr = create<Array>(create<I32>(), create<type::ConstantArrayCount>(5u), 4u, 20u, 4u, 4u);
     EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, 5>");
 }
 
 TEST_F(ArrayTest, FriendlyNameRuntimeSizedNonImplicitStride) {
-    auto* arr = create<Array>(create<I32>(), create<RuntimeArrayCount>(), 0u, 4u, 8u, 4u);
+    auto* arr = create<Array>(create<I32>(), create<type::RuntimeArrayCount>(), 0u, 4u, 8u, 4u);
     EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(8) array<i32>");
 }
 
 TEST_F(ArrayTest, FriendlyNameStaticSizedNonImplicitStride) {
-    auto* arr = create<Array>(create<I32>(), create<ConstantArrayCount>(5u), 4u, 20u, 8u, 4u);
+    auto* arr = create<Array>(create<I32>(), create<type::ConstantArrayCount>(5u), 4u, 20u, 8u, 4u);
     EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(8) array<i32, 5>");
 }
 
 TEST_F(ArrayTest, IsConstructable) {
     auto* fixed_sized =
-        create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
-    auto* named_override_sized =
-        create<Array>(create<U32>(), create<NamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
-    auto* unnamed_override_sized =
-        create<Array>(create<U32>(), create<UnnamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
+        create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* named_override_sized = create<Array>(
+        create<U32>(), create<sem::NamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
+    auto* unnamed_override_sized = create<Array>(
+        create<U32>(), create<sem::UnnamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
     auto* runtime_sized =
-        create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 32u, 16u);
+        create<Array>(create<U32>(), create<type::RuntimeArrayCount>(), 4u, 8u, 32u, 16u);
 
     EXPECT_TRUE(fixed_sized->IsConstructible());
     EXPECT_FALSE(named_override_sized->IsConstructible());
@@ -143,13 +143,13 @@
 
 TEST_F(ArrayTest, HasCreationFixedFootprint) {
     auto* fixed_sized =
-        create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
-    auto* named_override_sized =
-        create<Array>(create<U32>(), create<NamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
-    auto* unnamed_override_sized =
-        create<Array>(create<U32>(), create<UnnamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
+        create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* named_override_sized = create<Array>(
+        create<U32>(), create<sem::NamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
+    auto* unnamed_override_sized = create<Array>(
+        create<U32>(), create<sem::UnnamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
     auto* runtime_sized =
-        create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 32u, 16u);
+        create<Array>(create<U32>(), create<type::RuntimeArrayCount>(), 4u, 8u, 32u, 16u);
 
     EXPECT_TRUE(fixed_sized->HasCreationFixedFootprint());
     EXPECT_FALSE(named_override_sized->HasCreationFixedFootprint());
@@ -159,13 +159,13 @@
 
 TEST_F(ArrayTest, HasFixedFootprint) {
     auto* fixed_sized =
-        create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
-    auto* named_override_sized =
-        create<Array>(create<U32>(), create<NamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
-    auto* unnamed_override_sized =
-        create<Array>(create<U32>(), create<UnnamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
+        create<Array>(create<U32>(), create<type::ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* named_override_sized = create<Array>(
+        create<U32>(), create<sem::NamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
+    auto* unnamed_override_sized = create<Array>(
+        create<U32>(), create<sem::UnnamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
     auto* runtime_sized =
-        create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 32u, 16u);
+        create<Array>(create<U32>(), create<type::RuntimeArrayCount>(), 4u, 8u, 32u, 16u);
 
     EXPECT_TRUE(fixed_sized->HasFixedFootprint());
     EXPECT_TRUE(named_override_sized->HasFixedFootprint());
diff --git a/src/tint/sem/atomic.cc b/src/tint/sem/atomic.cc
index f8cf068..7f62be8 100644
--- a/src/tint/sem/atomic.cc
+++ b/src/tint/sem/atomic.cc
@@ -22,8 +22,8 @@
 
 namespace tint::sem {
 
-Atomic::Atomic(const sem::Type* subtype)
-    : Base(TypeFlags{
+Atomic::Atomic(const type::Type* subtype)
+    : Base(type::TypeFlags{
           Flag::kCreationFixedFootprint,
           Flag::kFixedFootprint,
       }),
@@ -35,7 +35,7 @@
     return utils::Hash(TypeInfo::Of<Atomic>().full_hashcode, subtype_);
 }
 
-bool Atomic::Equals(const sem::Type& other) const {
+bool Atomic::Equals(const type::Type& other) const {
     if (auto* o = other.As<Atomic>()) {
         return o->subtype_ == subtype_;
     }
diff --git a/src/tint/sem/atomic.h b/src/tint/sem/atomic.h
index 5b405a1..f1e89e9 100644
--- a/src/tint/sem/atomic.h
+++ b/src/tint/sem/atomic.h
@@ -17,16 +17,16 @@
 
 #include <string>
 
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 namespace tint::sem {
 
 /// A atomic type.
-class Atomic final : public Castable<Atomic, Type> {
+class Atomic final : public Castable<Atomic, type::Type> {
   public:
     /// Constructor
     /// @param subtype the atomic type
-    explicit Atomic(const sem::Type* subtype);
+    explicit Atomic(const type::Type* subtype);
 
     /// Move constructor
     Atomic(Atomic&&);
@@ -40,7 +40,7 @@
     bool Equals(const Type& other) const override;
 
     /// @returns the atomic type
-    const sem::Type* Type() const { return subtype_; }
+    const type::Type* Type() const { return subtype_; }
 
     /// @param symbols the program's symbol table
     /// @returns the name for this type that closely resembles how it would be
@@ -54,7 +54,7 @@
     uint32_t Align() const override;
 
   private:
-    sem::Type const* const subtype_;
+    type::Type const* const subtype_;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/bool.cc b/src/tint/sem/bool.cc
index fea6679..4c38335 100644
--- a/src/tint/sem/bool.cc
+++ b/src/tint/sem/bool.cc
@@ -21,7 +21,7 @@
 namespace tint::sem {
 
 Bool::Bool()
-    : Base(TypeFlags{
+    : Base(type::TypeFlags{
           Flag::kConstructable,
           Flag::kCreationFixedFootprint,
           Flag::kFixedFootprint,
diff --git a/src/tint/sem/bool.h b/src/tint/sem/bool.h
index 133ab72..f70f6f6 100644
--- a/src/tint/sem/bool.h
+++ b/src/tint/sem/bool.h
@@ -17,7 +17,7 @@
 
 #include <string>
 
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 // X11 likes to #define Bool leading to confusing error messages.
 // If its defined, undefine it.
@@ -28,7 +28,7 @@
 namespace tint::sem {
 
 /// A boolean type
-class Bool final : public Castable<Bool, Type> {
+class Bool final : public Castable<Bool, type::Type> {
   public:
     /// Constructor
     Bool();
diff --git a/src/tint/sem/builtin.cc b/src/tint/sem/builtin.cc
index 68d8fc8..3aafbce 100644
--- a/src/tint/sem/builtin.cc
+++ b/src/tint/sem/builtin.cc
@@ -106,7 +106,7 @@
 }
 
 Builtin::Builtin(BuiltinType type,
-                 const sem::Type* return_type,
+                 const type::Type* return_type,
                  utils::VectorRef<Parameter*> parameters,
                  EvaluationStage eval_stage,
                  PipelineStageSet supported_stages,
diff --git a/src/tint/sem/builtin.h b/src/tint/sem/builtin.h
index b63bece..a733131 100644
--- a/src/tint/sem/builtin.h
+++ b/src/tint/sem/builtin.h
@@ -89,7 +89,7 @@
     /// @param is_deprecated true if the particular overload is considered
     /// deprecated
     Builtin(BuiltinType type,
-            const sem::Type* return_type,
+            const type::Type* return_type,
             utils::VectorRef<Parameter*> parameters,
             EvaluationStage eval_stage,
             PipelineStageSet supported_stages,
diff --git a/src/tint/sem/call_target.cc b/src/tint/sem/call_target.cc
index d063fdd..93fe0f8 100644
--- a/src/tint/sem/call_target.cc
+++ b/src/tint/sem/call_target.cc
@@ -23,7 +23,7 @@
 
 namespace tint::sem {
 
-CallTarget::CallTarget(const sem::Type* return_type,
+CallTarget::CallTarget(const type::Type* return_type,
                        utils::VectorRef<const Parameter*> parameters,
                        EvaluationStage stage)
     : signature_{return_type, std::move(parameters)}, stage_(stage) {
@@ -33,7 +33,7 @@
 CallTarget::CallTarget(const CallTarget&) = default;
 CallTarget::~CallTarget() = default;
 
-CallTargetSignature::CallTargetSignature(const sem::Type* ret_ty,
+CallTargetSignature::CallTargetSignature(const type::Type* ret_ty,
                                          utils::VectorRef<const sem::Parameter*> params)
     : return_type(ret_ty), parameters(std::move(params)) {}
 CallTargetSignature::CallTargetSignature(const CallTargetSignature&) = default;
diff --git a/src/tint/sem/call_target.h b/src/tint/sem/call_target.h
index 55b73ad..013c490 100644
--- a/src/tint/sem/call_target.h
+++ b/src/tint/sem/call_target.h
@@ -23,11 +23,6 @@
 #include "src/tint/utils/hash.h"
 #include "src/tint/utils/vector.h"
 
-// Forward declarations
-namespace tint::sem {
-class Type;
-}  // namespace tint::sem
-
 namespace tint::sem {
 
 /// CallTargetSignature holds the return type and parameters for a call target
@@ -35,7 +30,7 @@
     /// Constructor
     /// @param ret_ty the call target return type
     /// @param params the call target parameters
-    CallTargetSignature(const sem::Type* ret_ty, utils::VectorRef<const Parameter*> params);
+    CallTargetSignature(const type::Type* ret_ty, utils::VectorRef<const Parameter*> params);
 
     /// Copy constructor
     CallTargetSignature(const CallTargetSignature&);
@@ -44,7 +39,7 @@
     ~CallTargetSignature();
 
     /// The type of the call target return value
-    const sem::Type* const return_type = nullptr;
+    const type::Type* const return_type = nullptr;
     /// The parameters of the call target
     const utils::Vector<const sem::Parameter*, 8> parameters;
 
@@ -75,7 +70,7 @@
     /// @param stage the earliest evaluation stage for a call to this target
     /// @param return_type the return type of the call target
     /// @param parameters the parameters for the call target
-    CallTarget(const sem::Type* return_type,
+    CallTarget(const type::Type* return_type,
                utils::VectorRef<const Parameter*> parameters,
                EvaluationStage stage);
 
@@ -86,7 +81,7 @@
     ~CallTarget() override;
 
     /// @return the return type of the call target
-    const sem::Type* ReturnType() const { return signature_.return_type; }
+    const type::Type* ReturnType() const { return signature_.return_type; }
 
     /// @return the parameters of the call target
     auto& Parameters() const { return signature_.parameters; }
diff --git a/src/tint/sem/constant.h b/src/tint/sem/constant.h
index 875864f..fcf45c7 100644
--- a/src/tint/sem/constant.h
+++ b/src/tint/sem/constant.h
@@ -18,11 +18,7 @@
 #include <variant>
 
 #include "src/tint/number.h"
-
-// Forward declarations
-namespace tint::sem {
-class Type;
-}
+#include "src/tint/type/type.h"
 
 namespace tint::sem {
 
@@ -36,7 +32,7 @@
     virtual ~Constant();
 
     /// @returns the type of the constant
-    virtual const sem::Type* Type() const = 0;
+    virtual const type::Type* Type() const = 0;
 
     /// @returns the value of this Constant, if this constant is of a scalar value or abstract
     /// numeric, otherwise std::monostate.
diff --git a/src/tint/sem/depth_multisampled_texture.cc b/src/tint/sem/depth_multisampled_texture.cc
index dd4e108..77f9cac 100644
--- a/src/tint/sem/depth_multisampled_texture.cc
+++ b/src/tint/sem/depth_multisampled_texture.cc
@@ -40,7 +40,7 @@
     return utils::Hash(TypeInfo::Of<DepthMultisampledTexture>().full_hashcode, dim());
 }
 
-bool DepthMultisampledTexture::Equals(const sem::Type& other) const {
+bool DepthMultisampledTexture::Equals(const type::Type& other) const {
     if (auto* o = other.As<DepthMultisampledTexture>()) {
         return o->dim() == dim();
     }
diff --git a/src/tint/sem/depth_texture.cc b/src/tint/sem/depth_texture.cc
index 664a721..4c8bf9a 100644
--- a/src/tint/sem/depth_texture.cc
+++ b/src/tint/sem/depth_texture.cc
@@ -41,7 +41,7 @@
     return utils::Hash(TypeInfo::Of<DepthTexture>().full_hashcode, dim());
 }
 
-bool DepthTexture::Equals(const sem::Type& other) const {
+bool DepthTexture::Equals(const type::Type& other) const {
     if (auto* o = other.As<DepthTexture>()) {
         return o->dim() == dim();
     }
diff --git a/src/tint/sem/expression.cc b/src/tint/sem/expression.cc
index d7fd255..4136852 100644
--- a/src/tint/sem/expression.cc
+++ b/src/tint/sem/expression.cc
@@ -23,7 +23,7 @@
 namespace tint::sem {
 
 Expression::Expression(const ast::Expression* declaration,
-                       const sem::Type* type,
+                       const type::Type* type,
                        EvaluationStage stage,
                        const Statement* statement,
                        const Constant* constant,
diff --git a/src/tint/sem/expression.h b/src/tint/sem/expression.h
index cc87031..1b3e102 100644
--- a/src/tint/sem/expression.h
+++ b/src/tint/sem/expression.h
@@ -24,7 +24,6 @@
 // Forward declarations
 namespace tint::sem {
 class Statement;
-class Type;
 class Variable;
 }  // namespace tint::sem
 
@@ -42,7 +41,7 @@
     /// @param has_side_effects true if this expression may have side-effects
     /// @param root_ident the (optional) root identifier for this expression
     Expression(const ast::Expression* declaration,
-               const sem::Type* type,
+               const type::Type* type,
                EvaluationStage stage,
                const Statement* statement,
                const Constant* constant,
@@ -56,7 +55,7 @@
     const ast::Expression* Declaration() const { return declaration_; }
 
     /// @return the resolved type of the expression
-    const sem::Type* Type() const { return type_; }
+    const type::Type* Type() const { return type_; }
 
     /// @return the earliest evaluation stage for the expression
     EvaluationStage Stage() const { return stage_; }
@@ -93,7 +92,7 @@
     const Variable* root_identifier_;
 
   private:
-    const sem::Type* const type_;
+    const type::Type* const type_;
     const EvaluationStage stage_;
     const Statement* const statement_;
     const Constant* const constant_;
diff --git a/src/tint/sem/expression_test.cc b/src/tint/sem/expression_test.cc
index 845b279..aae9f46 100644
--- a/src/tint/sem/expression_test.cc
+++ b/src/tint/sem/expression_test.cc
@@ -25,9 +25,9 @@
 
 class MockConstant : public sem::Constant {
   public:
-    explicit MockConstant(const sem::Type* ty) : type(ty) {}
+    explicit MockConstant(const type::Type* ty) : type(ty) {}
     ~MockConstant() override {}
-    const sem::Type* Type() const override { return type; }
+    const type::Type* Type() const override { return type; }
     std::variant<std::monostate, AInt, AFloat> Value() const override { return {}; }
     const Constant* Index(size_t) const override { return {}; }
     bool AllZero() const override { return {}; }
@@ -36,7 +36,7 @@
     size_t Hash() const override { return 0; }
 
   private:
-    const sem::Type* type;
+    const type::Type* type;
 };
 
 using ExpressionTest = TestHelper;
diff --git a/src/tint/sem/external_texture.cc b/src/tint/sem/external_texture.cc
index d677f65..05be4a1 100644
--- a/src/tint/sem/external_texture.cc
+++ b/src/tint/sem/external_texture.cc
@@ -30,7 +30,7 @@
     return static_cast<size_t>(TypeInfo::Of<ExternalTexture>().full_hashcode);
 }
 
-bool ExternalTexture::Equals(const sem::Type& other) const {
+bool ExternalTexture::Equals(const type::Type& other) const {
     return other.Is<ExternalTexture>();
 }
 
diff --git a/src/tint/sem/f16.cc b/src/tint/sem/f16.cc
index 158b826..f8ab347 100644
--- a/src/tint/sem/f16.cc
+++ b/src/tint/sem/f16.cc
@@ -22,7 +22,7 @@
 namespace sem {
 
 F16::F16()
-    : Base(TypeFlags{
+    : Base(type::TypeFlags{
           Flag::kConstructable,
           Flag::kCreationFixedFootprint,
           Flag::kFixedFootprint,
diff --git a/src/tint/sem/f16.h b/src/tint/sem/f16.h
index b126614..e196155 100644
--- a/src/tint/sem/f16.h
+++ b/src/tint/sem/f16.h
@@ -17,12 +17,12 @@
 
 #include <string>
 
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 namespace tint::sem {
 
 /// A float 16 type
-class F16 final : public Castable<F16, Type> {
+class F16 final : public Castable<F16, type::Type> {
   public:
     /// Constructor
     F16();
diff --git a/src/tint/sem/f32.cc b/src/tint/sem/f32.cc
index 73c98e6..86c0d05 100644
--- a/src/tint/sem/f32.cc
+++ b/src/tint/sem/f32.cc
@@ -21,7 +21,7 @@
 namespace tint::sem {
 
 F32::F32()
-    : Base(TypeFlags{
+    : Base(type::TypeFlags{
           Flag::kConstructable,
           Flag::kCreationFixedFootprint,
           Flag::kFixedFootprint,
diff --git a/src/tint/sem/f32.h b/src/tint/sem/f32.h
index 0559dfd..67979bc 100644
--- a/src/tint/sem/f32.h
+++ b/src/tint/sem/f32.h
@@ -17,12 +17,12 @@
 
 #include <string>
 
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 namespace tint::sem {
 
 /// A float 32 type
-class F32 final : public Castable<F32, Type> {
+class F32 final : public Castable<F32, type::Type> {
   public:
     /// Constructor
     F32();
diff --git a/src/tint/sem/function.cc b/src/tint/sem/function.cc
index af2ee12..5a7c323 100644
--- a/src/tint/sem/function.cc
+++ b/src/tint/sem/function.cc
@@ -39,7 +39,7 @@
 }  // namespace
 
 Function::Function(const ast::Function* declaration,
-                   Type* return_type,
+                   type::Type* return_type,
                    std::optional<uint32_t> return_location,
                    utils::VectorRef<Parameter*> parameters)
     : Base(return_type, SetOwner(std::move(parameters), this), EvaluationStage::kRuntime),
diff --git a/src/tint/sem/function.h b/src/tint/sem/function.h
index 7b5eaff..596b3d9 100644
--- a/src/tint/sem/function.h
+++ b/src/tint/sem/function.h
@@ -57,7 +57,7 @@
     /// @param return_location the location value for the return, if provided
     /// @param parameters the parameters to the function
     Function(const ast::Function* declaration,
-             Type* return_type,
+             type::Type* return_type,
              std::optional<uint32_t> return_location,
              utils::VectorRef<Parameter*> parameters);
 
diff --git a/src/tint/sem/i32.cc b/src/tint/sem/i32.cc
index 6b23155..0f4ccae 100644
--- a/src/tint/sem/i32.cc
+++ b/src/tint/sem/i32.cc
@@ -21,7 +21,7 @@
 namespace tint::sem {
 
 I32::I32()
-    : Base(TypeFlags{
+    : Base(type::TypeFlags{
           Flag::kConstructable,
           Flag::kCreationFixedFootprint,
           Flag::kFixedFootprint,
diff --git a/src/tint/sem/i32.h b/src/tint/sem/i32.h
index ff2df4d..9893f2f 100644
--- a/src/tint/sem/i32.h
+++ b/src/tint/sem/i32.h
@@ -17,12 +17,12 @@
 
 #include <string>
 
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 namespace tint::sem {
 
 /// A signed int 32 type.
-class I32 final : public Castable<I32, Type> {
+class I32 final : public Castable<I32, type::Type> {
   public:
     /// Constructor
     I32();
diff --git a/src/tint/sem/index_accessor_expression.cc b/src/tint/sem/index_accessor_expression.cc
index 0565695..43e6cdd 100644
--- a/src/tint/sem/index_accessor_expression.cc
+++ b/src/tint/sem/index_accessor_expression.cc
@@ -23,7 +23,7 @@
 namespace tint::sem {
 
 IndexAccessorExpression::IndexAccessorExpression(const ast::IndexAccessorExpression* declaration,
-                                                 const sem::Type* type,
+                                                 const type::Type* type,
                                                  EvaluationStage stage,
                                                  const Expression* object,
                                                  const Expression* index,
diff --git a/src/tint/sem/index_accessor_expression.h b/src/tint/sem/index_accessor_expression.h
index 19ae82b..62ecba6 100644
--- a/src/tint/sem/index_accessor_expression.h
+++ b/src/tint/sem/index_accessor_expression.h
@@ -40,7 +40,7 @@
     /// @param has_side_effects whether this expression may have side effects
     /// @param root_ident the (optional) root identifier for this expression
     IndexAccessorExpression(const ast::IndexAccessorExpression* declaration,
-                            const sem::Type* type,
+                            const type::Type* type,
                             EvaluationStage stage,
                             const Expression* object,
                             const Expression* index,
diff --git a/src/tint/sem/info.h b/src/tint/sem/info.h
index 28b41a6..246510c 100644
--- a/src/tint/sem/info.h
+++ b/src/tint/sem/info.h
@@ -30,6 +30,10 @@
 namespace tint::sem {
 class Module;
 }  // namespace tint::sem
+namespace tint::type {
+class Node;
+class Type;
+}  // namespace tint::type
 
 namespace tint::sem {
 
@@ -142,7 +146,7 @@
 
   private:
     // AST node index to semantic node
-    std::vector<const sem::Node*> nodes_;
+    std::vector<const CastableBase*> nodes_;
     // Lists transitively referenced overrides for the given item
     std::unordered_map<const CastableBase*, TransitivelyReferenced> referenced_overrides_;
     // The semantic module
diff --git a/src/tint/sem/matrix.cc b/src/tint/sem/matrix.cc
index 1d0453d..1809016 100644
--- a/src/tint/sem/matrix.cc
+++ b/src/tint/sem/matrix.cc
@@ -23,7 +23,7 @@
 namespace tint::sem {
 
 Matrix::Matrix(const Vector* column_type, uint32_t columns)
-    : Base(TypeFlags{
+    : Base(type::TypeFlags{
           Flag::kConstructable,
           Flag::kCreationFixedFootprint,
           Flag::kFixedFootprint,
diff --git a/src/tint/sem/matrix.h b/src/tint/sem/matrix.h
index 5d32269..8349410 100644
--- a/src/tint/sem/matrix.h
+++ b/src/tint/sem/matrix.h
@@ -17,7 +17,7 @@
 
 #include <string>
 
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 // Forward declarations
 namespace tint::sem {
@@ -27,7 +27,7 @@
 namespace tint::sem {
 
 /// A matrix type
-class Matrix final : public Castable<Matrix, Type> {
+class Matrix final : public Castable<Matrix, type::Type> {
   public:
     /// Constructor
     /// @param column_type the type of a column of the matrix
@@ -45,7 +45,7 @@
     bool Equals(const Type& other) const override;
 
     /// @returns the type of the matrix
-    const Type* type() const { return subtype_; }
+    const type::Type* type() const { return subtype_; }
     /// @returns the number of rows in the matrix
     uint32_t rows() const { return rows_; }
     /// @returns the number of columns in the matrix
@@ -70,7 +70,7 @@
     uint32_t ColumnStride() const;
 
   private:
-    const Type* const subtype_;
+    const type::Type* const subtype_;
     const Vector* const column_type_;
     const uint32_t rows_;
     const uint32_t columns_;
diff --git a/src/tint/sem/member_accessor_expression.cc b/src/tint/sem/member_accessor_expression.cc
index 8d08008..9daa946 100644
--- a/src/tint/sem/member_accessor_expression.cc
+++ b/src/tint/sem/member_accessor_expression.cc
@@ -24,7 +24,7 @@
 namespace tint::sem {
 
 MemberAccessorExpression::MemberAccessorExpression(const ast::MemberAccessorExpression* declaration,
-                                                   const sem::Type* type,
+                                                   const type::Type* type,
                                                    EvaluationStage stage,
                                                    const Statement* statement,
                                                    const Constant* constant,
@@ -37,7 +37,7 @@
 MemberAccessorExpression::~MemberAccessorExpression() = default;
 
 StructMemberAccess::StructMemberAccess(const ast::MemberAccessorExpression* declaration,
-                                       const sem::Type* type,
+                                       const type::Type* type,
                                        const Statement* statement,
                                        const Constant* constant,
                                        const Expression* object,
@@ -57,7 +57,7 @@
 StructMemberAccess::~StructMemberAccess() = default;
 
 Swizzle::Swizzle(const ast::MemberAccessorExpression* declaration,
-                 const sem::Type* type,
+                 const type::Type* type,
                  const Statement* statement,
                  const Constant* constant,
                  const Expression* object,
diff --git a/src/tint/sem/member_accessor_expression.h b/src/tint/sem/member_accessor_expression.h
index 5541d1c..ba9a552 100644
--- a/src/tint/sem/member_accessor_expression.h
+++ b/src/tint/sem/member_accessor_expression.h
@@ -50,7 +50,7 @@
     /// @param has_side_effects whether this expression may have side effects
     /// @param root_ident the (optional) root identifier for this expression
     MemberAccessorExpression(const ast::MemberAccessorExpression* declaration,
-                             const sem::Type* type,
+                             const type::Type* type,
                              EvaluationStage stage,
                              const Statement* statement,
                              const Constant* constant,
@@ -77,7 +77,7 @@
     /// @param has_side_effects whether this expression may have side effects
     /// @param root_ident the (optional) root identifier for this expression
     StructMemberAccess(const ast::MemberAccessorExpression* declaration,
-                       const sem::Type* type,
+                       const type::Type* type,
                        const Statement* statement,
                        const Constant* constant,
                        const Expression* object,
@@ -109,7 +109,7 @@
     /// @param has_side_effects whether this expression may have side effects
     /// @param root_ident the (optional) root identifier for this expression
     Swizzle(const ast::MemberAccessorExpression* declaration,
-            const sem::Type* type,
+            const type::Type* type,
             const Statement* statement,
             const Constant* constant,
             const Expression* object,
diff --git a/src/tint/sem/multisampled_texture.cc b/src/tint/sem/multisampled_texture.cc
index 922b967..a735cc8 100644
--- a/src/tint/sem/multisampled_texture.cc
+++ b/src/tint/sem/multisampled_texture.cc
@@ -21,7 +21,7 @@
 
 namespace tint::sem {
 
-MultisampledTexture::MultisampledTexture(ast::TextureDimension dim, const Type* type)
+MultisampledTexture::MultisampledTexture(ast::TextureDimension dim, const type::Type* type)
     : Base(dim), type_(type) {
     TINT_ASSERT(Semantic, type_);
 }
@@ -34,7 +34,7 @@
     return utils::Hash(TypeInfo::Of<MultisampledTexture>().full_hashcode, dim(), type_);
 }
 
-bool MultisampledTexture::Equals(const sem::Type& other) const {
+bool MultisampledTexture::Equals(const type::Type& other) const {
     if (auto* o = other.As<MultisampledTexture>()) {
         return o->dim() == dim() && o->type_ == type_;
     }
diff --git a/src/tint/sem/multisampled_texture.h b/src/tint/sem/multisampled_texture.h
index f178056..bfea08f 100644
--- a/src/tint/sem/multisampled_texture.h
+++ b/src/tint/sem/multisampled_texture.h
@@ -27,7 +27,7 @@
     /// Constructor
     /// @param dim the dimensionality of the texture
     /// @param type the data type of the multisampled texture
-    MultisampledTexture(ast::TextureDimension dim, const Type* type);
+    MultisampledTexture(ast::TextureDimension dim, const type::Type* type);
     /// Move constructor
     MultisampledTexture(MultisampledTexture&&);
     ~MultisampledTexture() override;
@@ -40,7 +40,7 @@
     bool Equals(const Type& other) const override;
 
     /// @returns the subtype of the sampled texture
-    const Type* type() const { return type_; }
+    const type::Type* type() const { return type_; }
 
     /// @param symbols the program's symbol table
     /// @returns the name for this type that closely resembles how it would be
@@ -48,7 +48,7 @@
     std::string FriendlyName(const SymbolTable& symbols) const override;
 
   private:
-    const Type* const type_;
+    const type::Type* const type_;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/pointer.cc b/src/tint/sem/pointer.cc
index b35185a..54dd3a6 100644
--- a/src/tint/sem/pointer.cc
+++ b/src/tint/sem/pointer.cc
@@ -22,8 +22,8 @@
 
 namespace tint::sem {
 
-Pointer::Pointer(const Type* subtype, ast::AddressSpace address_space, ast::Access access)
-    : Base(TypeFlags{}), subtype_(subtype), address_space_(address_space), access_(access) {
+Pointer::Pointer(const type::Type* subtype, ast::AddressSpace address_space, ast::Access access)
+    : Base(type::TypeFlags{}), subtype_(subtype), address_space_(address_space), access_(access) {
     TINT_ASSERT(Semantic, !subtype->Is<Reference>());
     TINT_ASSERT(Semantic, access != ast::Access::kUndefined);
 }
@@ -32,7 +32,7 @@
     return utils::Hash(TypeInfo::Of<Pointer>().full_hashcode, address_space_, subtype_, access_);
 }
 
-bool Pointer::Equals(const sem::Type& other) const {
+bool Pointer::Equals(const type::Type& other) const {
     if (auto* o = other.As<Pointer>()) {
         return o->address_space_ == address_space_ && o->subtype_ == subtype_ &&
                o->access_ == access_;
diff --git a/src/tint/sem/pointer.h b/src/tint/sem/pointer.h
index 806dcaa..4af26ed 100644
--- a/src/tint/sem/pointer.h
+++ b/src/tint/sem/pointer.h
@@ -19,18 +19,18 @@
 
 #include "src/tint/ast/access.h"
 #include "src/tint/ast/address_space.h"
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 namespace tint::sem {
 
 /// A pointer type.
-class Pointer final : public Castable<Pointer, Type> {
+class Pointer final : public Castable<Pointer, type::Type> {
   public:
     /// Constructor
     /// @param subtype the pointee type
     /// @param address_space the address space of the pointer
     /// @param access the resolved access control of the reference
-    Pointer(const Type* subtype, ast::AddressSpace address_space, ast::Access access);
+    Pointer(const type::Type* subtype, ast::AddressSpace address_space, ast::Access access);
 
     /// Move constructor
     Pointer(Pointer&&);
@@ -44,7 +44,7 @@
     bool Equals(const Type& other) const override;
 
     /// @returns the pointee type
-    const Type* StoreType() const { return subtype_; }
+    const type::Type* StoreType() const { return subtype_; }
 
     /// @returns the address space of the pointer
     ast::AddressSpace AddressSpace() const { return address_space_; }
diff --git a/src/tint/sem/reference.cc b/src/tint/sem/reference.cc
index 8d3bb6d..e5193e8 100644
--- a/src/tint/sem/reference.cc
+++ b/src/tint/sem/reference.cc
@@ -21,8 +21,8 @@
 
 namespace tint::sem {
 
-Reference::Reference(const Type* subtype, ast::AddressSpace address_space, ast::Access access)
-    : Base(TypeFlags{}), subtype_(subtype), address_space_(address_space), access_(access) {
+Reference::Reference(const type::Type* subtype, ast::AddressSpace address_space, ast::Access access)
+    : Base(type::TypeFlags{}), subtype_(subtype), address_space_(address_space), access_(access) {
     TINT_ASSERT(Semantic, !subtype->Is<Reference>());
     TINT_ASSERT(Semantic, access != ast::Access::kUndefined);
 }
@@ -31,7 +31,7 @@
     return utils::Hash(TypeInfo::Of<Reference>().full_hashcode, address_space_, subtype_, access_);
 }
 
-bool Reference::Equals(const sem::Type& other) const {
+bool Reference::Equals(const type::Type& other) const {
     if (auto* o = other.As<Reference>()) {
         return o->address_space_ == address_space_ && o->subtype_ == subtype_ &&
                o->access_ == access_;
diff --git a/src/tint/sem/reference.h b/src/tint/sem/reference.h
index f843da6..ba74f33 100644
--- a/src/tint/sem/reference.h
+++ b/src/tint/sem/reference.h
@@ -19,18 +19,18 @@
 
 #include "src/tint/ast/access.h"
 #include "src/tint/ast/address_space.h"
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 namespace tint::sem {
 
 /// A reference type.
-class Reference final : public Castable<Reference, Type> {
+class Reference final : public Castable<Reference, type::Type> {
   public:
     /// Constructor
     /// @param subtype the pointee type
     /// @param address_space the address space of the reference
     /// @param access the resolved access control of the reference
-    Reference(const Type* subtype, ast::AddressSpace address_space, ast::Access access);
+    Reference(const type::Type* subtype, ast::AddressSpace address_space, ast::Access access);
 
     /// Move constructor
     Reference(Reference&&);
@@ -44,7 +44,7 @@
     bool Equals(const Type& other) const override;
 
     /// @returns the pointee type
-    const Type* StoreType() const { return subtype_; }
+    const type::Type* StoreType() const { return subtype_; }
 
     /// @returns the address space of the reference
     ast::AddressSpace AddressSpace() const { return address_space_; }
diff --git a/src/tint/sem/sampled_texture.cc b/src/tint/sem/sampled_texture.cc
index 260901a..9481ed4 100644
--- a/src/tint/sem/sampled_texture.cc
+++ b/src/tint/sem/sampled_texture.cc
@@ -21,7 +21,7 @@
 
 namespace tint::sem {
 
-SampledTexture::SampledTexture(ast::TextureDimension dim, const Type* type)
+SampledTexture::SampledTexture(ast::TextureDimension dim, const type::Type* type)
     : Base(dim), type_(type) {
     TINT_ASSERT(Semantic, type_);
 }
@@ -34,7 +34,7 @@
     return utils::Hash(TypeInfo::Of<SampledTexture>().full_hashcode, dim(), type_);
 }
 
-bool SampledTexture::Equals(const sem::Type& other) const {
+bool SampledTexture::Equals(const type::Type& other) const {
     if (auto* o = other.As<SampledTexture>()) {
         return o->dim() == dim() && o->type_ == type_;
     }
diff --git a/src/tint/sem/sampled_texture.h b/src/tint/sem/sampled_texture.h
index 15e7949..076d063 100644
--- a/src/tint/sem/sampled_texture.h
+++ b/src/tint/sem/sampled_texture.h
@@ -27,7 +27,7 @@
     /// Constructor
     /// @param dim the dimensionality of the texture
     /// @param type the data type of the sampled texture
-    SampledTexture(ast::TextureDimension dim, const Type* type);
+    SampledTexture(ast::TextureDimension dim, const type::Type* type);
     /// Move constructor
     SampledTexture(SampledTexture&&);
     ~SampledTexture() override;
@@ -40,7 +40,7 @@
     bool Equals(const Type& other) const override;
 
     /// @returns the subtype of the sampled texture
-    Type* type() const { return const_cast<Type*>(type_); }
+    type::Type* type() const { return const_cast<type::Type*>(type_); }
 
     /// @param symbols the program's symbol table
     /// @returns the name for this type that closely resembles how it would be
@@ -48,7 +48,7 @@
     std::string FriendlyName(const SymbolTable& symbols) const override;
 
   private:
-    const Type* const type_;
+    const type::Type* const type_;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/sampler.cc b/src/tint/sem/sampler.cc
index 598c696..4d1e9b2 100644
--- a/src/tint/sem/sampler.cc
+++ b/src/tint/sem/sampler.cc
@@ -21,7 +21,7 @@
 
 namespace tint::sem {
 
-Sampler::Sampler(ast::SamplerKind kind) : Base(TypeFlags{}), kind_(kind) {}
+Sampler::Sampler(ast::SamplerKind kind) : Base(type::TypeFlags{}), kind_(kind) {}
 
 Sampler::Sampler(Sampler&&) = default;
 
@@ -31,7 +31,7 @@
     return utils::Hash(TypeInfo::Of<Sampler>().full_hashcode, kind_);
 }
 
-bool Sampler::Equals(const sem::Type& other) const {
+bool Sampler::Equals(const type::Type& other) const {
     if (auto* o = other.As<Sampler>()) {
         return o->kind_ == kind_;
     }
diff --git a/src/tint/sem/sampler.h b/src/tint/sem/sampler.h
index 96c184f..8ec485f 100644
--- a/src/tint/sem/sampler.h
+++ b/src/tint/sem/sampler.h
@@ -18,12 +18,12 @@
 #include <string>
 
 #include "src/tint/ast/sampler.h"
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 namespace tint::sem {
 
 /// A sampler type.
-class Sampler final : public Castable<Sampler, Type> {
+class Sampler final : public Castable<Sampler, type::Type> {
   public:
     /// Constructor
     /// @param kind the kind of sampler
diff --git a/src/tint/sem/storage_texture.cc b/src/tint/sem/storage_texture.cc
index 763eb21..62c443b 100644
--- a/src/tint/sem/storage_texture.cc
+++ b/src/tint/sem/storage_texture.cc
@@ -24,7 +24,7 @@
 StorageTexture::StorageTexture(ast::TextureDimension dim,
                                ast::TexelFormat format,
                                ast::Access access,
-                               sem::Type* subtype)
+                               type::Type* subtype)
     : Base(dim), texel_format_(format), access_(access), subtype_(subtype) {}
 
 StorageTexture::StorageTexture(StorageTexture&&) = default;
@@ -35,7 +35,7 @@
     return utils::Hash(TypeInfo::Of<StorageTexture>().full_hashcode, dim(), texel_format_, access_);
 }
 
-bool StorageTexture::Equals(const sem::Type& other) const {
+bool StorageTexture::Equals(const type::Type& other) const {
     if (auto* o = other.As<StorageTexture>()) {
         return o->dim() == dim() && o->texel_format_ == texel_format_ && o->access_ == access_;
     }
@@ -48,7 +48,7 @@
     return out.str();
 }
 
-sem::Type* StorageTexture::SubtypeFor(ast::TexelFormat format, sem::TypeManager& type_mgr) {
+type::Type* StorageTexture::SubtypeFor(ast::TexelFormat format, sem::TypeManager& type_mgr) {
     switch (format) {
         case ast::TexelFormat::kR32Uint:
         case ast::TexelFormat::kRgba8Uint:
diff --git a/src/tint/sem/storage_texture.h b/src/tint/sem/storage_texture.h
index 00258d6..3fdb1a7 100644
--- a/src/tint/sem/storage_texture.h
+++ b/src/tint/sem/storage_texture.h
@@ -39,7 +39,7 @@
     StorageTexture(ast::TextureDimension dim,
                    ast::TexelFormat format,
                    ast::Access access,
-                   sem::Type* subtype);
+                   type::Type* subtype);
 
     /// Move constructor
     StorageTexture(StorageTexture&&);
@@ -53,7 +53,7 @@
     bool Equals(const Type& other) const override;
 
     /// @returns the storage subtype
-    Type* type() const { return subtype_; }
+    type::Type* type() const { return subtype_; }
 
     /// @returns the texel format
     ast::TexelFormat texel_format() const { return texel_format_; }
@@ -69,12 +69,12 @@
     /// @param format the storage texture image format
     /// @param type_mgr the sem::TypeManager used to build the returned type
     /// @returns the storage texture subtype for the given TexelFormat
-    static sem::Type* SubtypeFor(ast::TexelFormat format, sem::TypeManager& type_mgr);
+    static type::Type* SubtypeFor(ast::TexelFormat format, sem::TypeManager& type_mgr);
 
   private:
     ast::TexelFormat const texel_format_;
     ast::Access const access_;
-    Type* const subtype_;
+    type::Type* const subtype_;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/storage_texture_test.cc b/src/tint/sem/storage_texture_test.cc
index 23d6964..c93d663 100644
--- a/src/tint/sem/storage_texture_test.cc
+++ b/src/tint/sem/storage_texture_test.cc
@@ -106,8 +106,8 @@
 }
 
 TEST_F(StorageTextureTest, F32) {
-    Type* s = Create(ast::TextureDimension::k2dArray, ast::TexelFormat::kRgba32Float,
-                     ast::Access::kReadWrite);
+    type::Type* s = Create(ast::TextureDimension::k2dArray, ast::TexelFormat::kRgba32Float,
+                           ast::Access::kReadWrite);
 
     auto program = Build();
 
@@ -119,8 +119,9 @@
 
 TEST_F(StorageTextureTest, U32) {
     auto* subtype = sem::StorageTexture::SubtypeFor(ast::TexelFormat::kRg32Uint, Types());
-    Type* s = create<StorageTexture>(ast::TextureDimension::k2dArray, ast::TexelFormat::kRg32Uint,
-                                     ast::Access::kReadWrite, subtype);
+    type::Type* s =
+        create<StorageTexture>(ast::TextureDimension::k2dArray, ast::TexelFormat::kRg32Uint,
+                               ast::Access::kReadWrite, subtype);
 
     auto program = Build();
 
@@ -132,8 +133,9 @@
 
 TEST_F(StorageTextureTest, I32) {
     auto* subtype = sem::StorageTexture::SubtypeFor(ast::TexelFormat::kRgba32Sint, Types());
-    Type* s = create<StorageTexture>(ast::TextureDimension::k2dArray, ast::TexelFormat::kRgba32Sint,
-                                     ast::Access::kReadWrite, subtype);
+    type::Type* s =
+        create<StorageTexture>(ast::TextureDimension::k2dArray, ast::TexelFormat::kRgba32Sint,
+                               ast::Access::kReadWrite, subtype);
 
     auto program = Build();
 
diff --git a/src/tint/sem/struct.cc b/src/tint/sem/struct.cc
index ecb06a3..0db346a 100644
--- a/src/tint/sem/struct.cc
+++ b/src/tint/sem/struct.cc
@@ -31,21 +31,21 @@
 namespace tint::sem {
 namespace {
 
-TypeFlags FlagsFrom(utils::VectorRef<const StructMemberBase*> members) {
-    TypeFlags flags{
-        TypeFlag::kConstructable,
-        TypeFlag::kCreationFixedFootprint,
-        TypeFlag::kFixedFootprint,
+type::TypeFlags FlagsFrom(utils::VectorRef<const StructMemberBase*> members) {
+    type::TypeFlags flags{
+        type::TypeFlag::kConstructable,
+        type::TypeFlag::kCreationFixedFootprint,
+        type::TypeFlag::kFixedFootprint,
     };
     for (auto* member : members) {
         if (!member->Type()->IsConstructible()) {
-            flags.Remove(TypeFlag::kConstructable);
+            flags.Remove(type::TypeFlag::kConstructable);
         }
         if (!member->Type()->HasFixedFootprint()) {
-            flags.Remove(TypeFlag::kFixedFootprint);
+            flags.Remove(type::TypeFlag::kFixedFootprint);
         }
         if (!member->Type()->HasCreationFixedFootprint()) {
-            flags.Remove(TypeFlag::kCreationFixedFootprint);
+            flags.Remove(type::TypeFlag::kCreationFixedFootprint);
         }
     }
     return flags;
@@ -84,7 +84,7 @@
     return utils::Hash(TypeInfo::Of<Struct>().full_hashcode, name_);
 }
 
-bool StructBase::Equals(const sem::Type& other) const {
+bool StructBase::Equals(const type::Type& other) const {
     if (auto* o = other.As<Struct>()) {
         return o->name_ == name_;
     }
@@ -183,7 +183,7 @@
 StructMember::StructMember(const ast::StructMember* declaration,
                            tint::Source source,
                            Symbol name,
-                           const sem::Type* type,
+                           const type::Type* type,
                            uint32_t index,
                            uint32_t offset,
                            uint32_t align,
@@ -195,7 +195,7 @@
 
 StructMemberBase::StructMemberBase(tint::Source source,
                                    Symbol name,
-                                   const sem::Type* type,
+                                   const type::Type* type,
                                    uint32_t index,
                                    uint32_t offset,
                                    uint32_t align,
diff --git a/src/tint/sem/struct.h b/src/tint/sem/struct.h
index 5ae2a93..ca2bc4c 100644
--- a/src/tint/sem/struct.h
+++ b/src/tint/sem/struct.h
@@ -24,8 +24,8 @@
 #include "src/tint/ast/address_space.h"
 #include "src/tint/ast/struct.h"
 #include "src/tint/sem/node.h"
-#include "src/tint/sem/type.h"
 #include "src/tint/symbol.h"
+#include "src/tint/type/type.h"
 #include "src/tint/utils/vector.h"
 
 // Forward declarations
@@ -35,7 +35,6 @@
 namespace tint::sem {
 class StructMember;
 class StructMemberBase;
-class Type;
 }  // namespace tint::sem
 
 namespace tint::sem {
@@ -51,7 +50,7 @@
 };
 
 /// StructBase holds the semantic information for structures.
-class StructBase : public Castable<StructBase, Type> {
+class StructBase : public Castable<StructBase, type::Type> {
   public:
     /// Constructor
     /// @param source the source of the structure
@@ -208,7 +207,7 @@
 };
 
 /// StructMemberBase holds the semantic information for structure members.
-class StructMemberBase : public Castable<StructMemberBase, Node> {
+class StructMemberBase : public Castable<StructMemberBase, type::Node> {
   public:
     /// Constructor
     /// @param source the source of the struct member
@@ -221,7 +220,7 @@
     /// @param location the location attribute, if present
     StructMemberBase(tint::Source source,
                      Symbol name,
-                     const sem::Type* type,
+                     const type::Type* type,
                      uint32_t index,
                      uint32_t offset,
                      uint32_t align,
@@ -245,7 +244,7 @@
     const sem::StructBase* Struct() const { return struct_; }
 
     /// @returns the type of the member
-    const sem::Type* Type() const { return type_; }
+    const type::Type* Type() const { return type_; }
 
     /// @returns the member index
     uint32_t Index() const { return index_; }
@@ -266,7 +265,7 @@
     const tint::Source source_;
     const Symbol name_;
     const sem::StructBase* struct_;
-    const sem::Type* type_;
+    const type::Type* type_;
     const uint32_t index_;
     const uint32_t offset_;
     const uint32_t align_;
@@ -290,7 +289,7 @@
     StructMember(const ast::StructMember* declaration,
                  tint::Source source,
                  Symbol name,
-                 const sem::Type* type,
+                 const type::Type* type,
                  uint32_t index,
                  uint32_t offset,
                  uint32_t align,
diff --git a/src/tint/sem/test_helper.h b/src/tint/sem/test_helper.h
index e1b4eb3..94f66e4 100644
--- a/src/tint/sem/test_helper.h
+++ b/src/tint/sem/test_helper.h
@@ -44,16 +44,4 @@
 
 }  // namespace tint::sem
 
-/// Helper macro for testing that a semantic type was as expected
-#define EXPECT_TYPE(GOT, EXPECT)                                         \
-    do {                                                                 \
-        const sem::Type* got = GOT;                                      \
-        const sem::Type* expect = EXPECT;                                \
-        if (got != expect) {                                             \
-            ADD_FAILURE() << #GOT " != " #EXPECT "\n"                    \
-                          << "  " #GOT ": " << FriendlyName(got) << "\n" \
-                          << "  " #EXPECT ": " << FriendlyName(expect);  \
-        }                                                                \
-    } while (false)
-
 #endif  // SRC_TINT_SEM_TEST_HELPER_H_
diff --git a/src/tint/sem/texture.cc b/src/tint/sem/texture.cc
index 5e5b1e2..d14d7a9 100644
--- a/src/tint/sem/texture.cc
+++ b/src/tint/sem/texture.cc
@@ -18,7 +18,7 @@
 
 namespace tint::sem {
 
-Texture::Texture(ast::TextureDimension dim) : Base(TypeFlags{}), dim_(dim) {}
+Texture::Texture(ast::TextureDimension dim) : Base(type::TypeFlags{}), dim_(dim) {}
 
 Texture::Texture(Texture&&) = default;
 
diff --git a/src/tint/sem/texture.h b/src/tint/sem/texture.h
index 67dd026..eac8bae 100644
--- a/src/tint/sem/texture.h
+++ b/src/tint/sem/texture.h
@@ -16,12 +16,12 @@
 #define SRC_TINT_SEM_TEXTURE_H_
 
 #include "src/tint/ast/texture.h"
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 namespace tint::sem {
 
 /// A texture type.
-class Texture : public Castable<Texture, Type> {
+class Texture : public Castable<Texture, type::Type> {
   public:
     /// Constructor
     /// @param dim the dimensionality of the texture
diff --git a/src/tint/sem/type_conversion.cc b/src/tint/sem/type_conversion.cc
index 42fa2e0..2cec5be 100644
--- a/src/tint/sem/type_conversion.cc
+++ b/src/tint/sem/type_conversion.cc
@@ -18,7 +18,7 @@
 
 namespace tint::sem {
 
-TypeConversion::TypeConversion(const sem::Type* type,
+TypeConversion::TypeConversion(const type::Type* type,
                                const sem::Parameter* parameter,
                                EvaluationStage stage)
     : Base(type, utils::Vector<const sem::Parameter*, 1>{parameter}, stage) {}
diff --git a/src/tint/sem/type_conversion.h b/src/tint/sem/type_conversion.h
index 5584b36..c489127 100644
--- a/src/tint/sem/type_conversion.h
+++ b/src/tint/sem/type_conversion.h
@@ -26,16 +26,16 @@
     /// @param type the target type of the cast
     /// @param parameter the type cast parameter
     /// @param stage the earliest evaluation stage for the expression
-    TypeConversion(const sem::Type* type, const sem::Parameter* parameter, EvaluationStage stage);
+    TypeConversion(const type::Type* type, const sem::Parameter* parameter, EvaluationStage stage);
 
     /// Destructor
     ~TypeConversion() override;
 
     /// @returns the cast source type
-    const sem::Type* Source() const { return Parameters()[0]->Type(); }
+    const type::Type* Source() const { return Parameters()[0]->Type(); }
 
     /// @returns the cast target type
-    const sem::Type* Target() const { return ReturnType(); }
+    const type::Type* Target() const { return ReturnType(); }
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/type_initializer.cc b/src/tint/sem/type_initializer.cc
index 2981064..0359eb8 100644
--- a/src/tint/sem/type_initializer.cc
+++ b/src/tint/sem/type_initializer.cc
@@ -20,7 +20,7 @@
 
 namespace tint::sem {
 
-TypeInitializer::TypeInitializer(const sem::Type* type,
+TypeInitializer::TypeInitializer(const type::Type* type,
                                  utils::VectorRef<const Parameter*> parameters,
                                  EvaluationStage stage)
     : Base(type, std::move(parameters), stage) {}
diff --git a/src/tint/sem/type_initializer.h b/src/tint/sem/type_initializer.h
index c9b123c..b46faa8 100644
--- a/src/tint/sem/type_initializer.h
+++ b/src/tint/sem/type_initializer.h
@@ -27,7 +27,7 @@
     /// @param type the type that's being constructed
     /// @param parameters the type initializer parameters
     /// @param stage the earliest evaluation stage for the expression
-    TypeInitializer(const sem::Type* type,
+    TypeInitializer(const type::Type* type,
                     utils::VectorRef<const Parameter*> parameters,
                     EvaluationStage stage);
 
diff --git a/src/tint/sem/type_manager.h b/src/tint/sem/type_manager.h
index 33c42b3..4decdef 100644
--- a/src/tint/sem/type_manager.h
+++ b/src/tint/sem/type_manager.h
@@ -15,12 +15,15 @@
 #ifndef SRC_TINT_SEM_TYPE_MANAGER_H_
 #define SRC_TINT_SEM_TYPE_MANAGER_H_
 
+#include <functional>
 #include <string>
 #include <unordered_map>
 #include <utility>
 
-#include "src/tint/sem/array_count.h"
-#include "src/tint/sem/type.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/type/array_count.h"
+#include "src/tint/type/node.h"
+#include "src/tint/type/type.h"
 #include "src/tint/utils/unique_allocator.h"
 
 namespace tint::sem {
@@ -29,7 +32,7 @@
 class TypeManager final {
   public:
     /// Iterator is the type returned by begin() and end()
-    using TypeIterator = utils::BlockAllocator<Type>::ConstIterator;
+    using TypeIterator = utils::BlockAllocator<type::Type>::ConstIterator;
 
     /// Constructor
     TypeManager();
@@ -57,7 +60,7 @@
     static TypeManager Wrap(const TypeManager& inner) {
         TypeManager out;
         out.types_.Wrap(inner.types_);
-        out.array_counts_.Wrap(inner.array_counts_);
+        out.nodes_.Wrap(inner.nodes_);
         return out;
     }
 
@@ -66,7 +69,7 @@
     ///         If an existing instance of `T` has been constructed, then the same
     ///         pointer is returned.
     template <typename TYPE,
-              typename _ = std::enable_if<traits::IsTypeOrDerived<TYPE, sem::Type>>,
+              typename _ = std::enable_if<traits::IsTypeOrDerived<TYPE, type::Type>>,
               typename... ARGS>
     TYPE* Get(ARGS&&... args) {
         return types_.Get<TYPE>(std::forward<ARGS>(args)...);
@@ -76,7 +79,7 @@
     /// @return a pointer to an instance of `T` with the provided arguments, or nullptr if the item
     ///         was not found.
     template <typename TYPE,
-              typename _ = std::enable_if<traits::IsTypeOrDerived<TYPE, sem::Type>>,
+              typename _ = std::enable_if<traits::IsTypeOrDerived<TYPE, type::Type>>,
               typename... ARGS>
     TYPE* Find(ARGS&&... args) const {
         return types_.Find<TYPE>(std::forward<ARGS>(args)...);
@@ -87,10 +90,11 @@
     ///         If an existing instance of `T` has been constructed, then the same
     ///         pointer is returned.
     template <typename TYPE,
-              typename _ = std::enable_if<traits::IsTypeOrDerived<TYPE, sem::ArrayCount>>,
+              typename _ = std::enable_if<traits::IsTypeOrDerived<TYPE, type::ArrayCount> ||
+                                          traits::IsTypeOrDerived<TYPE, sem::StructMemberBase>>,
               typename... ARGS>
-    TYPE* GetArrayCount(ARGS&&... args) {
-        return array_counts_.Get<TYPE>(std::forward<ARGS>(args)...);
+    TYPE* GetNode(ARGS&&... args) {
+        return nodes_.Get<TYPE>(std::forward<ARGS>(args)...);
     }
 
     /// @returns an iterator to the beginning of the types
@@ -99,10 +103,50 @@
     TypeIterator end() const { return types_.end(); }
 
   private:
-    utils::UniqueAllocator<Type> types_;
-    utils::UniqueAllocator<ArrayCount> array_counts_;
+    utils::UniqueAllocator<type::Type> types_;
+    utils::UniqueAllocator<type::Node> nodes_;
 };
 
 }  // namespace tint::sem
 
+namespace std {
+
+/// std::hash specialization for tint::type::Node
+template <>
+struct hash<tint::type::Node> {
+    /// @param type the type to obtain a hash from
+    /// @returns the hash of the type
+    size_t operator()(const tint::type::Node& type) const {
+        if (const auto* ac = type.As<tint::type::ArrayCount>()) {
+            return ac->Hash();
+        } else if (type.Is<tint::sem::StructMemberBase>()) {
+            return tint::TypeInfo::Of<tint::sem::StructMemberBase>().full_hashcode;
+        }
+        TINT_ASSERT(Type, false && "Unreachable");
+        return 0;
+    }
+};
+
+/// std::equal_to specialization for tint::type::Node
+template <>
+struct equal_to<tint::type::Node> {
+    /// @param a the first type to compare
+    /// @param b the second type to compare
+    /// @returns true if the two types are equal
+    bool operator()(const tint::type::Node& a, const tint::type::Node& b) const {
+        if (const auto* ac = a.As<tint::type::ArrayCount>()) {
+            if (const auto* bc = b.As<tint::type::ArrayCount>()) {
+                return ac->Equals(*bc);
+            }
+            return false;
+        } else if (a.Is<tint::sem::StructMemberBase>()) {
+            return &a == &b;
+        }
+        TINT_ASSERT(Type, false && "Unreachable");
+        return false;
+    }
+};
+
+}  // namespace std
+
 #endif  // SRC_TINT_SEM_TYPE_MANAGER_H_
diff --git a/src/tint/sem/type_manager_test.cc b/src/tint/sem/type_manager_test.cc
index d2ca916..a488913 100644
--- a/src/tint/sem/type_manager_test.cc
+++ b/src/tint/sem/type_manager_test.cc
@@ -52,11 +52,11 @@
 
 TEST_F(TypeManagerTest, GetDifferentTypeReturnsDifferentPtr) {
     TypeManager tm;
-    Type* t = tm.Get<I32>();
+    type::Type* t = tm.Get<I32>();
     ASSERT_NE(t, nullptr);
     EXPECT_TRUE(t->Is<I32>());
 
-    Type* t2 = tm.Get<U32>();
+    type::Type* t2 = tm.Get<U32>();
     ASSERT_NE(t2, nullptr);
     EXPECT_NE(t, t2);
     EXPECT_TRUE(t2->Is<U32>());
diff --git a/src/tint/sem/type_mappings.h b/src/tint/sem/type_mappings.h
index 07d32b3..9e0f290 100644
--- a/src/tint/sem/type_mappings.h
+++ b/src/tint/sem/type_mappings.h
@@ -47,10 +47,12 @@
 class Struct;
 class StructMember;
 class SwitchStatement;
-class Type;
 class Variable;
 class WhileStatement;
 }  // namespace tint::sem
+namespace tint::type {
+class Type;
+}  // namespace tint::type
 
 namespace tint::sem {
 
@@ -65,14 +67,14 @@
     ForLoopStatement* operator()(ast::ForLoopStatement*);
     Function* operator()(ast::Function*);
     IfStatement* operator()(ast::IfStatement*);
-    Node* operator()(ast::Node*);
+    CastableBase* operator()(ast::Node*);
     GlobalVariable* operator()(ast::Override*);
     Statement* operator()(ast::Statement*);
     Struct* operator()(ast::Struct*);
     StructMember* operator()(ast::StructMember*);
     SwitchStatement* operator()(ast::SwitchStatement*);
-    Type* operator()(ast::Type*);
-    Type* operator()(ast::TypeDecl*);
+    type::Type* operator()(ast::Type*);
+    type::Type* operator()(ast::TypeDecl*);
     Variable* operator()(ast::Variable*);
     WhileStatement* operator()(ast::WhileStatement*);
     //! @endcond
diff --git a/src/tint/sem/u32.cc b/src/tint/sem/u32.cc
index a93ff4c..6b15150 100644
--- a/src/tint/sem/u32.cc
+++ b/src/tint/sem/u32.cc
@@ -21,7 +21,7 @@
 namespace tint::sem {
 
 U32::U32()
-    : Base(TypeFlags{
+    : Base(type::TypeFlags{
           Flag::kConstructable,
           Flag::kCreationFixedFootprint,
           Flag::kFixedFootprint,
diff --git a/src/tint/sem/u32.h b/src/tint/sem/u32.h
index b0143d9..5f6ca0a 100644
--- a/src/tint/sem/u32.h
+++ b/src/tint/sem/u32.h
@@ -17,12 +17,12 @@
 
 #include <string>
 
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 namespace tint::sem {
 
 /// A unsigned int 32 type.
-class U32 final : public Castable<U32, Type> {
+class U32 final : public Castable<U32, type::Type> {
   public:
     /// Constructor
     U32();
diff --git a/src/tint/sem/variable.cc b/src/tint/sem/variable.cc
index f83745f..4de2cdd 100644
--- a/src/tint/sem/variable.cc
+++ b/src/tint/sem/variable.cc
@@ -29,7 +29,7 @@
 
 namespace tint::sem {
 Variable::Variable(const ast::Variable* declaration,
-                   const sem::Type* type,
+                   const type::Type* type,
                    EvaluationStage stage,
                    ast::AddressSpace address_space,
                    ast::Access access,
@@ -44,7 +44,7 @@
 Variable::~Variable() = default;
 
 LocalVariable::LocalVariable(const ast::Variable* declaration,
-                             const sem::Type* type,
+                             const type::Type* type,
                              EvaluationStage stage,
                              ast::AddressSpace address_space,
                              ast::Access access,
@@ -56,7 +56,7 @@
 LocalVariable::~LocalVariable() = default;
 
 GlobalVariable::GlobalVariable(const ast::Variable* declaration,
-                               const sem::Type* type,
+                               const type::Type* type,
                                EvaluationStage stage,
                                ast::AddressSpace address_space,
                                ast::Access access,
@@ -71,7 +71,7 @@
 
 Parameter::Parameter(const ast::Parameter* declaration,
                      uint32_t index,
-                     const sem::Type* type,
+                     const type::Type* type,
                      ast::AddressSpace address_space,
                      ast::Access access,
                      const ParameterUsage usage /* = ParameterUsage::kNone */,
diff --git a/src/tint/sem/variable.h b/src/tint/sem/variable.h
index b977797..bd6282e 100644
--- a/src/tint/sem/variable.h
+++ b/src/tint/sem/variable.h
@@ -27,6 +27,7 @@
 #include "src/tint/sem/binding_point.h"
 #include "src/tint/sem/expression.h"
 #include "src/tint/sem/parameter_usage.h"
+#include "src/tint/type/type.h"
 #include "src/tint/utils/unique_vector.h"
 
 // Forward declarations
@@ -37,7 +38,6 @@
 }  // namespace tint::ast
 namespace tint::sem {
 class CallTarget;
-class Type;
 class VariableUser;
 }  // namespace tint::sem
 
@@ -55,7 +55,7 @@
     /// @param access the variable access control type
     /// @param constant_value the constant value for the variable. May be null
     Variable(const ast::Variable* declaration,
-             const sem::Type* type,
+             const type::Type* type,
              EvaluationStage stage,
              ast::AddressSpace address_space,
              ast::Access access,
@@ -68,7 +68,7 @@
     const ast::Variable* Declaration() const { return declaration_; }
 
     /// @returns the canonical type for the variable
-    const sem::Type* Type() const { return type_; }
+    const type::Type* Type() const { return type_; }
 
     /// @returns the evaluation stage for an expression of this variable type
     EvaluationStage Stage() const { return stage_; }
@@ -98,7 +98,7 @@
 
   private:
     const ast::Variable* const declaration_;
-    const sem::Type* const type_;
+    const type::Type* const type_;
     const EvaluationStage stage_;
     const ast::AddressSpace address_space_;
     const ast::Access access_;
@@ -119,7 +119,7 @@
     /// @param statement the statement that declared this local variable
     /// @param constant_value the constant value for the variable. May be null
     LocalVariable(const ast::Variable* declaration,
-                  const sem::Type* type,
+                  const type::Type* type,
                   EvaluationStage stage,
                   ast::AddressSpace address_space,
                   ast::Access access,
@@ -133,15 +133,15 @@
     const sem::Statement* Statement() const { return statement_; }
 
     /// @returns the Type, Function or Variable that this local variable shadows
-    const sem::Node* Shadows() const { return shadows_; }
+    const CastableBase* Shadows() const { return shadows_; }
 
     /// Sets the Type, Function or Variable that this local variable shadows
     /// @param shadows the Type, Function or Variable that this variable shadows
-    void SetShadows(const sem::Node* shadows) { shadows_ = shadows; }
+    void SetShadows(const CastableBase* shadows) { shadows_ = shadows; }
 
   private:
     const sem::Statement* const statement_;
-    const sem::Node* shadows_ = nullptr;
+    const CastableBase* shadows_ = nullptr;
 };
 
 /// GlobalVariable is a module-scope variable
@@ -160,7 +160,7 @@
     /// Note, a GlobalVariable generally doesn't have a `location` in WGSL, as it isn't allowed by
     /// the spec. The location maybe attached by transforms such as CanonicalizeEntryPointIO.
     GlobalVariable(const ast::Variable* declaration,
-                   const sem::Type* type,
+                   const type::Type* type,
                    EvaluationStage stage,
                    ast::AddressSpace address_space,
                    ast::Access access,
@@ -204,7 +204,7 @@
     /// @param location the location value, if set
     Parameter(const ast::Parameter* declaration,
               uint32_t index,
-              const sem::Type* type,
+              const type::Type* type,
               ast::AddressSpace address_space,
               ast::Access access,
               const ParameterUsage usage = ParameterUsage::kNone,
@@ -232,11 +232,11 @@
     void SetOwner(CallTarget const* owner) { owner_ = owner; }
 
     /// @returns the Type, Function or Variable that this local variable shadows
-    const sem::Node* Shadows() const { return shadows_; }
+    const CastableBase* Shadows() const { return shadows_; }
 
     /// Sets the Type, Function or Variable that this local variable shadows
     /// @param shadows the Type, Function or Variable that this variable shadows
-    void SetShadows(const sem::Node* shadows) { shadows_ = shadows; }
+    void SetShadows(const CastableBase* shadows) { shadows_ = shadows; }
 
     /// @returns the resource binding point for the parameter
     sem::BindingPoint BindingPoint() const { return binding_point_; }
@@ -248,7 +248,7 @@
     const uint32_t index_;
     const ParameterUsage usage_;
     CallTarget const* owner_ = nullptr;
-    const sem::Node* shadows_ = nullptr;
+    const CastableBase* shadows_ = nullptr;
     const sem::BindingPoint binding_point_;
     const std::optional<uint32_t> location_;
 };
diff --git a/src/tint/sem/vector.cc b/src/tint/sem/vector.cc
index 32b7a34..650b58e 100644
--- a/src/tint/sem/vector.cc
+++ b/src/tint/sem/vector.cc
@@ -22,7 +22,7 @@
 namespace tint::sem {
 
 Vector::Vector(Type const* subtype, uint32_t width)
-    : Base(TypeFlags{
+    : Base(type::TypeFlags{
           Flag::kConstructable,
           Flag::kCreationFixedFootprint,
           Flag::kFixedFootprint,
diff --git a/src/tint/sem/vector.h b/src/tint/sem/vector.h
index 163c70d..cfe92cb 100644
--- a/src/tint/sem/vector.h
+++ b/src/tint/sem/vector.h
@@ -17,12 +17,12 @@
 
 #include <string>
 
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 namespace tint::sem {
 
 /// A vector type.
-class Vector final : public Castable<Vector, Type> {
+class Vector final : public Castable<Vector, type::Type> {
   public:
     /// Constructor
     /// @param subtype the vector element type
@@ -40,7 +40,7 @@
     bool Equals(const Type& other) const override;
 
     /// @returns the type of the vector elements
-    const Type* type() const { return subtype_; }
+    const type::Type* type() const { return subtype_; }
 
     /// @param symbols the program's symbol table
     /// @returns the name for this type that closely resembles how it would be
diff --git a/src/tint/sem/void.cc b/src/tint/sem/void.cc
index 8e5e3fb..bc8dbfb 100644
--- a/src/tint/sem/void.cc
+++ b/src/tint/sem/void.cc
@@ -20,7 +20,7 @@
 
 namespace tint::sem {
 
-Void::Void() : Base(TypeFlags{}) {}
+Void::Void() : Base(type::TypeFlags{}) {}
 
 Void::Void(Void&&) = default;
 
diff --git a/src/tint/sem/void.h b/src/tint/sem/void.h
index 21cc3b1..d5d0408 100644
--- a/src/tint/sem/void.h
+++ b/src/tint/sem/void.h
@@ -17,12 +17,12 @@
 
 #include <string>
 
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 namespace tint::sem {
 
 /// A void type
-class Void final : public Castable<Void, Type> {
+class Void final : public Castable<Void, type::Type> {
   public:
     /// Constructor
     Void();
diff --git a/src/tint/transform/add_block_attribute.cc b/src/tint/transform/add_block_attribute.cc
index 513925f..c50c2d5 100644
--- a/src/tint/transform/add_block_attribute.cc
+++ b/src/tint/transform/add_block_attribute.cc
@@ -41,7 +41,7 @@
 
     // A map from a type in the source program to a block-decorated wrapper that contains it in the
     // destination program.
-    utils::Hashmap<const sem::Type*, const ast::Struct*, 8> wrapper_structs;
+    utils::Hashmap<const type::Type*, const ast::Struct*, 8> wrapper_structs;
 
     // Process global 'var' declarations that are buffers.
     bool made_changes = false;
diff --git a/src/tint/transform/builtin_polyfill.cc b/src/tint/transform/builtin_polyfill.cc
index 62f8f4c..c4330f4 100644
--- a/src/tint/transform/builtin_polyfill.cc
+++ b/src/tint/transform/builtin_polyfill.cc
@@ -32,7 +32,7 @@
 namespace tint::transform {
 
 /// BinaryOpSignature is tuple of a binary op, LHS type and RHS type
-using BinaryOpSignature = std::tuple<ast::BinaryOp, const sem::Type*, const sem::Type*>;
+using BinaryOpSignature = std::tuple<ast::BinaryOp, const type::Type*, const type::Type*>;
 
 /// PIMPL state for the transform
 struct BuiltinPolyfill::State {
@@ -48,7 +48,7 @@
     /// Builds the polyfill function for the `acosh` builtin
     /// @param ty the parameter and return type for the function
     /// @return the polyfill function name
-    Symbol acosh(const sem::Type* ty) {
+    Symbol acosh(const type::Type* ty) {
         auto name = b.Symbols().New("tint_acosh");
         uint32_t width = WidthOf(ty);
 
@@ -87,7 +87,7 @@
     /// Builds the polyfill function for the `asinh` builtin
     /// @param ty the parameter and return type for the function
     /// @return the polyfill function name
-    Symbol asinh(const sem::Type* ty) {
+    Symbol asinh(const type::Type* ty) {
         auto name = b.Symbols().New("tint_sinh");
 
         // return log(x + sqrt(x*x + 1));
@@ -102,7 +102,7 @@
     /// Builds the polyfill function for the `atanh` builtin
     /// @param ty the parameter and return type for the function
     /// @return the polyfill function name
-    Symbol atanh(const sem::Type* ty) {
+    Symbol atanh(const type::Type* ty) {
         auto name = b.Symbols().New("tint_atanh");
         uint32_t width = WidthOf(ty);
 
@@ -141,7 +141,7 @@
     /// (scalar or vector)
     /// @param ty the parameter and return type for the function
     /// @return the polyfill function name
-    Symbol clampInteger(const sem::Type* ty) {
+    Symbol clampInteger(const type::Type* ty) {
         auto name = b.Symbols().New("tint_clamp");
 
         b.Func(name,
@@ -161,7 +161,7 @@
     /// Builds the polyfill function for the `countLeadingZeros` builtin
     /// @param ty the parameter and return type for the function
     /// @return the polyfill function name
-    Symbol countLeadingZeros(const sem::Type* ty) {
+    Symbol countLeadingZeros(const type::Type* ty) {
         auto name = b.Symbols().New("tint_count_leading_zeros");
         uint32_t width = WidthOf(ty);
 
@@ -219,7 +219,7 @@
     /// Builds the polyfill function for the `countTrailingZeros` builtin
     /// @param ty the parameter and return type for the function
     /// @return the polyfill function name
-    Symbol countTrailingZeros(const sem::Type* ty) {
+    Symbol countTrailingZeros(const type::Type* ty) {
         auto name = b.Symbols().New("tint_count_trailing_zeros");
         uint32_t width = WidthOf(ty);
 
@@ -279,7 +279,7 @@
     /// Builds the polyfill function for the `extractBits` builtin
     /// @param ty the parameter and return type for the function
     /// @return the polyfill function name
-    Symbol extractBits(const sem::Type* ty) {
+    Symbol extractBits(const type::Type* ty) {
         auto name = b.Symbols().New("tint_extract_bits");
         uint32_t width = WidthOf(ty);
 
@@ -337,7 +337,7 @@
     /// Builds the polyfill function for the `firstLeadingBit` builtin
     /// @param ty the parameter and return type for the function
     /// @return the polyfill function name
-    Symbol firstLeadingBit(const sem::Type* ty) {
+    Symbol firstLeadingBit(const type::Type* ty) {
         auto name = b.Symbols().New("tint_first_leading_bit");
         uint32_t width = WidthOf(ty);
 
@@ -359,7 +359,7 @@
         };
 
         const ast::Expression* x = nullptr;
-        if (ty->is_unsigned_scalar_or_vector()) {
+        if (ty->is_unsigned_integer_scalar_or_vector()) {
             x = b.Expr("v");
         } else {
             // If ty is signed, then the value is inverted if the sign is negative
@@ -409,7 +409,7 @@
     /// Builds the polyfill function for the `firstTrailingBit` builtin
     /// @param ty the parameter and return type for the function
     /// @return the polyfill function name
-    Symbol firstTrailingBit(const sem::Type* ty) {
+    Symbol firstTrailingBit(const type::Type* ty) {
         auto name = b.Symbols().New("tint_first_trailing_bit");
         uint32_t width = WidthOf(ty);
 
@@ -468,12 +468,12 @@
     /// Builds the polyfill function for the `insertBits` builtin
     /// @param ty the parameter and return type for the function
     /// @return the polyfill function name
-    Symbol insertBits(const sem::Type* ty) {
+    Symbol insertBits(const type::Type* ty) {
         auto name = b.Symbols().New("tint_insert_bits");
         uint32_t width = WidthOf(ty);
 
         // Currently in WGSL parameters of insertBits must be i32, u32, vecN<i32> or vecN<u32>
-        if (!sem::Type::DeepestElementOf(ty)->IsAnyOf<sem::I32, sem::U32>()) {
+        if (!type::Type::DeepestElementOf(ty)->IsAnyOf<sem::I32, sem::U32>()) {
             TINT_ICE(Transform, b.Diagnostics())
                 << "insertBits polyfill only support i32, u32, and vector of i32 or u32, got "
                 << b.FriendlyName(ty);
@@ -484,7 +484,7 @@
 
         auto V = [&](auto value) -> const ast::Expression* {
             const ast::Expression* expr = b.Expr(value);
-            if (!ty->is_unsigned_scalar_or_vector()) {
+            if (!ty->is_unsigned_integer_scalar_or_vector()) {
                 expr = b.Construct<i32>(expr);
             }
             if (ty->Is<sem::Vector>()) {
@@ -576,7 +576,7 @@
     /// Builds the polyfill function for the `saturate` builtin
     /// @param ty the parameter and return type for the function
     /// @return the polyfill function name
-    Symbol saturate(const sem::Type* ty) {
+    Symbol saturate(const type::Type* ty) {
         auto name = b.Symbols().New("tint_saturate");
         auto body = utils::Vector{
             b.Return(b.Call("clamp", "v", b.Construct(T(ty), 0_a), b.Construct(T(ty), 1_a))),
@@ -590,6 +590,32 @@
         return name;
     }
 
+    /// Builds the polyfill function for the `sign` builtin when the element type is integer
+    /// @param ty the parameter and return type for the function
+    /// @return the polyfill function name
+    Symbol sign_int(const type::Type* ty) {
+        const uint32_t width = WidthOf(ty);
+        auto zero = [&] { return ScalarOrVector(width, 0_a); };
+
+        // pos_or_neg_one = (v > 0) ? 1 : -1
+        auto pos_or_neg_one = b.Call("select",                     //
+                                     ScalarOrVector(width, -1_a),  //
+                                     ScalarOrVector(width, 1_a),   //
+                                     b.GreaterThan("v", zero()));
+
+        auto name = b.Symbols().New("tint_sign");
+        b.Func(name,
+               utils::Vector{
+                   b.Param("v", T(ty)),
+               },
+               T(ty),
+               utils::Vector{
+                   b.Return(b.Call("select", pos_or_neg_one, zero(), b.Equal("v", zero()))),
+               });
+
+        return name;
+    }
+
     /// Builds the polyfill function for the `textureSampleBaseClampToEdge` builtin, when the
     /// texture type is texture_2d<f32>.
     /// @return the polyfill function name
@@ -645,7 +671,7 @@
     const ast::Expression* BitshiftModulo(const ast::BinaryExpression* bin_op) {
         auto* lhs_ty = ctx.src->TypeOf(bin_op->lhs)->UnwrapRef();
         auto* rhs_ty = ctx.src->TypeOf(bin_op->rhs)->UnwrapRef();
-        auto* lhs_el_ty = sem::Type::DeepestElementOf(lhs_ty);
+        auto* lhs_el_ty = type::Type::DeepestElementOf(lhs_ty);
         const ast::Expression* mask = b.Expr(AInt(lhs_el_ty->Size() * 8 - 1));
         if (rhs_ty->Is<sem::Vector>()) {
             mask = b.Construct(CreateASTTypeFor(ctx, rhs_ty), mask);
@@ -668,8 +694,8 @@
 
             uint32_t lhs_width = 1;
             uint32_t rhs_width = 1;
-            const auto* lhs_el_ty = sem::Type::ElementOf(lhs_ty, &lhs_width);
-            const auto* rhs_el_ty = sem::Type::ElementOf(rhs_ty, &rhs_width);
+            const auto* lhs_el_ty = type::Type::ElementOf(lhs_ty, &lhs_width);
+            const auto* rhs_el_ty = type::Type::ElementOf(rhs_ty, &rhs_width);
 
             const uint32_t width = std::max(lhs_width, rhs_width);
 
@@ -691,7 +717,7 @@
 
             auto name = b.Symbols().New(is_div ? "tint_div" : "tint_mod");
             auto* use_one = b.Equal(rhs, ScalarOrVector(width, 0_a));
-            if (lhs_ty->is_signed_scalar_or_vector()) {
+            if (lhs_ty->is_signed_integer_scalar_or_vector()) {
                 const auto bits = lhs_el_ty->Size() * 8;
                 auto min_int = AInt(AInt::kLowestValue >> (AInt::kNumBits - bits));
                 const ast::Expression* lhs_is_min = b.Equal(lhs, ScalarOrVector(width, min_int));
@@ -731,10 +757,10 @@
     utils::Hashmap<BinaryOpSignature, Symbol, 8> binary_op_polyfills;
 
     /// @returns the AST type for the given sem type
-    const ast::Type* T(const sem::Type* ty) const { return CreateASTTypeFor(ctx, ty); }
+    const 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 sem::Type* ty) const {
+    uint32_t WidthOf(const type::Type* ty) const {
         if (auto* v = ty->As<sem::Vector>()) {
             return v->Width();
         }
@@ -855,6 +881,15 @@
                             builtin, [&] { return s.saturate(builtin->ReturnType()); });
                     }
                     break;
+                case sem::BuiltinType::kSign:
+                    if (polyfill.sign_int) {
+                        auto* ty = builtin->ReturnType();
+                        if (ty->is_signed_integer_scalar_or_vector()) {
+                            fn = builtin_polyfills.GetOrCreate(builtin,
+                                                               [&] { return s.sign_int(ty); });
+                        }
+                    }
+                    break;
                 case sem::BuiltinType::kTextureSampleBaseClampToEdge:
                     if (polyfill.texture_sample_base_clamp_to_edge_2d_f32) {
                         auto& sig = builtin->Signature();
diff --git a/src/tint/transform/builtin_polyfill.h b/src/tint/transform/builtin_polyfill.h
index f9eb029..1e2f73a 100644
--- a/src/tint/transform/builtin_polyfill.h
+++ b/src/tint/transform/builtin_polyfill.h
@@ -68,6 +68,8 @@
         bool int_div_mod = false;
         /// Should `saturate()` be polyfilled?
         bool saturate = false;
+        /// Should `sign()` be polyfilled for integer types?
+        bool sign_int = false;
         /// Should `textureSampleBaseClampToEdge()` be polyfilled for texture_2d<f32> textures?
         bool texture_sample_base_clamp_to_edge_2d_f32 = false;
         /// Should the vector form of `quantizeToF16()` be polyfilled with a scalar implementation?
diff --git a/src/tint/transform/builtin_polyfill_test.cc b/src/tint/transform/builtin_polyfill_test.cc
index 1fbea9c..2272173 100644
--- a/src/tint/transform/builtin_polyfill_test.cc
+++ b/src/tint/transform/builtin_polyfill_test.cc
@@ -34,11 +34,7 @@
 TEST_F(BuiltinPolyfillTest, EmptyModule) {
     auto* src = R"()";
 
-    auto* expect = src;
-
-    auto got = Run<BuiltinPolyfill>(src);
-
-    EXPECT_EQ(expect, str(got));
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -73,11 +69,7 @@
 }
 )";
 
-    auto* expect = src;
-
-    auto got = Run<BuiltinPolyfill>(src, polyfillAcosh(Level::kFull));
-
-    EXPECT_EQ(expect, str(got));
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src, polyfillAcosh(Level::kFull)));
 }
 
 TEST_F(BuiltinPolyfillTest, Acosh_Full_f32) {
@@ -206,11 +198,7 @@
 }
 )";
 
-    auto* expect = src;
-
-    auto got = Run<BuiltinPolyfill>(src, polyfillSinh());
-
-    EXPECT_EQ(expect, str(got));
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src, polyfillSinh()));
 }
 
 TEST_F(BuiltinPolyfillTest, Asinh_f32) {
@@ -293,11 +281,7 @@
 }
 )";
 
-    auto* expect = src;
-
-    auto got = Run<BuiltinPolyfill>(src, polyfillAtanh(Level::kFull));
-
-    EXPECT_EQ(expect, str(got));
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src, polyfillAtanh(Level::kFull)));
 }
 
 TEST_F(BuiltinPolyfillTest, Atanh_Full_f32) {
@@ -603,11 +587,7 @@
 }
 )";
 
-    auto* expect = src;
-
-    auto got = Run<BuiltinPolyfill>(src, polyfillClampInteger());
-
-    EXPECT_EQ(expect, str(got));
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src, polyfillClampInteger()));
 }
 
 TEST_F(BuiltinPolyfillTest, ClampInteger_i32) {
@@ -732,11 +712,7 @@
 }
 )";
 
-    auto* expect = src;
-
-    auto got = Run<BuiltinPolyfill>(src, polyfillCountLeadingZeros());
-
-    EXPECT_EQ(expect, str(got));
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src, polyfillCountLeadingZeros()));
 }
 
 TEST_F(BuiltinPolyfillTest, CountLeadingZeros_i32) {
@@ -909,11 +885,7 @@
 }
 )";
 
-    auto* expect = src;
-
-    auto got = Run<BuiltinPolyfill>(src, polyfillCountTrailingZeros());
-
-    EXPECT_EQ(expect, str(got));
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src, polyfillCountTrailingZeros()));
 }
 
 TEST_F(BuiltinPolyfillTest, CountTrailingZeros_i32) {
@@ -1088,11 +1060,7 @@
 }
 )";
 
-    auto* expect = src;
-
-    auto got = Run<BuiltinPolyfill>(src, polyfillExtractBits(Level::kFull));
-
-    EXPECT_EQ(expect, str(got));
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src, polyfillExtractBits(Level::kFull)));
 }
 
 TEST_F(BuiltinPolyfillTest, ExtractBits_Full_i32) {
@@ -1345,11 +1313,7 @@
 }
 )";
 
-    auto* expect = src;
-
-    auto got = Run<BuiltinPolyfill>(src, polyfillFirstLeadingBit());
-
-    EXPECT_EQ(expect, str(got));
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src, polyfillFirstLeadingBit()));
 }
 
 TEST_F(BuiltinPolyfillTest, FirstLeadingBit_i32) {
@@ -1522,11 +1486,7 @@
 }
 )";
 
-    auto* expect = src;
-
-    auto got = Run<BuiltinPolyfill>(src, polyfillFirstTrailingBit());
-
-    EXPECT_EQ(expect, str(got));
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src, polyfillFirstTrailingBit()));
 }
 
 TEST_F(BuiltinPolyfillTest, FirstTrailingBit_i32) {
@@ -1701,11 +1661,7 @@
 }
 )";
 
-    auto* expect = src;
-
-    auto got = Run<BuiltinPolyfill>(src, polyfillInsertBits(Level::kFull));
-
-    EXPECT_EQ(expect, str(got));
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src, polyfillInsertBits(Level::kFull)));
 }
 
 TEST_F(BuiltinPolyfillTest, InsertBits_Full_i32) {
@@ -2715,11 +2671,7 @@
 }
 )";
 
-    auto* expect = src;
-
-    auto got = Run<BuiltinPolyfill>(src, polyfillSaturate());
-
-    EXPECT_EQ(expect, str(got));
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src, polyfillSaturate()));
 }
 
 TEST_F(BuiltinPolyfillTest, Saturate_f32) {
@@ -2827,6 +2779,99 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// sign_int
+////////////////////////////////////////////////////////////////////////////////
+DataMap polyfillSignInt() {
+    BuiltinPolyfill::Builtins builtins;
+    builtins.sign_int = true;
+    DataMap data;
+    data.Add<BuiltinPolyfill::Config>(builtins);
+    return data;
+}
+
+TEST_F(BuiltinPolyfillTest, ShouldRunSign_i32) {
+    auto* src = R"(
+fn f() {
+  let v = 1i;
+  sign(v);
+}
+)";
+
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src));
+    EXPECT_TRUE(ShouldRun<BuiltinPolyfill>(src, polyfillSignInt()));
+}
+
+TEST_F(BuiltinPolyfillTest, ShouldRunSign_f32) {
+    auto* src = R"(
+fn f() {
+  let v = 1f;
+  sign(v);
+}
+)";
+
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src));
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src, polyfillSignInt()));
+}
+
+TEST_F(BuiltinPolyfillTest, SignInt_ConstantExpression) {
+    auto* src = R"(
+fn f() {
+  let r : i32 = sign(1i);
+}
+)";
+
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src, polyfillSignInt()));
+}
+
+TEST_F(BuiltinPolyfillTest, SignInt_i32) {
+    auto* src = R"(
+fn f() {
+  let v = 1i;
+  let r : i32 = sign(v);
+}
+)";
+
+    auto* expect = R"(
+fn tint_sign(v : i32) -> i32 {
+  return select(select(-1, 1, (v > 0)), 0, (v == 0));
+}
+
+fn f() {
+  let v = 1i;
+  let r : i32 = tint_sign(v);
+}
+)";
+
+    auto got = Run<BuiltinPolyfill>(src, polyfillSignInt());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, SignInt_vec3_i32) {
+    auto* src = R"(
+fn f() {
+  let v = 1i;
+  let r : vec3<i32> = sign(vec3<i32>(v));
+}
+)";
+
+    auto* expect = R"(
+fn tint_sign(v : vec3<i32>) -> vec3<i32> {
+  return select(select(vec3(-1), vec3(1), (v > vec3(0))), vec3(0), (v == vec3(0)));
+}
+
+fn f() {
+  let v = 1i;
+  let r : vec3<i32> = tint_sign(vec3<i32>(v));
+}
+)";
+
+    auto got = Run<BuiltinPolyfill>(src, polyfillSignInt());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // textureSampleBaseClampToEdge
 ////////////////////////////////////////////////////////////////////////////////
 DataMap polyfillTextureSampleBaseClampToEdge_2d_f32() {
diff --git a/src/tint/transform/canonicalize_entry_point_io.cc b/src/tint/transform/canonicalize_entry_point_io.cc
index b41529a..8899ac7 100644
--- a/src/tint/transform/canonicalize_entry_point_io.cc
+++ b/src/tint/transform/canonicalize_entry_point_io.cc
@@ -207,7 +207,7 @@
     /// @param attributes the attributes to apply to the shader input
     /// @returns an expression which evaluates to the value of the shader input
     const ast::Expression* AddInput(std::string name,
-                                    const sem::Type* type,
+                                    const type::Type* type,
                                     std::optional<uint32_t> location,
                                     utils::Vector<const ast::Attribute*, 8> attributes) {
         auto* ast_type = CreateASTTypeFor(ctx, type);
@@ -278,7 +278,7 @@
     /// @param attributes the attributes to apply to the shader output
     /// @param value the value of the shader output
     void AddOutput(std::string name,
-                   const sem::Type* type,
+                   const type::Type* type,
                    std::optional<uint32_t> location,
                    utils::Vector<const ast::Attribute*, 8> attributes,
                    const ast::Expression* value) {
@@ -378,7 +378,7 @@
     /// function.
     /// @param inner_ret_type the original function return type
     /// @param original_result the result object produced by the original function
-    void ProcessReturnType(const sem::Type* inner_ret_type, Symbol original_result) {
+    void ProcessReturnType(const type::Type* inner_ret_type, Symbol original_result) {
         // Do not add interpolation attributes on fragment output
         bool do_interpolate = func_ast->PipelineStage() != ast::PipelineStage::kFragment;
         if (auto* str = inner_ret_type->As<sem::Struct>()) {
@@ -754,7 +754,7 @@
     /// @returns the converted value which can be assigned to the GLSL builtin
     const ast::Expression* ToGLSLBuiltin(ast::BuiltinValue builtin,
                                          const ast::Expression* value,
-                                         const sem::Type*& type) {
+                                         const type::Type*& type) {
         switch (builtin) {
             case ast::BuiltinValue::kVertexIndex:
             case ast::BuiltinValue::kInstanceIndex:
diff --git a/src/tint/transform/combine_samplers.cc b/src/tint/transform/combine_samplers.cc
index e7286d4..a266dc3 100644
--- a/src/tint/transform/combine_samplers.cc
+++ b/src/tint/transform/combine_samplers.cc
@@ -140,7 +140,7 @@
     /// @returns the newly-created type
     const ast::Type* CreateCombinedASTTypeFor(const sem::Variable* texture,
                                               const sem::Variable* sampler) {
-        const sem::Type* texture_type = texture->Type()->UnwrapRef();
+        const type::Type* texture_type = texture->Type()->UnwrapRef();
         const sem::DepthTexture* depth = texture_type->As<sem::DepthTexture>();
         if (depth && !sampler) {
             return ctx.dst->create<ast::SampledTexture>(depth->dim(), ctx.dst->create<ast::F32>());
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index 1b674ba..7875a4e 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -111,8 +111,8 @@
 struct LoadStoreKey {
     ast::AddressSpace const address_space;  // buffer address space
     ast::Access const access;               // buffer access
-    sem::Type const* buf_ty = nullptr;      // buffer type
-    sem::Type const* el_ty = nullptr;       // element type
+    type::Type const* buf_ty = nullptr;     // buffer type
+    type::Type const* el_ty = nullptr;      // element type
     bool operator==(const LoadStoreKey& rhs) const {
         return address_space == rhs.address_space && access == rhs.access && buf_ty == rhs.buf_ty &&
                el_ty == rhs.el_ty;
@@ -127,8 +127,8 @@
 /// AtomicKey is the unordered map key to an atomic intrinsic.
 struct AtomicKey {
     ast::Access const access;           // buffer access
-    sem::Type const* buf_ty = nullptr;  // buffer type
-    sem::Type const* el_ty = nullptr;   // element type
+    type::Type const* buf_ty = nullptr;  // buffer type
+    type::Type const* el_ty = nullptr;   // element type
     sem::BuiltinType const op;          // atomic op
     bool operator==(const AtomicKey& rhs) const {
         return access == rhs.access && buf_ty == rhs.buf_ty && el_ty == rhs.el_ty && op == rhs.op;
@@ -140,7 +140,7 @@
     };
 };
 
-bool IntrinsicDataTypeFor(const sem::Type* ty, DecomposeMemoryAccess::Intrinsic::DataType& out) {
+bool IntrinsicDataTypeFor(const type::Type* ty, DecomposeMemoryAccess::Intrinsic::DataType& out) {
     if (ty->Is<sem::I32>()) {
         out = DecomposeMemoryAccess::Intrinsic::DataType::kI32;
         return true;
@@ -224,7 +224,7 @@
 /// to a stub function to load the type `ty`.
 DecomposeMemoryAccess::Intrinsic* IntrinsicLoadFor(ProgramBuilder* builder,
                                                    ast::AddressSpace address_space,
-                                                   const sem::Type* ty) {
+                                                   const type::Type* ty) {
     DecomposeMemoryAccess::Intrinsic::DataType type;
     if (!IntrinsicDataTypeFor(ty, type)) {
         return nullptr;
@@ -238,7 +238,7 @@
 /// to a stub function to store the type `ty`.
 DecomposeMemoryAccess::Intrinsic* IntrinsicStoreFor(ProgramBuilder* builder,
                                                     ast::AddressSpace address_space,
-                                                    const sem::Type* ty) {
+                                                    const type::Type* ty) {
     DecomposeMemoryAccess::Intrinsic::DataType type;
     if (!IntrinsicDataTypeFor(ty, type)) {
         return nullptr;
@@ -252,7 +252,7 @@
 /// to a stub function for the atomic op and the type `ty`.
 DecomposeMemoryAccess::Intrinsic* IntrinsicAtomicFor(ProgramBuilder* builder,
                                                      sem::BuiltinType ity,
-                                                     const sem::Type* ty) {
+                                                     const type::Type* ty) {
     auto op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicLoad;
     switch (ity) {
         case sem::BuiltinType::kAtomicLoad:
@@ -307,7 +307,7 @@
 struct BufferAccess {
     sem::Expression const* var = nullptr;  // Storage buffer variable
     Offset const* offset = nullptr;        // The byte offset on var
-    sem::Type const* type = nullptr;       // The type of the access
+    type::Type const* type = nullptr;      // The type of the access
     operator bool() const { return var; }  // Returns true if valid
 };
 
@@ -461,8 +461,8 @@
     /// @param el_ty the storage or uniform buffer element type
     /// @param var_user the variable user
     /// @return the name of the function that performs the load
-    Symbol LoadFunc(const sem::Type* buf_ty,
-                    const sem::Type* el_ty,
+    Symbol LoadFunc(const type::Type* buf_ty,
+                    const type::Type* el_ty,
                     const sem::VariableUser* var_user) {
         auto address_space = var_user->Variable()->AddressSpace();
         auto access = var_user->Variable()->Access();
@@ -560,8 +560,8 @@
     /// @param el_ty the storage buffer element type
     /// @param var_user the variable user
     /// @return the name of the function that performs the store
-    Symbol StoreFunc(const sem::Type* buf_ty,
-                     const sem::Type* el_ty,
+    Symbol StoreFunc(const type::Type* buf_ty,
+                     const type::Type* el_ty,
                      const sem::VariableUser* var_user) {
         auto address_space = var_user->Variable()->AddressSpace();
         auto access = var_user->Variable()->Access();
@@ -671,8 +671,8 @@
     /// @param intrinsic the atomic intrinsic
     /// @param var_user the variable user
     /// @return the name of the function that performs the load
-    Symbol AtomicFunc(const sem::Type* buf_ty,
-                      const sem::Type* el_ty,
+    Symbol AtomicFunc(const type::Type* buf_ty,
+                      const type::Type* el_ty,
                       const sem::Builtin* intrinsic,
                       const sem::VariableUser* var_user) {
         auto op = intrinsic->Type();
diff --git a/src/tint/transform/demote_to_helper.cc b/src/tint/transform/demote_to_helper.cc
index cb35c67..9b44777 100644
--- a/src/tint/transform/demote_to_helper.cc
+++ b/src/tint/transform/demote_to_helper.cc
@@ -104,7 +104,7 @@
     // Mask all writes to host-visible memory using the discarded flag.
     // We also insert a discard statement before all return statements in entry points for shaders
     // that discard.
-    std::unordered_map<const sem::Type*, Symbol> atomic_cmpxchg_result_types;
+    std::unordered_map<const type::Type*, Symbol> atomic_cmpxchg_result_types;
     for (auto* node : src->ASTNodes().Objects()) {
         Switch(
             node,
diff --git a/src/tint/transform/direct_variable_access.cc b/src/tint/transform/direct_variable_access.cc
index 26c4aba..07f159a 100644
--- a/src/tint/transform/direct_variable_access.cc
+++ b/src/tint/transform/direct_variable_access.cc
@@ -45,7 +45,7 @@
     /// The pointer-unwrapped type of the *transformed* variable.
     /// This may be different for pointers in 'private' and 'function' address space, as the pointer
     /// parameter type is to the *base object* instead of the input pointer type.
-    tint::sem::Type const* type = nullptr;
+    tint::type::Type const* type = nullptr;
     /// The originating module-scope variable ('private', 'storage', 'uniform', 'workgroup'),
     /// function-scope variable ('function'), or pointer parameter in the source program.
     tint::sem::Variable const* variable = nullptr;
diff --git a/src/tint/transform/localize_struct_array_assignment.cc b/src/tint/transform/localize_struct_array_assignment.cc
index cb53645..f36dc7d 100644
--- a/src/tint/transform/localize_struct_array_assignment.cc
+++ b/src/tint/transform/localize_struct_array_assignment.cc
@@ -172,7 +172,7 @@
     // Returns the type and address space of the originating variable of the lhs
     // of the assignment statement.
     // See https://www.w3.org/TR/WGSL/#originating-variable-section
-    std::pair<const sem::Type*, ast::AddressSpace> GetOriginatingTypeAndAddressSpace(
+    std::pair<const type::Type*, ast::AddressSpace> GetOriginatingTypeAndAddressSpace(
         const ast::AssignmentStatement* assign_stmt) {
         auto* root_ident = src->Sem().Get(assign_stmt->lhs)->RootIdentifier();
         if (!root_ident) {
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 9156205..12c1182 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
@@ -48,7 +48,7 @@
 }
 
 // Returns `true` if `type` is or contains a matrix type.
-bool ContainsMatrix(const sem::Type* type) {
+bool ContainsMatrix(const type::Type* type) {
     type = type->UnwrapRef();
     if (type->Is<sem::Matrix>()) {
         return true;
@@ -78,7 +78,7 @@
     /// and add it to the global declarations now, so that they precede new global
     /// declarations that need to reference them.
     /// @param ty the type to clone
-    void CloneStructTypes(const sem::Type* ty) {
+    void CloneStructTypes(const type::Type* ty) {
         if (auto* str = ty->As<sem::Struct>()) {
             if (!cloned_structs_.emplace(str).second) {
                 // The struct has already been cloned.
@@ -147,7 +147,7 @@
 
                 auto* param_type = store_type();
                 if (auto* arr = ty->As<sem::Array>();
-                    arr && arr->Count()->Is<sem::RuntimeArrayCount>()) {
+                    arr && arr->Count()->Is<type::RuntimeArrayCount>()) {
                     // Wrap runtime-sized arrays in structures, so that we can declare pointers to
                     // them. Ideally we'd just emit the array itself as a pointer, but this is not
                     // representable in Tint's AST.
diff --git a/src/tint/transform/pad_structs.cc b/src/tint/transform/pad_structs.cc
index e8c21a7..64ce97c 100644
--- a/src/tint/transform/pad_structs.cc
+++ b/src/tint/transform/pad_structs.cc
@@ -84,7 +84,7 @@
                 // std140 structs should be padded out to 16 bytes.
                 size = utils::RoundUp(16u, size);
             } else if (auto* array_ty = ty->As<sem::Array>()) {
-                if (array_ty->Count()->Is<sem::RuntimeArrayCount>()) {
+                if (array_ty->Count()->Is<type::RuntimeArrayCount>()) {
                     has_runtime_sized_array = true;
                 }
             }
diff --git a/src/tint/transform/preserve_padding.cc b/src/tint/transform/preserve_padding.cc
index d02dacc..8f0f6d8 100644
--- a/src/tint/transform/preserve_padding.cc
+++ b/src/tint/transform/preserve_padding.cc
@@ -94,7 +94,7 @@
     /// @param lhs the lhs expression (in the destination program)
     /// @param rhs the rhs expression (in the destination program)
     /// @returns the statement that performs the assignment
-    const ast::Statement* MakeAssignment(const sem::Type* ty,
+    const ast::Statement* MakeAssignment(const type::Type* ty,
                                          const ast::Expression* lhs,
                                          const ast::Expression* rhs) {
         if (!HasPadding(ty)) {
@@ -168,7 +168,7 @@
     /// Checks if a type contains padding bytes.
     /// @param ty the type to check
     /// @returns true if `ty` (or any of its contained types) have padding bytes
-    bool HasPadding(const sem::Type* ty) {
+    bool HasPadding(const type::Type* ty) {
         return Switch(
             ty,  //
             [&](const sem::Array* arr) {
@@ -214,7 +214,7 @@
     /// Flag to track whether we have already enabled the full pointer parameters extension.
     bool ext_enabled = false;
     /// Map of semantic types to their assignment helper functions.
-    utils::Hashmap<const sem::Type*, Symbol, 8> helpers;
+    utils::Hashmap<const type::Type*, Symbol, 8> helpers;
 };
 
 Transform::ApplyResult PreservePadding::Apply(const Program* program,
diff --git a/src/tint/transform/remove_phonies.cc b/src/tint/transform/remove_phonies.cc
index 080152c..cb2d914 100644
--- a/src/tint/transform/remove_phonies.cc
+++ b/src/tint/transform/remove_phonies.cc
@@ -33,7 +33,7 @@
 namespace tint::transform {
 namespace {
 
-using SinkSignature = std::vector<const sem::Type*>;
+using SinkSignature = std::vector<const type::Type*>;
 
 }  // namespace
 
diff --git a/src/tint/transform/robustness.cc b/src/tint/transform/robustness.cc
index c9f6292..2c7925b 100644
--- a/src/tint/transform/robustness.cc
+++ b/src/tint/transform/robustness.cc
@@ -106,7 +106,7 @@
             },
             [&](const sem::Array* arr) -> const ast::Expression* {
                 const ast::Expression* max = nullptr;
-                if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
+                if (arr->Count()->Is<type::RuntimeArrayCount>()) {
                     // Size is unknown until runtime.
                     // Must clamp, even if the index is constant.
                     auto* arr_ptr = b.AddressOf(ctx.Clone(expr->object));
@@ -176,7 +176,7 @@
         auto* coords_arg = expr->args[static_cast<size_t>(coords_idx)];
         auto* coords_ty = builtin->Parameters()[static_cast<size_t>(coords_idx)]->Type();
 
-        auto width_of = [&](const sem::Type* ty) {
+        auto width_of = [&](const type::Type* ty) {
             if (auto* vec = ty->As<sem::Vector>()) {
                 return vec->Width();
             }
@@ -241,7 +241,7 @@
 
             // texture_dims is u32 or vecN<u32>
             const auto* unsigned_max = b.Sub(texture_dims, scalar_or_vec(b.Expr(1_a), width));
-            if (target_ty->is_signed_scalar_or_vector()) {
+            if (target_ty->is_signed_integer_scalar_or_vector()) {
                 const auto* zero = scalar_or_vec(b.Expr(0_a), width);
                 const auto* signed_max = cast_to_signed(unsigned_max, width);
                 ctx.Replace(coords_arg, b.Call("clamp", ctx.Clone(coords_arg), zero, signed_max));
diff --git a/src/tint/transform/spirv_atomic.cc b/src/tint/transform/spirv_atomic.cc
index 72304e6..57fc420 100644
--- a/src/tint/transform/spirv_atomic.cc
+++ b/src/tint/transform/spirv_atomic.cc
@@ -198,14 +198,14 @@
         }
     }
 
-    const ast::Type* AtomicTypeFor(const sem::Type* ty) {
+    const ast::Type* AtomicTypeFor(const type::Type* ty) {
         return Switch(
             ty,  //
             [&](const sem::I32*) { return b.ty.atomic(CreateASTTypeFor(ctx, ty)); },
             [&](const sem::U32*) { return b.ty.atomic(CreateASTTypeFor(ctx, ty)); },
             [&](const sem::Struct* str) { return b.ty.type_name(Fork(str->Declaration()).name); },
             [&](const sem::Array* arr) -> const ast::Type* {
-                if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
+                if (arr->Count()->Is<type::RuntimeArrayCount>()) {
                     return b.ty.array(AtomicTypeFor(arr->ElemType()));
                 }
                 auto count = arr->ConstantCount();
diff --git a/src/tint/transform/std140.cc b/src/tint/transform/std140.cc
index a371f24..e1cc7c5 100644
--- a/src/tint/transform/std140.cc
+++ b/src/tint/transform/std140.cc
@@ -128,7 +128,7 @@
     /// @returns true if this transform should be run for the given program
     bool ShouldRun() const {
         // Returns true if the type needs to be forked for std140 usage.
-        auto needs_fork = [&](const sem::Type* ty) {
+        auto needs_fork = [&](const type::Type* ty) {
             while (auto* arr = ty->As<sem::Array>()) {
                 ty = arr->ElemType();
             }
@@ -218,7 +218,7 @@
     utils::Hashmap<LoadFnKey, Symbol, 8, LoadFnKey::Hasher> load_fns;
 
     /// Map of std140-forked type to converter function name
-    utils::Hashmap<const sem::Type*, Symbol, 8> conv_fns;
+    utils::Hashmap<const type::Type*, Symbol, 8> conv_fns;
 
     // Uniform variables that have been modified to use a std140 type
     utils::Hashset<const sem::Variable*, 8> std140_uniforms;
@@ -397,7 +397,7 @@
     ///          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 sem::Type* ty) {
+    const ast::Type* Std140Type(const type::Type* ty) {
         return Switch(
             ty,  //
             [&](const sem::Struct* str) -> const ast::Type* {
@@ -625,7 +625,7 @@
 
     /// @returns a name suffix for a std140 -> non-std140 conversion function based on the type
     ///          being converted.
-    const std::string ConvertSuffix(const sem::Type* ty) {
+    const std::string ConvertSuffix(const type::Type* ty) {
         return Switch(
             ty,  //
             [&](const sem::Struct* str) { return sym.NameFor(str->Name()); },
@@ -659,7 +659,7 @@
     /// @param chain the access chain from a uniform buffer to the value to load.
     const ast::Expression* LoadWithConvert(const AccessChain& chain) {
         const ast::Expression* expr = nullptr;
-        const sem::Type* ty = nullptr;
+        const type::Type* ty = nullptr;
         auto dynamic_index = [&](size_t idx) {
             return ctx.Clone(chain.dynamic_indices[idx]->Declaration());
         };
@@ -675,7 +675,7 @@
     /// std140-forked type to the type @p ty. If @p expr is not a std140-forked type, then Convert()
     /// will simply return @p expr.
     /// @returns the converted value expression.
-    const ast::Expression* Convert(const sem::Type* ty, const ast::Expression* expr) {
+    const ast::Expression* Convert(const type::Type* ty, const ast::Expression* expr) {
         // Get an existing, or create a new function for converting the std140 type to ty.
         auto fn = conv_fns.GetOrCreate(ty, [&] {
             auto std140_ty = Std140Type(ty);
@@ -815,7 +815,7 @@
         };
 
         const ast::Expression* expr = nullptr;
-        const sem::Type* ty = nullptr;
+        const type::Type* ty = nullptr;
 
         // Build the expression up to, but not including the matrix member
         auto std140_mat_idx = *chain.std140_mat_idx;
@@ -889,13 +889,13 @@
         utils::Vector<const ast::CaseStatement*, 4> cases;
 
         // The function return type.
-        const sem::Type* ret_ty = nullptr;
+        const type::Type* ret_ty = nullptr;
 
         // Build switch() cases for each column of the matrix
         auto num_columns = chain.std140_mat_ty->columns();
         for (uint32_t column_idx = 0; column_idx < num_columns; column_idx++) {
             const ast::Expression* expr = nullptr;
-            const sem::Type* ty = nullptr;
+            const type::Type* ty = nullptr;
 
             // Build the expression up to, but not including the matrix
             for (size_t i = 0; i < std140_mat_idx; i++) {
@@ -985,7 +985,7 @@
         auto dynamic_index = [&](size_t idx) { return b.Expr(dynamic_index_params[idx]->symbol); };
 
         const ast::Expression* expr = nullptr;
-        const sem::Type* ty = nullptr;
+        const type::Type* ty = nullptr;
         std::string name = "load";
 
         // Build the expression up to, but not including the matrix member
@@ -1048,7 +1048,7 @@
         /// The new, post-access expression
         const ast::Expression* expr;
         /// The type of #expr
-        const sem::Type* type;
+        const type::Type* type;
         /// A name segment which can be used to build sensible names for helper functions
         std::string name;
     };
@@ -1061,7 +1061,7 @@
     /// @returns a ExprTypeName which holds the new expression, new type and a name segment which
     ///          can be used for creating helper function names.
     ExprTypeName BuildAccessExpr(const ast::Expression* lhs,
-                                 const sem::Type* ty,
+                                 const type::Type* ty,
                                  const AccessChain& chain,
                                  size_t index,
                                  std::function<const ast::Expression*(size_t)> dynamic_index) {
diff --git a/src/tint/transform/transform.cc b/src/tint/transform/transform.cc
index cbfb90a..5877900 100644
--- a/src/tint/transform/transform.cc
+++ b/src/tint/transform/transform.cc
@@ -73,7 +73,7 @@
         << "unable to remove statement from parent of type " << sem->TypeInfo().name;
 }
 
-const ast::Type* Transform::CreateASTTypeFor(CloneContext& ctx, const sem::Type* ty) {
+const ast::Type* Transform::CreateASTTypeFor(CloneContext& ctx, const type::Type* ty) {
     if (ty->Is<sem::Void>()) {
         return ctx.dst->create<ast::Void>();
     }
@@ -106,7 +106,7 @@
         if (!a->IsStrideImplicit()) {
             attrs.Push(ctx.dst->create<ast::StrideAttribute>(a->Stride()));
         }
-        if (a->Count()->Is<sem::RuntimeArrayCount>()) {
+        if (a->Count()->Is<type::RuntimeArrayCount>()) {
             return ctx.dst->ty.array(el, nullptr, std::move(attrs));
         }
         if (auto* override = a->Count()->As<sem::NamedOverrideArrayCount>()) {
diff --git a/src/tint/transform/transform.h b/src/tint/transform/transform.h
index 6580e25..cc2f782 100644
--- a/src/tint/transform/transform.h
+++ b/src/tint/transform/transform.h
@@ -195,7 +195,7 @@
     /// @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 sem::Type* ty);
+    static const 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 001d38d..cb26b9f 100644
--- a/src/tint/transform/transform_test.cc
+++ b/src/tint/transform/transform_test.cc
@@ -29,7 +29,7 @@
         return SkipTransform;
     }
 
-    const ast::Type* create(std::function<sem::Type*(ProgramBuilder&)> create_sem_type) {
+    const 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));
@@ -69,8 +69,8 @@
 
 TEST_F(CreateASTTypeForTest, ArrayImplicitStride) {
     auto* arr = create([](ProgramBuilder& b) {
-        return b.create<sem::Array>(b.create<sem::F32>(), b.create<sem::ConstantArrayCount>(2u), 4u,
-                                    4u, 32u, 32u);
+        return b.create<sem::Array>(b.create<sem::F32>(), b.create<type::ConstantArrayCount>(2u),
+                                    4u, 4u, 32u, 32u);
     });
     ASSERT_TRUE(arr->Is<ast::Array>());
     ASSERT_TRUE(arr->As<ast::Array>()->type->Is<ast::F32>());
@@ -83,8 +83,8 @@
 
 TEST_F(CreateASTTypeForTest, ArrayNonImplicitStride) {
     auto* arr = create([](ProgramBuilder& b) {
-        return b.create<sem::Array>(b.create<sem::F32>(), b.create<sem::ConstantArrayCount>(2u), 4u,
-                                    4u, 64u, 32u);
+        return b.create<sem::Array>(b.create<sem::F32>(), b.create<type::ConstantArrayCount>(2u),
+                                    4u, 4u, 64u, 32u);
     });
     ASSERT_TRUE(arr->Is<ast::Array>());
     ASSERT_TRUE(arr->As<ast::Array>()->type->Is<ast::F32>());
diff --git a/src/tint/transform/vertex_pulling.cc b/src/tint/transform/vertex_pulling.cc
index e213ac6..a9b4421 100644
--- a/src/tint/transform/vertex_pulling.cc
+++ b/src/tint/transform/vertex_pulling.cc
@@ -150,7 +150,7 @@
     }
 }
 
-AttributeWGSLType WGSLTypeOf(const sem::Type* ty) {
+AttributeWGSLType WGSLTypeOf(const type::Type* ty) {
     return Switch(
         ty,
         [](const sem::I32*) -> AttributeWGSLType {
@@ -273,7 +273,7 @@
         /// A builder that builds the expression that resolves to the (transformed) input location
         std::function<const ast::Expression*()> expr;
         /// The store type of the location variable
-        const sem::Type* type;
+        const type::Type* type;
     };
 
     /// The source program
diff --git a/src/tint/transform/zero_init_workgroup_memory.cc b/src/tint/transform/zero_init_workgroup_memory.cc
index 2f3fd94..e6a3359 100644
--- a/src/tint/transform/zero_init_workgroup_memory.cc
+++ b/src/tint/transform/zero_init_workgroup_memory.cc
@@ -290,7 +290,7 @@
     /// @param ty the expression type
     /// @param get_expr a function that builds the AST nodes for the expression.
     /// @returns true on success, false on failure
-    [[nodiscard]] bool BuildZeroingStatements(const sem::Type* ty,
+    [[nodiscard]] bool BuildZeroingStatements(const type::Type* ty,
                                               const BuildZeroingExpr& get_expr) {
         if (CanTriviallyZero(ty)) {
             auto var = get_expr(1u);
@@ -438,7 +438,7 @@
     /// initialized by decomposing the initialization into multiple
     /// sub-initializations.
     /// @param ty the type to inspect
-    bool CanTriviallyZero(const sem::Type* ty) {
+    bool CanTriviallyZero(const type::Type* ty) {
         if (ty->Is<sem::Atomic>()) {
             return false;
         }
diff --git a/src/tint/type/array_count.cc b/src/tint/type/array_count.cc
new file mode 100644
index 0000000..fbf2c3d
--- /dev/null
+++ b/src/tint/type/array_count.cc
@@ -0,0 +1,51 @@
+// Copyright 2022 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/type/array_count.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::type::ArrayCount);
+TINT_INSTANTIATE_TYPEINFO(tint::type::ConstantArrayCount);
+TINT_INSTANTIATE_TYPEINFO(tint::type::RuntimeArrayCount);
+
+namespace tint::type {
+
+ArrayCount::ArrayCount() : Base() {}
+ArrayCount::~ArrayCount() = default;
+
+ConstantArrayCount::ConstantArrayCount(uint32_t val) : Base(), value(val) {}
+ConstantArrayCount::~ConstantArrayCount() = default;
+
+size_t ConstantArrayCount::Hash() const {
+    return static_cast<size_t>(TypeInfo::Of<ConstantArrayCount>().full_hashcode);
+}
+
+bool ConstantArrayCount::Equals(const ArrayCount& other) const {
+    if (auto* v = other.As<ConstantArrayCount>()) {
+        return value == v->value;
+    }
+    return false;
+}
+
+RuntimeArrayCount::RuntimeArrayCount() : Base() {}
+RuntimeArrayCount::~RuntimeArrayCount() = default;
+
+size_t RuntimeArrayCount::Hash() const {
+    return static_cast<size_t>(TypeInfo::Of<RuntimeArrayCount>().full_hashcode);
+}
+
+bool RuntimeArrayCount::Equals(const ArrayCount& other) const {
+    return other.Is<RuntimeArrayCount>();
+}
+
+}  // namespace tint::type
diff --git a/src/tint/type/array_count.h b/src/tint/type/array_count.h
new file mode 100644
index 0000000..d00aa3b
--- /dev/null
+++ b/src/tint/type/array_count.h
@@ -0,0 +1,108 @@
+// Copyright 2022 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_TYPE_ARRAY_COUNT_H_
+#define SRC_TINT_TYPE_ARRAY_COUNT_H_
+
+#include <functional>
+
+#include "src/tint/type/node.h"
+
+namespace tint::type {
+
+/// An array count
+class ArrayCount : public Castable<ArrayCount, Node> {
+  public:
+    ~ArrayCount() override;
+
+    /// @returns a hash of the array count.
+    virtual size_t Hash() const = 0;
+
+    /// @param t other array count
+    /// @returns true if this array count is equal to the given array count
+    virtual bool Equals(const ArrayCount& t) const = 0;
+
+  protected:
+    ArrayCount();
+};
+
+/// The variant of an ArrayCount when the array is a const-expression.
+/// Example:
+/// ```
+/// const N = 123;
+/// type arr = array<i32, N>
+/// ```
+class ConstantArrayCount final : public Castable<ConstantArrayCount, ArrayCount> {
+  public:
+    /// Constructor
+    /// @param val the constant-expression value
+    explicit ConstantArrayCount(uint32_t val);
+    ~ConstantArrayCount() override;
+
+    /// @returns a hash of the array count.
+    size_t Hash() const override;
+
+    /// @param t other array count
+    /// @returns true if this array count is equal to the given array count
+    bool Equals(const ArrayCount& t) const override;
+
+    /// The array count constant-expression value.
+    uint32_t value;
+};
+
+/// The variant of an ArrayCount when the array is is runtime-sized.
+/// Example:
+/// ```
+/// type arr = array<i32>
+/// ```
+class RuntimeArrayCount final : public Castable<RuntimeArrayCount, ArrayCount> {
+  public:
+    /// Constructor
+    RuntimeArrayCount();
+    ~RuntimeArrayCount() override;
+
+    /// @returns a hash of the array count.
+    size_t Hash() const override;
+
+    /// @param t other array count
+    /// @returns true if this array count is equal to the given array count
+    bool Equals(const ArrayCount& t) const override;
+};
+
+}  // namespace tint::type
+
+namespace std {
+
+/// std::hash specialization for tint::type::ArrayCount
+template <>
+struct hash<tint::type::ArrayCount> {
+    /// @param a the array count to obtain a hash from
+    /// @returns the hash of the array count
+    size_t operator()(const tint::type::ArrayCount& a) const { return a.Hash(); }
+};
+
+/// std::equal_to specialization for tint::type::ArrayCount
+template <>
+struct equal_to<tint::type::ArrayCount> {
+    /// @param a the first array count to compare
+    /// @param b the second array count to compare
+    /// @returns true if the two array counts are equal
+    bool operator()(const tint::type::ArrayCount& a, const tint::type::ArrayCount& b) const {
+        return a.Equals(b);
+    }
+};
+
+}  // namespace std
+
+#endif  // SRC_TINT_TYPE_ARRAY_COUNT_H_
diff --git a/src/tint/type/node.cc b/src/tint/type/node.cc
new file mode 100644
index 0000000..cb7e4ad
--- /dev/null
+++ b/src/tint/type/node.cc
@@ -0,0 +1,27 @@
+// Copyright 2022 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/type/node.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::type::Node);
+
+namespace tint::type {
+
+Node::Node() = default;
+
+Node::Node(const Node&) = default;
+
+Node::~Node() = default;
+
+}  // namespace tint::type
diff --git a/src/tint/type/node.h b/src/tint/type/node.h
new file mode 100644
index 0000000..eddcb27
--- /dev/null
+++ b/src/tint/type/node.h
@@ -0,0 +1,37 @@
+// Copyright 2022 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_TYPE_NODE_H_
+#define SRC_TINT_TYPE_NODE_H_
+
+#include "src/tint/castable.h"
+
+namespace tint::type {
+
+/// Node is the base class for all type nodes
+class Node : public Castable<Node> {
+  public:
+    /// Constructor
+    Node();
+
+    /// Copy constructor
+    Node(const Node&);
+
+    /// Destructor
+    ~Node() override;
+};
+
+}  // namespace tint::type
+
+#endif  // SRC_TINT_TYPE_NODE_H_
diff --git a/src/tint/type/test_helper.h b/src/tint/type/test_helper.h
new file mode 100644
index 0000000..45ff61a
--- /dev/null
+++ b/src/tint/type/test_helper.h
@@ -0,0 +1,59 @@
+// Copyright 2022 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_TYPE_TEST_HELPER_H_
+#define SRC_TINT_TYPE_TEST_HELPER_H_
+
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "src/tint/program_builder.h"
+
+namespace tint::type {
+
+/// Helper class for testing
+template <typename BASE>
+class TestHelperBase : public BASE, public ProgramBuilder {
+  public:
+    /// Builds and returns the program. Must only be called once per test
+    /// @return the built program
+    Program Build() {
+        diag::Formatter formatter;
+        [&]() {
+            ASSERT_TRUE(IsValid()) << "Builder program is not valid\n"
+                                   << formatter.format(Diagnostics());
+        }();
+        return Program(std::move(*this));
+    }
+};
+using TestHelper = TestHelperBase<testing::Test>;
+
+template <typename T>
+using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
+
+}  // namespace tint::type
+
+/// Helper macro for testing that a type was as expected
+#define EXPECT_TYPE(GOT, EXPECT)                                         \
+    do {                                                                 \
+        const type::Type* got = GOT;                                     \
+        const type::Type* expect = EXPECT;                               \
+        if (got != expect) {                                             \
+            ADD_FAILURE() << #GOT " != " #EXPECT "\n"                    \
+                          << "  " #GOT ": " << FriendlyName(got) << "\n" \
+                          << "  " #EXPECT ": " << FriendlyName(expect);  \
+        }                                                                \
+    } while (false)
+
+#endif  // SRC_TINT_TYPE_TEST_HELPER_H_
diff --git a/src/tint/sem/type.cc b/src/tint/type/type.cc
similarity index 64%
rename from src/tint/sem/type.cc
rename to src/tint/type/type.cc
index 51afc12..3f990e9 100644
--- a/src/tint/sem/type.cc
+++ b/src/tint/type/type.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/sem/type.h"
+#include "src/tint/type/type.h"
 
 #include "src/tint/sem/abstract_float.h"
 #include "src/tint/sem/abstract_int.h"
@@ -30,13 +30,13 @@
 #include "src/tint/sem/u32.h"
 #include "src/tint/sem/vector.h"
 
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Type);
+TINT_INSTANTIATE_TYPEINFO(tint::type::Type);
 
-namespace tint::sem {
+namespace tint::type {
 
 Type::Type(TypeFlags flags) : flags_(flags) {
     if (IsConstructible()) {
-        TINT_ASSERT(Semantic, HasCreationFixedFootprint());
+        TINT_ASSERT(Type, HasCreationFixedFootprint());
     }
 }
 
@@ -69,28 +69,29 @@
 }
 
 bool Type::is_scalar() const {
-    return IsAnyOf<F16, F32, U32, I32, AbstractNumeric, Bool>();
+    return IsAnyOf<sem::F16, sem::F32, sem::U32, sem::I32, sem::AbstractNumeric, sem::Bool>();
 }
 
 bool Type::is_numeric_scalar() const {
-    return IsAnyOf<F16, F32, U32, I32, AbstractNumeric>();
+    return IsAnyOf<sem::F16, sem::F32, sem::U32, sem::I32, sem::AbstractNumeric>();
 }
 
 bool Type::is_float_scalar() const {
-    return IsAnyOf<F16, F32, AbstractNumeric>();
+    return IsAnyOf<sem::F16, sem::F32, sem::AbstractNumeric>();
 }
 
 bool Type::is_float_matrix() const {
-    return Is([](const Matrix* m) { return m->type()->is_float_scalar(); });
+    return Is([](const sem::Matrix* m) { return m->type()->is_float_scalar(); });
 }
 
 bool Type::is_square_float_matrix() const {
-    return Is(
-        [](const Matrix* m) { return m->type()->is_float_scalar() && m->rows() == m->columns(); });
+    return Is([](const sem::Matrix* m) {
+        return m->type()->is_float_scalar() && m->rows() == m->columns();
+    });
 }
 
 bool Type::is_float_vector() const {
-    return Is([](const Vector* v) { return v->type()->is_float_scalar(); });
+    return Is([](const sem::Vector* v) { return v->type()->is_float_scalar(); });
 }
 
 bool Type::is_float_scalar_or_vector() const {
@@ -102,43 +103,44 @@
 }
 
 bool Type::is_integer_scalar() const {
-    return IsAnyOf<U32, I32>();
+    return IsAnyOf<sem::U32, sem::I32>();
 }
 
 bool Type::is_signed_integer_scalar() const {
-    return IsAnyOf<I32, AbstractInt>();
+    return IsAnyOf<sem::I32, sem::AbstractInt>();
 }
 
 bool Type::is_unsigned_integer_scalar() const {
-    return Is<U32>();
+    return Is<sem::U32>();
 }
 
 bool Type::is_signed_integer_vector() const {
-    return Is([](const Vector* v) { return v->type()->IsAnyOf<I32, AbstractInt>(); });
+    return Is(
+        [](const sem::Vector* v) { return v->type()->IsAnyOf<sem::I32, sem::AbstractInt>(); });
 }
 
 bool Type::is_unsigned_integer_vector() const {
-    return Is([](const Vector* v) { return v->type()->Is<U32>(); });
+    return Is([](const sem::Vector* v) { return v->type()->Is<sem::U32>(); });
 }
 
-bool Type::is_unsigned_scalar_or_vector() const {
-    return Is<U32>() || is_unsigned_integer_vector();
+bool Type::is_unsigned_integer_scalar_or_vector() const {
+    return Is<sem::U32>() || is_unsigned_integer_vector();
 }
 
-bool Type::is_signed_scalar_or_vector() const {
-    return IsAnyOf<I32, AbstractInt>() || is_signed_integer_vector();
+bool Type::is_signed_integer_scalar_or_vector() const {
+    return IsAnyOf<sem::I32, sem::AbstractInt>() || is_signed_integer_vector();
 }
 
 bool Type::is_integer_scalar_or_vector() const {
-    return is_unsigned_scalar_or_vector() || is_signed_scalar_or_vector();
+    return is_unsigned_integer_scalar_or_vector() || is_signed_integer_scalar_or_vector();
 }
 
 bool Type::is_abstract_integer_vector() const {
-    return Is([](const Vector* v) { return v->type()->Is<sem::AbstractInt>(); });
+    return Is([](const sem::Vector* v) { return v->type()->Is<sem::AbstractInt>(); });
 }
 
 bool Type::is_abstract_float_vector() const {
-    return Is([](const Vector* v) { return v->type()->Is<sem::AbstractFloat>(); });
+    return Is([](const sem::Vector* v) { return v->type()->Is<sem::AbstractFloat>(); });
 }
 
 bool Type::is_abstract_integer_scalar_or_vector() const {
@@ -150,19 +152,19 @@
 }
 
 bool Type::is_bool_vector() const {
-    return Is([](const Vector* v) { return v->type()->Is<Bool>(); });
+    return Is([](const sem::Vector* v) { return v->type()->Is<sem::Bool>(); });
 }
 
 bool Type::is_bool_scalar_or_vector() const {
-    return Is<Bool>() || is_bool_vector();
+    return Is<sem::Bool>() || is_bool_vector();
 }
 
 bool Type::is_numeric_vector() const {
-    return Is([](const Vector* v) { return v->type()->is_numeric_scalar(); });
+    return Is([](const sem::Vector* v) { return v->type()->is_numeric_scalar(); });
 }
 
 bool Type::is_scalar_vector() const {
-    return Is([](const Vector* v) { return v->type()->is_scalar(); });
+    return Is([](const sem::Vector* v) { return v->type()->is_scalar(); });
 }
 
 bool Type::is_numeric_scalar_or_vector() const {
@@ -170,17 +172,17 @@
 }
 
 bool Type::is_handle() const {
-    return IsAnyOf<Sampler, Texture>();
+    return IsAnyOf<sem::Sampler, sem::Texture>();
 }
 
 bool Type::HoldsAbstract() const {
     return Switch(
         this,  //
-        [&](const AbstractNumeric*) { return true; },
-        [&](const Vector* v) { return v->type()->HoldsAbstract(); },
-        [&](const Matrix* m) { return m->type()->HoldsAbstract(); },
-        [&](const Array* a) { return a->ElemType()->HoldsAbstract(); },
-        [&](const Struct* s) {
+        [&](const sem::AbstractNumeric*) { return true; },
+        [&](const sem::Vector* v) { return v->type()->HoldsAbstract(); },
+        [&](const sem::Matrix* m) { return m->type()->HoldsAbstract(); },
+        [&](const sem::Array* a) { return a->ElemType()->HoldsAbstract(); },
+        [&](const sem::Struct* s) {
             for (auto* m : s->Members()) {
                 if (m->Type()->HoldsAbstract()) {
                     return true;
@@ -196,33 +198,33 @@
     }
     return Switch(
         from,
-        [&](const AbstractFloat*) {
+        [&](const sem::AbstractFloat*) {
             return Switch(
-                to,                             //
-                [&](const F32*) { return 1; },  //
-                [&](const F16*) { return 2; },  //
+                to,                                  //
+                [&](const sem::F32*) { return 1; },  //
+                [&](const sem::F16*) { return 2; },  //
                 [&](Default) { return kNoConversion; });
         },
-        [&](const AbstractInt*) {
+        [&](const sem::AbstractInt*) {
             return Switch(
-                to,                                       //
-                [&](const I32*) { return 3; },            //
-                [&](const U32*) { return 4; },            //
-                [&](const AbstractFloat*) { return 5; },  //
-                [&](const F32*) { return 6; },            //
-                [&](const F16*) { return 7; },            //
+                to,                                            //
+                [&](const sem::I32*) { return 3; },            //
+                [&](const sem::U32*) { return 4; },            //
+                [&](const sem::AbstractFloat*) { return 5; },  //
+                [&](const sem::F32*) { return 6; },            //
+                [&](const sem::F16*) { return 7; },            //
                 [&](Default) { return kNoConversion; });
         },
-        [&](const Vector* from_vec) {
-            if (auto* to_vec = to->As<Vector>()) {
+        [&](const sem::Vector* from_vec) {
+            if (auto* to_vec = to->As<sem::Vector>()) {
                 if (from_vec->Width() == to_vec->Width()) {
                     return ConversionRank(from_vec->type(), to_vec->type());
                 }
             }
             return kNoConversion;
         },
-        [&](const Matrix* from_mat) {
-            if (auto* to_mat = to->As<Matrix>()) {
+        [&](const sem::Matrix* from_mat) {
+            if (auto* to_mat = to->As<sem::Matrix>()) {
                 if (from_mat->columns() == to_mat->columns() &&
                     from_mat->rows() == to_mat->rows()) {
                     return ConversionRank(from_mat->type(), to_mat->type());
@@ -230,15 +232,15 @@
             }
             return kNoConversion;
         },
-        [&](const Array* from_arr) {
-            if (auto* to_arr = to->As<Array>()) {
+        [&](const sem::Array* from_arr) {
+            if (auto* to_arr = to->As<sem::Array>()) {
                 if (from_arr->Count() == to_arr->Count()) {
                     return ConversionRank(from_arr->ElemType(), to_arr->ElemType());
                 }
             }
             return kNoConversion;
         },
-        [&](const Struct* from_str) {
+        [&](const sem::Struct* from_str) {
             auto concrete_tys = from_str->ConcreteTypes();
             for (size_t i = 0; i < concrete_tys.Length(); i++) {
                 if (concrete_tys[i] == to) {
@@ -259,21 +261,21 @@
     }
     return Switch(
         ty,  //
-        [&](const Vector* v) {
+        [&](const sem::Vector* v) {
             if (count) {
                 *count = v->Width();
             }
             return v->type();
         },
-        [&](const Matrix* m) {
+        [&](const sem::Matrix* m) {
             if (count) {
                 *count = m->columns();
             }
             return m->ColumnType();
         },
-        [&](const Array* a) {
+        [&](const sem::Array* a) {
             if (count) {
-                if (auto* const_count = a->Count()->As<ConstantArrayCount>()) {
+                if (auto* const_count = a->Count()->As<type::ConstantArrayCount>()) {
                     *count = const_count->value;
                 }
             }
@@ -301,7 +303,7 @@
     return el_ty;
 }
 
-const sem::Type* Type::Common(utils::VectorRef<const Type*> types) {
+const type::Type* Type::Common(utils::VectorRef<const Type*> types) {
     const auto count = types.Length();
     if (count == 0) {
         return nullptr;
@@ -312,10 +314,10 @@
         if (ty == common) {
             continue;  // ty == common
         }
-        if (sem::Type::ConversionRank(ty, common) != sem::Type::kNoConversion) {
+        if (type::Type::ConversionRank(ty, common) != type::Type::kNoConversion) {
             continue;  // ty can be converted to common.
         }
-        if (sem::Type::ConversionRank(common, ty) != sem::Type::kNoConversion) {
+        if (type::Type::ConversionRank(common, ty) != type::Type::kNoConversion) {
             common = ty;  // common can be converted to ty.
             continue;
         }
@@ -324,4 +326,4 @@
     return common;
 }
 
-}  // namespace tint::sem
+}  // namespace tint::type
diff --git a/src/tint/sem/type.h b/src/tint/type/type.h
similarity index 91%
rename from src/tint/sem/type.h
rename to src/tint/type/type.h
index 38f5ba8..ed8b6e7 100644
--- a/src/tint/sem/type.h
+++ b/src/tint/type/type.h
@@ -1,4 +1,4 @@
-// Copyright 2020 The Tint Authors.
+// Copyright 2022 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.
@@ -12,13 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SRC_TINT_SEM_TYPE_H_
-#define SRC_TINT_SEM_TYPE_H_
+#ifndef SRC_TINT_TYPE_TYPE_H_
+#define SRC_TINT_TYPE_TYPE_H_
 
 #include <functional>
 #include <string>
 
-#include "src/tint/sem/node.h"
+#include "src/tint/type/node.h"
 #include "src/tint/utils/enum_set.h"
 #include "src/tint/utils/vector.h"
 
@@ -28,7 +28,7 @@
 class SymbolTable;
 }  // namespace tint
 
-namespace tint::sem {
+namespace tint::type {
 
 enum TypeFlag {
     /// Type is constructable.
@@ -126,9 +126,9 @@
     /// @returns true if this type is an unsigned vector
     bool is_unsigned_integer_vector() const;
     /// @returns true if this type is an unsigned scalar or vector
-    bool is_unsigned_scalar_or_vector() const;
+    bool is_unsigned_integer_scalar_or_vector() const;
     /// @returns true if this type is a signed scalar or vector
-    bool is_signed_scalar_or_vector() const;
+    bool is_signed_integer_scalar_or_vector() const;
     /// @returns true if this type is an integer scalar or vector
     bool is_integer_scalar_or_vector() const;
     /// @returns true if this type is an abstract integer vector
@@ -192,7 +192,7 @@
     /// @returns the lowest-ranking type that all types in `types` can be implicitly converted to,
     ///          or nullptr if there is no consistent common type across all types in `types`.
     /// @see https://www.w3.org/TR/WGSL/#conversion-rank
-    static const sem::Type* Common(utils::VectorRef<const Type*> types);
+    static const type::Type* Common(utils::VectorRef<const Type*> types);
 
   protected:
     /// Constructor
@@ -203,29 +203,29 @@
     const TypeFlags flags_;
 };
 
-}  // namespace tint::sem
+}  // namespace tint::type
 
 namespace std {
 
-/// std::hash specialization for tint::sem::Type
+/// std::hash specialization for tint::type::Type
 template <>
-struct hash<tint::sem::Type> {
+struct hash<tint::type::Type> {
     /// @param type the type to obtain a hash from
-    /// @returns the hash of the semantic type
-    size_t operator()(const tint::sem::Type& type) const { return type.Hash(); }
+    /// @returns the hash of the type
+    size_t operator()(const tint::type::Type& type) const { return type.Hash(); }
 };
 
-/// std::equal_to specialization for tint::sem::Type
+/// std::equal_to specialization for tint::type::Type
 template <>
-struct equal_to<tint::sem::Type> {
+struct equal_to<tint::type::Type> {
     /// @param a the first type to compare
     /// @param b the second type to compare
     /// @returns true if the two types are equal
-    bool operator()(const tint::sem::Type& a, const tint::sem::Type& b) const {
+    bool operator()(const tint::type::Type& a, const tint::type::Type& b) const {
         return a.Equals(b);
     }
 };
 
 }  // namespace std
 
-#endif  // SRC_TINT_SEM_TYPE_H_
+#endif  // SRC_TINT_TYPE_TYPE_H_
diff --git a/src/tint/sem/type_test.cc b/src/tint/type/type_test.cc
similarity index 79%
rename from src/tint/sem/type_test.cc
rename to src/tint/type/type_test.cc
index 90e114f..d7833b3 100644
--- a/src/tint/sem/type_test.cc
+++ b/src/tint/type/type_test.cc
@@ -16,147 +16,148 @@
 #include "src/tint/sem/abstract_int.h"
 #include "src/tint/sem/f16.h"
 #include "src/tint/sem/reference.h"
-#include "src/tint/sem/test_helper.h"
+#include "src/tint/type/array_count.h"
+#include "src/tint/type/test_helper.h"
 
-namespace tint::sem {
+namespace tint::type {
 namespace {
 
 struct TypeTest : public TestHelper {
-    const sem::AbstractFloat* af = create<AbstractFloat>();
-    const sem::AbstractInt* ai = create<AbstractInt>();
-    const sem::F32* f32 = create<F32>();
-    const sem::F16* f16 = create<F16>();
-    const sem::I32* i32 = create<I32>();
-    const sem::U32* u32 = create<U32>();
-    const sem::Vector* vec2_f32 = create<Vector>(f32, 2u);
-    const sem::Vector* vec3_f32 = create<Vector>(f32, 3u);
-    const sem::Vector* vec3_f16 = create<Vector>(f16, 3u);
-    const sem::Vector* vec4_f32 = create<Vector>(f32, 4u);
-    const sem::Vector* vec3_u32 = create<Vector>(u32, 3u);
-    const sem::Vector* vec3_i32 = create<Vector>(i32, 3u);
-    const sem::Vector* vec3_af = create<Vector>(af, 3u);
-    const sem::Vector* vec3_ai = create<Vector>(ai, 3u);
-    const sem::Matrix* mat2x4_f32 = create<Matrix>(vec4_f32, 2u);
-    const sem::Matrix* mat3x4_f32 = create<Matrix>(vec4_f32, 3u);
-    const sem::Matrix* mat4x2_f32 = create<Matrix>(vec2_f32, 4u);
-    const sem::Matrix* mat4x3_f32 = create<Matrix>(vec3_f32, 4u);
-    const sem::Matrix* mat4x3_f16 = create<Matrix>(vec3_f16, 4u);
-    const sem::Matrix* mat4x3_af = create<Matrix>(vec3_af, 4u);
+    const sem::AbstractFloat* af = create<sem::AbstractFloat>();
+    const sem::AbstractInt* ai = create<sem::AbstractInt>();
+    const sem::F32* f32 = create<sem::F32>();
+    const sem::F16* f16 = create<sem::F16>();
+    const sem::I32* i32 = create<sem::I32>();
+    const sem::U32* u32 = create<sem::U32>();
+    const sem::Vector* vec2_f32 = create<sem::Vector>(f32, 2u);
+    const sem::Vector* vec3_f32 = create<sem::Vector>(f32, 3u);
+    const sem::Vector* vec3_f16 = create<sem::Vector>(f16, 3u);
+    const sem::Vector* vec4_f32 = create<sem::Vector>(f32, 4u);
+    const sem::Vector* vec3_u32 = create<sem::Vector>(u32, 3u);
+    const sem::Vector* vec3_i32 = create<sem::Vector>(i32, 3u);
+    const sem::Vector* vec3_af = create<sem::Vector>(af, 3u);
+    const sem::Vector* vec3_ai = create<sem::Vector>(ai, 3u);
+    const sem::Matrix* mat2x4_f32 = create<sem::Matrix>(vec4_f32, 2u);
+    const sem::Matrix* mat3x4_f32 = create<sem::Matrix>(vec4_f32, 3u);
+    const sem::Matrix* mat4x2_f32 = create<sem::Matrix>(vec2_f32, 4u);
+    const sem::Matrix* mat4x3_f32 = create<sem::Matrix>(vec3_f32, 4u);
+    const sem::Matrix* mat4x3_f16 = create<sem::Matrix>(vec3_f16, 4u);
+    const sem::Matrix* mat4x3_af = create<sem::Matrix>(vec3_af, 4u);
     const sem::Reference* ref_u32 =
-        create<Reference>(u32, ast::AddressSpace::kPrivate, ast::Access::kReadWrite);
-    const sem::Struct* str_f32 = create<Struct>(nullptr,
-                                                Source{},
-                                                Sym("str_f32"),
-                                                utils::Vector{
-                                                    create<StructMember>(
-                                                        /* declaration */ nullptr,
-                                                        /* source */ Source{},
-                                                        /* name */ Sym("x"),
-                                                        /* type */ f32,
-                                                        /* index */ 0u,
-                                                        /* offset */ 0u,
-                                                        /* align */ 4u,
-                                                        /* size */ 4u,
-                                                        /* location */ std::nullopt),
-                                                },
-                                                /* align*/ 4u,
-                                                /* size*/ 4u,
-                                                /* size_no_padding*/ 4u);
-    const sem::Struct* str_f16 = create<Struct>(nullptr,
-                                                Source{},
-                                                Sym("str_f16"),
-                                                utils::Vector{
-                                                    create<StructMember>(
-                                                        /* declaration */ nullptr,
-                                                        /* source */ Source{},
-                                                        /* name */ Sym("x"),
-                                                        /* type */ f16,
-                                                        /* index */ 0u,
-                                                        /* offset */ 0u,
-                                                        /* align */ 4u,
-                                                        /* size */ 4u,
-                                                        /* location */ std::nullopt),
-                                                },
-                                                /* align*/ 4u,
-                                                /* size*/ 4u,
-                                                /* size_no_padding*/ 4u);
-    sem::Struct* str_af = create<Struct>(nullptr,
-                                         Source{},
-                                         Sym("str_af"),
-                                         utils::Vector{
-                                             create<StructMember>(
-                                                 /* declaration */ nullptr,
-                                                 /* source */ Source{},
-                                                 /* name */ Sym("x"),
-                                                 /* type */ af,
-                                                 /* index */ 0u,
-                                                 /* offset */ 0u,
-                                                 /* align */ 4u,
-                                                 /* size */ 4u,
-                                                 /* location */ std::nullopt),
-                                         },
-                                         /* align*/ 4u,
-                                         /* size*/ 4u,
-                                         /* size_no_padding*/ 4u);
-    const sem::Array* arr_i32 = create<Array>(
+        create<sem::Reference>(u32, ast::AddressSpace::kPrivate, ast::Access::kReadWrite);
+    const sem::Struct* str_f32 = create<sem::Struct>(nullptr,
+                                                     Source{},
+                                                     Sym("str_f32"),
+                                                     utils::Vector{
+                                                         create<sem::StructMember>(
+                                                             /* declaration */ nullptr,
+                                                             /* source */ Source{},
+                                                             /* name */ Sym("x"),
+                                                             /* type */ f32,
+                                                             /* index */ 0u,
+                                                             /* offset */ 0u,
+                                                             /* align */ 4u,
+                                                             /* size */ 4u,
+                                                             /* location */ std::nullopt),
+                                                     },
+                                                     /* align*/ 4u,
+                                                     /* size*/ 4u,
+                                                     /* size_no_padding*/ 4u);
+    const sem::Struct* str_f16 = create<sem::Struct>(nullptr,
+                                                     Source{},
+                                                     Sym("str_f16"),
+                                                     utils::Vector{
+                                                         create<sem::StructMember>(
+                                                             /* declaration */ nullptr,
+                                                             /* source */ Source{},
+                                                             /* name */ Sym("x"),
+                                                             /* type */ f16,
+                                                             /* index */ 0u,
+                                                             /* offset */ 0u,
+                                                             /* align */ 4u,
+                                                             /* size */ 4u,
+                                                             /* location */ std::nullopt),
+                                                     },
+                                                     /* align*/ 4u,
+                                                     /* size*/ 4u,
+                                                     /* size_no_padding*/ 4u);
+    sem::Struct* str_af = create<sem::Struct>(nullptr,
+                                              Source{},
+                                              Sym("str_af"),
+                                              utils::Vector{
+                                                  create<sem::StructMember>(
+                                                      /* declaration */ nullptr,
+                                                      /* source */ Source{},
+                                                      /* name */ Sym("x"),
+                                                      /* type */ af,
+                                                      /* index */ 0u,
+                                                      /* offset */ 0u,
+                                                      /* align */ 4u,
+                                                      /* size */ 4u,
+                                                      /* location */ std::nullopt),
+                                              },
+                                              /* align*/ 4u,
+                                              /* size*/ 4u,
+                                              /* size_no_padding*/ 4u);
+    const sem::Array* arr_i32 = create<sem::Array>(
         /* element */ i32,
-        /* count */ create<ConstantArrayCount>(5u),
+        /* count */ create<type::ConstantArrayCount>(5u),
         /* align */ 4u,
         /* size */ 5u * 4u,
         /* stride */ 5u * 4u,
         /* implicit_stride */ 5u * 4u);
-    const sem::Array* arr_ai = create<Array>(
+    const sem::Array* arr_ai = create<sem::Array>(
         /* element */ ai,
-        /* count */ create<ConstantArrayCount>(5u),
+        /* count */ create<type::ConstantArrayCount>(5u),
         /* align */ 4u,
         /* size */ 5u * 4u,
         /* stride */ 5u * 4u,
         /* implicit_stride */ 5u * 4u);
-    const sem::Array* arr_vec3_i32 = create<Array>(
+    const sem::Array* arr_vec3_i32 = create<sem::Array>(
         /* element */ vec3_i32,
         /* count */ create<ConstantArrayCount>(5u),
         /* align */ 16u,
         /* size */ 5u * 16u,
         /* stride */ 5u * 16u,
         /* implicit_stride */ 5u * 16u);
-    const sem::Array* arr_vec3_ai = create<Array>(
+    const sem::Array* arr_vec3_ai = create<sem::Array>(
         /* element */ vec3_ai,
-        /* count */ create<ConstantArrayCount>(5u),
+        /* count */ create<type::ConstantArrayCount>(5u),
         /* align */ 16u,
         /* size */ 5u * 16u,
         /* stride */ 5u * 16u,
         /* implicit_stride */ 5u * 16u);
-    const sem::Array* arr_mat4x3_f16 = create<Array>(
+    const sem::Array* arr_mat4x3_f16 = create<sem::Array>(
         /* element */ mat4x3_f16,
-        /* count */ create<ConstantArrayCount>(5u),
+        /* count */ create<type::ConstantArrayCount>(5u),
         /* align */ 32u,
         /* size */ 5u * 32u,
         /* stride */ 5u * 32u,
         /* implicit_stride */ 5u * 32u);
-    const sem::Array* arr_mat4x3_f32 = create<Array>(
+    const sem::Array* arr_mat4x3_f32 = create<sem::Array>(
         /* element */ mat4x3_f32,
-        /* count */ create<ConstantArrayCount>(5u),
+        /* count */ create<type::ConstantArrayCount>(5u),
         /* align */ 64u,
         /* size */ 5u * 64u,
         /* stride */ 5u * 64u,
         /* implicit_stride */ 5u * 64u);
-    const sem::Array* arr_mat4x3_af = create<Array>(
+    const sem::Array* arr_mat4x3_af = create<sem::Array>(
         /* element */ mat4x3_af,
-        /* count */ create<ConstantArrayCount>(5u),
+        /* count */ create<type::ConstantArrayCount>(5u),
         /* align */ 64u,
         /* size */ 5u * 64u,
         /* stride */ 5u * 64u,
         /* implicit_stride */ 5u * 64u);
-    const sem::Array* arr_str_f16 = create<Array>(
+    const sem::Array* arr_str_f16 = create<sem::Array>(
         /* element */ str_f16,
-        /* count */ create<ConstantArrayCount>(5u),
+        /* count */ create<type::ConstantArrayCount>(5u),
         /* align */ 4u,
         /* size */ 5u * 4u,
         /* stride */ 5u * 4u,
         /* implicit_stride */ 5u * 4u);
-    const sem::Array* arr_str_af = create<Array>(
+    const sem::Array* arr_str_af = create<sem::Array>(
         /* element */ str_af,
-        /* count */ create<ConstantArrayCount>(5u),
+        /* count */ create<type::ConstantArrayCount>(5u),
         /* align */ 4u,
         /* size */ 5u * 4u,
         /* stride */ 5u * 4u,
@@ -613,4 +614,4 @@
 }
 
 }  // namespace
-}  // namespace tint::sem
+}  // namespace tint::type
diff --git a/src/tint/writer/append_vector.cc b/src/tint/writer/append_vector.cc
index dba28d6..c3a094a 100644
--- a/src/tint/writer/append_vector.cc
+++ b/src/tint/writer/append_vector.cc
@@ -44,7 +44,7 @@
     return {};
 }
 
-const sem::Expression* Zero(ProgramBuilder& b, const sem::Type* ty, const sem::Statement* stmt) {
+const sem::Expression* Zero(ProgramBuilder& b, const type::Type* ty, const sem::Statement* stmt) {
     const ast::Expression* expr = nullptr;
     if (ty->Is<sem::I32>()) {
         expr = b.Expr(0_i);
@@ -72,7 +72,7 @@
                               const ast::Expression* vector_ast,
                               const ast::Expression* scalar_ast) {
     uint32_t packed_size;
-    const sem::Type* packed_el_sem_ty;
+    const type::Type* packed_el_sem_ty;
     auto* vector_sem = b->Sem().Get(vector_ast);
     auto* scalar_sem = b->Sem().Get(scalar_ast);
     auto* vector_ty = vector_sem->Type()->UnwrapRef();
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 4f86d35..ec8c9b1 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -296,7 +296,7 @@
             auto* sem = builder_.Sem().Get(str);
             bool has_rt_arr = false;
             if (auto* arr = sem->Members().Back()->Type()->As<sem::Array>()) {
-                has_rt_arr = arr->Count()->Is<sem::RuntimeArrayCount>();
+                has_rt_arr = arr->Count()->Is<type::RuntimeArrayCount>();
             }
             bool is_block =
                 ast::HasAttribute<transform::AddBlockAttribute::BlockAttribute>(str->attributes);
@@ -396,13 +396,16 @@
         return EmitExpression(out, expr->expr);
     }
 
-    if (src_type->is_float_scalar_or_vector() && dst_type->is_signed_scalar_or_vector()) {
+    if (src_type->is_float_scalar_or_vector() && dst_type->is_signed_integer_scalar_or_vector()) {
         out << "floatBitsToInt";
-    } else if (src_type->is_float_scalar_or_vector() && dst_type->is_unsigned_scalar_or_vector()) {
+    } else if (src_type->is_float_scalar_or_vector() &&
+               dst_type->is_unsigned_integer_scalar_or_vector()) {
         out << "floatBitsToUint";
-    } else if (src_type->is_signed_scalar_or_vector() && dst_type->is_float_scalar_or_vector()) {
+    } else if (src_type->is_signed_integer_scalar_or_vector() &&
+               dst_type->is_float_scalar_or_vector()) {
         out << "intBitsToFloat";
-    } else if (src_type->is_unsigned_scalar_or_vector() && dst_type->is_float_scalar_or_vector()) {
+    } else if (src_type->is_unsigned_integer_scalar_or_vector() &&
+               dst_type->is_float_scalar_or_vector()) {
         out << "uintBitsToFloat";
     } else {
         if (!EmitType(out, dst_type, ast::AddressSpace::kNone, ast::Access::kReadWrite, "")) {
@@ -823,7 +826,7 @@
         return EmitEmulatedFMA(out, expr);
     }
     if (builtin->Type() == sem::BuiltinType::kAbs &&
-        TypeOf(expr->args[0])->UnwrapRef()->is_unsigned_scalar_or_vector()) {
+        TypeOf(expr->args[0])->UnwrapRef()->is_unsigned_integer_scalar_or_vector()) {
         // GLSL does not support abs() on unsigned arguments. However, it's a no-op.
         return EmitExpression(out, expr->args[0]);
     }
@@ -1276,7 +1279,7 @@
 bool GeneratorImpl::EmitDegreesCall(std::ostream& out,
                                     const ast::CallExpression* expr,
                                     const sem::Builtin* builtin) {
-    auto* return_elem_type = sem::Type::DeepestElementOf(builtin->ReturnType());
+    auto* return_elem_type = type::Type::DeepestElementOf(builtin->ReturnType());
     const std::string suffix = Is<sem::F16>(return_elem_type) ? "hf" : "f";
     return CallBuiltinHelper(out, expr, builtin,
                              [&](TextBuffer* b, const std::vector<std::string>& params) {
@@ -1289,7 +1292,7 @@
 bool GeneratorImpl::EmitRadiansCall(std::ostream& out,
                                     const ast::CallExpression* expr,
                                     const sem::Builtin* builtin) {
-    auto* return_elem_type = sem::Type::DeepestElementOf(builtin->ReturnType());
+    auto* return_elem_type = type::Type::DeepestElementOf(builtin->ReturnType());
     const std::string suffix = Is<sem::F16>(return_elem_type) ? "hf" : "f";
     return CallBuiltinHelper(out, expr, builtin,
                              [&](TextBuffer* b, const std::vector<std::string>& params) {
@@ -1379,9 +1382,9 @@
 
     auto* texture_type = TypeOf(texture)->UnwrapRef()->As<sem::Texture>();
 
-    auto emit_signed_int_type = [&](const sem::Type* ty) {
+    auto emit_signed_int_type = [&](const type::Type* ty) {
         uint32_t width = 0;
-        sem::Type::ElementOf(ty, &width);
+        type::Type::ElementOf(ty, &width);
         if (width > 1) {
             out << "ivec" << width;
         } else {
@@ -1389,9 +1392,9 @@
         }
     };
 
-    auto emit_unsigned_int_type = [&](const sem::Type* ty) {
+    auto emit_unsigned_int_type = [&](const type::Type* ty) {
         uint32_t width = 0;
-        sem::Type::ElementOf(ty, &width);
+        type::Type::ElementOf(ty, &width);
         if (width > 1) {
             out << "uvec" << width;
         } else {
@@ -1401,7 +1404,7 @@
 
     auto emit_expr_as_signed = [&](const ast::Expression* e) {
         auto* ty = TypeOf(e)->UnwrapRef();
-        if (!ty->is_unsigned_scalar_or_vector()) {
+        if (!ty->is_unsigned_integer_scalar_or_vector()) {
             return EmitExpression(out, e);
         }
         emit_signed_int_type(ty);
@@ -2432,7 +2435,7 @@
         });
 }
 
-bool GeneratorImpl::EmitZeroValue(std::ostream& out, const sem::Type* type) {
+bool GeneratorImpl::EmitZeroValue(std::ostream& out, const type::Type* type) {
     if (type->Is<sem::Bool>()) {
         out << "false";
     } else if (type->Is<sem::F32>()) {
@@ -2808,7 +2811,7 @@
 }
 
 bool GeneratorImpl::EmitType(std::ostream& out,
-                             const sem::Type* type,
+                             const type::Type* type,
                              ast::AddressSpace address_space,
                              ast::Access access,
                              const std::string& name,
@@ -2835,10 +2838,10 @@
     }
 
     if (auto* ary = type->As<sem::Array>()) {
-        const sem::Type* base_type = ary;
+        const type::Type* base_type = ary;
         std::vector<uint32_t> sizes;
         while (auto* arr = base_type->As<sem::Array>()) {
-            if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
+            if (arr->Count()->Is<type::RuntimeArrayCount>()) {
                 sizes.push_back(0);
             } else {
                 auto count = arr->ConstantCount();
@@ -2989,7 +2992,7 @@
 }
 
 bool GeneratorImpl::EmitTypeAndName(std::ostream& out,
-                                    const sem::Type* type,
+                                    const type::Type* type,
                                     ast::AddressSpace address_space,
                                     ast::Access access,
                                     const std::string& name) {
@@ -3202,7 +3205,7 @@
     return true;
 }
 
-sem::Type* GeneratorImpl::BoolTypeToUint(const sem::Type* type) {
+type::Type* GeneratorImpl::BoolTypeToUint(const type::Type* type) {
     auto* u32 = builder_.create<sem::U32>();
     if (type->Is<sem::Bool>()) {
         return u32;
diff --git a/src/tint/writer/glsl/generator_impl.h b/src/tint/writer/glsl/generator_impl.h
index 24d8357..1491f8f 100644
--- a/src/tint/writer/glsl/generator_impl.h
+++ b/src/tint/writer/glsl/generator_impl.h
@@ -413,7 +413,7 @@
     /// then the boolean is set to true.
     /// @returns true if the type is emitted
     bool EmitType(std::ostream& out,
-                  const sem::Type* type,
+                  const type::Type* type,
                   ast::AddressSpace address_space,
                   ast::Access access,
                   const std::string& name,
@@ -426,7 +426,7 @@
     /// @param name the name to emit
     /// @returns true if the type is emitted
     bool EmitTypeAndName(std::ostream& out,
-                         const sem::Type* type,
+                         const type::Type* type,
                          ast::AddressSpace address_space,
                          ast::Access access,
                          const std::string& name);
@@ -450,7 +450,7 @@
     /// @param out the output stream
     /// @param type the type to emit the value for
     /// @returns true if the zero value was successfully emitted.
-    bool EmitZeroValue(std::ostream& out, const sem::Type* type);
+    bool EmitZeroValue(std::ostream& out, const type::Type* type);
     /// Handles generating a 'var' declaration
     /// @param var the variable to generate
     /// @returns true if the variable was emitted
@@ -472,10 +472,10 @@
     /// @param stage pipeline stage in which this builtin is used
     /// @returns the string name of the builtin or blank on error
     const char* builtin_to_string(ast::BuiltinValue builtin, ast::PipelineStage stage);
-    /// Converts a builtin to a sem::Type appropriate for GLSL.
+    /// Converts a builtin to a type::Type appropriate for GLSL.
     /// @param builtin the builtin to convert
     /// @returns the appropriate semantic type or null on error.
-    sem::Type* builtin_type(ast::BuiltinValue builtin);
+    type::Type* builtin_type(ast::BuiltinValue builtin);
 
   private:
     enum class VarType { kIn, kOut };
@@ -487,7 +487,7 @@
 
     /// The map key for two semantic types.
     using BinaryOperandType =
-        utils::UnorderedKeyWrapper<std::tuple<const sem::Type*, const sem::Type*>>;
+        utils::UnorderedKeyWrapper<std::tuple<const type::Type*, const type::Type*>>;
 
     /// CallBuiltinHelper will call the builtin helper function, creating it
     /// if it hasn't been built already. If the builtin needs to be built then
@@ -511,7 +511,7 @@
     /// Create a uint type corresponding to the given bool or bool vector type.
     /// @param type the bool or bool vector type to convert
     /// @returns the corresponding uint type
-    sem::Type* BoolTypeToUint(const sem::Type* type);
+    type::Type* BoolTypeToUint(const type::Type* type);
 
     TextBuffer helpers_;  // Helper functions emitted at the top of the output
     std::function<bool()> emit_continuing_;
diff --git a/src/tint/writer/glsl/generator_impl_loop_test.cc b/src/tint/writer/glsl/generator_impl_loop_test.cc
index 639107b..53e26f6 100644
--- a/src/tint/writer/glsl/generator_impl_loop_test.cc
+++ b/src/tint/writer/glsl/generator_impl_loop_test.cc
@@ -212,14 +212,15 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInit) {
-    // for(var b = true && false; ; ) {
+    // let t = true;
+    // for(var b = t && false; ; ) {
     //   return;
     // }
 
-    auto* multi_stmt =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
+    auto* t = Let("t", Expr(true));
+    auto* multi_stmt = LogicalAnd(t, false);
     auto* f = For(Decl(Var("b", multi_stmt)), nullptr, nullptr, Block(Return()));
-    WrapInFunction(f);
+    WrapInFunction(t, f);
 
     GeneratorImpl& gen = Build();
 
@@ -227,7 +228,7 @@
 
     ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
     EXPECT_EQ(gen.result(), R"(  {
-    bool tint_tmp = true;
+    bool tint_tmp = t;
     if (tint_tmp) {
       tint_tmp = false;
     }
@@ -263,16 +264,16 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCond) {
-    // for(; true && false; ) {
+    // let t = true;
+    // for(; t && false; ) {
     //   return;
     // }
 
     Func("a_statement", {}, ty.void_(), {});
-
-    auto* multi_stmt =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
+    auto* t = Let("t", Expr(true));
+    auto* multi_stmt = LogicalAnd(t, false);
     auto* f = For(nullptr, multi_stmt, nullptr, Block(CallStmt(Call("a_statement"))));
-    WrapInFunction(f);
+    WrapInFunction(t, f);
 
     GeneratorImpl& gen = Build();
 
@@ -281,7 +282,7 @@
     ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
     EXPECT_EQ(gen.result(), R"(  {
     while (true) {
-      bool tint_tmp = true;
+      bool tint_tmp = t;
       if (tint_tmp) {
         tint_tmp = false;
       }
@@ -316,16 +317,17 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCont) {
-    // for(; ; i = true && false) {
+    // let t = true;
+    // for(; ; i = t && false) {
     //   return;
     // }
 
-    auto* multi_stmt =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
+    auto* t = Let("t", Expr(true));
+    auto* multi_stmt = LogicalAnd(t, false);
     auto* v = Decl(Var("i", ty.bool_()));
     auto* f = For(nullptr, nullptr, Assign("i", multi_stmt),  //
                   Block(Return()));
-    WrapInFunction(v, f);
+    WrapInFunction(t, v, f);
 
     GeneratorImpl& gen = Build();
 
@@ -335,7 +337,7 @@
     EXPECT_EQ(gen.result(), R"(  {
     while (true) {
       return;
-      bool tint_tmp = true;
+      bool tint_tmp = t;
       if (tint_tmp) {
         tint_tmp = false;
       }
@@ -367,20 +369,19 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInitCondCont) {
-    // for(var i = true && false; true && false; i = true && false) {
+    // let t = true;
+    // for(var i = t && false; t && false; i = t && false) {
     //   return;
     // }
 
-    auto* multi_stmt_a =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
-    auto* multi_stmt_b =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
-    auto* multi_stmt_c =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
+    auto* t = Let("t", Expr(true));
+    auto* multi_stmt_a = LogicalAnd(t, false);
+    auto* multi_stmt_b = LogicalAnd(t, false);
+    auto* multi_stmt_c = LogicalAnd(t, false);
 
     auto* f = For(Decl(Var("i", multi_stmt_a)), multi_stmt_b, Assign("i", multi_stmt_c),  //
                   Block(Return()));
-    WrapInFunction(f);
+    WrapInFunction(t, f);
 
     GeneratorImpl& gen = Build();
 
@@ -388,19 +389,19 @@
 
     ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
     EXPECT_EQ(gen.result(), R"(  {
-    bool tint_tmp = true;
+    bool tint_tmp = t;
     if (tint_tmp) {
       tint_tmp = false;
     }
     bool i = (tint_tmp);
     while (true) {
-      bool tint_tmp_1 = true;
+      bool tint_tmp_1 = t;
       if (tint_tmp_1) {
         tint_tmp_1 = false;
       }
       if (!((tint_tmp_1))) { break; }
       return;
-      bool tint_tmp_2 = true;
+      bool tint_tmp_2 = t;
       if (tint_tmp_2) {
         tint_tmp_2 = false;
       }
@@ -449,16 +450,17 @@
 }
 
 TEST_F(GlslGeneratorImplTest_Loop, Emit_WhileWithMultiStmtCond) {
-    // while(true && false) {
+    // let t = true;
+    // while(t && false) {
     //   return;
     // }
 
     Func("a_statement", {}, ty.void_(), {});
 
-    auto* multi_stmt =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
+    auto* t = Let("t", Expr(true));
+    auto* multi_stmt = LogicalAnd(t, false);
     auto* f = While(multi_stmt, Block(CallStmt(Call("a_statement"))));
-    WrapInFunction(f);
+    WrapInFunction(t, f);
 
     GeneratorImpl& gen = Build();
 
@@ -466,7 +468,7 @@
 
     ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
     EXPECT_EQ(gen.result(), R"(  while (true) {
-    bool tint_tmp = true;
+    bool tint_tmp = t;
     if (tint_tmp) {
       tint_tmp = false;
     }
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index 944481c..131f9b1 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -987,7 +987,7 @@
     // crbug.com/tint/1550
     if (type == sem::BuiltinType::kCountOneBits || type == sem::BuiltinType::kReverseBits) {
         auto* arg = call->Arguments()[0];
-        if (arg->Type()->UnwrapRef()->is_signed_scalar_or_vector()) {
+        if (arg->Type()->UnwrapRef()->is_signed_integer_scalar_or_vector()) {
             out << "asint(" << name << "(asuint(";
             if (!EmitExpression(out, arg->Declaration())) {
                 return false;
@@ -2021,7 +2021,7 @@
             }
 
             std::string member_type;
-            if (Is<sem::F16>(sem::Type::DeepestElementOf(ty))) {
+            if (Is<sem::F16>(type::Type::DeepestElementOf(ty))) {
                 member_type = width.empty() ? "float16_t" : ("vector<float16_t, " + width + ">");
             } else {
                 member_type = "float" + width;
@@ -3456,7 +3456,7 @@
         });
 }
 
-bool GeneratorImpl::EmitValue(std::ostream& out, const sem::Type* type, int value) {
+bool GeneratorImpl::EmitValue(std::ostream& out, const type::Type* type, int value) {
     return Switch(
         type,
         [&](const sem::Bool*) {
@@ -3527,7 +3527,7 @@
         });
 }
 
-bool GeneratorImpl::EmitZeroValue(std::ostream& out, const sem::Type* type) {
+bool GeneratorImpl::EmitZeroValue(std::ostream& out, const type::Type* type) {
     return EmitValue(out, type, 0);
 }
 
@@ -3891,7 +3891,7 @@
 }
 
 bool GeneratorImpl::EmitType(std::ostream& out,
-                             const sem::Type* type,
+                             const type::Type* type,
                              ast::AddressSpace address_space,
                              ast::Access access,
                              const std::string& name,
@@ -3921,10 +3921,10 @@
     return Switch(
         type,
         [&](const sem::Array* ary) {
-            const sem::Type* base_type = ary;
+            const type::Type* base_type = ary;
             std::vector<uint32_t> sizes;
             while (auto* arr = base_type->As<sem::Array>()) {
-                if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
+                if (arr->Count()->Is<type::RuntimeArrayCount>()) {
                     TINT_ICE(Writer, diagnostics_)
                         << "runtime arrays may only exist in storage buffers, which should have "
                            "been transformed into a ByteAddressBuffer";
@@ -4118,7 +4118,7 @@
 }
 
 bool GeneratorImpl::EmitTypeAndName(std::ostream& out,
-                                    const sem::Type* type,
+                                    const type::Type* type,
                                     ast::AddressSpace address_space,
                                     ast::Access access,
                                     const std::string& name) {
diff --git a/src/tint/writer/hlsl/generator_impl.h b/src/tint/writer/hlsl/generator_impl.h
index 74a172d..1258b00 100644
--- a/src/tint/writer/hlsl/generator_impl.h
+++ b/src/tint/writer/hlsl/generator_impl.h
@@ -407,7 +407,7 @@
     /// then the boolean is set to true.
     /// @returns true if the type is emitted
     bool EmitType(std::ostream& out,
-                  const sem::Type* type,
+                  const type::Type* type,
                   ast::AddressSpace address_space,
                   ast::Access access,
                   const std::string& name,
@@ -420,7 +420,7 @@
     /// @param name the name to emit
     /// @returns true if the type is emitted
     bool EmitTypeAndName(std::ostream& out,
-                         const sem::Type* type,
+                         const type::Type* type,
                          ast::AddressSpace address_space,
                          ast::Access access,
                          const std::string& name);
@@ -440,12 +440,12 @@
     /// @param type the type to emit the value for
     /// @param value the value to emit
     /// @returns true if the value was successfully emitted.
-    bool EmitValue(std::ostream& out, const sem::Type* type, int value);
+    bool EmitValue(std::ostream& out, const type::Type* type, int value);
     /// Emits the zero value for the given type
     /// @param out the output stream
     /// @param type the type to emit the value for
     /// @returns true if the zero value was successfully emitted.
-    bool EmitZeroValue(std::ostream& out, const sem::Type* type);
+    bool EmitZeroValue(std::ostream& out, const type::Type* type);
     /// Handles generating a 'var' declaration
     /// @param var the variable to generate
     /// @returns true if the variable was emitted
@@ -547,7 +547,7 @@
     std::unordered_map<const sem::Vector*, std::string> dynamic_vector_write_;
     std::unordered_map<const sem::Matrix*, std::string> dynamic_matrix_vector_write_;
     std::unordered_map<const sem::Matrix*, std::string> dynamic_matrix_scalar_write_;
-    std::unordered_map<const sem::Type*, std::string> value_or_one_if_zero_;
+    std::unordered_map<const type::Type*, std::string> value_or_one_if_zero_;
     std::unordered_set<const sem::Struct*> emitted_structs_;
 };
 
diff --git a/src/tint/writer/hlsl/generator_impl_loop_test.cc b/src/tint/writer/hlsl/generator_impl_loop_test.cc
index 9d2a4de..4249f1e 100644
--- a/src/tint/writer/hlsl/generator_impl_loop_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_loop_test.cc
@@ -211,14 +211,15 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInit) {
-    // for(var b = true && false; ; ) {
+    // let t = true;
+    // for(var b = t && false; ; ) {
     //   return;
     // }
 
-    auto* multi_stmt =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
+    auto* t = Let("t", Expr(true));
+    auto* multi_stmt = LogicalAnd(t, false);
     auto* f = For(Decl(Var("b", multi_stmt)), nullptr, nullptr, Block(Return()));
-    WrapInFunction(f);
+    WrapInFunction(t, f);
 
     GeneratorImpl& gen = Build();
 
@@ -226,7 +227,7 @@
 
     ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
     EXPECT_EQ(gen.result(), R"(  {
-    bool tint_tmp = true;
+    bool tint_tmp = t;
     if (tint_tmp) {
       tint_tmp = false;
     }
@@ -260,14 +261,16 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCond) {
-    // for(; true && false; ) {
+    // let t = true;
+    // for(; t && false; ) {
     //   return;
     // }
 
-    auto* multi_stmt =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
-    auto* f = For(nullptr, multi_stmt, nullptr, Block(Return()));
-    WrapInFunction(f);
+    Func("a_statement", {}, ty.void_(), {});
+    auto* t = Let("t", Expr(true));
+    auto* multi_stmt = LogicalAnd(t, false);
+    auto* f = For(nullptr, multi_stmt, nullptr, Block(CallStmt(Call("a_statement"))));
+    WrapInFunction(t, f);
 
     GeneratorImpl& gen = Build();
 
@@ -276,12 +279,12 @@
     ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
     EXPECT_EQ(gen.result(), R"(  {
     while (true) {
-      bool tint_tmp = true;
+      bool tint_tmp = t;
       if (tint_tmp) {
         tint_tmp = false;
       }
       if (!((tint_tmp))) { break; }
-      return;
+      a_statement();
     }
   }
 )");
@@ -310,15 +313,17 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCont) {
-    // for(; ; i = true && false) {
+    // let t = true;
+    // for(; ; i = t && false) {
     //   return;
     // }
 
-    auto* multi_stmt =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
+    auto* t = Let("t", Expr(true));
+    auto* multi_stmt = LogicalAnd(t, false);
     auto* v = Decl(Var("i", ty.bool_()));
-    auto* f = For(nullptr, nullptr, Assign("i", multi_stmt), Block(Return()));
-    WrapInFunction(v, f);
+    auto* f = For(nullptr, nullptr, Assign("i", multi_stmt),  //
+                  Block(Return()));
+    WrapInFunction(t, v, f);
 
     GeneratorImpl& gen = Build();
 
@@ -328,7 +333,7 @@
     EXPECT_EQ(gen.result(), R"(  {
     while (true) {
       return;
-      bool tint_tmp = true;
+      bool tint_tmp = t;
       if (tint_tmp) {
         tint_tmp = false;
       }
@@ -360,20 +365,19 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtInitCondCont) {
-    // for(var i = true && false; true && false; i = true && false) {
+    // let t = true;
+    // for(var i = t && false; t && false; i = t && false) {
     //   return;
     // }
 
-    auto* multi_stmt_a =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
-    auto* multi_stmt_b =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
-    auto* multi_stmt_c =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
+    auto* t = Let("t", Expr(true));
+    auto* multi_stmt_a = LogicalAnd(t, false);
+    auto* multi_stmt_b = LogicalAnd(t, false);
+    auto* multi_stmt_c = LogicalAnd(t, false);
 
-    auto* f =
-        For(Decl(Var("i", multi_stmt_a)), multi_stmt_b, Assign("i", multi_stmt_c), Block(Return()));
-    WrapInFunction(f);
+    auto* f = For(Decl(Var("i", multi_stmt_a)), multi_stmt_b, Assign("i", multi_stmt_c),  //
+                  Block(Return()));
+    WrapInFunction(t, f);
 
     GeneratorImpl& gen = Build();
 
@@ -381,19 +385,19 @@
 
     ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
     EXPECT_EQ(gen.result(), R"(  {
-    bool tint_tmp = true;
+    bool tint_tmp = t;
     if (tint_tmp) {
       tint_tmp = false;
     }
     bool i = (tint_tmp);
     while (true) {
-      bool tint_tmp_1 = true;
+      bool tint_tmp_1 = t;
       if (tint_tmp_1) {
         tint_tmp_1 = false;
       }
       if (!((tint_tmp_1))) { break; }
       return;
-      bool tint_tmp_2 = true;
+      bool tint_tmp_2 = t;
       if (tint_tmp_2) {
         tint_tmp_2 = false;
       }
@@ -442,14 +446,17 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Loop, Emit_WhileWithMultiStmtCond) {
-    // while(true && false) {
+    // let t = true;
+    // while(t && false) {
     //   return;
     // }
 
-    auto* multi_stmt =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
-    auto* f = While(multi_stmt, Block(Return()));
-    WrapInFunction(f);
+    Func("a_statement", {}, ty.void_(), {});
+
+    auto* t = Let("t", Expr(true));
+    auto* multi_stmt = LogicalAnd(t, false);
+    auto* f = While(multi_stmt, Block(CallStmt(Call("a_statement"))));
+    WrapInFunction(t, f);
 
     GeneratorImpl& gen = Build();
 
@@ -457,12 +464,12 @@
 
     ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
     EXPECT_EQ(gen.result(), R"(  while (true) {
-    bool tint_tmp = true;
+    bool tint_tmp = t;
     if (tint_tmp) {
       tint_tmp = false;
     }
     if (!((tint_tmp))) { break; }
-    return;
+    a_statement();
   }
 )");
 }
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 1056026..6687694 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -132,8 +132,8 @@
   public:
     ScopedBitCast(GeneratorImpl* generator,
                   std::ostream& stream,
-                  const sem::Type* curr_type,
-                  const sem::Type* target_type)
+                  const type::Type* curr_type,
+                  const type::Type* target_type)
         : s(stream) {
         auto* target_vec_type = target_type->As<sem::Vector>();
 
@@ -181,6 +181,7 @@
         polyfills.first_trailing_bit = true;
         polyfills.insert_bits = transform::BuiltinPolyfill::Level::kClampParameters;
         polyfills.int_div_mod = true;
+        polyfills.sign_int = true;
         polyfills.texture_sample_base_clamp_to_edge_2d_f32 = true;
         data.Add<transform::BuiltinPolyfill::Config>(polyfills);
         manager.Add<transform::BuiltinPolyfill>();
@@ -347,7 +348,7 @@
     return true;
 }
 
-bool GeneratorImpl::EmitTypeDecl(const sem::Type* ty) {
+bool GeneratorImpl::EmitTypeDecl(const type::Type* ty) {
     if (auto* str = ty->As<sem::Struct>()) {
         if (!EmitStructType(current_buffer_, str)) {
             return false;
@@ -491,7 +492,7 @@
         return true;
     };
 
-    auto signed_type_of = [&](const sem::Type* ty) -> const sem::Type* {
+    auto signed_type_of = [&](const type::Type* ty) -> const type::Type* {
         if (ty->is_integer_scalar()) {
             return builder_.create<sem::I32>();
         } else if (auto* v = ty->As<sem::Vector>()) {
@@ -500,7 +501,7 @@
         return {};
     };
 
-    auto unsigned_type_of = [&](const sem::Type* ty) -> const sem::Type* {
+    auto unsigned_type_of = [&](const type::Type* ty) -> const type::Type* {
         if (ty->is_integer_scalar()) {
             return builder_.create<sem::U32>();
         } else if (auto* v = ty->As<sem::Vector>()) {
@@ -528,7 +529,8 @@
 
     // Handle +/-/* of signed values
     if ((expr->IsAdd() || expr->IsSubtract() || expr->IsMultiply()) &&
-        lhs_type->is_signed_scalar_or_vector() && rhs_type->is_signed_scalar_or_vector()) {
+        lhs_type->is_signed_integer_scalar_or_vector() &&
+        rhs_type->is_signed_integer_scalar_or_vector()) {
         // If lhs or rhs is a vector, use that type (support implicit scalar to
         // vector promotion)
         auto* target_type = lhs_type->Is<sem::Vector>()
@@ -561,7 +563,7 @@
     // TODO(crbug.com/tint/1077): This may not be necessary. The MSL spec
     // seems to imply that left shifting a signed value is treated the same as
     // left shifting an unsigned value, but we need to make sure.
-    if (expr->IsShiftLeft() && lhs_type->is_signed_scalar_or_vector()) {
+    if (expr->IsShiftLeft() && lhs_type->is_signed_integer_scalar_or_vector()) {
         // Shift left: discards top bits, so convert first operand to unsigned
         // first, then convert result back to signed
         ScopedBitCast outer_int_cast(this, out, lhs_type, signed_type_of(lhs_type));
@@ -1607,7 +1609,7 @@
     return true;
 }
 
-bool GeneratorImpl::EmitZeroValue(std::ostream& out, const sem::Type* type) {
+bool GeneratorImpl::EmitZeroValue(std::ostream& out, const type::Type* type) {
     return Switch(
         type,
         [&](const sem::Bool*) {
@@ -2512,7 +2514,7 @@
 }
 
 bool GeneratorImpl::EmitType(std::ostream& out,
-                             const sem::Type* type,
+                             const type::Type* type,
                              const std::string& name,
                              bool* name_printed /* = nullptr */) {
     if (name_printed) {
@@ -2540,7 +2542,7 @@
                 return false;
             }
             out << ", ";
-            if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
+            if (arr->Count()->Is<type::RuntimeArrayCount>()) {
                 out << "1";
             } else {
                 auto count = arr->ConstantCount();
@@ -2717,7 +2719,7 @@
 }
 
 bool GeneratorImpl::EmitTypeAndName(std::ostream& out,
-                                    const sem::Type* type,
+                                    const type::Type* type,
                                     const std::string& name) {
     bool name_printed = false;
     if (!EmitType(out, type, name, &name_printed)) {
@@ -2918,7 +2920,7 @@
     // Handle `-e` when `e` is signed, so that we ensure that if `e` is the
     // largest negative value, it returns `e`.
     auto* expr_type = TypeOf(expr->expr)->UnwrapRef();
-    if (expr->op == ast::UnaryOp::kNegation && expr_type->is_signed_scalar_or_vector()) {
+    if (expr->op == ast::UnaryOp::kNegation && expr_type->is_signed_integer_scalar_or_vector()) {
         auto fn = utils::GetOrCreate(unary_minus_funcs_, expr_type, [&]() -> std::string {
             // e.g.:
             // int tint_unary_minus(const int v) {
@@ -3077,7 +3079,7 @@
     return true;
 }
 
-GeneratorImpl::SizeAndAlign GeneratorImpl::MslPackedTypeSizeAndAlign(const sem::Type* ty) {
+GeneratorImpl::SizeAndAlign GeneratorImpl::MslPackedTypeSizeAndAlign(const type::Type* ty) {
     return Switch(
         ty,
 
@@ -3168,7 +3170,7 @@
                     << "arrays with explicit strides should not exist past the SPIR-V reader";
                 return SizeAndAlign{};
             }
-            if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
+            if (arr->Count()->Is<type::RuntimeArrayCount>()) {
                 return SizeAndAlign{arr->Stride(), arr->Align()};
             }
             if (auto count = arr->ConstantCount()) {
diff --git a/src/tint/writer/msl/generator_impl.h b/src/tint/writer/msl/generator_impl.h
index 829088d..d2eea40 100644
--- a/src/tint/writer/msl/generator_impl.h
+++ b/src/tint/writer/msl/generator_impl.h
@@ -101,7 +101,7 @@
     /// Handles generating a declared type
     /// @param ty the declared type to generate
     /// @returns true if the declared type was emitted
-    bool EmitTypeDecl(const sem::Type* ty);
+    bool EmitTypeDecl(const type::Type* ty);
     /// Handles an index accessor expression
     /// @param out the output of the expression stream
     /// @param expr the expression to emit
@@ -314,7 +314,7 @@
     /// @param name_printed (optional) if not nullptr and an array was printed
     /// @returns true if the type is emitted
     bool EmitType(std::ostream& out,
-                  const sem::Type* type,
+                  const type::Type* type,
                   const std::string& name,
                   bool* name_printed = nullptr);
     /// Handles generating type and name
@@ -322,7 +322,7 @@
     /// @param type the type to generate
     /// @param name the name to emit
     /// @returns true if the type is emitted
-    bool EmitTypeAndName(std::ostream& out, const sem::Type* type, const std::string& name);
+    bool EmitTypeAndName(std::ostream& out, const type::Type* type, const std::string& name);
     /// Handles generating a address space
     /// @param out the output of the type stream
     /// @param sc the address space to generate
@@ -351,7 +351,7 @@
     /// @param out the output of the expression stream
     /// @param type the type to emit the value for
     /// @returns true if the zero value was successfully emitted.
-    bool EmitZeroValue(std::ostream& out, const sem::Type* type);
+    bool EmitZeroValue(std::ostream& out, const type::Type* type);
 
     /// Handles generating a builtin name
     /// @param builtin the semantic info for the builtin
@@ -404,7 +404,7 @@
 
     /// @returns the MSL packed type size and alignment in bytes for the given
     /// type.
-    SizeAndAlign MslPackedTypeSizeAndAlign(const sem::Type* ty);
+    SizeAndAlign MslPackedTypeSizeAndAlign(const type::Type* ty);
 
     std::function<bool()> emit_continuing_;
 
@@ -431,7 +431,7 @@
     std::unordered_map<std::string, std::vector<uint32_t>> workgroup_allocations_;
 
     std::unordered_map<const sem::Builtin*, std::string> builtins_;
-    std::unordered_map<const sem::Type*, std::string> unary_minus_funcs_;
+    std::unordered_map<const type::Type*, std::string> unary_minus_funcs_;
     std::unordered_map<uint32_t, std::string> int_dot_funcs_;
     std::unordered_set<const sem::Struct*> emitted_structs_;
 };
diff --git a/src/tint/writer/msl/generator_impl_loop_test.cc b/src/tint/writer/msl/generator_impl_loop_test.cc
index 89009e0..8926f2a 100644
--- a/src/tint/writer/msl/generator_impl_loop_test.cc
+++ b/src/tint/writer/msl/generator_impl_loop_test.cc
@@ -419,17 +419,18 @@
     //   return;
     // }
 
-    auto* multi_stmt =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
+    auto* t = Let("t", Expr(true));
+    auto* multi_stmt = LogicalAnd(t, false);
+    // create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(t), Expr(false));
     auto* f = While(multi_stmt, Block(Return()));
-    WrapInFunction(f);
+    WrapInFunction(t, f);
 
     GeneratorImpl& gen = Build();
 
     gen.increment_indent();
 
     ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-    EXPECT_EQ(gen.result(), R"(  while((true && false)) {
+    EXPECT_EQ(gen.result(), R"(  while((t && false)) {
     return;
   }
 )");
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index b5910ff..5869849 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -89,7 +89,7 @@
 /// one or more levels of an arrays inside of `type`.
 /// @param type the given type, which must not be null
 /// @returns the nested matrix type, or nullptr if none
-const sem::Matrix* GetNestedMatrixType(const sem::Type* type) {
+const sem::Matrix* GetNestedMatrixType(const type::Type* type) {
     while (auto* arr = type->As<sem::Array>()) {
         type = arr->ElemType();
     }
@@ -117,7 +117,7 @@
         case BuiltinType::kClamp:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 return GLSLstd450NClamp;
-            } else if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
+            } else if (builtin->ReturnType()->is_unsigned_integer_scalar_or_vector()) {
                 return GLSLstd450UClamp;
             } else {
                 return GLSLstd450SClamp;
@@ -161,7 +161,7 @@
         case BuiltinType::kMax:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 return GLSLstd450NMax;
-            } else if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
+            } else if (builtin->ReturnType()->is_unsigned_integer_scalar_or_vector()) {
                 return GLSLstd450UMax;
             } else {
                 return GLSLstd450SMax;
@@ -169,7 +169,7 @@
         case BuiltinType::kMin:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 return GLSLstd450NMin;
-            } else if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
+            } else if (builtin->ReturnType()->is_unsigned_integer_scalar_or_vector()) {
                 return GLSLstd450UMin;
             } else {
                 return GLSLstd450SMin;
@@ -201,7 +201,11 @@
         case BuiltinType::kRound:
             return GLSLstd450RoundEven;
         case BuiltinType::kSign:
-            return GLSLstd450FSign;
+            if (builtin->ReturnType()->is_signed_integer_scalar_or_vector()) {
+                return GLSLstd450SSign;
+            } else {
+                return GLSLstd450FSign;
+            }
         case BuiltinType::kSin:
             return GLSLstd450Sin;
         case BuiltinType::kSinh:
@@ -235,7 +239,7 @@
 }
 
 /// @return the vector element type if ty is a vector, otherwise return ty.
-const sem::Type* ElementTypeOf(const sem::Type* ty) {
+const type::Type* ElementTypeOf(const type::Type* ty) {
     if (auto* v = ty->As<sem::Vector>()) {
         return v->type();
     }
@@ -1153,7 +1157,7 @@
     return 0;
 }
 
-uint32_t Builder::GenerateLoadIfNeeded(const sem::Type* type, uint32_t id) {
+uint32_t Builder::GenerateLoadIfNeeded(const type::Type* type, uint32_t id) {
     if (auto* ref = type->As<sem::Reference>()) {
         type = ref->StoreType();
     } else {
@@ -1442,7 +1446,7 @@
     });
 }
 
-uint32_t Builder::GenerateCastOrCopyOrPassthrough(const sem::Type* to_type,
+uint32_t Builder::GenerateCastOrCopyOrPassthrough(const type::Type* to_type,
                                                   const ast::Expression* from_expr,
                                                   bool is_global_init) {
     // This should not happen as we rely on constant folding to obviate
@@ -1454,7 +1458,7 @@
         return 0;
     }
 
-    auto elem_type_of = [](const sem::Type* t) -> const sem::Type* {
+    auto elem_type_of = [](const type::Type* t) -> const type::Type* {
         if (t->is_scalar()) {
             return t;
         }
@@ -1784,7 +1788,7 @@
     return result_id;
 }
 
-uint32_t Builder::GenerateConstantNullIfNeeded(const sem::Type* type) {
+uint32_t Builder::GenerateConstantNullIfNeeded(const type::Type* type) {
     auto type_id = GenerateTypeIfNeeded(type);
     if (type_id == 0) {
         return 0;
@@ -1894,7 +1898,7 @@
     return result_id;
 }
 
-uint32_t Builder::GenerateSplat(uint32_t scalar_id, const sem::Type* vec_type) {
+uint32_t Builder::GenerateSplat(uint32_t scalar_id, const type::Type* vec_type) {
     // Create a new vector to splat scalar into
     auto splat_vector = result_op();
     auto* splat_vector_type = builder_.create<sem::Pointer>(vec_type, ast::AddressSpace::kFunction,
@@ -2052,7 +2056,7 @@
     bool lhs_is_float_or_vec = lhs_type->is_float_scalar_or_vector();
     bool lhs_is_bool_or_vec = lhs_type->is_bool_scalar_or_vector();
     bool lhs_is_integer_or_vec = lhs_type->is_integer_scalar_or_vector();
-    bool lhs_is_unsigned = lhs_type->is_unsigned_scalar_or_vector();
+    bool lhs_is_unsigned = lhs_type->is_unsigned_integer_scalar_or_vector();
 
     spv::Op op = spv::Op::OpNop;
     if (expr->IsAnd()) {
@@ -2187,7 +2191,7 @@
         }
     } else if (expr->IsShiftLeft()) {
         op = spv::Op::OpShiftLeftLogical;
-    } else if (expr->IsShiftRight() && lhs_type->is_signed_scalar_or_vector()) {
+    } else if (expr->IsShiftRight() && lhs_type->is_signed_integer_scalar_or_vector()) {
         // A shift right with a signed LHS is an arithmetic shift.
         op = spv::Op::OpShiftRightArithmetic;
     } else if (expr->IsShiftRight()) {
@@ -2458,7 +2462,7 @@
             op = spv::Op::OpDPdyFine;
             break;
         case BuiltinType::kExtractBits:
-            op = builtin->Parameters()[0]->Type()->is_unsigned_scalar_or_vector()
+            op = builtin->Parameters()[0]->Type()->is_unsigned_integer_scalar_or_vector()
                      ? spv::Op::OpBitFieldUExtract
                      : spv::Op::OpBitFieldSExtract;
             break;
@@ -2543,7 +2547,7 @@
             op = spv::Op::OpTranspose;
             break;
         case BuiltinType::kAbs:
-            if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
+            if (builtin->ReturnType()->is_unsigned_integer_scalar_or_vector()) {
                 // abs() only operates on *signed* integers.
                 // This is a no-op for unsigned integers.
                 return get_arg_as_value_id(0);
@@ -3269,7 +3273,7 @@
     }
 }
 
-uint32_t Builder::GenerateSampledImage(const sem::Type* texture_type,
+uint32_t Builder::GenerateSampledImage(const type::Type* texture_type,
                                        Operand texture_operand,
                                        Operand sampler_operand) {
     // DepthTexture is always declared as SampledTexture.
@@ -3625,7 +3629,7 @@
     return GenerateFunctionVariable(stmt->variable);
 }
 
-uint32_t Builder::GenerateTypeIfNeeded(const sem::Type* type) {
+uint32_t Builder::GenerateTypeIfNeeded(const type::Type* type) {
     if (type == nullptr) {
         error_ = "attempting to generate type from null type";
         return 0;
@@ -3839,7 +3843,7 @@
     }
 
     auto result_id = std::get<uint32_t>(result);
-    if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
+    if (arr->Count()->Is<type::RuntimeArrayCount>()) {
         push_type(spv::Op::OpTypeRuntimeArray, {result, Operand(elem_type)});
     } else {
         auto count = arr->ConstantCount();
diff --git a/src/tint/writer/spirv/builder.h b/src/tint/writer/spirv/builder.h
index 1e412fa..91fb628 100644
--- a/src/tint/writer/spirv/builder.h
+++ b/src/tint/writer/spirv/builder.h
@@ -67,7 +67,7 @@
         uint32_t source_id;
         /// The type of the current chain source. This type matches the deduced
         /// result_type of the current source defined above.
-        const sem::Type* source_type;
+        const type::Type* source_type;
         /// A list of access chain indices to emit. Note, we _only_ have access
         /// chain indices if the source is reference.
         std::vector<uint32_t> access_chain_indices;
@@ -286,7 +286,7 @@
     /// @param type the type to generate for
     /// @param struct_id the struct id
     /// @param member_idx the member index
-    void GenerateMemberAccessIfNeeded(const sem::Type* type,
+    void GenerateMemberAccessIfNeeded(const type::Type* type,
                                       uint32_t struct_id,
                                       uint32_t member_idx);
     /// Generates a function variable
@@ -402,7 +402,7 @@
     /// @param texture_operand the texture operand
     /// @param sampler_operand the sampler operand
     /// @returns the expression ID
-    uint32_t GenerateSampledImage(const sem::Type* texture_type,
+    uint32_t GenerateSampledImage(const type::Type* texture_type,
                                   Operand texture_operand,
                                   Operand sampler_operand);
     /// Generates a cast or object copy for the expression result,
@@ -412,7 +412,7 @@
     /// @param from_expr the expression to cast
     /// @param is_global_init if this is a global initializer
     /// @returns the expression ID on success or 0 otherwise
-    uint32_t GenerateCastOrCopyOrPassthrough(const sem::Type* to_type,
+    uint32_t GenerateCastOrCopyOrPassthrough(const type::Type* to_type,
                                              const ast::Expression* from_expr,
                                              bool is_global_init);
     /// Generates a loop statement
@@ -458,7 +458,7 @@
     /// @param type the type of the expression
     /// @param id the SPIR-V id of the experssion
     /// @returns the ID of the loaded value or `id` if type is not a reference
-    uint32_t GenerateLoadIfNeeded(const sem::Type* type, uint32_t id);
+    uint32_t GenerateLoadIfNeeded(const type::Type* type, uint32_t id);
     /// Generates an OpStore. Emits an error and returns false if we're
     /// currently outside a function.
     /// @param to the ID to store too
@@ -468,7 +468,7 @@
     /// Generates a type if not already created
     /// @param type the type to create
     /// @returns the ID to use for the given type. Returns 0 on unknown type.
-    uint32_t GenerateTypeIfNeeded(const sem::Type* type);
+    uint32_t GenerateTypeIfNeeded(const type::Type* type);
     /// Generates a texture type declaration
     /// @param texture the texture to generate
     /// @param result the result operand
@@ -522,7 +522,7 @@
     /// @param scalar_id scalar to splat
     /// @param vec_type type of vector
     /// @returns id of the new vector
-    uint32_t GenerateSplat(uint32_t scalar_id, const sem::Type* vec_type);
+    uint32_t GenerateSplat(uint32_t scalar_id, const type::Type* vec_type);
 
     /// Generates instructions to add or subtract two matrices
     /// @param lhs_id id of multiplicand
@@ -552,7 +552,7 @@
 
     /// @returns the resolved type of the ast::Expression `expr`
     /// @param expr the expression
-    const sem::Type* TypeOf(const ast::Expression* expr) const { return builder_.TypeOf(expr); }
+    const type::Type* TypeOf(const ast::Expression* expr) const { return builder_.TypeOf(expr); }
 
     /// Generates a constant value if needed
     /// @param constant the constant to generate.
@@ -567,7 +567,7 @@
     /// Generates a constant-null of the given type, if needed
     /// @param type the type of the constant null to generate.
     /// @returns the ID on success or 0 on failure
-    uint32_t GenerateConstantNullIfNeeded(const sem::Type* type);
+    uint32_t GenerateConstantNullIfNeeded(const type::Type* type);
 
     /// Generates a vector constant splat if needed
     /// @param type the type of the vector to generate
@@ -619,11 +619,11 @@
     std::unordered_map<std::string, uint32_t> import_name_to_id_;
     std::unordered_map<Symbol, uint32_t> func_symbol_to_id_;
     std::unordered_map<sem::CallTargetSignature, uint32_t> func_sig_to_id_;
-    std::unordered_map<const sem::Type*, uint32_t> type_to_id_;
+    std::unordered_map<const type::Type*, uint32_t> type_to_id_;
     std::unordered_map<ScalarConstant, uint32_t> const_to_id_;
-    std::unordered_map<const sem::Type*, uint32_t> const_null_to_id_;
+    std::unordered_map<const type::Type*, uint32_t> const_null_to_id_;
     std::unordered_map<uint64_t, uint32_t> const_splat_to_id_;
-    std::unordered_map<const sem::Type*, uint32_t> texture_type_to_sampled_image_type_id_;
+    std::unordered_map<const type::Type*, uint32_t> texture_type_to_sampled_image_type_id_;
     std::vector<Scope> scope_stack_;
     std::vector<uint32_t> merge_stack_;
     std::vector<uint32_t> continue_stack_;
diff --git a/src/tint/writer/spirv/builder_binary_expression_test.cc b/src/tint/writer/spirv/builder_binary_expression_test.cc
index e7237fa..f0a5639 100644
--- a/src/tint/writer/spirv/builder_binary_expression_test.cc
+++ b/src/tint/writer/spirv/builder_binary_expression_test.cc
@@ -1071,38 +1071,43 @@
     //    a || (b && c)
     // From: crbug.com/tint/355
 
+    auto* t = Let("t", Expr(true));
+    auto* f = Let("f", Expr(false));
+
     auto* logical_and_expr =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
+        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(t), Expr(f));
 
     auto* expr =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr(true), logical_and_expr);
+        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr(t), logical_and_expr);
 
-    WrapInFunction(expr);
+    WrapInFunction(t, f, expr);
 
     spirv::Builder& b = Build();
 
     b.push_function(Function{});
+    ASSERT_TRUE(b.GenerateFunctionVariable(t)) << b.error();
+    ASSERT_TRUE(b.GenerateFunctionVariable(f)) << b.error();
     b.GenerateLabel(b.next_id());
 
     EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%3 = OpConstantTrue %2
-%8 = OpConstantNull %2
+    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+%2 = OpConstantTrue %1
+%3 = OpConstantNull %1
 )");
     EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%1 = OpLabel
-OpSelectionMerge %4 None
-OpBranchConditional %3 %4 %5
-%5 = OpLabel
-OpSelectionMerge %6 None
-OpBranchConditional %3 %7 %6
-%7 = OpLabel
-OpBranch %6
+              R"(%4 = OpLabel
+OpSelectionMerge %5 None
+OpBranchConditional %2 %5 %6
 %6 = OpLabel
-%9 = OpPhi %2 %3 %5 %8 %7
-OpBranch %4
-%4 = OpLabel
-%10 = OpPhi %2 %3 %1 %9 %6
+OpSelectionMerge %7 None
+OpBranchConditional %2 %8 %7
+%8 = OpLabel
+OpBranch %7
+%7 = OpLabel
+%9 = OpPhi %1 %2 %6 %3 %8
+OpBranch %5
+%5 = OpLabel
+%10 = OpPhi %1 %2 %4 %9 %7
 )");
 }
 
@@ -1111,38 +1116,43 @@
     //    a && (b || c)
     // From: crbug.com/tint/355
 
+    auto* t = Let("t", Expr(true));
+    auto* f = Let("f", Expr(false));
+
     auto* logical_or_expr =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr(true), Expr(false));
+        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, Expr(t), Expr(f));
 
     auto* expr =
-        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(true), logical_or_expr);
+        create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, Expr(t), logical_or_expr);
 
-    WrapInFunction(expr);
+    WrapInFunction(t, f, expr);
 
     spirv::Builder& b = Build();
 
     b.push_function(Function{});
+    ASSERT_TRUE(b.GenerateFunctionVariable(t)) << b.error();
+    ASSERT_TRUE(b.GenerateFunctionVariable(f)) << b.error();
     b.GenerateLabel(b.next_id());
 
     EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
-%3 = OpConstantTrue %2
-%8 = OpConstantNull %2
+    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+%2 = OpConstantTrue %1
+%3 = OpConstantNull %1
 )");
     EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-              R"(%1 = OpLabel
-OpSelectionMerge %4 None
-OpBranchConditional %3 %5 %4
-%5 = OpLabel
-OpSelectionMerge %6 None
-OpBranchConditional %3 %6 %7
-%7 = OpLabel
-OpBranch %6
+              R"(%4 = OpLabel
+OpSelectionMerge %5 None
+OpBranchConditional %2 %6 %5
 %6 = OpLabel
-%9 = OpPhi %2 %3 %5 %8 %7
-OpBranch %4
-%4 = OpLabel
-%10 = OpPhi %2 %3 %1 %9 %6
+OpSelectionMerge %7 None
+OpBranchConditional %2 %7 %8
+%8 = OpLabel
+OpBranch %7
+%7 = OpLabel
+%9 = OpPhi %1 %2 %6 %3 %8
+OpBranch %5
+%5 = OpLabel
+%10 = OpPhi %1 %2 %4 %9 %7
 )");
 }
 
diff --git a/src/tint/writer/text_generator.h b/src/tint/writer/text_generator.h
index 2f4b578..248809a 100644
--- a/src/tint/writer/text_generator.h
+++ b/src/tint/writer/text_generator.h
@@ -194,15 +194,15 @@
 
     /// @returns the resolved type of the ast::Expression `expr`
     /// @param expr the expression
-    const sem::Type* TypeOf(const ast::Expression* expr) const { return builder_.TypeOf(expr); }
+    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 sem::Type* TypeOf(const ast::Type* type) const { return builder_.TypeOf(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 sem::Type* TypeOf(const ast::TypeDecl* type_decl) const {
+    const type::Type* TypeOf(const ast::TypeDecl* type_decl) const {
         return builder_.TypeOf(type_decl);
     }