reader/wgsl: Handle parentheses inside expect_argument_expression_list

This simplifies the callsites, which were previously each having to
handle the "empty list" case (and soon: trailing commas). This is also
a better match for the grammar rules in the WGSL spec.

Change-Id: I88ed54f94964f7b23a0fd9b584659037abb567ff
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/49465
Commit-Queue: James Price <jrprice@google.com>
Auto-Submit: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 7bbad45..874e05b 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -2131,31 +2131,20 @@
 }
 
 // func_call_stmt
-//    : IDENT PAREN_LEFT argument_expression_list* PAREN_RIGHT
+//    : IDENT argument_expression_list
 Maybe<ast::CallStatement*> ParserImpl::func_call_stmt() {
   auto t = peek();
   auto t2 = peek(1);
   if (!t.IsIdentifier() || !t2.IsParenLeft())
     return Failure::kNoMatch;
 
+  next();  // Consume the first peek
+
   auto source = t.source();
-
-  next();  // Consume the peek
-  next();  // Consume the 2nd peek
-
   auto name = t.to_str();
 
-  ast::ExpressionList params;
-
-  t = peek();
-  if (!t.IsParenRight() && !t.IsEof()) {
-    auto list = expect_argument_expression_list();
-    if (list.errored)
-      return Failure::kErrored;
-    params = std::move(list.value);
-  }
-
-  if (!expect("call statement", Token::Type::kParenRight))
+  auto params = expect_argument_expression_list("function call");
+  if (params.errored)
     return Failure::kErrored;
 
   return create<ast::CallStatement>(
@@ -2163,7 +2152,7 @@
                     source,
                     create<ast::IdentifierExpression>(
                         source, builder_.Symbols().Register(name)),
-                    std::move(params)));
+                    std::move(params.value)));
 }
 
 // break_stmt
@@ -2197,7 +2186,7 @@
 
 // primary_expression
 //   : IDENT argument_expression_list?
-//   | type_decl PAREN_LEFT argument_expression_list* PAREN_RIGHT
+//   | type_decl argument_expression_list
 //   | const_literal
 //   | paren_rhs_stmt
 //   | BITCAST LESS_THAN type_decl GREATER_THAN paren_rhs_stmt
@@ -2239,23 +2228,13 @@
     auto* ident = create<ast::IdentifierExpression>(
         t.source(), builder_.Symbols().Register(t.to_str()));
 
-    if (match(Token::Type::kParenLeft, &source)) {
-      return sync(Token::Type::kParenRight, [&]() -> Maybe<ast::Expression*> {
-        ast::ExpressionList params;
+    if (peek().IsParenLeft()) {
+      auto params = expect_argument_expression_list("function call");
+      if (params.errored)
+        return Failure::kErrored;
 
-        auto t2 = peek();
-        if (!t2.IsParenRight() && !t2.IsEof()) {
-          auto list = expect_argument_expression_list();
-          if (list.errored)
-            return Failure::kErrored;
-          params = list.value;
-        }
-
-        if (!expect("call expression", Token::Type::kParenRight))
-          return Failure::kErrored;
-
-        return create<ast::CallExpression>(source, ident, params);
-      });
+      return create<ast::CallExpression>(source, ident,
+                                         std::move(params.value));
     }
 
     return ident;
@@ -2265,25 +2244,12 @@
   if (type.errored)
     return Failure::kErrored;
   if (type.matched) {
-    auto expr = expect_paren_block(
-        "type constructor", [&]() -> Expect<ast::TypeConstructorExpression*> {
-          t = peek();
-          if (t.IsParenRight() || t.IsEof())
-            return create<ast::TypeConstructorExpression>(
-                source, type.value, ast::ExpressionList{});
-
-          auto params = expect_argument_expression_list();
-          if (params.errored)
-            return Failure::kErrored;
-
-          return create<ast::TypeConstructorExpression>(source, type.value,
-                                                        params.value);
-        });
-
-    if (expr.errored)
+    auto params = expect_argument_expression_list("type constructor");
+    if (params.errored)
       return Failure::kErrored;
 
-    return expr.value;
+    return create<ast::TypeConstructorExpression>(source, type.value,
+                                                  std::move(params.value));
   }
 
   return Failure::kNoMatch;
@@ -2339,28 +2305,34 @@
 }
 
 // argument_expression_list
