Import Tint changes from Dawn

Changes:
  - 0ea0d4b16eaadac57d35da6e708dbc360e94289d tint/resolver: Test f16 types in materialize unittests by Zhaoming Jiang <zhaoming.jiang@intel.com>
  - 6835c8df3e016f4607659bfeaae840483fb1cb24 tint/reader: Parse arrays with no type / size by Ben Clayton <bclayton@google.com>
  - 86329e286b23a23d284d213e904869fbfc8a53c1 tint/cmd: Dump disassembly with --verbose by Ben Clayton <bclayton@google.com>
  - 92f2cb79b5dc13590353df83da24e023dbadb1f8 tools/gen: clang-format generated .cc, .h and .inl files by Ben Clayton <bclayton@google.com>
  - 4aa360db5d36720ae2139ea1e1c84df0a60862b5 tint/resolver: Fix null-deref. by Ben Clayton <bclayton@google.com>
  - db5d2ea4f287e8cdc22e719f2182245de1c8c375 tint/writer/wgsl: Emit inferred-type arrays by Ben Clayton <bclayton@google.com>
  - 4a6562071605a1a73c14e1b705a8e4f002d03c39 tint/resolver: Handle array() constructors by Ben Clayton <bclayton@google.com>
  - 70a43fa5b9ff0ef1f8d31e62f670e0fc86f8b33e tint: Fix doxygen error. by Antonio Maiorano <amaiorano@google.com>
  - 239a92d2d00113098cee074a0548a5461c97737d tint: transform::RemovePhonies: skip builtins with no sid... by Antonio Maiorano <amaiorano@google.com>
  - 6315a27778728578708d109e5f28c1e0be48aeb8 tint/resolver: Tidy up type_constructor_validation_test.cc by Ben Clayton <bclayton@google.com>
  - bf32bd4e961bf82a9246d0e74b863e66073279fe tint/ast: Tighten AST definition of runtime-sized array by Ben Clayton <bclayton@google.com>
  - 8c9cc860763f871604ffb77209a167e6f9fe4bea tint/resolver: Simplify / optimize Validator::Call() by Ben Clayton <bclayton@google.com>
  - 9c4d0c941038f14c400f99a4952ae0d84ed73817 tint/resolver: Split up the Array resolving logic by Ben Clayton <bclayton@google.com>
GitOrigin-RevId: 0ea0d4b16eaadac57d35da6e708dbc360e94289d
Change-Id: I02c1053b0f39500b16b4559611eea9ffb2551188
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/97800
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/ast/array.cc b/src/tint/ast/array.cc
index cd1fc26..988dfaf 100644
--- a/src/tint/ast/array.cc
+++ b/src/tint/ast/array.cc
@@ -56,11 +56,14 @@
             out << "@stride(" << stride->stride << ") ";
         }
     }
-    out << "array<" << type->FriendlyName(symbols);
-    if (!IsRuntimeArray()) {
-        out << ", " << SizeExprToString(count, symbols);
+    out << "array";
+    if (type) {
+        out << "<" << type->FriendlyName(symbols);
+        if (count) {
+            out << ", " << SizeExprToString(count, symbols);
+        }
+        out << ">";
     }
-    out << ">";
     return out.str();
 }
 
diff --git a/src/tint/ast/array.h b/src/tint/ast/array.h
index ccc31ab..6ad1cbc 100644
--- a/src/tint/ast/array.h
+++ b/src/tint/ast/array.h
@@ -35,9 +35,9 @@
     /// @param nid the unique node identifier
     /// @param src the source of this node
     /// @param subtype the type of the array elements
-    /// @param count the number of elements in the array. nullptr represents a
-    /// runtime-sized array.
+    /// @param count the number of elements in the array
     /// @param attributes the array attributes
+    /// @note a runtime-sized array is represented by a null count and a non-null type
     Array(ProgramID pid,
           NodeID nid,
           const Source& src,
@@ -50,7 +50,7 @@
 
     /// @returns true if this is a runtime array.
     /// i.e. the size is determined at runtime
-    bool IsRuntimeArray() const { return count == nullptr; }
+    bool IsRuntimeArray() const { return type != nullptr && count == nullptr; }
 
     /// @param symbols the program's symbol table
     /// @returns the name for this type that closely resembles how it would be
diff --git a/src/tint/ast/array_test.cc b/src/tint/ast/array_test.cc
index f396e0c..f8d469b 100644
--- a/src/tint/ast/array_test.cc
+++ b/src/tint/ast/array_test.cc
@@ -42,6 +42,14 @@
     EXPECT_TRUE(arr->IsRuntimeArray());
 }
 
+TEST_F(AstArrayTest, CreateInferredTypeArray) {
+    auto* arr = create<Array>(nullptr, nullptr, AttributeList{});
+    EXPECT_EQ(arr->type, nullptr);
+    EXPECT_EQ(arr->count, nullptr);
+    EXPECT_TRUE(arr->Is<Array>());
+    EXPECT_FALSE(arr->IsRuntimeArray());
+}
+
 TEST_F(AstArrayTest, FriendlyName_RuntimeSized) {
     auto* i32 = create<I32>();
     auto* arr = create<Array>(i32, nullptr, AttributeList{});
@@ -66,5 +74,15 @@
     EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(32) array<i32, 5>");
 }
 
+TEST_F(AstArrayTest, FriendlyName_InferredTypeAndCount) {
+    auto* arr = create<Array>(nullptr, nullptr, AttributeList{});
+    EXPECT_EQ(arr->FriendlyName(Symbols()), "array");
+}
+
+TEST_F(AstArrayTest, FriendlyName_InferredTypeAndCount_WithStrize) {
+    auto* arr = create<Array>(nullptr, nullptr, AttributeList{create<StrideAttribute>(32u)});
+    EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(32) array");
+}
+
 }  // namespace
 }  // namespace tint::ast
diff --git a/src/tint/ast/storage_class.h b/src/tint/ast/storage_class.h
index cb21115..4da3db0 100644
--- a/src/tint/ast/storage_class.h
+++ b/src/tint/ast/storage_class.h
@@ -37,8 +37,8 @@
     kUniform,
     kStorage,
     kHandle,  // Tint-internal enum entry - not parsed
-    kIn,  // Tint-internal enum entry - not parsed
-    kOut,  // Tint-internal enum entry - not parsed
+    kIn,      // Tint-internal enum entry - not parsed
+    kOut,     // Tint-internal enum entry - not parsed
 };
 
 /// @param out the std::ostream to write to
