validation: validate 'location' attribute when applied to struct members

- 'location' attribute must only be applied to declarations of numeric scalar or numeric vector type
- 'location' attribute is not valid for compute shader
-  locations must not overlap

Bug: tint:1035
Change-Id: I0ba301996f390c8206192d2f81e787e0eac0aa6a
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/59760
Kokoro: Kokoro <noreply+kokoro@google.com>
Auto-Submit: Sarah Mashayekhi <sarahmashay@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/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index 03639c7..9786ff6 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -1143,74 +1143,6 @@
 }  // namespace
 }  // namespace ResourceTests
 
-namespace LocationDecorationTests {
-namespace {
-using LocationDecorationTests = ResolverTest;
-TEST_F(LocationDecorationTests, ComputeShaderLocation_Input) {
-  Func("main", {}, ty.i32(), {Return(Expr(1))},
-       {Stage(ast::PipelineStage::kCompute),
-        create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))},
-       ast::DecorationList{Location(Source{{12, 34}}, 1)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: decoration is not valid for compute shader output");
-}
-
-TEST_F(LocationDecorationTests, ComputeShaderLocation_Output) {
-  auto* input = Param("input", ty.i32(),
-                      ast::DecorationList{Location(Source{{12, 34}}, 0u)});
-  Func("main", {input}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: decoration is not valid for compute shader inputs");
-}
-
-TEST_F(LocationDecorationTests, ComputeShaderLocationStructMember_Output) {
-  auto* m = Member("m", ty.i32(),
-                   ast::DecorationList{Location(Source{{12, 34}}, 0u)});
-  auto* s = Structure("S", {m});
-  Func(Source{{56, 78}}, "main", {}, ty.Of(s),
-       ast::StatementList{Return(Expr(Construct(ty.Of(s))))},
-       {Stage(ast::PipelineStage::kCompute),
-        create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: decoration is not valid for compute shader output\n"
-            "56:78 note: while analysing entry point 'main'");
-}
-
-TEST_F(LocationDecorationTests, ComputeShaderLocationStructMember_Input) {
-  auto* m = Member("m", ty.i32(),
-                   ast::DecorationList{Location(Source{{12, 34}}, 0u)});
-  auto* s = Structure("S", {m});
-  auto* input = Param("input", ty.Of(s));
-  Func(Source{{56, 78}}, "main", {input}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kCompute),
-        create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: decoration is not valid for compute shader inputs\n"
-            "56:78 note: while analysing entry point 'main'");
-}
-
-TEST_F(LocationDecorationTests, BadType) {
-  auto* p = Param(Source{{12, 34}}, "a", ty.mat2x2<f32>(), {Location(0)});
-  Func("frag_main", {p}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: User defined entry point IO types must be a numeric "
-            "scalar, a numeric vector, or a structure");
-}
-}  // namespace
-}  // namespace LocationDecorationTests
 
 namespace InvariantDecorationTests {
 namespace {
diff --git a/src/resolver/entry_point_validation_test.cc b/src/resolver/entry_point_validation_test.cc
index 27c4dd6..6b07e91 100644
--- a/src/resolver/entry_point_validation_test.cc
+++ b/src/resolver/entry_point_validation_test.cc
@@ -100,26 +100,6 @@
 13:43 note: previously consumed location(0))");
 }
 
-TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Struct) {
-  // struct Output {
-  //   a : f32;
-  // };
-  // [[stage(vertex)]]
-  // fn main() -> [[location(0)]] Output {
-  //   return Output();
-  // }
-  auto* output = Structure("Output", {Member("a", ty.f32())});
-  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
-       {Return(Construct(ty.Of(output)))}, {Stage(ast::PipelineStage::kVertex)},
-       {Location(Source{{13, 43}}, 0)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "13:43 error: entry point IO attributes must not be used on structure "
-      "return types");
-}
-
 TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_Valid) {
   // struct Output {
   //   [[location(0)]] a : f32;
@@ -186,53 +166,6 @@
 12:34 note: while analysing entry point 'main')");
 }
 
-TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_NestedStruct) {
-  // struct Inner {
-  //   [[location(0)]] b : f32;
-  // };
-  // struct Output {
-  //   [[location(0)]] a : Inner;
-  // };
-  // [[stage(fragment)]]
-  // fn main() -> Output {
-  //   return Output();
-  // }
-  auto* inner = Structure(
-      "Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
-  auto* output = Structure(
-      "Output", {Member(Source{{14, 52}}, "a", ty.Of(inner), {Location(0)})});
-  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
-       {Return(Construct(ty.Of(output)))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(14:52 error: entry point IO types cannot contain nested structures
-12:34 note: while analysing entry point 'main')");
-}
-
-TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_RuntimeArray) {
-  // [[block]]
-  // struct Output {
-  //   [[location(0)]] a : array<f32>;
-  // };
-  // [[stage(fragment)]]
-  // fn main() -> Output {
-  //   return Output();
-  // }
-  auto* output = Structure(
-      "Output",
-      {Member(Source{{13, 43}}, "a", ty.array<float>(), {Location(0)})},
-      {create<ast::StructBlockDecoration>()});
-  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
-       {Return(Construct(ty.Of(output)))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            R"(error: function return type must be a constructible type)");
-}
 
 TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateBuiltins) {
   // struct Output {
@@ -257,28 +190,6 @@
 12:34 note: while analysing entry point 'main')");
 }
 
-TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateLocation) {
-  // struct Output {
-  //   [[location(1)]] a : f32;
-  //   [[location(1)]] b : f32;
-  // };
-  // [[stage(fragment)]]
-  // fn main() -> Output {
-  //   return Output();
-  // }
-  auto* output = Structure("Output", {Member("a", ty.f32(), {Location(1)}),
-                                      Member("b", ty.f32(), {Location(1)})});
-  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
-       {Return(Construct(ty.Of(output)))},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: location(1) attribute appears multiple times as pipeline output
-12:34 note: while analysing entry point 'main')");
-}
-
 TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Location) {
   // [[stage(fragment)]]
   // fn main([[location(0)]] param : f32) {}
@@ -315,24 +226,6 @@
 13:43 note: previously consumed location(0))");
 }
 
-TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Struct) {
-  // struct Input {
-  //   a : f32;
-  // };
-  // [[stage(fragment)]]
-  // fn main([[location(0)]] param : Input) {}
-  auto* input = Structure("Input", {Member("a", ty.f32())});
-  auto* param = Param("param", ty.Of(input), {Location(Source{{13, 43}}, 0)});
-  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "13:43 error: entry point IO attributes must not be used on structure "
-      "parameters");
-}
-
 TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_Valid) {
   // struct Input {
   //   [[location(0)]] a : f32;
@@ -392,51 +285,6 @@
 12:34 note: while analysing entry point 'main')");
 }
 
-TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_NestedStruct) {
-  // struct Inner {
-  //   [[location(0)]] b : f32;
-  // };
-  // struct Input {
-  //   [[location(0)]] a : Inner;
-  // };
-  // [[stage(fragment)]]
-  // fn main(param : Input) {}
-  auto* inner = Structure(
-      "Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
-  auto* input = Structure(
-      "Input", {Member(Source{{14, 52}}, "a", ty.Of(inner), {Location(0)})});
-  auto* param = Param("param", ty.Of(input));
-  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(14:52 error: entry point IO types cannot contain nested structures
-12:34 note: while analysing entry point 'main')");
-}
-
-TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_RuntimeArray) {
-  // [[block]]
-  // struct Input {
-  //   [[location(0)]] a : array<f32>;
-  // };
-  // [[stage(fragment)]]
-  // fn main(param : Input) {}
-  auto* input = Structure(
-      "Input",
-      {Member(Source{{13, 43}}, "a", ty.array<float>(), {Location(0)})},
-      {create<ast::StructBlockDecoration>()});
-  auto* param = Param("param", ty.Of(input));
-  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(error: store type of function parameter must be a constructible type)");
-}
-
 TEST_F(ResolverEntryPointValidationTest, Parameter_DuplicateBuiltins) {
   // [[stage(fragment)]]
   // fn main([[builtin(sample_index)]] param_a : u32,
@@ -480,44 +328,6 @@
 12:34 note: while analysing entry point 'main')");
 }
 
