Add ability to clone types.

This CL adds a `Clone` method into the type base classes. This allows
the IR to clone types provided by the program into the IR context.

Bug: tint:1718
Change-Id: Ieebf011dcf40bedc98bf5acebd3888acfde863bc
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/116362
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 2167eaf..c424b59 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -563,6 +563,7 @@
     "type/array_count.h",
     "type/atomic.h",
     "type/bool.h",
+    "type/clone_context.h",
     "type/depth_multisampled_texture.h",
     "type/depth_texture.h",
     "type/external_texture.h",
@@ -715,6 +716,7 @@
     "type/atomic.h",
     "type/bool.cc",
     "type/bool.h",
+    "type/clone_context.h",
     "type/depth_multisampled_texture.cc",
     "type/depth_multisampled_texture.h",
     "type/depth_texture.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index b30227a..7abc023 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -476,6 +476,7 @@
   type/atomic.h
   type/bool.cc
   type/bool.h
+  type/clone_context.h
   type/depth_multisampled_texture.cc
   type/depth_multisampled_texture.h
   type/depth_texture.cc
@@ -951,7 +952,7 @@
     traits_test.cc
     transform/transform_test.cc
     type/array_test.cc
-    type/atomic.cc
+    type/atomic_test.cc
     type/bool_test.cc
     type/depth_multisampled_texture_test.cc
     type/depth_texture_test.cc
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 517e09b..9006130 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -83,7 +83,9 @@
 
 }  // namespace
 
-BuilderImpl::BuilderImpl(const Program* program) : builder(program) {}
+BuilderImpl::BuilderImpl(const Program* program)
+    : builder(program),
+      type_clone_ctx_{{&program->Symbols()}, {&builder.ir.symbols, &builder.ir.types}} {}
 
 BuilderImpl::~BuilderImpl() = default;
 
@@ -565,61 +567,63 @@
     }
 
     auto* sem = builder.ir.program->Sem().Get(expr);
