Add type::Type::FriendlyName()

Gives a WGSL-like string for the given type.

Also cleans up some code in IntrinsicTable.

Change-Id: I89a2fadb5291b49dcbf43371bb970eef74670e2c
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/40605
Auto-Submit: Ben Clayton <bclayton@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/intrinsic_table.cc b/src/intrinsic_table.cc
index 3598372..0c99427 100644
--- a/src/intrinsic_table.cc
+++ b/src/intrinsic_table.cc
@@ -1240,69 +1240,6 @@
   }
   return ss.str();
 }
-/// TODO(bclayton): This really does not belong here. It would be nice if
-/// type::Type::type_name() returned these strings.
-/// @returns a human readable string for the type `ty`.
-std::string TypeName(type::Type* ty) {
-  ty = ty->UnwrapAll();
-  if (ty->Is<type::F32>()) {
-    return "f32";
-  }
-  if (ty->Is<type::U32>()) {
-    return "u32";
-  }
-  if (ty->Is<type::I32>()) {
-    return "i32";
-  }
-  if (ty->Is<type::Bool>()) {
-    return "bool";
-  }
-  if (ty->Is<type::Void>()) {
-    return "void";
-  }
-  if (auto* ptr = ty->As<type::Pointer>()) {
-    return "ptr<" + TypeName(ptr->type()) + ">";
-  }
-  if (auto* vec = ty->As<type::Vector>()) {
-    return "vec" + std::to_string(vec->size()) + "<" + TypeName(vec->type()) +
-           ">";
-  }
-  if (auto* mat = ty->As<type::Matrix>()) {
-    return "mat" + std::to_string(mat->columns()) + "x" +
-           std::to_string(mat->rows()) + "<" + TypeName(mat->type()) + ">";
-  }
-  if (auto* tex = ty->As<type::SampledTexture>()) {
-    std::stringstream ss;
-    ss << "texture_" << tex->dim() << "<" << TypeName(tex->type()) << ">";
-    return ss.str();
-  }
-  if (auto* tex = ty->As<type::MultisampledTexture>()) {
-    std::stringstream ss;
-    ss << "texture_multisampled_" << tex->dim() << "<" << TypeName(tex->type())
-       << ">";
-    return ss.str();
-  }
-  if (auto* tex = ty->As<type::DepthTexture>()) {
-    std::stringstream ss;
-    ss << "texture_depth_" << tex->dim();
-    return ss.str();
-  }
-  if (auto* tex = ty->As<type::StorageTexture>()) {
-    std::stringstream ss;
-    ss << "texture_storage_" << tex->dim() << "<" << tex->image_format() << ">";
-    return ss.str();
-  }
-  if (auto* sampler = ty->As<type::Sampler>()) {
-    switch (sampler->kind()) {
-      case type::SamplerKind::kSampler:
-        return "sampler";
-      case type::SamplerKind::kComparisonSampler:
-        return "sampler_comparison";
-    }
-    return "sampler";
-  }
-  return ty->type_name();
-}
 
 IntrinsicTable::Result Impl::Lookup(
     ProgramBuilder& builder,
@@ -1345,7 +1282,7 @@
         ss << ", ";
       }
       first = false;
-      ss << TypeName(arg);
+      ss << arg->UnwrapAll()->FriendlyName(builder.Symbols());
     }
   }
   ss << ")" << std::endl;
diff --git a/src/type/access_control_type.cc b/src/type/access_control_type.cc
index 9125cc8..4d96e89 100644
--- a/src/type/access_control_type.cc
+++ b/src/type/access_control_type.cc
@@ -50,6 +50,24 @@
   return name + subtype_->type_name();
 }
 
+std::string AccessControl::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "[[access(";
+  switch (access_) {
+    case ast::AccessControl::kReadOnly:
+      out << "read";
+      break;
+    case ast::AccessControl::kWriteOnly:
+      out << "write";
+      break;
+    case ast::AccessControl::kReadWrite:
+      out << "read_write";
+      break;
+  }
+  out << ")]] " << subtype_->FriendlyName(symbols);
+  return out.str();
+}
+
 uint64_t AccessControl::MinBufferBindingSize(MemoryLayout mem_layout) const {
   return subtype_->MinBufferBindingSize(mem_layout);
 }
