reader/wgsl: Allow trailing commas in lists Also allows empty constexpr type constructors. Fixed: tint:739 Change-Id: Ic4729c13b6ac538491d5d1d3c7960e78fac80127 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/49443 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 6f75fde..d303610 100644 --- a/src/reader/wgsl/parser_impl.cc +++ b/src/reader/wgsl/parser_impl.cc
@@ -1416,16 +1416,16 @@ // param_list // : -// | (param COMMA)* param +// | (param COMMA)* param COMMA? Expect<ast::VariableList> ParserImpl::expect_param_list() { - // Check for an empty list. - auto t = peek(); - if (!t.IsIdentifier() && !t.IsAttrLeft()) { - return ast::VariableList{}; - } - ast::VariableList ret; while (synchronized_) { + // Check for the end of the list. + auto t = peek(); + if (!t.IsIdentifier() && !t.IsAttrLeft()) { + break; + } + auto param = expect_param(); if (param.errored) return Failure::kErrored; @@ -1912,34 +1912,26 @@ } // case_selectors -// : const_literal (COMMA const_literal)* +// : const_literal (COMMA const_literal)* COMMA? Expect<ast::CaseSelectorList> ParserImpl::expect_case_selectors() { ast::CaseSelectorList selectors; - for (;;) { - auto t = peek(); - auto matched_comma = match(Token::Type::kComma); - - if (selectors.empty() && matched_comma) - return add_error(t, "a selector is expected before the comma"); - if (matched_comma) - t = peek(); - + while (synchronized_) { auto cond = const_literal(); - if (cond.errored) + if (cond.errored) { return Failure::kErrored; - if (!cond.matched) { - if (matched_comma) { - return add_error(t, "a selector is expected after the comma"); - } + } else if (!cond.matched) { break; + } else if (!cond->Is<ast::IntLiteral>()) { + return add_error(cond.value->source(), + "invalid case selector must be an integer value"); } - if (!cond->Is<ast::IntLiteral>()) - return add_error(t, "invalid case selector must be an integer value"); - if (!selectors.empty() && !matched_comma) - return add_error(t, "expected a comma after the previous selector"); selectors.push_back(cond.value->As<ast::IntLiteral>()); + + if (!match(Token::Type::kComma)) { + break; + } } if (selectors.empty()) @@ -2305,25 +2297,18 @@ } // argument_expression_list -// : PAREN_LEFT (logical_or_expression COMMA)* logical_or_expression +// : PAREN_LEFT ((logical_or_expression COMMA)* logical_or_expression COMMA?)? // 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{}; - } - ast::ExpressionList ret; while (synchronized_) { auto arg = logical_or_expression(); - if (arg.errored) + 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"); + } else if (!arg.matched) { + break; } ret.push_back(arg.value); @@ -2838,7 +2823,7 @@ } // const_expr -// : type_decl PAREN_LEFT (const_expr COMMA)? const_expr PAREN_RIGHT +// : type_decl PAREN_LEFT ((const_expr COMMA)? const_expr COMMA?)? PAREN_RIGHT // | const_literal Expect<ast::ConstructorExpression*> ParserImpl::expect_const_expr() { auto t = peek(); @@ -2852,15 +2837,20 @@ auto params = expect_paren_block("type constructor", [&]() -> Expect<ast::ExpressionList> { ast::ExpressionList list; - auto param = expect_const_expr(); - if (param.errored) - return Failure::kErrored; - list.emplace_back(param.value); - while (match(Token::Type::kComma)) { - param = expect_const_expr(); - if (param.errored) + while (synchronized_) { + if (peek().IsParenRight()) { + break; + } + + auto arg = expect_const_expr(); + if (arg.errored) { return Failure::kErrored; - list.emplace_back(param.value); + } + list.emplace_back(arg.value); + + if (!match(Token::Type::kComma)) { + break; + } } return list; });
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 ca8ed6c..bb73660 100644 --- a/src/reader/wgsl/parser_impl_argument_expression_list_test.cc +++ b/src/reader/wgsl/parser_impl_argument_expression_list_test.cc
@@ -50,6 +50,17 @@ ASSERT_TRUE(e.value[2]->Is<ast::BinaryExpression>()); } +TEST_F(ParserImplTest, ArgumentExpressionList_TrailingComma) { + auto p = parser("(a, 42,)"); + 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(), 2u); + ASSERT_TRUE(e.value[0]->Is<ast::IdentifierExpression>()); + ASSERT_TRUE(e.value[1]->Is<ast::ConstructorExpression>()); +} + TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingLeftParen) { auto p = parser("a)"); auto e = p->expect_argument_expression_list("argument list"); @@ -66,12 +77,20 @@ EXPECT_EQ(p->error(), "1:3: expected ')' for argument list"); } -TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingExpression) { - auto p = parser("(a, )"); +TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingExpression_0) { + auto p = parser("(,)"); 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"); + EXPECT_EQ(p->error(), "1:2: expected ')' for argument list"); +} + +TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingExpression_1) { + 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: expected ')' for argument list"); } TEST_F(ParserImplTest, ArgumentExpressionList_HandlesInvalidExpression) { @@ -79,7 +98,7 @@ auto e = p->expect_argument_expression_list("argument list"); ASSERT_TRUE(p->has_error()); ASSERT_TRUE(e.errored); - EXPECT_EQ(p->error(), "1:2: unable to parse argument expression"); + EXPECT_EQ(p->error(), "1:2: expected ')' for argument list"); } } // namespace
diff --git a/src/reader/wgsl/parser_impl_call_stmt_test.cc b/src/reader/wgsl/parser_impl_call_stmt_test.cc index 3a4d206..ec41195 100644 --- a/src/reader/wgsl/parser_impl_call_stmt_test.cc +++ b/src/reader/wgsl/parser_impl_call_stmt_test.cc
@@ -59,13 +59,33 @@ EXPECT_TRUE(c->params()[2]->Is<ast::BinaryExpression>()); } +TEST_F(ParserImplTest, Statement_Call_WithParams_TrailingComma) { + auto p = parser("a(1, b,);"); + auto e = p->statement(); + ASSERT_FALSE(p->has_error()) << p->error(); + ASSERT_NE(e.value, nullptr); + EXPECT_TRUE(e.matched); + EXPECT_FALSE(e.errored); + + ASSERT_TRUE(e->Is<ast::CallStatement>()); + auto* c = e->As<ast::CallStatement>()->expr(); + + ASSERT_TRUE(c->func()->Is<ast::IdentifierExpression>()); + auto* ident = c->func()->As<ast::IdentifierExpression>(); + EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a")); + + EXPECT_EQ(c->params().size(), 2u); + EXPECT_TRUE(c->params()[0]->Is<ast::ConstructorExpression>()); + EXPECT_TRUE(c->params()[1]->Is<ast::IdentifierExpression>()); +} + TEST_F(ParserImplTest, Statement_Call_Missing_RightParen) { auto p = parser("a("); auto e = p->statement(); EXPECT_TRUE(p->has_error()); EXPECT_TRUE(e.errored); EXPECT_FALSE(e.matched); - EXPECT_EQ(p->error(), "1:3: unable to parse argument expression"); + EXPECT_EQ(p->error(), "1:3: expected ')' for function call"); } TEST_F(ParserImplTest, Statement_Call_Missing_Semi) {
diff --git a/src/reader/wgsl/parser_impl_const_expr_test.cc b/src/reader/wgsl/parser_impl_const_expr_test.cc index 8498ed4..00a053c 100644 --- a/src/reader/wgsl/parser_impl_const_expr_test.cc +++ b/src/reader/wgsl/parser_impl_const_expr_test.cc
@@ -47,6 +47,38 @@ EXPECT_FLOAT_EQ(c->literal()->As<ast::FloatLiteral>()->value(), 2.); } +TEST_F(ParserImplTest, ConstExpr_TypeDecl_Empty) { + auto p = parser("vec2<f32>()"); + auto e = p->expect_const_expr(); + ASSERT_FALSE(p->has_error()) << p->error(); + ASSERT_FALSE(e.errored); + ASSERT_TRUE(e->Is<ast::ConstructorExpression>()); + ASSERT_TRUE(e->Is<ast::TypeConstructorExpression>()); + + auto* t = e->As<ast::TypeConstructorExpression>(); + ASSERT_TRUE(t->type()->Is<sem::Vector>()); + EXPECT_EQ(t->type()->As<sem::Vector>()->size(), 2u); + + ASSERT_EQ(t->values().size(), 0u); +} + +TEST_F(ParserImplTest, ConstExpr_TypeDecl_TrailingComma) { + auto p = parser("vec2<f32>(1., 2.,)"); + auto e = p->expect_const_expr(); + ASSERT_FALSE(p->has_error()) << p->error(); + ASSERT_FALSE(e.errored); + ASSERT_TRUE(e->Is<ast::ConstructorExpression>()); + ASSERT_TRUE(e->Is<ast::TypeConstructorExpression>()); + + auto* t = e->As<ast::TypeConstructorExpression>(); + ASSERT_TRUE(t->type()->Is<sem::Vector>()); + EXPECT_EQ(t->type()->As<sem::Vector>()->size(), 2u); + + ASSERT_EQ(t->values().size(), 2u); + ASSERT_TRUE(t->values()[0]->Is<ast::ScalarConstructorExpression>()); + ASSERT_TRUE(t->values()[1]->Is<ast::ScalarConstructorExpression>()); +} + TEST_F(ParserImplTest, ConstExpr_TypeDecl_MissingRightParen) { auto p = parser("vec2<f32>(1., 2."); auto e = p->expect_const_expr(); @@ -65,15 +97,6 @@ EXPECT_EQ(p->error(), "1:11: expected '(' for type constructor"); } -TEST_F(ParserImplTest, ConstExpr_TypeDecl_HangingComma) { - auto p = parser("vec2<f32>(1.,)"); - auto e = p->expect_const_expr(); - ASSERT_TRUE(p->has_error()); - ASSERT_TRUE(e.errored); - ASSERT_EQ(e.value, nullptr); - EXPECT_EQ(p->error(), "1:14: unable to parse constant literal"); -} - TEST_F(ParserImplTest, ConstExpr_TypeDecl_MissingComma) { auto p = parser("vec2<f32>(1. 2."); auto e = p->expect_const_expr(); @@ -83,15 +106,6 @@ EXPECT_EQ(p->error(), "1:14: expected ')' for type constructor"); } -TEST_F(ParserImplTest, ConstExpr_MissingExpr) { - auto p = parser("vec2<f32>()"); - auto e = p->expect_const_expr(); - ASSERT_TRUE(p->has_error()); - ASSERT_TRUE(e.errored); - ASSERT_EQ(e.value, nullptr); - EXPECT_EQ(p->error(), "1:11: unable to parse constant literal"); -} - TEST_F(ParserImplTest, ConstExpr_InvalidExpr) { auto p = parser("vec2<f32>(1., if(a) {})"); auto e = p->expect_const_expr();
diff --git a/src/reader/wgsl/parser_impl_error_msg_test.cc b/src/reader/wgsl/parser_impl_error_msg_test.cc index 17d5cd4..bca27f3 100644 --- a/src/reader/wgsl/parser_impl_error_msg_test.cc +++ b/src/reader/wgsl/parser_impl_error_msg_test.cc
@@ -137,14 +137,14 @@ TEST_F(ParserImplErrorTest, CallStmtInvalidArgument0) { EXPECT("fn f() { f(<); }", - "test.wgsl:1:12 error: unable to parse argument expression\n" + "test.wgsl:1:12 error: expected ')' for function call\n" "fn f() { f(<); }\n" " ^\n"); } TEST_F(ParserImplErrorTest, CallStmtInvalidArgument1) { EXPECT("fn f() { f(1.0, <); }", - "test.wgsl:1:17 error: unable to parse argument expression\n" + "test.wgsl:1:17 error: expected ')' for function call\n" "fn f() { f(1.0, <); }\n" " ^\n"); } @@ -446,9 +446,9 @@ } TEST_F(ParserImplErrorTest, FunctionDeclParamMissing) { - EXPECT("fn f(x : i32, ) {}", - "test.wgsl:1:15 error: expected identifier for parameter\n" - "fn f(x : i32, ) {}\n" + EXPECT("fn f(x : i32, ,) {}", + "test.wgsl:1:15 error: expected ')' for function declaration\n" + "fn f(x : i32, ,) {}\n" " ^\n"); }
diff --git a/src/reader/wgsl/parser_impl_for_stmt_test.cc b/src/reader/wgsl/parser_impl_for_stmt_test.cc index e82521d..5b72897 100644 --- a/src/reader/wgsl/parser_impl_for_stmt_test.cc +++ b/src/reader/wgsl/parser_impl_for_stmt_test.cc
@@ -250,7 +250,7 @@ // Test a for loop with an invalid continuing statement. TEST_F(ForStmtErrorTest, InvalidContinuingAsFuncCall) { std::string for_str = "for (;; a(,) ) { }"; - std::string error_str = "1:11: unable to parse argument expression"; + std::string error_str = "1:11: expected ')' for function call"; TestForWithError(for_str, error_str); }
diff --git a/src/reader/wgsl/parser_impl_function_header_test.cc b/src/reader/wgsl/parser_impl_function_header_test.cc index fba796e..f509264 100644 --- a/src/reader/wgsl/parser_impl_function_header_test.cc +++ b/src/reader/wgsl/parser_impl_function_header_test.cc
@@ -33,6 +33,18 @@ EXPECT_TRUE(f->return_type->Is<sem::Void>()); } +TEST_F(ParserImplTest, FunctionHeader_TrailingComma) { + auto p = parser("fn main(a :i32,)"); + auto f = p->function_header(); + EXPECT_TRUE(f.matched); + EXPECT_FALSE(f.errored); + + EXPECT_EQ(f->name, "main"); + ASSERT_EQ(f->params.size(), 1u); + EXPECT_EQ(f->params[0]->symbol(), p->builder().Symbols().Get("a")); + EXPECT_TRUE(f->return_type->Is<sem::Void>()); +} + TEST_F(ParserImplTest, FunctionHeader_DecoratedReturnType) { auto p = parser("fn main() -> [[location(1)]] f32"); auto f = p->function_header(); @@ -77,12 +89,12 @@ } TEST_F(ParserImplTest, FunctionHeader_InvalidParamList) { - auto p = parser("fn main(a :i32,) -> i32"); + auto p = parser("fn main(a :i32, ,) -> i32"); auto f = p->function_header(); EXPECT_FALSE(f.matched); EXPECT_TRUE(f.errored); EXPECT_TRUE(p->has_error()); - EXPECT_EQ(p->error(), "1:16: expected identifier for parameter"); + EXPECT_EQ(p->error(), "1:17: expected ')' for function declaration"); } TEST_F(ParserImplTest, FunctionHeader_MissingParenRight) {
diff --git a/src/reader/wgsl/parser_impl_param_list_test.cc b/src/reader/wgsl/parser_impl_param_list_test.cc index 54e30bd..60c97d2 100644 --- a/src/reader/wgsl/parser_impl_param_list_test.cc +++ b/src/reader/wgsl/parser_impl_param_list_test.cc
@@ -87,12 +87,12 @@ EXPECT_EQ(e.value.size(), 0u); } -TEST_F(ParserImplTest, ParamList_HangingComma) { +TEST_F(ParserImplTest, ParamList_TrailingComma) { auto p = parser("a : i32,"); auto e = p->expect_param_list(); - ASSERT_TRUE(p->has_error()); - ASSERT_TRUE(e.errored); - EXPECT_EQ(p->error(), "1:9: expected identifier for parameter"); + ASSERT_FALSE(p->has_error()); + ASSERT_FALSE(e.errored); + EXPECT_EQ(e.value.size(), 1u); } TEST_F(ParserImplTest, ParamList_Decorations) {
diff --git a/src/reader/wgsl/parser_impl_primary_expression_test.cc b/src/reader/wgsl/parser_impl_primary_expression_test.cc index 884f1c4..dac0e80 100644 --- a/src/reader/wgsl/parser_impl_primary_expression_test.cc +++ b/src/reader/wgsl/parser_impl_primary_expression_test.cc
@@ -121,7 +121,7 @@ EXPECT_TRUE(e.errored); EXPECT_EQ(e.value, nullptr); ASSERT_TRUE(p->has_error()); - EXPECT_EQ(p->error(), "1:5: unable to parse argument expression"); + EXPECT_EQ(p->error(), "1:5: expected ')' for type constructor"); } TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_StructConstructor_Empty) {
diff --git a/src/reader/wgsl/parser_impl_singular_expression_test.cc b/src/reader/wgsl/parser_impl_singular_expression_test.cc index 2d40f24..d74d1bf 100644 --- a/src/reader/wgsl/parser_impl_singular_expression_test.cc +++ b/src/reader/wgsl/parser_impl_singular_expression_test.cc
@@ -128,6 +128,18 @@ EXPECT_TRUE(c->params()[2]->Is<ast::BinaryExpression>()); } +TEST_F(ParserImplTest, SingularExpression_Call_TrailingComma) { + auto p = parser("a(b, )"); + auto e = p->singular_expression(); + EXPECT_TRUE(e.matched); + EXPECT_FALSE(e.errored); + ASSERT_NE(e.value, nullptr); + + ASSERT_TRUE(e->Is<ast::CallExpression>()); + auto* c = e->As<ast::CallExpression>(); + EXPECT_EQ(c->params().size(), 1u); +} + TEST_F(ParserImplTest, SingularExpression_Call_InvalidArg) { auto p = parser("a(if(a) {})"); auto e = p->singular_expression(); @@ -135,17 +147,7 @@ EXPECT_TRUE(e.errored); EXPECT_EQ(e.value, nullptr); EXPECT_TRUE(p->has_error()); - EXPECT_EQ(p->error(), "1:3: unable to parse argument expression"); -} - -TEST_F(ParserImplTest, SingularExpression_Call_HangingComma) { - auto p = parser("a(b, )"); - auto e = p->singular_expression(); - EXPECT_FALSE(e.matched); - 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"); + EXPECT_EQ(p->error(), "1:3: expected ')' for function call"); } TEST_F(ParserImplTest, SingularExpression_Call_MissingRightParen) { @@ -155,7 +157,7 @@ EXPECT_TRUE(e.errored); EXPECT_EQ(e.value, nullptr); EXPECT_TRUE(p->has_error()); - EXPECT_EQ(p->error(), "1:3: unable to parse argument expression"); + EXPECT_EQ(p->error(), "1:3: expected ')' for function call"); } TEST_F(ParserImplTest, SingularExpression_MemberAccessor) {
diff --git a/src/reader/wgsl/parser_impl_switch_body_test.cc b/src/reader/wgsl/parser_impl_switch_body_test.cc index 39f33b4..99d3899 100644 --- a/src/reader/wgsl/parser_impl_switch_body_test.cc +++ b/src/reader/wgsl/parser_impl_switch_body_test.cc
@@ -28,10 +28,28 @@ ASSERT_NE(e.value, nullptr); ASSERT_TRUE(e->Is<ast::CaseStatement>()); EXPECT_FALSE(e->IsDefault()); + auto* stmt = e->As<ast::CaseStatement>(); + ASSERT_EQ(stmt->selectors().size(), 1u); + EXPECT_EQ(stmt->selectors()[0]->value_as_u32(), 1u); ASSERT_EQ(e->body()->size(), 1u); EXPECT_TRUE(e->body()->get(0)->Is<ast::AssignmentStatement>()); } +TEST_F(ParserImplTest, SwitchBody_Case_TrailingComma) { + auto p = parser("case 1, 2,: { }"); + auto e = p->switch_body(); + EXPECT_FALSE(p->has_error()) << p->error(); + EXPECT_TRUE(e.matched); + EXPECT_FALSE(e.errored); + ASSERT_NE(e.value, nullptr); + ASSERT_TRUE(e->Is<ast::CaseStatement>()); + EXPECT_FALSE(e->IsDefault()); + auto* stmt = e->As<ast::CaseStatement>(); + ASSERT_EQ(stmt->selectors().size(), 2u); + EXPECT_EQ(stmt->selectors()[0]->value_as_u32(), 1u); + EXPECT_EQ(stmt->selectors()[1]->value_as_u32(), 2u); +} + TEST_F(ParserImplTest, SwitchBody_Case_InvalidConstLiteral) { auto p = parser("case a == 4: { a = 4; }"); auto e = p->switch_body(); @@ -134,17 +152,7 @@ EXPECT_TRUE(e.errored); EXPECT_FALSE(e.matched); EXPECT_EQ(e.value, nullptr); - EXPECT_EQ(p->error(), "1:8: expected a comma after the previous selector"); -} - -TEST_F(ParserImplTest, SwitchBody_Case_MultipleSelectorsEndsWithComma) { - auto p = parser("case 1, 2,: { }"); - auto e = p->switch_body(); - EXPECT_TRUE(p->has_error()); - EXPECT_TRUE(e.errored); - EXPECT_FALSE(e.matched); - EXPECT_EQ(e.value, nullptr); - EXPECT_EQ(p->error(), "1:11: a selector is expected after the comma"); + EXPECT_EQ(p->error(), "1:8: expected ':' for case statement"); } TEST_F(ParserImplTest, SwitchBody_Case_MultipleSelectorsStartsWithComma) { @@ -154,7 +162,7 @@ EXPECT_TRUE(e.errored); EXPECT_FALSE(e.matched); EXPECT_EQ(e.value, nullptr); - EXPECT_EQ(p->error(), "1:6: a selector is expected before the comma"); + EXPECT_EQ(p->error(), "1:6: unable to parse case selectors"); } TEST_F(ParserImplTest, SwitchBody_Default) {