tint/ast: Remove ast::StorageTexture

Instead use ast::TypeName.

Also improve the validation and diagnostics around providing template
arguments to types that do not accept them.

Bug: tint:1810
Change-Id: I4241d50ce0425ab721157686889e918993482876
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/119284
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@chromium.org>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 99d7c91..4c851e1 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -301,7 +301,6 @@
     "ast/sampled_texture.h",
     "ast/stage_attribute.h",
     "ast/statement.h",
-    "ast/storage_texture.h",
     "ast/stride_attribute.h",
     "ast/struct.h",
     "ast/struct_member.h",
@@ -665,8 +664,6 @@
     "ast/stage_attribute.h",
     "ast/statement.cc",
     "ast/statement.h",
-    "ast/storage_texture.cc",
-    "ast/storage_texture.h",
     "ast/stride_attribute.cc",
     "ast/stride_attribute.h",
     "ast/struct.cc",
@@ -1279,6 +1276,18 @@
     }
   }
 
+  tint_unittests_source_set("libtint_unittests_ast_helper") {
+    sources = [
+      "ast/test_helper.h",
+      "ast/test_helper_test.cc",
+    ]
+    deps = [
+      ":libtint_ast_src",
+      ":libtint_base_src",
+      ":libtint_syntax_tree_src",
+    ]
+  }
+
   tint_unittests_source_set("tint_unittests_ast_src") {
     sources = [
       "ast/alias_test.cc",
@@ -1333,7 +1342,6 @@
       "ast/return_statement_test.cc",
       "ast/sampled_texture_test.cc",
       "ast/stage_attribute_test.cc",
-      "ast/storage_texture_test.cc",
       "ast/stride_attribute_test.cc",
       "ast/struct_member_align_attribute_test.cc",
       "ast/struct_member_offset_attribute_test.cc",
@@ -1342,8 +1350,6 @@
       "ast/struct_test.cc",
       "ast/switch_statement_test.cc",
       "ast/templated_identifier_test.cc",
-      "ast/test_helper.h",
-      "ast/test_helper_test.cc",
       "ast/texture_test.cc",
       "ast/traverse_expressions_test.cc",
       "ast/type_name_test.cc",
@@ -1358,6 +1364,7 @@
       ":libtint_ast_src",
       ":libtint_base_src",
       ":libtint_transform_src",
+      ":libtint_unittests_ast_helper",
     ]
 
     if (tint_build_wgsl_reader && tint_build_wgsl_writer) {
@@ -1797,6 +1804,7 @@
 
     deps = [
       ":libtint_base_src",
+      ":libtint_unittests_ast_helper",
       ":libtint_wgsl_reader_src",
     ]
   }
@@ -1993,6 +2001,7 @@
 
     deps = [
       ":libtint_base_src",
+      ":libtint_unittests_ast_helper",
       ":tint_unittests_ast_src",
     ]
   }
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index d3d3097..e80e1f8 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -201,8 +201,6 @@
   ast/stage_attribute.h
   ast/statement.cc
   ast/statement.h
-  ast/storage_texture.cc
-  ast/storage_texture.h
   ast/stride_attribute.cc
   ast/stride_attribute.h
   ast/struct_member_align_attribute.cc
@@ -856,7 +854,6 @@
     ast/return_statement_test.cc
     ast/sampled_texture_test.cc
     ast/stage_attribute_test.cc
-    ast/storage_texture_test.cc
     ast/stride_attribute_test.cc
     ast/struct_member_align_attribute_test.cc
     ast/struct_member_offset_attribute_test.cc
diff --git a/src/tint/ast/multisampled_texture_test.cc b/src/tint/ast/multisampled_texture_test.cc
index 2b81a0a..6b8125c 100644
--- a/src/tint/ast/multisampled_texture_test.cc
+++ b/src/tint/ast/multisampled_texture_test.cc
@@ -19,7 +19,6 @@
 #include "src/tint/ast/matrix.h"
 #include "src/tint/ast/pointer.h"
 #include "src/tint/ast/sampled_texture.h"
-#include "src/tint/ast/storage_texture.h"
 #include "src/tint/ast/struct.h"
 #include "src/tint/ast/test_helper.h"
 #include "src/tint/ast/texture.h"
@@ -35,7 +34,6 @@
     Texture* t = create<MultisampledTexture>(type::TextureDimension::kCube, ty.f32());
     EXPECT_TRUE(t->Is<MultisampledTexture>());
     EXPECT_FALSE(t->Is<SampledTexture>());
