Resolver: Split tests into multiple files

Try and make sense of the huge number of tests we have.

Rename tests so they have a consistent naming style.

Change-Id: I0c089d5945778a8718480a1a2f854435e7b0e79a
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/44162
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 7a3a7ca..1ac5dac 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -842,9 +842,11 @@
     "src/intrinsic_table_test.cc",
     "src/program_builder_test.cc",
     "src/program_test.cc",
-    "src/resolver/resolver_test.cc",
+    "src/resolver/intrinsic_test.cc",
     "src/resolver/resolver_test_helper.cc",
     "src/resolver/resolver_test_helper.h",
+    "src/resolver/resolver_test.cc",
+    "src/resolver/validation_test.cc",
     "src/semantic/sem_intrinsic_test.cc",
     "src/scope_stack_test.cc",
     "src/symbol_table_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 684a9c4..d3e94d4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -470,11 +470,13 @@
     inspector/inspector_test.cc
     intrinsic_table_test.cc
     program_test.cc
-    resolver/resolver_test.cc
+    resolver/intrinsic_test.cc
     resolver/resolver_test_helper.cc
     resolver/resolver_test_helper.h
-    semantic/sem_intrinsic_test.cc
+    resolver/resolver_test.cc
+    resolver/validation_test.cc
     scope_stack_test.cc
+    semantic/sem_intrinsic_test.cc
     symbol_table_test.cc
     symbol_test.cc
     traits_test.cc