-//   : (logical_or_expression COMMA)* logical_or_expression
-Expect<ast::ExpressionList> ParserImpl::expect_argument_expression_list() {
-  auto arg = logical_or_expression();
-  if (arg.errored)
-    return Failure::kErrored;
-  if (!arg.matched)
-    return add_error(peek(), "unable to parse argument expression");
-
-  ast::ExpressionList ret;
-  ret.push_back(arg.value);
-
-  while (match(Token::Type::kComma)) {
-    arg = logical_or_expression();
-    if (arg.errored)
-      return Failure::kErrored;
-    if (!arg.matched) {
-      return add_error(peek(),
-                       "unable to parse argument expression after comma");
+//   : PAREN_LEFT (logical_or_expression COMMA)* logical_or_expression
+//   PAREN_RIGHT
+Expect<ast::ExpressionList> ParserImpl::expect_argument_expression_list(
+    const std::string& use) {
+  return expect_paren_block(use, [&]() -> Expect<ast::ExpressionList> {
+    // Check for empty list.
+    // TODO(crbug.com/tint/739): Remove this (handled by !arg.matched).
+    if (peek().IsParenRight()) {
+      return ast::ExpressionList{};
     }
-    ret.push_back(arg.value);
-  }
-  return ret;
+
+    ast::ExpressionList ret;
+    while (synchronized_) {
+      auto arg = logical_or_expression();
+      if (arg.errored)
+        return Failure::kErrored;
+      if (!arg.matched) {
+        // TODO(crbug.com/tint/739): remove error to allow trailing commas.
+        return add_error(peek(), "unable to parse argument expression");
+      }
+      ret.push_back(arg.value);
+
+      if (!match(Token::Type::kComma)) {
+        break;
+      }
+    }
+    return ret;
+  });
 }
 
 // unary_expression
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 49a2d26..a18550c 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -552,8 +552,10 @@
   Maybe<ast::Expression*> primary_expression();
   /// Parses a `argument_expression_list` grammar element, erroring on parse
   /// failure.
+  /// @param use a description of what was being parsed if an error was raised
   /// @returns the list of arguments
-  Expect<ast::ExpressionList> expect_argument_expression_list();
+  Expect<ast::ExpressionList> expect_argument_expression_list(
+      const std::string& use);
   /// Parses the recursive portion of the postfix_expression
   /// @param prefix the left side of the expression
   /// @returns the parsed expression or nullptr
diff --git a/src/reader/wgsl/parser_impl_argument_expression_list_test.cc b/src/reader/wgsl/parser_impl_argument_expression_list_test.cc
index 926abc0..ca8ed6c 100644
--- a/src/reader/wgsl/parser_impl_argument_expression_list_test.cc
+++ b/src/reader/wgsl/parser_impl_argument_expression_list_test.cc
@@ -20,8 +20,8 @@
 namespace {
 
 TEST_F(ParserImplTest, ArgumentExpressionList_Parses) {
-  auto p = parser("a");
-  auto e = p->expect_argument_expression_list();
+  auto p = parser("(a)");
+  auto e = p->expect_argument_expression_list("argument list");
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_FALSE(e.errored);
 
@@ -29,9 +29,18 @@
   ASSERT_TRUE(e.value[0]->Is<ast::IdentifierExpression>());
 }
 
+TEST_F(ParserImplTest, ArgumentExpressionList_ParsesEmptyList) {
+  auto p = parser("()");
+  auto e = p->expect_argument_expression_list("argument list");
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(e.errored);
+
+  ASSERT_EQ(e.value.size(), 0u);
+}
+
 TEST_F(ParserImplTest, ArgumentExpressionList_ParsesMultiple) {
-  auto p = parser("a, -33, 1+2");
-  auto e = p->expect_argument_expression_list();
+  auto p = parser("(a, -33, 1+2)");
+  auto e = p->expect_argument_expression_list("argument list");
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_FALSE(e.errored);
 
@@ -41,20 +50,36 @@
   ASSERT_TRUE(e.value[2]->Is<ast::BinaryExpression>());
 }
 
-TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingExpression) {
-  auto p = parser("a, ");
-  auto e = p->expect_argument_expression_list();
+TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingLeftParen) {
+  auto p = parser("a)");
+  auto e = p->expect_argument_expression_list("argument list");
   ASSERT_TRUE(p->has_error());
   ASSERT_TRUE(e.errored);
-  EXPECT_EQ(p->error(), "1:4: unable to parse argument expression after comma");
+  EXPECT_EQ(p->error(), "1:1: expected '(' for argument list");
+}
+
+TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingRightParen) {
+  auto p = parser("(a");
+  auto e = p->expect_argument_expression_list("argument list");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  EXPECT_EQ(p->error(), "1:3: expected ')' for argument list");
+}
+
+TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingExpression) {
+  auto p = parser("(a, )");
+  auto e = p->expect_argument_expression_list("argument list");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(e.errored);
+  EXPECT_EQ(p->error(), "1:5: unable to parse argument expression");
 }
 
 TEST_F(ParserImplTest, ArgumentExpressionList_HandlesInvalidExpression) {
-  auto p = parser("if(a) {}");
-  auto e = p->expect_argument_expression_list();
+  auto p = parser("(if(a) {})");
+  auto e = p->expect_argument_expression_list("argument list");
   ASSERT_TRUE(p->has_error());
   ASSERT_TRUE(e.errored);
-  EXPECT_EQ(p->error(), "1:1: unable to parse argument expression");
+  EXPECT_EQ(p->error(), "1:2: unable to parse argument expression");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_call_stmt_test.cc b/src/reader/wgsl/parser_impl_call_stmt_test.cc
index 5a1dfc2..3a4d206 100644
--- a/src/reader/wgsl/parser_impl_call_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_call_stmt_test.cc
@@ -65,7 +65,7 @@
   EXPECT_TRUE(p->has_error());
   EXPECT_TRUE(e.errored);
   EXPECT_FALSE(e.matched);
-  EXPECT_EQ(p->error(), "1:3: expected ')' for call statement");
+  EXPECT_EQ(p->error(), "1:3: unable to parse argument expression");
 }
 
 TEST_F(ParserImplTest, Statement_Call_Missing_Semi) {
@@ -83,7 +83,7 @@
   EXPECT_TRUE(p->has_error());
   EXPECT_TRUE(e.errored);
   EXPECT_FALSE(e.matched);
-  EXPECT_EQ(p->error(), "1:5: expected ')' for call statement");
+  EXPECT_EQ(p->error(), "1:5: expected ')' for function call");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_error_msg_test.cc b/src/reader/wgsl/parser_impl_error_msg_test.cc
index 843309a..17d5cd4 100644
--- a/src/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/reader/wgsl/parser_impl_error_msg_test.cc
@@ -123,14 +123,14 @@
 
 TEST_F(ParserImplErrorTest, CallExprMissingRParen) {
   EXPECT("fn f() { x = f(1.; }",
-         "test.wgsl:1:18 error: expected ')' for call expression\n"
+         "test.wgsl:1:18 error: expected ')' for function call\n"
          "fn f() { x = f(1.; }\n"
          "                 ^\n");
 }
 
 TEST_F(ParserImplErrorTest, CallStmtMissingRParen) {
   EXPECT("fn f() { f(1.; }",
-         "test.wgsl:1:14 error: expected ')' for call statement\n"
+         "test.wgsl:1:14 error: expected ')' for function call\n"
          "fn f() { f(1.; }\n"
          "             ^\n");
 }
@@ -143,11 +143,10 @@
 }
 
 TEST_F(ParserImplErrorTest, CallStmtInvalidArgument1) {
-  EXPECT(
-      "fn f() { f(1.0, <); }",
-      "test.wgsl:1:17 error: unable to parse argument expression after comma\n"
-      "fn f() { f(1.0, <); }\n"
-      "                ^\n");
+  EXPECT("fn f() { f(1.0, <); }",
+         "test.wgsl:1:17 error: unable to parse argument expression\n"
+         "fn f() { f(1.0, <); }\n"
+         "                ^\n");
 }
 
 TEST_F(ParserImplErrorTest, CallStmtMissingSemicolon) {
diff --git a/src/reader/wgsl/parser_impl_singular_expression_test.cc b/src/reader/wgsl/parser_impl_singular_expression_test.cc
index 30e2067..2d40f24 100644
--- a/src/reader/wgsl/parser_impl_singular_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_singular_expression_test.cc
@@ -145,7 +145,7 @@
   EXPECT_TRUE(e.errored);
   EXPECT_EQ(e.value, nullptr);
   EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:6: unable to parse argument expression after comma");
+  EXPECT_EQ(p->error(), "1:6: unable to parse argument expression");
 }
 
 TEST_F(ParserImplTest, SingularExpression_Call_MissingRightParen) {
@@ -155,7 +155,7 @@
   EXPECT_TRUE(e.errored);
   EXPECT_EQ(e.value, nullptr);
   EXPECT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:3: expected ')' for call expression");
+  EXPECT_EQ(p->error(), "1:3: unable to parse argument expression");
 }
 
 TEST_F(ParserImplTest, SingularExpression_MemberAccessor) {