validation: invariant attribute on struct members

Bug: tint:1008
Change-Id: If3c398b01952f6b482c60cf86ab8ddf724d385a9
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/59060
Auto-Submit: Sarah Mashayekhi <sarahmashay@google.com>
Commit-Queue: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index 6e9aa0a..48b6c8c 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -197,7 +197,6 @@
                     TestParams{DecorationKind::kBuiltin, true},
                     TestParams{DecorationKind::kGroup, false},
                     TestParams{DecorationKind::kInterpolate, true},
-                    // TODO(crbug.com/tint/1008)
                     // kInvariant tested separately (requires position builtin)
                     TestParams{DecorationKind::kLocation, true},
                     TestParams{DecorationKind::kOverride, false},
@@ -252,6 +251,34 @@
             "parameters");
 }
 
+TEST_F(EntryPointParameterDecorationTest, InvariantWithPosition) {
+  auto* param = Param("p", ty.vec4<f32>(),
+                      {Invariant(Source{{12, 34}}),
+                       Builtin(Source{{56, 78}}, ast::Builtin::kPosition)});
+  Func("main", ast::VariableList{param}, ty.vec4<f32>(),
+       ast::StatementList{Return(Construct(ty.vec4<f32>()))},
+       ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
+       ast::DecorationList{
+           Location(0),
+       });
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(EntryPointParameterDecorationTest, InvariantWithoutPosition) {
+  auto* param =
+      Param("p", ty.vec4<f32>(), {Invariant(Source{{12, 34}}), Location(0)});
+  Func("main", ast::VariableList{param}, ty.vec4<f32>(),
+       ast::StatementList{Return(Construct(ty.vec4<f32>()))},
+       ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
+       ast::DecorationList{
+           Location(0),
+       });
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: invariant attribute must only be applied to a "
+            "position builtin");
+}
+
 using FunctionReturnTypeDecorationTest = TestWithParams;
 TEST_P(FunctionReturnTypeDecorationTest, IsValid) {
   auto& params = GetParam();
@@ -359,7 +386,7 @@
 TEST_F(EntryPointReturnTypeDecorationTest, InvariantWithoutPosition) {
   Func("main", ast::VariableList{}, ty.vec4<f32>(),
        ast::StatementList{Return(Construct(ty.vec4<f32>()))},
-       ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
+       ast::DecorationList{Stage(ast::PipelineStage::kVertex)},
        ast::DecorationList{
            Invariant(Source{{12, 34}}),
            Location(Source{{56, 78}}, 0),
@@ -501,7 +528,7 @@
                     TestParams{DecorationKind::kBuiltin, true},
                     TestParams{DecorationKind::kGroup, false},
                     TestParams{DecorationKind::kInterpolate, true},
-                    TestParams{DecorationKind::kInvariant, true},
+                    // kInvariant tested separately (requires position builtin)
                     TestParams{DecorationKind::kLocation, true},
                     TestParams{DecorationKind::kOverride, false},
                     TestParams{DecorationKind::kOffset, true},
@@ -531,6 +558,34 @@
 12:34 note: first decoration declared here)");
 }
 
+TEST_F(StructMemberDecorationTest, InvariantDecorationWithPosition) {
+  Structure("mystruct", {
+                            Member("a", ty.vec4<f32>(),
+                                   {
+                                       Invariant(),
+                                       Builtin(ast::Builtin::kPosition),
+                                   }),
+                        });
+
+  WrapInFunction();
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(StructMemberDecorationTest, InvariantDecorationWithoutPosition) {
+  Structure("mystruct", {
+                            Member("a", ty.vec4<f32>(),
+                                   {
+                                       Invariant(Source{{12, 34}}),
+                                   }),
+                        });
+
+  WrapInFunction();
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: invariant attribute must only be applied to a "
+            "position builtin");
+}
+
 using VariableDecorationTest = TestWithParams;
 TEST_P(VariableDecorationTest, IsValid) {
   auto& params = GetParam();
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 844d444..b41b1bc 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -1149,7 +1149,7 @@
             deco->source());
         return false;
       }
-    } else if (!deco->IsAnyOf<ast::BuiltinDecoration,
+    } else if (!deco->IsAnyOf<ast::BuiltinDecoration, ast::InvariantDecoration,
                               ast::InternalDecoration>() &&
                (IsValidationEnabled(
                     info->declaration->decorations(),
@@ -3896,6 +3896,8 @@
       }
     }
 
+    auto has_position = false;
+    ast::InvariantDecoration* invariant_attribute = nullptr;
     for (auto* deco : member->Declaration()->decorations()) {
       if (!(deco->Is<ast::BuiltinDecoration>() ||
             deco->Is<ast::InterpolateDecoration>() ||
@@ -3908,10 +3910,16 @@
                  deco->source());
         return false;
       }
+      if (auto* invariant = deco->As<ast::InvariantDecoration>()) {
+        invariant_attribute = invariant;
+      }
       if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
         if (!ValidateBuiltinDecoration(builtin, member->Type())) {
           return false;
         }
+        if (builtin->value() == ast::Builtin::kPosition) {
+          has_position = true;
+        }
       } else if (auto* interpolate = deco->As<ast::InterpolateDecoration>()) {
         if (!ValidateInterpolateDecoration(interpolate, member->Type())) {
           return false;
@@ -3919,6 +3927,12 @@
       }
     }
 
+    if (invariant_attribute && !has_position) {
+      AddError("invariant attribute must only be applied to a position builtin",
+               invariant_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>(