ast: Add type nodes

Copy all of the type classes from src/type into ast.

Required the merging of:
* type::Struct into the existing ast::Struct - ast::Struct now has a name.
* type::AccessControl into the existing ast::AccessControl enumerator - The old ast::AccessControl enumerator is now ast::AccessControl::Access

Bug: tint:724
Change-Id: Ibb950036ed551ec769c6d3d2c8fb411809cf6931
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/48383
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/fuzzers/tint_ast_clone_fuzzer.cc b/fuzzers/tint_ast_clone_fuzzer.cc
index f773aa4..fdb83c1 100644
--- a/fuzzers/tint_ast_clone_fuzzer.cc
+++ b/fuzzers/tint_ast_clone_fuzzer.cc
@@ -77,7 +77,7 @@
   for (auto* src_node : src.ASTNodes().Objects()) {
     src_nodes.emplace(src_node);
   }
-  std::unordered_set<tint::type::Type*> src_types;
+  std::unordered_set<tint::sem::Type*> src_types;
   for (auto* src_type : src.Types()) {
     src_types.emplace(src_type);
   }
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 1914280..2a940b6 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -284,8 +284,12 @@
     "ast/access_control.h",
     "ast/access_decoration.cc",
     "ast/access_decoration.h",
+    "ast/alias.cc",
+    "ast/alias.h",
     "ast/array_accessor_expression.cc",
     "ast/array_accessor_expression.h",
+    "ast/array.cc",
+    "ast/array.h",
     "ast/assignment_statement.cc",
     "ast/assignment_statement.h",
     "ast/binary_expression.cc",
@@ -298,12 +302,14 @@
     "ast/block_statement.h",
     "ast/bool_literal.cc",
     "ast/bool_literal.h",
+    "ast/bool.cc",
+    "ast/bool.h",
     "ast/break_statement.cc",
     "ast/break_statement.h",
-    "ast/builtin.cc",
-    "ast/builtin.h",
     "ast/builtin_decoration.cc",
     "ast/builtin_decoration.h",
+    "ast/builtin.cc",
+    "ast/builtin.h",
     "ast/call_expression.cc",
     "ast/call_expression.h",
     "ast/call_statement.cc",
@@ -318,12 +324,16 @@
     "ast/continue_statement.h",
     "ast/decoration.cc",
     "ast/decoration.h",
+    "ast/depth_texture.cc",
+    "ast/depth_texture.h",
     "ast/discard_statement.cc",
     "ast/discard_statement.h",
     "ast/else_statement.cc",
     "ast/else_statement.h",
     "ast/expression.cc",
     "ast/expression.h",
+    "ast/f32.cc",
+    "ast/f32.h",
     "ast/fallthrough_statement.cc",
     "ast/fallthrough_statement.h",
     "ast/float_literal.cc",
@@ -332,6 +342,8 @@
     "ast/function.h",
     "ast/group_decoration.cc",
     "ast/group_decoration.h",
+    "ast/i32.cc",
+    "ast/i32.h",
     "ast/identifier_expression.cc",
     "ast/identifier_expression.h",
     "ast/if_statement.cc",
@@ -346,16 +358,26 @@
     "ast/location_decoration.h",
     "ast/loop_statement.cc",
     "ast/loop_statement.h",
+    "ast/matrix.cc",
+    "ast/matrix.h",
     "ast/member_accessor_expression.cc",
     "ast/member_accessor_expression.h",
     "ast/module.cc",
     "ast/module.h",
+    "ast/multisampled_texture.cc",
+    "ast/multisampled_texture.h",
     "ast/node.cc",
     "ast/node.h",
     "ast/pipeline_stage.cc",
     "ast/pipeline_stage.h",
+    "ast/pointer.cc",
+    "ast/pointer.h",
     "ast/return_statement.cc",
     "ast/return_statement.h",
+    "ast/sampled_texture.cc",
+    "ast/sampled_texture.h",
+    "ast/sampler.cc",
+    "ast/sampler.h",
     "ast/scalar_constructor_expression.cc",
     "ast/scalar_constructor_expression.h",
     "ast/sint_literal.cc",
@@ -366,34 +388,46 @@
     "ast/statement.h",
     "ast/storage_class.cc",
     "ast/storage_class.h",
+    "ast/storage_texture.cc",
+    "ast/storage_texture.h",
     "ast/stride_decoration.cc",
     "ast/stride_decoration.h",
-    "ast/struct.cc",
-    "ast/struct.h",
     "ast/struct_block_decoration.cc",
     "ast/struct_block_decoration.h",
-    "ast/struct_member.cc",
-    "ast/struct_member.h",
     "ast/struct_member_align_decoration.cc",
     "ast/struct_member_align_decoration.h",
     "ast/struct_member_offset_decoration.cc",
     "ast/struct_member_offset_decoration.h",
     "ast/struct_member_size_decoration.cc",
     "ast/struct_member_size_decoration.h",
+    "ast/struct_member.cc",
+    "ast/struct_member.h",
+    "ast/struct.cc",
+    "ast/struct.h",
     "ast/switch_statement.cc",
     "ast/switch_statement.h",
+    "ast/texture.cc",
+    "ast/texture.h",
     "ast/type_constructor_expression.cc",
     "ast/type_constructor_expression.h",
+    "ast/ast_type.cc",  # TODO(bclayton) - rename to type.cc
+    "ast/type.h",
+    "ast/u32.cc",
+    "ast/u32.h",
     "ast/uint_literal.cc",
     "ast/uint_literal.h",
-    "ast/unary_op.cc",
-    "ast/unary_op.h",
     "ast/unary_op_expression.cc",
     "ast/unary_op_expression.h",
-    "ast/variable.cc",
-    "ast/variable.h",
+    "ast/unary_op.cc",
+    "ast/unary_op.h",
     "ast/variable_decl_statement.cc",
     "ast/variable_decl_statement.h",
+    "ast/variable.cc",
+    "ast/variable.h",
+    "ast/vector.cc",
+    "ast/vector.h",
+    "ast/void.cc",
+    "ast/void.h",
     "ast/workgroup_decoration.cc",
     "ast/workgroup_decoration.h",
     "block_allocator.h",
@@ -419,12 +453,12 @@
     "inspector/scalar.h",
     "intrinsic_table.cc",
     "intrinsic_table.h",
-    "program.cc",
-    "program.h",
     "program_builder.cc",
     "program_builder.h",
     "program_id.cc",
     "program_id.h",
+    "program.cc",
+    "program.h",
     "reader/reader.cc",
     "reader/reader.h",
     "resolver/resolver.cc",
@@ -474,10 +508,10 @@
     "sem/void_type.h",
     "source.cc",
     "source.h",
-    "symbol.cc",
-    "symbol.h",
     "symbol_table.cc",
     "symbol_table.h",
+    "symbol.cc",
+    "symbol.h",
     "traits.h",
     "transform/binding_point.h",
     "transform/binding_remapper.cc",
@@ -510,10 +544,10 @@
     "writer/append_vector.h",
     "writer/float_to_string.cc",
     "writer/float_to_string.h",
-    "writer/text.cc",
-    "writer/text.h",
     "writer/text_generator.cc",
     "writer/text_generator.h",
+    "writer/text.cc",
+    "writer/text.h",
     "writer/writer.cc",
     "writer/writer.h",
   ]
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index cac6fbe..9e6de7b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -42,8 +42,12 @@
   ast/access_control.h
   ast/access_decoration.cc
   ast/access_decoration.h
+  ast/alias.cc
+  ast/alias.h
   ast/array_accessor_expression.cc
   ast/array_accessor_expression.h
+  ast/array.cc
+  ast/array.h
   ast/assignment_statement.cc
   ast/assignment_statement.h
   ast/binary_expression.cc
@@ -56,6 +60,8 @@
   ast/block_statement.h
   ast/bool_literal.cc
   ast/bool_literal.h
+  ast/bool.cc
+  ast/bool.h
   ast/break_statement.cc
   ast/break_statement.h
   ast/builtin_decoration.cc
@@ -76,12 +82,16 @@
   ast/continue_statement.h
   ast/decoration.cc
   ast/decoration.h
+  ast/depth_texture.cc
+  ast/depth_texture.h
   ast/discard_statement.cc
   ast/discard_statement.h
   ast/else_statement.cc
   ast/else_statement.h
   ast/expression.cc
   ast/expression.h
+  ast/f32.cc
+  ast/f32.h
   ast/fallthrough_statement.cc
   ast/fallthrough_statement.h
   ast/float_literal.cc
@@ -90,6 +100,8 @@
   ast/function.h
   ast/group_decoration.cc
   ast/group_decoration.h
+  ast/i32.cc
+  ast/i32.h
   ast/identifier_expression.cc
   ast/identifier_expression.h
   ast/if_statement.cc
@@ -104,16 +116,26 @@
   ast/location_decoration.h
   ast/loop_statement.cc
   ast/loop_statement.h
+  ast/matrix.cc
+  ast/matrix.h
   ast/member_accessor_expression.cc
   ast/member_accessor_expression.h
   ast/module.cc
   ast/module.h
+  ast/multisampled_texture.cc
+  ast/multisampled_texture.h
   ast/node.cc
   ast/node.h
   ast/pipeline_stage.cc
   ast/pipeline_stage.h
+  ast/pointer.cc
+  ast/pointer.h
   ast/return_statement.cc
   ast/return_statement.h
+  ast/sampled_texture.cc
+  ast/sampled_texture.h
+  ast/sampler.cc
+  ast/sampler.h
   ast/scalar_constructor_expression.cc
   ast/scalar_constructor_expression.h
   ast/sint_literal.cc
@@ -124,34 +146,46 @@
   ast/statement.h
   ast/storage_class.cc
   ast/storage_class.h
+  ast/storage_texture.cc
+  ast/storage_texture.h
   ast/stride_decoration.cc
   ast/stride_decoration.h
-  ast/struct.cc
-  ast/struct.h
   ast/struct_block_decoration.cc
   ast/struct_block_decoration.h
-  ast/struct_member.cc
-  ast/struct_member.h
   ast/struct_member_align_decoration.cc
   ast/struct_member_align_decoration.h
   ast/struct_member_offset_decoration.cc
   ast/struct_member_offset_decoration.h
   ast/struct_member_size_decoration.cc
   ast/struct_member_size_decoration.h
+  ast/struct_member.cc
+  ast/struct_member.h
+  ast/struct.cc
+  ast/struct.h
   ast/switch_statement.cc
   ast/switch_statement.h
+  ast/texture.cc
+  ast/texture.h
   ast/type_constructor_expression.cc
   ast/type_constructor_expression.h
+  ast/ast_type.cc  # TODO(bclayton) - rename to type.cc
+  ast/type.h
+  ast/u32.cc
+  ast/u32.h
   ast/uint_literal.cc
   ast/uint_literal.h
-  ast/unary_op.cc
-  ast/unary_op.h
   ast/unary_op_expression.cc
   ast/unary_op_expression.h
-  ast/variable.cc
-  ast/variable.h
+  ast/unary_op.cc
+  ast/unary_op.h
   ast/variable_decl_statement.cc
   ast/variable_decl_statement.h
+  ast/variable.cc
+  ast/variable.h
+  ast/vector.cc
+  ast/vector.h
+  ast/void.cc
+  ast/void.h
   ast/workgroup_decoration.cc
   ast/workgroup_decoration.h
   block_allocator.h
@@ -163,8 +197,6 @@
   debug.h
   demangler.cc
   demangler.h
-  intrinsic_table.cc
-  intrinsic_table.h
   diagnostic/diagnostic.cc
   diagnostic/diagnostic.h
   diagnostic/formatter.cc
@@ -177,6 +209,8 @@
   inspector/inspector.h
   inspector/scalar.cc
   inspector/scalar.h
+  intrinsic_table.cc
+  intrinsic_table.h
   program_builder.cc
   program_builder.h
   program_id.cc
@@ -188,32 +222,32 @@
   resolver/resolver.cc
   resolver/resolver.h
   scope_stack.h
-  sem/array.h
-  sem/call.h
-  sem/call_target.h
-  sem/expression.h
-  sem/info.h
-  sem/intrinsic.h
-  sem/node.h
   sem/array.cc
-  sem/call.cc
+  sem/array.h
   sem/call_target.cc
+  sem/call_target.h
+  sem/call.cc
+  sem/call.h
   sem/expression.cc
-  sem/member_accessor_expression.cc
+  sem/expression.h
   sem/function.cc
   sem/info.cc
+  sem/info.h
   sem/intrinsic.cc
+  sem/intrinsic.h
+  sem/member_accessor_expression.cc
   sem/node.cc
+  sem/node.h
   sem/statement.cc
   sem/struct.cc
-  sem/variable.cc
   sem/type_mappings.h
+  sem/variable.cc
   source.cc
   source.h
-  symbol.cc
-  symbol.h
   symbol_table.cc
   symbol_table.h
+  symbol.cc
+  symbol.h
   traits.h
   transform/binding_point.h
   transform/binding_remapper.cc
@@ -288,10 +322,10 @@
   writer/append_vector.h
   writer/float_to_string.cc
   writer/float_to_string.h
-  writer/text.cc
-  writer/text.h
   writer/text_generator.cc
   writer/text_generator.h
+  writer/text.cc
+  writer/text.h
   writer/writer.cc
   writer/writer.h
 )
