Implement vector initialization from single scalar (aka "splat")

Updated spir-v and HSLS backends to emit valid constructors.

Bug: tint:656
Change-Id: I53356945563b633239b12c0f4e207f160dbc23d8
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/53780
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@chromium.org>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index e80d11d..ee2674a 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -1895,10 +1895,10 @@
     }
   }
 
-  // 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()) {
+  // A correct vector constructor must either be a zero-value expression,
+  // a single-value initializer (splat) expression, or the number of components
+  // of all constructor arguments must add up to the vector cardinality.
+  if (value_cardinality_sum > 1 && value_cardinality_sum != vec_type->size()) {
     if (values.empty()) {
       TINT_ICE(diagnostics_)
           << "constructor arguments expected to be non-empty!";
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
index 1c62b89..aa53bce 100644
--- a/src/resolver/validation_test.cc
+++ b/src/resolver/validation_test.cc
@@ -777,18 +777,6 @@
 }
 
 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)),
@@ -1632,16 +1620,16 @@
 
 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);
+  auto* tc = vec4<f32>(vec4<f32>(1.0f, 1.0f,
+                                 vec3<f32>(Expr(Source{{12, 34}}, 1.0f),
+                                           Expr(Source{{12, 34}}, 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)");
+      "12:34 error: attempted to construct 'vec3<f32>' with 2 component(s)");
 }
 
 TEST_F(ResolverValidationTest,
diff --git a/src/sem/type.cc b/src/sem/type.cc
index 4d44dfe..9963003 100644
--- a/src/sem/type.cc
+++ b/src/sem/type.cc
@@ -119,6 +119,10 @@
       [](const Vector* v) { return v->type()->is_numeric_scalar(); });
 }
 
+bool Type::is_scalar_vector() const {
+  return Is<Vector>([](const Vector* v) { return v->type()->is_scalar(); });
+}
+
 bool Type::is_numeric_scalar_or_vector() const {
   return is_numeric_scalar() || is_numeric_vector();
 }
diff --git a/src/sem/type.h b/src/sem/type.h
index d3ee2ac..e63e03e 100644
--- a/src/sem/type.h
+++ b/src/sem/type.h
@@ -85,6 +85,8 @@
   bool is_bool_scalar_or_vector() const;
   /// @returns true if this type is a numeric vector
   bool is_numeric_vector() const;
+  /// @returns true if this type is a vector of scalar type
+  bool is_scalar_vector() const;
   /// @returns true if this type is a numeric scale or vector
   bool is_numeric_scalar_or_vector() const;
   /// @returns true if this type is a handle type
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index f265156..5e74d5b 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -1377,6 +1377,12 @@
 
   bool brackets = type->IsAnyOf<sem::Array, sem::Struct>();
 
+  // For single-value vector initializers, swizzle the scalar to the right
+  // vector dimension using .x
+  const bool is_single_value_vector_init =
+      type->is_scalar_vector() && expr->values().size() == 1 &&
+      TypeOf(expr->values()[0])->is_scalar();
+
   if (brackets) {
     out << "{";
   } else {
@@ -1387,6 +1393,10 @@
     out << "(";
   }
 
+  if (is_single_value_vector_init) {
+    out << "(";
+  }
+
   bool first = true;
   for (auto* e : expr->values()) {
     if (!first) {
@@ -1399,6 +1409,10 @@
     }
   }
 
+  if (is_single_value_vector_init) {
+    out << ")." << std::string(type->As<sem::Vector>()->size(), 'x');
+  }
+
   out << (brackets ? "}" : ")");
   return true;
 }
diff --git a/src/writer/hlsl/generator_impl_constructor_test.cc b/src/writer/hlsl/generator_impl_constructor_test.cc
index 8593fff..3564389 100644
--- a/src/writer/hlsl/generator_impl_constructor_test.cc
+++ b/src/writer/hlsl/generator_impl_constructor_test.cc
@@ -135,6 +135,54 @@
   Validate();
 }
 
+TEST_F(HlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_Float) {
+  WrapInFunction(vec3<f32>(2.0f));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  EXPECT_THAT(result(), HasSubstr("float3((2.0f).xxx)"));
+
+  Validate();
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_Bool) {
+  WrapInFunction(vec3<bool>(true));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  EXPECT_THAT(result(), HasSubstr("bool3((true).xxx)"));
+
+  Validate();
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_Int) {
+  WrapInFunction(vec3<i32>(2));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  EXPECT_THAT(result(), HasSubstr("int3((2).xxx)"));
+
+  Validate();
+}
+
+TEST_F(HlslGeneratorImplTest_Constructor,
+       EmitConstructor_Type_Vec_SingleScalar_UInt) {
+  WrapInFunction(vec3<u32>(2u));
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  EXPECT_THAT(result(), HasSubstr("uint3((2u).xxx)"));
+
+  Validate();
+}
+
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Mat) {
   WrapInFunction(
       mat2x3<f32>(vec3<f32>(1.f, 2.f, 3.f), vec3<f32>(3.f, 4.f, 5.f)));
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 6089a7d..c89e06c 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -1430,6 +1430,16 @@
     }
   }
 
