Improve fasta parser try/catch error messages and recovery
Change-Id: I6e591fa5d03c73aa6f3e8060f2ca310f68be9a78
Reviewed-on: https://dart-review.googlesource.com/48709
Commit-Queue: Dan Rubel <danrubel@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 6b1d4d1..1d0416c 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -321,6 +321,7 @@
ParserErrorCode.ANNOTATION_ON_ENUM_CONSTANT,
ParserErrorCode.ASYNC_KEYWORD_USED_AS_IDENTIFIER,
ParserErrorCode.BREAK_OUTSIDE_OF_LOOP,
+ ParserErrorCode.CATCH_SYNTAX,
ParserErrorCode.CLASS_IN_CLASS,
ParserErrorCode.COLON_IN_PLACE_OF_IN,
ParserErrorCode.CONSTRUCTOR_WITH_RETURN_TYPE,
diff --git a/pkg/analyzer/lib/src/dart/error/syntactic_errors.dart b/pkg/analyzer/lib/src/dart/error/syntactic_errors.dart
index 98e8ea6..87d69d3 100644
--- a/pkg/analyzer/lib/src/dart/error/syntactic_errors.dart
+++ b/pkg/analyzer/lib/src/dart/error/syntactic_errors.dart
@@ -67,6 +67,12 @@
"A break statement can't be used outside of a loop or switch statement.",
correction: "Try removing the break statement.");
+ static const ParserErrorCode CATCH_SYNTAX = const ParserErrorCode(
+ 'CATCH_SYNTAX',
+ "'catch' must be followed by '(identifier)' or '(identifier, identifier)'.",
+ correction:
+ "No types are needed, the first is given by 'on', the second is always 'StackTrace'.");
+
static const ParserErrorCode CLASS_IN_CLASS = const ParserErrorCode(
'CLASS_IN_CLASS', "Classes can't be declared inside other classes.",
correction: "Try moving the class to the top-level.");
diff --git a/pkg/analyzer/lib/src/fasta/error_converter.dart b/pkg/analyzer/lib/src/fasta/error_converter.dart
index c517b31..8fe08ec 100644
--- a/pkg/analyzer/lib/src/fasta/error_converter.dart
+++ b/pkg/analyzer/lib/src/fasta/error_converter.dart
@@ -52,6 +52,10 @@
length,
[lexeme()]);
return;
+ case "CATCH_SYNTAX":
+ errorReporter?.reportErrorForOffset(
+ ParserErrorCode.CATCH_SYNTAX, offset, length);
+ return;
case "CLASS_IN_CLASS":
errorReporter?.reportErrorForOffset(
ParserErrorCode.CLASS_IN_CLASS, offset, length);
diff --git a/pkg/analyzer/test/generated/parser_test.dart b/pkg/analyzer/test/generated/parser_test.dart
index 18fa2ba..ef46fe2 100644
--- a/pkg/analyzer/test/generated/parser_test.dart
+++ b/pkg/analyzer/test/generated/parser_test.dart
@@ -15196,6 +15196,77 @@
expect(statement.finallyBlock, isNull);
}
+ void test_parseTryStatement_catch_error_missingCatchParam() {
+ var statement = parseStatement('try {} catch () {}') as TryStatement;
+ listener.assertErrors(usingFastaParser
+ ? [expectedError(ParserErrorCode.CATCH_SYNTAX, 14, 1)]
+ : [
+ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 14, 1),
+ ]);
+ expect(statement.tryKeyword, isNotNull);
+ expect(statement.body, isNotNull);
+ NodeList<CatchClause> catchClauses = statement.catchClauses;
+ expect(catchClauses, hasLength(1));
+ CatchClause clause = catchClauses[0];
+ expect(clause.onKeyword, isNull);
+ expect(clause.exceptionType, isNull);
+ expect(clause.catchKeyword, isNotNull);
+ expect(clause.exceptionParameter, isNotNull);
+ expect(clause.comma, isNull);
+ expect(clause.stackTraceParameter, isNull);
+ expect(clause.body, isNotNull);
+ expect(statement.finallyKeyword, isNull);
+ expect(statement.finallyBlock, isNull);
+ }
+
+ void test_parseTryStatement_catch_error_missingCatchTrace() {
+ var statement = parseStatement('try {} catch (e,) {}') as TryStatement;
+ listener.assertErrors(usingFastaParser
+ ? [expectedError(ParserErrorCode.CATCH_SYNTAX, 14, 1)]
+ : [
+ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 14, 1),
+ ]);
+ expect(statement.tryKeyword, isNotNull);
+ expect(statement.body, isNotNull);
+ NodeList<CatchClause> catchClauses = statement.catchClauses;
+ expect(catchClauses, hasLength(1));
+ CatchClause clause = catchClauses[0];
+ expect(clause.onKeyword, isNull);
+ expect(clause.exceptionType, isNull);
+ expect(clause.catchKeyword, isNotNull);
+ expect(clause.exceptionParameter, isNotNull);
+ expect(clause.comma, isNotNull);
+ expect(clause.stackTraceParameter, isNotNull);
+ expect(clause.body, isNotNull);
+ expect(statement.finallyKeyword, isNull);
+ expect(statement.finallyBlock, isNull);
+ }
+
+ void test_parseTryStatement_catch_error_missingCatchParen() {
+ var statement = parseStatement('try {} catch {}') as TryStatement;
+ listener.assertErrors(usingFastaParser
+ ? [expectedError(ParserErrorCode.CATCH_SYNTAX, 13, 1)]
+ : [
+ expectedError(ParserErrorCode.EXPECTED_TOKEN, 13, 1),
+ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 13, 1),
+ expectedError(ParserErrorCode.EXPECTED_TOKEN, 13, 1)
+ ]);
+ expect(statement.tryKeyword, isNotNull);
+ expect(statement.body, isNotNull);
+ NodeList<CatchClause> catchClauses = statement.catchClauses;
+ expect(catchClauses, hasLength(1));
+ CatchClause clause = catchClauses[0];
+ expect(clause.onKeyword, isNull);
+ expect(clause.exceptionType, isNull);
+ expect(clause.catchKeyword, isNotNull);
+ expect(clause.exceptionParameter, isNotNull);
+ expect(clause.comma, isNull);
+ expect(clause.stackTraceParameter, isNull);
+ expect(clause.body, isNotNull);
+ expect(statement.finallyKeyword, isNull);
+ expect(statement.finallyBlock, isNull);
+ }
+
void test_parseTryStatement_catch_finally() {
var statement =
parseStatement('try {} catch (e, s) {} finally {}') as TryStatement;
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/try_statement_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/try_statement_test.dart
index 8b7046d..af85dc1 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/try_statement_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/try_statement_test.dart
@@ -53,23 +53,19 @@
new TestDescriptor(
'catch',
'try {} catch',
- [
- ParserErrorCode.EXPECTED_TOKEN,
- ParserErrorCode.MISSING_IDENTIFIER,
- ParserErrorCode.EXPECTED_TOKEN
- ],
+ [ParserErrorCode.CATCH_SYNTAX, ParserErrorCode.EXPECTED_TOKEN],
"try {} catch (e) {}",
- allFailing: true),
+ failing: ['block']),
new TestDescriptor(
'catch_leftParen',
'try {} catch (',
[
- ParserErrorCode.MISSING_IDENTIFIER,
- ParserErrorCode.EXPECTED_TOKEN,
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.CATCH_SYNTAX,
ParserErrorCode.EXPECTED_TOKEN
],
"try {} catch (e) {}",
- allFailing: true),
+ failing: allExceptEof),
new TestDescriptor(
'catch_identifier',
'try {} catch (e',
@@ -80,12 +76,12 @@
'catch_identifierComma',
'try {} catch (e, ',
[
- ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.CATCH_SYNTAX,
ParserErrorCode.EXPECTED_TOKEN,
- ParserErrorCode.EXPECTED_TOKEN
+ ScannerErrorCode.EXPECTED_TOKEN
],
"try {} catch (e, _s_) {}",
- allFailing: true),
+ failing: allExceptEof),
new TestDescriptor(
'catch_identifierCommaIdentifier',
'try {} catch (e, s',
@@ -101,23 +97,19 @@
new TestDescriptor(
'on_catch',
'try {} on A catch',
- [
- ParserErrorCode.EXPECTED_TOKEN,
- ParserErrorCode.MISSING_IDENTIFIER,
- ParserErrorCode.EXPECTED_TOKEN
- ],
+ [ParserErrorCode.CATCH_SYNTAX, ParserErrorCode.EXPECTED_TOKEN],
"try {} on A catch (e) {}",
- allFailing: true),
+ failing: ['block']),
new TestDescriptor(
'on_catch_leftParen',
'try {} on A catch (',
[
- ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.CATCH_SYNTAX,
ParserErrorCode.EXPECTED_TOKEN,
- ParserErrorCode.EXPECTED_TOKEN
+ ScannerErrorCode.EXPECTED_TOKEN
],
"try {} on A catch (e) {}",
- allFailing: true),
+ failing: allExceptEof),
new TestDescriptor(
'on_catch_identifier',
'try {} on A catch (e',
@@ -128,12 +120,12 @@
'on_catch_identifierComma',
'try {} on A catch (e, ',
[
- ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.CATCH_SYNTAX,
ParserErrorCode.EXPECTED_TOKEN,
- ParserErrorCode.EXPECTED_TOKEN
+ ScannerErrorCode.EXPECTED_TOKEN
],
"try {} on A catch (e, _s_) {}",
- allFailing: true),
+ failing: allExceptEof),
new TestDescriptor(
'on_catch_identifierCommaIdentifier',
'try {} on A catch (e, s',
diff --git a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
index d351d58..725203e 100644
--- a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
+++ b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
@@ -480,6 +480,7 @@
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode messageCatchSyntax = const MessageCode("CatchSyntax",
+ analyzerCode: "CATCH_SYNTAX",
dart2jsCode: "*ignored*",
message:
r"""'catch' must be followed by '(identifier)' or '(identifier, identifier)'.""",
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 66a831e..8f72afc 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -6009,29 +6009,51 @@
Token comma = null;
if (identical(value, 'catch')) {
catchKeyword = token;
+
Token openParens = catchKeyword.next;
- Token exceptionName = openParens.next;
- Token commaOrCloseParens = exceptionName.next;
- Token traceName = commaOrCloseParens.next;
- Token closeParens = traceName.next;
if (!optional("(", openParens)) {
- // Handled below by parseFormalParameters.
- } else if (!exceptionName.isIdentifier) {
+ reportRecoverableError(openParens, fasta.messageCatchSyntax);
+ BeginToken open = new SyntheticBeginToken(
+ TokenType.OPEN_PAREN, openParens.charOffset);
+ Token identifier = open.setNext(new SyntheticStringToken(
+ TokenType.IDENTIFIER, '', openParens.charOffset));
+ Token close = identifier.setNext(
+ new SyntheticToken(TokenType.CLOSE_PAREN, openParens.charOffset));
+ open.endGroup = close;
+ rewriter.insertTokenAfter(catchKeyword, open);
+ openParens = open;
+ }
+
+ Token exceptionName = openParens.next;
+ if (!exceptionName.isIdentifier) {
reportRecoverableError(exceptionName, fasta.messageCatchSyntax);
- } else if (optional(")", commaOrCloseParens)) {
+ if (!exceptionName.isKeywordOrIdentifier) {
+ exceptionName = new SyntheticStringToken(
+ TokenType.IDENTIFIER, '', exceptionName.charOffset, 0);
+ rewriter.insertTokenAfter(openParens, exceptionName);
+ }
+ }
+
+ Token commaOrCloseParens = exceptionName.next;
+ if (optional(")", commaOrCloseParens)) {
// OK: `catch (identifier)`.
} else if (!optional(",", commaOrCloseParens)) {
reportRecoverableError(exceptionName, fasta.messageCatchSyntax);
} else {
comma = commaOrCloseParens;
+ Token traceName = comma.next;
if (!traceName.isIdentifier) {
reportRecoverableError(exceptionName, fasta.messageCatchSyntax);
- } else if (!optional(")", closeParens)) {
+ if (!traceName.isKeywordOrIdentifier) {
+ traceName = new SyntheticStringToken(
+ TokenType.IDENTIFIER, '', traceName.charOffset, 0);
+ rewriter.insertTokenAfter(comma, traceName);
+ }
+ } else if (!optional(")", traceName.next)) {
reportRecoverableError(exceptionName, fasta.messageCatchSyntax);
}
}
- lastConsumed =
- parseFormalParametersRequiredOpt(token, MemberKind.Catch);
+ lastConsumed = parseFormalParameters(catchKeyword, MemberKind.Catch);
token = lastConsumed.next;
}
listener.endCatchClause(token);
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index e93ba87..01a8aa0 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -40,8 +40,6 @@
CantInferTypeDueToInconsistentOverrides/example: Fail
CantUseSuperBoundedTypeForInstanceCreation/analyzerCode: Fail
CantUseSuperBoundedTypeForInstanceCreation/example: Fail
-CatchSyntax/analyzerCode: Fail
-CatchSyntax/example: Fail
ColonInPlaceOfIn/example: Fail
ConflictsWithConstructor/analyzerCode: Fail
ConflictsWithConstructor/example: Fail
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 295071e..dcdc6d1 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -927,7 +927,12 @@
CatchSyntax:
template: "'catch' must be followed by '(identifier)' or '(identifier, identifier)'."
tip: "No types are needed, the first is given by 'on', the second is always 'StackTrace'."
+ analyzerCode: CATCH_SYNTAX
dart2jsCode: "*ignored*"
+ statement:
+ - "try {} catch {}"
+ - "try {} catch () {}"
+ - "try {} catch (e,) {}"
SuperNullAware:
template: "'super' can't be null."