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^ {}}}'); addTestSource('main() {foo(() a^ {}}}');
await computeSuggestions(); await computeSuggestions();
// Fasta adds a closing paren after the first `}` // 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` // while analyzer adds the closing paren before the `a`
// and adds synthetic `;`s making `a` a statement. // and adds synthetic `;`s making `a` a statement.
assertSuggestKeywords( if (request.target.entity is BlockFunctionBody) {
request.target.entity is Expression assertSuggestKeywords([],
? EXPRESSION_START_NO_INSTANCE pseudoKeywords: ['async', 'async*', 'sync*'],
: STMT_START_OUTSIDE_CLASS, relevance: DART_RELEVANCE_HIGH);
pseudoKeywords: ['async', 'async*', 'sync*']); } else {
assertSuggestKeywords(STMT_START_OUTSIDE_CLASS,
pseudoKeywords: ['async', 'async*', 'sync*']);
}
} }
test_anonymous_function_async3() async { test_anonymous_function_async3() async {
@ -360,7 +363,9 @@ class KeywordContributorTest extends DartCompletionContributorTest {
addTestSource('main() {foo("bar", () as^ => null'); addTestSource('main() {foo("bar", () as^ => null');
await computeSuggestions(); await computeSuggestions();
assertSuggestKeywords([], assertSuggestKeywords([],
pseudoKeywords: ['async', 'async*', 'sync*'], pseudoKeywords: request.target.entity is ExpressionFunctionBody
? ['async']
: ['async', 'async*', 'sync*'],
relevance: DART_RELEVANCE_HIGH); relevance: DART_RELEVANCE_HIGH);
} }
@ -375,13 +380,16 @@ class KeywordContributorTest extends DartCompletionContributorTest {
test_anonymous_function_async9() async { test_anonymous_function_async9() async {
addTestSource('main() {foo(() a^ {})}}'); addTestSource('main() {foo(() a^ {})}}');
await computeSuggestions(); 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. // while analyzer adds synthetic `;`s making `a` a statement.
assertSuggestKeywords( if (request.target.entity is BlockFunctionBody) {
request.target.entity is Expression assertSuggestKeywords([],
? EXPRESSION_START_NO_INSTANCE pseudoKeywords: ['async', 'async*', 'sync*'],
: STMT_START_OUTSIDE_CLASS, relevance: DART_RELEVANCE_HIGH);
pseudoKeywords: ['async', 'async*', 'sync*']); } else {
assertSuggestKeywords(STMT_START_OUTSIDE_CLASS,
pseudoKeywords: ['async', 'async*', 'sync*']);
}
} }
test_argument() async { 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 @failingTest
void test_incomplete_returnType() { void test_incomplete_returnType() {
parseCompilationUnit(r''' parseCompilationUnit(r'''

View file

@ -4000,19 +4000,7 @@ class Parser {
listener.handleEmptyFunctionBody(next); listener.handleEmptyFunctionBody(next);
return next; return next;
} else if (optional('=>', next)) { } else if (optional('=>', next)) {
Token begin = next; return parseExpressionFunctionBody(next, ofFunctionExpression);
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;
} else if (optional('=', next)) { } else if (optional('=', next)) {
Token begin = next; Token begin = next;
// Recover from a bad factory method. // Recover from a bad factory method.
@ -4029,9 +4017,24 @@ class Parser {
Token begin = next; Token begin = next;
int statementCount = 0; int statementCount = 0;
if (!optional('{', next)) { if (!optional('{', next)) {
token = ensureBlock(token, fasta.templateExpectedFunctionBody); // Recovery
listener.handleInvalidFunctionBody(token); // If there is a stray simple identifier in the function expression
return token.endGroup; // 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); listener.beginBlockFunctionBody(begin);
@ -4054,6 +4057,23 @@ class Parser {
return token; 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) { Token skipAsyncModifier(Token token) {
String value = token.next.stringValue; String value = token.next.stringValue;
if (identical(value, 'async')) { if (identical(value, 'async')) {
@ -4709,21 +4729,34 @@ class Parser {
assert(optional('(', next)); assert(optional('(', next));
Token nextToken = closeBraceTokenFor(next).next; Token nextToken = closeBraceTokenFor(next).next;
int kind = nextToken.kind; int kind = nextToken.kind;
if (mayParseFunctionExpressions && if (mayParseFunctionExpressions) {
(identical(kind, FUNCTION_TOKEN) || if ((identical(kind, FUNCTION_TOKEN) ||
identical(kind, OPEN_CURLY_BRACKET_TOKEN) || identical(kind, OPEN_CURLY_BRACKET_TOKEN))) {
(identical(kind, KEYWORD_TOKEN) && listener.handleNoTypeVariables(next);
(optional('async', nextToken) || return parseFunctionExpression(token);
optional('sync', nextToken))))) { } else if (identical(kind, KEYWORD_TOKEN) ||
listener.handleNoTypeVariables(next); identical(kind, IDENTIFIER_TOKEN)) {
return parseFunctionExpression(token); if (optional('async', nextToken) || optional('sync', nextToken)) {
} else { listener.handleNoTypeVariables(next);
bool old = mayParseFunctionExpressions; return parseFunctionExpression(token);
mayParseFunctionExpressions = true; }
token = parseParenthesizedExpression(token); // Recovery
mayParseFunctionExpressions = old; // If there is a stray simple identifier in the function expression
return token; // 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) { Token parseParenthesizedExpression(Token token) {