+    auto* ty = sem->Type()->Clone(type_clone_ctx_);
+
     Binary* instr = nullptr;
     switch (expr->op) {
         case ast::BinaryOp::kAnd:
-            instr = builder.And(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.And(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kOr:
-            instr = builder.Or(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.Or(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kXor:
-            instr = builder.Xor(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.Xor(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kLogicalAnd:
-            instr = builder.LogicalAnd(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.LogicalAnd(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kLogicalOr:
-            instr = builder.LogicalOr(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.LogicalOr(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kEqual:
-            instr = builder.Equal(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.Equal(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kNotEqual:
-            instr = builder.NotEqual(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.NotEqual(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kLessThan:
-            instr = builder.LessThan(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.LessThan(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kGreaterThan:
-            instr = builder.GreaterThan(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.GreaterThan(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kLessThanEqual:
-            instr = builder.LessThanEqual(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.LessThanEqual(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kGreaterThanEqual:
-            instr = builder.GreaterThanEqual(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.GreaterThanEqual(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kShiftLeft:
-            instr = builder.ShiftLeft(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.ShiftLeft(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kShiftRight:
-            instr = builder.ShiftRight(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.ShiftRight(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kAdd:
-            instr = builder.Add(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.Add(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kSubtract:
-            instr = builder.Subtract(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.Subtract(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kMultiply:
-            instr = builder.Multiply(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.Multiply(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kDivide:
-            instr = builder.Divide(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.Divide(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kModulo:
-            instr = builder.Modulo(sem->Type(), lhs.Get(), rhs.Get());
+            instr = builder.Modulo(ty, lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kNone:
             TINT_ICE(IR, diagnostics_) << "missing binary operand type";
@@ -637,7 +641,8 @@
     }
 
     auto* sem = builder.ir.program->Sem().Get(expr);
-    auto* instr = builder.Bitcast(sem->Type(), val.Get());
+    auto* ty = sem->Type()->Clone(type_clone_ctx_);
+    auto* instr = builder.Bitcast(ty, val.Get());
 
     current_flow_block->instructions.Push(instr);
     return instr->Result();
diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h
index 5d6fbd7..1227b10 100644
--- a/src/tint/ir/builder_impl.h
+++ b/src/tint/ir/builder_impl.h
@@ -212,6 +212,8 @@
     /// Map from ast nodes to flow nodes, used to retrieve the flow node for a given AST node.
     /// Used for testing purposes.
     std::unordered_map<const ast::Node*, const FlowNode*> ast_to_flow_;
+
+    type::CloneContext type_clone_ctx_;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/module.h b/src/tint/ir/module.h
index 1ed9ea9..5e7a9dd 100644
--- a/src/tint/ir/module.h
+++ b/src/tint/ir/module.h
@@ -21,6 +21,8 @@
 #include "src/tint/ir/function.h"
 #include "src/tint/ir/instruction.h"
 #include "src/tint/ir/value.h"
+#include "src/tint/program_id.h"
+#include "src/tint/symbol_table.h"
 #include "src/tint/type/manager.h"
 #include "src/tint/utils/block_allocator.h"
 #include "src/tint/utils/result.h"
@@ -68,6 +70,11 @@
     ///  (Note, this will probably turn into a utils::Result, just stubbing for now)
     const Program* ToProgram() const;
 
+  private:
+    /// Program Id required to create other components
+    ProgramID prog_id_;
+
+  public:
     /// The flow node allocator
     utils::BlockAllocator<FlowNode> flow_nodes;
     /// The constant allocator
@@ -87,6 +94,9 @@
 
     /// The type manager for the module
     type::Manager types;
+
+    /// The symbol table for the module
+    SymbolTable symbols{prog_id_};
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/resolver/intrinsic_table.cc b/src/tint/resolver/intrinsic_table.cc
index 0b1a6d8..e49be48 100644
--- a/src/tint/resolver/intrinsic_table.cc
+++ b/src/tint/resolver/intrinsic_table.cc
@@ -63,6 +63,7 @@
     // Stub implementations for type::Type conformance.
     bool Equals(const type::UniqueNode&) const override { return false; }
     std::string FriendlyName(const SymbolTable&) const override { return "<any>"; }
+    type::Type* Clone(type::CloneContext&) const override { return nullptr; }
 };
 
 /// Number is an 32 bit unsigned integer, which can be in one of three states:
diff --git a/src/tint/sem/array_count.cc b/src/tint/sem/array_count.cc
index a324ea5..4fee970 100644
--- a/src/tint/sem/array_count.cc
+++ b/src/tint/sem/array_count.cc
@@ -35,6 +35,11 @@
     return symbols.NameFor(variable->Declaration()->symbol);
 }
 
+type::ArrayCount* NamedOverrideArrayCount::Clone(type::CloneContext&) const {
+    TINT_ASSERT(Type, false && "Named override array count clone not available");
+    return nullptr;
+}
+
 UnnamedOverrideArrayCount::UnnamedOverrideArrayCount(const Expression* e)
     : Base(static_cast<size_t>(TypeInfo::Of<UnnamedOverrideArrayCount>().full_hashcode)), expr(e) {}
 UnnamedOverrideArrayCount::~UnnamedOverrideArrayCount() = default;
@@ -50,4 +55,9 @@
     return "[unnamed override-expression]";
 }
 
+type::ArrayCount* UnnamedOverrideArrayCount::Clone(type::CloneContext&) const {
+    TINT_ASSERT(Type, false && "Unnamed override array count clone not available");
+    return nullptr;
+}
+
 }  // namespace tint::sem
diff --git a/src/tint/sem/array_count.h b/src/tint/sem/array_count.h
index fd441e8..2d3c27f 100644
--- a/src/tint/sem/array_count.h
+++ b/src/tint/sem/array_count.h
@@ -44,6 +44,10 @@
     /// @returns the friendly name for this array count
     std::string FriendlyName(const SymbolTable& symbols) const override;
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    type::ArrayCount* Clone(type::CloneContext& ctx) const override;
+
     /// The `override` variable.
     const GlobalVariable* variable;
 };
@@ -70,6 +74,10 @@
     /// @returns the friendly name for this array count
     std::string FriendlyName(const SymbolTable& symbols) const override;
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    type::ArrayCount* Clone(type::CloneContext& ctx) const override;
+
     /// The unnamed override expression.
     /// Note: Each AST expression gets a unique semantic expression node, so two equivalent AST
     /// expressions will not result in the same `expr` pointer. This property is important to ensure
diff --git a/src/tint/symbol_table.h b/src/tint/symbol_table.h
index 617266f..4881c2d 100644
--- a/src/tint/symbol_table.h
+++ b/src/tint/symbol_table.h
@@ -52,7 +52,7 @@
 
     /// Returns the symbol for the given `name`
     /// @param name the name to lookup
-    /// @returns the symbol for the name or symbol::kUndefined if not found.
+    /// @returns the symbol for the name or Symbol() if not found.
     Symbol Get(const std::string& name) const;
 
     /// Returns the name for the given symbol
diff --git a/src/tint/type/abstract_float.cc b/src/tint/type/abstract_float.cc
index 443bb21..f3032e0 100644
--- a/src/tint/type/abstract_float.cc
+++ b/src/tint/type/abstract_float.cc
@@ -32,4 +32,8 @@
     return "abstract-float";
 }
 
+AbstractFloat* AbstractFloat::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<AbstractFloat>();
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/abstract_float.h b/src/tint/type/abstract_float.h
index 9a7d4a8..8ec0882 100644
--- a/src/tint/type/abstract_float.h
+++ b/src/tint/type/abstract_float.h
@@ -38,6 +38,10 @@
     /// @param symbols the program's symbol table
     /// @returns the name for this type when printed in diagnostics.
     std::string FriendlyName(const SymbolTable& symbols) const override;
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    AbstractFloat* Clone(CloneContext& ctx) const override;
 };
 
 }  // namespace tint::type
diff --git a/src/tint/type/abstract_int.cc b/src/tint/type/abstract_int.cc
index 990d4e6..806cdd3 100644
--- a/src/tint/type/abstract_int.cc
+++ b/src/tint/type/abstract_int.cc
@@ -33,4 +33,8 @@
     return "abstract-int";
 }
 
+AbstractInt* AbstractInt::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<AbstractInt>();
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/abstract_int.h b/src/tint/type/abstract_int.h
index 2df6328..7c96c8f 100644
--- a/src/tint/type/abstract_int.h
+++ b/src/tint/type/abstract_int.h
@@ -38,6 +38,10 @@
     /// @param symbols the program's symbol table
     /// @returns the name for this type when printed in diagnostics.
     std::string FriendlyName(const SymbolTable& symbols) const override;
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    AbstractInt* Clone(CloneContext& ctx) const override;
 };
 
 }  // namespace tint::type
diff --git a/src/tint/type/array.cc b/src/tint/type/array.cc
index 6df6cdc..6e1d297 100644
--- a/src/tint/type/array.cc
+++ b/src/tint/type/array.cc
@@ -19,6 +19,7 @@
 #include "src/tint/ast/variable.h"
 #include "src/tint/debug.h"
 #include "src/tint/symbol_table.h"
+#include "src/tint/type/manager.h"
 #include "src/tint/utils/hash.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::Array);
@@ -103,4 +104,11 @@
     return size_;
 }
 
+Array* Array::Clone(CloneContext& ctx) const {
+    auto* elem_ty = element_->Clone(ctx);
+    auto* count = count_->Clone(ctx);
+
+    return ctx.dst.mgr->Get<Array>(elem_ty, count, align_, size_, stride_, implicit_stride_);
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/array.h b/src/tint/type/array.h
index 18882a4..95ed164 100644
--- a/src/tint/type/array.h
+++ b/src/tint/type/array.h
@@ -98,6 +98,10 @@
     /// declared in WGSL.
     std::string FriendlyName(const SymbolTable& symbols) const override;
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    Array* Clone(CloneContext& ctx) const override;
+
   private:
     Type const* const element_;
     const ArrayCount* count_;
diff --git a/src/tint/type/array_count.cc b/src/tint/type/array_count.cc
index bebe896..e50db29 100644
--- a/src/tint/type/array_count.cc
+++ b/src/tint/type/array_count.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/type/array_count.h"
 
+#include "src/tint/type/manager.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::type::ArrayCount);
 TINT_INSTANTIATE_TYPEINFO(tint::type::ConstantArrayCount);
 TINT_INSTANTIATE_TYPEINFO(tint::type::RuntimeArrayCount);
@@ -38,6 +40,10 @@
     return std::to_string(value);
 }
 
+ConstantArrayCount* ConstantArrayCount::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<ConstantArrayCount>(value);
+}
+
 RuntimeArrayCount::RuntimeArrayCount()
     : Base(static_cast<size_t>(TypeInfo::Of<RuntimeArrayCount>().full_hashcode)) {}
 RuntimeArrayCount::~RuntimeArrayCount() = default;
@@ -50,4 +56,8 @@
     return "";
 }
 
+RuntimeArrayCount* RuntimeArrayCount::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<RuntimeArrayCount>();
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/array_count.h b/src/tint/type/array_count.h
index aa2c182..90fc253 100644
--- a/src/tint/type/array_count.h
+++ b/src/tint/type/array_count.h
@@ -19,6 +19,7 @@
 #include <string>
 
 #include "src/tint/symbol_table.h"
+#include "src/tint/type/clone_context.h"
 #include "src/tint/type/unique_node.h"
 
 namespace tint::type {
@@ -32,6 +33,10 @@
     /// @returns the friendly name for this array count
     virtual std::string FriendlyName(const SymbolTable& symbols) const = 0;
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    virtual ArrayCount* Clone(CloneContext& ctx) const = 0;
+
   protected:
     /// Constructor
     /// @param hash the unique hash of the node
@@ -59,6 +64,10 @@
     /// @returns the friendly name for this array count
     std::string FriendlyName(const SymbolTable& symbols) const override;
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    ConstantArrayCount* Clone(CloneContext& ctx) const override;
+
     /// The array count constant-expression value.
     uint32_t value;
 };
@@ -81,6 +90,10 @@
     /// @param symbols the symbol table
     /// @returns the friendly name for this array count
     std::string FriendlyName(const SymbolTable& symbols) const override;
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    RuntimeArrayCount* Clone(CloneContext& ctx) const override;
 };
 
 }  // namespace tint::type
diff --git a/src/tint/type/array_test.cc b/src/tint/type/array_test.cc
index 089b12e..8f80a63 100644
--- a/src/tint/type/array_test.cc
+++ b/src/tint/type/array_test.cc
@@ -164,5 +164,41 @@
     EXPECT_FALSE(runtime_sized->HasFixedFootprint());
 }
 
+TEST_F(ArrayTest, CloneSizedArray) {
+    auto* ary = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* val = ary->Clone(ctx);
+
+    ASSERT_NE(val, nullptr);
+    EXPECT_TRUE(val->ElemType()->Is<U32>());
+    EXPECT_TRUE(val->Count()->Is<ConstantArrayCount>());
+    EXPECT_EQ(val->Count()->As<ConstantArrayCount>()->value, 2u);
+    EXPECT_EQ(val->Align(), 4u);
+    EXPECT_EQ(val->Size(), 8u);
+    EXPECT_EQ(val->Stride(), 32u);
+    EXPECT_EQ(val->ImplicitStride(), 16u);
+    EXPECT_FALSE(val->IsStrideImplicit());
+}
+
+TEST_F(ArrayTest, CloneRuntimeArray) {
+    auto* ary = create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 32u, 32u);
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* val = ary->Clone(ctx);
+    ASSERT_NE(val, nullptr);
+    EXPECT_TRUE(val->ElemType()->Is<U32>());
+    EXPECT_TRUE(val->Count()->Is<RuntimeArrayCount>());
+    EXPECT_EQ(val->Align(), 4u);
+    EXPECT_EQ(val->Size(), 8u);
+    EXPECT_EQ(val->Stride(), 32u);
+    EXPECT_EQ(val->ImplicitStride(), 32u);
+    EXPECT_TRUE(val->IsStrideImplicit());
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/atomic.cc b/src/tint/type/atomic.cc
index 91efcd4..0daa7f5 100644
--- a/src/tint/type/atomic.cc
+++ b/src/tint/type/atomic.cc
@@ -55,4 +55,9 @@
 
 Atomic::~Atomic() = default;
 
+Atomic* Atomic::Clone(CloneContext& ctx) const {
+    auto* ty = subtype_->Clone(ctx);
+    return ctx.dst.mgr->Get<Atomic>(ty);
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/atomic.h b/src/tint/type/atomic.h
index 31d1c5b..9b3d4fc 100644
--- a/src/tint/type/atomic.h
+++ b/src/tint/type/atomic.h
@@ -49,6 +49,10 @@
     /// @returns the alignment in bytes of the type.
     uint32_t Align() const override;
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    Atomic* Clone(CloneContext& ctx) const override;
+
   private:
     type::Type const* const subtype_;
 };
diff --git a/src/tint/type/atomic_test.cc b/src/tint/type/atomic_test.cc
index ed0bdec..e7126cb 100644
--- a/src/tint/type/atomic_test.cc
+++ b/src/tint/type/atomic_test.cc
@@ -50,5 +50,15 @@
     EXPECT_EQ(a->FriendlyName(Symbols()), "atomic<i32>");
 }
 
+TEST_F(AtomicTest, Clone) {
+    auto* atomic = create<Atomic>(create<I32>());
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* val = atomic->Clone(ctx);
+    EXPECT_TRUE(val->Type()->Is<I32>());
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/bool.cc b/src/tint/type/bool.cc
index 89c55a1..4c3849c 100644
--- a/src/tint/type/bool.cc
+++ b/src/tint/type/bool.cc
@@ -46,4 +46,8 @@
     return 4;
 }
 
+Bool* Bool::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<Bool>();
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/bool.h b/src/tint/type/bool.h
index a33a352..d73341c 100644
--- a/src/tint/type/bool.h
+++ b/src/tint/type/bool.h
@@ -54,6 +54,10 @@
     /// @note: booleans are not host-sharable, but still may exist in workgroup
     /// storage.
     uint32_t Align() const override;
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    Bool* Clone(CloneContext& ctx) const override;
 };
 
 }  // namespace tint::type
diff --git a/src/tint/type/bool_test.cc b/src/tint/type/bool_test.cc
index d9b9d7a..b061927 100644
--- a/src/tint/type/bool_test.cc
+++ b/src/tint/type/bool_test.cc
@@ -44,5 +44,14 @@
     EXPECT_EQ(b.FriendlyName(Symbols()), "bool");
 }
 
+TEST_F(BoolTest, Clone) {
+    auto* a = create<Bool>();
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* b = a->Clone(ctx);
+    ASSERT_TRUE(b->Is<Bool>());
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/clone_context.h b/src/tint/type/clone_context.h
new file mode 100644
index 0000000..55ed44d
--- /dev/null
+++ b/src/tint/type/clone_context.h
@@ -0,0 +1,47 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TYPE_CLONE_CONTEXT_H_
+#define SRC_TINT_TYPE_CLONE_CONTEXT_H_
+
+// Forward Declarations
+namespace tint {
+class SymbolTable;
+}  // namespace tint
+namespace tint::type {
+class Manager;
+}  // namespace tint::type
+
+namespace tint::type {
+
+/// Context information for cloning of types
+struct CloneContext {
+    /// Source information
+    struct {
+        /// The source symbol table
+        const SymbolTable* st;
+    } src;
+
+    /// Destination information
+    struct {
+        /// The destination symbol table
+        SymbolTable* st;
+        /// The destination type manger
+        Manager* mgr;
+    } dst;
+};
+
+}  // namespace tint::type
+
+#endif  // SRC_TINT_TYPE_CLONE_CONTEXT_H_
diff --git a/src/tint/type/depth_multisampled_texture.cc b/src/tint/type/depth_multisampled_texture.cc
index 1313d70..7e80eb1 100644
--- a/src/tint/type/depth_multisampled_texture.cc
+++ b/src/tint/type/depth_multisampled_texture.cc
@@ -48,4 +48,8 @@
     return out.str();
 }
 
+DepthMultisampledTexture* DepthMultisampledTexture::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<DepthMultisampledTexture>(dim());
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/depth_multisampled_texture.h b/src/tint/type/depth_multisampled_texture.h
index f92a00d..d91b463 100644
--- a/src/tint/type/depth_multisampled_texture.h
+++ b/src/tint/type/depth_multisampled_texture.h
@@ -39,6 +39,10 @@
     /// @returns the name for this type that closely resembles how it would be
     /// declared in WGSL.
     std::string FriendlyName(const SymbolTable& symbols) const override;
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    DepthMultisampledTexture* Clone(CloneContext& ctx) const override;
 };
 
 }  // namespace tint::type
diff --git a/src/tint/type/depth_multisampled_texture_test.cc b/src/tint/type/depth_multisampled_texture_test.cc
index 46897e5..fa94d93 100644
--- a/src/tint/type/depth_multisampled_texture_test.cc
+++ b/src/tint/type/depth_multisampled_texture_test.cc
@@ -57,5 +57,15 @@
     EXPECT_EQ(d.FriendlyName(Symbols()), "texture_depth_multisampled_2d");
 }
 
+TEST_F(DepthMultisampledTextureTest, Clone) {
+    auto* a = create<DepthMultisampledTexture>(ast::TextureDimension::k2d);
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* dt = a->Clone(ctx);
+    EXPECT_EQ(dt->dim(), ast::TextureDimension::k2d);
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/depth_texture.cc b/src/tint/type/depth_texture.cc
index c5d5aae..2295b39 100644
--- a/src/tint/type/depth_texture.cc
+++ b/src/tint/type/depth_texture.cc
@@ -49,4 +49,8 @@
     return out.str();
 }
 
+DepthTexture* DepthTexture::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<DepthTexture>(dim());
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/depth_texture.h b/src/tint/type/depth_texture.h
index 0e5bef3..21cd780 100644
--- a/src/tint/type/depth_texture.h
+++ b/src/tint/type/depth_texture.h
@@ -39,6 +39,10 @@
     /// @returns the name for this type that closely resembles how it would be
     /// declared in WGSL.
     std::string FriendlyName(const SymbolTable& symbols) const override;
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    DepthTexture* Clone(CloneContext& ctx) const override;
 };
 
 }  // namespace tint::type
diff --git a/src/tint/type/depth_texture_test.cc b/src/tint/type/depth_texture_test.cc
index eab9ba8..18dda3d 100644
--- a/src/tint/type/depth_texture_test.cc
+++ b/src/tint/type/depth_texture_test.cc
@@ -69,5 +69,15 @@
     EXPECT_EQ(d.FriendlyName(Symbols()), "texture_depth_cube");
 }
 
+TEST_F(DepthTextureTest, Clone) {
+    auto* a = create<DepthTexture>(ast::TextureDimension::k2d);
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* dt = a->Clone(ctx);
+    EXPECT_EQ(dt->dim(), ast::TextureDimension::k2d);
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/external_texture.cc b/src/tint/type/external_texture.cc
index 9bc319c..77ac5ba 100644
--- a/src/tint/type/external_texture.cc
+++ b/src/tint/type/external_texture.cc
@@ -34,4 +34,8 @@
     return "texture_external";
 }
 
+ExternalTexture* ExternalTexture::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<ExternalTexture>();
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/external_texture.h b/src/tint/type/external_texture.h
index a60eddc..947eb88 100644
--- a/src/tint/type/external_texture.h
+++ b/src/tint/type/external_texture.h
@@ -38,6 +38,10 @@
     /// @returns the name for this type that closely resembles how it would be
     /// declared in WGSL.
     std::string FriendlyName(const SymbolTable& symbols) const override;
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    ExternalTexture* Clone(CloneContext& ctx) const override;
 };
 
 }  // namespace tint::type
diff --git a/src/tint/type/external_texture_test.cc b/src/tint/type/external_texture_test.cc
index d04b973..e9ea95d 100644
--- a/src/tint/type/external_texture_test.cc
+++ b/src/tint/type/external_texture_test.cc
@@ -66,5 +66,15 @@
     EXPECT_EQ(s.FriendlyName(Symbols()), "texture_external");
 }
 
+TEST_F(ExternalTextureTest, Clone) {
+    auto* a = create<ExternalTexture>();
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* b = a->Clone(ctx);
+    ASSERT_TRUE(b->Is<ExternalTexture>());
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/f16.cc b/src/tint/type/f16.cc
index 94849e5..5ea9aca 100644
--- a/src/tint/type/f16.cc
+++ b/src/tint/type/f16.cc
@@ -46,4 +46,8 @@
     return 2;
 }
 
+F16* F16::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<F16>();
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/f16.h b/src/tint/type/f16.h
index 9cac21b..c35d878 100644
--- a/src/tint/type/f16.h
+++ b/src/tint/type/f16.h
@@ -44,6 +44,10 @@
 
     /// @returns the alignment in bytes of the type.
     uint32_t Align() const override;
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    F16* Clone(CloneContext& ctx) const override;
 };
 
 }  // namespace tint::type
diff --git a/src/tint/type/f16_test.cc b/src/tint/type/f16_test.cc
index e486380..a9a2fe0 100644
--- a/src/tint/type/f16_test.cc
+++ b/src/tint/type/f16_test.cc
@@ -44,5 +44,15 @@
     EXPECT_EQ(f.FriendlyName(Symbols()), "f16");
 }
 
+TEST_F(F16Test, Clone) {
+    auto* a = create<F16>();
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* b = a->Clone(ctx);
+    ASSERT_TRUE(b->Is<F16>());
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/f32.cc b/src/tint/type/f32.cc
index 4ccd13a..91872a2 100644
--- a/src/tint/type/f32.cc
+++ b/src/tint/type/f32.cc
@@ -46,4 +46,8 @@
     return 4;
 }
 
+F32* F32::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<F32>();
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/f32.h b/src/tint/type/f32.h
index 7a1e864..12b298f 100644
--- a/src/tint/type/f32.h
+++ b/src/tint/type/f32.h
@@ -44,6 +44,10 @@
 
     /// @returns the alignment in bytes of the type.
     uint32_t Align() const override;
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    F32* Clone(CloneContext& ctx) const override;
 };
 
 }  // namespace tint::type