diff --git a/src/tint/ast/storage_class_bench.cc b/src/tint/ast/storage_class_bench.cc
index d1232df..d8e9ae9 100644
--- a/src/tint/ast/storage_class_bench.cc
+++ b/src/tint/ast/storage_class_bench.cc
@@ -31,41 +31,12 @@
 
 void StorageClassParser(::benchmark::State& state) {
     std::array kStrings{
-        "fccnctin",
-        "ucti3",
-        "functVon",
-        "function",
-        "1unction",
-        "unJtqqon",
-        "llun77tion",
-        "ppqqivtHH",
-        "prcv",
-        "bivaGe",
-        "private",
-        "priviive",
-        "8WWivate",
-        "pxxvate",
-        "wXkgrggup",
-        "worXVup",
-        "3orkgroup",
-        "workgroup",
-        "workgroEp",
-        "woTTPkroup",
-        "ddorkroxxp",
-        "u44iform",
-        "unSSfoVVm",
-        "RniR22m",
-        "uniform",
-        "uFfo9m",
-        "uniorm",
-        "VOORRHrm",
-        "straye",
-        "llntrrr77ge",
-        "stor4g00",
-        "storage",
-        "trooe",
-        "zzrage",
-        "siioppa1",
+        "fccnctin",   "ucti3",      "functVon",   "function", "1unction",  "unJtqqon",
+        "llun77tion", "ppqqivtHH",  "prcv",       "bivaGe",   "private",   "priviive",
+        "8WWivate",   "pxxvate",    "wXkgrggup",  "worXVup",  "3orkgroup", "workgroup",
+        "workgroEp",  "woTTPkroup", "ddorkroxxp", "u44iform", "unSSfoVVm", "RniR22m",
+        "uniform",    "uFfo9m",     "uniorm",     "VOORRHrm", "straye",    "llntrrr77ge",
+        "stor4g00",   "storage",    "trooe",      "zzrage",   "siioppa1",
     };
     for (auto _ : state) {
         for (auto& str : kStrings) {
diff --git a/src/tint/ast/storage_class_test.cc b/src/tint/ast/storage_class_test.cc
index 2085168..9688236 100644
--- a/src/tint/ast/storage_class_test.cc
+++ b/src/tint/ast/storage_class_test.cc
@@ -42,28 +42,19 @@
 }
 
 static constexpr Case kValidCases[] = {
-    {"function", StorageClass::kFunction},
-    {"private", StorageClass::kPrivate},
-    {"workgroup", StorageClass::kWorkgroup},
-    {"uniform", StorageClass::kUniform},
+    {"function", StorageClass::kFunction},   {"private", StorageClass::kPrivate},
+    {"workgroup", StorageClass::kWorkgroup}, {"uniform", StorageClass::kUniform},
     {"storage", StorageClass::kStorage},
 };
 
 static constexpr Case kInvalidCases[] = {
-    {"fccnctin", StorageClass::kInvalid},
-    {"ucti3", StorageClass::kInvalid},
-    {"functVon", StorageClass::kInvalid},
-    {"priv1te", StorageClass::kInvalid},
-    {"pqiJate", StorageClass::kInvalid},
-    {"privat7ll", StorageClass::kInvalid},
-    {"workroppqHH", StorageClass::kInvalid},
-    {"workru", StorageClass::kInvalid},
-    {"wbkgGoup", StorageClass::kInvalid},
-    {"unifiivm", StorageClass::kInvalid},
-    {"8WWiform", StorageClass::kInvalid},
-    {"uxxform", StorageClass::kInvalid},
-    {"sXraggg", StorageClass::kInvalid},
-    {"traXe", StorageClass::kInvalid},
+    {"fccnctin", StorageClass::kInvalid},    {"ucti3", StorageClass::kInvalid},
+    {"functVon", StorageClass::kInvalid},    {"priv1te", StorageClass::kInvalid},
+    {"pqiJate", StorageClass::kInvalid},     {"privat7ll", StorageClass::kInvalid},
+    {"workroppqHH", StorageClass::kInvalid}, {"workru", StorageClass::kInvalid},
+    {"wbkgGoup", StorageClass::kInvalid},    {"unifiivm", StorageClass::kInvalid},
+    {"8WWiform", StorageClass::kInvalid},    {"uxxform", StorageClass::kInvalid},
+    {"sXraggg", StorageClass::kInvalid},     {"traXe", StorageClass::kInvalid},
     {"stor3ge", StorageClass::kInvalid},
 };
 
diff --git a/src/tint/ast/texel_format_bench.cc b/src/tint/ast/texel_format_bench.cc
index 8dde6b2..a17906a 100644
--- a/src/tint/ast/texel_format_bench.cc
+++ b/src/tint/ast/texel_format_bench.cc
@@ -31,118 +31,29 @@
 
 void TexelFormatParser(::benchmark::State& state) {
     std::array kStrings{
-        "rgbaunccrm",
-        "rlbanr3",
-        "rVba8unorm",
-        "rgba8unorm",
-        "rgba1unorm",
-        "rgbJqqnorm",
-        "rgb7ll8unorm",
-        "rgqqappnoHHm",
-        "rv8scor",
-        "rgbbGsnrm",
-        "rgba8snorm",
-        "rgba8vniirm",
-        "rg8a8snoWWm",
-        "Mgbaxxnorm",
-        "rXa8uggnt",
-        "rgbXVut",
-        "3gba8uint",
-        "rgba8uint",
-        "rgba8uiEt",
-        "rgTTPauint",
-        "ddgbauixxt",
-        "44gba8sint",
-        "VVgbaSSsint",
-        "rba8si2Rt",
-        "rgba8sint",
-        "r9bFsint",
-        "rgba8int",
-        "rgVROOsHnt",
-        "ryba1uint",
-        "r77ba1nnullrrt",
-        "rgb4006uint",
-        "rgba16uint",
-        "rb1uioot",
-        "rga1uzznt",
-        "r11b1uppiit",
-        "XXgba16sint",
-        "IIgb9916nni55t",
-        "rYbaSSrrsiHHat",
-        "rgba16sint",
-        "rbkk6Hit",
-        "jgba1sgRR",
-        "rgbab6si",
-        "rgba16fljat",
-        "rgba6float",
-        "rbq6float",
-        "rgba16float",
-        "rgba1NNloat",
-        "rgbvv6flot",
-        "rgbaQQ6foat",
-        "r3ffir",
-        "r32uijt",
-        "rNNwuin8",
-        "r32uint",
-        "r32int",
-        "rrr2uint",
-        "G32uint",
-        "r32sinFF",
-        "32st",
-        "r3rrint",
-        "r32sint",
-        "2sint",
-        "D3siJJt",
-        "r38n",
-        "r211lk",
-        "r32floa",
-        "r3flJat",
-        "r32float",
-        "r32fcoat",
-        "r32floOt",
-        "r32floKK_vtt",
-        "rxx32ui8",
-        "Fg3qq__n",
-        "rg32iqqt",
-        "rg32uint",
-        "rg333uin6",
-        "rtto62u9QQt",
-        "rg366uin",
-        "rOx2si6zz",
-        "rg3yysint",
-        "rHHsint",
-        "rg32sint",
-        "qWW432snt",
-        "rg3OOsnt",
-        "g32siYt",
-        "g32flo",
-        "rg32foaF",
-        "rg32fwat",
-        "rg32float",
-        "G3fKoaff",
-        "KKgq2float",
-        "rg32mmlo3t",
-        "rgba32uit",
-        "rqba3uint",
-        "rgbabb2uin",
-        "rgba32uint",
-        "rba32iint",
-        "qgba32uiOt",
-        "rgba32uiTTvv",
-        "rgFFa32sint",
-        "rg00Q2sPnt",
-        "rgbaP2sint",
-        "rgba32sint",
-        "rgb77s2sint",
-        "rgba32sbbRRC",
-        "rgbXX32sint",
-        "rOOOba3CCqoat",
-        "rgbu32fsLt",
-        "rgba3Xfloat",
-        "rgba32float",
-        "rba32float",
-        "qqb3float",
-        "rgba32fl22at",
+        "rgbaunccrm",    "rlbanr3",        "rVba8unorm",     "rgba8unorm",   "rgba1unorm",
+        "rgbJqqnorm",    "rgb7ll8unorm",   "rgqqappnoHHm",   "rv8scor",      "rgbbGsnrm",
+        "rgba8snorm",    "rgba8vniirm",    "rg8a8snoWWm",    "Mgbaxxnorm",   "rXa8uggnt",
+        "rgbXVut",       "3gba8uint",      "rgba8uint",      "rgba8uiEt",    "rgTTPauint",
+        "ddgbauixxt",    "44gba8sint",     "VVgbaSSsint",    "rba8si2Rt",    "rgba8sint",
+        "r9bFsint",      "rgba8int",       "rgVROOsHnt",     "ryba1uint",    "r77ba1nnullrrt",
+        "rgb4006uint",   "rgba16uint",     "rb1uioot",       "rga1uzznt",    "r11b1uppiit",
+        "XXgba16sint",   "IIgb9916nni55t", "rYbaSSrrsiHHat", "rgba16sint",   "rbkk6Hit",
+        "jgba1sgRR",     "rgbab6si",       "rgba16fljat",    "rgba6float",   "rbq6float",
+        "rgba16float",   "rgba1NNloat",    "rgbvv6flot",     "rgbaQQ6foat",  "r3ffir",
+        "r32uijt",       "rNNwuin8",       "r32uint",        "r32int",       "rrr2uint",
+        "G32uint",       "r32sinFF",       "32st",           "r3rrint",      "r32sint",
+        "2sint",         "D3siJJt",        "r38n",           "r211lk",       "r32floa",
+        "r3flJat",       "r32float",       "r32fcoat",       "r32floOt",     "r32floKK_vtt",
+        "rxx32ui8",      "Fg3qq__n",       "rg32iqqt",       "rg32uint",     "rg333uin6",
+        "rtto62u9QQt",   "rg366uin",       "rOx2si6zz",      "rg3yysint",    "rHHsint",
+        "rg32sint",      "qWW432snt",      "rg3OOsnt",       "g32siYt",      "g32flo",
+        "rg32foaF",      "rg32fwat",       "rg32float",      "G3fKoaff",     "KKgq2float",
+        "rg32mmlo3t",    "rgba32uit",      "rqba3uint",      "rgbabb2uin",   "rgba32uint",
+        "rba32iint",     "qgba32uiOt",     "rgba32uiTTvv",   "rgFFa32sint",  "rg00Q2sPnt",
+        "rgbaP2sint",    "rgba32sint",     "rgb77s2sint",    "rgba32sbbRRC", "rgbXX32sint",
+        "rOOOba3CCqoat", "rgbu32fsLt",     "rgba3Xfloat",    "rgba32float",  "rba32float",
+        "qqb3float",     "rgba32fl22at",
     };
     for (auto _ : state) {
         for (auto& str : kStrings) {
diff --git a/src/tint/ast/texel_format_test.cc b/src/tint/ast/texel_format_test.cc
index 9603961..606d78f 100644
--- a/src/tint/ast/texel_format_test.cc
+++ b/src/tint/ast/texel_format_test.cc
@@ -42,73 +42,41 @@
 }
 
 static constexpr Case kValidCases[] = {
-    {"rgba8unorm", TexelFormat::kRgba8Unorm},
-    {"rgba8snorm", TexelFormat::kRgba8Snorm},
-    {"rgba8uint", TexelFormat::kRgba8Uint},
-    {"rgba8sint", TexelFormat::kRgba8Sint},
-    {"rgba16uint", TexelFormat::kRgba16Uint},
-    {"rgba16sint", TexelFormat::kRgba16Sint},
-    {"rgba16float", TexelFormat::kRgba16Float},
-    {"r32uint", TexelFormat::kR32Uint},
-    {"r32sint", TexelFormat::kR32Sint},
-    {"r32float", TexelFormat::kR32Float},
-    {"rg32uint", TexelFormat::kRg32Uint},
-    {"rg32sint", TexelFormat::kRg32Sint},
-    {"rg32float", TexelFormat::kRg32Float},
-    {"rgba32uint", TexelFormat::kRgba32Uint},
-    {"rgba32sint", TexelFormat::kRgba32Sint},
-    {"rgba32float", TexelFormat::kRgba32Float},
+    {"rgba8unorm", TexelFormat::kRgba8Unorm},   {"rgba8snorm", TexelFormat::kRgba8Snorm},
+    {"rgba8uint", TexelFormat::kRgba8Uint},     {"rgba8sint", TexelFormat::kRgba8Sint},
+    {"rgba16uint", TexelFormat::kRgba16Uint},   {"rgba16sint", TexelFormat::kRgba16Sint},
+    {"rgba16float", TexelFormat::kRgba16Float}, {"r32uint", TexelFormat::kR32Uint},
+    {"r32sint", TexelFormat::kR32Sint},         {"r32float", TexelFormat::kR32Float},
+    {"rg32uint", TexelFormat::kRg32Uint},       {"rg32sint", TexelFormat::kRg32Sint},
+    {"rg32float", TexelFormat::kRg32Float},     {"rgba32uint", TexelFormat::kRgba32Uint},
+    {"rgba32sint", TexelFormat::kRgba32Sint},   {"rgba32float", TexelFormat::kRgba32Float},
 };
 
 static constexpr Case kInvalidCases[] = {
-    {"rgbaunccrm", TexelFormat::kInvalid},
-    {"rlbanr3", TexelFormat::kInvalid},
-    {"rVba8unorm", TexelFormat::kInvalid},
-    {"rgba1snorm", TexelFormat::kInvalid},
-    {"rgbJqqnorm", TexelFormat::kInvalid},
-    {"rgb7ll8snorm", TexelFormat::kInvalid},
-    {"rgbauippqHH", TexelFormat::kInvalid},
-    {"rgbaun", TexelFormat::kInvalid},
-    {"rba8Gint", TexelFormat::kInvalid},
-    {"rgvia8sint", TexelFormat::kInvalid},
-    {"rgba8WWint", TexelFormat::kInvalid},
-    {"rgbasxxMt", TexelFormat::kInvalid},
-    {"rXba16ungg", TexelFormat::kInvalid},
-    {"rba1XuVt", TexelFormat::kInvalid},
-    {"rgba16uin3", TexelFormat::kInvalid},
-    {"rgba16sinE", TexelFormat::kInvalid},
-    {"TTgba16sPPn", TexelFormat::kInvalid},
-    {"rgbad6xxint", TexelFormat::kInvalid},
-    {"rgba446float", TexelFormat::kInvalid},
-    {"SSVVba16float", TexelFormat::kInvalid},
-    {"rgbRR6float", TexelFormat::kInvalid},
-    {"rFui9t", TexelFormat::kInvalid},
-    {"r32int", TexelFormat::kInvalid},
-    {"VOORRHnt", TexelFormat::kInvalid},
-    {"r3siyt", TexelFormat::kInvalid},
-    {"lln3rrs77nt", TexelFormat::kInvalid},
-    {"r32s4n00", TexelFormat::kInvalid},
-    {"32ooat", TexelFormat::kInvalid},
-    {"r32fzzt", TexelFormat::kInvalid},
-    {"r3iippl1a", TexelFormat::kInvalid},
-    {"XXg32uint", TexelFormat::kInvalid},
-    {"rII39955nnnt", TexelFormat::kInvalid},
-    {"aagHH2uinYSS", TexelFormat::kInvalid},
-    {"rkk3it", TexelFormat::kInvalid},
-    {"gj3sRRn", TexelFormat::kInvalid},
-    {"r3bsnt", TexelFormat::kInvalid},
-    {"rg32flojt", TexelFormat::kInvalid},
-    {"r32floa", TexelFormat::kInvalid},
-    {"rg32lot", TexelFormat::kInvalid},
-    {"rgb3uit", TexelFormat::kInvalid},
-    {"rgjj3uint", TexelFormat::kInvalid},
-    {"rgb2urnff", TexelFormat::kInvalid},
-    {"rgba32sijt", TexelFormat::kInvalid},
-    {"NNgba32ww2t", TexelFormat::kInvalid},
-    {"rgba32snt", TexelFormat::kInvalid},
-    {"rgba32rrloat", TexelFormat::kInvalid},
-    {"rgGa32float", TexelFormat::kInvalid},
-    {"FFgba32float", TexelFormat::kInvalid},
+    {"rgbaunccrm", TexelFormat::kInvalid},   {"rlbanr3", TexelFormat::kInvalid},
+    {"rVba8unorm", TexelFormat::kInvalid},   {"rgba1snorm", TexelFormat::kInvalid},
+    {"rgbJqqnorm", TexelFormat::kInvalid},   {"rgb7ll8snorm", TexelFormat::kInvalid},
+    {"rgbauippqHH", TexelFormat::kInvalid},  {"rgbaun", TexelFormat::kInvalid},
+    {"rba8Gint", TexelFormat::kInvalid},     {"rgvia8sint", TexelFormat::kInvalid},
+    {"rgba8WWint", TexelFormat::kInvalid},   {"rgbasxxMt", TexelFormat::kInvalid},
+    {"rXba16ungg", TexelFormat::kInvalid},   {"rba1XuVt", TexelFormat::kInvalid},
+    {"rgba16uin3", TexelFormat::kInvalid},   {"rgba16sinE", TexelFormat::kInvalid},
+    {"TTgba16sPPn", TexelFormat::kInvalid},  {"rgbad6xxint", TexelFormat::kInvalid},
+    {"rgba446float", TexelFormat::kInvalid}, {"SSVVba16float", TexelFormat::kInvalid},
+    {"rgbRR6float", TexelFormat::kInvalid},  {"rFui9t", TexelFormat::kInvalid},
+    {"r32int", TexelFormat::kInvalid},       {"VOORRHnt", TexelFormat::kInvalid},
+    {"r3siyt", TexelFormat::kInvalid},       {"lln3rrs77nt", TexelFormat::kInvalid},
+    {"r32s4n00", TexelFormat::kInvalid},     {"32ooat", TexelFormat::kInvalid},
+    {"r32fzzt", TexelFormat::kInvalid},      {"r3iippl1a", TexelFormat::kInvalid},
+    {"XXg32uint", TexelFormat::kInvalid},    {"rII39955nnnt", TexelFormat::kInvalid},
+    {"aagHH2uinYSS", TexelFormat::kInvalid}, {"rkk3it", TexelFormat::kInvalid},
+    {"gj3sRRn", TexelFormat::kInvalid},      {"r3bsnt", TexelFormat::kInvalid},
+    {"rg32flojt", TexelFormat::kInvalid},    {"r32floa", TexelFormat::kInvalid},
+    {"rg32lot", TexelFormat::kInvalid},      {"rgb3uit", TexelFormat::kInvalid},
+    {"rgjj3uint", TexelFormat::kInvalid},    {"rgb2urnff", TexelFormat::kInvalid},
+    {"rgba32sijt", TexelFormat::kInvalid},   {"NNgba32ww2t", TexelFormat::kInvalid},
+    {"rgba32snt", TexelFormat::kInvalid},    {"rgba32rrloat", TexelFormat::kInvalid},
+    {"rgGa32float", TexelFormat::kInvalid},  {"FFgba32float", TexelFormat::kInvalid},
 };
 
 using TexelFormatParseTest = testing::TestWithParam<Case>;
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc
index 0c2bcdf..c38e6f9 100644
--- a/src/tint/cmd/main.cc
+++ b/src/tint/cmd/main.cc
@@ -868,9 +868,13 @@
         if (options.verbose) {
             if (fxc_found && !fxc_res.failed) {
                 std::cout << "Passed FXC validation" << std::endl;
+                std::cout << fxc_res.output;
+                std::cout << std::endl;
             }
             if (dxc_found && !dxc_res.failed) {
                 std::cout << "Passed DXC validation" << std::endl;
+                std::cout << dxc_res.output;
+                std::cout << std::endl;
             }
         }
     }
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index 61954ee..1a3313b 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -489,14 +489,35 @@
         /// @return the tint AST type for a 2-element vector of `type`.
         const ast::Vector* vec2(const ast::Type* type) const { return vec(type, 2u); }
 
+        /// @param source the vector source
+        /// @param type vector subtype
+        /// @return the tint AST type for a 2-element vector of `type`.
+        const ast::Vector* vec2(const Source& source, const ast::Type* type) const {
+            return vec(source, type, 2u);
+        }
+
         /// @param type vector subtype
         /// @return the tint AST type for a 3-element vector of `type`.
         const ast::Vector* vec3(const ast::Type* type) const { return vec(type, 3u); }
 
+        /// @param source the vector source
+        /// @param type vector subtype
+        /// @return the tint AST type for a 3-element vector of `type`.
+        const ast::Vector* vec3(const Source& source, const ast::Type* type) const {
+            return vec(source, type, 3u);
+        }
+
         /// @param type vector subtype
         /// @return the tint AST type for a 4-element vector of `type`.
         const ast::Vector* vec4(const ast::Type* type) const { return vec(type, 4u); }
 
+        /// @param source the vector source
+        /// @param type vector subtype
+        /// @return the tint AST type for a 4-element vector of `type`.
+        const ast::Vector* vec4(const Source& source, const ast::Type* type) const {
+            return vec(source, type, 4u);
+        }
+
         /// @param n vector width in elements
         /// @return the tint AST type for a `n`-element vector of `type`.
         template <typename T>
@@ -510,18 +531,39 @@
             return vec2(Of<T>());
         }
 
+        /// @param source the Source of the node
+        /// @return the tint AST type for a 2-element vector of the C type `T`.
+        template <typename T>
+        const ast::Vector* vec2(const Source& source) const {
+            return vec2(source, Of<T>());
+        }
+
         /// @return the tint AST type for a 3-element vector of the C type `T`.
         template <typename T>
         const ast::Vector* vec3() const {
             return vec3(Of<T>());
         }
 
+        /// @param source the Source of the node
+        /// @return the tint AST type for a 3-element vector of the C type `T`.
+        template <typename T>
+        const ast::Vector* vec3(const Source& source) const {
+            return vec3(source, Of<T>());
+        }
+
         /// @return the tint AST type for a 4-element vector of the C type `T`.
         template <typename T>
         const ast::Vector* vec4() const {
             return vec4(Of<T>());
         }
 
+        /// @param source the Source of the node
+        /// @return the tint AST type for a 4-element vector of the C type `T`.
+        template <typename T>
+        const ast::Vector* vec4(const Source& source) const {
+            return vec4(source, Of<T>());
+        }
+
         /// @param type matrix subtype
         /// @param columns number of columns for the matrix
         /// @param rows number of rows for the matrix
@@ -1245,99 +1287,207 @@
     /// @param args the arguments for the vector constructor
     /// @return an `ast::CallExpression` of a 2-element vector of type
     /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS>
+    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
     const ast::CallExpression* vec2(ARGS&&... args) {
         return Construct(ty.vec2<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the vector source
+    /// @param args the arguments for the vector constructor
+    /// @return an `ast::CallExpression` of a 2-element vector of type
+    /// `T`, constructed with the values `args`.
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* vec2(const Source& source, ARGS&&... args) {
+        return Construct(source, ty.vec2<T>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param args the arguments for the vector constructor
     /// @return an `ast::CallExpression` of a 3-element vector of type
     /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS>
+    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
     const ast::CallExpression* vec3(ARGS&&... args) {
         return Construct(ty.vec3<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the vector source
+    /// @param args the arguments for the vector constructor
+    /// @return an `ast::CallExpression` of a 3-element vector of type
+    /// `T`, constructed with the values `args`.
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* vec3(const Source& source, ARGS&&... args) {
+        return Construct(source, ty.vec3<T>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param args the arguments for the vector constructor
     /// @return an `ast::CallExpression` of a 4-element vector of type
     /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS>
+    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
     const ast::CallExpression* vec4(ARGS&&... args) {
         return Construct(ty.vec4<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the vector source
+    /// @param args the arguments for the vector constructor
+    /// @return an `ast::CallExpression` of a 4-element vector of type
+    /// `T`, constructed with the values `args`.
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* vec4(const Source& source, ARGS&&... args) {
+        return Construct(source, ty.vec4<T>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param args the arguments for the matrix constructor
     /// @return an `ast::CallExpression` of a 2x2 matrix of type
     /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS>
+    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat2x2(ARGS&&... args) {
         return Construct(ty.mat2x2<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the matrix source
+    /// @param args the arguments for the matrix constructor
+    /// @return an `ast::CallExpression` of a 2x2 matrix of type
+    /// `T`, constructed with the values `args`.
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* mat2x2(const Source& source, ARGS&&... args) {
+        return Construct(source, ty.mat2x2<T>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param args the arguments for the matrix constructor
     /// @return an `ast::CallExpression` of a 2x3 matrix of type
     /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS>
+    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat2x3(ARGS&&... args) {
         return Construct(ty.mat2x3<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the matrix source
+    /// @param args the arguments for the matrix constructor
+    /// @return an `ast::CallExpression` of a 2x3 matrix of type
+    /// `T`, constructed with the values `args`.
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* mat2x3(const Source& source, ARGS&&... args) {
+        return Construct(source, ty.mat2x3<T>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param args the arguments for the matrix constructor
     /// @return an `ast::CallExpression` of a 2x4 matrix of type
     /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS>
+    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat2x4(ARGS&&... args) {
         return Construct(ty.mat2x4<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the matrix source
+    /// @param args the arguments for the matrix constructor
+    /// @return an `ast::CallExpression` of a 2x4 matrix of type
+    /// `T`, constructed with the values `args`.
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* mat2x4(const Source& source, ARGS&&... args) {
+        return Construct(source, ty.mat2x4<T>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param args the arguments for the matrix constructor
     /// @return an `ast::CallExpression` of a 3x2 matrix of type
     /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS>
+    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat3x2(ARGS&&... args) {
         return Construct(ty.mat3x2<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the matrix source
+    /// @param args the arguments for the matrix constructor
+    /// @return an `ast::CallExpression` of a 3x2 matrix of type
+    /// `T`, constructed with the values `args`.
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* mat3x2(const Source& source, ARGS&&... args) {
+        return Construct(source, ty.mat3x2<T>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param args the arguments for the matrix constructor
     /// @return an `ast::CallExpression` of a 3x3 matrix of type
     /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS>
+    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat3x3(ARGS&&... args) {
         return Construct(ty.mat3x3<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the matrix source
+    /// @param args the arguments for the matrix constructor
+    /// @return an `ast::CallExpression` of a 3x3 matrix of type
+    /// `T`, constructed with the values `args`.
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* mat3x3(const Source& source, ARGS&&... args) {
+        return Construct(source, ty.mat3x3<T>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param args the arguments for the matrix constructor
     /// @return an `ast::CallExpression` of a 3x4 matrix of type
     /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS>
+    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat3x4(ARGS&&... args) {
         return Construct(ty.mat3x4<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the matrix source
+    /// @param args the arguments for the matrix constructor
+    /// @return an `ast::CallExpression` of a 3x4 matrix of type
+    /// `T`, constructed with the values `args`.
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* mat3x4(const Source& source, ARGS&&... args) {
+        return Construct(source, ty.mat3x4<T>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param args the arguments for the matrix constructor
     /// @return an `ast::CallExpression` of a 4x2 matrix of type
     /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS>
+    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat4x2(ARGS&&... args) {
         return Construct(ty.mat4x2<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the matrix source
+    /// @param args the arguments for the matrix constructor
+    /// @return an `ast::CallExpression` of a 4x2 matrix of type
+    /// `T`, constructed with the values `args`.
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* mat4x2(const Source& source, ARGS&&... args) {
+        return Construct(source, ty.mat4x2<T>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param args the arguments for the matrix constructor
     /// @return an `ast::CallExpression` of a 4x3 matrix of type
     /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS>
+    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat4x3(ARGS&&... args) {
         return Construct(ty.mat4x3<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the matrix source
+    /// @param args the arguments for the matrix constructor
+    /// @return an `ast::CallExpression` of a 4x3 matrix of type
+    /// `T`, constructed with the values `args`.
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* mat4x3(const Source& source, ARGS&&... args) {
+        return Construct(source, ty.mat4x3<T>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param args the arguments for the matrix constructor
     /// @return an `ast::CallExpression` of a 4x4 matrix of type
     /// `T`, constructed with the values `args`.
-    template <typename T, typename... ARGS>
+    template <typename T, typename... ARGS, typename _ = DisableIfSource<ARGS...>>
     const ast::CallExpression* mat4x4(ARGS&&... args) {
         return Construct(ty.mat4x4<T>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the matrix source
+    /// @param args the arguments for the matrix constructor
+    /// @return an `ast::CallExpression` of a 4x4 matrix of type
+    /// `T`, constructed with the values `args`.
+    template <typename T, typename... ARGS>
+    const ast::CallExpression* mat4x4(const Source& source, ARGS&&... args) {
+        return Construct(source, ty.mat4x4<T>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param args the arguments for the array constructor
     /// @return an `ast::CallExpression` of an array with element type
     /// `T` and size `N`, constructed with the values `args`.
@@ -1346,6 +1496,15 @@
         return Construct(ty.array<T, N>(), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the array source
+    /// @param args the arguments for the array constructor
+    /// @return an `ast::CallExpression` of an array with element type
+    /// `T` and size `N`, constructed with the values `args`.
+    template <typename T, int N, typename... ARGS>
+    const ast::CallExpression* array(const Source& source, ARGS&&... args) {
+        return Construct(source, ty.array<T, N>(), std::forward<ARGS>(args)...);
+    }
+
     /// @param subtype the array element type
     /// @param n the array size. nullptr represents a runtime-array.
     /// @param args the arguments for the array constructor
@@ -1356,6 +1515,21 @@
         return Construct(ty.array(subtype, std::forward<EXPR>(n)), std::forward<ARGS>(args)...);
     }
 
+    /// @param source the array source
+    /// @param subtype the array element type
+    /// @param n the array size. nullptr represents a runtime-array.
+    /// @param args the arguments for the array constructor
+    /// @return an `ast::CallExpression` of an array with element type
+    /// `subtype`, constructed with the values `args`.
+    template <typename EXPR, typename... ARGS>
+    const ast::CallExpression* array(const Source& source,
+                                     const ast::Type* subtype,
+                                     EXPR&& n,
+                                     ARGS&&... args) {
+        return Construct(source, ty.array(subtype, std::forward<EXPR>(n)),
+                         std::forward<ARGS>(args)...);
+    }
+
     /// Adds the extension to the list of enable directives at the top of the module.
     /// @param ext the extension to enable
     /// @return an `ast::Enable` enabling the given extension.
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index 3adab59..59f4fac 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -1175,33 +1175,40 @@
 Expect<const ast::Type*> ParserImpl::expect_type_decl_array(const Token& t) {
     const char* use = "array declaration";
 
-    const ast::Expression* size = nullptr;
+    struct TypeAndSize {
+        const ast::Type* type = nullptr;
+        const ast::Expression* size = nullptr;
+    };
 
-    auto subtype = expect_lt_gt_block(use, [&]() -> Expect<const ast::Type*> {
+    if (!peek_is(Token::Type::kLessThan)) {
+        return builder_.ty.array(make_source_range_from(t.source()), nullptr, nullptr);
+    }
+
+    auto type_size = expect_lt_gt_block(use, [&]() -> Expect<TypeAndSize> {
         auto type = expect_type(use);
         if (type.errored) {
             return Failure::kErrored;
         }
 
-        if (match(Token::Type::kComma)) {
-            auto expr = primary_expression();
-            if (expr.errored) {
-                return Failure::kErrored;
-            } else if (!expr.matched) {
-                return add_error(peek(), "expected array size expression");
-            }
-
-            size = std::move(expr.value);
+        if (!match(Token::Type::kComma)) {
+            return TypeAndSize{type.value, nullptr};
         }
 
-        return type.value;
+        auto size = primary_expression();
+        if (size.errored) {
+            return Failure::kErrored;
+        } else if (!size.matched) {
+            return add_error(peek(), "expected array size expression");
+        }
+
+        return TypeAndSize{type.value, size.value};
     });
 
-    if (subtype.errored) {
+    if (type_size.errored) {
         return Failure::kErrored;
     }
 
-    return builder_.ty.array(make_source_range_from(t.source()), subtype.value, size);
+    return builder_.ty.array(make_source_range_from(t.source()), type_size->type, type_size->size);
 }
 
 Expect<const ast::Type*> ParserImpl::expect_type_decl_matrix(const Token& t) {
diff --git a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
index 6a825de..81613b4 100644
--- a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
@@ -603,8 +603,9 @@
 }
 
 TEST_F(ParserImplErrorTest, GlobalDeclLetBadConstLiteral) {
-    EXPECT("let i : vec2<i32> = vec2<i32>(!);",
-           R"(test.wgsl:1:1 warning: use of deprecated language feature: module-scope 'let' has been replaced with 'const'
+    EXPECT(
+        "let i : vec2<i32> = vec2<i32>(!);",
+        R"(test.wgsl:1:1 warning: use of deprecated language feature: module-scope 'let' has been replaced with 'const'
 let i : vec2<i32> = vec2<i32>(!);
 ^^^
 
@@ -821,14 +822,6 @@
 )");
 }
 
-TEST_F(ParserImplErrorTest, GlobalDeclVarArrayMissingLessThan) {
-    EXPECT("var i : array;",
-           R"(test.wgsl:1:14 error: expected '<' for array declaration
-var i : array;
-             ^
-)");
-}
-
 TEST_F(ParserImplErrorTest, GlobalDeclVarArrayMissingGreaterThan) {
     EXPECT("var i : array<u32, 3;",
            R"(test.wgsl:1:21 error: expected '>' for array declaration
diff --git a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
index 5459af6..880f87a 100644
--- a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
@@ -501,6 +501,22 @@
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
 }
 
+TEST_F(ParserImplTest, TypeDecl_Array_InferTypeAndSize) {
+    auto p = parser("array");
+    auto t = p->type_decl();
+    EXPECT_TRUE(t.matched);
+    EXPECT_FALSE(t.errored);
+    ASSERT_NE(t.value, nullptr) << p->error();
+    ASSERT_FALSE(p->has_error());
+    ASSERT_TRUE(t.value->Is<ast::Array>());
+
+    auto* a = t.value->As<ast::Array>();
+    EXPECT_FALSE(a->IsRuntimeArray());
+    EXPECT_EQ(a->type, nullptr);
+    EXPECT_EQ(a->count, nullptr);
+    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 6u}}));
+}
+
 TEST_F(ParserImplTest, TypeDecl_Array_BadSize) {
     auto p = parser("array<f32, !>");
     auto t = p->type_decl();
@@ -521,16 +537,6 @@
     ASSERT_EQ(p->error(), "1:11: expected array size expression");
 }
 
-TEST_F(ParserImplTest, TypeDecl_Array_MissingLessThan) {
-    auto p = parser("array f32>");
-    auto t = p->type_decl();
-    EXPECT_TRUE(t.errored);
-    EXPECT_FALSE(t.matched);
-    ASSERT_EQ(t.value, nullptr);
-    ASSERT_TRUE(p->has_error());
-    ASSERT_EQ(p->error(), "1:7: expected '<' for array declaration");
-}
-
 TEST_F(ParserImplTest, TypeDecl_Array_MissingGreaterThan) {
     auto p = parser("array<f32");
     auto t = p->type_decl();
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc
index 2cb8905..36221e2 100644
--- a/src/tint/resolver/attribute_validation_test.cc
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -895,7 +895,8 @@
        << ", should_pass: " << params.should_pass;
     SCOPED_TRACE(ss.str());
 
-    auto* arr = ty.array(Source{{12, 34}}, el_ty, 4_u, params.stride);
+    auto* arr =
+        ty.array(el_ty, 4_u, {create<ast::StrideAttribute>(Source{{12, 34}}, params.stride)});
 
     GlobalVar("myarray", arr, ast::StorageClass::kPrivate);
 
@@ -906,7 +907,7 @@
         EXPECT_EQ(r()->error(),
                   "12:34 error: arrays decorated with the stride attribute must "
                   "have a stride that is at least the size of the element type, "
-                  "and be a multiple of the element type's alignment value.");
+                  "and be a multiple of the element type's alignment value");
     }
 }
 
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index bd363c1..70985dd 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -27,22 +27,28 @@
 
 using AFloatV = builder::vec<3, AFloat>;
 using AFloatM = builder::mat<3, 2, AFloat>;
+using AFloatA = builder::array<3, AFloat>;
 using AIntV = builder::vec<3, AInt>;
+using AIntA = builder::array<3, AInt>;
 using f32V = builder::vec<3, f32>;
 using f16V = builder::vec<3, f16>;
 using i32V = builder::vec<3, i32>;
 using u32V = builder::vec<3, u32>;
 using f32M = builder::mat<3, 2, f32>;
-using i32Varr = builder::array<3, i32>;
+using f16M = builder::mat<3, 2, f16>;
+using f32A = builder::array<3, f32>;
+using f16A = builder::array<3, f16>;
+using i32A = builder::array<3, i32>;
+using u32A = builder::array<3, u32>;
 
 constexpr double kTooBigF32 = static_cast<double>(3.5e+38);
-// constexpr double kTooBigF16 = static_cast<double>(6.6e+4);
+constexpr double kTooBigF16 = static_cast<double>(6.6e+4);
 constexpr double kPiF64 = 3.141592653589793;
 constexpr double kPiF32 = 3.1415927410125732;  // kPiF64 quantized to f32
-// constexpr double kPiF16 = 3.140625;         // kPiF64 quantized to f16
+constexpr double kPiF16 = 3.140625;            // kPiF64 quantized to f16
 
 constexpr double kSubnormalF32 = 0x1.0p-128;
-// constexpr double kSubnormalF16 = 0x1.0p-16;
+constexpr double kSubnormalF16 = 0x1.0p-16;
 
 enum class Expectation {
     kMaterialize,
@@ -111,6 +117,14 @@
                     }
                 }
             },
+            [&](const sem::Array* a) {
+                for (uint32_t i = 0; i < a->Count(); i++) {
+                    auto* el = value->Index(i);
+                    ASSERT_NE(el, nullptr);
+                    EXPECT_TYPE(el->Type(), a->ElemType());
+                    EXPECT_EQ(std::get<T>(el->Value()), expected_value);
+                }
+            },
             [&](Default) { EXPECT_EQ(std::get<T>(value->Value()), expected_value); });
     }
 };
@@ -231,7 +245,7 @@
         case Method::kWorkgroupSize:
             return o << "workgroup-size";
         case Method::kRuntimeIndex:
-            return o << "dynamic-index";
+            return o << "runtime-index";
     }
     return o << "<unknown>";
 }
@@ -296,8 +310,7 @@
     MaterializeTest<std::tuple<Expectation, Method, Data>>;
 
 TEST_P(MaterializeAbstractNumericToConcreteType, Test) {
-    // Once built-in and ops using f16 is properly supported, we'll need to enable this:
-    // Enable(ast::Extension::kF16);
+    Enable(ast::Extension::kF16);
 
     const auto& param = GetParam();
     const auto& expectation = std::get<0>(param);
@@ -435,6 +448,12 @@
     Method::kReturn, Method::kArray, Method::kStruct, Method::kBinaryOp,
 };
 
+/// Methods that support array materialization
+constexpr Method kArrayMethods[] = {
+    Method::kLet,    Method::kVar,   Method::kAssign, Method::kFnArg,
+    Method::kReturn, Method::kArray, Method::kStruct,
+};
+
 /// Methods that support materialization for switch cases
 constexpr Method kSwitchMethods[] = {
     Method::kSwitchCond,
@@ -471,13 +490,13 @@
             Types<f32, AFloat>(AFloat(kPiF32), kPiF64),                                       //
             Types<f32, AFloat>(AFloat(kSubnormalF32), kSubnormalF32),                         //
             Types<f32, AFloat>(AFloat(-kSubnormalF32), -kSubnormalF32),                       //
-            /* Types<f16, AFloat>(0.0_a, 0.0),                             */                 //
-            /* Types<f16, AFloat>(1.0_a, 1.0),                             */                 //
-            /* Types<f16, AFloat>(AFloat(kHighestF16), kHighestF16),       */                 //
-            /* Types<f16, AFloat>(AFloat(kLowestF16), kLowestF16),         */                 //
-            /* Types<f16, AFloat>(AFloat(kPiF16), kPiF64),                 */                 //
-            /* Types<f16, AFloat>(AFloat(kSubnormalF16), kSubnormalF16),   */                 //
-            /* Types<f16, AFloat>(AFloat(-kSubnormalF16), -kSubnormalF16), */                 //
+            Types<f16, AFloat>(0.0_a, 0.0),                                                   //
+            Types<f16, AFloat>(1.0_a, 1.0),                                                   //
+            Types<f16, AFloat>(AFloat(f16::Highest()), static_cast<double>(f16::Highest())),  //
+            Types<f16, AFloat>(AFloat(f16::Lowest()), static_cast<double>(f16::Lowest())),    //
+            Types<f16, AFloat>(AFloat(kPiF16), kPiF64),                                       //
+            Types<f16, AFloat>(AFloat(kSubnormalF16), kSubnormalF16),                         //
+            Types<f16, AFloat>(AFloat(-kSubnormalF16), -kSubnormalF16),                       //
         })));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -504,14 +523,14 @@
             Types<f32V, AFloatV>(AFloat(kPiF32), kPiF64),                                       //
             Types<f32V, AFloatV>(AFloat(kSubnormalF32), kSubnormalF32),                         //
             Types<f32V, AFloatV>(AFloat(-kSubnormalF32), -kSubnormalF32),                       //
-            /* Types<f16V, AFloatV>(0.0_a, 0.0),                             */                 //
-            /* Types<f16V, AFloatV>(1.0_a, 1.0),                             */                 //
-            /* Types<f16V, AFloatV>(-1.0_a, -1.0),                           */                 //
-            /* Types<f16V, AFloatV>(AFloat(kHighestF16), kHighestF16),       */                 //
-            /* Types<f16V, AFloatV>(AFloat(kLowestF16), kLowestF16),         */                 //
-            /* Types<f16V, AFloatV>(AFloat(kPiF16), kPiF64),                 */                 //
-            /* Types<f16V, AFloatV>(AFloat(kSubnormalF16), kSubnormalF16),   */                 //
-            /* Types<f16V, AFloatV>(AFloat(-kSubnormalF16), -kSubnormalF16), */                 //
+            Types<f16V, AFloatV>(0.0_a, 0.0),                                                   //
+            Types<f16V, AFloatV>(1.0_a, 1.0),                                                   //
+            Types<f16V, AFloatV>(-1.0_a, -1.0),                                                 //
+            Types<f16V, AFloatV>(AFloat(f16::Highest()), static_cast<double>(f16::Highest())),  //
+            Types<f16V, AFloatV>(AFloat(f16::Lowest()), static_cast<double>(f16::Lowest())),    //
+            Types<f16V, AFloatV>(AFloat(kPiF16), kPiF64),                                       //
+            Types<f16V, AFloatV>(AFloat(kSubnormalF16), kSubnormalF16),                         //
+            Types<f16V, AFloatV>(AFloat(-kSubnormalF16), -kSubnormalF16),                       //
         })));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -551,14 +570,14 @@
             Types<f32M, AFloatM>(AFloat(kPiF32), kPiF64),                                       //
             Types<f32M, AFloatM>(AFloat(kSubnormalF32), kSubnormalF32),                         //
             Types<f32M, AFloatM>(AFloat(-kSubnormalF32), -kSubnormalF32),                       //
-            /* Types<f16M, AFloatM>(0.0_a, 0.0),                             */                 //
-            /* Types<f16M, AFloatM>(1.0_a, 1.0),                             */                 //
-            /* Types<f16M, AFloatM>(-1.0_a, -1.0),                           */                 //
-            /* Types<f16M, AFloatM>(AFloat(kHighestF16), kHighestF16),       */                 //
-            /* Types<f16M, AFloatM>(AFloat(kLowestF16), kLowestF16),         */                 //
-            /* Types<f16M, AFloatM>(AFloat(kPiF16), kPiF64),                 */                 //
-            /* Types<f16M, AFloatM>(AFloat(kSubnormalF16), kSubnormalF16),   */                 //
-            /* Types<f16M, AFloatM>(AFloat(-kSubnormalF16), -kSubnormalF16), */                 //
+            Types<f16M, AFloatM>(0.0_a, 0.0),                                                   //
+            Types<f16M, AFloatM>(1.0_a, 1.0),                                                   //
+            Types<f16M, AFloatM>(-1.0_a, -1.0),                                                 //
+            Types<f16M, AFloatM>(AFloat(f16::Highest()), static_cast<double>(f16::Highest())),  //
+            Types<f16M, AFloatM>(AFloat(f16::Lowest()), static_cast<double>(f16::Lowest())),    //
+            Types<f16M, AFloatM>(AFloat(kPiF16), kPiF64),                                       //
+            Types<f16M, AFloatM>(AFloat(kSubnormalF16), kSubnormalF16),                         //
+            Types<f16M, AFloatM>(AFloat(-kSubnormalF16), -kSubnormalF16),                       //
         })));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -595,6 +614,57 @@
                          Types<u32, AInt>(AInt(u32::Lowest()), u32::Lowest()),    //
                      })));
 
+INSTANTIATE_TEST_SUITE_P(
+    MaterializeArray,
+    MaterializeAbstractNumericToConcreteType,
+    testing::Combine(
+        testing::Values(Expectation::kMaterialize),
+        testing::ValuesIn(kArrayMethods),
+        testing::ValuesIn(std::vector<Data>{
+            Types<i32A, AIntA>(0_a, 0.0),                                                       //
+            Types<i32A, AIntA>(1_a, 1.0),                                                       //
+            Types<i32A, AIntA>(-1_a, -1.0),                                                     //
+            Types<i32A, AIntA>(AInt(i32::Highest()), i32::Highest()),                           //
+            Types<i32A, AIntA>(AInt(i32::Lowest()), i32::Lowest()),                             //
+            Types<u32A, AIntA>(0_a, 0.0),                                                       //
+            Types<u32A, AIntA>(1_a, 1.0),                                                       //
+            Types<u32A, AIntA>(AInt(u32::Highest()), u32::Highest()),                           //
+            Types<u32A, AIntA>(AInt(u32::Lowest()), u32::Lowest()),                             //
+            Types<f32A, AFloatA>(0.0_a, 0.0),                                                   //
+            Types<f32A, AFloatA>(1.0_a, 1.0),                                                   //
+            Types<f32A, AFloatA>(-1.0_a, -1.0),                                                 //
+            Types<f32A, AFloatA>(AFloat(f32::Highest()), static_cast<double>(f32::Highest())),  //
+            Types<f32A, AFloatA>(AFloat(f32::Lowest()), static_cast<double>(f32::Lowest())),    //
+            Types<f32A, AFloatA>(AFloat(kPiF32), kPiF64),                                       //
+            Types<f32A, AFloatA>(AFloat(kSubnormalF32), kSubnormalF32),                         //
+            Types<f32A, AFloatA>(AFloat(-kSubnormalF32), -kSubnormalF32),                       //
+            Types<f16A, AFloatA>(0.0_a, 0.0),                                                   //
+            Types<f16A, AFloatA>(1.0_a, 1.0),                                                   //
+            Types<f16A, AFloatA>(-1.0_a, -1.0),                                                 //
+            Types<f16A, AFloatA>(AFloat(f16::Highest()), static_cast<double>(f16::Highest())),  //
+            Types<f16A, AFloatA>(AFloat(f16::Lowest()), static_cast<double>(f16::Lowest())),    //
+            Types<f16A, AFloatA>(AFloat(kPiF16), kPiF64),                                       //
+            Types<f16A, AFloatA>(AFloat(kSubnormalF16), kSubnormalF16),                         //
+            Types<f16A, AFloatA>(AFloat(-kSubnormalF16), -kSubnormalF16),                       //
+        })));
+
+INSTANTIATE_TEST_SUITE_P(
+    MaterializeArrayRuntimeIndex,
+    MaterializeAbstractNumericToConcreteType,
+    testing::Combine(
+        testing::Values(Expectation::kMaterialize),
+        testing::Values(Method::kRuntimeIndex),
+        testing::ValuesIn(std::vector<Data>{
+            Types<f32A, AFloatA>(0.0_a, 0.0),                                                   //
+            Types<f32A, AFloatA>(1.0_a, 1.0),                                                   //
+            Types<f32A, AFloatA>(-1.0_a, -1.0),                                                 //
+            Types<f32A, AFloatA>(AFloat(f32::Highest()), static_cast<double>(f32::Highest())),  //
+            Types<f32A, AFloatA>(AFloat(f32::Lowest()), static_cast<double>(f32::Lowest())),    //
+            Types<f32A, AFloatA>(AFloat(kPiF32), kPiF64),                                       //
+            Types<f32A, AFloatA>(AFloat(kSubnormalF32), kSubnormalF32),                         //
+            Types<f32A, AFloatA>(AFloat(-kSubnormalF32), -kSubnormalF32),                       //
+        })));
+
 INSTANTIATE_TEST_SUITE_P(MaterializeWorkgroupSize,
                          MaterializeAbstractNumericToConcreteType,
                          testing::Combine(testing::Values(Expectation::kMaterialize),
@@ -625,14 +695,14 @@
                          testing::Combine(testing::Values(Expectation::kInvalidConversion),
                                           testing::ValuesIn(kScalarMethods),
                                           testing::ValuesIn(std::vector<Data>{
-                                              Types<i32, AFloat>(),       //
-                                              Types<u32, AFloat>(),       //
-                                              Types<i32V, AFloatV>(),     //
-                                              Types<u32V, AFloatV>(),     //
-                                              Types<i32Varr, AInt>(),     //
-                                              Types<i32Varr, AIntV>(),    //
-                                              Types<i32Varr, AFloat>(),   //
-                                              Types<i32Varr, AFloatV>(),  //
+                                              Types<i32, AFloat>(),    //
+                                              Types<u32, AFloat>(),    //
+                                              Types<i32V, AFloatV>(),  //
+                                              Types<u32V, AFloatV>(),  //
+                                              Types<i32A, AInt>(),     //
+                                              Types<i32A, AIntV>(),    //
+                                              Types<i32A, AFloat>(),   //
+                                              Types<i32A, AFloatV>(),  //
                                           })));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -647,8 +717,8 @@
                          Types<u32, AInt>(0_a, static_cast<double>(u32::kLowestValue) - 1),   //
                          Types<f32, AFloat>(0.0_a, kTooBigF32),                               //
                          Types<f32, AFloat>(0.0_a, -kTooBigF32),                              //
-                         /* Types<f16, AFloat>(0.0_a, kTooBigF16),  */                        //
-                         /* Types<f16, AFloat>(0.0_a, -kTooBigF16), */                        //
+                         Types<f16, AFloat>(0.0_a, kTooBigF16),                               //
+                         Types<f16, AFloat>(0.0_a, -kTooBigF16),                              //
                      })));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -663,8 +733,8 @@
                          Types<u32V, AIntV>(0_a, static_cast<double>(u32::kLowestValue) - 1),   //
                          Types<f32V, AFloatV>(0.0_a, kTooBigF32),                               //
                          Types<f32V, AFloatV>(0.0_a, -kTooBigF32),                              //
-                         /* Types<f16V, AFloatV>(0.0_a, kTooBigF16),  */                        //
-                         /* Types<f16V, AFloatV>(0.0_a, -kTooBigF16), */                        //
+                         Types<f16V, AFloatV>(0.0_a, kTooBigF16),                               //
+                         Types<f16V, AFloatV>(0.0_a, -kTooBigF16),                              //
                      })));
 
 INSTANTIATE_TEST_SUITE_P(MatrixValueCannotBeRepresented,
@@ -672,10 +742,10 @@
                          testing::Combine(testing::Values(Expectation::kValueCannotBeRepresented),
                                           testing::ValuesIn(kMatrixMethods),
                                           testing::ValuesIn(std::vector<Data>{
-                                              Types<f32M, AFloatM>(0.0_a, kTooBigF32),         //
-                                              Types<f32M, AFloatM>(0.0_a, -kTooBigF32),        //
-                                              /* Types<f16M, AFloatM>(0.0_a, kTooBigF16),  */  //
-                                              /* Types<f16M, AFloatM>(0.0_a, -kTooBigF16), */  //
+                                              Types<f32M, AFloatM>(0.0_a, kTooBigF32),   //
+                                              Types<f32M, AFloatM>(0.0_a, -kTooBigF32),  //
+                                              Types<f16M, AFloatM>(0.0_a, kTooBigF16),   //
+                                              Types<f16M, AFloatM>(0.0_a, -kTooBigF16),  //
                                           })));
 
 }  // namespace materialize_abstract_numeric_to_concrete_type
@@ -719,6 +789,9 @@
 
     // arr[abstract_expr]
     kIndex,
+
+    // abstract_expr[runtime-index]
+    kRuntimeIndex,
 };
 
 static std::ostream& operator<<(std::ostream& o, Method m) {
@@ -741,6 +814,8 @@
             return o << "workgroup-size";
         case Method::kIndex:
             return o << "index";
+        case Method::kRuntimeIndex:
+            return o << "runtime-index";
     }
     return o << "<unknown>";
 }
@@ -826,6 +901,10 @@
             GlobalVar("arr", ty.array<i32, 4>(), ast::StorageClass::kPrivate);
             WrapInFunction(IndexAccessor("arr", abstract_expr()));
             break;
+        case Method::kRuntimeIndex:
+            auto* runtime_index = Var("runtime_index", nullptr, Expr(1_i));
+            WrapInFunction(runtime_index, IndexAccessor(abstract_expr(), runtime_index));
+            break;
     }
 
     switch (expectation) {
@@ -883,10 +962,8 @@
 
 /// Methods that support vector materialization
 constexpr Method kVectorMethods[] = {
-    Method::kLet,
-    Method::kVar,
-    Method::kBuiltinArg,
-    Method::kBitcastVec3F32Arg,
+    Method::kLet,          Method::kVar, Method::kBuiltinArg, Method::kBitcastVec3F32Arg,
+    Method::kRuntimeIndex,
 };
 
 /// Methods that support matrix materialization
@@ -895,6 +972,12 @@
     Method::kVar,
 };
 
+/// Methods that support array materialization
+constexpr Method kArrayMethods[] = {
+    Method::kLet,
+    Method::kVar,
+};
+
 INSTANTIATE_TEST_SUITE_P(
     MaterializeScalar,
     MaterializeAbstractNumericToDefaultType,
@@ -967,6 +1050,28 @@
                      })));
 
 INSTANTIATE_TEST_SUITE_P(
+    MaterializeArray,
+    MaterializeAbstractNumericToDefaultType,
+    testing::Combine(
+        testing::Values(Expectation::kMaterialize),
+        testing::ValuesIn(kArrayMethods),
+        testing::ValuesIn(std::vector<Data>{
+            Types<i32A, AIntA>(0_a, 0.0),                                                       //
+            Types<i32A, AIntA>(1_a, 1.0),                                                       //
+            Types<i32A, AIntA>(-1_a, -1.0),                                                     //
+            Types<i32A, AIntA>(AInt(i32::Highest()), i32::Highest()),                           //
+            Types<i32A, AIntA>(AInt(i32::Lowest()), i32::Lowest()),                             //
+            Types<f32A, AFloatA>(0.0_a, 0.0),                                                   //
+            Types<f32A, AFloatA>(1.0_a, 1.0),                                                   //
+            Types<f32A, AFloatA>(-1.0_a, -1.0),                                                 //
+            Types<f32A, AFloatA>(AFloat(f32::Highest()), static_cast<double>(f32::Highest())),  //
+            Types<f32A, AFloatA>(AFloat(f32::Lowest()), static_cast<double>(f32::Lowest())),    //
+            Types<f32A, AFloatA>(AFloat(kPiF32), kPiF64),                                       //
+            Types<f32A, AFloatA>(AFloat(kSubnormalF32), kSubnormalF32),                         //
+            Types<f32A, AFloatA>(AFloat(-kSubnormalF32), -kSubnormalF32),                       //
+        })));
+
+INSTANTIATE_TEST_SUITE_P(
     MaterializeArrayLength,
     MaterializeAbstractNumericToDefaultType,
     testing::Combine(testing::Values(Expectation::kMaterialize),
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index b1961c3..097d591 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -1316,7 +1316,9 @@
     return nullptr;
 }
 
-const sem::Type* Resolver::ConcreteType(const sem::Type* ty, const sem::Type* target_ty) {
+const sem::Type* Resolver::ConcreteType(const sem::Type* ty,
+                                        const sem::Type* target_ty,
+                                        const Source& source) {
     auto i32 = [&] { return builder_->create<sem::I32>(); };
     auto f32 = [&] { return builder_->create<sem::F32>(); };
     auto i32v = [&](uint32_t width) { return builder_->create<sem::Vector>(i32(), width); };
@@ -1342,6 +1344,16 @@
                           [&](const sem::AbstractFloat*) {
                               return target_ty ? target_ty : f32m(m->columns(), m->rows());
                           });
+        },
+        [&](const sem::Array* a) -> const sem::Type* {
+            const sem::Type* target_el_ty = nullptr;
+            if (auto* target_arr_ty = As<sem::Array>(target_ty)) {
+                target_el_ty = target_arr_ty->ElemType();
+            }
+            if (auto* el_ty = ConcreteType(a->ElemType(), target_el_ty, source)) {
+                return Array(source, el_ty, a->Count(), /* explicit_stride */ 0);
+            }
+            return nullptr;
         });
 }
 
@@ -1354,7 +1366,7 @@
 
     auto* decl = expr->Declaration();
 
-    auto* concrete_ty = ConcreteType(expr->Type(), target_type);
+    auto* concrete_ty = ConcreteType(expr->Type(), target_type, decl->source);
     if (!concrete_ty) {
         return expr;  // Does not require materialization
     }
@@ -1422,6 +1434,9 @@
         //     vec2(1, 2)[runtime-index]
         obj = Materialize(obj);
     }
+    if (!obj) {
+        return nullptr;
+    }
     auto* obj_raw_ty = obj->Type();
     auto* obj_ty = obj_raw_ty->UnwrapRef();
     auto* ty = Switch(
@@ -1579,20 +1594,28 @@
                 auto* call_target = utils::GetOrCreate(
                     array_ctors_, ArrayConstructorSig{{arr, args.Length(), args_stage}},
                     [&]() -> sem::TypeConstructor* {
-                        utils::Vector<const sem::Parameter*, 8> params;
-                        params.Reserve(args.Length());
-                        for (size_t i = 0; i < args.Length(); i++) {
-                            params.Push(builder_->create<sem::Parameter>(
-                                nullptr,                    // declaration
-                                static_cast<uint32_t>(i),   // index
-                                arr->ElemType(),            // type
-                                ast::StorageClass::kNone,   // storage_class
-                                ast::Access::kUndefined));  // access
-                        }
+                        auto params = utils::Transform(args, [&](auto, size_t i) {
+                            return builder_->create<sem::Parameter>(
+                                nullptr,                   // declaration
+                                static_cast<uint32_t>(i),  // index
+                                arr->ElemType(),           // type
+                                ast::StorageClass::kNone,  // storage_class
+                                ast::Access::kUndefined);
+                        });
                         return builder_->create<sem::TypeConstructor>(arr, std::move(params),
                                                                       args_stage);
                     });
-                return arr_or_str_ctor(arr, call_target);
+
+                auto* call = arr_or_str_ctor(arr, call_target);
+                if (!call) {
+                    return nullptr;
+                }
+
+                // Validation must occur after argument materialization in arr_or_str_ctor().
+                if (!validator_.ArrayConstructor(expr, arr)) {
+                    return nullptr;
+                }
+                return call;
             },
             [&](const sem::Struct* str) -> sem::Call* {
                 auto* call_target = utils::GetOrCreate(
@@ -1611,7 +1634,17 @@
                         return builder_->create<sem::TypeConstructor>(str, std::move(params),
                                                                       args_stage);
                     });
-                return arr_or_str_ctor(str, call_target);
+
+                auto* call = arr_or_str_ctor(str, call_target);
+                if (!call) {
+                    return nullptr;
+                }
+
+                // Validation must occur after argument materialization in arr_or_str_ctor().
+                if (!validator_.StructureConstructor(expr, str)) {
+                    return nullptr;
+                }
+                return call;
             },
             [&](Default) {
                 AddError("type is not constructible", expr->source);
@@ -1659,6 +1692,59 @@
                 }
                 return nullptr;
             },
+            [&](const ast::Array* a) -> sem::Call* {
+                Mark(a);
+                // array element type must be inferred if it was not specified.
+                auto el_count = static_cast<uint32_t>(args.Length());
+                const sem::Type* el_ty = nullptr;
+                if (a->type) {
+                    el_ty = Type(a->type);
+                    if (!el_ty) {
+                        return nullptr;
+                    }
+                    if (!a->count) {
+                        AddError("cannot construct a runtime-sized array", expr->source);
+                        return nullptr;
+                    }
+                    if (auto count = ArrayCount(a->count)) {
+                        el_count = count.Get();
+                    } else {
+                        return nullptr;
+                    }
+                    // Note: validation later will detect any mismatches between explicit array
+                    // size and number of constructor expressions.
+                } else {
+                    auto arg_tys =
+                        utils::Transform(args, [](auto* arg) { return arg->Type()->UnwrapRef(); });
+                    el_ty = sem::Type::Common(arg_tys);
+                    if (!el_ty) {
+                        AddError(
+                            "cannot infer common array element type from constructor arguments",
+                            expr->source);
+                        std::unordered_set<const sem::Type*> types;
+                        for (size_t i = 0; i < args.Length(); i++) {
+                            if (types.emplace(args[i]->Type()).second) {
+                                AddNote("argument " + std::to_string(i) + " is of type '" +
+                                            sem_.TypeNameOf(args[i]->Type()) + "'",
+                                        args[i]->Declaration()->source);
+                            }
+                        }
+                        return nullptr;
+                    }
+                }
+                uint32_t explicit_stride = 0;
+                if (!ArrayAttributes(a->attributes, el_ty, explicit_stride)) {
+                    return nullptr;
+                }
+
+                auto* arr = Array(a->source, el_ty, el_count, explicit_stride);
+                if (!arr) {
+                    return nullptr;
+                }
+                builder_->Sem().Add(a, arr);
+
+                return ty_ctor_or_conv(arr);
+            },
             [&](const ast::Type* ast) -> sem::Call* {
                 // Handler for AST types that do not have an optional element type.
                 if (auto* ty = Type(ast)) {
@@ -2259,102 +2345,128 @@
 }
 
 sem::Array* Resolver::Array(const ast::Array* arr) {
-    auto source = arr->source;
-
-    auto* elem_type = Type(arr->type);
-    if (!elem_type) {
+    if (!arr->type) {
+        AddError("missing array element type", arr->source.End());
         return nullptr;
     }
 
-    if (!validator_.IsPlain(elem_type)) {  // Check must come before GetDefaultAlignAndSize()
-        AddError(sem_.TypeNameOf(elem_type) + " cannot be used as an element type of an array",
-                 source);
-        return nullptr;
-    }
-
-    uint32_t el_align = elem_type->Align();
-    uint32_t el_size = elem_type->Size();
-
-    if (!validator_.NoDuplicateAttributes(arr->attributes)) {
+    auto* el_ty = Type(arr->type);
+    if (!el_ty) {
         return nullptr;
     }
 
     // Look for explicit stride via @stride(n) attribute
     uint32_t explicit_stride = 0;
-    for (auto* attr : arr->attributes) {
+    if (!ArrayAttributes(arr->attributes, el_ty, explicit_stride)) {
+        return nullptr;
+    }
+
+    uint32_t el_count = 0;  // sem::Array uses a size of 0 for a runtime-sized array.
+
+    // Evaluate the constant array size expression.
+    if (auto* count_expr = arr->count) {
+        if (auto count = ArrayCount(count_expr)) {
+            el_count = count.Get();
+        } else {
+            return nullptr;
+        }
+    }
+
+    auto* out = Array(arr->source, el_ty, el_count, explicit_stride);
+    if (out == nullptr) {
+        return nullptr;
+    }
+
+    if (el_ty->Is<sem::Atomic>()) {
+        atomic_composite_info_.emplace(out, arr->type->source);
+    } else {
+        auto found = atomic_composite_info_.find(el_ty);
+        if (found != atomic_composite_info_.end()) {
+            atomic_composite_info_.emplace(out, found->second);
+        }
+    }
+
+    return out;
+}
+
+utils::Result<uint32_t> Resolver::ArrayCount(const ast::Expression* count_expr) {
+    // Evaluate the constant array size expression.
+    const auto* count_sem = Materialize(Expression(count_expr));
+    if (!count_sem) {
+        return utils::Failure;
+    }
+
+    auto* count_val = count_sem->ConstantValue();
+    if (!count_val) {
+        AddError("array size must evaluate to a constant integer expression", count_expr->source);
+        return utils::Failure;
+    }
+
+    if (auto* ty = count_val->Type(); !ty->is_integer_scalar()) {
+        AddError("array size must evaluate to a constant integer expression, but is type '" +
+                     builder_->FriendlyName(ty) + "'",
+                 count_expr->source);
+        return utils::Failure;
+    }
+
+    int64_t count = count_val->As<AInt>();
+    if (count < 1) {
+        AddError("array size (" + std::to_string(count) + ") must be greater than 0",
+                 count_expr->source);
+        return utils::Failure;
+    }
+
+    return static_cast<uint32_t>(count);
+}
+
+bool Resolver::ArrayAttributes(const ast::AttributeList& attributes,
+                               const sem::Type* el_ty,
+                               uint32_t& explicit_stride) {
+    if (!validator_.NoDuplicateAttributes(attributes)) {
+        return false;
+    }
+
+    for (auto* attr : attributes) {
         Mark(attr);
         if (auto* sd = attr->As<ast::StrideAttribute>()) {
             explicit_stride = sd->stride;
-            if (!validator_.ArrayStrideAttribute(sd, el_size, el_align, source)) {
-                return nullptr;
+            if (!validator_.ArrayStrideAttribute(sd, el_ty->Size(), el_ty->Align())) {
+                return false;
             }
             continue;
         }
 
         AddError("attribute is not valid for array types", attr->source);
-        return nullptr;
+        return false;
     }
 
-    // Calculate implicit stride
-    uint64_t implicit_stride = utils::RoundUp<uint64_t>(el_align, el_size);
+    return true;
+}
 
+sem::Array* Resolver::Array(const Source& source,
+                            const sem::Type* el_ty,
+                            uint32_t el_count,
+                            uint32_t explicit_stride) {
+    uint32_t el_align = el_ty->Align();
+    uint32_t el_size = el_ty->Size();
+    uint64_t implicit_stride = el_size ? utils::RoundUp<uint64_t>(el_align, el_size) : 0;
     uint64_t stride = explicit_stride ? explicit_stride : implicit_stride;
 
-    int64_t count = 0;  // sem::Array uses a size of 0 for a runtime-sized array.
-
-    // Evaluate the constant array size expression.
-    if (auto* count_expr = arr->count) {
-        const auto* count_sem = Materialize(Expression(count_expr));
-        if (!count_sem) {
-            return nullptr;
-        }
-
-        auto* count_val = count_sem->ConstantValue();
-        if (!count_val) {
-            AddError("array size must evaluate to a constant integer expression",
-                     count_expr->source);
-            return nullptr;
-        }
-
-        if (auto* ty = count_val->Type(); !ty->is_integer_scalar()) {
-            AddError("array size must evaluate to a constant integer expression, but is type '" +
-                         builder_->FriendlyName(ty) + "'",
-                     count_expr->source);
-            return nullptr;
-        }
-
-        count = count_val->As<AInt>();
-        if (count < 1) {
-            AddError("array size (" + std::to_string(count) + ") must be greater than 0",
-                     count_expr->source);
-            return nullptr;
-        }
-    }
-
-    auto size = std::max<uint64_t>(static_cast<uint32_t>(count), 1u) * stride;
+    auto size = std::max<uint64_t>(el_count, 1u) * stride;
     if (size > std::numeric_limits<uint32_t>::max()) {
         std::stringstream msg;
         msg << "array size (0x" << std::hex << size << ") must not exceed 0xffffffff bytes";
-        AddError(msg.str(), arr->source);
+        AddError(msg.str(), source);
         return nullptr;
     }
-    auto* out = builder_->create<sem::Array>(
-        elem_type, static_cast<uint32_t>(count), el_align, static_cast<uint32_t>(size),
-        static_cast<uint32_t>(stride), static_cast<uint32_t>(implicit_stride));
+    auto* out = builder_->create<sem::Array>(el_ty, el_count, el_align, static_cast<uint32_t>(size),
+                                             static_cast<uint32_t>(stride),
+                                             static_cast<uint32_t>(implicit_stride));
 
     if (!validator_.Array(out, source)) {
         return nullptr;
     }
 
-    if (elem_type->Is<sem::Atomic>()) {
-        atomic_composite_info_.emplace(out, arr->type->source);
-    } else {
-        auto found = atomic_composite_info_.find(elem_type);
-        if (found != atomic_composite_info_.end()) {
-            atomic_composite_info_.emplace(out, found->second);
-        }
-    }
-
     return out;
 }
 
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index ccd3895..fa5a569 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -233,9 +233,12 @@
     /// @param ty the type that may hold abstract numeric types
     /// @param target_ty the target type for the expression (variable type, parameter type, etc).
     ///        May be nullptr.
+    /// @param source the source of the expression requiring materialization
     /// @returns the concrete (materialized) type for the given type, or nullptr if the type is
     ///          already concrete.
-    const sem::Type* ConcreteType(const sem::Type* ty, const sem::Type* target_ty);
+    const sem::Type* ConcreteType(const sem::Type* ty,
+                                  const sem::Type* target_ty,
+                                  const Source& source);
 
     // Statement resolving methods
     // Each return true on success, false on failure.
@@ -286,14 +289,38 @@
     /// @returns the resolved semantic type
     sem::Type* TypeDecl(const ast::TypeDecl* named_type);
 
-    /// 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.
+    /// Builds and returns the semantic information for the AST 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
     sem::Array* Array(const ast::Array* arr);
 
+    /// Resolves and validates the expression used as the count parameter of an array.
+    /// @param count_expr the expression used as the second template parameter to an array<>.
+    /// @returns the number of elements in the array.
+    utils::Result<uint32_t> ArrayCount(const ast::Expression* count_expr);
+
+    /// Resolves and validates the attributes on an array.
+    /// @param attributes the attributes on the array type.
+    /// @param el_ty the element type of the array.
+    /// @param explicit_stride assigned the specified stride of the array in bytes.
+    /// @returns true on success, false on failure
+    bool ArrayAttributes(const ast::AttributeList& attributes,
+                         const sem::Type* el_ty,
+                         uint32_t& explicit_stride);
+
+    /// Builds and returns the semantic information for an array.
+    /// @returns the semantic Array information, or nullptr if an error is raised.
+    /// @param source the source of the array declaration
+    /// @param el_ty the Array element type
+    /// @param el_count the number of elements in the array. Zero means runtime-sized.
+    /// @param explicit_stride the explicit byte stride of the array. Zero means implicit stride.
+    sem::Array* Array(const Source& source,
+                      const sem::Type* el_ty,
+                      uint32_t el_count,
+                      uint32_t explicit_stride);
+
     /// Builds and returns the semantic information for the alias `alias`.
     /// This method does not mark the ast::Alias node, nor attach the generated
     /// semantic information to the AST node.
diff --git a/src/tint/resolver/resolver_test_helper.h b/src/tint/resolver/resolver_test_helper.h
index f56d822..efb215b 100644
--- a/src/tint/resolver/resolver_test_helper.h
+++ b/src/tint/resolver/resolver_test_helper.h
@@ -538,7 +538,10 @@
     /// @param b the ProgramBuilder
     /// @return a new AST array type
     static inline const ast::Type* AST(ProgramBuilder& b) {
-        return b.ty.array(DataType<T>::AST(b), u32(N));
+        if (auto* ast = DataType<T>::AST(b)) {
+            return b.ty.array(ast, u32(N));
+        }
+        return b.ty.array(nullptr, nullptr);
     }
     /// @param b the ProgramBuilder
     /// @return the semantic array type
@@ -548,7 +551,7 @@
             /* element */ el,
             /* count */ N,
             /* align */ el->Align(),
-            /* size */ el->Size(),
+            /* size */ N * el->Size(),
             /* stride */ el->Align(),
             /* implicit_stride */ el->Align());
     }
diff --git a/src/tint/resolver/type_constructor_validation_test.cc b/src/tint/resolver/type_constructor_validation_test.cc
index 0fb01bc..fe1973b 100644
--- a/src/tint/resolver/type_constructor_validation_test.cc
+++ b/src/tint/resolver/type_constructor_validation_test.cc
@@ -486,7 +486,7 @@
 
 namespace ArrayConstructor {
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Array_ZeroValue_Pass) {
+TEST_F(ResolverTypeConstructorValidationTest, Array_ZeroValue_Pass) {
     // array<u32, 10u>();
     auto* tc = array<u32, 10>();
     WrapInFunction(tc);
@@ -502,9 +502,9 @@
     ASSERT_EQ(ctor->Parameters().Length(), 0u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Array_type_match) {
-    // array<u32, 3u>(0u, 10u. 20u);
-    auto* tc = array<u32, 3>(Expr(0_u), Expr(10_u), Expr(20_u));
+TEST_F(ResolverTypeConstructorValidationTest, Array_U32U32U32) {
+    // array<u32, 3u>(0u, 10u, 20u);
+    auto* tc = array<u32, 3>(0_u, 10_u, 20_u);
     WrapInFunction(tc);
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -521,112 +521,330 @@
     EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::U32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Array_type_Mismatch_U32F32) {
+TEST_F(ResolverTypeConstructorValidationTest, InferredArray_U32U32U32) {
+    // array(0u, 10u, 20u);
+    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 0_u, 10_u, 20_u);
+    WrapInFunction(tc);
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* call = Sem().Get<sem::Call>(tc);
+    ASSERT_NE(call, nullptr);
+    EXPECT_TRUE(call->Type()->Is<sem::Array>());
+    auto* ctor = call->Target()->As<sem::TypeConstructor>();
+    ASSERT_NE(ctor, nullptr);
+    EXPECT_EQ(call->Type(), ctor->ReturnType());
+    ASSERT_EQ(ctor->Parameters().Length(), 3u);
+    EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
+    EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::U32>());
+    EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::U32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, Array_U32AIU32) {
+    // array<u32, 3u>(0u, 10, 20u);
+    auto* tc = array<u32, 3>(0_u, 10_a, 20_u);
+    WrapInFunction(tc);
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* call = Sem().Get<sem::Call>(tc);
+    ASSERT_NE(call, nullptr);
+    EXPECT_TRUE(call->Type()->Is<sem::Array>());
+    auto* ctor = call->Target()->As<sem::TypeConstructor>();
+    ASSERT_NE(ctor, nullptr);
+    EXPECT_EQ(call->Type(), ctor->ReturnType());
+    ASSERT_EQ(ctor->Parameters().Length(), 3u);
+    EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
+    EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::U32>());
+    EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::U32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferredArray_U32AIU32) {
+    // array(0u, 10u, 20u);
+    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 0_u, 10_a, 20_u);
+    WrapInFunction(tc);
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* call = Sem().Get<sem::Call>(tc);
+    ASSERT_NE(call, nullptr);
+    EXPECT_TRUE(call->Type()->Is<sem::Array>());
+    auto* ctor = call->Target()->As<sem::TypeConstructor>();
+    ASSERT_NE(ctor, nullptr);
+    EXPECT_EQ(call->Type(), ctor->ReturnType());
+    ASSERT_EQ(ctor->Parameters().Length(), 3u);
+    EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
+    EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::U32>());
+    EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::U32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, ArrayU32_AIAIAI) {
+    // array<u32, 3u>(0, 10, 20);
+    auto* tc = array<u32, 3>(0_a, 10_a, 20_a);
+    WrapInFunction(tc);
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* call = Sem().Get<sem::Call>(tc);
+    ASSERT_NE(call, nullptr);
+    EXPECT_TRUE(call->Type()->Is<sem::Array>());
+    auto* ctor = call->Target()->As<sem::TypeConstructor>();
+    ASSERT_NE(ctor, nullptr);
+    EXPECT_EQ(call->Type(), ctor->ReturnType());
+    ASSERT_EQ(ctor->Parameters().Length(), 3u);
+    EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
+    EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::U32>());
+    EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::U32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferredArray_AIAIAI) {
+    // const c = array(0, 10, 20);
+    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 0_a, 10_a, 20_a);
+    WrapInFunction(Decl(Const("C", nullptr, tc)));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* call = Sem().Get<sem::Call>(tc);
+    ASSERT_NE(call, nullptr);
+    EXPECT_TRUE(call->Type()->Is<sem::Array>());
+    auto* ctor = call->Target()->As<sem::TypeConstructor>();
+    ASSERT_NE(ctor, nullptr);
+    EXPECT_EQ(call->Type(), ctor->ReturnType());
+    ASSERT_EQ(ctor->Parameters().Length(), 3u);
+    EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::AbstractInt>());
+    EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::AbstractInt>());
+    EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::AbstractInt>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferredArrayU32_VecI32_VecAI) {
+    // array(vec2(10i), vec2(20));
+    auto* tc = array(Source{{12, 34}}, nullptr, nullptr,   //
+                     Construct(ty.vec(nullptr, 2), 20_i),  //
+                     Construct(ty.vec(nullptr, 2), 20_a));
+    WrapInFunction(tc);
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* call = Sem().Get<sem::Call>(tc);
+    ASSERT_NE(call, nullptr);
+    EXPECT_TRUE(call->Type()->Is<sem::Array>());
+    auto* ctor = call->Target()->As<sem::TypeConstructor>();
+    ASSERT_NE(ctor, nullptr);
+    EXPECT_EQ(call->Type(), ctor->ReturnType());
+    ASSERT_EQ(ctor->Parameters().Length(), 2u);
+    ASSERT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
+    EXPECT_TRUE(ctor->Parameters()[0]->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+    ASSERT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::Vector>());
+    EXPECT_TRUE(ctor->Parameters()[1]->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferredArrayU32_VecAI_VecF32) {
+    // array(vec2(20), vec2(10f));
+    auto* tc = array(Source{{12, 34}}, nullptr, nullptr,   //
+                     Construct(ty.vec(nullptr, 2), 20_a),  //
+                     Construct(ty.vec(nullptr, 2), 20_f));
+    WrapInFunction(tc);
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* call = Sem().Get<sem::Call>(tc);
+    ASSERT_NE(call, nullptr);
+    EXPECT_TRUE(call->Type()->Is<sem::Array>());
+    auto* ctor = call->Target()->As<sem::TypeConstructor>();
+    ASSERT_NE(ctor, nullptr);
+    EXPECT_EQ(call->Type(), ctor->ReturnType());
+    ASSERT_EQ(ctor->Parameters().Length(), 2u);
+    ASSERT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
+    EXPECT_TRUE(ctor->Parameters()[0]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+    ASSERT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::Vector>());
+    EXPECT_TRUE(ctor->Parameters()[1]->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, ArrayArgumentTypeMismatch_U32F32) {
     // array<u32, 3u>(0u, 1.0f, 20u);
-    auto* tc = array<u32, 3>(Expr(0_u), Expr(Source{{12, 34}}, 1_f), Expr(20_u));
+    auto* tc = array<u32, 3>(0_u, Expr(Source{{12, 34}}, 1_f), 20_u);
+    WrapInFunction(tc);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:34 error: 'f32' cannot be used to construct an array of 'u32')");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferredArrayArgumentTypeMismatch_U32F32) {
+    // array(0u, 1.0f, 20u);
+    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 0_u, 1_f, 20_u);
     WrapInFunction(tc);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "12:34 error: type in array constructor does not match array type: "
-              "expected 'u32', found 'f32'");
+              R"(12:34 error: cannot infer common array element type from constructor arguments
+note: argument 0 is of type 'u32'
+note: argument 1 is of type 'f32')");
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Array_ScalarArgumentTypeMismatch_F32I32) {
+TEST_F(ResolverTypeConstructorValidationTest, ArrayArgumentTypeMismatch_F32I32) {
     // array<f32, 1u>(1i);
     auto* tc = array<f32, 1>(Expr(Source{{12, 34}}, 1_i));
     WrapInFunction(tc);
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: type in array constructor does not match array type: "
-              "expected 'f32', found 'i32'");
+    EXPECT_EQ(r()->error(), R"(12:34 error: 'i32' cannot be used to construct an array of 'f32')");
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Array_ScalarArgumentTypeMismatch_U32I32) {
+TEST_F(ResolverTypeConstructorValidationTest, InferredArrayArgumentTypeMismatch_F32I32) {
+    // array(1f, 1i);
+    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 1_f, 1_i);
+    WrapInFunction(tc);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: cannot infer common array element type from constructor arguments
+note: argument 0 is of type 'f32'
+note: argument 1 is of type 'i32')");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, ArrayArgumentTypeMismatch_U32I32) {
     // array<u32, 1u>(1i, 0u, 0u, 0u, 0u, 0u);
-    auto* tc =
-        array<u32, 1>(Expr(Source{{12, 34}}, 1_i), Expr(0_u), Expr(0_u), Expr(0_u), Expr(0_u));
+    auto* tc = array<u32, 1>(Expr(Source{{12, 34}}, 1_i), 0_u, 0_u, 0_u, 0_u);
+    WrapInFunction(tc);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:34 error: 'i32' cannot be used to construct an array of 'u32')");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferredArrayArgumentTypeMismatch_U32I32) {
+    // array(1i, 0u, 0u, 0u, 0u, 0u);
+    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 1_i, 0_u, 0_u, 0_u, 0_u);
     WrapInFunction(tc);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "12:34 error: type in array constructor does not match array type: "
-              "expected 'u32', found 'i32'");
+              R"(12:34 error: cannot infer common array element type from constructor arguments
+note: argument 0 is of type 'i32'
+note: argument 1 is of type 'u32')");
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Array_ScalarArgumentTypeMismatch_Vec2) {
+TEST_F(ResolverTypeConstructorValidationTest, ArrayArgumentTypeMismatch_I32Vec2) {
     // array<i32, 3u>(1i, vec2<i32>());
-    auto* tc = array<i32, 3>(Expr(1_i), Construct(Source{{12, 34}}, ty.vec2<i32>()));
+    auto* tc = array<i32, 3>(1_i, vec2<i32>(Source{{12, 34}}));
     WrapInFunction(tc);
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "12:34 error: type in array constructor does not match array type: "
-              "expected 'i32', found 'vec2<i32>'");
+              R"(12:34 error: 'vec2<i32>' cannot be used to construct an array of 'i32')");
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_ArrayOfVector_SubElemTypeMismatch_I32U32) {
-    // array<vec3<i32>, 2u>(vec3<i32>(), vec3<u32>());
-    auto* e0 = vec3<i32>();
-    SetSource(Source::Location({12, 34}));
-    auto* e1 = vec3<u32>();
-    auto* t = Construct(ty.array(ty.vec3<i32>(), 2_i), e0, e1);
+TEST_F(ResolverTypeConstructorValidationTest, InferredArrayArgumentTypeMismatch_I32Vec2) {
+    // array(1i, vec2<i32>());
+    auto* tc = array(Source{{12, 34}}, nullptr, nullptr, 1_i, vec2<i32>());
+    WrapInFunction(tc);
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: cannot infer common array element type from constructor arguments
+note: argument 0 is of type 'i32'
+note: argument 1 is of type 'vec2<i32>')");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, ArrayArgumentTypeMismatch_Vec3i32_Vec3u32) {
+    // array<vec3<i32>, 2u>(vec3<u32>(), vec3<u32>());
+    auto* t = array(ty.vec3<i32>(), 2_u, vec3<u32>(Source{{12, 34}}), vec3<u32>());
     WrapInFunction(t);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "12:34 error: type in array constructor does not match array type: "
-              "expected 'vec3<i32>', found 'vec3<u32>'");
+              R"(12:34 error: 'vec3<u32>' cannot be used to construct an array of 'vec3<i32>')");
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_ArrayOfVector_SubElemTypeMismatch_I32Bool) {
-    // array<vec3<i32>, 2u>(vec3<i32>(), vec3<bool>(true, true, false));
-    SetSource(Source::Location({12, 34}));
-    auto* e0 = vec3<bool>(true, true, false);
-    auto* e1 = vec3<i32>();
-    auto* t = Construct(ty.array(ty.vec3<i32>(), 2_i), e0, e1);
+TEST_F(ResolverTypeConstructorValidationTest, InferredArrayArgumentTypeMismatch_Vec3i32_Vec3u32) {
+    // array(vec3<i32>(), vec3<u32>());
+    auto* t = array(Source{{12, 34}}, nullptr, nullptr, vec3<i32>(), vec3<u32>());
     WrapInFunction(t);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "12:34 error: type in array constructor does not match array type: "
-              "expected 'vec3<i32>', found 'vec3<bool>'");
+              R"(12:34 error: cannot infer common array element type from constructor arguments
+note: argument 0 is of type 'vec3<i32>'
+note: argument 1 is of type 'vec3<u32>')");
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_ArrayOfArray_SubElemSizeMismatch) {
+TEST_F(ResolverTypeConstructorValidationTest, InferredArrayArgumentTypeMismatch_Vec3i32_Vec3AF) {
+    // array(vec3<i32>(), vec3(1.0));
+    auto* t =
+        array(Source{{12, 34}}, nullptr, nullptr, vec3<i32>(), Construct(ty.vec3(nullptr), 1._a));
+    WrapInFunction(t);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: cannot infer common array element type from constructor arguments
+note: argument 0 is of type 'vec3<i32>'
+note: argument 1 is of type 'vec3<abstract-float>')");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, ArrayArgumentTypeMismatch_Vec3i32_Vec3bool) {
+    // array<vec3<i32>, 2u>(vec3<i32>(), vec3<bool>());
+    auto* t = array(ty.vec3<i32>(), 2_u, vec3<i32>(), vec3<bool>());
+    WrapInFunction(t);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(error: 'vec3<bool>' cannot be used to construct an array of 'vec3<i32>')");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferredArrayArgumentTypeMismatch_Vec3i32_Vec3bool) {
+    // array(vec3<i32>(), vec3<bool>());
+    auto* t = array(Source{{12, 34}}, nullptr, nullptr, vec3<i32>(), vec3<bool>());
+    WrapInFunction(t);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: cannot infer common array element type from constructor arguments
+note: argument 0 is of type 'vec3<i32>'
+note: argument 1 is of type 'vec3<bool>')");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, ArrayOfArray_SubElemSizeMismatch) {
     // array<array<i32, 2u>, 2u>(array<i32, 3u>(), array<i32, 2u>());
