Update structs to allow multiple decorations

This CL updates to match the spec change allowing multiple struct
decorations.

Bug: tint:240
Change-Id: Id859c6a331c67c46597fc3c70de06d6cc0f486ec
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/29260
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/ast/struct.cc b/src/ast/struct.cc
index 01344b6..c16632e 100644
--- a/src/ast/struct.cc
+++ b/src/ast/struct.cc
@@ -19,13 +19,19 @@
 
 Struct::Struct() : Node() {}
 
-Struct::Struct(StructDecoration decoration, StructMemberList members)
-    : Node(), decoration_(decoration), members_(std::move(members)) {}
+Struct::Struct(StructMemberList members)
+    : Node(), members_(std::move(members)) {}
+
+Struct::Struct(StructDecorationList decorations, StructMemberList members)
+    : Node(), decorations_(decorations), members_(std::move(members)) {}
+
+Struct::Struct(const Source& source, StructMemberList members)
+    : Node(source), members_(std::move(members)) {}
 
 Struct::Struct(const Source& source,
-               StructDecoration decoration,
+               StructDecorationList decorations,
                StructMemberList members)
-    : Node(source), decoration_(decoration), members_(std::move(members)) {}
+    : Node(source), decorations_(decorations), members_(std::move(members)) {}
 
 Struct::Struct(Struct&&) = default;
 
@@ -40,7 +46,21 @@
   return nullptr;
 }
 
+bool Struct::IsBlockDecorated() const {
+  for (auto deco : decorations_) {
+    if (deco == StructDecoration::kBlock) {
+      return true;
+    }
+  }
+  return false;
+}
+
 bool Struct::IsValid() const {
+  for (auto deco : decorations_) {
+    if (deco == StructDecoration::kNone) {
+      return false;
+    }
+  }
   for (const auto& mem : members_) {
     if (mem == nullptr || !mem->IsValid()) {
       return false;
@@ -51,10 +71,11 @@
 
 void Struct::to_str(std::ostream& out, size_t indent) const {
   make_indent(out, indent);
-  if (decoration_ != StructDecoration::kNone) {
-    out << "[[" << decoration_ << "]] ";
-  }
   out << "Struct{" << std::endl;
+  for (auto deco : decorations_) {
+    make_indent(out, indent + 2);
+    out << "[[" << deco << "]]" << std::endl;
+  }
   for (const auto& member : members_) {
     member->to_str(out, indent + 2);
   }
diff --git a/src/ast/struct.h b/src/ast/struct.h
index e12cfd5..8a55b8d 100644
--- a/src/ast/struct.h
+++ b/src/ast/struct.h
@@ -32,15 +32,22 @@
   /// Create a new empty struct statement
   Struct();
   /// Create a new struct statement
-  /// @param decoration The struct decorations
   /// @param members The struct members
-  Struct(StructDecoration decoration, StructMemberList members);
+  explicit Struct(StructMemberList members);
+  /// Create a new struct statement
+  /// @param decorations The struct decorations
+  /// @param members The struct members
+  Struct(StructDecorationList decorations, StructMemberList members);
   /// Create a new struct statement
   /// @param source The input source for the import statement
-  /// @param decoration The struct decorations
+  /// @param members The struct members
+  Struct(const Source& source, StructMemberList members);
+  /// Create a new struct statement
+  /// @param source The input source for the import statement
+  /// @param decorations The struct decorations
   /// @param members The struct members
   Struct(const Source& source,
-         StructDecoration decoration,
+         StructDecorationList decorations,
          StructMemberList members);
   /// Move constructor
   Struct(Struct&&);
@@ -48,10 +55,12 @@
   ~Struct() override;
 
   /// Sets the struct decoration
-  /// @param deco the decoration to set
-  void set_decoration(StructDecoration deco) { decoration_ = deco; }
-  /// @returns the struct decoration
-  StructDecoration decoration() const { return decoration_; }
+  /// @param decos the list of decorations to set
+  void set_decorations(StructDecorationList decos) {
+    decorations_ = std::move(decos);
+  }
+  /// @returns the struct decorations
+  const StructDecorationList& decorations() const { return decorations_; }
 
   /// Sets the struct members
   /// @param members the members to set
@@ -64,6 +73,9 @@
   /// @returns the struct member or nullptr if not found
   StructMember* get_member(const std::string& name) const;
 
+  /// @returns true if the struct is block decorated
+  bool IsBlockDecorated() const;
+
   /// @returns true if the node is valid
   bool IsValid() const override;
 
@@ -75,7 +87,7 @@
  private:
   Struct(const Struct&) = delete;
 
-  StructDecoration decoration_ = StructDecoration::kNone;
+  StructDecorationList decorations_;
   StructMemberList members_;
 };
 
diff --git a/src/ast/struct_decoration.h b/src/ast/struct_decoration.h
index fab582c..fecea8a 100644
--- a/src/ast/struct_decoration.h
+++ b/src/ast/struct_decoration.h
@@ -25,6 +25,9 @@
 
 std::ostream& operator<<(std::ostream& out, StructDecoration stage);
 
+/// List of struct decorations
+using StructDecorationList = std::vector<StructDecoration>;
+
 }  // namespace ast
 }  // namespace tint
 
diff --git a/src/ast/struct_test.cc b/src/ast/struct_test.cc
index 0b416a2..d3bc623 100644
--- a/src/ast/struct_test.cc
+++ b/src/ast/struct_test.cc
@@ -35,23 +35,45 @@
   members.push_back(
       std::make_unique<StructMember>("a", &i32, StructMemberDecorationList()));
 
-  Struct s{StructDecoration::kNone, std::move(members)};
+  Struct s{std::move(members)};
   EXPECT_EQ(s.members().size(), 1u);
-  EXPECT_EQ(s.decoration(), StructDecoration::kNone);
+  EXPECT_TRUE(s.decorations().empty());
   EXPECT_EQ(s.line(), 0u);
   EXPECT_EQ(s.column(), 0u);
 }
 