diff --git a/src/tint/type/f32_test.cc b/src/tint/type/f32_test.cc
index 8ecbdbd..43d7823 100644
--- a/src/tint/type/f32_test.cc
+++ b/src/tint/type/f32_test.cc
@@ -44,5 +44,15 @@
     EXPECT_EQ(f.FriendlyName(Symbols()), "f32");
 }
 
+TEST_F(F32Test, Clone) {
+    auto* a = create<F32>();
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* b = a->Clone(ctx);
+    ASSERT_TRUE(b->Is<F32>());
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/i32.cc b/src/tint/type/i32.cc
index c616d14..9fe0d95 100644
--- a/src/tint/type/i32.cc
+++ b/src/tint/type/i32.cc
@@ -46,4 +46,8 @@
     return 4;
 }
 
+I32* I32::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<I32>();
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/i32.h b/src/tint/type/i32.h
index 2160c3a..bdbf559 100644
--- a/src/tint/type/i32.h
+++ b/src/tint/type/i32.h
@@ -44,6 +44,10 @@
 
     /// @returns the alignment in bytes of the type.
     uint32_t Align() const override;
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    I32* Clone(CloneContext& ctx) const override;
 };
 
 }  // namespace tint::type
diff --git a/src/tint/type/i32_test.cc b/src/tint/type/i32_test.cc
index bdcbf8e..97c90b1 100644
--- a/src/tint/type/i32_test.cc
+++ b/src/tint/type/i32_test.cc
@@ -44,5 +44,15 @@
     EXPECT_EQ(i.FriendlyName(Symbols()), "i32");
 }
 