-TEST_F(ResolverEntryPointValidationTest, Parameter_DuplicateLocation) {
-  // [[stage(fragment)]]
-  // fn main([[location(1)]] param_a : f32,
-  //         [[location(1)]] param_b : f32) {}
-  auto* param_a = Param("param_a", ty.u32(), {Location(1)});
-  auto* param_b = Param("param_b", ty.u32(), {Location(1)});
-  Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: location(1) attribute appears multiple times as "
-            "pipeline input");
-}
-
-TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_DuplicateLocation) {
-  // struct InputA {
-  //   [[location(1)]] a : f32;
-  // };
-  // struct InputB {
-  //   [[location(1)]] a : f32;
-  // };
-  // [[stage(fragment)]]
-  // fn main(param_a : InputA, param_b : InputB) {}
-  auto* input_a = Structure("InputA", {Member("a", ty.f32(), {Location(1)})});
-  auto* input_b = Structure("InputB", {Member("a", ty.f32(), {Location(1)})});
-  auto* param_a = Param("param_a", ty.Of(input_a));
-  auto* param_b = Param("param_b", ty.Of(input_b));
-  Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
-       {Stage(ast::PipelineStage::kFragment)});
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      R"(12:34 error: location(1) attribute appears multiple times as pipeline input
-12:34 note: while analysing entry point 'main')");
-}
-
 TEST_F(ResolverEntryPointValidationTest, VertexShaderMustReturnPosition) {
   // [[stage(vertex)]]
   // fn main() {}
@@ -639,6 +449,356 @@
 
 }  // namespace TypeValidationTests
 
