From c284c428bb40c9d1eb3c65828e1fcb45153356c7 Mon Sep 17 00:00:00 2001 From: Dan Rubel Date: Tue, 12 Dec 2017 14:06:40 +0000 Subject: [PATCH] Add fasta parser type parameter recovery Change-Id: I6363aa558b2ac6dec3fb3c7ba1abd82dd8e98874 Reviewed-on: https://dart-review.googlesource.com/28060 Commit-Queue: Dan Rubel Reviewed-by: Brian Wilkerson --- .../test/generated/parser_fasta_test.dart | 7 -- pkg/analyzer/test/generated/parser_test.dart | 51 +++++++++- .../lib/src/fasta/fasta_codes_generated.dart | 2 +- .../lib/src/fasta/parser/parser.dart | 94 ++++++++++--------- pkg/front_end/messages.yaml | 2 +- 5 files changed, 102 insertions(+), 54 deletions(-) diff --git a/pkg/analyzer/test/generated/parser_fasta_test.dart b/pkg/analyzer/test/generated/parser_fasta_test.dart index 929c31bac23..0d640cd2d2e 100644 --- a/pkg/analyzer/test/generated/parser_fasta_test.dart +++ b/pkg/analyzer/test/generated/parser_fasta_test.dart @@ -2740,13 +2740,6 @@ class RecoveryParserTest_Fasta extends FastaParserTestCase super.test_incompleteTypeArguments_field(); } - @override - @failingTest - void test_incompleteTypeParameters() { - // TODO(brianwilkerson) reportUnrecoverableErrorWithToken - super.test_incompleteTypeParameters(); - } - @override @failingTest void test_isExpression_noType() { diff --git a/pkg/analyzer/test/generated/parser_test.dart b/pkg/analyzer/test/generated/parser_test.dart index aec9b4c1e3f..5099b5001b1 100644 --- a/pkg/analyzer/test/generated/parser_test.dart +++ b/pkg/analyzer/test/generated/parser_test.dart @@ -10433,7 +10433,24 @@ class C { void test_incompleteTypeParameters() { CompilationUnit unit = parseCompilationUnit(r''' class C declarations = unit.declarations; + expect(declarations, hasLength(1)); + ClassDeclaration classDecl = declarations[0] as ClassDeclaration; + // validate the type parameters + TypeParameterList typeParameters = classDecl.typeParameters; + expect(typeParameters.typeParameters, hasLength(1)); + // synthetic '>' + Token token = typeParameters.endToken; + expect(token.type, TokenType.GT); + expect(token.isSynthetic, isTrue); + } + + void test_incompleteTypeParameters2() { + CompilationUnit unit = parseCompilationUnit(r''' +class C { +}''', errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 23, 1)]); // one class List declarations = unit.declarations; expect(declarations, hasLength(1)); @@ -10452,6 +10469,38 @@ class C g; +}''', + errors: usingFastaParser + ? [ + expectedError(ParserErrorCode.EXPECTED_TOKEN, 18, 6), + expectedError(ParserErrorCode.EXTRANEOUS_MODIFIER, 18, 6) + ] + : [ + expectedError(ParserErrorCode.EXPECTED_TOKEN, 18, 6), + expectedError(ParserErrorCode.EXPECTED_TOKEN, 18, 6), + expectedError(ParserErrorCode.EXPECTED_CLASS_MEMBER, 24, 1), + expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 24, 1), + expectedError( + ParserErrorCode.MISSING_CONST_FINAL_VAR_OR_TYPE, 26, 1) + ]); + // one class + List declarations = unit.declarations; + expect(declarations, hasLength(1)); + ClassDeclaration classDecl = declarations[0] as ClassDeclaration; + // validate members + if (usingFastaParser) { + expect(classDecl.members, hasLength(1)); + FieldDeclaration fields = classDecl.members.first; + expect(fields.fields.variables, hasLength(1)); + VariableDeclaration field = fields.fields.variables.first; + expect(field.name.name, 'g'); + } + } + void test_isExpression_noType() { CompilationUnit unit = parseCompilationUnit( "class Bar {m(x){if (x is ) return;if (x is !)}}", 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 c056fcc8952..c1adf768985 100644 --- a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart +++ b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart @@ -1372,7 +1372,7 @@ const Template templateExpectedToken = const Code codeExpectedToken = const Code( "ExpectedToken", templateExpectedToken, - analyzerCode: "EXPECTED_TOKEN", dart2jsCode: "GENERIC"); + analyzerCode: "EXPECTED_TOKEN", dart2jsCode: "*fatal*"); // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. Message _withArgumentsExpectedToken(String string) { diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart index 31f3daf78b7..6ebf25a6e30 100644 --- a/pkg/front_end/lib/src/fasta/parser/parser.dart +++ b/pkg/front_end/lib/src/fasta/parser/parser.dart @@ -2480,7 +2480,6 @@ class Parser { if (!token.isIdentifier) { // Recover from a missing identifier by inserting one. token = insertSyntheticIdentifier(beforeToken, nameContext); - beforeToken = previousToken(periodAfterThis, token); } beforeNameToken = beforeToken; beforeToken = nameToken = token; @@ -2577,7 +2576,6 @@ class Parser { // We need to recompute the before tokens because [ensureIdentifier] // can cause synthetic tokens to be inserted. beforeToken = previousToken(beforeToken, token); - beforeNameToken = previousToken(beforeNameToken, nameToken); } else { listener.handleNoName(nameToken); } @@ -2635,59 +2633,21 @@ class Parser { Function endStuff, Function handleNoStuff) { // TODO(brianwilkerson): Rename to `parseStuffOpt`? - bool rewriteEndToken(Token beforeEnd) { - Token end = beforeEnd.next; - String value = end?.stringValue; - if (value != null && value.length > 1) { - if (identical(value, '>>')) { - Token replacement = new Token(TokenType.GT, end.charOffset); - replacement.next = new Token(TokenType.GT, end.charOffset + 1); - rewriter.replaceTokenFollowing(beforeEnd, replacement); - return true; - } else if (identical(value, '>=')) { - Token replacement = new Token(TokenType.GT, end.charOffset); - replacement.next = new Token(TokenType.EQ, end.charOffset + 1); - rewriter.replaceTokenFollowing(beforeEnd, replacement); - return true; - } else if (identical(value, '>>=')) { - Token replacement = new Token(TokenType.GT, end.charOffset); - replacement.next = new Token(TokenType.GT, end.charOffset + 1); - replacement.next.next = new Token(TokenType.EQ, end.charOffset + 2); - rewriter.replaceTokenFollowing(beforeEnd, replacement); - return true; - } - } - return false; - } - // TODO(brianwilkerson): Remove the invocation of `previous` when // `injectGenericCommentTypeList` returns the last consumed token. token = listener.injectGenericCommentTypeList(token.next).previous; Token next = token.next; if (optional('<', next)) { BeginToken begin = next; - Token end = begin.endToken; - if (end != null) { - Token beforeEnd = previousToken(begin, end); - if (rewriteEndToken(beforeEnd)) { - begin.endToken = null; - } - } + rewriteLtEndGroupOpt(begin); beginStuff(begin); int count = 0; do { token = stuffParser(token.next); ++count; } while (optional(',', token.next)); - if (end == null) { - if (rewriteEndToken(token)) { - begin.endToken = null; - } - } - token = token.next; + token = begin.endToken = ensureGt(token); endStuff(count, begin, token); - expect('>', token); - begin.endToken ??= token; return token; } handleNoStuff(next); @@ -3232,8 +3192,24 @@ class Parser { return parseLiteralString(token); } - /// If the given [token] is a semi-colon, return it. Otherwise, report an - /// error, insert a synthetic semi-colon, and return the inserted semi-colon. + /// If the token after [token] is a '>', return it. + /// If the next token is a composite greater-than token such as '>>', + /// then replace that token with separate tokens, and return the first '>'. + /// Otherwise, report an error, insert a synthetic '>', + /// and return that newly inserted synthetic '>'. + Token ensureGt(Token token) { + Token next = token.next; + String value = next.stringValue; + if (value == '>') { + return next; + } + rewriteGtCompositeOrRecover(token, next, value); + return token.next; + } + + /// If the token after [token] is a semi-colon, return it. + /// Otherwise, report an error, insert a synthetic semi-colon, + /// and return the inserted semi-colon. Token ensureSemicolon(Token token) { // TODO(danrubel): Once all expect(';'...) call sites have been converted // to use this method, remove similar semicolon recovery code @@ -3251,6 +3227,36 @@ class Parser { return newToken; } + void rewriteGtCompositeOrRecover(Token token, Token next, String value) { + assert(value != '>'); + Token replacement = new Token(TokenType.GT, next.charOffset); + if (identical(value, '>>')) { + replacement.next = new Token(TokenType.GT, next.charOffset + 1); + } else if (identical(value, '>=')) { + replacement.next = new Token(TokenType.EQ, next.charOffset + 1); + } else if (identical(value, '>>=')) { + replacement.next = new Token(TokenType.GT, next.charOffset + 1); + replacement.next.next = new Token(TokenType.EQ, next.charOffset + 2); + } else { + // Recovery + rewriteAndRecover(token, fasta.templateExpectedToken.withArguments('>'), + new SyntheticToken(TokenType.GT, next.offset)); + return; + } + rewriter.replaceTokenFollowing(token, replacement); + } + + void rewriteLtEndGroupOpt(BeginToken beginToken) { + assert(optional('<', beginToken)); + Token end = beginToken.endGroup; + String value = end?.stringValue; + if (value != null && value.length > 1) { + Token beforeEnd = previousToken(beginToken, end); + rewriteGtCompositeOrRecover(beforeEnd, end, value); + beginToken.endGroup = null; + } + } + /// Report the given token as unexpected and return the next token if the next /// token is one of the [expectedNext], otherwise just return the given token. Token skipUnexpectedTokenOpt(Token token, List expectedNext) { diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml index 58c48c51a46..5444111dc18 100644 --- a/pkg/front_end/messages.yaml +++ b/pkg/front_end/messages.yaml @@ -260,7 +260,7 @@ ExpectedString: ExpectedToken: template: "Expected to find '#string'." analyzerCode: EXPECTED_TOKEN - dart2jsCode: GENERIC + dart2jsCode: "*fatal*" ExpectedType: template: "Expected a type, but got '#lexeme'."