Resolver: Enforce vector constructor type rules

Added enforcement for vector constructor type rules according to the
table in https://gpuweb.github.io/gpuweb/wgsl.html#type-constructor-expr.

This surfaced a number of existing tests that violated some of these
rules or had a type-declaration related bug, so this CL fixes those as
well (these tests either passed the incorrect number of arguments to a
vector constructor or relied on implicit conversions between numeric
types).

Fixed: tint:632
Fixed: tint:476
Change-Id: I8279be3eeae50b64db486ee7a91a43bd94fdff62
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/44480
Commit-Queue: Arman Uguray <armansito@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/ast/module_clone_test.cc b/src/ast/module_clone_test.cc
index 4e3fea6..87710e7 100644
--- a/src/ast/module_clone_test.cc
+++ b/src/ast/module_clone_test.cc
@@ -65,7 +65,7 @@
   var l0 : i32 = 3;
   var l1 : f32 = 8.0;
   var l2 : u32 = bitcast<u32>(4);
-  var l3 : vec2<u32> = vec2<u32>(l0, l1);
+  var l3 : vec2<u32> = vec2<u32>(u32(l0), u32(l1));
   var l4 : S;
   var l5 : u32 = l4.m1[5];
   var l6 : ptr<private, u32>;
diff --git a/src/reader/wgsl/parser_test.cc b/src/reader/wgsl/parser_test.cc
index d75a742..b63c557 100644
--- a/src/reader/wgsl/parser_test.cc
+++ b/src/reader/wgsl/parser_test.cc
@@ -38,7 +38,7 @@
 
 [[stage(vertex)]]
 fn main() -> void {
-  gl_FragColor = vec4<f32>(.4, .2, .3, 1);
+  gl_FragColor = vec4<f32>(.4, .2, .3, 1.);
 }
 )");
   auto program = Parse(&file);
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index e1f1301..8b27d47 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -597,16 +597,97 @@
 }
 
 bool Resolver::Constructor(ast::ConstructorExpression* expr) {
-  if (auto* ty = expr->As<ast::TypeConstructorExpression>()) {
-    for (auto* value : ty->values()) {
+  if (auto* type_ctor = expr->As<ast::TypeConstructorExpression>()) {
+    for (auto* value : type_ctor->values()) {
       if (!Expression(value)) {
         return false;
       }
     }
-    SetType(expr, ty->type());
+    SetType(expr, type_ctor->type());
+
+    // 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.html#type-constructor-expr.
+    if (auto* vec_type = type_ctor->type()->As<type::Vector>()) {
+      return VectorConstructor(*vec_type, type_ctor->values());
+    }
+    // TODO(crbug.com/tint/633): Validate matrix constructor
+    // TODO(crbug.com/tint/634): Validate array constructor
+  } else if (auto* scalar_ctor = expr->As<ast::ScalarConstructorExpression>()) {
+    SetType(expr, scalar_ctor->literal()->type());
   } else {
-    SetType(expr,
-            expr->As<ast::ScalarConstructorExpression>()->literal()->type());
+    TINT_ICE(diagnostics_) << "unexpected constructor expression type";
+  }
+  return true;
+}
+
+bool Resolver::VectorConstructor(const type::Vector& vec_type,
+                                 const ast::ExpressionList& values) {
+  type::Type* elem_type = vec_type.type()->UnwrapAll();
+  size_t value_cardinality_sum = 0;
+  for (auto* value : values) {
+    type::Type* value_type = TypeOf(value)->UnwrapAll();
+    if (value_type->is_scalar()) {
+      if (elem_type != value_type) {
+        diagnostics_.add_error(
+            "type in vector constructor does not match vector type: "
+            "expected '" +
+                elem_type->FriendlyName(builder_->Symbols()) + "', found '" +
+                value_type->FriendlyName(builder_->Symbols()) + "'",
+            value->source());
+        return false;
+      }
+
+      value_cardinality_sum++;
+    } else if (auto* value_vec = value_type->As<type::Vector>()) {
+      type::Type* value_elem_type = value_vec->type()->UnwrapAll();
+      // A mismatch of vector type parameter T is only an error if multiple
+      // arguments are present. A single argument constructor constitutes a
+      // type conversion expression.
+      // NOTE: A conversion expression from a vec<bool> to any other vecN<T>
+      // is disallowed (see
+      // https://gpuweb.github.io/gpuweb/wgsl.html#conversion-expr).
+      if (elem_type != value_elem_type &&
+          (values.size() > 1u || value_vec->is_bool_vector())) {
+        diagnostics_.add_error(
+            "type in vector constructor does not match vector type: "
+            "expected '" +
+                elem_type->FriendlyName(builder_->Symbols()) + "', found '" +
+                value_elem_type->FriendlyName(builder_->Symbols()) + "'",
+            value->source());
+        return false;
+      }
+
+      value_cardinality_sum += value_vec->size();
+    } else {
+      // A vector constructor can only accept vectors and scalars.
+      diagnostics_.add_error(
+          "expected vector or scalar type in vector constructor; found: " +
+              value_type->FriendlyName(builder_->Symbols()),
+          value->source());
+      return false;
+    }
+  }
+
+  // A correct vector constructor must either be a zero-value expression
+  // or the number of components of all constructor arguments must add up
+  // to the vector cardinality.
+  if (value_cardinality_sum > 0 && value_cardinality_sum != vec_type.size()) {
+    if (values.empty()) {
+      TINT_ICE(diagnostics_)
+          << "constructor arguments expected to be non-empty!";
+    }
+    const Source& values_start = values[0]->source();
+    const Source& values_end = values[values.size() - 1]->source();
+    const Source src(
+        Source::Range(values_start.range.begin, values_end.range.end),
+        values_start.file_path, values_start.file_content);
+    diagnostics_.add_error(
+        "attempted to construct '" +
+            vec_type.FriendlyName(builder_->Symbols()) + "' with " +
+            std::to_string(value_cardinality_sum) + " component(s)",
+        src);
+    return false;
   }
   return true;
 }
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 485bff8..ddf7c11 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -177,6 +177,8 @@
   bool Call(ast::CallExpression*);
   bool CaseStatement(ast::CaseStatement*);
   bool Constructor(ast::ConstructorExpression*);
+  bool VectorConstructor(const type::Vector& vec_type,
+                         const ast::ExpressionList& values);
   bool Expression(ast::Expression*);
   bool Expressions(const ast::ExpressionList&);
   bool Function(ast::Function*);
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index 66bf2af..caf608e 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -578,8 +578,20 @@
   EXPECT_TRUE(TypeOf(s)->Is<type::F32>());
 }
 
-TEST_F(ResolverTest, Expr_Constructor_Type) {
-  auto* tc = vec3<f32>(1.0f, 1.0f, 3.0f);
+TEST_F(ResolverTest, Expr_Constructor_Type_Vec2) {
+  auto* tc = vec2<f32>(1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 2u);
+}
+
+TEST_F(ResolverTest, Expr_Constructor_Type_Vec3) {
+  auto* tc = vec3<f32>(1.0f, 1.0f, 1.0f);
   WrapInFunction(tc);
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -590,6 +602,18 @@
   EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 3u);
 }
 
+TEST_F(ResolverTest, Expr_Constructor_Type_Vec4) {
+  auto* tc = vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
 TEST_F(ResolverTest, Expr_Identifier_GlobalVariable) {
   auto* my_var = Global("my_var", ty.f32(), ast::StorageClass::kNone);
 
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
index 02965fe..9dc2e0d 100644
--- a/src/resolver/validation_test.cc
+++ b/src/resolver/validation_test.cc
@@ -373,7 +373,7 @@
 }
 
 TEST_F(
-    ResolverTest,
+    ResolverValidationTest,
     Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuingSubscope) {
   // loop  {
   //     if (true) {
@@ -624,6 +624,1046 @@
             "decorations");
 }
 
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec2F32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec2<f32>(
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1)),
+      1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'f32', found 'i32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec2U32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec2<u32>(1u, create<ast::ScalarConstructorExpression>(
+                               Source{{12, 34}}, Literal(1)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'u32', found 'i32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec2I32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec2<i32>(
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1u)),
+      1);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'i32', found 'u32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec2Bool_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec2<bool>(true, create<ast::ScalarConstructorExpression>(
+                                  Source{{12, 34}}, Literal(1)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'bool', found 'i32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec2_Error_Vec3ArgumentCardinalityTooLarge) {
+  auto* tc = vec2<f32>(create<ast::TypeConstructorExpression>(
+      Source{{12, 34}}, ty.vec3<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec2<f32>' with 3 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec2_Error_Vec4ArgumentCardinalityTooLarge) {
+  auto* tc = vec2<f32>(create<ast::TypeConstructorExpression>(
+      Source{{12, 34}}, ty.vec4<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec2<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec2_Error_TooFewArgumentsScalar) {
+  auto* tc = vec2<f32>(create<ast::ScalarConstructorExpression>(
+      Source{{12, 34}}, Literal(1.0f)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec2<f32>' with 1 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec2_Error_TooManyArgumentsScalar) {
+  auto* tc = vec2<f32>(
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 40}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 46}},
+                                               Literal(1.0f)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec2<f32>' with 3 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec2_Error_TooManyArgumentsVector) {
+  auto* tc = vec2<f32>(create<ast::TypeConstructorExpression>(
+                           Source{{12, 34}}, ty.vec2<f32>(), ExprList()),
+                       create<ast::TypeConstructorExpression>(
+                           Source{{12, 40}}, ty.vec2<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec2<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec2_Error_TooManyArgumentsVectorAndScalar) {
+  auto* tc = vec2<f32>(create<ast::TypeConstructorExpression>(
+                           Source{{12, 34}}, ty.vec2<f32>(), ExprList()),
+                       create<ast::ScalarConstructorExpression>(
+                           Source{{12, 40}}, Literal(1.0f)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec2<f32>' with 3 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec2_Error_InvalidConversionFromVec2Bool) {
+  SetSource(Source::Location({12, 34}));
+
+  auto* tc = vec2<f32>(create<ast::TypeConstructorExpression>(
+      Source{{12, 34}}, ty.vec2<bool>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'f32', found 'bool'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec2_Error_InvalidArgumentType) {
+  auto* tc = vec2<f32>(create<ast::TypeConstructorExpression>(
+      Source{{12, 34}}, ty.mat2x2<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: expected vector or scalar type in vector "
+            "constructor; found: mat2x2<f32>");
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec2_Success_ZeroValue) {
+  auto* tc = vec2<f32>();
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 2u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec2F32_Success_Scalar) {
+  auto* tc = vec2<f32>(1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 2u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec2U32_Success_Scalar) {
+  auto* tc = vec2<u32>(1u, 1u);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::U32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 2u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec2I32_Success_Scalar) {
+  auto* tc = vec2<i32>(1, 1);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::I32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 2u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec2Bool_Success_Scalar) {
+  auto* tc = vec2<bool>(true, false);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::Bool>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 2u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec2_Success_Identity) {
+  auto* tc = vec2<f32>(vec2<f32>());
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 2u);
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec2_Success_Vec2TypeConversion) {
+  auto* tc = vec2<f32>(vec2<i32>());
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 2u);
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3F32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec3<f32>(
+      1.0f, 1.0f,
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'f32', found 'i32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3U32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec3<u32>(
+      1u,
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1)),
+      1u);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'u32', found 'i32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3I32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec3<i32>(
+      1,
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1u)),
+      1);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'i32', found 'u32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3Bool_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec3<bool>(
+      true,
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1)),
+      false);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'bool', found 'i32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3_Error_Vec4ArgumentCardinalityTooLarge) {
+  auto* tc = vec3<f32>(create<ast::TypeConstructorExpression>(
+      Source{{12, 34}}, ty.vec4<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3_Error_TooFewArgumentsScalar) {
+  auto* tc = vec3<f32>(
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 40}},
+                                               Literal(1.0f)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 2 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3_Error_TooManyArgumentsScalar) {
+  auto* tc = vec3<f32>(
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 40}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 46}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 52}},
+                                               Literal(1.0f)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3_Error_TooFewArgumentsVec2) {
+  auto* tc = vec3<f32>(create<ast::TypeConstructorExpression>(
+      Source{{12, 34}}, ty.vec2<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 2 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3_Error_TooManyArgumentsVec2) {
+  auto* tc = vec3<f32>(create<ast::TypeConstructorExpression>(
+                           Source{{12, 34}}, ty.vec2<f32>(), ExprList()),
+                       create<ast::TypeConstructorExpression>(
+                           Source{{12, 40}}, ty.vec2<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3_Error_TooManyArgumentsVec2AndScalar) {
+  auto* tc = vec3<f32>(
+      create<ast::TypeConstructorExpression>(Source{{12, 34}}, ty.vec2<f32>(),
+                                             ExprList()),
+      create<ast::ScalarConstructorExpression>(Source{{12, 40}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 46}},
+                                               Literal(1.0f)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3_Error_TooManyArgumentsVec3) {
+  auto* tc = vec3<f32>(create<ast::TypeConstructorExpression>(
+                           Source{{12, 34}}, ty.vec3<f32>(), ExprList()),
+                       create<ast::ScalarConstructorExpression>(
+                           Source{{12, 40}}, Literal(1.0f)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec3<f32>' with 4 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3_Error_InvalidConversionFromVec3Bool) {
+  auto* tc = vec3<f32>(create<ast::TypeConstructorExpression>(
+      Source{{12, 34}}, ty.vec3<bool>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'f32', found 'bool'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3_Error_InvalidArgumentType) {
+  auto* tc = vec3<f32>(create<ast::TypeConstructorExpression>(
+      Source{{12, 34}}, ty.mat2x2<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: expected vector or scalar type in vector "
+            "constructor; found: mat2x2<f32>");
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec3_Success_ZeroValue) {
+  auto* tc = vec3<f32>();
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec3F32_Success_Scalar) {
+  auto* tc = vec3<f32>(1.0f, 1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec3U32_Success_Scalar) {
+  auto* tc = vec3<u32>(1u, 1u, 1u);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::U32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec3I32_Success_Scalar) {
+  auto* tc = vec3<i32>(1, 1, 1);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::I32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec3Bool_Success_Scalar) {
+  auto* tc = vec3<bool>(true, false, true);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::Bool>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec3_Success_Vec2AndScalar) {
+  auto* tc = vec3<f32>(vec2<f32>(), 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec3_Success_ScalarAndVec2) {
+  auto* tc = vec3<f32>(1.0f, vec2<f32>());
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec3_Success_Identity) {
+  auto* tc = vec3<f32>(vec3<f32>());
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec3_Success_Vec3TypeConversion) {
+  auto* tc = vec3<f32>(vec3<i32>());
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4F32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec4<f32>(
+      1.0f, 1.0f,
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1)),
+      1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'f32', found 'i32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4U32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec4<u32>(
+      1u, 1u,
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1)),
+      1u);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'u32', found 'i32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4I32_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec4<i32>(
+      1, 1,
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1u)),
+      1);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'i32', found 'u32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4Bool_Error_ScalarArgumentTypeMismatch) {
+  auto* tc = vec4<bool>(
+      true, false,
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1)),
+      true);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'bool', found 'i32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Error_TooFewArgumentsScalar) {
+  auto* tc = vec4<f32>(
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 40}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 46}},
+                                               Literal(1.0f)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 3 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsScalar) {
+  auto* tc = vec4<f32>(
+      create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 40}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 46}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 52}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 58}},
+                                               Literal(1.0f)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Error_TooFewArgumentsVec2AndScalar) {
+  auto* tc = vec4<f32>(create<ast::TypeConstructorExpression>(
+                           Source{{12, 34}}, ty.vec2<f32>(), ExprList()),
+                       create<ast::ScalarConstructorExpression>(
+                           Source{{12, 40}}, Literal(1.0f)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 3 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2AndScalars) {
+  auto* tc = vec4<f32>(
+      create<ast::TypeConstructorExpression>(Source{{12, 34}}, ty.vec2<f32>(),
+                                             ExprList()),
+      create<ast::ScalarConstructorExpression>(Source{{12, 40}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 46}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 52}},
+                                               Literal(1.0f)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2Vec2Scalar) {
+  auto* tc = vec4<f32>(create<ast::TypeConstructorExpression>(
+                           Source{{12, 34}}, ty.vec2<f32>(), ExprList()),
+                       create<ast::TypeConstructorExpression>(
+                           Source{{12, 40}}, ty.vec2<f32>(), ExprList()),
+                       create<ast::ScalarConstructorExpression>(
+                           Source{{12, 46}}, Literal(1.0f)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2Vec2Vec2) {
+  auto* tc = vec4<f32>(create<ast::TypeConstructorExpression>(
+                           Source{{12, 34}}, ty.vec2<f32>(), ExprList()),
+                       create<ast::TypeConstructorExpression>(
+                           Source{{12, 40}}, ty.vec2<f32>(), ExprList()),
+                       create<ast::TypeConstructorExpression>(
+                           Source{{12, 40}}, ty.vec2<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 6 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Error_TooFewArgumentsVec3) {
+  auto* tc = vec4<f32>(create<ast::TypeConstructorExpression>(
+      Source{{12, 34}}, ty.vec3<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 3 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec3AndScalars) {
+  auto* tc = vec4<f32>(
+      create<ast::TypeConstructorExpression>(Source{{12, 34}}, ty.vec3<f32>(),
+                                             ExprList()),
+      create<ast::ScalarConstructorExpression>(Source{{12, 40}}, Literal(1.0f)),
+      create<ast::ScalarConstructorExpression>(Source{{12, 46}},
+                                               Literal(1.0f)));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec3AndVec2) {
+  auto* tc = vec4<f32>(create<ast::TypeConstructorExpression>(
+                           Source{{12, 34}}, ty.vec3<f32>(), ExprList()),
+                       create<ast::TypeConstructorExpression>(
+                           Source{{12, 40}}, ty.vec2<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec2AndVec3) {
+  auto* tc = vec4<f32>(create<ast::TypeConstructorExpression>(
+                           Source{{12, 34}}, ty.vec2<f32>(), ExprList()),
+                       create<ast::TypeConstructorExpression>(
+                           Source{{12, 40}}, ty.vec3<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 5 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Error_TooManyArgumentsVec3AndVec3) {
+  auto* tc = vec4<f32>(create<ast::TypeConstructorExpression>(
+                           Source{{12, 34}}, ty.vec3<f32>(), ExprList()),
+                       create<ast::TypeConstructorExpression>(
+                           Source{{12, 40}}, ty.vec3<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec4<f32>' with 6 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Error_InvalidConversionFromVec4Bool) {
+  auto* tc = vec4<f32>(create<ast::TypeConstructorExpression>(
+      Source{{12, 34}}, ty.vec4<bool>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'f32', found 'bool'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Error_InvalidArgumentType) {
+  auto* tc = vec4<f32>(create<ast::TypeConstructorExpression>(
+      Source{{12, 34}}, ty.mat2x2<f32>(), ExprList()));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: expected vector or scalar type in vector "
+            "constructor; found: mat2x2<f32>");
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec4_Success_ZeroValue) {
+  auto* tc = vec4<f32>();
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec4F32_Success_Scalar) {
+  auto* tc = vec4<f32>(1.0f, 1.0f, 1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec4U32_Success_Scalar) {
+  auto* tc = vec4<u32>(1u, 1u, 1u, 1u);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::U32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec4I32_Success_Scalar) {
+  auto* tc = vec4<i32>(1, 1, 1, 1);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::I32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec4Bool_Success_Scalar) {
+  auto* tc = vec4<bool>(true, false, true, false);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::Bool>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec4_Success_Vec2ScalarScalar) {
+  auto* tc = vec4<f32>(vec2<f32>(), 1.0f, 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec4_Success_ScalarVec2Scalar) {
+  auto* tc = vec4<f32>(1.0f, vec2<f32>(), 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec4_Success_ScalarScalarVec2) {
+  auto* tc = vec4<f32>(1.0f, 1.0f, vec2<f32>());
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec4_Success_Vec2AndVec2) {
+  auto* tc = vec4<f32>(vec2<f32>(), vec2<f32>());
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec4_Success_Vec3AndScalar) {
+  auto* tc = vec4<f32>(vec3<f32>(), 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec4_Success_ScalarAndVec3) {
+  auto* tc = vec4<f32>(1.0f, vec3<f32>());
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vec4_Success_Identity) {
+  auto* tc = vec4<f32>(vec4<f32>());
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vec4_Success_Vec4TypeConversion) {
+  auto* tc = vec4<f32>(vec4<i32>());
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_NestedVectorConstructors_InnerError) {
+  auto* tc = vec4<f32>(
+      vec3<f32>(1.0f, vec2<f32>(create<ast::ScalarConstructorExpression>(
+                          Source{{12, 34}}, Literal(1.0f)))),
+      1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: attempted to construct 'vec2<f32>' with 1 component(s)");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_NestedVectorConstructors_Success) {
+  auto* tc = vec4<f32>(vec3<f32>(vec2<f32>(1.0f, 1.0f), 1.0f), 1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(tc), nullptr);
+  ASSERT_TRUE(TypeOf(tc)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(tc)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(tc)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vector_Alias_Argument_Error) {
+  auto* alias = ty.alias("UnsignedInt", ty.u32());
+  Global("uint_var", alias, ast::StorageClass::kNone);
+
+  auto* tc = vec2<f32>(Expr(Source{{12, 34}}, "uint_var"));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'f32', found 'u32'");
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vector_Alias_Argument_Success) {
+  auto* f32_alias = ty.alias("Float32", ty.f32());
+  auto* vec2_alias = ty.alias("VectorFloat2", ty.vec2<f32>());
+  Global("my_f32", f32_alias, ast::StorageClass::kNone);
+  Global("my_vec2", vec2_alias, ast::StorageClass::kNone);
+
+  auto* tc = vec3<f32>("my_vec2", "my_f32");
+  WrapInFunction(tc);
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, Expr_Constructor_Vector_ElementTypeAlias_Error) {
+  auto* f32_alias = ty.alias("Float32", ty.f32());
+  auto* vec_type = create<type::Vector>(f32_alias, 2);
+
+  // vec2<Float32>(1.0f, 1u)
+  auto* tc = create<ast::TypeConstructorExpression>(
+      Source{{12, 34}}, vec_type,
+      ExprList(1.0f, create<ast::ScalarConstructorExpression>(Source{{12, 40}},
+                                                              Literal(1u))));
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:40 error: type in vector constructor does not match vector "
+            "type: expected 'f32', found 'u32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vector_ElementTypeAlias_Success) {
+  auto* f32_alias = ty.alias("Float32", ty.f32());
+  auto* vec_type = create<type::Vector>(f32_alias, 2);
+
+  // vec2<Float32>(1.0f, 1.0f)
+  auto* tc = create<ast::TypeConstructorExpression>(Source{{12, 34}}, vec_type,
+                                                    ExprList(1.0f, 1.0f));
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vector_ArgumentElementTypeAlias_Error) {
+  auto* f32_alias = ty.alias("Float32", ty.f32());
+  auto* vec_type = create<type::Vector>(f32_alias, 2);
+
+  // vec3<u32>(vec<Float32>(), 1.0f)
+  auto* tc = vec3<u32>(create<ast::TypeConstructorExpression>(
+                           Source{{12, 34}}, vec_type, ExprList()),
+                       1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: type in vector constructor does not match vector "
+            "type: expected 'u32', found 'f32'");
+}
+
+TEST_F(ResolverValidationTest,
+       Expr_Constructor_Vector_ArgumentElementTypeAlias_Success) {
+  auto* f32_alias = ty.alias("Float32", ty.f32());
+  auto* vec_type = create<type::Vector>(f32_alias, 2);
+
+  // vec3<f32>(vec<Float32>(), 1.0f)
+  auto* tc = vec3<f32>(create<ast::TypeConstructorExpression>(
+                           Source{{12, 34}}, vec_type, ExprList()),
+                       1.0f);
+  WrapInFunction(tc);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
 }  // namespace
 }  // namespace resolver
 }  // namespace tint
diff --git a/src/transform/hlsl_test.cc b/src/transform/hlsl_test.cc
index 70fdfb3..82c732d 100644
--- a/src/transform/hlsl_test.cc
+++ b/src/transform/hlsl_test.cc
@@ -108,7 +108,7 @@
 [[stage(vertex)]]
 fn main() -> void {
   const transform : mat2x2<f32> = ubo.transform;
-  var coord : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+  var coord : vec2<f32> = array<vec2<f32>, 3>(
       vec2<f32>(-1.0,  1.0),
       vec2<f32>( 1.0,  1.0),
       vec2<f32>(-1.0, -1.0)
@@ -133,7 +133,7 @@
 fn main() -> void {
   const transform : mat2x2<f32> = ubo.transform;
   const tint_symbol_1 : array<vec2<f32>, 3> = array<vec2<f32>, 3>(vec2<f32>(-1.0, 1.0), vec2<f32>(1.0, 1.0), vec2<f32>(-1.0, -1.0));
-  var coord : array<vec2<f32>, 3> = tint_symbol_1[vertex_index];
+  var coord : vec2<f32> = tint_symbol_1[vertex_index];
   position = vec4<f32>((transform * coord), 0.0, 1.0);
 }
 )";
diff --git a/src/validator/validator_builtins_test.cc b/src/validator/validator_builtins_test.cc
index c65393b..fa9aede 100644
--- a/src/validator/validator_builtins_test.cc
+++ b/src/validator/validator_builtins_test.cc
@@ -509,7 +509,7 @@
 
   ast::ExpressionList params;
   for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec2<uint32_t>(1, 1));
+    params.push_back(vec2<uint32_t>(1u, 1u));
   }
   auto* builtin = Call(name, params);
   WrapInFunction(builtin);
@@ -526,7 +526,7 @@
 
   ast::ExpressionList params;
   for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec3<uint32_t>(1, 1, 1));
+    params.push_back(vec3<uint32_t>(1u, 1u, 1u));
   }
   auto* builtin = Call(name, params);
   WrapInFunction(builtin);
@@ -543,7 +543,7 @@
 
   ast::ExpressionList params;
   for (uint32_t i = 0; i < num_params; ++i) {
-    params.push_back(vec4<uint32_t>(1, 1, 1, 1));
+    params.push_back(vec4<uint32_t>(1u, 1u, 1u, 1u));
   }
   auto* builtin = Call(name, params);
   WrapInFunction(builtin);
diff --git a/src/writer/spirv/builder_constructor_expression_test.cc b/src/writer/spirv/builder_constructor_expression_test.cc
index 6a11cb0..e11f4ea 100644
--- a/src/writer/spirv/builder_constructor_expression_test.cc
+++ b/src/writer/spirv/builder_constructor_expression_test.cc
@@ -141,7 +141,7 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, Vector_Bitcast_Params) {
-  auto* t = vec2<u32>(1, 1);
+  auto* t = vec2<u32>(Construct<u32>(1), Construct<u32>(1));
   WrapInFunction(t);
 
   spirv::Builder& b = Build();
@@ -153,14 +153,14 @@
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
 %1 = OpTypeVector %2 2
-%3 = OpTypeInt 32 1
-%4 = OpConstant %3 1
+%4 = OpTypeInt 32 1
+%5 = OpConstant %4 1
 )");
 
   EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
-            R"(%5 = OpBitcast %2 %4
-%6 = OpBitcast %2 %4
-%7 = OpCompositeConstruct %1 %5 %6
+            R"(%3 = OpBitcast %2 %5
+%6 = OpBitcast %2 %5
+%7 = OpCompositeConstruct %1 %3 %6
 )");
 }
 
@@ -1450,7 +1450,7 @@
 
 TEST_F(SpvBuilderConstructorTest,
        IsConstructorConst_GlobalVectorWithMatchingTypeConstructors) {
-  // vec3<f32>(f32(1.0), f32(2.0))  -> false
+  // vec2<f32>(f32(1.0), f32(2.0))  -> false
 
   auto* t = vec2<f32>(Construct<f32>(1.f), Construct<f32>(2.f));
   WrapInFunction(t);
@@ -1463,7 +1463,7 @@
 
 TEST_F(SpvBuilderConstructorTest,
        IsConstructorConst_GlobalWithTypeCastConstructor) {
-  // vec3<f32>(f32(1), f32(2)) -> false
+  // vec2<f32>(f32(1), f32(2)) -> false
 
   auto* t = vec2<f32>(Construct<f32>(1), Construct<f32>(2));
   WrapInFunction(t);
@@ -1521,22 +1521,10 @@
 }
 
 TEST_F(SpvBuilderConstructorTest,
-       IsConstructorConst_VectorWith_TypeCastConstConstructors) {
+       IsConstructorConst_VectorWithTypeCastConstConstructors) {
   // vec2<f32>(f32(1), f32(2))  -> false
 
-  auto* t = vec2<f32>(1, 2);
-  WrapInFunction(t);
-
-  spirv::Builder& b = Build();
-
-  EXPECT_FALSE(b.is_constructor_const(t, false));
-  EXPECT_FALSE(b.has_error());
-}
-
-TEST_F(SpvBuilderConstructorTest, IsConstructorConst_WithTypeCastConstructor) {
-  // vec3<f32>(f32(1), f32(2)) -> false
-
-  auto* t = vec3<f32>(1, 2);
+  auto* t = vec2<f32>(Construct<f32>(1), Construct<f32>(2));
   WrapInFunction(t);
 
   spirv::Builder& b = Build();
@@ -1546,7 +1534,7 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, IsConstructorConst_BitCastScalars) {
-  auto* t = vec2<u32>(1, 1);
+  auto* t = vec2<u32>(Construct<u32>(1), Construct<u32>(1));
   WrapInFunction(t);
 
   spirv::Builder& b = Build();