Update struct member decorations to allow multiple blocks.

This CL updates the struct member decoration parsing to allow multiple
blocks of decorations.

Bug: tint:240
Change-Id: I97293ef30333f63c33bbc6e728dba11abc020c7c
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/29280
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index a91712f..032c14c 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -1557,12 +1557,21 @@
 }
 
 // struct_member
-//   : struct_member_decoration_decl variable_ident_decl SEMICOLON
+//   : struct_member_decoration_decl+ variable_ident_decl SEMICOLON
 std::unique_ptr<ast::StructMember> ParserImpl::struct_member() {
   auto t = peek();
   auto source = t.source();
 
-  auto decos = struct_member_decoration_decl();
+  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;
 
@@ -1590,21 +1599,21 @@
 //   :
 //   | ATTR_LEFT (struct_member_decoration COMMA)*
 //                struct_member_decoration ATTR_RIGHT
-ast::StructMemberDecorationList ParserImpl::struct_member_decoration_decl() {
+bool ParserImpl::struct_member_decoration_decl(
+    ast::StructMemberDecorationList& decos) {
   auto t = peek();
-  if (!t.IsAttrLeft())
-    return {};
+  if (!t.IsAttrLeft()) {
+    return true;
+  }
 
   next();  // Consume the peek
 
   t = peek();
   if (t.IsAttrRight()) {
     set_error(t, "empty struct member decoration found");
-    return {};
+    return false;
   }
 
-  ast::StructMemberDecorationList decos;
-  bool found_offset = false;
   for (;;) {
     auto deco = struct_member_decoration();
     if (has_error())
@@ -1612,13 +1621,6 @@
     if (deco == nullptr)
       break;
 
-    if (deco->IsOffset()) {
-      if (found_offset) {
-        set_error(peek(), "duplicate offset decoration found");
-        return {};
-      }
-      found_offset = true;
-    }
     decos.push_back(std::move(deco));
 
     t = next();
@@ -1628,9 +1630,9 @@
 
   if (!t.IsAttrRight()) {
     set_error(t, "missing ]] for struct member decoration");
-    return {};
+    return false;
   }
-  return decos;
+  return true;
 }
 
 // struct_member_decoration
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index bd7ae82..2d53423 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -170,9 +170,11 @@
   /// Parses a `struct_member` grammar element
   /// @returns the struct member or nullptr
   std::unique_ptr<ast::StructMember> struct_member();
-  /// Parses a `struct_member_decoration_decl` grammar element
+  /// Parses a `struct_member_decoration_decl` grammar element, appending newly
+  /// parsed decorations to the end of |decos|.
+  /// @params decos the decoration list
   /// @returns the list of decorations
-  ast::StructMemberDecorationList struct_member_decoration_decl();
+  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();
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 9f57e0e..af0f721 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,43 +24,41 @@
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_EmptyStr) {
   auto* p = parser("");
-  auto deco = p->struct_member_decoration_decl();
+  ast::StructMemberDecorationList decos;
+  ASSERT_TRUE(p->struct_member_decoration_decl(decos));
   ASSERT_FALSE(p->has_error());
-  EXPECT_EQ(deco.size(), 0u);
+  EXPECT_EQ(decos.size(), 0u);
 }
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_EmptyBlock) {
   auto* p = parser("[[]]");
-  auto deco = p->struct_member_decoration_decl();
+  ast::StructMemberDecorationList decos;
+  ASSERT_FALSE(p->struct_member_decoration_decl(decos));
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:3: empty struct member decoration found");
 }
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_Single) {
   auto* p = parser("[[offset(4)]]");
-  auto deco = p->struct_member_decoration_decl();
+  ast::StructMemberDecorationList decos;
+  ASSERT_TRUE(p->struct_member_decoration_decl(decos));
   ASSERT_FALSE(p->has_error());
-  ASSERT_EQ(deco.size(), 1u);
-  EXPECT_TRUE(deco[0]->IsOffset());
-}
-
-TEST_F(ParserImplTest, StructMemberDecorationDecl_HandlesDuplicate) {
-  auto* p = parser("[[offset(2), offset(4)]]");
-  auto deco = p->struct_member_decoration_decl();
-  ASSERT_TRUE(p->has_error()) << p->error();
-  EXPECT_EQ(p->error(), "1:23: duplicate offset decoration found");
+  ASSERT_EQ(decos.size(), 1u);
+  EXPECT_TRUE(decos[0]->IsOffset());
 }
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_InvalidDecoration) {
   auto* p = parser("[[offset(nan)]]");
-  auto deco = p->struct_member_decoration_decl();
+  ast::StructMemberDecorationList decos;
+  ASSERT_FALSE(p->struct_member_decoration_decl(decos));
   ASSERT_TRUE(p->has_error()) << p->error();
   EXPECT_EQ(p->error(), "1:10: invalid value for offset decoration");
 }
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_MissingClose) {
   auto* p = parser("[[offset(4)");
-  auto deco = p->struct_member_decoration_decl();
+  ast::StructMemberDecorationList decos;
+  ASSERT_FALSE(p->struct_member_decoration_decl(decos));
   ASSERT_TRUE(p->has_error()) << p->error();
   EXPECT_EQ(p->error(), "1:12: missing ]] for struct member decoration");
 }