-TEST_F(StructTest, CreationWithSource) {
+TEST_F(StructTest, Creation_WithDecorations) {
   type::I32Type i32;
-  Source source{27, 4};
+
+  StructMemberList members;
+  members.push_back(
+      std::make_unique<StructMember>("a", &i32, StructMemberDecorationList()));
+
+  StructDecorationList decos;
+  decos.push_back(StructDecoration::kBlock);
+
+  Struct s{std::move(decos), std::move(members)};
+  EXPECT_EQ(s.members().size(), 1u);
+  ASSERT_EQ(s.decorations().size(), 1u);
+  EXPECT_EQ(s.decorations()[0], StructDecoration::kBlock);
+  EXPECT_EQ(s.line(), 0u);
+  EXPECT_EQ(s.column(), 0u);
+}
+
+TEST_F(StructTest, CreationWithSourceAndDecorations) {
+  type::I32Type i32;
+
   StructMemberList members;
   members.emplace_back(
       std::make_unique<StructMember>("a", &i32, StructMemberDecorationList()));
 
-  Struct s{source, StructDecoration::kNone, std::move(members)};
+  StructDecorationList decos;
+  decos.push_back(StructDecoration::kBlock);
+
+  Struct s{Source{27, 4}, std::move(decos), std::move(members)};
   EXPECT_EQ(s.members().size(), 1u);
-  EXPECT_EQ(s.decoration(), StructDecoration::kNone);
+  ASSERT_EQ(s.decorations().size(), 1u);
+  EXPECT_EQ(s.decorations()[0], StructDecoration::kBlock);
   EXPECT_EQ(s.line(), 27u);
   EXPECT_EQ(s.column(), 4u);
 }
@@ -63,37 +85,57 @@
 
 TEST_F(StructTest, IsValid_Null_StructMember) {
   type::I32Type i32;
+
   StructMemberList members;
   members.push_back(
       std::make_unique<StructMember>("a", &i32, StructMemberDecorationList()));
   members.push_back(nullptr);
 
-  Struct s{StructDecoration::kNone, std::move(members)};
+  Struct s{std::move(members)};
   EXPECT_FALSE(s.IsValid());
 }
 
 TEST_F(StructTest, IsValid_Invalid_StructMember) {
   type::I32Type i32;
+
   StructMemberList members;
   members.push_back(
       std::make_unique<StructMember>("", &i32, StructMemberDecorationList()));
 
-  Struct s{StructDecoration::kNone, std::move(members)};
+  Struct s{std::move(members)};
+  EXPECT_FALSE(s.IsValid());
+}
+
+TEST_F(StructTest, IsValid_NoneDecoration) {
+  type::I32Type i32;
+
+  StructMemberList members;
+  members.push_back(
+      std::make_unique<StructMember>("a", &i32, StructMemberDecorationList()));
+
+  StructDecorationList decos;
+  decos.push_back(StructDecoration::kNone);
+
+  Struct s{std::move(decos), std::move(members)};
   EXPECT_FALSE(s.IsValid());
 }
 
 TEST_F(StructTest, ToStr) {
   type::I32Type i32;
-  Source source{27, 4};
+
   StructMemberList members;
   members.emplace_back(
       std::make_unique<StructMember>("a", &i32, StructMemberDecorationList()));
 
-  Struct s{source, StructDecoration::kNone, std::move(members)};
+  StructDecorationList decos;
+  decos.push_back(StructDecoration::kBlock);
+
+  Struct s{std::move(decos), std::move(members)};
 
   std::ostringstream out;
   s.to_str(out, 2);
   EXPECT_EQ(out.str(), R"(  Struct{
+    [[block]]
     StructMember{a: __i32}
   }
 )");
diff --git a/src/ast/type/struct_type.h b/src/ast/type/struct_type.h
index 97cb4f2..6a41e67 100644
--- a/src/ast/type/struct_type.h
+++ b/src/ast/type/struct_type.h
@@ -42,9 +42,7 @@
   const std::string& name() const { return name_; }
 
   /// @returns true if the struct has a block decoration
-  bool IsBlockDecorated() const {
-    return struct_->decoration() == StructDecoration::kBlock;
-  }
+  bool IsBlockDecorated() const { return struct_->IsBlockDecorated(); }
 
   /// @returns true if the type is a struct type
   bool IsStruct() const override;
diff --git a/src/reader/spirv/function_memory_test.cc b/src/reader/spirv/function_memory_test.cc
index 5d4a436..267ae07 100644
--- a/src/reader/spirv/function_memory_test.cc
+++ b/src/reader/spirv/function_memory_test.cc
@@ -806,7 +806,8 @@
   }
   RTArr -> __array__u32_stride_4
   S -> __struct_S
