From 166cc11a6443b975ad242aa8dd5250bc7dc90f60 Mon Sep 17 00:00:00 2001 From: Dan Rubel Date: Tue, 19 Dec 2017 20:57:43 +0000 Subject: [PATCH] Add fasta parser function expression recovery Change-Id: Id5e27b8f186da23fd141fda12621f1ae3bcd1f8c Reviewed-on: https://dart-review.googlesource.com/30580 Reviewed-by: Brian Wilkerson Commit-Queue: Dan Rubel --- .../dart/keyword_contributor_test.dart | 34 ++++--- pkg/analyzer/test/generated/parser_test.dart | 22 +++++ .../lib/src/fasta/parser/parser.dart | 93 +++++++++++++------ 3 files changed, 106 insertions(+), 43 deletions(-) diff --git a/pkg/analysis_server/test/services/completion/dart/keyword_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/keyword_contributor_test.dart index 24e56b41b44..20a7d313812 100644 --- a/pkg/analysis_server/test/services/completion/dart/keyword_contributor_test.dart +++ b/pkg/analysis_server/test/services/completion/dart/keyword_contributor_test.dart @@ -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 { diff --git a/pkg/analyzer/test/generated/parser_test.dart b/pkg/analyzer/test/generated/parser_test.dart index be085d7bbdf..1d3601e4dc7 100644 --- a/pkg/analyzer/test/generated/parser_test.dart +++ b/pkg/analyzer/test/generated/parser_test.dart @@ -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''' diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart index 976a89624cb..a9464728a98 100644 --- a/pkg/front_end/lib/src/fasta/parser/parser.dart +++ b/pkg/front_end/lib/src/fasta/parser/parser.dart @@ -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) {