diff --git a/src/resolver/intrinsic_test.cc b/src/resolver/intrinsic_test.cc
new file mode 100644
index 0000000..b266b00
--- /dev/null
+++ b/src/resolver/intrinsic_test.cc
@@ -0,0 +1,1961 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/ast/assignment_statement.h"
+#include "src/ast/bitcast_expression.h"
+#include "src/ast/break_statement.h"
+#include "src/ast/call_statement.h"
+#include "src/ast/continue_statement.h"
+#include "src/ast/if_statement.h"
+#include "src/ast/intrinsic_texture_helper_test.h"
+#include "src/ast/loop_statement.h"
+#include "src/ast/return_statement.h"
+#include "src/ast/stage_decoration.h"
+#include "src/ast/switch_statement.h"
+#include "src/ast/unary_op_expression.h"
+#include "src/ast/variable_decl_statement.h"
+#include "src/resolver/resolver_test_helper.h"
+#include "src/semantic/call.h"
+#include "src/semantic/function.h"
+#include "src/semantic/member_accessor_expression.h"
+#include "src/semantic/statement.h"
+#include "src/semantic/variable.h"
+#include "src/type/access_control_type.h"
+#include "src/type/sampled_texture_type.h"
+
+using ::testing::ElementsAre;
+using ::testing::HasSubstr;
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using IntrinsicType = semantic::IntrinsicType;
+
+using ResolverIntrinsicTest = ResolverTest;
+
+using ResolverIntrinsicDerivativeTest = ResolverTestWithParam<std::string>;
+TEST_P(ResolverIntrinsicDerivativeTest, Scalar) {
+  auto name = GetParam();
+
+  Global("ident", ty.f32(), ast::StorageClass::kNone);
+
+  auto* expr = Call(name, "ident");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<type::F32>());
+}
+
+TEST_P(ResolverIntrinsicDerivativeTest, Vector) {
+  auto name = GetParam();
+  Global("ident", ty.vec4<f32>(), ast::StorageClass::kNone);
+
+  auto* expr = Call(name, "ident");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::F32>());
+  EXPECT_EQ(TypeOf(expr)->As<type::Vector>()->size(), 4u);
+}
+
+TEST_P(ResolverIntrinsicDerivativeTest, MissingParam) {
+  auto name = GetParam();
+
+  auto* expr = Call(name);
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "error: no matching call to " + name +
+                              "()\n\n"
+                              "2 candidate functions:\n  " +
+                              name + "(f32) -> f32\n  " + name +
+                              "(vecN<f32>) -> vecN<f32>\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         ResolverIntrinsicDerivativeTest,
+                         testing::Values("dpdx",
+                                         "dpdxCoarse",
+                                         "dpdxFine",
+                                         "dpdy",
+                                         "dpdyCoarse",
+                                         "dpdyFine",
+                                         "fwidth",
+                                         "fwidthCoarse",
+                                         "fwidthFine"));
+
+using ResolverIntrinsic = ResolverTestWithParam<std::string>;
+TEST_P(ResolverIntrinsic, Test) {
+  auto name = GetParam();
+
+  Global("my_var", ty.vec3<bool>(), ast::StorageClass::kNone);
+
+  auto* expr = Call(name, "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<type::Bool>());
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         ResolverIntrinsic,
+                         testing::Values("any", "all"));
+
+using ResolverIntrinsicTest_FloatMethod = ResolverTestWithParam<std::string>;
+TEST_P(ResolverIntrinsicTest_FloatMethod, Vector) {
+  auto name = GetParam();
+
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kNone);
+
+  auto* expr = Call(name, "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::Bool>());
+  EXPECT_EQ(TypeOf(expr)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_P(ResolverIntrinsicTest_FloatMethod, Scalar) {
+  auto name = GetParam();
+
+  Global("my_var", ty.f32(), ast::StorageClass::kNone);
+
+  auto* expr = Call(name, "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<type::Bool>());
+}
+
+TEST_P(ResolverIntrinsicTest_FloatMethod, MissingParam) {
+  auto name = GetParam();
+
+  Global("my_var", ty.f32(), ast::StorageClass::kNone);
+
+  auto* expr = Call(name);
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "error: no matching call to " + name +
+                              "()\n\n"
+                              "2 candidate functions:\n  " +
+                              name + "(f32) -> bool\n  " + name +
+                              "(vecN<f32>) -> vecN<bool>\n");
+}
+
+TEST_P(ResolverIntrinsicTest_FloatMethod, TooManyParams) {
+  auto name = GetParam();
+
+  Global("my_var", ty.f32(), ast::StorageClass::kNone);
+
+  auto* expr = Call(name, "my_var", 1.23f);
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "error: no matching call to " + name +
+                              "(ptr<f32>, f32)\n\n"
+                              "2 candidate functions:\n  " +
+                              name + "(f32) -> bool\n  " + name +
+                              "(vecN<f32>) -> vecN<bool>\n");
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverIntrinsicTest_FloatMethod,
+    testing::Values("isInf", "isNan", "isFinite", "isNormal"));
+
+enum class Texture { kF32, kI32, kU32 };
+inline std::ostream& operator<<(std::ostream& out, Texture data) {
+  if (data == Texture::kF32) {
+    out << "f32";
+  } else if (data == Texture::kI32) {
+    out << "i32";
+  } else {
+    out << "u32";
+  }
+  return out;
+}
+
+struct TextureTestParams {
+  type::TextureDimension dim;
+  Texture type = Texture::kF32;
+  type::ImageFormat format = type::ImageFormat::kR16Float;
+};
+inline std::ostream& operator<<(std::ostream& out, TextureTestParams data) {
+  out << data.dim << "_" << data.type;
+  return out;
+}
+
+class ResolverIntrinsicTest_TextureOperation
+    : public ResolverTestWithParam<TextureTestParams> {
+ public:
+  /// Gets an appropriate type for the coords parameter depending the the
+  /// dimensionality of the texture being sampled.
+  /// @param dim dimensionality of the texture being sampled
+  /// @param scalar the scalar type
+  /// @returns a pointer to a type appropriate for the coord param
+  type::Type* GetCoordsType(type::TextureDimension dim, type::Type* scalar) {
+    switch (dim) {
+      case type::TextureDimension::k1d:
+        return scalar;
+      case type::TextureDimension::k2d:
+      case type::TextureDimension::k2dArray:
+        return create<type::Vector>(scalar, 2);
+      case type::TextureDimension::k3d:
+      case type::TextureDimension::kCube:
+      case type::TextureDimension::kCubeArray:
+        return create<type::Vector>(scalar, 3);
+      default:
+        [=]() { FAIL() << "Unsupported texture dimension: " << dim; }();
+    }
+    return nullptr;
+  }
+
+  void add_call_param(std::string name,
+                      type::Type* type,
+                      ast::ExpressionList* call_params) {
+    Global(name, type, ast::StorageClass::kNone);
+    call_params->push_back(Expr(name));
+  }
+  type::Type* subtype(Texture type) {
+    if (type == Texture::kF32) {
+      return create<type::F32>();
+    }
+    if (type == Texture::kI32) {
+      return create<type::I32>();
+    }
+    return create<type::U32>();
+  }
+};
+
+using ResolverIntrinsicTest_StorageTextureOperation =
+    ResolverIntrinsicTest_TextureOperation;
+TEST_P(ResolverIntrinsicTest_StorageTextureOperation, TextureLoadRo) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+  auto format = GetParam().format;
+
+  auto* coords_type = GetCoordsType(dim, ty.i32());
+
+  auto* subtype = type::StorageTexture::SubtypeFor(format, Types());
+  auto* texture_type = create<type::StorageTexture>(dim, format, subtype);
+  auto* ro_texture_type =
+      create<type::AccessControl>(ast::AccessControl::kReadOnly, texture_type);
+
+  ast::ExpressionList call_params;
+
+  add_call_param("texture", ro_texture_type, &call_params);
+  add_call_param("coords", coords_type, &call_params);
+
+  if (type::IsTextureArray(dim)) {
+    add_call_param("array_index", ty.i32(), &call_params);
+  }
+
+  auto* expr = Call("textureLoad", call_params);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<type::Vector>());
+  if (type == Texture::kF32) {
+    EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::F32>());
+  } else if (type == Texture::kI32) {
+    EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::I32>());
+  } else {
+    EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::U32>());
+  }
+  EXPECT_EQ(TypeOf(expr)->As<type::Vector>()->size(), 4u);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverIntrinsicTest_StorageTextureOperation,
+    testing::Values(
+        TextureTestParams{type::TextureDimension::k1d, Texture::kF32,
+                          type::ImageFormat::kR16Float},
+        TextureTestParams{type::TextureDimension::k1d, Texture::kI32,
+                          type::ImageFormat::kR16Sint},
+        TextureTestParams{type::TextureDimension::k1d, Texture::kF32,
+                          type::ImageFormat::kR8Unorm},
+        TextureTestParams{type::TextureDimension::k2d, Texture::kF32,
+                          type::ImageFormat::kR16Float},
+        TextureTestParams{type::TextureDimension::k2d, Texture::kI32,
+                          type::ImageFormat::kR16Sint},
+        TextureTestParams{type::TextureDimension::k2d, Texture::kF32,
+                          type::ImageFormat::kR8Unorm},
+        TextureTestParams{type::TextureDimension::k2dArray, Texture::kF32,
+                          type::ImageFormat::kR16Float},
+        TextureTestParams{type::TextureDimension::k2dArray, Texture::kI32,
+                          type::ImageFormat::kR16Sint},
+        TextureTestParams{type::TextureDimension::k2dArray, Texture::kF32,
+                          type::ImageFormat::kR8Unorm},
+        TextureTestParams{type::TextureDimension::k3d, Texture::kF32,
+                          type::ImageFormat::kR16Float},
+        TextureTestParams{type::TextureDimension::k3d, Texture::kI32,
+                          type::ImageFormat::kR16Sint},
+        TextureTestParams{type::TextureDimension::k3d, Texture::kF32,
+                          type::ImageFormat::kR8Unorm}));
+
+using ResolverIntrinsicTest_SampledTextureOperation =
+    ResolverIntrinsicTest_TextureOperation;
+TEST_P(ResolverIntrinsicTest_SampledTextureOperation, TextureLoadSampled) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+
+  type::Type* s = subtype(type);
+  auto* coords_type = GetCoordsType(dim, ty.i32());
+  auto* texture_type = create<type::SampledTexture>(dim, s);
+
+  ast::ExpressionList call_params;
+
+  add_call_param("texture", texture_type, &call_params);
+  add_call_param("coords", coords_type, &call_params);
+  if (dim == type::TextureDimension::k2dArray) {
+    add_call_param("array_index", ty.i32(), &call_params);
+  }
+  add_call_param("level", ty.i32(), &call_params);
+
+  auto* expr = Call("textureLoad", call_params);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  ASSERT_TRUE(TypeOf(expr)->Is<type::Vector>());
+  if (type == Texture::kF32) {
+    EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::F32>());
+  } else if (type == Texture::kI32) {
+    EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::I32>());
+  } else {
+    EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::U32>());
+  }
+  EXPECT_EQ(TypeOf(expr)->As<type::Vector>()->size(), 4u);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverIntrinsicTest_SampledTextureOperation,
+    testing::Values(TextureTestParams{type::TextureDimension::k1d},
+                    TextureTestParams{type::TextureDimension::k2d},
+                    TextureTestParams{type::TextureDimension::k2dArray},
+                    TextureTestParams{type::TextureDimension::k3d}));
+
+TEST_F(ResolverIntrinsicTest, Dot_Vec2) {
+  Global("my_var", ty.vec2<f32>(), ast::StorageClass::kNone);
+
+  auto* expr = Call("dot", "my_var", "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<type::F32>());
+}
+
+TEST_F(ResolverIntrinsicTest, Dot_Vec3) {
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kNone);
+
+  auto* expr = Call("dot", "my_var", "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<type::F32>());
+}
+
+TEST_F(ResolverIntrinsicTest, Dot_Vec4) {
+  Global("my_var", ty.vec4<f32>(), ast::StorageClass::kNone);
+
+  auto* expr = Call("dot", "my_var", "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<type::F32>());
+}
+
+TEST_F(ResolverIntrinsicTest, Dot_Error_Scalar) {
+  auto* expr = Call("dot", 1.0f, 1.0f);
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to dot(f32, f32)
+
+1 candidate function:
+  dot(vecN<f32>, vecN<f32>) -> f32
+)");
+}
+
+TEST_F(ResolverIntrinsicTest, Dot_Error_VectorInt) {
+  Global("my_var", ty.vec4<i32>(), ast::StorageClass::kNone);
+
+  auto* expr = Call("dot", "my_var", "my_var");
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to dot(ptr<vec4<i32>>, ptr<vec4<i32>>)
+
+1 candidate function:
+  dot(vecN<f32>, vecN<f32>) -> f32
+)");
+}
+
+TEST_F(ResolverIntrinsicTest, Select) {
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kNone);
+
+  Global("bool_var", ty.vec3<bool>(), ast::StorageClass::kNone);
+
+  auto* expr = Call("select", "my_var", "my_var", "bool_var");
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(expr), nullptr);
+  EXPECT_TRUE(TypeOf(expr)->Is<type::Vector>());
+  EXPECT_EQ(TypeOf(expr)->As<type::Vector>()->size(), 3u);
+  EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::F32>());
+}
+
+TEST_F(ResolverIntrinsicTest, Select_Error_NoParams) {
+  auto* expr = Call("select");
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to select()
+
+2 candidate functions:
+  select(T, T, bool) -> T  where: T is scalar
+  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is scalar
+)");
+}
+
+TEST_F(ResolverIntrinsicTest, Select_Error_SelectorInt) {
+  auto* expr = Call("select", Expr(1), Expr(1), Expr(1));
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to select(i32, i32, i32)
+
+2 candidate functions:
+  select(T, T, bool) -> T  where: T is scalar
+  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is scalar
+)");
+}
+
+TEST_F(ResolverIntrinsicTest, Select_Error_Matrix) {
+  auto* mat = mat2x2<float>(vec2<float>(1.0f, 1.0f), vec2<float>(1.0f, 1.0f));
+  auto* expr = Call("select", mat, mat, Expr(true));
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to select(mat2x2<f32>, mat2x2<f32>, bool)
+
+2 candidate functions:
+  select(T, T, bool) -> T  where: T is scalar
+  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is scalar
+)");
+}
+
+TEST_F(ResolverIntrinsicTest, Select_Error_MismatchTypes) {
+  auto* expr = Call("select", 1.0f, vec2<float>(2.0f, 3.0f), Expr(true));
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to select(f32, vec2<f32>, bool)
+
+2 candidate functions:
+  select(T, T, bool) -> T  where: T is scalar
+  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is scalar
+)");
+}
+
+TEST_F(ResolverIntrinsicTest, Select_Error_MismatchVectorSize) {
+  auto* expr = Call("select", vec2<float>(1.0f, 2.0f),
+                    vec3<float>(3.0f, 4.0f, 5.0f), Expr(true));
+  WrapInFunction(expr);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to select(vec2<f32>, vec3<f32>, bool)
+
+2 candidate functions:
+  select(T, T, bool) -> T  where: T is scalar
+  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is scalar
+)");
+}
+
+struct IntrinsicData {
+  const char* name;
+  IntrinsicType intrinsic;
+};
+
+inline std::ostream& operator<<(std::ostream& out, IntrinsicData data) {
+  out << data.name;
+  return out;
+}
+
+using ResolverIntrinsicTest_DataPacking = ResolverTestWithParam<IntrinsicData>;
+TEST_P(ResolverIntrinsicTest_DataPacking, InferType) {
+  auto param = GetParam();
+
+  bool pack4 = param.intrinsic == IntrinsicType::kPack4x8Snorm ||
+               param.intrinsic == IntrinsicType::kPack4x8Unorm;
+
+  auto* call = pack4 ? Call(param.name, vec4<f32>(1.f, 2.f, 3.f, 4.f))
+                     : Call(param.name, vec2<f32>(1.f, 2.f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::U32>());
+}
+
+TEST_P(ResolverIntrinsicTest_DataPacking, Error_IncorrectParamType) {
+  auto param = GetParam();
+
+  bool pack4 = param.intrinsic == IntrinsicType::kPack4x8Snorm ||
+               param.intrinsic == IntrinsicType::kPack4x8Unorm;
+
+  auto* call = pack4 ? Call(param.name, vec4<i32>(1, 2, 3, 4))
+                     : Call(param.name, vec2<i32>(1, 2));
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
+                                      std::string(param.name)));
+}
+
+TEST_P(ResolverIntrinsicTest_DataPacking, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
+                                      std::string(param.name)));
+}
+
+TEST_P(ResolverIntrinsicTest_DataPacking, Error_TooManyParams) {
+  auto param = GetParam();
+
+  bool pack4 = param.intrinsic == IntrinsicType::kPack4x8Snorm ||
+               param.intrinsic == IntrinsicType::kPack4x8Unorm;
+
+  auto* call = pack4 ? Call(param.name, vec4<f32>(1.f, 2.f, 3.f, 4.f), 1.0f)
+                     : Call(param.name, vec2<f32>(1.f, 2.f), 1.0f);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
+                                      std::string(param.name)));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverIntrinsicTest_DataPacking,
+    testing::Values(
+        IntrinsicData{"pack4x8snorm", IntrinsicType::kPack4x8Snorm},
+        IntrinsicData{"pack4x8unorm", IntrinsicType::kPack4x8Unorm},
+        IntrinsicData{"pack2x16snorm", IntrinsicType::kPack2x16Snorm},
+        IntrinsicData{"pack2x16unorm", IntrinsicType::kPack2x16Unorm},
+        IntrinsicData{"pack2x16float", IntrinsicType::kPack2x16Float}));
+
+using ResolverIntrinsicTest_DataUnpacking =
+    ResolverTestWithParam<IntrinsicData>;
+TEST_P(ResolverIntrinsicTest_DataUnpacking, InferType) {
+  auto param = GetParam();
+
+  bool pack4 = param.intrinsic == IntrinsicType::kUnpack4x8Snorm ||
+               param.intrinsic == IntrinsicType::kUnpack4x8Unorm;
+
+  auto* call = Call(param.name, 1u);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  if (pack4) {
+    EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 4u);
+  } else {
+    EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 2u);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverIntrinsicTest_DataUnpacking,
+    testing::Values(
+        IntrinsicData{"unpack4x8snorm", IntrinsicType::kUnpack4x8Snorm},
+        IntrinsicData{"unpack4x8unorm", IntrinsicType::kUnpack4x8Unorm},
+        IntrinsicData{"unpack2x16snorm", IntrinsicType::kUnpack2x16Snorm},
+        IntrinsicData{"unpack2x16unorm", IntrinsicType::kUnpack2x16Unorm},
+        IntrinsicData{"unpack2x16float", IntrinsicType::kUnpack2x16Float}));
+
+using ResolverIntrinsicTest_SingleParam = ResolverTestWithParam<IntrinsicData>;
+TEST_P(ResolverIntrinsicTest_SingleParam, Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_P(ResolverIntrinsicTest_SingleParam, Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_P(ResolverIntrinsicTest_SingleParam, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "()\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) + "(f32) -> f32\n  " +
+                std::string(param.name) + "(vecN<f32>) -> vecN<f32>\n");
+}
+
+TEST_P(ResolverIntrinsicTest_SingleParam, Error_TooManyParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1, 2, 3);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "(i32, i32, i32)\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) + "(f32) -> f32\n  " +
+                std::string(param.name) + "(vecN<f32>) -> vecN<f32>\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverIntrinsicTest_SingleParam,
+    testing::Values(IntrinsicData{"acos", IntrinsicType::kAcos},
+                    IntrinsicData{"asin", IntrinsicType::kAsin},
+                    IntrinsicData{"atan", IntrinsicType::kAtan},
+                    IntrinsicData{"ceil", IntrinsicType::kCeil},
+                    IntrinsicData{"cos", IntrinsicType::kCos},
+                    IntrinsicData{"cosh", IntrinsicType::kCosh},
+                    IntrinsicData{"exp", IntrinsicType::kExp},
+                    IntrinsicData{"exp2", IntrinsicType::kExp2},
+                    IntrinsicData{"floor", IntrinsicType::kFloor},
+                    IntrinsicData{"fract", IntrinsicType::kFract},
+                    IntrinsicData{"inverseSqrt", IntrinsicType::kInverseSqrt},
+                    IntrinsicData{"log", IntrinsicType::kLog},
+                    IntrinsicData{"log2", IntrinsicType::kLog2},
+                    IntrinsicData{"round", IntrinsicType::kRound},
+                    IntrinsicData{"sign", IntrinsicType::kSign},
+                    IntrinsicData{"sin", IntrinsicType::kSin},
+                    IntrinsicData{"sinh", IntrinsicType::kSinh},
+                    IntrinsicData{"sqrt", IntrinsicType::kSqrt},
+                    IntrinsicData{"tan", IntrinsicType::kTan},
+                    IntrinsicData{"tanh", IntrinsicType::kTanh},
+                    IntrinsicData{"trunc", IntrinsicType::kTrunc}));
+
+using ResolverIntrinsicDataTest = ResolverTest;
+
+TEST_F(ResolverIntrinsicDataTest, ArrayLength_Vector) {
+  Global("arr", ty.array<int>(), ast::StorageClass::kNone);
+  auto* call = Call("arrayLength", "arr");
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::U32>());
+}
+
+TEST_F(ResolverIntrinsicDataTest, ArrayLength_Error_ArraySized) {
+  Global("arr", ty.array<int, 4>(), ast::StorageClass::kNone);
+  auto* call = Call("arrayLength", "arr");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to arrayLength(ptr<array<i32, 4>>)\n\n"
+            "1 candidate function:\n"
+            "  arrayLength(array<T>) -> u32\n");
+}
+
+TEST_F(ResolverIntrinsicDataTest, Normalize_Vector) {
+  auto* call = Call("normalize", vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_F(ResolverIntrinsicDataTest, Normalize_Error_NoParams) {
+  auto* call = Call("normalize");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to normalize()\n\n"
+            "1 candidate function:\n"
+            "  normalize(vecN<f32>) -> vecN<f32>\n");
+}
+
+TEST_F(ResolverIntrinsicDataTest, FrexpScalar) {
+  Global("exp", ty.i32(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("frexp", 1.0f, "exp");
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
+}
+
+TEST_F(ResolverIntrinsicDataTest, FrexpVector) {
+  Global("exp", ty.vec3<i32>(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("frexp", vec3<f32>(1.0f, 2.0f, 3.0f), "exp");
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(call)->As<type::Vector>()->type()->Is<type::F32>());
+}
+
+TEST_F(ResolverIntrinsicDataTest, Frexp_Error_FirstParamInt) {
+  Global("exp", ty.i32(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("frexp", 1, "exp");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to frexp(i32, ptr<workgroup, i32>)\n\n"
+            "2 candidate functions:\n"
+            "  frexp(f32, ptr<T>) -> f32  where: T is i32 or u32\n"
+            "  frexp(vecN<f32>, ptr<vecN<T>>) -> vecN<f32>  "
+            "where: T is i32 or u32\n");
+}
+
+TEST_F(ResolverIntrinsicDataTest, Frexp_Error_SecondParamFloatPtr) {
+  Global("exp", ty.f32(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("frexp", 1.0f, "exp");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to frexp(f32, ptr<workgroup, f32>)\n\n"
+            "2 candidate functions:\n"
+            "  frexp(f32, ptr<T>) -> f32  where: T is i32 or u32\n"
+            "  frexp(vecN<f32>, ptr<vecN<T>>) -> vecN<f32>  "
+            "where: T is i32 or u32\n");
+}
+
+TEST_F(ResolverIntrinsicDataTest, Frexp_Error_SecondParamNotAPointer) {
+  auto* call = Call("frexp", 1.0f, 1);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to frexp(f32, i32)\n\n"
+            "2 candidate functions:\n"
+            "  frexp(f32, ptr<T>) -> f32  where: T is i32 or u32\n"
+            "  frexp(vecN<f32>, ptr<vecN<T>>) -> vecN<f32>  "
+            "where: T is i32 or u32\n");
+}
+
+TEST_F(ResolverIntrinsicDataTest, Frexp_Error_VectorSizesDontMatch) {
+  Global("exp", ty.vec4<i32>(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("frexp", vec2<f32>(1.0f, 2.0f), "exp");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to frexp(vec2<f32>, ptr<workgroup, "
+            "vec4<i32>>)\n\n"
+            "2 candidate functions:\n"
+            "  frexp(f32, ptr<T>) -> f32  where: T is i32 or u32\n"
+            "  frexp(vecN<f32>, ptr<vecN<T>>) -> vecN<f32>  "
+            "where: T is i32 or u32\n");
+}
+
+TEST_F(ResolverIntrinsicDataTest, ModfScalar) {
+  Global("whole", ty.f32(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("modf", 1.0f, "whole");
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
+}
+
+TEST_F(ResolverIntrinsicDataTest, ModfVector) {
+  Global("whole", ty.vec3<f32>(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("modf", vec3<f32>(1.0f, 2.0f, 3.0f), "whole");
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::Vector>());
+  EXPECT_TRUE(TypeOf(call)->As<type::Vector>()->type()->Is<type::F32>());
+}
+
+TEST_F(ResolverIntrinsicDataTest, Modf_Error_FirstParamInt) {
+  Global("whole", ty.f32(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("modf", 1, "whole");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to modf(i32, ptr<workgroup, f32>)\n\n"
+            "2 candidate functions:\n"
+            "  modf(f32, ptr<f32>) -> f32\n"
+            "  modf(vecN<f32>, ptr<vecN<f32>>) -> vecN<f32>\n");
+}
+
+TEST_F(ResolverIntrinsicDataTest, Modf_Error_SecondParamIntPtr) {
+  Global("whole", ty.i32(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("modf", 1.0f, "whole");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to modf(f32, ptr<workgroup, i32>)\n\n"
+            "2 candidate functions:\n"
+            "  modf(f32, ptr<f32>) -> f32\n"
+            "  modf(vecN<f32>, ptr<vecN<f32>>) -> vecN<f32>\n");
+}
+
+TEST_F(ResolverIntrinsicDataTest, Modf_Error_SecondParamNotAPointer) {
+  auto* call = Call("modf", 1.0f, 1.0f);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to modf(f32, f32)\n\n"
+            "2 candidate functions:\n"
+            "  modf(f32, ptr<f32>) -> f32\n"
+            "  modf(vecN<f32>, ptr<vecN<f32>>) -> vecN<f32>\n");
+}
+
+TEST_F(ResolverIntrinsicDataTest, Modf_Error_VectorSizesDontMatch) {
+  Global("whole", ty.vec4<f32>(), ast::StorageClass::kWorkgroup);
+  auto* call = Call("modf", vec2<f32>(1.0f, 2.0f), "whole");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to modf(vec2<f32>, ptr<workgroup, "
+            "vec4<f32>>)\n\n"
+            "2 candidate functions:\n"
+            "  modf(vecN<f32>, ptr<vecN<f32>>) -> vecN<f32>\n"
+            "  modf(f32, ptr<f32>) -> f32\n");
+}
+
+using ResolverIntrinsicTest_SingleParam_FloatOrInt =
+    ResolverTestWithParam<IntrinsicData>;
+TEST_P(ResolverIntrinsicTest_SingleParam_FloatOrInt, Float_Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_P(ResolverIntrinsicTest_SingleParam_FloatOrInt, Float_Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_P(ResolverIntrinsicTest_SingleParam_FloatOrInt, Sint_Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, -1);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::I32>());
+}
+
+TEST_P(ResolverIntrinsicTest_SingleParam_FloatOrInt, Sint_Vector) {
+  auto param = GetParam();
+
+  ast::ExpressionList vals;
+  vals.push_back(Expr(1));
+  vals.push_back(Expr(1));
+  vals.push_back(Expr(3));
+
+  ast::ExpressionList params;
+  params.push_back(vec3<i32>(vals));
+
+  auto* call = Call(param.name, params);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_P(ResolverIntrinsicTest_SingleParam_FloatOrInt, Uint_Scalar) {
+  auto param = GetParam();
+
+  ast::ExpressionList params;
+  params.push_back(Expr(1u));
+
+  auto* call = Call(param.name, params);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::U32>());
+}
+
+TEST_P(ResolverIntrinsicTest_SingleParam_FloatOrInt, Uint_Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<u32>(1u, 1u, 3u));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_unsigned_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_P(ResolverIntrinsicTest_SingleParam_FloatOrInt, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "()\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) +
+                "(T) -> T  where: T is f32, i32 or u32\n  " +
+                std::string(param.name) +
+                "(vecN<T>) -> vecN<T>  where: T is f32, i32 or u32\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         ResolverIntrinsicTest_SingleParam_FloatOrInt,
+                         testing::Values(IntrinsicData{"abs",
+                                                       IntrinsicType::kAbs}));
+
+TEST_F(ResolverIntrinsicTest, Length_Scalar) {
+  auto* call = Call("length", 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_F(ResolverIntrinsicTest, Length_FloatVector) {
+  ast::ExpressionList params;
+  params.push_back(vec3<f32>(1.0f, 1.0f, 3.0f));
+
+  auto* call = Call("length", params);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+using ResolverIntrinsicTest_TwoParam = ResolverTestWithParam<IntrinsicData>;
+TEST_P(ResolverIntrinsicTest_TwoParam, Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1.f, 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_P(ResolverIntrinsicTest_TwoParam, Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f),
+                    vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_P(ResolverIntrinsicTest_TwoParam, Error_NoTooManyParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1, 2, 3);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "(i32, i32, i32)\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) + "(f32, f32) -> f32\n  " +
+                std::string(param.name) +
+                "(vecN<f32>, vecN<f32>) -> vecN<f32>\n");
+}
+
+TEST_P(ResolverIntrinsicTest_TwoParam, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "()\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) + "(f32, f32) -> f32\n  " +
+                std::string(param.name) +
+                "(vecN<f32>, vecN<f32>) -> vecN<f32>\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverIntrinsicTest_TwoParam,
+    testing::Values(IntrinsicData{"atan2", IntrinsicType::kAtan2},
+                    IntrinsicData{"pow", IntrinsicType::kPow},
+                    IntrinsicData{"step", IntrinsicType::kStep},
+                    IntrinsicData{"reflect", IntrinsicType::kReflect}));
+
+TEST_F(ResolverIntrinsicTest, Distance_Scalar) {
+  auto* call = Call("distance", 1.f, 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_F(ResolverIntrinsicTest, Distance_Vector) {
+  auto* call = Call("distance", vec3<f32>(1.0f, 1.0f, 3.0f),
+                    vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
+}
+
+TEST_F(ResolverIntrinsicTest, Cross) {
+  auto* call =
+      Call("cross", vec3<f32>(1.0f, 2.0f, 3.0f), vec3<f32>(1.0f, 2.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_F(ResolverIntrinsicTest, Cross_Error_NoArgs) {
+  auto* call = Call("cross");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), R"(error: no matching call to cross()
+
+1 candidate function:
+  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
+)");
+}
+
+TEST_F(ResolverIntrinsicTest, Cross_Error_Scalar) {
+  auto* call = Call("cross", 1.0f, 1.0f);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), R"(error: no matching call to cross(f32, f32)
+
+1 candidate function:
+  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
+)");
+}
+
+TEST_F(ResolverIntrinsicTest, Cross_Error_Vec3Int) {
+  auto* call = Call("cross", vec3<i32>(1, 2, 3), vec3<i32>(1, 2, 3));
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to cross(vec3<i32>, vec3<i32>)
+
+1 candidate function:
+  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
+)");
+}
+
+TEST_F(ResolverIntrinsicTest, Cross_Error_Vec4) {
+  auto* call = Call("cross", vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f),
+                    vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f));
+
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to cross(vec4<f32>, vec4<f32>)
+
+1 candidate function:
+  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
+)");
+}
+
+TEST_F(ResolverIntrinsicTest, Cross_Error_TooManyParams) {
+  auto* call = Call("cross", vec3<f32>(1.0f, 2.0f, 3.0f),
+                    vec3<f32>(1.0f, 2.0f, 3.0f), vec3<f32>(1.0f, 2.0f, 3.0f));
+
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            R"(error: no matching call to cross(vec3<f32>, vec3<f32>, vec3<f32>)
+
+1 candidate function:
+  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
+)");
+}
+TEST_F(ResolverIntrinsicTest, Normalize) {
+  auto* call = Call("normalize", vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_F(ResolverIntrinsicTest, Normalize_NoArgs) {
+  auto* call = Call("normalize");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), R"(error: no matching call to normalize()
+
+1 candidate function:
+  normalize(vecN<f32>) -> vecN<f32>
+)");
+}
+
+using ResolverIntrinsicTest_ThreeParam = ResolverTestWithParam<IntrinsicData>;
+TEST_P(ResolverIntrinsicTest_ThreeParam, Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1.f, 1.f, 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_P(ResolverIntrinsicTest_ThreeParam, Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f),
+                    vec3<f32>(1.0f, 1.0f, 3.0f), vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+TEST_P(ResolverIntrinsicTest_ThreeParam, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "()\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) + "(f32, f32, f32) -> f32\n  " +
+                std::string(param.name) +
+                "(vecN<f32>, vecN<f32>, vecN<f32>) -> vecN<f32>\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverIntrinsicTest_ThreeParam,
+    testing::Values(IntrinsicData{"mix", IntrinsicType::kMix},
+                    IntrinsicData{"smoothStep", IntrinsicType::kSmoothStep},
+                    IntrinsicData{"fma", IntrinsicType::kFma},
+                    IntrinsicData{"faceForward", IntrinsicType::kFaceForward}));
+
+using ResolverIntrinsicTest_ThreeParam_FloatOrInt =
+    ResolverTestWithParam<IntrinsicData>;
+TEST_P(ResolverIntrinsicTest_ThreeParam_FloatOrInt, Float_Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1.f, 1.f, 1.f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
+}
+
+TEST_P(ResolverIntrinsicTest_ThreeParam_FloatOrInt, Float_Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f),
+                    vec3<f32>(1.0f, 1.0f, 3.0f), vec3<f32>(1.0f, 1.0f, 3.0f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_P(ResolverIntrinsicTest_ThreeParam_FloatOrInt, Sint_Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1, 1, 1);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::I32>());
+}
+
+TEST_P(ResolverIntrinsicTest_ThreeParam_FloatOrInt, Sint_Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<i32>(1, 1, 3), vec3<i32>(1, 1, 3),
+                    vec3<i32>(1, 1, 3));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_P(ResolverIntrinsicTest_ThreeParam_FloatOrInt, Uint_Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1u, 1u, 1u);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::U32>());
+}
+
+TEST_P(ResolverIntrinsicTest_ThreeParam_FloatOrInt, Uint_Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<u32>(1u, 1u, 3u), vec3<u32>(1u, 1u, 3u),
+                    vec3<u32>(1u, 1u, 3u));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_unsigned_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_P(ResolverIntrinsicTest_ThreeParam_FloatOrInt, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "()\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) +
+                "(T, T, T) -> T  where: T is f32, i32 or u32\n  " +
+                std::string(param.name) +
+                "(vecN<T>, vecN<T>, vecN<T>) -> vecN<T>  where: T is f32, i32 "
+                "or u32\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         ResolverIntrinsicTest_ThreeParam_FloatOrInt,
+                         testing::Values(IntrinsicData{"clamp",
+                                                       IntrinsicType::kClamp}));
+
+using ResolverIntrinsicTest_Int_SingleParam =
+    ResolverTestWithParam<IntrinsicData>;
+TEST_P(ResolverIntrinsicTest_Int_SingleParam, Scalar) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_integer_scalar());
+}
+
+TEST_P(ResolverIntrinsicTest_Int_SingleParam, Vector) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<i32>(1, 1, 3));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_P(ResolverIntrinsicTest_Int_SingleParam, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(), "error: no matching call to " +
+                              std::string(param.name) +
+                              "()\n\n"
+                              "2 candidate functions:\n  " +
+                              std::string(param.name) +
+                              "(T) -> T  where: T is i32 or u32\n  " +
+                              std::string(param.name) +
+                              "(vecN<T>) -> vecN<T>  where: T is i32 or u32\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverIntrinsicTest_Int_SingleParam,
+    testing::Values(IntrinsicData{"countOneBits", IntrinsicType::kCountOneBits},
+                    IntrinsicData{"reverseBits", IntrinsicType::kReverseBits}));
+
+using ResolverIntrinsicTest_FloatOrInt_TwoParam =
+    ResolverTestWithParam<IntrinsicData>;
+TEST_P(ResolverIntrinsicTest_FloatOrInt_TwoParam, Scalar_Signed) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1, 1);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::I32>());
+}
+
+TEST_P(ResolverIntrinsicTest_FloatOrInt_TwoParam, Scalar_Unsigned) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1u, 1u);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::U32>());
+}
+
+TEST_P(ResolverIntrinsicTest_FloatOrInt_TwoParam, Scalar_Float) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, 1.0f, 1.0f);
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
+}
+
+TEST_P(ResolverIntrinsicTest_FloatOrInt_TwoParam, Vector_Signed) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<i32>(1, 1, 3), vec3<i32>(1, 1, 3));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_P(ResolverIntrinsicTest_FloatOrInt_TwoParam, Vector_Unsigned) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name, vec3<u32>(1u, 1u, 3u), vec3<u32>(1u, 1u, 3u));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_unsigned_integer_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_P(ResolverIntrinsicTest_FloatOrInt_TwoParam, Vector_Float) {
+  auto param = GetParam();
+
+  auto* call =
+      Call(param.name, vec3<f32>(1.f, 1.f, 3.f), vec3<f32>(1.f, 1.f, 3.f));
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->is_float_vector());
+  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
+}
+
+TEST_P(ResolverIntrinsicTest_FloatOrInt_TwoParam, Error_NoParams) {
+  auto param = GetParam();
+
+  auto* call = Call(param.name);
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to " + std::string(param.name) +
+                "()\n\n"
+                "2 candidate functions:\n  " +
+                std::string(param.name) +
+                "(T, T) -> T  where: T is f32, i32 or u32\n  " +
+                std::string(param.name) +
+                "(vecN<T>, vecN<T>) -> vecN<T>  where: T is f32, i32 or u32\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverIntrinsicTest_FloatOrInt_TwoParam,
+    testing::Values(IntrinsicData{"min", IntrinsicType::kMin},
+                    IntrinsicData{"max", IntrinsicType::kMax}));
+
+TEST_F(ResolverIntrinsicTest, Determinant_2x2) {
+  Global("var", ty.mat2x2<f32>(), ast::StorageClass::kFunction);
+
+  auto* call = Call("determinant", "var");
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
+}
+
+TEST_F(ResolverIntrinsicTest, Determinant_3x3) {
+  Global("var", ty.mat3x3<f32>(), ast::StorageClass::kFunction);
+
+  auto* call = Call("determinant", "var");
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
+}
+
+TEST_F(ResolverIntrinsicTest, Determinant_4x4) {
+  Global("var", ty.mat4x4<f32>(), ast::StorageClass::kFunction);
+
+  auto* call = Call("determinant", "var");
+  WrapInFunction(call);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  ASSERT_NE(TypeOf(call), nullptr);
+  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
+}
+
+TEST_F(ResolverIntrinsicTest, Determinant_NotSquare) {
+  Global("var", ty.mat2x3<f32>(), ast::StorageClass::kFunction);
+
+  auto* call = Call("determinant", "var");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      "error: no matching call to determinant(ptr<function, mat2x3<f32>>)\n\n"
+      "1 candidate function:\n"
+      "  determinant(matNxN<f32>) -> f32\n");
+}
+
+TEST_F(ResolverIntrinsicTest, Determinant_NotMatrix) {
+  Global("var", ty.f32(), ast::StorageClass::kFunction);
+
+  auto* call = Call("determinant", "var");
+  WrapInFunction(call);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: no matching call to determinant(ptr<function, f32>)\n\n"
+            "1 candidate function:\n"
+            "  determinant(matNxN<f32>) -> f32\n");
+}
+
+using ResolverIntrinsicTest_Texture =
+    ResolverTestWithParam<ast::intrinsic::test::TextureOverloadCase>;
+
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverIntrinsicTest_Texture,
+    testing::ValuesIn(ast::intrinsic::test::TextureOverloadCase::ValidCases()));
+
+std::string to_str(const std::string& function,
+                   const semantic::ParameterList& params) {
+  std::stringstream out;
+  out << function << "(";
+  bool first = true;
+  for (auto& param : params) {
+    if (!first) {
+      out << ", ";
+    }
+    out << semantic::str(param.usage);
+    first = false;
+  }
+  out << ")";
+  return out.str();
+}
+
+const char* expected_texture_overload(
+    ast::intrinsic::test::ValidTextureOverload overload) {
+  using ValidTextureOverload = ast::intrinsic::test::ValidTextureOverload;
+  switch (overload) {
+    case ValidTextureOverload::kDimensions1d:
+    case ValidTextureOverload::kDimensions2d:
+    case ValidTextureOverload::kDimensions2dArray:
+    case ValidTextureOverload::kDimensions3d:
+    case ValidTextureOverload::kDimensionsCube:
+    case ValidTextureOverload::kDimensionsCubeArray:
+    case ValidTextureOverload::kDimensionsMultisampled2d:
+    case ValidTextureOverload::kDimensionsMultisampled2dArray:
+    case ValidTextureOverload::kDimensionsDepth2d:
+    case ValidTextureOverload::kDimensionsDepth2dArray:
+    case ValidTextureOverload::kDimensionsDepthCube:
+    case ValidTextureOverload::kDimensionsDepthCubeArray:
+    case ValidTextureOverload::kDimensionsStorageRO1d:
+    case ValidTextureOverload::kDimensionsStorageRO2d:
+    case ValidTextureOverload::kDimensionsStorageRO2dArray:
+    case ValidTextureOverload::kDimensionsStorageRO3d:
+    case ValidTextureOverload::kDimensionsStorageWO1d:
+    case ValidTextureOverload::kDimensionsStorageWO2d:
+    case ValidTextureOverload::kDimensionsStorageWO2dArray:
+    case ValidTextureOverload::kDimensionsStorageWO3d:
+      return R"(textureDimensions(texture))";
+    case ValidTextureOverload::kNumLayers2dArray:
+    case ValidTextureOverload::kNumLayersCubeArray:
+    case ValidTextureOverload::kNumLayersMultisampled2dArray:
+    case ValidTextureOverload::kNumLayersDepth2dArray:
+    case ValidTextureOverload::kNumLayersDepthCubeArray:
+    case ValidTextureOverload::kNumLayersStorageWO2dArray:
+      return R"(textureNumLayers(texture))";
+    case ValidTextureOverload::kNumLevels2d:
+    case ValidTextureOverload::kNumLevels2dArray:
+    case ValidTextureOverload::kNumLevels3d:
+    case ValidTextureOverload::kNumLevelsCube:
+    case ValidTextureOverload::kNumLevelsCubeArray:
+    case ValidTextureOverload::kNumLevelsDepth2d:
+    case ValidTextureOverload::kNumLevelsDepth2dArray:
+    case ValidTextureOverload::kNumLevelsDepthCube:
+    case ValidTextureOverload::kNumLevelsDepthCubeArray:
+      return R"(textureNumLevels(texture))";
+    case ValidTextureOverload::kNumSamplesMultisampled2d:
+    case ValidTextureOverload::kNumSamplesMultisampled2dArray:
+      return R"(textureNumSamples(texture))";
+    case ValidTextureOverload::kDimensions2dLevel:
+    case ValidTextureOverload::kDimensions2dArrayLevel:
+    case ValidTextureOverload::kDimensions3dLevel:
+    case ValidTextureOverload::kDimensionsCubeLevel:
+    case ValidTextureOverload::kDimensionsCubeArrayLevel:
+    case ValidTextureOverload::kDimensionsDepth2dLevel:
+    case ValidTextureOverload::kDimensionsDepth2dArrayLevel:
+    case ValidTextureOverload::kDimensionsDepthCubeLevel:
+    case ValidTextureOverload::kDimensionsDepthCubeArrayLevel:
+      return R"(textureDimensions(texture, level))";
+    case ValidTextureOverload::kSample1dF32:
+      return R"(textureSample(texture, sampler, coords))";
+    case ValidTextureOverload::kSample2dF32:
+      return R"(textureSample(texture, sampler, coords))";
+    case ValidTextureOverload::kSample2dOffsetF32:
+      return R"(textureSample(texture, sampler, coords, offset))";
+    case ValidTextureOverload::kSample2dArrayF32:
+      return R"(textureSample(texture, sampler, coords, array_index))";
+    case ValidTextureOverload::kSample2dArrayOffsetF32:
+      return R"(textureSample(texture, sampler, coords, array_index, offset))";
+    case ValidTextureOverload::kSample3dF32:
+      return R"(textureSample(texture, sampler, coords))";
+    case ValidTextureOverload::kSample3dOffsetF32:
+      return R"(textureSample(texture, sampler, coords, offset))";
+    case ValidTextureOverload::kSampleCubeF32:
+      return R"(textureSample(texture, sampler, coords))";
+    case ValidTextureOverload::kSampleCubeArrayF32:
+      return R"(textureSample(texture, sampler, coords, array_index))";
+    case ValidTextureOverload::kSampleDepth2dF32:
+      return R"(textureSample(texture, sampler, coords))";
+    case ValidTextureOverload::kSampleDepth2dOffsetF32:
+      return R"(textureSample(texture, sampler, coords, offset))";
+    case ValidTextureOverload::kSampleDepth2dArrayF32:
+      return R"(textureSample(texture, sampler, coords, array_index))";
+    case ValidTextureOverload::kSampleDepth2dArrayOffsetF32:
+      return R"(textureSample(texture, sampler, coords, array_index, offset))";
+    case ValidTextureOverload::kSampleDepthCubeF32:
+      return R"(textureSample(texture, sampler, coords))";
+    case ValidTextureOverload::kSampleDepthCubeArrayF32:
+      return R"(textureSample(texture, sampler, coords, array_index))";
+    case ValidTextureOverload::kSampleBias2dF32:
+      return R"(textureSampleBias(texture, sampler, coords, bias))";
+    case ValidTextureOverload::kSampleBias2dOffsetF32:
+      return R"(textureSampleBias(texture, sampler, coords, bias, offset))";
+    case ValidTextureOverload::kSampleBias2dArrayF32:
+      return R"(textureSampleBias(texture, sampler, coords, array_index, bias))";
+    case ValidTextureOverload::kSampleBias2dArrayOffsetF32:
+      return R"(textureSampleBias(texture, sampler, coords, array_index, bias, offset))";
+    case ValidTextureOverload::kSampleBias3dF32:
+      return R"(textureSampleBias(texture, sampler, coords, bias))";
+    case ValidTextureOverload::kSampleBias3dOffsetF32:
+      return R"(textureSampleBias(texture, sampler, coords, bias, offset))";
+    case ValidTextureOverload::kSampleBiasCubeF32:
+      return R"(textureSampleBias(texture, sampler, coords, bias))";
+    case ValidTextureOverload::kSampleBiasCubeArrayF32:
+      return R"(textureSampleBias(texture, sampler, coords, array_index, bias))";
+    case ValidTextureOverload::kSampleLevel2dF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level))";
+    case ValidTextureOverload::kSampleLevel2dOffsetF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level, offset))";
+    case ValidTextureOverload::kSampleLevel2dArrayF32:
+      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
+    case ValidTextureOverload::kSampleLevel2dArrayOffsetF32:
+      return R"(textureSampleLevel(texture, sampler, coords, array_index, level, offset))";
+    case ValidTextureOverload::kSampleLevel3dF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level))";
+    case ValidTextureOverload::kSampleLevel3dOffsetF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level, offset))";
+    case ValidTextureOverload::kSampleLevelCubeF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level))";
+    case ValidTextureOverload::kSampleLevelCubeArrayF32:
+      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
+    case ValidTextureOverload::kSampleLevelDepth2dF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level))";
+    case ValidTextureOverload::kSampleLevelDepth2dOffsetF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level, offset))";
+    case ValidTextureOverload::kSampleLevelDepth2dArrayF32:
+      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
+    case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32:
+      return R"(textureSampleLevel(texture, sampler, coords, array_index, level, offset))";
+    case ValidTextureOverload::kSampleLevelDepthCubeF32:
+      return R"(textureSampleLevel(texture, sampler, coords, level))";
+    case ValidTextureOverload::kSampleLevelDepthCubeArrayF32:
+      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
+    case ValidTextureOverload::kSampleGrad2dF32:
+      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy))";
+    case ValidTextureOverload::kSampleGrad2dOffsetF32:
+      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy, offset))";
+    case ValidTextureOverload::kSampleGrad2dArrayF32:
+      return R"(textureSampleGrad(texture, sampler, coords, array_index, ddx, ddy))";
+    case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
+      return R"(textureSampleGrad(texture, sampler, coords, array_index, ddx, ddy, offset))";
+    case ValidTextureOverload::kSampleGrad3dF32:
+      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy))";
+    case ValidTextureOverload::kSampleGrad3dOffsetF32:
+      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy, offset))";
+    case ValidTextureOverload::kSampleGradCubeF32:
+      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy))";
+    case ValidTextureOverload::kSampleGradCubeArrayF32:
+      return R"(textureSampleGrad(texture, sampler, coords, array_index, ddx, ddy))";
+    case ValidTextureOverload::kSampleCompareDepth2dF32:
+      return R"(textureSampleCompare(texture, sampler, coords, depth_ref))";
+    case ValidTextureOverload::kSampleCompareDepth2dOffsetF32:
+      return R"(textureSampleCompare(texture, sampler, coords, depth_ref, offset))";
+    case ValidTextureOverload::kSampleCompareDepth2dArrayF32:
+      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref))";
+    case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32:
+      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref, offset))";
+    case ValidTextureOverload::kSampleCompareDepthCubeF32:
+      return R"(textureSampleCompare(texture, sampler, coords, depth_ref))";
+    case ValidTextureOverload::kSampleCompareDepthCubeArrayF32:
+      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref))";
+    case ValidTextureOverload::kLoad1dLevelF32:
+      return R"(textureLoad(texture, coords, level))";
+    case ValidTextureOverload::kLoad1dLevelU32:
+      return R"(textureLoad(texture, coords, level))";
+    case ValidTextureOverload::kLoad1dLevelI32:
+      return R"(textureLoad(texture, coords, level))";
+    case ValidTextureOverload::kLoad2dLevelF32:
+      return R"(textureLoad(texture, coords, level))";
+    case ValidTextureOverload::kLoad2dLevelU32:
+      return R"(textureLoad(texture, coords, level))";
+    case ValidTextureOverload::kLoad2dLevelI32:
+      return R"(textureLoad(texture, coords, level))";
+    case ValidTextureOverload::kLoad2dArrayLevelF32:
+      return R"(textureLoad(texture, coords, array_index, level))";
+    case ValidTextureOverload::kLoad2dArrayLevelU32:
+      return R"(textureLoad(texture, coords, array_index, level))";
+    case ValidTextureOverload::kLoad2dArrayLevelI32:
+      return R"(textureLoad(texture, coords, array_index, level))";
+    case ValidTextureOverload::kLoad3dLevelF32:
+      return R"(textureLoad(texture, coords, level))";
+    case ValidTextureOverload::kLoad3dLevelU32:
+      return R"(textureLoad(texture, coords, level))";
+    case ValidTextureOverload::kLoad3dLevelI32:
+      return R"(textureLoad(texture, coords, level))";
+    case ValidTextureOverload::kLoadMultisampled2dF32:
+      return R"(textureLoad(texture, coords, sample_index))";
+    case ValidTextureOverload::kLoadMultisampled2dU32:
+      return R"(textureLoad(texture, coords, sample_index))";
+    case ValidTextureOverload::kLoadMultisampled2dI32:
+      return R"(textureLoad(texture, coords, sample_index))";
+    case ValidTextureOverload::kLoadMultisampled2dArrayF32:
+      return R"(textureLoad(texture, coords, array_index, sample_index))";
+    case ValidTextureOverload::kLoadMultisampled2dArrayU32:
+      return R"(textureLoad(texture, coords, array_index, sample_index))";
+    case ValidTextureOverload::kLoadMultisampled2dArrayI32:
+      return R"(textureLoad(texture, coords, array_index, sample_index))";
+    case ValidTextureOverload::kLoadDepth2dLevelF32:
+      return R"(textureLoad(texture, coords, level))";
+    case ValidTextureOverload::kLoadDepth2dArrayLevelF32:
+      return R"(textureLoad(texture, coords, array_index, level))";
+    case ValidTextureOverload::kLoadStorageRO1dRgba32float:
+      return R"(textureLoad(texture, coords))";
+    case ValidTextureOverload::kLoadStorageRO2dRgba8unorm:
+    case ValidTextureOverload::kLoadStorageRO2dRgba8snorm:
+    case ValidTextureOverload::kLoadStorageRO2dRgba8uint:
+    case ValidTextureOverload::kLoadStorageRO2dRgba8sint:
+    case ValidTextureOverload::kLoadStorageRO2dRgba16uint:
+    case ValidTextureOverload::kLoadStorageRO2dRgba16sint:
+    case ValidTextureOverload::kLoadStorageRO2dRgba16float:
+    case ValidTextureOverload::kLoadStorageRO2dR32uint:
+    case ValidTextureOverload::kLoadStorageRO2dR32sint:
+    case ValidTextureOverload::kLoadStorageRO2dR32float:
+    case ValidTextureOverload::kLoadStorageRO2dRg32uint:
+    case ValidTextureOverload::kLoadStorageRO2dRg32sint:
+    case ValidTextureOverload::kLoadStorageRO2dRg32float:
+    case ValidTextureOverload::kLoadStorageRO2dRgba32uint:
+    case ValidTextureOverload::kLoadStorageRO2dRgba32sint:
+    case ValidTextureOverload::kLoadStorageRO2dRgba32float:
+      return R"(textureLoad(texture, coords))";
+    case ValidTextureOverload::kLoadStorageRO2dArrayRgba32float:
+      return R"(textureLoad(texture, coords, array_index))";
+    case ValidTextureOverload::kLoadStorageRO3dRgba32float:
+      return R"(textureLoad(texture, coords))";
+    case ValidTextureOverload::kStoreWO1dRgba32float:
+      return R"(textureStore(texture, coords, value))";
+    case ValidTextureOverload::kStoreWO2dRgba32float:
+      return R"(textureStore(texture, coords, value))";
+    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
+      return R"(textureStore(texture, coords, array_index, value))";
+    case ValidTextureOverload::kStoreWO3dRgba32float:
+      return R"(textureStore(texture, coords, value))";
+  }
+  return "<unmatched texture overload>";
+}
+
+TEST_P(ResolverIntrinsicTest_Texture, Call) {
+  auto param = GetParam();
+
+  param.buildTextureVariable(this);
+  param.buildSamplerVariable(this);
+
+  auto* call = Call(param.function, param.args(this));
+  WrapInFunction(call);
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  if (std::string(param.function) == "textureDimensions") {
+    switch (param.texture_dimension) {
+      default:
+        FAIL() << "invalid texture dimensions: " << param.texture_dimension;
+      case type::TextureDimension::k1d:
+        EXPECT_EQ(TypeOf(call)->type_name(), ty.i32()->type_name());
+        break;
+      case type::TextureDimension::k2d:
+      case type::TextureDimension::k2dArray:
+        EXPECT_EQ(TypeOf(call)->type_name(), ty.vec2<i32>()->type_name());
+        break;
+      case type::TextureDimension::k3d:
+      case type::TextureDimension::kCube:
+      case type::TextureDimension::kCubeArray:
+        EXPECT_EQ(TypeOf(call)->type_name(), ty.vec3<i32>()->type_name());
+        break;
+    }
+  } else if (std::string(param.function) == "textureNumLayers") {
+    EXPECT_EQ(TypeOf(call), ty.i32());
+  } else if (std::string(param.function) == "textureNumLevels") {
+    EXPECT_EQ(TypeOf(call), ty.i32());
+  } else if (std::string(param.function) == "textureNumSamples") {
+    EXPECT_EQ(TypeOf(call), ty.i32());
+  } else if (std::string(param.function) == "textureStore") {
+    EXPECT_EQ(TypeOf(call), ty.void_());
+  } else {
+    switch (param.texture_kind) {
+      case ast::intrinsic::test::TextureKind::kRegular:
+      case ast::intrinsic::test::TextureKind::kMultisampled:
+      case ast::intrinsic::test::TextureKind::kStorage: {
+        auto* datatype = param.resultVectorComponentType(this);
+        ASSERT_TRUE(TypeOf(call)->Is<type::Vector>());
+        EXPECT_EQ(TypeOf(call)->As<type::Vector>()->type(), datatype);
+        break;
+      }
+      case ast::intrinsic::test::TextureKind::kDepth: {
+        EXPECT_EQ(TypeOf(call), ty.f32());
+        break;
+      }
+    }
+  }
+
+  auto* call_sem = Sem().Get(call);
+  ASSERT_NE(call_sem, nullptr);
+  auto* target = call_sem->Target();
+  ASSERT_NE(target, nullptr);
+
+  auto got = resolver::to_str(param.function, target->Parameters());
+  auto* expected = expected_texture_overload(param.overload);
+  EXPECT_EQ(got, expected);
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index 0e11556..057e142 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -44,46 +44,6 @@
 namespace resolver {
 namespace {
 
-using IntrinsicType = semantic::IntrinsicType;
-
-class FakeStmt : public ast::Statement {
- public:
-  explicit FakeStmt(Source source) : ast::Statement(source) {}
-  FakeStmt* Clone(CloneContext*) const override { return nullptr; }
-  bool IsValid() const override { return true; }
-  void to_str(const semantic::Info&, std::ostream& out, size_t) const override {
-    out << "Fake";
-  }
-};
-
-class FakeExpr : public ast::Expression {
- public:
-  explicit FakeExpr(Source source) : ast::Expression(source) {}
-  FakeExpr* Clone(CloneContext*) const override { return nullptr; }
-  bool IsValid() const override { return true; }
-  void to_str(const semantic::Info&, std::ostream&, size_t) const override {}
-};
-
-TEST_F(ResolverTest, Error_WithEmptySource) {
-  auto* s = create<FakeStmt>();
-  WrapInFunction(s);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: unknown statement type for type determination: Fake");
-}
-
-TEST_F(ResolverTest, Stmt_Error_Unknown) {
-  auto* s = create<FakeStmt>(Source{Source::Location{2, 30}});
-  WrapInFunction(s);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "2:30 error: unknown statement type for type determination: Fake");
-}
-
 TEST_F(ResolverTest, Stmt_Assign) {
   auto* lhs = Expr(2);
   auto* rhs = Expr(2.3f);
@@ -239,244 +199,6 @@
   EXPECT_TRUE(TypeOf(continuing_rhs)->Is<type::F32>());
 }
 
-TEST_F(ResolverTest, Stmt_Loop_ContinueInLoopBodyBeforeDecl_UsageInContinuing) {
-  // loop  {
-  //     continue; // Bypasses z decl
-  //     var z : i32;
-  //
-  //     continuing {
-  //         z = 2;
-  //     }
-  // }
-
-  auto error_loc = Source{Source::Location{12, 34}};
-  auto* body = Block(create<ast::ContinueStatement>(),
-                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
-  auto* continuing = Block(Assign(Expr(error_loc, "z"), Expr(2)));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            "12:34 error: continue statement bypasses declaration of 'z' in "
-            "continuing block");
-}
-
-TEST_F(ResolverTest,
-       Stmt_Loop_ContinueInLoopBodyBeforeDeclAndAfterDecl_UsageInContinuing) {
-  // loop  {
-  //     continue; // Bypasses z decl
-  //     var z : i32;
-  //     continue; // Ok
-  //
-  //     continuing {
-  //         z = 2;
-  //     }
-  // }
-
-  auto error_loc = Source{Source::Location{12, 34}};
-  auto* body = Block(create<ast::ContinueStatement>(),
-                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
-                     create<ast::ContinueStatement>());
-  auto* continuing = Block(Assign(Expr(error_loc, "z"), Expr(2)));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            "12:34 error: continue statement bypasses declaration of 'z' in "
-            "continuing block");
-}
-
-TEST_F(ResolverTest,
-       Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuing) {
-  // loop  {
-  //     if (true) {
-  //         continue; // Still bypasses z decl (if we reach here)
-  //     }
-  //     var z : i32;
-  //     continuing {
-  //         z = 2;
-  //     }
-  // }
-
-  auto error_loc = Source{Source::Location{12, 34}};
-  auto* body = Block(If(Expr(true), Block(create<ast::ContinueStatement>())),
-                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
-  auto* continuing = Block(Assign(Expr(error_loc, "z"), Expr(2)));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            "12:34 error: continue statement bypasses declaration of 'z' in "
-            "continuing block");
-}
-
-TEST_F(
-    ResolverTest,
-    Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuingSubscope) {
-  // loop  {
-  //     if (true) {
-  //         continue; // Still bypasses z decl (if we reach here)
-  //     }
-  //     var z : i32;
-  //     continuing {
-  //         if (true) {
-  //             z = 2; // Must fail even if z is in a sub-scope
-  //         }
-  //     }
-  // }
-
-  auto error_loc = Source{Source::Location{12, 34}};
-  auto* body = Block(If(Expr(true), Block(create<ast::ContinueStatement>())),
-                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
-
-  auto* continuing =
-      Block(If(Expr(true), Block(Assign(Expr(error_loc, "z"), Expr(2)))));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            "12:34 error: continue statement bypasses declaration of 'z' in "
-            "continuing block");
-}
-
-TEST_F(ResolverTest,
-       Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuingLoop) {
-  // loop  {
-  //     if (true) {
-  //         continue; // Still bypasses z decl (if we reach here)
-  //     }
-  //     var z : i32;
-  //     continuing {
-  //         loop {
-  //             z = 2; // Must fail even if z is in a sub-scope
-  //         }
-  //     }
-  // }
-
-  auto error_loc = Source{Source::Location{12, 34}};
-  auto* body = Block(If(Expr(true), Block(create<ast::ContinueStatement>())),
-                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
-
-  auto* continuing = Block(Loop(Block(Assign(Expr(error_loc, "z"), Expr(2)))));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            "12:34 error: continue statement bypasses declaration of 'z' in "
-            "continuing block");
-}
-
-TEST_F(ResolverTest,
-       Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuing) {
-  // loop  {
-  //     loop {
-  //         continue; // OK: not part of the outer loop
-  //     }
-  //     var z : i32;
-  //
-  //     continuing {
-  //         z = 2;
-  //     }
-  // }
-
-  auto* inner_loop = Loop(Block(create<ast::ContinueStatement>()));
-  auto* body =
-      Block(inner_loop, Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
-  auto* continuing = Block(Assign(Expr("z"), Expr(2)));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTest,
-       Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuingSubscope) {
-  // loop  {
-  //     loop {
-  //         continue; // OK: not part of the outer loop
-  //     }
-  //     var z : i32;
-  //
-  //     continuing {
-  //         if (true) {
-  //             z = 2;
-  //         }
-  //     }
-  // }
-
-  auto* inner_loop = Loop(Block(create<ast::ContinueStatement>()));
-  auto* body =
-      Block(inner_loop, Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
-  auto* continuing = Block(If(Expr(true), Block(Assign(Expr("z"), Expr(2)))));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTest,
-       Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuingLoop) {
-  // loop  {
-  //     loop {
-  //         continue; // OK: not part of the outer loop
-  //     }
-  //     var z : i32;
-  //
-  //     continuing {
-  //         loop {
-  //             z = 2;
-  //         }
-  //     }
-  // }
-
-  auto* inner_loop = Loop(Block(create<ast::ContinueStatement>()));
-  auto* body =
-      Block(inner_loop, Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
-  auto* continuing = Block(Loop(Block(Assign(Expr("z"), Expr(2)))));
-  auto* loop_stmt = Loop(body, continuing);
-  WrapInFunction(loop_stmt);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTest, Stmt_ContinueInLoop) {
-  WrapInFunction(Loop(Block(create<ast::ContinueStatement>(Source{{12, 34}}))));
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTest, Stmt_ContinueNotInLoop) {
-  WrapInFunction(create<ast::ContinueStatement>(Source{{12, 34}}));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error: continue statement must be in a loop");
-}
-
-TEST_F(ResolverTest, Stmt_BreakInLoop) {
-  WrapInFunction(Loop(Block(create<ast::BreakStatement>(Source{{12, 34}}))));
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTest, Stmt_BreakInSwitch) {
-  WrapInFunction(Loop(Block(create<ast::SwitchStatement>(
-      Expr(1), ast::CaseStatementList{
-                   create<ast::CaseStatement>(
-                       ast::CaseSelectorList{Literal(1)},
-                       Block(create<ast::BreakStatement>(Source{{12, 34}}))),
-               }))));
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverTest, Stmt_BreakNotInLoopOrSwitch) {
-  WrapInFunction(create<ast::BreakStatement>(Source{{12, 34}}));
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: break statement must be in a loop or switch case");
-}
-
 TEST_F(ResolverTest, Stmt_Return) {
   auto* cond = Expr(2);
 
@@ -540,55 +262,6 @@
   EXPECT_EQ(StmtOf(expr), call);
 }
 
-TEST_F(ResolverTest, Stmt_Call_undeclared) {
-  // fn main() -> void {func(); return; }
-  // fn func() -> void { return; }
-
-  SetSource(Source::Location{12, 34});
-  auto* call_expr = Call("func");
-  ast::VariableList params0;
-
-  Func("main", params0, ty.f32(),
-       ast::StatementList{
-           create<ast::CallStatement>(call_expr),
-           create<ast::ReturnStatement>(),
-       },
-       ast::FunctionDecorationList{});
-
-  Func("func", params0, ty.f32(),
-       ast::StatementList{
-           create<ast::ReturnStatement>(),
-       },
-       ast::FunctionDecorationList{});
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "12:34 error: v-0006: unable to find called function: func");
-}
-
-TEST_F(ResolverTest, Stmt_Call_recursive) {
-  // fn main() -> void {main(); }
-
-  SetSource(Source::Location{12, 34});
-  auto* call_expr = Call("main");
-  ast::VariableList params0;
-
-  Func("main", params0, ty.f32(),
-       ast::StatementList{
-           create<ast::CallStatement>(call_expr),
-       },
-       ast::FunctionDecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
-       });
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "12:34 error: recursion is not permitted. 'main' attempted to call "
-            "itself.");
-}
-
 TEST_F(ResolverTest, Stmt_VariableDecl) {
   auto* var = Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(2));
   auto* init = var->constructor();
@@ -616,37 +289,6 @@
   EXPECT_TRUE(TypeOf(init)->Is<type::I32>());
 }
 