+TEST_F(I32Test, Clone) {
+    auto* a = create<I32>();
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* b = a->Clone(ctx);
+    ASSERT_TRUE(b->Is<I32>());
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/manager.cc b/src/tint/type/manager.cc
index 2c455eb..666a1d3 100644
--- a/src/tint/type/manager.cc
+++ b/src/tint/type/manager.cc
@@ -17,8 +17,11 @@
 namespace tint::type {
 
 Manager::Manager() = default;
+
 Manager::Manager(Manager&&) = default;
+
 Manager& Manager::operator=(Manager&& rhs) = default;
+
 Manager::~Manager() = default;
 
 }  // namespace tint::type
diff --git a/src/tint/type/matrix.cc b/src/tint/type/matrix.cc
index 0d7571f..600f4b0 100644
--- a/src/tint/type/matrix.cc
+++ b/src/tint/type/matrix.cc
@@ -66,4 +66,9 @@
     return column_type_->Align();
 }
 
+Matrix* Matrix::Clone(CloneContext& ctx) const {
+    auto* col_ty = column_type_->Clone(ctx);
+    return ctx.dst.mgr->Get<Matrix>(col_ty, columns_);
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/matrix.h b/src/tint/type/matrix.h
index 70555ea..72bb2e5 100644
--- a/src/tint/type/matrix.h
+++ b/src/tint/type/matrix.h
@@ -66,6 +66,10 @@
     /// @returns the number of bytes between columns of the matrix
     uint32_t ColumnStride() const;
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    Matrix* Clone(CloneContext& ctx) const override;
+
   private:
     const Type* const subtype_;
     const Vector* const column_type_;
diff --git a/src/tint/type/matrix_test.cc b/src/tint/type/matrix_test.cc
index 3a74741..09b46f1 100644
--- a/src/tint/type/matrix_test.cc
+++ b/src/tint/type/matrix_test.cc
@@ -65,5 +65,17 @@
     EXPECT_EQ(m.FriendlyName(Symbols()), "mat2x3<i32>");
 }
 
