resolver: Implement element inference of vecN and matNxM

Fixed: tint:1334
Change-Id: Idc94d49ecd41e37354bb93138348e3af3e733932
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/72143
Reviewed-by: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/docs/origin-trial-changes.md b/docs/origin-trial-changes.md
index 09f66a3..154c510 100644
--- a/docs/origin-trial-changes.md
+++ b/docs/origin-trial-changes.md
@@ -14,6 +14,7 @@
 
 ### New Features
 
+* Vector and matrix element type can now be inferred from constructor argument types. [tint:1334](https://crbug.com/tint/1334)
 * New texture gather builtins: `textureGather()` and `textureGatherCompare()`. [tint:1330](https://crbug.com/tint/1330)
 * Shadowing is now fully supported. [tint:819](https://crbug.com/tint/819)
 * The `dot()` builtin now supports integer vector types.
diff --git a/src/ast/matrix.h b/src/ast/matrix.h
index 023adc6..1f24e96 100644
--- a/src/ast/matrix.h
+++ b/src/ast/matrix.h
@@ -28,7 +28,9 @@
   /// Constructor
   /// @param pid the identifier of the program that owns this node
   /// @param src the source of this node
-  /// @param subtype type matrix type
+  /// @param subtype the declared type of the matrix components. May be null for
+  ///        matrix constructors, where the element type will be inferred from
+  ///        the constructor arguments
   /// @param rows the number of rows in the matrix
   /// @param columns the number of columns in the matrix
   Matrix(ProgramID pid,
@@ -50,7 +52,9 @@
   /// @return the newly cloned type
   const Matrix* Clone(CloneContext* ctx) const override;
 
-  /// The type of the matrix
+  /// The declared type of the matrix components. May be null for matrix
+  /// constructors, where the element type will be inferred from the constructor
+  /// arguments
   const Type* const type;
 
   /// The number of rows in the matrix
diff --git a/src/ast/vector.cc b/src/ast/vector.cc
index bea0dc2..50b7712 100644
--- a/src/ast/vector.cc
+++ b/src/ast/vector.cc
@@ -37,7 +37,10 @@
 
 std::string Vector::FriendlyName(const SymbolTable& symbols) const {
   std::ostringstream out;
-  out << "vec" << width << "<" << type->FriendlyName(symbols) << ">";
+  out << "vec" << width;
+  if (type) {
+    out << "<" << type->FriendlyName(symbols) << ">";
+  }
   return out.str();
 }
 
diff --git a/src/ast/vector.h b/src/ast/vector.h
index 956594d..1d64666 100644
--- a/src/ast/vector.h
+++ b/src/ast/vector.h
@@ -28,7 +28,9 @@
   /// Constructor
   /// @param pid the identifier of the program that owns this node
   /// @param src the source of this node
-  /// @param subtype the vector element type
+  /// @param subtype the declared type of the vector components. May be null
+  ///        for vector constructors, where the element type will be inferred
+  ///        from the constructor arguments
   /// @param width the number of elements in the vector
   Vector(ProgramID pid, Source const& src, const Type* subtype, uint32_t width);
   /// Move constructor
@@ -45,7 +47,9 @@
   /// @return the newly cloned type
   const Vector* Clone(CloneContext* ctx) const override;
 
-  /// The type of the vector elements
+  /// The declared type of the vector components. May be null for vector
+  /// constructors, where the element type will be inferred from the constructor
+  /// arguments
   const Type* const type;
 
   /// The number of elements in the vector
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index f1fca91..6845fbd 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -1154,19 +1154,23 @@
 
 Expect<const ast::Type*> ParserImpl::expect_type_decl_vector(Token t) {
   uint32_t count = 2;
-  if (t.Is(Token::Type::kVec3))
+  if (t.Is(Token::Type::kVec3)) {
     count = 3;
-  else if (t.Is(Token::Type::kVec4))
+  } else if (t.Is(Token::Type::kVec4)) {
     count = 4;
+  }
 
-  const char* use = "vector";
+  const ast::Type* subtype = nullptr;
+  if (peek_is(Token::Type::kLessThan)) {
+    const char* use = "vector";
+    auto ty = expect_lt_gt_block(use, [&] { return expect_type(use); });
+    if (ty.errored) {
+      return Failure::kErrored;
+    }
+    subtype = ty.value;
+  }
 
-  auto subtype = expect_lt_gt_block(use, [&] { return expect_type(use); });
-  if (subtype.errored)
-    return Failure::kErrored;
-
-  return builder_.ty.vec(make_source_range_from(t.source()), subtype.value,
-                         count);
+  return builder_.ty.vec(make_source_range_from(t.source()), subtype, count);
 }
 
 Expect<const ast::Type*> ParserImpl::expect_type_decl_array(
@@ -1217,14 +1221,18 @@
     rows = 4;
   }
 
-  const char* use = "matrix";
+  const ast::Type* subtype = nullptr;
+  if (peek_is(Token::Type::kLessThan)) {
+    const char* use = "matrix";
+    auto ty = expect_lt_gt_block(use, [&] { return expect_type(use); });
+    if (ty.errored) {
+      return Failure::kErrored;
+    }
+    subtype = ty.value;
+  }
 
-  auto subtype = expect_lt_gt_block(use, [&] { return expect_type(use); });
-  if (subtype.errored)
-    return Failure::kErrored;
-
-  return builder_.ty.mat(make_source_range_from(t.source()), subtype.value,
-                         columns, rows);
+  return builder_.ty.mat(make_source_range_from(t.source()), subtype, columns,
+                         rows);
 }
 
 // storage_class
diff --git a/src/reader/wgsl/parser_impl_error_msg_test.cc b/src/reader/wgsl/parser_impl_error_msg_test.cc
index e30177b..79ab884 100644
--- a/src/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/reader/wgsl/parser_impl_error_msg_test.cc
@@ -975,13 +975,6 @@
          "    ^\n");
 }
 
-TEST_F(ParserImplErrorTest, GlobalDeclVarMatrixMissingLessThan) {
-  EXPECT("var i : mat4x4;",
-         "test.wgsl:1:15 error: expected '<' for matrix\n"
-         "var i : mat4x4;\n"
-         "              ^\n");
-}
-
 TEST_F(ParserImplErrorTest, GlobalDeclVarMatrixMissingGreaterThan) {
   EXPECT("var i : mat4x4<u32;",
          "test.wgsl:1:19 error: expected '>' for matrix\n"
@@ -1066,13 +1059,6 @@
          "            ^\n");
 }
 
-TEST_F(ParserImplErrorTest, GlobalDeclVarVectorMissingLessThan) {
-  EXPECT("var i : vec3;",
-         "test.wgsl:1:13 error: expected '<' for vector\n"
-         "var i : vec3;\n"
-         "            ^\n");
-}
-
 TEST_F(ParserImplErrorTest, GlobalDeclVarVectorMissingGreaterThan) {
   EXPECT("var i : vec3<u32;",
          "test.wgsl:1:17 error: expected '>' for vector\n"
diff --git a/src/reader/wgsl/parser_impl_type_decl_test.cc b/src/reader/wgsl/parser_impl_type_decl_test.cc
index d08a23d..f3cc52d 100644
--- a/src/reader/wgsl/parser_impl_type_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_type_decl_test.cc
@@ -139,24 +139,6 @@
                                          VecData{"vec3<f32", 3, {}},
                                          VecData{"vec4<f32", 4, {}}));
 
-class VecMissingLessThanTest : public ParserImplTestWithParam<VecData> {};
-
-TEST_P(VecMissingLessThanTest, Handles_Missing_GreaterThan) {
-  auto params = GetParam();
-  auto p = parser(params.input);
-  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:5: expected '<' for vector");
-}
-INSTANTIATE_TEST_SUITE_P(ParserImplTest,
-                         VecMissingLessThanTest,
-                         testing::Values(VecData{"vec2", 2, {}},
-                                         VecData{"vec3", 3, {}},
-                                         VecData{"vec4", 4, {}}));
-
 class VecMissingType : public ParserImplTestWithParam<VecData> {};
 
 TEST_P(VecMissingType, Handles_Missing_Type) {
@@ -774,30 +756,6 @@
                                          MatrixData{"mat4x3<f32", 4, 3, {}},
                                          MatrixData{"mat4x4<f32", 4, 4, {}}));
 
-class MatrixMissingLessThanTest : public ParserImplTestWithParam<MatrixData> {};
-
-TEST_P(MatrixMissingLessThanTest, Handles_Missing_GreaterThan) {
-  auto params = GetParam();
-  auto p = parser(params.input);
-  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:8: expected '<' for matrix");
-}
-INSTANTIATE_TEST_SUITE_P(ParserImplTest,
-                         MatrixMissingLessThanTest,
-                         testing::Values(MatrixData{"mat2x2 f32>", 2, 2, {}},
-                                         MatrixData{"mat2x3 f32>", 2, 3, {}},
-                                         MatrixData{"mat2x4 f32>", 2, 4, {}},
-                                         MatrixData{"mat3x2 f32>", 3, 2, {}},
-                                         MatrixData{"mat3x3 f32>", 3, 3, {}},
-                                         MatrixData{"mat3x4 f32>", 3, 4, {}},
-                                         MatrixData{"mat4x2 f32>", 4, 2, {}},
-                                         MatrixData{"mat4x3 f32>", 4, 3, {}},
-                                         MatrixData{"mat4x4 f32>", 4, 4, {}}));
-
 class MatrixMissingType : public ParserImplTestWithParam<MatrixData> {};
 
 TEST_P(MatrixMissingType, Handles_Missing_Type) {
diff --git a/src/resolver/function_validation_test.cc b/src/resolver/function_validation_test.cc
index cd6bd6a..f2976de 100644
--- a/src/resolver/function_validation_test.cc
+++ b/src/resolver/function_validation_test.cc
@@ -766,6 +766,28 @@
             "12:34 error: functions may declare at most 255 parameters");
 }
 