diff --git a/src/type/access_control_type.h b/src/type/access_control_type.h
index 3e0da48..7dce8bd 100644
--- a/src/type/access_control_type.h
+++ b/src/type/access_control_type.h
@@ -49,6 +49,11 @@
   /// @returns the name for this type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// @param mem_layout type of memory layout to use in calculation.
   /// @returns minimum size required for this type, in bytes.
   ///          0 for non-host shareable types.
diff --git a/src/type/access_control_type_test.cc b/src/type/access_control_type_test.cc
index 893eb8b..d398fca 100644
--- a/src/type/access_control_type_test.cc
+++ b/src/type/access_control_type_test.cc
@@ -97,6 +97,21 @@
   EXPECT_EQ(at.type_name(), "__access_control_read_write__i32");
 }
 
+TEST_F(AccessControlTest, FriendlyNameReadOnly) {
+  AccessControl at{ast::AccessControl::kReadOnly, ty.i32()};
+  EXPECT_EQ(at.FriendlyName(Symbols()), "[[access(read)]] i32");
+}
+
+TEST_F(AccessControlTest, FriendlyNameWriteOnly) {
+  AccessControl at{ast::AccessControl::kWriteOnly, ty.i32()};
+  EXPECT_EQ(at.FriendlyName(Symbols()), "[[access(write)]] i32");
+}
+
+TEST_F(AccessControlTest, FriendlyNameReadWrite) {
+  AccessControl at{ast::AccessControl::kReadWrite, ty.i32()};
+  EXPECT_EQ(at.FriendlyName(Symbols()), "[[access(read_write)]] i32");
+}
+
 TEST_F(AccessControlTest, MinBufferBindingSizeU32) {
   U32 u32;
   AccessControl at{ast::AccessControl::kReadOnly, &u32};
diff --git a/src/type/alias_type.cc b/src/type/alias_type.cc
index cd89bda..95fffe2 100644
--- a/src/type/alias_type.cc
+++ b/src/type/alias_type.cc
@@ -37,6 +37,10 @@
   return "__alias_" + symbol_.to_str() + subtype_->type_name();
 }
 
+std::string Alias::FriendlyName(const SymbolTable& symbols) const {
+  return symbols.NameFor(symbol_);
+}
+
 uint64_t Alias::MinBufferBindingSize(MemoryLayout mem_layout) const {
   return subtype_->MinBufferBindingSize(mem_layout);
 }
diff --git a/src/type/alias_type.h b/src/type/alias_type.h
index c1f858d..57e44d9 100644
--- a/src/type/alias_type.h
+++ b/src/type/alias_type.h
@@ -43,6 +43,11 @@
   /// @returns the type_name for this type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// @param mem_layout type of memory layout to use in calculation.
   /// @returns minimum size required for this type, in bytes.
   ///          0 for non-host shareable types.
diff --git a/src/type/alias_type_test.cc b/src/type/alias_type_test.cc
index a404918..d39b201 100644
--- a/src/type/alias_type_test.cc
+++ b/src/type/alias_type_test.cc
@@ -69,6 +69,11 @@
   EXPECT_EQ(at->type_name(), "__alias_tint_symbol_1__i32");
 }
 
+TEST_F(AliasTest, FriendlyName) {
+  auto* at = ty.alias("Particle", ty.i32());
+  EXPECT_EQ(at->FriendlyName(Symbols()), "Particle");
+}
+
 TEST_F(AliasTest, UnwrapIfNeeded_Alias) {
   auto* a = ty.alias("a_type", ty.u32());
   EXPECT_EQ(a->symbol(), Symbol(1));
diff --git a/src/type/array_type.cc b/src/type/array_type.cc
index ce90ad4..549b88f 100644
--- a/src/type/array_type.cc
+++ b/src/type/array_type.cc
@@ -95,6 +95,19 @@
   return type_name;
 }
 
