Add validation rules for multisampled textures

Fixes:tint:717
Change-Id: I7f23086ec516c1196a9cdc741bbaa150754a533a
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/47940
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index dfcb456..ff7f50c 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -40,6 +40,7 @@
 #include "src/sem/call.h"
 #include "src/sem/function.h"
 #include "src/sem/member_accessor_expression.h"
+#include "src/sem/multisampled_texture_type.h"
 #include "src/sem/statement.h"
 #include "src/sem/struct.h"
 #include "src/sem/variable.h"
@@ -366,6 +367,23 @@
       return false;
     }
   }
+
+  if (auto* mst = type->UnwrapAll()->As<sem::MultisampledTexture>()) {
+    if (mst->dim() != sem::TextureDimension::k2d) {
+      diagnostics_.add_error("Only 2d multisampled textures are supported",
+                             var->source());
+      return false;
+    }
+
+    auto* data_type = mst->type()->UnwrapAll();
+    if (!data_type->is_numeric_scalar()) {
+      diagnostics_.add_error(
+          "texture_multisampled_2d<type>: type must be f32, i32 or u32",
+          var->source());
+      return false;
+    }
+  }
+
   return true;
 }
 
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index b8de796..48cf806 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -17,6 +17,7 @@
 #include "src/ast/struct_block_decoration.h"
 #include "src/resolver/resolver.h"
 #include "src/resolver/resolver_test_helper.h"
+#include "src/sem/multisampled_texture_type.h"
 
 #include "gmock/gmock.h"
 
@@ -532,6 +533,86 @@
 
 }  // namespace GetCanonicalTests
 
+namespace MultisampledTextureTests {
+struct DimensionParams {
+  sem::TextureDimension dim;
+  bool is_valid;
+};
+
+static constexpr DimensionParams dimension_cases[] = {
+    DimensionParams{sem::TextureDimension::k1d, false},
+    DimensionParams{sem::TextureDimension::k2d, true},
+    DimensionParams{sem::TextureDimension::k2dArray, false},
+    DimensionParams{sem::TextureDimension::k3d, false},
+    DimensionParams{sem::TextureDimension::kCube, false},
+    DimensionParams{sem::TextureDimension::kCubeArray, false}};
+
+using MultisampledTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
+TEST_P(MultisampledTextureDimensionTest, All) {
+  auto& params = GetParam();
+  Global("a", create<sem::MultisampledTexture>(params.dim, ty.i32()),
+         ast::StorageClass::kUniformConstant, nullptr,
+         ast::DecorationList{
+             create<ast::BindingDecoration>(0),
+             create<ast::GroupDecoration>(0),
+         });
+
+  if (params.is_valid) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+  }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
+                         MultisampledTextureDimensionTest,
+                         testing::ValuesIn(dimension_cases));
+
+struct TypeParams {
+  create_type_func_ptr type_func;
+  bool is_valid;
+};
+
+static constexpr TypeParams type_cases[] = {
+    TypeParams{ty_bool_, false},
+    TypeParams{ty_i32, true},
+    TypeParams{ty_u32, true},
+    TypeParams{ty_f32, true},
+
+    TypeParams{ty_alias<ty_bool_>, false},
+    TypeParams{ty_alias<ty_i32>, true},
+    TypeParams{ty_alias<ty_u32>, true},
+    TypeParams{ty_alias<ty_f32>, true},
+
+    TypeParams{ty_vec3<ty_f32>, false},
+    TypeParams{ty_mat3x3<ty_f32>, false},
+
+    TypeParams{ty_alias<ty_vec3<ty_f32>>, false},
+    TypeParams{ty_alias<ty_mat3x3<ty_f32>>, false}};
+
+using MultisampledTextureTypeTest = ResolverTestWithParam<TypeParams>;
+TEST_P(MultisampledTextureTypeTest, All) {
+  auto& params = GetParam();
+  Global("a",
+         create<sem::MultisampledTexture>(sem::TextureDimension::k2d,
+                                          params.type_func(ty)),
+         ast::StorageClass::kUniformConstant, nullptr,
+         ast::DecorationList{
+             create<ast::BindingDecoration>(0),
+             create<ast::GroupDecoration>(0),
+         });
+
+  if (params.is_valid) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve());
+  }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
+                         MultisampledTextureTypeTest,
+                         testing::ValuesIn(type_cases));
+
+}  // namespace MultisampledTextureTests
+
 }  // namespace
 }  // namespace resolver
 }  // namespace tint
diff --git a/src/sem/type.cc b/src/sem/type.cc
index 537bec1..947b149 100644
--- a/src/sem/type.cc
+++ b/src/sem/type.cc
@@ -74,6 +74,10 @@
   return IsAnyOf<F32, U32, I32, Bool>();
 }
 
+bool Type::is_numeric_scalar() const {
+  return IsAnyOf<F32, U32, I32>();
+}
+
 bool Type::is_float_scalar() const {
   return Is<F32>();
 }
diff --git a/src/sem/type.h b/src/sem/type.h
index 8a7137c..870bc16 100644
--- a/src/sem/type.h
+++ b/src/sem/type.h
@@ -69,6 +69,8 @@
 
   /// @returns true if this type is a scalar
   bool is_scalar() const;
+  /// @returns true if this type is a numeric scalar
+  bool is_numeric_scalar() const;
   /// @returns true if this type is a float scalar
   bool is_float_scalar() const;
   /// @returns true if this type is a float matrix