validation: Remove requirement for block attribute

Replace all validation rules that rely on the block attribute with the
new rules based on fixed-footprint types.

Bug: tint:1324
Change-Id: I02656537bee66e6e1af95875e503a37bf23d4a6b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/72081
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index 1f41c7a..8f043d6 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -963,41 +963,6 @@
 }  // namespace
 }  // namespace ArrayStrideTests
 
-namespace StructBlockTests {
-namespace {
-
-using StructBlockTest = ResolverTest;
-TEST_F(StructBlockTest, StructUsedAsArrayElement) {
-  auto* s = Structure("S", {Member("x", ty.i32())},
-                      {create<ast::StructBlockDecoration>()});
-  auto* a = ty.array(ty.Of(s), 4);
-  Global("G", a, ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "error: A structure type with a [[block]] decoration cannot be "
-            "used as an element of an array");
-}
-
-TEST_F(StructBlockTest, StructWithNestedBlockMember_Invalid) {
-  auto* inner =
-      Structure("Inner", {Member("x", ty.i32())},
-                {create<ast::StructBlockDecoration>(Source{{56, 78}})});
-
-  auto* outer =
-      Structure("Outer", {Member(Source{{12, 34}}, "y", ty.Of(inner))});
-
-  Global("G", ty.Of(outer), ast::StorageClass::kPrivate);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: structs must not contain [[block]] decorated struct members
-56:78 note: see member's struct decoration here)");
-}
-}  // namespace
-}  // namespace StructBlockTests
-
 namespace ResourceTests {
 namespace {
 
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index d211dde..65820f9 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -43,7 +43,6 @@
 #include "src/ast/sampled_texture.h"
 #include "src/ast/sampler.h"
 #include "src/ast/storage_texture.h"
-#include "src/ast/struct_block_decoration.h"
 #include "src/ast/switch_statement.h"
 #include "src/ast/traverse_expressions.h"
 #include "src/ast/type_name.h"
@@ -2707,6 +2706,34 @@
                        sem::Struct>();
 }
 
+// https://gpuweb.github.io/gpuweb/wgsl/#fixed-footprint-types
+bool Resolver::IsFixedFootprint(const sem::Type* type) const {
+  if (type->is_scalar()) {
+    return true;
+  }
+  if (type->Is<sem::Vector>()) {
+    return true;
+  }
+  if (type->Is<sem::Matrix>()) {
+    return true;
+  }
+  if (type->Is<sem::Atomic>()) {
+    return true;
+  }
+  if (auto* arr = type->As<sem::Array>()) {
+    return !arr->IsRuntimeSized() && IsFixedFootprint(arr->ElemType());
+  }
+  if (auto* str = type->As<sem::Struct>()) {
+    for (auto* member : str->Members()) {
+      if (!IsFixedFootprint(member->Type())) {
+        return false;
+      }
+    }
+    return true;
+  }
+  return false;
+}
+
 // https://gpuweb.github.io/gpuweb/wgsl.html#storable-types
 bool Resolver::IsStorable(const sem::Type* type) const {
   return IsPlain(type) || type->IsAnyOf<sem::Texture, sem::Sampler>();
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index ff4cf96..8aed028 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -93,6 +93,10 @@
   bool IsPlain(const sem::Type* type) const;
 
   /// @param type the given type
+  /// @returns true if the given type is a fixed-footprint type
+  bool IsFixedFootprint(const sem::Type* type) const;
+
+  /// @param type the given type
   /// @returns true if the given type is storable
   bool IsStorable(const sem::Type* type) const;
 
diff --git a/src/resolver/resolver_validation.cc b/src/resolver/resolver_validation.cc
index 917c8db..1d1199a 100644
--- a/src/resolver/resolver_validation.cc
+++ b/src/resolver/resolver_validation.cc
@@ -43,7 +43,6 @@
 #include "src/ast/sampled_texture.h"
 #include "src/ast/sampler.h"
 #include "src/ast/storage_texture.h"
-#include "src/ast/struct_block_decoration.h"
 #include "src/ast/switch_statement.h"
 #include "src/ast/traverse_expressions.h"
 #include "src/ast/type_name.h"
@@ -541,7 +540,6 @@
       // attribute, satisfying the storage class constraints.
 
       auto* str = var->Type()->UnwrapRef()->As<sem::Struct>();
-
       if (!str) {
         AddError(
             "variables declared in the <storage> storage class must be of a "
@@ -549,17 +547,6 @@
             decl->source);
         return false;
       }
-
-      if (!str->IsBlockDecorated()) {
-        AddError(
-            "structure used as a storage buffer must be declared with the "
-            "[[block]] decoration",
-            str->Declaration()->source);
-        if (decl->source.range.begin.line) {
-          AddNote("structure used as storage buffer here", decl->source);
-        }
-        return false;
-      }
       break;
     }
     case ast::StorageClass::kUniform: {
@@ -575,18 +562,6 @@
             decl->source);
         return false;
       }