+std::string Array::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  if (has_array_stride()) {
+    out << "[[stride(" << array_stride() << ")]] ";
+  }
+  out << "array<" << subtype_->FriendlyName(symbols);
+  if (!IsRuntimeArray()) {
+    out << ", " << size_;
+  }
+  out << ">";
+  return out.str();
+}
+
 Array* Array::Clone(CloneContext* ctx) const {
   return ctx->dst->create<Array>(ctx->Clone(subtype_), size_,
                                  ctx->Clone(decorations()));
diff --git a/src/type/array_type.h b/src/type/array_type.h
index 404fa3b..1fccc27 100644
--- a/src/type/array_type.h
+++ b/src/type/array_type.h
@@ -69,6 +69,11 @@
   /// @returns the name for the type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/array_type_test.cc b/src/type/array_type_test.cc
index 54bc12a..85c0655 100644
--- a/src/type/array_type_test.cc
+++ b/src/type/array_type_test.cc
@@ -80,6 +80,22 @@
   EXPECT_EQ(arr.type_name(), "__array__i32");
 }
 
+TEST_F(ArrayTest, FriendlyNameRuntimeSized) {
+  Array arr{ty.i32(), 0, ast::ArrayDecorationList{}};
+  EXPECT_EQ(arr.FriendlyName(Symbols()), "array<i32>");
+}
+
+TEST_F(ArrayTest, FriendlyNameStaticSized) {
+  Array arr{ty.i32(), 5, ast::ArrayDecorationList{}};
+  EXPECT_EQ(arr.FriendlyName(Symbols()), "array<i32, 5>");
+}
+
+TEST_F(ArrayTest, FriendlyNameWithStride) {
+  Array arr{ty.i32(), 5,
+            ast::ArrayDecorationList{create<ast::StrideDecoration>(32)}};
+  EXPECT_EQ(arr.FriendlyName(Symbols()), "[[stride(32)]] array<i32, 5>");
+}
+
 TEST_F(ArrayTest, TypeName_RuntimeArray) {
   I32 i32;
   Array arr{&i32, 3, ast::ArrayDecorationList{}};
diff --git a/src/type/bool_type.cc b/src/type/bool_type.cc
index 5b97dec..e548fde 100644
--- a/src/type/bool_type.cc
+++ b/src/type/bool_type.cc
@@ -32,6 +32,10 @@
   return "__bool";
 }
 
+std::string Bool::FriendlyName(const SymbolTable&) const {
+  return "bool";
+}
+
 Bool* Bool::Clone(CloneContext* ctx) const {
   return ctx->dst->create<Bool>();
 }
diff --git a/src/type/bool_type.h b/src/type/bool_type.h
index cc35062..97863c5 100644
--- a/src/type/bool_type.h
+++ b/src/type/bool_type.h
@@ -40,6 +40,11 @@
   /// @returns the name for this type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/bool_type_test.cc b/src/type/bool_type_test.cc
index 552857b..49a18f1 100644
--- a/src/type/bool_type_test.cc
+++ b/src/type/bool_type_test.cc
@@ -55,6 +55,11 @@
   EXPECT_EQ(b.type_name(), "__bool");
 }
 
+TEST_F(BoolTest, FriendlyName) {
+  Bool b;
+  EXPECT_EQ(b.FriendlyName(Symbols()), "bool");
+}
+
 TEST_F(BoolTest, MinBufferBindingSize) {
   Bool b;
   EXPECT_EQ(0u, b.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
diff --git a/src/type/depth_texture_type.cc b/src/type/depth_texture_type.cc
index c3cb32e..57f4b21 100644
--- a/src/type/depth_texture_type.cc
+++ b/src/type/depth_texture_type.cc
@@ -51,6 +51,12 @@
   return out.str();
 }
 
+std::string DepthTexture::FriendlyName(const SymbolTable&) const {
+  std::ostringstream out;
+  out << "texture_depth_" << dim();
+  return out.str();
+}
+
 DepthTexture* DepthTexture::Clone(CloneContext* ctx) const {
   return ctx->dst->create<DepthTexture>(dim());
 }
diff --git a/src/type/depth_texture_type.h b/src/type/depth_texture_type.h
index 43eed57..2cff86c 100644
--- a/src/type/depth_texture_type.h
+++ b/src/type/depth_texture_type.h
@@ -35,6 +35,11 @@
   /// @returns the name for this type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/depth_texture_type_test.cc b/src/type/depth_texture_type_test.cc
index 26be3e9..7303930 100644
--- a/src/type/depth_texture_type_test.cc
+++ b/src/type/depth_texture_type_test.cc
@@ -71,6 +71,11 @@
   EXPECT_EQ(d.type_name(), "__depth_texture_cube");
 }
 
