Improve catch parameter recovery

Change-Id: Ib4479eb7682c7093e9076f12a85d5a2f911a3f1d
Reviewed-on: https://dart-review.googlesource.com/57022
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
This commit is contained in:
danrubel 2018-05-30 01:31:49 +00:00 committed by commit-bot@chromium.org
parent aa8e2ee178
commit d116e62d76
5 changed files with 77 additions and 64 deletions

View file

@ -15720,7 +15720,7 @@ main() {
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.CATCH_SYNTAX, 16, 1)]
: [
expectedError(ParserErrorCode.MISSING_IDENTIFIER, 14, 1),
]);

View file

@ -65,19 +65,10 @@ class TryStatementTest extends PartialCodeTest {
[
ScannerErrorCode.EXPECTED_TOKEN,
ParserErrorCode.CATCH_SYNTAX,
ParserErrorCode.CATCH_SYNTAX,
ParserErrorCode.MISSING_IDENTIFIER,
ParserErrorCode.EXPECTED_TOKEN
],
"try {} catch (e) {}",
failing: [
'eof',
'block',
'labeled',
'localFunctionNonVoid',
'localFunctionVoid',
'localVariable'
]),
failing: ['block', 'labeled', 'localFunctionNonVoid']),
new TestDescriptor(
'catch_identifier',
'try {} catch (e',
@ -87,25 +78,17 @@ class TryStatementTest extends PartialCodeTest {
ScannerErrorCode.EXPECTED_TOKEN
],
"try {} catch (e) {}",
failing: ['eof', 'block', 'labeled', 'localFunctionNonVoid']),
failing: ['eof', 'block']),
new TestDescriptor(
'catch_identifierComma',
'try {} catch (e, ',
[
ParserErrorCode.CATCH_SYNTAX,
ParserErrorCode.MISSING_IDENTIFIER,
ParserErrorCode.EXPECTED_TOKEN,
ScannerErrorCode.EXPECTED_TOKEN
],
"try {} catch (e, _s_) {}",
failing: [
'eof',
'block',
'labeled',
'localFunctionNonVoid',
'localFunctionVoid',
'localVariable'
]),
failing: ['block', 'labeled', 'localFunctionNonVoid']),
new TestDescriptor(
'catch_identifierCommaIdentifier',
'try {} catch (e, s',
@ -115,7 +98,7 @@ class TryStatementTest extends PartialCodeTest {
ScannerErrorCode.EXPECTED_TOKEN
],
"try {} catch (e, s) {}",
failing: ['eof', 'block', 'labeled', 'localFunctionNonVoid']),
failing: ['eof', 'block']),
new TestDescriptor('catch_rightParen', 'try {} catch (e, s)',
[ParserErrorCode.EXPECTED_TOKEN], "try {} catch (e, s) {}",
failing: ['block']),
@ -133,20 +116,11 @@ class TryStatementTest extends PartialCodeTest {
'try {} on A catch (',
[
ParserErrorCode.CATCH_SYNTAX,
ParserErrorCode.CATCH_SYNTAX,
ParserErrorCode.MISSING_IDENTIFIER,
ParserErrorCode.EXPECTED_TOKEN,
ScannerErrorCode.EXPECTED_TOKEN
],
"try {} on A catch (e) {}",
failing: [
'eof',
'block',
'labeled',
'localFunctionNonVoid',
'localFunctionVoid',
'localVariable'
]),
failing: ['block', 'labeled', 'localFunctionNonVoid']),
new TestDescriptor(
'on_catch_identifier',
'try {} on A catch (e',
@ -156,25 +130,17 @@ class TryStatementTest extends PartialCodeTest {
ScannerErrorCode.EXPECTED_TOKEN
],
"try {} on A catch (e) {}",
failing: ['eof', 'block', 'labeled', 'localFunctionNonVoid']),
failing: ['eof', 'block']),
new TestDescriptor(
'on_catch_identifierComma',
'try {} on A catch (e, ',
[
ParserErrorCode.MISSING_IDENTIFIER,
ParserErrorCode.CATCH_SYNTAX,
ParserErrorCode.EXPECTED_TOKEN,
ScannerErrorCode.EXPECTED_TOKEN
],
"try {} on A catch (e, _s_) {}",
failing: [
'eof',
'block',
'labeled',
'localFunctionVoid',
'localFunctionNonVoid',
'localVariable'
]),
failing: ['block', 'labeled', 'localFunctionNonVoid']),
new TestDescriptor(
'on_catch_identifierCommaIdentifier',
'try {} on A catch (e, s',
@ -184,7 +150,7 @@ class TryStatementTest extends PartialCodeTest {
ScannerErrorCode.EXPECTED_TOKEN
],
"try {} on A catch (e, s) {}",
failing: ['eof', 'block', 'labeled', 'localFunctionNonVoid']),
failing: ['eof', 'block']),
new TestDescriptor('on_catch_rightParen', 'try {} on A catch (e, s)',
[ParserErrorCode.EXPECTED_TOKEN], "try {} on A catch (e, s) {}",
failing: ['block']),

View file