+TEST_F(ResolverFunctionValidationTest, ParameterVectorNoType) {
+  // fn f(p : vec3) {}
+
+  Func(Source{{12, 34}}, "f",
+       {Param("p", create<ast::Vector>(Source{{12, 34}}, nullptr, 3))},
+       ty.void_(), {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+}
+
+TEST_F(ResolverFunctionValidationTest, ParameterMatrixNoType) {
+  // fn f(p : vec3) {}
+
+  Func(Source{{12, 34}}, "f",
+       {Param("p", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3))},
+       ty.void_(), {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
+}
+
 struct TestParams {
   ast::StorageClass storage_class;
   bool should_pass;
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index abe47c4..d211dde 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -180,6 +180,10 @@
       return builder_->create<sem::F32>();
     }
     if (auto* t = ty->As<ast::Vector>()) {
+      if (!t->type) {
+        AddError("missing vector element type", t->source.End());
+        return nullptr;
+      }
       if (auto* el = Type(t->type)) {
         if (auto* vector = builder_->create<sem::Vector>(el, t->width)) {
           if (ValidateVector(vector, t->source)) {
@@ -190,6 +194,10 @@
       return nullptr;
     }
     if (auto* t = ty->As<ast::Matrix>()) {
+      if (!t->type) {
+        AddError("missing matrix element type", t->source.End());
+        return nullptr;
+      }
       if (auto* el = Type(t->type)) {
         if (auto* column_type = builder_->create<sem::Vector>(el, t->rows)) {
           if (auto* matrix =
@@ -1240,6 +1248,10 @@
   std::vector<const sem::Type*> arg_tys(args.size());
   sem::Behaviors arg_behaviors;
 
+  // The element type of all the arguments. Nullptr if argument types are
+  // different.
+  const sem::Type* arg_el_ty = nullptr;
+
   for (size_t i = 0; i < expr->args.size(); i++) {
     auto* arg = Sem(expr->args[i]);
     if (!arg) {
@@ -1248,6 +1260,19 @@
     args[i] = arg;
     arg_tys[i] = args[i]->Type();
     arg_behaviors.Add(arg->Behaviors());
+
+    // Determine the common argument element type
+    auto* el_ty = arg_tys[i]->UnwrapRef();
+    if (auto* vec = el_ty->As<sem::Vector>()) {
+      el_ty = vec->type();
+    } else if (auto* mat = el_ty->As<sem::Matrix>()) {
+      el_ty = mat->type();
+    }
+    if (i == 0) {
+      arg_el_ty = el_ty;
+    } else if (arg_el_ty != el_ty) {
+      arg_el_ty = nullptr;
+    }
   }
 
   arg_behaviors.Remove(sem::Behavior::kNext);
@@ -1273,10 +1298,71 @@
   // Resolve the target of the CallExpression to determine whether this is a
   // function call, cast or type constructor expression.
   if (expr->target.type) {
-    auto* ty = Type(expr->target.type);
-    if (!ty) {
-      return nullptr;
+    const sem::Type* ty = nullptr;
+
+    auto err_cannot_infer_el_ty = [&](std::string name) {
+      AddError(
+          "cannot infer " + name +
+              " element type, as constructor arguments have different types",
+          expr->source);
+      for (size_t i = 0; i < args.size(); i++) {
+        auto* arg = args[i];
+        AddNote("argument " + std::to_string(i) + " has type " +
+                    arg->Type()->FriendlyName(builder_->Symbols()),
+                arg->Declaration()->source);
+      }
+    };
+
+    if (!expr->args.empty()) {
+      // vecN() without explicit element type?
+      // Try to infer element type from args
+      if (auto* vec = expr->target.type->As<ast::Vector>()) {
+        if (!vec->type) {
+          if (!arg_el_ty) {
+            err_cannot_infer_el_ty("vector");
+            return nullptr;
+          }
+
+          Mark(vec);
+          auto* v = builder_->create<sem::Vector>(
+              arg_el_ty, static_cast<uint32_t>(vec->width));
+          if (!ValidateVector(v, vec->source)) {
+            return nullptr;
+          }
+          builder_->Sem().Add(vec, v);
+          ty = v;
+        }
+      }
+
+      // matNxM() without explicit element type?
+      // Try to infer element type from args
+      if (auto* mat = expr->target.type->As<ast::Matrix>()) {
+        if (!mat->type) {
+          if (!arg_el_ty) {
+            err_cannot_infer_el_ty("matrix");
+            return nullptr;
+          }
+
+          Mark(mat);
+          auto* column_type =
+              builder_->create<sem::Vector>(arg_el_ty, mat->rows);
+          auto* m = builder_->create<sem::Matrix>(column_type, mat->columns);
+          if (!ValidateMatrix(m, mat->source)) {
+            return nullptr;
+          }
+          builder_->Sem().Add(mat, m);
+          ty = m;
+        }
+      }
     }
+
+    if (ty == nullptr) {
+      ty = Type(expr->target.type);
+      if (!ty) {
+        return nullptr;
+      }
+    }
+
     return type_ctor_or_conv(ty);
   }
 
@@ -1393,16 +1479,16 @@
   auto* call_target = utils::GetOrCreate(
       type_conversions_, TypeConversionSig{target, source},
       [&]() -> sem::TypeConversion* {
-        // Now that the argument types have been determined, make sure that they
-        // obey the conversion rules laid out in
+        // Now that the argument types have been determined, make sure that
+        // they obey the conversion rules laid out in
         // https://gpuweb.github.io/gpuweb/wgsl/#conversion-expr.
         bool ok = true;
         if (auto* vec_type = target->As<sem::Vector>()) {
           ok = ValidateVectorConstructorOrCast(expr, vec_type);
         } else if (auto* mat_type = target->As<sem::Matrix>()) {
-          // Note: Matrix types currently cannot be converted (the element type
-          // must only be f32). We implement this for the day we support other
-          // matrix element types.
+          // Note: Matrix types currently cannot be converted (the element
+          // type must only be f32). We implement this for the day we support
+          // other matrix element types.
           ok = ValidateMatrixConstructorOrCast(expr, mat_type);
         } else if (target->is_scalar()) {
           ok = ValidateScalarConstructorOrCast(expr, target);
@@ -1452,8 +1538,8 @@
   auto* call_target = utils::GetOrCreate(
       type_ctors_, TypeConstructorSig{ty, arg_tys},
       [&]() -> sem::TypeConstructor* {
-        // Now that the argument types have been determined, make sure that they
-        // obey the constructor type rules laid out in
+        // Now that the argument types have been determined, make sure that
+        // they obey the constructor type rules laid out in
         // https://gpuweb.github.io/gpuweb/wgsl/#type-constructor-expr.
         bool ok = true;
         if (auto* vec_type = ty->As<sem::Vector>()) {
@@ -2359,8 +2445,8 @@
       behaviors.Add(expr->Behaviors() - sem::Behavior::kNext);
     }
 
-    // Validate after processing the return value expression so that its type is
-    // available for validation.
+    // Validate after processing the return value expression so that its type
+    // is available for validation.
     return ValidateReturn(stmt);
   });
 }
diff --git a/src/resolver/resolver_validation.cc b/src/resolver/resolver_validation.cc
index b15aaf5..917c8db 100644
--- a/src/resolver/resolver_validation.cc
+++ b/src/resolver/resolver_validation.cc
@@ -1853,6 +1853,12 @@
     return false;
   }
 
+  std::vector<const sem::Type*> arg_tys;
+  arg_tys.reserve(values.size());
+  for (auto* value : values) {
+    arg_tys.emplace_back(TypeOf(value)->UnwrapRef());
+  }
+
   auto* elem_type = matrix_ty->type();
   auto num_elements = matrix_ty->columns() * matrix_ty->rows();
 
@@ -1864,7 +1870,14 @@
     auto type_name = TypeNameOf(matrix_ty);
     auto elem_type_name = TypeNameOf(elem_type);
     std::stringstream ss;
-    ss << "invalid constructor for " + type_name << std::endl << std::endl;
+    ss << "no matching constructor " + type_name << "(";
+    for (size_t i = 0; i < values.size(); i++) {
+      if (i > 0) {
+        ss << ", ";
+      }
+      ss << arg_tys[i]->FriendlyName(builder_->Symbols());
+    }
+    ss << ")" << std::endl << std::endl;
     ss << "3 candidates available:" << std::endl;
     ss << "  " << type_name << "()" << std::endl;
     ss << "  " << type_name << "(" << elem_type_name << ",...,"
@@ -1893,8 +1906,8 @@
     return false;
   }
 
-  for (auto* value : values) {
-    if (TypeOf(value)->UnwrapRef() != expected_arg_type) {
+  for (auto* arg_ty : arg_tys) {
+    if (arg_ty != expected_arg_type) {
       print_error();
       return false;
     }
diff --git a/src/resolver/type_constructor_validation_test.cc b/src/resolver/type_constructor_validation_test.cc
index 2c83801..1bd7775 100644
--- a/src/resolver/type_constructor_validation_test.cc
+++ b/src/resolver/type_constructor_validation_test.cc
@@ -1821,6 +1821,386 @@
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
+TEST_F(ResolverTypeConstructorValidationTest, InferVec2ElementTypeFromScalars) {
+  auto* vec2_bool =
+      Construct(create<ast::Vector>(nullptr, 2), Expr(true), Expr(false));
+  auto* vec2_i32 = Construct(create<ast::Vector>(nullptr, 2), Expr(1), Expr(2));
+  auto* vec2_u32 =
+      Construct(create<ast::Vector>(nullptr, 2), Expr(1u), Expr(2u));
+  auto* vec2_f32 =
+      Construct(create<ast::Vector>(nullptr, 2), Expr(1.0f), Expr(2.0f));
+  WrapInFunction(vec2_bool, vec2_i32, vec2_u32, vec2_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec2_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec2_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec2_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec2_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec2_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec2_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec2_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec2_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec2_bool)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_i32)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_u32)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_f32)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_bool), TypeOf(vec2_bool->target.type));
+  EXPECT_EQ(TypeOf(vec2_i32), TypeOf(vec2_i32->target.type));
+  EXPECT_EQ(TypeOf(vec2_u32), TypeOf(vec2_u32->target.type));
+  EXPECT_EQ(TypeOf(vec2_f32), TypeOf(vec2_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferVec2ElementTypeFromVec2) {
+  auto* vec2_bool =
+      Construct(create<ast::Vector>(nullptr, 2), vec2<bool>(true, false));
+  auto* vec2_i32 = Construct(create<ast::Vector>(nullptr, 2), vec2<i32>(1, 2));
+  auto* vec2_u32 =
+      Construct(create<ast::Vector>(nullptr, 2), vec2<u32>(1u, 2u));
+  auto* vec2_f32 =
+      Construct(create<ast::Vector>(nullptr, 2), vec2<f32>(1.0f, 2.0f));
+  WrapInFunction(vec2_bool, vec2_i32, vec2_u32, vec2_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec2_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec2_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec2_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec2_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec2_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec2_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec2_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec2_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec2_bool)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_i32)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_u32)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_f32)->As<sem::Vector>()->Width(), 2u);
+  EXPECT_EQ(TypeOf(vec2_bool), TypeOf(vec2_bool->target.type));
+  EXPECT_EQ(TypeOf(vec2_i32), TypeOf(vec2_i32->target.type));
+  EXPECT_EQ(TypeOf(vec2_u32), TypeOf(vec2_u32->target.type));
+  EXPECT_EQ(TypeOf(vec2_f32), TypeOf(vec2_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferVec3ElementTypeFromScalars) {
+  auto* vec3_bool = Construct(create<ast::Vector>(nullptr, 3), Expr(true),
+                              Expr(false), Expr(true));
+  auto* vec3_i32 =
+      Construct(create<ast::Vector>(nullptr, 3), Expr(1), Expr(2), Expr(3));
+  auto* vec3_u32 =
+      Construct(create<ast::Vector>(nullptr, 3), Expr(1u), Expr(2u), Expr(3u));
+  auto* vec3_f32 = Construct(create<ast::Vector>(nullptr, 3), Expr(1.0f),
+                             Expr(2.0f), Expr(3.0f));
+  WrapInFunction(vec3_bool, vec3_i32, vec3_u32, vec3_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec3_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec3_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec3_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec3_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec3_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec3_bool)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_i32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_u32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_f32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_bool), TypeOf(vec3_bool->target.type));
+  EXPECT_EQ(TypeOf(vec3_i32), TypeOf(vec3_i32->target.type));
+  EXPECT_EQ(TypeOf(vec3_u32), TypeOf(vec3_u32->target.type));
+  EXPECT_EQ(TypeOf(vec3_f32), TypeOf(vec3_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferVec3ElementTypeFromVec3) {
+  auto* vec3_bool =
+      Construct(create<ast::Vector>(nullptr, 3), vec3<bool>(true, false, true));
+  auto* vec3_i32 =
+      Construct(create<ast::Vector>(nullptr, 3), vec3<i32>(1, 2, 3));
+  auto* vec3_u32 =
+      Construct(create<ast::Vector>(nullptr, 3), vec3<u32>(1u, 2u, 3u));
+  auto* vec3_f32 =
+      Construct(create<ast::Vector>(nullptr, 3), vec3<f32>(1.0f, 2.0f, 3.0f));
+  WrapInFunction(vec3_bool, vec3_i32, vec3_u32, vec3_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec3_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec3_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec3_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec3_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec3_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec3_bool)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_i32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_u32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_f32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_bool), TypeOf(vec3_bool->target.type));
+  EXPECT_EQ(TypeOf(vec3_i32), TypeOf(vec3_i32->target.type));
+  EXPECT_EQ(TypeOf(vec3_u32), TypeOf(vec3_u32->target.type));
+  EXPECT_EQ(TypeOf(vec3_f32), TypeOf(vec3_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       InferVec3ElementTypeFromScalarAndVec2) {
+  auto* vec3_bool = Construct(create<ast::Vector>(nullptr, 3), Expr(true),
+                              vec2<bool>(false, true));
+  auto* vec3_i32 =
+      Construct(create<ast::Vector>(nullptr, 3), Expr(1), vec2<i32>(2, 3));
+  auto* vec3_u32 =
+      Construct(create<ast::Vector>(nullptr, 3), Expr(1u), vec2<u32>(2u, 3u));
+  auto* vec3_f32 = Construct(create<ast::Vector>(nullptr, 3), Expr(1.0f),
+                             vec2<f32>(2.0f, 3.0f));
+  WrapInFunction(vec3_bool, vec3_i32, vec3_u32, vec3_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec3_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec3_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec3_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec3_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec3_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec3_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec3_bool)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_i32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_u32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_f32)->As<sem::Vector>()->Width(), 3u);
+  EXPECT_EQ(TypeOf(vec3_bool), TypeOf(vec3_bool->target.type));
+  EXPECT_EQ(TypeOf(vec3_i32), TypeOf(vec3_i32->target.type));
+  EXPECT_EQ(TypeOf(vec3_u32), TypeOf(vec3_u32->target.type));
+  EXPECT_EQ(TypeOf(vec3_f32), TypeOf(vec3_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferVec4ElementTypeFromScalars) {
+  auto* vec4_bool = Construct(create<ast::Vector>(nullptr, 4), Expr(true),
+                              Expr(false), Expr(true), Expr(false));
+  auto* vec4_i32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1), Expr(2),
+                             Expr(3), Expr(4));
+  auto* vec4_u32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1u),
+                             Expr(2u), Expr(3u), Expr(4u));
+  auto* vec4_f32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1.0f),
+                             Expr(2.0f), Expr(3.0f), Expr(4.0f));
+  WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec4_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec4_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec4_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec4_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec4_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec4_bool)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_i32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_u32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_f32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
+  EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
+  EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
+  EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest, InferVec4ElementTypeFromVec4) {
+  auto* vec4_bool = Construct(create<ast::Vector>(nullptr, 4),
+                              vec4<bool>(true, false, true, false));
+  auto* vec4_i32 =
+      Construct(create<ast::Vector>(nullptr, 4), vec4<i32>(1, 2, 3, 4));
+  auto* vec4_u32 =
+      Construct(create<ast::Vector>(nullptr, 4), vec4<u32>(1u, 2u, 3u, 4u));
+  auto* vec4_f32 = Construct(create<ast::Vector>(nullptr, 4),
+                             vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f));
+  WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec4_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec4_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec4_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec4_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec4_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec4_bool)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_i32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_u32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_f32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
+  EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
+  EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
+  EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       InferVec4ElementTypeFromScalarAndVec3) {
+  auto* vec4_bool = Construct(create<ast::Vector>(nullptr, 4), Expr(true),
+                              vec3<bool>(false, true, false));
+  auto* vec4_i32 =
+      Construct(create<ast::Vector>(nullptr, 4), Expr(1), vec3<i32>(2, 3, 4));
+  auto* vec4_u32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1u),
+                             vec3<u32>(2u, 3u, 4u));
+  auto* vec4_f32 = Construct(create<ast::Vector>(nullptr, 4), Expr(1.0f),
+                             vec3<f32>(2.0f, 3.0f, 4.0f));
+  WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec4_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec4_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec4_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec4_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec4_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec4_bool)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_i32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_u32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_f32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
+  EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
+  EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
+  EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       InferVec4ElementTypeFromVec2AndVec2) {
+  auto* vec4_bool = Construct(create<ast::Vector>(nullptr, 4),
+                              vec2<bool>(true, false), vec2<bool>(true, false));
+  auto* vec4_i32 = Construct(create<ast::Vector>(nullptr, 4), vec2<i32>(1, 2),
+                             vec2<i32>(3, 4));
+  auto* vec4_u32 = Construct(create<ast::Vector>(nullptr, 4), vec2<u32>(1u, 2u),
+                             vec2<u32>(3u, 4u));
+  auto* vec4_f32 = Construct(create<ast::Vector>(nullptr, 4),
+                             vec2<f32>(1.0f, 2.0f), vec2<f32>(3.0f, 4.0f));
+  WrapInFunction(vec4_bool, vec4_i32, vec4_u32, vec4_f32);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_TRUE(TypeOf(vec4_bool)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_i32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_u32)->Is<sem::Vector>());
+  ASSERT_TRUE(TypeOf(vec4_f32)->Is<sem::Vector>());
+  EXPECT_TRUE(TypeOf(vec4_bool)->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_TRUE(TypeOf(vec4_i32)->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_TRUE(TypeOf(vec4_u32)->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_TRUE(TypeOf(vec4_f32)->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(TypeOf(vec4_bool)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_i32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_u32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_f32)->As<sem::Vector>()->Width(), 4u);
+  EXPECT_EQ(TypeOf(vec4_bool), TypeOf(vec4_bool->target.type));
+  EXPECT_EQ(TypeOf(vec4_i32), TypeOf(vec4_i32->target.type));
+  EXPECT_EQ(TypeOf(vec4_u32), TypeOf(vec4_u32->target.type));
+  EXPECT_EQ(TypeOf(vec4_f32), TypeOf(vec4_f32->target.type));
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVectorElementTypeWithoutArgs) {
+  WrapInFunction(Construct(create<ast::Vector>(Source{{12, 34}}, nullptr, 3)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVec2ElementTypeFromScalarsMismatch) {
+  WrapInFunction(Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 2),
+                           Expr(Source{{1, 2}}, 1),  //
+                           Expr(Source{{1, 3}}, 2u)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
+1:2 note: argument 0 has type i32
+1:3 note: argument 1 has type u32)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVec3ElementTypeFromScalarsMismatch) {
+  WrapInFunction(Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 3),
+                           Expr(Source{{1, 2}}, 1),   //
+                           Expr(Source{{1, 3}}, 2u),  //
+                           Expr(Source{{1, 4}}, 3)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
+1:2 note: argument 0 has type i32
+1:3 note: argument 1 has type u32
+1:4 note: argument 2 has type i32)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVec3ElementTypeFromScalarAndVec2Mismatch) {
+  WrapInFunction(
+      Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 3),
+                Expr(Source{{1, 2}}, 1),  //
+                Construct(Source{{1, 3}}, ty.vec2<f32>(), 2.0f, 3.0f)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
+1:2 note: argument 0 has type i32
+1:3 note: argument 1 has type vec2<f32>)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVec4ElementTypeFromScalarsMismatch) {
+  WrapInFunction(Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 4),
+                           Expr(Source{{1, 2}}, 1),     //
+                           Expr(Source{{1, 3}}, 2),     //
+                           Expr(Source{{1, 4}}, 3.0f),  //
+                           Expr(Source{{1, 5}}, 4)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
+1:2 note: argument 0 has type i32
+1:3 note: argument 1 has type i32
+1:4 note: argument 2 has type f32
+1:5 note: argument 3 has type i32)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVec4ElementTypeFromScalarAndVec3Mismatch) {
+  WrapInFunction(
+      Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 4),
+                Expr(Source{{1, 2}}, 1),  //
+                Construct(Source{{1, 3}}, ty.vec3<u32>(), 2u, 3u, 4u)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
+1:2 note: argument 0 has type i32
+1:3 note: argument 1 has type vec3<u32>)");
+}
+
+TEST_F(ResolverTypeConstructorValidationTest,
+       CannotInferVec4ElementTypeFromVec2AndVec2Mismatch) {
+  WrapInFunction(Construct(Source{{1, 1}}, create<ast::Vector>(nullptr, 4),
+                           Construct(Source{{1, 2}}, ty.vec2<i32>(), 3, 4),  //
+                           Construct(Source{{1, 3}}, ty.vec2<u32>(), 3u, 4u)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(1:1 error: cannot infer vector element type, as constructor arguments have different types
+1:2 note: argument 0 has type vec2<i32>
+1:3 note: argument 1 has type vec2<u32>)");
+}
+
 }  // namespace VectorConstructor
 
 namespace MatrixConstructor {
@@ -1841,10 +2221,15 @@
 
   const auto param = GetParam();
 
+  std::stringstream args_tys;
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns - 1; i++) {
     auto* vec_type = ty.vec<f32>(param.rows);
     args.push_back(Construct(Source{{12, i}}, vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<f32>";
   }
 
   auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
@@ -1852,9 +2237,9 @@
   WrapInFunction(tc);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(),
-              HasSubstr("12:1 error: invalid constructor for " +
-                        MatrixStr(param) + "\n\n3 candidates available:"));
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
 }
 
 TEST_P(MatrixConstructorTest, Expr_ElementConstructor_Error_TooFewArguments) {
@@ -1862,9 +2247,14 @@
 
   const auto param = GetParam();
 
+  std::stringstream args_tys;
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns * param.rows - 1; i++) {
     args.push_back(Construct(Source{{12, i}}, ty.f32()));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "f32";
   }
 
   auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
@@ -1872,9 +2262,9 @@
   WrapInFunction(tc);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(),
-              HasSubstr("12:1 error: invalid constructor for " +
-                        MatrixStr(param) + "\n\n3 candidates available:"));
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
 }
 
 TEST_P(MatrixConstructorTest, Expr_ColumnConstructor_Error_TooManyArguments) {
@@ -1882,10 +2272,15 @@
 
   const auto param = GetParam();
 
+  std::stringstream args_tys;
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns + 1; i++) {
     auto* vec_type = ty.vec<f32>(param.rows);
     args.push_back(Construct(Source{{12, i}}, vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<f32>";
   }
 
   auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
@@ -1893,9 +2288,9 @@
   WrapInFunction(tc);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(),
-              HasSubstr("12:1 error: invalid constructor for " +
-                        MatrixStr(param) + "\n\n3 candidates available:"));
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
 }
 
 TEST_P(MatrixConstructorTest, Expr_ElementConstructor_Error_TooManyArguments) {
@@ -1903,9 +2298,14 @@
 
   const auto param = GetParam();
 
+  std::stringstream args_tys;
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns * param.rows + 1; i++) {
     args.push_back(Construct(Source{{12, i}}, ty.f32()));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "f32";
   }
 
   auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
