Add fasta parser function expression recovery

Change-Id: Id5e27b8f186da23fd141fda12621f1ae3bcd1f8c
Reviewed-on: https://dart-review.googlesource.com/30580
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
This commit is contained in:
Dan Rubel 2017-12-19 20:57:43 +00:00 committed by commit-bot@chromium.org
parent 414bad86df
commit 166cc11a64
3 changed files with 106 additions and 43 deletions

View file

@ -318,14 +318,17 @@ class KeywordContributorTest extends DartCompletionContributorTest {
addTestSource('main() {foo(() a^ {}}}');
await computeSuggestions();
// Fasta adds a closing paren after the first `}`
// and adds synthetic `,`s making `a` an argument
// and reports a single function expression argument
// while analyzer adds the closing paren before the `a`
// and adds synthetic `;`s making `a` a statement.
assertSuggestKeywords(
request.target.entity is Expression
? EXPRESSION_START_NO_INSTANCE
: STMT_START_OUTSIDE_CLASS,
pseudoKeywords: ['async', 'async*', 'sync*']);
if (request.target.entity is BlockFunctionBody) {
assertSuggestKeywords([],
pseudoKeywords: ['async', 'async*', 'sync*'],
relevance: DART_RELEVANCE_HIGH);
} else {
assertSuggestKeywords(STMT_START_OUTSIDE_CLASS,
pseudoKeywords: ['async', 'async*', 'sync*']);
}
}
test_anonymous_function_async3() async {
@ -360,7 +363,9 @@ class KeywordContributorTest extends DartCompletionContributorTest {
addTestSource('main() {foo("bar", () as^ => null');
await computeSuggestions();
assertSuggestKeywords([],
pseudoKeywords: ['async', 'async*', 'sync*'],
pseudoKeywords: request.target.entity is ExpressionFunctionBody
? ['async']
: ['async', 'async*', 'sync*'],
relevance: DART_RELEVANCE_HIGH);
}
@ -375,13 +380,16 @@ class KeywordContributorTest extends DartCompletionContributorTest {
test_anonymous_function_async9() async {
addTestSource('main() {foo(() a^ {})}}');
await computeSuggestions();
// Fasta adds synthetic `,`s making `a` an argument
// Fasta interprets the argument as a function expression
// while analyzer adds synthetic `;`s making `a` a statement.
assertSuggestKeywords(
request.target.entity is Expression
? EXPRESSION_START_NO_INSTANCE
: STMT_START_OUTSIDE_CLASS,
pseudoKeywords: ['async', 'async*', 'sync*']);
if (request.target.entity is BlockFunctionBody) {
assertSuggestKeywords([],
pseudoKeywords: ['async', 'async*', 'sync*'],
relevance: DART_RELEVANCE_HIGH);
} else {
assertSuggestKeywords(STMT_START_OUTSIDE_CLASS,
pseudoKeywords: ['async', 'async*', 'sync*']);
}
}
test_argument() async {

View file

@ -10225,6 +10225,28 @@ class B = Object with A {}''', codes: [ParserErrorCode.EXPECTED_TOKEN]);
]);
}
void test_incomplete_functionExpression() {
var expression = parseExpression("() a => null",
errors: usingFastaParser
? [expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 3, 1)]
: [expectedError(ParserErrorCode.MISSING_IDENTIFIER, 2, 1)]);
if (usingFastaParser) {
FunctionExpression functionExpression = expression;
expect(functionExpression.parameters.parameters, hasLength(0));
}
}
void test_incomplete_functionExpression2() {
var expression = parseExpression("() a {}",
errors: usingFastaParser
? [expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 3, 1)]
: [expectedError(ParserErrorCode.MISSING_IDENTIFIER, 2, 1)]);
if (usingFastaParser) {
FunctionExpression functionExpression = expression;
expect(functionExpression.parameters.parameters, hasLength(0));
}
}
@failingTest
void test_incomplete_returnType() {
parseCompilationUnit(r'''

View file

@ -4000,19 +4000,7 @@ class Parser {
listener.handleEmptyFunctionBody(next);
return next;
} else if (optional('=>', next)) {
Token begin = next;
token = parseExpression(next);
if (!ofFunctionExpression) {
token = ensureSemicolon(token);
listener.handleExpressionFunctionBody(begin, token);
} else {
listener.handleExpressionFunctionBody(begin, null);
}
if (inGenerator) {
listener.handleInvalidStatement(
begin, fasta.messageGeneratorReturnsValue);
}
return token;
return parseExpressionFunctionBody(next, ofFunctionExpression);
} else if (optional('=', next)) {
Token begin = next;
// Recover from a bad factory method.
@ -4029,9 +4017,24 @@ class Parser {
Token begin = next;
int statementCount = 0;
if (!optional('{', next)) {
token = ensureBlock(token, fasta.templateExpectedFunctionBody);
listener.handleInvalidFunctionBody(token);
return token.endGroup;
// Recovery
// If there is a stray simple identifier in the function expression
// because the user is typing (e.g. `() asy => null;`)
// then report an error, skip the token, and continue parsing.
if (next.isKeywordOrIdentifier && optional('=>', next.next)) {
reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
return parseExpressionFunctionBody(next.next, ofFunctionExpression);
}
if (next.isKeywordOrIdentifier && optional('{', next.next)) {
reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
token = next;
begin = next = token.next;
// Fall through to parse the block.
} else {
token = ensureBlock(token, fasta.templateExpectedFunctionBody);
listener.handleInvalidFunctionBody(token);
return token.endGroup;
}
}
listener.beginBlockFunctionBody(begin);
@ -4054,6 +4057,23 @@ class Parser {
return token;
}
Token parseExpressionFunctionBody(Token token, bool ofFunctionExpression) {
assert(optional('=>', token));
Token begin = token;
token = parseExpression(token);
if (!ofFunctionExpression) {
token = ensureSemicolon(token);
listener.handleExpressionFunctionBody(begin, token);
} else {
listener.handleExpressionFunctionBody(begin, null);
}
if (inGenerator) {
listener.handleInvalidStatement(
begin, fasta.messageGeneratorReturnsValue);
}
return token;
}
Token skipAsyncModifier(Token token) {
String value = token.next.stringValue;
if (identical(value, 'async')) {
@ -4709,21 +4729,34 @@ class Parser {
assert(optional('(', next));
Token nextToken = closeBraceTokenFor(next).next;
int kind = nextToken.kind;
if (mayParseFunctionExpressions &&
(identical(kind, FUNCTION_TOKEN) ||
identical(kind, OPEN_CURLY_BRACKET_TOKEN) ||
(identical(kind, KEYWORD_TOKEN) &&
(optional('async', nextToken) ||
optional('sync', nextToken))))) {
listener.handleNoTypeVariables(next);
return parseFunctionExpression(token);
} else {
bool old = mayParseFunctionExpressions;
mayParseFunctionExpressions = true;
token = parseParenthesizedExpression(token);
mayParseFunctionExpressions = old;
return token;
if (mayParseFunctionExpressions) {
if ((identical(kind, FUNCTION_TOKEN) ||
identical(kind, OPEN_CURLY_BRACKET_TOKEN))) {
listener.handleNoTypeVariables(next);
return parseFunctionExpression(token);
} else if (identical(kind, KEYWORD_TOKEN) ||
identical(kind, IDENTIFIER_TOKEN)) {
if (optional('async', nextToken) || optional('sync', nextToken)) {
listener.handleNoTypeVariables(next);
return parseFunctionExpression(token);
}
// Recovery
// If there is a stray simple identifier in the function expression
// because the user is typing (e.g. `() asy {}`) then continue parsing
// and allow parseFunctionExpression to report an unexpected token.
kind = nextToken.next.kind;
if ((identical(kind, FUNCTION_TOKEN) ||
identical(kind, OPEN_CURLY_BRACKET_TOKEN))) {
listener.handleNoTypeVariables(next);
return parseFunctionExpression(token);
}
}
}
bool old = mayParseFunctionExpressions;
mayParseFunctionExpressions = true;
token = parseParenthesizedExpression(token);
mayParseFunctionExpressions = old;
return token;
}
Token parseParenthesizedExpression(Token token) {