+namespace LocationDecorationTests {
+namespace {
+using LocationDecorationTests = ResolverTest;
+
+TEST_F(LocationDecorationTests, Pass) {
+  // [[stage(fragment)]]
+  // fn frag_main([[location(0)]] a: i32) {}
+
+  auto* p = Param(Source{{12, 34}}, "a", ty.i32(), {Location(0)});
+  Func("frag_main", {p}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(LocationDecorationTests, BadType_Input_bool) {
+  // [[stage(fragment)]]
+  // fn frag_main([[location(0)]] a: bool) {}
+
+  auto* p =
+      Param(Source{{12, 34}}, "a", ty.bool_(), {Location(Source{{34, 56}}, 0)});
+  Func("frag_main", {p}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot apply 'location' attribute to declaration of "
+            "type 'bool'\n"
+            "34:56 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationDecorationTests, BadType_Output_Array) {
+  // [[stage(fragment)]]
+  // fn frag_main()->[[location(0)]] array<f32, 2> { return array<f32, 2>(); }
+
+  Func(Source{{12, 34}}, "frag_main", {}, ty.array<f32, 2>(),
+       {Return(Construct(ty.array<f32, 2>()))},
+       {Stage(ast::PipelineStage::kFragment)}, {Location(Source{{34, 56}}, 0)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot apply 'location' attribute to declaration of "
+            "type 'array<f32, 2>'\n"
+            "34:56 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationDecorationTests, BadType_Input_Struct) {
+  // struct Input {
+  //   a : f32;
+  // };
+  // [[stage(fragment)]]
+  // fn main([[location(0)]] param : Input) {}
+  auto* input = Structure("Input", {Member("a", ty.f32())});
+  auto* param = Param(Source{{12, 34}}, "param", ty.Of(input),
+                      {Location(Source{{13, 43}}, 0)});
+  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot apply 'location' attribute to declaration of "
+            "type 'Input'\n"
+            "13:43 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationDecorationTests, BadType_Input_Struct_NestedStruct) {
+  // struct Inner {
+  //   [[location(0)]] b : f32;
+  // };
+  // struct Input {
+  //   [[location(0)]] a : Inner;
+  // };
+  // [[stage(fragment)]]
+  // fn main(param : Input) {}
+  auto* inner = Structure(
+      "Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
+  auto* input = Structure("Input", {Member(Source{{14, 52}}, "a", ty.Of(inner),
+                                           {Location(Source{{12, 34}}, 0)})});
+  auto* param = Param("param", ty.Of(input));
+  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "14:52 error: cannot apply 'location' attribute to declaration of "
+            "type 'Inner'\n"
+            "12:34 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationDecorationTests, BadType_Input_Struct_RuntimeArray) {
+  // [[block]]
+  // struct Input {
+  //   [[location(0)]] a : array<f32>;
+  // };
+  // [[stage(fragment)]]
+  // fn main(param : Input) {}
+  auto* input = Structure(
+      "Input",
+      {Member(Source{{13, 43}}, "a", ty.array<float>(), {Location(0)})},
+      {create<ast::StructBlockDecoration>()});
+  auto* param = Param("param", ty.Of(input));
+  Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "13:43 error: cannot apply 'location' attribute to declaration of "
+            "type 'array<f32>'\n"
+            "note: 'location' attribute must only be applied to declarations "
+            "of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationDecorationTests, BadMemberType_Input) {
+  // [[block]]
+  // struct S { [[location(0)]] m: array<i32>; };
+  // [[stage(fragment)]]
+  // fn frag_main( a: S) {}
+
+  auto* m = Member(Source{{34, 56}}, "m", ty.array<i32>(),
+                   ast::DecorationList{Location(Source{{12, 34}}, 0u)});
+  auto* s = Structure("S", {m}, ast::DecorationList{StructBlock()});
+  auto* p = Param("a", ty.Of(s));
+
+  Func("frag_main", {p}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "34:56 error: cannot apply 'location' attribute to declaration of "
+            "type 'array<i32>'\n"
+            "12:34 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationDecorationTests, BadMemberType_Output) {
+  // struct S { [[location(0)]] m: atomic<i32>; };
+  // [[stage(fragment)]]
+  // fn frag_main() -> S {}
+  auto* m = Member(Source{{34, 56}}, "m", ty.atomic<i32>(),
+                   ast::DecorationList{Location(Source{{12, 34}}, 0u)});
+  auto* s = Structure("S", {m});
+
+  Func("frag_main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
+       {Stage(ast::PipelineStage::kFragment)}, {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "34:56 error: cannot apply 'location' attribute to declaration of "
+            "type 'atomic<i32>'\n"
+            "12:34 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationDecorationTests, BadMemberType_Unused) {
+  // struct S { [[location(0)]] m: mat3x2<f32>; };
+
+  auto* m = Member(Source{{34, 56}}, "m", ty.mat3x2<f32>(),
+                   ast::DecorationList{Location(Source{{12, 34}}, 0u)});
+  Structure("S", {m});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "34:56 error: cannot apply 'location' attribute to declaration of "
+            "type 'mat3x2<f32>'\n"
+            "12:34 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationDecorationTests, ReturnType_Struct_Valid) {
+  // struct Output {
+  //   [[location(0)]] a : f32;
+  //   [[builtin(frag_depth)]] b : f32;
+  // };
+  // [[stage(fragment)]]
+  // fn main() -> Output {
+  //   return Output();
+  // }
+  auto* output = Structure(
+      "Output", {Member("a", ty.f32(), {Location(0)}),
+                 Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(LocationDecorationTests, ReturnType_Struct) {
+  // struct Output {
+  //   a : f32;
+  // };
+  // [[stage(vertex)]]
+  // fn main() -> [[location(0)]] Output {
+  //   return Output();
+  // }
+  auto* output = Structure("Output", {Member("a", ty.f32())});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))}, {Stage(ast::PipelineStage::kVertex)},
+       {Location(Source{{13, 43}}, 0)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: cannot apply 'location' attribute to declaration of "
+            "type 'Output'\n"
+            "13:43 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationDecorationTests, ReturnType_Struct_NestedStruct) {
+  // struct Inner {
+  //   [[location(0)]] b : f32;
+  // };
+  // struct Output {
+  //   [[location(0)]] a : Inner;
+  // };
+  auto* inner = Structure(
+      "Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
+  Structure("Output", {Member(Source{{14, 52}}, "a", ty.Of(inner),
+                              {Location(Source{{12, 34}}, 0)})});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "14:52 error: cannot apply 'location' attribute to declaration of "
+            "type 'Inner'\n"
+            "12:34 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationDecorationTests, ReturnType_Struct_RuntimeArray) {
+  // [[block]]
+  // struct Output {
+  //   [[location(0)]] a : array<f32>;
+  // };
+  // [[stage(fragment)]]
+  // fn main() -> Output {
+  //   return Output();
+  // }
+  auto* output = Structure("Output",
+                           {Member(Source{{13, 43}}, "a", ty.array<float>(),
+                                   {Location(Source{{12, 34}}, 0)})},
+                           {create<ast::StructBlockDecoration>()});
+  Func(Source{{12, 34}}, "main", {}, ty.Of(output),
+       {Return(Construct(ty.Of(output)))},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "13:43 error: cannot apply 'location' attribute to declaration of "
+            "type 'array<f32>'\n"
+            "12:34 note: 'location' attribute must only be applied to "
+            "declarations of numeric scalar or numeric vector type");
+}
+
+TEST_F(LocationDecorationTests, ComputeShaderLocation_Input) {
+  Func("main", {}, ty.i32(), {Return(Expr(1))},
+       {Stage(ast::PipelineStage::kCompute),
+        create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))},
+       ast::DecorationList{Location(Source{{12, 34}}, 1)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: decoration is not valid for compute shader output");
+}
+
+TEST_F(LocationDecorationTests, ComputeShaderLocation_Output) {
+  auto* input = Param("input", ty.i32(),
+                      ast::DecorationList{Location(Source{{12, 34}}, 0u)});
+  Func("main", {input}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: decoration is not valid for compute shader inputs");
+}
+
+TEST_F(LocationDecorationTests, ComputeShaderLocationStructMember_Output) {
+  auto* m = Member("m", ty.i32(),
+                   ast::DecorationList{Location(Source{{12, 34}}, 0u)});
+  auto* s = Structure("S", {m});
+  Func(Source{{56, 78}}, "main", {}, ty.Of(s),
+       ast::StatementList{Return(Expr(Construct(ty.Of(s))))},
+       {Stage(ast::PipelineStage::kCompute),
+        create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: decoration is not valid for compute shader output\n"
+            "56:78 note: while analysing entry point 'main'");
+}
+
+TEST_F(LocationDecorationTests, ComputeShaderLocationStructMember_Input) {
+  auto* m = Member("m", ty.i32(),
+                   ast::DecorationList{Location(Source{{12, 34}}, 0u)});
+  auto* s = Structure("S", {m});
+  auto* input = Param("input", ty.Of(s));
+  Func(Source{{56, 78}}, "main", {input}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kCompute),
+        create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: decoration is not valid for compute shader inputs\n"
+            "56:78 note: while analysing entry point 'main'");
+}
+
+TEST_F(LocationDecorationTests, Duplicate_input) {
+  // [[stage(fragment)]]
+  // fn main([[location(1)]] param_a : f32,
+  //         [[location(1)]] param_b : f32) {}
+  auto* param_a = Param("param_a", ty.u32(), {Location(1)});
+  auto* param_b = Param("param_b", ty.u32(), {Location(Source{{12, 34}}, 1)});
+  Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: location(1) attribute appears multiple times");
+}
+
+TEST_F(LocationDecorationTests, Duplicate_struct) {
+  // struct InputA {
+  //   [[location(1)]] a : f32;
+  // };
+  // struct InputB {
+  //   [[location(1)]] a : f32;
+  // };
+  // [[stage(fragment)]]
+  // fn main(param_a : InputA, param_b : InputB) {}
+  auto* input_a = Structure("InputA", {Member("a", ty.f32(), {Location(1)})});
+  auto* input_b = Structure(
+      "InputB", {Member("a", ty.f32(), {Location(Source{{34, 56}}, 1)})});
+  auto* param_a = Param("param_a", ty.Of(input_a));
+  auto* param_b = Param("param_b", ty.Of(input_b));
+  Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kFragment)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "34:56 error: location(1) attribute appears multiple times\n"
+            "12:34 note: while analysing entry point 'main'");
+}
+
+}  // namespace
+}  // namespace LocationDecorationTests
+
 }  // namespace
 }  // namespace resolver
 }  // namespace tint
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 264b41b..16cbeaf 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -1500,19 +1500,11 @@
         }
         pipeline_io_attribute = deco;
 
-        if (locations.count(location->value())) {
-          AddError(deco_to_str(location) +
-                       " attribute appears multiple times as pipeline " +
-                       (param_or_ret == ParamOrRetType::kParameter ? "input"
-                                                                   : "output"),
-                   func->source());
+        bool is_input = param_or_ret == ParamOrRetType::kParameter;
+        if (!ValidateLocationDecoration(location, ty, locations, source,
+                                        is_input)) {
           return false;
         }
-
-        if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
-          is_invalid_compute_shader_decoration = true;
-        }
-        locations.emplace(location->value());
       } else if (auto* interpolate = deco->As<ast::InterpolateDecoration>()) {
         if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
           is_invalid_compute_shader_decoration = true;
@@ -1535,19 +1527,9 @@
       }
     }
 
-    // Check that we saw a pipeline IO attribute iff we need one.
-    if (ty->Is<sem::Struct>()) {
-      if (pipeline_io_attribute) {
-        AddError("entry point IO attributes must not be used on structure " +
-                     std::string(param_or_ret == ParamOrRetType::kParameter
-                                     ? "parameters"
-                                     : "return types"),
-                 pipeline_io_attribute->source());
-        return false;
-      }
-    } else if (IsValidationEnabled(
-                   decos, ast::DisabledValidation::kEntryPointParameter)) {
-      if (!pipeline_io_attribute) {
+    if (IsValidationEnabled(decos,
+                            ast::DisabledValidation::kEntryPointParameter)) {
+      if (!ty->Is<sem::Struct>() && !pipeline_io_attribute) {
         std::string err = "missing entry point IO attribute";
         if (!is_struct_member) {
           err +=
@@ -1558,29 +1540,23 @@
         return false;
       }
 
-      auto* builtin = pipeline_io_attribute->As<ast::BuiltinDecoration>();
-      if (invariant_attribute &&
-          !(builtin && builtin->value() == ast::Builtin::kPosition)) {
-        AddError(
-            "invariant attribute must only be applied to a position builtin",
-            invariant_attribute->source());
-        return false;
-      }
-
-      // Check that all user defined attributes are numeric scalars, vectors
-      // of numeric scalars.
-      // Testing for being a struct is handled by the if portion above.
-      if (!builtin) {
-        if (!ty->is_numeric_scalar_or_vector()) {
+      if (invariant_attribute) {
+        bool has_position = false;
+        if (pipeline_io_attribute) {
+          if (auto* builtin =
+                  pipeline_io_attribute->As<ast::BuiltinDecoration>()) {
+            has_position = (builtin->value() == ast::Builtin::kPosition);
+          }
+        }
+        if (!has_position) {
           AddError(
-              "User defined entry point IO types must be a numeric scalar, "
-              "a numeric vector, or a structure",
-              source);
+              "invariant attribute must only be applied to a position "
+              "builtin",
+              invariant_attribute->source());
           return false;
         }
       }
     }
-
     return true;
   };
 
@@ -1588,39 +1564,17 @@
   auto validate_entry_point_decorations = [&](const ast::DecorationList& decos,
                                               sem::Type* ty, Source source,
                                               ParamOrRetType param_or_ret) {
-    // Validate the decorations for the type.
     if (!validate_entry_point_decorations_inner(decos, ty, source, param_or_ret,
-                                                false)) {
+                                                /*is_struct_member*/ false)) {
       return false;
     }
 
     if (auto* str = ty->As<sem::Struct>()) {
-      // Validate the decorations for each struct members, and also check for
-      // invalid member types.
       for (auto* member : str->Members()) {
-        if (member->Type()->Is<sem::Struct>()) {
-          AddError("entry point IO types cannot contain nested structures",
-                   member->Declaration()->source());
-          AddNote("while analysing entry point '" +
-                      builder_->Symbols().NameFor(func->symbol()) + "'",
-                  func->source());
-          return false;
-        }
-
-        if (auto* arr = member->Type()->As<sem::Array>()) {
-          if (arr->IsRuntimeSized()) {
-            AddError("entry point IO types cannot contain runtime sized arrays",
-                     member->Declaration()->source());
-            AddNote("while analysing entry point '" +
-                        builder_->Symbols().NameFor(func->symbol()) + "'",
-                    func->source());
-            return false;
-          }
-        }
-
         if (!validate_entry_point_decorations_inner(
                 member->Declaration()->decorations(), member->Type(),
-                member->Declaration()->source(), param_or_ret, true)) {
+                member->Declaration()->source(), param_or_ret,
+                /*is_struct_member*/ true)) {
           AddNote("while analysing entry point '" +
                       builder_->Symbols().NameFor(func->symbol()) + "'",
                   func->source());
@@ -3920,6 +3874,7 @@
     return false;
   }
 
+  std::unordered_set<uint32_t> locations;
   for (auto* member : str->Members()) {
     if (auto* r = member->Type()->As<sem::Array>()) {
       if (r->IsRuntimeSized()) {
@@ -3961,10 +3916,15 @@
                  deco->source());
         return false;
       }
+
       if (auto* invariant = deco->As<ast::InvariantDecoration>()) {
         invariant_attribute = invariant;
-      }
-      if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
+      } else if (auto* location = deco->As<ast::LocationDecoration>()) {
+        if (!ValidateLocationDecoration(location, member->Type(), locations,
+                                        member->Declaration()->source())) {
+          return false;
+        }
+      } else if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
         if (!ValidateBuiltinDecoration(builtin, member->Type(),
                                        /* is_input */ false,
                                        /* is_struct_member */ true)) {
@@ -4010,6 +3970,42 @@
   return true;
 }
 
+bool Resolver::ValidateLocationDecoration(
+    const ast::LocationDecoration* location,
+    const sem::Type* type,
+    std::unordered_set<uint32_t>& locations,
+    const Source& source,
+    const bool is_input) {
+  std::string inputs_or_output = is_input ? "inputs" : "output";
+  if (current_function_ && current_function_->declaration->pipeline_stage() ==
+                               ast::PipelineStage::kCompute) {
+    AddError("decoration is not valid for compute shader " + inputs_or_output,
+             location->source());
+    return false;
+  }
+
+  if (!type->is_numeric_scalar_or_vector()) {
+    std::string invalid_type = type->FriendlyName(builder_->Symbols());
+    AddError("cannot apply 'location' attribute to declaration of type '" +
+                 invalid_type + "'",
+             source);
+    AddNote(
+        "'location' attribute must only be applied to declarations of "
+        "numeric scalar or numeric vector type",
+        location->source());
+    return false;
+  }
+
+  if (locations.count(location->value())) {
+    AddError(deco_to_str(location) + " attribute appears multiple times",
+             location->source());
+    return false;
+  }
+  locations.emplace(location->value());
+
+  return true;
+}
+
 sem::Struct* Resolver::Structure(const ast::Struct* str) {
   if (!ValidateNoDuplicateDecorations(str->decorations())) {
     return nullptr;
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 446bac6..86074f2 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -291,6 +291,11 @@
   bool ValidateGlobalVariable(const VariableInfo* var);
   bool ValidateInterpolateDecoration(const ast::InterpolateDecoration* deco,
                                      const sem::Type* storage_type);
+  bool ValidateLocationDecoration(const ast::LocationDecoration* location,
+                                  const sem::Type* type,
+                                  std::unordered_set<uint32_t>& locations,
+                                  const Source& source,
+                                  const bool is_input = false);
   bool ValidateMatrix(const sem::Matrix* ty, const Source& source);
   bool ValidateFunctionParameter(const ast::Function* func,
                                  const VariableInfo* info);