-    EXPECT_FALSE(t->Is<StorageTexture>());
 }
 
 TEST_F(AstMultisampledTextureTest, Dim) {
diff --git a/src/tint/ast/sampled_texture_test.cc b/src/tint/ast/sampled_texture_test.cc
index 506b259..01d7b65 100644
--- a/src/tint/ast/sampled_texture_test.cc
+++ b/src/tint/ast/sampled_texture_test.cc
@@ -24,7 +24,6 @@
 TEST_F(AstSampledTextureTest, IsTexture) {
     Texture* t = create<SampledTexture>(type::TextureDimension::kCube, ty.f32());
     EXPECT_TRUE(t->Is<SampledTexture>());
-    EXPECT_FALSE(t->Is<StorageTexture>());
 }
 
 TEST_F(AstSampledTextureTest, Dim) {
diff --git a/src/tint/ast/storage_texture.cc b/src/tint/ast/storage_texture.cc
deleted file mode 100644
index cb2cee7..0000000
--- a/src/tint/ast/storage_texture.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/storage_texture.h"
-
-#include "src/tint/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::StorageTexture);
-
-namespace tint::ast {
-
-StorageTexture::StorageTexture(ProgramID pid,
-                               NodeID nid,
-                               const Source& src,
-                               type::TextureDimension d,
-                               type::TexelFormat fmt,
-                               const Type* subtype,
-                               type::Access ac)
-    : Base(pid, nid, src, d), format(fmt), type(subtype), access(ac) {}
-
-StorageTexture::StorageTexture(StorageTexture&&) = default;
-
-StorageTexture::~StorageTexture() = default;
-
-std::string StorageTexture::FriendlyName(const SymbolTable&) const {
-    std::ostringstream out;
-    out << "texture_storage_" << dim << "<" << format << ", " << access << ">";
-    return out.str();
-}
-
-const StorageTexture* StorageTexture::Clone(CloneContext* ctx) const {
-    // Clone arguments outside of create() call to have deterministic ordering
-    auto src = ctx->Clone(source);
-    auto* ty = ctx->Clone(type);
-    return ctx->dst->create<StorageTexture>(src, dim, format, ty, access);
-}
-
-const Type* StorageTexture::SubtypeFor(type::TexelFormat format, ProgramBuilder& builder) {
-    switch (format) {
-        case type::TexelFormat::kR32Uint:
-        case type::TexelFormat::kRgba8Uint:
-        case type::TexelFormat::kRg32Uint:
-        case type::TexelFormat::kRgba16Uint:
-        case type::TexelFormat::kRgba32Uint: {
-            return builder.ty.u32();
-        }
-
-        case type::TexelFormat::kR32Sint:
-        case type::TexelFormat::kRgba8Sint:
-        case type::TexelFormat::kRg32Sint:
-        case type::TexelFormat::kRgba16Sint:
-        case type::TexelFormat::kRgba32Sint: {
-            return builder.ty.i32();
-        }
-
-        case type::TexelFormat::kBgra8Unorm:
-        case type::TexelFormat::kRgba8Unorm:
-        case type::TexelFormat::kRgba8Snorm:
-        case type::TexelFormat::kR32Float:
-        case type::TexelFormat::kRg32Float:
-        case type::TexelFormat::kRgba16Float:
-        case type::TexelFormat::kRgba32Float: {
-            return builder.ty.f32();
-        }
-
-        case type::TexelFormat::kUndefined:
-            break;
-    }
-
-    return nullptr;
-}
-
-}  // namespace tint::ast
diff --git a/src/tint/ast/storage_texture.h b/src/tint/ast/storage_texture.h
deleted file mode 100644
index 646bca0..0000000
--- a/src/tint/ast/storage_texture.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_AST_STORAGE_TEXTURE_H_
-#define SRC_TINT_AST_STORAGE_TEXTURE_H_
-
-#include <string>
-
-#include "src/tint/ast/texture.h"
-#include "src/tint/type/access.h"
-#include "src/tint/type/texel_format.h"
-#include "src/tint/type/texture_dimension.h"
-
-namespace tint::ast {
-
-/// A storage texture type.
-class StorageTexture final : public Castable<StorageTexture, Texture> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param src the source of this node
-    /// @param dim the dimensionality of the texture
-    /// @param format the image format of the texture
-    /// @param subtype the storage subtype. Use SubtypeFor() to calculate this.
-    /// @param access_control the access control for the texture.
-    StorageTexture(ProgramID pid,
-                   NodeID nid,
-                   const Source& src,
-                   type::TextureDimension dim,
-                   type::TexelFormat format,
-                   const Type* subtype,
-                   type::Access access_control);
-
-    /// Move constructor
-    StorageTexture(StorageTexture&&);
-    ~StorageTexture() override;
-
-    /// @param symbols the program's symbol table
-    /// @returns the name for this type that closely resembles how it would be
-    /// declared in WGSL.
-    std::string FriendlyName(const SymbolTable& symbols) const override;
-
-    /// Clones this type and all transitive types using the `CloneContext` `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned type
-    const StorageTexture* Clone(CloneContext* ctx) const override;
-
-    /// @param format the storage texture image format
-    /// @param builder the ProgramBuilder used to build the returned type
-    /// @returns the storage texture subtype for the given TexelFormat
-    static const Type* SubtypeFor(type::TexelFormat format, ProgramBuilder& builder);
-
-    /// The image format
-    const type::TexelFormat format;
-
-    /// The storage subtype
-    const Type* const type;
-
-    /// The access control
-    const type::Access access;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_AST_STORAGE_TEXTURE_H_
diff --git a/src/tint/ast/storage_texture_test.cc b/src/tint/ast/storage_texture_test.cc
deleted file mode 100644
index e03c4b3..0000000
--- a/src/tint/ast/storage_texture_test.cc
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2020 The Tint Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/storage_texture.h"
-
-#include "src/tint/ast/test_helper.h"
-
-namespace tint::ast {
-namespace {
-
-using AstStorageTextureTest = TestHelper;
-
-TEST_F(AstStorageTextureTest, IsTexture) {
-    auto* subtype = StorageTexture::SubtypeFor(type::TexelFormat::kRgba32Float, *this);
-    Texture* ty =
-        create<StorageTexture>(type::TextureDimension::k2dArray, type::TexelFormat::kRgba32Float,
-                               subtype, type::Access::kRead);
-    EXPECT_FALSE(ty->Is<SampledTexture>());
-    EXPECT_TRUE(ty->Is<StorageTexture>());
-}
-
-TEST_F(AstStorageTextureTest, Dim) {
-    auto* subtype = StorageTexture::SubtypeFor(type::TexelFormat::kRgba32Float, *this);
-    auto* s = create<StorageTexture>(type::TextureDimension::k2dArray,
-                                     type::TexelFormat::kRgba32Float, subtype, type::Access::kRead);
-    EXPECT_EQ(s->dim, type::TextureDimension::k2dArray);
-}
-
-TEST_F(AstStorageTextureTest, Format) {
-    auto* subtype = StorageTexture::SubtypeFor(type::TexelFormat::kRgba32Float, *this);
-    auto* s = create<StorageTexture>(type::TextureDimension::k2dArray,
-                                     type::TexelFormat::kRgba32Float, subtype, type::Access::kRead);
-    EXPECT_EQ(s->format, type::TexelFormat::kRgba32Float);
-}
-
-TEST_F(AstStorageTextureTest, FriendlyName) {
-    auto* subtype = StorageTexture::SubtypeFor(type::TexelFormat::kRgba32Float, *this);
-    auto* s = create<StorageTexture>(type::TextureDimension::k2dArray,
-                                     type::TexelFormat::kRgba32Float, subtype, type::Access::kRead);
-    EXPECT_EQ(s->FriendlyName(Symbols()), "texture_storage_2d_array<rgba32float, read>");
-}
-
-TEST_F(AstStorageTextureTest, F32) {
-    auto* subtype = StorageTexture::SubtypeFor(type::TexelFormat::kRgba32Float, *this);
-    Type* s = create<StorageTexture>(type::TextureDimension::k2dArray,
-                                     type::TexelFormat::kRgba32Float, subtype, type::Access::kRead);
-
-    ASSERT_TRUE(s->Is<Texture>());
-    ASSERT_TRUE(s->Is<StorageTexture>());
-    ASSERT_TRUE(s->As<StorageTexture>()->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(s->As<StorageTexture>()->type->As<ast::TypeName>()->name->symbol),
-              "f32");
-}
-
-TEST_F(AstStorageTextureTest, U32) {
-    auto* subtype = StorageTexture::SubtypeFor(type::TexelFormat::kRg32Uint, *this);
-    Type* s = create<StorageTexture>(type::TextureDimension::k2dArray, type::TexelFormat::kRg32Uint,
-                                     subtype, type::Access::kRead);
-
-    ASSERT_TRUE(s->Is<Texture>());
-    ASSERT_TRUE(s->Is<StorageTexture>());
-    ASSERT_TRUE(s->As<StorageTexture>()->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(s->As<StorageTexture>()->type->As<ast::TypeName>()->name->symbol),
-              "u32");
-}
-
-TEST_F(AstStorageTextureTest, I32) {
-    auto* subtype = StorageTexture::SubtypeFor(type::TexelFormat::kRgba32Sint, *this);
-    Type* s = create<StorageTexture>(type::TextureDimension::k2dArray,
-                                     type::TexelFormat::kRgba32Sint, subtype, type::Access::kRead);
-
-    ASSERT_TRUE(s->Is<Texture>());
-    ASSERT_TRUE(s->Is<StorageTexture>());
-    ASSERT_TRUE(s->As<StorageTexture>()->type->Is<ast::TypeName>());
-    EXPECT_EQ(Symbols().NameFor(s->As<StorageTexture>()->type->As<ast::TypeName>()->name->symbol),
-              "i32");
-}
-
-}  // namespace
-}  // namespace tint::ast
diff --git a/src/tint/inspector/resource_binding.h b/src/tint/inspector/resource_binding.h
index d968604..4c1e0c7 100644
--- a/src/tint/inspector/resource_binding.h
+++ b/src/tint/inspector/resource_binding.h
@@ -17,7 +17,7 @@
 
 #include <cstdint>
 
-#include "src/tint/ast/storage_texture.h"
+#include "src/tint/type/texel_format.h"
 #include "src/tint/type/texture_dimension.h"
 #include "src/tint/type/type.h"
 
diff --git a/src/tint/intrinsics.def b/src/tint/intrinsics.def
index db589a1..c490482 100644
--- a/src/tint/intrinsics.def
+++ b/src/tint/intrinsics.def
@@ -176,6 +176,11 @@
   texture_depth_cube
   texture_depth_cube_array
   texture_depth_multisampled_2d
+  // https://www.w3.org/TR/WGSL/#texture-storage
+  texture_storage_1d
+  texture_storage_2d
+  texture_storage_2d_array
+  texture_storage_3d
   // https://www.w3.org/TR/WGSL/#external-texture-type
   texture_external
 }
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index c24dc25..0541511 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -68,7 +68,6 @@
 #include "src/tint/ast/return_statement.h"
 #include "src/tint/ast/sampled_texture.h"
 #include "src/tint/ast/stage_attribute.h"
-#include "src/tint/ast/storage_texture.h"
 #include "src/tint/ast/stride_attribute.h"
 #include "src/tint/ast/struct_member_align_attribute.h"
 #include "src/tint/ast/struct_member_offset_attribute.h"
@@ -1063,25 +1062,41 @@
         /// @param dims the dimensionality of the texture
         /// @param format the texel format of the texture
         /// @param access the access control of the texture
-        /// @returns the storage texture
-        const ast::StorageTexture* storage_texture(type::TextureDimension dims,
-                                                   type::TexelFormat format,
-                                                   type::Access access) const {
-            auto* subtype = ast::StorageTexture::SubtypeFor(format, *builder);
-            return builder->create<ast::StorageTexture>(dims, format, subtype, access);
+        /// @returns the storage texture typename
+        const ast::TypeName* storage_texture(type::TextureDimension dims,
+                                             type::TexelFormat format,
+                                             type::Access access) const {
+            return storage_texture(builder->source_, dims, format, access);
         }
 
         /// @param source the Source of the node
         /// @param dims the dimensionality of the texture
         /// @param format the texel format of the texture
         /// @param access the access control of the texture
-        /// @returns the storage texture
-        const ast::StorageTexture* storage_texture(const Source& source,
-                                                   type::TextureDimension dims,
-                                                   type::TexelFormat format,
-                                                   type::Access access) const {
-            auto* subtype = ast::StorageTexture::SubtypeFor(format, *builder);
-            return builder->create<ast::StorageTexture>(source, dims, format, subtype, access);
+        /// @returns the storage texture typename
+        const ast::TypeName* storage_texture(const Source& source,
+                                             type::TextureDimension dims,
+                                             type::TexelFormat format,
+                                             type::Access access) const {
+            switch (dims) {
+                case type::TextureDimension::k1d:
+                    return (*this)(source, "texture_storage_1d", utils::ToString(format),
+                                   utils::ToString(access));
+                case type::TextureDimension::k2d:
+                    return (*this)(source, "texture_storage_2d", utils::ToString(format),
+                                   utils::ToString(access));
+                case type::TextureDimension::k2dArray:
+                    return (*this)(source, "texture_storage_2d_array", utils::ToString(format),
+                                   utils::ToString(access));
+                case type::TextureDimension::k3d:
+                    return (*this)(source, "texture_storage_3d", utils::ToString(format),
+                                   utils::ToString(access));
+                default:
+                    break;
+            }
+            TINT_ICE(ProgramBuilder, builder->Diagnostics())
+                << "invalid sampled_texture dimensions: " << dims;
+            return nullptr;
         }
 
         /// @returns the external texture
diff --git a/src/tint/reader/spirv/parser_type.h b/src/tint/reader/spirv/parser_type.h
index 33f7e05..04c8c67 100644
--- a/src/tint/reader/spirv/parser_type.h
+++ b/src/tint/reader/spirv/parser_type.h
@@ -19,11 +19,12 @@
 #include <string>
 #include <vector>
 
-#include "src/tint/ast/storage_texture.h"
 #include "src/tint/castable.h"
+#include "src/tint/symbol.h"
 #include "src/tint/type/access.h"
 #include "src/tint/type/address_space.h"
 #include "src/tint/type/sampler_kind.h"
+#include "src/tint/type/texel_format.h"
 #include "src/tint/type/texture_dimension.h"
 #include "src/tint/utils/block_allocator.h"
 
diff --git a/src/tint/reader/wgsl/parser_impl_texture_sampler_test.cc b/src/tint/reader/wgsl/parser_impl_texture_sampler_test.cc
index 29d62c5..37dbd97 100644
--- a/src/tint/reader/wgsl/parser_impl_texture_sampler_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_texture_sampler_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/ast/test_helper.h"
 #include "src/tint/reader/wgsl/parser_impl_test_helper.h"
 #include "src/tint/type/depth_texture.h"
 #include "src/tint/type/multisampled_texture.h"
@@ -195,11 +196,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
 
-    ASSERT_TRUE(t->Is<ast::Texture>());
-    ASSERT_TRUE(t->Is<ast::StorageTexture>());
-    EXPECT_EQ(t->As<ast::StorageTexture>()->format, type::TexelFormat::kRg32Float);
-    EXPECT_EQ(t->As<ast::StorageTexture>()->access, type::Access::kRead);
-    EXPECT_EQ(t->As<ast::Texture>()->dim, type::TextureDimension::k1d);
+    ast::CheckIdentifier(p->builder().Symbols(), t->As<ast::TypeName>()->name,
+                         ast::Template("texture_storage_1d", "rg32float", "read"));
     EXPECT_EQ(t->source.range, (Source::Range{{1u, 1u}, {1u, 36u}}));
 }
 
@@ -211,11 +209,8 @@
     EXPECT_FALSE(t.errored);
     ASSERT_NE(t.value, nullptr);
 
-    ASSERT_TRUE(t->Is<ast::Texture>());
-    ASSERT_TRUE(t->Is<ast::StorageTexture>());
-    EXPECT_EQ(t->As<ast::StorageTexture>()->format, type::TexelFormat::kR32Uint);
-    EXPECT_EQ(t->As<ast::StorageTexture>()->access, type::Access::kWrite);
-    EXPECT_EQ(t->As<ast::Texture>()->dim, type::TextureDimension::k2d);
+    ast::CheckIdentifier(p->builder().Symbols(), t->As<ast::TypeName>()->name,
+                         ast::Template("texture_storage_2d", "r32uint", "write"));
     EXPECT_EQ(t->source.range, (Source::Range{{1u, 1u}, {1u, 35u}}));
 }
 
diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc
index 9358335..e581b9a 100644
--- a/src/tint/resolver/dependency_graph.cc
+++ b/src/tint/resolver/dependency_graph.cc
@@ -48,7 +48,6 @@
 #include "src/tint/ast/return_statement.h"
 #include "src/tint/ast/sampled_texture.h"
 #include "src/tint/ast/stage_attribute.h"
-#include "src/tint/ast/storage_texture.h"
 #include "src/tint/ast/stride_attribute.h"
 #include "src/tint/ast/struct.h"
 #include "src/tint/ast/struct_member_align_attribute.h"
@@ -338,31 +337,44 @@
         }
     }
 
-    /// Traverses the expression, performing symbol resolution and determining
-    /// global dependencies.
+    /// Traverses the expression, performing symbol resolution and determining global dependencies.
     void TraverseExpression(const ast::Expression* root) {
         if (!root) {
             return;
         }
-        ast::TraverseExpressions(root, diagnostics_, [&](const ast::Expression* expr) {
-            Switch(
-                expr,
-                [&](const ast::IdentifierExpression* ident) {
-                    AddDependency(ident->identifier, ident->identifier->symbol, "identifier",
-                                  "references");
-                },
-                [&](const ast::CallExpression* call) {
-                    if (call->target.name) {
-                        AddDependency(call->target.name, call->target.name->symbol, "function",
-                                      "calls");
-                    }
-                    if (call->target.type) {
-                        TraverseType(call->target.type);
-                    }
-                },
-                [&](const ast::BitcastExpression* cast) { TraverseType(cast->type); });
-            return ast::TraverseAction::Descend;
-        });
+        utils::Vector<const ast::Expression*, 8> pending{root};
+        while (!pending.IsEmpty()) {
+            ast::TraverseExpressions(pending.Pop(), diagnostics_, [&](const ast::Expression* expr) {
+                Switch(
+                    expr,
+                    [&](const ast::IdentifierExpression* e) {
+                        AddDependency(e->identifier, e->identifier->symbol, "identifier",
+                                      "references");
+                        if (auto* tmpl_ident = e->identifier->As<ast::TemplatedIdentifier>()) {
+                            for (auto* arg : tmpl_ident->arguments) {
+                                pending.Push(arg);
+                            }
+                        }
+                    },
+                    [&](const ast::CallExpression* call) {
+                        if (call->target.name) {
+                            AddDependency(call->target.name, call->target.name->symbol, "function",
+                                          "calls");
+                            if (auto* tmpl_ident =
+                                    call->target.name->As<ast::TemplatedIdentifier>()) {
+                                for (auto* arg : tmpl_ident->arguments) {
+                                    pending.Push(arg);
+                                }
+                            }
+                        }
+                        if (call->target.type) {
+                            TraverseType(call->target.type);
+                        }
+                    },
+                    [&](const ast::BitcastExpression* cast) { TraverseType(cast->type); });
+                return ast::TraverseAction::Descend;
+            });
+        }
     }
 
     /// Traverses the type node, performing symbol resolution and determining