-
-      if (!str->IsBlockDecorated()) {
-        AddError(
-            "structure used as a uniform buffer must be declared with the "
-            "[[block]] decoration",
-            str->Declaration()->source);
-        if (decl->source.range.begin.line) {
-          AddNote("structure used as uniform buffer here", decl->source);
-        }
-        return false;
-      }
-
       for (auto* member : str->Members()) {
         if (auto* arr = member->Type()->As<sem::Array>()) {
           if (arr->IsRuntimeSized()) {
@@ -2050,18 +2025,10 @@
 bool Resolver::ValidateArray(const sem::Array* arr, const Source& source) {
   auto* el_ty = arr->ElemType();
 
-  if (auto* el_str = el_ty->As<sem::Struct>()) {
-    if (el_str->IsBlockDecorated()) {
-      // https://gpuweb.github.io/gpuweb/wgsl/#attributes
-      // A structure type with the block attribute must not be:
-      // * the element type of an array type
-      // * the member type in another structure
-      AddError(
-          "A structure type with a [[block]] decoration cannot be used as an "
-          "element of an array",
-          source);
-      return false;
-    }
+  if (!IsFixedFootprint(el_ty)) {
+    AddError("an array element type cannot contain a runtime-sized array",
+             source);
+    return false;
   }
   return true;
 }
@@ -2123,15 +2090,13 @@
               member->Declaration()->source);
           return false;
         }
-        if (!str->IsBlockDecorated()) {
-          AddError(
-              "a struct containing a runtime-sized array "
-              "requires the [[block]] attribute: '" +
-                  builder_->Symbols().NameFor(str->Declaration()->name) + "'",
-              member->Declaration()->source);
-          return false;
-        }
       }
+    } else if (!IsFixedFootprint(member->Type())) {
+      AddError(
+          "a struct that contains a runtime array cannot be nested inside "
+          "another struct",
+          member->Declaration()->source);
+      return false;
     }
 
     auto has_location = false;
@@ -2192,18 +2157,6 @@
                interpolate_attribute->source);
       return false;
     }
-
-    if (auto* member_struct_type = member->Type()->As<sem::Struct>()) {
-      if (auto* member_struct_type_block_decoration =
-              ast::GetDecoration<ast::StructBlockDecoration>(
-                  member_struct_type->Declaration()->decorations)) {
-        AddError("structs must not contain [[block]] decorated struct members",
-                 member->Declaration()->source);
-        AddNote("see member's struct decoration here",
-                member_struct_type_block_decoration->source);
-        return false;
-      }
-    }
   }
 
   for (auto* deco : str->Declaration()->decorations) {
diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc
index f902a9e..9391edc 100644
--- a/src/resolver/storage_class_validation_test.cc
+++ b/src/resolver/storage_class_validation_test.cc
@@ -112,25 +112,6 @@
       R"(56:78 error: only variables in <storage> storage class may declare an access mode)");
 }
 
-TEST_F(ResolverStorageClassValidationTest, StorageBufferNoBlockDecoration) {
-  // struct S { x : i32 };
-  // var<storage, read> g : S;
-  auto* s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())});
-  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
-         ast::Access::kRead,
-         ast::DecorationList{
-             create<ast::BindingDecoration>(0),
-             create<ast::GroupDecoration>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: structure used as a storage buffer must be declared with the [[block]] decoration
-56:78 note: structure used as storage buffer here)");
-}
-
 TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Basic) {
   // [[block]] struct S { x : i32 };
   // var<storage, read> g : S;
@@ -249,24 +230,6 @@
 56:78 note: while instantiating variable g)");
 }
 
-TEST_F(ResolverStorageClassValidationTest, UniformBufferNoBlockDecoration) {
-  // struct S { x : i32 };
-  // var<uniform> g : S;
-  auto* s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())});
-  Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kUniform,
-         ast::DecorationList{
-             create<ast::BindingDecoration>(0),
-             create<ast::GroupDecoration>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: structure used as a uniform buffer must be declared with the [[block]] decoration
-56:78 note: structure used as uniform buffer here)");
-}
-
 TEST_F(ResolverStorageClassValidationTest, UniformBufferNoError_Basic) {
   // [[block]] struct S { x : i32 };
   // var<uniform> g :  S;
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index 82037b3..aa9a7be 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -499,23 +499,51 @@
   EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverTypeValidationTest, RuntimeArrayIsLastNoBlock_Fail) {
+TEST_F(ResolverTypeValidationTest, RuntimeArrayInArray) {
   // struct Foo {
-  //   vf: f32;
-  //   rt: array<f32>;
+  //   rt : array<array<f32>, 4>;
   // };
 
-  Structure("Foo", {
-                       Member("vf", ty.f32()),
-                       Member(Source{{12, 34}}, "rt", ty.array<f32>()),
-                   });
-
-  WrapInFunction();
+  Structure("Foo",
+            {Member("rt", ty.array(Source{{12, 34}}, ty.array<f32>(), 4))});
 
   EXPECT_FALSE(r()->Resolve()) << r()->error();
   EXPECT_EQ(r()->error(),
-            "12:34 error: a struct containing a runtime-sized array requires "
-            "the [[block]] attribute: 'Foo'");
+            "12:34 error: an array element type cannot contain a runtime-sized "
+            "array");
+}
+
+TEST_F(ResolverTypeValidationTest, RuntimeArrayInStructInArray) {
+  // struct Foo {
+  //   rt : array<f32>;
+  // };
+  // var<private> a : array<Foo, 4>;
+
+  auto* foo = Structure("Foo", {Member("rt", ty.array<f32>())});
+  Global("v", ty.array(Source{{12, 34}}, ty.Of(foo), 4),
+         ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            "12:34 error: an array element type cannot contain a runtime-sized "
+            "array");
+}
+
+TEST_F(ResolverTypeValidationTest, RuntimeArrayInStructInStruct) {
+  // struct Foo {
+  //   rt : array<f32>;
+  // };
+  // struct Outer {
+  //   inner : Foo;
+  // };
+
+  auto* foo = Structure("Foo", {Member("rt", ty.array<f32>())});
+  Structure("Outer", {Member(Source{{12, 34}}, "inner", ty.Of(foo))});
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(),
+            "12:34 error: a struct that contains a runtime array cannot be "
+            "nested inside another struct");
 }
 
 TEST_F(ResolverTypeValidationTest, RuntimeArrayIsNotLast_Fail) {