+TEST_F(MatrixTest, Clone) {
+    auto* a = create<Matrix>(create<Vector>(create<I32>(), 3u), 4u);
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* mat = a->Clone(ctx);
+    EXPECT_TRUE(mat->type()->Is<I32>());
+    EXPECT_EQ(mat->rows(), 3u);
+    EXPECT_EQ(mat->columns(), 4u);
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/multisampled_texture.cc b/src/tint/type/multisampled_texture.cc
index 16588f4..47d3e21 100644
--- a/src/tint/type/multisampled_texture.cc
+++ b/src/tint/type/multisampled_texture.cc
@@ -42,4 +42,9 @@
     return out.str();
 }
 
+MultisampledTexture* MultisampledTexture::Clone(CloneContext& ctx) const {
+    auto* ty = type_->Clone(ctx);
+    return ctx.dst.mgr->Get<MultisampledTexture>(dim(), ty);
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/multisampled_texture.h b/src/tint/type/multisampled_texture.h
index f78d40b..3048590 100644
--- a/src/tint/type/multisampled_texture.h
+++ b/src/tint/type/multisampled_texture.h
@@ -44,6 +44,10 @@
     /// declared in WGSL.
     std::string FriendlyName(const SymbolTable& symbols) const override;
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    MultisampledTexture* Clone(CloneContext& ctx) const override;
+
   private:
     const Type* const type_;
 };