diff --git a/src/reader/wgsl/parser_impl_struct_member_test.cc b/src/reader/wgsl/parser_impl_struct_member_test.cc
index 7efe135..3c1ca11 100644
--- a/src/reader/wgsl/parser_impl_struct_member_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_member_test.cc
@@ -52,6 +52,24 @@
   EXPECT_EQ(m->decorations()[0]->AsOffset()->offset(), 2u);
 }
 
+TEST_F(ParserImplTest, StructMember_ParsesWithMultipleDecorations) {
+  auto* i32 = tm()->Get(std::make_unique<ast::type::I32Type>());
+
+  auto* p = parser(R"([[offset(2)]]
+[[offset(4)]] a : i32;)");
+  auto m = p->struct_member();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_NE(m, nullptr);
+
+  EXPECT_EQ(m->name(), "a");
+  EXPECT_EQ(m->type(), i32);
+  EXPECT_EQ(m->decorations().size(), 2u);
+  EXPECT_TRUE(m->decorations()[0]->IsOffset());
+  EXPECT_EQ(m->decorations()[0]->AsOffset()->offset(), 2u);
+  EXPECT_TRUE(m->decorations()[1]->IsOffset());
+  EXPECT_EQ(m->decorations()[1]->AsOffset()->offset(), 4u);
+}
+
 TEST_F(ParserImplTest, StructMember_InvalidDecoration) {
   auto* p = parser("[[offset(nan)]] a : i32;");
   auto m = p->struct_member();
diff --git a/src/writer/wgsl/generator_impl.cc b/src/writer/wgsl/generator_impl.cc
index 446c92b..187d06e 100644
--- a/src/writer/wgsl/generator_impl.cc
+++ b/src/writer/wgsl/generator_impl.cc
@@ -444,24 +444,14 @@
 
     increment_indent();
     for (const auto& mem : str->members()) {
-      make_indent();
-      if (!mem->decorations().empty()) {
-        out_ << "[[";
-        bool first = true;
-        for (const auto& deco : mem->decorations()) {
-          if (!first) {
-            out_ << ", ";
-          }
+      for (const auto& deco : mem->decorations()) {
+        make_indent();
 
-          first = false;
-          // TODO(dsinclair): Split this out when we have more then one
-          assert(deco->IsOffset());
-
-          out_ << "offset(" << deco->AsOffset()->offset() << ")";
-        }
-        out_ << "]] ";
+        // TODO(dsinclair): Split this out when we have more then one
+        assert(deco->IsOffset());
+        out_ << "[[offset(" << deco->AsOffset()->offset() << ")]]" << std::endl;
       }
-
+      make_indent();
       out_ << mem->name() << " : ";
       if (!EmitType(mem->type())) {
         return false;
diff --git a/src/writer/wgsl/generator_impl_alias_type_test.cc b/src/writer/wgsl/generator_impl_alias_type_test.cc
index e0b04ff..009b236 100644
--- a/src/writer/wgsl/generator_impl_alias_type_test.cc
+++ b/src/writer/wgsl/generator_impl_alias_type_test.cc
@@ -62,7 +62,8 @@
   ASSERT_TRUE(g.EmitAliasType(&alias)) << g.error();
   EXPECT_EQ(g.result(), R"(type a = struct {
   a : f32;
-  [[offset(4)]] b : i32;
+  [[offset(4)]]
+  b : i32;
 };
 )");
 }
diff --git a/src/writer/wgsl/generator_impl_type_test.cc b/src/writer/wgsl/generator_impl_type_test.cc
index 495e1e4..d7360b3 100644
--- a/src/writer/wgsl/generator_impl_type_test.cc
+++ b/src/writer/wgsl/generator_impl_type_test.cc
@@ -143,7 +143,8 @@
   ASSERT_TRUE(g.EmitType(&s)) << g.error();
   EXPECT_EQ(g.result(), R"(struct {
   a : i32;
-  [[offset(4)]] b : f32;
+  [[offset(4)]]
+  b : f32;
 })");
 }
 
@@ -173,7 +174,8 @@
   EXPECT_EQ(g.result(), R"([[block]]
 struct {
   a : i32;
-  [[offset(4)]] b : f32;
+  [[offset(4)]]
+  b : f32;
 })");
 }