-TEST_F(ResolverTest, Stmt_VariableDecl_MismatchedTypeScalarConstructor) {
-  u32 unsigned_value = 2u;  // Type does not match variable type
-  auto* var =
-      Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(unsigned_value));
-
-  auto* decl =
-      create<ast::VariableDeclStatement>(Source{{{3, 3}, {3, 22}}}, var);
-  WrapInFunction(decl);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(3:3 error: constructor expression type does not match variable type)");
-}
-
-TEST_F(ResolverTest, Stmt_VariableDecl_MismatchedTypeScalarConstructor_Alias) {
-  auto* my_int = ty.alias("MyInt", ty.i32());
-  u32 unsigned_value = 2u;  // Type does not match variable type
-  auto* var =
-      Var("my_var", my_int, ast::StorageClass::kNone, Expr(unsigned_value));
-
-  auto* decl =
-      create<ast::VariableDeclStatement>(Source{{{3, 3}, {3, 22}}}, var);
-  WrapInFunction(decl);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(3:3 error: constructor expression type does not match variable type)");
-}
-
 TEST_F(ResolverTest, Stmt_VariableDecl_ModuleScope) {
   auto* init = Expr(2);
   Global("my_var", ty.i32(), ast::StorageClass::kNone, init);
@@ -758,16 +400,6 @@
   EXPECT_TRUE(CheckVarUsers(mod_f32, {fn_f32->constructor()}));
 }
 