+TEST_F(DepthTextureTest, FriendlyName) {
+  DepthTexture d(TextureDimension::kCube);
+  EXPECT_EQ(d.FriendlyName(Symbols()), "texture_depth_cube");
+}
+
 TEST_F(DepthTextureTest, MinBufferBindingSize) {
   DepthTexture d(TextureDimension::kCube);
   EXPECT_EQ(0u, d.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
diff --git a/src/type/f32_type.cc b/src/type/f32_type.cc
index 976a549..e9b341a 100644
--- a/src/type/f32_type.cc
+++ b/src/type/f32_type.cc
@@ -32,6 +32,10 @@
   return "__f32";
 }
 
+std::string F32::FriendlyName(const SymbolTable&) const {
+  return "f32";
+}
+
 uint64_t F32::MinBufferBindingSize(MemoryLayout) const {
   return 4;
 }
diff --git a/src/type/f32_type.h b/src/type/f32_type.h
index 55e4224..50e990d 100644
--- a/src/type/f32_type.h
+++ b/src/type/f32_type.h
@@ -34,6 +34,11 @@
   /// @returns the name for this type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// @param mem_layout type of memory layout to use in calculation.
   /// @returns minimum size required for this type, in bytes.
   ///          0 for non-host shareable types.
diff --git a/src/type/f32_type_test.cc b/src/type/f32_type_test.cc
index 7a25dc7..92fca64 100644
--- a/src/type/f32_type_test.cc
+++ b/src/type/f32_type_test.cc
@@ -55,6 +55,11 @@
   EXPECT_EQ(f.type_name(), "__f32");
 }
 
+TEST_F(F32Test, FriendlyName) {
+  F32 f;
+  EXPECT_EQ(f.FriendlyName(Symbols()), "f32");
+}
+
 TEST_F(F32Test, MinBufferBindingSize) {
   F32 f;
   EXPECT_EQ(4u, f.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
diff --git a/src/type/i32_type.cc b/src/type/i32_type.cc
index ded1932..d3f72ec 100644
--- a/src/type/i32_type.cc
+++ b/src/type/i32_type.cc
@@ -32,6 +32,10 @@
   return "__i32";
 }
 
+std::string I32::FriendlyName(const SymbolTable&) const {
+  return "i32";
+}
+
 uint64_t I32::MinBufferBindingSize(MemoryLayout) const {
   return 4;
 }
diff --git a/src/type/i32_type.h b/src/type/i32_type.h
index cb4421f..e688a0c 100644
--- a/src/type/i32_type.h
+++ b/src/type/i32_type.h
@@ -34,6 +34,11 @@
   /// @returns the name for this type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// @param mem_layout type of memory layout to use in calculation.
   /// @returns minimum size required for this type, in bytes.
   ///          0 for non-host shareable types.
diff --git a/src/type/i32_type_test.cc b/src/type/i32_type_test.cc
index 0fa098a..60df02f 100644
--- a/src/type/i32_type_test.cc
+++ b/src/type/i32_type_test.cc
@@ -55,6 +55,11 @@
   EXPECT_EQ(i.type_name(), "__i32");
 }
 
+TEST_F(I32Test, FriendlyName) {
+  I32 i;
+  EXPECT_EQ(i.FriendlyName(Symbols()), "i32");
+}
+
 TEST_F(I32Test, MinBufferBindingSize) {
   I32 i;
   EXPECT_EQ(4u, i.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
diff --git a/src/type/matrix_type.cc b/src/type/matrix_type.cc
index f32b396..c678f4e 100644
--- a/src/type/matrix_type.cc
+++ b/src/type/matrix_type.cc
@@ -43,6 +43,13 @@
          subtype_->type_name();
 }
 
+std::string Matrix::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "mat" << columns_ << "x" << rows_ << "<"
+      << subtype_->FriendlyName(symbols) << ">";
+  return out.str();
+}
+
 uint64_t Matrix::MinBufferBindingSize(MemoryLayout mem_layout) const {
   Vector vec(subtype_, rows_);
   return (columns_ - 1) * vec.BaseAlignment(mem_layout) +
diff --git a/src/type/matrix_type.h b/src/type/matrix_type.h
index d37a115..f76d578 100644
--- a/src/type/matrix_type.h
+++ b/src/type/matrix_type.h
@@ -44,6 +44,11 @@
   /// @returns the name for this type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// @param mem_layout type of memory layout to use in calculation.
   /// @returns minimum size required for this type, in bytes.
   ///          0 for non-host shareable types.
diff --git a/src/type/matrix_type_test.cc b/src/type/matrix_type_test.cc
index c1b6c43..e7b6227 100644
--- a/src/type/matrix_type_test.cc
+++ b/src/type/matrix_type_test.cc
@@ -65,6 +65,11 @@
   EXPECT_EQ(m.type_name(), "__mat_2_3__i32");
 }
 
