wsgl parser: Unify logic for all decorations

Parse all decorations with the same function, and create a
`ast::DecorationList`. Once the parser has progressed to the consumer of
the decorations, we attempt to downcast these to the required type,
erroring if they're the wrong kind.

While the error message could be improved further, this greatly reduces
the headscratching around crbug.com/tint/291.

Also knocks another 223 lines off parser_impl.cc.

Bug: tint:291
Bug: tint:282
Change-Id: I7506faeb56d876e5446d900c7c134669a9db6409
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/31660
Commit-Queue: Ben Clayton <bclayton@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index ec4fc39..c533548 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -110,12 +110,10 @@
   return ast::Builtin::kNone;
 }
 
-bool IsVariableDecoration(Token t) {
-  return t.IsLocation() || t.IsBuiltin() || t.IsBinding() || t.IsSet();
-}
-
-bool IsFunctionDecoration(Token t) {
-  return t.IsWorkgroupSize() || t.IsStage();
+bool is_decoration(Token t) {
+  return t.IsLocation() || t.IsBinding() || t.IsSet() || t.IsBuiltin() ||
+         t.IsWorkgroupSize() || t.IsStage() || t.IsBlock() || t.IsStride() ||
+         t.IsOffset();
 }
 
 }  // namespace
@@ -219,7 +217,9 @@
     return;
   }
 
-  auto gv = global_variable_decl();
+  auto decos = decoration_list();
+
+  auto gv = global_variable_decl(decos);
   if (has_error()) {
     return;
   }
@@ -255,7 +255,7 @@
     return;
   }
 
-  auto str = struct_decl();
+  auto str = struct_decl(decos);
   if (has_error()) {
     return;
   }
@@ -269,7 +269,7 @@
     return;
   }
 
-  auto func = function_decl();
+  auto func = function_decl(decos);
   if (has_error()) {
     return;
   }
@@ -278,40 +278,27 @@
     return;
   }
 
-  add_error(t, "invalid token");
+  t = peek();
+  if (decos.size() > 0) {
+    add_error(t, "expected declaration after decorations");
+  } else {
+    add_error(t, "invalid token");
+  }
 }
 
 // global_variable_decl
 //  : variable_decoration_list* variable_decl
 //  | variable_decoration_list* variable_decl EQUAL const_expr