-TEST_F(ResolverTest, Expr_Error_Unknown) {
-  FakeExpr e(Source{Source::Location{2, 30}});
-  WrapInFunction(&e);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "2:30 error: unknown expression for type determination");
-}
-
 TEST_F(ResolverTest, Expr_ArrayAccessor_Array) {
   auto* idx = Expr(2);
   Global("my_var", ty.array<f32, 3>(), ast::StorageClass::kFunction);
@@ -925,27 +557,6 @@
   EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
 }
 
-TEST_F(ResolverTest, Expr_DontCall_Function) {
-  Func("func", {}, ty.void_(), {}, {});
-  auto* ident = create<ast::IdentifierExpression>(
-      Source{{Source::Location{3, 3}, Source::Location{3, 8}}},
-      Symbols().Register("func"));
-  WrapInFunction(ident);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "3:8 error: missing '(' for function call");
-}
-
-TEST_F(ResolverTest, Expr_DontCall_Intrinsic) {
-  auto* ident = create<ast::IdentifierExpression>(
-      Source{{Source::Location{3, 3}, Source::Location{3, 8}}},
-      Symbols().Register("round"));
-  WrapInFunction(ident);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "3:8 error: missing '(' for intrinsic call");
-}
-
 TEST_F(ResolverTest, Expr_Cast) {
   Global("name", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -1104,39 +715,6 @@
   EXPECT_FALSE(r()->Resolve());
 }
 
-TEST_F(ResolverTest, UsingUndefinedVariable_Fail) {
-  // b = 2;
-
-  SetSource(Source{Source::Location{12, 34}});
-  auto* lhs = Expr("b");
-  auto* rhs = Expr(2);
-  auto* assign = create<ast::AssignmentStatement>(lhs, rhs);
-  WrapInFunction(assign);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: v-0006: identifier must be declared before use: b");
-}
-
-TEST_F(ResolverTest, UsingUndefinedVariableInBlockStatement_Fail) {
-  // {
-  //  b = 2;
-  // }
-
-  SetSource(Source{Source::Location{12, 34}});
-  auto* lhs = Expr("b");
-  auto* rhs = Expr(2);
-
-  auto* body = create<ast::BlockStatement>(ast::StatementList{
-      create<ast::AssignmentStatement>(lhs, rhs),
-  });
-  WrapInFunction(body);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: v-0006: identifier must be declared before use: b");
-}
-
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
   auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
   auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput);
@@ -1300,49 +878,6 @@
   EXPECT_THAT(Sem().Get(mem)->Swizzle(), ElementsAre(2));
 }
 
