sem: Fold together sem::Array and sem::ArrayType

There's now no need to have both.
Removes a whole bunch of Sem().Get() smell, and simplifies the resolver.

Also fixes a long-standing issue where an array with an explicit, but equal-to-implicit-stride attribute would result in a different type to an array without the decoration.

Bug: tint:724
Fixed: tint:782
Change-Id: I0202459009cd45be427cdb621993a5a3b07ff51e
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/50301
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 699c066..0be18bd 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -450,8 +450,6 @@
     "sem/alias_type.cc",
     "sem/alias_type.h",
     "sem/array.h",
-    "sem/array_type.cc",
-    "sem/array_type.h",
     "sem/bool_type.cc",
     "sem/bool_type.h",
     "sem/call.h",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b2a3369..b067715 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -289,8 +289,6 @@
   sem/access_control_type.h
   sem/alias_type.cc
   sem/alias_type.h
-  sem/array_type.cc
-  sem/array_type.h
   sem/bool_type.cc
   sem/bool_type.h
   sem/depth_texture_type.cc
@@ -568,7 +566,6 @@
     test_main.cc
     sem/access_control_type_test.cc
     sem/alias_type_test.cc
-    sem/array_type_test.cc
     sem/bool_type_test.cc
     sem/depth_texture_type_test.cc
     sem/external_texture_type_test.cc
@@ -579,6 +576,7 @@
     sem/pointer_type_test.cc
     sem/sampled_texture_type_test.cc
     sem/sampler_type_test.cc
+    sem/sem_array_test.cc
     sem/sem_struct_test.cc
     sem/storage_texture_type_test.cc
     sem/texture_type_test.cc
diff --git a/src/inspector/inspector.cc b/src/inspector/inspector.cc
index c91edc1..f1100dc 100644
--- a/src/inspector/inspector.cc
+++ b/src/inspector/inspector.cc
@@ -24,7 +24,7 @@
 #include "src/ast/sint_literal.h"
 #include "src/ast/uint_literal.h"
 #include "src/sem/access_control_type.h"
-#include "src/sem/array_type.h"
+#include "src/sem/array.h"
 #include "src/sem/f32_type.h"
 #include "src/sem/function.h"
 #include "src/sem/i32_type.h"
@@ -76,13 +76,13 @@
   return ResourceBinding::TextureDimension::kNone;
 }
 