-  [[block]] Struct{
+  Struct{
+    [[block]]
     StructMember{[[ offset 0 ]] field0: __u32}
     StructMember{[[ offset 4 ]] field1: __alias_RTArr__array__u32_stride_4}
   })"));
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index a21bf6d..92c6a29 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -789,13 +789,13 @@
     const spvtools::opt::analysis::Struct* struct_ty) {
   // Compute the struct decoration.
   auto struct_decorations = this->GetDecorationsFor(type_id);
-  auto ast_struct_decoration = ast::StructDecoration::kNone;
+  ast::StructDecorationList ast_struct_decorations;
   if (struct_decorations.size() == 1) {
     const auto decoration = struct_decorations[0][0];
     if (decoration == SpvDecorationBlock) {
-      ast_struct_decoration = ast::StructDecoration::kBlock;
+      ast_struct_decorations.push_back(ast::StructDecoration::kBlock);
     } else if (decoration == SpvDecorationBufferBlock) {
-      ast_struct_decoration = ast::StructDecoration::kBlock;
+      ast_struct_decorations.push_back(ast::StructDecoration::kBlock);
       remap_buffer_block_type_.insert(type_id);
     } else {
       Fail() << "struct with ID " << type_id
@@ -859,8 +859,8 @@
   }
 
   // Now make the struct.
-  auto ast_struct = std::make_unique<ast::Struct>(ast_struct_decoration,
-                                                  std::move(ast_members));
+  auto ast_struct = std::make_unique<ast::Struct>(
+      std::move(ast_struct_decorations), std::move(ast_members));
   // The struct type will be assigned a name during EmitAliasTypes.
   auto ast_struct_type =
       std::make_unique<ast::type::StructType>(std::move(ast_struct));
diff --git a/src/reader/spirv/parser_impl_convert_type_test.cc b/src/reader/spirv/parser_impl_convert_type_test.cc
index 8ee2f1d..3f18da7 100644
--- a/src/reader/spirv/parser_impl_convert_type_test.cc
+++ b/src/reader/spirv/parser_impl_convert_type_test.cc
@@ -582,7 +582,8 @@
   EXPECT_TRUE(type->IsStruct());
   std::stringstream ss;
   type->AsStruct()->impl()->to_str(ss, 0);
-  EXPECT_THAT(ss.str(), Eq(R"([[block]] Struct{
+  EXPECT_THAT(ss.str(), Eq(R"(Struct{
+  [[block]]
   StructMember{field0: __u32}
 }
 )"));
diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc
index 139358e..06a81c8 100644
--- a/src/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/reader/spirv/parser_impl_module_var_test.cc
@@ -1311,7 +1311,8 @@
     __alias_S__struct_S
   }
   S -> __struct_S
-  [[block]] Struct{
+  Struct{
+    [[block]]
     StructMember{field0: __u32}
     StructMember{field1: __f32}
     StructMember{field2: __array__u32_2}
@@ -1338,7 +1339,8 @@
     __alias_S__struct_S
   }
   S -> __struct_S
-  [[block]] Struct{
+  Struct{
+    [[block]]
     StructMember{field0: __u32}
     StructMember{field1: __f32}
     StructMember{field2: __array__u32_2}
@@ -1369,7 +1371,8 @@
     __alias_S__struct_S
   }
   S -> __struct_S
-  [[block]] Struct{
+  Struct{
+    [[block]]
     StructMember{field0: __mat_2_3__f32}
   }
 })")) << module_str;
@@ -1398,7 +1401,8 @@
     __alias_S__struct_S
   }
   S -> __struct_S
-  [[block]] Struct{
+  Struct{
+    [[block]]
     StructMember{field0: __mat_2_3__f32}
   }
 })")) << module_str;
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 26044d4..a91712f 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -1445,14 +1445,21 @@
 }
 
 // struct_decl
-//   : struct_decoration_decl? STRUCT struct_body_decl
+//   : struct_decoration_decl* STRUCT struct_body_decl
 std::unique_ptr<ast::type::StructType> ParserImpl::struct_decl() {
   auto t = peek();
   auto source = t.source();
 
-  auto deco = struct_decoration_decl();
-  if (has_error())
-    return nullptr;
+  ast::StructDecorationList decos;
+  for (;;) {
+    size_t s = decos.size();
+    if (!struct_decoration_decl(decos)) {
+      return nullptr;
+    }
+    if (decos.size() == s) {
+      break;
+    }
+  }
 
   t = next();
   if (!t.IsStruct()) {
@@ -1466,22 +1473,25 @@
   }
 
   return std::make_unique<ast::type::StructType>(
-      std::make_unique<ast::Struct>(source, deco, std::move(body)));
+      std::make_unique<ast::Struct>(source, std::move(decos), std::move(body)));
 }
 
 // struct_decoration_decl
 //  : ATTR_LEFT struct_decoration ATTR_RIGHT