@@ -388,6 +400,11 @@
             },
             [&](const ast::TypeName* tn) {  //
                 AddDependency(tn->name, tn->name->symbol, "type", "references");
+                if (auto* tmpl_ident = tn->name->As<ast::TemplatedIdentifier>()) {
+                    for (auto* arg : tmpl_ident->arguments) {
+                        TraverseExpression(arg);
+                    }
+                }
             },
             [&](const ast::Vector* vec) {  //
                 TraverseType(vec->type);
@@ -398,9 +415,6 @@
             [&](const ast::MultisampledTexture* tex) {  //
                 TraverseType(tex->type);
             },
-            [&](const ast::StorageTexture* tex) {  //
-                TraverseType(tex->type);
-            },
             [&](Default) { UnhandledNode(diagnostics_, ty); });
     }
 
diff --git a/src/tint/resolver/expression_kind_test.cc b/src/tint/resolver/expression_kind_test.cc
index 8581560..7737654 100644
--- a/src/tint/resolver/expression_kind_test.cc
+++ b/src/tint/resolver/expression_kind_test.cc
@@ -121,7 +121,7 @@
     Symbol sym;
     switch (GetParam().def) {
         case Def::kAccess:
-            sym = Sym("read_write");
+            sym = Sym("write");
             break;
         case Def::kAddressSpace:
             sym = Sym("workgroup");
@@ -155,7 +155,8 @@
 
     switch (GetParam().use) {
         case Use::kAccess:
-            return;  // TODO(crbug.com/tint/1810)
+            GlobalVar("v", ty("texture_storage_2d", "rgba8unorm", sym), Group(0_u), Binding(0_u));
+            break;
         case Use::kAddressSpace:
             return;  // TODO(crbug.com/tint/1810)
         case Use::kCallExpr:
@@ -174,7 +175,8 @@
             Structure("s", utils::Vector{Member("m", ty(kUseSource, sym))});
             break;
         case Use::kTexelFormat:
-            return;  // TODO(crbug.com/tint/1810)
+            GlobalVar("v", ty("texture_storage_2d", sym, "write"), Group(0_u), Binding(0_u));
+            break;
         case Use::kValueExpression:
             GlobalVar("v", type::AddressSpace::kPrivate, Expr(kUseSource, sym));
             break;
@@ -201,21 +203,18 @@
     testing::ValuesIn(std::vector<Case>{
         {Def::kAccess, Use::kAccess, kPass},
         {Def::kAccess, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
-        {Def::kAccess, Use::kBinaryOp, R"(5:6 error: cannot use access 'read_write' as value)"},
-        {Def::kAccess, Use::kCallExpr,
-         R"(5:6 error: cannot use access 'read_write' as call target)"},
-        {Def::kAccess, Use::kCallStmt,
-         R"(5:6 error: cannot use access 'read_write' as call target)"},
-        {Def::kAccess, Use::kFunctionReturnType,
-         R"(5:6 error: cannot use access 'read_write' as type)"},
-        {Def::kAccess, Use::kMemberType, R"(5:6 error: cannot use access 'read_write' as type)"},
-        {Def::kAccess, Use::kTexelFormat, R"(TODO(crbug.com/tint/1810))"},
-        {Def::kAccess, Use::kValueExpression,
-         R"(5:6 error: cannot use access 'read_write' as value)"},
-        {Def::kAccess, Use::kVariableType, R"(5:6 error: cannot use access 'read_write' as type)"},
-        {Def::kAccess, Use::kUnaryOp, R"(5:6 error: cannot use access 'read_write' as value)"},
+        {Def::kAccess, Use::kBinaryOp, R"(5:6 error: cannot use access 'write' as value)"},
+        {Def::kAccess, Use::kCallExpr, R"(5:6 error: cannot use access 'write' as call target)"},
+        {Def::kAccess, Use::kCallStmt, R"(5:6 error: cannot use access 'write' as call target)"},
+        {Def::kAccess, Use::kFunctionReturnType, R"(5:6 error: cannot use access 'write' as type)"},
+        {Def::kAccess, Use::kMemberType, R"(5:6 error: cannot use access 'write' as type)"},
+        {Def::kAccess, Use::kTexelFormat, R"(error: cannot use access 'write' as texel format)"},
+        {Def::kAccess, Use::kValueExpression, R"(5:6 error: cannot use access 'write' as value)"},
+        {Def::kAccess, Use::kVariableType, R"(5:6 error: cannot use access 'write' as type)"},
+        {Def::kAccess, Use::kUnaryOp, R"(5:6 error: cannot use access 'write' as value)"},
 
-        {Def::kAddressSpace, Use::kAccess, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kAddressSpace, Use::kAccess,
+         R"(error: cannot use address space 'workgroup' as access)"},
         {Def::kAddressSpace, Use::kAddressSpace, kPass},
         {Def::kAddressSpace, Use::kBinaryOp,
          R"(5:6 error: cannot use address space 'workgroup' as value)"},
@@ -227,7 +226,8 @@
          R"(5:6 error: cannot use address space 'workgroup' as type)"},
         {Def::kAddressSpace, Use::kMemberType,
          R"(5:6 error: cannot use address space 'workgroup' as type)"},
-        {Def::kAddressSpace, Use::kTexelFormat, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kAddressSpace, Use::kTexelFormat,
+         R"(error: cannot use address space 'workgroup' as texel format)"},
         {Def::kAddressSpace, Use::kValueExpression,
          R"(5:6 error: cannot use address space 'workgroup' as value)"},
         {Def::kAddressSpace, Use::kVariableType,
@@ -235,7 +235,7 @@
         {Def::kAddressSpace, Use::kUnaryOp,
          R"(5:6 error: cannot use address space 'workgroup' as value)"},
 
-        {Def::kBuiltinFunction, Use::kAccess, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kBuiltinFunction, Use::kAccess, R"(error: missing '(' for builtin function call)"},
         {Def::kBuiltinFunction, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
         {Def::kBuiltinFunction, Use::kBinaryOp,
          R"(7:8 error: missing '(' for builtin function call)"},
@@ -244,7 +244,8 @@
          R"(5:6 error: cannot use builtin function 'workgroupBarrier' as type)"},
         {Def::kBuiltinFunction, Use::kMemberType,
          R"(5:6 error: cannot use builtin function 'workgroupBarrier' as type)"},
-        {Def::kBuiltinFunction, Use::kTexelFormat, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kBuiltinFunction, Use::kTexelFormat,
+         R"(error: missing '(' for builtin function call)"},
         {Def::kBuiltinFunction, Use::kValueExpression,
          R"(7:8 error: missing '(' for builtin function call)"},
         {Def::kBuiltinFunction, Use::kVariableType,
@@ -252,7 +253,7 @@
         {Def::kBuiltinFunction, Use::kUnaryOp,
          R"(7:8 error: missing '(' for builtin function call)"},
 
-        {Def::kBuiltinType, Use::kAccess, kPass},
+        {Def::kBuiltinType, Use::kAccess, R"(error: cannot use type 'vec4<f32>' as access)"},
         {Def::kBuiltinType, Use::kAddressSpace, kPass},
         {Def::kBuiltinType, Use::kBinaryOp,
          R"(5:6 error: cannot use type 'vec4<f32>' as value
@@ -260,7 +261,8 @@
         {Def::kBuiltinType, Use::kCallExpr, kPass},
         {Def::kBuiltinType, Use::kFunctionReturnType, kPass},
         {Def::kBuiltinType, Use::kMemberType, kPass},
-        {Def::kBuiltinType, Use::kTexelFormat, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kBuiltinType, Use::kTexelFormat,
+         R"(error: cannot use type 'vec4<f32>' as texel format)"},
         {Def::kBuiltinType, Use::kValueExpression,
          R"(5:6 error: cannot use type 'vec4<f32>' as value
 7:8 note: are you missing '()' for type initializer?)"},
@@ -269,43 +271,44 @@
          R"(5:6 error: cannot use type 'vec4<f32>' as value
 7:8 note: are you missing '()' for type initializer?)"},
 
-        {Def::kFunction, Use::kAccess, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kFunction, Use::kAccess, R"(error: missing '(' for function call)"},
         {Def::kFunction, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
         {Def::kFunction, Use::kBinaryOp, R"(7:8 error: missing '(' for function call)"},
         {Def::kFunction, Use::kCallExpr, kPass},
         {Def::kFunction, Use::kCallStmt, kPass},
         {Def::kFunction, Use::kFunctionReturnType,
          R"(5:6 error: cannot use function 'FUNCTION' as type
-1:2 note: 'FUNCTION' declared here)"},
+1:2 note: function 'FUNCTION' declared here)"},
         {Def::kFunction, Use::kMemberType,
          R"(5:6 error: cannot use function 'FUNCTION' as type
-1:2 note: 'FUNCTION' declared here)"},
-        {Def::kFunction, Use::kTexelFormat, R"(TODO(crbug.com/tint/1810))"},
+1:2 note: function 'FUNCTION' declared here)"},
+        {Def::kFunction, Use::kTexelFormat, R"(error: missing '(' for function call)"},
         {Def::kFunction, Use::kValueExpression, R"(7:8 error: missing '(' for function call)"},
         {Def::kFunction, Use::kVariableType,
          R"(5:6 error: cannot use function 'FUNCTION' as type
-1:2 note: 'FUNCTION' declared here)"},
+1:2 note: function 'FUNCTION' declared here)"},
         {Def::kFunction, Use::kUnaryOp, R"(7:8 error: missing '(' for function call)"},
 
-        {Def::kStruct, Use::kAccess, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kStruct, Use::kAccess, R"(error: cannot use type 'STRUCT' as access)"},
         {Def::kStruct, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
         {Def::kStruct, Use::kBinaryOp, R"(5:6 error: cannot use type 'STRUCT' as value
 7:8 note: are you missing '()' for type initializer?
-1:2 note: 'STRUCT' declared here)"},
+1:2 note: struct 'STRUCT' declared here)"},
         {Def::kStruct, Use::kFunctionReturnType, kPass},
         {Def::kStruct, Use::kMemberType, kPass},
-        {Def::kStruct, Use::kTexelFormat, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kStruct, Use::kTexelFormat, R"(error: cannot use type 'STRUCT' as texel format)"},
         {Def::kStruct, Use::kValueExpression,
          R"(5:6 error: cannot use type 'STRUCT' as value
 7:8 note: are you missing '()' for type initializer?
-1:2 note: 'STRUCT' declared here)"},
+1:2 note: struct 'STRUCT' declared here)"},
         {Def::kStruct, Use::kVariableType, kPass},
         {Def::kStruct, Use::kUnaryOp,
          R"(5:6 error: cannot use type 'STRUCT' as value
 7:8 note: are you missing '()' for type initializer?
-1:2 note: 'STRUCT' declared here)"},
+1:2 note: struct 'STRUCT' declared here)"},
 
-        {Def::kTexelFormat, Use::kAccess, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kTexelFormat, Use::kAccess,
+         R"(error: cannot use texel format 'rgba8unorm' as access)"},
         {Def::kTexelFormat, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
         {Def::kTexelFormat, Use::kBinaryOp,
          R"(5:6 error: cannot use texel format 'rgba8unorm' as value)"},
@@ -325,7 +328,7 @@
         {Def::kTexelFormat, Use::kUnaryOp,
          R"(5:6 error: cannot use texel format 'rgba8unorm' as value)"},
 
-        {Def::kTypeAlias, Use::kAccess, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kTypeAlias, Use::kAccess, R"(error: cannot use type 'i32' as access)"},
         {Def::kTypeAlias, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
         {Def::kTypeAlias, Use::kBinaryOp,
          R"(5:6 error: cannot use type 'i32' as value
@@ -333,7 +336,7 @@
         {Def::kTypeAlias, Use::kCallExpr, kPass},
         {Def::kTypeAlias, Use::kFunctionReturnType, kPass},
         {Def::kTypeAlias, Use::kMemberType, kPass},
-        {Def::kTypeAlias, Use::kTexelFormat, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kTypeAlias, Use::kTexelFormat, R"(error: cannot use type 'i32' as texel format)"},
         {Def::kTypeAlias, Use::kValueExpression,
          R"(5:6 error: cannot use type 'i32' as value
 7:8 note: are you missing '()' for type initializer?)"},
@@ -342,26 +345,27 @@
          R"(5:6 error: cannot use type 'i32' as value
 7:8 note: are you missing '()' for type initializer?)"},
 
-        {Def::kVariable, Use::kAccess, R"(TODO(crbug.com/tint/1810))"},
+        {Def::kVariable, Use::kAccess, R"(error: cannot use 'VARIABLE' of type 'i32' as access)"},
         {Def::kVariable, Use::kAddressSpace, R"(TODO(crbug.com/tint/1810))"},
         {Def::kVariable, Use::kBinaryOp, kPass},
         {Def::kVariable, Use::kCallStmt,
          R"(5:6 error: cannot use const 'VARIABLE' as call target
-1:2 note: 'VARIABLE' declared here)"},
+1:2 note: const 'VARIABLE' declared here)"},
         {Def::kVariable, Use::kCallExpr,
          R"(5:6 error: cannot use const 'VARIABLE' as call target
-1:2 note: 'VARIABLE' declared here)"},
+1:2 note: const 'VARIABLE' declared here)"},
         {Def::kVariable, Use::kFunctionReturnType,
          R"(5:6 error: cannot use const 'VARIABLE' as type
-1:2 note: 'VARIABLE' declared here)"},
+1:2 note: const 'VARIABLE' declared here)"},
         {Def::kVariable, Use::kMemberType,
          R"(5:6 error: cannot use const 'VARIABLE' as type
-1:2 note: 'VARIABLE' declared here)"},
-        {Def::kVariable, Use::kTexelFormat, R"(TODO(crbug.com/tint/1810))"},
+1:2 note: const 'VARIABLE' declared here)"},
+        {Def::kVariable, Use::kTexelFormat,
+         R"(error: cannot use 'VARIABLE' of type 'i32' as texel format)"},
         {Def::kVariable, Use::kValueExpression, kPass},
         {Def::kVariable, Use::kVariableType,
          R"(5:6 error: cannot use const 'VARIABLE' as type
-1:2 note: 'VARIABLE' declared here)"},
+1:2 note: const 'VARIABLE' declared here)"},
         {Def::kVariable, Use::kUnaryOp, kPass},
     }));
 
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 06b0c18..ab1970a 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -40,7 +40,6 @@
 #include "src/tint/ast/pointer.h"
 #include "src/tint/ast/return_statement.h"
 #include "src/tint/ast/sampled_texture.h"
