[tint][ir][val] Break up tests with multiple EXPECTs

These tests have either been split into multiple test fixtures or a
parameterized test. In one case the test was removed, because the
individual conditions being tested together were already being tested
in separate tests, and it wasn't obvious why they needed to be tested
together.

Fixes: 391907150
Change-Id: I3638464c4e2b0b2145d8398ecf9b2bb0445f838f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/223634
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/validator_access_test.cc b/src/tint/lang/core/ir/validator_access_test.cc
index ce321ec..e9c9a47 100644
--- a/src/tint/lang/core/ir/validator_access_test.cc
+++ b/src/tint/lang/core/ir/validator_access_test.cc
@@ -650,28 +650,6 @@
 )")) << res.Failure().reason.Str();
 }
 
-TEST_F(IR_ValidatorTest, Store_NullToAndFrom) {
-    auto* f = b.Function("my_func", ty.void_());
-
-    b.Append(f->Block(), [&] {
-        b.Append(mod.CreateInstruction<ir::Store>(nullptr, nullptr));
-        b.Return(f);
-    });
-
-    auto res = ir::Validate(mod);
-    ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:11 error: store: operand is undefined
-    store undef, undef
-          ^^^^^
-)")) << res.Failure().reason.Str();
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:18 error: store: operand is undefined
-    store undef, undef
-                 ^^^^^
-)")) << res.Failure().reason.Str();
-}
-
 TEST_F(IR_ValidatorTest, Store_NonEmptyResult) {
     auto* f = b.Function("my_func", ty.void_());
 
diff --git a/src/tint/lang/core/ir/validator_call_test.cc b/src/tint/lang/core/ir/validator_call_test.cc
index e76cd8e..a944140 100644
--- a/src/tint/lang/core/ir/validator_call_test.cc
+++ b/src/tint/lang/core/ir/validator_call_test.cc
@@ -308,7 +308,29 @@
 )")) << res.Failure().reason.Str();
 }
 