-ast::StructDecoration ParserImpl::struct_decoration_decl() {
+bool ParserImpl::struct_decoration_decl(ast::StructDecorationList& decos) {
   auto t = peek();
-  if (!t.IsAttrLeft())
-    return ast::StructDecoration::kNone;
+  if (!t.IsAttrLeft()) {
+    return true;
+  }
 
   auto deco = struct_decoration(peek(1));
-  if (has_error())
-    return ast::StructDecoration::kNone;
-  if (deco == ast::StructDecoration::kNone) {
-    return deco;
+  if (has_error()) {
+    return false;
   }
+  if (deco == ast::StructDecoration::kNone) {
+    return true;
+  }
+  decos.push_back(deco);
 
   next();  // Consume the peek of [[
   next();  // Consume the peek from the struct_decoration
@@ -1489,10 +1499,10 @@
   t = next();
   if (!t.IsAttrRight()) {
     set_error(t, "missing ]] for struct decoration");
-    return ast::StructDecoration::kNone;
+    return false;
   }
 
-  return deco;
+  return true;
 }
 
 // struct_decoration
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 5cc5248..bd7ae82 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -155,9 +155,11 @@
   /// Parses a `struct_decl` grammar element
   /// @returns the struct type or nullptr on error
   std::unique_ptr<ast::type::StructType> struct_decl();
-  /// Parses a `struct_decoration_decl` grammar element
-  /// @returns the struct decoration or StructDecoraton::kNone
-  ast::StructDecoration struct_decoration_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
@@ -177,7 +179,8 @@
   /// Parses a `function_decl` grammar element
   /// @returns the parsed function, nullptr otherwise
   std::unique_ptr<ast::Function> function_decl();
-  /// Parses a `function_decoration_decl` grammar element
+  /// 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);
diff --git a/src/reader/wgsl/parser_impl_struct_decl_test.cc b/src/reader/wgsl/parser_impl_struct_decl_test.cc
index b1480e1..178b3a5 100644
--- a/src/reader/wgsl/parser_impl_struct_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decl_test.cc
@@ -48,6 +48,26 @@
   ASSERT_EQ(s->impl()->members().size(), 2u);
   EXPECT_EQ(s->impl()->members()[0]->name(), "a");
   EXPECT_EQ(s->impl()->members()[1]->name(), "b");
+  ASSERT_EQ(s->impl()->decorations().size(), 1u);
+  EXPECT_EQ(s->impl()->decorations()[0], ast::StructDecoration::kBlock);
+}
+
+TEST_F(ParserImplTest, StructDecl_ParsesWithMultipleDecoration) {
+  auto* p = parser(R"(
+[[block]]
+[[block]] struct {
+  a : f32;
+  b : f32;
+})");
+  auto s = p->struct_decl();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_NE(s, nullptr);
+  ASSERT_EQ(s->impl()->members().size(), 2u);
+  EXPECT_EQ(s->impl()->members()[0]->name(), "a");
+  EXPECT_EQ(s->impl()->members()[1]->name(), "b");
+  ASSERT_EQ(s->impl()->decorations().size(), 2u);
+  EXPECT_EQ(s->impl()->decorations()[0], ast::StructDecoration::kBlock);
+  EXPECT_EQ(s->impl()->decorations()[1], ast::StructDecoration::kBlock);
 }
 
 TEST_F(ParserImplTest, StructDecl_EmptyMembers) {
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 bd64e51..5c0ef8e 100644
--- a/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
@@ -23,14 +23,17 @@
 
 TEST_F(ParserImplTest, StructDecorationDecl_Parses) {
   auto* p = parser("[[block]]");
-  auto d = p->struct_decoration_decl();
+  ast::StructDecorationList decos;
+  ASSERT_TRUE(p->struct_decoration_decl(decos));
   ASSERT_FALSE(p->has_error());
-  EXPECT_EQ(d, ast::StructDecoration::kBlock);
+  EXPECT_EQ(decos.size(), 1u);
+  EXPECT_EQ(decos[0], ast::StructDecoration::kBlock);
 }
 
 TEST_F(ParserImplTest, StructDecorationDecl_MissingAttrRight) {
   auto* p = parser("[[block");
-  p->struct_decoration_decl();
+  ast::StructDecorationList decos;
+  ASSERT_FALSE(p->struct_decoration_decl(decos));
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:8: missing ]] for struct decoration");
 }