-    SetSource(Source::Location({12, 34}));
-    auto* e0 = array<i32, 3>();
-    auto* e1 = array<i32, 2>();
-    auto* t = Construct(ty.array(ty.array<i32, 2>(), 2_i), e0, e1);
+    auto* t = array(Source{{12, 34}}, ty.array<i32, 2>(), 2_i, array<i32, 3>(), array<i32, 2>());
     WrapInFunction(t);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "12:34 error: type in array constructor does not match array type: "
-              "expected 'array<i32, 2>', found 'array<i32, 3>'");
+              R"(error: 'array<i32, 3>' cannot be used to construct an array of 'array<i32, 2>')");
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_ArrayOfArray_SubElemTypeMismatch) {
+TEST_F(ResolverTypeConstructorValidationTest, InferredArrayOfArray_SubElemSizeMismatch) {
+    // array<array<i32, 2u>, 2u>(array<i32, 3u>(), array<i32, 2u>());
+    auto* t = array(Source{{12, 34}}, nullptr, nullptr, array<i32, 3>(), array<i32, 2>());
+    WrapInFunction(t);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: cannot infer common array element type from constructor arguments
+note: argument 0 is of type 'array<i32, 3>'
+note: argument 1 is of type 'array<i32, 2>')");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, ArrayOfArray_SubElemTypeMismatch) {
     // array<array<i32, 2u>, 2u>(array<i32, 2u>(), array<u32, 2u>());
-    auto* e0 = array<i32, 2>();
-    SetSource(Source::Location({12, 34}));
-    auto* e1 = array<u32, 2>();
-    auto* t = Construct(ty.array(ty.array<i32, 2>(), 2_i), e0, e1);
+    auto* t = array(Source{{12, 34}}, ty.array<i32, 2>(), 2_i, array<i32, 2>(), array<u32, 2>());
     WrapInFunction(t);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "12:34 error: type in array constructor does not match array type: "
-              "expected 'array<i32, 2>', found 'array<u32, 2>'");
+              R"(error: 'array<u32, 2>' cannot be used to construct an array of 'array<i32, 2>')");
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Array_TooFewElements) {
+TEST_F(ResolverTypeConstructorValidationTest, InferredArrayOfArray_SubElemTypeMismatch) {
+    // array<array<i32, 2u>, 2u>(array<i32, 2u>(), array<u32, 2u>());
+    auto* t = array(Source{{12, 34}}, nullptr, nullptr, array<i32, 2>(), array<u32, 2>());
+    WrapInFunction(t);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: cannot infer common array element type from constructor arguments
+note: argument 0 is of type 'array<i32, 2>'
+note: argument 1 is of type 'array<u32, 2>')");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, Array_TooFewElements) {
     // array<i32, 4u>(1i, 2i, 3i);
     SetSource(Source::Location({12, 34}));
     auto* tc = array<i32, 4>(Expr(1_i), Expr(2_i), Expr(3_i));
@@ -637,7 +855,7 @@
               "12:34 error: array constructor has too few elements: expected 4, found 3");
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Array_TooManyElements) {
+TEST_F(ResolverTypeConstructorValidationTest, Array_TooManyElements) {
     // array<i32, 4u>(1i, 2i, 3i, 4i, 5i);
     SetSource(Source::Location({12, 34}));
     auto* tc = array<i32, 4>(Expr(1_i), Expr(2_i), Expr(3_i), Expr(4_i), Expr(5_i));
@@ -650,29 +868,29 @@
               "found 5");
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Array_Runtime) {
+TEST_F(ResolverTypeConstructorValidationTest, Array_Runtime) {
     // array<i32>(1i);
-    auto* tc = array(ty.i32(), nullptr, Expr(Source{{12, 34}}, 1_i));
+    auto* tc = array(Source{{12, 34}}, ty.i32(), nullptr, Expr(1_i));
     WrapInFunction(tc);
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "error: cannot init a runtime-sized array");
+    EXPECT_EQ(r()->error(), "12:34 error: cannot construct a runtime-sized array");
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Array_RuntimeZeroValue) {
+TEST_F(ResolverTypeConstructorValidationTest, Array_RuntimeZeroValue) {
     // array<i32>();
-    auto* tc = array(ty.i32(), nullptr);
+    auto* tc = array(Source{{12, 34}}, ty.i32(), nullptr);
     WrapInFunction(tc);
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "error: cannot init a runtime-sized array");
+    EXPECT_EQ(r()->error(), "12:34 error: cannot construct a runtime-sized array");
 }
 
 }  // namespace ArrayConstructor
 
 namespace ScalarConstructor {
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Construct_i32_Success) {
+TEST_F(ResolverTypeConstructorValidationTest, I32_Success) {
     auto* expr = Construct<i32>(Expr(123_i));
     WrapInFunction(expr);
 
@@ -690,7 +908,7 @@
     EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Construct_u32_Success) {
+TEST_F(ResolverTypeConstructorValidationTest, U32_Success) {
     auto* expr = Construct<u32>(Expr(123_u));
     WrapInFunction(expr);
 
@@ -708,7 +926,7 @@
     EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Construct_f32_Success) {
+TEST_F(ResolverTypeConstructorValidationTest, F32_Success) {
     auto* expr = Construct<f32>(Expr(1.23_f));
     WrapInFunction(expr);
 
@@ -726,7 +944,7 @@
     EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::F32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Construct_f16_Success) {
+TEST_F(ResolverTypeConstructorValidationTest, F16_Success) {
     Enable(ast::Extension::kF16);
 
     auto* expr = Construct<f16>(Expr(1.5_h));
@@ -746,7 +964,7 @@
     EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::F16>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Convert_f32_to_i32_Success) {
+TEST_F(ResolverTypeConstructorValidationTest, Convert_f32_to_i32_Success) {
     auto* expr = Construct<i32>(1.23_f);
     WrapInFunction(expr);
 
@@ -764,7 +982,7 @@
     EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::F32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Convert_i32_to_u32_Success) {
+TEST_F(ResolverTypeConstructorValidationTest, Convert_i32_to_u32_Success) {
     auto* expr = Construct<u32>(123_i);
     WrapInFunction(expr);
 
@@ -782,7 +1000,7 @@
     EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::I32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Convert_u32_to_f16_Success) {
+TEST_F(ResolverTypeConstructorValidationTest, Convert_u32_to_f16_Success) {
     Enable(ast::Extension::kF16);
 
     auto* expr = Construct<f16>(123_u);
@@ -802,7 +1020,7 @@
     EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::U32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Convert_f16_to_f32_Success) {
+TEST_F(ResolverTypeConstructorValidationTest, Convert_f16_to_f32_Success) {
     Enable(ast::Extension::kF16);
 
     auto* expr = Construct<f32>(123_h);
@@ -826,81 +1044,74 @@
 
 namespace VectorConstructor {
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2F32_Error_ScalarArgumentTypeMismatch) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec2<f32>(), 1_i, 2_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec2F32_Error_ScalarArgumentTypeMismatch) {
+    WrapInFunction(vec2<f32>(Source{{12, 34}}, 1_i, 2_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec2<f32>(i32, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2F16_Error_ScalarArgumentTypeMismatch) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec2F16_Error_ScalarArgumentTypeMismatch) {
     Enable(ast::Extension::kF16);
 
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec2<f16>(), 1_h, 2_f));
+    WrapInFunction(vec2<f16>(Source{{12, 34}}, 1_h, 2_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec2<f16>(f16, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2U32_Error_ScalarArgumentTypeMismatch) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec2<u32>(), 1_u, 2_i));
+TEST_F(ResolverTypeConstructorValidationTest, Vec2U32_Error_ScalarArgumentTypeMismatch) {
+    WrapInFunction(vec2<u32>(Source{{12, 34}}, 1_u, 2_i));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec2<u32>(u32, i32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2I32_Error_ScalarArgumentTypeMismatch) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec2<i32>(), 1_u, 2_i));
+TEST_F(ResolverTypeConstructorValidationTest, Vec2I32_Error_ScalarArgumentTypeMismatch) {
+    WrapInFunction(vec2<i32>(Source{{12, 34}}, 1_u, 2_i));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec2<i32>(u32, i32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2Bool_Error_ScalarArgumentTypeMismatch) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec2<bool>(), true, 1_i));
+TEST_F(ResolverTypeConstructorValidationTest, Vec2Bool_Error_ScalarArgumentTypeMismatch) {
+    WrapInFunction(vec2<bool>(Source{{12, 34}}, true, 1_i));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec2<bool>(bool, i32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2_Error_Vec3ArgumentCardinalityTooLarge) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec2<f32>(), vec3<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec2_Error_Vec3ArgumentCardinalityTooLarge) {
+    WrapInFunction(vec2<f32>(Source{{12, 34}}, vec3<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec2<f32>(vec3<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2_Error_Vec4ArgumentCardinalityTooLarge) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec2<f32>(), vec4<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec2_Error_Vec4ArgumentCardinalityTooLarge) {
+    WrapInFunction(vec2<f32>(Source{{12, 34}}, vec4<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec2<f32>(vec4<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec2_Error_TooManyArgumentsScalar) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec2<f32>(), 1_f, 2_f, 3_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec2_Error_TooManyArgumentsScalar) {
+    WrapInFunction(vec2<f32>(Source{{12, 34}}, 1_f, 2_f, 3_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec2<f32>(f32, f32, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec2_Error_TooManyArgumentsVector) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec2<f32>(), vec2<f32>(), vec2<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec2_Error_TooManyArgumentsVector) {
+    WrapInFunction(vec2<f32>(Source{{12, 34}}, vec2<f32>(), vec2<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -908,24 +1119,23 @@
         HasSubstr("12:34 error: no matching constructor for vec2<f32>(vec2<f32>, vec2<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec2_Error_TooManyArgumentsVectorAndScalar) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec2<f32>(), vec2<f32>(), 1_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec2_Error_TooManyArgumentsVectorAndScalar) {
+    WrapInFunction(vec2<f32>(Source{{12, 34}}, vec2<f32>(), 1_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec2<f32>(vec2<f32>, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec2_Error_InvalidArgumentType) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec2<f32>(), mat2x2<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec2_Error_InvalidArgumentType) {
+    WrapInFunction(vec2<f32>(Source{{12, 34}}, mat2x2<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec2<f32>(mat2x2<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec2_Success_ZeroValue) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec2_Success_ZeroValue) {
     auto* tc = vec2<f32>();
     WrapInFunction(tc);
 
@@ -944,7 +1154,7 @@
     ASSERT_EQ(ctor->Parameters().Length(), 0u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec2F32_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec2F32_Success_Scalar) {
     auto* tc = vec2<f32>(1_f, 1_f);
     WrapInFunction(tc);
 
@@ -965,7 +1175,7 @@
     EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::F32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec2F16_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec2F16_Success_Scalar) {
     Enable(ast::Extension::kF16);
 
     auto* tc = vec2<f16>(1_h, 1_h);
@@ -988,7 +1198,7 @@
     EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::F16>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec2U32_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec2U32_Success_Scalar) {
     auto* tc = vec2<u32>(1_u, 1_u);
     WrapInFunction(tc);
 
@@ -1009,7 +1219,7 @@
     EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::U32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec2I32_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec2I32_Success_Scalar) {
     auto* tc = vec2<i32>(1_i, 1_i);
     WrapInFunction(tc);
 
@@ -1030,7 +1240,7 @@
     EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::I32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec2Bool_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec2Bool_Success_Scalar) {
     auto* tc = vec2<bool>(true, false);
     WrapInFunction(tc);
 
@@ -1051,7 +1261,7 @@
     EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::Bool>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec2_Success_Identity) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec2_Success_Identity) {
     auto* tc = vec2<f32>(vec2<f32>());
     WrapInFunction(tc);
 
@@ -1071,7 +1281,7 @@
     EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec2_Success_Vec2TypeConversion) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec2_Success_Vec2TypeConversion) {
     auto* tc = vec2<f32>(vec2<i32>());
     WrapInFunction(tc);
 
@@ -1091,72 +1301,66 @@
     EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3F32_Error_ScalarArgumentTypeMismatch) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<f32>(), 1_f, 2_f, 3_i));
+TEST_F(ResolverTypeConstructorValidationTest, Vec3F32_Error_ScalarArgumentTypeMismatch) {
+    WrapInFunction(vec3<f32>(Source{{12, 34}}, 1_f, 2_f, 3_i));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec3<f32>(f32, f32, i32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3F16_Error_ScalarArgumentTypeMismatch) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec3F16_Error_ScalarArgumentTypeMismatch) {
     Enable(ast::Extension::kF16);
 
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<f16>(), 1_h, 2_h, 3_f));
+    WrapInFunction(vec3<f16>(Source{{12, 34}}, 1_h, 2_h, 3_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec3<f16>(f16, f16, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3U32_Error_ScalarArgumentTypeMismatch) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<u32>(), 1_u, 2_i, 3_u));
+TEST_F(ResolverTypeConstructorValidationTest, Vec3U32_Error_ScalarArgumentTypeMismatch) {
+    WrapInFunction(vec3<u32>(Source{{12, 34}}, 1_u, 2_i, 3_u));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec3<u32>(u32, i32, u32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3I32_Error_ScalarArgumentTypeMismatch) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<i32>(), 1_i, 2_u, 3_i));
+TEST_F(ResolverTypeConstructorValidationTest, Vec3I32_Error_ScalarArgumentTypeMismatch) {
+    WrapInFunction(vec3<i32>(Source{{12, 34}}, 1_i, 2_u, 3_i));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec3<i32>(i32, u32, i32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3Bool_Error_ScalarArgumentTypeMismatch) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<bool>(), false, 1_i, true));
+TEST_F(ResolverTypeConstructorValidationTest, Vec3Bool_Error_ScalarArgumentTypeMismatch) {
+    WrapInFunction(vec3<bool>(Source{{12, 34}}, false, 1_i, true));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec3<bool>(bool, i32, bool)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Error_Vec4ArgumentCardinalityTooLarge) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<f32>(), vec4<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec3_Error_Vec4ArgumentCardinalityTooLarge) {
+    WrapInFunction(vec3<f32>(Source{{12, 34}}, vec4<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec3<f32>(vec4<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3_Error_TooFewArgumentsScalar) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<f32>(), 1_f, 2_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec3_Error_TooFewArgumentsScalar) {
+    WrapInFunction(vec3<f32>(Source{{12, 34}}, 1_f, 2_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec3<f32>(f32, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3_Error_TooManyArgumentsScalar) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<f32>(), 1_f, 2_f, 3_f, 4_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec3_Error_TooManyArgumentsScalar) {
+    WrapInFunction(vec3<f32>(Source{{12, 34}}, 1_f, 2_f, 3_f, 4_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1164,16 +1368,16 @@
         HasSubstr("12:34 error: no matching constructor for vec3<f32>(f32, f32, f32, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3_Error_TooFewArgumentsVec2) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<f32>(), vec2<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec3_Error_TooFewArgumentsVec2) {
+    WrapInFunction(vec3<f32>(Source{{12, 34}}, vec2<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec3<f32>(vec2<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3_Error_TooManyArgumentsVec2) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<f32>(), vec2<f32>(), vec2<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec3_Error_TooManyArgumentsVec2) {
+    WrapInFunction(vec3<f32>(Source{{12, 34}}, vec2<f32>(), vec2<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1181,9 +1385,8 @@
         HasSubstr("12:34 error: no matching constructor for vec3<f32>(vec2<f32>, vec2<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec3_Error_TooManyArgumentsVec2AndScalar) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<f32>(), vec2<f32>(), 1_f, 1_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec3_Error_TooManyArgumentsVec2AndScalar) {
+    WrapInFunction(vec3<f32>(Source{{12, 34}}, vec2<f32>(), 1_f, 1_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1191,23 +1394,23 @@
         HasSubstr("12:34 error: no matching constructor for vec3<f32>(vec2<f32>, f32, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3_Error_TooManyArgumentsVec3) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<f32>(), vec3<f32>(), 1_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec3_Error_TooManyArgumentsVec3) {
+    WrapInFunction(vec3<f32>(Source{{12, 34}}, vec3<f32>(), 1_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec3<f32>(vec3<f32>, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3_Error_InvalidArgumentType) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<f32>(), mat2x2<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec3_Error_InvalidArgumentType) {
+    WrapInFunction(vec3<f32>(Source{{12, 34}}, mat2x2<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec3<f32>(mat2x2<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3_Success_ZeroValue) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec3_Success_ZeroValue) {
     auto* tc = vec3<f32>();
     WrapInFunction(tc);
 
@@ -1226,7 +1429,7 @@
     ASSERT_EQ(ctor->Parameters().Length(), 0u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3F32_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec3F32_Success_Scalar) {
     auto* tc = vec3<f32>(1_f, 1_f, 1_f);
     WrapInFunction(tc);
 
@@ -1248,7 +1451,7 @@
     EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::F32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3F16_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec3F16_Success_Scalar) {
     Enable(ast::Extension::kF16);
 
     auto* tc = vec3<f16>(1_h, 1_h, 1_h);
@@ -1272,7 +1475,7 @@
     EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::F16>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3U32_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec3U32_Success_Scalar) {
     auto* tc = vec3<u32>(1_u, 1_u, 1_u);
     WrapInFunction(tc);
 
@@ -1294,7 +1497,7 @@
     EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::U32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3I32_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec3I32_Success_Scalar) {
     auto* tc = vec3<i32>(1_i, 1_i, 1_i);
     WrapInFunction(tc);
 
@@ -1316,7 +1519,7 @@
     EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::I32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3Bool_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec3Bool_Success_Scalar) {
     auto* tc = vec3<bool>(true, false, true);
     WrapInFunction(tc);
 
@@ -1338,7 +1541,7 @@
     EXPECT_TRUE(ctor->Parameters()[2]->Type()->Is<sem::Bool>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3_Success_Vec2AndScalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec3_Success_Vec2AndScalar) {
     auto* tc = vec3<f32>(vec2<f32>(), 1_f);
     WrapInFunction(tc);
 
@@ -1359,7 +1562,7 @@
     EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::F32>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3_Success_ScalarAndVec2) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec3_Success_ScalarAndVec2) {
     auto* tc = vec3<f32>(1_f, vec2<f32>());
     WrapInFunction(tc);
 
@@ -1380,7 +1583,7 @@
     EXPECT_TRUE(ctor->Parameters()[1]->Type()->Is<sem::Vector>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3_Success_Identity) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec3_Success_Identity) {
     auto* tc = vec3<f32>(vec3<f32>());
     WrapInFunction(tc);
 
@@ -1400,7 +1603,7 @@
     EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec3_Success_Vec3TypeConversion) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec3_Success_Vec3TypeConversion) {
     auto* tc = vec3<f32>(vec3<i32>());
     WrapInFunction(tc);
 
@@ -1420,9 +1623,8 @@
     EXPECT_TRUE(ctor->Parameters()[0]->Type()->Is<sem::Vector>());
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4F32_Error_ScalarArgumentTypeMismatch) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<f32>(), 1_f, 1_f, 1_i, 1_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4F32_Error_ScalarArgumentTypeMismatch) {
+    WrapInFunction(vec4<f32>(Source{{12, 34}}, 1_f, 1_f, 1_i, 1_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1430,11 +1632,10 @@
         HasSubstr("12:34 error: no matching constructor for vec4<f32>(f32, f32, i32, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4F16_Error_ScalarArgumentTypeMismatch) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4F16_Error_ScalarArgumentTypeMismatch) {
     Enable(ast::Extension::kF16);
 
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<f16>(), 1_h, 1_h, 1_f, 1_h));
+    WrapInFunction(vec4<f16>(Source{{12, 34}}, 1_h, 1_h, 1_f, 1_h));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1442,9 +1643,8 @@
         HasSubstr("12:34 error: no matching constructor for vec4<f16>(f16, f16, f32, f16)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4U32_Error_ScalarArgumentTypeMismatch) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<u32>(), 1_u, 1_u, 1_i, 1_u));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4U32_Error_ScalarArgumentTypeMismatch) {
+    WrapInFunction(vec4<u32>(Source{{12, 34}}, 1_u, 1_u, 1_i, 1_u));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1452,9 +1652,8 @@
         HasSubstr("12:34 error: no matching constructor for vec4<u32>(u32, u32, i32, u32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4I32_Error_ScalarArgumentTypeMismatch) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<i32>(), 1_i, 1_i, 1_u, 1_i));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4I32_Error_ScalarArgumentTypeMismatch) {
+    WrapInFunction(vec4<i32>(Source{{12, 34}}, 1_i, 1_i, 1_u, 1_i));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1462,9 +1661,8 @@
         HasSubstr("12:34 error: no matching constructor for vec4<i32>(i32, i32, u32, i32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4Bool_Error_ScalarArgumentTypeMismatch) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<bool>(), true, false, 1_i, true));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4Bool_Error_ScalarArgumentTypeMismatch) {
+    WrapInFunction(vec4<bool>(Source{{12, 34}}, true, false, 1_i, true));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1472,16 +1670,16 @@
         HasSubstr("12:34 error: no matching constructor for vec4<bool>(bool, bool, i32, bool)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4_Error_TooFewArgumentsScalar) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<f32>(), 1_f, 2_f, 3_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Error_TooFewArgumentsScalar) {
+    WrapInFunction(vec4<f32>(Source{{12, 34}}, 1_f, 2_f, 3_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec4<f32>(f32, f32, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4_Error_TooManyArgumentsScalar) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<f32>(), 1_f, 2_f, 3_f, 4_f, 5_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Error_TooManyArgumentsScalar) {
+    WrapInFunction(vec4<f32>(Source{{12, 34}}, 1_f, 2_f, 3_f, 4_f, 5_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1489,18 +1687,16 @@
         HasSubstr("12:34 error: no matching constructor for vec4<f32>(f32, f32, f32, f32, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooFewArgumentsVec2AndScalar) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<f32>(), vec2<f32>(), 1_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Error_TooFewArgumentsVec2AndScalar) {
+    WrapInFunction(vec4<f32>(Source{{12, 34}}, vec2<f32>(), 1_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec4<f32>(vec2<f32>, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2AndScalars) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<f32>(), vec2<f32>(), 1_f, 2_f, 3_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Error_TooManyArgumentsVec2AndScalars) {
+    WrapInFunction(vec4<f32>(Source{{12, 34}}, vec2<f32>(), 1_f, 2_f, 3_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1508,9 +1704,8 @@
         HasSubstr("12:34 error: no matching constructor for vec4<f32>(vec2<f32>, f32, f32, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2Vec2Scalar) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<f32>(), vec2<f32>(), vec2<f32>(), 1_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Error_TooManyArgumentsVec2Vec2Scalar) {
+    WrapInFunction(vec4<f32>(Source{{12, 34}}, vec2<f32>(), vec2<f32>(), 1_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1518,10 +1713,8 @@
         HasSubstr("12:34 error: no matching constructor for vec4<f32>(vec2<f32>, vec2<f32>, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2Vec2Vec2) {
-    WrapInFunction(
-        Construct(Source{{12, 34}}, ty.vec4<f32>(), vec2<f32>(), vec2<f32>(), vec2<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Error_TooManyArgumentsVec2Vec2Vec2) {
+    WrapInFunction(vec4<f32>(Source{{12, 34}}, vec2<f32>(), vec2<f32>(), vec2<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1530,17 +1723,16 @@
             "12:34 error: no matching constructor for vec4<f32>(vec2<f32>, vec2<f32>, vec2<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4_Error_TooFewArgumentsVec3) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<f32>(), vec3<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Error_TooFewArgumentsVec3) {
+    WrapInFunction(vec4<f32>(Source{{12, 34}}, vec3<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec4<f32>(vec3<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec3AndScalars) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<f32>(), vec3<f32>(), 1_f, 2_f));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Error_TooManyArgumentsVec3AndScalars) {
+    WrapInFunction(vec4<f32>(Source{{12, 34}}, vec3<f32>(), 1_f, 2_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1548,9 +1740,8 @@
         HasSubstr("12:34 error: no matching constructor for vec4<f32>(vec3<f32>, f32, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec3AndVec2) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<f32>(), vec3<f32>(), vec2<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Error_TooManyArgumentsVec3AndVec2) {
+    WrapInFunction(vec4<f32>(Source{{12, 34}}, vec3<f32>(), vec2<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1558,9 +1749,8 @@
         HasSubstr("12:34 error: no matching constructor for vec4<f32>(vec3<f32>, vec2<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2AndVec3) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<f32>(), vec2<f32>(), vec3<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Error_TooManyArgumentsVec2AndVec3) {
+    WrapInFunction(vec4<f32>(Source{{12, 34}}, vec2<f32>(), vec3<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1568,9 +1758,8 @@
         HasSubstr("12:34 error: no matching constructor for vec4<f32>(vec2<f32>, vec3<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vec4_Error_TooManyArgumentsVec3AndVec3) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<f32>(), vec3<f32>(), vec3<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Error_TooManyArgumentsVec3AndVec3) {
+    WrapInFunction(vec4<f32>(Source{{12, 34}}, vec3<f32>(), vec3<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(
@@ -1578,15 +1767,15 @@
         HasSubstr("12:34 error: no matching constructor for vec4<f32>(vec3<f32>, vec3<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4_Error_InvalidArgumentType) {
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec4<f32>(), mat2x2<f32>()));
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Error_InvalidArgumentType) {
+    WrapInFunction(vec4<f32>(Source{{12, 34}}, mat2x2<f32>()));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec4<f32>(mat2x2<f32>)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4_Success_ZeroValue) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Success_ZeroValue) {
     auto* tc = vec4<f32>();
     WrapInFunction(tc);
 
@@ -1598,7 +1787,7 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4F32_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4F32_Success_Scalar) {
     auto* tc = vec4<f32>(1_f, 1_f, 1_f, 1_f);
     WrapInFunction(tc);
 
@@ -1610,7 +1799,7 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4F16_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4F16_Success_Scalar) {
     Enable(ast::Extension::kF16);
 
     auto* tc = vec4<f16>(1_h, 1_h, 1_h, 1_h);
@@ -1624,7 +1813,7 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4U32_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4U32_Success_Scalar) {
     auto* tc = vec4<u32>(1_u, 1_u, 1_u, 1_u);
     WrapInFunction(tc);
 
@@ -1636,7 +1825,7 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4I32_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4I32_Success_Scalar) {
     auto* tc = vec4<i32>(1_i, 1_i, 1_i, 1_i);
     WrapInFunction(tc);
 
@@ -1648,7 +1837,7 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4Bool_Success_Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4Bool_Success_Scalar) {
     auto* tc = vec4<bool>(true, false, true, false);
     WrapInFunction(tc);
 
@@ -1660,7 +1849,7 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4_Success_Vec2ScalarScalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Success_Vec2ScalarScalar) {
     auto* tc = vec4<f32>(vec2<f32>(), 1_f, 1_f);
     WrapInFunction(tc);
 
@@ -1672,7 +1861,7 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4_Success_ScalarVec2Scalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Success_ScalarVec2Scalar) {
     auto* tc = vec4<f32>(1_f, vec2<f32>(), 1_f);
     WrapInFunction(tc);
 
@@ -1684,7 +1873,7 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4_Success_ScalarScalarVec2) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Success_ScalarScalarVec2) {
     auto* tc = vec4<f32>(1_f, 1_f, vec2<f32>());
     WrapInFunction(tc);
 
@@ -1696,7 +1885,7 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4_Success_Vec2AndVec2) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Success_Vec2AndVec2) {
     auto* tc = vec4<f32>(vec2<f32>(), vec2<f32>());
     WrapInFunction(tc);
 
@@ -1708,7 +1897,7 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4_Success_Vec3AndScalar) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Success_Vec3AndScalar) {
     auto* tc = vec4<f32>(vec3<f32>(), 1_f);
     WrapInFunction(tc);
 
@@ -1720,7 +1909,7 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4_Success_ScalarAndVec3) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Success_ScalarAndVec3) {
     auto* tc = vec4<f32>(1_f, vec3<f32>());
     WrapInFunction(tc);
 
@@ -1732,7 +1921,7 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4_Success_Identity) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Success_Identity) {
     auto* tc = vec4<f32>(vec4<f32>());
     WrapInFunction(tc);
 
@@ -1744,7 +1933,7 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vec4_Success_Vec4TypeConversion) {
+TEST_F(ResolverTypeConstructorValidationTest, Vec4_Success_Vec4TypeConversion) {
     auto* tc = vec4<f32>(vec4<i32>());
     WrapInFunction(tc);
 
@@ -1756,10 +1945,9 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_NestedVectorConstructors_InnerError) {
+TEST_F(ResolverTypeConstructorValidationTest, NestedVectorConstructors_InnerError) {
     WrapInFunction(vec4<f32>(vec4<f32>(1_f, 1_f,  //
-                                       Construct(Source{{12, 34}}, ty.vec3<f32>(), 1_f, 1_f)),
+                                       vec3<f32>(Source{{12, 34}}, 1_f, 1_f)),
                              1_f));
 
     EXPECT_FALSE(r()->Resolve());
@@ -1767,7 +1955,7 @@
                 HasSubstr("12:34 error: no matching constructor for vec3<f32>(f32, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_NestedVectorConstructors_Success) {
+TEST_F(ResolverTypeConstructorValidationTest, NestedVectorConstructors_Success) {
     auto* tc = vec4<f32>(vec3<f32>(vec2<f32>(1_f, 1_f), 1_f), 1_f);
     WrapInFunction(tc);
 
@@ -1779,18 +1967,18 @@
     EXPECT_EQ(TypeOf(tc)->As<sem::Vector>()->Width(), 4u);
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vector_Alias_Argument_Error) {
+TEST_F(ResolverTypeConstructorValidationTest, Vector_Alias_Argument_Error) {
     auto* alias = Alias("UnsignedInt", ty.u32());
     GlobalVar("uint_var", ty.Of(alias), ast::StorageClass::kPrivate);
 
-    auto* tc = Construct(Source{{12, 34}}, ty.vec2<f32>(), "uint_var");
+    auto* tc = vec2<f32>(Source{{12, 34}}, "uint_var");
     WrapInFunction(tc);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(), HasSubstr("12:34 error: no matching constructor for vec2<f32>(u32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vector_Alias_Argument_Success) {
+TEST_F(ResolverTypeConstructorValidationTest, Vector_Alias_Argument_Success) {
     auto* f32_alias = Alias("Float32", ty.f32());
     auto* vec2_alias = Alias("VectorFloat2", ty.vec2<f32>());
     GlobalVar("my_f32", ty.Of(f32_alias), ast::StorageClass::kPrivate);
@@ -1801,7 +1989,7 @@
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vector_ElementTypeAlias_Error) {
+TEST_F(ResolverTypeConstructorValidationTest, Vector_ElementTypeAlias_Error) {
     auto* f32_alias = Alias("Float32", ty.f32());
 
     // vec2<Float32>(1.0f, 1u)
@@ -1813,7 +2001,7 @@
                 HasSubstr("12:34 error: no matching constructor for vec2<f32>(f32, u32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Vector_ElementTypeAlias_Success) {
+TEST_F(ResolverTypeConstructorValidationTest, Vector_ElementTypeAlias_Success) {
     auto* f32_alias = Alias("Float32", ty.f32());
 
     // vec2<Float32>(1.0f, 1.0f)
@@ -1824,21 +2012,19 @@
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vector_ArgumentElementTypeAlias_Error) {
+TEST_F(ResolverTypeConstructorValidationTest, Vector_ArgumentElementTypeAlias_Error) {
     auto* f32_alias = Alias("Float32", ty.f32());
 
     // vec3<u32>(vec<Float32>(), 1.0f)
     auto* vec_type = ty.vec(ty.Of(f32_alias), 2);
-    WrapInFunction(Construct(Source{{12, 34}}, ty.vec3<u32>(), Construct(vec_type), 1_f));
+    WrapInFunction(vec3<u32>(Source{{12, 34}}, Construct(vec_type), 1_f));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_THAT(r()->error(),
                 HasSubstr("12:34 error: no matching constructor for vec3<u32>(vec2<f32>, f32)"));
 }
 
-TEST_F(ResolverTypeConstructorValidationTest,
-       Expr_Constructor_Vector_ArgumentElementTypeAlias_Success) {
+TEST_F(ResolverTypeConstructorValidationTest, Vector_ArgumentElementTypeAlias_Success) {
     auto* f32_alias = Alias("Float32", ty.f32());
 
     // vec3<f32>(vec<Float32>(), 1.0f)
@@ -2278,7 +2464,7 @@
 
 using MatrixConstructorTest = ResolverTestWithParam<MatrixParams>;
 
-TEST_P(MatrixConstructorTest, Expr_ColumnConstructor_Error_TooFewArguments) {
+TEST_P(MatrixConstructorTest, ColumnConstructor_Error_TooFewArguments) {
     // matNxM<f32>(vecM<f32>(), ...); with N - 1 arguments
     // matNxM<f16>(vecM<f16>(), ...); with N - 1 arguments
 
@@ -2307,7 +2493,7 @@
                                         MatrixStr(param) + "(" + args_tys.str() + ")"));
 }
 
-TEST_P(MatrixConstructorTest, Expr_ElementConstructor_Error_TooFewArguments) {
+TEST_P(MatrixConstructorTest, ElementConstructor_Error_TooFewArguments) {
     // matNxM<f32>(f32,...,f32); with N*M - 1 arguments
     // matNxM<f16>(f16,...,f16); with N*M - 1 arguments
 
@@ -2335,7 +2521,7 @@
                                         MatrixStr(param) + "(" + args_tys.str() + ")"));
 }
 
-TEST_P(MatrixConstructorTest, Expr_ColumnConstructor_Error_TooManyArguments) {
+TEST_P(MatrixConstructorTest, ColumnConstructor_Error_TooManyArguments) {
     // matNxM<f32>(vecM<f32>(), ...); with N + 1 arguments
     // matNxM<f16>(vecM<f16>(), ...); with N + 1 arguments
 
@@ -2364,7 +2550,7 @@
                                         MatrixStr(param) + "(" + args_tys.str() + ")"));
 }
 
-TEST_P(MatrixConstructorTest, Expr_ElementConstructor_Error_TooManyArguments) {
+TEST_P(MatrixConstructorTest, ElementConstructor_Error_TooManyArguments) {
     // matNxM<f32>(f32,...,f32); with N*M + 1 arguments
     // matNxM<f16>(f16,...,f16); with N*M + 1 arguments
 
@@ -2392,7 +2578,7 @@
                                         MatrixStr(param) + "(" + args_tys.str() + ")"));
 }
 
-TEST_P(MatrixConstructorTest, Expr_ColumnConstructor_Error_InvalidArgumentType) {
+TEST_P(MatrixConstructorTest, ColumnConstructor_Error_InvalidArgumentType) {
     // matNxM<f32>(vec<u32>, vec<u32>, ...); N arguments
     // matNxM<f16>(vec<u32>, vec<u32>, ...); N arguments
 
@@ -2420,7 +2606,7 @@
                                         MatrixStr(param) + "(" + args_tys.str() + ")"));
 }
 
-TEST_P(MatrixConstructorTest, Expr_ElementConstructor_Error_InvalidArgumentType) {
+TEST_P(MatrixConstructorTest, ElementConstructor_Error_InvalidArgumentType) {
     // matNxM<f32>(u32, u32, ...); N*M arguments
     // matNxM<f16>(u32, u32, ...); N*M arguments
 
@@ -2447,7 +2633,7 @@
                                         MatrixStr(param) + "(" + args_tys.str() + ")"));
 }
 
-TEST_P(MatrixConstructorTest, Expr_ColumnConstructor_Error_TooFewRowsInVectorArgument) {
+TEST_P(MatrixConstructorTest, ColumnConstructor_Error_TooFewRowsInVectorArgument) {
     // matNxM<f32>(vecM<f32>(),...,vecM-1<f32>());
     // matNxM<f16>(vecM<f16>(),...,vecM-1<f32>());
 
@@ -2485,7 +2671,7 @@
                                         MatrixStr(param) + "(" + args_tys.str() + ")"));
 }
 
-TEST_P(MatrixConstructorTest, Expr_ColumnConstructor_Error_TooManyRowsInVectorArgument) {
+TEST_P(MatrixConstructorTest, ColumnConstructor_Error_TooManyRowsInVectorArgument) {
     // matNxM<f32>(vecM<f32>(),...,vecM+1<f32>());
     // matNxM<f16>(vecM<f16>(),...,vecM+1<f16>());
 
@@ -2522,7 +2708,7 @@
                                         MatrixStr(param) + "(" + args_tys.str() + ")"));
 }
 
-TEST_P(MatrixConstructorTest, Expr_Constructor_ZeroValue_Success) {
+TEST_P(MatrixConstructorTest, ZeroValue_Success) {
     // matNxM<f32>();
     // matNxM<f16>();
 
@@ -2537,7 +2723,7 @@
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_P(MatrixConstructorTest, Expr_Constructor_WithColumns_Success) {
+TEST_P(MatrixConstructorTest, WithColumns_Success) {
     // matNxM<f32>(vecM<f32>(), ...); with N arguments
     // matNxM<f16>(vecM<f16>(), ...); with N arguments
 
@@ -2558,7 +2744,7 @@
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_P(MatrixConstructorTest, Expr_Constructor_WithElements_Success) {
+TEST_P(MatrixConstructorTest, WithElements_Success) {
     // matNxM<f32>(f32,...,f32); with N*M arguments
     // matNxM<f16>(f16,...,f16); with N*M arguments
 
@@ -2578,7 +2764,7 @@
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_P(MatrixConstructorTest, Expr_Constructor_ElementTypeAlias_Error) {
+TEST_P(MatrixConstructorTest, ElementTypeAlias_Error) {
     // matNxM<Float32>(vecM<u32>(), ...); with N arguments
     // matNxM<Float16>(vecM<u32>(), ...); with N arguments
 
@@ -2608,7 +2794,7 @@
                                         MatrixStr(param) + "(" + args_tys.str() + ")"));
 }
 
-TEST_P(MatrixConstructorTest, Expr_Constructor_ElementTypeAlias_Success) {
+TEST_P(MatrixConstructorTest, ElementTypeAlias_Success) {
     // matNxM<Float32>(vecM<f32>(), ...); with N arguments
     // matNxM<Float16>(vecM<f16>(), ...); with N arguments
 
@@ -2631,7 +2817,7 @@
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_MatrixConstructor_ArgumentTypeAlias_Error) {
+TEST_F(ResolverTypeConstructorValidationTest, MatrixConstructor_ArgumentTypeAlias_Error) {
     auto* alias = Alias("VectorUnsigned2", ty.vec2<u32>());
     auto* tc = Construct(Source{{12, 34}}, ty.mat2x2<f32>(), Construct(ty.Of(alias)), vec2<f32>());
     WrapInFunction(tc);
@@ -2642,7 +2828,7 @@
         HasSubstr("12:34 error: no matching constructor for mat2x2<f32>(vec2<u32>, vec2<f32>)"));
 }
 
-TEST_P(MatrixConstructorTest, Expr_Constructor_ArgumentTypeAlias_Success) {
+TEST_P(MatrixConstructorTest, ArgumentTypeAlias_Success) {
     const auto param = GetParam();
 
     Enable(ast::Extension::kF16);
@@ -2662,7 +2848,7 @@
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_P(MatrixConstructorTest, Expr_Constructor_ArgumentElementTypeAlias_Error) {
+TEST_P(MatrixConstructorTest, ArgumentElementTypeAlias_Error) {
     const auto param = GetParam();
 
     Enable(ast::Extension::kF16);
@@ -2689,7 +2875,7 @@
                                         MatrixStr(param) + "(" + args_tys.str() + ")"));
 }
 
-TEST_P(MatrixConstructorTest, Expr_Constructor_ArgumentElementTypeAlias_Success) {
+TEST_P(MatrixConstructorTest, ArgumentElementTypeAlias_Success) {
     const auto param = GetParam();
 
     Enable(ast::Extension::kF16);
@@ -2961,7 +3147,7 @@
                                           testing::ValuesIn(all_types),
                                           number_of_members));
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Struct_Nested) {
+TEST_F(ResolverTypeConstructorValidationTest, Struct_Nested) {
     auto* inner_m = Member("m", ty.i32());
     auto* inner_s = Structure("inner_s", {inner_m});
 
@@ -2978,7 +3164,7 @@
               "type: expected 'inner_s', found 'i32'");
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Struct) {
+TEST_F(ResolverTypeConstructorValidationTest, Struct) {
     auto* m = Member("m", ty.i32());
     auto* s = Structure("MyInputs", {m});
     auto* tc = Construct(Source{{12, 34}}, ty.Of(s));
@@ -2986,7 +3172,7 @@
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Struct_Empty) {
+TEST_F(ResolverTypeConstructorValidationTest, Struct_Empty) {
     auto* str = Structure("S", {
                                    Member("a", ty.i32()),
                                    Member("b", ty.f32()),
@@ -3030,7 +3216,7 @@
 }
 
 TEST_F(ResolverTypeConstructorValidationTest, TypeConstructorAsStatement) {
-    WrapInFunction(CallStmt(Construct(Source{{12, 34}}, ty.vec2<f32>(), 1_f, 2_f)));
+    WrapInFunction(CallStmt(vec2<f32>(Source{{12, 34}}, 1_f, 2_f)));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: type constructor evaluated but not used");
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 940e154..ec858c4 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -1495,28 +1495,21 @@
     bool is_call_stmt =
         current_statement && Is<ast::CallStatement>(current_statement->Declaration(),
                                                     [&](auto* stmt) { return stmt->expr == expr; });
-
-    return Switch(
-        call->Target(),  //
-        [&](const sem::TypeConversion*) {
-            if (is_call_stmt) {
+    if (is_call_stmt) {
+        return Switch(
+            call->Target(),  //
+            [&](const sem::TypeConversion*) {
                 AddError("type conversion evaluated but not used", call->Declaration()->source);
                 return false;
-            }
-            return true;
-        },
-        [&](const sem::TypeConstructor* ctor) {
-            if (is_call_stmt) {
+            },
+            [&](const sem::TypeConstructor*) {
                 AddError("type constructor evaluated but not used", call->Declaration()->source);
                 return false;
-            }
-            return Switch(
-                ctor->ReturnType(),  //
-                [&](const sem::Array* arr) { return ArrayConstructor(expr, arr); },
-                [&](const sem::Struct* str) { return StructureConstructor(expr, str); },
-                [&](Default) { return true; });
-        },
-        [&](Default) { return true; });
+            },
+            [&](Default) { return true; });
+    }
+
+    return true;
 }
 
 bool Validator::DiscardStatement(const sem::Statement* stmt,
@@ -1874,18 +1867,17 @@
     auto* elem_ty = array_type->ElemType();
     for (auto* value : values) {
         auto* value_ty = sem_.TypeOf(value)->UnwrapRef();
-        if (value_ty != elem_ty) {
-            AddError(
-                "type in array constructor does not match array type: "
-                "expected '" +
-                    sem_.TypeNameOf(elem_ty) + "', found '" + sem_.TypeNameOf(value_ty) + "'",
-                value->source);
+        if (sem::Type::ConversionRank(value_ty, elem_ty) == sem::Type::kNoConversion) {
+            AddError("'" + sem_.TypeNameOf(value_ty) +
+                         "' cannot be used to construct an array of '" + sem_.TypeNameOf(elem_ty) +
+                         "'",
+                     value->source);
             return false;
         }
     }
 
     if (array_type->IsRuntimeSized()) {
-        AddError("cannot init a runtime-sized array", ctor->source);
+        AddError("cannot construct a runtime-sized array", ctor->source);
         return false;
     } else if (!elem_ty->IsConstructible()) {
         AddError("array constructor has non-constructible element type", ctor->source);
@@ -2011,6 +2003,11 @@
 bool Validator::Array(const sem::Array* arr, const Source& source) const {
     auto* el_ty = arr->ElemType();
 
+    if (!IsPlain(el_ty)) {
+        AddError(sem_.TypeNameOf(el_ty) + " cannot be used as an element type of an array", source);
+        return false;
+    }
+
     if (!IsFixedFootprint(el_ty)) {
         AddError("an array element type cannot contain a runtime-sized array", source);
         return false;
@@ -2020,8 +2017,7 @@
 
 bool Validator::ArrayStrideAttribute(const ast::StrideAttribute* attr,
                                      uint32_t el_size,
-                                     uint32_t el_align,
-                                     const Source& source) const {
+                                     uint32_t el_align) const {
     auto stride = attr->stride;
     bool is_valid_stride = (stride >= el_size) && (stride >= el_align) && (stride % el_align == 0);
     if (!is_valid_stride) {
@@ -2032,8 +2028,8 @@
         AddError(
             "arrays decorated with the stride attribute must have a stride "
             "that is at least the size of the element type, and be a multiple "
-            "of the element type's alignment value.",
-            source);
+            "of the element type's alignment value",
+            attr->source);
         return false;
     }
     return true;
diff --git a/src/tint/resolver/validator.h b/src/tint/resolver/validator.h
index e9f9cea..385e020 100644
--- a/src/tint/resolver/validator.h
+++ b/src/tint/resolver/validator.h
@@ -131,12 +131,10 @@
     /// @param attr the stride attribute to validate
     /// @param el_size the element size
     /// @param el_align the element alignment
-    /// @param source the source of the attribute
     /// @returns true on success, false otherwise
     bool ArrayStrideAttribute(const ast::StrideAttribute* attr,
                               uint32_t el_size,
-                              uint32_t el_align,
-                              const Source& source) const;
+                              uint32_t el_align) const;
 
     /// Validates an atomic
     /// @param a the atomic ast node to validate
diff --git a/src/tint/sem/abstract_numeric.cc b/src/tint/sem/abstract_numeric.cc
index 6481a43..e7d1846 100644
--- a/src/tint/sem/abstract_numeric.cc
+++ b/src/tint/sem/abstract_numeric.cc
@@ -31,7 +31,7 @@
 }
 
 bool AbstractNumeric::IsConstructible() const {
-    return false;
+    return true;
 }
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/abstract_numeric.h b/src/tint/sem/abstract_numeric.h
index 0b38448..620c528 100644
--- a/src/tint/sem/abstract_numeric.h
+++ b/src/tint/sem/abstract_numeric.h
@@ -38,7 +38,7 @@
     /// @returns 0, as the type is abstract.
     uint32_t Align() const override;
 
-    /// @returns 0, as the type is abstract.
+    /// @returns true.
     bool IsConstructible() const override;
 };
 
diff --git a/src/tint/symbol.h b/src/tint/symbol.h
index e344341..fc0a0df 100644
--- a/src/tint/symbol.h
+++ b/src/tint/symbol.h
@@ -41,7 +41,7 @@
 #if TINT_SYMBOL_STORE_DEBUG_NAME
     /// Constructor
     /// @param val the symbol value
-    /// @param program_id the identifier of the program that owns this Symbol
+    /// @param pid the identifier of the program that owns this Symbol
     /// @param debug_name name of symbols used only for debugging
     Symbol(uint32_t val, tint::ProgramID pid, std::string debug_name);
 #endif
diff --git a/src/tint/transform/remove_phonies.cc b/src/tint/transform/remove_phonies.cc
index 1cac0b7..7ba109a 100644
--- a/src/tint/transform/remove_phonies.cc
+++ b/src/tint/transform/remove_phonies.cc
@@ -97,7 +97,8 @@
                                 // Just skip.
                                 return ast::TraverseAction::Skip;
                             }
-                            if (call->Target()->IsAnyOf<sem::Function, sem::Builtin>()) {
+                            if (call->Target()->IsAnyOf<sem::Function, sem::Builtin>() &&
+                                call->HasSideEffects()) {
                                 side_effects.push_back(expr);
                                 return ast::TraverseAction::Skip;
                             }
diff --git a/src/tint/transform/remove_phonies_test.cc b/src/tint/transform/remove_phonies_test.cc
index 220f1db..dc91b5a 100644
--- a/src/tint/transform/remove_phonies_test.cc
+++ b/src/tint/transform/remove_phonies_test.cc
@@ -65,6 +65,8 @@
   _ = vec2<f32>(5.0);
   _ = vec3<i32>(6, 7, 8);
   _ = mat2x2<f32>(9.0, 10.0, 11.0, 12.0);
+  _ = atan2(1.0, 2.0);
+  _ = clamp(1.0, 2.0, 3.0);
 }
 )";
 
diff --git a/src/tint/val/hlsl.cc b/src/tint/val/hlsl.cc
index 18bded6..f1cfb05 100644
--- a/src/tint/val/hlsl.cc
+++ b/src/tint/val/hlsl.cc
@@ -114,13 +114,21 @@
         return result;
     }
 
-    pD3DCompile d3dCompile = reinterpret_cast<pD3DCompile>(
+    auto* d3dCompile = reinterpret_cast<pD3DCompile>(
         reinterpret_cast<void*>(GetProcAddress(fxcLib, "D3DCompile")));
+    auto* d3dDisassemble = reinterpret_cast<pD3DDisassemble>(
+        reinterpret_cast<void*>(GetProcAddress(fxcLib, "D3DDisassemble")));
+
     if (d3dCompile == nullptr) {
         result.output = "Couldn't load D3DCompile from FXC";
         result.failed = true;
         return result;
     }
+    if (d3dDisassemble == nullptr) {
+        result.output = "Couldn't load D3DDisassemble from FXC";
+        result.failed = true;
+        return result;
+    }
 
     for (auto ep : entry_points) {
         const char* profile = "";
@@ -147,21 +155,30 @@
 
         ComPtr<ID3DBlob> compiledShader;
         ComPtr<ID3DBlob> errors;
-        HRESULT cr = d3dCompile(source.c_str(),    // pSrcData
-                                source.length(),   // SrcDataSize
-                                nullptr,           // pSourceName
-                                nullptr,           // pDefines
-                                nullptr,           // pInclude
-                                ep.first.c_str(),  // pEntrypoint
-                                profile,           // pTarget
-                                compileFlags,      // Flags1
-                                0,                 // Flags2
-                                &compiledShader,   // ppCode
-                                &errors);          // ppErrorMsgs
-        if (FAILED(cr)) {
+        HRESULT res = d3dCompile(source.c_str(),    // pSrcData
+                                 source.length(),   // SrcDataSize
+                                 nullptr,           // pSourceName
+                                 nullptr,           // pDefines
+                                 nullptr,           // pInclude
+                                 ep.first.c_str(),  // pEntrypoint
+                                 profile,           // pTarget
+                                 compileFlags,      // Flags1
+                                 0,                 // Flags2
+                                 &compiledShader,   // ppCode
+                                 &errors);          // ppErrorMsgs
+        if (FAILED(res)) {
             result.output = static_cast<char*>(errors->GetBufferPointer());
             result.failed = true;
             return result;
+        } else {
+            ComPtr<ID3DBlob> disassembly;
+            res = d3dDisassemble(compiledShader->GetBufferPointer(),
+                                 compiledShader->GetBufferSize(), 0, "", &disassembly);
+            if (FAILED(res)) {
+                result.output = "failed to disassemble shader";
+            } else {
+                result.output = static_cast<char*>(disassembly->GetBufferPointer());
+            }
         }
     }
 
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index 6ca7083..f3129c0 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -382,19 +382,22 @@
                 }
             }
 
-            out << "array<";
-            if (!EmitType(out, ary->type)) {
-                return false;
-            }
+            out << "array";
+            if (ary->type) {
+                out << "<";
+                TINT_DEFER(out << ">");
 
-            if (!ary->IsRuntimeArray()) {
-                out << ", ";
-                if (!EmitExpression(out, ary->count)) {
+                if (!EmitType(out, ary->type)) {
                     return false;
                 }
-            }
 
-            out << ">";
+                if (!ary->IsRuntimeArray()) {
+                    out << ", ";
+                    if (!EmitExpression(out, ary->count)) {
+                        return false;
+                    }
+                }
+            }
             return true;
         },
         [&](const ast::Bool*) {
diff --git a/src/tint/writer/wgsl/generator_impl_constructor_test.cc b/src/tint/writer/wgsl/generator_impl_constructor_test.cc
index 07b10ad..40bf393 100644
--- a/src/tint/writer/wgsl/generator_impl_constructor_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_constructor_test.cc
@@ -174,5 +174,17 @@
                           "vec3<f32>(4.0f, 5.0f, 6.0f), vec3<f32>(7.0f, 8.0f, 9.0f))"));
 }
 
+TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_ImplicitArray) {
+    WrapInFunction(Construct(ty.array(nullptr, nullptr), vec3<f32>(1_f, 2_f, 3_f),
+                             vec3<f32>(4_f, 5_f, 6_f), vec3<f32>(7_f, 8_f, 9_f)));
+
+    GeneratorImpl& gen = Build();
+
+    ASSERT_TRUE(gen.Generate()) << gen.error();
+    EXPECT_THAT(gen.result(),
+                HasSubstr("array(vec3<f32>(1.0f, 2.0f, 3.0f), "
+                          "vec3<f32>(4.0f, 5.0f, 6.0f), vec3<f32>(7.0f, 8.0f, 9.0f))"));
+}
+
 }  // namespace
 }  // namespace tint::writer::wgsl