diff --git a/src/tint/type/multisampled_texture_test.cc b/src/tint/type/multisampled_texture_test.cc
index e919ccf..b3b671a 100644
--- a/src/tint/type/multisampled_texture_test.cc
+++ b/src/tint/type/multisampled_texture_test.cc
@@ -81,5 +81,16 @@
     EXPECT_EQ(s.FriendlyName(Symbols()), "texture_multisampled_3d<f32>");
 }
 
+TEST_F(MultisampledTextureTest, Clone) {
+    auto* a = create<MultisampledTexture>(ast::TextureDimension::k2d, create<F32>());
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* mt = a->Clone(ctx);
+    EXPECT_EQ(mt->dim(), ast::TextureDimension::k2d);
+    EXPECT_TRUE(mt->type()->Is<F32>());
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/pointer.cc b/src/tint/type/pointer.cc
index 0ae8e0a..8716b92 100644
--- a/src/tint/type/pointer.cc
+++ b/src/tint/type/pointer.cc
@@ -53,4 +53,9 @@
 
 Pointer::~Pointer() = default;
 
+Pointer* Pointer::Clone(CloneContext& ctx) const {
+    auto* ty = subtype_->Clone(ctx);
+    return ctx.dst.mgr->Get<Pointer>(ty, address_space_, access_);
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/pointer.h b/src/tint/type/pointer.h
index 3f7ff01..083647e 100644
--- a/src/tint/type/pointer.h
+++ b/src/tint/type/pointer.h
@@ -53,6 +53,10 @@
     /// declared in WGSL.
     std::string FriendlyName(const SymbolTable& symbols) const override;
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    Pointer* Clone(CloneContext& ctx) const override;
+
   private:
     Type const* const subtype_;
     ast::AddressSpace const address_space_;
diff --git a/src/tint/type/pointer_test.cc b/src/tint/type/pointer_test.cc
index af3587a..85fcab7 100644
--- a/src/tint/type/pointer_test.cc
+++ b/src/tint/type/pointer_test.cc
@@ -68,5 +68,17 @@
     EXPECT_EQ(r->FriendlyName(Symbols()), "ptr<workgroup, i32, read>");
 }
 
+TEST_F(PointerTest, Clone) {
+    auto* a = create<Pointer>(create<I32>(), ast::AddressSpace::kStorage, ast::Access::kReadWrite);
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* ptr = a->Clone(ctx);
+    EXPECT_TRUE(ptr->StoreType()->Is<I32>());
+    EXPECT_EQ(ptr->AddressSpace(), ast::AddressSpace::kStorage);
+    EXPECT_EQ(ptr->Access(), ast::Access::kReadWrite);
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/reference.cc b/src/tint/type/reference.cc
index f811b28..36915fc 100644
--- a/src/tint/type/reference.cc
+++ b/src/tint/type/reference.cc
@@ -52,4 +52,9 @@
 
 Reference::~Reference() = default;
 
+Reference* Reference::Clone(CloneContext& ctx) const {
+    auto* ty = subtype_->Clone(ctx);
+    return ctx.dst.mgr->Get<Reference>(ty, address_space_, access_);
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/reference.h b/src/tint/type/reference.h
index e13b3cf..d4235c1 100644
--- a/src/tint/type/reference.h
+++ b/src/tint/type/reference.h
@@ -53,6 +53,10 @@
     /// declared in WGSL.
     std::string FriendlyName(const SymbolTable& symbols) const override;
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    Reference* Clone(CloneContext& ctx) const override;
+
   private:
     Type const* const subtype_;
     ast::AddressSpace const address_space_;
diff --git a/src/tint/type/reference_test.cc b/src/tint/type/reference_test.cc
index e9f6128..93e42ad 100644
--- a/src/tint/type/reference_test.cc
+++ b/src/tint/type/reference_test.cc
@@ -78,5 +78,18 @@
     EXPECT_EQ(r->FriendlyName(Symbols()), "ref<workgroup, i32, read>");
 }
 
+TEST_F(ReferenceTest, Clone) {
+    auto* a =
+        create<Reference>(create<I32>(), ast::AddressSpace::kStorage, ast::Access::kReadWrite);
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* ref = a->Clone(ctx);
+    EXPECT_TRUE(ref->StoreType()->Is<I32>());
+    EXPECT_EQ(ref->AddressSpace(), ast::AddressSpace::kStorage);
+    EXPECT_EQ(ref->Access(), ast::Access::kReadWrite);
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/sampled_texture.cc b/src/tint/type/sampled_texture.cc
index a8bdce6..7651b410 100644
--- a/src/tint/type/sampled_texture.cc
+++ b/src/tint/type/sampled_texture.cc
@@ -41,4 +41,9 @@
     return out.str();
 }
 
+SampledTexture* SampledTexture::Clone(CloneContext& ctx) const {
+    auto* ty = type_->Clone(ctx);
+    return ctx.dst.mgr->Get<SampledTexture>(dim(), ty);
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/sampled_texture.h b/src/tint/type/sampled_texture.h
index 5272102..1f37790 100644
--- a/src/tint/type/sampled_texture.h
+++ b/src/tint/type/sampled_texture.h
@@ -44,6 +44,10 @@
     /// declared in WGSL.
     std::string FriendlyName(const SymbolTable& symbols) const override;
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    SampledTexture* Clone(CloneContext& ctx) const override;
+
   private:
     const Type* const type_;
 };
diff --git a/src/tint/type/sampled_texture_test.cc b/src/tint/type/sampled_texture_test.cc
index 772629f..78ab2d3 100644
--- a/src/tint/type/sampled_texture_test.cc
+++ b/src/tint/type/sampled_texture_test.cc
@@ -85,5 +85,16 @@
     EXPECT_EQ(s.FriendlyName(Symbols()), "texture_3d<f32>");
 }
 
+TEST_F(SampledTextureTest, Clone) {
+    auto* a = create<SampledTexture>(ast::TextureDimension::kCube, create<F32>());
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* mt = a->Clone(ctx);
+    EXPECT_EQ(mt->dim(), ast::TextureDimension::kCube);
+    EXPECT_TRUE(mt->type()->Is<F32>());
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/sampler.cc b/src/tint/type/sampler.cc
index 9de1ea8..50d3f28 100644
--- a/src/tint/type/sampler.cc
+++ b/src/tint/type/sampler.cc
@@ -37,4 +37,8 @@
     return kind_ == ast::SamplerKind::kSampler ? "sampler" : "sampler_comparison";
 }
 
+Sampler* Sampler::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<Sampler>(kind_);
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/sampler.h b/src/tint/type/sampler.h
index 7778dcd..ab81073 100644
--- a/src/tint/type/sampler.h
+++ b/src/tint/type/sampler.h
@@ -47,6 +47,10 @@
     /// declared in WGSL.
     std::string FriendlyName(const SymbolTable& symbols) const override;
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    Sampler* Clone(CloneContext& ctx) const override;
+
   private:
     ast::SamplerKind const kind_;
 };