@@ -38,8 +41,10 @@
 // Note, this isn't an error because it could be an array decoration
 TEST_F(ParserImplTest, StructDecorationDecl_InvalidDecoration) {
   auto* p = parser("[[invalid]]");
-  p->struct_decoration_decl();
+  ast::StructDecorationList decos;
+  ASSERT_TRUE(p->struct_decoration_decl(decos));
   ASSERT_FALSE(p->has_error());
+  EXPECT_TRUE(decos.empty());
 }
 
 }  // namespace
diff --git a/src/transform/vertex_pulling_transform.cc b/src/transform/vertex_pulling_transform.cc
index fe12192..5e4d7ce 100644
--- a/src/transform/vertex_pulling_transform.cc
+++ b/src/transform/vertex_pulling_transform.cc
@@ -226,9 +226,12 @@
   members.push_back(std::make_unique<ast::StructMember>(
       kStructBufferName, internal_array_type, std::move(member_dec)));
 
-  auto* struct_type = ctx_->type_mgr().Get(
-      std::make_unique<ast::type::StructType>(std::make_unique<ast::Struct>(
-          ast::StructDecoration::kBlock, std::move(members))));
+  ast::StructDecorationList decos;
+  decos.push_back(ast::StructDecoration::kBlock);
+
+  auto* struct_type =
+      ctx_->type_mgr().Get(std::make_unique<ast::type::StructType>(
+          std::make_unique<ast::Struct>(std::move(decos), std::move(members))));
 
   for (uint32_t i = 0; i < vertex_state_->vertex_buffers.size(); ++i) {
     // The decorated variable with struct type
diff --git a/src/type_determiner_test.cc b/src/type_determiner_test.cc
index 74adef9..4b11bf4 100644
--- a/src/type_determiner_test.cc
+++ b/src/type_determiner_test.cc
@@ -1022,8 +1022,7 @@
   members.push_back(std::make_unique<ast::StructMember>("second_member", &f32,
                                                         std::move(decos)));
 
-  auto strct = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                             std::move(members));
+  auto strct = std::make_unique<ast::Struct>(std::move(members));
 
   ast::type::StructType st(std::move(strct));
 
@@ -1058,8 +1057,7 @@
   members.push_back(std::make_unique<ast::StructMember>("second_member", &f32,
                                                         std::move(decos)));
 
-  auto strct = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                             std::move(members));
+  auto strct = std::make_unique<ast::Struct>(std::move(members));
 
   auto st = std::make_unique<ast::type::StructType>(std::move(strct));
   ast::type::AliasType alias("alias", st.get());
@@ -1164,8 +1162,7 @@
   b_members.push_back(
       std::make_unique<ast::StructMember>("foo", &vec4, std::move(decos)));
 
-  auto strctB = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                              std::move(b_members));
+  auto strctB = std::make_unique<ast::Struct>(std::move(b_members));
   ast::type::StructType stB(std::move(strctB));
   stB.set_name("B");
 
@@ -1175,8 +1172,7 @@
   a_members.push_back(
       std::make_unique<ast::StructMember>("mem", &vecB, std::move(decos)));
 