-TEST_F(ResolverTest, Expr_MemberAccessor_VectorSwizzle_BadChar) {
-  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kNone);
-
-  auto* ident = create<ast::IdentifierExpression>(
-      Source{{Source::Location{3, 3}, Source::Location{3, 7}}},
-      Symbols().Register("xyqz"));
-
-  auto* mem = MemberAccessor("my_vec", ident);
-  WrapInFunction(mem);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "3:5 error: invalid vector swizzle character");
-}
-
-TEST_F(ResolverTest, Expr_MemberAccessor_VectorSwizzle_MixedChars) {
-  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kNone);
-
-  auto* ident = create<ast::IdentifierExpression>(
-      Source{{Source::Location{3, 3}, Source::Location{3, 7}}},
-      Symbols().Register("rgyw"));
-
-  auto* mem = MemberAccessor("my_vec", ident);
-  WrapInFunction(mem);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "3:3 error: invalid mixing of vector swizzle characters rgba with xyzw");
-}
-
-TEST_F(ResolverTest, Expr_MemberAccessor_VectorSwizzle_BadLength) {
-  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kNone);
-
-  auto* ident = create<ast::IdentifierExpression>(
-      Source{{Source::Location{3, 3}, Source::Location{3, 8}}},
-      Symbols().Register("zzzzz"));
-  auto* mem = MemberAccessor("my_vec", ident);
-  WrapInFunction(mem);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "3:3 error: invalid vector swizzle size");
-}
-
 TEST_F(ResolverTest, Expr_Accessor_MultiLevel) {
   // struct b {
   //   vec4<f32> foo
@@ -1654,484 +1189,6 @@
   EXPECT_EQ(mat->columns(), 4u);
 }
 
-using IntrinsicDerivativeTest = ResolverTestWithParam<std::string>;
-TEST_P(IntrinsicDerivativeTest, Scalar) {
-  auto name = GetParam();
-
-  Global("ident", ty.f32(), ast::StorageClass::kNone);
-
-  auto* expr = Call(name, "ident");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<type::F32>());
-}
-
-TEST_P(IntrinsicDerivativeTest, Vector) {
-  auto name = GetParam();
-  Global("ident", ty.vec4<f32>(), ast::StorageClass::kNone);
-
-  auto* expr = Call(name, "ident");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<type::Vector>());
-  EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::F32>());
-  EXPECT_EQ(TypeOf(expr)->As<type::Vector>()->size(), 4u);
-}
-
-TEST_P(IntrinsicDerivativeTest, MissingParam) {
-  auto name = GetParam();
-
-  auto* expr = Call(name);
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "error: no matching call to " + name +
-                              "()\n\n"
-                              "2 candidate functions:\n  " +
-                              name + "(f32) -> f32\n  " + name +
-                              "(vecN<f32>) -> vecN<f32>\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         IntrinsicDerivativeTest,
-                         testing::Values("dpdx",
-                                         "dpdxCoarse",
-                                         "dpdxFine",
-                                         "dpdy",
-                                         "dpdyCoarse",
-                                         "dpdyFine",
-                                         "fwidth",
-                                         "fwidthCoarse",
-                                         "fwidthFine"));
-
-using Intrinsic = ResolverTestWithParam<std::string>;
-TEST_P(Intrinsic, Test) {
-  auto name = GetParam();
-
-  Global("my_var", ty.vec3<bool>(), ast::StorageClass::kNone);
-
-  auto* expr = Call(name, "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<type::Bool>());
-}
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         Intrinsic,
-                         testing::Values("any", "all"));
-
-using Intrinsic_FloatMethod = ResolverTestWithParam<std::string>;
-TEST_P(Intrinsic_FloatMethod, Vector) {
-  auto name = GetParam();
-
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kNone);
-
-  auto* expr = Call(name, "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<type::Vector>());
-  EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::Bool>());
-  EXPECT_EQ(TypeOf(expr)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_P(Intrinsic_FloatMethod, Scalar) {
-  auto name = GetParam();
-
-  Global("my_var", ty.f32(), ast::StorageClass::kNone);
-
-  auto* expr = Call(name, "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<type::Bool>());
-}
-
-TEST_P(Intrinsic_FloatMethod, MissingParam) {
-  auto name = GetParam();
-
-  Global("my_var", ty.f32(), ast::StorageClass::kNone);
-
-  auto* expr = Call(name);
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "error: no matching call to " + name +
-                              "()\n\n"
-                              "2 candidate functions:\n  " +
-                              name + "(f32) -> bool\n  " + name +
-                              "(vecN<f32>) -> vecN<bool>\n");
-}
-
-TEST_P(Intrinsic_FloatMethod, TooManyParams) {
-  auto name = GetParam();
-
-  Global("my_var", ty.f32(), ast::StorageClass::kNone);
-
-  auto* expr = Call(name, "my_var", 1.23f);
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "error: no matching call to " + name +
-                              "(ptr<f32>, f32)\n\n"
-                              "2 candidate functions:\n  " +
-                              name + "(f32) -> bool\n  " + name +
-                              "(vecN<f32>) -> vecN<bool>\n");
-}
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    Intrinsic_FloatMethod,
-    testing::Values("isInf", "isNan", "isFinite", "isNormal"));
-
-enum class Texture { kF32, kI32, kU32 };
-inline std::ostream& operator<<(std::ostream& out, Texture data) {
-  if (data == Texture::kF32) {
-    out << "f32";
-  } else if (data == Texture::kI32) {
-    out << "i32";
-  } else {
-    out << "u32";
-  }
-  return out;
-}
-
-struct TextureTestParams {
-  type::TextureDimension dim;
-  Texture type = Texture::kF32;
-  type::ImageFormat format = type::ImageFormat::kR16Float;
-};
-inline std::ostream& operator<<(std::ostream& out, TextureTestParams data) {
-  out << data.dim << "_" << data.type;
-  return out;
-}
-
-class Intrinsic_TextureOperation
-    : public ResolverTestWithParam<TextureTestParams> {
- public:
-  /// Gets an appropriate type for the coords parameter depending the the
-  /// dimensionality of the texture being sampled.
-  /// @param dim dimensionality of the texture being sampled
-  /// @param scalar the scalar type
-  /// @returns a pointer to a type appropriate for the coord param
-  type::Type* GetCoordsType(type::TextureDimension dim, type::Type* scalar) {
-    switch (dim) {
-      case type::TextureDimension::k1d:
-        return scalar;
-      case type::TextureDimension::k2d:
-      case type::TextureDimension::k2dArray:
-        return create<type::Vector>(scalar, 2);
-      case type::TextureDimension::k3d:
-      case type::TextureDimension::kCube:
-      case type::TextureDimension::kCubeArray:
-        return create<type::Vector>(scalar, 3);
-      default:
-        [=]() { FAIL() << "Unsupported texture dimension: " << dim; }();
-    }
-    return nullptr;
-  }
-
-  void add_call_param(std::string name,
-                      type::Type* type,
-                      ast::ExpressionList* call_params) {
-    Global(name, type, ast::StorageClass::kNone);
-    call_params->push_back(Expr(name));
-  }
-  type::Type* subtype(Texture type) {
-    if (type == Texture::kF32) {
-      return create<type::F32>();
-    }
-    if (type == Texture::kI32) {
-      return create<type::I32>();
-    }
-    return create<type::U32>();
-  }
-};
-
-using Intrinsic_StorageTextureOperation = Intrinsic_TextureOperation;
-TEST_P(Intrinsic_StorageTextureOperation, TextureLoadRo) {
-  auto dim = GetParam().dim;
-  auto type = GetParam().type;
-  auto format = GetParam().format;
-
-  auto* coords_type = GetCoordsType(dim, ty.i32());
-
-  auto* subtype = type::StorageTexture::SubtypeFor(format, Types());
-  auto* texture_type = create<type::StorageTexture>(dim, format, subtype);
-  auto* ro_texture_type =
-      create<type::AccessControl>(ast::AccessControl::kReadOnly, texture_type);
-
-  ast::ExpressionList call_params;
-
-  add_call_param("texture", ro_texture_type, &call_params);
-  add_call_param("coords", coords_type, &call_params);
-
-  if (type::IsTextureArray(dim)) {
-    add_call_param("array_index", ty.i32(), &call_params);
-  }
-
-  auto* expr = Call("textureLoad", call_params);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<type::Vector>());
-  if (type == Texture::kF32) {
-    EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::F32>());
-  } else if (type == Texture::kI32) {
-    EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::I32>());
-  } else {
-    EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::U32>());
-  }
-  EXPECT_EQ(TypeOf(expr)->As<type::Vector>()->size(), 4u);
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    Intrinsic_StorageTextureOperation,
-    testing::Values(
-        TextureTestParams{type::TextureDimension::k1d, Texture::kF32,
-                          type::ImageFormat::kR16Float},
-        TextureTestParams{type::TextureDimension::k1d, Texture::kI32,
-                          type::ImageFormat::kR16Sint},
-        TextureTestParams{type::TextureDimension::k1d, Texture::kF32,
-                          type::ImageFormat::kR8Unorm},
-        TextureTestParams{type::TextureDimension::k2d, Texture::kF32,
-                          type::ImageFormat::kR16Float},
-        TextureTestParams{type::TextureDimension::k2d, Texture::kI32,
-                          type::ImageFormat::kR16Sint},
-        TextureTestParams{type::TextureDimension::k2d, Texture::kF32,
-                          type::ImageFormat::kR8Unorm},
-        TextureTestParams{type::TextureDimension::k2dArray, Texture::kF32,
-                          type::ImageFormat::kR16Float},
-        TextureTestParams{type::TextureDimension::k2dArray, Texture::kI32,
-                          type::ImageFormat::kR16Sint},
-        TextureTestParams{type::TextureDimension::k2dArray, Texture::kF32,
-                          type::ImageFormat::kR8Unorm},
-        TextureTestParams{type::TextureDimension::k3d, Texture::kF32,
-                          type::ImageFormat::kR16Float},
-        TextureTestParams{type::TextureDimension::k3d, Texture::kI32,
-                          type::ImageFormat::kR16Sint},
-        TextureTestParams{type::TextureDimension::k3d, Texture::kF32,
-                          type::ImageFormat::kR8Unorm}));
-
-using Intrinsic_SampledTextureOperation = Intrinsic_TextureOperation;
-TEST_P(Intrinsic_SampledTextureOperation, TextureLoadSampled) {
-  auto dim = GetParam().dim;
-  auto type = GetParam().type;
-
-  type::Type* s = subtype(type);
-  auto* coords_type = GetCoordsType(dim, ty.i32());
-  auto* texture_type = create<type::SampledTexture>(dim, s);
-
-  ast::ExpressionList call_params;
-
-  add_call_param("texture", texture_type, &call_params);
-  add_call_param("coords", coords_type, &call_params);
-  if (dim == type::TextureDimension::k2dArray) {
-    add_call_param("array_index", ty.i32(), &call_params);
-  }
-  add_call_param("level", ty.i32(), &call_params);
-
-  auto* expr = Call("textureLoad", call_params);
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  ASSERT_TRUE(TypeOf(expr)->Is<type::Vector>());
-  if (type == Texture::kF32) {
-    EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::F32>());
-  } else if (type == Texture::kI32) {
-    EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::I32>());
-  } else {
-    EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::U32>());
-  }
-  EXPECT_EQ(TypeOf(expr)->As<type::Vector>()->size(), 4u);
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    Intrinsic_SampledTextureOperation,
-    testing::Values(TextureTestParams{type::TextureDimension::k1d},
-                    TextureTestParams{type::TextureDimension::k2d},
-                    TextureTestParams{type::TextureDimension::k2dArray},
-                    TextureTestParams{type::TextureDimension::k3d}));
-
-TEST_F(ResolverTest, Intrinsic_Dot_Vec2) {
-  Global("my_var", ty.vec2<f32>(), ast::StorageClass::kNone);
-
-  auto* expr = Call("dot", "my_var", "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<type::F32>());
-}
-
-TEST_F(ResolverTest, Intrinsic_Dot_Vec3) {
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kNone);
-
-  auto* expr = Call("dot", "my_var", "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<type::F32>());
-}
-
-TEST_F(ResolverTest, Intrinsic_Dot_Vec4) {
-  Global("my_var", ty.vec4<f32>(), ast::StorageClass::kNone);
-
-  auto* expr = Call("dot", "my_var", "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<type::F32>());
-}
-
-TEST_F(ResolverTest, Intrinsic_Dot_Error_Scalar) {
-  auto* expr = Call("dot", 1.0f, 1.0f);
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to dot(f32, f32)
-
-1 candidate function:
-  dot(vecN<f32>, vecN<f32>) -> f32
-)");
-}
-
-TEST_F(ResolverTest, Intrinsic_Dot_Error_VectorInt) {
-  Global("my_var", ty.vec4<i32>(), ast::StorageClass::kNone);
-
-  auto* expr = Call("dot", "my_var", "my_var");
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to dot(ptr<vec4<i32>>, ptr<vec4<i32>>)
-
-1 candidate function:
-  dot(vecN<f32>, vecN<f32>) -> f32
-)");
-}
-
-TEST_F(ResolverTest, Intrinsic_Select) {
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kNone);
-
-  Global("bool_var", ty.vec3<bool>(), ast::StorageClass::kNone);
-
-  auto* expr = Call("select", "my_var", "my_var", "bool_var");
-  WrapInFunction(expr);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(expr), nullptr);
-  EXPECT_TRUE(TypeOf(expr)->Is<type::Vector>());
-  EXPECT_EQ(TypeOf(expr)->As<type::Vector>()->size(), 3u);
-  EXPECT_TRUE(TypeOf(expr)->As<type::Vector>()->type()->Is<type::F32>());
-}
-
-TEST_F(ResolverTest, Intrinsic_Select_Error_NoParams) {
-  auto* expr = Call("select");
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to select()
-
-2 candidate functions:
-  select(T, T, bool) -> T  where: T is scalar
-  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is scalar
-)");
-}
-
-TEST_F(ResolverTest, Intrinsic_Select_Error_SelectorInt) {
-  auto* expr = Call("select", Expr(1), Expr(1), Expr(1));
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to select(i32, i32, i32)
-
-2 candidate functions:
-  select(T, T, bool) -> T  where: T is scalar
-  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is scalar
-)");
-}
-
-TEST_F(ResolverTest, Intrinsic_Select_Error_Matrix) {
-  auto* mat = mat2x2<float>(vec2<float>(1.0f, 1.0f), vec2<float>(1.0f, 1.0f));
-  auto* expr = Call("select", mat, mat, Expr(true));
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to select(mat2x2<f32>, mat2x2<f32>, bool)
-
-2 candidate functions:
-  select(T, T, bool) -> T  where: T is scalar
-  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is scalar
-)");
-}
-
-TEST_F(ResolverTest, Intrinsic_Select_Error_MismatchTypes) {
-  auto* expr = Call("select", 1.0f, vec2<float>(2.0f, 3.0f), Expr(true));
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to select(f32, vec2<f32>, bool)
-
-2 candidate functions:
-  select(T, T, bool) -> T  where: T is scalar
-  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is scalar
-)");
-}
-
-TEST_F(ResolverTest, Intrinsic_Select_Error_MismatchVectorSize) {
-  auto* expr = Call("select", vec2<float>(1.0f, 2.0f),
-                    vec3<float>(3.0f, 4.0f, 5.0f), Expr(true));
-  WrapInFunction(expr);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to select(vec2<f32>, vec3<f32>, bool)
-
-2 candidate functions:
-  select(T, T, bool) -> T  where: T is scalar
-  select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T>  where: T is scalar
-)");
-}
-
 using UnaryOpExpressionTest = ResolverTestWithParam<ast::UnaryOp>;
 TEST_P(UnaryOpExpressionTest, Expr_UnaryOp) {
   auto op = GetParam();
@@ -2175,1125 +1232,6 @@
   EXPECT_EQ(Sem().Get(var)->StorageClass(), ast::StorageClass::kNone);
 }
 