@@ -1913,9 +2313,9 @@
   WrapInFunction(tc);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(),
-              HasSubstr("12:1 error: invalid constructor for " +
-                        MatrixStr(param) + "\n\n3 candidates available:"));
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
 }
 
 TEST_P(MatrixConstructorTest,
@@ -1924,10 +2324,15 @@
 
   const auto param = GetParam();
 
+  std::stringstream args_tys;
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns; i++) {
     auto* vec_type = ty.vec<u32>(param.rows);
     args.push_back(Construct(Source{{12, i}}, vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<u32>";
   }
 
   auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
@@ -1935,9 +2340,9 @@
   WrapInFunction(tc);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(),
-              HasSubstr("12:1 error: invalid constructor for " +
-                        MatrixStr(param) + "\n\n3 candidates available:"));
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
 }
 
 TEST_P(MatrixConstructorTest,
@@ -1946,9 +2351,14 @@
 
   const auto param = GetParam();
 
+  std::stringstream args_tys;
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns; i++) {
     args.push_back(Expr(Source{{12, i}}, 1u));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "u32";
   }
 
   auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
@@ -1956,9 +2366,9 @@
   WrapInFunction(tc);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(),
-              HasSubstr("12:1 error: invalid constructor for " +
-                        MatrixStr(param) + "\n\n3 candidates available:"));
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
 }
 
 TEST_P(MatrixConstructorTest,
@@ -1972,23 +2382,29 @@
     return;
   }
 