-  auto strctA = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                              std::move(a_members));
+  auto strctA = std::make_unique<ast::Struct>(std::move(a_members));
 
   ast::type::StructType stA(std::move(strctA));
   stA.set_name("A");
diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc
index 4a5b76d..85bbda4 100644
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ b/src/writer/hlsl/generator_impl_type_test.cc
@@ -261,9 +261,11 @@
   members.push_back(
       std::make_unique<ast::StructMember>("b", &f32, std::move(b_deco)));
 
-  auto str = std::make_unique<ast::Struct>();
-  str->set_members(std::move(members));
-  str->set_decoration(ast::StructDecoration::kBlock);
+  ast::StructDecorationList decos;
+  decos.push_back(ast::StructDecoration::kBlock);
+
+  auto str =
+      std::make_unique<ast::Struct>(std::move(decos), std::move(members));
 
   ast::type::StructType s(std::move(str));
 
diff --git a/src/writer/msl/generator_impl_type_test.cc b/src/writer/msl/generator_impl_type_test.cc
index 6d4f8d1..02a0581 100644
--- a/src/writer/msl/generator_impl_type_test.cc
+++ b/src/writer/msl/generator_impl_type_test.cc
@@ -296,9 +296,10 @@
   members.push_back(
       std::make_unique<ast::StructMember>("b", &f32, std::move(b_deco)));
 
-  auto str = std::make_unique<ast::Struct>();
-  str->set_members(std::move(members));
-  str->set_decoration(ast::StructDecoration::kBlock);
+  ast::StructDecorationList decos;
+  decos.push_back(ast::StructDecoration::kBlock);
+  auto str =
+      std::make_unique<ast::Struct>(std::move(decos), std::move(members));
 
   ast::type::StructType s(std::move(str));
 
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 45b8e34..c10f957 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -2384,14 +2384,9 @@
   OperandList ops;
   ops.push_back(result);
 
-  if (impl->decoration() == ast::StructDecoration::kBlock) {
+  if (impl->IsBlockDecorated()) {
     push_annot(spv::Op::OpDecorate,
                {Operand::Int(struct_id), Operand::Int(SpvDecorationBlock)});
-  } else {
-    if (impl->decoration() != ast::StructDecoration::kNone) {
-      error_ = "unknown struct decoration";
-      return false;
-    }
   }
 
   auto& members = impl->members();
diff --git a/src/writer/spirv/builder_accessor_expression_test.cc b/src/writer/spirv/builder_accessor_expression_test.cc
index 44b5038..7bafbed 100644
--- a/src/writer/spirv/builder_accessor_expression_test.cc
+++ b/src/writer/spirv/builder_accessor_expression_test.cc
@@ -305,8 +305,7 @@
   members.push_back(
       std::make_unique<ast::StructMember>("b", &f32, std::move(decos)));
 
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                         std::move(members));
+  auto s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType s_type(std::move(s));
   s_type.set_name("my_struct");
 
@@ -363,15 +362,15 @@
   inner_members.push_back(
       std::make_unique<ast::StructMember>("b", &f32, std::move(decos)));
 
-  ast::type::StructType inner_struct(std::make_unique<ast::Struct>(
-      ast::StructDecoration::kNone, std::move(inner_members)));
+  ast::type::StructType inner_struct(
+      std::make_unique<ast::Struct>(std::move(inner_members)));
 
   ast::StructMemberList outer_members;
   outer_members.push_back(std::make_unique<ast::StructMember>(
       "inner", &inner_struct, std::move(decos)));
 
-  ast::type::StructType s_type(std::make_unique<ast::Struct>(
-      ast::StructDecoration::kNone, std::move(outer_members)));
+  ast::type::StructType s_type(
+      std::make_unique<ast::Struct>(std::move(outer_members)));
   s_type.set_name("my_struct");
 
   ast::Variable var("ident", ast::StorageClass::kFunction, &s_type);
@@ -431,8 +430,8 @@
   inner_members.push_back(
       std::make_unique<ast::StructMember>("b", &f32, std::move(decos)));
 
-  ast::type::StructType inner_struct(std::make_unique<ast::Struct>(
-      ast::StructDecoration::kNone, std::move(inner_members)));
+  ast::type::StructType inner_struct(
+      std::make_unique<ast::Struct>(std::move(inner_members)));
 
   ast::type::AliasType alias("Inner", &inner_struct);
 
@@ -440,8 +439,8 @@
   outer_members.push_back(
       std::make_unique<ast::StructMember>("inner", &alias, std::move(decos)));
 