+TEST_F(MatrixTest, FriendlyName) {
+  Matrix m{ty.i32(), 3, 2};
+  EXPECT_EQ(m.FriendlyName(Symbols()), "mat2x3<i32>");
+}
+
 TEST_F(MatrixTest, MinBufferBindingSize4x2) {
   I32 i32;
   Matrix m{&i32, 4, 2};
diff --git a/src/type/multisampled_texture_type.cc b/src/type/multisampled_texture_type.cc
index e9d415d..f647016 100644
--- a/src/type/multisampled_texture_type.cc
+++ b/src/type/multisampled_texture_type.cc
@@ -40,6 +40,14 @@
   return out.str();
 }
 
+std::string MultisampledTexture::FriendlyName(
+    const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "texture_multisampled_" << dim() << "<" << type_->FriendlyName(symbols)
+      << ">";
+  return out.str();
+}
+
 MultisampledTexture* MultisampledTexture::Clone(CloneContext* ctx) const {
   return ctx->dst->create<MultisampledTexture>(dim(), ctx->Clone(type_));
 }
diff --git a/src/type/multisampled_texture_type.h b/src/type/multisampled_texture_type.h
index afe7d6b..78c4073 100644
--- a/src/type/multisampled_texture_type.h
+++ b/src/type/multisampled_texture_type.h
@@ -39,6 +39,11 @@
   /// @returns the name for this type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/multisampled_texture_type_test.cc b/src/type/multisampled_texture_type_test.cc
index 788d7df..d4ac2ee 100644
--- a/src/type/multisampled_texture_type_test.cc
+++ b/src/type/multisampled_texture_type_test.cc
@@ -82,6 +82,11 @@
   EXPECT_EQ(s.type_name(), "__multisampled_texture_3d__f32");
 }
 
+TEST_F(MultisampledTextureTest, FriendlyName) {
+  MultisampledTexture s(TextureDimension::k3d, ty.f32());
+  EXPECT_EQ(s.FriendlyName(Symbols()), "texture_multisampled_3d<f32>");
+}
+
 TEST_F(MultisampledTextureTest, MinBufferBindingSize) {
   F32 f32;
   MultisampledTexture s(TextureDimension::k3d, &f32);
diff --git a/src/type/pointer_type.cc b/src/type/pointer_type.cc
index 8ac4a94..ec564ec 100644
--- a/src/type/pointer_type.cc
+++ b/src/type/pointer_type.cc
@@ -31,6 +31,16 @@
   return out.str();
 }
 
+std::string Pointer::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "ptr<";
+  if (storage_class_ != ast::StorageClass::kNone) {
+    out << storage_class_ << ", ";
+  }
+  out << subtype_->FriendlyName(symbols) << ">";
+  return out.str();
+}
+
 Pointer::Pointer(Pointer&&) = default;
 
 Pointer::~Pointer() = default;
diff --git a/src/type/pointer_type.h b/src/type/pointer_type.h
index bf64785..9df8e73 100644
--- a/src/type/pointer_type.h
+++ b/src/type/pointer_type.h
@@ -43,6 +43,11 @@
   /// @returns the name for this type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/pointer_type_test.cc b/src/type/pointer_type_test.cc
index 7b6c6a8..dd48053 100644
--- a/src/type/pointer_type_test.cc
+++ b/src/type/pointer_type_test.cc
@@ -64,6 +64,16 @@
   EXPECT_EQ(p.type_name(), "__ptr_workgroup__i32");
 }
 