-#include "src/tint/ast/storage_texture.h"
 #include "src/tint/ast/switch_statement.h"
 #include "src/tint/ast/traverse_expressions.h"
 #include "src/tint/ast/type_name.h"
@@ -308,22 +307,9 @@
             }
             return nullptr;
         },
-        [&](const ast::StorageTexture* t) -> type::StorageTexture* {
-            if (auto* el = Type(t->type)) {
-                if (!validator_.StorageTexture(t)) {
-                    return nullptr;
-                }
-                return builder_->create<type::StorageTexture>(t->dim, t->format, t->access, el);
-            }
-            return nullptr;
-        },
         [&](const ast::TypeName* t) -> type::Type* {
             Mark(t->name);
 
-            if (t->name->Is<ast::TemplatedIdentifier>()) {
-                TINT_UNREACHABLE(Resolver, diagnostics_) << "TODO(crbug.com/tint/1810)";
-            }
-
             auto resolved = dependencies_.resolved_identifiers.Get(t->name);
             if (!resolved) {
                 TINT_ICE(Resolver, diagnostics_)
@@ -338,6 +324,15 @@
                     ErrorMismatchedResolvedIdentifier(t->source, *resolved, "type");
                     return nullptr;
                 }
+
+                if (TINT_UNLIKELY(t->name->Is<ast::TemplatedIdentifier>())) {
+                    AddError("type '" + builder_->Symbols().NameFor(t->name->symbol) +
+                                 "' does not take template arguments",
+                             t->source);
+                    NoteDeclarationSource(ast_node);
+                    return nullptr;
+                }
+
                 return type;
             }
             if (auto b = resolved->BuiltinType(); b != type::Builtin::kUndefined) {
@@ -2431,9 +2426,18 @@
     return call;
 }
 
