validation: disallow non-constructible assignments

The storage type of an assignment has to be constructible, which
prevents stores to atomic or runtime-sized array types.

Change-Id: Ie7bb703bb4c6953a4ddf0286f39d0d3e17b5b1d2
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/61801
Kokoro: Kokoro <noreply+kokoro@google.com>
Auto-Submit: James Price <jrprice@google.com>
Reviewed-by: Sarah Mashayekhi <sarahmashay@google.com>
diff --git a/src/resolver/assignment_validation_test.cc b/src/resolver/assignment_validation_test.cc
index 0e0ad42..580e552 100644
--- a/src/resolver/assignment_validation_test.cc
+++ b/src/resolver/assignment_validation_test.cc
@@ -177,7 +177,7 @@
             "12:34 error: cannot assign to const\nnote: 'a' is declared here:");
 }
 
-TEST_F(ResolverAssignmentValidationTest, AssignNonStorable_Fail) {
+TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_Handle) {
   // var a : texture_storage_1d<rgba8unorm, read>;
   // var b : texture_storage_1d<rgba8unorm, read>;
   // a = b;
@@ -203,8 +203,51 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "56:78 error: cannot store into a read-only type "
-            "'texture_storage_1d<rgba8unorm, read>'");
+            "56:78 error: storage type of assignment must be constructible");
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_Atomic) {
+  // [[block]] struct S { a : atomic<i32>; };
+  // [[group(0), binding(0)]] var<storage, read_write> v : S;
+  // v.a = v.a;
+
+  auto* s = Structure("S", {Member("a", ty.atomic(ty.i32()))},
+                      {create<ast::StructBlockDecoration>()});
+  Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
+         ast::DecorationList{
+             create<ast::BindingDecoration>(0),
+             create<ast::GroupDecoration>(0),
+         });
+
+  WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("v", "a"),
+                        MemberAccessor("v", "a")));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "56:78 error: storage type of assignment must be constructible");
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_RuntimeArray) {
+  // [[block]] struct S { a : array<f32>; };
+  // [[group(0), binding(0)]] var<storage, read_write> v : S;
+  // v.a = v.a;
+
+  auto* s = Structure("S", {Member("a", ty.array(ty.f32()))},
+                      {create<ast::StructBlockDecoration>()});
+  Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage,
+         ast::Access::kReadWrite,
+         ast::DecorationList{
+             create<ast::BindingDecoration>(0),
+             create<ast::GroupDecoration>(0),
+         });
+
+  WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("v", "a"),
+                        MemberAccessor("v", "a")));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "56:78 error: storage type of assignment must be constructible");
 }
 
 }  // namespace
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index b8fca54..be8e45a 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -4330,6 +4330,10 @@
              a->source());
     return false;
   }
+  if (!storage_type->IsConstructible()) {
+    AddError("storage type of assignment must be constructible", a->source());
+    return false;
+  }
   if (lhs_ref->Access() == ast::Access::kRead) {
     AddError(
         "cannot store into a read-only type '" + TypeNameOf(a->lhs()) + "'",