@ -63,6 +63,10 @@ class IdentifierContext {
static const formalParameterDeclaration =
const FormalParameterDeclarationIdentifierContext();
/// Identifier is a formal parameter being declared as part of a catch block
/// in a try/catch/finally statement.
static const catchParameter = const CatchParameterIdentifierContext();
/// Identifier is the start of a library name (e.g. `foo` in the directive
/// 'library foo;`).
static const libraryName = const LibraryIdentifierContext();

View file

@ -16,6 +16,33 @@ import 'type_info.dart' show isValidTypeReference;
import 'util.dart' show isOneOfOrEof, optional;
/// See [IdentifierContext.catchParameter].
class CatchParameterIdentifierContext extends IdentifierContext {
const CatchParameterIdentifierContext() : super('catchParameter');
@override
Token ensureIdentifier(Token token, Parser parser) {
Token identifier = token.next;
assert(identifier.kind != IDENTIFIER_TOKEN);
if (identifier.isIdentifier) {
checkAsyncAwaitYieldAsIdentifier(identifier, parser);
return identifier;
}
// Recovery
parser.reportRecoverableError(identifier, fasta.messageCatchSyntax);
if (looksLikeStartOfNextStatement(identifier) ||
isOneOfOrEof(identifier, const [',', ')'])) {
return parser.rewriter.insertSyntheticIdentifier(token);
} else if (!identifier.isKeywordOrIdentifier) {
// When in doubt, consume the token to ensure we make progress
// but insert a synthetic identifier to satisfy listeners.
return parser.rewriter.insertSyntheticIdentifier(identifier);
}
return identifier;
}
}
/// See [IdentifierContext.classOrNamedMixinDeclaration].
class ClassOrNamedMixinIdentifierContext extends IdentifierContext {
const ClassOrNamedMixinIdentifierContext()
@ -471,6 +498,7 @@ class LabelDeclarationIdentifierContext extends IdentifierContext {
Token identifier = token.next;
assert(identifier.kind != IDENTIFIER_TOKEN);
if (identifier.isIdentifier) {
checkAsyncAwaitYieldAsIdentifier(identifier, parser);
return identifier;
}
@ -501,6 +529,7 @@ class LabelReferenceIdentifierContext extends IdentifierContext {
Token identifier = token.next;
assert(identifier.kind != IDENTIFIER_TOKEN);
if (identifier.isIdentifier) {
checkAsyncAwaitYieldAsIdentifier(identifier, parser);
return identifier;
}
@ -625,6 +654,7 @@ class MetadataReferenceIdentifierContext extends IdentifierContext {
Token identifier = token.next;
assert(identifier.kind != IDENTIFIER_TOKEN);
if (identifier.isIdentifier) {
checkAsyncAwaitYieldAsIdentifier(identifier, parser);
return identifier;
}

View file

@ -5466,32 +5466,45 @@ class Parser {
}
Token exceptionName = openParens.next;
if (!exceptionName.isIdentifier) {
reportRecoverableError(exceptionName, fasta.messageCatchSyntax);
if (!exceptionName.isKeywordOrIdentifier) {
exceptionName = new SyntheticStringToken(
TokenType.IDENTIFIER, '', exceptionName.charOffset, 0);
rewriter.insertTokenAfter(openParens, exceptionName);
}
if (exceptionName.kind != IDENTIFIER_TOKEN) {
exceptionName = IdentifierContext.catchParameter
.ensureIdentifier(openParens, this);
}
Token commaOrCloseParens = exceptionName.next;
if (optional(")", commaOrCloseParens)) {
if (optional(")", exceptionName.next)) {
// 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);
if (!traceName.isKeywordOrIdentifier) {
traceName = new SyntheticStringToken(
TokenType.IDENTIFIER, '', traceName.charOffset, 0);
rewriter.insertTokenAfter(comma, traceName);
comma = exceptionName.next;
if (!optional(",", comma)) {
// Recovery
if (!exceptionName.isSynthetic) {
reportRecoverableError(comma, fasta.messageCatchSyntax);
}
if (openParens.endGroup.isSynthetic) {
// The scanner did not place the synthetic ')' correctly.
rewriter.moveSynthetic(exceptionName, openParens.endGroup);
comma = null;
} else {
comma = rewriter.insertTokenAfter(exceptionName,
new SyntheticToken(TokenType.COMMA, comma.charOffset));
}
}
if (comma != null) {
Token traceName = comma.next;
if (traceName.kind != IDENTIFIER_TOKEN) {
traceName = IdentifierContext.catchParameter
.ensureIdentifier(comma, this);
}
if (!optional(")", traceName.next)) {
// Recovery
if (!traceName.isSynthetic) {
reportRecoverableError(exceptionName, fasta.messageCatchSyntax);
}
if (openParens.endGroup.isSynthetic) {
// The scanner did not place the synthetic ')' correctly.
rewriter.moveSynthetic(traceName, openParens.endGroup);
}
}
} else if (!optional(")", traceName.next)) {
reportRecoverableError(exceptionName, fasta.messageCatchSyntax);
}
}
lastConsumed = parseFormalParameters(catchKeyword, MemberKind.Catch);