@@ -418,14 +452,18 @@
 
 if(${TINT_BUILD_TESTS})
   set(TINT_TEST_SRCS
+    ast/access_control_test.cc
     ast/access_decoration_test.cc
+    ast/alias_test.cc
     ast/array_accessor_expression_test.cc
+    ast/array_test.cc
     ast/assignment_statement_test.cc
     ast/binary_expression_test.cc
     ast/binding_decoration_test.cc
     ast/bitcast_expression_test.cc
     ast/block_statement_test.cc
     ast/bool_literal_test.cc
+    ast/bool_test.cc
     ast/break_statement_test.cc
     ast/builtin_decoration_test.cc
     ast/call_expression_test.cc
@@ -433,12 +471,15 @@
     ast/case_statement_test.cc
     ast/constant_id_decoration_test.cc
     ast/continue_statement_test.cc
+    ast/depth_texture_test.cc
     ast/discard_statement_test.cc
     ast/else_statement_test.cc
+    ast/f32_test.cc
     ast/fallthrough_statement_test.cc
     ast/float_literal_test.cc
     ast/function_test.cc
     ast/group_decoration_test.cc
+    ast/i32_test.cc
     ast/identifier_expression_test.cc
     ast/if_statement_test.cc
     ast/int_literal_test.cc
@@ -446,13 +487,19 @@
     ast/intrinsic_texture_helper_test.h
     ast/location_decoration_test.cc
     ast/loop_statement_test.cc
+    ast/matrix_test.cc
     ast/member_accessor_expression_test.cc
     ast/module_clone_test.cc
     ast/module_test.cc
+    ast/multisampled_texture_test.cc
+    ast/pointer_test.cc
     ast/return_statement_test.cc
+    ast/sampled_texture_test.cc
+    ast/sampler_test.cc
     ast/scalar_constructor_expression_test.cc
     ast/sint_literal_test.cc
     ast/stage_decoration_test.cc
+    ast/storage_texture_test.cc
     ast/stride_decoration_test.cc
     ast/struct_member_align_decoration_test.cc
     ast/struct_member_offset_decoration_test.cc
@@ -461,11 +508,14 @@
     ast/struct_test.cc
     ast/switch_statement_test.cc
     ast/test_helper.h
+    ast/texture_test.cc
     ast/type_constructor_expression_test.cc
+    ast/u32_test.cc
     ast/uint_literal_test.cc
     ast/unary_op_expression_test.cc
     ast/variable_decl_statement_test.cc
     ast/variable_test.cc
+    ast/vector_test.cc
     ast/workgroup_decoration_test.cc
     block_allocator_test.cc
     castable_test.cc
@@ -501,7 +551,6 @@
     sem/intrinsic_test.cc
     symbol_table_test.cc
     symbol_test.cc
-    traits_test.cc
     test_main.cc
     sem/access_control_type_test.cc
     sem/alias_type_test.cc
diff --git a/src/ast/access_control.cc b/src/ast/access_control.cc
index 9a70a64..0c1c1d3 100644
--- a/src/ast/access_control.cc
+++ b/src/ast/access_control.cc
@@ -14,10 +14,68 @@
 
 #include "src/ast/access_control.h"
 
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::AccessControl);
+
 namespace tint {
 namespace ast {
 
-std::ostream& operator<<(std::ostream& out, AccessControl access) {
+AccessControl::AccessControl(ProgramID program_id,
+                             const Source& source,
+                             Access access,
+                             Type* subtype)
+    : Base(program_id, source), access_(access), subtype_(subtype) {
+  TINT_ASSERT(subtype_);
+  TINT_ASSERT(!subtype_->Is<AccessControl>());
+}
+
+AccessControl::AccessControl(AccessControl&&) = default;
+
+AccessControl::~AccessControl() = default;
+
+std::string AccessControl::type_name() const {
+  std::string name = "__access_control_";
+  switch (access_) {
+    case ast::AccessControl::kReadOnly:
+      name += "read_only";
+      break;
+    case ast::AccessControl::kWriteOnly:
+      name += "write_only";
+      break;
+    case ast::AccessControl::kReadWrite:
+      name += "read_write";
+      break;
+  }
+  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();
+}
+
+AccessControl* AccessControl::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<AccessControl>(src, access_, ty);
+}
+
+std::ostream& operator<<(std::ostream& out, AccessControl::Access access) {
   switch (access) {
     case ast::AccessControl::kReadOnly: {
       out << "read_only";
diff --git a/src/ast/access_control.h b/src/ast/access_control.h
index 4539cd1..106b31e 100644
--- a/src/ast/access_control.h
+++ b/src/ast/access_control.h
@@ -16,24 +16,73 @@
 #define SRC_AST_ACCESS_CONTROL_H_
 
 #include <ostream>
+#include <string>
+
+#include "src/ast/type.h"
 
 namespace tint {
 namespace ast {
 
-/// The access control settings
-enum class AccessControl {
-  /// Read only
-  kReadOnly = 0,
-  /// Write only
-  kWriteOnly,
-  /// Read write
-  kReadWrite
+/// An access control type. Holds an access setting and pointer to another type.
+class AccessControl : public Castable<AccessControl, Type> {
+ public:
+  /// The access control settings
+  enum Access {
+    /// Read only
+    kReadOnly = 0,
+    /// Write only
+    kWriteOnly,
+    /// Read write
+    kReadWrite
+  };
+
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  /// @param access the access control setting
+  /// @param subtype the access controlled type
+  AccessControl(ProgramID program_id,
+                const Source& source,
+                Access access,
+                Type* subtype);
+  /// Move constructor
+  AccessControl(AccessControl&&);
+  ~AccessControl() override;
+
+  /// @returns true if the access control is read only
+  bool IsReadOnly() const { return access_ == Access::kReadOnly; }
+  /// @returns true if the access control is write only
+  bool IsWriteOnly() const { return access_ == Access::kWriteOnly; }
+  /// @returns true if the access control is read/write
+  bool IsReadWrite() const { return access_ == Access::kReadWrite; }
+
+  /// @returns the access control value
+  Access access_control() const { return access_; }
+  /// @returns the subtype type
+  Type* type() const { return subtype_; }
+
+  /// @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
+  AccessControl* Clone(CloneContext* ctx) const override;
+
+ private:
+  Access const access_;
+  Type* const subtype_;
 };
 
 /// @param out the std::ostream to write to
 /// @param access the AccessControl
 /// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, AccessControl access);
+std::ostream& operator<<(std::ostream& out, AccessControl::Access access);
 
 }  // namespace ast
 }  // namespace tint
diff --git a/src/ast/access_control_test.cc b/src/ast/access_control_test.cc
new file mode 100644
index 0000000..c03d833
--- /dev/null
+++ b/src/ast/access_control_test.cc
@@ -0,0 +1,112 @@
+// 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/ast/access_control.h"
+
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampler.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstAccessControlTest = TestHelper;
+
+TEST_F(AstAccessControlTest, Create) {
+  auto* u32 = create<U32>();
+  auto* a = create<AccessControl>(AccessControl::kReadWrite, u32);
+  EXPECT_TRUE(a->IsReadWrite());
+  EXPECT_EQ(a->type(), u32);
+}
+
+TEST_F(AstAccessControlTest, Is) {
+  auto* i32 = create<I32>();
+  Type* ty = create<AccessControl>(AccessControl::kReadOnly, i32);
+  EXPECT_TRUE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_FALSE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstAccessControlTest, AccessRead) {
+  auto* i32 = create<I32>();
+  auto* ac = create<AccessControl>(AccessControl::kReadOnly, i32);
+  EXPECT_TRUE(ac->IsReadOnly());
+  EXPECT_FALSE(ac->IsWriteOnly());
+  EXPECT_FALSE(ac->IsReadWrite());
+
+  EXPECT_EQ(ac->type_name(), "__access_control_read_only__i32");
+}
+
+TEST_F(AstAccessControlTest, AccessWrite) {
+  auto* i32 = create<I32>();
+  auto* ac = create<AccessControl>(AccessControl::kWriteOnly, i32);
+  EXPECT_FALSE(ac->IsReadOnly());
+  EXPECT_TRUE(ac->IsWriteOnly());
+  EXPECT_FALSE(ac->IsReadWrite());
+
+  EXPECT_EQ(ac->type_name(), "__access_control_write_only__i32");
+}
+
+TEST_F(AstAccessControlTest, AccessReadWrite) {
+  auto* i32 = create<I32>();
+  auto* ac = create<AccessControl>(AccessControl::kReadWrite, i32);
+  EXPECT_FALSE(ac->IsReadOnly());
+  EXPECT_FALSE(ac->IsWriteOnly());
+  EXPECT_TRUE(ac->IsReadWrite());
+
+  EXPECT_EQ(ac->type_name(), "__access_control_read_write__i32");
+}
+
+TEST_F(AstAccessControlTest, FriendlyNameReadOnly) {
+  auto* i32 = create<I32>();
+  auto* ac = create<AccessControl>(AccessControl::kReadOnly, i32);
+  EXPECT_EQ(ac->FriendlyName(Symbols()), "[[access(read)]] i32");
+}
+
+TEST_F(AstAccessControlTest, FriendlyNameWriteOnly) {
+  auto* i32 = create<I32>();
+  auto* ac = create<AccessControl>(AccessControl::kWriteOnly, i32);
+  EXPECT_EQ(ac->FriendlyName(Symbols()), "[[access(write)]] i32");
+}
+
+TEST_F(AstAccessControlTest, FriendlyNameReadWrite) {
+  auto* i32 = create<I32>();
+  auto* ac = create<AccessControl>(AccessControl::kReadWrite, i32);
+  EXPECT_EQ(ac->FriendlyName(Symbols()), "[[access(read_write)]] i32");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/access_decoration.cc b/src/ast/access_decoration.cc
index ad5be5b..e00ace6 100644
--- a/src/ast/access_decoration.cc
+++ b/src/ast/access_decoration.cc
@@ -23,7 +23,7 @@
 
 AccessDecoration::AccessDecoration(ProgramID program_id,
                                    const Source& source,
-                                   AccessControl val)
+                                   AccessControl::Access val)
     : Base(program_id, source), value_(val) {}
 
 AccessDecoration::~AccessDecoration() = default;
diff --git a/src/ast/access_decoration.h b/src/ast/access_decoration.h
index 0ad41bf..894bb69 100644
--- a/src/ast/access_decoration.h
+++ b/src/ast/access_decoration.h
@@ -30,11 +30,11 @@
   /// @param value the access value
   AccessDecoration(ProgramID program_id,
                    const Source& source,
-                   AccessControl value);
+                   AccessControl::Access value);
   ~AccessDecoration() override;
 
   /// @returns the access control value
-  AccessControl value() const { return value_; }
+  AccessControl::Access value() const { return value_; }
 
   /// Outputs the decoration to the given stream
   /// @param sem the semantic info for the program
@@ -51,7 +51,7 @@
   AccessDecoration* Clone(CloneContext* ctx) const override;
 
  private:
-  AccessControl const value_;
+  AccessControl::Access const value_;
 };
 
 }  // namespace ast
diff --git a/src/ast/alias.cc b/src/ast/alias.cc
new file mode 100644
index 0000000..80e5957
--- /dev/null
+++ b/src/ast/alias.cc
@@ -0,0 +1,53 @@
+// 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/ast/alias.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Alias);
+
+namespace tint {
+namespace ast {
+
+Alias::Alias(ProgramID program_id,
+             const Source& source,
+             const Symbol& sym,
+             Type* subtype)
+    : Base(program_id, source), symbol_(sym), subtype_(subtype) {
+  TINT_ASSERT(subtype_);
+}
+
+Alias::Alias(Alias&&) = default;
+
+Alias::~Alias() = default;
+
+std::string Alias::type_name() const {
+  return "__alias_" + symbol_.to_str() + subtype_->type_name();
+}
+
+std::string Alias::FriendlyName(const SymbolTable& symbols) const {
+  return symbols.NameFor(symbol_);
+}
+
+Alias* Alias::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source());
+  auto sym = ctx->Clone(symbol());
+  auto* ty = ctx->Clone(type());
+  return ctx->dst->create<Alias>(src, sym, ty);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/alias.h b/src/ast/alias.h
new file mode 100644
index 0000000..d95387d
--- /dev/null
+++ b/src/ast/alias.h
@@ -0,0 +1,68 @@
+// 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_AST_ALIAS_H_
+#define SRC_AST_ALIAS_H_
+
+#include <string>
+
+#include "src/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A type alias type. Holds a name and pointer to another type.
+class Alias : public Castable<Alias, Type> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  /// @param sym the symbol for the alias
+  /// @param subtype the alias'd type
+  Alias(ProgramID program_id,
+        const Source& source,
+        const Symbol& sym,
+        Type* subtype);
+  /// Move constructor
+  Alias(Alias&&);
+  /// Destructor
+  ~Alias() override;
+
+  /// @returns the alias symbol
+  Symbol symbol() const { return symbol_; }
+  /// @returns the alias type
+  Type* type() const { return subtype_; }
+
+  /// @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;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  Alias* Clone(CloneContext* ctx) const override;
+
+ private:
+  Symbol const symbol_;
+  Type* const subtype_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_ALIAS_H_
diff --git a/src/ast/alias_test.cc b/src/ast/alias_test.cc
new file mode 100644
index 0000000..ae36314
--- /dev/null
+++ b/src/ast/alias_test.cc
@@ -0,0 +1,170 @@
+// 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/ast/alias.h"
+#include "src/ast/access_control.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampler.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstAliasTest = TestHelper;
+
+TEST_F(AstAliasTest, Create) {
+  auto* u32 = create<U32>();
+  auto* a = create<Alias>(Sym("a_type"), u32);
+  EXPECT_EQ(a->symbol(), Symbol(1, ID()));
+  EXPECT_EQ(a->type(), u32);
+}
+
+TEST_F(AstAliasTest, Is) {
+  Type* ty = create<Alias>(Sym("a"), create<I32>());
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_TRUE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_FALSE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstAliasTest, TypeName) {
+  auto* at = create<Alias>(Sym("Particle"), create<I32>());
+  EXPECT_EQ(at->type_name(), "__alias_$1__i32");
+}
+
+TEST_F(AstAliasTest, FriendlyName) {
+  auto* at = create<Alias>(Sym("Particle"), create<I32>());
+  EXPECT_EQ(at->FriendlyName(Symbols()), "Particle");
+}
+
+TEST_F(AstAliasTest, UnwrapIfNeeded_Alias) {
+  auto* u32 = create<U32>();
+  auto* a = create<Alias>(Sym("a_type"), u32);
+  EXPECT_EQ(a->symbol(), Symbol(1, ID()));
+  EXPECT_EQ(a->type(), u32);
+  EXPECT_EQ(a->UnwrapIfNeeded(), u32);
+  EXPECT_EQ(u32->UnwrapIfNeeded(), u32);
+}
+
+TEST_F(AstAliasTest, UnwrapIfNeeded_AccessControl) {
+  auto* u32 = create<U32>();
+  auto* ac = create<AccessControl>(AccessControl::kReadOnly, u32);
+  EXPECT_EQ(ac->type(), u32);
+  EXPECT_EQ(ac->UnwrapIfNeeded(), u32);
+}
+
+TEST_F(AstAliasTest, UnwrapIfNeeded_MultiLevel) {
+  auto* u32 = create<U32>();
+  auto* a = create<Alias>(Sym("a_type"), u32);
+  auto* aa = create<Alias>(Sym("aa_type"), a);
+
+  EXPECT_EQ(aa->symbol(), Symbol(2, ID()));
+  EXPECT_EQ(aa->type(), a);
+  EXPECT_EQ(aa->UnwrapIfNeeded(), u32);
+}
+
+TEST_F(AstAliasTest, UnwrapIfNeeded_MultiLevel_AliasAccessControl) {
+  auto* u32 = create<U32>();
+  auto* a = create<Alias>(Sym("a_type"), u32);
+
+  auto* ac = create<AccessControl>(AccessControl::kReadWrite, a);
+  EXPECT_EQ(ac->type(), a);
+  EXPECT_EQ(ac->UnwrapIfNeeded(), u32);
+}
+
+TEST_F(AstAliasTest, UnwrapAll_TwiceAliasPointerTwiceAlias) {
+  auto* u32 = create<U32>();
+  auto* a = create<Alias>(Sym("a_type"), u32);
+  auto* aa = create<Alias>(Sym("aa_type"), a);
+  auto* paa = create<Pointer>(aa, StorageClass::kUniform);
+  auto* apaa = create<Alias>(Sym("paa_type"), paa);
+  auto* aapaa = create<Alias>(Sym("aapaa_type"), apaa);
+
+  EXPECT_EQ(aapaa->symbol(), Symbol(4, ID()));
+  EXPECT_EQ(aapaa->type(), apaa);
+  EXPECT_EQ(aapaa->UnwrapAll(), u32);
+}
+
+TEST_F(AstAliasTest, UnwrapAll_SecondConsecutivePointerBlocksUnrapping) {
+  auto* u32 = create<U32>();
+  auto* a = create<Alias>(Sym("a_type"), u32);
+  auto* aa = create<Alias>(Sym("aa_type"), a);
+
+  auto* paa = create<Pointer>(aa, StorageClass::kUniform);
+  auto* ppaa = create<Pointer>(paa, StorageClass::kUniform);
+  auto* appaa = create<Alias>(Sym("appaa_type"), ppaa);
+  EXPECT_EQ(appaa->UnwrapAll(), paa);
+}
+
+TEST_F(AstAliasTest, UnwrapAll_SecondNonConsecutivePointerBlocksUnrapping) {
+  auto* u32 = create<U32>();
+  auto* a = create<Alias>(Sym("a_type"), u32);
+  auto* aa = create<Alias>(Sym("aa_type"), a);
+  auto* paa = create<Pointer>(aa, StorageClass::kUniform);
+
+  auto* apaa = create<Alias>(Sym("apaa_type"), paa);
+  auto* aapaa = create<Alias>(Sym("aapaa_type"), apaa);
+  auto* paapaa = create<Pointer>(aapaa, StorageClass::kUniform);
+  auto* apaapaa = create<Alias>(Sym("apaapaa_type"), paapaa);
+
+  EXPECT_EQ(apaapaa->UnwrapAll(), paa);
+}
+
+TEST_F(AstAliasTest, UnwrapAll_AccessControlPointer) {
+  auto* u32 = create<U32>();
+  auto* a = create<AccessControl>(AccessControl::kReadOnly, u32);
+  auto* pa = create<Pointer>(a, StorageClass::kUniform);
+  EXPECT_EQ(pa->type(), a);
+  EXPECT_EQ(pa->UnwrapAll(), u32);
+}
+
+TEST_F(AstAliasTest, UnwrapAll_PointerAccessControl) {
+  auto* u32 = create<U32>();
+  auto* p = create<Pointer>(u32, StorageClass::kUniform);
+  auto* a = create<AccessControl>(AccessControl::kReadOnly, p);
+
+  EXPECT_EQ(a->type(), p);
+  EXPECT_EQ(a->UnwrapAll(), u32);
+}
+
+TEST_F(AstAliasTest, UnwrapAliasIfNeeded) {
+  auto* f32 = create<F32>();
+  auto* alias1 = create<Alias>(Sym("alias1"), f32);
+  auto* alias2 = create<Alias>(Sym("alias2"), alias1);
+  auto* alias3 = create<Alias>(Sym("alias3"), alias2);
+  EXPECT_EQ(alias3->UnwrapAliasIfNeeded(), f32);
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/array.cc b/src/ast/array.cc
new file mode 100644
index 0000000..96ac1de
--- /dev/null
+++ b/src/ast/array.cc
@@ -0,0 +1,80 @@
+// 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/ast/array.h"
+
+#include <cmath>
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Array);
+
+namespace tint {
+namespace ast {
+
+Array::Array(ProgramID program_id,
+             const Source& source,
+             Type* subtype,
+             uint32_t size,
+             ast::DecorationList decorations)
+    : Base(program_id, source),
+      subtype_(subtype),
+      size_(size),
+      decos_(decorations) {}
+
+Array::Array(Array&&) = default;
+
+Array::~Array() = default;
+
+std::string Array::type_name() const {
+  TINT_ASSERT(subtype_);
+
+  std::string type_name = "__array" + subtype_->type_name();
+  if (!IsRuntimeArray()) {
+    type_name += "_" + std::to_string(size_);
+  }
+  for (auto* deco : decos_) {
+    if (auto* stride = deco->As<ast::StrideDecoration>()) {
+      type_name += "_stride_" + std::to_string(stride->stride());
+    }
+  }
+
+  return type_name;
+}
+
+std::string Array::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  for (auto* deco : decos_) {
+    if (auto* stride = deco->As<ast::StrideDecoration>()) {
+      out << "[[stride(" << stride->stride() << ")]] ";
+    }
+  }
+  out << "array<" << subtype_->FriendlyName(symbols);
+  if (!IsRuntimeArray()) {
+    out << ", " << size_;
+  }
+  out << ">";
+  return out.str();
+}
+
+Array* Array::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source());
+  auto* ty = ctx->Clone(type());
+  auto decos = ctx->Clone(decorations());
+  return ctx->dst->create<Array>(src, ty, size_, decos);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/array.h b/src/ast/array.h
new file mode 100644
index 0000000..c5ba1b8
--- /dev/null
+++ b/src/ast/array.h
@@ -0,0 +1,79 @@
+// 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_AST_ARRAY_H_
+#define SRC_AST_ARRAY_H_
+
+#include <string>
+
+#include "src/ast/decoration.h"
+#include "src/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// An array type. If size is zero then it is a runtime array.
+class Array : public Castable<Array, Type> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  /// @param subtype the type of the array elements
+  /// @param size the number of elements in the array. `0` represents a
+  /// runtime-sized array.
+  /// @param decorations the array decorations
+  Array(ProgramID program_id,
+        const Source& source,
+        Type* subtype,
+        uint32_t size,
+        ast::DecorationList decorations);
+  /// Move constructor
+  Array(Array&&);
+  ~Array() override;
+
+  /// @returns true if this is a runtime array.
+  /// i.e. the size is determined at runtime
+  bool IsRuntimeArray() const { return size_ == 0; }
+
+  /// @returns the array decorations
+  const ast::DecorationList& decorations() const { return decos_; }
+
+  /// @returns the array type
+  Type* type() const { return subtype_; }
+  /// @returns the array size. Size is 0 for a runtime array
+  uint32_t size() const { return size_; }
+
+  /// @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
+  Array* Clone(CloneContext* ctx) const override;
+
+ private:
+  Type* const subtype_;
+  uint32_t const size_;
+  ast::DecorationList const decos_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_ARRAY_H_
diff --git a/src/ast/array_test.cc b/src/ast/array_test.cc
new file mode 100644
index 0000000..9619bdf
--- /dev/null
+++ b/src/ast/array_test.cc
@@ -0,0 +1,112 @@
+// 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/ast/array.h"
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/bool.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampler.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstArrayTest = TestHelper;
+
+TEST_F(AstArrayTest, CreateSizedArray) {
+  auto* u32 = create<U32>();
+  auto* arr = create<Array>(u32, 3, DecorationList{});
+  EXPECT_EQ(arr->type(), u32);
+  EXPECT_EQ(arr->size(), 3u);
+  EXPECT_TRUE(arr->Is<Array>());
+  EXPECT_FALSE(arr->IsRuntimeArray());
+}
+
+TEST_F(AstArrayTest, CreateRuntimeArray) {
+  auto* u32 = create<U32>();
+  auto* arr = create<Array>(u32, 0, DecorationList{});
+  EXPECT_EQ(arr->type(), u32);
+  EXPECT_EQ(arr->size(), 0u);
+  EXPECT_TRUE(arr->Is<Array>());
+  EXPECT_TRUE(arr->IsRuntimeArray());
+}
+
+TEST_F(AstArrayTest, Is) {
+  auto* i32 = create<I32>();
+  Type* ty = create<Array>(i32, 3, DecorationList{});
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_TRUE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_FALSE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstArrayTest, TypeName) {
+  auto* i32 = create<I32>();
+  auto* arr = create<Array>(i32, 0, DecorationList{});
+  EXPECT_EQ(arr->type_name(), "__array__i32");
+}
+
+TEST_F(AstArrayTest, FriendlyNameRuntimeSized) {
+  auto* i32 = create<I32>();
+  auto* arr = create<Array>(i32, 0, DecorationList{});
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32>");
+}
+
+TEST_F(AstArrayTest, FriendlyNameStaticSized) {
+  auto* i32 = create<I32>();
+  auto* arr = create<Array>(i32, 5, DecorationList{});
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, 5>");
+}
+
+TEST_F(AstArrayTest, FriendlyNameWithStride) {
+  auto* i32 = create<I32>();
+  auto* arr =
+      create<Array>(i32, 5, DecorationList{create<StrideDecoration>(32)});
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "[[stride(32)]] array<i32, 5>");
+}
+
+TEST_F(AstArrayTest, TypeName_RuntimeArray) {
+  auto* i32 = create<I32>();
+  auto* arr = create<Array>(i32, 3, DecorationList{});
+  EXPECT_EQ(arr->type_name(), "__array__i32_3");
+}
+
+TEST_F(AstArrayTest, TypeName_WithStride) {
+  auto* i32 = create<I32>();
+  auto* arr =
+      create<Array>(i32, 3, DecorationList{create<StrideDecoration>(16)});
+  EXPECT_EQ(arr->type_name(), "__array__i32_3_stride_16");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/ast_type.cc b/src/ast/ast_type.cc
new file mode 100644
index 0000000..766b7a5
--- /dev/null
+++ b/src/ast/ast_type.cc
@@ -0,0 +1,142 @@
+// 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/ast/type.h"
+
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/bool.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampler.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Type);
+
+namespace tint {
+namespace ast {
+
+Type::Type(ProgramID program_id, const Source& source)
+    : Base(program_id, source) {}
+
+Type::Type(Type&&) = default;
+
+Type::~Type() = default;
+
+Type* Type::UnwrapPtrIfNeeded() {
+  if (auto* ptr = As<Pointer>()) {
+    return ptr->type();
+  }
+  return this;
+}
+
+Type* Type::UnwrapAliasIfNeeded() {
+  Type* unwrapped = this;
+  while (auto* ptr = unwrapped->As<Alias>()) {
+    unwrapped = ptr->type();
+  }
+  return unwrapped;
+}
+
+Type* Type::UnwrapIfNeeded() {
+  auto* where = this;
+  while (true) {
+    if (auto* alias = where->As<Alias>()) {
+      where = alias->type();
+    } else if (auto* access = where->As<AccessControl>()) {
+      where = access->type();
+    } else {
+      break;
+    }
+  }
+  return where;
+}
+
+Type* Type::UnwrapAll() {
+  return UnwrapIfNeeded()->UnwrapPtrIfNeeded()->UnwrapIfNeeded();
+}
+
+bool Type::is_scalar() const {
+  return IsAnyOf<F32, U32, I32, Bool>();
+}
+
+bool Type::is_float_scalar() const {
+  return Is<F32>();
+}
+
+bool Type::is_float_matrix() const {
+  return Is<Matrix>(
+      [](const Matrix* m) { return m->type()->is_float_scalar(); });
+}
+
+bool Type::is_float_vector() const {
+  return Is<Vector>(
+      [](const Vector* v) { return v->type()->is_float_scalar(); });
+}
+
+bool Type::is_float_scalar_or_vector() const {
+  return is_float_scalar() || is_float_vector();
+}
+
+bool Type::is_float_scalar_or_vector_or_matrix() const {
+  return is_float_scalar() || is_float_vector() || is_float_matrix();
+}
+
+bool Type::is_integer_scalar() const {
+  return IsAnyOf<U32, I32>();
+}
+
+bool Type::is_unsigned_integer_vector() const {
+  return Is<Vector>([](const Vector* v) { return v->type()->Is<U32>(); });
+}
+
+bool Type::is_signed_integer_vector() const {
+  return Is<Vector>([](const Vector* v) { return v->type()->Is<I32>(); });
+}
+
+bool Type::is_unsigned_scalar_or_vector() const {
+  return Is<U32>() || is_unsigned_integer_vector();
+}
+
+bool Type::is_signed_scalar_or_vector() const {
+  return Is<I32>() || is_signed_integer_vector();
+}
+
+bool Type::is_integer_scalar_or_vector() const {
+  return is_unsigned_scalar_or_vector() || is_signed_scalar_or_vector();
+}
+
+bool Type::is_bool_vector() const {
+  return Is<Vector>([](const Vector* v) { return v->type()->Is<Bool>(); });
+}
+
+bool Type::is_bool_scalar_or_vector() const {
+  return Is<Bool>() || is_bool_vector();
+}
+
+bool Type::is_handle() const {
+  return IsAnyOf<Sampler, Texture>();
+}
+
+void Type::to_str(const sem::Info&, std::ostream& out, size_t indent) const {
+  make_indent(out, indent);
+  out << type_name();
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/bool.cc b/src/ast/bool.cc
new file mode 100644
index 0000000..d212812
--- /dev/null
+++ b/src/ast/bool.cc
@@ -0,0 +1,45 @@
+// 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/ast/bool.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Bool);
+
+namespace tint {
+namespace ast {
+
+Bool::Bool(ProgramID program_id, const Source& source)
+    : Base(program_id, source) {}
+
+Bool::Bool(Bool&&) = default;
+
+Bool::~Bool() = default;
+
+std::string Bool::type_name() const {
+  return "__bool";
+}
+
+std::string Bool::FriendlyName(const SymbolTable&) const {
+  return "bool";
+}
+
+Bool* Bool::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source());
+  return ctx->dst->create<Bool>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/bool.h b/src/ast/bool.h
new file mode 100644
index 0000000..ad5b858
--- /dev/null
+++ b/src/ast/bool.h
@@ -0,0 +1,59 @@
+// 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_AST_BOOL_H_
+#define SRC_AST_BOOL_H_
+
+#include <string>
+
+#include "src/ast/type.h"
+
+// X11 likes to #define Bool leading to confusing error messages.
+// If its defined, undefine it.
+#ifdef Bool
+#undef Bool
+#endif
+
+namespace tint {
+namespace ast {
+
+/// A boolean type
+class Bool : public Castable<Bool, Type> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  Bool(ProgramID program_id, const Source& source);
+  /// Move constructor
+  Bool(Bool&&);
+  ~Bool() override;
+
+  /// @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
+  Bool* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_BOOL_H_
diff --git a/src/ast/bool_test.cc b/src/ast/bool_test.cc
new file mode 100644
index 0000000..3d2b2a8
--- /dev/null
+++ b/src/ast/bool_test.cc
@@ -0,0 +1,65 @@
+// 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/ast/bool.h"
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampler.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstBoolTest = TestHelper;
+
+TEST_F(AstBoolTest, Is) {
+  Type* ty = create<Bool>();
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_TRUE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_FALSE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstBoolTest, TypeName) {
+  auto* b = create<Bool>();
+  EXPECT_EQ(b->type_name(), "__bool");
+}
+
+TEST_F(AstBoolTest, FriendlyName) {
+  auto* b = create<Bool>();
+  EXPECT_EQ(b->FriendlyName(Symbols()), "bool");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/depth_texture.cc b/src/ast/depth_texture.cc
new file mode 100644
index 0000000..b669113
--- /dev/null
+++ b/src/ast/depth_texture.cc
@@ -0,0 +1,61 @@
+// 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/ast/depth_texture.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::DepthTexture);
+
+namespace tint {
+namespace ast {
+namespace {
+
+bool IsValidDepthDimension(TextureDimension dim) {
+  return dim == TextureDimension::k2d || dim == TextureDimension::k2dArray ||
+         dim == TextureDimension::kCube || dim == TextureDimension::kCubeArray;
+}
+
+}  // namespace
+
+DepthTexture::DepthTexture(ProgramID program_id,
+                           const Source& source,
+                           TextureDimension dim)
+    : Base(program_id, source, dim) {
+  TINT_ASSERT(IsValidDepthDimension(dim));
+}
+
+DepthTexture::DepthTexture(DepthTexture&&) = default;
+
+DepthTexture::~DepthTexture() = default;
+
+std::string DepthTexture::type_name() const {
+  std::ostringstream out;
+  out << "__depth_texture_" << dim();
+  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 {
+  auto src = ctx->Clone(source());
+  return ctx->dst->create<DepthTexture>(src, dim());
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/depth_texture.h b/src/ast/depth_texture.h
new file mode 100644
index 0000000..7fe82e5
--- /dev/null
+++ b/src/ast/depth_texture.h
@@ -0,0 +1,56 @@
+// 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_AST_DEPTH_TEXTURE_H_
+#define SRC_AST_DEPTH_TEXTURE_H_
+
+#include <string>
+
+#include "src/ast/texture.h"
+
+namespace tint {
+namespace ast {
+
+/// A depth texture type.
+class DepthTexture : public Castable<DepthTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  /// @param dim the dimensionality of the texture
+  DepthTexture(ProgramID program_id,
+               const Source& source,
+               TextureDimension dim);
+  /// Move constructor
+  DepthTexture(DepthTexture&&);
+  ~DepthTexture() override;
+
+  /// @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
+  DepthTexture* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_DEPTH_TEXTURE_H_
diff --git a/src/ast/depth_texture_test.cc b/src/ast/depth_texture_test.cc
new file mode 100644
index 0000000..a682d9b
--- /dev/null
+++ b/src/ast/depth_texture_test.cc
@@ -0,0 +1,80 @@
+// 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/ast/depth_texture.h"
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampled_texture.h"
+#include "src/ast/sampler.h"
+#include "src/ast/storage_texture.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstDepthTextureTest = TestHelper;
+
+TEST_F(AstDepthTextureTest, Is) {
+  Type* ty = create<DepthTexture>(TextureDimension::kCube);
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_TRUE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstDepthTextureTest, IsTexture) {
+  Texture* ty = create<DepthTexture>(TextureDimension::kCube);
+  EXPECT_TRUE(ty->Is<DepthTexture>());
+  EXPECT_FALSE(ty->Is<SampledTexture>());
+  EXPECT_FALSE(ty->Is<StorageTexture>());
+}
+
+TEST_F(AstDepthTextureTest, Dim) {
+  auto* d = create<DepthTexture>(TextureDimension::kCube);
+  EXPECT_EQ(d->dim(), TextureDimension::kCube);
+}
+
+TEST_F(AstDepthTextureTest, TypeName) {
+  auto* d = create<DepthTexture>(TextureDimension::kCube);
+  EXPECT_EQ(d->type_name(), "__depth_texture_cube");
+}
+
+TEST_F(AstDepthTextureTest, FriendlyName) {
+  auto* d = create<DepthTexture>(TextureDimension::kCube);
+  EXPECT_EQ(d->FriendlyName(Symbols()), "texture_depth_cube");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/f32.cc b/src/ast/f32.cc
new file mode 100644
index 0000000..66a8fe0
--- /dev/null
+++ b/src/ast/f32.cc
@@ -0,0 +1,45 @@
+// 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/ast/f32.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::F32);
+
+namespace tint {
+namespace ast {
+
+F32::F32(ProgramID program_id, const Source& source)
+    : Base(program_id, source) {}
+
+F32::F32(F32&&) = default;
+
+F32::~F32() = default;
+
+std::string F32::type_name() const {
+  return "__f32";
+}
+
+std::string F32::FriendlyName(const SymbolTable&) const {
+  return "f32";
+}
+
+F32* F32::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source());
+  return ctx->dst->create<F32>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/f32.h b/src/ast/f32.h
new file mode 100644
index 0000000..42e9bc7
--- /dev/null
+++ b/src/ast/f32.h
@@ -0,0 +1,53 @@
+// 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_AST_F32_H_
+#define SRC_AST_F32_H_
+
+#include <string>
+
+#include "src/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A float 32 type
+class F32 : public Castable<F32, Type> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  F32(ProgramID program_id, const Source& source);
+  /// Move constructor
+  F32(F32&&);
+  ~F32() override;
+
+  /// @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
+  F32* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_F32_H_
diff --git a/src/ast/f32_test.cc b/src/ast/f32_test.cc
new file mode 100644
index 0000000..2045d83
--- /dev/null
+++ b/src/ast/f32_test.cc
@@ -0,0 +1,65 @@
+// 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/ast/f32.h"
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampler.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstF32Test = TestHelper;
+
+TEST_F(AstF32Test, Is) {
+  Type* ty = create<F32>();
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_TRUE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_FALSE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstF32Test, TypeName) {
+  auto* f = create<F32>();
+  EXPECT_EQ(f->type_name(), "__f32");
+}
+
+TEST_F(AstF32Test, FriendlyName) {
+  auto* f = create<F32>();
+  EXPECT_EQ(f->FriendlyName(Symbols()), "f32");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/i32.cc b/src/ast/i32.cc
new file mode 100644
index 0000000..358e775
--- /dev/null
+++ b/src/ast/i32.cc
@@ -0,0 +1,45 @@
+// 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/ast/i32.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::I32);
+
+namespace tint {
+namespace ast {
+
+I32::I32(ProgramID program_id, const Source& source)
+    : Base(program_id, source) {}
+
+I32::I32(I32&&) = default;
+
+I32::~I32() = default;
+
+std::string I32::type_name() const {
+  return "__i32";
+}
+
+std::string I32::FriendlyName(const SymbolTable&) const {
+  return "i32";
+}
+
+I32* I32::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source());
+  return ctx->dst->create<I32>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/i32.h b/src/ast/i32.h
new file mode 100644
index 0000000..182f102
--- /dev/null
+++ b/src/ast/i32.h
@@ -0,0 +1,53 @@
+// 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_AST_I32_H_
+#define SRC_AST_I32_H_
+
+#include <string>
+
+#include "src/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A signed int 32 type.
+class I32 : public Castable<I32, Type> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  I32(ProgramID program_id, const Source& source);
+  /// Move constructor
+  I32(I32&&);
+  ~I32() override;
+
+  /// @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
+  I32* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_I32_H_
diff --git a/src/ast/i32_test.cc b/src/ast/i32_test.cc
new file mode 100644
index 0000000..7a85707
--- /dev/null
+++ b/src/ast/i32_test.cc
@@ -0,0 +1,66 @@
+// 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/ast/i32.h"
+
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/f32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampler.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstI32Test = TestHelper;
+
+TEST_F(AstI32Test, Is) {
+  Type* ty = create<I32>();
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_TRUE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_FALSE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstI32Test, TypeName) {
+  auto* i = create<I32>();
+  EXPECT_EQ(i->type_name(), "__i32");
+}
+
+TEST_F(AstI32Test, FriendlyName) {
+  auto* i = create<I32>();
+  EXPECT_EQ(i->FriendlyName(Symbols()), "i32");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/intrinsic_texture_helper_test.cc b/src/ast/intrinsic_texture_helper_test.cc
index e044cd6..1669270 100644
--- a/src/ast/intrinsic_texture_helper_test.cc
+++ b/src/ast/intrinsic_texture_helper_test.cc
@@ -63,7 +63,7 @@
 TextureOverloadCase::TextureOverloadCase(
     ValidTextureOverload o,
     const char* d,
-    AccessControl access,
+    AccessControl::Access access,
     sem::ImageFormat i,
     sem::TextureDimension dims,
     TextureDataType datatype,
diff --git a/src/ast/intrinsic_texture_helper_test.h b/src/ast/intrinsic_texture_helper_test.h
index e5c5628..b7a9a80 100644
--- a/src/ast/intrinsic_texture_helper_test.h
+++ b/src/ast/intrinsic_texture_helper_test.h
@@ -198,7 +198,7 @@
   /// Constructor for textureLoad() with storage textures
   TextureOverloadCase(ValidTextureOverload,
                       const char*,
-                      AccessControl,
+                      AccessControl::Access,
                       sem::ImageFormat,
                       sem::TextureDimension,
                       TextureDataType,
@@ -236,7 +236,7 @@
   sem::SamplerKind const sampler_kind = sem::SamplerKind::kSampler;
   /// The access control for the storage texture
   /// Used only when texture_kind is kStorage
-  AccessControl const access_control = AccessControl::kReadWrite;
+  AccessControl::Access const access_control = AccessControl::kReadWrite;
   /// The image format for the storage texture
   /// Used only when texture_kind is kStorage
   sem::ImageFormat const image_format = sem::ImageFormat::kNone;
diff --git a/src/ast/matrix.cc b/src/ast/matrix.cc
new file mode 100644
index 0000000..da8e019
--- /dev/null
+++ b/src/ast/matrix.cc
@@ -0,0 +1,63 @@
+// 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/ast/matrix.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Matrix);
+
+namespace tint {
+namespace ast {
+
+Matrix::Matrix(ProgramID program_id,
+               const Source& source,
+               Type* subtype,
+               uint32_t rows,
+               uint32_t columns)
+    : Base(program_id, source),
+      subtype_(subtype),
+      rows_(rows),
+      columns_(columns) {
+  TINT_ASSERT(rows > 1);
+  TINT_ASSERT(rows < 5);
+  TINT_ASSERT(columns > 1);
+  TINT_ASSERT(columns < 5);
+}
+
+Matrix::Matrix(Matrix&&) = default;
+
+Matrix::~Matrix() = default;
+
+std::string Matrix::type_name() const {
+  return "__mat_" + std::to_string(rows_) + "_" + std::to_string(columns_) +
+         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();
+}
+
+Matrix* Matrix::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source());
+  auto* ty = ctx->Clone(type());
+  return ctx->dst->create<Matrix>(src, ty, rows_, columns_);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/matrix.h b/src/ast/matrix.h
new file mode 100644
index 0000000..1a419dd0
--- /dev/null
+++ b/src/ast/matrix.h
@@ -0,0 +1,72 @@
+// 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_AST_MATRIX_H_
+#define SRC_AST_MATRIX_H_
+
+#include <string>
+
+#include "src/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A matrix type
+class Matrix : public Castable<Matrix, Type> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  /// @param subtype type matrix type
+  /// @param rows the number of rows in the matrix
+  /// @param columns the number of columns in the matrix
+  Matrix(ProgramID program_id,
+         const Source& source,
+         Type* subtype,
+         uint32_t rows,
+         uint32_t columns);
+  /// Move constructor
+  Matrix(Matrix&&);
+  ~Matrix() override;
+
+  /// @returns the type of the matrix
+  Type* type() const { return subtype_; }
+  /// @returns the number of rows in the matrix
+  uint32_t rows() const { return rows_; }
+  /// @returns the number of columns in the matrix
+  uint32_t columns() const { return columns_; }
+
+  /// @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
+  Matrix* Clone(CloneContext* ctx) const override;
+
+ private:
+  Type* const subtype_;
+  uint32_t const rows_;
+  uint32_t const columns_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_MATRIX_H_
diff --git a/src/ast/matrix_test.cc b/src/ast/matrix_test.cc
new file mode 100644
index 0000000..fc5e40b
--- /dev/null
+++ b/src/ast/matrix_test.cc
@@ -0,0 +1,76 @@
+// 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/ast/matrix.h"
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampler.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstMatrixTest = TestHelper;
+
+TEST_F(AstMatrixTest, Creation) {
+  auto* i32 = create<I32>();
+  auto* m = create<Matrix>(i32, 2, 4);
+  EXPECT_EQ(m->type(), i32);
+  EXPECT_EQ(m->rows(), 2u);
+  EXPECT_EQ(m->columns(), 4u);
+}
+
+TEST_F(AstMatrixTest, Is) {
+  auto* i32 = create<I32>();
+  Type* ty = create<Matrix>(i32, 2, 3);
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_TRUE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_FALSE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstMatrixTest, TypeName) {
+  auto* i32 = create<I32>();
+  auto* m = create<Matrix>(i32, 2, 3);
+  EXPECT_EQ(m->type_name(), "__mat_2_3__i32");
+}
+
+TEST_F(AstMatrixTest, FriendlyName) {
+  auto* i32 = create<I32>();
+  auto* m = create<Matrix>(i32, 3, 2);
+  EXPECT_EQ(m->FriendlyName(Symbols()), "mat2x3<i32>");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/module.cc b/src/ast/module.cc
index 31ac2f3..d6cc39a 100644
--- a/src/ast/module.cc
+++ b/src/ast/module.cc
@@ -89,7 +89,6 @@
         str->impl()->to_str(sem, out, indent);
       }
     } else if (auto* str = ty->As<sem::StructType>()) {
-      out << str->symbol().to_str() << " ";
       str->impl()->to_str(sem, out, indent);
     }
   }
diff --git a/src/ast/multisampled_texture.cc b/src/ast/multisampled_texture.cc
new file mode 100644
index 0000000..5f907ab
--- /dev/null
+++ b/src/ast/multisampled_texture.cc
@@ -0,0 +1,58 @@
+// 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/ast/multisampled_texture.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::MultisampledTexture);
+
+namespace tint {
+namespace ast {
+
+MultisampledTexture::MultisampledTexture(ProgramID program_id,
+                                         const Source& source,
+                                         TextureDimension dim,
+                                         Type* type)
+    : Base(program_id, source, dim), type_(type) {
+  TINT_ASSERT(type_);
+}
+
+MultisampledTexture::MultisampledTexture(MultisampledTexture&&) = default;
+
+MultisampledTexture::~MultisampledTexture() = default;
+
+std::string MultisampledTexture::type_name() const {
+  std::ostringstream out;
+  out << "__multisampled_texture_" << dim() << type_->type_name();
+  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 {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source());
+  auto* ty = ctx->Clone(type());
+  return ctx->dst->create<MultisampledTexture>(src, dim(), ty);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/multisampled_texture.h b/src/ast/multisampled_texture.h
new file mode 100644
index 0000000..9fcc2a8
--- /dev/null
+++ b/src/ast/multisampled_texture.h
@@ -0,0 +1,64 @@
+// 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_AST_MULTISAMPLED_TEXTURE_H_
+#define SRC_AST_MULTISAMPLED_TEXTURE_H_
+
+#include <string>
+
+#include "src/ast/texture.h"
+
+namespace tint {
+namespace ast {
+
+/// A multisampled texture type.
+class MultisampledTexture : public Castable<MultisampledTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  /// @param dim the dimensionality of the texture
+  /// @param type the data type of the multisampled texture
+  MultisampledTexture(ProgramID program_id,
+                      const Source& source,
+                      TextureDimension dim,
+                      Type* type);
+  /// Move constructor
+  MultisampledTexture(MultisampledTexture&&);
+  ~MultisampledTexture() override;
+
+  /// @returns the subtype of the sampled texture
+  Type* type() const { return type_; }
+
+  /// @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
+  MultisampledTexture* Clone(CloneContext* ctx) const override;
+
+ private:
+  Type* const type_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_MULTISAMPLED_TEXTURE_H_
diff --git a/src/ast/multisampled_texture_test.cc b/src/ast/multisampled_texture_test.cc
new file mode 100644
index 0000000..277c25a
--- /dev/null
+++ b/src/ast/multisampled_texture_test.cc
@@ -0,0 +1,94 @@
+// 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/ast/multisampled_texture.h"
+
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/depth_texture.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampled_texture.h"
+#include "src/ast/sampler.h"
+#include "src/ast/storage_texture.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstMultisampledTextureTest = TestHelper;
+
+TEST_F(AstMultisampledTextureTest, Is) {
+  auto* f32 = create<F32>();
+  Type* ty = create<MultisampledTexture>(TextureDimension::kCube, f32);
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_TRUE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstMultisampledTextureTest, IsTexture) {
+  auto* f32 = create<F32>();
+  Texture* ty = create<MultisampledTexture>(TextureDimension::kCube, f32);
+  EXPECT_FALSE(ty->Is<DepthTexture>());
+  EXPECT_TRUE(ty->Is<MultisampledTexture>());
+  EXPECT_FALSE(ty->Is<SampledTexture>());
+  EXPECT_FALSE(ty->Is<StorageTexture>());
+}
+
+TEST_F(AstMultisampledTextureTest, Dim) {
+  auto* f32 = create<F32>();
+  auto* s = create<MultisampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->dim(), TextureDimension::k3d);
+}
+
+TEST_F(AstMultisampledTextureTest, Type) {
+  auto* f32 = create<F32>();
+  auto* s = create<MultisampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->type(), f32);
+}
+
+TEST_F(AstMultisampledTextureTest, TypeName) {
+  auto* f32 = create<F32>();
+  auto* s = create<MultisampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->type_name(), "__multisampled_texture_3d__f32");
+}
+
+TEST_F(AstMultisampledTextureTest, FriendlyName) {
+  auto* f32 = create<F32>();
+  auto* s = create<MultisampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->FriendlyName(Symbols()), "texture_multisampled_3d<f32>");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/pointer.cc b/src/ast/pointer.cc
new file mode 100644
index 0000000..3a968dc
--- /dev/null
+++ b/src/ast/pointer.cc
@@ -0,0 +1,60 @@
+// 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/ast/pointer.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Pointer);
+
+namespace tint {
+namespace ast {
+
+Pointer::Pointer(ProgramID program_id,
+                 const Source& source,
+                 Type* subtype,
+                 ast::StorageClass storage_class)
+    : Base(program_id, source),
+      subtype_(subtype),
+      storage_class_(storage_class) {}
+
+std::string Pointer::type_name() const {
+  std::ostringstream out;
+  out << "__ptr_" << storage_class_ << subtype_->type_name();
+  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;
+
+Pointer* Pointer::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source());
+  auto* ty = ctx->Clone(type());
+  return ctx->dst->create<Pointer>(src, ty, storage_class_);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/pointer.h b/src/ast/pointer.h
new file mode 100644
index 0000000..55882ed
--- /dev/null
+++ b/src/ast/pointer.h
@@ -0,0 +1,68 @@
+// 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_AST_POINTER_H_
+#define SRC_AST_POINTER_H_
+
+#include <string>
+
+#include "src/ast/storage_class.h"
+#include "src/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A pointer type.
+class Pointer : public Castable<Pointer, Type> {
+ public:
+  /// Construtor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  /// @param subtype the pointee type
+  /// @param storage_class the storage class of the pointer
+  Pointer(ProgramID program_id,
+          const Source& source,
+          Type* subtype,
+          ast::StorageClass storage_class);
+  /// Move constructor
+  Pointer(Pointer&&);
+  ~Pointer() override;
+
+  /// @returns the pointee type
+  Type* type() const { return subtype_; }
+  /// @returns the storage class of the pointer
+  ast::StorageClass storage_class() const { return storage_class_; }
+
+  /// @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
+  Pointer* Clone(CloneContext* ctx) const override;
+
+ private:
+  Type* const subtype_;
+  ast::StorageClass const storage_class_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_POINTER_H_
diff --git a/src/ast/pointer_test.cc b/src/ast/pointer_test.cc
new file mode 100644
index 0000000..9e11d2e
--- /dev/null
+++ b/src/ast/pointer_test.cc
@@ -0,0 +1,81 @@
+// 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/ast/pointer.h"
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/sampler.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstPointerTest = TestHelper;
+
+TEST_F(AstPointerTest, Creation) {
+  auto* i32 = create<I32>();
+  auto* p = create<Pointer>(i32, ast::StorageClass::kStorage);
+  EXPECT_EQ(p->type(), i32);
+  EXPECT_EQ(p->storage_class(), ast::StorageClass::kStorage);
+}
+
+TEST_F(AstPointerTest, Is) {
+  auto* i32 = create<I32>();
+  Type* ty = create<Pointer>(i32, ast::StorageClass::kFunction);
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_TRUE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_FALSE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstPointerTest, TypeName) {
+  auto* i32 = create<I32>();
+  auto* p = create<Pointer>(i32, ast::StorageClass::kWorkgroup);
+  EXPECT_EQ(p->type_name(), "__ptr_workgroup__i32");
+}
+
+TEST_F(AstPointerTest, FriendlyNameWithStorageClass) {
+  auto* i32 = create<I32>();
+  auto* p = create<Pointer>(i32, ast::StorageClass::kWorkgroup);
+  EXPECT_EQ(p->FriendlyName(Symbols()), "ptr<workgroup, i32>");
+}
+
+TEST_F(AstPointerTest, FriendlyNameWithoutStorageClass) {
+  auto* i32 = create<I32>();
+  auto* p = create<Pointer>(i32, ast::StorageClass::kNone);
+  EXPECT_EQ(p->FriendlyName(Symbols()), "ptr<i32>");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/sampled_texture.cc b/src/ast/sampled_texture.cc
new file mode 100644
index 0000000..9e1ad0d
--- /dev/null
+++ b/src/ast/sampled_texture.cc
@@ -0,0 +1,56 @@
+// 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/ast/sampled_texture.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::SampledTexture);
+
+namespace tint {
+namespace ast {
+
+SampledTexture::SampledTexture(ProgramID program_id,
+                               const Source& source,
+                               TextureDimension dim,
+                               Type* type)
+    : Base(program_id, source, dim), type_(type) {
+  TINT_ASSERT(type_);
+}
+
+SampledTexture::SampledTexture(SampledTexture&&) = default;
+
+SampledTexture::~SampledTexture() = default;
+
+std::string SampledTexture::type_name() const {
+  std::ostringstream out;
+  out << "__sampled_texture_" << dim() << type_->type_name();
+  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 {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source());
+  auto* ty = ctx->Clone(type());
+  return ctx->dst->create<SampledTexture>(src, dim(), ty);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/sampled_texture.h b/src/ast/sampled_texture.h
new file mode 100644
index 0000000..f7d9af9
--- /dev/null
+++ b/src/ast/sampled_texture.h
@@ -0,0 +1,64 @@
+// 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_AST_SAMPLED_TEXTURE_H_
+#define SRC_AST_SAMPLED_TEXTURE_H_
+
+#include <string>
+
+#include "src/ast/texture.h"
+
+namespace tint {
+namespace ast {
+
+/// A sampled texture type.
+class SampledTexture : public Castable<SampledTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  /// @param dim the dimensionality of the texture
+  /// @param type the data type of the sampled texture
+  SampledTexture(ProgramID program_id,
+                 const Source& source,
+                 TextureDimension dim,
+                 Type* type);
+  /// Move constructor
+  SampledTexture(SampledTexture&&);
+  ~SampledTexture() override;
+
+  /// @returns the subtype of the sampled texture
+  Type* type() const { return type_; }
+
+  /// @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
+  SampledTexture* Clone(CloneContext* ctx) const override;
+
+ private:
+  Type* const type_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_SAMPLED_TEXTURE_H_
diff --git a/src/ast/sampled_texture_test.cc b/src/ast/sampled_texture_test.cc
new file mode 100644
index 0000000..4cba040
--- /dev/null
+++ b/src/ast/sampled_texture_test.cc
@@ -0,0 +1,92 @@
+// 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/ast/sampled_texture.h"
+
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/depth_texture.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampler.h"
+#include "src/ast/storage_texture.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstSampledTextureTest = TestHelper;
+
+TEST_F(AstSampledTextureTest, Is) {
+  auto* f32 = create<F32>();
+  Type* ty = create<SampledTexture>(TextureDimension::kCube, f32);
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_TRUE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstSampledTextureTest, IsTexture) {
+  auto* f32 = create<F32>();
+  Texture* ty = create<SampledTexture>(TextureDimension::kCube, f32);
+  EXPECT_FALSE(ty->Is<DepthTexture>());
+  EXPECT_TRUE(ty->Is<SampledTexture>());
+  EXPECT_FALSE(ty->Is<StorageTexture>());
+}
+
+TEST_F(AstSampledTextureTest, Dim) {
+  auto* f32 = create<F32>();
+  auto* s = create<SampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->dim(), TextureDimension::k3d);
+}
+
+TEST_F(AstSampledTextureTest, Type) {
+  auto* f32 = create<F32>();
+  auto* s = create<SampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->type(), f32);
+}
+
+TEST_F(AstSampledTextureTest, TypeName) {
+  auto* f32 = create<F32>();
+  auto* s = create<SampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->type_name(), "__sampled_texture_3d__f32");
+}
+
+TEST_F(AstSampledTextureTest, FriendlyName) {
+  auto* f32 = create<F32>();
+  auto* s = create<SampledTexture>(TextureDimension::k3d, f32);
+  EXPECT_EQ(s->FriendlyName(Symbols()), "texture_3d<f32>");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/sampler.cc b/src/ast/sampler.cc
new file mode 100644
index 0000000..bcf95c8
--- /dev/null
+++ b/src/ast/sampler.cc
@@ -0,0 +1,58 @@
+// 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/ast/sampler.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Sampler);
+
+namespace tint {
+namespace ast {
+
+std::ostream& operator<<(std::ostream& out, SamplerKind kind) {
+  switch (kind) {
+    case SamplerKind::kSampler:
+      out << "sampler";
+      break;
+    case SamplerKind::kComparisonSampler:
+      out << "comparison_sampler";
+      break;
+  }
+  return out;
+}
+
+Sampler::Sampler(ProgramID program_id, const Source& source, SamplerKind kind)
+    : Base(program_id, source), kind_(kind) {}
+
+Sampler::Sampler(Sampler&&) = default;
+
+Sampler::~Sampler() = default;
+
+std::string Sampler::type_name() const {
+  return std::string("__sampler_") +
+         (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 {
+  auto src = ctx->Clone(source());
+  return ctx->dst->create<Sampler>(src, kind_);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/sampler.h b/src/ast/sampler.h
new file mode 100644
index 0000000..3800de9
--- /dev/null
+++ b/src/ast/sampler.h
@@ -0,0 +1,76 @@
+// 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_AST_SAMPLER_H_
+#define SRC_AST_SAMPLER_H_
+
+#include <string>
+
+#include "src/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// The different kinds of samplers
+enum class SamplerKind {
+  /// A regular sampler
+  kSampler,
+  /// A comparison sampler
+  kComparisonSampler
+};
+
+/// @param out the std::ostream to write to
+/// @param kind the SamplerKind
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, SamplerKind kind);
+
+/// A sampler type.
+class Sampler : public Castable<Sampler, Type> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  /// @param kind the kind of sampler
+  Sampler(ProgramID program_id, const Source& source, SamplerKind kind);
+  /// Move constructor
+  Sampler(Sampler&&);
+  ~Sampler() override;
+
+  /// @returns the sampler type
+  SamplerKind kind() const { return kind_; }
+
+  /// @returns true if this is a comparison sampler
+  bool IsComparison() const { return kind_ == SamplerKind::kComparisonSampler; }
+
+  /// @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
+  Sampler* Clone(CloneContext* ctx) const override;
+
+ private:
+  SamplerKind const kind_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_SAMPLER_H_
diff --git a/src/ast/sampler_test.cc b/src/ast/sampler_test.cc
new file mode 100644
index 0000000..9ddf1fe
--- /dev/null
+++ b/src/ast/sampler_test.cc
@@ -0,0 +1,86 @@
+// 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/ast/sampler.h"
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstSamplerTest = TestHelper;
+
+TEST_F(AstSamplerTest, Creation) {
+  auto* s = create<Sampler>(SamplerKind::kSampler);
+  EXPECT_EQ(s->kind(), SamplerKind::kSampler);
+}
+
+TEST_F(AstSamplerTest, Creation_ComparisonSampler) {
+  auto* s = create<Sampler>(SamplerKind::kComparisonSampler);
+  EXPECT_EQ(s->kind(), SamplerKind::kComparisonSampler);
+  EXPECT_TRUE(s->IsComparison());
+}
+
+TEST_F(AstSamplerTest, Is) {
+  Type* ty = create<Sampler>(SamplerKind::kSampler);
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_TRUE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_FALSE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstSamplerTest, TypeName_Sampler) {
+  auto* s = create<Sampler>(SamplerKind::kSampler);
+  EXPECT_EQ(s->type_name(), "__sampler_sampler");
+}
+
+TEST_F(AstSamplerTest, TypeName_Comparison) {
+  auto* s = create<Sampler>(SamplerKind::kComparisonSampler);
+  EXPECT_EQ(s->type_name(), "__sampler_comparison");
+}
+
+TEST_F(AstSamplerTest, FriendlyNameSampler) {
+  auto* s = create<Sampler>(SamplerKind::kSampler);
+  EXPECT_EQ(s->FriendlyName(Symbols()), "sampler");
+}
+
+TEST_F(AstSamplerTest, FriendlyNameComparisonSampler) {
+  auto* s = create<Sampler>(SamplerKind::kComparisonSampler);
+  EXPECT_EQ(s->FriendlyName(Symbols()), "sampler_comparison");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/storage_texture.cc b/src/ast/storage_texture.cc
new file mode 100644
index 0000000..f1f23d6
--- /dev/null
+++ b/src/ast/storage_texture.cc
@@ -0,0 +1,227 @@
+// 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/ast/storage_texture.h"
+
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/u32.h"
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::StorageTexture);
+
+namespace tint {
+namespace ast {
+
+// Note, these names match the names in the WGSL spec. This behaviour is used
+// in the WGSL writer to emit the texture format names.
+std::ostream& operator<<(std::ostream& out, ImageFormat format) {
+  switch (format) {
+    case ImageFormat::kNone:
+      out << "none";
+      break;
+    case ImageFormat::kR8Unorm:
+      out << "r8unorm";
+      break;
+    case ImageFormat::kR8Snorm:
+      out << "r8snorm";
+      break;
+    case ImageFormat::kR8Uint:
+      out << "r8uint";
+      break;
+    case ImageFormat::kR8Sint:
+      out << "r8sint";
+      break;
+    case ImageFormat::kR16Uint:
+      out << "r16uint";
+      break;
+    case ImageFormat::kR16Sint:
+      out << "r16sint";
+      break;
+    case ImageFormat::kR16Float:
+      out << "r16float";
+      break;
+    case ImageFormat::kRg8Unorm:
+      out << "rg8unorm";
+      break;
+    case ImageFormat::kRg8Snorm:
+      out << "rg8snorm";
+      break;
+    case ImageFormat::kRg8Uint:
+      out << "rg8uint";
+      break;
+    case ImageFormat::kRg8Sint:
+      out << "rg8sint";
+      break;
+    case ImageFormat::kR32Uint:
+      out << "r32uint";
+      break;
+    case ImageFormat::kR32Sint:
+      out << "r32sint";
+      break;
+    case ImageFormat::kR32Float:
+      out << "r32float";
+      break;
+    case ImageFormat::kRg16Uint:
+      out << "rg16uint";
+      break;
+    case ImageFormat::kRg16Sint:
+      out << "rg16sint";
+      break;
+    case ImageFormat::kRg16Float:
+      out << "rg16float";
+      break;
+    case ImageFormat::kRgba8Unorm:
+      out << "rgba8unorm";
+      break;
+    case ImageFormat::kRgba8UnormSrgb:
+      out << "rgba8unorm_srgb";
+      break;
+    case ImageFormat::kRgba8Snorm:
+      out << "rgba8snorm";
+      break;
+    case ImageFormat::kRgba8Uint:
+      out << "rgba8uint";
+      break;
+    case ImageFormat::kRgba8Sint:
+      out << "rgba8sint";
+      break;
+    case ImageFormat::kBgra8Unorm:
+      out << "bgra8unorm";
+      break;
+    case ImageFormat::kBgra8UnormSrgb:
+      out << "bgra8unorm_srgb";
+      break;
+    case ImageFormat::kRgb10A2Unorm:
+      out << "rgb10a2unorm";
+      break;
+    case ImageFormat::kRg11B10Float:
+      out << "rg11b10float";
+      break;
+    case ImageFormat::kRg32Uint:
+      out << "rg32uint";
+      break;
+    case ImageFormat::kRg32Sint:
+      out << "rg32sint";
+      break;
+    case ImageFormat::kRg32Float:
+      out << "rg32float";
+      break;
+    case ImageFormat::kRgba16Uint:
+      out << "rgba16uint";
+      break;
+    case ImageFormat::kRgba16Sint:
+      out << "rgba16sint";
+      break;
+    case ImageFormat::kRgba16Float:
+      out << "rgba16float";
+      break;
+    case ImageFormat::kRgba32Uint:
+      out << "rgba32uint";
+      break;
+    case ImageFormat::kRgba32Sint:
+      out << "rgba32sint";
+      break;
+    case ImageFormat::kRgba32Float:
+      out << "rgba32float";
+      break;
+  }
+  return out;
+}
+
+StorageTexture::StorageTexture(ProgramID program_id,
+                               const Source& source,
+                               TextureDimension dim,
+                               ImageFormat format,
+                               Type* subtype)
+    : Base(program_id, source, dim), image_format_(format), subtype_(subtype) {}
+
+StorageTexture::StorageTexture(StorageTexture&&) = default;
+
+StorageTexture::~StorageTexture() = default;
+
+std::string StorageTexture::type_name() const {
+  std::ostringstream out;
+  out << "__storage_texture_" << dim() << "_" << image_format_;
+  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 {
+  // 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(), image_format_, ty);
+}
+
+Type* StorageTexture::SubtypeFor(ImageFormat format, ProgramBuilder& builder) {
+  switch (format) {
+    case ImageFormat::kR8Uint:
+    case ImageFormat::kR16Uint:
+    case ImageFormat::kRg8Uint:
+    case ImageFormat::kR32Uint:
+    case ImageFormat::kRg16Uint:
+    case ImageFormat::kRgba8Uint:
+    case ImageFormat::kRg32Uint:
+    case ImageFormat::kRgba16Uint:
+    case ImageFormat::kRgba32Uint: {
+      return builder.create<U32>();
+    }
+
+    case ImageFormat::kR8Sint:
+    case ImageFormat::kR16Sint:
+    case ImageFormat::kRg8Sint:
+    case ImageFormat::kR32Sint:
+    case ImageFormat::kRg16Sint:
+    case ImageFormat::kRgba8Sint:
+    case ImageFormat::kRg32Sint:
+    case ImageFormat::kRgba16Sint:
+    case ImageFormat::kRgba32Sint: {
+      return builder.create<I32>();
+    }
+
+    case ImageFormat::kR8Unorm:
+    case ImageFormat::kRg8Unorm:
+    case ImageFormat::kRgba8Unorm:
+    case ImageFormat::kRgba8UnormSrgb:
+    case ImageFormat::kBgra8Unorm:
+    case ImageFormat::kBgra8UnormSrgb:
+    case ImageFormat::kRgb10A2Unorm:
+    case ImageFormat::kR8Snorm:
+    case ImageFormat::kRg8Snorm:
+    case ImageFormat::kRgba8Snorm:
+    case ImageFormat::kR16Float:
+    case ImageFormat::kR32Float:
+    case ImageFormat::kRg16Float:
+    case ImageFormat::kRg11B10Float:
+    case ImageFormat::kRg32Float:
+    case ImageFormat::kRgba16Float:
+    case ImageFormat::kRgba32Float: {
+      return builder.create<F32>();
+    }
+
+    case ImageFormat::kNone:
+      break;
+  }
+
+  return nullptr;
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/storage_texture.h b/src/ast/storage_texture.h
new file mode 100644
index 0000000..bdf16b3
--- /dev/null
+++ b/src/ast/storage_texture.h
@@ -0,0 +1,123 @@
+// 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_AST_STORAGE_TEXTURE_H_
+#define SRC_AST_STORAGE_TEXTURE_H_
+
+#include <string>
+
+#include "src/ast/texture.h"
+
+namespace tint {
+namespace ast {
+
+class Manager;
+
+/// The image format in the storage texture
+enum class ImageFormat {
+  kNone = -1,
+  kR8Unorm,
+  kR8Snorm,
+  kR8Uint,
+  kR8Sint,
+  kR16Uint,
+  kR16Sint,
+  kR16Float,
+  kRg8Unorm,
+  kRg8Snorm,
+  kRg8Uint,
+  kRg8Sint,
+  kR32Uint,
+  kR32Sint,
+  kR32Float,
+  kRg16Uint,
+  kRg16Sint,
+  kRg16Float,
+  kRgba8Unorm,
+  kRgba8UnormSrgb,
+  kRgba8Snorm,
+  kRgba8Uint,
+  kRgba8Sint,
+  kBgra8Unorm,
+  kBgra8UnormSrgb,
+  kRgb10A2Unorm,
+  kRg11B10Float,
+  kRg32Uint,
+  kRg32Sint,
+  kRg32Float,
+  kRgba16Uint,
+  kRgba16Sint,
+  kRgba16Float,
+  kRgba32Uint,
+  kRgba32Sint,
+  kRgba32Float,
+};
+
+/// @param out the std::ostream to write to
+/// @param format the ImageFormat
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, ImageFormat format);
+
+/// A storage texture type.
+class StorageTexture : public Castable<StorageTexture, Texture> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source 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.
+  StorageTexture(ProgramID program_id,
+                 const Source& source,
+                 TextureDimension dim,
+                 ImageFormat format,
+                 Type* subtype);
+
+  /// Move constructor
+  StorageTexture(StorageTexture&&);
+  ~StorageTexture() override;
+
+  /// @returns the storage subtype
+  Type* type() const { return subtype_; }
+
+  /// @returns the image format
+  ImageFormat image_format() const { return image_format_; }
+
+  /// @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
+  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 ImageFormat
+  static Type* SubtypeFor(ImageFormat format, ProgramBuilder& builder);
+
+ private:
+  ImageFormat const image_format_;
+  Type* const subtype_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_STORAGE_TEXTURE_H_
diff --git a/src/ast/storage_texture_test.cc b/src/ast/storage_texture_test.cc
new file mode 100644
index 0000000..0f29196
--- /dev/null
+++ b/src/ast/storage_texture_test.cc
@@ -0,0 +1,128 @@
+// 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/ast/storage_texture.h"
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/depth_texture.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampled_texture.h"
+#include "src/ast/sampler.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstStorageTextureTest = TestHelper;
+
+TEST_F(AstStorageTextureTest, Is) {
+  auto* subtype = StorageTexture::SubtypeFor(ImageFormat::kRgba32Float, *this);
+  Type* ty = create<StorageTexture>(TextureDimension::k2dArray,
+                                    ImageFormat::kRgba32Float, subtype);
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_TRUE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstStorageTextureTest, IsTexture) {
+  auto* subtype = StorageTexture::SubtypeFor(ImageFormat::kRgba32Float, *this);
+  Texture* ty = create<StorageTexture>(TextureDimension::k2dArray,
+                                       ImageFormat::kRgba32Float, subtype);
+  EXPECT_FALSE(ty->Is<DepthTexture>());
+  EXPECT_FALSE(ty->Is<SampledTexture>());
+  EXPECT_TRUE(ty->Is<StorageTexture>());
+}
+
+TEST_F(AstStorageTextureTest, Dim) {
+  auto* subtype = StorageTexture::SubtypeFor(ImageFormat::kRgba32Float, *this);
+  auto* s = create<StorageTexture>(TextureDimension::k2dArray,
+                                   ImageFormat::kRgba32Float, subtype);
+  EXPECT_EQ(s->dim(), TextureDimension::k2dArray);
+}
+
+TEST_F(AstStorageTextureTest, Format) {
+  auto* subtype = StorageTexture::SubtypeFor(ImageFormat::kRgba32Float, *this);
+  auto* s = create<StorageTexture>(TextureDimension::k2dArray,
+                                   ImageFormat::kRgba32Float, subtype);
+  EXPECT_EQ(s->image_format(), ImageFormat::kRgba32Float);
+}
+
+TEST_F(AstStorageTextureTest, TypeName) {
+  auto* subtype = StorageTexture::SubtypeFor(ImageFormat::kRgba32Float, *this);
+  auto* s = create<StorageTexture>(TextureDimension::k2dArray,
+                                   ImageFormat::kRgba32Float, subtype);
+  EXPECT_EQ(s->type_name(), "__storage_texture_2d_array_rgba32float");
+}
+
+TEST_F(AstStorageTextureTest, FriendlyName) {
+  auto* subtype = StorageTexture::SubtypeFor(ImageFormat::kRgba32Float, *this);
+  auto* s = create<StorageTexture>(TextureDimension::k2dArray,
+                                   ImageFormat::kRgba32Float, subtype);
+  EXPECT_EQ(s->FriendlyName(Symbols()),
+            "texture_storage_2d_array<rgba32float>");
+}
+
+TEST_F(AstStorageTextureTest, F32) {
+  auto* subtype = StorageTexture::SubtypeFor(ImageFormat::kRgba32Float, *this);
+  Type* s = create<StorageTexture>(TextureDimension::k2dArray,
+                                   ImageFormat::kRgba32Float, subtype);
+
+  ASSERT_TRUE(s->Is<Texture>());
+  ASSERT_TRUE(s->Is<StorageTexture>());
+  EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<F32>());
+}
+
+TEST_F(AstStorageTextureTest, U32) {
+  auto* subtype = StorageTexture::SubtypeFor(ImageFormat::kRg32Uint, *this);
+  Type* s = create<StorageTexture>(TextureDimension::k2dArray,
+                                   ImageFormat::kRg32Uint, subtype);
+
+  ASSERT_TRUE(s->Is<Texture>());
+  ASSERT_TRUE(s->Is<StorageTexture>());
+  EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<U32>());
+}
+
+TEST_F(AstStorageTextureTest, I32) {
+  auto* subtype = StorageTexture::SubtypeFor(ImageFormat::kRgba32Sint, *this);
+  Type* s = create<StorageTexture>(TextureDimension::k2dArray,
+                                   ImageFormat::kRgba32Sint, subtype);
+
+  ASSERT_TRUE(s->Is<Texture>());
+  ASSERT_TRUE(s->Is<StorageTexture>());
+  EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<I32>());
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/struct.cc b/src/ast/struct.cc
index a3a396d..1ebf174 100644
--- a/src/ast/struct.cc
+++ b/src/ast/struct.cc
@@ -14,6 +14,8 @@
 
 #include "src/ast/struct.h"
 
+#include <string>
+
 #include "src/ast/struct_block_decoration.h"
 #include "src/program_builder.h"
 
@@ -24,9 +26,11 @@
 
 Struct::Struct(ProgramID program_id,
                const Source& source,
+               Symbol name,
                StructMemberList members,
                DecorationList decorations)
     : Base(program_id, source),
+      name_(name),
       members_(std::move(members)),
       decorations_(std::move(decorations)) {
   for (auto* mem : members_) {
@@ -59,15 +63,16 @@
 Struct* Struct::Clone(CloneContext* ctx) const {
   // Clone arguments outside of create() call to have deterministic ordering
   auto src = ctx->Clone(source());
+  auto n = ctx->Clone(name());
   auto mem = ctx->Clone(members());
   auto decos = ctx->Clone(decorations());
-  return ctx->dst->create<Struct>(src, mem, decos);
+  return ctx->dst->create<Struct>(src, n, mem, decos);
 }
 
 void Struct::to_str(const sem::Info& sem,
                     std::ostream& out,
                     size_t indent) const {
-  out << "Struct{" << std::endl;
+  out << "Struct " << name().to_str() << " {" << std::endl;
   for (auto* deco : decorations_) {
     make_indent(out, indent + 2);
     out << "[[";
@@ -81,5 +86,13 @@
   out << "}" << std::endl;
 }
 
+std::string Struct::type_name() const {
+  return "__struct_" + name().to_str();
+}
+
+std::string Struct::FriendlyName(const SymbolTable& symbols) const {
+  return symbols.NameFor(name());
+}
+
 }  // namespace ast
 }  // namespace tint
diff --git a/src/ast/struct.h b/src/ast/struct.h
index b6a0314..770fb3e 100644
--- a/src/ast/struct.h
+++ b/src/ast/struct.h
@@ -15,24 +15,28 @@
 #ifndef SRC_AST_STRUCT_H_
 #define SRC_AST_STRUCT_H_
 
+#include <string>
 #include <utility>
 
 #include "src/ast/decoration.h"
 #include "src/ast/struct_member.h"
+#include "src/ast/type.h"
 
 namespace tint {
 namespace ast {
 
 /// A struct statement.
-class Struct : public Castable<Struct, Node> {
+class Struct : public Castable<Struct, Type> {
  public:
   /// Create a new struct statement
   /// @param program_id the identifier of the program that owns this node
   /// @param source The input source for the import statement
+  /// @param name The name of the structure
   /// @param members The struct members
   /// @param decorations The struct decorations
   Struct(ProgramID program_id,
          const Source& source,
+         Symbol name,
          StructMemberList members,
          DecorationList decorations);
   /// Move constructor
@@ -40,6 +44,9 @@
 
   ~Struct() override;
 
+  /// @returns the name of the structure
+  Symbol name() const { return name_; }
+
   /// @returns the struct decorations
   const DecorationList& decorations() const { return decorations_; }
 
@@ -68,9 +75,18 @@
               std::ostream& out,
               size_t indent) const override;
 
+  /// @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;
+
  private:
   Struct(const Struct&) = delete;
 
+  Symbol const name_;
   StructMemberList const members_;
   DecorationList const decorations_;
 };
diff --git a/src/ast/struct_test.cc b/src/ast/struct_test.cc
index b7f9b1f..6ac527c 100644
--- a/src/ast/struct_test.cc
+++ b/src/ast/struct_test.cc
@@ -12,19 +12,33 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/ast/struct.h"
 #include "gtest/gtest-spi.h"
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampler.h"
 #include "src/ast/struct_block_decoration.h"
 #include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
 
 namespace tint {
 namespace ast {
 namespace {
 
-using StructTest = TestHelper;
+using AstStructTest = TestHelper;
 
-TEST_F(StructTest, Creation) {
-  auto* s =
-      create<Struct>(StructMemberList{Member("a", ty.i32())}, DecorationList{});
+TEST_F(AstStructTest, Creation) {
+  auto name = Sym("s");
+  auto* s = create<Struct>(name, StructMemberList{Member("a", ty.i32())},
+                           DecorationList{});
+  EXPECT_EQ(s->name(), name);
   EXPECT_EQ(s->members().size(), 1u);
   EXPECT_TRUE(s->decorations().empty());
   EXPECT_EQ(s->source().range.begin.line, 0u);
@@ -33,11 +47,14 @@
   EXPECT_EQ(s->source().range.end.column, 0u);
 }
 
-TEST_F(StructTest, Creation_WithDecorations) {
+TEST_F(AstStructTest, Creation_WithDecorations) {
+  auto name = Sym("s");
   DecorationList decos;
   decos.push_back(create<StructBlockDecoration>());
 
-  auto* s = create<Struct>(StructMemberList{Member("a", ty.i32())}, decos);
+  auto* s =
+      create<Struct>(name, StructMemberList{Member("a", ty.i32())}, decos);
+  EXPECT_EQ(s->name(), name);
   EXPECT_EQ(s->members().size(), 1u);
   ASSERT_EQ(s->decorations().size(), 1u);
   EXPECT_TRUE(s->decorations()[0]->Is<StructBlockDecoration>());
@@ -47,13 +64,13 @@
   EXPECT_EQ(s->source().range.end.column, 0u);
 }
 
-TEST_F(StructTest, CreationWithSourceAndDecorations) {
-  DecorationList decos;
-  decos.push_back(create<StructBlockDecoration>());
-
+TEST_F(AstStructTest, CreationWithSourceAndDecorations) {
+  auto name = Sym("s");
   auto* s = create<Struct>(
       Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 8}}},
-      StructMemberList{Member("a", ty.i32())}, decos);
+      name, StructMemberList{Member("a", ty.i32())},
+      DecorationList{create<StructBlockDecoration>()});
+  EXPECT_EQ(s->name(), name);
   EXPECT_EQ(s->members().size(), 1u);
   ASSERT_EQ(s->decorations().size(), 1u);
   EXPECT_TRUE(s->decorations()[0]->Is<StructBlockDecoration>());
@@ -63,60 +80,95 @@
   EXPECT_EQ(s->source().range.end.column, 8u);
 }
 
-TEST_F(StructTest, Assert_Null_StructMember) {
+TEST_F(AstStructTest, Assert_Null_StructMember) {
   EXPECT_FATAL_FAILURE(
       {
         ProgramBuilder b;
-        b.create<Struct>(StructMemberList{b.Member("a", b.ty.i32()), nullptr},
+        b.create<Struct>(b.Sym("S"),
+                         StructMemberList{b.Member("a", b.ty.i32()), nullptr},
                          DecorationList{});
       },
       "internal compiler error");
 }
 
-TEST_F(StructTest, Assert_Null_Decoration) {
+TEST_F(AstStructTest, Assert_Null_Decoration) {
   EXPECT_FATAL_FAILURE(
       {
         ProgramBuilder b;
-        b.create<Struct>(StructMemberList{b.Member("a", b.ty.i32())},
+        b.create<Struct>(b.Sym("S"),
+                         StructMemberList{b.Member("a", b.ty.i32())},
                          DecorationList{nullptr});
       },
       "internal compiler error");
 }
 
-TEST_F(StructTest, Assert_DifferentProgramID_StructMember) {
+TEST_F(AstStructTest, Assert_DifferentProgramID_StructMember) {
   EXPECT_FATAL_FAILURE(
       {
         ProgramBuilder b1;
         ProgramBuilder b2;
-        b1.create<Struct>(StructMemberList{b2.Member("a", b2.ty.i32())},
+        b1.create<Struct>(b2.Sym("S"),
+                          StructMemberList{b2.Member("a", b2.ty.i32())},
                           DecorationList{});
       },
       "internal compiler error");
 }
 
-TEST_F(StructTest, Assert_DifferentProgramID_Decoration) {
+TEST_F(AstStructTest, Assert_DifferentProgramID_Decoration) {
   EXPECT_FATAL_FAILURE(
       {
         ProgramBuilder b1;
         ProgramBuilder b2;
-        b1.create<Struct>(StructMemberList{b1.Member("a", b1.ty.i32())},
+        b1.create<Struct>(b1.Sym("S"),
+                          StructMemberList{b1.Member("a", b1.ty.i32())},
                           DecorationList{b2.create<StructBlockDecoration>()});
       },
       "internal compiler error");
 }
 
-TEST_F(StructTest, ToStr) {
-  DecorationList decos;
-  decos.push_back(create<StructBlockDecoration>());
-  auto* s = create<Struct>(StructMemberList{Member("a", ty.i32())}, decos);
+TEST_F(AstStructTest, ToStr) {
+  auto* s = create<Struct>(Sym("S"), StructMemberList{Member("a", ty.i32())},
+                           DecorationList{create<StructBlockDecoration>()});
 
-  EXPECT_EQ(str(s), R"(Struct{
+  EXPECT_EQ(str(s), R"(Struct S {
   [[block]]
   StructMember{a: __i32}
 }
 )");
 }
 
+TEST_F(AstStructTest, Is) {
+  Type* ty = create<ast::Struct>(Sym("S"), ast::StructMemberList{},
+                                 ast::DecorationList{});
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_TRUE(ty->Is<Struct>());
+  EXPECT_FALSE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstStructTest, TypeName) {
+  auto name = Sym("my_struct");
+  auto* s =
+      create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
+  EXPECT_EQ(s->type_name(), "__struct_$1");
+}
+
+TEST_F(AstStructTest, FriendlyName) {
+  auto name = Sym("my_struct");
+  auto* s =
+      create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
+  EXPECT_EQ(s->FriendlyName(Symbols()), "my_struct");
+}
+
 }  // namespace
 }  // namespace ast
 }  // namespace tint
diff --git a/src/ast/texture.cc b/src/ast/texture.cc
new file mode 100644
index 0000000..28de192
--- /dev/null
+++ b/src/ast/texture.cc
@@ -0,0 +1,91 @@
+// 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/ast/texture.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Texture);
+
+namespace tint {
+namespace ast {
+
+std::ostream& operator<<(std::ostream& out, TextureDimension dim) {
+  switch (dim) {
+    case TextureDimension::kNone:
+      out << "None";
+      break;
+    case TextureDimension::k1d:
+      out << "1d";
+      break;
+    case TextureDimension::k2d:
+      out << "2d";
+      break;
+    case TextureDimension::k2dArray:
+      out << "2d_array";
+      break;
+    case TextureDimension::k3d:
+      out << "3d";
+      break;
+    case TextureDimension::kCube:
+      out << "cube";
+      break;
+    case TextureDimension::kCubeArray:
+      out << "cube_array";
+      break;
+  }
+  return out;
+}
+
+bool IsTextureArray(TextureDimension dim) {
+  switch (dim) {
+    case TextureDimension::k2dArray:
+    case TextureDimension::kCubeArray:
+      return true;
+    case TextureDimension::k2d:
+    case TextureDimension::kNone:
+    case TextureDimension::k1d:
+    case TextureDimension::k3d:
+    case TextureDimension::kCube:
+      return false;
+  }
+  return false;
+}
+
+int NumCoordinateAxes(TextureDimension dim) {
+  switch (dim) {
+    case TextureDimension::kNone:
+      return 0;
+    case TextureDimension::k1d:
+      return 1;
+    case TextureDimension::k2d:
+    case TextureDimension::k2dArray:
+      return 2;
+    case TextureDimension::k3d:
+    case TextureDimension::kCube:
+    case TextureDimension::kCubeArray:
+      return 3;
+  }
+  return 0;
+}
+
+Texture::Texture(ProgramID program_id,
+                 const Source& source,
+                 TextureDimension dim)
+    : Base(program_id, source), dim_(dim) {}
+
+Texture::Texture(Texture&&) = default;
+
+Texture::~Texture() = default;
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/texture.h b/src/ast/texture.h
new file mode 100644
index 0000000..c8c871f
--- /dev/null
+++ b/src/ast/texture.h
@@ -0,0 +1,81 @@
+// 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_AST_TEXTURE_H_
+#define SRC_AST_TEXTURE_H_
+
+#include "src/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// The dimensionality of the texture
+enum class TextureDimension {
+  /// Invalid texture
+  kNone = -1,
+  /// 1 dimensional texture
+  k1d,
+  /// 2 dimensional texture
+  k2d,
+  /// 2 dimensional array texture
+  k2dArray,
+  /// 3 dimensional texture
+  k3d,
+  /// cube texture
+  kCube,
+  /// cube array texture
+  kCubeArray,
+};
+
+/// @param out the std::ostream to write to
+/// @param dim the TextureDimension
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, TextureDimension dim);
+
+/// @param dim the TextureDimension to query
+/// @return true if the given TextureDimension is an array texture
+bool IsTextureArray(TextureDimension dim);
+
+/// Returns the number of axes in the coordinate for a dimensionality.
+///  None -> 0
+///  1D -> 1
+///  2D, 2DArray -> 2
+///  3D, Cube, CubeArray -> 3
+/// @param dim the TextureDimension to query
+/// @return number of dimensions in a coordinate for the dimensionality
+int NumCoordinateAxes(TextureDimension dim);
+
+/// A texture type.
+class Texture : public Castable<Texture, Type> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  /// @param dim the dimensionality of the texture
+  Texture(ProgramID program_id, const Source& source, TextureDimension dim);
+  /// Move constructor
+  Texture(Texture&&);
+  ~Texture() override;
+
+  /// @returns the texture dimension
+  TextureDimension dim() const { return dim_; }
+
+ private:
+  TextureDimension const dim_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_TEXTURE_H_
diff --git a/src/ast/texture_test.cc b/src/ast/texture_test.cc
new file mode 100644
index 0000000..10ff52b
--- /dev/null
+++ b/src/ast/texture_test.cc
@@ -0,0 +1,58 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/ast/texture.h"
+
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampler.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/u32.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstTextureTypeTest = TestHelper;
+
+TEST_F(AstTextureTypeTest, IsTextureArray) {
+  EXPECT_EQ(false, IsTextureArray(TextureDimension::kNone));
+  EXPECT_EQ(false, IsTextureArray(TextureDimension::k1d));
+  EXPECT_EQ(false, IsTextureArray(TextureDimension::k2d));
+  EXPECT_EQ(true, IsTextureArray(TextureDimension::k2dArray));
+  EXPECT_EQ(false, IsTextureArray(TextureDimension::k3d));
+  EXPECT_EQ(false, IsTextureArray(TextureDimension::kCube));
+  EXPECT_EQ(true, IsTextureArray(TextureDimension::kCubeArray));
+}
+
+TEST_F(AstTextureTypeTest, NumCoordinateAxes) {
+  EXPECT_EQ(0, NumCoordinateAxes(TextureDimension::kNone));
+  EXPECT_EQ(1, NumCoordinateAxes(TextureDimension::k1d));
+  EXPECT_EQ(2, NumCoordinateAxes(TextureDimension::k2d));
+  EXPECT_EQ(2, NumCoordinateAxes(TextureDimension::k2dArray));
+  EXPECT_EQ(3, NumCoordinateAxes(TextureDimension::k3d));
+  EXPECT_EQ(3, NumCoordinateAxes(TextureDimension::kCube));
+  EXPECT_EQ(3, NumCoordinateAxes(TextureDimension::kCubeArray));
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/type.h b/src/ast/type.h
new file mode 100644
index 0000000..cd0bc0f
--- /dev/null
+++ b/src/ast/type.h
@@ -0,0 +1,124 @@
+// 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_AST_TYPE_H_
+#define SRC_AST_TYPE_H_
+
+#include <string>
+
+#include "src/ast/node.h"
+#include "src/clone_context.h"
+
+namespace tint {
+
+// Forward declarations
+class ProgramBuilder;
+class SymbolTable;
+
+namespace ast {
+
+/// Base class for a type in the system
+class Type : public Castable<Type, Node> {
+ public:
+  /// Move constructor
+  Type(Type&&);
+  ~Type() override;
+
+  /// @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;
+
+  /// @returns the pointee type if this is a pointer, `this` otherwise
+  Type* UnwrapPtrIfNeeded();
+
+  /// @returns the most deeply nested aliased type if this is an alias, `this`
+  /// otherwise
+  Type* UnwrapAliasIfNeeded();
+
+  /// Removes all levels of aliasing and access control.
+  /// This is just enough to assist with WGSL translation
+  /// in that you want see through one level of pointer to get from an
+  /// identifier-like expression as an l-value to its corresponding r-value,
+  /// plus see through the wrappers on either side.
+  /// @returns the completely unaliased type.
+  Type* UnwrapIfNeeded();
+
+  /// Returns the type found after:
+  /// - removing all layers of aliasing and access control if they exist, then
+  /// - removing the pointer, if it exists, then
+  /// - removing all further layers of aliasing or access control, if they exist
+  /// @returns the unwrapped type
+  Type* UnwrapAll();
+
+  /// @returns true if this type is a scalar
+  bool is_scalar() const;
+  /// @returns true if this type is a float scalar
+  bool is_float_scalar() const;
+  /// @returns true if this type is a float matrix
+  bool is_float_matrix() const;
+  /// @returns true if this type is a float vector
+  bool is_float_vector() const;
+  /// @returns true if this type is a float scalar or vector
+  bool is_float_scalar_or_vector() const;
+  /// @returns true if this type is a float scalar or vector or matrix
+  bool is_float_scalar_or_vector_or_matrix() const;
+  /// @returns true if this type is an integer scalar
+  bool is_integer_scalar() const;
+  /// @returns true if this type is a signed integer vector
+  bool is_signed_integer_vector() const;
+  /// @returns true if this type is an unsigned vector
+  bool is_unsigned_integer_vector() const;
+  /// @returns true if this type is an unsigned scalar or vector
+  bool is_unsigned_scalar_or_vector() const;
+  /// @returns true if this type is a signed scalar or vector
+  bool is_signed_scalar_or_vector() const;
+  /// @returns true if this type is an integer scalar or vector
+  bool is_integer_scalar_or_vector() const;
+  /// @returns true if this type is a boolean vector
+  bool is_bool_vector() const;
+  /// @returns true if this type is boolean scalar or vector
+  bool is_bool_scalar_or_vector() const;
+  /// @returns true if this type is a handle type
+  bool is_handle() const;
+
+  /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
+  /// @param out the stream to write to
+  /// @param indent number of spaces to indent the node when writing
+  void to_str(const sem::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
+
+ protected:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  Type(ProgramID program_id, const Source& source);
+};
+
+/// @returns the ProgramID of the given type.
+inline ProgramID ProgramIDOf(const Type*) {
+  /// TODO(crbug.com/tint/724): Actually implement this once we split the `type`
+  /// namespace into ast::Type and sem::Type.
+  return ProgramID();
+}
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_TYPE_H_
diff --git a/src/ast/u32.cc b/src/ast/u32.cc
new file mode 100644
index 0000000..8a533c1
--- /dev/null
+++ b/src/ast/u32.cc
@@ -0,0 +1,45 @@
+// 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/ast/u32.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::U32);
+
+namespace tint {
+namespace ast {
+
+U32::U32(ProgramID program_id, const Source& source)
+    : Base(program_id, source) {}
+
+U32::~U32() = default;
+
+U32::U32(U32&&) = default;
+
+std::string U32::type_name() const {
+  return "__u32";
+}
+
+std::string U32::FriendlyName(const SymbolTable&) const {
+  return "u32";
+}
+
+U32* U32::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source());
+  return ctx->dst->create<U32>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/u32.h b/src/ast/u32.h
new file mode 100644
index 0000000..22d3959
--- /dev/null
+++ b/src/ast/u32.h
@@ -0,0 +1,53 @@
+// 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_AST_U32_H_
+#define SRC_AST_U32_H_
+
+#include <string>
+
+#include "src/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A unsigned int 32 type.
+class U32 : public Castable<U32, Type> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  U32(ProgramID program_id, const Source& source);
+  /// Move constructor
+  U32(U32&&);
+  ~U32() override;
+
+  /// @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;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  U32* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_U32_H_
diff --git a/src/ast/u32_test.cc b/src/ast/u32_test.cc
new file mode 100644
index 0000000..94deea6
--- /dev/null
+++ b/src/ast/u32_test.cc
@@ -0,0 +1,66 @@
+// 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/ast/u32.h"
+
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampler.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/vector.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstU32Test = TestHelper;
+
+TEST_F(AstU32Test, Is) {
+  Type* ty = create<U32>();
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_FALSE(ty->Is<Texture>());
+  EXPECT_TRUE(ty->Is<U32>());
+  EXPECT_FALSE(ty->Is<Vector>());
+}
+
+TEST_F(AstU32Test, TypeName) {
+  auto* u = create<U32>();
+  EXPECT_EQ(u->type_name(), "__u32");
+}
+
+TEST_F(AstU32Test, FriendlyName) {
+  auto* u = create<U32>();
+  EXPECT_EQ(u->FriendlyName(Symbols()), "u32");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/vector.cc b/src/ast/vector.cc
new file mode 100644
index 0000000..60e52a3
--- /dev/null
+++ b/src/ast/vector.cc
@@ -0,0 +1,55 @@
+// 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/ast/vector.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Vector);
+
+namespace tint {
+namespace ast {
+
+Vector::Vector(ProgramID program_id,
+               const Source& source,
+               Type* subtype,
+               uint32_t size)
+    : Base(program_id, source), subtype_(subtype), size_(size) {
+  TINT_ASSERT(size_ > 1);
+  TINT_ASSERT(size_ < 5);
+}
+
+Vector::Vector(Vector&&) = default;
+
+Vector::~Vector() = default;
+
+std::string Vector::type_name() const {
+  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();
+}
+
+Vector* Vector::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source());
+  auto* ty = ctx->Clone(type());
+  return ctx->dst->create<Vector>(src, ty, size_);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/vector.h b/src/ast/vector.h
new file mode 100644
index 0000000..13e7cad
--- /dev/null
+++ b/src/ast/vector.h
@@ -0,0 +1,67 @@
+// 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_AST_VECTOR_H_
+#define SRC_AST_VECTOR_H_
+
+#include <string>
+
+#include "src/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A vector type.
+class Vector : public Castable<Vector, Type> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  /// @param subtype the vector element type
+  /// @param size the number of elements in the vector
+  Vector(ProgramID program_id,
+         const Source& source,
+         Type* subtype,
+         uint32_t size);
+  /// Move constructor
+  Vector(Vector&&);
+  ~Vector() override;
+
+  /// @returns the type of the vector elements
+  Type* type() const { return subtype_; }
+  /// @returns the size of the vector
+  uint32_t size() const { return size_; }
+
+  /// @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;
+
+  /// Clones this type and all transitive types using the `CloneContext` `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned type
+  Vector* Clone(CloneContext* ctx) const override;
+
+ private:
+  Type* const subtype_;
+  uint32_t const size_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_VECTOR_H_
diff --git a/src/ast/vector_test.cc b/src/ast/vector_test.cc
new file mode 100644
index 0000000..940cdc3
--- /dev/null
+++ b/src/ast/vector_test.cc
@@ -0,0 +1,74 @@
+// 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/ast/vector.h"
+#include "src/ast/access_control.h"
+#include "src/ast/alias.h"
+#include "src/ast/array.h"
+#include "src/ast/bool.h"
+#include "src/ast/f32.h"
+#include "src/ast/i32.h"
+#include "src/ast/matrix.h"
+#include "src/ast/pointer.h"
+#include "src/ast/sampler.h"
+#include "src/ast/struct.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/texture.h"
+#include "src/ast/u32.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AstVectorTest = TestHelper;
+
+TEST_F(AstVectorTest, Creation) {
+  auto* i32 = create<I32>();
+  auto* v = create<Vector>(i32, 2);
+  EXPECT_EQ(v->type(), i32);
+  EXPECT_EQ(v->size(), 2u);
+}
+
+TEST_F(AstVectorTest, Is) {
+  auto* i32 = create<I32>();
+  Type* ty = create<Vector>(i32, 4);
+  EXPECT_FALSE(ty->Is<AccessControl>());
+  EXPECT_FALSE(ty->Is<Alias>());
+  EXPECT_FALSE(ty->Is<Array>());
+  EXPECT_FALSE(ty->Is<Bool>());
+  EXPECT_FALSE(ty->Is<F32>());
+  EXPECT_FALSE(ty->Is<I32>());
+  EXPECT_FALSE(ty->Is<Matrix>());
+  EXPECT_FALSE(ty->Is<Pointer>());
+  EXPECT_FALSE(ty->Is<Sampler>());
+  EXPECT_FALSE(ty->Is<Struct>());
+  EXPECT_FALSE(ty->Is<Texture>());
+  EXPECT_FALSE(ty->Is<U32>());
+  EXPECT_TRUE(ty->Is<Vector>());
+}
+
+TEST_F(AstVectorTest, TypeName) {
+  auto* i32 = create<I32>();
+  auto* v = create<Vector>(i32, 3);
+  EXPECT_EQ(v->type_name(), "__vec_3__i32");
+}
+
+TEST_F(AstVectorTest, FriendlyName) {
+  auto* v = ty.vec3<f32>();
+  EXPECT_EQ(v->FriendlyName(Symbols()), "vec3<f32>");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/void.cc b/src/ast/void.cc
new file mode 100644
index 0000000..5591ceb
--- /dev/null
+++ b/src/ast/void.cc
@@ -0,0 +1,45 @@
+// 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/ast/void.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Void);
+
+namespace tint {
+namespace ast {
+
+Void::Void(ProgramID program_id, const Source& source)
+    : Base(program_id, source) {}
+
+Void::Void(Void&&) = default;
+
+Void::~Void() = default;
+
+std::string Void::type_name() const {
+  return "__void";
+}
+
+std::string Void::FriendlyName(const SymbolTable&) const {
+  return "void";
+}
+
+Void* Void::Clone(CloneContext* ctx) const {
+  auto src = ctx->Clone(source());
+  return ctx->dst->create<Void>(src);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/void.h b/src/ast/void.h
new file mode 100644
index 0000000..3ed79b0
--- /dev/null
+++ b/src/ast/void.h
@@ -0,0 +1,53 @@
+// 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_AST_VOID_H_
+#define SRC_AST_VOID_H_
+
+#include <string>
+
+#include "src/ast/type.h"
+
+namespace tint {
+namespace ast {
+
+/// A void type
+class Void : public Castable<Void, Type> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the source of this node
+  Void(ProgramID program_id, const Source& source);
+  /// Move constructor
+  Void(Void&&);
+  ~Void() override;
+
+  /// @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
+  Void* Clone(CloneContext* ctx) const override;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_VOID_H_
diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc
index f02f1f3..68f0683 100644
--- a/src/inspector/inspector_test.cc
+++ b/src/inspector/inspector_test.cc
@@ -225,8 +225,9 @@
       decos.push_back(create<ast::StructBlockDecoration>());
     }
 
-    auto* str = create<ast::Struct>(members, decos);
-    auto* str_ty = ty.struct_(name, str);
+    auto sym = Sym(name);
+    auto* str = create<ast::Struct>(sym, members, decos);
+    auto* str_ty = ty.struct_(sym, str);
     AST().AddConstructedType(str_ty);
     return str_ty;
   }
@@ -1824,6 +1825,7 @@
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, MissingBlockDeco) {
   ast::DecorationList decos;
   auto* str = create<ast::Struct>(
+      Sym("foo_type"),
       ast::StructMemberList{Member(StructMemberName(0, ty.i32()), ty.i32())},
       decos);
 
diff --git a/src/intrinsic_table.cc b/src/intrinsic_table.cc
index db7dc54..40d2ef1 100644
--- a/src/intrinsic_table.cc
+++ b/src/intrinsic_table.cc
@@ -603,7 +603,7 @@
 /// AccessControlBuilder is a Matcher / Builder for AccessControl types
 class AccessControlBuilder : public Builder {
  public:
-  explicit AccessControlBuilder(ast::AccessControl access_control,
+  explicit AccessControlBuilder(ast::AccessControl::Access access_control,
                                 Builder* type)
       : access_control_(access_control), type_(type) {}
 
@@ -628,7 +628,7 @@
   }
 
  private:
-  ast::AccessControl const access_control_;
+  ast::AccessControl::Access const access_control_;
   Builder* const type_;
 };
 
@@ -765,7 +765,8 @@
   }
 
   /// @returns a Matcher / Builder that matches an access control type
-  Builder* access_control(ast::AccessControl access_control, Builder* type) {
+  Builder* access_control(ast::AccessControl::Access access_control,
+                          Builder* type) {
     return matcher_allocator_.Create<AccessControlBuilder>(access_control,
                                                            type);
   }
diff --git a/src/program_builder.h b/src/program_builder.h
index e5dbe14..3e518fc 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -535,7 +535,7 @@
     /// @param access the access control
     /// @param type the inner type
     /// @returns the access control qualifier type
-    sem::AccessControl* access(ast::AccessControl access,
+    sem::AccessControl* access(ast::AccessControl::Access access,
                                sem::Type* type) const {
       return builder->create<sem::AccessControl>(access, type);
     }
@@ -1185,9 +1185,10 @@
                              NAME&& name,
                              ast::StructMemberList members,
                              ast::DecorationList decorations = {}) {
-    auto* impl =
-        create<ast::Struct>(source, std::move(members), std::move(decorations));
-    auto* type = ty.struct_(Sym(std::forward<NAME>(name)), impl);
+    auto sym = Sym(std::forward<NAME>(name));
+    auto* impl = create<ast::Struct>(source, sym, std::move(members),
+                                     std::move(decorations));
+    auto* type = ty.struct_(sym, impl);
     AST().AddConstructedType(type);
     return type;
   }
@@ -1202,9 +1203,10 @@
   sem::StructType* Structure(NAME&& name,
                              ast::StructMemberList members,
                              ast::DecorationList decorations = {}) {
+    auto sym = Sym(std::forward<NAME>(name));
     auto* impl =
-        create<ast::Struct>(std::move(members), std::move(decorations));
-    auto* type = ty.struct_(Sym(std::forward<NAME>(name)), impl);
+        create<ast::Struct>(sym, std::move(members), std::move(decorations));
+    auto* type = ty.struct_(sym, impl);
     AST().AddConstructedType(type);
     return type;
   }
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 1f9a48e..00e94b6 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -1338,7 +1338,8 @@
             if (top->ContainsPos(target0_pos) &&
                 top->ContainsPos(target1_pos)) {
               // Insert a synthetic if-selection
-              top = push_construct(depth+1, Construct::kIfSelection, header, ct);
+              top = push_construct(depth + 1, Construct::kIfSelection, header,
+                                   ct);
             }
           }
         }
diff --git a/src/reader/spirv/function_memory_test.cc b/src/reader/spirv/function_memory_test.cc
index 6e0c699..2870f5a 100644
--- a/src/reader/spirv/function_memory_test.cc
+++ b/src/reader/spirv/function_memory_test.cc
@@ -798,7 +798,7 @@
   const auto module_str = p->program().to_str();
   EXPECT_THAT(module_str, HasSubstr(R"(
   RTArr -> __array__u32_stride_4
-  S Struct{
+  Struct S {
     [[block]]
     StructMember{[[ offset 0 ]] field0: __u32}
     StructMember{[[ offset 4 ]] field1: __alias_RTArr__array__u32_stride_4}
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 87315ca..e22c749 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -551,7 +551,7 @@
   if (where == inst_source_.end()) {
     return {};
   }
-  return Source{where->second };
+  return Source{where->second};
 }
 
 bool ParserImpl::ParseInternalModuleExceptFunctions() {
@@ -940,15 +940,16 @@
     return nullptr;
   }
 
-  // Now make the struct.
-  auto* ast_struct = create<ast::Struct>(Source{}, std::move(ast_members),
-                                         std::move(ast_struct_decorations));
-
   namer_.SuggestSanitizedName(type_id, "S");
 
   auto name = namer_.GetName(type_id);
-  auto* result = builder_.create<sem::StructType>(
-      builder_.Symbols().Register(name), ast_struct);
+
+  // Now make the struct.
+  auto sym = builder_.Symbols().Register(name);
+  auto* ast_struct = create<ast::Struct>(Source{}, sym, std::move(ast_members),
+                                         std::move(ast_struct_decorations));
+
+  auto* result = builder_.create<sem::StructType>(sym, ast_struct);
   id_to_type_[type_id] = result;
   if (num_non_writable_members == members.size()) {
     read_only_struct_types_.insert(result);
diff --git a/src/reader/spirv/parser_impl_convert_type_test.cc b/src/reader/spirv/parser_impl_convert_type_test.cc
index 5957329..5915beb 100644
--- a/src/reader/spirv/parser_impl_convert_type_test.cc
+++ b/src/reader/spirv/parser_impl_convert_type_test.cc
@@ -553,7 +553,7 @@
   EXPECT_TRUE(type->Is<sem::StructType>());
 
   Program program = p->program();
-  EXPECT_THAT(program.str(type->As<sem::StructType>()->impl()), Eq(R"(Struct{
+  EXPECT_THAT(program.str(type->As<sem::StructType>()->impl()), Eq(R"(Struct S {
   StructMember{field0: __u32}
   StructMember{field1: __f32}
 }
@@ -574,7 +574,7 @@
   EXPECT_TRUE(type->Is<sem::StructType>());
 
   Program program = p->program();
-  EXPECT_THAT(program.str(type->As<sem::StructType>()->impl()), Eq(R"(Struct{
+  EXPECT_THAT(program.str(type->As<sem::StructType>()->impl()), Eq(R"(Struct S {
   [[block]]
   StructMember{field0: __u32}
 }
@@ -599,7 +599,7 @@
   EXPECT_TRUE(type->Is<sem::StructType>());
 
   Program program = p->program();
-  EXPECT_THAT(program.str(type->As<sem::StructType>()->impl()), Eq(R"(Struct{
+  EXPECT_THAT(program.str(type->As<sem::StructType>()->impl()), Eq(R"(Struct S {
   StructMember{[[ offset 0 ]] field0: __f32}
   StructMember{[[ offset 8 ]] field1: __vec_2__f32}
   StructMember{[[ offset 16 ]] field2: __mat_2_2__f32}
diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc
index 220261e..b52daa2 100644
--- a/src/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/reader/spirv/parser_impl_module_var_test.cc
@@ -1654,7 +1654,7 @@
   EXPECT_TRUE(p->error().empty());
   const auto module_str = p->program().to_str();
   EXPECT_THAT(module_str, HasSubstr(R"(
-  S Struct{
+  Struct S {
     [[block]]
     StructMember{field0: __u32}
     StructMember{field1: __f32}
@@ -1685,7 +1685,7 @@
   EXPECT_TRUE(p->error().empty());
   const auto module_str = p->program().to_str();
   EXPECT_THAT(module_str, HasSubstr(R"(
-  S Struct{
+  Struct S {
     [[block]]
     StructMember{field0: __mat_2_3__f32}
   }
@@ -1714,7 +1714,7 @@
   EXPECT_TRUE(p->error().empty());
   const auto module_str = p->program().to_str();
   EXPECT_THAT(module_str, HasSubstr(R"(
-  S Struct{
+  Struct S {
     [[block]]
     StructMember{field0: __mat_2_3__f32}
   }
@@ -1763,7 +1763,7 @@
   EXPECT_TRUE(p->error().empty());
   const auto module_str = p->program().to_str();
   EXPECT_THAT(module_str, HasSubstr(R"(
-  S Struct{
+  Struct S {
     [[block]]
     StructMember{field0: __f32}
     StructMember{field1: __f32}
@@ -1792,7 +1792,7 @@
   EXPECT_TRUE(p->error().empty());
   const auto module_str = p->program().to_str();
   EXPECT_THAT(module_str, HasSubstr(R"(
-  S Struct{
+  Struct S {
     [[block]]
     StructMember{field0: __f32}
     StructMember{field1: __f32}
@@ -1824,7 +1824,7 @@
   EXPECT_TRUE(p->error().empty());
   const auto module_str = p->program().to_str();
   EXPECT_THAT(module_str, HasSubstr(R"(
-  S Struct{
+  Struct S {
     [[block]]
     StructMember{field0: __f32}
     StructMember{field1: __f32}
diff --git a/src/reader/spirv/parser_impl_named_types_test.cc b/src/reader/spirv/parser_impl_named_types_test.cc
index 9f029d8..2366939 100644
--- a/src/reader/spirv/parser_impl_named_types_test.cc
+++ b/src/reader/spirv/parser_impl_named_types_test.cc
@@ -29,7 +29,7 @@
     %s = OpTypeStruct %uint %uint
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->program().to_str(), HasSubstr("S Struct"));
+  EXPECT_THAT(p->program().to_str(), HasSubstr("Struct S"));
 }
 
 TEST_F(SpvParserTest, NamedTypes_NamedStruct) {
@@ -39,7 +39,7 @@
     %s = OpTypeStruct %uint %uint
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(p->program().to_str(), HasSubstr("mystruct Struct"));
+  EXPECT_THAT(p->program().to_str(), HasSubstr("Struct mystruct"));
 }
 
 TEST_F(SpvParserTest, NamedTypes_Dup_EmitBoth) {
@@ -49,11 +49,11 @@
     %s2 = OpTypeStruct %uint %uint
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  EXPECT_THAT(p->program().to_str(), HasSubstr(R"(S Struct{
+  EXPECT_THAT(p->program().to_str(), HasSubstr(R"(Struct S {
     StructMember{field0: __u32}
     StructMember{field1: __u32}
   }
-  S_1 Struct{
+  Struct S_1 {
     StructMember{field0: __u32}
     StructMember{field1: __u32}
   })"));
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index d230a95..6db1b7e 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -836,7 +836,7 @@
   return TypedIdentifier{ty, ident.value, ident.source};
 }
 
-Expect<ast::AccessControl> ParserImpl::expect_access_type() {
+Expect<ast::AccessControl::Access> ParserImpl::expect_access_type() {
   auto ident = expect_ident("access_type");
   if (ident.errored)
     return Failure::kErrored;
@@ -1134,9 +1134,10 @@
   if (body.errored)
     return Failure::kErrored;
 
+  auto sym = builder_.Symbols().Register(name.value);
   return create<sem::StructType>(
-      builder_.Symbols().Register(name.value),
-      create<ast::Struct>(source, std::move(body.value), std::move(decos)));
+      sym, create<ast::Struct>(source, sym, std::move(body.value),
+                               std::move(decos)));
 }
 
 // struct_body_decl
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index b15fd16..a7ae443 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -437,7 +437,7 @@
   /// Parses an access type identifier, erroring if the next token does not
   /// match a valid access type name.
   /// @returns the parsed access control.
-  Expect<ast::AccessControl> expect_access_type();
+  Expect<ast::AccessControl::Access> expect_access_type();
   /// Parses a builtin identifier, erroring if the next token does not match a
   /// valid builtin name.
   /// @returns the parsed builtin.
diff --git a/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc b/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
index 3270881..6719ade 100644
--- a/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
@@ -115,7 +115,7 @@
   ast::DecorationList decos;
   decos.push_back(block_deco);
 
-  auto* str = create<ast::Struct>(members, decos);
+  auto* str = create<ast::Struct>(Sym("S"), members, decos);
   auto* s = ty.struct_("S", str);
 
   p->register_constructed("S", s);
@@ -140,7 +140,7 @@
   ast::DecorationList decos;
   decos.push_back(block_deco);
 
-  auto* str = create<ast::Struct>(members, decos);
+  auto* str = create<ast::Struct>(Sym("S"), members, decos);
   auto* s = ty.struct_("S", str);
 
   p->register_constructed("S", s);
@@ -165,7 +165,7 @@
   ast::DecorationList decos;
   decos.push_back(block_deco);
 
-  auto* str = create<ast::Struct>(members, decos);
+  auto* str = create<ast::Struct>(Sym("S"), members, decos);
   auto* s = ty.struct_("S", str);
 
   p->register_constructed("S", s);
@@ -187,7 +187,7 @@
   ast::DecorationList decos;
   decos.push_back(block_deco);
 
-  auto* str = create<ast::Struct>(members, decos);
+  auto* str = create<ast::Struct>(Sym("S"), members, decos);
   auto* s = ty.struct_("S", str);
 
   p->register_constructed("S", s);
@@ -225,7 +225,7 @@
   ast::DecorationList decos;
   decos.push_back(block_deco);
 
-  auto* str = create<ast::Struct>(members, decos);
+  auto* str = create<ast::Struct>(Sym("S"), members, decos);
   auto* s = ty.struct_("S", str);
 
   p->register_constructed("S", s);
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index cb4f29f..b60a95d 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -131,7 +131,8 @@
                                   ast::DecorationList{createDecoration(
                                       Source{{12, 34}}, *this, params.kind)}))};
   auto* s = create<ast::Struct>(
-      members, ast::DecorationList{create<ast::StructBlockDecoration>()});
+      Sym("mystruct"), members,
+      ast::DecorationList{create<ast::StructBlockDecoration>()});
   auto* s_ty = ty.struct_("mystruct", s);
   AST().AddConstructedType(s_ty);
 
@@ -166,7 +167,7 @@
 TEST_P(StructDecorationTest, IsValid) {
   auto& params = GetParam();
 
-  auto* s = create<ast::Struct>(ast::StructMemberList{},
+  auto* s = create<ast::Struct>(Sym("mystruct"), ast::StructMemberList{},
                                 ast::DecorationList{createDecoration(
                                     Source{{12, 34}}, *this, params.kind)});
   auto* s_ty = ty.struct_("mystruct", s);
@@ -207,7 +208,8 @@
       Member("a", ty.i32(),
              ast::DecorationList{
                  createDecoration(Source{{12, 34}}, *this, params.kind)})};
-  auto* s = create<ast::Struct>(members, ast::DecorationList{});
+  auto* s =
+      create<ast::Struct>(Sym("mystruct"), members, ast::DecorationList{});
   auto* s_ty = ty.struct_("mystruct", s);
   AST().AddConstructedType(s_ty);
 
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index 6ea8536..23bede2 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -889,6 +889,7 @@
 
 TEST_F(ResolverTest, Expr_MemberAccessor_Struct) {
   auto* strct = create<ast::Struct>(
+      Sym("S"),
       ast::StructMemberList{Member("first_member", ty.i32()),
                             Member("second_member", ty.f32())},
       ast::DecorationList{});
@@ -918,6 +919,7 @@
 
 TEST_F(ResolverTest, Expr_MemberAccessor_Struct_Alias) {
   auto* strct = create<ast::Struct>(
+      Sym("alias"),
       ast::StructMemberList{Member("first_member", ty.i32()),
                             Member("second_member", ty.f32())},
       ast::DecorationList{});
@@ -999,14 +1001,15 @@
   // }
   //
 
-  auto* strctB =
-      create<ast::Struct>(ast::StructMemberList{Member("foo", ty.vec4<f32>())},
-                          ast::DecorationList{});
+  auto* strctB = create<ast::Struct>(
+      Sym("B"), ast::StructMemberList{Member("foo", ty.vec4<f32>())},
+      ast::DecorationList{});
   auto* stB = ty.struct_("B", strctB);
 
   sem::Vector vecB(stB, 3);
-  auto* strctA = create<ast::Struct>(
-      ast::StructMemberList{Member("mem", &vecB)}, ast::DecorationList{});
+  auto* strctA =
+      create<ast::Struct>(Sym("A"), ast::StructMemberList{Member("mem", &vecB)},
+                          ast::DecorationList{});
 
   auto* stA = ty.struct_("A", strctA);
   Global("c", stA, ast::StorageClass::kInput);
@@ -1027,6 +1030,7 @@
 
 TEST_F(ResolverTest, Expr_MemberAccessor_InBinaryOp) {
   auto* strct = create<ast::Struct>(
+      Sym("S"),
       ast::StructMemberList{Member("first_member", ty.f32()),
                             Member("second_member", ty.f32())},
       ast::DecorationList{});
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index 138efe9..49aeb13 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -315,7 +315,8 @@
   ast::DecorationList decos;
   decos.push_back(create<ast::StructBlockDecoration>());
   auto* st =
-      create<ast::Struct>(ast::StructMemberList{Member("vf", ty.f32()),
+      create<ast::Struct>(Sym("Foo"),
+                          ast::StructMemberList{Member("vf", ty.f32()),
                                                 Member("rt", ty.array<f32>())},
                           decos);
 
@@ -335,6 +336,7 @@
 
   ast::DecorationList decos;
   auto* st = create<ast::Struct>(
+      Sym("Foo"),
       ast::StructMemberList{Member("vf", ty.f32()),
                             Member(Source{{12, 34}}, "rt", ty.array<f32>())},
       decos);
@@ -362,7 +364,7 @@
 
   auto* rt = Member(Source{{12, 34}}, "rt", ty.array<f32>());
   auto* st = create<ast::Struct>(
-      ast::StructMemberList{rt, Member("vf", ty.f32())}, decos);
+      Sym("Foo"), ast::StructMemberList{rt, Member("vf", ty.f32())}, decos);
 
   auto* struct_type = ty.struct_("Foo", st);
 
@@ -438,6 +440,7 @@
   ast::DecorationList decos;
   decos.push_back(create<ast::StructBlockDecoration>());
   auto* st = create<ast::Struct>(
+      Sym("s"),
       ast::StructMemberList{Member(Source{{12, 34}}, "b", alias),
                             Member("a", ty.u32())},
       decos);
@@ -467,6 +470,7 @@
   ast::DecorationList decos;
   decos.push_back(create<ast::StructBlockDecoration>());
   auto* st = create<ast::Struct>(
+      Sym("s"),
       ast::StructMemberList{Member("a", ty.u32()), Member("b", alias)}, decos);
 
   auto* struct_type = ty.struct_("s", st);
diff --git a/src/sem/access_control_type.cc b/src/sem/access_control_type.cc
index 09e9af2..6c3101f 100644
--- a/src/sem/access_control_type.cc
+++ b/src/sem/access_control_type.cc
@@ -21,7 +21,7 @@
 namespace tint {
 namespace sem {
 
-AccessControl::AccessControl(ast::AccessControl access, Type* subtype)
+AccessControl::AccessControl(ast::AccessControl::Access access, Type* subtype)
     : access_(access), subtype_(subtype) {
   TINT_ASSERT(subtype_);
   TINT_ASSERT(!subtype_->Is<AccessControl>());
diff --git a/src/sem/access_control_type.h b/src/sem/access_control_type.h
index 7f6ac37..5772970 100644
--- a/src/sem/access_control_type.h
+++ b/src/sem/access_control_type.h
@@ -29,7 +29,7 @@
   /// Constructor
   /// @param access the access control setting
   /// @param subtype the access controlled type
-  AccessControl(ast::AccessControl access, Type* subtype);
+  AccessControl(ast::AccessControl::Access access, Type* subtype);
   /// Move constructor
   AccessControl(AccessControl&&);
   ~AccessControl() override;
@@ -42,7 +42,7 @@
   bool IsReadWrite() const { return access_ == ast::AccessControl::kReadWrite; }
 
   /// @returns the access control value
-  ast::AccessControl access_control() const { return access_; }
+  ast::AccessControl::Access access_control() const { return access_; }
   /// @returns the subtype type
   Type* type() const { return subtype_; }
 
@@ -60,7 +60,7 @@
   AccessControl* Clone(CloneContext* ctx) const override;
 
  private:
-  ast::AccessControl const access_;
+  ast::AccessControl::Access const access_;
   Type* const subtype_;
 };
 
diff --git a/src/sem/external_texture_type.h b/src/sem/external_texture_type.h
index 8179d9f..e91cd00 100644
--- a/src/sem/external_texture_type.h
+++ b/src/sem/external_texture_type.h
@@ -21,6 +21,8 @@
 
 namespace tint {
 namespace sem {
+
+/// An external texture type
 class ExternalTexture : public Castable<ExternalTexture, Texture> {
  public:
   /// Constructor
@@ -46,4 +48,5 @@
 
 }  // namespace sem
 }  // namespace tint
+
 #endif  // SRC_SEM_EXTERNAL_TEXTURE_TYPE_H_
diff --git a/src/sem/struct_type_test.cc b/src/sem/struct_type_test.cc
index a667cfa..0571c88 100644
--- a/src/sem/struct_type_test.cc
+++ b/src/sem/struct_type_test.cc
@@ -23,17 +23,19 @@
 using StructTypeTest = TestHelper;
 
 TEST_F(StructTypeTest, Creation) {
+  auto name = Sym("S");
   auto* impl =
-      create<ast::Struct>(ast::StructMemberList{}, ast::DecorationList{});
+      create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
   auto* ptr = impl;
-  auto* s = ty.struct_("S", impl);
+  auto* s = ty.struct_(name, impl);
   EXPECT_EQ(s->impl(), ptr);
 }
 
 TEST_F(StructTypeTest, Is) {
+  auto name = Sym("S");
   auto* impl =
-      create<ast::Struct>(ast::StructMemberList{}, ast::DecorationList{});
-  auto* s = ty.struct_("S", impl);
+      create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
+  auto* s = ty.struct_(name, impl);
   sem::Type* ty = s;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
@@ -51,16 +53,18 @@
 }
 
 TEST_F(StructTypeTest, TypeName) {
+  auto name = Sym("my_struct");
   auto* impl =
-      create<ast::Struct>(ast::StructMemberList{}, ast::DecorationList{});
-  auto* s = ty.struct_("my_struct", impl);
+      create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
+  auto* s = ty.struct_(name, impl);
   EXPECT_EQ(s->type_name(), "__struct_$1");
 }
 
 TEST_F(StructTypeTest, FriendlyName) {
+  auto name = Sym("my_struct");
   auto* impl =
-      create<ast::Struct>(ast::StructMemberList{}, ast::DecorationList{});
-  auto* s = ty.struct_("my_struct", impl);
+      create<ast::Struct>(name, ast::StructMemberList{}, ast::DecorationList{});
+  auto* s = ty.struct_(name, impl);
   EXPECT_EQ(s->FriendlyName(Symbols()), "my_struct");
 }
 
diff --git a/src/transform/binding_remapper.cc b/src/transform/binding_remapper.cc
index 2dc14a3..1a19e78 100644
--- a/src/transform/binding_remapper.cc
+++ b/src/transform/binding_remapper.cc
@@ -63,7 +63,7 @@
       // Replace any access controls.
       auto ac_it = remappings->access_controls.find(from);
       if (ac_it != remappings->access_controls.end()) {
-        ast::AccessControl ac = ac_it->second;
+        ast::AccessControl::Access ac = ac_it->second;
         auto* ty = in->Sem().Get(var)->Type();
         sem::Type* inner_ty = nullptr;
         if (auto* old_ac = ty->As<sem::AccessControl>()) {
diff --git a/src/transform/binding_remapper.h b/src/transform/binding_remapper.h
index b80d823..4b6ef83 100644
--- a/src/transform/binding_remapper.h
+++ b/src/transform/binding_remapper.h
@@ -32,7 +32,8 @@
   using BindingPoints = std::unordered_map<BindingPoint, BindingPoint>;
 
   /// AccessControls is a map of old binding point to new access control
-  using AccessControls = std::unordered_map<BindingPoint, ast::AccessControl>;
+  using AccessControls =
+      std::unordered_map<BindingPoint, ast::AccessControl::Access>;
 
   /// Remappings is consumed by the BindingRemapper transform.
   /// Data holds information about shader usage and constant buffer offsets.
diff --git a/src/transform/canonicalize_entry_point_io.cc b/src/transform/canonicalize_entry_point_io.cc
index 90d72b1..55b86d1 100644
--- a/src/transform/canonicalize_entry_point_io.cc
+++ b/src/transform/canonicalize_entry_point_io.cc
@@ -81,10 +81,11 @@
       }
 
       // Redeclare the struct.
+      auto new_struct_name = ctx.Clone(struct_ty->symbol());
       auto* new_struct = ctx.dst->create<sem::StructType>(
-          ctx.Clone(struct_ty->symbol()),
-          ctx.dst->create<ast::Struct>(
-              new_struct_members, ctx.Clone(struct_ty->impl()->decorations())));
+          new_struct_name, ctx.dst->create<ast::Struct>(
+                               new_struct_name, new_struct_members,
+                               ctx.Clone(struct_ty->impl()->decorations())));
       ctx.Replace(struct_ty, new_struct);
     }
   }
@@ -174,9 +175,10 @@
                 StructMemberComparator);
 
       // Create the new struct type.
+      auto in_struct_name = ctx.dst->Symbols().New();
       auto* in_struct = ctx.dst->create<sem::StructType>(
-          ctx.dst->Symbols().New(),
-          ctx.dst->create<ast::Struct>(new_struct_members,
+          in_struct_name,
+          ctx.dst->create<ast::Struct>(in_struct_name, new_struct_members,
                                        ast::DecorationList{}));
       ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func, in_struct);
 
@@ -220,9 +222,10 @@
                 StructMemberComparator);
 
       // Create the new struct type.
+      auto out_struct_name = ctx.dst->Symbols().New();
       auto* out_struct = ctx.dst->create<sem::StructType>(
-          ctx.dst->Symbols().New(),
-          ctx.dst->create<ast::Struct>(new_struct_members,
+          out_struct_name,
+          ctx.dst->create<ast::Struct>(out_struct_name, new_struct_members,
                                        ast::DecorationList{}));
       ctx.InsertBefore(ctx.src->AST().GlobalDeclarations(), func, out_struct);
       new_ret_type = out_struct;
diff --git a/src/transform/spirv.cc b/src/transform/spirv.cc
index e16a22a..09d7324 100644
--- a/src/transform/spirv.cc
+++ b/src/transform/spirv.cc
@@ -126,10 +126,11 @@
       }
 
       // Redeclare the struct.
+      auto new_struct_name = ctx.Clone(struct_ty->symbol());
       auto* new_struct = ctx.dst->create<sem::StructType>(
-          ctx.Clone(struct_ty->symbol()),
-          ctx.dst->create<ast::Struct>(
-              new_struct_members, ctx.Clone(struct_ty->impl()->decorations())));
+          new_struct_name, ctx.dst->create<ast::Struct>(
+                               new_struct_name, new_struct_members,
+                               ctx.Clone(struct_ty->impl()->decorations())));
       ctx.Replace(struct_ty, new_struct);
     }
   }
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index de53041..610f9b5 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -3145,7 +3145,7 @@
 }
 
 bool Builder::GenerateStructType(sem::StructType* struct_type,
-                                 ast::AccessControl access_control,
+                                 ast::AccessControl::Access access_control,
                                  const Operand& result) {
   auto struct_id = result.to_i();
   auto* impl = struct_type->impl();
diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h
index 5f44e0f..bea880d 100644
--- a/src/writer/spirv/builder.h
+++ b/src/writer/spirv/builder.h
@@ -450,7 +450,7 @@
   /// @param result the result operand
   /// @returns true if the vector was successfully generated
   bool GenerateStructType(sem::StructType* struct_type,
-                          ast::AccessControl access_control,
+                          ast::AccessControl::Access access_control,
                           const Operand& result);
   /// Generates a struct member
   /// @param struct_id the id of the parent structure
diff --git a/src/writer/wgsl/generator_impl_type_test.cc b/src/writer/wgsl/generator_impl_type_test.cc
index 167fef7..0ac4460 100644
--- a/src/writer/wgsl/generator_impl_type_test.cc
+++ b/src/writer/wgsl/generator_impl_type_test.cc
@@ -436,7 +436,7 @@
 struct StorageTextureData {
   sem::ImageFormat fmt;
   sem::TextureDimension dim;
-  ast::AccessControl access;
+  ast::AccessControl::Access access;
   const char* name;
 };
 inline std::ostream& operator<<(std::ostream& out, StorageTextureData data) {
diff --git a/test/BUILD.gn b/test/BUILD.gn
index ba2a815..2b19a60 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -138,14 +138,18 @@
 
 tint_unittests_source_set("tint_unittests_core_src") {
   sources = [
+    "../src/ast/access_control_test.cc",
     "../src/ast/access_decoration_test.cc",
+    "../src/ast/alias_test.cc",
     "../src/ast/array_accessor_expression_test.cc",
+    "../src/ast/array_test.cc",
     "../src/ast/assignment_statement_test.cc",
     "../src/ast/binary_expression_test.cc",
     "../src/ast/binding_decoration_test.cc",
     "../src/ast/bitcast_expression_test.cc",
     "../src/ast/block_statement_test.cc",
     "../src/ast/bool_literal_test.cc",
+    "../src/ast/bool_test.cc",
     "../src/ast/break_statement_test.cc",
     "../src/ast/builtin_decoration_test.cc",
     "../src/ast/call_expression_test.cc",
@@ -153,12 +157,15 @@
     "../src/ast/case_statement_test.cc",
     "../src/ast/constant_id_decoration_test.cc",
     "../src/ast/continue_statement_test.cc",
+    "../src/ast/depth_texture_test.cc",
     "../src/ast/discard_statement_test.cc",
     "../src/ast/else_statement_test.cc",
+    "../src/ast/f32_test.cc",
     "../src/ast/fallthrough_statement_test.cc",
     "../src/ast/float_literal_test.cc",
     "../src/ast/function_test.cc",
     "../src/ast/group_decoration_test.cc",
+    "../src/ast/i32_test.cc",
     "../src/ast/identifier_expression_test.cc",
     "../src/ast/if_statement_test.cc",
     "../src/ast/int_literal_test.cc",
@@ -166,13 +173,19 @@
     "../src/ast/intrinsic_texture_helper_test.h",
     "../src/ast/location_decoration_test.cc",
     "../src/ast/loop_statement_test.cc",
+    "../src/ast/matrix_test.cc",
     "../src/ast/member_accessor_expression_test.cc",
     "../src/ast/module_clone_test.cc",
     "../src/ast/module_test.cc",
+    "../src/ast/multisampled_texture_test.cc",
+    "../src/ast/pointer_test.cc",
     "../src/ast/return_statement_test.cc",
+    "../src/ast/sampled_texture_test.cc",
+    "../src/ast/sampler_test.cc",
     "../src/ast/scalar_constructor_expression_test.cc",
     "../src/ast/sint_literal_test.cc",
     "../src/ast/stage_decoration_test.cc",
+    "../src/ast/storage_texture_test.cc",
     "../src/ast/stride_decoration_test.cc",
     "../src/ast/struct_member_align_decoration_test.cc",
     "../src/ast/struct_member_offset_decoration_test.cc",
@@ -181,11 +194,14 @@
     "../src/ast/struct_test.cc",
     "../src/ast/switch_statement_test.cc",
     "../src/ast/test_helper.h",
+    "../src/ast/texture_test.cc",
     "../src/ast/type_constructor_expression_test.cc",
+    "../src/ast/u32_test.cc",
     "../src/ast/uint_literal_test.cc",
     "../src/ast/unary_op_expression_test.cc",
     "../src/ast/variable_decl_statement_test.cc",
     "../src/ast/variable_test.cc",
+    "../src/ast/vector_test.cc",
     "../src/ast/workgroup_decoration_test.cc",
     "../src/block_allocator_test.cc",
     "../src/castable_test.cc",
@@ -208,9 +224,9 @@
     "../src/resolver/intrinsic_test.cc",
     "../src/resolver/is_host_shareable_test.cc",
     "../src/resolver/is_storeable_test.cc",
-    "../src/resolver/resolver_test.cc",
     "../src/resolver/resolver_test_helper.cc",
     "../src/resolver/resolver_test_helper.h",
+    "../src/resolver/resolver_test.cc",
     "../src/resolver/storage_class_validation_test.cc",
     "../src/resolver/struct_layout_test.cc",
     "../src/resolver/struct_pipeline_stage_use_test.cc",
@@ -253,11 +269,12 @@
     "../src/transform/vertex_pulling_test.cc",
     "../src/utils/command.h",
     "../src/utils/command_test.cc",
+    "../src/utils/command.h",
     "../src/utils/get_or_create_test.cc",
     "../src/utils/hash_test.cc",
     "../src/utils/math_test.cc",
-    "../src/utils/tmpfile.h",
     "../src/utils/tmpfile_test.cc",
+    "../src/utils/tmpfile.h",
     "../src/utils/unique_vector_test.cc",
     "../src/writer/append_vector_test.cc",
     "../src/writer/float_to_string_test.cc",