-ResourceBinding::SampledKind BaseTypeToSampledKind(sem::Type* base_type) {
+ResourceBinding::SampledKind BaseTypeToSampledKind(const sem::Type* base_type) {
   if (!base_type) {
     return ResourceBinding::SampledKind::kUnknown;
   }
 
-  if (auto* at = base_type->As<sem::ArrayType>()) {
-    base_type = at->type();
+  if (auto* at = base_type->As<sem::Array>()) {
+    base_type = const_cast<sem::Type*>(at->ElemType());
   } else if (auto* mt = base_type->As<sem::Matrix>()) {
     base_type = mt->type();
   } else if (auto* vt = base_type->As<sem::Vector>()) {
@@ -650,7 +650,7 @@
     entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(
         texture_type->dim());
 
-    sem::Type* base_type = nullptr;
+    const sem::Type* base_type = nullptr;
     if (multisampled_only) {
       base_type = texture_type->As<sem::MultisampledTexture>()
                       ->type()
@@ -702,7 +702,7 @@
     entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(
         texture_type->dim());
 
-    sem::Type* base_type = texture_type->type()->UnwrapIfNeeded();
+    auto* base_type = texture_type->type()->UnwrapIfNeeded();
     entry.sampled_kind = BaseTypeToSampledKind(base_type);
     entry.image_format = TypeImageFormatToResourceBindingImageFormat(
         texture_type->image_format());
diff --git a/src/intrinsic_table.cc b/src/intrinsic_table.cc
index 276f52c..ec28a4f 100644
--- a/src/intrinsic_table.cc
+++ b/src/intrinsic_table.cc
@@ -408,9 +408,9 @@
       : element_builder_(element_builder) {}
 
   bool MatchUnwrapped(MatchState& state, const sem::Type* ty) const override {
-    if (auto* arr = ty->As<sem::ArrayType>()) {
-      if (arr->size() == 0) {
-        return element_builder_->Match(state, arr->type());
+    if (auto* arr = ty->As<sem::Array>()) {
+      if (arr->IsRuntimeSized()) {
+        return element_builder_->Match(state, arr->ElemType());
       }
     }
     return false;
@@ -418,8 +418,7 @@
 
   sem::Type* Build(BuildState& state) const override {
     auto* el = element_builder_->Build(state);
-    return state.ty_mgr.Get<sem::ArrayType>(const_cast<sem::Type*>(el), 0,
-                                            ast::DecorationList{});
+    return state.ty_mgr.Get<sem::Array>(el, 0, 0, 0, 0, true);
   }
 
   std::string str() const override {
diff --git a/src/intrinsic_table_test.cc b/src/intrinsic_table_test.cc
index deae118..3f7874a 100644
--- a/src/intrinsic_table_test.cc
+++ b/src/intrinsic_table_test.cc
@@ -202,14 +202,15 @@
 }
 
 TEST_F(IntrinsicTableTest, MatchArray) {
-  auto result = table->Lookup(*this, IntrinsicType::kArrayLength,
-                              {ty.array<f32>()}, Source{});
+  auto* arr = create<sem::Array>(create<sem::U32>(), 0, 4, 4, 4, true);
+  auto result =
+      table->Lookup(*this, IntrinsicType::kArrayLength, {arr}, Source{});
   ASSERT_NE(result.intrinsic, nullptr);
   ASSERT_EQ(result.diagnostics.str(), "");
   EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kArrayLength);
   EXPECT_THAT(result.intrinsic->ReturnType(), ty.u32());
-  EXPECT_THAT(result.intrinsic->Parameters(),
-              ElementsAre(Parameter{ty.array<f32>()}));
+  ASSERT_EQ(result.intrinsic->Parameters().size(), 1u);
+  EXPECT_TRUE(result.intrinsic->Parameters()[0].type->Is<sem::Array>());
 }
 
 TEST_F(IntrinsicTableTest, MismatchArray) {
diff --git a/src/program_builder.h b/src/program_builder.h
index 7b0fdce..b013ffb 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -61,7 +61,7 @@
 #include "src/program_id.h"
 #include "src/sem/access_control_type.h"
 #include "src/sem/alias_type.h"
-#include "src/sem/array_type.h"
+#include "src/sem/array.h"
 #include "src/sem/bool_type.h"
 #include "src/sem/depth_texture_type.h"
 #include "src/sem/external_texture_type.h"
@@ -595,15 +595,11 @@
     /// @param n the array size. 0 represents a runtime-array
     /// @param decos the optional decorations for the array
     /// @return the tint AST type for a array of size `n` of type `T`
-    typ::Array array(typ::Type subtype,
-                     uint32_t n = 0,
-                     ast::DecorationList decos = {}) const {
+    ast::Array* array(typ::Type subtype,
+                      uint32_t n = 0,
+                      ast::DecorationList decos = {}) const {
       subtype = MaybeCreateTypename(subtype);
-      return {subtype.ast ? builder->create<ast::Array>(subtype, n, decos)
-                          : nullptr,
-              subtype.sem ? builder->create<sem::ArrayType>(subtype, n,
-                                                            std::move(decos))
-                          : nullptr};
+      return builder->create<ast::Array>(subtype, n, decos);
     }
 
     /// @param source the Source of the node
@@ -611,24 +607,19 @@
     /// @param n the array size. 0 represents a runtime-array
     /// @param decos the optional decorations for the array
     /// @return the tint AST type for a array of size `n` of type `T`
-    typ::Array array(const Source& source,
-                     typ::Type subtype,
-                     uint32_t n = 0,
-                     ast::DecorationList decos = {}) const {
+    ast::Array* array(const Source& source,
+                      typ::Type subtype,
+                      uint32_t n = 0,
+                      ast::DecorationList decos = {}) const {
       subtype = MaybeCreateTypename(subtype);
-      return {
-          subtype.ast ? builder->create<ast::Array>(source, subtype, n, decos)
-                      : nullptr,
-          subtype.sem
-              ? builder->create<sem::ArrayType>(subtype, n, std::move(decos))
-              : nullptr};
+      return builder->create<ast::Array>(source, subtype, n, decos);
     }
 
     /// @param subtype the array element type
     /// @param n the array size. 0 represents a runtime-array
     /// @param stride the array stride
     /// @return the tint AST type for a array of size `n` of type `T`
-    typ::Array array(typ::Type subtype, uint32_t n, uint32_t stride) const {
+    ast::Array* array(typ::Type subtype, uint32_t n, uint32_t stride) const {
       subtype = MaybeCreateTypename(subtype);
       return array(subtype, n,
                    {builder->create<ast::StrideDecoration>(stride)});
@@ -639,10 +630,10 @@
     /// @param n the array size. 0 represents a runtime-array
     /// @param stride the array stride
     /// @return the tint AST type for a array of size `n` of type `T`
-    typ::Array array(const Source& source,
-                     typ::Type subtype,
-                     uint32_t n,
-                     uint32_t stride) const {
+    ast::Array* array(const Source& source,
+                      typ::Type subtype,
+                      uint32_t n,
+                      uint32_t stride) const {
       subtype = MaybeCreateTypename(subtype);
       return array(source, subtype, n,
                    {builder->create<ast::StrideDecoration>(stride)});
@@ -650,14 +641,14 @@
 
     /// @return the tint AST type for an array of size `N` of type `T`
     template <typename T, int N = 0>
-    typ::Array array() const {
+    ast::Array* array() const {
       return array(Of<T>(), N);
     }
 
     /// @param stride the array stride
     /// @return the tint AST type for an array of size `N` of type `T`
     template <typename T, int N = 0>
-    typ::Array array(uint32_t stride) const {
+    ast::Array* array(uint32_t stride) const {
       return array(Of<T>(), N, stride);
     }
 
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index 75ba8a3..2ec17c5 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -122,7 +122,7 @@
 TEST_P(ArrayDecorationTest, IsValid) {
   auto& params = GetParam();
 
-  auto arr =
+  auto* arr =
       ty.array(ty.f32(), 0,
                {
                    createDecoration(Source{{12, 34}}, *this, params.kind),
@@ -360,7 +360,7 @@
      << ", should_pass: " << params.should_pass;
   SCOPED_TRACE(ss.str());
 
-  auto arr = ty.array(Source{{12, 34}}, el_ty, 4, params.stride);
+  auto* arr = ty.array(Source{{12, 34}}, el_ty, 4, params.stride);
 
   Global("myarray", arr, ast::StorageClass::kInput);
 
@@ -445,11 +445,11 @@
         Params{ast_mat4x4<f32>, (default_mat4x4.align - 1) * 7, false}));
 
 TEST_F(ArrayStrideTest, MultipleDecorations) {
-  auto arr = ty.array(Source{{12, 34}}, ty.i32(), 4,
-                      {
-                          create<ast::StrideDecoration>(4),
-                          create<ast::StrideDecoration>(4),
-                      });
+  auto* arr = ty.array(Source{{12, 34}}, ty.i32(), 4,
+                       {
+                           create<ast::StrideDecoration>(4),
+                           create<ast::StrideDecoration>(4),
+                       });
 
   Global("myarray", arr, ast::StorageClass::kInput);
 
@@ -468,7 +468,7 @@
 TEST_F(StructBlockTest, StructUsedAsArrayElement) {
   auto* s = Structure("S", {Member("x", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  auto a = ty.array(s, 4);
+  auto* a = ty.array(s, 4);
   Global("G", a, ast::StorageClass::kPrivate);
 
   EXPECT_FALSE(r()->Resolve());
diff --git a/src/resolver/intrinsic_test.cc b/src/resolver/intrinsic_test.cc
index f73b19a..876fa1d 100644
--- a/src/resolver/intrinsic_test.cc
+++ b/src/resolver/intrinsic_test.cc
@@ -756,7 +756,7 @@
 using ResolverIntrinsicDataTest = ResolverTest;
 
 TEST_F(ResolverIntrinsicDataTest, ArrayLength_Vector) {
-  auto ary = ty.array<i32>();
+  auto* ary = ty.array<i32>();
   auto* str = Structure("S", {Member("x", ary)},
                         {create<ast::StructBlockDecoration>()});
   auto ac = ty.access(ast::AccessControl::kReadOnly, str);
diff --git a/src/resolver/is_host_shareable_test.cc b/src/resolver/is_host_shareable_test.cc
index 346a132..7f50b06 100644
--- a/src/resolver/is_host_shareable_test.cc
+++ b/src/resolver/is_host_shareable_test.cc
@@ -98,11 +98,13 @@
 }
 
 TEST_F(ResolverIsHostShareable, ArraySizedOfHostShareable) {
-  EXPECT_TRUE(r()->IsHostShareable(ty.array(ty.i32(), 5)));
+  auto* arr = create<sem::Array>(create<sem::I32>(), 5, 4, 20, 4, true);
+  EXPECT_TRUE(r()->IsHostShareable(arr));
 }
 
 TEST_F(ResolverIsHostShareable, ArrayUnsizedOfHostShareable) {
-  EXPECT_TRUE(r()->IsHostShareable(ty.array<i32>()));
+  auto* arr = create<sem::Array>(create<sem::I32>(), 0, 4, 4, 4, true);
+  EXPECT_TRUE(r()->IsHostShareable(arr));
 }
 
 // Note: Structure tests covered in host_shareable_validation_test.cc
diff --git a/src/resolver/is_storeable_test.cc b/src/resolver/is_storeable_test.cc
index 72f9cc6..62010e9 100644
--- a/src/resolver/is_storeable_test.cc
+++ b/src/resolver/is_storeable_test.cc
@@ -82,11 +82,13 @@
 }
 
 TEST_F(ResolverIsStorableTest, ArraySizedOfStorable) {
-  EXPECT_TRUE(r()->IsStorable(ty.array(ty.i32(), 5)));
+  auto* arr = create<sem::Array>(create<sem::I32>(), 5, 4, 20, 4, true);
+  EXPECT_TRUE(r()->IsStorable(arr));
 }
 
 TEST_F(ResolverIsStorableTest, ArrayUnsizedOfStorable) {
-  EXPECT_TRUE(r()->IsStorable(ty.array<i32>()));
+  auto* arr = create<sem::Array>(create<sem::I32>(), 0, 4, 4, 4, true);
+  EXPECT_TRUE(r()->IsStorable(arr));
 }
 
 TEST_F(ResolverIsStorableTest, Struct_AllMembersStorable) {
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 32273e2..a3162c2 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -178,8 +178,8 @@
   if (type->is_scalar() || type->Is<sem::Vector>() || type->Is<sem::Matrix>()) {
     return true;
   }
-  if (auto* arr = type->As<sem::ArrayType>()) {
-    return IsStorable(arr->type());
+  if (auto* arr = type->As<sem::Array>()) {
+    return IsStorable(arr->ElemType());
   }
   if (auto* str = type->As<sem::Struct>()) {
     for (const auto* member : str->Members()) {
@@ -204,8 +204,8 @@
   if (auto* mat = type->As<sem::Matrix>()) {
     return IsHostShareable(mat->type());
   }
-  if (auto* arr = type->As<sem::ArrayType>()) {
-    return IsHostShareable(arr->type());
+  if (auto* arr = type->As<sem::Array>()) {
+    return IsHostShareable(arr->ElemType());
   }
   if (auto* str = type->As<sem::Struct>()) {
     for (auto* member : str->Members()) {
@@ -287,7 +287,7 @@
         // TODO(crbug.com/tint/724) - Remove once tint:724 is complete.
         // ast::AccessDecorations are generated by the WGSL parser, used to
         // build sem::AccessControls and then leaked.
-        // ast::StrideDecoration are used to build a sem::ArrayTypes, but
+        // ast::StrideDecoration are used to build a sem::Arrays, but
         // multiple arrays of the same stride, size and element type are
         // currently de-duplicated by the type manager, and we leak these
         // decorations.
@@ -350,14 +350,7 @@
       return nullptr;
     }
     if (auto* t = ty->As<ast::Array>()) {
-      if (auto* el = Type(t->type())) {
-        auto* sem = builder_->create<sem::ArrayType>(
-            const_cast<sem::Type*>(el), t->size(), t->decorations());
-        if (Array(sem, ty->source())) {
-          return sem;
-        }
-      }
-      return nullptr;
+      return Array(t);
     }
     if (auto* t = ty->As<ast::Pointer>()) {
       if (auto* el = Type(t->type())) {
@@ -420,9 +413,10 @@
   return s;
 }
 
-Resolver::VariableInfo* Resolver::Variable(ast::Variable* var,
-                                           sem::Type* type, /* = nullptr */
-                                           std::string type_name /* = "" */) {
+Resolver::VariableInfo* Resolver::Variable(
+    ast::Variable* var,
+    const sem::Type* type, /* = nullptr */
+    std::string type_name /* = "" */) {
   auto it = variable_to_info_.find(var);
   if (it != variable_to_info_.end()) {
     return it->second;
@@ -436,18 +430,10 @@
     return nullptr;
   }
 
-  auto* ctype = Canonical(type);
+  auto* ctype = Canonical(const_cast<sem::Type*>(type));
   auto* info = variable_infos_.Create(var, ctype, type_name);
   variable_to_info_.emplace(var, info);
 
-  // TODO(bclayton): Why is this here? Needed?
-  // Resolve variable's type
-  if (auto* arr = info->type->As<sem::ArrayType>()) {
-    if (!Array(arr, var->source())) {
-      return nullptr;
-    }
-  }
-
   return info;
 }
 
@@ -596,8 +582,8 @@
 
 bool Resolver::ValidateVariable(const ast::Variable* var) {
   auto* type = variable_to_info_[var]->type;
-  if (auto* r = type->As<sem::ArrayType>()) {
-    if (r->IsRuntimeArray()) {
+  if (auto* r = type->As<sem::Array>()) {
+    if (r->IsRuntimeSized()) {
       diagnostics_.add_error(
           "v-0015",
           "runtime arrays may only appear as the last member of a struct",
@@ -873,8 +859,8 @@
                                     builder_->Symbols().NameFor(func->symbol()),
                                 func->source());
           return false;
-        } else if (auto* arr = member_ty->As<sem::ArrayType>()) {
-          if (arr->IsRuntimeArray()) {
+        } else if (auto* arr = member_ty->As<sem::Array>()) {
+          if (arr->IsRuntimeSized()) {
             diagnostics_.add_error(
                 "entry point IO types cannot contain runtime sized arrays",
                 member->Declaration()->source());
@@ -1276,9 +1262,9 @@
 
   auto* res = TypeOf(expr->array());
   auto* parent_type = res->UnwrapAll();
-  sem::Type* ret = nullptr;
-  if (auto* arr = parent_type->As<sem::ArrayType>()) {
-    ret = arr->type();
+  const sem::Type* ret = nullptr;
+  if (auto* arr = parent_type->As<sem::Array>()) {
+    ret = arr->ElemType();
   } else if (auto* vec = parent_type->As<sem::Vector>()) {
     ret = vec->type();
   } else if (auto* mat = parent_type->As<sem::Matrix>()) {
@@ -1293,8 +1279,8 @@
   // If we're extracting from a pointer, we return a pointer.
   if (auto* ptr = res->As<sem::Pointer>()) {
     ret = builder_->create<sem::Pointer>(ret, ptr->storage_class());
-  } else if (auto* arr = parent_type->As<sem::ArrayType>()) {
-    if (!arr->type()->is_scalar()) {
+  } else if (auto* arr = parent_type->As<sem::Array>()) {
+    if (!arr->ElemType()->is_scalar()) {
       // If we extract a non-scalar from an array then we also get a pointer. We
       // will generate a Function storage class variable to store this into.
       ret = builder_->create<sem::Pointer>(ret, ast::StorageClass::kFunction);
@@ -1459,7 +1445,7 @@
 
       value_cardinality_sum++;
     } else if (auto* value_vec = value_type->As<sem::Vector>()) {
-      sem::Type* value_elem_type = value_vec->type()->UnwrapAll();
+      auto* value_elem_type = value_vec->type()->UnwrapAll();
       // A mismatch of vector type parameter T is only an error if multiple
       // arguments are present. A single argument constructor constitutes a
       // type conversion expression.
@@ -1754,8 +1740,8 @@
   auto* lhs_declared_type = TypeOf(expr->lhs())->UnwrapAll();
   auto* rhs_declared_type = TypeOf(expr->rhs())->UnwrapAll();
 
-  auto* lhs_type = Canonical(lhs_declared_type);
-  auto* rhs_type = Canonical(rhs_declared_type);
+  auto* lhs_type = Canonical(const_cast<sem::Type*>(lhs_declared_type));
+  auto* rhs_type = Canonical(const_cast<sem::Type*>(rhs_declared_type));
 
   auto* lhs_vec = lhs_type->As<Vector>();
   auto* lhs_vec_elem_type = lhs_vec ? lhs_vec->type() : nullptr;
@@ -2006,7 +1992,7 @@
 
   // If the variable has a declared type, resolve it.
   std::string type_name;
-  sem::Type* type = nullptr;
+  const sem::Type* type = nullptr;
   if (auto* ast_ty = var->type()) {
     type_name = ast_ty->FriendlyName(builder_->Symbols());
     type = Type(ast_ty);
@@ -2065,7 +2051,7 @@
   }
   // TODO(bclayton): Remove this and fix tests. We're overriding the semantic
   // type stored in info->type here with a possibly non-canonicalized type.
-  info->type = type;
+  info->type = const_cast<sem::Type*>(type);
   variable_stack_.set(var->symbol(), info);
   current_block_->decls.push_back(var);
 
@@ -2251,8 +2237,7 @@
 
 bool Resolver::DefaultAlignAndSize(const sem::Type* ty,
                                    uint32_t& align,
-                                   uint32_t& size,
-                                   const Source& source) {
+                                   uint32_t& size) {
   static constexpr uint32_t vector_size[] = {
       /* padding */ 0,
       /* padding */ 0,
@@ -2297,76 +2282,71 @@
     align = s->Align();
     size = s->Size();
     return true;
-  } else if (cty->Is<sem::ArrayType>()) {
-    if (auto* sem =
-            Array(ty->UnwrapAliasIfNeeded()->As<sem::ArrayType>(), source)) {
-      align = sem->Align();
-      size = sem->Size();
-      return true;
-    }
-    return false;
+  } else if (auto* a = cty->As<sem::Array>()) {
+    align = a->Align();
+    size = a->SizeInBytes();
+    return true;
   }
   TINT_UNREACHABLE(diagnostics_) << "Invalid type " << ty->TypeInfo().name;
   return false;
 }
 
-const sem::Array* Resolver::Array(const sem::ArrayType* arr,
-                                  const Source& source) {
-  if (auto* sem = builder_->Sem().Get(arr)) {
-    // Semantic info already constructed for this array type
-    return sem;
-  }
+sem::Array* Resolver::Array(const ast::Array* arr) {
+  auto source = arr->source();
 
-  if (!ValidateArray(arr, source)) {
+  auto* el_ty = Type(arr->type());
+  if (!el_ty) {
     return nullptr;
   }
 
-  auto* el_ty = arr->type();
-
   uint32_t el_align = 0;
   uint32_t el_size = 0;
-  if (!DefaultAlignAndSize(el_ty, el_align, el_size, source)) {
+  if (!DefaultAlignAndSize(el_ty, el_align, el_size)) {
     return nullptr;
   }
 
-  auto create_semantic = [&](uint32_t stride) -> sem::Array* {
-    auto align = el_align;
-    // WebGPU requires runtime arrays have at least one element, but the AST
-    // records an element count of 0 for it.
-    auto size = std::max<uint32_t>(arr->size(), 1) * stride;
-    auto* sem = builder_->create<sem::Array>(const_cast<sem::ArrayType*>(arr),
-                                             align, size, stride);
-    builder_->Sem().Add(arr, sem);
-    return sem;
-  };
-
   // Look for explicit stride via [[stride(n)]] decoration
   uint32_t explicit_stride = 0;
   for (auto* deco : arr->decorations()) {
     Mark(deco);
-    if (auto* stride = deco->As<ast::StrideDecoration>()) {
+    if (auto* sd = deco->As<ast::StrideDecoration>()) {
       if (explicit_stride) {
         diagnostics_.add_error(
             "array must have at most one [[stride]] decoration", source);
         return nullptr;
       }
-      explicit_stride = stride->stride();
-      if (!ValidateArrayStrideDecoration(stride, el_size, el_align, source)) {
+      explicit_stride = sd->stride();
+      if (!ValidateArrayStrideDecoration(sd, el_size, el_align, source)) {
         return nullptr;
       }
+      continue;
     }
-  }
-  if (explicit_stride) {
-    return create_semantic(explicit_stride);
+
+    diagnostics_.add_error("decoration is not valid for array types",
+                           deco->source());
+    return nullptr;
   }
 
   // Calculate implicit stride
   auto implicit_stride = utils::RoundUp(el_align, el_size);
-  return create_semantic(implicit_stride);
+
+  auto stride = explicit_stride ? explicit_stride : implicit_stride;
+
+  // WebGPU requires runtime arrays have at least one element, but the AST
+  // records an element count of 0 for it.
+  auto size = std::max<uint32_t>(arr->size(), 1) * stride;
+  auto* sem = builder_->create<sem::Array>(el_ty, arr->size(), el_align, size,
+                                           stride, stride == implicit_stride);
+
+  if (!ValidateArray(sem, source)) {
+    return nullptr;
+  }
+
+  return sem;
 }
 
-bool Resolver::ValidateArray(const sem::ArrayType* arr, const Source& source) {
-  auto* el_ty = arr->type();
+bool Resolver::ValidateArray(const sem::Array* arr, const Source& source) {
+  auto* el_ty = arr->ElemType();
 
   if (!IsStorable(el_ty)) {
     builder_->Diagnostics().add_error(
@@ -2416,8 +2396,8 @@
 
 bool Resolver::ValidateStructure(const sem::Struct* str) {
   for (auto* member : str->Members()) {
-    if (auto* r = member->Type()->UnwrapAll()->As<sem::ArrayType>()) {
-      if (r->IsRuntimeArray()) {
+    if (auto* r = member->Type()->UnwrapAll()->As<sem::Array>()) {
+      if (r->IsRuntimeSized()) {
         if (member != str->Members().back()) {
           diagnostics_.add_error(
               "v-0015",
@@ -2434,14 +2414,6 @@
               member->Declaration()->source());
           return false;
         }
-
-        for (auto* deco : r->decorations()) {
-          if (!deco->Is<ast::StrideDecoration>()) {
-            diagnostics_.add_error("decoration is not valid for array types",
-                                   deco->source());
-            return false;
-          }
-        }
       }
     }
 
@@ -2511,7 +2483,7 @@
     uint32_t offset = struct_size;
     uint32_t align = 0;
     uint32_t size = 0;
-    if (!DefaultAlignAndSize(type, align, size, member->source())) {
+    if (!DefaultAlignAndSize(type, align, size)) {
       return nullptr;
     }
 
@@ -2779,7 +2751,7 @@
 bool Resolver::ApplyStorageClassUsageToType(ast::StorageClass sc,
                                             sem::Type* ty,
                                             const Source& usage) {
-  ty = ty->UnwrapIfNeeded();
+  ty = const_cast<sem::Type*>(ty->UnwrapIfNeeded());
 
   if (auto* str = ty->As<sem::Struct>()) {
     if (str->StorageClassUsage().count(sc)) {
@@ -2801,8 +2773,9 @@
     return true;
   }
 
-  if (auto* arr = ty->As<sem::ArrayType>()) {
-    return ApplyStorageClassUsageToType(sc, arr->type(), usage);
+  if (auto* arr = ty->As<sem::Array>()) {
+    return ApplyStorageClassUsageToType(
+        sc, const_cast<sem::Type*>(arr->ElemType()), usage);
   }
 
   if (ast::IsHostShareable(sc) && !IsHostShareable(ty)) {
@@ -2829,7 +2802,8 @@
   return result;
 }
 
-std::string Resolver::VectorPretty(uint32_t size, sem::Type* element_type) {
+std::string Resolver::VectorPretty(uint32_t size,
+                                   const sem::Type* element_type) {
   sem::Vector vec_type(element_type, size);
   return vec_type.FriendlyName(builder_->Symbols());
 }
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 4e94403..a5f72f8 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -224,7 +224,7 @@
 
   // AST and Type validation methods
   // Each return true on success, false on failure.
-  bool ValidateArray(const sem::ArrayType* arr, const Source& source);
+  bool ValidateArray(const sem::Array* arr, const Source& source);
   bool ValidateArrayStrideDecoration(const ast::StrideDecoration* deco,
                                      uint32_t el_size,
                                      uint32_t el_align,
@@ -250,15 +250,18 @@
   /// @param ty the ast::Type
   sem::Type* Type(const ast::Type* ty);
 
-  /// @returns the semantic information for the array `arr`, building it if it
-  /// hasn't been constructed already. If an error is raised, nullptr is
-  /// returned.
+  /// Builds and returns the semantic information for the array `arr`.
+  /// This method does not mark the ast::Array node, nor attach the generated
+  /// semantic information to the AST node.
+  /// @returns the semantic Array information, or nullptr if an error is raised.
   /// @param arr the Array to get semantic information for
-  /// @param source the Source of the ast node with this array as its type
-  const sem::Array* Array(const sem::ArrayType* arr, const Source& source);
+  sem::Array* Array(const ast::Array* arr);
 
-  /// @returns the sem::Struct for the AST structure `str`. If an error is
-  /// raised, nullptr is returned.
+  /// Builds and returns the semantic information for the structure `str`.
+  /// This method does not mark the ast::Struct node, nor attach the generated
+  /// semantic information to the AST node.
+  /// @returns the semantic Struct information, or nullptr if an error is
+  /// raised. raised, nullptr is returned.
   sem::Struct* Structure(const ast::Struct* str);
 
   /// @returns the VariableInfo for the variable `var`, building it if it hasn't
@@ -268,7 +271,7 @@
   /// @param type_name optional type name of `var` to use instead of
   /// `var->type()->FriendlyName()`.
   VariableInfo* Variable(ast::Variable* var,
-                         sem::Type* type = nullptr,
+                         const sem::Type* type = nullptr,
                          std::string type_name = "");
 
   /// Records the storage class usage for the given type, and any transient
@@ -285,12 +288,10 @@
 
   /// @param align the output default alignment in bytes for the type `ty`
   /// @param size the output default size in bytes for the type `ty`
-  /// @param source the Source of the variable declaration of type `ty`
   /// @returns true on success, false on error
   bool DefaultAlignAndSize(const sem::Type* ty,
                            uint32_t& align,
-                           uint32_t& size,
-                           const Source& source);
+                           uint32_t& size);
 
   /// @returns the resolved type of the ast::Expression `expr`
   /// @param expr the expression
@@ -333,7 +334,7 @@
   /// @param size the vector dimension
   /// @param element_type scalar vector sub-element type
   /// @return pretty string representation
-  std::string VectorPretty(uint32_t size, sem::Type* element_type);
+  std::string VectorPretty(uint32_t size, const sem::Type* element_type);
 
   /// Mark records that the given AST node has been visited, and asserts that
   /// the given node has not already been seen. Diamonds in the AST are illegal.
diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc
index db6be8e..9ee3547 100644
--- a/src/resolver/storage_class_validation_test.cc
+++ b/src/resolver/storage_class_validation_test.cc
@@ -61,7 +61,7 @@
 TEST_F(ResolverStorageClassValidationTest, StorageBufferArray) {
   // var<storage> g : [[access(read)]] array<S, 3>;
   auto* s = Structure("S", {Member("a", ty.f32())});
-  auto a = ty.array(s, 3);
+  auto* a = ty.array(s, 3);
   auto ac = ty.access(ast::AccessControl::kReadOnly, a);
   Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kStorage);
 
@@ -169,7 +169,7 @@
 TEST_F(ResolverStorageClassValidationTest, UniformBufferArray) {
   // var<uniform> g : [[access(read)]] array<S, 3>;
   auto* s = Structure("S", {Member("a", ty.f32())});
-  auto a = ty.array(s, 3);
+  auto* a = ty.array(s, 3);
   auto ac = ty.access(ast::AccessControl::kReadOnly, a);
   Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kUniform);
 
diff --git a/src/resolver/struct_layout_test.cc b/src/resolver/struct_layout_test.cc
index 55d5d2e..c0d0507 100644
--- a/src/resolver/struct_layout_test.cc
+++ b/src/resolver/struct_layout_test.cc
@@ -173,8 +173,8 @@
 }
 
 TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfExplicitStrideArray) {
-  auto inner = ty.array<i32, 2>(/*stride*/ 16);  // size: 32
-  auto outer = ty.array(inner, 12);              // size: 12 * 32
+  auto* inner = ty.array<i32, 2>(/*stride*/ 16);  // size: 32
+  auto* outer = ty.array(inner, 12);              // size: 12 * 32
   auto* s = Structure("S", {
                                Member("c", outer),
                            });
@@ -198,7 +198,7 @@
                                        Member("b", ty.vec3<i32>()),
                                        Member("c", ty.vec4<i32>()),
                                    });  // size: 48
-  auto outer = ty.array(inner, 12);     // size: 12 * 48
+  auto* outer = ty.array(inner, 12);    // size: 12 * 48
   auto* s = Structure("S", {
                                Member("c", outer),
                            });
diff --git a/src/resolver/struct_storage_class_use_test.cc b/src/resolver/struct_storage_class_use_test.cc
index b41a5e3..d087069 100644
--- a/src/resolver/struct_storage_class_use_test.cc
+++ b/src/resolver/struct_storage_class_use_test.cc
@@ -105,7 +105,7 @@
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalArray) {
   auto* s = Structure("S", {Member("a", ty.f32())});
-  auto a = ty.array(s, 3);
+  auto* a = ty.array(s, 3);
   Global("g", a, ast::StorageClass::kPrivate);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -158,7 +158,7 @@
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalArray) {
   auto* s = Structure("S", {Member("a", ty.f32())});
-  auto a = ty.array(s, 3);
+  auto* a = ty.array(s, 3);
   WrapInFunction(Var("g", a, ast::StorageClass::kFunction));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/sem/access_control_type_test.cc b/src/sem/access_control_type_test.cc
index 3b3138b..828b721 100644
--- a/src/sem/access_control_type_test.cc
+++ b/src/sem/access_control_type_test.cc
@@ -37,7 +37,7 @@
   Type* ty = &at;
   EXPECT_TRUE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/alias_type_test.cc b/src/sem/alias_type_test.cc
index 27d5084..ed8d611 100644
--- a/src/sem/alias_type_test.cc
+++ b/src/sem/alias_type_test.cc
@@ -33,7 +33,7 @@
   sem::Type* ty = at;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_TRUE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/array.cc b/src/sem/array.cc
index fefd8a1..1c6bc9c 100644
--- a/src/sem/array.cc
+++ b/src/sem/array.cc
@@ -14,16 +14,53 @@
 
 #include "src/sem/array.h"
 
+#include <string>
+
+#include "src/debug.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::sem::Array);
 
 namespace tint {
 namespace sem {
 
-Array::Array(sem::ArrayType* type,
+Array::Array(const Type* element,
+             uint32_t count,
              uint32_t align,
              uint32_t size,
-             uint32_t stride)
-    : type_(type), align_(align), size_(size), stride_(stride) {}
+             uint32_t stride,
+             bool stride_implicit)
+    : element_(element),
+      count_(count),
+      align_(align),
+      size_(size),
+      stride_(stride),
+      stride_implicit_(stride_implicit) {
+  TINT_ASSERT(element_);
+}
+
+std::string Array::type_name() const {
+  std::string type_name = "__array" + element_->type_name();
+  type_name += "_count_" + std::to_string(count_);
+  type_name += "_align_" + std::to_string(align_);
+  type_name += "_size_" + std::to_string(size_);
+  type_name += "_stride_" + std::to_string(stride_);
+  // Note: stride_implicit is not part of the type_name string as this is a
+  // property derived from the other fields.
+  return type_name;
+}
+
+std::string Array::FriendlyName(const SymbolTable& symbols) const {
+  std::ostringstream out;
+  if (!stride_implicit_) {
+    out << "[[stride(" << stride_ << ")]] ";
+  }
+  out << "array<" << element_->FriendlyName(symbols);
+  if (!IsRuntimeSized()) {
+    out << ", " << count_;
+  }
+  out << ">";
+  return out.str();
+}
 
 }  // namespace sem
 }  // namespace tint
diff --git a/src/sem/array.h b/src/sem/array.h
index 68297b9..df363c0 100644
--- a/src/sem/array.h
+++ b/src/sem/array.h
@@ -16,28 +16,47 @@
 #define SRC_SEM_ARRAY_H_
 
 #include <stdint.h>
+#include <string>
 
 #include "src/sem/node.h"
+#include "src/sem/type.h"
+
+// Forward declarations
+namespace tint {
+namespace ast {
+class Array;
+}  // namespace ast
+}  // namespace tint
 
 namespace tint {
-
 namespace sem {
-// Forward declarations
-class ArrayType;
 
 /// Array holds the semantic information for Array nodes.
-class Array : public Castable<Array, Node> {
+class Array : public Castable<Array, Type> {
  public:
   /// Constructor
-  /// @param type the Array type
-  /// @param align the byte alignment of the structure
-  /// @param size the byte size of the structure
+  /// @param element the array element type
+  /// @param count the number of elements in the array. 0 represents a
+  /// runtime-sized array.
+  /// @param align the byte alignment of the array
+  /// @param size the byte size of the array
   /// @param stride the number of bytes from the start of one element of the
   /// array to the start of the next element
-  Array(sem::ArrayType* type, uint32_t align, uint32_t size, uint32_t stride);
+  /// @param stride_implicit is true if the value of `stride` matches the
+  /// element's natural stride.
+  Array(Type const* element,
+        uint32_t count,
+        uint32_t align,
+        uint32_t size,
+        uint32_t stride,
+        bool stride_implicit);
 
-  /// @return the resolved type of the Array
-  sem::ArrayType* Type() const { return type_; }
+  /// @return the array element type
+  Type const* ElemType() const { return element_; }
+
+  /// @returns the number of elements in the array. 0 represents a runtime-sized
+  /// array.
+  uint32_t Count() const { return count_; }
 
   /// @returns the byte alignment of the array
   /// @note this may differ from the alignment of a structure member of this
@@ -47,17 +66,34 @@
   /// @returns the byte size of the array
   /// @note this may differ from the size of a structure member of this array
   /// type, if the member is annotated with the `[[size(n)]]` decoration.
-  uint32_t Size() const { return size_; }
+  uint32_t SizeInBytes() const { return size_; }
 
   /// @returns the number of bytes from the start of one element of the
   /// array to the start of the next element
   uint32_t Stride() const { return stride_; }
 
+  /// @returns true if the value returned by Stride() does matches the
+  /// element's natural stride
+  bool IsStrideImplicit() const { return stride_implicit_; }
+
+  /// @returns true if this array is runtime sized
+  bool IsRuntimeSized() const { return count_ == 0; }
+
+  /// @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:
-  sem::ArrayType* const type_;
+  Type const* const element_;
+  uint32_t const count_;
   uint32_t const align_;
   uint32_t const size_;
   uint32_t const stride_;
+  bool const stride_implicit_;
 };
 
 }  // namespace sem
diff --git a/src/sem/array_type.cc b/src/sem/array_type.cc
deleted file mode 100644
index f0846e4..0000000
--- a/src/sem/array_type.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/array_type.h"
-
-#include <cmath>
-
-#include "src/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::ArrayType);
-
-namespace tint {
-namespace sem {
-
-ArrayType::ArrayType(Type* subtype,
-                     uint32_t size,
-                     ast::DecorationList decorations)
-    : subtype_(subtype), size_(size), decos_(decorations) {}
-
-ArrayType::ArrayType(ArrayType&&) = default;
-
-ArrayType::~ArrayType() = default;
-
-std::string ArrayType::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 ArrayType::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();
-}
-
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/array_type.h b/src/sem/array_type.h
deleted file mode 100644
index 77fe676..0000000
--- a/src/sem/array_type.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_SEM_ARRAY_TYPE_H_
-#define SRC_SEM_ARRAY_TYPE_H_
-
-#include <string>
-
-#include "src/ast/decoration.h"
-#include "src/sem/type.h"
-
-namespace tint {
-namespace sem {
-
-/// An array type. If size is zero then it is a runtime array.
-// TODO(amaiorano): https://crbug.com/tint/724 Fold into sem::Array once parsers
-// don't create this anymore.
-class ArrayType : public Castable<ArrayType, Type> {
- public:
-  /// Constructor
-  /// @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
-  ArrayType(Type* subtype, uint32_t size, ast::DecorationList decorations);
-  /// Move constructor
-  ArrayType(ArrayType&&);
-  ~ArrayType() 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;
-
- private:
-  Type* const subtype_;
-  uint32_t const size_;
-  ast::DecorationList const decos_;
-};
-
-}  // namespace sem
-}  // namespace tint
-
-#endif  // SRC_SEM_ARRAY_TYPE_H_
diff --git a/src/sem/array_type_test.cc b/src/sem/array_type_test.cc
deleted file mode 100644
index 636520b..0000000
--- a/src/sem/array_type_test.cc
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/sem/access_control_type.h"
-#include "src/sem/test_helper.h"
-#include "src/sem/texture_type.h"
-
-namespace tint {
-namespace sem {
-namespace {
-
-using ArrayTest = TestHelper;
-
-TEST_F(ArrayTest, CreateSizedArray) {
-  U32 u32;
-  ArrayType arr{&u32, 3, ast::DecorationList{}};
-  EXPECT_EQ(arr.type(), &u32);
-  EXPECT_EQ(arr.size(), 3u);
-  EXPECT_TRUE(arr.Is<ArrayType>());
-  EXPECT_FALSE(arr.IsRuntimeArray());
-}
-
-TEST_F(ArrayTest, CreateRuntimeArray) {
-  U32 u32;
-  ArrayType arr{&u32, 0, ast::DecorationList{}};
-  EXPECT_EQ(arr.type(), &u32);
-  EXPECT_EQ(arr.size(), 0u);
-  EXPECT_TRUE(arr.Is<ArrayType>());
-  EXPECT_TRUE(arr.IsRuntimeArray());
-}
-
-TEST_F(ArrayTest, Is) {
-  I32 i32;
-
-  ArrayType arr{&i32, 3, ast::DecorationList{}};
-  Type* ty = &arr;
-  EXPECT_FALSE(ty->Is<AccessControl>());
-  EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_TRUE(ty->Is<ArrayType>());
-  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(ArrayTest, TypeName) {
-  I32 i32;
-  ArrayType arr{&i32, 0, ast::DecorationList{}};
-  EXPECT_EQ(arr.type_name(), "__array__i32");
-}
-
-TEST_F(ArrayTest, FriendlyNameRuntimeSized) {
-  ArrayType arr{ty.i32(), 0, ast::DecorationList{}};
-  EXPECT_EQ(arr.FriendlyName(Symbols()), "array<i32>");
-}
-
-TEST_F(ArrayTest, FriendlyNameStaticSized) {
-  ArrayType arr{ty.i32(), 5, ast::DecorationList{}};
-  EXPECT_EQ(arr.FriendlyName(Symbols()), "array<i32, 5>");
-}
-
-TEST_F(ArrayTest, FriendlyNameWithStride) {
-  ArrayType arr{ty.i32(), 5,
-                ast::DecorationList{create<ast::StrideDecoration>(32)}};
-  EXPECT_EQ(arr.FriendlyName(Symbols()), "[[stride(32)]] array<i32, 5>");
-}
-
-TEST_F(ArrayTest, TypeName_RuntimeArray) {
-  I32 i32;
-  ArrayType arr{&i32, 3, ast::DecorationList{}};
-  EXPECT_EQ(arr.type_name(), "__array__i32_3");
-}
-
-TEST_F(ArrayTest, TypeName_WithStride) {
-  I32 i32;
-  ArrayType arr{&i32, 3,
-                ast::DecorationList{create<ast::StrideDecoration>(16)}};
-  EXPECT_EQ(arr.type_name(), "__array__i32_3_stride_16");
-}
-
-}  // namespace
-}  // namespace sem
-}  // namespace tint
diff --git a/src/sem/bool_type_test.cc b/src/sem/bool_type_test.cc
index a76f1fb..51d6ef0 100644
--- a/src/sem/bool_type_test.cc
+++ b/src/sem/bool_type_test.cc
@@ -27,7 +27,7 @@
   Type* ty = &b;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_TRUE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/depth_texture_type_test.cc b/src/sem/depth_texture_type_test.cc
index 3f1ddcd..e51ebfc 100644
--- a/src/sem/depth_texture_type_test.cc
+++ b/src/sem/depth_texture_type_test.cc
@@ -32,7 +32,7 @@
   Type* ty = &d;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/external_texture_type_test.cc b/src/sem/external_texture_type_test.cc
index afbb98e..3e48ea8 100644
--- a/src/sem/external_texture_type_test.cc
+++ b/src/sem/external_texture_type_test.cc
@@ -33,7 +33,7 @@
   Type* ty = &s;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/f32_type_test.cc b/src/sem/f32_type_test.cc
index bc18606..670d831 100644
--- a/src/sem/f32_type_test.cc
+++ b/src/sem/f32_type_test.cc
@@ -27,7 +27,7 @@
   Type* ty = &f;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_TRUE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/i32_type_test.cc b/src/sem/i32_type_test.cc
index 3d14f35..437aa16 100644
--- a/src/sem/i32_type_test.cc
+++ b/src/sem/i32_type_test.cc
@@ -27,7 +27,7 @@
   Type* ty = &i;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_TRUE(ty->Is<I32>());
diff --git a/src/sem/matrix_type_test.cc b/src/sem/matrix_type_test.cc
index 4896ca2..dbcb898 100644
--- a/src/sem/matrix_type_test.cc
+++ b/src/sem/matrix_type_test.cc
@@ -38,7 +38,7 @@
   Type* ty = &m;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/multisampled_texture_type_test.cc b/src/sem/multisampled_texture_type_test.cc
index e35a2e9..d202970 100644
--- a/src/sem/multisampled_texture_type_test.cc
+++ b/src/sem/multisampled_texture_type_test.cc
@@ -33,7 +33,7 @@
   Type* ty = &s;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/pointer_type.cc b/src/sem/pointer_type.cc
index 1bb9c12..699ea9c 100644
--- a/src/sem/pointer_type.cc
+++ b/src/sem/pointer_type.cc
@@ -21,7 +21,7 @@
 namespace tint {
 namespace sem {
 
-Pointer::Pointer(Type* subtype, ast::StorageClass storage_class)
+Pointer::Pointer(const Type* subtype, ast::StorageClass storage_class)
     : subtype_(subtype), storage_class_(storage_class) {}
 
 std::string Pointer::type_name() const {
diff --git a/src/sem/pointer_type.h b/src/sem/pointer_type.h
index 843167b..929853c 100644
--- a/src/sem/pointer_type.h
+++ b/src/sem/pointer_type.h
@@ -26,16 +26,16 @@
 /// A pointer type.
 class Pointer : public Castable<Pointer, Type> {
  public:
-  /// Construtor
+  /// Constructor
   /// @param subtype the pointee type
   /// @param storage_class the storage class of the pointer
-  Pointer(Type* subtype, ast::StorageClass storage_class);
+  Pointer(const Type* subtype, ast::StorageClass storage_class);
   /// Move constructor
   Pointer(Pointer&&);
   ~Pointer() override;
 
   /// @returns the pointee type
-  Type* type() const { return subtype_; }
+  const Type* type() const { return subtype_; }
   /// @returns the storage class of the pointer
   ast::StorageClass storage_class() const { return storage_class_; }
 
@@ -48,7 +48,7 @@
   std::string FriendlyName(const SymbolTable& symbols) const override;
 
  private:
-  Type* const subtype_;
+  Type const* const subtype_;
   ast::StorageClass const storage_class_;
 };
 
diff --git a/src/sem/pointer_type_test.cc b/src/sem/pointer_type_test.cc
index 00cec4b..e9f0893 100644
--- a/src/sem/pointer_type_test.cc
+++ b/src/sem/pointer_type_test.cc
@@ -35,7 +35,7 @@
   Type* ty = &p;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/sampled_texture_type_test.cc b/src/sem/sampled_texture_type_test.cc
index cb00b03..85b8e2b 100644
--- a/src/sem/sampled_texture_type_test.cc
+++ b/src/sem/sampled_texture_type_test.cc
@@ -32,7 +32,7 @@
   Type* ty = &s;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/sampler_type_test.cc b/src/sem/sampler_type_test.cc
index c419ee6..9ee25d3 100644
--- a/src/sem/sampler_type_test.cc
+++ b/src/sem/sampler_type_test.cc
@@ -38,7 +38,7 @@
   Type* ty = &s;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/sem_array_test.cc b/src/sem/sem_array_test.cc
new file mode 100644
index 0000000..9620af9
--- /dev/null
+++ b/src/sem/sem_array_test.cc
@@ -0,0 +1,102 @@
+// 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/sem/access_control_type.h"
+#include "src/sem/test_helper.h"
+#include "src/sem/texture_type.h"
+
+namespace tint {
+namespace sem {
+namespace {
+
+using ArrayTest = TestHelper;
+
+TEST_F(ArrayTest, CreateSizedArray) {
+  U32 u32;
+  auto* arr = create<Array>(&u32, 2, 4, 8, 16, true);
+  EXPECT_EQ(arr->ElemType(), &u32);
+  EXPECT_EQ(arr->Count(), 2u);
+  EXPECT_EQ(arr->Align(), 4u);
+  EXPECT_EQ(arr->SizeInBytes(), 8u);
+  EXPECT_EQ(arr->Stride(), 16u);
+  EXPECT_TRUE(arr->IsStrideImplicit());
+  EXPECT_FALSE(arr->IsRuntimeSized());
+}
+
+TEST_F(ArrayTest, CreateRuntimeArray) {
+  U32 u32;
+  auto* arr = create<Array>(&u32, 0, 4, 8, 16, true);
+  EXPECT_EQ(arr->ElemType(), &u32);
+  EXPECT_EQ(arr->Count(), 0u);
+  EXPECT_EQ(arr->Align(), 4u);
+  EXPECT_EQ(arr->SizeInBytes(), 8u);
+  EXPECT_EQ(arr->Stride(), 16u);
+  EXPECT_TRUE(arr->IsStrideImplicit());
+  EXPECT_TRUE(arr->IsRuntimeSized());
+}
+
+TEST_F(ArrayTest, Is) {
+  I32 i32;
+
+  Type* ty = create<Array>(&i32, 2, 4, 8, 4, true);
+  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(ArrayTest, TypeName) {
+  I32 i32;
+  auto* arr = create<Array>(&i32, 2, 0, 4, 4, true);
+  EXPECT_EQ(arr->type_name(), "__array__i32_count_2_align_0_size_4_stride_4");
+}
+
+TEST_F(ArrayTest, FriendlyNameRuntimeSized) {
+  auto* arr = create<Array>(ty.i32(), 0, 0, 4, 4, true);
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32>");
+}
+
+TEST_F(ArrayTest, FriendlyNameStaticSized) {
+  auto* arr = create<Array>(ty.i32(), 5, 4, 20, 4, true);
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, 5>");
+}
+
+TEST_F(ArrayTest, FriendlyNameRuntimeSizedNonImplicitStride) {
+  auto* arr = create<Array>(ty.i32(), 0, 0, 4, 4, false);
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "[[stride(4)]] array<i32>");
+}
+
+TEST_F(ArrayTest, FriendlyNameStaticSizedNonImplicitStride) {
+  auto* arr = create<Array>(ty.i32(), 5, 4, 20, 4, false);
+  EXPECT_EQ(arr->FriendlyName(Symbols()), "[[stride(4)]] array<i32, 5>");
+}
+
+TEST_F(ArrayTest, TypeName_RuntimeArray) {
+  I32 i32;
+  auto* arr = create<Array>(&i32, 2, 4, 8, 16, true);
+  EXPECT_EQ(arr->type_name(), "__array__i32_count_2_align_4_size_8_stride_16");
+}
+
+}  // namespace
+}  // namespace sem
+}  // namespace tint
diff --git a/src/sem/sem_struct_test.cc b/src/sem/sem_struct_test.cc
index 0e5742a..88309fa 100644
--- a/src/sem/sem_struct_test.cc
+++ b/src/sem/sem_struct_test.cc
@@ -45,7 +45,7 @@
   sem::Type* ty = s;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/storage_texture_type_test.cc b/src/sem/storage_texture_type_test.cc
index 77b8944..3fef3ef 100644
--- a/src/sem/storage_texture_type_test.cc
+++ b/src/sem/storage_texture_type_test.cc
@@ -34,7 +34,7 @@
   Type* ty = s;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/type.cc b/src/sem/type.cc
index 589688f..349506c 100644
--- a/src/sem/type.cc
+++ b/src/sem/type.cc
@@ -37,28 +37,28 @@
 
 Type::~Type() = default;
 
-Type* Type::UnwrapPtrIfNeeded() {
+const Type* Type::UnwrapPtrIfNeeded() const {
   if (auto* ptr = As<sem::Pointer>()) {
     return ptr->type();
   }
   return this;
 }
 
-Type* Type::UnwrapAliasIfNeeded() {
-  Type* unwrapped = this;
+const Type* Type::UnwrapAliasIfNeeded() const {
+  const Type* unwrapped = this;
   while (auto* ptr = unwrapped->As<sem::Alias>()) {
     unwrapped = ptr->type();
   }
   return unwrapped;
 }
 
-Type* Type::UnwrapIfNeeded() {
+const Type* Type::UnwrapIfNeeded() const {
   auto* where = this;
   while (true) {
     if (auto* alias = where->As<sem::Alias>()) {
-          where = alias->type();
+      where = alias->type();
     } else if (auto* access = where->As<sem::AccessControl>()) {
-          where = access->type();
+      where = access->type();
     } else {
       break;
     }
@@ -66,7 +66,7 @@
   return where;
 }
 
-Type* Type::UnwrapAll() {
+const Type* Type::UnwrapAll() const {
   return UnwrapIfNeeded()->UnwrapPtrIfNeeded()->UnwrapIfNeeded();
 }
 
diff --git a/src/sem/type.h b/src/sem/type.h
index ca7bdd4..45de49c 100644
--- a/src/sem/type.h
+++ b/src/sem/type.h
@@ -46,22 +46,11 @@
   virtual std::string FriendlyName(const SymbolTable& symbols) const = 0;
 
   /// @returns the pointee type if this is a pointer, `this` otherwise
-  Type* UnwrapPtrIfNeeded();
-
-  /// @returns the pointee type if this is a pointer, `this` otherwise
-  const Type* UnwrapPtrIfNeeded() const {
-    return const_cast<Type*>(this)->UnwrapPtrIfNeeded();
-  }
+  const Type* UnwrapPtrIfNeeded() const;
 
   /// @returns the most deeply nested aliased type if this is an alias, `this`
   /// otherwise
-  Type* UnwrapAliasIfNeeded();
-
-  /// @returns the most deeply nested aliased type if this is an alias, `this`
-  /// otherwise
-  const Type* UnwrapAliasIfNeeded() const {
-    return const_cast<Type*>(this)->UnwrapAliasIfNeeded();
-  }
+  const Type* UnwrapAliasIfNeeded() const;
 
   /// Removes all levels of aliasing and access control.
   /// This is just enough to assist with WGSL translation
@@ -69,31 +58,14 @@
   /// 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();
-
-  /// 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.
-  const Type* UnwrapIfNeeded() const {
-    return const_cast<Type*>(this)->UnwrapIfNeeded();
-  }
+  const Type* UnwrapIfNeeded() const;
 
   /// 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 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
-  const Type* UnwrapAll() const { return const_cast<Type*>(this)->UnwrapAll(); }
+  const Type* UnwrapAll() const;
 
   /// @returns true if this type is a scalar
   bool is_scalar() const;
diff --git a/src/sem/type_mappings.h b/src/sem/type_mappings.h
index e963a33..7193547 100644
--- a/src/sem/type_mappings.h
+++ b/src/sem/type_mappings.h
@@ -34,7 +34,6 @@
 namespace sem {
 // Forward declarations
 class Array;
-class ArrayType;
 class Call;
 class Expression;
 class Function;
@@ -51,7 +50,6 @@
 /// rules will be used to infer the return type based on the argument type.
 struct TypeMappings {
   //! @cond Doxygen_Suppress
-  Array* operator()(sem::ArrayType*);
   Call* operator()(ast::CallExpression*);
   Expression* operator()(ast::Expression*);
   Function* operator()(ast::Function*);
diff --git a/src/sem/u32_type_test.cc b/src/sem/u32_type_test.cc
index bbb9e08..00d0428 100644
--- a/src/sem/u32_type_test.cc
+++ b/src/sem/u32_type_test.cc
@@ -27,7 +27,7 @@
   Type* ty = &u;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/sem/vector_type_test.cc b/src/sem/vector_type_test.cc
index 6e52afb..78b1e09 100644
--- a/src/sem/vector_type_test.cc
+++ b/src/sem/vector_type_test.cc
@@ -35,7 +35,7 @@
   Type* ty = &v;
   EXPECT_FALSE(ty->Is<AccessControl>());
   EXPECT_FALSE(ty->Is<Alias>());
-  EXPECT_FALSE(ty->Is<ArrayType>());
+  EXPECT_FALSE(ty->Is<Array>());
   EXPECT_FALSE(ty->Is<Bool>());
   EXPECT_FALSE(ty->Is<F32>());
   EXPECT_FALSE(ty->Is<I32>());
diff --git a/src/transform/bound_array_accessors.cc b/src/transform/bound_array_accessors.cc
index 3f56cf0..82c221b 100644
--- a/src/transform/bound_array_accessors.cc
+++ b/src/transform/bound_array_accessors.cc
@@ -42,7 +42,7 @@
   auto& diags = ctx->dst->Diagnostics();
 
   auto* ret_type = ctx->src->Sem().Get(expr->array())->Type()->UnwrapAll();
-  if (!ret_type->Is<sem::ArrayType>() && !ret_type->Is<sem::Matrix>() &&
+  if (!ret_type->Is<sem::Array>() && !ret_type->Is<sem::Matrix>() &&
       !ret_type->Is<sem::Vector>()) {
     return nullptr;
   }
@@ -52,10 +52,10 @@
 
   uint32_t size = 0;
   bool is_vec = ret_type->Is<sem::Vector>();
-  bool is_arr = ret_type->Is<sem::ArrayType>();
+  bool is_arr = ret_type->Is<sem::Array>();
   if (is_vec || is_arr) {
     size = is_vec ? ret_type->As<sem::Vector>()->size()
-                  : ret_type->As<sem::ArrayType>()->size();
+                  : ret_type->As<sem::Array>()->Count();
   } else {
     // The row accessor would have been an embedded array accessor and already
     // handled, so we just need to do columns here.
diff --git a/src/transform/calculate_array_length.cc b/src/transform/calculate_array_length.cc
index c36acce..22407a4 100644
--- a/src/transform/calculate_array_length.cc
+++ b/src/transform/calculate_array_length.cc
@@ -77,8 +77,8 @@
   // get_buffer_size_intrinsic() emits the function decorated with
   // BufferSizeIntrinsic that is transformed by the HLSL writer into a call to
   // [RW]ByteAddressBuffer.GetDimensions().
-  std::unordered_map<sem::Struct*, Symbol> buffer_size_intrinsics;
-  auto get_buffer_size_intrinsic = [&](sem::Struct* buffer_type) {
+  std::unordered_map<const sem::Struct*, Symbol> buffer_size_intrinsics;
+  auto get_buffer_size_intrinsic = [&](const sem::Struct* buffer_type) {
     return utils::GetOrCreate(buffer_size_intrinsics, buffer_type, [&] {
       auto name = ctx.dst->Sym();
       auto* buffer_typename =
diff --git a/src/transform/decompose_storage_access.cc b/src/transform/decompose_storage_access.cc
index b4ff19b..9d51bbb 100644
--- a/src/transform/decompose_storage_access.cc
+++ b/src/transform/decompose_storage_access.cc
@@ -175,8 +175,8 @@
 
 /// TypePair is a pair of types that can be used as a unordered map or set key.
 struct TypePair {
-  sem::Type* first;
-  sem::Type* second;
+  sem::Type const* first;
+  sem::Type const* second;
   bool operator==(const TypePair& rhs) const {
     return first == rhs.first && second == rhs.second;
   }
@@ -188,20 +188,20 @@
 };
 
 /// @returns the size in bytes of a scalar
-uint32_t ScalarSize(sem::Type*) {
+uint32_t ScalarSize(const sem::Type*) {
   // TODO(bclayton): Assumes 32-bit elements
   return 4;
 }
 
 /// @returns the numer of bytes between columns of the given matrix
-uint32_t MatrixColumnStride(sem::Matrix* mat) {
+uint32_t MatrixColumnStride(const sem::Matrix* mat) {
   return ScalarSize(mat->type()) * ((mat->rows() == 2) ? 2 : 4);
 }
 
 /// @returns a DecomposeStorageAccess::Intrinsic decoration that can be applied
 /// to a stub function to load the type `ty`.
 DecomposeStorageAccess::Intrinsic* IntrinsicLoadFor(ProgramBuilder* builder,
-                                                    sem::Type* ty) {
+                                                    const sem::Type* ty) {
   using Intrinsic = DecomposeStorageAccess::Intrinsic;
 
   auto intrinsic = [builder](Intrinsic::Type type) {
@@ -260,7 +260,7 @@
 /// @returns a DecomposeStorageAccess::Intrinsic decoration that can be applied
 /// to a stub function to store the type `ty`.
 DecomposeStorageAccess::Intrinsic* IntrinsicStoreFor(ProgramBuilder* builder,
-                                                     sem::Type* ty) {
+                                                     const sem::Type* ty) {
   using Intrinsic = DecomposeStorageAccess::Intrinsic;
 
   auto intrinsic = [builder](Intrinsic::Type type) {
@@ -331,7 +331,7 @@
 }
 
 /// @returns the unwrapped, user-declared constructed type of ty.
-const ast::NamedType* ConstructedTypeOf(sem::Type* ty) {
+const ast::NamedType* ConstructedTypeOf(const sem::Type* ty) {
   while (true) {
     if (auto* ptr = ty->As<sem::Pointer>()) {
       ty = ptr->type();
@@ -350,7 +350,7 @@
 }
 
 /// @returns the given type with all pointers and aliases removed.
-sem::Type* UnwrapPtrAndAlias(sem::Type* ty) {
+const sem::Type* UnwrapPtrAndAlias(const sem::Type* ty) {
   return ty->UnwrapPtrIfNeeded()->UnwrapAliasIfNeeded()->UnwrapPtrIfNeeded();
 }
 
@@ -358,7 +358,7 @@
 struct StorageBufferAccess {
   sem::Expression const* var = nullptr;  // Storage buffer variable
   std::unique_ptr<Offset> offset;        // The byte offset on var
-  sem::Type* type = nullptr;             // The type of the access
+  sem::Type const* type = nullptr;       // The type of the access
   operator bool() const { return var; }  // Returns true if valid
 };
 
@@ -422,8 +422,8 @@
   /// @return the name of the function that performs the load
   Symbol LoadFunc(CloneContext& ctx,
                   const ast::NamedType* insert_after,
-                  sem::Type* buf_ty,
-                  sem::Type* el_ty) {
+                  const sem::Type* buf_ty,
+                  const sem::Type* el_ty) {
     return utils::GetOrCreate(load_funcs, TypePair{buf_ty, el_ty}, [&] {
       auto* buf_ast_ty = CreateASTTypeFor(&ctx, buf_ty);
       ast::VariableList params = {
@@ -458,13 +458,11 @@
                                    member->Type()->UnwrapAll());
             values.emplace_back(ctx.dst->Call(load, "buffer", offset));
           }
-        } else if (auto* arr_ty = el_ty->As<sem::ArrayType>()) {
-          auto& sem = ctx.src->Sem();
-          auto* arr = sem.Get(arr_ty);
-          for (uint32_t i = 0; i < arr_ty->size(); i++) {
+        } else if (auto* arr = el_ty->As<sem::Array>()) {
+          for (uint32_t i = 0; i < arr->Count(); i++) {
             auto* offset = ctx.dst->Add("offset", arr->Stride() * i);
             Symbol load = LoadFunc(ctx, insert_after, buf_ty,
-                                   arr_ty->type()->UnwrapAll());
+                                   arr->ElemType()->UnwrapAll());
             values.emplace_back(ctx.dst->Call(load, "buffer", offset));
           }
         }
@@ -491,8 +489,8 @@
   /// @return the name of the function that performs the store
   Symbol StoreFunc(CloneContext& ctx,
                    const ast::NamedType* insert_after,
-                   sem::Type* buf_ty,
-                   sem::Type* el_ty) {
+                   const sem::Type* buf_ty,
+                   const sem::Type* el_ty) {
     return utils::GetOrCreate(store_funcs, TypePair{buf_ty, el_ty}, [&] {
       auto* buf_ast_ty = CreateASTTypeFor(&ctx, buf_ty);
       auto* el_ast_ty = CreateASTTypeFor(&ctx, el_ty);
@@ -533,14 +531,12 @@
             auto* call = ctx.dst->Call(store, "buffer", offset, access);
             body.emplace_back(ctx.dst->create<ast::CallStatement>(call));
           }
-        } else if (auto* arr_ty = el_ty->As<sem::ArrayType>()) {
-          auto& sem = ctx.src->Sem();
-          auto* arr = sem.Get(arr_ty);
-          for (uint32_t i = 0; i < arr_ty->size(); i++) {
+        } else if (auto* arr = el_ty->As<sem::Array>()) {
+          for (uint32_t i = 0; i < arr->Count(); i++) {
             auto* offset = ctx.dst->Add("offset", arr->Stride() * i);
             auto* access = ctx.dst->IndexAccessor("value", ctx.dst->Expr(i));
             Symbol store = StoreFunc(ctx, insert_after, buf_ty,
-                                     arr_ty->type()->UnwrapAll());
+                                     arr->ElemType()->UnwrapAll());
             auto* call = ctx.dst->Call(store, "buffer", offset, access);
             body.emplace_back(ctx.dst->create<ast::CallStatement>(call));
           }
@@ -689,14 +685,13 @@
     if (auto* accessor = node->As<ast::ArrayAccessorExpression>()) {
       if (auto access = state.TakeAccess(accessor->array())) {
         // X[Y]
-        if (auto* arr_ty = access.type->As<sem::ArrayType>()) {
-          auto stride = sem.Get(arr_ty)->Stride();
-          auto offset = Mul(stride, accessor->idx_expr());
+        if (auto* arr = access.type->As<sem::Array>()) {
+          auto offset = Mul(arr->Stride(), accessor->idx_expr());
           state.AddAccess(accessor,
                           {
                               access.var,
                               Add(std::move(access.offset), std::move(offset)),
-                              arr_ty->type()->UnwrapAll(),
+                              arr->ElemType()->UnwrapAll(),
                           });
           continue;
         }
diff --git a/src/transform/hlsl.cc b/src/transform/hlsl.cc
index d3d1656..15df034 100644
--- a/src/transform/hlsl.cc
+++ b/src/transform/hlsl.cc
@@ -96,7 +96,7 @@
       }
 
       auto* src_ty = src_sem_expr->Type();
-      if (src_ty->IsAnyOf<sem::ArrayType, sem::Struct>()) {
+      if (src_ty->IsAnyOf<sem::Array, sem::Struct>()) {
         // Create a new symbol for the constant
         auto dst_symbol = ctx.dst->Sym();
         // Clone the type
diff --git a/src/transform/spirv.cc b/src/transform/spirv.cc
index 0a8d45d..219524e 100644
--- a/src/transform/spirv.cc
+++ b/src/transform/spirv.cc
@@ -230,7 +230,7 @@
         // Use the same name as the old variable.
         auto var_name = ctx.Clone(var->symbol());
         // Use `array<u32, 1>` for the new variable.
-        auto type = ctx.dst->ty.array(ctx.dst->ty.u32(), 1u);
+        auto* type = ctx.dst->ty.array(ctx.dst->ty.u32(), 1u);
         // Create the new variable.
         auto* var_arr = ctx.dst->Var(var->source(), var_name, type,
                                      var->declared_storage_class(), nullptr,
diff --git a/src/transform/transform.cc b/src/transform/transform.cc
index d53cd7a..804566d 100644
--- a/src/transform/transform.cc
+++ b/src/transform/transform.cc
@@ -95,10 +95,13 @@
     auto* el = CreateASTTypeFor(ctx, v->type());
     return ctx->dst->create<ast::Vector>(el, v->size());
   }
-  if (auto* a = ty->As<sem::ArrayType>()) {
-    auto* el = CreateASTTypeFor(ctx, a->type());
-    auto decos = ctx->Clone(a->decorations());
-    return ctx->dst->create<ast::Array>(el, a->size(), std::move(decos));
+  if (auto* a = ty->As<sem::Array>()) {
+    auto* el = CreateASTTypeFor(ctx, a->ElemType());
+    ast::DecorationList decos;
+    if (!a->IsStrideImplicit()) {
+      decos.emplace_back(ctx->dst->create<ast::StrideDecoration>(a->Stride()));
+    }
+    return ctx->dst->create<ast::Array>(el, a->Count(), std::move(decos));
   }
   if (auto* ac = ty->As<sem::AccessControl>()) {
     auto* el = CreateASTTypeFor(ctx, ac->type());
diff --git a/src/transform/transform_test.cc b/src/transform/transform_test.cc
index e36cacd..5d30d0f 100644
--- a/src/transform/transform_test.cc
+++ b/src/transform/transform_test.cc
@@ -76,16 +76,23 @@
   ASSERT_EQ(vec->As<ast::Vector>()->size(), 2u);
 }
 
-TEST_F(CreateASTTypeForTest, Array) {
+TEST_F(CreateASTTypeForTest, ArrayImplicitStride) {
   auto* arr = create([](ProgramBuilder& b) {
-    return b.create<sem::ArrayType>(b.create<sem::F32>(), 4,
-                                    ast::DecorationList{
-                                        b.create<ast::StrideDecoration>(32u),
-                                    });
+    return b.create<sem::Array>(b.create<sem::F32>(), 2, 4, 4, 32u, true);
   });
   ASSERT_TRUE(arr->Is<ast::Array>());
   ASSERT_TRUE(arr->As<ast::Array>()->type()->Is<ast::F32>());
-  ASSERT_EQ(arr->As<ast::Array>()->size(), 4u);
+  ASSERT_EQ(arr->As<ast::Array>()->size(), 2u);
+  ASSERT_EQ(arr->As<ast::Array>()->decorations().size(), 0u);
+}
+
+TEST_F(CreateASTTypeForTest, ArrayNonImplicitStride) {
+  auto* arr = create([](ProgramBuilder& b) {
+    return b.create<sem::Array>(b.create<sem::F32>(), 2, 4, 4, 32u, false);
+  });
+  ASSERT_TRUE(arr->Is<ast::Array>());
+  ASSERT_TRUE(arr->As<ast::Array>()->type()->Is<ast::F32>());
+  ASSERT_EQ(arr->As<ast::Array>()->size(), 2u);
   ASSERT_EQ(arr->As<ast::Array>()->decorations().size(), 1u);
   ASSERT_TRUE(
       arr->As<ast::Array>()->decorations()[0]->Is<ast::StrideDecoration>());
@@ -95,7 +102,6 @@
                 ->stride(),
             32u);
 }
-
 TEST_F(CreateASTTypeForTest, AccessControl) {
   auto* ac = create([](ProgramBuilder& b) {
     auto* decl = b.Structure("S", {}, {});
diff --git a/src/typepair.h b/src/typepair.h
index d258dea..2e6d679 100644
--- a/src/typepair.h
+++ b/src/typepair.h
@@ -57,7 +57,7 @@
 namespace sem {
 class AccessControl;
 class Alias;
-class ArrayType;
+class Array;
 class Bool;
 class DepthTexture;
 class ExternalTexture;
@@ -241,7 +241,7 @@
 
 using AccessControl = TypePair<ast::AccessControl, sem::AccessControl>;
 using Alias = TypePair<ast::Alias, sem::Alias>;
-using Array = TypePair<ast::Array, sem::ArrayType>;
+using Array = TypePair<ast::Array, sem::Array>;
 using Bool = TypePair<ast::Bool, sem::Bool>;
 using DepthTexture = TypePair<ast::DepthTexture, sem::DepthTexture>;
 using ExternalTexture = TypePair<ast::ExternalTexture, sem::ExternalTexture>;
diff --git a/src/writer/append_vector.cc b/src/writer/append_vector.cc
index 7e76346..f080887 100644
--- a/src/writer/append_vector.cc
+++ b/src/writer/append_vector.cc
@@ -39,7 +39,7 @@
                                              ast::Expression* vector,
                                              ast::Expression* scalar) {
   uint32_t packed_size;
-  sem::Type* packed_el_sem_ty;
+  const sem::Type* packed_el_sem_ty;
   auto* vector_sem = b->Sem().Get(vector);
   auto* vector_ty = vector_sem->Type()->UnwrapPtrIfNeeded();
   if (auto* vec = vector_ty->As<sem::Vector>()) {
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index ebd29de..1fb4c42 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -1325,7 +1325,7 @@
   }
 
   bool brackets =
-      type->UnwrapAliasIfNeeded()->IsAnyOf<sem::ArrayType, sem::Struct>();
+      type->UnwrapAliasIfNeeded()->IsAnyOf<sem::Array, sem::Struct>();
 
   if (brackets) {
     out << "{";
@@ -1651,7 +1651,7 @@
       return false;
     }
     // Array name is output as part of the type
-    if (!type->Is<sem::ArrayType>()) {
+    if (!type->Is<sem::Array>()) {
       out << " " << builder_.Symbols().NameFor(v->Declaration()->symbol());
     }
   }
@@ -1918,7 +1918,7 @@
       if (!EmitType(out, var->Type(), var->StorageClass(), name)) {
         return false;
       }
-      if (!var->Type()->UnwrapAliasIfNeeded()->Is<sem::ArrayType>()) {
+      if (!var->Type()->UnwrapAliasIfNeeded()->Is<sem::Array>()) {
         out << " " << name;
       }
 
@@ -2406,18 +2406,18 @@
 
   if (auto* alias = type->As<sem::Alias>()) {
     out << builder_.Symbols().NameFor(alias->symbol());
-  } else if (auto* ary = type->As<sem::ArrayType>()) {
+  } else if (auto* ary = type->As<sem::Array>()) {
     const sem::Type* base_type = ary;
     std::vector<uint32_t> sizes;
-    while (auto* arr = base_type->As<sem::ArrayType>()) {
-      if (arr->IsRuntimeArray()) {
+    while (auto* arr = base_type->As<sem::Array>()) {
+      if (arr->IsRuntimeSized()) {
         TINT_ICE(diagnostics_)
             << "Runtime arrays may only exist in storage buffers, which should "
                "have been transformed into a ByteAddressBuffer";
         return false;
       }
-      sizes.push_back(arr->size());
-      base_type = arr->type();
+      sizes.push_back(arr->Count());
+      base_type = arr->ElemType();
     }
     if (!EmitType(out, base_type, storage_class, "")) {
       return false;
@@ -2571,7 +2571,7 @@
       return false;
     }
     // Array member name will be output with the type
-    if (!mem->Type()->Is<sem::ArrayType>()) {
+    if (!mem->Type()->Is<sem::Array>()) {
       out << " " << mem_name;
     }
 
@@ -2669,7 +2669,7 @@
                 builder_.Symbols().NameFor(var->symbol()))) {
     return false;
   }
-  if (!type->Is<sem::ArrayType>()) {
+  if (!type->Is<sem::Array>()) {
     out << " " << builder_.Symbols().NameFor(var->symbol());
   }
   out << constructor_out.str() << ";" << std::endl;
@@ -2731,7 +2731,7 @@
                   builder_.Symbols().NameFor(var->symbol()))) {
       return false;
     }
-    if (!type->Is<sem::ArrayType>()) {
+    if (!type->Is<sem::Array>()) {
       out << " " << builder_.Symbols().NameFor(var->symbol());
     }
 
diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc
index e4cf34e..9015afe 100644
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ b/src/writer/hlsl/generator_impl_type_test.cc
@@ -43,21 +43,25 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Array) {
-  auto arr = ty.array<bool, 4>();
+  auto* arr = ty.array<bool, 4>();
+  Global("G", arr, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, arr, ast::StorageClass::kNone, "ary"))
+  ASSERT_TRUE(
+      gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone, "ary"))
       << gen.error();
   EXPECT_EQ(result(), "bool ary[4]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_ArrayOfArray) {
-  auto arr = ty.array(ty.array<bool, 4>(), 5);
+  auto* arr = ty.array(ty.array<bool, 4>(), 5);
+  Global("G", arr, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, arr, ast::StorageClass::kNone, "ary"))
+  ASSERT_TRUE(
+      gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone, "ary"))
       << gen.error();
   EXPECT_EQ(result(), "bool ary[5][4]");
 }
@@ -65,41 +69,49 @@
 // TODO(dsinclair): Is this possible? What order should it output in?
 TEST_F(HlslGeneratorImplTest_Type,
        DISABLED_EmitType_ArrayOfArrayOfRuntimeArray) {
-  auto arr = ty.array(ty.array(ty.array<bool, 4>(), 5), 0);
+  auto* arr = ty.array(ty.array(ty.array<bool, 4>(), 5), 0);
+  Global("G", arr, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, arr, ast::StorageClass::kNone, "ary"))
+  ASSERT_TRUE(
+      gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone, "ary"))
       << gen.error();
   EXPECT_EQ(result(), "bool ary[5][4][1]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_ArrayOfArrayOfArray) {
-  auto arr = ty.array(ty.array(ty.array<bool, 4>(), 5), 6);
+  auto* arr = ty.array(ty.array(ty.array<bool, 4>(), 5), 6);
+  Global("G", arr, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, arr, ast::StorageClass::kNone, "ary"))
+  ASSERT_TRUE(
+      gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone, "ary"))
       << gen.error();
   EXPECT_EQ(result(), "bool ary[6][5][4]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Array_WithoutName) {
-  auto arr = ty.array<bool, 4>();
+  auto* arr = ty.array<bool, 4>();
+  Global("G", arr, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, arr, ast::StorageClass::kNone, ""))
+  ASSERT_TRUE(
+      gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone, ""))
       << gen.error();
   EXPECT_EQ(result(), "bool[4]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, DISABLED_EmitType_RuntimeArray) {
-  auto arr = ty.array<bool>();
+  auto* arr = ty.array<bool>();
+  Global("G", arr, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, arr, ast::StorageClass::kNone, "ary"))
+  ASSERT_TRUE(
+      gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone, "ary"))
       << gen.error();
   EXPECT_EQ(result(), "bool ary[]");
 }
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index a2d4513..15d3946 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -19,6 +19,7 @@
 #include <utility>
 #include <vector>
 
+#include "src/ast/alias.h"
 #include "src/ast/bool_literal.h"
 #include "src/ast/call_statement.h"
 #include "src/ast/fallthrough_statement.h"
@@ -32,7 +33,6 @@
 #include "src/sem/access_control_type.h"
 #include "src/sem/alias_type.h"
 #include "src/sem/array.h"
-#include "src/sem/array_type.h"
 #include "src/sem/bool_type.h"
 #include "src/sem/call.h"
 #include "src/sem/depth_texture_type.h"
@@ -88,8 +88,10 @@
   }
 
   for (auto* const ty : program_->AST().ConstructedTypes()) {
-    if (!EmitConstructedType(TypeOf(ty))) {
-      return false;
+    if (!ty->Is<ast::Alias>()) {
+      if (!EmitConstructedType(TypeOf(ty))) {
+        return false;
+      }
     }
   }
   if (!program_->AST().ConstructedTypes().empty()) {
@@ -889,7 +891,7 @@
 bool GeneratorImpl::EmitTypeConstructor(ast::TypeConstructorExpression* expr) {
   auto* type = TypeOf(expr);
 
-  if (type->IsAnyOf<sem::ArrayType, sem::Struct>()) {
+  if (type->IsAnyOf<sem::Array, sem::Struct>()) {
     out_ << "{";
   } else {
     if (!EmitType(type, "")) {
@@ -918,7 +920,7 @@
     }
   }
 
-  if (type->IsAnyOf<sem::ArrayType, sem::Struct>()) {
+  if (type->IsAnyOf<sem::Array, sem::Struct>()) {
     out_ << "}";
   } else {
     out_ << ")";
@@ -942,9 +944,9 @@
     return EmitZeroValue(vec->type());
   } else if (auto* mat = type->As<sem::Matrix>()) {
     return EmitZeroValue(mat->type());
-  } else if (auto* arr = type->As<sem::ArrayType>()) {
+  } else if (auto* arr = type->As<sem::Array>()) {
     out_ << "{";
-    if (!EmitZeroValue(arr->type())) {
+    if (!EmitZeroValue(arr->ElemType())) {
       return false;
     }
     out_ << "}";
@@ -1331,7 +1333,7 @@
       return false;
     }
     // Array name is output as part of the type
-    if (!type->Is<sem::ArrayType>()) {
+    if (!type->Is<sem::Array>()) {
       out_ << " " << program_->Symbols().NameFor(v->symbol());
     }
   }
@@ -1918,16 +1920,16 @@
 
   if (auto* alias = type->As<sem::Alias>()) {
     out_ << program_->Symbols().NameFor(alias->symbol());
-  } else if (auto* ary = type->As<sem::ArrayType>()) {
-    sem::Type* base_type = ary;
+  } else if (auto* ary = type->As<sem::Array>()) {
+    const sem::Type* base_type = ary;
     std::vector<uint32_t> sizes;
-    while (auto* arr = base_type->As<sem::ArrayType>()) {
-      if (arr->IsRuntimeArray()) {
+    while (auto* arr = base_type->As<sem::Array>()) {
+      if (arr->IsRuntimeSized()) {
         sizes.push_back(1);
       } else {
-        sizes.push_back(arr->size());
+        sizes.push_back(arr->Count());
       }
-      base_type = arr->type();
+      base_type = arr->ElemType();
     }
     if (!EmitType(base_type, "")) {
       return false;
@@ -2122,7 +2124,7 @@
     auto* ty = mem->Type()->UnwrapAliasIfNeeded();
 
     // Array member name will be output with the type
-    if (!ty->Is<sem::ArrayType>()) {
+    if (!ty->Is<sem::Array>()) {
       out_ << " " << name;
     }
 
@@ -2223,7 +2225,7 @@
   if (!EmitType(var->Type(), program_->Symbols().NameFor(decl->symbol()))) {
     return false;
   }
-  if (!var->Type()->Is<sem::ArrayType>()) {
+  if (!var->Type()->Is<sem::Array>()) {
     out_ << " " << program_->Symbols().NameFor(decl->symbol());
   }
 
@@ -2266,7 +2268,7 @@
   if (!EmitType(type, program_->Symbols().NameFor(var->symbol()))) {
     return false;
   }
-  if (!type->Is<sem::ArrayType>()) {
+  if (!type->Is<sem::Array>()) {
     out_ << " " << program_->Symbols().NameFor(var->symbol());
   }
 
@@ -2284,7 +2286,7 @@
 }
 
 GeneratorImpl::SizeAndAlign GeneratorImpl::MslPackedTypeSizeAndAlign(
-    sem::Type* ty) {
+    const sem::Type* ty) {
   ty = ty->UnwrapAliasIfNeeded();
 
   if (ty->IsAnyOf<sem::U32, sem::I32, sem::F32>()) {
@@ -2327,14 +2329,9 @@
     }
   }
 
-  if (auto* arr = ty->As<sem::ArrayType>()) {
-    auto* sem = program_->Sem().Get(arr);
-    if (!sem) {
-      TINT_ICE(diagnostics_) << "Array missing semantic info";
-      return {};
-    }
-    auto el_size_align = MslPackedTypeSizeAndAlign(arr->type());
-    if (sem->Stride() != el_size_align.size) {
+  if (auto* arr = ty->As<sem::Array>()) {
+    auto el_size_align = MslPackedTypeSizeAndAlign(arr->ElemType());
+    if (arr->Stride() != el_size_align.size) {
       // TODO(crbug.com/tint/649): transform::Msl needs to replace these arrays
       // with a new array type that has the element type padded to the required
       // stride.
@@ -2342,7 +2339,7 @@
           << "Arrays with custom strides not yet implemented";
       return {};
     }
-    auto num_els = std::max<uint32_t>(arr->size(), 1);
+    auto num_els = std::max<uint32_t>(arr->Count(), 1);
     return SizeAndAlign{el_size_align.size * num_els, el_size_align.align};
   }
 
diff --git a/src/writer/msl/generator_impl.h b/src/writer/msl/generator_impl.h
index 22e5d75..b4fa982 100644
--- a/src/writer/msl/generator_impl.h
+++ b/src/writer/msl/generator_impl.h
@@ -287,7 +287,7 @@
 
   /// @returns the MSL packed type size and alignment in bytes for the given
   /// type.
-  SizeAndAlign MslPackedTypeSizeAndAlign(sem::Type* ty);
+  SizeAndAlign MslPackedTypeSizeAndAlign(const sem::Type* ty);
 
   ScopeStack<const sem::Variable*> global_variables_;
   Symbol current_ep_sym_;
diff --git a/src/writer/msl/generator_impl_type_test.cc b/src/writer/msl/generator_impl_type_test.cc
index 7e28db8..fd35026 100644
--- a/src/writer/msl/generator_impl_type_test.cc
+++ b/src/writer/msl/generator_impl_type_test.cc
@@ -67,62 +67,68 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Array) {
-  auto arr = ty.array<bool, 4>();
+  auto* arr = ty.array<bool, 4>();
+  Global("G", arr, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(arr, "ary")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(program->TypeOf(arr), "ary")) << gen.error();
   EXPECT_EQ(gen.result(), "bool ary[4]");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_ArrayOfArray) {
-  auto a = ty.array<bool, 4>();
-  auto b = ty.array(a, 5);
+  auto* a = ty.array<bool, 4>();
+  auto* b = ty.array(a, 5);
+  Global("G", b, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(b, "ary")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(program->TypeOf(b), "ary")) << gen.error();
   EXPECT_EQ(gen.result(), "bool ary[5][4]");
 }
 
 // TODO(dsinclair): Is this possible? What order should it output in?
 TEST_F(MslGeneratorImplTest, DISABLED_EmitType_ArrayOfArrayOfRuntimeArray) {
-  auto a = ty.array<bool, 4>();
-  auto b = ty.array(a, 5);
-  auto c = ty.array(b, 0);
+  auto* a = ty.array<bool, 4>();
+  auto* b = ty.array(a, 5);
+  auto* c = ty.array(b, 0);
+  Global("G", c, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(c, "ary")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(program->TypeOf(c), "ary")) << gen.error();
   EXPECT_EQ(gen.result(), "bool ary[5][4][1]");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_ArrayOfArrayOfArray) {
-  auto a = ty.array<bool, 4>();
-  auto b = ty.array(a, 5);
-  auto c = ty.array(b, 6);
+  auto* a = ty.array<bool, 4>();
+  auto* b = ty.array(a, 5);
+  auto* c = ty.array(b, 6);
+  Global("G", c, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(c, "ary")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(program->TypeOf(c), "ary")) << gen.error();
   EXPECT_EQ(gen.result(), "bool ary[6][5][4]");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Array_WithoutName) {
-  auto arr = ty.array<bool, 4>();
+  auto* arr = ty.array<bool, 4>();
+  Global("G", arr, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(arr, "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(program->TypeOf(arr), "")) << gen.error();
   EXPECT_EQ(gen.result(), "bool[4]");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_RuntimeArray) {
-  auto arr = ty.array<bool, 1>();
+  auto* arr = ty.array<bool, 1>();
+  Global("G", arr, ast::StorageClass::kPrivate);
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(arr, "ary")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(program->TypeOf(arr), "ary")) << gen.error();
   EXPECT_EQ(gen.result(), "bool ary[1]");
 }
 
@@ -411,13 +417,13 @@
                          });
 
   // array_x: size(28), align(4)
-  auto array_x = ty.array<f32, 7>();
+  auto* array_x = ty.array<f32, 7>();
 
   // array_y: size(4096), align(512)
-  auto array_y = ty.array(inner, 4);
+  auto* array_y = ty.array(inner, 4);
 
   // array_z: size(4), align(4)
-  auto array_z = ty.array<f32>();
+  auto* array_z = ty.array<f32>();
 
   auto* s =
       Structure("S",
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 27fed86..dee0885 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -111,9 +111,9 @@
 /// one or more levels of an arrays inside of `type`.
 /// @param type the given type, which must not be null
 /// @returns the nested matrix type, or nullptr if none
-sem::Matrix* GetNestedMatrixType(sem::Type* type) {
-  while (auto* arr = type->As<sem::ArrayType>()) {
-    type = arr->type();
+const sem::Matrix* GetNestedMatrixType(const sem::Type* type) {
+  while (auto* arr = type->As<sem::Array>()) {
+    type = arr->ElemType();
   }
   return type->As<sem::Matrix>();
 }
@@ -251,7 +251,7 @@
 }
 
 /// @return the vector element type if ty is a vector, otherwise return ty.
-sem::Type* ElementTypeOf(sem::Type* ty) {
+const sem::Type* ElementTypeOf(const sem::Type* ty) {
   if (auto* v = ty->As<sem::Vector>()) {
     return v->type();
   }
@@ -1067,9 +1067,9 @@
   // how the Resolver currently determines the type of these expression. This
   // should be fixed when proper support for ptr/ref types is implemented.
   if (auto* array = accessors[0]->As<ast::ArrayAccessorExpression>()) {
-    auto* ary_res_type = TypeOf(array->array())->As<sem::ArrayType>();
+    auto* ary_res_type = TypeOf(array->array())->As<sem::Array>();
     if (ary_res_type &&
-        (!ary_res_type->type()->is_scalar() ||
+        (!ary_res_type->ElemType()->is_scalar() ||
          !array->idx_expr()->Is<ast::ScalarConstructorExpression>())) {
       // Wrap the source type in a pointer to function storage.
       auto ptr =
@@ -1167,7 +1167,7 @@
   return 0;
 }
 
-uint32_t Builder::GenerateLoadIfNeeded(sem::Type* type, uint32_t id) {
+uint32_t Builder::GenerateLoadIfNeeded(const sem::Type* type, uint32_t id) {
   if (!type->Is<sem::Pointer>()) {
     return id;
   }
@@ -1287,13 +1287,13 @@
       continue;
     }
 
-    sem::Type* subtype = result_type->UnwrapAll();
+    const sem::Type* subtype = result_type->UnwrapAll();
     if (auto* vec = subtype->As<sem::Vector>()) {
       subtype = vec->type()->UnwrapAll();
     } else if (auto* mat = subtype->As<sem::Matrix>()) {
       subtype = mat->type()->UnwrapAll();
-    } else if (auto* arr = subtype->As<sem::ArrayType>()) {
-      subtype = arr->type()->UnwrapAll();
+    } else if (auto* arr = subtype->As<sem::Array>()) {
+      subtype = arr->ElemType()->UnwrapAll();
     } else if (auto* str = subtype->As<sem::Struct>()) {
       subtype = str->Members()[i]->Type()->UnwrapAll();
     }
@@ -1373,7 +1373,7 @@
     // If the result is not a vector then we should have validated that the
     // value type is a correctly sized vector so we can just use it directly.
     if (result_type == value_type || result_type->Is<sem::Matrix>() ||
-        result_type->Is<sem::ArrayType>() || result_type->Is<sem::Struct>()) {
+        result_type->Is<sem::Array>() || result_type->Is<sem::Struct>()) {
       out << "_" << id;
 
       ops.push_back(Operand::Int(id));
@@ -2574,7 +2574,7 @@
                                 });
 }
 
-uint32_t Builder::GenerateSampledImage(sem::Type* texture_type,
+uint32_t Builder::GenerateSampledImage(const sem::Type* texture_type,
                                        Operand texture_operand,
                                        Operand sampler_operand) {
   uint32_t sampled_image_type_id = 0;
@@ -2996,7 +2996,7 @@
                             result)) {
       return 0;
     }
-  } else if (auto* arr = type->As<sem::ArrayType>()) {
+  } else if (auto* arr = type->As<sem::Array>()) {
     if (!GenerateArrayType(arr, result)) {
       return 0;
     }
@@ -3126,18 +3126,17 @@
   return true;
 }
 
-bool Builder::GenerateArrayType(const sem::ArrayType* ary,
-                                const Operand& result) {
-  auto elem_type = GenerateTypeIfNeeded(ary->type());
+bool Builder::GenerateArrayType(const sem::Array* ary, const Operand& result) {
+  auto elem_type = GenerateTypeIfNeeded(ary->ElemType());
   if (elem_type == 0) {
     return false;
   }
 
   auto result_id = result.to_i();
-  if (ary->IsRuntimeArray()) {
+  if (ary->IsRuntimeSized()) {
     push_type(spv::Op::OpTypeRuntimeArray, {result, Operand::Int(elem_type)});
   } else {
-    auto len_id = GenerateConstantIfNeeded(ScalarConstant::U32(ary->size()));
+    auto len_id = GenerateConstantIfNeeded(ScalarConstant::U32(ary->Count()));
     if (len_id == 0) {
       return false;
     }
@@ -3146,14 +3145,9 @@
               {result, Operand::Int(elem_type), Operand::Int(len_id)});
   }
 
-  auto* sem_arr = builder_.Sem().Get(ary);
-  if (!sem_arr) {
-    error_ = "array type missing semantic info";
-    return false;
-  }
   push_annot(spv::Op::OpDecorate,
              {Operand::Int(result_id), Operand::Int(SpvDecorationArrayStride),
-              Operand::Int(sem_arr->Stride())});
+              Operand::Int(ary->Stride())});
   return true;
 }
 
diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h
index 82d27a3..7163e27 100644
--- a/src/writer/spirv/builder.h
+++ b/src/writer/spirv/builder.h
@@ -65,7 +65,7 @@
     uint32_t source_id;
     /// The type of the current chain source. This type matches the deduced
     /// result_type of the current source defined above.
-    sem::Type* source_type;
+    const sem::Type* source_type;
     /// A list of access chain indices to emit. Note, we _only_ have access
     /// chain indices if the source is pointer.
     std::vector<uint32_t> access_chain_indices;
@@ -263,7 +263,7 @@
   /// @param type the type to generate for
   /// @param struct_id the struct id
   /// @param member_idx the member index
-  void GenerateMemberAccessControlIfNeeded(sem::Type* type,
+  void GenerateMemberAccessControlIfNeeded(const sem::Type* type,
                                            uint32_t struct_id,
                                            uint32_t member_idx);
   /// Generates a function variable
@@ -372,7 +372,7 @@
   /// @param texture_operand the texture operand
   /// @param sampler_operand the sampler operand
   /// @returns the expression ID
-  uint32_t GenerateSampledImage(sem::Type* texture_type,
+  uint32_t GenerateSampledImage(const sem::Type* texture_type,
                                 Operand texture_operand,
                                 Operand sampler_operand);
   /// Generates a cast or object copy for the expression result,
@@ -413,7 +413,7 @@
   /// @param type the type to load
   /// @param id the variable id to load
   /// @returns the ID of the loaded value or `id` if type is not a pointer
-  uint32_t GenerateLoadIfNeeded(sem::Type* type, uint32_t id);
+  uint32_t GenerateLoadIfNeeded(const sem::Type* type, uint32_t id);
   /// Generates an OpStore. Emits an error and returns false if we're
   /// currently outside a function.
   /// @param to the ID to store too
@@ -433,7 +433,7 @@
   /// @param ary the array to generate
   /// @param result the result operand
   /// @returns true if the array was successfully generated
-  bool GenerateArrayType(const sem::ArrayType* ary, const Operand& result);
+  bool GenerateArrayType(const sem::Array* ary, const Operand& result);
   /// Generates a matrix type declaration
   /// @param mat the matrix to generate
   /// @param result the result operand
@@ -488,7 +488,7 @@
 
   /// @returns the resolved type of the ast::Expression `expr`
   /// @param expr the expression
-  sem::Type* TypeOf(ast::Expression* expr) const {
+  const sem::Type* TypeOf(ast::Expression* expr) const {
     return builder_.TypeOf(expr);
   }
 
diff --git a/src/writer/spirv/builder_accessor_expression_test.cc b/src/writer/spirv/builder_accessor_expression_test.cc
index f1d4a89..00919e0 100644
--- a/src/writer/spirv/builder_accessor_expression_test.cc
+++ b/src/writer/spirv/builder_accessor_expression_test.cc
@@ -135,7 +135,7 @@
 }
 
 TEST_F(BuilderTest, ArrayAccessor_MultiLevel) {
-  auto ary4 = ty.array(ty.vec3<f32>(), 4);
+  auto* ary4 = ty.array(ty.vec3<f32>(), 4);
 
   // ary = array<vec3<f32>, 4>
   // ary[3][2];
@@ -173,7 +173,7 @@
 }
 
 TEST_F(BuilderTest, Accessor_ArrayWithSwizzle) {
-  auto ary4 = ty.array(ty.vec3<f32>(), 4);
+  auto* ary4 = ty.array(ty.vec3<f32>(), 4);
 
   // var a : array<vec3<f32>, 4>;
   // a[2].xy;
@@ -696,10 +696,10 @@
   auto* c_type = Structure("C", {Member("baz", ty.vec3<f32>())});
 
   auto* b_type = Structure("B", {Member("bar", c_type)});
-  auto b_ary_type = ty.array(b_type, 3);
+  auto* b_ary_type = ty.array(b_type, 3);
   auto* a_type = Structure("A", {Member("foo", b_ary_type)});
 
-  auto a_ary_type = ty.array(a_type, 2);
+  auto* a_ary_type = ty.array(a_type, 2);
   auto* var = Global("index", a_ary_type, ast::StorageClass::kFunction);
   auto* expr = MemberAccessor(
       MemberAccessor(
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index 3fdf1ad..3de7d99 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -58,7 +58,7 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateRuntimeArray) {
-  auto ary = ty.array(ty.i32(), 0);
+  auto* ary = ty.array(ty.i32(), 0);
   auto* str = Structure("S", {Member("x", ary)},
                         {create<ast::StructBlockDecoration>()});
   auto ac = ty.access(ast::AccessControl::kReadOnly, str);
@@ -66,7 +66,7 @@
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(ary);
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(ary));
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(1u, id);
 
@@ -76,7 +76,7 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedRuntimeArray) {
-  auto ary = ty.array(ty.i32(), 0);
+  auto* ary = ty.array(ty.i32(), 0);
   auto* str = Structure("S", {Member("x", ary)},
                         {create<ast::StructBlockDecoration>()});
   auto ac = ty.access(ast::AccessControl::kReadOnly, str);
@@ -84,8 +84,8 @@
 
   spirv::Builder& b = Build();
 
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ary), 1u);
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ary), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
@@ -94,12 +94,12 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateArray) {
-  auto ary = ty.array(ty.i32(), 4);
+  auto* ary = ty.array(ty.i32(), 4);
   Global("a", ary, ast::StorageClass::kInput);
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(ary);
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(ary));
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(1u, id);
 
@@ -111,12 +111,12 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateArray_WithStride) {
-  auto ary = ty.array(ty.i32(), 4, 16u);
+  auto* ary = ty.array(ty.i32(), 4, 16u);
   Global("a", ary, ast::StorageClass::kInput);
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(ary);
+  auto id = b.GenerateTypeIfNeeded(program->TypeOf(ary));
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(1u, id);
 
@@ -131,13 +131,13 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedArray) {
-  auto ary = ty.array(ty.i32(), 4);
+  auto* ary = ty.array(ty.i32(), 4);
   Global("a", ary, ast::StorageClass::kInput);
 
   spirv::Builder& b = Build();
 
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ary), 1u);
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ary), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ary)), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
@@ -445,9 +445,9 @@
   // We have to infer layout for matrix when it also has an offset.
   // The decoration goes on the struct member, even if the matrix is buried
   // in levels of arrays.
-  auto arr_mat2x2 = ty.array(ty.mat2x2<f32>(), 1);      // Singly nested array
-  auto arr_arr_mat2x3 = ty.array(ty.mat2x3<f32>(), 1);  // Doubly nested array
-  auto rtarr_mat4x4 = ty.array(ty.mat4x4<f32>(), 0);    // Runtime array
+  auto* arr_mat2x2 = ty.array(ty.mat2x2<f32>(), 1);      // Singly nested array
+  auto* arr_arr_mat2x3 = ty.array(ty.mat2x3<f32>(), 1);  // Doubly nested array
+  auto* rtarr_mat4x4 = ty.array(ty.mat4x4<f32>(), 0);    // Runtime array
 
   auto* s =
       Structure("S",
diff --git a/src/writer/wgsl/generator_impl_type_test.cc b/src/writer/wgsl/generator_impl_type_test.cc
index 1afdba6..c311247 100644
--- a/src/writer/wgsl/generator_impl_type_test.cc
+++ b/src/writer/wgsl/generator_impl_type_test.cc
@@ -37,7 +37,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Array) {
-  auto arr = ty.array<bool, 4>();
+  auto* arr = ty.array<bool, 4>();
   AST().AddConstructedType(ty.alias("make_type_reachable", arr));
 
   GeneratorImpl& gen = Build();
@@ -73,7 +73,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Array_Decoration) {
-  auto a = ty.array(ty.bool_(), 4, 16u);
+  auto* a = ty.array(ty.bool_(), 4, 16u);
   AST().AddConstructedType(ty.alias("make_type_reachable", a));
 
   GeneratorImpl& gen = Build();
@@ -83,7 +83,7 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_RuntimeArray) {
-  auto a = ty.array(ty.bool_(), 0);
+  auto* a = ty.array(ty.bool_(), 0);
   AST().AddConstructedType(ty.alias("make_type_reachable", a));
 
   GeneratorImpl& gen = Build();
diff --git a/test/BUILD.gn b/test/BUILD.gn
index a985eb9..740ce6b 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -270,7 +270,6 @@
     "../src/scope_stack_test.cc",
     "../src/sem/access_control_type_test.cc",
     "../src/sem/alias_type_test.cc",
-    "../src/sem/array_type_test.cc",
     "../src/sem/bool_type_test.cc",
     "../src/sem/depth_texture_type_test.cc",
     "../src/sem/external_texture_type_test.cc",
@@ -281,6 +280,7 @@
     "../src/sem/pointer_type_test.cc",
     "../src/sem/sampled_texture_type_test.cc",
     "../src/sem/sampler_type_test.cc",
+    "../src/sem/sem_array_test.cc",
     "../src/sem/sem_struct_test.cc",
     "../src/sem/storage_texture_type_test.cc",
     "../src/sem/texture_type_test.cc",
diff --git a/test/bug_tint_782.wgsl b/test/bug_tint_782.wgsl
new file mode 100644
index 0000000..dbfcb00
--- /dev/null
+++ b/test/bug_tint_782.wgsl
@@ -0,0 +1,8 @@
+type ArrayExplicitStride = [[stride(4)]] array<i32, 2>;
+type ArrayImplicitStride =               array<i32, 2>;
+
+fn foo() {
+  var explicit : ArrayExplicitStride;
+  var implict  : ArrayImplicitStride;
+  implict = explicit;
+}