Add fasta parser type parameter recovery

Change-Id: I6363aa558b2ac6dec3fb3c7ba1abd82dd8e98874
Reviewed-on: https://dart-review.googlesource.com/28060
Commit-Queue: Dan Rubel <danrubel@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Dan Rubel 2017-12-12 14:06:40 +00:00 committed by commit-bot@chromium.org
parent 455b177ebb
commit c284c428bb
5 changed files with 102 additions and 54 deletions

View file

@ -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() {

View file

@ -10433,7 +10433,24 @@ class C {
void test_incompleteTypeParameters() {
CompilationUnit unit = parseCompilationUnit(r'''
class C<K {
}''', codes: [ParserErrorCode.EXPECTED_TOKEN]);
}''', errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 10, 1)]);
// one class
List<CompilationUnitMember> 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<K extends L<T> {
}''', errors: [expectedError(ParserErrorCode.EXPECTED_TOKEN, 23, 1)]);
// one class
List<CompilationUnitMember> declarations = unit.declarations;
expect(declarations, hasLength(1));
@ -10452,6 +10469,38 @@ class C<K {
codes: [ParserErrorCode.MISSING_STAR_AFTER_SYNC]);
}
void test_invalidTypeParameters() {
CompilationUnit unit = parseCompilationUnit(r'''
class C {
G<int double> 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<CompilationUnitMember> 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<T extends Foo> {m(x){if (x is ) return;if (x is !)}}",

View file

@ -1372,7 +1372,7 @@ const Template<Message Function(String string)> templateExpectedToken =
const Code<Message Function(String string)> codeExpectedToken =
const Code<Message Function(String string)>(
"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) {

View file

@ -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<String> expectedNext) {

View file

@ -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'."