[tint][reader][wgsl]: Improve diagnostics for template error
Detect when expression lists parse as a template list and then fail to parse.
Give a clear diagnostic highlighting what the parser saw, and how to fix.
Fixed: tint:1945
Change-Id: I08be99cf4fc9cbf7e6bd95c2704f4fd385eb2b4d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/135642
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Auto-Submit: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index b7ad4c5..835c305 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -579,7 +579,7 @@
errored = true;
}
if (decl.matched) {
- if (!expect_attributes_consumed(attrs.value)) {
+ if (expect_attributes_consumed(attrs.value).errored) {
return Failure::kErrored;
}
return kSuccess;
@@ -590,7 +590,7 @@
errored = true;
}
if (str.matched) {
- if (!expect_attributes_consumed(attrs.value)) {
+ if (expect_attributes_consumed(attrs.value).errored) {
return Failure::kErrored;
}
return kSuccess;
@@ -2496,7 +2496,22 @@
if (peek_is(terminator)) {
break;
}
- if (!expect(use, Token::Type::kComma)) {
+
+ // Check if the next token is a template start, which was likely intended as a less-than.
+ if (expect_next_not_template_list(expr->source).errored) {
+ return Failure::kErrored; // expect_next_not_template_list() raised an error.
+ }
+ if (!match(Token::Type::kComma)) {
+ // Next expression is not a terminator or comma, so this is a parse error.
+
+ // Check if last parsed expression was a templated identifier, which was likely indented
+ // as a less-than / greater-than.
+ if (expect_not_templated_ident_expr(expr.value).errored) {
+ return Failure::kErrored; // expect_not_templated_ident_expr() raised an error.
+ }
+
+ // Emit the expected ',' error
+ expect(use, Token::Type::kComma);
return Failure::kErrored;
}
if (peek_is(terminator)) {
@@ -3074,12 +3089,58 @@
}
}
-bool ParserImpl::expect_attributes_consumed(utils::VectorRef<const ast::Attribute*> in) {
+Expect<Void> ParserImpl::expect_attributes_consumed(utils::VectorRef<const ast::Attribute*> in) {
if (in.IsEmpty()) {
- return true;
+ return kSuccess;
}
add_error(in[0]->source, "unexpected attributes");
- return false;
+ return Failure::kErrored;
+}
+
+Expect<Void> ParserImpl::expect_next_not_template_list(const Source& lhs_source) {
+ Source end;
+ if (!match(Token::Type::kTemplateArgsLeft, &end)) {
+ return kSuccess;
+ }
+
+ // Try to find end of template
+ for (size_t i = 0; i < 32; i++) {
+ if (auto& t = peek(i); t.type() == Token::Type::kTemplateArgsRight) {
+ end = t.source();
+ }
+ }
+ Source template_source = lhs_source;
+ template_source.range.end = end.range.end;
+ add_error(template_source, "parsed as template list");
+
+ if (auto rhs = expression(); rhs.matched) {
+ Source lt_source = lhs_source;
+ lt_source.range.end = rhs->source.range.end;
+ add_note(lt_source,
+ "if this is intended to be a less-than expression then wrap in parentheses");
+ }
+ return Failure::kErrored;
+}
+
+Expect<Void> ParserImpl::expect_not_templated_ident_expr(const ast::Expression* expr) {
+ auto* ident_expr = expr->As<ast::IdentifierExpression>();
+ if (!ident_expr) {
+ return kSuccess;
+ }
+ auto* ident = ident_expr->identifier->As<ast::TemplatedIdentifier>();
+ if (!ident) {
+ return kSuccess;
+ }
+
+ add_error(ident->source, "parsed as template list");
+
+ if (auto rhs = expression(); rhs.matched) {
+ Source gt_source = ident->arguments.Back()->source;
+ gt_source.range.end = rhs->source.range.end;
+ add_note(gt_source,
+ "if this is intended to be a greater-than expression then wrap in parentheses");
+ }
+ return Failure::kErrored;
}
// severity_control_name
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h
index 6d8c408..b167f56 100644
--- a/src/tint/reader/wgsl/parser_impl.h
+++ b/src/tint/reader/wgsl/parser_impl.h
@@ -824,7 +824,28 @@
/// Reports an error if the attribute list `list` is not empty.
/// Used to ensure that all attributes are consumed.
- bool expect_attributes_consumed(utils::VectorRef<const ast::Attribute*> list);
+ Expect<Void> expect_attributes_consumed(utils::VectorRef<const ast::Attribute*> list);
+
+ /// Raises an error if the next token is the start of a template list.
+ /// Used to hint to the user that the parser interpreted the following as a templated identifier
+ /// expression:
+ ///
+ /// ```
+ /// a < b, c >
+ /// ^~~~~~~~
+ /// ```
+ Expect<Void> expect_next_not_template_list(const Source& lhs_source);
+
+ /// Raises an error if the parsed expression is a templated identifier expression
+ /// Used to hint to the user that the parser intepreted the following as a templated identifier
+ /// expression:
+ ///
+ /// ```
+ /// a < b, c > d
+ /// ^^^^^^^^^^
+ /// expr
+ /// ```
+ Expect<Void> expect_not_templated_ident_expr(const ast::Expression* expr);
/// Parses the given enum, providing sensible error messages if the next token does not match
/// any of the enum values.
diff --git a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
index e5ce8b9..9156931 100644
--- a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
@@ -164,6 +164,30 @@
)");
}
+TEST_F(ParserImplErrorTest, CallStmtArgsTreatedAsTemplateLHS) {
+ EXPECT("fn f() { f( a, b.x < c, d > e ); }",
+ R"(test.wgsl:1:16 error: parsed as template list
+fn f() { f( a, b.x < c, d > e ); }
+ ^^^^^^^^^^^^
+
+test.wgsl:1:16 note: if this is intended to be a less-than expression then wrap in parentheses
+fn f() { f( a, b.x < c, d > e ); }
+ ^^^^^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, CallStmtArgsTreatedAsTemplateRHS) {
+ EXPECT("fn f() { f( a, b < c, d > e ); }",
+ R"(test.wgsl:1:16 error: parsed as template list
+fn f() { f( a, b < c, d > e ); }
+ ^^^^^^^^^^
+
+test.wgsl:1:23 note: if this is intended to be a greater-than expression then wrap in parentheses
+fn f() { f( a, b < c, d > e ); }
+ ^^^^^
+)");
+}
+
TEST_F(ParserImplErrorTest, CallStmtMissingRParen) {
EXPECT("fn f() { f(1.; }",
R"(test.wgsl:1:14 error: expected ',' for function call