-type::Type* Resolver::BuiltinType(type::Builtin builtin_ty, const ast::Identifier* ident) const {
+type::Type* Resolver::BuiltinType(type::Builtin builtin_ty, const ast::Identifier* ident) {
     auto& b = *builder_;
 
+    auto check_no_tmpl_args = [&](type::Type* ty) -> type::Type* {
+        if (TINT_UNLIKELY(ident->Is<ast::TemplatedIdentifier>())) {
+            AddError("type '" + b.Symbols().NameFor(ident->symbol) +
+                         "' does not take template arguments",
+                     ident->source);
+            return nullptr;
+        }
+        return ty;
+    };
     auto f32 = [&] { return b.create<type::F32>(); };
     auto i32 = [&] { return b.create<type::I32>(); };
     auto u32 = [&] { return b.create<type::U32>(); };
@@ -2446,94 +2450,143 @@
     auto mat = [&](type::Type* el, uint32_t num_columns, uint32_t num_rows) {
         return el ? b.create<type::Matrix>(vec(el, num_rows), num_columns) : nullptr;
     };
+    auto templated_identifier = [&](size_t num_args) -> const ast::TemplatedIdentifier* {
+        auto* tmpl_ident = ident->As<ast::TemplatedIdentifier>();
+        if (TINT_UNLIKELY(!tmpl_ident)) {
+            AddError("expected '<' for '" + b.Symbols().NameFor(ident->symbol) + "'",
+                     Source{ident->source.range.end});
+            return nullptr;
+        }
+        if (TINT_UNLIKELY(tmpl_ident->arguments.Length() != num_args)) {
+            AddError("'" + b.Symbols().NameFor(ident->symbol) + "' requires " +
+                         std::to_string(num_args) + " template arguments",
+                     ident->source);
+            return nullptr;
+        }
+        return tmpl_ident;
+    };
+    auto storage_texture = [&](type::TextureDimension dim) -> type::StorageTexture* {
+        auto* tmpl_ident = templated_identifier(2);
+        if (TINT_UNLIKELY(!tmpl_ident)) {
+            return nullptr;
+        }
+        auto* format = sem_.AsTexelFormat(Expression(tmpl_ident->arguments[0]));
+        if (TINT_UNLIKELY(!format)) {
+            return nullptr;
+        }
+        auto* access = sem_.AsAccess(Expression(tmpl_ident->arguments[1]));
+        if (TINT_UNLIKELY(!access)) {
+            return nullptr;
+        }
+        auto* subtype = type::StorageTexture::SubtypeFor(format->Value(), builder_->Types());
+        auto* tex = b.create<type::StorageTexture>(dim, format->Value(), access->Value(), subtype);
+        if (!validator_.StorageTexture(tex, ident->source)) {
+            return nullptr;
+        }
+        return tex;
+    };
 
     switch (builtin_ty) {
         case type::Builtin::kBool:
-            return b.create<type::Bool>();
+            return check_no_tmpl_args(b.create<type::Bool>());
         case type::Builtin::kI32:
-            return i32();
+            return check_no_tmpl_args(i32());
         case type::Builtin::kU32:
-            return u32();
+            return check_no_tmpl_args(u32());
         case type::Builtin::kF16:
-            return f16();
+            return check_no_tmpl_args(f16());
         case type::Builtin::kF32:
-            return b.create<type::F32>();
+            return check_no_tmpl_args(b.create<type::F32>());
         case type::Builtin::kMat2X2F:
-            return mat(f32(), 2u, 2u);
+            return check_no_tmpl_args(mat(f32(), 2u, 2u));
         case type::Builtin::kMat2X3F:
-            return mat(f32(), 2u, 3u);
+            return check_no_tmpl_args(mat(f32(), 2u, 3u));
         case type::Builtin::kMat2X4F:
-            return mat(f32(), 2u, 4u);
+            return check_no_tmpl_args(mat(f32(), 2u, 4u));
         case type::Builtin::kMat3X2F:
-            return mat(f32(), 3u, 2u);
+            return check_no_tmpl_args(mat(f32(), 3u, 2u));
         case type::Builtin::kMat3X3F:
-            return mat(f32(), 3u, 3u);
+            return check_no_tmpl_args(mat(f32(), 3u, 3u));
         case type::Builtin::kMat3X4F:
-            return mat(f32(), 3u, 4u);
+            return check_no_tmpl_args(mat(f32(), 3u, 4u));
         case type::Builtin::kMat4X2F:
-            return mat(f32(), 4u, 2u);
+            return check_no_tmpl_args(mat(f32(), 4u, 2u));
         case type::Builtin::kMat4X3F:
-            return mat(f32(), 4u, 3u);
+            return check_no_tmpl_args(mat(f32(), 4u, 3u));
         case type::Builtin::kMat4X4F:
-            return mat(f32(), 4u, 4u);
+            return check_no_tmpl_args(mat(f32(), 4u, 4u));
         case type::Builtin::kMat2X2H:
-            return mat(f16(), 2u, 2u);
+            return check_no_tmpl_args(mat(f16(), 2u, 2u));
         case type::Builtin::kMat2X3H:
-            return mat(f16(), 2u, 3u);
+            return check_no_tmpl_args(mat(f16(), 2u, 3u));
         case type::Builtin::kMat2X4H:
-            return mat(f16(), 2u, 4u);
+            return check_no_tmpl_args(mat(f16(), 2u, 4u));
         case type::Builtin::kMat3X2H:
-            return mat(f16(), 3u, 2u);
+            return check_no_tmpl_args(mat(f16(), 3u, 2u));
         case type::Builtin::kMat3X3H:
-            return mat(f16(), 3u, 3u);
+            return check_no_tmpl_args(mat(f16(), 3u, 3u));
         case type::Builtin::kMat3X4H:
-            return mat(f16(), 3u, 4u);
+            return check_no_tmpl_args(mat(f16(), 3u, 4u));
         case type::Builtin::kMat4X2H:
-            return mat(f16(), 4u, 2u);
+            return check_no_tmpl_args(mat(f16(), 4u, 2u));
         case type::Builtin::kMat4X3H:
-            return mat(f16(), 4u, 3u);
+            return check_no_tmpl_args(mat(f16(), 4u, 3u));
         case type::Builtin::kMat4X4H:
-            return mat(f16(), 4u, 4u);
+            return check_no_tmpl_args(mat(f16(), 4u, 4u));
         case type::Builtin::kVec2F:
-            return vec(f32(), 2u);
+            return check_no_tmpl_args(vec(f32(), 2u));
         case type::Builtin::kVec3F:
-            return vec(f32(), 3u);
+            return check_no_tmpl_args(vec(f32(), 3u));
         case type::Builtin::kVec4F:
-            return vec(f32(), 4u);
+            return check_no_tmpl_args(vec(f32(), 4u));
         case type::Builtin::kVec2H:
-            return vec(f16(), 2u);
+            return check_no_tmpl_args(vec(f16(), 2u));
         case type::Builtin::kVec3H:
-            return vec(f16(), 3u);
+            return check_no_tmpl_args(vec(f16(), 3u));
         case type::Builtin::kVec4H:
-            return vec(f16(), 4u);
+            return check_no_tmpl_args(vec(f16(), 4u));
         case type::Builtin::kVec2I:
-            return vec(i32(), 2u);
+            return check_no_tmpl_args(vec(i32(), 2u));
         case type::Builtin::kVec3I:
-            return vec(i32(), 3u);
+            return check_no_tmpl_args(vec(i32(), 3u));
         case type::Builtin::kVec4I:
-            return vec(i32(), 4u);
+            return check_no_tmpl_args(vec(i32(), 4u));
         case type::Builtin::kVec2U:
-            return vec(u32(), 2u);
+            return check_no_tmpl_args(vec(u32(), 2u));
         case type::Builtin::kVec3U:
-            return vec(u32(), 3u);
+            return check_no_tmpl_args(vec(u32(), 3u));
         case type::Builtin::kVec4U:
-            return vec(u32(), 4u);
+            return check_no_tmpl_args(vec(u32(), 4u));
         case type::Builtin::kSampler:
-            return builder_->create<type::Sampler>(type::SamplerKind::kSampler);
+            return check_no_tmpl_args(builder_->create<type::Sampler>(type::SamplerKind::kSampler));
         case type::Builtin::kSamplerComparison:
-            return builder_->create<type::Sampler>(type::SamplerKind::kComparisonSampler);
+            return check_no_tmpl_args(
+                builder_->create<type::Sampler>(type::SamplerKind::kComparisonSampler));
         case type::Builtin::kTextureDepth2D:
-            return builder_->create<type::DepthTexture>(type::TextureDimension::k2d);
+            return check_no_tmpl_args(
+                builder_->create<type::DepthTexture>(type::TextureDimension::k2d));
         case type::Builtin::kTextureDepth2DArray:
-            return builder_->create<type::DepthTexture>(type::TextureDimension::k2dArray);
+            return check_no_tmpl_args(
+                builder_->create<type::DepthTexture>(type::TextureDimension::k2dArray));
         case type::Builtin::kTextureDepthCube:
-            return builder_->create<type::DepthTexture>(type::TextureDimension::kCube);
+            return check_no_tmpl_args(
+                builder_->create<type::DepthTexture>(type::TextureDimension::kCube));
         case type::Builtin::kTextureDepthCubeArray:
-            return builder_->create<type::DepthTexture>(type::TextureDimension::kCubeArray);
+            return check_no_tmpl_args(
+                builder_->create<type::DepthTexture>(type::TextureDimension::kCubeArray));
         case type::Builtin::kTextureDepthMultisampled2D:
-            return builder_->create<type::DepthMultisampledTexture>(type::TextureDimension::k2d);
+            return check_no_tmpl_args(
+                builder_->create<type::DepthMultisampledTexture>(type::TextureDimension::k2d));
         case type::Builtin::kTextureExternal:
-            return builder_->create<type::ExternalTexture>();
+            return check_no_tmpl_args(builder_->create<type::ExternalTexture>());
+        case type::Builtin::kTextureStorage1D:
+            return storage_texture(type::TextureDimension::k1d);
+        case type::Builtin::kTextureStorage2D:
+            return storage_texture(type::TextureDimension::k2d);
+        case type::Builtin::kTextureStorage2DArray:
+            return storage_texture(type::TextureDimension::k2dArray);
+        case type::Builtin::kTextureStorage3D:
+            return storage_texture(type::TextureDimension::k3d);
         case type::Builtin::kUndefined:
             break;
     }
@@ -4021,19 +4074,43 @@
     AddError("cannot use " + resolved.String(builder_->Symbols(), diagnostics_) + " as " +
                  std::string(wanted),
              source);
+    NoteDeclarationSource(resolved.Node());
+}
 
+void Resolver::NoteDeclarationSource(const ast::Node* node) {
     Switch(
-        resolved.Node(),
-        [&](const ast::TypeDecl* n) {
-            AddNote("'" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+        node,
+        [&](const ast::Struct* n) {
+            AddNote("struct '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
                     n->source);
         },
-        [&](const ast::Variable* n) {
-            AddNote("'" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+        [&](const ast::Alias* n) {
+            AddNote("alias '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
                     n->source);
         },
+        [&](const ast::Var* n) {
+            AddNote("var '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+                    n->source);
+        },
+        [&](const ast::Let* n) {
+            AddNote("let '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+                    n->source);
+        },
+        [&](const ast::Override* n) {
+            AddNote("override '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+                    n->source);
+        },
+        [&](const ast::Const* n) {
+            AddNote("const '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+                    n->source);
+        },
+        [&](const ast::Parameter* n) {
+            AddNote(
+                "parameter '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+                n->source);
+        },
         [&](const ast::Function* n) {
-            AddNote("'" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
+            AddNote("function '" + builder_->Symbols().NameFor(n->name->symbol) + "' declared here",
                     n->source);
         });
 }
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index 0d3c879..bc8821a 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -432,6 +432,11 @@
                                            const ResolvedIdentifier& resolved,
                                            std::string_view wanted);
 
+    /// If @p node is a module-scope type, variable or function declaration, then appends a note
+    /// diagnostic where this declaration was declared, otherwise the function does nothing.
+    /// @param node the AST node.
+    void NoteDeclarationSource(const ast::Node* node);
+
     /// Adds the given error message to the diagnostics
     void AddError(const std::string& msg, const Source& source) const;
 
@@ -443,7 +448,7 @@
 
     /// @returns the type::Type for the builtin type @p builtin_ty with the identifier @p ident
     /// @note: Will raise an ICE if @p symbol is not a builtin type.
-    type::Type* BuiltinType(type::Builtin builtin_ty, const ast::Identifier* ident) const;
+    type::Type* BuiltinType(type::Builtin builtin_ty, const ast::Identifier* ident);
 
     // ArrayInitializerSig represents a unique array initializer signature.
     // It is a tuple of the array type, number of arguments provided and earliest evaluation stage.
diff --git a/src/tint/resolver/sem_helper.cc b/src/tint/resolver/sem_helper.cc
index bb769ab..e288707 100644
--- a/src/tint/resolver/sem_helper.cc
+++ b/src/tint/resolver/sem_helper.cc
@@ -37,30 +37,40 @@
     return sem ? const_cast<type::Type*>(sem->Type()) : nullptr;
 }
 
-void SemHelper::ErrorExpectedValueExpr(const sem::Expression* expr) const {
+void SemHelper::ErrorUnexpectedExprKind(const sem::Expression* expr,
+                                        std::string_view wanted) const {
     Switch(
         expr,  //
+        [&](const sem::VariableUser* var_expr) {
+            auto name =
+                builder_->Symbols().NameFor(var_expr->Variable()->Declaration()->name->symbol);
+            auto type = var_expr->Type()->FriendlyName(builder_->Symbols());
+            AddError("cannot use '" + name + "' of type '" + type + "' as " + std::string(wanted),
+                     var_expr->Declaration()->source);
+        },
+        [&](const sem::ValueExpression* val_expr) {
+            auto type = val_expr->Type()->FriendlyName(builder_->Symbols());
+            AddError("cannot use expression of type '" + type + "' as " + std::string(wanted),
+                     val_expr->Declaration()->source);
+        },
         [&](const sem::TypeExpression* ty_expr) {
             auto name = ty_expr->Type()->FriendlyName(builder_->Symbols());
-            AddError("cannot use type '" + name + "' as value", ty_expr->Declaration()->source);
-            if (auto* ident = ty_expr->Declaration()->As<ast::IdentifierExpression>()) {
-                AddNote("are you missing '()' for type initializer?",
-                        Source{{ident->source.range.end}});
-            }
-            if (auto* str = ty_expr->Type()->As<type::Struct>()) {
-                AddNote("'" + name + "' declared here", str->Source());
-            }
+            AddError("cannot use type '" + name + "' as " + std::string(wanted),
+                     ty_expr->Declaration()->source);
         },
         [&](const sem::BuiltinEnumExpression<type::Access>* access) {
-            AddError("cannot use access '" + utils::ToString(access->Value()) + "' as value",
+            AddError("cannot use access '" + utils::ToString(access->Value()) + "' as " +
+                         std::string(wanted),
                      access->Declaration()->source);
         },
         [&](const sem::BuiltinEnumExpression<type::AddressSpace>* addr) {
-            AddError("cannot use address space '" + utils::ToString(addr->Value()) + "' as value",
+            AddError("cannot use address space '" + utils::ToString(addr->Value()) + "' as " +
+                         std::string(wanted),
                      addr->Declaration()->source);
         },
         [&](const sem::BuiltinEnumExpression<type::TexelFormat>* fmt) {
-            AddError("cannot use texel format '" + utils::ToString(fmt->Value()) + "' as value",
+            AddError("cannot use texel format '" + utils::ToString(fmt->Value()) + "' as " +
+                         std::string(wanted),
                      fmt->Declaration()->source);
         },
         [&](Default) {
@@ -69,6 +79,20 @@
         });
 }
 
+void SemHelper::ErrorExpectedValueExpr(const sem::Expression* expr) const {
+    ErrorUnexpectedExprKind(expr, "value");
+    if (auto* ty_expr = expr->As<sem::TypeExpression>()) {
+        if (auto* ident = ty_expr->Declaration()->As<ast::IdentifierExpression>()) {
+            AddNote("are you missing '()' for type initializer?",
+                    Source{{ident->source.range.end}});
+        }
+        if (auto* str = ty_expr->Type()->As<type::Struct>()) {
+            AddNote("struct '" + str->FriendlyName(builder_->Symbols()) + "' declared here",
+                    str->Source());
+        }
+    }
+}
+
 void SemHelper::AddError(const std::string& msg, const Source& source) const {
     builder_->Diagnostics().add_error(diag::System::Resolver, msg, source);
 }
diff --git a/src/tint/resolver/sem_helper.h b/src/tint/resolver/sem_helper.h
index 9c918cd..db3eda4 100644
--- a/src/tint/resolver/sem_helper.h
+++ b/src/tint/resolver/sem_helper.h
@@ -20,6 +20,7 @@
 #include "src/tint/diagnostic/diagnostic.h"
 #include "src/tint/program_builder.h"
 #include "src/tint/resolver/dependency_graph.h"
+#include "src/tint/sem/builtin_enum_expression.h"
 #include "src/tint/utils/map.h"
 
 namespace tint::resolver {
@@ -61,25 +62,46 @@
     }
 
     /// @param expr the semantic node
-    /// @returns one of:
-    /// * nullptr if @p expr is nullptr
-    /// * @p expr if the static pointer type already derives from sem::ValueExpression
-    /// * @p expr cast to sem::ValueExpression if the cast is successful
-    /// * nullptr if @p expr is not a sem::ValueExpression. In this case an error diagnostic is
-    ///   raised.
-    template <typename EXPR>
-    auto* AsValue(EXPR* expr) const {
-        if constexpr (traits::IsTypeOrDerived<EXPR, sem::ValueExpression>) {
-            return expr;
-        } else {
-            if (TINT_LIKELY(expr)) {
-                if (auto* val = expr->template As<sem::ValueExpression>(); TINT_LIKELY(val)) {
-                    return val;
-                }
-                ErrorExpectedValueExpr(expr);
+    /// @returns nullptr if @p expr is nullptr, or @p expr cast to sem::ValueExpression if the cast
+    /// is successful, otherwise an error diagnostic is raised.
+    sem::ValueExpression* AsValue(sem::Expression* expr) const {
+        if (TINT_LIKELY(expr)) {
+            if (auto* val = expr->As<sem::ValueExpression>(); TINT_LIKELY(val)) {
+                return val;
             }
-            return static_cast<sem::ValueExpression*>(nullptr);
+            ErrorExpectedValueExpr(expr);
         }
+        return nullptr;
+    }
+
+    /// @param expr the semantic node
+    /// @returns nullptr if @p expr is nullptr, or @p expr cast to
+    /// sem::BuiltinEnumExpression<type::TexelFormat> if the cast is successful, otherwise an error
+    /// diagnostic is raised.
+    sem::BuiltinEnumExpression<type::TexelFormat>* AsTexelFormat(sem::Expression* expr) const {
+        if (TINT_LIKELY(expr)) {
+            if (auto* val = expr->As<sem::BuiltinEnumExpression<type::TexelFormat>>();
+                TINT_LIKELY(val)) {
+                return val;
+            }
+            ErrorUnexpectedExprKind(expr, "texel format");
+        }
+        return nullptr;
+    }
+
+    /// @param expr the semantic node
+    /// @returns nullptr if @p expr is nullptr, or @p expr cast to
+    /// sem::BuiltinEnumExpression<type::Access> if the cast is successful, otherwise an error
+    /// diagnostic is raised.
+    sem::BuiltinEnumExpression<type::Access>* AsAccess(sem::Expression* expr) const {
+        if (TINT_LIKELY(expr)) {
+            if (auto* val = expr->As<sem::BuiltinEnumExpression<type::Access>>();
+                TINT_LIKELY(val)) {
+                return val;
+            }
+            ErrorUnexpectedExprKind(expr, "access");
+        }
+        return nullptr;
     }
 
     /// @returns the resolved type of the ast::Expression @p expr
@@ -100,6 +122,10 @@
     void ErrorExpectedValueExpr(const sem::Expression* expr) const;
 
   private:
+    /// Raises an error diagnostic that the expression @p got was not of the kind @p wanted.
+    /// @param expr the expression
+    void ErrorUnexpectedExprKind(const sem::Expression* expr, std::string_view wanted) const;
+
     /// Adds the given error message to the diagnostics
     void AddError(const std::string& msg, const Source& source) const;
 
diff --git a/src/tint/resolver/type_validation_test.cc b/src/tint/resolver/type_validation_test.cc
index b28cecf..70b3837 100644
--- a/src/tint/resolver/type_validation_test.cc
+++ b/src/tint/resolver/type_validation_test.cc
@@ -1064,26 +1064,26 @@
 
 namespace StorageTextureTests {
 struct DimensionParams {
-    type::TextureDimension dim;
+    const char* name;
     bool is_valid;
 };
 
 static constexpr DimensionParams Dimension_cases[] = {
-    DimensionParams{type::TextureDimension::k1d, true},
-    DimensionParams{type::TextureDimension::k2d, true},
-    DimensionParams{type::TextureDimension::k2dArray, true},
-    DimensionParams{type::TextureDimension::k3d, true},
-    DimensionParams{type::TextureDimension::kCube, false},
-    DimensionParams{type::TextureDimension::kCubeArray, false}};
+    DimensionParams{"texture_storage_1d", true},
+    DimensionParams{"texture_storage_2d", true},
+    DimensionParams{"texture_storage_2d_array", true},
+    DimensionParams{"texture_storage_3d", true},
+    DimensionParams{"texture_storage_cube", false},
+    DimensionParams{"texture_storage_cube_array", false}};
 
 using StorageTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
 TEST_P(StorageTextureDimensionTest, All) {
     // @group(0) @binding(0)
-    // var a : texture_storage_*<ru32int, write>;
+    // var a : texture_storage_*<r32uint, write>;
     auto& params = GetParam();
 
-    auto* st = ty.storage_texture(Source{{12, 34}}, params.dim, type::TexelFormat::kR32Uint,
-                                  type::Access::kWrite);
+    auto* st = ty(Source{{12, 34}}, params.name, utils::ToString(type::TexelFormat::kR32Uint),
+                  utils::ToString(type::Access::kWrite));
 
     GlobalVar("a", st, Group(0_a), Binding(0_a));
 
@@ -1091,8 +1091,7 @@
         EXPECT_TRUE(r()->Resolve()) << r()->error();
     } else {
         EXPECT_FALSE(r()->Resolve());
-        EXPECT_EQ(r()->error(),
-                  "12:34 error: cube dimensions for storage textures are not supported");
+        EXPECT_EQ(r()->error(), "12:34 error: unknown type: '" + std::string(params.name) + "'");
     }
 }
 INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
@@ -1165,22 +1164,33 @@
 
 using StorageTextureAccessTest = ResolverTest;
 
-TEST_F(StorageTextureAccessTest, MissingAccess_Fail) {
+TEST_F(StorageTextureAccessTest, MissingTemplates) {
     // @group(0) @binding(0)
-    // var a : texture_storage_1d<ru32int>;
+    // var a : texture_storage_1d<r32uint>;
 
-    auto* st = ty.storage_texture(Source{{12, 34}}, type::TextureDimension::k1d,
-                                  type::TexelFormat::kR32Uint, type::Access::kUndefined);
+    auto* st = ty(Source{{12, 34}}, "texture_storage_1d");
 
     GlobalVar("a", st, Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: storage texture missing access control");
+    EXPECT_EQ(r()->error(), "12:34 error: expected '<' for 'texture_storage_1d'");
+}
+
+TEST_F(StorageTextureAccessTest, MissingAccess_Fail) {
+    // @group(0) @binding(0)
+    // var a : texture_storage_1d<r32uint>;
+
+    auto* st = ty(Source{{12, 34}}, "texture_storage_1d", "r32uint");
+
+    GlobalVar("a", st, Group(0_a), Binding(0_a));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:34 error: 'texture_storage_1d' requires 2 template arguments)");
 }
 
 TEST_F(StorageTextureAccessTest, RWAccess_Fail) {
     // @group(0) @binding(0)
-    // var a : texture_storage_1d<ru32int, read_write>;
+    // var a : texture_storage_1d<r32uint, read_write>;
 
     auto* st = ty.storage_texture(Source{{12, 34}}, type::TextureDimension::k1d,
                                   type::TexelFormat::kR32Uint, type::Access::kReadWrite);
@@ -1194,7 +1204,7 @@
 
 TEST_F(StorageTextureAccessTest, ReadOnlyAccess_Fail) {
     // @group(0) @binding(0)
-    // var a : texture_storage_1d<ru32int, read>;
+    // var a : texture_storage_1d<r32uint, read>;
 
     auto* st = ty.storage_texture(Source{{12, 34}}, type::TextureDimension::k1d,
                                   type::TexelFormat::kR32Uint, type::Access::kRead);
@@ -1208,7 +1218,7 @@
 
 TEST_F(StorageTextureAccessTest, WriteOnlyAccess_Pass) {
     // @group(0) @binding(0)
-    // var a : texture_storage_1d<ru32int, write>;
+    // var a : texture_storage_1d<r32uint, write>;
 
     auto* st = ty.storage_texture(type::TextureDimension::k1d, type::TexelFormat::kR32Uint,
                                   type::Access::kWrite);
@@ -1445,5 +1455,91 @@
 
 }  // namespace BuiltinTypeAliasTests
 
+namespace TypeDoesNotTakeTemplateArgs {
+
+using ResolverUntemplatedTypeUsedWithTemplateArgs = ResolverTestWithParam<const char*>;
+
+TEST_P(ResolverUntemplatedTypeUsedWithTemplateArgs, Builtin_UseWithTemplateArgs) {
+    // enable f16;
+    // var<private> v : f32<true>;
+
+    Enable(ast::Extension::kF16);
+    GlobalVar("v", type::AddressSpace::kPrivate, ty(Source{{12, 34}}, GetParam(), true));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "12:34 error: type '" + std::string(GetParam()) +
+                                "' does not take template arguments");
+}
+
+TEST_P(ResolverUntemplatedTypeUsedWithTemplateArgs, BuiltinAlias_UseWithTemplateArgs) {
+    // enable f16;
+    // alias A = f32;
+    // var<private> v : S<true>;
+
+    Enable(ast::Extension::kF16);
+    Alias(Source{{56, 78}}, "A", ty(GetParam()));
+    GlobalVar("v", type::AddressSpace::kPrivate, ty(Source{{12, 34}}, "A", true));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: type 'A' does not take template arguments
+56:78 note: alias 'A' declared here)");
+}
+
+INSTANTIATE_TEST_SUITE_P(BuiltinTypes,
+                         ResolverUntemplatedTypeUsedWithTemplateArgs,
+                         testing::Values("bool",
+                                         "f16",
+                                         "f32",
+                                         "i32",
+                                         "u32",
+                                         "mat2x2f",
+                                         "mat2x2h",
+                                         "mat2x3f",
+                                         "mat2x3h",
+                                         "mat2x4f",
+                                         "mat2x4h",
+                                         "mat3x2f",
+                                         "mat3x2h",
+                                         "mat3x3f",
+                                         "mat3x3h",
+                                         "mat3x4f",
+                                         "mat3x4h",
+                                         "mat4x2f",
+                                         "mat4x2h",
+                                         "mat4x3f",
+                                         "mat4x3h",
+                                         "mat4x4f",
+                                         "mat4x4h",
+                                         "vec2f",
+                                         "vec2h",
+                                         "vec2i",
+                                         "vec2u",
+                                         "vec3f",
+                                         "vec3h",
+                                         "vec3i",
+                                         "vec3u",
+                                         "vec4f",
+                                         "vec4h",
+                                         "vec4i",
+                                         "vec4u"));
+
+TEST_F(ResolverUntemplatedTypeUsedWithTemplateArgs, Struct_UseWithTemplateArgs) {
+    // struct S {
+    //   i: i32;
+    // };
+    // var<private> v : S<true>;
+
+    Structure(Source{{56, 78}}, "S", utils::Vector{Member("i", ty.i32())});
+    GlobalVar("v", type::AddressSpace::kPrivate, ty(Source{{12, 34}}, "S", true));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: type 'S' does not take template arguments
+56:78 note: struct 'S' declared here)");
+}
+
+}  // namespace TypeDoesNotTakeTemplateArgs
+
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 24a2bd7..71e39af 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -37,7 +37,6 @@
 #include "src/tint/ast/pointer.h"
 #include "src/tint/ast/return_statement.h"
 #include "src/tint/ast/sampled_texture.h"
-#include "src/tint/ast/storage_texture.h"
 #include "src/tint/ast/switch_statement.h"
 #include "src/tint/ast/traverse_expressions.h"
 #include "src/tint/ast/type_name.h"
@@ -316,28 +315,28 @@
                                        a->source);
 }
 
-bool Validator::StorageTexture(const ast::StorageTexture* t) const {
-    switch (t->access) {
+bool Validator::StorageTexture(const type::StorageTexture* t, const Source& source) const {
+    switch (t->access()) {
         case type::Access::kWrite:
             break;
         case type::Access::kUndefined:
-            AddError("storage texture missing access control", t->source);
+            AddError("storage texture missing access control", source);
             return false;
         default:
-            AddError("storage textures currently only support 'write' access control", t->source);
+            AddError("storage textures currently only support 'write' access control", source);
             return false;
     }
 
-    if (!IsValidStorageTextureDimension(t->dim)) {
-        AddError("cube dimensions for storage textures are not supported", t->source);
+    if (!IsValidStorageTextureDimension(t->dim())) {
+        AddError("cube dimensions for storage textures are not supported", source);
         return false;
     }
 
-    if (!IsValidStorageTextureTexelFormat(t->format)) {
+    if (!IsValidStorageTextureTexelFormat(t->texel_format())) {
         AddError(
             "image format must be one of the texel formats specified for storage "
             "textues in https://gpuweb.github.io/gpuweb/wgsl/#texel-formats",
-            t->source);
+            source);
         return false;
     }
     return true;
diff --git a/src/tint/resolver/validator.h b/src/tint/resolver/validator.h
index 55625d3..9e11f50 100644
--- a/src/tint/resolver/validator.h
+++ b/src/tint/resolver/validator.h
@@ -372,8 +372,9 @@
 
     /// Validates a storage texture
     /// @param t the texture to validate
+    /// @param source the source of the texture
     /// @returns true on success, false otherwise
-    bool StorageTexture(const ast::StorageTexture* t) const;
+    bool StorageTexture(const type::StorageTexture* t, const Source& source) const;
 
     /// Validates a sampled texture
     /// @param t the texture to validate
diff --git a/src/tint/transform/builtin_polyfill.cc b/src/tint/transform/builtin_polyfill.cc
index 9924da3..3d647a7 100644
--- a/src/tint/transform/builtin_polyfill.cc
+++ b/src/tint/transform/builtin_polyfill.cc
@@ -22,6 +22,8 @@
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/builtin.h"
 #include "src/tint/sem/call.h"
+#include "src/tint/sem/type_expression.h"
+#include "src/tint/type/storage_texture.h"
 #include "src/tint/type/texture_dimension.h"
 #include "src/tint/utils/map.h"
 
@@ -1065,13 +1067,17 @@
                         break;
                 }
             },
-            [&](const ast::StorageTexture* tex) {
-                if (polyfill.bgra8unorm && tex->format == type::TexelFormat::kBgra8Unorm) {
-                    ctx.Replace(tex, [&ctx, tex] {
-                        return ctx.dst->ty.storage_texture(tex->dim, type::TexelFormat::kRgba8Unorm,
-                                                           tex->access);
-                    });
-                    made_changes = true;
+            [&](const ast::TypeName* type_name) {
+                if (polyfill.bgra8unorm) {
+                    if (auto* tex = src->Sem().Get<type::StorageTexture>(type_name)) {
+                        if (tex->texel_format() == type::TexelFormat::kBgra8Unorm) {
+                            ctx.Replace(type_name, [&ctx, tex] {
+                                return ctx.dst->ty.storage_texture(
+                                    tex->dim(), type::TexelFormat::kRgba8Unorm, tex->access());
+                            });
+                            made_changes = true;
+                        }
+                    }
                 }
             });
     }
diff --git a/src/tint/transform/texture_1d_to_2d.cc b/src/tint/transform/texture_1d_to_2d.cc
index 4561814..84d480a 100644
--- a/src/tint/transform/texture_1d_to_2d.cc
+++ b/src/tint/transform/texture_1d_to_2d.cc
@@ -83,7 +83,8 @@
             return SkipTransform;
         }
 
-        auto create_var = [&](const ast::Variable* v, ast::Type* type) -> const ast::Variable* {
+        auto create_var = [&](const ast::Variable* v,
+                              const ast::Type* type) -> const ast::Variable* {
             if (v->As<ast::Parameter>()) {
                 return ctx.dst->Param(ctx.Clone(v->name->symbol), type, ctx.Clone(v->attributes));
             } else {
@@ -105,9 +106,9 @@
                 },
                 [&](const type::StorageTexture* storage_tex) -> const ast::Variable* {
                     if (storage_tex->dim() == type::TextureDimension::k1d) {
-                        auto* type = ctx.dst->create<ast::StorageTexture>(
-                            type::TextureDimension::k2d, storage_tex->texel_format(),
-                            CreateASTTypeFor(ctx, storage_tex->type()), storage_tex->access());
+                        auto* type = ctx.dst->ty.storage_texture(type::TextureDimension::k2d,
+                                                                 storage_tex->texel_format(),
+                                                                 storage_tex->access());
                         return create_var(v, type);
                     } else {
                         return nullptr;
diff --git a/src/tint/transform/transform.cc b/src/tint/transform/transform.cc
index 1b5b345..a2ae237 100644
--- a/src/tint/transform/transform.cc
+++ b/src/tint/transform/transform.cc
@@ -163,8 +163,7 @@
         return ctx.dst->create<ast::SampledTexture>(t->dim(), CreateASTTypeFor(ctx, t->type()));
     }
     if (auto* t = ty->As<type::StorageTexture>()) {
-        return ctx.dst->create<ast::StorageTexture>(t->dim(), t->texel_format(),
-                                                    CreateASTTypeFor(ctx, t->type()), t->access());
+        return ctx.dst->ty.storage_texture(t->dim(), t->texel_format(), t->access());
     }
     if (auto* s = ty->As<type::Sampler>()) {
         return ctx.dst->ty.sampler(s->kind());
diff --git a/src/tint/type/builtin.cc b/src/tint/type/builtin.cc
index e8bf467..b400ccb 100644
--- a/src/tint/type/builtin.cc
+++ b/src/tint/type/builtin.cc
@@ -118,6 +118,18 @@
     if (str == "texture_external") {
         return Builtin::kTextureExternal;
     }
+    if (str == "texture_storage_1d") {
+        return Builtin::kTextureStorage1D;
+    }
+    if (str == "texture_storage_2d") {
+        return Builtin::kTextureStorage2D;
+    }
+    if (str == "texture_storage_2d_array") {
+        return Builtin::kTextureStorage2DArray;
+    }
+    if (str == "texture_storage_3d") {
+        return Builtin::kTextureStorage3D;
+    }
     if (str == "u32") {
         return Builtin::kU32;
     }
@@ -224,6 +236,14 @@
             return out << "texture_depth_multisampled_2d";
         case Builtin::kTextureExternal:
             return out << "texture_external";
+        case Builtin::kTextureStorage1D:
+            return out << "texture_storage_1d";
+        case Builtin::kTextureStorage2D:
+            return out << "texture_storage_2d";
+        case Builtin::kTextureStorage2DArray:
+            return out << "texture_storage_2d_array";
+        case Builtin::kTextureStorage3D:
+            return out << "texture_storage_3d";
         case Builtin::kU32:
             return out << "u32";
         case Builtin::kVec2F:
diff --git a/src/tint/type/builtin.h b/src/tint/type/builtin.h
index 8b73461..3b57489 100644
--- a/src/tint/type/builtin.h
+++ b/src/tint/type/builtin.h
@@ -60,6 +60,10 @@
     kTextureDepthCubeArray,
     kTextureDepthMultisampled2D,
     kTextureExternal,
+    kTextureStorage1D,
+    kTextureStorage2D,
+    kTextureStorage2DArray,
+    kTextureStorage3D,
     kU32,
     kVec2F,
     kVec2H,
@@ -116,6 +120,10 @@
     "texture_depth_cube_array",
     "texture_depth_multisampled_2d",
     "texture_external",
+    "texture_storage_1d",
+    "texture_storage_2d",
+    "texture_storage_2d_array",
+    "texture_storage_3d",
     "u32",
     "vec2f",
     "vec2h",
diff --git a/src/tint/type/builtin_bench.cc b/src/tint/type/builtin_bench.cc
index 31fa29a..aea57f5 100644
--- a/src/tint/type/builtin_bench.cc
+++ b/src/tint/type/builtin_bench.cc
@@ -241,97 +241,125 @@
         "textuXe_ZZxtJJrnal",
         "textuPPe_eternal",
         "texturc_external",
-        "ull62",
-        "93yy",
-        "u3KK",
+        "tllxture_storage_P6d",
+        "tex99ure_yytorag_1d",
+        "textuKKe_storage_1d",
+        "texture_storage_1d",
+        "texture__xorage_d",
+        "yxKur_storage_1d",
+        "textureVstorkge_1z",
+        "texKure_Storqge_2d",
+        "texture_storage_d",
+        "teture_storage_VVd",
+        "texture_storage_2d",
+        "textureIstoraAUe_2d",
+        "jextre_storaR_2d",
+        "extue44storYYge_2",
+        "textre_storage_2d_array",
+        "tex9ur_stor11ge_d_xxrray",
+        "tmmxture_storJe_2d_arrcc",
+        "texture_storage_2d_array",
+        "tJJxture_storage_2_array",
+        "DDCCltufe_storaUe_2d_array",
+        "tegture_storage_2d_array",
+        "exture_srageCC3d",
+        "txture_storage_3d",
+        "textuIe_sto__age_3d",
+        "texture_storage_3d",
+        "texttte_PPorage_3d",
+        "texture_stora3de_3d",
+        "exture_Ktoragyy_3d",
+        "",
+        "03nn",
+        "uCnuu",
         "u32",
-        "x_",
-        "K",
-        "kVz",
-        "veKSf",
-        "vc2f",
-        "ec2VV",
-        "vec2f",
-        "IAAc2f",
-        "jbR",
-        "veY4",
-        "ec2h",
-        "vc911",
-        "mmcch",
-        "vec2h",
-        "vJJch",
-        "lDDcUfC",
-        "vec2g",
-        "CCe",
-        "ec2i",
-        "vIc__i",
-        "vec2i",
-        "ePPtt",
-        "v3dc2i",
-        "vcyyi",
-        "u2",
-        "v03nnu",
-        "Cuuecnv",
-        "vec2u",
-        "vX2ll",
-        "vocppu",
-        "vwwc2",
+        "3Xl",
+        "pp3o",
+        "uww",
         "veuug",
         "vaac",
         "TRZcccf",
-        "vec3f",
-        "vTc3O8",
-        "vem03f",
-        "meBB3f",
-        "Mpp3",
-        "OOe3h",
-        "veG3G",
-        "vec3h",
-        "11eHH3h",
+        "vec2f",
+        "vTc2O8",
+        "vem02f",
+        "meBB2f",
+        "Mpp2",
+        "OOe2h",
+        "veG2G",
+        "vec2h",
+        "11eHH2h",
         "veFFe6",
-        "ve3",
-        "vKii3l",
-        "ec3i",
-        "v993IIv",
-        "vec3i",
+        "ve2",
+        "vKii2l",
+        "ec2i",
+        "v992IIv",
+        "vec2i",
         "veci",
         "vechi",
         "vczllPi",
         "u",
-        "vffqq3",
-        "vJdd3u",
-        "vec3u",
+        "vffqq2",
+        "vJdd2u",
+        "vec2u",
         "vecXX",
-        "ve32",
-        "Nyyc3u",
-        "vO4",
+        "ve22",
+        "Nyyc2u",
+        "vO3",
         "PEruZ",
         "vlc2edd",
-        "vec4f",
+        "vec3f",
         "ec9f",
         "ve1II",
-        "veb4f",
+        "veb3f",
         "vi7",
-        "oec4ii",
-        "ec4",
-        "vec4h",
+        "oec3ii",
+        "ec3",
+        "vec3h",
         "veci",
         "22ec",
-        "vGc4C",
-        "ffec48",
-        "c4i",
+        "vGc3C",
+        "ffec38",
+        "c3i",
         "JJecSSi",
-        "vec4i",
-        "94i",
-        "vbbJJ4TT",
+        "vec3i",
+        "93i",
+        "vbbJJ3TT",
         "e66i",
-        "u664u",
-        "vW4u",
-        "v4u",
-        "vec4u",
+        "u663u",
+        "vW3u",
+        "v3u",
+        "vec3u",
         "vecu",
-        "rec4u",
-        "2ec4B",
+        "rec3u",
+        "2ec3B",
+        "vcBBf",
+        "vRc4f",
+        "v4LL0",
+        "vec4f",
+        "vKOOf",
+        "vgwcf",
+        "vLphf",
+        "eiiEh",
+        "ec4h",
+        "UU884",
+        "vec4h",
+        "rrecvvh",
+        "ecmm",
+        "vec4j",
+        "vec4X",
+        "vec48",
+        "vecvEE",
+        "vec4i",
+        "z99ci",
+        "GGeJJA4i",
+        "vess4i",
+        "vPcKu",
+        "tpc4u",
+        "vec",
+        "vec4u",
+        "MMec4u",
+        "vJJc40",
+        "8c",
     };
     for (auto _ : state) {
         for (auto* str : kStrings) {
diff --git a/src/tint/type/builtin_test.cc b/src/tint/type/builtin_test.cc
index 97f3164..ebd2cea 100644
--- a/src/tint/type/builtin_test.cc
+++ b/src/tint/type/builtin_test.cc
@@ -73,6 +73,10 @@
     {"texture_depth_cube_array", Builtin::kTextureDepthCubeArray},
     {"texture_depth_multisampled_2d", Builtin::kTextureDepthMultisampled2D},
     {"texture_external", Builtin::kTextureExternal},
+    {"texture_storage_1d", Builtin::kTextureStorage1D},
+    {"texture_storage_2d", Builtin::kTextureStorage2D},
+    {"texture_storage_2d_array", Builtin::kTextureStorage2DArray},
+    {"texture_storage_3d", Builtin::kTextureStorage3D},
     {"u32", Builtin::kU32},
     {"vec2f", Builtin::kVec2F},
     {"vec2h", Builtin::kVec2H},
@@ -179,45 +183,57 @@
     {"texture_exernss77", Builtin::kUndefined},
     {"texture_bbxternRRl", Builtin::kUndefined},
     {"textureXXexternal", Builtin::kUndefined},
-    {"qOOO2", Builtin::kUndefined},
-    {"us", Builtin::kUndefined},
-    {"u3X", Builtin::kUndefined},
-    {"ve2f", Builtin::kUndefined},
-    {"qq2", Builtin::kUndefined},
-    {"vec222", Builtin::kUndefined},
-    {"vezzXy", Builtin::kUndefined},
-    {"ieVVP", Builtin::kUndefined},
-    {"venCh", Builtin::kUndefined},
-    {"vHc2Aq", Builtin::kUndefined},
-    {"ve2i", Builtin::kUndefined},
-    {"vefK", Builtin::kUndefined},
-    {"vgg2", Builtin::kUndefined},
-    {"vecu", Builtin::kUndefined},
-    {"4TNc2u", Builtin::kUndefined},
+    {"CCextOOre_stoage_qOd", Builtin::kUndefined},
+    {"txtsre_sturage_1L", Builtin::kUndefined},
+    {"texture_stoXage_1d", Builtin::kUndefined},
+    {"textue_storage_2d", Builtin::kUndefined},
+    {"teuresOorageqq2d", Builtin::kUndefined},
+    {"texture_sto22age_2d", Builtin::kUndefined},
+    {"exture_syora0e_2d_Xzzrray", Builtin::kUndefined},
+    {"texiVVr_storageP2d_array", Builtin::kUndefined},
+    {"texturestorage_2nn_arCay", Builtin::kUndefined},
+    {"texturHHstorAAe_qqd", Builtin::kUndefined},
+    {"textur_storage_3d", Builtin::kUndefined},
+    {"texure_sfKorage3d", Builtin::kUndefined},
+    {"gg", Builtin::kUndefined},
+    {"u3", Builtin::kUndefined},
+    {"NT42", Builtin::kUndefined},
     {"ppec7l", Builtin::kUndefined},
-    {"zNe3f", Builtin::kUndefined},
-    {"uXXb3f", Builtin::kUndefined},
-    {"vec3", Builtin::kUndefined},
-    {"883K", Builtin::kUndefined},
+    {"zNe2f", Builtin::kUndefined},
+    {"uXXb2f", Builtin::kUndefined},
+    {"vec2", Builtin::kUndefined},
+    {"882K", Builtin::kUndefined},
     {"vq9h", Builtin::kUndefined},
-    {"vec311", Builtin::kUndefined},
+    {"vec211", Builtin::kUndefined},
     {"22ciii", Builtin::kUndefined},
     {"ec77i", Builtin::kUndefined},
-    {"NN23u", Builtin::kUndefined},
-    {"vVVc3u", Builtin::kUndefined},
-    {"WW11w3u", Builtin::kUndefined},
+    {"NN22u", Builtin::kUndefined},
+    {"vVVc2u", Builtin::kUndefined},
+    {"WW11w2u", Builtin::kUndefined},
     {"vcwwf", Builtin::kUndefined},
-    {"vDc4f", Builtin::kUndefined},
+    {"vDc3f", Builtin::kUndefined},
     {"vecK", Builtin::kUndefined},
-    {"f11r4PP", Builtin::kUndefined},
-    {"ve4h", Builtin::kUndefined},
-    {"vec4YY", Builtin::kUndefined},
+    {"f11r3PP", Builtin::kUndefined},
+    {"ve3h", Builtin::kUndefined},
+    {"vec3YY", Builtin::kUndefined},
     {"vkktHH", Builtin::kUndefined},
-    {"rrec4i", Builtin::kUndefined},
+    {"rrec3i", Builtin::kUndefined},
     {"vWWssi", Builtin::kUndefined},
     {"veYu", Builtin::kUndefined},
-    {"eq4f", Builtin::kUndefined},
-    {"u22ec4u", Builtin::kUndefined},
+    {"eq3f", Builtin::kUndefined},
+    {"u22ec3u", Builtin::kUndefined},
+    {"c4f", Builtin::kUndefined},
+    {"vec4", Builtin::kUndefined},
+    {"vYyc47E", Builtin::kUndefined},
+    {"veMoh", Builtin::kUndefined},
+    {"ve4MM", Builtin::kUndefined},
+    {"55ec4h", Builtin::kUndefined},
+    {"N4i", Builtin::kUndefined},
+    {"ve33i", Builtin::kUndefined},
+    {"3ec4i", Builtin::kUndefined},
+    {"mecI", Builtin::kUndefined},
+    {"vrnK4u", Builtin::kUndefined},
+    {"v4", Builtin::kUndefined},
 };
 
 using BuiltinParseTest = testing::TestWithParam<Case>;
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index d99ecde..19345d0 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -32,7 +32,6 @@
 #include "src/tint/ast/pointer.h"
 #include "src/tint/ast/sampled_texture.h"
 #include "src/tint/ast/stage_attribute.h"
-#include "src/tint/ast/storage_texture.h"
 #include "src/tint/ast/stride_attribute.h"
 #include "src/tint/ast/struct_member_align_attribute.h"
 #include "src/tint/ast/struct_member_offset_attribute.h"
@@ -290,7 +289,23 @@
 }
 
 bool GeneratorImpl::EmitIdentifier(std::ostream& out, const ast::IdentifierExpression* expr) {
-    out << program_->Symbols().NameFor(expr->identifier->symbol);
+    return EmitIdentifier(out, expr->identifier);
+}
+
+bool GeneratorImpl::EmitIdentifier(std::ostream& out, const ast::Identifier* ident) {
+    out << program_->Symbols().NameFor(ident->symbol);
+    if (auto* tmpl_ident = ident->As<ast::TemplatedIdentifier>()) {
+        out << "<";
+        TINT_DEFER(out << ">");
+        for (auto* expr : tmpl_ident->arguments) {
+            if (expr != tmpl_ident->arguments.Front()) {
+                out << ", ";
+            }
+            if (!EmitExpression(out, expr)) {
+                return false;
+            }
+        }
+    }
     return true;
 }
 
@@ -462,10 +477,6 @@
                     out << "multisampled_";
                     return true;
                 },
-                [&](const ast::StorageTexture*) {  //
-                    out << "storage_";
-                    return true;
-                },
                 [&](Default) {  //
                     diagnostics_.add_error(diag::System::Writer, "unknown texture type");
                     return false;
@@ -516,18 +527,6 @@
                     out << ">";
                     return true;
                 },
-                [&](const ast::StorageTexture* storage) {  //
-                    out << "<";
-                    if (!EmitImageFormat(out, storage->format)) {
-                        return false;
-                    }
-                    out << ", ";
-                    if (!EmitAccess(out, storage->access)) {
-                        return false;
-                    }
-                    out << ">";
-                    return true;
-                },
                 [&](Default) {  //
                     return true;
                 });
@@ -543,10 +542,7 @@
             }
             return true;
         },
-        [&](const ast::TypeName* tn) {
-            out << program_->Symbols().NameFor(tn->name->symbol);
-            return true;
-        },
+        [&](const ast::TypeName* tn) { return EmitIdentifier(out, tn->name); },
         [&](Default) {
             diagnostics_.add_error(diag::System::Writer,
                                    "unknown type in EmitType: " + std::string(ty->TypeInfo().name));
diff --git a/src/tint/writer/wgsl/generator_impl.h b/src/tint/writer/wgsl/generator_impl.h
index c6c74f7..cf5d270 100644
--- a/src/tint/writer/wgsl/generator_impl.h
+++ b/src/tint/writer/wgsl/generator_impl.h
@@ -142,6 +142,11 @@
     /// @param expr the identifier expression
     /// @returns true if the identifier was emitted
     bool EmitIdentifier(std::ostream& out, const ast::IdentifierExpression* expr);
+    /// Handles generating an identifier
+    /// @param out the output of the expression stream
+    /// @param ident the identifier
+    /// @returns true if the identifier was emitted
+    bool EmitIdentifier(std::ostream& out, const ast::Identifier* ident);
     /// Handles an if statement
     /// @param stmt the statement to emit
     /// @returns true if the statement was successfully emitted