+  // For a single-value vector initializer, splat the initializer value.
+  auto* const init_result_type = TypeOf(init)->UnwrapRef();
+  if (values.size() == 1 && init_result_type->is_scalar_vector() &&
+      TypeOf(values[0])->is_scalar()) {
+    size_t vec_size = init_result_type->As<sem::Vector>()->size();
+    for (size_t i = 0; i < (vec_size - 1); ++i) {
+      ops.push_back(ops[0]);
+    }
+  }
+
   auto str = out.str();
   auto val = type_constructor_to_id_.find(str);
   if (val != type_constructor_to_id_.end()) {
diff --git a/src/writer/spirv/builder_constructor_expression_test.cc b/src/writer/spirv/builder_constructor_expression_test.cc
index 3f7e66b..0a7232a 100644
--- a/src/writer/spirv/builder_constructor_expression_test.cc
+++ b/src/writer/spirv/builder_constructor_expression_test.cc
@@ -241,6 +241,38 @@
   EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
 }
 
+TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_Bool) {
+  auto* cast = vec2<bool>(true);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%1 = OpTypeVector %2 2
+%3 = OpConstantTrue %2
+%4 = OpConstantComposite %1 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_F32) {
+  auto* cast = vec2<f32>(2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 2
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3
+)");
+}
+
 TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_F32_F32) {
   auto* cast = vec2<f32>(2.0f, 2.0f);
   WrapInFunction(cast);
@@ -275,6 +307,38 @@
   EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
 }
 
+TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F32) {
+  auto* cast = vec3<f32>(2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_Bool) {
+  auto* cast = vec3<bool>(true);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%1 = OpTypeVector %2 3
+%3 = OpConstantTrue %2
+%4 = OpConstantComposite %1 %3 %3 %3
+)");
+}
+
 TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F32_F32_F32) {
   auto* cast = vec3<f32>(2.0f, 2.0f, 2.0f);
   WrapInFunction(cast);
@@ -353,6 +417,38 @@
   EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
 }
 
+TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Bool) {
+  auto* cast = vec4<bool>(true);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+%1 = OpTypeVector %2 4
+%3 = OpConstantTrue %2
+%4 = OpConstantComposite %1 %3 %3 %3 %3
+)");
+}
+
+TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32) {
+  auto* cast = vec4<f32>(2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateExpression(cast), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3 %3 %3
+)");
+}
+
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32_F32_F32_F32) {
   auto* cast = vec4<f32>(2.0f, 2.0f, 2.0f, 2.0f);
   WrapInFunction(cast);
@@ -523,6 +619,22 @@
   EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
 }
 
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec2_With_F32) {
+  auto* cast = vec2<f32>(2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(nullptr, cast, true), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 2
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3
+)");
+}
+
 TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec2_With_Vec2) {
   auto* cast = vec2<f32>(vec2<f32>(2.0f, 2.0f));
   WrapInFunction(cast);
@@ -571,6 +683,22 @@
 )");
 }
 
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec3_With_F32) {
+  auto* cast = vec3<f32>(2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(nullptr, cast, true), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3 %3
+)");
+}
+
 TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec3_With_F32_Vec2) {
   auto* cast = vec3<f32>(2.0f, vec2<f32>(2.0f, 2.0f));
   WrapInFunction(cast);
@@ -617,6 +745,22 @@
 )");
 }
 
+TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_F32) {
+  auto* cast = vec4<f32>(2.0f);
+  WrapInFunction(cast);
+
+  spirv::Builder& b = Build();
+
+  b.push_function(Function{});
+  EXPECT_EQ(b.GenerateConstructorExpression(nullptr, cast, true), 4u);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 4
+%3 = OpConstant %2 2
+%4 = OpConstantComposite %1 %3 %3 %3 %3
+)");
+}
+
 TEST_F(SpvBuilderConstructorTest, Type_ModuleScope_Vec4_With_F32_F32_Vec2) {
   auto* cast = vec4<f32>(2.0f, 2.0f, vec2<f32>(2.0f, 2.0f));
   WrapInFunction(cast);
diff --git a/test/var/splat.wgsl b/test/var/splat.wgsl
new file mode 100644
index 0000000..1ede944
--- /dev/null
+++ b/test/var/splat.wgsl
@@ -0,0 +1,91 @@
+var<private> g_v2 : vec2<f32> = vec2<f32>(1.0);

+var<private> g_v3 : vec3<f32> = vec3<f32>(1.0);

+var<private> g_v4 : vec4<f32> = vec4<f32>(1.0);

+

+fn from_immediate_bool() {

+    var v2 : vec2<bool> = vec2<bool>(true);

+    var v3 : vec3<bool> = vec3<bool>(true);

+    var v4 : vec4<bool> = vec4<bool>(true);

+}

+

+fn from_immediate_f32() {

+    var v2 : vec2<f32> = vec2<f32>(1.0);

+    var v3 : vec3<f32> = vec3<f32>(1.0);

+    var v4 : vec4<f32> = vec4<f32>(1.0);

+}

+

+fn from_immediate_i32() {

+    var v2 : vec2<i32> = vec2<i32>(1);

+    var v3 : vec3<i32> = vec3<i32>(1);

+    var v4 : vec4<i32> = vec4<i32>(1);

+}

+

+fn from_immediate_u32() {

+    var v2 : vec2<u32> = vec2<u32>(1u);

+    var v3 : vec3<u32> = vec3<u32>(1u);

+    var v4 : vec4<u32> = vec4<u32>(1u);

+}

+

+fn from_expression_bool() {

+    var v2 : vec2<bool> = vec2<bool>(true);

+    var v3 : vec3<bool> = vec3<bool>(true);

+    var v4 : vec4<bool> = vec4<bool>(true);

+}

+

+fn from_expression_f32() {

+    var v2 : vec2<f32> = vec2<f32>(1.0 + 2.0);

+    var v3 : vec3<f32> = vec3<f32>(1.0 + 2.0);

+    var v4 : vec4<f32> = vec4<f32>(1.0 + 2.0);

+}

+

+fn from_expression_i32() {

+    var v2 : vec2<i32> = vec2<i32>(1 + 2);

+    var v3 : vec3<i32> = vec3<i32>(1 + 2);

+    var v4 : vec4<i32> = vec4<i32>(1 + 2);

+}

+

+fn from_expression_u32() {

+    var v2 : vec2<u32> = vec2<u32>(1u + 2u);

+    var v3 : vec3<u32> = vec3<u32>(1u + 2u);

+    var v4 : vec4<u32> = vec4<u32>(1u + 2u);

+}

+

+fn get_bool() -> bool { return true; }

+fn get_f32() -> f32 { return 1.0; }

+fn get_i32() -> i32 { return 1; }

+fn get_u32() -> u32 { return 1u; }

+

+fn from_call_bool() {

+    var v2 : vec2<bool> = vec2<bool>(get_bool());

+    var v3 : vec3<bool> = vec3<bool>(get_bool());

+    var v4 : vec4<bool> = vec4<bool>(get_bool());

+}

+

+fn from_call_f32() {

+    var v2 : vec2<f32> = vec2<f32>(get_f32());

+    var v3 : vec3<f32> = vec3<f32>(get_f32());

+    var v4 : vec4<f32> = vec4<f32>(get_f32());

+}

+

+fn from_call_i32() {

+    var v2 : vec2<i32> = vec2<i32>(get_i32());

+    var v3 : vec3<i32> = vec3<i32>(get_i32());

+    var v4 : vec4<i32> = vec4<i32>(get_i32());

+}

+

+fn from_call_u32() {

+    var v2 : vec2<u32> = vec2<u32>(get_u32());

+    var v3 : vec3<u32> = vec3<u32>(get_u32());

+    var v4 : vec4<u32> = vec4<u32>(get_u32());

+}

+

+fn with_swizzle() {

+    var a = vec2<f32>(1.0).y;

+    var b = vec3<f32>(1.0).z;

+    var c = vec4<f32>(1.0).w;

+}

+

+[[stage(fragment)]]

+fn main() -> [[location(0)]] vec4<f32> {

+    return vec4<f32>(0.0,0.0,0.0,0.0);

+}

diff --git a/test/var/splat.wgsl.expected.hlsl b/test/var/splat.wgsl.expected.hlsl
new file mode 100644
index 0000000..279743b
--- /dev/null
+++ b/test/var/splat.wgsl.expected.hlsl
@@ -0,0 +1,103 @@
+struct tint_symbol {
+  float4 value : SV_Target0;
+};
+
+void from_immediate_bool() {
+  bool2 v2 = bool2((true).xx);
+  bool3 v3 = bool3((true).xxx);
+  bool4 v4 = bool4((true).xxxx);
+}
+
+void from_immediate_f32() {
+  float2 v2 = float2((1.0f).xx);
+  float3 v3 = float3((1.0f).xxx);
+  float4 v4 = float4((1.0f).xxxx);
+}
+
+void from_immediate_i32() {
+  int2 v2 = int2((1).xx);
+  int3 v3 = int3((1).xxx);
+  int4 v4 = int4((1).xxxx);
+}
+
+void from_immediate_u32() {
+  uint2 v2 = uint2((1u).xx);
+  uint3 v3 = uint3((1u).xxx);
+  uint4 v4 = uint4((1u).xxxx);
+}
+
+void from_expression_bool() {
+  bool2 v2 = bool2((true).xx);
+  bool3 v3 = bool3((true).xxx);
+  bool4 v4 = bool4((true).xxxx);
+}
+
+void from_expression_f32() {
+  float2 v2 = float2(((1.0f + 2.0f)).xx);
+  float3 v3 = float3(((1.0f + 2.0f)).xxx);
+  float4 v4 = float4(((1.0f + 2.0f)).xxxx);
+}
+
+void from_expression_i32() {
+  int2 v2 = int2(((1 + 2)).xx);
+  int3 v3 = int3(((1 + 2)).xxx);
+  int4 v4 = int4(((1 + 2)).xxxx);
+}
+
+void from_expression_u32() {
+  uint2 v2 = uint2(((1u + 2u)).xx);
+  uint3 v3 = uint3(((1u + 2u)).xxx);
+  uint4 v4 = uint4(((1u + 2u)).xxxx);
+}
+
+bool get_bool() {
+  return true;
+}
+
+float get_f32() {
+  return 1.0f;
+}
+
+int get_i32() {
+  return 1;
+}
+
+uint get_u32() {
+  return 1u;
+}
+
+void from_call_bool() {
+  bool2 v2 = bool2((get_bool()).xx);
+  bool3 v3 = bool3((get_bool()).xxx);
+  bool4 v4 = bool4((get_bool()).xxxx);
+}
+
+void from_call_f32() {
+  float2 v2 = float2((get_f32()).xx);
+  float3 v3 = float3((get_f32()).xxx);
+  float4 v4 = float4((get_f32()).xxxx);
+}
+
+void from_call_i32() {
+  int2 v2 = int2((get_i32()).xx);
+  int3 v3 = int3((get_i32()).xxx);
+  int4 v4 = int4((get_i32()).xxxx);
+}
+
+void from_call_u32() {
+  uint2 v2 = uint2((get_u32()).xx);
+  uint3 v3 = uint3((get_u32()).xxx);
+  uint4 v4 = uint4((get_u32()).xxxx);
+}
+
+void with_swizzle() {
+  float a = float2((1.0f).xx).y;
+  float b = float3((1.0f).xxx).z;
+  float c = float4((1.0f).xxxx).w;
+}
+
+tint_symbol main() {
+  const tint_symbol tint_symbol_1 = {float4(0.0f, 0.0f, 0.0f, 0.0f)};
+  return tint_symbol_1;
+}
+
diff --git a/test/var/splat.wgsl.expected.msl b/test/var/splat.wgsl.expected.msl
new file mode 100644
index 0000000..bd8803e
--- /dev/null
+++ b/test/var/splat.wgsl.expected.msl
@@ -0,0 +1,106 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_1 {
+  float4 value [[color(0)]];
+};
+
+void from_immediate_bool() {
+  bool2 v2 = bool2(true);
+  bool3 v3 = bool3(true);
+  bool4 v4 = bool4(true);
+}
+
+void from_immediate_f32() {
+  float2 v2 = float2(1.0f);
+  float3 v3 = float3(1.0f);
+  float4 v4 = float4(1.0f);
+}
+
+void from_immediate_i32() {
+  int2 v2 = int2(1);
+  int3 v3 = int3(1);
+  int4 v4 = int4(1);
+}
+
+void from_immediate_u32() {
+  uint2 v2 = uint2(1u);
+  uint3 v3 = uint3(1u);
+  uint4 v4 = uint4(1u);
+}
+
+void from_expression_bool() {
+  bool2 v2 = bool2(true);
+  bool3 v3 = bool3(true);
+  bool4 v4 = bool4(true);
+}
+
+void from_expression_f32() {
+  float2 v2 = float2((1.0f + 2.0f));
+  float3 v3 = float3((1.0f + 2.0f));
+  float4 v4 = float4((1.0f + 2.0f));
+}
+
+void from_expression_i32() {
+  int2 v2 = int2((1 + 2));
+  int3 v3 = int3((1 + 2));
+  int4 v4 = int4((1 + 2));
+}
+
+void from_expression_u32() {
+  uint2 v2 = uint2((1u + 2u));
+  uint3 v3 = uint3((1u + 2u));
+  uint4 v4 = uint4((1u + 2u));
+}
+
+bool get_bool() {
+  return true;
+}
+
+float get_f32() {
+  return 1.0f;
+}
+
+int get_i32() {
+  return 1;
+}
+
+uint get_u32() {
+  return 1u;
+}
+
+void from_call_bool() {
+  bool2 v2 = bool2(get_bool());
+  bool3 v3 = bool3(get_bool());
+  bool4 v4 = bool4(get_bool());
+}
+
+void from_call_f32() {
+  float2 v2 = float2(get_f32());
+  float3 v3 = float3(get_f32());
+  float4 v4 = float4(get_f32());
+}
+
+void from_call_i32() {
+  int2 v2 = int2(get_i32());
+  int3 v3 = int3(get_i32());
+  int4 v4 = int4(get_i32());
+}
+
+void from_call_u32() {
+  uint2 v2 = uint2(get_u32());
+  uint3 v3 = uint3(get_u32());
+  uint4 v4 = uint4(get_u32());
+}
+
+void with_swizzle() {
+  float a = float2(1.0f).y;
+  float b = float3(1.0f).z;
+  float c = float4(1.0f).w;
+}
+
+fragment tint_symbol_1 tint_symbol() {
+  tint_symbol_1 const tint_symbol_2 = {.value=float4(0.0f, 0.0f, 0.0f, 0.0f)};
+  return tint_symbol_2;
+}
+
diff --git a/test/var/splat.wgsl.expected.spvasm b/test/var/splat.wgsl.expected.spvasm
new file mode 100644
index 0000000..5ac13ce
--- /dev/null
+++ b/test/var/splat.wgsl.expected.spvasm
@@ -0,0 +1,353 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 203
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %tint_symbol_1
+               OpExecutionMode %main OriginUpperLeft
+               OpName %g_v2 "g_v2"
+               OpName %g_v3 "g_v3"
+               OpName %g_v4 "g_v4"
+               OpName %tint_symbol_1 "tint_symbol_1"
+               OpName %from_immediate_bool "from_immediate_bool"
+               OpName %v2 "v2"
+               OpName %v3 "v3"
+               OpName %v4 "v4"
+               OpName %from_immediate_f32 "from_immediate_f32"
+               OpName %v2_0 "v2"
+               OpName %v3_0 "v3"
+               OpName %v4_0 "v4"
+               OpName %from_immediate_i32 "from_immediate_i32"
+               OpName %v2_1 "v2"
+               OpName %v3_1 "v3"
+               OpName %v4_1 "v4"
+               OpName %from_immediate_u32 "from_immediate_u32"
+               OpName %v2_2 "v2"
+               OpName %v3_2 "v3"
+               OpName %v4_2 "v4"
+               OpName %from_expression_bool "from_expression_bool"
+               OpName %v2_3 "v2"
+               OpName %v3_3 "v3"
+               OpName %v4_3 "v4"
+               OpName %from_expression_f32 "from_expression_f32"
+               OpName %v2_4 "v2"
+               OpName %v3_4 "v3"
+               OpName %v4_4 "v4"
+               OpName %from_expression_i32 "from_expression_i32"
+               OpName %v2_5 "v2"
+               OpName %v3_5 "v3"
+               OpName %v4_5 "v4"
+               OpName %from_expression_u32 "from_expression_u32"
+               OpName %v2_6 "v2"
+               OpName %v3_6 "v3"
+               OpName %v4_6 "v4"
+               OpName %get_bool "get_bool"
+               OpName %get_f32 "get_f32"
+               OpName %get_i32 "get_i32"
+               OpName %get_u32 "get_u32"
+               OpName %from_call_bool "from_call_bool"
+               OpName %v2_7 "v2"
+               OpName %v3_7 "v3"
+               OpName %v4_7 "v4"
+               OpName %from_call_f32 "from_call_f32"
+               OpName %v2_8 "v2"
+               OpName %v3_8 "v3"
+               OpName %v4_8 "v4"
+               OpName %from_call_i32 "from_call_i32"
+               OpName %v2_9 "v2"
+               OpName %v3_9 "v3"
+               OpName %v4_9 "v4"
+               OpName %from_call_u32 "from_call_u32"
+               OpName %v2_10 "v2"
+               OpName %v3_10 "v3"
+               OpName %v4_10 "v4"
+               OpName %with_swizzle "with_swizzle"
+               OpName %a "a"
+               OpName %b "b"
+               OpName %c "c"
+               OpName %tint_symbol_2 "tint_symbol_2"
+               OpName %tint_symbol "tint_symbol"
+               OpName %main "main"
+               OpDecorate %tint_symbol_1 Location 0
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+    %float_1 = OpConstant %float 1
+          %4 = OpConstantComposite %v2float %float_1 %float_1
+%_ptr_Private_v2float = OpTypePointer Private %v2float
+       %g_v2 = OpVariable %_ptr_Private_v2float Private %4
+    %v3float = OpTypeVector %float 3
+          %8 = OpConstantComposite %v3float %float_1 %float_1 %float_1
+%_ptr_Private_v3float = OpTypePointer Private %v3float
+       %g_v3 = OpVariable %_ptr_Private_v3float Private %8
+    %v4float = OpTypeVector %float 4
+         %12 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+       %g_v4 = OpVariable %_ptr_Private_v4float Private %12
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %17 = OpConstantNull %v4float
+%tint_symbol_1 = OpVariable %_ptr_Output_v4float Output %17
+       %void = OpTypeVoid
+         %18 = OpTypeFunction %void
+       %bool = OpTypeBool
+     %v2bool = OpTypeVector %bool 2
+       %true = OpConstantTrue %bool
+         %25 = OpConstantComposite %v2bool %true %true
+%_ptr_Function_v2bool = OpTypePointer Function %v2bool
+         %28 = OpConstantNull %v2bool
+     %v3bool = OpTypeVector %bool 3
+         %30 = OpConstantComposite %v3bool %true %true %true
+%_ptr_Function_v3bool = OpTypePointer Function %v3bool
+         %33 = OpConstantNull %v3bool
+     %v4bool = OpTypeVector %bool 4
+         %35 = OpConstantComposite %v4bool %true %true %true %true
+%_ptr_Function_v4bool = OpTypePointer Function %v4bool
+         %38 = OpConstantNull %v4bool
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+         %43 = OpConstantNull %v2float
+%_ptr_Function_v3float = OpTypePointer Function %v3float
+         %46 = OpConstantNull %v3float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+        %int = OpTypeInt 32 1
+      %v2int = OpTypeVector %int 2
+      %int_1 = OpConstant %int 1
+         %54 = OpConstantComposite %v2int %int_1 %int_1
+%_ptr_Function_v2int = OpTypePointer Function %v2int
+         %57 = OpConstantNull %v2int
+      %v3int = OpTypeVector %int 3
+         %59 = OpConstantComposite %v3int %int_1 %int_1 %int_1
+%_ptr_Function_v3int = OpTypePointer Function %v3int
+         %62 = OpConstantNull %v3int
+      %v4int = OpTypeVector %int 4
+         %64 = OpConstantComposite %v4int %int_1 %int_1 %int_1 %int_1
+%_ptr_Function_v4int = OpTypePointer Function %v4int
+         %67 = OpConstantNull %v4int
+       %uint = OpTypeInt 32 0
+     %v2uint = OpTypeVector %uint 2
+     %uint_1 = OpConstant %uint 1
+         %73 = OpConstantComposite %v2uint %uint_1 %uint_1
+%_ptr_Function_v2uint = OpTypePointer Function %v2uint
+         %76 = OpConstantNull %v2uint
+     %v3uint = OpTypeVector %uint 3
+         %78 = OpConstantComposite %v3uint %uint_1 %uint_1 %uint_1
+%_ptr_Function_v3uint = OpTypePointer Function %v3uint
+         %81 = OpConstantNull %v3uint
+     %v4uint = OpTypeVector %uint 4
+         %83 = OpConstantComposite %v4uint %uint_1 %uint_1 %uint_1 %uint_1
+%_ptr_Function_v4uint = OpTypePointer Function %v4uint
+         %86 = OpConstantNull %v4uint
+    %float_2 = OpConstant %float 2
+      %int_2 = OpConstant %int 2
+     %uint_2 = OpConstant %uint 2
+        %128 = OpTypeFunction %bool
+        %131 = OpTypeFunction %float
+        %134 = OpTypeFunction %int
+        %137 = OpTypeFunction %uint
+%_ptr_Function_float = OpTypePointer Function %float
+        %189 = OpConstantNull %float
+        %194 = OpTypeFunction %void %v4float
+    %float_0 = OpConstant %float 0
+        %202 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%from_immediate_bool = OpFunction %void None %18
+         %21 = OpLabel
+         %v2 = OpVariable %_ptr_Function_v2bool Function %28
+         %v3 = OpVariable %_ptr_Function_v3bool Function %33
+         %v4 = OpVariable %_ptr_Function_v4bool Function %38
+               OpStore %v2 %25
+               OpStore %v3 %30
+               OpStore %v4 %35
+               OpReturn
+               OpFunctionEnd
+%from_immediate_f32 = OpFunction %void None %18
+         %40 = OpLabel
+       %v2_0 = OpVariable %_ptr_Function_v2float Function %43
+       %v3_0 = OpVariable %_ptr_Function_v3float Function %46
+       %v4_0 = OpVariable %_ptr_Function_v4float Function %17
+               OpStore %v2_0 %4
+               OpStore %v3_0 %8
+               OpStore %v4_0 %12
+               OpReturn
+               OpFunctionEnd
+%from_immediate_i32 = OpFunction %void None %18
+         %50 = OpLabel
+       %v2_1 = OpVariable %_ptr_Function_v2int Function %57
+       %v3_1 = OpVariable %_ptr_Function_v3int Function %62
+       %v4_1 = OpVariable %_ptr_Function_v4int Function %67
+               OpStore %v2_1 %54
+               OpStore %v3_1 %59
+               OpStore %v4_1 %64
+               OpReturn
+               OpFunctionEnd
+%from_immediate_u32 = OpFunction %void None %18
+         %69 = OpLabel
+       %v2_2 = OpVariable %_ptr_Function_v2uint Function %76
+       %v3_2 = OpVariable %_ptr_Function_v3uint Function %81
+       %v4_2 = OpVariable %_ptr_Function_v4uint Function %86
+               OpStore %v2_2 %73
+               OpStore %v3_2 %78
+               OpStore %v4_2 %83
+               OpReturn
+               OpFunctionEnd
+%from_expression_bool = OpFunction %void None %18
+         %88 = OpLabel
+       %v2_3 = OpVariable %_ptr_Function_v2bool Function %28
+       %v3_3 = OpVariable %_ptr_Function_v3bool Function %33
+       %v4_3 = OpVariable %_ptr_Function_v4bool Function %38
+               OpStore %v2_3 %25
+               OpStore %v3_3 %30
+               OpStore %v4_3 %35
+               OpReturn
+               OpFunctionEnd
+%from_expression_f32 = OpFunction %void None %18
+         %93 = OpLabel
+       %v2_4 = OpVariable %_ptr_Function_v2float Function %43
+       %v3_4 = OpVariable %_ptr_Function_v3float Function %46
+       %v4_4 = OpVariable %_ptr_Function_v4float Function %17
+         %95 = OpFAdd %float %float_1 %float_2
+         %96 = OpCompositeConstruct %v2float %95 %95
+               OpStore %v2_4 %96
+         %98 = OpFAdd %float %float_1 %float_2
+         %99 = OpCompositeConstruct %v3float %98 %98 %98
+               OpStore %v3_4 %99
+        %101 = OpFAdd %float %float_1 %float_2
+        %102 = OpCompositeConstruct %v4float %101 %101 %101 %101
+               OpStore %v4_4 %102
+               OpReturn
+               OpFunctionEnd
+%from_expression_i32 = OpFunction %void None %18
+        %105 = OpLabel
+       %v2_5 = OpVariable %_ptr_Function_v2int Function %57
+       %v3_5 = OpVariable %_ptr_Function_v3int Function %62
+       %v4_5 = OpVariable %_ptr_Function_v4int Function %67
+        %107 = OpIAdd %int %int_1 %int_2
+        %108 = OpCompositeConstruct %v2int %107 %107
+               OpStore %v2_5 %108
+        %110 = OpIAdd %int %int_1 %int_2
+        %111 = OpCompositeConstruct %v3int %110 %110 %110
+               OpStore %v3_5 %111
+        %113 = OpIAdd %int %int_1 %int_2
+        %114 = OpCompositeConstruct %v4int %113 %113 %113 %113
+               OpStore %v4_5 %114
+               OpReturn
+               OpFunctionEnd
+%from_expression_u32 = OpFunction %void None %18
+        %117 = OpLabel
+       %v2_6 = OpVariable %_ptr_Function_v2uint Function %76
+       %v3_6 = OpVariable %_ptr_Function_v3uint Function %81
+       %v4_6 = OpVariable %_ptr_Function_v4uint Function %86
+        %119 = OpIAdd %uint %uint_1 %uint_2
+        %120 = OpCompositeConstruct %v2uint %119 %119
+               OpStore %v2_6 %120
+        %122 = OpIAdd %uint %uint_1 %uint_2
+        %123 = OpCompositeConstruct %v3uint %122 %122 %122
+               OpStore %v3_6 %123
+        %125 = OpIAdd %uint %uint_1 %uint_2
+        %126 = OpCompositeConstruct %v4uint %125 %125 %125 %125
+               OpStore %v4_6 %126
+               OpReturn
+               OpFunctionEnd
+   %get_bool = OpFunction %bool None %128
+        %130 = OpLabel
+               OpReturnValue %true
+               OpFunctionEnd
+    %get_f32 = OpFunction %float None %131
+        %133 = OpLabel
+               OpReturnValue %float_1
+               OpFunctionEnd
+    %get_i32 = OpFunction %int None %134
+        %136 = OpLabel
+               OpReturnValue %int_1
+               OpFunctionEnd
+    %get_u32 = OpFunction %uint None %137
+        %139 = OpLabel
+               OpReturnValue %uint_1
+               OpFunctionEnd
+%from_call_bool = OpFunction %void None %18
+        %141 = OpLabel
+       %v2_7 = OpVariable %_ptr_Function_v2bool Function %28
+       %v3_7 = OpVariable %_ptr_Function_v3bool Function %33
+       %v4_7 = OpVariable %_ptr_Function_v4bool Function %38
+        %142 = OpFunctionCall %bool %get_bool
+        %143 = OpCompositeConstruct %v2bool %142 %142
+               OpStore %v2_7 %143
+        %145 = OpFunctionCall %bool %get_bool
+        %146 = OpCompositeConstruct %v3bool %145 %145 %145
+               OpStore %v3_7 %146
+        %148 = OpFunctionCall %bool %get_bool
+        %149 = OpCompositeConstruct %v4bool %148 %148 %148 %148
+               OpStore %v4_7 %149
+               OpReturn
+               OpFunctionEnd
+%from_call_f32 = OpFunction %void None %18
+        %152 = OpLabel
+       %v2_8 = OpVariable %_ptr_Function_v2float Function %43
+       %v3_8 = OpVariable %_ptr_Function_v3float Function %46
+       %v4_8 = OpVariable %_ptr_Function_v4float Function %17
+        %153 = OpFunctionCall %float %get_f32
+        %154 = OpCompositeConstruct %v2float %153 %153
+               OpStore %v2_8 %154
+        %156 = OpFunctionCall %float %get_f32
+        %157 = OpCompositeConstruct %v3float %156 %156 %156
+               OpStore %v3_8 %157
+        %159 = OpFunctionCall %float %get_f32
+        %160 = OpCompositeConstruct %v4float %159 %159 %159 %159
+               OpStore %v4_8 %160
+               OpReturn
+               OpFunctionEnd
+%from_call_i32 = OpFunction %void None %18
+        %163 = OpLabel
+       %v2_9 = OpVariable %_ptr_Function_v2int Function %57
+       %v3_9 = OpVariable %_ptr_Function_v3int Function %62
+       %v4_9 = OpVariable %_ptr_Function_v4int Function %67
+        %164 = OpFunctionCall %int %get_i32
+        %165 = OpCompositeConstruct %v2int %164 %164
+               OpStore %v2_9 %165
+        %167 = OpFunctionCall %int %get_i32
+        %168 = OpCompositeConstruct %v3int %167 %167 %167
+               OpStore %v3_9 %168
+        %170 = OpFunctionCall %int %get_i32
+        %171 = OpCompositeConstruct %v4int %170 %170 %170 %170
+               OpStore %v4_9 %171
+               OpReturn
+               OpFunctionEnd
+%from_call_u32 = OpFunction %void None %18
+        %174 = OpLabel
+      %v2_10 = OpVariable %_ptr_Function_v2uint Function %76
+      %v3_10 = OpVariable %_ptr_Function_v3uint Function %81
+      %v4_10 = OpVariable %_ptr_Function_v4uint Function %86
+        %175 = OpFunctionCall %uint %get_u32
+        %176 = OpCompositeConstruct %v2uint %175 %175
+               OpStore %v2_10 %176
+        %178 = OpFunctionCall %uint %get_u32
+        %179 = OpCompositeConstruct %v3uint %178 %178 %178
+               OpStore %v3_10 %179
+        %181 = OpFunctionCall %uint %get_u32
+        %182 = OpCompositeConstruct %v4uint %181 %181 %181 %181
+               OpStore %v4_10 %182
+               OpReturn
+               OpFunctionEnd
+%with_swizzle = OpFunction %void None %18
+        %185 = OpLabel
+          %a = OpVariable %_ptr_Function_float Function %189
+          %b = OpVariable %_ptr_Function_float Function %189
+          %c = OpVariable %_ptr_Function_float Function %189
+        %186 = OpCompositeExtract %float %4 1
+               OpStore %a %186
+        %190 = OpCompositeExtract %float %8 2
+               OpStore %b %190
+        %192 = OpCompositeExtract %float %12 3
+               OpStore %c %192
+               OpReturn
+               OpFunctionEnd
+%tint_symbol_2 = OpFunction %void None %194
+%tint_symbol = OpFunctionParameter %v4float
+        %197 = OpLabel
+               OpStore %tint_symbol_1 %tint_symbol
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %18
+        %199 = OpLabel
+        %200 = OpFunctionCall %void %tint_symbol_2 %202
+               OpReturn
+               OpFunctionEnd
diff --git a/test/var/splat.wgsl.expected.wgsl b/test/var/splat.wgsl.expected.wgsl
new file mode 100644
index 0000000..9adddf9
--- /dev/null
+++ b/test/var/splat.wgsl.expected.wgsl
@@ -0,0 +1,104 @@
+var<private> g_v2 : vec2<f32> = vec2<f32>(1.0);
+
+var<private> g_v3 : vec3<f32> = vec3<f32>(1.0);
+
+var<private> g_v4 : vec4<f32> = vec4<f32>(1.0);
+
+fn from_immediate_bool() {
+  var v2 : vec2<bool> = vec2<bool>(true);
+  var v3 : vec3<bool> = vec3<bool>(true);
+  var v4 : vec4<bool> = vec4<bool>(true);
+}
+
+fn from_immediate_f32() {
+  var v2 : vec2<f32> = vec2<f32>(1.0);
+  var v3 : vec3<f32> = vec3<f32>(1.0);
+  var v4 : vec4<f32> = vec4<f32>(1.0);
+}
+
+fn from_immediate_i32() {
+  var v2 : vec2<i32> = vec2<i32>(1);
+  var v3 : vec3<i32> = vec3<i32>(1);
+  var v4 : vec4<i32> = vec4<i32>(1);
+}
+
+fn from_immediate_u32() {
+  var v2 : vec2<u32> = vec2<u32>(1u);
+  var v3 : vec3<u32> = vec3<u32>(1u);
+  var v4 : vec4<u32> = vec4<u32>(1u);
+}
+
+fn from_expression_bool() {
+  var v2 : vec2<bool> = vec2<bool>(true);
+  var v3 : vec3<bool> = vec3<bool>(true);
+  var v4 : vec4<bool> = vec4<bool>(true);
+}
+
+fn from_expression_f32() {
+  var v2 : vec2<f32> = vec2<f32>((1.0 + 2.0));
+  var v3 : vec3<f32> = vec3<f32>((1.0 + 2.0));
+  var v4 : vec4<f32> = vec4<f32>((1.0 + 2.0));
+}
+
+fn from_expression_i32() {
+  var v2 : vec2<i32> = vec2<i32>((1 + 2));
+  var v3 : vec3<i32> = vec3<i32>((1 + 2));
+  var v4 : vec4<i32> = vec4<i32>((1 + 2));
+}
+
+fn from_expression_u32() {
+  var v2 : vec2<u32> = vec2<u32>((1u + 2u));
+  var v3 : vec3<u32> = vec3<u32>((1u + 2u));
+  var v4 : vec4<u32> = vec4<u32>((1u + 2u));
+}
+
+fn get_bool() -> bool {
+  return true;
+}
+
+fn get_f32() -> f32 {
+  return 1.0;
+}
+
+fn get_i32() -> i32 {
+  return 1;
+}
+
+fn get_u32() -> u32 {
+  return 1u;
+}
+
+fn from_call_bool() {
+  var v2 : vec2<bool> = vec2<bool>(get_bool());
+  var v3 : vec3<bool> = vec3<bool>(get_bool());
+  var v4 : vec4<bool> = vec4<bool>(get_bool());
+}
+
+fn from_call_f32() {
+  var v2 : vec2<f32> = vec2<f32>(get_f32());
+  var v3 : vec3<f32> = vec3<f32>(get_f32());
+  var v4 : vec4<f32> = vec4<f32>(get_f32());
+}
+
+fn from_call_i32() {
+  var v2 : vec2<i32> = vec2<i32>(get_i32());
+  var v3 : vec3<i32> = vec3<i32>(get_i32());
+  var v4 : vec4<i32> = vec4<i32>(get_i32());
+}
+
+fn from_call_u32() {
+  var v2 : vec2<u32> = vec2<u32>(get_u32());
+  var v3 : vec3<u32> = vec3<u32>(get_u32());
+  var v4 : vec4<u32> = vec4<u32>(get_u32());
+}
+
+fn with_swizzle() {
+  var a = vec2<f32>(1.0).y;
+  var b = vec3<f32>(1.0).z;
+  var c = vec4<f32>(1.0).w;
+}
+
+[[stage(fragment)]]
+fn main() -> [[location(0)]] vec4<f32> {
+  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+}