-TEST_F(ResolverTest, StorageClass_NonFunctionClassError) {
-  auto* var = Var("var", ty.i32(), ast::StorageClass::kWorkgroup);
-
-  auto* stmt = create<ast::VariableDeclStatement>(var);
-  Func("func", ast::VariableList{}, ty.i32(), ast::StatementList{stmt},
-       ast::FunctionDecorationList{});
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: function variable has a non-function storage class");
-}
-
-struct IntrinsicData {
-  const char* name;
-  IntrinsicType intrinsic;
-};
-
-inline std::ostream& operator<<(std::ostream& out, IntrinsicData data) {
-  out << data.name;
-  return out;
-}
-
-using Intrinsic_DataPackingTest = ResolverTestWithParam<IntrinsicData>;
-TEST_P(Intrinsic_DataPackingTest, InferType) {
-  auto param = GetParam();
-
-  bool pack4 = param.intrinsic == IntrinsicType::kPack4x8Snorm ||
-               param.intrinsic == IntrinsicType::kPack4x8Unorm;
-
-  auto* call = pack4 ? Call(param.name, vec4<f32>(1.f, 2.f, 3.f, 4.f))
-                     : Call(param.name, vec2<f32>(1.f, 2.f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::U32>());
-}
-
-TEST_P(Intrinsic_DataPackingTest, Error_IncorrectParamType) {
-  auto param = GetParam();
-
-  bool pack4 = param.intrinsic == IntrinsicType::kPack4x8Snorm ||
-               param.intrinsic == IntrinsicType::kPack4x8Unorm;
-
-  auto* call = pack4 ? Call(param.name, vec4<i32>(1, 2, 3, 4))
-                     : Call(param.name, vec2<i32>(1, 2));
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
-                                      std::string(param.name)));
-}
-
-TEST_P(Intrinsic_DataPackingTest, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
-                                      std::string(param.name)));
-}
-
-TEST_P(Intrinsic_DataPackingTest, Error_TooManyParams) {
-  auto param = GetParam();
-
-  bool pack4 = param.intrinsic == IntrinsicType::kPack4x8Snorm ||
-               param.intrinsic == IntrinsicType::kPack4x8Unorm;
-
-  auto* call = pack4 ? Call(param.name, vec4<f32>(1.f, 2.f, 3.f, 4.f), 1.0f)
-                     : Call(param.name, vec2<f32>(1.f, 2.f), 1.0f);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " +
-                                      std::string(param.name)));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    Intrinsic_DataPackingTest,
-    testing::Values(
-        IntrinsicData{"pack4x8snorm", IntrinsicType::kPack4x8Snorm},
-        IntrinsicData{"pack4x8unorm", IntrinsicType::kPack4x8Unorm},
-        IntrinsicData{"pack2x16snorm", IntrinsicType::kPack2x16Snorm},
-        IntrinsicData{"pack2x16unorm", IntrinsicType::kPack2x16Unorm},
-        IntrinsicData{"pack2x16float", IntrinsicType::kPack2x16Float}));
-
-using Intrinsic_DataUnpackingTest = ResolverTestWithParam<IntrinsicData>;
-TEST_P(Intrinsic_DataUnpackingTest, InferType) {
-  auto param = GetParam();
-
-  bool pack4 = param.intrinsic == IntrinsicType::kUnpack4x8Snorm ||
-               param.intrinsic == IntrinsicType::kUnpack4x8Unorm;
-
-  auto* call = Call(param.name, 1u);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  if (pack4) {
-    EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 4u);
-  } else {
-    EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 2u);
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    Intrinsic_DataUnpackingTest,
-    testing::Values(
-        IntrinsicData{"unpack4x8snorm", IntrinsicType::kUnpack4x8Snorm},
-        IntrinsicData{"unpack4x8unorm", IntrinsicType::kUnpack4x8Unorm},
-        IntrinsicData{"unpack2x16snorm", IntrinsicType::kUnpack2x16Snorm},
-        IntrinsicData{"unpack2x16unorm", IntrinsicType::kUnpack2x16Unorm},
-        IntrinsicData{"unpack2x16float", IntrinsicType::kUnpack2x16Float}));
-
-using Intrinsic_SingleParamTest = ResolverTestWithParam<IntrinsicData>;
-TEST_P(Intrinsic_SingleParamTest, Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_P(Intrinsic_SingleParamTest, Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_P(Intrinsic_SingleParamTest, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "()\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) + "(f32) -> f32\n  " +
-                std::string(param.name) + "(vecN<f32>) -> vecN<f32>\n");
-}
-
-TEST_P(Intrinsic_SingleParamTest, Error_TooManyParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1, 2, 3);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "(i32, i32, i32)\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) + "(f32) -> f32\n  " +
-                std::string(param.name) + "(vecN<f32>) -> vecN<f32>\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    Intrinsic_SingleParamTest,
-    testing::Values(IntrinsicData{"acos", IntrinsicType::kAcos},
-                    IntrinsicData{"asin", IntrinsicType::kAsin},
-                    IntrinsicData{"atan", IntrinsicType::kAtan},
-                    IntrinsicData{"ceil", IntrinsicType::kCeil},
-                    IntrinsicData{"cos", IntrinsicType::kCos},
-                    IntrinsicData{"cosh", IntrinsicType::kCosh},
-                    IntrinsicData{"exp", IntrinsicType::kExp},
-                    IntrinsicData{"exp2", IntrinsicType::kExp2},
-                    IntrinsicData{"floor", IntrinsicType::kFloor},
-                    IntrinsicData{"fract", IntrinsicType::kFract},
-                    IntrinsicData{"inverseSqrt", IntrinsicType::kInverseSqrt},
-                    IntrinsicData{"log", IntrinsicType::kLog},
-                    IntrinsicData{"log2", IntrinsicType::kLog2},
-                    IntrinsicData{"round", IntrinsicType::kRound},
-                    IntrinsicData{"sign", IntrinsicType::kSign},
-                    IntrinsicData{"sin", IntrinsicType::kSin},
-                    IntrinsicData{"sinh", IntrinsicType::kSinh},
-                    IntrinsicData{"sqrt", IntrinsicType::kSqrt},
-                    IntrinsicData{"tan", IntrinsicType::kTan},
-                    IntrinsicData{"tanh", IntrinsicType::kTanh},
-                    IntrinsicData{"trunc", IntrinsicType::kTrunc}));
-
-using IntrinsicDataTest = ResolverTest;
-
-TEST_F(IntrinsicDataTest, ArrayLength_Vector) {
-  Global("arr", ty.array<int>(), ast::StorageClass::kNone);
-  auto* call = Call("arrayLength", "arr");
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::U32>());
-}
-
-TEST_F(IntrinsicDataTest, ArrayLength_Error_ArraySized) {
-  Global("arr", ty.array<int, 4>(), ast::StorageClass::kNone);
-  auto* call = Call("arrayLength", "arr");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to arrayLength(ptr<array<i32, 4>>)\n\n"
-            "1 candidate function:\n"
-            "  arrayLength(array<T>) -> u32\n");
-}
-
-TEST_F(IntrinsicDataTest, Normalize_Vector) {
-  auto* call = Call("normalize", vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_F(IntrinsicDataTest, Normalize_Error_NoParams) {
-  auto* call = Call("normalize");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to normalize()\n\n"
-            "1 candidate function:\n"
-            "  normalize(vecN<f32>) -> vecN<f32>\n");
-}
-
-TEST_F(IntrinsicDataTest, FrexpScalar) {
-  Global("exp", ty.i32(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("frexp", 1.0f, "exp");
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
-}
-
-TEST_F(IntrinsicDataTest, FrexpVector) {
-  Global("exp", ty.vec3<i32>(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("frexp", vec3<f32>(1.0f, 2.0f, 3.0f), "exp");
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::Vector>());
-  EXPECT_TRUE(TypeOf(call)->As<type::Vector>()->type()->Is<type::F32>());
-}
-
-TEST_F(IntrinsicDataTest, Frexp_Error_FirstParamInt) {
-  Global("exp", ty.i32(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("frexp", 1, "exp");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to frexp(i32, ptr<workgroup, i32>)\n\n"
-            "2 candidate functions:\n"
-            "  frexp(f32, ptr<T>) -> f32  where: T is i32 or u32\n"
-            "  frexp(vecN<f32>, ptr<vecN<T>>) -> vecN<f32>  "
-            "where: T is i32 or u32\n");
-}
-
-TEST_F(IntrinsicDataTest, Frexp_Error_SecondParamFloatPtr) {
-  Global("exp", ty.f32(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("frexp", 1.0f, "exp");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to frexp(f32, ptr<workgroup, f32>)\n\n"
-            "2 candidate functions:\n"
-            "  frexp(f32, ptr<T>) -> f32  where: T is i32 or u32\n"
-            "  frexp(vecN<f32>, ptr<vecN<T>>) -> vecN<f32>  "
-            "where: T is i32 or u32\n");
-}
-
-TEST_F(IntrinsicDataTest, Frexp_Error_SecondParamNotAPointer) {
-  auto* call = Call("frexp", 1.0f, 1);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to frexp(f32, i32)\n\n"
-            "2 candidate functions:\n"
-            "  frexp(f32, ptr<T>) -> f32  where: T is i32 or u32\n"
-            "  frexp(vecN<f32>, ptr<vecN<T>>) -> vecN<f32>  "
-            "where: T is i32 or u32\n");
-}
-
-TEST_F(IntrinsicDataTest, Frexp_Error_VectorSizesDontMatch) {
-  Global("exp", ty.vec4<i32>(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("frexp", vec2<f32>(1.0f, 2.0f), "exp");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to frexp(vec2<f32>, ptr<workgroup, "
-            "vec4<i32>>)\n\n"
-            "2 candidate functions:\n"
-            "  frexp(f32, ptr<T>) -> f32  where: T is i32 or u32\n"
-            "  frexp(vecN<f32>, ptr<vecN<T>>) -> vecN<f32>  "
-            "where: T is i32 or u32\n");
-}
-
-TEST_F(IntrinsicDataTest, ModfScalar) {
-  Global("whole", ty.f32(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("modf", 1.0f, "whole");
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
-}
-
-TEST_F(IntrinsicDataTest, ModfVector) {
-  Global("whole", ty.vec3<f32>(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("modf", vec3<f32>(1.0f, 2.0f, 3.0f), "whole");
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::Vector>());
-  EXPECT_TRUE(TypeOf(call)->As<type::Vector>()->type()->Is<type::F32>());
-}
-
-TEST_F(IntrinsicDataTest, Modf_Error_FirstParamInt) {
-  Global("whole", ty.f32(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("modf", 1, "whole");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to modf(i32, ptr<workgroup, f32>)\n\n"
-            "2 candidate functions:\n"
-            "  modf(f32, ptr<f32>) -> f32\n"
-            "  modf(vecN<f32>, ptr<vecN<f32>>) -> vecN<f32>\n");
-}
-
-TEST_F(IntrinsicDataTest, Modf_Error_SecondParamIntPtr) {
-  Global("whole", ty.i32(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("modf", 1.0f, "whole");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to modf(f32, ptr<workgroup, i32>)\n\n"
-            "2 candidate functions:\n"
-            "  modf(f32, ptr<f32>) -> f32\n"
-            "  modf(vecN<f32>, ptr<vecN<f32>>) -> vecN<f32>\n");
-}
-
-TEST_F(IntrinsicDataTest, Modf_Error_SecondParamNotAPointer) {
-  auto* call = Call("modf", 1.0f, 1.0f);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to modf(f32, f32)\n\n"
-            "2 candidate functions:\n"
-            "  modf(f32, ptr<f32>) -> f32\n"
-            "  modf(vecN<f32>, ptr<vecN<f32>>) -> vecN<f32>\n");
-}
-
-TEST_F(IntrinsicDataTest, Modf_Error_VectorSizesDontMatch) {
-  Global("whole", ty.vec4<f32>(), ast::StorageClass::kWorkgroup);
-  auto* call = Call("modf", vec2<f32>(1.0f, 2.0f), "whole");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to modf(vec2<f32>, ptr<workgroup, "
-            "vec4<f32>>)\n\n"
-            "2 candidate functions:\n"
-            "  modf(vecN<f32>, ptr<vecN<f32>>) -> vecN<f32>\n"
-            "  modf(f32, ptr<f32>) -> f32\n");
-}
-
-using Intrinsic_SingleParam_FloatOrInt_Test =
-    ResolverTestWithParam<IntrinsicData>;
-TEST_P(Intrinsic_SingleParam_FloatOrInt_Test, Float_Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_P(Intrinsic_SingleParam_FloatOrInt_Test, Float_Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_P(Intrinsic_SingleParam_FloatOrInt_Test, Sint_Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, -1);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::I32>());
-}
-
-TEST_P(Intrinsic_SingleParam_FloatOrInt_Test, Sint_Vector) {
-  auto param = GetParam();
-
-  ast::ExpressionList vals;
-  vals.push_back(Expr(1));
-  vals.push_back(Expr(1));
-  vals.push_back(Expr(3));
-
-  ast::ExpressionList params;
-  params.push_back(vec3<i32>(vals));
-
-  auto* call = Call(param.name, params);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_P(Intrinsic_SingleParam_FloatOrInt_Test, Uint_Scalar) {
-  auto param = GetParam();
-
-  ast::ExpressionList params;
-  params.push_back(Expr(1u));
-
-  auto* call = Call(param.name, params);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::U32>());
-}
-
-TEST_P(Intrinsic_SingleParam_FloatOrInt_Test, Uint_Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<u32>(1u, 1u, 3u));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_unsigned_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_P(Intrinsic_SingleParam_FloatOrInt_Test, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "()\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) +
-                "(T) -> T  where: T is f32, i32 or u32\n  " +
-                std::string(param.name) +
-                "(vecN<T>) -> vecN<T>  where: T is f32, i32 or u32\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         Intrinsic_SingleParam_FloatOrInt_Test,
-                         testing::Values(IntrinsicData{"abs",
-                                                       IntrinsicType::kAbs}));
-
-TEST_F(ResolverTest, Intrinsic_Length_Scalar) {
-  auto* call = Call("length", 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_F(ResolverTest, Intrinsic_Length_FloatVector) {
-  ast::ExpressionList params;
-  params.push_back(vec3<f32>(1.0f, 1.0f, 3.0f));
-
-  auto* call = Call("length", params);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-using Intrinsic_TwoParamTest = ResolverTestWithParam<IntrinsicData>;
-TEST_P(Intrinsic_TwoParamTest, Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1.f, 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_P(Intrinsic_TwoParamTest, Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f),
-                    vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_P(Intrinsic_TwoParamTest, Error_NoTooManyParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1, 2, 3);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "(i32, i32, i32)\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) + "(f32, f32) -> f32\n  " +
-                std::string(param.name) +
-                "(vecN<f32>, vecN<f32>) -> vecN<f32>\n");
-}
-
-TEST_P(Intrinsic_TwoParamTest, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "()\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) + "(f32, f32) -> f32\n  " +
-                std::string(param.name) +
-                "(vecN<f32>, vecN<f32>) -> vecN<f32>\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    Intrinsic_TwoParamTest,
-    testing::Values(IntrinsicData{"atan2", IntrinsicType::kAtan2},
-                    IntrinsicData{"pow", IntrinsicType::kPow},
-                    IntrinsicData{"step", IntrinsicType::kStep},
-                    IntrinsicData{"reflect", IntrinsicType::kReflect}));
-
-TEST_F(ResolverTest, Intrinsic_Distance_Scalar) {
-  auto* call = Call("distance", 1.f, 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_F(ResolverTest, Intrinsic_Distance_Vector) {
-  auto* call = Call("distance", vec3<f32>(1.0f, 1.0f, 3.0f),
-                    vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
-}
-
-TEST_F(ResolverTest, Intrinsic_Cross) {
-  auto* call =
-      Call("cross", vec3<f32>(1.0f, 2.0f, 3.0f), vec3<f32>(1.0f, 2.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_F(ResolverTest, Intrinsic_Cross_Error_NoArgs) {
-  auto* call = Call("cross");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), R"(error: no matching call to cross()
-
-1 candidate function:
-  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
-)");
-}
-
-TEST_F(ResolverTest, Intrinsic_Cross_Error_Scalar) {
-  auto* call = Call("cross", 1.0f, 1.0f);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), R"(error: no matching call to cross(f32, f32)
-
-1 candidate function:
-  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
-)");
-}
-
-TEST_F(ResolverTest, Intrinsic_Cross_Error_Vec3Int) {
-  auto* call = Call("cross", vec3<i32>(1, 2, 3), vec3<i32>(1, 2, 3));
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to cross(vec3<i32>, vec3<i32>)
-
-1 candidate function:
-  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
-)");
-}
-
-TEST_F(ResolverTest, Intrinsic_Cross_Error_Vec4) {
-  auto* call = Call("cross", vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f),
-                    vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f));
-
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to cross(vec4<f32>, vec4<f32>)
-
-1 candidate function:
-  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
-)");
-}
-
-TEST_F(ResolverTest, Intrinsic_Cross_Error_TooManyParams) {
-  auto* call = Call("cross", vec3<f32>(1.0f, 2.0f, 3.0f),
-                    vec3<f32>(1.0f, 2.0f, 3.0f), vec3<f32>(1.0f, 2.0f, 3.0f));
-
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            R"(error: no matching call to cross(vec3<f32>, vec3<f32>, vec3<f32>)
-
-1 candidate function:
-  cross(vec3<f32>, vec3<f32>) -> vec3<f32>
-)");
-}
-TEST_F(ResolverTest, Intrinsic_Normalize) {
-  auto* call = Call("normalize", vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_F(ResolverTest, Intrinsic_Normalize_NoArgs) {
-  auto* call = Call("normalize");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), R"(error: no matching call to normalize()
-
-1 candidate function:
-  normalize(vecN<f32>) -> vecN<f32>
-)");
-}
-
-using Intrinsic_ThreeParamTest = ResolverTestWithParam<IntrinsicData>;
-TEST_P(Intrinsic_ThreeParamTest, Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1.f, 1.f, 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_P(Intrinsic_ThreeParamTest, Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f),
-                    vec3<f32>(1.0f, 1.0f, 3.0f), vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-TEST_P(Intrinsic_ThreeParamTest, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "()\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) + "(f32, f32, f32) -> f32\n  " +
-                std::string(param.name) +
-                "(vecN<f32>, vecN<f32>, vecN<f32>) -> vecN<f32>\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    Intrinsic_ThreeParamTest,
-    testing::Values(IntrinsicData{"mix", IntrinsicType::kMix},
-                    IntrinsicData{"smoothStep", IntrinsicType::kSmoothStep},
-                    IntrinsicData{"fma", IntrinsicType::kFma},
-                    IntrinsicData{"faceForward", IntrinsicType::kFaceForward}));
-
-using Intrinsic_ThreeParam_FloatOrInt_Test =
-    ResolverTestWithParam<IntrinsicData>;
-TEST_P(Intrinsic_ThreeParam_FloatOrInt_Test, Float_Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1.f, 1.f, 1.f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_scalar());
-}
-
-TEST_P(Intrinsic_ThreeParam_FloatOrInt_Test, Float_Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<f32>(1.0f, 1.0f, 3.0f),
-                    vec3<f32>(1.0f, 1.0f, 3.0f), vec3<f32>(1.0f, 1.0f, 3.0f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_P(Intrinsic_ThreeParam_FloatOrInt_Test, Sint_Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1, 1, 1);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::I32>());
-}
-
-TEST_P(Intrinsic_ThreeParam_FloatOrInt_Test, Sint_Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<i32>(1, 1, 3), vec3<i32>(1, 1, 3),
-                    vec3<i32>(1, 1, 3));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_P(Intrinsic_ThreeParam_FloatOrInt_Test, Uint_Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1u, 1u, 1u);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::U32>());
-}
-
-TEST_P(Intrinsic_ThreeParam_FloatOrInt_Test, Uint_Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<u32>(1u, 1u, 3u), vec3<u32>(1u, 1u, 3u),
-                    vec3<u32>(1u, 1u, 3u));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_unsigned_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_P(Intrinsic_ThreeParam_FloatOrInt_Test, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "()\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) +
-                "(T, T, T) -> T  where: T is f32, i32 or u32\n  " +
-                std::string(param.name) +
-                "(vecN<T>, vecN<T>, vecN<T>) -> vecN<T>  where: T is f32, i32 "
-                "or u32\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         Intrinsic_ThreeParam_FloatOrInt_Test,
-                         testing::Values(IntrinsicData{"clamp",
-                                                       IntrinsicType::kClamp}));
-
-using Intrinsic_Int_SingleParamTest = ResolverTestWithParam<IntrinsicData>;
-TEST_P(Intrinsic_Int_SingleParamTest, Scalar) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_integer_scalar());
-}
-
-TEST_P(Intrinsic_Int_SingleParamTest, Vector) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<i32>(1, 1, 3));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_P(Intrinsic_Int_SingleParamTest, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(), "error: no matching call to " +
-                              std::string(param.name) +
-                              "()\n\n"
-                              "2 candidate functions:\n  " +
-                              std::string(param.name) +
-                              "(T) -> T  where: T is i32 or u32\n  " +
-                              std::string(param.name) +
-                              "(vecN<T>) -> vecN<T>  where: T is i32 or u32\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    Intrinsic_Int_SingleParamTest,
-    testing::Values(IntrinsicData{"countOneBits", IntrinsicType::kCountOneBits},
-                    IntrinsicData{"reverseBits", IntrinsicType::kReverseBits}));
-
-using Intrinsic_FloatOrInt_TwoParamTest = ResolverTestWithParam<IntrinsicData>;
-TEST_P(Intrinsic_FloatOrInt_TwoParamTest, Scalar_Signed) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1, 1);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::I32>());
-}
-
-TEST_P(Intrinsic_FloatOrInt_TwoParamTest, Scalar_Unsigned) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1u, 1u);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::U32>());
-}
-
-TEST_P(Intrinsic_FloatOrInt_TwoParamTest, Scalar_Float) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, 1.0f, 1.0f);
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
-}
-
-TEST_P(Intrinsic_FloatOrInt_TwoParamTest, Vector_Signed) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<i32>(1, 1, 3), vec3<i32>(1, 1, 3));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_signed_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_P(Intrinsic_FloatOrInt_TwoParamTest, Vector_Unsigned) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name, vec3<u32>(1u, 1u, 3u), vec3<u32>(1u, 1u, 3u));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_unsigned_integer_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_P(Intrinsic_FloatOrInt_TwoParamTest, Vector_Float) {
-  auto param = GetParam();
-
-  auto* call =
-      Call(param.name, vec3<f32>(1.f, 1.f, 3.f), vec3<f32>(1.f, 1.f, 3.f));
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->is_float_vector());
-  EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
-}
-
-TEST_P(Intrinsic_FloatOrInt_TwoParamTest, Error_NoParams) {
-  auto param = GetParam();
-
-  auto* call = Call(param.name);
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to " + std::string(param.name) +
-                "()\n\n"
-                "2 candidate functions:\n  " +
-                std::string(param.name) +
-                "(T, T) -> T  where: T is f32, i32 or u32\n  " +
-                std::string(param.name) +
-                "(vecN<T>, vecN<T>) -> vecN<T>  where: T is f32, i32 or u32\n");
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    Intrinsic_FloatOrInt_TwoParamTest,
-    testing::Values(IntrinsicData{"min", IntrinsicType::kMin},
-                    IntrinsicData{"max", IntrinsicType::kMax}));
-
-TEST_F(ResolverTest, Intrinsic_Determinant_2x2) {
-  Global("var", ty.mat2x2<f32>(), ast::StorageClass::kFunction);
-
-  auto* call = Call("determinant", "var");
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
-}
-
-TEST_F(ResolverTest, Intrinsic_Determinant_3x3) {
-  Global("var", ty.mat3x3<f32>(), ast::StorageClass::kFunction);
-
-  auto* call = Call("determinant", "var");
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
-}
-
-TEST_F(ResolverTest, Intrinsic_Determinant_4x4) {
-  Global("var", ty.mat4x4<f32>(), ast::StorageClass::kFunction);
-
-  auto* call = Call("determinant", "var");
-  WrapInFunction(call);
-
-  EXPECT_TRUE(r()->Resolve()) << r()->error();
-
-  ASSERT_NE(TypeOf(call), nullptr);
-  EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
-}
-
-TEST_F(ResolverTest, Intrinsic_Determinant_NotSquare) {
-  Global("var", ty.mat2x3<f32>(), ast::StorageClass::kFunction);
-
-  auto* call = Call("determinant", "var");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      "error: no matching call to determinant(ptr<function, mat2x3<f32>>)\n\n"
-      "1 candidate function:\n"
-      "  determinant(matNxN<f32>) -> f32\n");
-}
-
-TEST_F(ResolverTest, Intrinsic_Determinant_NotMatrix) {
-  Global("var", ty.f32(), ast::StorageClass::kFunction);
-
-  auto* call = Call("determinant", "var");
-  WrapInFunction(call);
-
-  EXPECT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(r()->error(),
-            "error: no matching call to determinant(ptr<function, f32>)\n\n"
-            "1 candidate function:\n"
-            "  determinant(matNxN<f32>) -> f32\n");
-}
-
 TEST_F(ResolverTest, Function_EntryPoints_StageDecoration) {
   // fn b() {}
   // fn c() { b(); }
@@ -3427,325 +1365,6 @@
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-using ResolverTextureIntrinsicTest =
-    ResolverTestWithParam<ast::intrinsic::test::TextureOverloadCase>;
-
-INSTANTIATE_TEST_SUITE_P(
-    ResolverTest,
-    ResolverTextureIntrinsicTest,
-    testing::ValuesIn(ast::intrinsic::test::TextureOverloadCase::ValidCases()));
-
-std::string to_str(const std::string& function,
-                   const semantic::ParameterList& params) {
-  std::stringstream out;
-  out << function << "(";
-  bool first = true;
-  for (auto& param : params) {
-    if (!first) {
-      out << ", ";
-    }
-    out << semantic::str(param.usage);
-    first = false;
-  }
-  out << ")";
-  return out.str();
-}
-
-const char* expected_texture_overload(
-    ast::intrinsic::test::ValidTextureOverload overload) {
-  using ValidTextureOverload = ast::intrinsic::test::ValidTextureOverload;
-  switch (overload) {
-    case ValidTextureOverload::kDimensions1d:
-    case ValidTextureOverload::kDimensions2d:
-    case ValidTextureOverload::kDimensions2dArray:
-    case ValidTextureOverload::kDimensions3d:
-    case ValidTextureOverload::kDimensionsCube:
-    case ValidTextureOverload::kDimensionsCubeArray:
-    case ValidTextureOverload::kDimensionsMultisampled2d:
-    case ValidTextureOverload::kDimensionsMultisampled2dArray:
-    case ValidTextureOverload::kDimensionsDepth2d:
-    case ValidTextureOverload::kDimensionsDepth2dArray:
-    case ValidTextureOverload::kDimensionsDepthCube:
-    case ValidTextureOverload::kDimensionsDepthCubeArray:
-    case ValidTextureOverload::kDimensionsStorageRO1d:
-    case ValidTextureOverload::kDimensionsStorageRO2d:
-    case ValidTextureOverload::kDimensionsStorageRO2dArray:
-    case ValidTextureOverload::kDimensionsStorageRO3d:
-    case ValidTextureOverload::kDimensionsStorageWO1d:
-    case ValidTextureOverload::kDimensionsStorageWO2d:
-    case ValidTextureOverload::kDimensionsStorageWO2dArray:
-    case ValidTextureOverload::kDimensionsStorageWO3d:
-      return R"(textureDimensions(texture))";
-    case ValidTextureOverload::kNumLayers2dArray:
-    case ValidTextureOverload::kNumLayersCubeArray:
-    case ValidTextureOverload::kNumLayersMultisampled2dArray:
-    case ValidTextureOverload::kNumLayersDepth2dArray:
-    case ValidTextureOverload::kNumLayersDepthCubeArray:
-    case ValidTextureOverload::kNumLayersStorageWO2dArray:
-      return R"(textureNumLayers(texture))";
-    case ValidTextureOverload::kNumLevels2d:
-    case ValidTextureOverload::kNumLevels2dArray:
-    case ValidTextureOverload::kNumLevels3d:
-    case ValidTextureOverload::kNumLevelsCube:
-    case ValidTextureOverload::kNumLevelsCubeArray:
-    case ValidTextureOverload::kNumLevelsDepth2d:
-    case ValidTextureOverload::kNumLevelsDepth2dArray:
-    case ValidTextureOverload::kNumLevelsDepthCube:
-    case ValidTextureOverload::kNumLevelsDepthCubeArray:
-      return R"(textureNumLevels(texture))";
-    case ValidTextureOverload::kNumSamplesMultisampled2d:
-    case ValidTextureOverload::kNumSamplesMultisampled2dArray:
-      return R"(textureNumSamples(texture))";
-    case ValidTextureOverload::kDimensions2dLevel:
-    case ValidTextureOverload::kDimensions2dArrayLevel:
-    case ValidTextureOverload::kDimensions3dLevel:
-    case ValidTextureOverload::kDimensionsCubeLevel:
-    case ValidTextureOverload::kDimensionsCubeArrayLevel:
-    case ValidTextureOverload::kDimensionsDepth2dLevel:
-    case ValidTextureOverload::kDimensionsDepth2dArrayLevel:
-    case ValidTextureOverload::kDimensionsDepthCubeLevel:
-    case ValidTextureOverload::kDimensionsDepthCubeArrayLevel:
-      return R"(textureDimensions(texture, level))";
-    case ValidTextureOverload::kSample1dF32:
-      return R"(textureSample(texture, sampler, coords))";
-    case ValidTextureOverload::kSample2dF32:
-      return R"(textureSample(texture, sampler, coords))";
-    case ValidTextureOverload::kSample2dOffsetF32:
-      return R"(textureSample(texture, sampler, coords, offset))";
-    case ValidTextureOverload::kSample2dArrayF32:
-      return R"(textureSample(texture, sampler, coords, array_index))";
-    case ValidTextureOverload::kSample2dArrayOffsetF32:
-      return R"(textureSample(texture, sampler, coords, array_index, offset))";
-    case ValidTextureOverload::kSample3dF32:
-      return R"(textureSample(texture, sampler, coords))";
-    case ValidTextureOverload::kSample3dOffsetF32:
-      return R"(textureSample(texture, sampler, coords, offset))";
-    case ValidTextureOverload::kSampleCubeF32:
-      return R"(textureSample(texture, sampler, coords))";
-    case ValidTextureOverload::kSampleCubeArrayF32:
-      return R"(textureSample(texture, sampler, coords, array_index))";
-    case ValidTextureOverload::kSampleDepth2dF32:
-      return R"(textureSample(texture, sampler, coords))";
-    case ValidTextureOverload::kSampleDepth2dOffsetF32:
-      return R"(textureSample(texture, sampler, coords, offset))";
-    case ValidTextureOverload::kSampleDepth2dArrayF32:
-      return R"(textureSample(texture, sampler, coords, array_index))";
-    case ValidTextureOverload::kSampleDepth2dArrayOffsetF32:
-      return R"(textureSample(texture, sampler, coords, array_index, offset))";
-    case ValidTextureOverload::kSampleDepthCubeF32:
-      return R"(textureSample(texture, sampler, coords))";
-    case ValidTextureOverload::kSampleDepthCubeArrayF32:
-      return R"(textureSample(texture, sampler, coords, array_index))";
-    case ValidTextureOverload::kSampleBias2dF32:
-      return R"(textureSampleBias(texture, sampler, coords, bias))";
-    case ValidTextureOverload::kSampleBias2dOffsetF32:
-      return R"(textureSampleBias(texture, sampler, coords, bias, offset))";
-    case ValidTextureOverload::kSampleBias2dArrayF32:
-      return R"(textureSampleBias(texture, sampler, coords, array_index, bias))";
-    case ValidTextureOverload::kSampleBias2dArrayOffsetF32:
-      return R"(textureSampleBias(texture, sampler, coords, array_index, bias, offset))";
-    case ValidTextureOverload::kSampleBias3dF32:
-      return R"(textureSampleBias(texture, sampler, coords, bias))";
-    case ValidTextureOverload::kSampleBias3dOffsetF32:
-      return R"(textureSampleBias(texture, sampler, coords, bias, offset))";
-    case ValidTextureOverload::kSampleBiasCubeF32:
-      return R"(textureSampleBias(texture, sampler, coords, bias))";
-    case ValidTextureOverload::kSampleBiasCubeArrayF32:
-      return R"(textureSampleBias(texture, sampler, coords, array_index, bias))";
-    case ValidTextureOverload::kSampleLevel2dF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level))";
-    case ValidTextureOverload::kSampleLevel2dOffsetF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level, offset))";
-    case ValidTextureOverload::kSampleLevel2dArrayF32:
-      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
-    case ValidTextureOverload::kSampleLevel2dArrayOffsetF32:
-      return R"(textureSampleLevel(texture, sampler, coords, array_index, level, offset))";
-    case ValidTextureOverload::kSampleLevel3dF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level))";
-    case ValidTextureOverload::kSampleLevel3dOffsetF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level, offset))";
-    case ValidTextureOverload::kSampleLevelCubeF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level))";
-    case ValidTextureOverload::kSampleLevelCubeArrayF32:
-      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
-    case ValidTextureOverload::kSampleLevelDepth2dF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level))";
-    case ValidTextureOverload::kSampleLevelDepth2dOffsetF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level, offset))";
-    case ValidTextureOverload::kSampleLevelDepth2dArrayF32:
-      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
-    case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32:
-      return R"(textureSampleLevel(texture, sampler, coords, array_index, level, offset))";
-    case ValidTextureOverload::kSampleLevelDepthCubeF32:
-      return R"(textureSampleLevel(texture, sampler, coords, level))";
-    case ValidTextureOverload::kSampleLevelDepthCubeArrayF32:
-      return R"(textureSampleLevel(texture, sampler, coords, array_index, level))";
-    case ValidTextureOverload::kSampleGrad2dF32:
-      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy))";
-    case ValidTextureOverload::kSampleGrad2dOffsetF32:
-      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy, offset))";
-    case ValidTextureOverload::kSampleGrad2dArrayF32:
-      return R"(textureSampleGrad(texture, sampler, coords, array_index, ddx, ddy))";
-    case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
-      return R"(textureSampleGrad(texture, sampler, coords, array_index, ddx, ddy, offset))";
-    case ValidTextureOverload::kSampleGrad3dF32:
-      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy))";
-    case ValidTextureOverload::kSampleGrad3dOffsetF32:
-      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy, offset))";
-    case ValidTextureOverload::kSampleGradCubeF32:
-      return R"(textureSampleGrad(texture, sampler, coords, ddx, ddy))";
-    case ValidTextureOverload::kSampleGradCubeArrayF32:
-      return R"(textureSampleGrad(texture, sampler, coords, array_index, ddx, ddy))";
-    case ValidTextureOverload::kSampleCompareDepth2dF32:
-      return R"(textureSampleCompare(texture, sampler, coords, depth_ref))";
-    case ValidTextureOverload::kSampleCompareDepth2dOffsetF32:
-      return R"(textureSampleCompare(texture, sampler, coords, depth_ref, offset))";
-    case ValidTextureOverload::kSampleCompareDepth2dArrayF32:
-      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref))";
-    case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32:
-      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref, offset))";
-    case ValidTextureOverload::kSampleCompareDepthCubeF32:
-      return R"(textureSampleCompare(texture, sampler, coords, depth_ref))";
-    case ValidTextureOverload::kSampleCompareDepthCubeArrayF32:
-      return R"(textureSampleCompare(texture, sampler, coords, array_index, depth_ref))";
-    case ValidTextureOverload::kLoad1dLevelF32:
-      return R"(textureLoad(texture, coords, level))";
-    case ValidTextureOverload::kLoad1dLevelU32:
-      return R"(textureLoad(texture, coords, level))";
-    case ValidTextureOverload::kLoad1dLevelI32:
-      return R"(textureLoad(texture, coords, level))";
-    case ValidTextureOverload::kLoad2dLevelF32:
-      return R"(textureLoad(texture, coords, level))";
-    case ValidTextureOverload::kLoad2dLevelU32:
-      return R"(textureLoad(texture, coords, level))";
-    case ValidTextureOverload::kLoad2dLevelI32:
-      return R"(textureLoad(texture, coords, level))";
-    case ValidTextureOverload::kLoad2dArrayLevelF32:
-      return R"(textureLoad(texture, coords, array_index, level))";
-    case ValidTextureOverload::kLoad2dArrayLevelU32:
-      return R"(textureLoad(texture, coords, array_index, level))";
-    case ValidTextureOverload::kLoad2dArrayLevelI32:
-      return R"(textureLoad(texture, coords, array_index, level))";
-    case ValidTextureOverload::kLoad3dLevelF32:
-      return R"(textureLoad(texture, coords, level))";
-    case ValidTextureOverload::kLoad3dLevelU32:
-      return R"(textureLoad(texture, coords, level))";
-    case ValidTextureOverload::kLoad3dLevelI32:
-      return R"(textureLoad(texture, coords, level))";
-    case ValidTextureOverload::kLoadMultisampled2dF32:
-      return R"(textureLoad(texture, coords, sample_index))";
-    case ValidTextureOverload::kLoadMultisampled2dU32:
-      return R"(textureLoad(texture, coords, sample_index))";
-    case ValidTextureOverload::kLoadMultisampled2dI32:
-      return R"(textureLoad(texture, coords, sample_index))";
-    case ValidTextureOverload::kLoadMultisampled2dArrayF32:
-      return R"(textureLoad(texture, coords, array_index, sample_index))";
-    case ValidTextureOverload::kLoadMultisampled2dArrayU32:
-      return R"(textureLoad(texture, coords, array_index, sample_index))";
-    case ValidTextureOverload::kLoadMultisampled2dArrayI32:
-      return R"(textureLoad(texture, coords, array_index, sample_index))";
-    case ValidTextureOverload::kLoadDepth2dLevelF32:
-      return R"(textureLoad(texture, coords, level))";
-    case ValidTextureOverload::kLoadDepth2dArrayLevelF32:
-      return R"(textureLoad(texture, coords, array_index, level))";
-    case ValidTextureOverload::kLoadStorageRO1dRgba32float:
-      return R"(textureLoad(texture, coords))";
-    case ValidTextureOverload::kLoadStorageRO2dRgba8unorm:
-    case ValidTextureOverload::kLoadStorageRO2dRgba8snorm:
-    case ValidTextureOverload::kLoadStorageRO2dRgba8uint:
-    case ValidTextureOverload::kLoadStorageRO2dRgba8sint:
-    case ValidTextureOverload::kLoadStorageRO2dRgba16uint:
-    case ValidTextureOverload::kLoadStorageRO2dRgba16sint:
-    case ValidTextureOverload::kLoadStorageRO2dRgba16float:
-    case ValidTextureOverload::kLoadStorageRO2dR32uint:
-    case ValidTextureOverload::kLoadStorageRO2dR32sint:
-    case ValidTextureOverload::kLoadStorageRO2dR32float:
-    case ValidTextureOverload::kLoadStorageRO2dRg32uint:
-    case ValidTextureOverload::kLoadStorageRO2dRg32sint:
-    case ValidTextureOverload::kLoadStorageRO2dRg32float:
-    case ValidTextureOverload::kLoadStorageRO2dRgba32uint:
-    case ValidTextureOverload::kLoadStorageRO2dRgba32sint:
-    case ValidTextureOverload::kLoadStorageRO2dRgba32float:
-      return R"(textureLoad(texture, coords))";
-    case ValidTextureOverload::kLoadStorageRO2dArrayRgba32float:
-      return R"(textureLoad(texture, coords, array_index))";
-    case ValidTextureOverload::kLoadStorageRO3dRgba32float:
-      return R"(textureLoad(texture, coords))";
-    case ValidTextureOverload::kStoreWO1dRgba32float:
-      return R"(textureStore(texture, coords, value))";
-    case ValidTextureOverload::kStoreWO2dRgba32float:
-      return R"(textureStore(texture, coords, value))";
-    case ValidTextureOverload::kStoreWO2dArrayRgba32float:
-      return R"(textureStore(texture, coords, array_index, value))";
-    case ValidTextureOverload::kStoreWO3dRgba32float:
-      return R"(textureStore(texture, coords, value))";
-  }
-  return "<unmatched texture overload>";
-}
-
-TEST_P(ResolverTextureIntrinsicTest, Call) {
-  auto param = GetParam();
-
-  param.buildTextureVariable(this);
-  param.buildSamplerVariable(this);
-
-  auto* call = Call(param.function, param.args(this));
-  WrapInFunction(call);
-
-  ASSERT_TRUE(r()->Resolve()) << r()->error();
-
-  if (std::string(param.function) == "textureDimensions") {
-    switch (param.texture_dimension) {
-      default:
-        FAIL() << "invalid texture dimensions: " << param.texture_dimension;
-      case type::TextureDimension::k1d:
-        EXPECT_EQ(TypeOf(call)->type_name(), ty.i32()->type_name());
-        break;
-      case type::TextureDimension::k2d:
-      case type::TextureDimension::k2dArray:
-        EXPECT_EQ(TypeOf(call)->type_name(), ty.vec2<i32>()->type_name());
-        break;
-      case type::TextureDimension::k3d:
-      case type::TextureDimension::kCube:
-      case type::TextureDimension::kCubeArray:
-        EXPECT_EQ(TypeOf(call)->type_name(), ty.vec3<i32>()->type_name());
-        break;
-    }
-  } else if (std::string(param.function) == "textureNumLayers") {
-    EXPECT_EQ(TypeOf(call), ty.i32());
-  } else if (std::string(param.function) == "textureNumLevels") {
-    EXPECT_EQ(TypeOf(call), ty.i32());
-  } else if (std::string(param.function) == "textureNumSamples") {
-    EXPECT_EQ(TypeOf(call), ty.i32());
-  } else if (std::string(param.function) == "textureStore") {
-    EXPECT_EQ(TypeOf(call), ty.void_());
-  } else {
-    switch (param.texture_kind) {
-      case ast::intrinsic::test::TextureKind::kRegular:
-      case ast::intrinsic::test::TextureKind::kMultisampled:
-      case ast::intrinsic::test::TextureKind::kStorage: {
-        auto* datatype = param.resultVectorComponentType(this);
-        ASSERT_TRUE(TypeOf(call)->Is<type::Vector>());
-        EXPECT_EQ(TypeOf(call)->As<type::Vector>()->type(), datatype);
-        break;
-      }
-      case ast::intrinsic::test::TextureKind::kDepth: {
-        EXPECT_EQ(TypeOf(call), ty.f32());
-        break;
-      }
-    }
-  }
-
-  auto* call_sem = Sem().Get(call);
-  ASSERT_NE(call_sem, nullptr);
-  auto* target = call_sem->Target();
-  ASSERT_NE(target, nullptr);
-
-  auto got = resolver::to_str(param.function, target->Parameters());
-  auto* expected = expected_texture_overload(param.overload);
-  EXPECT_EQ(got, expected);
-}
-
 }  // namespace
 }  // namespace resolver
 }  // namespace tint
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
new file mode 100644
index 0000000..1d22b05
--- /dev/null
+++ b/src/resolver/validation_test.cc
@@ -0,0 +1,530 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/ast/assignment_statement.h"
+#include "src/ast/bitcast_expression.h"
+#include "src/ast/break_statement.h"
+#include "src/ast/call_statement.h"
+#include "src/ast/continue_statement.h"
+#include "src/ast/if_statement.h"
+#include "src/ast/intrinsic_texture_helper_test.h"
+#include "src/ast/loop_statement.h"
+#include "src/ast/return_statement.h"
+#include "src/ast/stage_decoration.h"
+#include "src/ast/switch_statement.h"
+#include "src/ast/unary_op_expression.h"
+#include "src/ast/variable_decl_statement.h"
+#include "src/resolver/resolver_test_helper.h"
+#include "src/semantic/call.h"
+#include "src/semantic/function.h"
+#include "src/semantic/member_accessor_expression.h"
+#include "src/semantic/statement.h"
+#include "src/semantic/variable.h"
+#include "src/type/access_control_type.h"
+#include "src/type/sampled_texture_type.h"
+
+using ::testing::ElementsAre;
+using ::testing::HasSubstr;
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverValidationTest = ResolverTest;
+
+class FakeStmt : public ast::Statement {
+ public:
+  explicit FakeStmt(Source source) : ast::Statement(source) {}
+  FakeStmt* Clone(CloneContext*) const override { return nullptr; }
+  bool IsValid() const override { return true; }
+  void to_str(const semantic::Info&, std::ostream& out, size_t) const override {
+    out << "Fake";
+  }
+};
+
+class FakeExpr : public ast::Expression {
+ public:
+  explicit FakeExpr(Source source) : ast::Expression(source) {}
+  FakeExpr* Clone(CloneContext*) const override { return nullptr; }
+  bool IsValid() const override { return true; }
+  void to_str(const semantic::Info&, std::ostream&, size_t) const override {}
+};
+
+TEST_F(ResolverValidationTest, Error_WithEmptySource) {
+  auto* s = create<FakeStmt>();
+  WrapInFunction(s);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: unknown statement type for type determination: Fake");
+}
+
+TEST_F(ResolverValidationTest, Stmt_Error_Unknown) {
+  auto* s = create<FakeStmt>(Source{Source::Location{2, 30}});
+  WrapInFunction(s);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "2:30 error: unknown statement type for type determination: Fake");
+}
+
+TEST_F(ResolverValidationTest, Stmt_Call_undeclared) {
+  // fn main() -> void {func(); return; }
+  // fn func() -> void { return; }
+
+  SetSource(Source::Location{12, 34});
+  auto* call_expr = Call("func");
+  ast::VariableList params0;
+
+  Func("main", params0, ty.f32(),
+       ast::StatementList{
+           create<ast::CallStatement>(call_expr),
+           create<ast::ReturnStatement>(),
+       },
+       ast::FunctionDecorationList{});
+
+  Func("func", params0, ty.f32(),
+       ast::StatementList{
+           create<ast::ReturnStatement>(),
+       },
+       ast::FunctionDecorationList{});
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "12:34 error: v-0006: unable to find called function: func");
+}
+
+TEST_F(ResolverValidationTest, Stmt_Call_recursive) {
+  // fn main() -> void {main(); }
+
+  SetSource(Source::Location{12, 34});
+  auto* call_expr = Call("main");
+  ast::VariableList params0;
+
+  Func("main", params0, ty.f32(),
+       ast::StatementList{
+           create<ast::CallStatement>(call_expr),
+       },
+       ast::FunctionDecorationList{
+           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+       });
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "12:34 error: recursion is not permitted. 'main' attempted to call "
+            "itself.");
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_VariableDecl_MismatchedTypeScalarConstructor) {
+  u32 unsigned_value = 2u;  // Type does not match variable type
+  auto* var =
+      Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(unsigned_value));
+
+  auto* decl =
+      create<ast::VariableDeclStatement>(Source{{{3, 3}, {3, 22}}}, var);
+  WrapInFunction(decl);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(3:3 error: constructor expression type does not match variable type)");
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_VariableDecl_MismatchedTypeScalarConstructor_Alias) {
+  auto* my_int = ty.alias("MyInt", ty.i32());
+  u32 unsigned_value = 2u;  // Type does not match variable type
+  auto* var =
+      Var("my_var", my_int, ast::StorageClass::kNone, Expr(unsigned_value));
+
+  auto* decl =
+      create<ast::VariableDeclStatement>(Source{{{3, 3}, {3, 22}}}, var);
+  WrapInFunction(decl);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(3:3 error: constructor expression type does not match variable type)");
+}
+
+TEST_F(ResolverValidationTest, Expr_Error_Unknown) {
+  FakeExpr e(Source{Source::Location{2, 30}});
+  WrapInFunction(&e);
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "2:30 error: unknown expression for type determination");
+}
+
+TEST_F(ResolverValidationTest, Expr_DontCall_Function) {
+  Func("func", {}, ty.void_(), {}, {});
+  auto* ident = create<ast::IdentifierExpression>(
+      Source{{Source::Location{3, 3}, Source::Location{3, 8}}},
+      Symbols().Register("func"));
+  WrapInFunction(ident);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "3:8 error: missing '(' for function call");
+}
+
+TEST_F(ResolverValidationTest, Expr_DontCall_Intrinsic) {
+  auto* ident = create<ast::IdentifierExpression>(
+      Source{{Source::Location{3, 3}, Source::Location{3, 8}}},
+      Symbols().Register("round"));
+  WrapInFunction(ident);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "3:8 error: missing '(' for intrinsic call");
+}
+
+TEST_F(ResolverValidationTest, UsingUndefinedVariable_Fail) {
+  // b = 2;
+
+  SetSource(Source{Source::Location{12, 34}});
+  auto* lhs = Expr("b");
+  auto* rhs = Expr(2);
+  auto* assign = create<ast::AssignmentStatement>(lhs, rhs);
+  WrapInFunction(assign);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: v-0006: identifier must be declared before use: b");
+}
+
+TEST_F(ResolverValidationTest, UsingUndefinedVariableInBlockStatement_Fail) {
+  // {
+  //  b = 2;
+  // }
+
+  SetSource(Source{Source::Location{12, 34}});
+  auto* lhs = Expr("b");
+  auto* rhs = Expr(2);
+
+  auto* body = create<ast::BlockStatement>(ast::StatementList{
+      create<ast::AssignmentStatement>(lhs, rhs),
+  });
+  WrapInFunction(body);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: v-0006: identifier must be declared before use: b");
+}
+
+TEST_F(ResolverValidationTest, StorageClass_NonFunctionClassError) {
+  auto* var = Var("var", ty.i32(), ast::StorageClass::kWorkgroup);
+
+  auto* stmt = create<ast::VariableDeclStatement>(var);
+  Func("func", ast::VariableList{}, ty.i32(), ast::StatementList{stmt},
+       ast::FunctionDecorationList{});
+
+  EXPECT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(r()->error(),
+            "error: function variable has a non-function storage class");
+}
+
+TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadChar) {
+  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kNone);
+
+  auto* ident = create<ast::IdentifierExpression>(
+      Source{{Source::Location{3, 3}, Source::Location{3, 7}}},
+      Symbols().Register("xyqz"));
+
+  auto* mem = MemberAccessor("my_vec", ident);
+  WrapInFunction(mem);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "3:5 error: invalid vector swizzle character");
+}
+
+TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_MixedChars) {
+  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kNone);
+
+  auto* ident = create<ast::IdentifierExpression>(
+      Source{{Source::Location{3, 3}, Source::Location{3, 7}}},
+      Symbols().Register("rgyw"));
+
+  auto* mem = MemberAccessor("my_vec", ident);
+  WrapInFunction(mem);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "3:3 error: invalid mixing of vector swizzle characters rgba with xyzw");
+}
+
+TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadLength) {
+  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kNone);
+
+  auto* ident = create<ast::IdentifierExpression>(
+      Source{{Source::Location{3, 3}, Source::Location{3, 8}}},
+      Symbols().Register("zzzzz"));
+  auto* mem = MemberAccessor("my_vec", ident);
+  WrapInFunction(mem);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "3:3 error: invalid vector swizzle size");
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInLoopBodyBeforeDecl_UsageInContinuing) {
+  // loop  {
+  //     continue; // Bypasses z decl
+  //     var z : i32;
+  //
+  //     continuing {
+  //         z = 2;
+  //     }
+  // }
+
+  auto error_loc = Source{Source::Location{12, 34}};
+  auto* body = Block(create<ast::ContinueStatement>(),
+                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
+  auto* continuing = Block(Assign(Expr(error_loc, "z"), Expr(2)));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            "12:34 error: continue statement bypasses declaration of 'z' in "
+            "continuing block");
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInLoopBodyBeforeDeclAndAfterDecl_UsageInContinuing) {
+  // loop  {
+  //     continue; // Bypasses z decl
+  //     var z : i32;
+  //     continue; // Ok
+  //
+  //     continuing {
+  //         z = 2;
+  //     }
+  // }
+
+  auto error_loc = Source{Source::Location{12, 34}};
+  auto* body = Block(create<ast::ContinueStatement>(),
+                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
+                     create<ast::ContinueStatement>());
+  auto* continuing = Block(Assign(Expr(error_loc, "z"), Expr(2)));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            "12:34 error: continue statement bypasses declaration of 'z' in "
+            "continuing block");
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuing) {
+  // loop  {
+  //     if (true) {
+  //         continue; // Still bypasses z decl (if we reach here)
+  //     }
+  //     var z : i32;
+  //     continuing {
+  //         z = 2;
+  //     }
+  // }
+
+  auto error_loc = Source{Source::Location{12, 34}};
+  auto* body = Block(If(Expr(true), Block(create<ast::ContinueStatement>())),
+                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
+  auto* continuing = Block(Assign(Expr(error_loc, "z"), Expr(2)));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            "12:34 error: continue statement bypasses declaration of 'z' in "
+            "continuing block");
+}
+
+TEST_F(
+    ResolverTest,
+    Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuingSubscope) {
+  // loop  {
+  //     if (true) {
+  //         continue; // Still bypasses z decl (if we reach here)
+  //     }
+  //     var z : i32;
+  //     continuing {
+  //         if (true) {
+  //             z = 2; // Must fail even if z is in a sub-scope
+  //         }
+  //     }
+  // }
+
+  auto error_loc = Source{Source::Location{12, 34}};
+  auto* body = Block(If(Expr(true), Block(create<ast::ContinueStatement>())),
+                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
+
+  auto* continuing =
+      Block(If(Expr(true), Block(Assign(Expr(error_loc, "z"), Expr(2)))));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            "12:34 error: continue statement bypasses declaration of 'z' in "
+            "continuing block");
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuingLoop) {
+  // loop  {
+  //     if (true) {
+  //         continue; // Still bypasses z decl (if we reach here)
+  //     }
+  //     var z : i32;
+  //     continuing {
+  //         loop {
+  //             z = 2; // Must fail even if z is in a sub-scope
+  //         }
+  //     }
+  // }
+
+  auto error_loc = Source{Source::Location{12, 34}};
+  auto* body = Block(If(Expr(true), Block(create<ast::ContinueStatement>())),
+                     Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
+
+  auto* continuing = Block(Loop(Block(Assign(Expr(error_loc, "z"), Expr(2)))));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            "12:34 error: continue statement bypasses declaration of 'z' in "
+            "continuing block");
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuing) {
+  // loop  {
+  //     loop {
+  //         continue; // OK: not part of the outer loop
+  //     }
+  //     var z : i32;
+  //
+  //     continuing {
+  //         z = 2;
+  //     }
+  // }
+
+  auto* inner_loop = Loop(Block(create<ast::ContinueStatement>()));
+  auto* body =
+      Block(inner_loop, Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
+  auto* continuing = Block(Assign(Expr("z"), Expr(2)));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuingSubscope) {
+  // loop  {
+  //     loop {
+  //         continue; // OK: not part of the outer loop
+  //     }
+  //     var z : i32;
+  //
+  //     continuing {
+  //         if (true) {
+  //             z = 2;
+  //         }
+  //     }
+  // }
+
+  auto* inner_loop = Loop(Block(create<ast::ContinueStatement>()));
+  auto* body =
+      Block(inner_loop, Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
+  auto* continuing = Block(If(Expr(true), Block(Assign(Expr("z"), Expr(2)))));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest,
+       Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuingLoop) {
+  // loop  {
+  //     loop {
+  //         continue; // OK: not part of the outer loop
+  //     }
+  //     var z : i32;
+  //
+  //     continuing {
+  //         loop {
+  //             z = 2;
+  //         }
+  //     }
+  // }
+
+  auto* inner_loop = Loop(Block(create<ast::ContinueStatement>()));
+  auto* body =
+      Block(inner_loop, Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
+  auto* continuing = Block(Loop(Block(Assign(Expr("z"), Expr(2)))));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, Stmt_ContinueInLoop) {
+  WrapInFunction(Loop(Block(create<ast::ContinueStatement>(Source{{12, 34}}))));
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, Stmt_ContinueNotInLoop) {
+  WrapInFunction(create<ast::ContinueStatement>(Source{{12, 34}}));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: continue statement must be in a loop");
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInLoop) {
+  WrapInFunction(Loop(Block(create<ast::BreakStatement>(Source{{12, 34}}))));
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakInSwitch) {
+  WrapInFunction(Loop(Block(create<ast::SwitchStatement>(
+      Expr(1), ast::CaseStatementList{
+                   create<ast::CaseStatement>(
+                       ast::CaseSelectorList{Literal(1)},
+                       Block(create<ast::BreakStatement>(Source{{12, 34}}))),
+               }))));
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, Stmt_BreakNotInLoopOrSwitch) {
+  WrapInFunction(create<ast::BreakStatement>(Source{{12, 34}}));
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: break statement must be in a loop or switch case");
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint