validation: Reject struct builtins on wrong stages

Change-Id: I047f28c5be6a7d878b9f7ad37b6accc842b4fe98
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/65840
Auto-Submit: James Price <jrprice@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/resolver/builtins_validation_test.cc b/src/resolver/builtins_validation_test.cc
index e29b6f2..733093a 100644
--- a/src/resolver/builtins_validation_test.cc
+++ b/src/resolver/builtins_validation_test.cc
@@ -217,7 +217,7 @@
             "fragment pipeline stage");
 }
 
-TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInputStruct_Ignored) {
+TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInputStruct_Fail) {
   // struct MyInputs {
   //   [[builtin(frag_depth)]] ff: f32;
   // };
@@ -231,8 +231,28 @@
 
   Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
        {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: builtin(frag_depth) cannot be used in input of "
+            "fragment pipeline stage\n"
+            "note: while analysing entry point 'fragShader'");
+}
+
+TEST_F(ResolverBuiltinsValidationTest, StructBuiltinInsideEntryPoint_Ignored) {
+  // struct S {
+  //   [[builtin(vertex_index)]] idx: u32;
+  // };
+  // [[stage(fragment)]]
+  // fn fragShader() { var s : S; }
+
+  Structure("S",
+            {Member("idx", ty.u32(), {Builtin(ast::Builtin::kVertexIndex)})});
+
+  Func("fragShader", {}, ty.void_(), {Decl(Var("s", ty.type_name("S")))},
+       {Stage(ast::PipelineStage::kFragment)});
   EXPECT_TRUE(r()->Resolve());
 }
+
 }  // namespace StageTest
 
 TEST_F(ResolverBuiltinsValidationTest, PositionNotF32_Struct_Fail) {
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 9617d5d..dd9904c 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -1277,8 +1277,7 @@
 
 bool Resolver::ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
                                          const sem::Type* storage_type,
-                                         const bool is_input,
-                                         const bool is_struct_member) {
+                                         const bool is_input) {
   auto* type = storage_type->UnwrapRef();
   const auto stage = current_function_
                          ? current_function_->declaration->pipeline_stage()
@@ -1386,16 +1385,12 @@
       break;
   }
 
-  // ignore builtin attribute on struct members to facillate data movement
-  // between stages
-  if (!is_struct_member) {
-    if (is_stage_mismatch) {
-      AddError(deco_to_str(deco) + " cannot be used in " +
-                   (is_input ? "input of " : "output of ") + stage_name.str() +
-                   " pipeline stage",
-               deco->source());
-      return false;
-    }
+  if (is_stage_mismatch) {
+    AddError(deco_to_str(deco) + " cannot be used in " +
+                 (is_input ? "input of " : "output of ") + stage_name.str() +
+                 " pipeline stage",
+             deco->source());
+    return false;
   }
 
   return true;
@@ -1556,10 +1551,9 @@
               return false;
             }
 
-            if (!ValidateBuiltinDecoration(
-                    builtin, ty,
-                    /* is_input */ param_or_ret == ParamOrRetType::kParameter,
-                    /* is_struct_member */ is_struct_member)) {
+            if (!ValidateBuiltinDecoration(builtin, ty,
+                                           /* is_input */ param_or_ret ==
+                                               ParamOrRetType::kParameter)) {
               return false;
             }
             builtins.emplace(builtin->value());
@@ -4098,8 +4092,7 @@
         }
       } else if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
         if (!ValidateBuiltinDecoration(builtin, member->Type(),
-                                       /* is_input */ false,
-                                       /* is_struct_member */ true)) {
+                                       /* is_input */ false)) {
           return false;
         }
         if (builtin->value() == ast::Builtin::kPosition) {
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 18965be..3b05f2c 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -283,8 +283,7 @@
   bool ValidateAssignment(const ast::AssignmentStatement* a);
   bool ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
                                  const sem::Type* storage_type,
-                                 const bool is_input,
-                                 const bool is_struct_member);
+                                 const bool is_input);
   bool ValidateCall(ast::CallExpression* call);
   bool ValidateCallStatement(ast::CallStatement* stmt);
   bool ValidateEntryPoint(const ast::Function* func, const FunctionInfo* info);
diff --git a/src/resolver/struct_pipeline_stage_use_test.cc b/src/resolver/struct_pipeline_stage_use_test.cc
index 7c0c022..9cd4871 100644
--- a/src/resolver/struct_pipeline_stage_use_test.cc
+++ b/src/resolver/struct_pipeline_stage_use_test.cc
@@ -140,8 +140,8 @@
   auto* s = Structure(
       "S", {Member("a", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
 
-  Func("vert_main", {Param("param", ty.Of(s))}, ty.Of(s),
-       {Return(Construct(ty.Of(s)))}, {Stage(ast::PipelineStage::kVertex)});
+  Func("vert_main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
+       {Stage(ast::PipelineStage::kVertex)});
 
   Func("frag_main", {Param("param", ty.Of(s))}, ty.void_(), {},
        {Stage(ast::PipelineStage::kFragment)});
@@ -151,8 +151,7 @@
   auto* sem = TypeOf(s)->As<sem::Struct>();
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->PipelineStageUses(),
-              UnorderedElementsAre(sem::PipelineStageUsage::kVertexInput,
-                                   sem::PipelineStageUsage::kVertexOutput,
+              UnorderedElementsAre(sem::PipelineStageUsage::kVertexOutput,
                                    sem::PipelineStageUsage::kFragmentInput));
 }