-  ast::type::StructType s_type(std::make_unique<ast::Struct>(
-      ast::StructDecoration::kNone, std::move(outer_members)));
+  ast::type::StructType s_type(
+      std::make_unique<ast::Struct>(std::move(outer_members)));
   s_type.set_name("my_struct");
 
   ast::Variable var("ident", ast::StorageClass::kFunction, &s_type);
@@ -501,15 +500,15 @@
   inner_members.push_back(
       std::make_unique<ast::StructMember>("b", &f32, std::move(decos)));
 
-  ast::type::StructType inner_struct(std::make_unique<ast::Struct>(
-      ast::StructDecoration::kNone, std::move(inner_members)));
+  ast::type::StructType inner_struct(
+      std::make_unique<ast::Struct>(std::move(inner_members)));
 
   ast::StructMemberList outer_members;
   outer_members.push_back(std::make_unique<ast::StructMember>(
       "inner", &inner_struct, std::move(decos)));
 
-  ast::type::StructType s_type(std::make_unique<ast::Struct>(
-      ast::StructDecoration::kNone, std::move(outer_members)));
+  ast::type::StructType s_type(
+      std::make_unique<ast::Struct>(std::move(outer_members)));
   s_type.set_name("my_struct");
 
   ast::Variable var("ident", ast::StorageClass::kFunction, &s_type);
@@ -576,15 +575,15 @@
   inner_members.push_back(
       std::make_unique<ast::StructMember>("b", &f32, std::move(decos)));
 
-  ast::type::StructType inner_struct(std::make_unique<ast::Struct>(
-      ast::StructDecoration::kNone, std::move(inner_members)));
+  ast::type::StructType inner_struct(
+      std::make_unique<ast::Struct>(std::move(inner_members)));
 
   ast::StructMemberList outer_members;
   outer_members.push_back(std::make_unique<ast::StructMember>(
       "inner", &inner_struct, std::move(decos)));
 
-  ast::type::StructType s_type(std::make_unique<ast::Struct>(
-      ast::StructDecoration::kNone, std::move(outer_members)));
+  ast::type::StructType s_type(
+      std::make_unique<ast::Struct>(std::move(outer_members)));
   s_type.set_name("my_struct");
 
   ast::Variable var("ident", ast::StorageClass::kFunction, &s_type);
@@ -863,15 +862,13 @@
   ast::StructMemberList members;
   members.push_back(
       std::make_unique<ast::StructMember>("baz", &vec3, std::move(decos)));
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                         std::move(members));
+  auto s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType c_type(std::move(s));
   c_type.set_name("C");
 
   members.push_back(
       std::make_unique<ast::StructMember>("bar", &c_type, std::move(decos)));
-  s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                    std::move(members));
+  s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType b_type(std::move(s));
   b_type.set_name("B");
 
@@ -879,8 +876,7 @@
 
   members.push_back(std::make_unique<ast::StructMember>("foo", &b_ary_type,
                                                         std::move(decos)));
-  s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                    std::move(members));
+  s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType a_type(std::move(s));
   a_type.set_name("A");
 
diff --git a/src/writer/spirv/builder_assign_test.cc b/src/writer/spirv/builder_assign_test.cc
index d81af3b..ef36625 100644
--- a/src/writer/spirv/builder_assign_test.cc
+++ b/src/writer/spirv/builder_assign_test.cc
@@ -269,8 +269,7 @@
   members.push_back(
       std::make_unique<ast::StructMember>("b", &f32, std::move(decos)));
 
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                         std::move(members));
+  auto s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType s_type(std::move(s));
   s_type.set_name("my_struct");
 
diff --git a/src/writer/spirv/builder_constructor_expression_test.cc b/src/writer/spirv/builder_constructor_expression_test.cc
index db910e7..2624086 100644
--- a/src/writer/spirv/builder_constructor_expression_test.cc
+++ b/src/writer/spirv/builder_constructor_expression_test.cc
@@ -1747,8 +1747,7 @@
   members.push_back(
       std::make_unique<ast::StructMember>("b", &vec, std::move(decos)));
 
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                         std::move(members));
+  auto s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType s_type(std::move(s));
   s_type.set_name("my_struct");
 
@@ -1959,8 +1958,7 @@
   members.push_back(
       std::make_unique<ast::StructMember>("a", &f32, std::move(decos)));
 
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                         std::move(members));
+  auto s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType s_type(std::move(s));
   s_type.set_name("my_struct");
 
diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc
index a16c21e..f89c0e3 100644
--- a/src/writer/spirv/builder_intrinsic_test.cc
+++ b/src/writer/spirv/builder_intrinsic_test.cc
@@ -2732,8 +2732,7 @@
   members.push_back(
       std::make_unique<ast::StructMember>("a", &ary, std::move(decos)));
 
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                         std::move(members));
+  auto s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType s_type(std::move(s));
   s_type.set_name("my_struct");
 
@@ -2792,8 +2791,7 @@
   members.push_back(
       std::make_unique<ast::StructMember>("a", &ary, std::move(decos)));
 
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                         std::move(members));
+  auto s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType s_type(std::move(s));
   s_type.set_name("my_struct");
 
@@ -2854,8 +2852,7 @@
   members.push_back(
       std::make_unique<ast::StructMember>("a", &ary, std::move(decos)));
 
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                         std::move(members));
+  auto s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType s_type(std::move(s));
   s_type.set_name("my_struct");
 
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index 4e2ae08..3a7e84d 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -328,8 +328,7 @@
   members.push_back(
       std::make_unique<ast::StructMember>("a", &f32, std::move(decos)));
 
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                         std::move(members));
+  auto s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType s_type(std::move(s));
   s_type.set_name("my_struct");
 
@@ -355,7 +354,10 @@
   members.push_back(
       std::make_unique<ast::StructMember>("a", &f32, std::move(decos)));
 
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kBlock,
+  ast::StructDecorationList struct_decos;
+  struct_decos.push_back(ast::StructDecoration::kBlock);
+
+  auto s = std::make_unique<ast::Struct>(std::move(struct_decos),
                                          std::move(members));
   ast::type::StructType s_type(std::move(s));
   s_type.set_name("my_struct");
@@ -390,8 +392,7 @@
   members.push_back(
       std::make_unique<ast::StructMember>("b", &f32, std::move(b_decos)));
 
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                         std::move(members));
+  auto s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType s_type(std::move(s));
 
   ast::Module mod;
@@ -429,8 +430,7 @@
   members.push_back(std::make_unique<ast::StructMember>("c", &glsl_mat4x4,
                                                         std::move(empty_c)));
 
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                         std::move(members));
+  auto s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType s_type(std::move(s));
 
   ast::Module mod;
@@ -477,8 +477,7 @@
   members.push_back(std::make_unique<ast::StructMember>("c", &glsl_mat4x4,
                                                         std::move(c_decos)));
 
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                         std::move(members));
+  auto s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType s_type(std::move(s));
 
   ast::Module mod;
@@ -543,8 +542,7 @@
   members.push_back(std::make_unique<ast::StructMember>("c", &glsl_mat4x4,
                                                         std::move(c_decos)));
 
-  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
-                                         std::move(members));
+  auto s = std::make_unique<ast::Struct>(std::move(members));
   ast::type::StructType s_type(std::move(s));
 
   ast::Module mod;
diff --git a/src/writer/wgsl/generator_impl.cc b/src/writer/wgsl/generator_impl.cc
index a8a4a88..446c92b 100644
--- a/src/writer/wgsl/generator_impl.cc
+++ b/src/writer/wgsl/generator_impl.cc
@@ -437,8 +437,8 @@
     }
   } else if (type->IsStruct()) {
     auto* str = type->AsStruct()->impl();
-    if (str->decoration() != ast::StructDecoration::kNone) {
-      out_ << "[[" << str->decoration() << "]] ";
+    for (auto deco : str->decorations()) {
+      out_ << "[[" << deco << "]]" << std::endl;
     }
     out_ << "struct {" << std::endl;
 
diff --git a/src/writer/wgsl/generator_impl_type_test.cc b/src/writer/wgsl/generator_impl_type_test.cc
index c6e1352..495e1e4 100644
--- a/src/writer/wgsl/generator_impl_type_test.cc
+++ b/src/writer/wgsl/generator_impl_type_test.cc
@@ -160,15 +160,18 @@
   members.push_back(
       std::make_unique<ast::StructMember>("b", &f32, std::move(b_deco)));
 
-  auto str = std::make_unique<ast::Struct>();
-  str->set_members(std::move(members));
-  str->set_decoration(ast::StructDecoration::kBlock);
+  ast::StructDecorationList decos;
+  decos.push_back(ast::StructDecoration::kBlock);
+
+  auto str =
+      std::make_unique<ast::Struct>(std::move(decos), std::move(members));
 
   ast::type::StructType s(std::move(str));
 
   GeneratorImpl g;
   ASSERT_TRUE(g.EmitType(&s)) << g.error();
-  EXPECT_EQ(g.result(), R"([[block]] struct {
+  EXPECT_EQ(g.result(), R"([[block]]
+struct {
   a : i32;
   [[offset(4)]] b : f32;
 })");