-std::unique_ptr<ast::Variable> ParserImpl::global_variable_decl() {
-  ast::VariableDecorationList decos;
-  for (;;) {
-    auto s = decos.size();
-    if (!variable_decoration_list(decos)) {
-      return nullptr;
-    }
-    if (s == decos.size()) {
-      break;
-    }
-  }
-  if (has_error())
-    return nullptr;
-
+std::unique_ptr<ast::Variable> ParserImpl::global_variable_decl(
+    ast::DecorationList& decos) {
   auto var = variable_decl();
-  if (has_error())
+  if (has_error() || var == nullptr)
     return nullptr;
-  if (var == nullptr) {
-    if (decos.size() > 0)
-      add_error(peek(), "error parsing variable declaration");
 
-    return nullptr;
-  }
-
-  if (decos.size() > 0) {
+  auto var_decos = cast_decorations<ast::VariableDecoration>(decos);
+  if (var_decos.size() > 0) {
     auto dv = std::make_unique<ast::DecoratedVariable>(std::move(var));
-    dv->set_decorations(std::move(decos));
-
+    dv->set_decorations(std::move(var_decos));
     var = std::move(dv);
   }
 
@@ -365,113 +352,6 @@
   return var;
 }
 
-// variable_decoration_list
-//  : ATTR_LEFT (variable_decoration COMMA)* variable_decoration ATTR_RIGHT
-bool ParserImpl::variable_decoration_list(ast::VariableDecorationList& decos) {
-  auto t = peek();
-  if (!t.IsAttrLeft()) {
-    return true;
-  }
-
-  // Check the empty list before verifying the contents
-  t = peek(1);
-  if (t.IsAttrRight()) {
-    add_error(t, "empty variable decoration list");
-    return false;
-  }
-
-  // Make sure we're looking at variable decorations not some other kind
-  if (!IsVariableDecoration(peek(1))) {
-    return true;
-  }
-
-  next();  // consume the peek
-
-  auto deco = variable_decoration();
-  if (has_error()) {
-    return false;
-  }
-  if (deco == nullptr) {
-    add_error(peek(), "missing variable decoration for decoration list");
-    return false;
-  }
-  for (;;) {
-    decos.push_back(std::move(deco));
-
-    if (!match(Token::Type::kComma))
-      break;
-
-    deco = variable_decoration();
-    if (has_error()) {
-      return false;
-    }
-    if (deco == nullptr) {
-      add_error(peek(), "missing variable decoration after comma");
-      return false;
-    }
-  }
-
-  t = peek();
-  if (!t.IsAttrRight()) {
-    deco = variable_decoration();
-    if (deco != nullptr) {
-      add_error(t, "missing comma in variable decoration list");
-      return false;
-    }
-    add_error(t, "missing ]] for variable decoration");
-    return false;
-  }
-  next();  // consume the peek
-
-  return true;
-}
-
-// variable_decoration
-//  : LOCATION PAREN_LEFT INT_LITERAL PAREN_RIGHT
-//  | BUILTIN PAREN_LEFT IDENT PAREN_RIGHT
-//  | BINDING PAREN_LEFT INT_LITERAL PAREN_RIGHT
-//  | SET INT PAREN_LEFT_LITERAL PAREN_RIGHT
-std::unique_ptr<ast::VariableDecoration> ParserImpl::variable_decoration() {
-  Source source;
-  if (match(Token::Type::kLocation, &source)) {
-    const char* use = "location decoration";
-    return expect_paren_block(use, [&] {
-      uint32_t val;
-      bool ok = expect_positive_sint(use, &val);
-      return ok ? std::make_unique<ast::LocationDecoration>(val, source)
-                : nullptr;
-    });
-  }
-  if (match(Token::Type::kBuiltin, &source)) {
-    return expect_paren_block("builtin decoration", [&] {
-      ast::Builtin builtin;
-      std::tie(builtin, source) = expect_builtin();
-      return (builtin != ast::Builtin::kNone)
-                 ? std::make_unique<ast::BuiltinDecoration>(builtin, source)
-                 : nullptr;
-    });
-  }
-  if (match(Token::Type::kBinding, &source)) {
-    const char* use = "binding decoration";
-    return expect_paren_block(use, [&] {
-      uint32_t val;
-      bool ok = expect_positive_sint(use, &val);
-      return ok ? std::make_unique<ast::BindingDecoration>(val, source)
-                : nullptr;
-    });
-  }
-  if (match(Token::Type::kSet, &source)) {
-    const char* use = "set decoration";
-    return expect_paren_block(use, [&] {
-      uint32_t val;
-      bool ok = expect_positive_sint(use, &val);
-      return ok ? std::make_unique<ast::SetDecoration>(val, source) : nullptr;
-    });
-  }
-
-  return nullptr;
-}
-
 // variable_decl
 //   : VAR variable_storage_decoration? variable_ident_decl
 std::unique_ptr<ast::Variable> ParserImpl::variable_decl() {
@@ -1012,30 +892,18 @@
     return type_decl_pointer(t);
   }
 
-  ast::ArrayDecorationList decos;
-  for (;;) {
-    size_t s = decos.size();
-    if (!array_decoration_list(decos)) {
-      return nullptr;
-    }
-    if (decos.size() == s) {
-      break;
-    }
-  }
+  auto decos = decoration_list();
   if (has_error()) {
     return nullptr;
   }
-  if (!decos.empty()) {
-    t = peek();
+
+  if (match(Token::Type::kArray)) {
+    auto array_decos = cast_decorations<ast::ArrayDecoration>(decos);
+    return type_decl_array(std::move(array_decos));
   }
-  if (!decos.empty() && !t.IsArray()) {
-    add_error(t, "found array decoration but no array");
-    return nullptr;
-  }
-  if (t.IsArray()) {
-    next();  // Consume the peek
-    return type_decl_array(std::move(decos));
-  }
+
+  expect_decorations_consumed(decos);
+
   if (t.IsMat2x2() || t.IsMat2x3() || t.IsMat2x4() || t.IsMat3x2() ||
       t.IsMat3x3() || t.IsMat3x4() || t.IsMat4x2() || t.IsMat4x3() ||
       t.IsMat4x4()) {
@@ -1155,56 +1023,6 @@
   return ctx_.type_mgr().Get(std::move(ty));
 }
 
-// array_decoration_list
-//   : ATTR_LEFT (array_decoration COMMA)* array_decoration ATTR_RIGHT
-// array_decoration
-//   : STRIDE PAREN_LEFT INT_LITERAL PAREN_RIGHT
-//
-// As there is currently only one decoration I'm combining these for now.
-// we can split apart later if needed.
-bool ParserImpl::array_decoration_list(ast::ArrayDecorationList& decos) {
-  auto t = peek();
-  if (!t.IsAttrLeft()) {
-    return true;
-  }
-  t = peek(1);
-  if (!t.IsStride()) {
-    return true;
-  }
-
-  next();  // consume the peek of [[
-
-  for (;;) {
-    Source source;
-    if (match(Token::Type::kStride, &source)) {
-      const char* use = "stride decoration";
-      auto deco = expect_paren_block(use, [&] {
-        uint32_t val;
-        bool ok = expect_nonzero_positive_sint(use, &val);
-        return ok ? std::make_unique<ast::StrideDecoration>(val, source)
-                  : nullptr;
-      });
-
-      if (!deco)
-        return false;
-
-      decos.emplace_back(std::move(deco));
-    } else {
-      add_error(source, "unknown array decoration");
-      return false;
-    }
-
-    if (!match(Token::Type::kComma))
-      break;
-  }
-
-  if (!expect("array decoration", Token::Type::kAttrRight)) {
-    return false;
-  }
-
-  return true;
-}
-
 ast::type::Type* ParserImpl::type_decl_matrix(Token t) {
   next();  // Consume the peek
 
@@ -1298,29 +1116,15 @@
 
 // struct_decl
 //   : struct_decoration_decl* STRUCT IDENT struct_body_decl
-std::unique_ptr<ast::type::StructType> ParserImpl::struct_decl() {
+std::unique_ptr<ast::type::StructType> ParserImpl::struct_decl(
+    ast::DecorationList& decos) {
   auto t = peek();
   auto source = t.source();
 
-  ast::StructDecorationList decos;
-  for (;;) {
-    size_t s = decos.size();
-    if (!struct_decoration_decl(decos)) {
-      return nullptr;
-    }
-    if (decos.size() == s) {
-      break;
-    }
-  }
+  if (!match(Token::Type::kStruct))
+    return nullptr;
 
-  t = peek();
-  if (!decos.empty() && !t.IsStruct()) {
-    add_error(t, "missing struct declaration");
-    return nullptr;
-  } else if (!t.IsStruct()) {
-    return nullptr;
-  }
-  next();  // Consume the peek
+  auto struct_decos = cast_decorations<ast::StructDecoration>(decos);
 
   std::string name;
   if (!expect_ident("struct declaration", &name))
@@ -1332,46 +1136,8 @@
   }
 
   return std::make_unique<ast::type::StructType>(
-      name,
-      std::make_unique<ast::Struct>(source, std::move(decos), std::move(body)));
-}
-
-// struct_decoration_decl
-//  : ATTR_LEFT struct_decoration ATTR_RIGHT
-bool ParserImpl::struct_decoration_decl(ast::StructDecorationList& decos) {
-  auto t = peek();
-  if (!t.IsAttrLeft()) {
-    return true;
-  }
-
-  auto deco = struct_decoration(peek(1));
-  if (has_error()) {
-    return false;
-  }
-  if (deco == nullptr) {
-    return true;
-  }
-  decos.emplace_back(std::move(deco));
-
-  next();  // Consume the peek of [[
-  next();  // Consume the peek from the struct_decoration
-
-  t = next();
-  if (!t.IsAttrRight()) {
-    add_error(t, "missing ]] for struct decoration");
-    return false;
-  }
-
-  return true;
-}
-
-// struct_decoration
-//  : BLOCK
-std::unique_ptr<ast::StructDecoration> ParserImpl::struct_decoration(Token t) {
-  if (t.IsBlock()) {
-    return std::make_unique<ast::StructBlockDecoration>(t.source());
-  }
-  return nullptr;
+      name, std::make_unique<ast::Struct>(source, std::move(struct_decos),
+                                          std::move(body)));
 }
 
 // struct_body_decl
@@ -1381,7 +1147,9 @@
     ast::StructMemberList members;
 
     while (!peek().IsBraceRight() && !peek().IsEof()) {
-      auto mem = struct_member();
+      auto decos = decoration_list();
+
+      auto mem = struct_member(decos);
       if (has_error())
         return ast::StructMemberList{};
       if (mem == nullptr) {
@@ -1398,22 +1166,10 @@
 
 // struct_member
 //   : struct_member_decoration_decl+ variable_ident_decl SEMICOLON
-std::unique_ptr<ast::StructMember> ParserImpl::struct_member() {
+std::unique_ptr<ast::StructMember> ParserImpl::struct_member(
+    ast::DecorationList& decos) {
   auto t = peek();
 
-  ast::StructMemberDecorationList decos;
-  for (;;) {
-    size_t s = decos.size();
-    if (!struct_member_decoration_decl(decos)) {
-      return nullptr;
-    }
-    if (decos.size() == s) {
-      break;
-    }
-  }
-  if (has_error())
-    return nullptr;
-
   auto decl = variable_ident_decl();
   if (has_error())
     return nullptr;
@@ -1422,90 +1178,25 @@
     return nullptr;
   }
 
+  auto member_decos = cast_decorations<ast::StructMemberDecoration>(decos);
+
   if (!expect("struct member", Token::Type::kSemicolon))
     return nullptr;
 
   return std::make_unique<ast::StructMember>(decl.source, decl.name, decl.type,
-                                             std::move(decos));
-}
-
-// struct_member_decoration_decl
-//   :
-//   | ATTR_LEFT (struct_member_decoration COMMA)*
-//                struct_member_decoration ATTR_RIGHT
-bool ParserImpl::struct_member_decoration_decl(
-    ast::StructMemberDecorationList& decos) {
-  if (!match(Token::Type::kAttrLeft))
-    return true;
-
-  auto t = peek();
-  if (t.IsAttrRight()) {
-    add_error(t, "empty struct member decoration found");
-    return false;
-  }
-
-  for (;;) {
-    auto deco = struct_member_decoration();
-    if (has_error())
-      return false;
-    if (deco == nullptr)
-      break;
-
-    decos.push_back(std::move(deco));
-
-    t = next();
-    if (!t.IsComma())
-      break;
-  }
-
-  if (!t.IsAttrRight()) {
-    add_error(t, "missing ]] for struct member decoration");
-    return false;
-  }
-  return true;
-}
-
-// struct_member_decoration
-//   : OFFSET PAREN_LEFT INT_LITERAL PAREN_RIGHT
-std::unique_ptr<ast::StructMemberDecoration>
-ParserImpl::struct_member_decoration() {
-  Source source;
-  if (!match(Token::Type::kOffset, &source))
-    return nullptr;
-
-  const char* use = "offset decoration";
-  return expect_paren_block(use, [&] {
-    uint32_t val;
-    bool ok = expect_positive_sint(use, &val);
-    return ok ? std::make_unique<ast::StructMemberOffsetDecoration>(val, source)
-              : nullptr;
-  });
+                                             std::move(member_decos));
 }
 
 // function_decl
-//   : function_decoration_decl* function_header body_stmt
-std::unique_ptr<ast::Function> ParserImpl::function_decl() {
-  ast::FunctionDecorationList decos;
-  for (;;) {
-    size_t s = decos.size();
-    if (!function_decoration_decl(decos)) {
-      return nullptr;
-    }
-    if (decos.size() == s) {
-      break;
-    }
-  }
-
+//   : function_header body_stmt
+std::unique_ptr<ast::Function> ParserImpl::function_decl(
+    ast::DecorationList& decos) {
   auto f = function_header();
-  if (has_error())
+  if (f == nullptr || has_error())
     return nullptr;
-  if (f == nullptr) {
-    if (decos.size() > 0) {
-      add_error(peek(), "error parsing function declaration");
-    }
-    return nullptr;
-  }
-  f->set_decorations(std::move(decos));
+
+  auto func_decos = cast_decorations<ast::FunctionDecoration>(decos);
+  f->set_decorations(std::move(func_decos));
 
   auto body = body_stmt();
   if (has_error())
@@ -1515,98 +1206,6 @@
   return f;
 }
 
-// function_decoration_decl
-//   : ATTR_LEFT (function_decoration COMMA)* function_decoration ATTR_RIGHT
-bool ParserImpl::function_decoration_decl(ast::FunctionDecorationList& decos) {
-  auto t = peek();
-  if (!t.IsAttrLeft()) {
-    return true;
-  }
-  // Handle error on empty attributes before the type check
-  t = peek(1);
-  if (t.IsAttrRight()) {
-    add_error(t, "missing decorations for function decoration block");
-    return false;
-  }
-
-  // Make sure we're looking at function decorations and not some other kind
-  if (!IsFunctionDecoration(peek(1))) {
-    return true;
-  }
-
-  next();  // Consume the peek
-
-  size_t count = 0;
-  for (;;) {
-    auto deco = function_decoration();
-    if (has_error()) {
-      return false;
-    }
-    if (deco == nullptr) {
-      add_error(peek(), "expected decoration but none found");
-      return false;
-    }
-    decos.push_back(std::move(deco));
-    count++;
-
-    t = peek();
-    if (!t.IsComma()) {
-      break;
-    }
-    next();  // Consume the peek
-  }
-  if (count == 0) {
-    add_error(peek(), "missing decorations for function decoration block");
-    return false;
-  }
-
-  t = next();
-  if (!t.IsAttrRight()) {
-    add_error(t, "missing ]] for function decorations");
-    return false;
-  }
-  return true;
-}
-
-// function_decoration
-//   : STAGE PAREN_LEFT pipeline_stage PAREN_RIGHT
-//   | WORKGROUP_SIZE PAREN_LEFT INT_LITERAL
-//         (COMMA INT_LITERAL (COMMA INT_LITERAL)?)? PAREN_RIGHT
-std::unique_ptr<ast::FunctionDecoration> ParserImpl::function_decoration() {
-  Source source;
-  if (match(Token::Type::kWorkgroupSize, &source)) {
-    return expect_paren_block("workgroup_size decoration", [&]() {
-      uint32_t x;
-      if (!expect_nonzero_positive_sint("workgroup_size x parameter", &x)) {
-        return std::unique_ptr<ast::WorkgroupDecoration>(nullptr);
-      }
-      uint32_t y = 1;
-      uint32_t z = 1;
-      if (match(Token::Type::kComma)) {
-        if (!expect_nonzero_positive_sint("workgroup_size y parameter", &y)) {
-          return std::unique_ptr<ast::WorkgroupDecoration>(nullptr);
-        }
-        if (match(Token::Type::kComma)) {
-          if (!expect_nonzero_positive_sint("workgroup_size z parameter", &z)) {
-            return std::unique_ptr<ast::WorkgroupDecoration>(nullptr);
-          }
-        }
-      }
-      return std::make_unique<ast::WorkgroupDecoration>(x, y, z, source);
-    });
-  }
-  if (match(Token::Type::kStage, &source)) {
-    return expect_paren_block("stage decoration", [&]() {
-      ast::PipelineStage stage;
-      std::tie(stage, source) = expect_pipeline_stage();
-      return (stage != ast::PipelineStage::kNone)
-                 ? std::make_unique<ast::StageDecoration>(stage, source)
-                 : nullptr;
-    });
-  }
-  return nullptr;
-}
-
 // function_type_decl
 //   : type_decl
 //   | VOID
@@ -3174,6 +2773,186 @@
                                                             std::move(lit));
 }
 
+ast::DecorationList ParserImpl::decoration_list() {
+  ast::DecorationList decos;
+  while (decoration_bracketed_list(decos)) {
+  }
+  return decos;
+}
+
+bool ParserImpl::decoration_bracketed_list(ast::DecorationList& decos) {
+  if (!match(Token::Type::kAttrLeft)) {
+    return false;
+  }
+
+  auto t = peek();
+  if (match(Token::Type::kAttrRight)) {
+    add_error(t, "empty decoration list");
+    return false;
+  }
+
+  while (true) {
+    if (auto deco = expect_decoration()) {
+      decos.emplace_back(std::move(deco));
+    } else {
+      return false;
+    }
+
+    if (has_error()) {
+      return false;
+    }
+
+    if (match(Token::Type::kComma)) {
+      continue;
+    }
+
+    if (is_decoration(peek())) {
+      // We have two decorations in a bracket without a separating comma.
+      // e.g. [[location(1) set(2)]]
+      //                    ^^^ expected comma
+      expect("decoration list", Token::Type::kComma);
+      return false;
+    }
+
+    return expect("decoration list", Token::Type::kAttrRight);
+  }
+}
+
+std::unique_ptr<ast::Decoration> ParserImpl::expect_decoration() {
+  auto t = peek();
+  if (auto deco = decoration()) {
+    return deco;
+  }
+  if (!has_error()) {
+    add_error(t, "expected decoration");
+  }
+  return nullptr;
+}
+
+std::unique_ptr<ast::Decoration> ParserImpl::decoration() {
+  auto t = next();
+  if (t.IsLocation()) {
+    const char* use = "location decoration";
+    return expect_paren_block(use, [&]() {
+      uint32_t val;
+      bool ok = expect_positive_sint(use, &val);
+      return ok ? std::make_unique<ast::LocationDecoration>(val, t.source())
+                : nullptr;
+    });
+  }
+  if (t.IsBinding()) {
+    const char* use = "binding decoration";
+    return expect_paren_block(use, [&]() {
+      uint32_t val;
+      bool ok = expect_positive_sint(use, &val);
+      return ok ? std::make_unique<ast::BindingDecoration>(val, t.source())
+                : nullptr;
+    });
+  }
+  if (t.IsSet()) {
+    const char* use = "set decoration";
+    return expect_paren_block(use, [&]() {
+      uint32_t val;
+      bool ok = expect_positive_sint(use, &val);
+      return ok ? std::make_unique<ast::SetDecoration>(val, t.source())
+                : nullptr;
+    });
+  }
+  if (t.IsBuiltin()) {
+    return expect_paren_block("builtin decoration", [&]() {
+      ast::Builtin builtin;
+      Source source;
+      std::tie(builtin, source) = expect_builtin();
+      return (builtin != ast::Builtin::kNone)
+                 ? std::make_unique<ast::BuiltinDecoration>(builtin, source)
+                 : nullptr;
+    });
+  }
+  if (t.IsWorkgroupSize()) {
+    return expect_paren_block("workgroup_size decoration", [&]() {
+      uint32_t x;
+      if (!expect_nonzero_positive_sint("workgroup_size x parameter", &x)) {
+        return std::unique_ptr<ast::WorkgroupDecoration>(nullptr);
+      }
+      uint32_t y = 1;
+      uint32_t z = 1;
+      if (match(Token::Type::kComma)) {
+        if (!expect_nonzero_positive_sint("workgroup_size y parameter", &y)) {
+          return std::unique_ptr<ast::WorkgroupDecoration>(nullptr);
+        }
+        if (match(Token::Type::kComma)) {
+          if (!expect_nonzero_positive_sint("workgroup_size z parameter", &z)) {
+            return std::unique_ptr<ast::WorkgroupDecoration>(nullptr);
+          }
+        }
+      }
+      return std::make_unique<ast::WorkgroupDecoration>(x, y, z, t.source());
+    });
+  }
+  if (t.IsStage()) {
+    return expect_paren_block("stage decoration", [&]() {
+      ast::PipelineStage stage;
+      Source source;
+      std::tie(stage, source) = expect_pipeline_stage();
+      return (stage != ast::PipelineStage::kNone)
+                 ? std::make_unique<ast::StageDecoration>(stage, source)
+                 : nullptr;
+    });
+  }
+  if (t.IsBlock()) {
+    return std::make_unique<ast::StructBlockDecoration>(t.source());
+  }
+  if (t.IsStride()) {
+    const char* use = "stride decoration";
+    return expect_paren_block(use, [&]() {
+      uint32_t val;
+      bool ok = expect_nonzero_positive_sint(use, &val);
+      return ok ? std::make_unique<ast::StrideDecoration>(val, t.source())
+                : nullptr;
+    });
+  }
+  if (t.IsOffset()) {
+    const char* use = "offset decoration";
+    return expect_paren_block(use, [&]() {
+      uint32_t val;
+      bool ok = expect_positive_sint(use, &val);
+      return ok ? std::make_unique<ast::StructMemberOffsetDecoration>(
+                      val, t.source())
+                : nullptr;
+    });
+  }
+  return nullptr;
+}
+
+template <typename T>
+std::vector<std::unique_ptr<T>> ParserImpl::cast_decorations(
+    ast::DecorationList& in) {
+  std::vector<std::unique_ptr<T>> out;
+  out.reserve(in.size());
+  for (auto& deco : in) {
+    if (!deco->Is<T>()) {
+      std::stringstream msg;
+      msg << deco->GetKind() << " decoration type cannot be used for "
+          << T::Kind;
+      add_error(deco->GetSource(), msg.str());
+      continue;
+    }
+    out.emplace_back(ast::As<T>(std::move(deco)));
+  }
+  // clear in so that we can verify decorations were consumed with
+  // expect_decorations_consumed()
+  in.clear();
+  return out;
+}
+
+bool ParserImpl::expect_decorations_consumed(const ast::DecorationList& in) {
+  if (in.empty()) {
+    return true;
+  }
+  add_error(in[0]->GetSource(), "unexpected decorations");
+  return false;
+}
+
 bool ParserImpl::match(Token::Type tok, Source* source /*= nullptr*/) {
   auto t = peek();
 
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index e1de67f..dd4dd11 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -21,6 +21,7 @@
 #include <type_traits>
 #include <unordered_map>
 #include <utility>
+#include <vector>
 
 #include "src/ast/array_decoration.h"
 #include "src/ast/assignment_statement.h"
@@ -156,20 +157,15 @@
   void translation_unit();
   /// Parses the `global_decl` grammar element
   void global_decl();
-  /// Parses a `global_variable_decl` grammar element
+  /// Parses a `global_variable_decl` grammar element with the initial
+  /// `variable_decoration_list*` provided as |decos|.
   /// @returns the variable parsed or nullptr
-  std::unique_ptr<ast::Variable> global_variable_decl();
+  /// @param decos the list of decorations for the variable declaration.
+  std::unique_ptr<ast::Variable> global_variable_decl(
+      ast::DecorationList& decos);
   /// Parses a `global_constant_decl` grammar element
   /// @returns the const object or nullptr
   std::unique_ptr<ast::Variable> global_constant_decl();
-  /// Parses a `variable_decoration_list` grammar element, appending newly
-  /// parsed decorations to the end of |decos|.
-  /// @param decos list to store the parsed decorations
-  /// @returns the true on successful parse; false otherwise
-  bool variable_decoration_list(ast::VariableDecorationList& decos);
-  /// Parses a `variable_decoration` grammar element
-  /// @returns the variable decoration or nullptr if an error is encountered
-  std::unique_ptr<ast::VariableDecoration> variable_decoration();
   /// Parses a `variable_decl` grammar element
   /// @returns the parsed variable or nullptr otherwise
   std::unique_ptr<ast::Variable> variable_decl();
@@ -188,43 +184,25 @@
   /// Parses a `storage_class` grammar element
   /// @returns the storage class or StorageClass::kNone if none matched
   ast::StorageClass storage_class();
-  /// Parses a `struct_decl` grammar element
+  /// Parses a `struct_decl` grammar element with the initial
+  /// `struct_decoration_decl*` provided as |decos|.
   /// @returns the struct type or nullptr on error
-  std::unique_ptr<ast::type::StructType> struct_decl();
-  /// Parses a `struct_decoration_decl` grammar element, appending newly
-  /// parsed decorations to the end of |decos|.
-  /// @param decos list to store the parsed decorations
-  /// @returns the true on successful parse; false otherwise
-  bool struct_decoration_decl(ast::StructDecorationList& decos);
-  /// Parses a `struct_decoration` grammar element
-  /// @param t the current token
-  /// @returns the struct decoration or StructDecoraton::kNone if none matched
-  std::unique_ptr<ast::StructDecoration> struct_decoration(Token t);
+  /// @param decos the list of decorations for the struct declaration.
+  std::unique_ptr<ast::type::StructType> struct_decl(
+      ast::DecorationList& decos);
   /// Parses a `struct_body_decl` grammar element
   /// @returns the struct members
   ast::StructMemberList struct_body_decl();
-  /// Parses a `struct_member` grammar element
+  /// Parses a `struct_member` grammar element with the initial
+  /// `struct_member_decoration_decl+` provided as |decos|.
+  /// @param decos the list of decorations for the struct member.
   /// @returns the struct member or nullptr
-  std::unique_ptr<ast::StructMember> struct_member();
-  /// Parses a `struct_member_decoration_decl` grammar element, appending newly
-  /// parsed decorations to the end of |decos|.
-  /// @param decos the decoration list
-  /// @returns true if parsing was successful.
-  bool struct_member_decoration_decl(ast::StructMemberDecorationList& decos);
-  /// Parses a `struct_member_decoration` grammar element
-  /// @returns the decoration or nullptr if none found
-  std::unique_ptr<ast::StructMemberDecoration> struct_member_decoration();
-  /// Parses a `function_decl` grammar element
+  std::unique_ptr<ast::StructMember> struct_member(ast::DecorationList& decos);
+  /// Parses a `function_decl` grammar element with the initial
+  /// `function_decoration_decl*` provided as |decos|.
+  /// @param decos the list of decorations for the function declaration.
   /// @returns the parsed function, nullptr otherwise
-  std::unique_ptr<ast::Function> function_decl();
-  /// Parses a `function_decoration_decl` grammar element, appending newly
-  /// parsed decorations to the end of |decos|.
-  /// @param decos list to store the parsed decorations
-  /// @returns true on successful parse; false otherwise
-  bool function_decoration_decl(ast::FunctionDecorationList& decos);
-  /// Parses a `function_decoration` grammar element
-  /// @returns the parsed decoration, nullptr otherwise
-  std::unique_ptr<ast::FunctionDecoration> function_decoration();
+  std::unique_ptr<ast::Function> function_decl(ast::DecorationList& decos);
   /// Parses a `texture_sampler_types` grammar element
   /// @returns the parsed Type or nullptr if none matched.
   ast::type::Type* texture_sampler_types();
@@ -434,6 +412,28 @@
   /// Parses a `assignment_stmt` grammar element
   /// @returns the parsed assignment or nullptr
   std::unique_ptr<ast::AssignmentStatement> assignment_stmt();
+  /// Parses one or more bracketed decoration lists.
+  /// @return the parsed decoration list, or an empty list on error.
+  ast::DecorationList decoration_list();
+  /// Parses a list of decorations between `ATTR_LEFT` and `ATTR_RIGHT`
+  /// brackets.
+  /// @param decos the list to append newly parsed decorations to.
+  /// @return true if any decorations were be parsed, otherwise false.
+  bool decoration_bracketed_list(ast::DecorationList& decos);
+  /// Parses a single decoration of the following types:
+  /// * `struct_decoration`
+  /// * `struct_member_decoration`
+  /// * `array_decoration`
+  /// * `variable_decoration`
+  /// * `global_const_decoration`
+  /// * `function_decoration`
+  /// @return the parsed decoration, or nullptr.
+  std::unique_ptr<ast::Decoration> decoration();
+  /// Parses a single decoration, reporting an error if the next token does not
+  /// represent a decoration.
+  /// @see #decoration for the full list of decorations this method parses.
+  /// @return the parsed decoration, or nullptr on error.
+  std::unique_ptr<ast::Decoration> expect_decoration();
 
  private:
   /// ResultType resolves to the return type for the function or lambda F.
@@ -520,11 +520,17 @@
   /// a zero-initialized |T|.
   template <typename F, typename T = ResultType<F>>
   T expect_brace_block(const std::string& use, F&& body);
+  /// Downcasts all the decorations in |list| to the type |T|, raising a parser
+  /// error if any of the decorations aren't of the type |T|.
+  template <typename T>
+  std::vector<std::unique_ptr<T>> cast_decorations(ast::DecorationList& in);
+  /// Reports an error if the decoration list |list| is not empty.
+  /// Used to ensure that all decorations are consumed.
+  bool expect_decorations_consumed(const ast::DecorationList& list);
 
   ast::type::Type* type_decl_pointer(Token t);
   ast::type::Type* type_decl_vector(Token t);
   ast::type::Type* type_decl_array(ast::ArrayDecorationList decos);
-  bool array_decoration_list(ast::ArrayDecorationList& decos);
   ast::type::Type* type_decl_matrix(Token t);
 
   std::unique_ptr<ast::ConstructorExpression> const_expr_internal(
diff --git a/src/reader/wgsl/parser_impl_error_msg_test.cc b/src/reader/wgsl/parser_impl_error_msg_test.cc
index 899a6af..224f658 100644
--- a/src/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/reader/wgsl/parser_impl_error_msg_test.cc
@@ -253,14 +253,14 @@
 
 TEST_F(ParserImplErrorTest, FunctionDeclInvalid) {
   EXPECT("[[stage(vertex)]] x;",
-         "test.wgsl:1:19 error: error parsing function declaration\n"
+         "test.wgsl:1:19 error: expected declaration after decorations\n"
          "[[stage(vertex)]] x;\n"
          "                  ^\n");
 }
 
 TEST_F(ParserImplErrorTest, FunctionDeclDecoMissingEnd) {
   EXPECT("[[stage(vertex) fn f() -> void {}",
-         "test.wgsl:1:17 error: missing ]] for function decorations\n"
+         "test.wgsl:1:17 error: expected ']]' for decoration list\n"
          "[[stage(vertex) fn f() -> void {}\n"
          "                ^^\n");
 }
@@ -287,11 +287,10 @@
 }
 
 TEST_F(ParserImplErrorTest, FunctionDeclDecoStageTypeInvalid) {
-  // TODO(bclayton) - BUG(https://crbug.com/tint/291)
   EXPECT("[[shader(vertex)]] fn main() -> void {}",
-         "test.wgsl:1:1 error: invalid token\n"
+         "test.wgsl:1:3 error: expected decoration\n"
          "[[shader(vertex)]] fn main() -> void {}\n"
-         "^^\n");
+         "  ^^^^^^\n");
 }
 
 TEST_F(ParserImplErrorTest, FunctionDeclDecoWorkgroupSizeMissingLParen) {
@@ -619,14 +618,14 @@
 
 TEST_F(ParserImplErrorTest, GlobalDeclStructDecoMissingStruct) {
   EXPECT("[[block]];",
-         "test.wgsl:1:10 error: missing struct declaration\n"
+         "test.wgsl:1:10 error: expected declaration after decorations\n"
          "[[block]];\n"
          "         ^\n");
 }
 
 TEST_F(ParserImplErrorTest, GlobalDeclStructDecoMissingEnd) {
   EXPECT("[[block struct {};",
-         "test.wgsl:1:9 error: missing ]] for struct decoration\n"
+         "test.wgsl:1:9 error: expected ']]' for decoration list\n"
          "[[block struct {};\n"
          "        ^^^^^^\n");
 }
@@ -661,14 +660,14 @@
 
 TEST_F(ParserImplErrorTest, GlobalDeclStructMemberDecoEmpty) {
   EXPECT("struct S { [[]] i : i32; };",
-         "test.wgsl:1:14 error: empty struct member decoration found\n"
+         "test.wgsl:1:14 error: empty decoration list\n"
          "struct S { [[]] i : i32; };\n"
          "             ^^\n");
 }
 
 TEST_F(ParserImplErrorTest, GlobalDeclStructMemberDecoMissingEnd) {
   EXPECT("struct S { [[ i : i32; };",
-         "test.wgsl:1:15 error: missing ]] for struct member decoration\n"
+         "test.wgsl:1:15 error: expected decoration\n"
          "struct S { [[ i : i32; };\n"
          "              ^\n");
 }
@@ -753,9 +752,9 @@
 
 TEST_F(ParserImplErrorTest, GlobalDeclTypeDecoInvalid) {
   EXPECT("var x : [[]] i32;",
-         "test.wgsl:1:9 error: invalid type for identifier declaration\n"
+         "test.wgsl:1:11 error: empty decoration list\n"
          "var x : [[]] i32;\n"
-         "        ^^\n");
+         "          ^^\n");
 }
 
 TEST_F(ParserImplErrorTest, GlobalDeclVarArrayMissingLessThan) {
@@ -774,14 +773,14 @@
 
 TEST_F(ParserImplErrorTest, GlobalDeclVarArrayDecoNotArray) {
   EXPECT("var i : [[stride(1)]] i32;",
-         "test.wgsl:1:23 error: found array decoration but no array\n"
+         "test.wgsl:1:11 error: unexpected decorations\n"
          "var i : [[stride(1)]] i32;\n"
-         "                      ^^^\n");
+         "          ^^^^^^\n");
 }
 
 TEST_F(ParserImplErrorTest, GlobalDeclVarArrayDecoMissingEnd) {
   EXPECT("var i : [[stride(1) array<i32>;",
-         "test.wgsl:1:21 error: expected ']]' for array decoration\n"
+         "test.wgsl:1:21 error: expected ']]' for decoration list\n"
          "var i : [[stride(1) array<i32>;\n"
          "                    ^^^^^\n");
 }
@@ -839,28 +838,28 @@
 
 TEST_F(ParserImplErrorTest, GlobalDeclVarDecoListEmpty) {
   EXPECT("[[]] var i : i32;",
-         "test.wgsl:1:3 error: empty variable decoration list\n"
+         "test.wgsl:1:3 error: empty decoration list\n"
          "[[]] var i : i32;\n"
          "  ^^\n");
 }
 
 TEST_F(ParserImplErrorTest, GlobalDeclVarDecoListInvalid) {
   EXPECT("[[location(1), meow]] var i : i32;",
-         "test.wgsl:1:16 error: missing variable decoration after comma\n"
+         "test.wgsl:1:16 error: expected decoration\n"
          "[[location(1), meow]] var i : i32;\n"
          "               ^^^^\n");
 }
 
 TEST_F(ParserImplErrorTest, GlobalDeclVarDecoListMissingComma) {
   EXPECT("[[location(1) set(2)]] var i : i32;",
-         "test.wgsl:1:15 error: missing comma in variable decoration list\n"
+         "test.wgsl:1:15 error: expected ',' for decoration list\n"
          "[[location(1) set(2)]] var i : i32;\n"
          "              ^^^\n");
 }
 
 TEST_F(ParserImplErrorTest, GlobalDeclVarDecoListMissingEnd) {
   EXPECT("[[location(1) meow]] var i : i32;",
-         "test.wgsl:1:15 error: missing ]] for variable decoration\n"
+         "test.wgsl:1:15 error: expected ']]' for decoration list\n"
          "[[location(1) meow]] var i : i32;\n"
          "              ^^^^\n");
 }
diff --git a/src/reader/wgsl/parser_impl_function_decl_test.cc b/src/reader/wgsl/parser_impl_function_decl_test.cc
index 549273a..2de8075 100644
--- a/src/reader/wgsl/parser_impl_function_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_function_decl_test.cc
@@ -26,7 +26,9 @@
 
 TEST_F(ParserImplTest, FunctionDecl) {
   auto* p = parser("fn main(a : i32, b : f32) -> void { return; }");
-  auto f = p->function_decl();
+  auto decorations = p->decoration_list();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  auto f = p->function_decl(decorations);
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(f, nullptr);
 
@@ -48,7 +50,9 @@
 
 TEST_F(ParserImplTest, FunctionDecl_DecorationList) {
   auto* p = parser("[[workgroup_size(2, 3, 4)]] fn main() -> void { return; }");
-  auto f = p->function_decl();
+  auto decorations = p->decoration_list();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  auto f = p->function_decl(decorations);
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(f, nullptr);
 
@@ -80,7 +84,9 @@
   auto* p = parser(R"(
 [[workgroup_size(2, 3, 4), workgroup_size(5, 6, 7)]]
 fn main() -> void { return; })");
-  auto f = p->function_decl();
+  auto decorations = p->decoration_list();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  auto f = p->function_decl(decorations);
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(f, nullptr);
 
@@ -119,7 +125,9 @@
 [[workgroup_size(2, 3, 4)]]
 [[workgroup_size(5, 6, 7)]]
 fn main() -> void { return; })");
-  auto f = p->function_decl();
+  auto decorations = p->decoration_list();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  auto f = p->function_decl(decorations);
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(f, nullptr);
 
@@ -155,7 +163,9 @@
 
 TEST_F(ParserImplTest, FunctionDecl_InvalidHeader) {
   auto* p = parser("fn main() -> { }");
-  auto f = p->function_decl();
+  auto decorations = p->decoration_list();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  auto f = p->function_decl(decorations);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(f, nullptr);
   EXPECT_EQ(p->error(), "1:14: unable to determine function return type");
@@ -163,7 +173,9 @@
 
 TEST_F(ParserImplTest, FunctionDecl_InvalidBody) {
   auto* p = parser("fn main() -> void { return }");
-  auto f = p->function_decl();
+  auto decorations = p->decoration_list();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  auto f = p->function_decl(decorations);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(f, nullptr);
   EXPECT_EQ(p->error(), "1:28: expected ';' for return statement");
@@ -171,7 +183,9 @@
 
 TEST_F(ParserImplTest, FunctionDecl_MissingLeftBrace) {
   auto* p = parser("fn main() -> void return; }");
-  auto f = p->function_decl();
+  auto decorations = p->decoration_list();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  auto f = p->function_decl(decorations);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(f, nullptr);
   EXPECT_EQ(p->error(), "1:19: expected '{'");
diff --git a/src/reader/wgsl/parser_impl_function_decoration_list_test.cc b/src/reader/wgsl/parser_impl_function_decoration_list_test.cc
index 77c1fbc..3010a46 100644
--- a/src/reader/wgsl/parser_impl_function_decoration_list_test.cc
+++ b/src/reader/wgsl/parser_impl_function_decoration_list_test.cc
@@ -24,20 +24,24 @@
 
 TEST_F(ParserImplTest, FunctionDecorationList_Parses) {
   auto* p = parser("[[workgroup_size(2), workgroup_size(3, 4, 5)]]");
-  ast::FunctionDecorationList decos;
-  ASSERT_TRUE(p->function_decoration_decl(decos));
+  auto decos = p->decoration_list();
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_EQ(decos.size(), 2u);
 
+  auto deco_0 = ast::As<ast::FunctionDecoration>(std::move(decos[0]));
+  auto deco_1 = ast::As<ast::FunctionDecoration>(std::move(decos[1]));
+  ASSERT_NE(deco_0, nullptr);
+  ASSERT_NE(deco_1, nullptr);
+
   uint32_t x = 0;
   uint32_t y = 0;
   uint32_t z = 0;
-  ASSERT_TRUE(decos[0]->IsWorkgroup());
-  std::tie(x, y, z) = decos[0]->AsWorkgroup()->values();
+  ASSERT_TRUE(deco_0->IsWorkgroup());
+  std::tie(x, y, z) = deco_0->AsWorkgroup()->values();
   EXPECT_EQ(x, 2u);
 
-  ASSERT_TRUE(decos[1]->IsWorkgroup());
-  std::tie(x, y, z) = decos[1]->AsWorkgroup()->values();
+  ASSERT_TRUE(deco_1->IsWorkgroup());
+  std::tie(x, y, z) = deco_1->AsWorkgroup()->values();
   EXPECT_EQ(x, 3u);
   EXPECT_EQ(y, 4u);
   EXPECT_EQ(z, 5u);
@@ -45,41 +49,36 @@
 
 TEST_F(ParserImplTest, FunctionDecorationList_Empty) {
   auto* p = parser("[[]]");
-  ast::FunctionDecorationList decos;
-  ASSERT_FALSE(p->function_decoration_decl(decos));
+  ast::DecorationList decos = p->decoration_list();
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(),
-            "1:3: missing decorations for function decoration block");
+  ASSERT_EQ(p->error(), "1:3: empty decoration list");
 }
 
 TEST_F(ParserImplTest, FunctionDecorationList_Invalid) {
   auto* p = parser("[[invalid]]");
-  ast::FunctionDecorationList decos;
-  ASSERT_TRUE(p->function_decoration_decl(decos));
-  ASSERT_FALSE(p->has_error());
+  ast::DecorationList decos = p->decoration_list();
+  ASSERT_TRUE(p->has_error());
   ASSERT_TRUE(decos.empty());
+  ASSERT_EQ(p->error(), "1:3: expected decoration");
 }
 
 TEST_F(ParserImplTest, FunctionDecorationList_ExtraComma) {
   auto* p = parser("[[workgroup_size(2), ]]");
-  ast::FunctionDecorationList decos;
-  ASSERT_FALSE(p->function_decoration_decl(decos));
+  ast::DecorationList decos = p->decoration_list();
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:22: expected decoration but none found");
+  ASSERT_EQ(p->error(), "1:22: expected decoration");
 }
 
 TEST_F(ParserImplTest, FunctionDecorationList_MissingComma) {
   auto* p = parser("[[workgroup_size(2) workgroup_size(2)]]");
-  ast::FunctionDecorationList decos;
-  ASSERT_FALSE(p->function_decoration_decl(decos));
+  ast::DecorationList decos = p->decoration_list();
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:21: missing ]] for function decorations");
+  ASSERT_EQ(p->error(), "1:21: expected ',' for decoration list");
 }
 
 TEST_F(ParserImplTest, FunctionDecorationList_BadDecoration) {
   auto* p = parser("[[workgroup_size()]]");
-  ast::FunctionDecorationList decos;
-  ASSERT_FALSE(p->function_decoration_decl(decos));
+  ast::DecorationList decos = p->decoration_list();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(
       p->error(),
@@ -88,10 +87,9 @@
 
 TEST_F(ParserImplTest, FunctionDecorationList_MissingRightAttr) {
   auto* p = parser("[[workgroup_size(2), workgroup_size(3, 4, 5)");
-  ast::FunctionDecorationList decos;
-  ASSERT_FALSE(p->function_decoration_decl(decos));
+  ast::DecorationList decos = p->decoration_list();
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:45: missing ]] for function decorations");
+  ASSERT_EQ(p->error(), "1:45: expected ']]' for decoration list");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_function_decoration_test.cc b/src/reader/wgsl/parser_impl_function_decoration_test.cc
index 0ab58b2..90c9344 100644
--- a/src/reader/wgsl/parser_impl_function_decoration_test.cc
+++ b/src/reader/wgsl/parser_impl_function_decoration_test.cc
@@ -25,15 +25,17 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup) {
   auto* p = parser("workgroup_size(4)");
-  auto deco = p->function_decoration();
-  ASSERT_NE(deco, nullptr);
+  auto deco = p->decoration();
+  ASSERT_NE(deco, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(deco->IsWorkgroup());
+  auto func_deco = ast::As<ast::FunctionDecoration>(std::move(deco));
+  ASSERT_NE(func_deco, nullptr);
+  ASSERT_TRUE(func_deco->IsWorkgroup());
 
   uint32_t x = 0;
   uint32_t y = 0;
   uint32_t z = 0;
-  std::tie(x, y, z) = deco->AsWorkgroup()->values();
+  std::tie(x, y, z) = func_deco->AsWorkgroup()->values();
   EXPECT_EQ(x, 4u);
   EXPECT_EQ(y, 1u);
   EXPECT_EQ(z, 1u);
@@ -41,15 +43,17 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_2Param) {
   auto* p = parser("workgroup_size(4, 5)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_NE(deco, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(deco->IsWorkgroup());
+  auto func_deco = ast::As<ast::FunctionDecoration>(std::move(deco));
+  ASSERT_NE(func_deco, nullptr) << p->error();
+  ASSERT_TRUE(func_deco->IsWorkgroup());
 
   uint32_t x = 0;
   uint32_t y = 0;
   uint32_t z = 0;
-  std::tie(x, y, z) = deco->AsWorkgroup()->values();
+  std::tie(x, y, z) = func_deco->AsWorkgroup()->values();
   EXPECT_EQ(x, 4u);
   EXPECT_EQ(y, 5u);
   EXPECT_EQ(z, 1u);
@@ -57,15 +61,17 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_3Param) {
   auto* p = parser("workgroup_size(4, 5, 6)");
-  auto deco = p->function_decoration();
-  ASSERT_NE(deco, nullptr);
+  auto deco = p->decoration();
+  ASSERT_NE(deco, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(deco->IsWorkgroup());
+  auto func_deco = ast::As<ast::FunctionDecoration>(std::move(deco));
+  ASSERT_NE(func_deco, nullptr);
+  ASSERT_TRUE(func_deco->IsWorkgroup());
 
   uint32_t x = 0;
   uint32_t y = 0;
   uint32_t z = 0;
-  std::tie(x, y, z) = deco->AsWorkgroup()->values();
+  std::tie(x, y, z) = func_deco->AsWorkgroup()->values();
   EXPECT_EQ(x, 4u);
   EXPECT_EQ(y, 5u);
   EXPECT_EQ(z, 6u);
@@ -73,7 +79,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_TooManyValues) {
   auto* p = parser("workgroup_size(1, 2, 3, 4)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:23: expected ')' for workgroup_size decoration");
@@ -81,7 +87,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Invalid_X_Value) {
   auto* p = parser("workgroup_size(-2, 5, 6)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
@@ -90,7 +96,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Invalid_Y_Value) {
   auto* p = parser("workgroup_size(4, 0, 6)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
@@ -99,7 +105,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Invalid_Z_Value) {
   auto* p = parser("workgroup_size(4, 5, -3)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
@@ -108,7 +114,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_MissingLeftParam) {
   auto* p = parser("workgroup_size 4, 5, 6)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:16: expected '(' for workgroup_size decoration");
@@ -116,7 +122,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_MissingRightParam) {
   auto* p = parser("workgroup_size(4, 5, 6");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:23: expected ')' for workgroup_size decoration");
@@ -124,7 +130,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_MissingValues) {
   auto* p = parser("workgroup_size()");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(
@@ -134,7 +140,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_X_Value) {
   auto* p = parser("workgroup_size(, 2, 3)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(
@@ -144,7 +150,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_Y_Comma) {
   auto* p = parser("workgroup_size(1 2, 3)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:18: expected ')' for workgroup_size decoration");
@@ -152,7 +158,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_Y_Value) {
   auto* p = parser("workgroup_size(1, , 3)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(
@@ -162,7 +168,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_Z_Comma) {
   auto* p = parser("workgroup_size(1, 2 3)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:21: expected ')' for workgroup_size decoration");
@@ -170,7 +176,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_Z_Value) {
   auto* p = parser("workgroup_size(1, 2, )");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(
@@ -180,7 +186,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_X_Invalid) {
   auto* p = parser("workgroup_size(nan)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(
@@ -190,7 +196,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_Y_Invalid) {
   auto* p = parser("workgroup_size(2, nan)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(
@@ -200,7 +206,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_Z_Invalid) {
   auto* p = parser("workgroup_size(2, 3, nan)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(
@@ -210,16 +216,18 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Stage) {
   auto* p = parser("stage(compute)");
-  auto deco = p->function_decoration();
-  ASSERT_NE(deco, nullptr);
+  auto deco = p->decoration();
+  ASSERT_NE(deco, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(deco->IsStage());
-  EXPECT_EQ(deco->AsStage()->value(), ast::PipelineStage::kCompute);
+  auto func_deco = ast::As<ast::FunctionDecoration>(std::move(deco));
+  ASSERT_NE(func_deco, nullptr);
+  ASSERT_TRUE(func_deco->IsStage());
+  EXPECT_EQ(func_deco->AsStage()->value(), ast::PipelineStage::kCompute);
 }
 
 TEST_F(ParserImplTest, FunctionDecoration_Stage_MissingValue) {
   auto* p = parser("stage()");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:7: invalid value for stage decoration");
@@ -227,7 +235,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Stage_MissingInvalid) {
   auto* p = parser("stage(nan)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:7: invalid value for stage decoration");
@@ -235,7 +243,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Stage_MissingLeftParen) {
   auto* p = parser("stage compute)");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:7: expected '(' for stage decoration");
@@ -243,7 +251,7 @@
 
 TEST_F(ParserImplTest, FunctionDecoration_Stage_MissingRightParen) {
   auto* p = parser("stage(compute");
-  auto deco = p->function_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:14: expected ')' for stage decoration");
diff --git a/src/reader/wgsl/parser_impl_global_decl_test.cc b/src/reader/wgsl/parser_impl_global_decl_test.cc
index 34a7dbb..e76b0b6 100644
--- a/src/reader/wgsl/parser_impl_global_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_decl_test.cc
@@ -218,7 +218,7 @@
   auto* p = parser("[[block]] A {};");
   p->global_decl();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:11: missing struct declaration");
+  EXPECT_EQ(p->error(), "1:11: expected declaration after decorations");
 }
 
 TEST_F(ParserImplTest, GlobalDecl_StructMissing_Semi) {
diff --git a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc b/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
index 4339900..49494c1 100644
--- a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
@@ -25,7 +25,8 @@
 
 TEST_F(ParserImplTest, GlobalVariableDecl_WithoutConstructor) {
   auto* p = parser("var<out> a : f32");
-  auto e = p->global_variable_decl();
+  auto decorations = p->decoration_list();
+  auto e = p->global_variable_decl(decorations);
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e, nullptr);
 
@@ -44,7 +45,8 @@
 
 TEST_F(ParserImplTest, GlobalVariableDecl_WithConstructor) {
   auto* p = parser("var<out> a : f32 = 1.");
-  auto e = p->global_variable_decl();
+  auto decorations = p->decoration_list();
+  auto e = p->global_variable_decl(decorations);
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e, nullptr);
 
@@ -66,7 +68,8 @@
 
 TEST_F(ParserImplTest, GlobalVariableDecl_WithDecoration) {
   auto* p = parser("[[binding(2), set(1)]] var<out> a : f32");
-  auto e = p->global_variable_decl();
+  auto decorations = p->decoration_list();
+  auto e = p->global_variable_decl(decorations);
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e, nullptr);
   ASSERT_TRUE(e->IsDecorated());
@@ -94,7 +97,8 @@
 
 TEST_F(ParserImplTest, GlobalVariableDecl_WithDecoration_MulitpleGroups) {
   auto* p = parser("[[binding(2)]] [[set(1)]] var<out> a : f32");
-  auto e = p->global_variable_decl();
+  auto decorations = p->decoration_list();
+  auto e = p->global_variable_decl(decorations);
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e, nullptr);
   ASSERT_TRUE(e->IsDecorated());
@@ -122,7 +126,8 @@
 
 TEST_F(ParserImplTest, GlobalVariableDecl_InvalidDecoration) {
   auto* p = parser("[[binding()]] var<out> a : f32");
-  auto e = p->global_variable_decl();
+  auto decorations = p->decoration_list();
+  auto e = p->global_variable_decl(decorations);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(),
@@ -131,7 +136,8 @@
 
 TEST_F(ParserImplTest, GlobalVariableDecl_InvalidConstExpr) {
   auto* p = parser("var<out> a : f32 = if (a) {}");
-  auto e = p->global_variable_decl();
+  auto decorations = p->decoration_list();
+  auto e = p->global_variable_decl(decorations);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:20: unable to parse const literal");
@@ -139,7 +145,8 @@
 
 TEST_F(ParserImplTest, GlobalVariableDecl_InvalidVariableDecl) {
   auto* p = parser("var<invalid> a : f32;");
-  auto e = p->global_variable_decl();
+  auto decorations = p->decoration_list();
+  auto e = p->global_variable_decl(decorations);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:5: invalid storage class for variable decoration");
diff --git a/src/reader/wgsl/parser_impl_struct_decl_test.cc b/src/reader/wgsl/parser_impl_struct_decl_test.cc
index ebf1c4a..78aafaa 100644
--- a/src/reader/wgsl/parser_impl_struct_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decl_test.cc
@@ -28,7 +28,9 @@
   a : i32;
   [[offset(4)]] b : f32;
 })");
-  auto s = p->struct_decl();
+  auto decos = p->decoration_list();
+  ASSERT_EQ(decos.size(), 0u);
+  auto s = p->struct_decl(decos);
   ASSERT_FALSE(p->has_error());
   ASSERT_NE(s, nullptr);
   ASSERT_EQ(s->name(), "S");
@@ -43,7 +45,9 @@
   a : f32;
   b : f32;
 })");
-  auto s = p->struct_decl();
+  auto decos = p->decoration_list();
+  ASSERT_EQ(decos.size(), 1u);
+  auto s = p->struct_decl(decos);
   ASSERT_FALSE(p->has_error());
   ASSERT_NE(s, nullptr);
   ASSERT_EQ(s->name(), "B");
@@ -61,7 +65,9 @@
   a : f32;
   b : f32;
 })");
-  auto s = p->struct_decl();
+  auto decos = p->decoration_list();
+  ASSERT_EQ(decos.size(), 2u);
+  auto s = p->struct_decl(decos);
   ASSERT_FALSE(p->has_error());
   ASSERT_NE(s, nullptr);
   ASSERT_EQ(s->name(), "S");
@@ -75,7 +81,9 @@
 
 TEST_F(ParserImplTest, StructDecl_EmptyMembers) {
   auto* p = parser("struct S {}");
-  auto s = p->struct_decl();
+  auto decos = p->decoration_list();
+  ASSERT_EQ(decos.size(), 0u);
+  auto s = p->struct_decl(decos);
   ASSERT_FALSE(p->has_error());
   ASSERT_NE(s, nullptr);
   ASSERT_EQ(s->impl()->members().size(), 0u);
@@ -83,7 +91,9 @@
 
 TEST_F(ParserImplTest, StructDecl_MissingIdent) {
   auto* p = parser("struct {}");
-  auto s = p->struct_decl();
+  auto decos = p->decoration_list();
+  ASSERT_EQ(decos.size(), 0u);
+  auto s = p->struct_decl(decos);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(s, nullptr);
   EXPECT_EQ(p->error(), "1:8: expected identifier for struct declaration");
@@ -91,7 +101,9 @@
 
 TEST_F(ParserImplTest, StructDecl_MissingBracketLeft) {
   auto* p = parser("struct S }");
-  auto s = p->struct_decl();
+  auto decos = p->decoration_list();
+  ASSERT_EQ(decos.size(), 0u);
+  auto s = p->struct_decl(decos);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(s, nullptr);
   EXPECT_EQ(p->error(), "1:10: expected '{' for struct declaration");
@@ -99,7 +111,9 @@
 
 TEST_F(ParserImplTest, StructDecl_InvalidStructBody) {
   auto* p = parser("struct S { a : B; }");
-  auto s = p->struct_decl();
+  auto decos = p->decoration_list();
+  ASSERT_EQ(decos.size(), 0u);
+  auto s = p->struct_decl(decos);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(s, nullptr);
   EXPECT_EQ(p->error(), "1:16: unknown constructed type 'B'");
@@ -107,18 +121,20 @@
 
 TEST_F(ParserImplTest, StructDecl_InvalidStructDecorationDecl) {
   auto* p = parser("[[block struct S { a : i32; }");
-  auto s = p->struct_decl();
+  auto decos = p->decoration_list();
+  auto s = p->struct_decl(decos);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(s, nullptr);
-  EXPECT_EQ(p->error(), "1:9: missing ]] for struct decoration");
+  EXPECT_EQ(p->error(), "1:9: expected ']]' for decoration list");
 }
 
 TEST_F(ParserImplTest, StructDecl_MissingStruct) {
   auto* p = parser("[[block]] S {}");
-  auto s = p->struct_decl();
-  ASSERT_TRUE(p->has_error());
+  auto decos = p->decoration_list();
+  ASSERT_EQ(decos.size(), 1u);
+  auto s = p->struct_decl(decos);
+  ASSERT_FALSE(p->has_error());
   ASSERT_EQ(s, nullptr);
-  EXPECT_EQ(p->error(), "1:11: missing struct declaration");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc b/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
index 3ff9674..0549a29 100644
--- a/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
@@ -23,27 +23,25 @@
 
 TEST_F(ParserImplTest, StructDecorationDecl_Parses) {
   auto* p = parser("[[block]]");
-  ast::StructDecorationList decos;
-  ASSERT_TRUE(p->struct_decoration_decl(decos));
+  auto decos = p->decoration_list();
   ASSERT_FALSE(p->has_error());
-  EXPECT_EQ(decos.size(), 1u);
-  EXPECT_TRUE(decos[0]->IsBlock());
+  ASSERT_EQ(decos.size(), 1u);
+  auto struct_deco = ast::As<ast::StructDecoration>(std::move(decos[0]));
+  EXPECT_TRUE(struct_deco->IsBlock());
 }
 
 TEST_F(ParserImplTest, StructDecorationDecl_MissingAttrRight) {
   auto* p = parser("[[block");
-  ast::StructDecorationList decos;
-  ASSERT_FALSE(p->struct_decoration_decl(decos));
+  auto decos = p->decoration_list();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:8: missing ]] for struct decoration");
+  EXPECT_EQ(p->error(), "1:8: expected ']]' for decoration list");
 }
 
-// Note, this isn't an error because it could be an array decoration
 TEST_F(ParserImplTest, StructDecorationDecl_InvalidDecoration) {
   auto* p = parser("[[invalid]]");
-  ast::StructDecorationList decos;
-  ASSERT_TRUE(p->struct_decoration_decl(decos));
-  ASSERT_FALSE(p->has_error());
+  auto decos = p->decoration_list();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(decos.size(), 0u);
   EXPECT_TRUE(decos.empty());
 }
 
diff --git a/src/reader/wgsl/parser_impl_struct_decoration_test.cc b/src/reader/wgsl/parser_impl_struct_decoration_test.cc
index a8da5b2..84c6e22 100644
--- a/src/reader/wgsl/parser_impl_struct_decoration_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decoration_test.cc
@@ -38,9 +38,12 @@
   auto params = GetParam();
   auto* p = parser(params.input);
 
-  auto deco = p->struct_decoration(p->peek());
+  auto deco = p->decoration();
   ASSERT_FALSE(p->has_error());
-  EXPECT_EQ(deco->IsBlock(), params.is_block);
+  ASSERT_NE(deco, nullptr);
+  auto struct_deco = ast::As<ast::StructDecoration>(std::move(deco));
+  ASSERT_NE(struct_deco, nullptr);
+  EXPECT_EQ(struct_deco->IsBlock(), params.is_block);
 }
 INSTANTIATE_TEST_SUITE_P(ParserImplTest,
                          StructDecorationTest,
@@ -48,12 +51,8 @@
 
 TEST_F(ParserImplTest, StructDecoration_NoMatch) {
   auto* p = parser("not-a-stage");
-  auto deco = p->struct_decoration(p->peek());
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
-
-  auto t = p->next();
-  EXPECT_TRUE(t.IsIdentifier());
-  EXPECT_EQ(t.to_str(), "not");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_struct_member_decoration_decl_test.cc b/src/reader/wgsl/parser_impl_struct_member_decoration_decl_test.cc
index f15b14a..36cc93d 100644
--- a/src/reader/wgsl/parser_impl_struct_member_decoration_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_member_decoration_decl_test.cc
@@ -24,33 +24,32 @@
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_EmptyStr) {
   auto* p = parser("");
-  ast::StructMemberDecorationList decos;
-  ASSERT_TRUE(p->struct_member_decoration_decl(decos));
+  auto decos = p->decoration_list();
   ASSERT_FALSE(p->has_error());
   EXPECT_EQ(decos.size(), 0u);
 }
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_EmptyBlock) {
   auto* p = parser("[[]]");
-  ast::StructMemberDecorationList decos;
-  ASSERT_FALSE(p->struct_member_decoration_decl(decos));
+  auto decos = p->decoration_list();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:3: empty struct member decoration found");
+  EXPECT_EQ(decos.size(), 0u);
+  EXPECT_EQ(p->error(), "1:3: empty decoration list");
 }
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_Single) {
   auto* p = parser("[[offset(4)]]");
-  ast::StructMemberDecorationList decos;
-  ASSERT_TRUE(p->struct_member_decoration_decl(decos));
+  auto decos = p->decoration_list();
   ASSERT_FALSE(p->has_error());
   ASSERT_EQ(decos.size(), 1u);
-  EXPECT_TRUE(decos[0]->IsOffset());
+  auto deco = ast::As<ast::StructMemberDecoration>(std::move(decos[0]));
+  ASSERT_NE(deco, nullptr);
+  EXPECT_TRUE(deco->IsOffset());
 }
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_InvalidDecoration) {
   auto* p = parser("[[offset(nan)]]");
-  ast::StructMemberDecorationList decos;
-  ASSERT_FALSE(p->struct_member_decoration_decl(decos));
+  p->decoration_list();
   ASSERT_TRUE(p->has_error()) << p->error();
   EXPECT_EQ(p->error(),
             "1:10: expected signed integer literal for offset decoration");
@@ -58,10 +57,9 @@
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_MissingClose) {
   auto* p = parser("[[offset(4)");
-  ast::StructMemberDecorationList decos;
-  ASSERT_FALSE(p->struct_member_decoration_decl(decos));
+  p->decoration_list();
   ASSERT_TRUE(p->has_error()) << p->error();
-  EXPECT_EQ(p->error(), "1:12: missing ]] for struct member decoration");
+  EXPECT_EQ(p->error(), "1:12: expected ']]' for decoration list");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_struct_member_decoration_test.cc b/src/reader/wgsl/parser_impl_struct_member_decoration_test.cc
index da419e2..ec25733 100644
--- a/src/reader/wgsl/parser_impl_struct_member_decoration_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_member_decoration_test.cc
@@ -24,18 +24,21 @@
 
 TEST_F(ParserImplTest, StructMemberDecoration_Offset) {
   auto* p = parser("offset(4)");
-  auto deco = p->struct_member_decoration();
+  auto deco = p->decoration();
   ASSERT_NE(deco, nullptr);
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(deco->IsOffset());
 
-  auto* o = deco->AsOffset();
+  auto member_deco = ast::As<ast::StructMemberDecoration>(std::move(deco));
+  ASSERT_NE(member_deco, nullptr);
+  ASSERT_TRUE(member_deco->IsOffset());
+
+  auto* o = member_deco->AsOffset();
   EXPECT_EQ(o->offset(), 4u);
 }
 
 TEST_F(ParserImplTest, StructMemberDecoration_Offset_MissingLeftParen) {
   auto* p = parser("offset 4)");
-  auto deco = p->struct_member_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:8: expected '(' for offset decoration");
@@ -43,7 +46,7 @@
 
 TEST_F(ParserImplTest, StructMemberDecoration_Offset_MissingRightParen) {
   auto* p = parser("offset(4");
-  auto deco = p->struct_member_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: expected ')' for offset decoration");
@@ -51,7 +54,7 @@
 
 TEST_F(ParserImplTest, StructMemberDecoration_Offset_MissingValue) {
   auto* p = parser("offset()");
-  auto deco = p->struct_member_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
@@ -60,7 +63,7 @@
 
 TEST_F(ParserImplTest, StructMemberDecoration_Offset_MissingInvalid) {
   auto* p = parser("offset(nan)");
-  auto deco = p->struct_member_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
diff --git a/src/reader/wgsl/parser_impl_struct_member_test.cc b/src/reader/wgsl/parser_impl_struct_member_test.cc
index ad3ce9f..188afb7 100644
--- a/src/reader/wgsl/parser_impl_struct_member_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_member_test.cc
@@ -28,7 +28,9 @@
   auto* i32 = tm()->Get(std::make_unique<ast::type::I32Type>());
 
   auto* p = parser("a : i32;");
-  auto m = p->struct_member();
+  auto decos = p->decoration_list();
+  EXPECT_EQ(decos.size(), 0u);
+  auto m = p->struct_member(decos);
   ASSERT_FALSE(p->has_error());
   ASSERT_NE(m, nullptr);
 
@@ -46,7 +48,9 @@
   auto* i32 = tm()->Get(std::make_unique<ast::type::I32Type>());
 
   auto* p = parser("[[offset(2)]] a : i32;");
-  auto m = p->struct_member();
+  auto decos = p->decoration_list();
+  EXPECT_EQ(decos.size(), 1u);
+  auto m = p->struct_member(decos);
   ASSERT_FALSE(p->has_error());
   ASSERT_NE(m, nullptr);
 
@@ -67,7 +71,9 @@
 
   auto* p = parser(R"([[offset(2)]]
 [[offset(4)]] a : i32;)");
-  auto m = p->struct_member();
+  auto decos = p->decoration_list();
+  EXPECT_EQ(decos.size(), 2u);
+  auto m = p->struct_member(decos);
   ASSERT_FALSE(p->has_error());
   ASSERT_NE(m, nullptr);
 
@@ -87,7 +93,8 @@
 
 TEST_F(ParserImplTest, StructMember_InvalidDecoration) {
   auto* p = parser("[[offset(nan)]] a : i32;");
-  auto m = p->struct_member();
+  auto decos = p->decoration_list();
+  auto m = p->struct_member(decos);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(m, nullptr);
   EXPECT_EQ(p->error(),
@@ -96,7 +103,8 @@
 
 TEST_F(ParserImplTest, StructMember_InvalidVariable) {
   auto* p = parser("[[offset(4)]] a : B;");
-  auto m = p->struct_member();
+  auto decos = p->decoration_list();
+  auto m = p->struct_member(decos);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(m, nullptr);
   EXPECT_EQ(p->error(), "1:19: unknown constructed type 'B'");
@@ -104,7 +112,8 @@
 
 TEST_F(ParserImplTest, StructMember_MissingSemicolon) {
   auto* p = parser("a : i32");
-  auto m = p->struct_member();
+  auto decos = p->decoration_list();
+  auto m = p->struct_member(decos);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(m, nullptr);
   EXPECT_EQ(p->error(), "1:8: expected ';' for struct member");
diff --git a/src/reader/wgsl/parser_impl_type_decl_test.cc b/src/reader/wgsl/parser_impl_type_decl_test.cc
index a3c435b..e9a6af8 100644
--- a/src/reader/wgsl/parser_impl_type_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_type_decl_test.cc
@@ -383,7 +383,7 @@
   auto* t = p->type_decl();
   ASSERT_EQ(t, nullptr);
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:16: found array decoration but no array");
+  EXPECT_EQ(p->error(), "1:3: unexpected decorations");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_Decoration_MissingClosingAttr) {
@@ -391,16 +391,15 @@
   auto* t = p->type_decl();
   ASSERT_EQ(t, nullptr);
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:14: expected ']]' for array decoration");
+  EXPECT_EQ(p->error(), "1:14: expected ']]' for decoration list");
 }
 
-// Note, this isn't an error because it could be a struct decoration, we just
-// don't have an array ...
 TEST_F(ParserImplTest, TypeDecl_Array_Decoration_UnknownDecoration) {
   auto* p = parser("[[unknown 16]] array<f32, 5>");
   auto* t = p->type_decl();
   ASSERT_EQ(t, nullptr);
-  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:3: expected decoration");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_Stride_MissingLeftParen) {
diff --git a/src/reader/wgsl/parser_impl_variable_decoration_list_test.cc b/src/reader/wgsl/parser_impl_variable_decoration_list_test.cc
index 215e202..dcbac85 100644
--- a/src/reader/wgsl/parser_impl_variable_decoration_list_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_decoration_list_test.cc
@@ -25,52 +25,52 @@
 
 TEST_F(ParserImplTest, VariableDecorationList_Parses) {
   auto* p = parser(R"([[location(4), builtin(position)]])");
-  ast::VariableDecorationList decos;
-  EXPECT_TRUE(p->variable_decoration_list(decos));
+  auto decos = p->decoration_list();
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_EQ(decos.size(), 2u);
-  ASSERT_TRUE(decos[0]->IsLocation());
-  EXPECT_EQ(decos[0]->AsLocation()->value(), 4u);
-  ASSERT_TRUE(decos[1]->IsBuiltin());
-  EXPECT_EQ(decos[1]->AsBuiltin()->value(), ast::Builtin::kPosition);
+
+  auto deco_0 = ast::As<ast::VariableDecoration>(std::move(decos[0]));
+  auto deco_1 = ast::As<ast::VariableDecoration>(std::move(decos[1]));
+  ASSERT_NE(deco_0, nullptr);
+  ASSERT_NE(deco_1, nullptr);
+
+  ASSERT_TRUE(deco_0->IsLocation());
+  EXPECT_EQ(deco_0->AsLocation()->value(), 4u);
+  ASSERT_TRUE(deco_1->IsBuiltin());
+  EXPECT_EQ(deco_1->AsBuiltin()->value(), ast::Builtin::kPosition);
 }
 
 TEST_F(ParserImplTest, VariableDecorationList_Empty) {
   auto* p = parser(R"([[]])");
-  ast::VariableDecorationList decos;
-  EXPECT_FALSE(p->variable_decoration_list(decos));
+  auto decos = p->decoration_list();
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:3: empty variable decoration list");
+  ASSERT_EQ(p->error(), "1:3: empty decoration list");
 }
 
 TEST_F(ParserImplTest, VariableDecorationList_Invalid) {
   auto* p = parser(R"([[invalid]])");
-  ast::VariableDecorationList decos;
-  EXPECT_TRUE(p->variable_decoration_list(decos));
-  ASSERT_FALSE(p->has_error());
+  auto decos = p->decoration_list();
+  ASSERT_TRUE(p->has_error());
   ASSERT_TRUE(decos.empty());
 }
 
 TEST_F(ParserImplTest, VariableDecorationList_ExtraComma) {
   auto* p = parser(R"([[builtin(position), ]])");
-  ast::VariableDecorationList decos;
-  EXPECT_FALSE(p->variable_decoration_list(decos));
+  auto decos = p->decoration_list();
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:22: missing variable decoration after comma");
+  ASSERT_EQ(p->error(), "1:22: expected decoration");
 }
 
 TEST_F(ParserImplTest, VariableDecorationList_MissingComma) {
   auto* p = parser(R"([[binding(4) location(5)]])");
-  ast::VariableDecorationList decos;
-  EXPECT_FALSE(p->variable_decoration_list(decos));
+  auto decos = p->decoration_list();
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:14: missing comma in variable decoration list");
+  ASSERT_EQ(p->error(), "1:14: expected ',' for decoration list");
 }
 
 TEST_F(ParserImplTest, VariableDecorationList_BadDecoration) {
   auto* p = parser(R"([[location(bad)]])");
-  ast::VariableDecorationList decos;
-  EXPECT_FALSE(p->variable_decoration_list(decos));
+  auto decos = p->decoration_list();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(),
             "1:12: expected signed integer literal for location decoration");
@@ -78,8 +78,7 @@
 
 TEST_F(ParserImplTest, VariableDecorationList_InvalidBuiltin) {
   auto* p = parser("[[builtin(invalid)]]");
-  ast::VariableDecorationList decos;
-  EXPECT_FALSE(p->variable_decoration_list(decos));
+  auto decos = p->decoration_list();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:11: invalid value for builtin decoration");
 }
diff --git a/src/reader/wgsl/parser_impl_variable_decoration_test.cc b/src/reader/wgsl/parser_impl_variable_decoration_test.cc
index 63af2bb..5ada60c 100644
--- a/src/reader/wgsl/parser_impl_variable_decoration_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_decoration_test.cc
@@ -27,18 +27,20 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Location) {
   auto* p = parser("location(4)");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_NE(deco, nullptr);
+  auto var_deco = ast::As<ast::VariableDecoration>(std::move(deco));
+  ASSERT_NE(var_deco, nullptr);
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(deco->IsLocation());
+  ASSERT_TRUE(var_deco->IsLocation());
 
-  auto* loc = deco->AsLocation();
+  auto* loc = var_deco->AsLocation();
   EXPECT_EQ(loc->value(), 4u);
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Location_MissingLeftParen) {
   auto* p = parser("location 4)");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:10: expected '(' for location decoration");
@@ -46,7 +48,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Location_MissingRightParen) {
   auto* p = parser("location(4");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:11: expected ')' for location decoration");
@@ -54,7 +56,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Location_MissingValue) {
   auto* p = parser("location()");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
@@ -63,7 +65,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Location_MissingInvalid) {
   auto* p = parser("location(nan)");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
@@ -85,12 +87,14 @@
   auto params = GetParam();
   auto* p = parser(std::string("builtin(") + params.input + ")");
 
-  auto deco = p->variable_decoration();
-  ASSERT_FALSE(p->has_error()) << p->error();
+  auto deco = p->decoration();
   ASSERT_NE(deco, nullptr);
-  ASSERT_TRUE(deco->IsBuiltin());
+  auto var_deco = ast::As<ast::VariableDecoration>(std::move(deco));
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(var_deco, nullptr);
+  ASSERT_TRUE(var_deco->IsBuiltin());
 
-  auto* builtin = deco->AsBuiltin();
+  auto* builtin = var_deco->AsBuiltin();
   EXPECT_EQ(builtin->value(), params.result);
 }
 INSTANTIATE_TEST_SUITE_P(
@@ -110,7 +114,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Builtin_MissingLeftParen) {
   auto* p = parser("builtin position)");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: expected '(' for builtin decoration");
@@ -118,7 +122,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Builtin_MissingRightParen) {
   auto* p = parser("builtin(position");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:17: expected ')' for builtin decoration");
@@ -126,7 +130,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Builtin_MissingValue) {
   auto* p = parser("builtin()");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: expected identifier for builtin");
@@ -134,7 +138,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Builtin_InvalidValue) {
   auto* p = parser("builtin(other_thingy)");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: invalid value for builtin decoration");
@@ -142,7 +146,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Builtin_MissingInvalid) {
   auto* p = parser("builtin(3)");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: expected identifier for builtin");
@@ -150,18 +154,20 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Binding) {
   auto* p = parser("binding(4)");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_NE(deco, nullptr);
+  auto var_deco = ast::As<ast::VariableDecoration>(std::move(deco));
+  ASSERT_NE(var_deco, nullptr);
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(deco->IsBinding());
+  ASSERT_TRUE(var_deco->IsBinding());
 
-  auto* binding = deco->AsBinding();
+  auto* binding = var_deco->AsBinding();
   EXPECT_EQ(binding->value(), 4u);
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Binding_MissingLeftParen) {
   auto* p = parser("binding 4)");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: expected '(' for binding decoration");
@@ -169,7 +175,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Binding_MissingRightParen) {
   auto* p = parser("binding(4");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:10: expected ')' for binding decoration");
@@ -177,7 +183,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Binding_MissingValue) {
   auto* p = parser("binding()");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
@@ -186,7 +192,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Binding_MissingInvalid) {
   auto* p = parser("binding(nan)");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
@@ -195,18 +201,20 @@
 
 TEST_F(ParserImplTest, VariableDecoration_set) {
   auto* p = parser("set(4)");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
+  ASSERT_NE(deco, nullptr);
+  auto var_deco = ast::As<ast::VariableDecoration>(std::move(deco));
   ASSERT_FALSE(p->has_error());
-  ASSERT_NE(deco.get(), nullptr);
-  ASSERT_TRUE(deco->IsSet());
+  ASSERT_NE(var_deco.get(), nullptr);
+  ASSERT_TRUE(var_deco->IsSet());
 
-  auto* set = deco->AsSet();
+  auto* set = var_deco->AsSet();
   EXPECT_EQ(set->value(), 4u);
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Set_MissingLeftParen) {
   auto* p = parser("set 2)");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:5: expected '(' for set decoration");
@@ -214,7 +222,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Set_MissingRightParen) {
   auto* p = parser("set(2");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:6: expected ')' for set decoration");
@@ -222,7 +230,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Set_MissingValue) {
   auto* p = parser("set()");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
@@ -231,7 +239,7 @@
 
 TEST_F(ParserImplTest, VariableDecoration_Set_MissingInvalid) {
   auto* p = parser("set(nan)");
-  auto deco = p->variable_decoration();
+  auto deco = p->decoration();
   ASSERT_EQ(deco, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),