+TEST_F(PointerTest, FriendlyNameWithStorageClass) {
+  Pointer p{ty.i32(), ast::StorageClass::kWorkgroup};
+  EXPECT_EQ(p.FriendlyName(Symbols()), "ptr<workgroup, i32>");
+}
+
+TEST_F(PointerTest, FriendlyNameWithoutStorageClass) {
+  Pointer p{ty.i32(), ast::StorageClass::kNone};
+  EXPECT_EQ(p.FriendlyName(Symbols()), "ptr<i32>");
+}
+
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/sampled_texture_type.cc b/src/type/sampled_texture_type.cc
index a63577b..bd4fb5c 100644
--- a/src/type/sampled_texture_type.cc
+++ b/src/type/sampled_texture_type.cc
@@ -40,6 +40,12 @@
   return out.str();
 }
 
+std::string SampledTexture::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "texture_" << dim() << "<" << type_->FriendlyName(symbols) << ">";
+  return out.str();
+}
+
 SampledTexture* SampledTexture::Clone(CloneContext* ctx) const {
   return ctx->dst->create<SampledTexture>(dim(), ctx->Clone(type_));
 }
diff --git a/src/type/sampled_texture_type.h b/src/type/sampled_texture_type.h
index 3211050..fc03f72 100644
--- a/src/type/sampled_texture_type.h
+++ b/src/type/sampled_texture_type.h
@@ -39,6 +39,11 @@
   /// @returns the name for this type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/sampled_texture_type_test.cc b/src/type/sampled_texture_type_test.cc
index a80312f..7eee0fa 100644
--- a/src/type/sampled_texture_type_test.cc
+++ b/src/type/sampled_texture_type_test.cc
@@ -80,6 +80,11 @@
   EXPECT_EQ(s.type_name(), "__sampled_texture_3d__f32");
 }
 
+TEST_F(SampledTextureTest, FriendlyName) {
+  SampledTexture s(TextureDimension::k3d, ty.f32());
+  EXPECT_EQ(s.FriendlyName(Symbols()), "texture_3d<f32>");
+}
+
 TEST_F(SampledTextureTest, MinBufferBindingSize) {
   F32 f32;
   SampledTexture s(TextureDimension::kCube, &f32);
diff --git a/src/type/sampler_type.cc b/src/type/sampler_type.cc
index bff290b..01579ca 100644
--- a/src/type/sampler_type.cc
+++ b/src/type/sampler_type.cc
@@ -45,6 +45,10 @@
          (kind_ == SamplerKind::kSampler ? "sampler" : "comparison");
 }
 
+std::string Sampler::FriendlyName(const SymbolTable&) const {
+  return kind_ == SamplerKind::kSampler ? "sampler" : "sampler_comparison";
+}
+
 Sampler* Sampler::Clone(CloneContext* ctx) const {
   return ctx->dst->create<Sampler>(kind_);
 }
diff --git a/src/type/sampler_type.h b/src/type/sampler_type.h
index 8c71128..486446b 100644
--- a/src/type/sampler_type.h
+++ b/src/type/sampler_type.h
@@ -51,6 +51,11 @@
   /// @returns the name for this type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/sampler_type_test.cc b/src/type/sampler_type_test.cc
index 2648300..2b3184b 100644
--- a/src/type/sampler_type_test.cc
+++ b/src/type/sampler_type_test.cc
@@ -72,6 +72,16 @@
   EXPECT_EQ(s.type_name(), "__sampler_comparison");
 }
 
