Fix parsing typedefs with <T>=
This is the first CL of several needed to fix typedef parsing
when there is no space between the type parameters and the `=`.
typedef F<T>= T Function(T);
* Fix some typedef parsing failures
* Add new consts for simple type var ending with ">>" and ">="
* Update computeType and computeTypeParamOrArg to return new consts
* Extract splitGtEq/GtGt/GtGtEq from splitEndGroup
* Hide internal TypeInfo and TypeParamOrArgInfo consts
Change-Id: Ice550f5b15df1f3cad616a2a342644974f14d8ee
Reviewed-on: https://dart-review.googlesource.com/69261
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/test/generated/parser_test.dart b/pkg/analyzer/test/generated/parser_test.dart
index 0024e05..e7298b6 100644
--- a/pkg/analyzer/test/generated/parser_test.dart
+++ b/pkg/analyzer/test/generated/parser_test.dart
@@ -17917,6 +17917,21 @@
expect(alias.semicolon, isNotNull);
}
+ void test_parseGenericTypeAlias_typeParameters2() {
+ // The scanner creates a single token for `>=`
+ // then the parser must split it into two separate tokens.
+ createParser('typedef F<T>= T Function(T);');
+ GenericTypeAlias alias = parseFullCompilationUnitMember();
+ expect(alias, isNotNull);
+ assertNoErrors();
+ expect(alias.name, isNotNull);
+ expect(alias.name.name, 'F');
+ expect(alias.typeParameters, isNotNull);
+ expect(alias.equals, isNotNull);
+ expect(alias.functionType, isNotNull);
+ expect(alias.semicolon, isNotNull);
+ }
+
void test_parseImportDirective_configuration_multiple() {
createParser("import 'lib/lib.dart' if (a) 'b.dart' if (c) 'd.dart';");
ImportDirective directive = parseFullDirective();
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 01b159d..d0e4017 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -1060,7 +1060,7 @@
TypeInfo typeInfo = computeType(typedefKeyword, false);
token = typeInfo.skipType(typedefKeyword).next;
Token equals;
- TypeParamOrArgInfo typeParam = computeTypeParamOrArg(token);
+ TypeParamOrArgInfo typeParam = computeTypeParamOrArg(token, true);
if (typeInfo == noType &&
(token.kind == IDENTIFIER_TOKEN || token.type.isPseudo) &&
optional('=', typeParam.skip(token).next)) {
diff --git a/pkg/front_end/lib/src/fasta/parser/token_stream_rewriter.dart b/pkg/front_end/lib/src/fasta/parser/token_stream_rewriter.dart
index e3ae092..0c8d710 100644
--- a/pkg/front_end/lib/src/fasta/parser/token_stream_rewriter.dart
+++ b/pkg/front_end/lib/src/fasta/parser/token_stream_rewriter.dart
@@ -14,7 +14,8 @@
Token,
TokenType;
-import 'util.dart' show optional;
+import 'util.dart'
+ show optional, splitGtEq, splitGtGt, splitGtGtEq, syntheticGt;
/// Provides the capability of inserting tokens into a token stream. This
/// implementation does this by rewriting the previous token to point to the
@@ -143,20 +144,13 @@
Token gt;
if (optional('>>', end)) {
- gt = new SimpleToken(TokenType.GT, end.charOffset, end.precedingComments)
- ..setNext(new SimpleToken(TokenType.GT, end.charOffset + 1)
- ..setNext(end.next));
+ gt = splitGtGt(end);
} else if (optional('>=', end)) {
- gt = new SimpleToken(TokenType.GT, end.charOffset, end.precedingComments)
- ..setNext(new SimpleToken(TokenType.EQ, end.charOffset + 1)
- ..setNext(end.next));
+ gt = splitGtEq(end);
} else if (optional('>>=', end)) {
- gt = new SimpleToken(TokenType.GT, end.charOffset, end.precedingComments)
- ..setNext(new SimpleToken(TokenType.GT, end.charOffset + 1)
- ..setNext(new SimpleToken(TokenType.EQ, end.charOffset + 2)
- ..setNext(end.next)));
+ gt = splitGtGtEq(end);
} else {
- gt = new SyntheticToken(TokenType.GT, end.charOffset)..setNext(end);
+ gt = syntheticGt(end);
}
Token token = start;
diff --git a/pkg/front_end/lib/src/fasta/parser/type_info.dart b/pkg/front_end/lib/src/fasta/parser/type_info.dart
index c94e6f4..19d5bd6 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info.dart
@@ -59,6 +59,17 @@
/// [computeTypeParamOrArg] about a particular group of type arguments
/// or type parameters.
abstract class TypeParamOrArgInfo {
+ const TypeParamOrArgInfo();
+
+ /// Return `true` if the receiver represents a single type argument
+ bool get isSimpleTypeArgument => false;
+
+ /// Return the simple type associated with this simple type argument
+ /// or throw an exception if this is not a simple type argument.
+ TypeInfo get typeInfo {
+ throw "Internal error: $runtimeType is not a SimpleTypeArgument.";
+ }
+
/// Call this function to parse optional type arguments after [token].
/// This function will call the appropriate event methods on the [Parser]'s
/// listener to handle the arguments. This may modify the token stream
@@ -82,31 +93,13 @@
/// there is no type information in the source.
const TypeInfo noType = const NoType();
-/// [VoidType] is a specialized [TypeInfo] returned by [computeType] when
-/// there is a single identifier as the type reference.
-const TypeInfo voidType = const VoidType();
-
-/// [SimpleType] is a specialized [TypeInfo] returned by [computeType]
-/// when there is a single identifier as the type reference.
-const TypeInfo simpleType = const SimpleType();
-
-/// [PrefixedType] is a specialized [TypeInfo] returned by [computeType]
-/// when the type reference is of the form: identifier `.` identifier.
-const TypeInfo prefixedType = const PrefixedType();
-
-/// [SimpleTypeWith1Argument] is a specialized [TypeInfo] returned by
-/// [computeType] when the type reference is of the form:
-/// identifier `<` identifier `>`.
-const TypeInfo simpleTypeWith1Argument = const SimpleTypeWith1Argument();
-
/// [NoTypeParamOrArg] is a specialized [TypeParamOrArgInfo] returned by
/// [computeTypeParamOrArg] when no type parameters or arguments are found.
const TypeParamOrArgInfo noTypeParamOrArg = const NoTypeParamOrArg();
-/// [SimpleTypeArgument1] is a specialized [TypeParamOrArgInfo] returned by
-/// [computeTypeParamOrArg] when the type reference is of the form:
-/// `<` identifier `>`.
-const TypeParamOrArgInfo simpleTypeArgument1 = const SimpleTypeArgument1();
+/// [VoidType] is a specialized [TypeInfo] returned by [computeType] when
+/// there is a single identifier as the type reference.
+const TypeInfo voidType = const VoidType();
bool isGeneralizedFunctionType(Token token) {
return optional('Function', token) &&
@@ -191,13 +184,13 @@
TypeParamOrArgInfo typeParamOrArg =
computeTypeParamOrArg(next, inDeclaration, innerEndGroup);
if (typeParamOrArg != noTypeParamOrArg) {
- if (typeParamOrArg == simpleTypeArgument1) {
+ if (typeParamOrArg.isSimpleTypeArgument) {
// We've seen identifier `<` identifier `>`
next = typeParamOrArg.skip(next).next;
if (!isGeneralizedFunctionType(next)) {
if (required || looksLikeName(next)) {
// identifier `<` identifier `>` identifier
- return simpleTypeWith1Argument;
+ return typeParamOrArg.typeInfo;
} else {
// identifier `<` identifier `>` non-identifier
return noType;
@@ -272,28 +265,33 @@
/// has not been split or the first `>` if the `>>` token has been split.
TypeParamOrArgInfo computeTypeParamOrArg(Token token,
[bool inDeclaration = false, Token innerEndGroup]) {
- Token next = token.next;
- if (!optional('<', next)) {
+ Token beginGroup = token.next;
+ if (!optional('<', beginGroup)) {
return noTypeParamOrArg;
}
- Token endGroup = next.endGroup ?? innerEndGroup;
+ Token endGroup = beginGroup.endGroup ?? innerEndGroup;
if (endGroup == null) {
- if (inDeclaration) {
- // Recovery
- // Since the leading `<` cannot be part of an expression,
- // try to more aggressively recover given an unbalanced '<'.
- return new ComplexTypeParamOrArgInfo(token, inDeclaration)
- .compute(innerEndGroup);
+ if (!inDeclaration) {
+ return noTypeParamOrArg;
}
- return noTypeParamOrArg;
+ // Recovery:
+ // Since the leading `<` cannot be part of an expression, fall through and
+ // try to more aggressively recover given an unbalanced '<'.
}
- Token identifier = next.next;
+
// identifier `<` `void` `>` and `<` `dynamic` `>`
// are handled by ComplexTypeInfo.
- if ((identifier.kind == IDENTIFIER_TOKEN || identifier.type.isPseudo) &&
- identifier.next == endGroup) {
- return simpleTypeArgument1;
+ Token identifier = beginGroup.next;
+ if ((identifier.kind == IDENTIFIER_TOKEN || identifier.type.isPseudo)) {
+ if (optional('>', identifier.next)) {
+ return simpleTypeArgument1;
+ } else if (optional('>>', identifier.next)) {
+ return simpleTypeArgument1GtGt;
+ } else if (optional('>=', identifier.next)) {
+ return simpleTypeArgument1GtEq;
+ }
}
+
// TODO(danrubel): Consider adding additional const for common situations.
return new ComplexTypeParamOrArgInfo(token, inDeclaration)
.compute(innerEndGroup);
diff --git a/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart b/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart
index 3ec6155..e3bb31c 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart
@@ -23,7 +23,50 @@
import 'type_info.dart';
-import 'util.dart' show isOneOf, optional, skipMetadata;
+import 'util.dart' show isOneOf, optional, skipMetadata, splitGtEq, splitGtGt;
+
+/// [SimpleType] is a specialized [TypeInfo] returned by [computeType]
+/// when there is a single identifier as the type reference.
+const TypeInfo simpleType = const SimpleType();
+
+/// [PrefixedType] is a specialized [TypeInfo] returned by [computeType]
+/// when the type reference is of the form: identifier `.` identifier.
+const TypeInfo prefixedType = const PrefixedType();
+
+/// [SimpleTypeWith1Argument] is a specialized [TypeInfo] returned by
+/// [computeType] when the type reference is of the form:
+/// identifier `<` identifier `>`.
+const TypeInfo simpleTypeWith1Argument =
+ const SimpleTypeWith1Argument(simpleTypeArgument1);
+
+/// [SimpleTypeWith1Argument] is a specialized [TypeInfo] returned by
+/// [computeType] when the type reference is of the form:
+/// identifier `<` identifier `>=`.
+const TypeInfo simpleTypeWith1ArgumentGtEq =
+ const SimpleTypeWith1Argument(simpleTypeArgument1GtEq);
+
+/// [SimpleTypeWith1Argument] is a specialized [TypeInfo] returned by
+/// [computeType] when the type reference is of the form:
+/// identifier `<` identifier `>>`.
+const TypeInfo simpleTypeWith1ArgumentGtGt =
+ const SimpleTypeWith1Argument(simpleTypeArgument1GtGt);
+
+/// [SimpleTypeArgument1] is a specialized [TypeParamOrArgInfo] returned by
+/// [computeTypeParamOrArg] when the type reference is of the form:
+/// `<` identifier `>`.
+const TypeParamOrArgInfo simpleTypeArgument1 = const SimpleTypeArgument1();
+
+/// [SimpleTypeArgument1] is a specialized [TypeParamOrArgInfo] returned by
+/// [computeTypeParamOrArg] when the type reference is of the form:
+/// `<` identifier `>=`.
+const TypeParamOrArgInfo simpleTypeArgument1GtEq =
+ const SimpleTypeArgument1GtEq();
+
+/// [SimpleTypeArgument1] is a specialized [TypeParamOrArgInfo] returned by
+/// [computeTypeParamOrArg] when the type reference is of the form:
+/// `<` identifier `>>`.
+const TypeParamOrArgInfo simpleTypeArgument1GtGt =
+ const SimpleTypeArgument1GtGt();
/// See documentation on the [noType] const.
class NoType implements TypeInfo {
@@ -108,7 +151,9 @@
/// See documentation on the [simpleTypeWith1Argument] const.
class SimpleTypeWith1Argument implements TypeInfo {
- const SimpleTypeWith1Argument();
+ final TypeParamOrArgInfo typeArg;
+
+ const SimpleTypeWith1Argument(this.typeArg);
@override
bool get couldBeExpression => false;
@@ -131,7 +176,7 @@
assert(token.isKeywordOrIdentifier);
Listener listener = parser.listener;
listener.handleIdentifier(token, IdentifierContext.typeReference);
- token = simpleTypeArgument1.parseArguments(token, parser);
+ token = typeArg.parseArguments(token, parser);
listener.handleType(start, token.next);
return token;
}
@@ -140,7 +185,7 @@
Token skipType(Token token) {
token = token.next;
assert(token.isKeywordOrIdentifier);
- return simpleTypeArgument1.skip(token);
+ return typeArg.skip(token);
}
}
@@ -470,7 +515,7 @@
}
/// See [noTypeParamOrArg].
-class NoTypeParamOrArg implements TypeParamOrArgInfo {
+class NoTypeParamOrArg extends TypeParamOrArgInfo {
const NoTypeParamOrArg();
@override
@@ -489,37 +534,43 @@
Token skip(Token token) => token;
}
-class SimpleTypeArgument1 implements TypeParamOrArgInfo {
+class SimpleTypeArgument1 extends TypeParamOrArgInfo {
const SimpleTypeArgument1();
@override
+ bool get isSimpleTypeArgument => true;
+
+ @override
+ TypeInfo get typeInfo => simpleTypeWith1Argument;
+
+ @override
Token parseArguments(Token token, Parser parser) {
- BeginToken start = token = token.next;
- assert(optional('<', token));
+ BeginToken beginGroup = token.next;
+ assert(optional('<', beginGroup));
Listener listener = parser.listener;
- listener.beginTypeArguments(token);
- token = simpleType.parseType(token, parser);
- token = processEndGroup(token, start, parser);
- parser.listener.endTypeArguments(1, start, token);
+ listener.beginTypeArguments(beginGroup);
+ token = simpleType.parseType(beginGroup, parser);
+ token = updateEndGroup(beginGroup, token);
+ parser.listener.endTypeArguments(1, beginGroup, token);
return token;
}
@override
Token parseVariables(Token token, Parser parser) {
- BeginToken start = token = token.next;
- assert(optional('<', token));
+ BeginToken beginGroup = token.next;
+ assert(optional('<', beginGroup));
Listener listener = parser.listener;
- listener.beginTypeVariables(token);
- token = token.next;
+ listener.beginTypeVariables(beginGroup);
+ token = beginGroup.next;
listener.beginMetadataStar(token);
listener.endMetadataStar(0);
listener.handleIdentifier(token, IdentifierContext.typeVariableDeclaration);
listener.beginTypeVariable(token);
listener.handleTypeVariablesDefined(token, 1);
listener.handleNoType(token);
- token = processEndGroup(token, start, parser);
+ token = updateEndGroup(beginGroup, token);
listener.endTypeVariable(token, 0, null);
- listener.endTypeVariables(start, token);
+ listener.endTypeVariables(beginGroup, token);
return token;
}
@@ -527,15 +578,68 @@
Token skip(Token token) {
token = token.next;
assert(optional('<', token));
- assert(token.endGroup != null ||
- (optional('>', token.next.next) || optional('>>', token.next.next)));
- return (optional('>', token.endGroup ?? token.next.next)
- ? token.next.next
- : token.next);
+ token = token.next;
+ assert(token.isKeywordOrIdentifier);
+ return skipEndGroup(token);
+ }
+
+ Token skipEndGroup(Token token) {
+ token = token.next;
+ assert(optional('>', token));
+ return token;
+ }
+
+ Token updateEndGroup(BeginToken beginGroup, Token token) {
+ token = token.next;
+ assert(optional('>', token));
+ beginGroup.endGroup = token;
+ return token;
}
}
-class ComplexTypeParamOrArgInfo implements TypeParamOrArgInfo {
+class SimpleTypeArgument1GtEq extends SimpleTypeArgument1 {
+ const SimpleTypeArgument1GtEq();
+
+ @override
+ TypeInfo get typeInfo => simpleTypeWith1ArgumentGtEq;
+
+ Token skipEndGroup(Token token) {
+ token = token.next;
+ assert(optional('>=', token));
+ return splitGtEq(token);
+ }
+
+ Token updateEndGroup(BeginToken beginGroup, Token beforeEndGroup) {
+ Token endGroup = beforeEndGroup.next;
+ assert(optional('>=', endGroup));
+ beginGroup.endGroup = endGroup = splitGtEq(endGroup);
+ beforeEndGroup.setNext(endGroup);
+ return endGroup;
+ }
+}
+
+class SimpleTypeArgument1GtGt extends SimpleTypeArgument1 {
+ const SimpleTypeArgument1GtGt();
+
+ @override
+ TypeInfo get typeInfo => simpleTypeWith1ArgumentGtGt;
+
+ Token skipEndGroup(Token token) {
+ assert(optional('>>', token.next));
+ return token;
+ }
+
+ Token updateEndGroup(BeginToken beginGroup, Token beforeEndGroup) {
+ Token endGroup = beforeEndGroup.next;
+ if (!optional('>', endGroup)) {
+ endGroup = splitGtGt(endGroup);
+ }
+ beginGroup.endGroup = endGroup;
+ return endGroup;
+ }
+}
+
+class ComplexTypeParamOrArgInfo extends TypeParamOrArgInfo {
/// The first token in the type var.
final BeginToken start;
diff --git a/pkg/front_end/lib/src/fasta/parser/util.dart b/pkg/front_end/lib/src/fasta/parser/util.dart
index 4329810..7d57ecd 100644
--- a/pkg/front_end/lib/src/fasta/parser/util.dart
+++ b/pkg/front_end/lib/src/fasta/parser/util.dart
@@ -10,7 +10,8 @@
import '../scanner.dart' show Token;
-import '../../scanner/token.dart' show BeginToken;
+import '../../scanner/token.dart'
+ show BeginToken, SimpleToken, SyntheticToken, TokenType;
/// Returns true if [token] is the symbol or keyword [value].
bool optional(String value, Token token) {
@@ -127,3 +128,44 @@
}
return token;
}
+
+/// Split `>=` into two separate tokens.
+Token splitGtEq(Token token) {
+ assert(optional('>=', token));
+ return new SimpleToken(
+ TokenType.GT, token.charOffset, token.precedingComments)
+ ..setNext(new SimpleToken(TokenType.EQ, token.charOffset + 1)
+ ..setNext(token.next));
+}
+
+/// Split `>>` into two separate tokens.
+SimpleToken splitGtGt(Token token) {
+ assert(optional('>>', token));
+ return new SimpleToken(
+ TokenType.GT, token.charOffset, token.precedingComments)
+ ..setNext(new SimpleToken(TokenType.GT, token.charOffset + 1)
+ ..setNext(token.next));
+}
+
+/// Split `>>=` into three separate tokens.
+Token splitGtGtEq(Token token) {
+ assert(optional('>>=', token));
+ return new SimpleToken(
+ TokenType.GT, token.charOffset, token.precedingComments)
+ ..setNext(new SimpleToken(TokenType.GT, token.charOffset + 1)
+ ..setNext(new SimpleToken(TokenType.EQ, token.charOffset + 2)
+ ..setNext(token.next)));
+}
+
+/// Split `>>=` into two separate tokens... `>` followed by `>=`.
+Token splitGtFromGtGtEq(Token token) {
+ assert(optional('>>=', token));
+ return new SimpleToken(
+ TokenType.GT, token.charOffset, token.precedingComments)
+ ..setNext(new SimpleToken(TokenType.GT_EQ, token.charOffset + 1)
+ ..setNext(token.next));
+}
+
+Token syntheticGt(Token token) {
+ return new SyntheticToken(TokenType.GT, token.charOffset)..setNext(token);
+}
diff --git a/pkg/front_end/test/fasta/parser/type_info_test.dart b/pkg/front_end/test/fasta/parser/type_info_test.dart
index 6cde453..453b0b0 100644
--- a/pkg/front_end/test/fasta/parser/type_info_test.dart
+++ b/pkg/front_end/test/fasta/parser/type_info_test.dart
@@ -580,7 +580,7 @@
void test_computeType_nested() {
expectNestedInfo(simpleType, '<T>');
- expectNestedInfo(simpleTypeWith1Argument, '<T<S>>');
+ expectNestedInfo(simpleTypeWith1ArgumentGtGt, '<T<S>>');
expectNestedComplexInfo('<T<S,R>>');
expectNestedComplexInfo('<T<S Function()>>');
expectNestedComplexInfo('<T<S Function()>>');
@@ -837,7 +837,18 @@
Token t = start.next.next;
expect(t.next.lexeme, '>>');
- expect(simpleTypeArgument1.skip(start), t);
+ expect(simpleTypeArgument1GtGt.skip(start), t);
+ }
+
+ void test_simple_skip3() {
+ final Token start = scanString('before <T>= after').tokens;
+ Token t = start.next.next;
+ expect(t.next.lexeme, '>=');
+
+ Token skip = simpleTypeArgument1GtEq.skip(start);
+ expect(skip.lexeme, '>');
+ expect(skip.next.lexeme, '=');
+ expect(skip.next.next, t.next.next);
}
void test_simple_parseArguments() {
@@ -891,6 +902,10 @@
expectTypeParamOrArg(simpleTypeArgument1, '<T>');
}
+ void test_computeTypeParamOrArg_simple2() {
+ expectTypeParamOrArg(simpleTypeArgument1GtEq, '<T>=', inDeclaration: true);
+ }
+
void test_computeTypeParamOrArg_simple_nested() {
String source = '<C<T>>';
Token start = scan(source).next.next;
@@ -899,7 +914,7 @@
expect(gtgt.lexeme, '>>');
TypeParamOrArgInfo typeVarInfo = computeTypeParamOrArg(start, false, gtgt);
- expect(typeVarInfo, simpleTypeArgument1, reason: source);
+ expect(typeVarInfo, simpleTypeArgument1GtGt, reason: source);
}
void test_computeTypeArg_complex() {