+  std::stringstream args_tys;
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns - 1; i++) {
     auto* valid_vec_type = ty.vec<f32>(param.rows);
     args.push_back(Construct(Source{{12, i}}, valid_vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<f32>";
   }
   const size_t kInvalidLoc = 2 * (param.columns - 1);
   auto* invalid_vec_type = ty.vec<f32>(param.rows - 1);
   args.push_back(Construct(Source{{12, kInvalidLoc}}, invalid_vec_type));
+  args_tys << ", vec" << (param.rows - 1) << "<f32>";
 
   auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
   auto* tc = Construct(Source{}, matrix_type, std::move(args));
   WrapInFunction(tc);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(),
-              HasSubstr("12:1 error: invalid constructor for " +
-                        MatrixStr(param) + "\n\n3 candidates available:"));
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
 }
 
 TEST_P(MatrixConstructorTest,
@@ -1997,28 +2413,34 @@
 
   const auto param = GetParam();
 
-  // Skip the test if parameters would have resuled in an invalid vec5 type.
+  // Skip the test if parameters would have resulted in an invalid vec5 type.
   if (param.rows == 4) {
     return;
   }
 
+  std::stringstream args_tys;
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns - 1; i++) {
     auto* valid_vec_type = ty.vec<f32>(param.rows);
     args.push_back(Construct(Source{{12, i}}, valid_vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<f32>";
   }
   const size_t kInvalidLoc = 2 * (param.columns - 1);
   auto* invalid_vec_type = ty.vec<f32>(param.rows + 1);
   args.push_back(Construct(Source{{12, kInvalidLoc}}, invalid_vec_type));
+  args_tys << ", vec" << (param.rows + 1) << "<f32>";
 
   auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
   auto* tc = Construct(Source{}, matrix_type, std::move(args));
   WrapInFunction(tc);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(),
-              HasSubstr("12:1 error: invalid constructor for " +
-                        MatrixStr(param) + "\n\n3 candidates available:"));
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
 }
 
 TEST_P(MatrixConstructorTest, Expr_Constructor_ZeroValue_Success) {
@@ -2073,10 +2495,15 @@
   const auto param = GetParam();
   auto* f32_alias = Alias("Float32", ty.f32());
 
+  std::stringstream args_tys;
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns; i++) {
     auto* vec_type = ty.vec(ty.u32(), param.rows);
     args.push_back(Construct(Source{{12, i}}, vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<u32>";
   }
 
   auto* matrix_type = ty.mat(ty.Of(f32_alias), param.columns, param.rows);
@@ -2084,9 +2511,9 @@
   WrapInFunction(tc);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(),
-              HasSubstr("12:1 error: invalid constructor for " +
-                        MatrixStr(param) + "\n\n3 candidates available:"));
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
 }
 
 TEST_P(MatrixConstructorTest, Expr_Constructor_ElementTypeAlias_Success) {
@@ -2116,8 +2543,9 @@
   WrapInFunction(tc);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(12:34 error: invalid constructor for mat2x2<f32>
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: no matching constructor mat2x2<f32>(vec2<u32>, vec2<f32>)
 
 3 candidates available:
   mat2x2<f32>()
@@ -2148,19 +2576,24 @@
   auto* matrix_type = ty.mat<f32>(param.columns, param.rows);
   auto* f32_alias = Alias("UnsignedInt", ty.u32());
 
+  std::stringstream args_tys;
   ast::ExpressionList args;
   for (uint32_t i = 1; i <= param.columns; i++) {
     auto* vec_type = ty.vec(ty.Of(f32_alias), param.rows);
     args.push_back(Construct(Source{{12, i}}, vec_type));
+    if (i > 1) {
+      args_tys << ", ";
+    }
+    args_tys << "vec" << param.rows << "<u32>";
   }
 
   auto* tc = Construct(Source{}, matrix_type, std::move(args));
   WrapInFunction(tc);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_THAT(r()->error(),
-              HasSubstr("12:1 error: invalid constructor for " +
-                        MatrixStr(param) + "\n\n3 candidates available:"));
+  EXPECT_THAT(r()->error(), HasSubstr("12:1 error: no matching constructor " +
+                                      MatrixStr(param) + "(" + args_tys.str() +
+                                      ")\n\n3 candidates available:"));
 }
 
 TEST_P(MatrixConstructorTest,
@@ -2181,6 +2614,91 @@
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
+TEST_P(MatrixConstructorTest, InferElementTypeFromVectors) {
+  const auto param = GetParam();
+
+  ast::ExpressionList args;
+  for (uint32_t i = 1; i <= param.columns; i++) {
+    args.push_back(Construct(ty.vec<f32>(param.rows)));
+  }
+
+  auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
+  auto* tc = Construct(Source{}, matrix_type, std::move(args));
+  WrapInFunction(tc);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_P(MatrixConstructorTest, InferElementTypeFromScalars) {
+  const auto param = GetParam();
+
+  ast::ExpressionList args;
+  for (uint32_t i = 0; i < param.rows * param.columns; i++) {
+    args.push_back(Expr(static_cast<f32>(i)));
+  }
+
+  auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
+  WrapInFunction(Construct(Source{{12, 34}}, matrix_type, std::move(args)));
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_P(MatrixConstructorTest, CannotInferElementTypeFromVectors_Mismatch) {
+  const auto param = GetParam();
+
+  std::stringstream err;
+  err << "12:34 error: cannot infer matrix element type, as constructor "
+         "arguments have different types";
+
+  ast::ExpressionList args;
+  for (uint32_t i = 0; i < param.columns; i++) {
+    err << "\n";
+    auto src = Source{{1, 10 + i}};
+    if (i == 1) {
+      // Odd one out
+      args.push_back(Construct(src, ty.vec<i32>(param.rows)));
+      err << src << " note: argument " << i << " has type vec" << param.rows
+          << "<i32>";
+    } else {
+      args.push_back(Construct(src, ty.vec<f32>(param.rows)));
+      err << src << " note: argument " << i << " has type vec" << param.rows
+          << "<f32>";
+    }
+  }
+
+  auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
+  WrapInFunction(Construct(Source{{12, 34}}, matrix_type, std::move(args)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), err.str());
+}
+
+TEST_P(MatrixConstructorTest, CannotInferElementTypeFromScalars_Mismatch) {
+  const auto param = GetParam();
+
+  std::stringstream err;
+  err << "12:34 error: cannot infer matrix element type, as constructor "
+         "arguments have different types";
+  ast::ExpressionList args;
+  for (uint32_t i = 0; i < param.rows * param.columns; i++) {
+    err << "\n";
+    auto src = Source{{1, 10 + i}};
+    if (i == 3) {
+      args.push_back(Expr(src, static_cast<i32>(i)));  // The odd one out
+      err << src << " note: argument " << i << " has type i32";
+    } else {
+      args.push_back(Expr(src, static_cast<f32>(i)));
+      err << src << " note: argument " << i << " has type f32";
+    }
+  }
+
+  auto* matrix_type = create<ast::Matrix>(nullptr, param.rows, param.columns);
+  WrapInFunction(Construct(Source{{12, 34}}, matrix_type, std::move(args)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_THAT(r()->error(), err.str());
+}
+
 INSTANTIATE_TEST_SUITE_P(ResolverTypeConstructorValidationTest,
                          MatrixConstructorTest,
                          testing::Values(MatrixDimensions{2, 2},
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index 6c71d67..82037b3 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -416,6 +416,29 @@
             "a struct");
 }
 
+TEST_F(ResolverTypeValidationTest, Struct_Member_VectorNoType) {
+  // struct S {
+  //   a: vec3;
+  // };
+
+  Structure("S",
+            {Member("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+}
+
+TEST_F(ResolverTypeValidationTest, Struct_Member_MatrixNoType) {
+  // struct S {
+  //   a: mat3x3;
+  // };
+  Structure(
+      "S", {Member("a", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
+}
+
 TEST_F(ResolverTypeValidationTest, Struct_TooBig) {
   // struct Foo {
   //   a: array<f32, 0x20000000>;
@@ -795,9 +818,9 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
   } else {
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(
-        r()->error(),
-        "12:34 error: cube dimensions for storage textures are not supported");
+    EXPECT_EQ(r()->error(),
+              "12:34 error: cube dimensions for storage textures are not "
+              "supported");
   }
 }
 INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
@@ -1064,9 +1087,9 @@
   Global("a", ty.vec(Source{{12, 34}}, params.elem_ty(*this), params.width),
          ast::StorageClass::kPrivate);
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error: vector element type must be 'bool', 'f32', 'i32' or 'u32'");
+  EXPECT_EQ(r()->error(),
+            "12:34 error: vector element type must be 'bool', 'f32', 'i32' "
+            "or 'u32'");
 }
 INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
                          InvalidVectorElementTypes,
diff --git a/src/resolver/var_let_validation_test.cc b/src/resolver/var_let_validation_test.cc
index 6326f3c..bc0377a 100644
--- a/src/resolver/var_let_validation_test.cc
+++ b/src/resolver/var_let_validation_test.cc
@@ -307,6 +307,42 @@
             "storage classes 'private' and 'function'");
 }
 
+TEST_F(ResolverVarLetValidationTest, VectorLetNoType) {
+  // let a : mat3x3 = mat3x3<f32>();
+  WrapInFunction(Const("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3),
+                       vec3<f32>()));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+}
+
+TEST_F(ResolverVarLetValidationTest, VectorVarNoType) {
+  // var a : mat3x3;
+  WrapInFunction(Var("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
+}
+
+TEST_F(ResolverVarLetValidationTest, MatrixLetNoType) {
+  // let a : mat3x3 = mat3x3<f32>();
+  WrapInFunction(Const("a",
+                       create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3),
+                       mat3x3<f32>()));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
+}
+
+TEST_F(ResolverVarLetValidationTest, MatrixVarNoType) {
+  // var a : mat3x3;
+  WrapInFunction(
+      Var("a", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3)));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
+}
+
 }  // namespace
 }  // namespace resolver
 }  // namespace tint
diff --git a/src/writer/wgsl/generator_impl.cc b/src/writer/wgsl/generator_impl.cc
index 29ea3ef..0140fe7 100644
--- a/src/writer/wgsl/generator_impl.cc
+++ b/src/writer/wgsl/generator_impl.cc
@@ -393,11 +393,14 @@
   } else if (ty->Is<ast::I32>()) {
     out << "i32";
   } else if (auto* mat = ty->As<ast::Matrix>()) {
-    out << "mat" << mat->columns << "x" << mat->rows << "<";
-    if (!EmitType(out, mat->type)) {
-      return false;
+    out << "mat" << mat->columns << "x" << mat->rows;
+    if (auto* el_ty = mat->type) {
+      out << "<";
+      if (!EmitType(out, el_ty)) {
+        return false;
+      }
+      out << ">";
     }
-    out << ">";
   } else if (auto* ptr = ty->As<ast::Pointer>()) {
     out << "ptr<" << ptr->storage_class << ", ";
     if (!EmitType(out, ptr->type)) {
@@ -493,11 +496,14 @@
   } else if (ty->Is<ast::U32>()) {
     out << "u32";
   } else if (auto* vec = ty->As<ast::Vector>()) {
-    out << "vec" << vec->width << "<";
-    if (!EmitType(out, vec->type)) {
-      return false;
+    out << "vec" << vec->width;
+    if (auto* el_ty = vec->type) {
+      out << "<";
+      if (!EmitType(out, el_ty)) {
+        return false;
+      }
+      out << ">";
     }
-    out << ">";
   } else if (ty->Is<ast::Void>()) {
     out << "void";
   } else if (auto* tn = ty->As<ast::TypeName>()) {
diff --git a/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl b/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl
new file mode 100644
index 0000000..0195661
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl
@@ -0,0 +1,2 @@
+let m = mat2x2(0.0, 1.0,
+               2.0, 3.0);
diff --git a/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..6dd93cf
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float2x2 m = float2x2(0.0f, 1.0f, 2.0f, 3.0f);
diff --git a/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.msl
new file mode 100644
index 0000000..e33850f
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float2x2 m = float2x2(float2(0.0f, 1.0f), float2(2.0f, 3.0f));
diff --git a/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..16c6345
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.spvasm
@@ -0,0 +1,27 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 15
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat2v2float = OpTypeMatrix %v2float 2
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+          %6 = OpConstantComposite %v2float %float_0 %float_1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+          %9 = OpConstantComposite %v2float %float_2 %float_3
+          %m = OpConstantComposite %mat2v2float %6 %9
+       %void = OpTypeVoid
+         %11 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %11
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..e4941d5
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x2/inferred/scalars/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat2x2(0.0, 1.0, 2.0, 3.0);
diff --git a/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl b/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl
new file mode 100644
index 0000000..f802821
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl
@@ -0,0 +1,2 @@
+let m = mat2x2(vec2<f32>(0.0, 1.0),
+               vec2<f32>(2.0, 3.0));
diff --git a/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..c2e0d31
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float2x2 m = float2x2(float2(0.0f, 1.0f), float2(2.0f, 3.0f));
diff --git a/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.msl
new file mode 100644
index 0000000..e33850f
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float2x2 m = float2x2(float2(0.0f, 1.0f), float2(2.0f, 3.0f));
diff --git a/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..16c6345
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.spvasm
@@ -0,0 +1,27 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 15
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat2v2float = OpTypeMatrix %v2float 2
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+          %6 = OpConstantComposite %v2float %float_0 %float_1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+          %9 = OpConstantComposite %v2float %float_2 %float_3
+          %m = OpConstantComposite %mat2v2float %6 %9
+       %void = OpTypeVoid
+         %11 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %11
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..9a1f4c6
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x2/inferred/vectors/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat2x2(vec2<f32>(0.0, 1.0), vec2<f32>(2.0, 3.0));
diff --git a/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl b/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl
new file mode 100644
index 0000000..dc7f65f
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl
@@ -0,0 +1,2 @@
+let m = mat2x3(0.0, 1.0, 2.0,
+               3.0, 4.0, 5.0);
diff --git a/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..adc216f
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float2x3 m = float2x3(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f);
diff --git a/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.msl
new file mode 100644
index 0000000..86b17e2
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float2x3 m = float2x3(float3(0.0f, 1.0f, 2.0f), float3(3.0f, 4.0f, 5.0f));
diff --git a/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..d99921c
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.spvasm
@@ -0,0 +1,29 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 17
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+%mat2v3float = OpTypeMatrix %v3float 2
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+          %7 = OpConstantComposite %v3float %float_0 %float_1 %float_2
+    %float_3 = OpConstant %float 3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+         %11 = OpConstantComposite %v3float %float_3 %float_4 %float_5
+          %m = OpConstantComposite %mat2v3float %7 %11
+       %void = OpTypeVoid
+         %13 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %13
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..0be516f
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x3/inferred/scalars/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat2x3(0.0, 1.0, 2.0, 3.0, 4.0, 5.0);
diff --git a/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl b/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl
new file mode 100644
index 0000000..6819a81
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl
@@ -0,0 +1,2 @@
+let m = mat2x3(vec3<f32>(0.0, 1.0, 2.0),
+               vec3<f32>(3.0, 4.0, 5.0));
diff --git a/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..fb12d01
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float2x3 m = float2x3(float3(0.0f, 1.0f, 2.0f), float3(3.0f, 4.0f, 5.0f));
diff --git a/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.msl
new file mode 100644
index 0000000..86b17e2
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float2x3 m = float2x3(float3(0.0f, 1.0f, 2.0f), float3(3.0f, 4.0f, 5.0f));
diff --git a/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..d99921c
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.spvasm
@@ -0,0 +1,29 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 17
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+%mat2v3float = OpTypeMatrix %v3float 2
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+          %7 = OpConstantComposite %v3float %float_0 %float_1 %float_2
+    %float_3 = OpConstant %float 3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+         %11 = OpConstantComposite %v3float %float_3 %float_4 %float_5
+          %m = OpConstantComposite %mat2v3float %7 %11
+       %void = OpTypeVoid
+         %13 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %13
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..092445a
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x3/inferred/vectors/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat2x3(vec3<f32>(0.0, 1.0, 2.0), vec3<f32>(3.0, 4.0, 5.0));
diff --git a/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl b/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl
new file mode 100644
index 0000000..81abe9f
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl
@@ -0,0 +1,2 @@
+let m = mat2x4(0.0, 1.0, 2.0, 3.0,
+               4.0, 5.0, 6.0, 7.0);
diff --git a/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..7bcde85
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float2x4 m = float2x4(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f);
diff --git a/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.msl
new file mode 100644
index 0000000..684918a
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float2x4 m = float2x4(float4(0.0f, 1.0f, 2.0f, 3.0f), float4(4.0f, 5.0f, 6.0f, 7.0f));
diff --git a/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..57110c5
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.spvasm
@@ -0,0 +1,31 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 19
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat2v4float = OpTypeMatrix %v4float 2
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+          %8 = OpConstantComposite %v4float %float_0 %float_1 %float_2 %float_3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+    %float_6 = OpConstant %float 6
+    %float_7 = OpConstant %float 7
+         %13 = OpConstantComposite %v4float %float_4 %float_5 %float_6 %float_7
+          %m = OpConstantComposite %mat2v4float %8 %13
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %15
+         %18 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..bcb4dfe
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x4/inferred/scalars/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat2x4(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0);
diff --git a/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl b/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl
new file mode 100644
index 0000000..c57c2ed
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl
@@ -0,0 +1,2 @@
+let m = mat2x4(vec4<f32>(0.0, 1.0, 2.0, 3.0),
+               vec4<f32>(4.0, 5.0, 6.0, 7.0));
diff --git a/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..5ed2b1b
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float2x4 m = float2x4(float4(0.0f, 1.0f, 2.0f, 3.0f), float4(4.0f, 5.0f, 6.0f, 7.0f));
diff --git a/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.msl
new file mode 100644
index 0000000..684918a
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float2x4 m = float2x4(float4(0.0f, 1.0f, 2.0f, 3.0f), float4(4.0f, 5.0f, 6.0f, 7.0f));
diff --git a/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..57110c5
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.spvasm
@@ -0,0 +1,31 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 19
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat2v4float = OpTypeMatrix %v4float 2
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+          %8 = OpConstantComposite %v4float %float_0 %float_1 %float_2 %float_3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+    %float_6 = OpConstant %float 6
+    %float_7 = OpConstant %float 7
+         %13 = OpConstantComposite %v4float %float_4 %float_5 %float_6 %float_7
+          %m = OpConstantComposite %mat2v4float %8 %13
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %15
+         %18 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..f6335df
--- /dev/null
+++ b/test/expressions/type_ctor/mat2x4/inferred/vectors/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat2x4(vec4<f32>(0.0, 1.0, 2.0, 3.0), vec4<f32>(4.0, 5.0, 6.0, 7.0));
diff --git a/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl b/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl
new file mode 100644
index 0000000..6fbe3b8
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl
@@ -0,0 +1,3 @@
+let m = mat3x2(0.0, 1.0,
+               2.0, 3.0,
+               4.0, 5.0);
diff --git a/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..32160ad
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float3x2 m = float3x2(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f);
diff --git a/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.msl
new file mode 100644
index 0000000..04aad8e
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float3x2 m = float3x2(float2(0.0f, 1.0f), float2(2.0f, 3.0f), float2(4.0f, 5.0f));
diff --git a/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..ffaa504
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.spvasm
@@ -0,0 +1,30 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 18
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat3v2float = OpTypeMatrix %v2float 3
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+          %6 = OpConstantComposite %v2float %float_0 %float_1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+          %9 = OpConstantComposite %v2float %float_2 %float_3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+         %12 = OpConstantComposite %v2float %float_4 %float_5
+          %m = OpConstantComposite %mat3v2float %6 %9 %12
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %14
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..4d48f59
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x2/inferred/scalars/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat3x2(0.0, 1.0, 2.0, 3.0, 4.0, 5.0);
diff --git a/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl b/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl
new file mode 100644
index 0000000..d9950ca
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl
@@ -0,0 +1,3 @@
+let m = mat3x2(vec2<f32>(0.0, 1.0),
+               vec2<f32>(2.0, 3.0),
+               vec2<f32>(4.0, 5.0));
diff --git a/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..27568ed
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float3x2 m = float3x2(float2(0.0f, 1.0f), float2(2.0f, 3.0f), float2(4.0f, 5.0f));
diff --git a/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.msl
new file mode 100644
index 0000000..04aad8e
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float3x2 m = float3x2(float2(0.0f, 1.0f), float2(2.0f, 3.0f), float2(4.0f, 5.0f));
diff --git a/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..ffaa504
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.spvasm
@@ -0,0 +1,30 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 18
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat3v2float = OpTypeMatrix %v2float 3
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+          %6 = OpConstantComposite %v2float %float_0 %float_1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+          %9 = OpConstantComposite %v2float %float_2 %float_3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+         %12 = OpConstantComposite %v2float %float_4 %float_5
+          %m = OpConstantComposite %mat3v2float %6 %9 %12
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %14
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..70fc4e6
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x2/inferred/vectors/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat3x2(vec2<f32>(0.0, 1.0), vec2<f32>(2.0, 3.0), vec2<f32>(4.0, 5.0));
diff --git a/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl b/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl
new file mode 100644
index 0000000..ecdafe7
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl
@@ -0,0 +1,3 @@
+let m = mat3x3(0.0, 1.0, 2.0,
+               3.0, 4.0, 5.0,
+               6.0, 7.0, 8.0);
diff --git a/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..513d8f8
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float3x3 m = float3x3(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f);
diff --git a/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.msl
new file mode 100644
index 0000000..c8c096a
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float3x3 m = float3x3(float3(0.0f, 1.0f, 2.0f), float3(3.0f, 4.0f, 5.0f), float3(6.0f, 7.0f, 8.0f));
diff --git a/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..8ec1da4
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.spvasm
@@ -0,0 +1,33 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 21
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+%mat3v3float = OpTypeMatrix %v3float 3
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+          %7 = OpConstantComposite %v3float %float_0 %float_1 %float_2
+    %float_3 = OpConstant %float 3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+         %11 = OpConstantComposite %v3float %float_3 %float_4 %float_5
+    %float_6 = OpConstant %float 6
+    %float_7 = OpConstant %float 7
+    %float_8 = OpConstant %float 8
+         %15 = OpConstantComposite %v3float %float_6 %float_7 %float_8
+          %m = OpConstantComposite %mat3v3float %7 %11 %15
+       %void = OpTypeVoid
+         %17 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %17
+         %20 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..18ca6e3
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x3/inferred/scalars/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat3x3(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0);
diff --git a/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl b/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl
new file mode 100644
index 0000000..5c2b614
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl
@@ -0,0 +1,3 @@
+let m = mat3x3(vec3<f32>(0.0, 1.0, 2.0),
+               vec3<f32>(3.0, 4.0, 5.0),
+               vec3<f32>(6.0, 7.0, 8.0));
diff --git a/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..c35ca2a
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float3x3 m = float3x3(float3(0.0f, 1.0f, 2.0f), float3(3.0f, 4.0f, 5.0f), float3(6.0f, 7.0f, 8.0f));
diff --git a/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.msl
new file mode 100644
index 0000000..c8c096a
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float3x3 m = float3x3(float3(0.0f, 1.0f, 2.0f), float3(3.0f, 4.0f, 5.0f), float3(6.0f, 7.0f, 8.0f));
diff --git a/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..8ec1da4
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.spvasm
@@ -0,0 +1,33 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 21
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+%mat3v3float = OpTypeMatrix %v3float 3
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+          %7 = OpConstantComposite %v3float %float_0 %float_1 %float_2
+    %float_3 = OpConstant %float 3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+         %11 = OpConstantComposite %v3float %float_3 %float_4 %float_5
+    %float_6 = OpConstant %float 6
+    %float_7 = OpConstant %float 7
+    %float_8 = OpConstant %float 8
+         %15 = OpConstantComposite %v3float %float_6 %float_7 %float_8
+          %m = OpConstantComposite %mat3v3float %7 %11 %15
+       %void = OpTypeVoid
+         %17 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %17
+         %20 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..9fdc4c9
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x3/inferred/vectors/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat3x3(vec3<f32>(0.0, 1.0, 2.0), vec3<f32>(3.0, 4.0, 5.0), vec3<f32>(6.0, 7.0, 8.0));
diff --git a/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl b/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl
new file mode 100644
index 0000000..cce8161
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl
@@ -0,0 +1,3 @@
+let m = mat3x4(0.0, 1.0, 2.0, 3.0,
+               4.0, 5.0, 6.0, 7.0,
+               8.0, 9.0, 10.0, 11.0);
diff --git a/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..c76e3b8
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float3x4 m = float3x4(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f);
diff --git a/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.msl
new file mode 100644
index 0000000..114cb09
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float3x4 m = float3x4(float4(0.0f, 1.0f, 2.0f, 3.0f), float4(4.0f, 5.0f, 6.0f, 7.0f), float4(8.0f, 9.0f, 10.0f, 11.0f));
diff --git a/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..b6ff57d
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.spvasm
@@ -0,0 +1,36 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 24
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat3v4float = OpTypeMatrix %v4float 3
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+          %8 = OpConstantComposite %v4float %float_0 %float_1 %float_2 %float_3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+    %float_6 = OpConstant %float 6
+    %float_7 = OpConstant %float 7
+         %13 = OpConstantComposite %v4float %float_4 %float_5 %float_6 %float_7
+    %float_8 = OpConstant %float 8
+    %float_9 = OpConstant %float 9
+   %float_10 = OpConstant %float 10
+   %float_11 = OpConstant %float 11
+         %18 = OpConstantComposite %v4float %float_8 %float_9 %float_10 %float_11
+          %m = OpConstantComposite %mat3v4float %8 %13 %18
+       %void = OpTypeVoid
+         %20 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %20
+         %23 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..5fce07f
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x4/inferred/scalars/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat3x4(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0);
diff --git a/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl b/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl
new file mode 100644
index 0000000..eb6fad2
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl
@@ -0,0 +1,3 @@
+let m = mat3x4(vec4<f32>(0.0, 1.0, 2.0, 3.0),
+               vec4<f32>(4.0, 5.0, 6.0, 7.0),
+               vec4<f32>(8.0, 9.0, 10.0, 11.0));
diff --git a/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..fae68df
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float3x4 m = float3x4(float4(0.0f, 1.0f, 2.0f, 3.0f), float4(4.0f, 5.0f, 6.0f, 7.0f), float4(8.0f, 9.0f, 10.0f, 11.0f));
diff --git a/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.msl
new file mode 100644
index 0000000..114cb09
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float3x4 m = float3x4(float4(0.0f, 1.0f, 2.0f, 3.0f), float4(4.0f, 5.0f, 6.0f, 7.0f), float4(8.0f, 9.0f, 10.0f, 11.0f));
diff --git a/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..b6ff57d
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.spvasm
@@ -0,0 +1,36 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 24
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat3v4float = OpTypeMatrix %v4float 3
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+          %8 = OpConstantComposite %v4float %float_0 %float_1 %float_2 %float_3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+    %float_6 = OpConstant %float 6
+    %float_7 = OpConstant %float 7
+         %13 = OpConstantComposite %v4float %float_4 %float_5 %float_6 %float_7
+    %float_8 = OpConstant %float 8
+    %float_9 = OpConstant %float 9
+   %float_10 = OpConstant %float 10
+   %float_11 = OpConstant %float 11
+         %18 = OpConstantComposite %v4float %float_8 %float_9 %float_10 %float_11
+          %m = OpConstantComposite %mat3v4float %8 %13 %18
+       %void = OpTypeVoid
+         %20 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %20
+         %23 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..6d3960e
--- /dev/null
+++ b/test/expressions/type_ctor/mat3x4/inferred/vectors/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat3x4(vec4<f32>(0.0, 1.0, 2.0, 3.0), vec4<f32>(4.0, 5.0, 6.0, 7.0), vec4<f32>(8.0, 9.0, 10.0, 11.0));
diff --git a/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl b/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl
new file mode 100644
index 0000000..f0946b9
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl
@@ -0,0 +1,4 @@
+let m = mat4x2(0.0, 1.0,
+               2.0, 3.0,
+               4.0, 5.0,
+               6.0, 7.0);
diff --git a/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..de86670
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float4x2 m = float4x2(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f);
diff --git a/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.msl
new file mode 100644
index 0000000..0df65ff
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float4x2 m = float4x2(float2(0.0f, 1.0f), float2(2.0f, 3.0f), float2(4.0f, 5.0f), float2(6.0f, 7.0f));
diff --git a/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..c6a6c63
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.spvasm
@@ -0,0 +1,33 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 21
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat4v2float = OpTypeMatrix %v2float 4
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+          %6 = OpConstantComposite %v2float %float_0 %float_1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+          %9 = OpConstantComposite %v2float %float_2 %float_3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+         %12 = OpConstantComposite %v2float %float_4 %float_5
+    %float_6 = OpConstant %float 6
+    %float_7 = OpConstant %float 7
+         %15 = OpConstantComposite %v2float %float_6 %float_7
+          %m = OpConstantComposite %mat4v2float %6 %9 %12 %15
+       %void = OpTypeVoid
+         %17 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %17
+         %20 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..8f69508
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x2/inferred/scalars/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat4x2(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0);
diff --git a/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl b/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl
new file mode 100644
index 0000000..c1bd5ee
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl
@@ -0,0 +1,4 @@
+let m = mat4x2(vec2<f32>(0.0, 1.0),
+               vec2<f32>(2.0, 3.0),
+               vec2<f32>(4.0, 5.0),
+               vec2<f32>(6.0, 7.0));
diff --git a/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..2acab36
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float4x2 m = float4x2(float2(0.0f, 1.0f), float2(2.0f, 3.0f), float2(4.0f, 5.0f), float2(6.0f, 7.0f));
diff --git a/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.msl
new file mode 100644
index 0000000..0df65ff
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float4x2 m = float4x2(float2(0.0f, 1.0f), float2(2.0f, 3.0f), float2(4.0f, 5.0f), float2(6.0f, 7.0f));
diff --git a/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..c6a6c63
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.spvasm
@@ -0,0 +1,33 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 21
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat4v2float = OpTypeMatrix %v2float 4
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+          %6 = OpConstantComposite %v2float %float_0 %float_1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+          %9 = OpConstantComposite %v2float %float_2 %float_3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+         %12 = OpConstantComposite %v2float %float_4 %float_5
+    %float_6 = OpConstant %float 6
+    %float_7 = OpConstant %float 7
+         %15 = OpConstantComposite %v2float %float_6 %float_7
+          %m = OpConstantComposite %mat4v2float %6 %9 %12 %15
+       %void = OpTypeVoid
+         %17 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %17
+         %20 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..ca404bf
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x2/inferred/vectors/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat4x2(vec2<f32>(0.0, 1.0), vec2<f32>(2.0, 3.0), vec2<f32>(4.0, 5.0), vec2<f32>(6.0, 7.0));
diff --git a/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl b/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl
new file mode 100644
index 0000000..32326a9
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl
@@ -0,0 +1,4 @@
+let m = mat4x3(0.0, 1.0, 2.0,
+               3.0, 4.0, 5.0,
+               6.0, 7.0, 8.0,
+               9.0, 10.0, 11.0);
diff --git a/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..38a7333
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float4x3 m = float4x3(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f);
diff --git a/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.msl
new file mode 100644
index 0000000..2a7c078
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float4x3 m = float4x3(float3(0.0f, 1.0f, 2.0f), float3(3.0f, 4.0f, 5.0f), float3(6.0f, 7.0f, 8.0f), float3(9.0f, 10.0f, 11.0f));
diff --git a/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..d99b492
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.spvasm
@@ -0,0 +1,37 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 25
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+%mat4v3float = OpTypeMatrix %v3float 4
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+          %7 = OpConstantComposite %v3float %float_0 %float_1 %float_2
+    %float_3 = OpConstant %float 3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+         %11 = OpConstantComposite %v3float %float_3 %float_4 %float_5
+    %float_6 = OpConstant %float 6
+    %float_7 = OpConstant %float 7
+    %float_8 = OpConstant %float 8
+         %15 = OpConstantComposite %v3float %float_6 %float_7 %float_8
+    %float_9 = OpConstant %float 9
+   %float_10 = OpConstant %float 10
+   %float_11 = OpConstant %float 11
+         %19 = OpConstantComposite %v3float %float_9 %float_10 %float_11
+          %m = OpConstantComposite %mat4v3float %7 %11 %15 %19
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %21
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..209a376
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x3/inferred/scalars/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat4x3(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0);
diff --git a/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl b/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl
new file mode 100644
index 0000000..a3ae9f2
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl
@@ -0,0 +1,4 @@
+let m = mat4x3(vec3<f32>(0.0, 1.0, 2.0),
+               vec3<f32>(3.0, 4.0, 5.0),
+               vec3<f32>(6.0, 7.0, 8.0),
+               vec3<f32>(9.0, 10.0, 11.0));
diff --git a/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..254cc40
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float4x3 m = float4x3(float3(0.0f, 1.0f, 2.0f), float3(3.0f, 4.0f, 5.0f), float3(6.0f, 7.0f, 8.0f), float3(9.0f, 10.0f, 11.0f));
diff --git a/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.msl
new file mode 100644
index 0000000..2a7c078
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float4x3 m = float4x3(float3(0.0f, 1.0f, 2.0f), float3(3.0f, 4.0f, 5.0f), float3(6.0f, 7.0f, 8.0f), float3(9.0f, 10.0f, 11.0f));
diff --git a/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..d99b492
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.spvasm
@@ -0,0 +1,37 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 25
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+%mat4v3float = OpTypeMatrix %v3float 4
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+          %7 = OpConstantComposite %v3float %float_0 %float_1 %float_2
+    %float_3 = OpConstant %float 3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+         %11 = OpConstantComposite %v3float %float_3 %float_4 %float_5
+    %float_6 = OpConstant %float 6
+    %float_7 = OpConstant %float 7
+    %float_8 = OpConstant %float 8
+         %15 = OpConstantComposite %v3float %float_6 %float_7 %float_8
+    %float_9 = OpConstant %float 9
+   %float_10 = OpConstant %float 10
+   %float_11 = OpConstant %float 11
+         %19 = OpConstantComposite %v3float %float_9 %float_10 %float_11
+          %m = OpConstantComposite %mat4v3float %7 %11 %15 %19
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %21
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..ca7b45f
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x3/inferred/vectors/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat4x3(vec3<f32>(0.0, 1.0, 2.0), vec3<f32>(3.0, 4.0, 5.0), vec3<f32>(6.0, 7.0, 8.0), vec3<f32>(9.0, 10.0, 11.0));
diff --git a/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl b/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl
new file mode 100644
index 0000000..571610f
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl
@@ -0,0 +1,4 @@
+let m = mat4x4(0.0, 1.0, 2.0, 3.0,
+               4.0, 5.0, 6.0, 7.0,
+               8.0, 9.0, 10.0, 11.0,
+               12.0, 13.0, 14.0, 15.0);
diff --git a/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..3a70922
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float4x4 m = float4x4(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f);
diff --git a/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.msl
new file mode 100644
index 0000000..6fb51a2
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float4x4 m = float4x4(float4(0.0f, 1.0f, 2.0f, 3.0f), float4(4.0f, 5.0f, 6.0f, 7.0f), float4(8.0f, 9.0f, 10.0f, 11.0f), float4(12.0f, 13.0f, 14.0f, 15.0f));
diff --git a/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..84a8f89
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.spvasm
@@ -0,0 +1,41 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat4v4float = OpTypeMatrix %v4float 4
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+          %8 = OpConstantComposite %v4float %float_0 %float_1 %float_2 %float_3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+    %float_6 = OpConstant %float 6
+    %float_7 = OpConstant %float 7
+         %13 = OpConstantComposite %v4float %float_4 %float_5 %float_6 %float_7
+    %float_8 = OpConstant %float 8
+    %float_9 = OpConstant %float 9
+   %float_10 = OpConstant %float 10
+   %float_11 = OpConstant %float 11
+         %18 = OpConstantComposite %v4float %float_8 %float_9 %float_10 %float_11
+   %float_12 = OpConstant %float 12
+   %float_13 = OpConstant %float 13
+   %float_14 = OpConstant %float 14
+   %float_15 = OpConstant %float 15
+         %23 = OpConstantComposite %v4float %float_12 %float_13 %float_14 %float_15
+          %m = OpConstantComposite %mat4v4float %8 %13 %18 %23
+       %void = OpTypeVoid
+         %25 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %25
+         %28 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..33b0598
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x4/inferred/scalars/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat4x4(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0);
diff --git a/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl b/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl
new file mode 100644
index 0000000..1e2c0d7
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl
@@ -0,0 +1,4 @@
+let m = mat4x4(vec4<f32>(0.0, 1.0, 2.0, 3.0),
+               vec4<f32>(4.0, 5.0, 6.0, 7.0),
+               vec4<f32>(8.0, 9.0, 10.0, 11.0),
+               vec4<f32>(12.0, 13.0, 14.0, 15.0));
diff --git a/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..2707999
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float4x4 m = float4x4(float4(0.0f, 1.0f, 2.0f, 3.0f), float4(4.0f, 5.0f, 6.0f, 7.0f), float4(8.0f, 9.0f, 10.0f, 11.0f), float4(12.0f, 13.0f, 14.0f, 15.0f));
diff --git a/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.msl b/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.msl
new file mode 100644
index 0000000..6fb51a2
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float4x4 m = float4x4(float4(0.0f, 1.0f, 2.0f, 3.0f), float4(4.0f, 5.0f, 6.0f, 7.0f), float4(8.0f, 9.0f, 10.0f, 11.0f), float4(12.0f, 13.0f, 14.0f, 15.0f));
diff --git a/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..84a8f89
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.spvasm
@@ -0,0 +1,41 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %m "m"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat4v4float = OpTypeMatrix %v4float 4
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+          %8 = OpConstantComposite %v4float %float_0 %float_1 %float_2 %float_3
+    %float_4 = OpConstant %float 4
+    %float_5 = OpConstant %float 5
+    %float_6 = OpConstant %float 6
+    %float_7 = OpConstant %float 7
+         %13 = OpConstantComposite %v4float %float_4 %float_5 %float_6 %float_7
+    %float_8 = OpConstant %float 8
+    %float_9 = OpConstant %float 9
+   %float_10 = OpConstant %float 10
+   %float_11 = OpConstant %float 11
+         %18 = OpConstantComposite %v4float %float_8 %float_9 %float_10 %float_11
+   %float_12 = OpConstant %float 12
+   %float_13 = OpConstant %float 13
+   %float_14 = OpConstant %float 14
+   %float_15 = OpConstant %float 15
+         %23 = OpConstantComposite %v4float %float_12 %float_13 %float_14 %float_15
+          %m = OpConstantComposite %mat4v4float %8 %13 %18 %23
+       %void = OpTypeVoid
+         %25 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %25
+         %28 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..b022afe
--- /dev/null
+++ b/test/expressions/type_ctor/mat4x4/inferred/vectors/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let m = mat4x4(vec4<f32>(0.0, 1.0, 2.0, 3.0), vec4<f32>(4.0, 5.0, 6.0, 7.0), vec4<f32>(8.0, 9.0, 10.0, 11.0), vec4<f32>(12.0, 13.0, 14.0, 15.0));
diff --git a/test/expressions/type_ctor/vec2/inferred/bool.wgsl b/test/expressions/type_ctor/vec2/inferred/bool.wgsl
new file mode 100644
index 0000000..f5aced3
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/bool.wgsl
@@ -0,0 +1 @@
+let v = vec2(false, true);
diff --git a/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.hlsl b/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.hlsl
new file mode 100644
index 0000000..0ba8ae6
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const bool2 v = bool2(false, true);
diff --git a/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.msl b/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.msl
new file mode 100644
index 0000000..3103a85
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant bool2 v = bool2(false, true);
diff --git a/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.spvasm b/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.spvasm
new file mode 100644
index 0000000..b982dbd
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.spvasm
@@ -0,0 +1,22 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 10
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %v "v"
+               OpName %unused_entry_point "unused_entry_point"
+       %bool = OpTypeBool
+     %v2bool = OpTypeVector %bool 2
+      %false = OpConstantFalse %bool
+       %true = OpConstantTrue %bool
+          %v = OpConstantComposite %v2bool %false %true
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %6
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.wgsl b/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.wgsl
new file mode 100644
index 0000000..f5aced3
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/bool.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let v = vec2(false, true);
diff --git a/test/expressions/type_ctor/vec2/inferred/f32.wgsl b/test/expressions/type_ctor/vec2/inferred/f32.wgsl
new file mode 100644
index 0000000..e0e5dc9
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/f32.wgsl
@@ -0,0 +1 @@
+let v = vec2(0.0, 1.0);
diff --git a/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.hlsl b/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.hlsl
new file mode 100644
index 0000000..8802bbb
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const float2 v = float2(0.0f, 1.0f);
diff --git a/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.msl b/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.msl
new file mode 100644
index 0000000..6e66829
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant float2 v = float2(0.0f, 1.0f);
diff --git a/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.spvasm b/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..d12bbf2
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.spvasm
@@ -0,0 +1,22 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 10
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %v "v"
+               OpName %unused_entry_point "unused_entry_point"
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+          %v = OpConstantComposite %v2float %float_0 %float_1
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %6
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..e0e5dc9
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/f32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let v = vec2(0.0, 1.0);
diff --git a/test/expressions/type_ctor/vec2/inferred/i32.wgsl b/test/expressions/type_ctor/vec2/inferred/i32.wgsl
new file mode 100644
index 0000000..de5642e
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/i32.wgsl
@@ -0,0 +1 @@
+let v = vec2(0, 1);
diff --git a/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.hlsl b/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.hlsl
new file mode 100644
index 0000000..afa632e
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const int2 v = int2(0, 1);
diff --git a/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.msl b/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.msl
new file mode 100644
index 0000000..2a0fc8d
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant int2 v = int2(0, 1);
diff --git a/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.spvasm b/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.spvasm
new file mode 100644
index 0000000..2e021f9
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.spvasm
@@ -0,0 +1,22 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 10
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %v "v"
+               OpName %unused_entry_point "unused_entry_point"
+        %int = OpTypeInt 32 1
+      %v2int = OpTypeVector %int 2
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+          %v = OpConstantComposite %v2int %int_0 %int_1
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %6
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.wgsl b/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.wgsl
new file mode 100644
index 0000000..de5642e
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/i32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let v = vec2(0, 1);
diff --git a/test/expressions/type_ctor/vec2/inferred/u32.wgsl b/test/expressions/type_ctor/vec2/inferred/u32.wgsl
new file mode 100644
index 0000000..bd3e939
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/u32.wgsl
@@ -0,0 +1 @@
+let v = vec2(0u, 1u);
diff --git a/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.hlsl b/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.hlsl
new file mode 100644
index 0000000..c298713
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.hlsl
@@ -0,0 +1,6 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+static const uint2 v = uint2(0u, 1u);
diff --git a/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.msl b/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.msl
new file mode 100644
index 0000000..132ee56
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.msl
@@ -0,0 +1,4 @@
+#include <metal_stdlib>
+
+using namespace metal;
+constant uint2 v = uint2(0u, 1u);
diff --git a/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.spvasm b/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.spvasm
new file mode 100644
index 0000000..8c3ad8f
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.spvasm
@@ -0,0 +1,22 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 10
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %v "v"
+               OpName %unused_entry_point "unused_entry_point"
+       %uint = OpTypeInt 32 0
+     %v2uint = OpTypeVector %uint 2
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+          %v = OpConstantComposite %v2uint %uint_0 %uint_1
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %6
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.wgsl b/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.wgsl
new file mode 100644
index 0000000..bd3e939
--- /dev/null
+++ b/test/expressions/type_ctor/vec2/inferred/u32.wgsl.expected.wgsl
@@ -0,0 +1 @@
+let v = vec2(0u, 1u);
diff --git a/test/expressions/type_ctor/vec3/explicit/bool.wgsl b/test/expressions/type_ctor/vec3/explicit/bool.wgsl
index 0acf3bf..9f3a1f3 100644
--- a/test/expressions/type_ctor/vec3/explicit/bool.wgsl
+++ b/test/expressions/type_ctor/vec3/explicit/bool.wgsl
@@ -1 +1 @@
-let v = vec3<bool>(false, true, false);
+let v = vec3(false, true, false);
diff --git a/test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.wgsl b/test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.wgsl
index 0acf3bf..9f3a1f3 100644
--- a/test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.wgsl
+++ b/test/expressions/type_ctor/vec3/explicit/bool.wgsl.expected.wgsl
@@ -1 +1 @@
-let v = vec3<bool>(false, true, false);
+let v = vec3(false, true, false);
diff --git a/test/expressions/type_ctor/vec3/explicit/f32.wgsl b/test/expressions/type_ctor/vec3/explicit/f32.wgsl
index 2164502..376f901 100644
--- a/test/expressions/type_ctor/vec3/explicit/f32.wgsl
+++ b/test/expressions/type_ctor/vec3/explicit/f32.wgsl
@@ -1 +1 @@
-let v = vec3<f32>(0.0, 1.0, 2.0);
+let v = vec3(0.0, 1.0, 2.0);
diff --git a/test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.wgsl
index 2164502..376f901 100644
--- a/test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.wgsl
+++ b/test/expressions/type_ctor/vec3/explicit/f32.wgsl.expected.wgsl
@@ -1 +1 @@
-let v = vec3<f32>(0.0, 1.0, 2.0);
+let v = vec3(0.0, 1.0, 2.0);
diff --git a/test/expressions/type_ctor/vec3/explicit/i32.wgsl b/test/expressions/type_ctor/vec3/explicit/i32.wgsl
index 7d28b9e..7b9ebcb 100644
--- a/test/expressions/type_ctor/vec3/explicit/i32.wgsl
+++ b/test/expressions/type_ctor/vec3/explicit/i32.wgsl
@@ -1 +1 @@
-let v = vec3<i32>(0, 1, 2);
+let v = vec3(0, 1, 2);
diff --git a/test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.wgsl b/test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.wgsl
index 7d28b9e..7b9ebcb 100644
--- a/test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.wgsl
+++ b/test/expressions/type_ctor/vec3/explicit/i32.wgsl.expected.wgsl
@@ -1 +1 @@
-let v = vec3<i32>(0, 1, 2);
+let v = vec3(0, 1, 2);
diff --git a/test/expressions/type_ctor/vec3/explicit/u32.wgsl b/test/expressions/type_ctor/vec3/explicit/u32.wgsl
index 111a1d3..0040d27 100644
--- a/test/expressions/type_ctor/vec3/explicit/u32.wgsl
+++ b/test/expressions/type_ctor/vec3/explicit/u32.wgsl
@@ -1 +1 @@
-let v = vec3<u32>(0u, 1u, 2u);
+let v = vec3(0u, 1u, 2u);
diff --git a/test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.wgsl b/test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.wgsl
index 111a1d3..0040d27 100644
--- a/test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.wgsl
+++ b/test/expressions/type_ctor/vec3/explicit/u32.wgsl.expected.wgsl
@@ -1 +1 @@
-let v = vec3<u32>(0u, 1u, 2u);
+let v = vec3(0u, 1u, 2u);
diff --git a/test/expressions/type_ctor/vec4/explicit/bool.wgsl b/test/expressions/type_ctor/vec4/explicit/bool.wgsl
index 70cb499..baeff0f 100644
--- a/test/expressions/type_ctor/vec4/explicit/bool.wgsl
+++ b/test/expressions/type_ctor/vec4/explicit/bool.wgsl
@@ -1 +1 @@
-let v = vec4<bool>(false, true, false, true);
+let v = vec4(false, true, false, true);
diff --git a/test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.wgsl b/test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.wgsl
index 70cb499..baeff0f 100644
--- a/test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.wgsl
+++ b/test/expressions/type_ctor/vec4/explicit/bool.wgsl.expected.wgsl
@@ -1 +1 @@
-let v = vec4<bool>(false, true, false, true);
+let v = vec4(false, true, false, true);
diff --git a/test/expressions/type_ctor/vec4/explicit/f32.wgsl b/test/expressions/type_ctor/vec4/explicit/f32.wgsl
index 3e7b8b6..bb1280e 100644
--- a/test/expressions/type_ctor/vec4/explicit/f32.wgsl
+++ b/test/expressions/type_ctor/vec4/explicit/f32.wgsl
@@ -1 +1 @@
-let v = vec4<f32>(0.0, 1.0, 2.0, 3.0);
+let v = vec4(0.0, 1.0, 2.0, 3.0);
diff --git a/test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.wgsl b/test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.wgsl
index 3e7b8b6..bb1280e 100644
--- a/test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.wgsl
+++ b/test/expressions/type_ctor/vec4/explicit/f32.wgsl.expected.wgsl
@@ -1 +1 @@
-let v = vec4<f32>(0.0, 1.0, 2.0, 3.0);
+let v = vec4(0.0, 1.0, 2.0, 3.0);
diff --git a/test/expressions/type_ctor/vec4/explicit/i32.wgsl b/test/expressions/type_ctor/vec4/explicit/i32.wgsl
index 9c86a2e..ab60e6e 100644
--- a/test/expressions/type_ctor/vec4/explicit/i32.wgsl
+++ b/test/expressions/type_ctor/vec4/explicit/i32.wgsl
@@ -1 +1 @@
-let v = vec4<i32>(0, 1, 2, 3);
+let v = vec4(0, 1, 2, 3);
diff --git a/test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.wgsl b/test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.wgsl
index 9c86a2e..ab60e6e 100644
--- a/test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.wgsl
+++ b/test/expressions/type_ctor/vec4/explicit/i32.wgsl.expected.wgsl
@@ -1 +1 @@
-let v = vec4<i32>(0, 1, 2, 3);
+let v = vec4(0, 1, 2, 3);
diff --git a/test/expressions/type_ctor/vec4/explicit/u32.wgsl b/test/expressions/type_ctor/vec4/explicit/u32.wgsl
index feaa000..ef7c766 100644
--- a/test/expressions/type_ctor/vec4/explicit/u32.wgsl
+++ b/test/expressions/type_ctor/vec4/explicit/u32.wgsl
@@ -1 +1 @@
-let v = vec4<u32>(0u, 1u, 2u, 3u);
+let v = vec4(0u, 1u, 2u, 3u);
diff --git a/test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.wgsl b/test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.wgsl
index feaa000..ef7c766 100644
--- a/test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.wgsl
+++ b/test/expressions/type_ctor/vec4/explicit/u32.wgsl.expected.wgsl
@@ -1 +1 @@
-let v = vec4<u32>(0u, 1u, 2u, 3u);
+let v = vec4(0u, 1u, 2u, 3u);