+TEST_F(SamplerTest, FriendlyNameSampler) {
+  Sampler s{SamplerKind::kSampler};
+  EXPECT_EQ(s.FriendlyName(Symbols()), "sampler");
+}
+
+TEST_F(SamplerTest, FriendlyNameComparisonSampler) {
+  Sampler s{SamplerKind::kComparisonSampler};
+  EXPECT_EQ(s.FriendlyName(Symbols()), "sampler_comparison");
+}
+
 TEST_F(SamplerTest, MinBufferBindingSize) {
   Sampler s{SamplerKind::kSampler};
   EXPECT_EQ(0u, s.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
diff --git a/src/type/storage_texture_type.cc b/src/type/storage_texture_type.cc
index 91be1ad..14f86aa 100644
--- a/src/type/storage_texture_type.cc
+++ b/src/type/storage_texture_type.cc
@@ -156,6 +156,12 @@
   return out.str();
 }
 
+std::string StorageTexture::FriendlyName(const SymbolTable&) const {
+  std::ostringstream out;
+  out << "texture_storage_" << dim() << "<" << image_format_ << ">";
+  return out.str();
+}
+
 StorageTexture* StorageTexture::Clone(CloneContext* ctx) const {
   return ctx->dst->create<StorageTexture>(dim(), image_format_,
                                           ctx->Clone(subtype_));
diff --git a/src/type/storage_texture_type.h b/src/type/storage_texture_type.h
index cff03e2..7342c57 100644
--- a/src/type/storage_texture_type.h
+++ b/src/type/storage_texture_type.h
@@ -87,6 +87,11 @@
   /// @returns the name for this type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/storage_texture_type_test.cc b/src/type/storage_texture_type_test.cc
index 95c498a..bbacb47 100644
--- a/src/type/storage_texture_type_test.cc
+++ b/src/type/storage_texture_type_test.cc
@@ -94,6 +94,15 @@
   EXPECT_EQ(s->type_name(), "__storage_texture_2d_array_rgba32float");
 }
 
+TEST_F(StorageTextureTest, FriendlyName) {
+  auto* subtype =
+      StorageTexture::SubtypeFor(ImageFormat::kRgba32Float, Types());
+  auto* s = create<StorageTexture>(TextureDimension::k2dArray,
+                                   ImageFormat::kRgba32Float, subtype);
+  EXPECT_EQ(s->FriendlyName(Symbols()),
+            "texture_storage_2d_array<rgba32float>");
+}
+
 TEST_F(StorageTextureTest, F32) {
   auto* subtype =
       type::StorageTexture::SubtypeFor(ImageFormat::kRgba32Float, Types());
diff --git a/src/type/struct_type.cc b/src/type/struct_type.cc
index 39efc95..05ba4c7 100644
--- a/src/type/struct_type.cc
+++ b/src/type/struct_type.cc
@@ -40,6 +40,10 @@
   return "__struct_" + symbol_.to_str();
 }
 
+std::string Struct::FriendlyName(const SymbolTable& symbols) const {
+  return symbols.NameFor(symbol_);
+}
+
 uint64_t Struct::MinBufferBindingSize(MemoryLayout mem_layout) const {
   if (!struct_->members().size()) {
     return 0;
diff --git a/src/type/struct_type.h b/src/type/struct_type.h
index 63fcaa2..eb0c37d 100644
--- a/src/type/struct_type.h
+++ b/src/type/struct_type.h
@@ -48,6 +48,11 @@
   /// @returns the name for the type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// @param mem_layout type of memory layout to use in calculation.
   /// @returns minimum size required for this type, in bytes.
   ///          0 for non-host shareable types.
diff --git a/src/type/struct_type_test.cc b/src/type/struct_type_test.cc
index e1e7fa0..4419587 100644
--- a/src/type/struct_type_test.cc
+++ b/src/type/struct_type_test.cc
@@ -73,6 +73,13 @@
   EXPECT_EQ(s->type_name(), "__struct_tint_symbol_1");
 }
 
+TEST_F(StructTypeTest, FriendlyName) {
+  auto* impl =
+      create<ast::Struct>(ast::StructMemberList{}, ast::StructDecorationList{});
+  auto* s = ty.struct_("my_struct", impl);
+  EXPECT_EQ(s->FriendlyName(Symbols()), "my_struct");
+}
+
 TEST_F(StructTypeTest, MinBufferBindingSize) {
   auto* str = create<ast::Struct>(
       ast::StructMemberList{Member("foo", ty.u32(), {MemberOffset(0)}),
diff --git a/src/type/type.h b/src/type/type.h
index 2834e7b..7c747c9 100644
--- a/src/type/type.h
+++ b/src/type/type.h
@@ -24,6 +24,7 @@
 
 // Forward declarations
 class ProgramBuilder;
+class SymbolTable;
 
 namespace type {
 
@@ -45,6 +46,11 @@
   /// @returns the name for this type. The type name is unique over all types.
   virtual std::string type_name() const = 0;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  virtual std::string FriendlyName(const SymbolTable& symbols) const = 0;
+
   /// @param mem_layout type of memory layout to use in calculation.
   /// @returns minimum size required for this type, in bytes.
   ///          0 for non-host shareable types.
diff --git a/src/type/u32_type.cc b/src/type/u32_type.cc
index 0736b05..b97fde6 100644
--- a/src/type/u32_type.cc
+++ b/src/type/u32_type.cc
@@ -32,6 +32,10 @@
   return "__u32";
 }
 
+std::string U32::FriendlyName(const SymbolTable&) const {
+  return "u32";
+}
+
 uint64_t U32::MinBufferBindingSize(MemoryLayout) const {
   return 4;
 }
diff --git a/src/type/u32_type.h b/src/type/u32_type.h
index 77d1eda..7e92a3a 100644
--- a/src/type/u32_type.h
+++ b/src/type/u32_type.h
@@ -34,6 +34,11 @@
   /// @returns the name for th type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// @param mem_layout type of memory layout to use in calculation.
   /// @returns minimum size required for this type, in bytes.
   ///          0 for non-host shareable types.
diff --git a/src/type/u32_type_test.cc b/src/type/u32_type_test.cc
index e0390ce..b9de713 100644
--- a/src/type/u32_type_test.cc
+++ b/src/type/u32_type_test.cc
@@ -56,6 +56,11 @@
   EXPECT_EQ(u.type_name(), "__u32");
 }
 
+TEST_F(U32Test, FriendlyName) {
+  U32 u;
+  EXPECT_EQ(u.FriendlyName(Symbols()), "u32");
+}
+
 TEST_F(U32Test, MinBufferBindingSize) {
   U32 u;
   EXPECT_EQ(4u, u.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
diff --git a/src/type/vector_type.cc b/src/type/vector_type.cc
index 96e3b16..84cdf24 100644
--- a/src/type/vector_type.cc
+++ b/src/type/vector_type.cc
@@ -38,6 +38,12 @@
   return "__vec_" + std::to_string(size_) + subtype_->type_name();
 }
 
+std::string Vector::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  out << "vec" << size_ << "<" << subtype_->FriendlyName(symbols) << ">";
+  return out.str();
+}
+
 uint64_t Vector::MinBufferBindingSize(MemoryLayout mem_layout) const {
   return size_ * subtype_->MinBufferBindingSize(mem_layout);
 }
diff --git a/src/type/vector_type.h b/src/type/vector_type.h
index e9f1af5..88e41f2 100644
--- a/src/type/vector_type.h
+++ b/src/type/vector_type.h
@@ -41,6 +41,11 @@
   /// @returns the name for th type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// @param mem_layout type of memory layout to use in calculation.
   /// @returns minimum size required for this type, in bytes.
   ///          0 for non-host shareable types.
diff --git a/src/type/vector_type_test.cc b/src/type/vector_type_test.cc
index 20656d7..51dfa4b 100644
--- a/src/type/vector_type_test.cc
+++ b/src/type/vector_type_test.cc
@@ -64,6 +64,11 @@
   EXPECT_EQ(v.type_name(), "__vec_3__i32");
 }
 
+TEST_F(VectorTest, FriendlyName) {
+  auto* v = ty.vec3<f32>();
+  EXPECT_EQ(v->FriendlyName(Symbols()), "vec3<f32>");
+}
+
 TEST_F(VectorTest, MinBufferBindingSizeVec2) {
   I32 i32;
   Vector v{&i32, 2};
diff --git a/src/type/void_type.cc b/src/type/void_type.cc
index fe2a1a6..342c5ec 100644
--- a/src/type/void_type.cc
+++ b/src/type/void_type.cc
@@ -32,6 +32,10 @@
   return "__void";
 }
 
+std::string Void::FriendlyName(const SymbolTable&) const {
+  return "void";
+}
+
 Void* Void::Clone(CloneContext* ctx) const {
   return ctx->dst->create<Void>();
 }
diff --git a/src/type/void_type.h b/src/type/void_type.h
index 1207808..406b36b 100644
--- a/src/type/void_type.h
+++ b/src/type/void_type.h
@@ -34,6 +34,11 @@
   /// @returns the name for this type
   std::string type_name() const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns the name for this type that closely resembles how it would be
+  /// declared in WGSL.
+  std::string FriendlyName(const SymbolTable& symbols) const override;
+
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type