diff --git a/src/tint/type/sampler_test.cc b/src/tint/type/sampler_test.cc
index 262dd18..ef0bdf5 100644
--- a/src/tint/type/sampler_test.cc
+++ b/src/tint/type/sampler_test.cc
@@ -63,5 +63,15 @@
     EXPECT_EQ(s.FriendlyName(Symbols()), "sampler_comparison");
 }
 
+TEST_F(SamplerTest, Clone) {
+    auto* a = create<Sampler>(ast::SamplerKind::kSampler);
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* mt = a->Clone(ctx);
+    EXPECT_EQ(mt->kind(), ast::SamplerKind::kSampler);
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/storage_texture.cc b/src/tint/type/storage_texture.cc
index ae574f1..d893767 100644
--- a/src/tint/type/storage_texture.cc
+++ b/src/tint/type/storage_texture.cc
@@ -79,4 +79,9 @@
     return nullptr;
 }
 
+StorageTexture* StorageTexture::Clone(CloneContext& ctx) const {
+    auto* ty = subtype_->Clone(ctx);
+    return ctx.dst.mgr->Get<StorageTexture>(dim(), texel_format_, access_, ty);
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/storage_texture.h b/src/tint/type/storage_texture.h
index 25b452b..276e045 100644
--- a/src/tint/type/storage_texture.h
+++ b/src/tint/type/storage_texture.h
@@ -67,6 +67,10 @@
     /// @returns the storage texture subtype for the given TexelFormat
     static Type* SubtypeFor(ast::TexelFormat format, Manager& type_mgr);
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    StorageTexture* Clone(CloneContext& ctx) const override;
+
   private:
     ast::TexelFormat const texel_format_;
     ast::Access const access_;
diff --git a/src/tint/type/storage_texture_test.cc b/src/tint/type/storage_texture_test.cc
index 30293ee..ad8a975 100644
--- a/src/tint/type/storage_texture_test.cc
+++ b/src/tint/type/storage_texture_test.cc
@@ -134,5 +134,18 @@
     EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<I32>());
 }
 
+TEST_F(StorageTextureTest, Clone) {
+    auto* a = Create(ast::TextureDimension::kCube, ast::TexelFormat::kRgba32Float,
+                     ast::Access::kReadWrite);
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* mt = a->Clone(ctx);
+    EXPECT_EQ(mt->dim(), ast::TextureDimension::kCube);
+    EXPECT_EQ(mt->texel_format(), ast::TexelFormat::kRgba32Float);
+    EXPECT_TRUE(mt->type()->Is<F32>());
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/struct.cc b/src/tint/type/struct.cc
index f54f21f..c39916b 100644
--- a/src/tint/type/struct.cc
+++ b/src/tint/type/struct.cc
@@ -20,6 +20,7 @@
 #include <utility>
 
 #include "src/tint/symbol_table.h"
+#include "src/tint/type/manager.h"
 #include "src/tint/utils/hash.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::Struct);
@@ -160,6 +161,16 @@
     return ss.str();
 }
 
