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/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;