-TEST_F(IR_ValidatorTest, CallToBuiltin_NonSingularResult) {
+TEST_F(IR_ValidatorTest, CallToBuiltin_TooFewResults) {
+    auto* f = b.Function("f", ty.void_());
+    b.Append(f->Block(), [&] {
+        auto* i = b.Var<function, f32>("i");
+        i->SetInitializer(b.Constant(0_f));
+        auto* load = b.Load(i);
+        auto* load_ret = load->Result(0);
+        auto* too_few_call = b.Call(ty.f32(), BuiltinFn::kAbs, load_ret);
+        too_few_call->ClearResults();
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(
+        res.Failure().reason.Str(),
+        testing::HasSubstr(R"(:5:13 error: abs: call to builtin has 0 results, when 1 is expected
+    undef = abs %3
+            ^^^
+)")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, CallToBuiltin_TooManyResults) {
     auto* f = b.Function("f", ty.void_());
     b.Append(f->Block(), [&] {
         auto* i = b.Var<function, f32>("i");
@@ -317,8 +339,6 @@
         auto* load_ret = load->Result(0);
         auto* too_many_call = b.Call(ty.f32(), BuiltinFn::kAbs, load_ret);
         too_many_call->SetResults(Vector{load_ret, load_ret});
-        auto* too_few_call = b.Call(ty.f32(), BuiltinFn::kAbs, load_ret);
-        too_few_call->ClearResults();
         b.Return(f);
     });
 
@@ -330,13 +350,6 @@
     %3:f32 = abs %3
              ^^^
 )")) << res.Failure().reason.Str();
-
-    EXPECT_THAT(
-        res.Failure().reason.Str(),
-        testing::HasSubstr(R"(:6:13 error: abs: call to builtin has 0 results, when 1 is expected
-    undef = abs %3
-            ^^^
-)")) << res.Failure().reason.Str();
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/validator_flow_control_test.cc b/src/tint/lang/core/ir/validator_flow_control_test.cc
index e4a399a..bf1781d 100644
--- a/src/tint/lang/core/ir/validator_flow_control_test.cc
+++ b/src/tint/lang/core/ir/validator_flow_control_test.cc
@@ -129,50 +129,23 @@
 )")) << res.Failure().reason.Str();
 }
 
-TEST_F(IR_ValidatorTest, Terminator_RootBlock) {
+TEST_F(IR_ValidatorTest, Terminate_RootBlock) {
     auto f = b.Function("f", ty.void_());
     b.Append(f->Block(), [&] { b.Unreachable(); });
 
-    mod.root_block->Append(b.Return(f));
-    mod.root_block->Append(b.Unreachable());
     mod.root_block->Append(b.TerminateInvocation());
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(
-                    R"(:2:3 error: return: root block: invalid instruction: tint::core::ir::Return
-  ret
-  ^^^
-)")) << res.Failure().reason.Str();
     EXPECT_THAT(
         res.Failure().reason.Str(),
         testing::HasSubstr(
-            R"(:3:3 error: unreachable: root block: invalid instruction: tint::core::ir::Unreachable
-  unreachable
-  ^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-    EXPECT_THAT(
-        res.Failure().reason.Str(),
-        testing::HasSubstr(
-            R"(:4:3 error: terminate_invocation: root block: invalid instruction: tint::core::ir::TerminateInvocation
+            R"(:2:3 error: terminate_invocation: root block: invalid instruction: tint::core::ir::TerminateInvocation
   terminate_invocation
   ^^^^^^^^^^^^^^^^^^^^
 )")) << res.Failure().reason.Str();
 }
 
-TEST_F(IR_ValidatorTest, Terminator_HasResult) {
-    auto* ret_func = b.Function("ret_func", ty.void_());
-    b.Append(ret_func->Block(), [&] {
-        auto* r = b.Return(ret_func);
-        r->SetResults(Vector{b.InstructionResult(ty.i32())});
-    });
-
-    auto* unreachable_func = b.Function("unreachable_func", ty.void_());
-    b.Append(unreachable_func->Block(), [&] {
-        auto* r = b.Unreachable();
-        r->SetResults(Vector{b.InstructionResult(ty.i32())});
-    });
-
+TEST_F(IR_ValidatorTest, Terminate_MissingResult) {
     auto* terminate_func = b.Function("terminate_func", ty.void_());
     b.Append(terminate_func->Block(), [&] {
         auto* r = b.TerminateInvocation();
@@ -181,19 +154,9 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:5 error: return: expected exactly 0 results, got 1
-    ret
-    ^^^
-)")) << res.Failure().reason.Str();
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:8:5 error: unreachable: expected exactly 0 results, got 1
-    unreachable
-    ^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
     EXPECT_THAT(
         res.Failure().reason.Str(),
-        testing::HasSubstr(R"(:13:5 error: terminate_invocation: expected exactly 0 results, got 1
+        testing::HasSubstr(R"(:3:5 error: terminate_invocation: expected exactly 0 results, got 1
     terminate_invocation
     ^^^^^^^^^^^^^^^^^^^^
 )")) << res.Failure().reason.Str();
@@ -964,13 +927,13 @@
 )")) << res.Failure().reason.Str();
 }
 
-TEST_F(IR_ValidatorTest, Continue_MismatchedTypes) {
+TEST_F(IR_ValidatorTest, Continue_MismatchedInt) {
     auto* f = b.Function("my_func", ty.void_());
     b.Append(f->Block(), [&] {
         auto* loop = b.Loop();
         loop->Continuing()->SetParams(
             {b.BlockParam<i32>(), b.BlockParam<f32>(), b.BlockParam<u32>(), b.BlockParam<bool>()});
-        b.Append(loop->Body(), [&] { b.Continue(loop, 1_i, 2_i, 3_f, false); });
+        b.Append(loop->Body(), [&] { b.Continue(loop, 1_i, 2_i, 3_u, false); });
         b.Append(loop->Continuing(), [&] { b.BreakIf(loop, true); });
         b.Return(f);
     });
@@ -981,16 +944,30 @@
         res.Failure().reason.Str(),
         testing::HasSubstr(
             R"(:5:22 error: continue: operand with type 'i32' does not match 'loop' block $B3 target type 'f32'
-        continue 1i, 2i, 3.0f, false  # -> $B3
+        continue 1i, 2i, 3u, false  # -> $B3
                      ^^
 )")) << res.Failure().reason.Str();
+}
 
+TEST_F(IR_ValidatorTest, Continue_MismatchedFloat) {
+    auto* f = b.Function("my_func", ty.void_());
+    b.Append(f->Block(), [&] {
+        auto* loop = b.Loop();
+        loop->Continuing()->SetParams(
+            {b.BlockParam<i32>(), b.BlockParam<f32>(), b.BlockParam<u32>(), b.BlockParam<bool>()});
+        b.Append(loop->Body(), [&] { b.Continue(loop, 1_i, 2_f, 3_f, false); });
+        b.Append(loop->Continuing(), [&] { b.BreakIf(loop, true); });
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
     EXPECT_THAT(
         res.Failure().reason.Str(),
         testing::HasSubstr(
-            R"(:5:26 error: continue: operand with type 'f32' does not match 'loop' block $B3 target type 'u32'
-        continue 1i, 2i, 3.0f, false  # -> $B3
-                         ^^^^
+            R"(:5:28 error: continue: operand with type 'f32' does not match 'loop' block $B3 target type 'u32'
+        continue 1i, 2.0f, 3.0f, false  # -> $B3
+                           ^^^^
 )")) << res.Failure().reason.Str();
 }
 
@@ -1112,13 +1089,13 @@
 )")) << res.Failure().reason.Str();
 }
 
-TEST_F(IR_ValidatorTest, NextIteration_MismatchedTypes) {
+TEST_F(IR_ValidatorTest, NextIteration_MismatchedInt) {
     auto* f = b.Function("my_func", ty.void_());
     b.Append(f->Block(), [&] {
         auto* loop = b.Loop();
         loop->Body()->SetParams(
             {b.BlockParam<i32>(), b.BlockParam<f32>(), b.BlockParam<u32>(), b.BlockParam<bool>()});
-        b.Append(loop->Initializer(), [&] { b.NextIteration(loop, 1_i, 2_i, 3_f, false); });
+        b.Append(loop->Initializer(), [&] { b.NextIteration(loop, 1_i, 2_i, 3_u, false); });
         b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
         b.Return(f);
     });
@@ -1129,15 +1106,30 @@
         res.Failure().reason.Str(),
         testing::HasSubstr(
             R"(:5:28 error: next_iteration: operand with type 'i32' does not match 'loop' block $B3 target type 'f32'
-        next_iteration 1i, 2i, 3.0f, false  # -> $B3
+        next_iteration 1i, 2i, 3u, false  # -> $B3
                            ^^
 )")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, NextIteration_MismatchedFloat) {
+    auto* f = b.Function("my_func", ty.void_());
+    b.Append(f->Block(), [&] {
+        auto* loop = b.Loop();
+        loop->Body()->SetParams(
+            {b.BlockParam<i32>(), b.BlockParam<f32>(), b.BlockParam<u32>(), b.BlockParam<bool>()});
+        b.Append(loop->Initializer(), [&] { b.NextIteration(loop, 1_i, 2_f, 3_f, false); });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
     EXPECT_THAT(
         res.Failure().reason.Str(),
         testing::HasSubstr(
-            R"(:5:32 error: next_iteration: operand with type 'f32' does not match 'loop' block $B3 target type 'u32'
-        next_iteration 1i, 2i, 3.0f, false  # -> $B3
-                               ^^^^
+            R"(:5:34 error: next_iteration: operand with type 'f32' does not match 'loop' block $B3 target type 'u32'
+        next_iteration 1i, 2.0f, 3.0f, false  # -> $B3
+                                 ^^^^
 )")) << res.Failure().reason.Str();
 }
 
@@ -1265,7 +1257,7 @@
 )")) << res.Failure().reason.Str();
 }
 
-TEST_F(IR_ValidatorTest, BreakIf_NextIterMismatchedTypes) {
+TEST_F(IR_ValidatorTest, BreakIf_NextIterMismatchedInt) {
     auto* f = b.Function("my_func", ty.void_());
     b.Append(f->Block(), [&] {
         auto* loop = b.Loop();
@@ -1275,7 +1267,7 @@
                  [&] { b.NextIteration(loop, nullptr, nullptr, nullptr, nullptr); });
         b.Append(loop->Body(), [&] { b.Continue(loop); });
         b.Append(loop->Continuing(),
-                 [&] { b.BreakIf(loop, true, b.Values(1_i, 2_i, 3_f, false), Empty); });
+                 [&] { b.BreakIf(loop, true, b.Values(1_i, 2_i, 3_u, false), Empty); });
         b.Return(f);
     });
 
@@ -1285,15 +1277,33 @@
         res.Failure().reason.Str(),
         testing::HasSubstr(
             R"(:11:45 error: break_if: operand with type 'i32' does not match 'loop' block $B3 target type 'f32'
-        break_if true next_iteration: [ 1i, 2i, 3.0f, false ]  # -> [t: exit_loop loop_1, f: $B3]
+        break_if true next_iteration: [ 1i, 2i, 3u, false ]  # -> [t: exit_loop loop_1, f: $B3]
                                             ^^
 )")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, BreakIf_NextIterMismatchedFloat) {
+    auto* f = b.Function("my_func", ty.void_());
+    b.Append(f->Block(), [&] {
+        auto* loop = b.Loop();
+        loop->Body()->SetParams(
+            {b.BlockParam<i32>(), b.BlockParam<f32>(), b.BlockParam<u32>(), b.BlockParam<bool>()});
+        b.Append(loop->Initializer(),
+                 [&] { b.NextIteration(loop, nullptr, nullptr, nullptr, nullptr); });
+        b.Append(loop->Body(), [&] { b.Continue(loop); });
+        b.Append(loop->Continuing(),
+                 [&] { b.BreakIf(loop, true, b.Values(1_i, 2_f, 3_f, false), Empty); });
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
     EXPECT_THAT(
         res.Failure().reason.Str(),
         testing::HasSubstr(
-            R"(:11:49 error: break_if: operand with type 'f32' does not match 'loop' block $B3 target type 'u32'
-        break_if true next_iteration: [ 1i, 2i, 3.0f, false ]  # -> [t: exit_loop loop_1, f: $B3]
-                                                ^^^^
+            R"(:11:51 error: break_if: operand with type 'f32' does not match 'loop' block $B3 target type 'u32'
+        break_if true next_iteration: [ 1i, 2.0f, 3.0f, false ]  # -> [t: exit_loop loop_1, f: $B3]
+                                                  ^^^^
 )")) << res.Failure().reason.Str();
 }
 
@@ -1354,7 +1364,7 @@
 )")) << res.Failure().reason.Str();
 }
 
-TEST_F(IR_ValidatorTest, BreakIf_ExitMismatchedTypes) {
+TEST_F(IR_ValidatorTest, BreakIf_ExitMismatchedInt) {
     auto* f = b.Function("my_func", ty.void_());
     b.Append(f->Block(), [&] {
         auto* loop = b.Loop();
@@ -1362,7 +1372,7 @@
                          b.InstructionResult<u32>(), b.InstructionResult<bool>());
         b.Append(loop->Body(), [&] { b.Continue(loop); });
         b.Append(loop->Continuing(),
-                 [&] { b.BreakIf(loop, true, Empty, b.Values(1_i, 2_i, 3_f, false)); });
+                 [&] { b.BreakIf(loop, true, Empty, b.Values(1_i, 2_i, 3_u, false)); });
         b.Return(f);
     });
 
@@ -1372,15 +1382,31 @@
         res.Failure().reason.Str(),
         testing::HasSubstr(
             R"(:8:40 error: break_if: operand with type 'i32' does not match 'loop' target type 'f32'
-        break_if true exit_loop: [ 1i, 2i, 3.0f, false ]  # -> [t: exit_loop loop_1, f: $B2]
+        break_if true exit_loop: [ 1i, 2i, 3u, false ]  # -> [t: exit_loop loop_1, f: $B2]
                                        ^^
 )")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, BreakIf_ExitMismatchedTypes) {
+    auto* f = b.Function("my_func", ty.void_());
+    b.Append(f->Block(), [&] {
+        auto* loop = b.Loop();
+        loop->SetResults(b.InstructionResult<i32>(), b.InstructionResult<f32>(),
+                         b.InstructionResult<u32>(), b.InstructionResult<bool>());
+        b.Append(loop->Body(), [&] { b.Continue(loop); });
+        b.Append(loop->Continuing(),
+                 [&] { b.BreakIf(loop, true, Empty, b.Values(1_i, 2_f, 3_f, false)); });
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
     EXPECT_THAT(
         res.Failure().reason.Str(),
         testing::HasSubstr(
-            R"(:8:44 error: break_if: operand with type 'f32' does not match 'loop' target type 'u32'
-        break_if true exit_loop: [ 1i, 2i, 3.0f, false ]  # -> [t: exit_loop loop_1, f: $B2]
-                                           ^^^^
+            R"(:8:46 error: break_if: operand with type 'f32' does not match 'loop' target type 'u32'
+        break_if true exit_loop: [ 1i, 2.0f, 3.0f, false ]  # -> [t: exit_loop loop_1, f: $B2]
+                                             ^^^^
 )")) << res.Failure().reason.Str();
 }
 
@@ -1857,6 +1883,37 @@
 )")) << res.Failure().reason.Str();
 }
 
+TEST_F(IR_ValidatorTest, Return_RootBlock) {
+    auto f = b.Function("f", ty.void_());
+    b.Append(f->Block(), [&] { b.Unreachable(); });
+
+    mod.root_block->Append(b.Return(f));
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(
+                    R"(:2:3 error: return: root block: invalid instruction: tint::core::ir::Return
+  ret
+  ^^^
+)")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, Return_MissingResult) {
+    auto* ret_func = b.Function("ret_func", ty.void_());
+    b.Append(ret_func->Block(), [&] {
+        auto* r = b.Return(ret_func);
+        r->SetResults(Vector{b.InstructionResult(ty.i32())});
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:3:5 error: return: expected exactly 0 results, got 1
+    ret
+    ^^^
+)")) << res.Failure().reason.Str();
+}
+
 TEST_F(IR_ValidatorTest, Unreachable_UnexpectedResult) {
     auto* f = b.Function("my_func", ty.void_());
     b.Append(f->Block(), [&] {  //
@@ -1889,6 +1946,37 @@
 )")) << res.Failure().reason.Str();
 }
 
+TEST_F(IR_ValidatorTest, Unreachable_RootBlock) {
+    auto f = b.Function("f", ty.void_());
+    b.Append(f->Block(), [&] { b.Unreachable(); });
+
+    mod.root_block->Append(b.Unreachable());
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(
+        res.Failure().reason.Str(),
+        testing::HasSubstr(
+            R"(:2:3 error: unreachable: root block: invalid instruction: tint::core::ir::Unreachable
+  unreachable
+  ^^^^^^^^^^^
+)")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, Unreachable_MissingResult) {
+    auto* unreachable_func = b.Function("unreachable_func", ty.void_());
+    b.Append(unreachable_func->Block(), [&] {
+        auto* r = b.Unreachable();
+        r->SetResults(Vector{b.InstructionResult(ty.i32())});
+    });
+
+    auto res = ir::Validate(mod);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:3:5 error: unreachable: expected exactly 0 results, got 1
+    unreachable
+    ^^^^^^^^^^^
+)")) << res.Failure().reason.Str();
+}
+
 TEST_F(IR_ValidatorTest, Switch_ConditionPointer) {
     auto* f = b.Function("my_func", ty.void_());
 
diff --git a/src/tint/lang/core/ir/validator_function_test.cc b/src/tint/lang/core/ir/validator_function_test.cc
index 126aaa7..b7ee675 100644
--- a/src/tint/lang/core/ir/validator_function_test.cc
+++ b/src/tint/lang/core/ir/validator_function_test.cc
@@ -689,16 +689,12 @@
 )")) << res.Failure().reason.Str();
 }
 
-TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType) {
-    auto types = Vector<const core::type::Type*, 2>{
-        ty.external_texture(),   ty.sampler(), ty.runtime_array(ty.f32()), ty.ptr<function, i32>(),
-        ty.ref<function, u32>(),
-    };
-
-    for (auto t : types) {
-        auto* f = b.Function(t);
-        b.Append(f->Block(), [&] { b.Unreachable(); });
-    }
+// Parameterizing these tests is very difficult/unreadable due to the the
+// multiple layers of forwarding/templating occurring in the type manager, so
+// writing them out explicitly instead.
+TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_ExternalTexture) {
+    auto* f = b.Function(ty.external_texture());
+    b.Append(f->Block(), [&] { b.Unreachable(); });
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
@@ -706,25 +702,57 @@
                 testing::HasSubstr(R"(:1:1 error: function return type must be constructible
 %1 = func():texture_external {
 ^^
+)")) << res.Failure().reason.Str();
+}
 
-:6:1 error: function return type must be constructible
-%2 = func():sampler {
+TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_Sampler) {
+    auto* f = b.Function(ty.sampler());
+    b.Append(f->Block(), [&] { b.Unreachable(); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:1:1 error: function return type must be constructible
+%1 = func():sampler {
 ^^
+)")) << res.Failure().reason.Str();
+}
 
-:11:1 error: function return type must be constructible
-%3 = func():array<f32> {
+TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_RuntimeArray) {
+    auto* f = b.Function(ty.runtime_array(ty.f32()));
+    b.Append(f->Block(), [&] { b.Unreachable(); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:1:1 error: function return type must be constructible
+%1 = func():array<f32> {
 ^^
+)")) << res.Failure().reason.Str();
+}
 
-:16:1 error: function return type must be constructible
-%4 = func():ptr<function, i32, read_write> {
+TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_Ptr) {
+    auto* f = b.Function(ty.ptr<function, i32>());
+    b.Append(f->Block(), [&] { b.Unreachable(); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:1:1 error: function return type must be constructible
+%1 = func():ptr<function, i32, read_write> {
 ^^
+)")) << res.Failure().reason.Str();
+}
 
-:21:1 error: reference types are not permitted here
-%5 = func():ref<function, u32, read_write> {
-^^
+TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_Ref) {
+    auto* f = b.Function(ty.ref<function, u32>());
+    b.Append(f->Block(), [&] { b.Unreachable(); });
 
-:21:1 error: function return type must be constructible
-%5 = func():ref<function, u32, read_write> {
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:1:1 error: function return type must be constructible
+%1 = func():ref<function, u32, read_write> {
 ^^
 )")) << res.Failure().reason.Str();
 }
diff --git a/src/tint/lang/core/ir/validator_type_test.cc b/src/tint/lang/core/ir/validator_type_test.cc
index e7fed99..fb00509 100644
--- a/src/tint/lang/core/ir/validator_type_test.cc
+++ b/src/tint/lang/core/ir/validator_type_test.cc
@@ -51,234 +51,6 @@
 using namespace tint::core::fluent_types;     // NOLINT
 using namespace tint::core::number_suffixes;  // NOLINT
 
-TEST_F(IR_ValidatorTest, Abstract_Scalar) {
-    auto* f = b.Function("my_func", ty.void_());
-    b.Append(f->Block(), [&] {
-        b.Var("af", function, ty.AFloat());
-        b.Var("af", function, ty.AInt());
-        b.Return(f);
-    });
-
-    auto res = ir::Validate(mod);
-    ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
-    %af:ptr<function, abstract-float, read_write> = var
-    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-:2:3 note: in block
-  $B1: {
-  ^^^
-
-:4:5 error: var: abstracts are not permitted
-    %af_1:ptr<function, abstract-int, read_write> = var  # %af_1: 'af'
-    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-}
-
-TEST_F(IR_ValidatorTest, Abstract_Vector) {
-    auto* f = b.Function("my_func", ty.void_());
-    b.Append(f->Block(), [&] {
-        b.Var("af", function, ty.vec2<AFloat>());
-        b.Var("ai", function, ty.vec3<AInt>());
-        b.Return(f);
-    });
-
-    auto res = ir::Validate(mod);
-    ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
-    %af:ptr<function, vec2<abstract-float>, read_write> = var
-    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:4:5 error: var: abstracts are not permitted
-    %ai:ptr<function, vec3<abstract-int>, read_write> = var
-    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-}
-
-TEST_F(IR_ValidatorTest, Abstract_Matrix) {
-    auto* f = b.Function("my_func", ty.void_());
-    b.Append(f->Block(), [&] {
-        b.Var("af", function, ty.mat2x2<AFloat>());
-        b.Var("ai", function, ty.mat3x4<AInt>());
-        b.Return(f);
-    });
-
-    auto res = ir::Validate(mod);
-    ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
-    %af:ptr<function, mat2x2<abstract-float>, read_write> = var
-    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:4:5 error: var: abstracts are not permitted
-    %ai:ptr<function, mat3x4<abstract-int>, read_write> = var
-    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-}
-
-TEST_F(IR_ValidatorTest, Abstract_Struct) {
-    auto* str_ty =
-        ty.Struct(mod.symbols.New("MyStruct"), {
-                                                   {mod.symbols.New("af"), ty.AFloat(), {}},
-                                                   {mod.symbols.New("ai"), ty.AInt(), {}},
-                                               });
-    auto* v = b.Var(ty.ptr(private_, str_ty));
-    mod.root_block->Append(v);
-
-    auto res = ir::Validate(mod);
-    ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:7:3 error: var: abstracts are not permitted
-  %1:ptr<private, MyStruct, read_write> = var
-  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-}
-
-TEST_F(IR_ValidatorTest, Abstract_FunctionParam) {
-    auto* f = b.Function("my_func", ty.void_());
-
-    f->SetParams({b.FunctionParam(ty.AFloat()), b.FunctionParam(ty.AInt())});
-    f->Block()->Append(b.Return(f));
-
-    auto res = ir::Validate(mod);
-    ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:1:17 error: abstracts are not permitted
-%my_func = func(%2:abstract-float, %3:abstract-int):void {
-                ^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:1:36 error: abstracts are not permitted
-%my_func = func(%2:abstract-float, %3:abstract-int):void {
-                                   ^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-}
-
-TEST_F(IR_ValidatorTest, Type_VectorElements) {
-    auto* f = b.Function("my_func", ty.void_());
-
-    b.Append(f->Block(), [&] {
-        b.Var("u32_valid", AddressSpace::kFunction, ty.vec4(ty.u32()));
-        b.Var("i32_valid", AddressSpace::kFunction, ty.vec4(ty.i32()));
-        b.Var("bool_valid", AddressSpace::kFunction, ty.vec2(ty.bool_()));
-        b.Var("f16_valid", AddressSpace::kFunction, ty.vec3(ty.f16()));
-        b.Var("f32_valid", AddressSpace::kFunction, ty.vec3(ty.f32()));
-        b.Var("void_invalid", AddressSpace::kFunction, ty.vec2(ty.void_()));
-        b.Return(f);
-    });
-
-    auto res = ir::Validate(mod);
-    ASSERT_NE(res, Success);
-    EXPECT_THAT(
-        res.Failure().reason.Str(),
-        testing::HasSubstr(R"(:8:5 error: var: vector elements, 'vec2<void>', must be scalars
-    %void_invalid:ptr<function, vec2<void>, read_write> = var
-    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-}
-
-TEST_F(IR_ValidatorTest, Type_MatrixElements) {
-    auto* f = b.Function("my_func", ty.void_());
-
-    b.Append(f->Block(), [&] {
-        b.Var("u32_invalid", AddressSpace::kFunction, ty.mat2x2(ty.u32()));
-        b.Var("i32_invalid", AddressSpace::kFunction, ty.mat3x2(ty.i32()));
-        b.Var("bool_invalid", AddressSpace::kFunction, ty.mat4x2(ty.bool_()));
-        b.Var("f16_valid", AddressSpace::kFunction, ty.mat2x3(ty.f16()));
-        b.Var("f32_valid", AddressSpace::kFunction, ty.mat4x4(ty.f32()));
-        b.Var("void_invalid", AddressSpace::kFunction, ty.mat3x3(ty.void_()));
-        b.Return(f);
-    });
-
-    auto res = ir::Validate(mod);
-    ASSERT_NE(res, Success);
-    EXPECT_THAT(
-        res.Failure().reason.Str(),
-        testing::HasSubstr(R"(:3:5 error: var: matrix elements, 'mat2x2<u32>', must be float scalars
-    %u32_invalid:ptr<function, mat2x2<u32>, read_write> = var
-    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-    EXPECT_THAT(
-        res.Failure().reason.Str(),
-        testing::HasSubstr(R"(:4:5 error: var: matrix elements, 'mat3x2<i32>', must be float scalars
-    %i32_invalid:ptr<function, mat3x2<i32>, read_write> = var
-    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(
-                    R"(:5:5 error: var: matrix elements, 'mat4x2<bool>', must be float scalars
-    %bool_invalid:ptr<function, mat4x2<bool>, read_write> = var
-    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(
-                    R"(:8:5 error: var: matrix elements, 'mat3x3<void>', must be float scalars
-    %void_invalid:ptr<function, mat3x3<void>, read_write> = var
-    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-}
-
-TEST_F(IR_ValidatorTest, Type_StorageTextureDimension) {
-    auto* valid =
-        b.Var("valid", AddressSpace::kStorage,
-              ty.storage_texture(core::type::TextureDimension::k2d, core::TexelFormat::kRgba32Float,
-                                 core::Access::kReadWrite),
-              read_write);
-    valid->SetBindingPoint(0, 0);
-    mod.root_block->Append(valid);
-
-    auto* cube =
-        b.Var("cube_invalid", AddressSpace::kStorage,
-              ty.storage_texture(core::type::TextureDimension::kCube,
-                                 core::TexelFormat::kRgba32Float, core::Access::kReadWrite),
-              read_write);
-    cube->SetBindingPoint(1, 1);
-    mod.root_block->Append(cube);
-
-    auto* cube_array =
-        b.Var("cube_array_invalid", AddressSpace::kStorage,
-              ty.storage_texture(core::type::TextureDimension::kCubeArray,
-                                 core::TexelFormat::kRgba32Float, core::Access::kReadWrite),
-              read_write);
-    cube_array->SetBindingPoint(2, 2);
-    mod.root_block->Append(cube_array);
-
-    auto* none =
-        b.Var("none_invalid", AddressSpace::kStorage,
-              ty.storage_texture(core::type::TextureDimension::kNone,
-                                 core::TexelFormat::kRgba32Float, core::Access::kReadWrite),
-              read_write);
-    none->SetBindingPoint(3, 3);
-    mod.root_block->Append(none);
-
-    auto res = ir::Validate(mod);
-    ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(
-                    R"(:3:3 error: var: dimension 'cube' for storage textures does not in WGSL yet
-  %cube_invalid:ptr<storage, texture_storage_cube<rgba32float, read_write>, read_write> = var @binding_point(1, 1)
-  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-    EXPECT_THAT(
-        res.Failure().reason.Str(),
-        testing::HasSubstr(
-            R"(:4:3 error: var: dimension 'cube_array' for storage textures does not in WGSL yet
-  %cube_array_invalid:ptr<storage, texture_storage_cube_array<rgba32float, read_write>, read_write> = var @binding_point(2, 2)
-  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:5:3 error: var: invalid texture dimension 'none'
-  %none_invalid:ptr<storage, texture_storage_none<rgba32float, read_write>, read_write> = var @binding_point(3, 3)
-  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
-}
-
 namespace {
 template <typename T>
 static const core::type::Type* TypeBuilder(core::type::Manager& m) {
@@ -293,6 +65,311 @@
 using TypeBuilderFn = decltype(&TypeBuilder<i32>);
 }  // namespace
 
+// Note: Just parameterizing abstract int vs float doesn't significantly reduce
+// the code size of these tests, and made the code less readable.
+//
+// Combining them with the non-abstract parameterizing helps a little but adds
+// some switching logic to the fixtures since the error strings are different.
+// There is also still an issue with needing different fixtures for vec2 vs vec3
+// vs vec4, which gets even worst for matrices. There is also variance in what
+// is allowed where, so it feels like mostly a wash for code length, and makes
+// harder to read code.
+//
+// There is probably something sophisticated using builders for
+// scalar/vector/etc and testing::Combine to parameterize things, but that
+// requires handling how the type manager allows templating/dispatch.
+
+TEST_F(IR_ValidatorTest, AbstractFloat_Scalar) {
+    auto* f = b.Function("my_func", ty.void_());
+    b.Append(f->Block(), [&] {
+        b.Var("af", function, ty.AFloat());
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
+    %af:ptr<function, abstract-float, read_write> = var
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+)")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, AbstractInt_Scalar) {
+    auto* f = b.Function("my_func", ty.void_());
+    b.Append(f->Block(), [&] {
+        b.Var("ai", function, ty.AInt());
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
+    %ai:ptr<function, abstract-int, read_write> = var
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+)")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, AbstractFloat_Vector) {
+    auto* f = b.Function("my_func", ty.void_());
+    b.Append(f->Block(), [&] {
+        b.Var("af", function, ty.vec2<AFloat>());
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
+    %af:ptr<function, vec2<abstract-float>, read_write> = var
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+)")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, AbstractInt_Vector) {
+    auto* f = b.Function("my_func", ty.void_());
+    b.Append(f->Block(), [&] {
+        b.Var("ai", function, ty.vec3<AInt>());
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(3:5 error: var: abstracts are not permitted
+    %ai:ptr<function, vec3<abstract-int>, read_write> = var
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+)")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, AbstractFloat_Matrix) {
+    auto* f = b.Function("my_func", ty.void_());
+    b.Append(f->Block(), [&] {
+        b.Var("af", function, ty.mat2x2<AFloat>());
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
+    %af:ptr<function, mat2x2<abstract-float>, read_write> = var
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+)")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, AbstractInt_Matrix) {
+    auto* f = b.Function("my_func", ty.void_());
+    b.Append(f->Block(), [&] {
+        b.Var("ai", function, ty.mat3x4<AInt>());
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
+    %ai:ptr<function, mat3x4<abstract-int>, read_write> = var
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+)")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, AbstractFloat_Struct) {
+    auto* str_ty =
+        ty.Struct(mod.symbols.New("MyStruct"), {
+                                                   {mod.symbols.New("af"), ty.AFloat(), {}},
+                                               });
+    auto* v = b.Var(ty.ptr(private_, str_ty));
+    mod.root_block->Append(v);
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:6:3 error: var: abstracts are not permitted
+  %1:ptr<private, MyStruct, read_write> = var
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+)")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, AbstractInt_Struct) {
+    auto* str_ty =
+        ty.Struct(mod.symbols.New("MyStruct"), {
+                                                   {mod.symbols.New("ai"), ty.AInt(), {}},
+                                               });
+    auto* v = b.Var(ty.ptr(private_, str_ty));
+    mod.root_block->Append(v);
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:6:3 error: var: abstracts are not permitted
+  %1:ptr<private, MyStruct, read_write> = var
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+)")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, AbstractFloat_FunctionParam) {
+    auto* f = b.Function("my_func", ty.void_());
+
+    f->SetParams({b.FunctionParam(ty.AFloat())});
+    f->Block()->Append(b.Return(f));
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:1:17 error: abstracts are not permitted
+%my_func = func(%2:abstract-float):void {
+                ^^^^^^^^^^^^^^^^^
+)")) << res.Failure().reason.Str();
+}
+
+TEST_F(IR_ValidatorTest, AbstractInt_FunctionParam) {
+    auto* f = b.Function("my_func", ty.void_());
+
+    f->SetParams({b.FunctionParam(ty.AInt())});
+    f->Block()->Append(b.Return(f));
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason.Str(),
+                testing::HasSubstr(R"(:1:17 error: abstracts are not permitted
+%my_func = func(%2:abstract-int):void {
+                ^^^^^^^^^^^^^^^
+)")) << res.Failure().reason.Str();
+}
+
+using TypeTest = IRTestParamHelper<std::tuple<
+    /* allowed */ bool,
+    /* type_builder */ TypeBuilderFn>>;
+
+using Type_VectorElements = TypeTest;
+
+TEST_P(Type_VectorElements, Test) {
+    bool allowed = std::get<0>(GetParam());
+    auto* type = std::get<1>(GetParam())(ty);
+    if (allowed) {
+        auto* f = b.Function("my_func", ty.void_());
+        b.Append(f->Block(), [&] {
+            b.Var("v2", AddressSpace::kFunction, ty.vec2(type));
+            b.Var("v3", AddressSpace::kFunction, ty.vec3(type));
+            b.Var("v4", AddressSpace::kFunction, ty.vec4(type));
+            b.Return(f);
+        });
+
+        auto res = ir::Validate(mod);
+        ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+    } else {
+        auto* f = b.Function("my_func", ty.void_());
+        b.Append(f->Block(), [&] {
+            b.Var("invalid", AddressSpace::kFunction, ty.vec2(type));
+            b.Return(f);
+        });
+
+        auto res = ir::Validate(mod);
+        ASSERT_NE(res, Success) << res.Failure().reason.Str();
+        EXPECT_THAT(res.Failure().reason.Str(),
+                    testing::HasSubstr(R"(:3:5 error: var: vector elements, ')" +
+                                       ty.vec2(type)->FriendlyName() + R"(', must be scalars
+ )")) << res.Failure().reason.Str();
+    }
+}
+
+INSTANTIATE_TEST_SUITE_P(IR_ValidatorTest,
+                         Type_VectorElements,
+                         testing::Values(std::make_tuple(true, TypeBuilder<u32>),
+                                         std::make_tuple(true, TypeBuilder<i32>),
+                                         std::make_tuple(true, TypeBuilder<f32>),
+                                         std::make_tuple(true, TypeBuilder<f16>),
+                                         std::make_tuple(true, TypeBuilder<core::type::Bool>),
+                                         std::make_tuple(false, TypeBuilder<core::type::Void>)));
+
+using Type_MatrixElements = TypeTest;
+
+TEST_P(Type_MatrixElements, Test) {
+    bool allowed = std::get<0>(GetParam());
+    auto* type = std::get<1>(GetParam())(ty);
+    if (allowed) {
+        auto* f = b.Function("my_func", ty.void_());
+        b.Append(f->Block(), [&] {
+            b.Var("m2x2", AddressSpace::kFunction, ty.mat2x2(type));
+            b.Var("m2x3", AddressSpace::kFunction, ty.mat2x3(type));
+            b.Var("m2x4", AddressSpace::kFunction, ty.mat2x4(type));
+            b.Var("m3x2", AddressSpace::kFunction, ty.mat3x2(type));
+            b.Var("m3x3", AddressSpace::kFunction, ty.mat3x3(type));
+            b.Var("m3x4", AddressSpace::kFunction, ty.mat3x4(type));
+            b.Var("m4x2", AddressSpace::kFunction, ty.mat4x2(type));
+            b.Var("m4x3", AddressSpace::kFunction, ty.mat4x3(type));
+            b.Var("m4x4", AddressSpace::kFunction, ty.mat4x4(type));
+            b.Return(f);
+        });
+
+        auto res = ir::Validate(mod);
+        ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+    } else {
+        auto* f = b.Function("my_func", ty.void_());
+        b.Append(f->Block(), [&] {
+            b.Var("invalid", AddressSpace::kFunction, ty.mat3x3(type));
+            b.Return(f);
+        });
+
+        auto res = ir::Validate(mod);
+        ASSERT_NE(res, Success) << res.Failure().reason.Str();
+        EXPECT_THAT(res.Failure().reason.Str(),
+                    testing::HasSubstr(R"(:3:5 error: var: matrix elements, ')" +
+                                       ty.mat3x3(type)->FriendlyName() + R"(', must be float scalars
+ )")) << res.Failure().reason.Str();
+    }
+}
+
+INSTANTIATE_TEST_SUITE_P(IR_ValidatorTest,
+                         Type_MatrixElements,
+                         testing::Values(std::make_tuple(false, TypeBuilder<u32>),
+                                         std::make_tuple(false, TypeBuilder<i32>),
+                                         std::make_tuple(true, TypeBuilder<f32>),
+                                         std::make_tuple(true, TypeBuilder<f16>),
+                                         std::make_tuple(false, TypeBuilder<core::type::Bool>),
+                                         std::make_tuple(false, TypeBuilder<core::type::Void>)));
+
+using Type_StorageTextureDimension = IRTestParamHelper<std::tuple<
+    /* allowed */ bool,
+    /* dim */ core::type::TextureDimension>>;
+
+TEST_P(Type_StorageTextureDimension, Test) {
+    bool allowed = std::get<0>(GetParam());
+    auto dim = std::get<1>(GetParam());
+
+    auto* v =
+        b.Var("v", AddressSpace::kStorage,
+              ty.storage_texture(dim, core::TexelFormat::kRgba32Float, core::Access::kReadWrite),
+              read_write);
+    v->SetBindingPoint(0, 0);
+    mod.root_block->Append(v);
+
+    if (allowed) {
+        auto res = ir::Validate(mod);
+        ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+    } else {
+        auto res = ir::Validate(mod);
+        ASSERT_NE(res, Success) << res.Failure().reason.Str();
+        EXPECT_THAT(res.Failure().reason.Str(),
+                    testing::HasSubstr(
+                        dim != type::TextureDimension::kNone
+                            ? R"(:2:3 error: var: dimension ')" + std::string(ToString(dim)) +
+                                  R"(' for storage textures does not in WGSL yet)"
+                            : R"(:2:3 error: var: invalid texture dimension 'none')"))
+            << res.Failure().reason.Str();
+    }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    IR_ValidatorTest,
+    Type_StorageTextureDimension,
+    testing::Values(std::make_tuple(true, core::type::TextureDimension::k2d),
+                    std::make_tuple(false, core::type::TextureDimension::kCube),
+                    std::make_tuple(false, core::type::TextureDimension::kCubeArray),
+                    std::make_tuple(false, core::type::TextureDimension::kNone)));
+
 using IR_ValidatorRefTypeTest = IRTestParamHelper<std::tuple</* holds_ref */ bool,
                                                              /* refs_allowed */ bool,
                                                              /* type_builder */ TypeBuilderFn>>;