+Struct* Struct::Clone(CloneContext& ctx) const {
+    auto sym = ctx.dst.st->Register(ctx.src.st->NameFor(name_));
+
+    utils::Vector<const StructMember*, 4> members;
+    for (const auto& mem : members_) {
+        members.Push(mem->Clone(ctx));
+    }
+    return ctx.dst.mgr->Get<Struct>(source_, sym, members, align_, size_, size_no_padding_);
+}
+
 StructMember::StructMember(tint::Source source,
                            Symbol name,
                            const type::Type* type,
@@ -179,4 +190,11 @@
 
 StructMember::~StructMember() = default;
 
+StructMember* StructMember::Clone(CloneContext& ctx) const {
+    auto sym = ctx.dst.st->Register(ctx.src.st->NameFor(name_));
+    auto* ty = type_->Clone(ctx);
+    return ctx.dst.mgr->Get<StructMember>(source_, sym, ty, index_, offset_, align_, size_,
+                                          location_);
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/struct.h b/src/tint/type/struct.h
index ff23a89..cd545e5 100644
--- a/src/tint/type/struct.h
+++ b/src/tint/type/struct.h
@@ -149,6 +149,10 @@
     /// @note only structures returned by builtins may be abstract (e.g. modf, frexp)
     utils::VectorRef<const Struct*> ConcreteTypes() const { return concrete_types_; }
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    Struct* Clone(CloneContext& ctx) const override;
+
   private:
     const tint::Source source_;
     const Symbol name_;
@@ -216,6 +220,10 @@
     /// @returns the location, if set
     std::optional<uint32_t> Location() const { return location_; }
 
+    /// @param ctx the clone context
+    /// @returns a clone of this struct member
+    StructMember* Clone(CloneContext& ctx) const;
+
   private:
     const tint::Source source_;
     const Symbol name_;
diff --git a/src/tint/type/struct_test.cc b/src/tint/type/struct_test.cc
index 8bacfca..ff72d0b 100644
--- a/src/tint/type/struct_test.cc
+++ b/src/tint/type/struct_test.cc
@@ -206,5 +206,47 @@
     EXPECT_FALSE(sem_outer_with_runtime_sized_array->HasFixedFootprint());
 }
 
+TEST_F(TypeStructTest, Clone) {
+    auto* s = create<Struct>(
+        Source{}, Sym("my_struct"),
+        utils::Vector{create<StructMember>(Source{}, Sym("b"), create<Vector>(create<F32>(), 3u),
+                                           0u, 0u, 16u, 12u, std::optional<uint32_t>{2}),
+                      create<StructMember>(Source{}, Sym("a"), create<I32>(), 1u, 16u, 4u, 4u,
+                                           std::optional<uint32_t>())},
+        4u /* align */, 8u /* size */, 16u /* size_no_padding */);
+
+    ProgramID id;
+    SymbolTable new_st{id};
+
+    type::Manager mgr;
+    type::CloneContext ctx{{&Symbols()}, {&new_st, &mgr}};
+
+    auto* st = s->Clone(ctx);
+
+    EXPECT_TRUE(new_st.Get("my_struct").IsValid());
+    EXPECT_EQ(new_st.NameFor(st->Name()), "my_struct");
+
+    EXPECT_EQ(st->Align(), 4u);
+    EXPECT_EQ(st->Size(), 8u);
+    EXPECT_EQ(st->SizeNoPadding(), 16u);
+
+    auto members = st->Members();
+    ASSERT_EQ(members.Length(), 2u);
+
+    EXPECT_EQ(new_st.NameFor(members[0]->Name()), "b");
+    EXPECT_TRUE(members[0]->Type()->Is<Vector>());
+    EXPECT_EQ(members[0]->Index(), 0u);
+    EXPECT_EQ(members[0]->Offset(), 0u);
+    EXPECT_EQ(members[0]->Align(), 16u);
+    EXPECT_EQ(members[0]->Size(), 12u);
+
+    EXPECT_EQ(new_st.NameFor(members[1]->Name()), "a");
+    EXPECT_TRUE(members[1]->Type()->Is<I32>());
+    EXPECT_EQ(members[1]->Index(), 1u);
+    EXPECT_EQ(members[1]->Offset(), 16u);
+    EXPECT_EQ(members[1]->Align(), 4u);
+    EXPECT_EQ(members[1]->Size(), 4u);
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/type.h b/src/tint/type/type.h
index ccd0fe5..a02c9ac 100644
--- a/src/tint/type/type.h
+++ b/src/tint/type/type.h
@@ -18,6 +18,7 @@
 #include <functional>
 #include <string>
 
+#include "src/tint/type/clone_context.h"
 #include "src/tint/type/unique_node.h"
 #include "src/tint/utils/enum_set.h"
 #include "src/tint/utils/vector.h"
@@ -72,6 +73,10 @@
     /// @note opaque types will return a size of 0.
     virtual uint32_t Align() const;
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type created in the provided context
+    virtual Type* Clone(CloneContext& ctx) const = 0;
+
     /// @returns the flags on the type
     type::Flags Flags() { return flags_; }
 
diff --git a/src/tint/type/u32.cc b/src/tint/type/u32.cc
index 34ae027..a749f85 100644
--- a/src/tint/type/u32.cc
+++ b/src/tint/type/u32.cc
@@ -46,4 +46,8 @@
     return 4;
 }
 
+U32* U32::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<U32>();
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/u32.h b/src/tint/type/u32.h
index 222a387..8d79642 100644
--- a/src/tint/type/u32.h
+++ b/src/tint/type/u32.h
@@ -44,6 +44,10 @@
 
     /// @returns the alignment in bytes of the type.
     uint32_t Align() const override;
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    U32* Clone(CloneContext& ctx) const override;
 };
 
 }  // namespace tint::type
diff --git a/src/tint/type/u32_test.cc b/src/tint/type/u32_test.cc
index 5ec5502..8f17e62 100644
--- a/src/tint/type/u32_test.cc
+++ b/src/tint/type/u32_test.cc
@@ -44,5 +44,15 @@
     EXPECT_EQ(u.FriendlyName(Symbols()), "u32");
 }
 
+TEST_F(U32Test, Clone) {
+    auto* a = create<U32>();
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* b = a->Clone(ctx);
+    ASSERT_TRUE(b->Is<U32>());
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/vector.cc b/src/tint/type/vector.cc
index a1f964e..acd952c 100644
--- a/src/tint/type/vector.cc
+++ b/src/tint/type/vector.cc
@@ -65,4 +65,9 @@
     return 0;  // Unreachable
 }
 
+Vector* Vector::Clone(CloneContext& ctx) const {
+    auto* subtype = subtype_->Clone(ctx);
+    return ctx.dst.mgr->Get<Vector>(subtype, width_);
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/vector.h b/src/tint/type/vector.h
index 86f6f95..acffb8c 100644
--- a/src/tint/type/vector.h
+++ b/src/tint/type/vector.h
@@ -62,6 +62,10 @@
     /// @returns the alignment in bytes of a vector of the given width.
     static uint32_t AlignOf(uint32_t width);
 
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    Vector* Clone(CloneContext& ctx) const override;
+
   private:
     Type const* const subtype_;
     const uint32_t width_;
diff --git a/src/tint/type/vector_test.cc b/src/tint/type/vector_test.cc
index 2bd144e..7dd5c9d 100644
--- a/src/tint/type/vector_test.cc
+++ b/src/tint/type/vector_test.cc
@@ -59,5 +59,16 @@
     EXPECT_EQ(v->FriendlyName(Symbols()), "vec3<f32>");
 }
 
+TEST_F(VectorTest, Clone) {
+    auto* a = create<Vector>(create<I32>(), 2u);
+
+    type::Manager mgr;
+    type::CloneContext ctx{{nullptr}, {nullptr, &mgr}};
+
+    auto* vec = a->Clone(ctx);
+    EXPECT_TRUE(vec->type()->Is<I32>());
+    EXPECT_EQ(vec->Width(), 2u);
+}
+
 }  // namespace
 }  // namespace tint::type
diff --git a/src/tint/type/void.cc b/src/tint/type/void.cc
index 3522607..e29def1 100644
--- a/src/tint/type/void.cc
+++ b/src/tint/type/void.cc
@@ -32,4 +32,8 @@
     return "void";
 }
 
+Void* Void::Clone(CloneContext& ctx) const {
+    return ctx.dst.mgr->Get<Void>();
+}
+
 }  // namespace tint::type
diff --git a/src/tint/type/void.h b/src/tint/type/void.h
index 801a671..02b2eee 100644
--- a/src/tint/type/void.h
+++ b/src/tint/type/void.h
@@ -38,6 +38,10 @@
     /// @returns the name for this type that closely resembles how it would be
     /// declared in WGSL.
     std::string FriendlyName(const SymbolTable& symbols) const override;
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    Void* Clone(CloneContext& ctx) const override;
 };
 
 }  // namespace tint::type