blob: 4204c6fc2dced354dbfa4372883a637dd385ce4d [file] [log] [blame]
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library fasta.parser.type_info_impl;
import '../../scanner/token.dart' show SyntheticToken, Token, TokenType;
import '../fasta_codes.dart' as fasta;
import '../scanner/token_constants.dart' show IDENTIFIER_TOKEN;
import '../util/link.dart' show Link;
import 'forwarding_listener.dart' show ForwardingListener;
import 'identifier_context.dart' show IdentifierContext;
import 'member_kind.dart' show MemberKind;
import 'listener.dart' show Listener;
import 'parser.dart' show Parser;
import 'type_info.dart';
import 'util.dart'
show
optional,
skipMetadata,
splitGtEq,
splitGtFromGtGtEq,
splitGtFromGtGtGt,
splitGtGt,
syntheticGt;
/// [SimpleType] is a specialized [TypeInfo] returned by [computeType]
/// when there is a single identifier as the type reference.
const TypeInfo simpleType = const SimpleType();
/// [SimpleNullableType] is a specialized [TypeInfo] returned by [computeType]
/// when there is a single identifier followed by `?` as the type reference.
const TypeInfo simpleNullableType = const SimpleNullableType();
/// [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);
/// [SimpleNullableTypeWith1Argument] is a specialized [TypeInfo] returned by
/// [computeType] when the type reference is of the form:
/// identifier `<` identifier `>` `?`.
const TypeInfo simpleNullableTypeWith1Argument =
const SimpleNullableTypeWith1Argument();
/// [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 {
const NoType();
@override
TypeInfo get asNonNullable => this;
@override
bool get couldBeExpression => false;
@override
bool get isNullable => false;
@override
Token ensureTypeNotVoid(Token token, Parser parser) {
parser.reportRecoverableErrorWithToken(
token.next, fasta.templateExpectedType);
parser.rewriter.insertSyntheticIdentifier(token);
return simpleType.parseType(token, parser);
}
@override
Token ensureTypeOrVoid(Token token, Parser parser) =>
ensureTypeNotVoid(token, parser);
@override
Token parseTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token parseType(Token token, Parser parser) {
parser.listener.handleNoType(token);
return token;
}
@override
Token skipType(Token token) {
return token;
}
}
/// See documentation on the [prefixedType] const.
class PrefixedType implements TypeInfo {
const PrefixedType();
@override
TypeInfo get asNonNullable => this;
@override
bool get couldBeExpression => true;
@override
bool get isNullable => false;
@override
Token ensureTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token ensureTypeOrVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token parseTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token parseType(Token token, Parser parser) {
Token start = token = token.next;
assert(token.isKeywordOrIdentifier);
Listener listener = parser.listener;
listener.handleIdentifier(token, IdentifierContext.prefixedTypeReference);
Token period = token = token.next;
assert(optional('.', token));
token = token.next;
assert(token.isKeywordOrIdentifier);
listener.handleIdentifier(
token, IdentifierContext.typeReferenceContinuation);
listener.handleQualified(period);
listener.handleNoTypeArguments(token.next);
listener.handleType(start, null);
return token;
}
@override
Token skipType(Token token) {
return token.next.next.next;
}
}
/// See documentation on the [simpleNullableTypeWith1Argument] const.
class SimpleNullableTypeWith1Argument extends SimpleTypeWith1Argument {
const SimpleNullableTypeWith1Argument() : super(simpleTypeArgument1);
@override
TypeInfo get asNonNullable => simpleTypeWith1Argument;
@override
bool get isNullable => true;
@override
Token parseTypeRest(Token start, Token token, Parser parser) {
token = token.next;
assert(optional('?', token));
parser.listener.handleType(start, token);
return token;
}
@override
Token skipType(Token token) {
token = super.skipType(token).next;
assert(optional('?', token));
return token;
}
}
/// See documentation on the [simpleTypeWith1Argument] const.
class SimpleTypeWith1Argument implements TypeInfo {
final TypeParamOrArgInfo typeArg;
const SimpleTypeWith1Argument(this.typeArg);
@override
TypeInfo get asNonNullable => this;
@override
bool get couldBeExpression => false;
@override
bool get isNullable => false;
@override
Token ensureTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token ensureTypeOrVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token parseTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token parseType(Token token, Parser parser) {
Token start = token = token.next;
assert(token.isKeywordOrIdentifier);
parser.listener.handleIdentifier(token, IdentifierContext.typeReference);
token = typeArg.parseArguments(token, parser);
return parseTypeRest(start, token, parser);
}
Token parseTypeRest(Token start, Token token, Parser parser) {
parser.listener.handleType(start, null);
return token;
}
@override
Token skipType(Token token) {
token = token.next;
assert(token.isKeywordOrIdentifier);
return typeArg.skip(token);
}
}
/// See documentation on the [simpleNullableType] const.
class SimpleNullableType extends SimpleType {
const SimpleNullableType();
@override
TypeInfo get asNonNullable => simpleType;
@override
bool get isNullable => true;
@override
Token parseTypeRest(Token start, Parser parser) {
Token token = start.next;
assert(optional('?', token));
parser.listener.handleType(start, token);
return token;
}
@override
Token skipType(Token token) {
return token.next.next;
}
}
/// See documentation on the [simpleType] const.
class SimpleType implements TypeInfo {
const SimpleType();
@override
TypeInfo get asNonNullable => this;
@override
bool get couldBeExpression => true;
@override
bool get isNullable => false;
@override
Token ensureTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token ensureTypeOrVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token parseTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token parseType(Token token, Parser parser) {
token = token.next;
assert(isValidTypeReference(token));
parser.listener.handleIdentifier(token, IdentifierContext.typeReference);
token = noTypeParamOrArg.parseArguments(token, parser);
return parseTypeRest(token, parser);
}
Token parseTypeRest(Token token, Parser parser) {
parser.listener.handleType(token, null);
return token;
}
@override
Token skipType(Token token) {
return token.next;
}
}
/// See documentation on the [voidType] const.
class VoidType implements TypeInfo {
const VoidType();
@override
TypeInfo get asNonNullable => this;
@override
bool get couldBeExpression => false;
@override
bool get isNullable => false;
@override
Token ensureTypeNotVoid(Token token, Parser parser) {
// Report an error, then parse `void` as if it were a type name.
parser.reportRecoverableError(token.next, fasta.messageInvalidVoid);
return simpleType.parseTypeNotVoid(token, parser);
}
@override
Token ensureTypeOrVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token parseTypeNotVoid(Token token, Parser parser) =>
ensureTypeNotVoid(token, parser);
@override
Token parseType(Token token, Parser parser) {
token = token.next;
parser.listener.handleVoidKeyword(token);
return token;
}
@override
Token skipType(Token token) {
return token.next;
}
}
bool looksLikeName(Token token) =>
token.kind == IDENTIFIER_TOKEN ||
optional('this', token) ||
(token.isIdentifier &&
// Although `typedef` is a legal identifier,
// type `typedef` identifier is not legal and in this situation
// `typedef` is probably a separate declaration.
(!optional('typedef', token) || !token.next.isIdentifier));
/// When missing a comma, determine if the given token looks like it should
/// be part of a collection of type parameters or arguments.
bool looksLikeTypeParamOrArg(bool inDeclaration, Token token) {
if (inDeclaration && token.kind == IDENTIFIER_TOKEN) {
Token next = token.next;
if (next.kind == IDENTIFIER_TOKEN ||
optional(',', next) ||
isCloser(next)) {
return true;
}
}
return false;
}
/// Instances of [ComplexTypeInfo] are returned by [computeType] to represent
/// type references that cannot be represented by the constants above.
class ComplexTypeInfo implements TypeInfo {
/// The first token in the type reference.
Token start;
/// Type arguments were seen during analysis.
final TypeParamOrArgInfo typeArguments;
/// The last token in the type reference.
Token end;
/// The `Function` tokens before the start of type variables of function types
/// as seen during analysis.
Link<Token> typeVariableStarters = const Link<Token>();
/// If the receiver represents a generalized function type then this indicates
/// whether it has a return type, otherwise this is `null`.
bool gftHasReturnType;
ComplexTypeInfo(Token beforeStart, this.typeArguments)
: this.start = beforeStart.next;
@override
TypeInfo get asNonNullable {
return this;
}
@override
bool get couldBeExpression =>
typeArguments == noTypeParamOrArg && typeVariableStarters.isEmpty;
@override
bool get isNullable => false;
@override
Token ensureTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token ensureTypeOrVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token parseTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@override
Token parseType(Token token, Parser parser) {
assert(identical(token.next, start));
if (optional('.', start)) {
// Recovery: Insert missing identifier without sending events
start = parser.insertSyntheticIdentifier(
token, IdentifierContext.prefixedTypeReference);
}
final typeVariableEndGroups = <Token>[];
for (Link<Token> t = typeVariableStarters; t.isNotEmpty; t = t.tail) {
typeVariableEndGroups.add(
computeTypeParamOrArg(t.head, true).parseVariables(t.head, parser));
parser.listener.beginFunctionType(start);
}
if (gftHasReturnType == false) {
// A function type without return type.
// Push the non-existing return type first. The loop below will
// generate the full type.
noType.parseType(token, parser);
} else {
Token typeRefOrPrefix = token.next;
if (optional('void', typeRefOrPrefix)) {
token = voidType.parseType(token, parser);
} else {
if (!optional('.', typeRefOrPrefix) &&
!optional('.', typeRefOrPrefix.next)) {
token =
parser.ensureIdentifier(token, IdentifierContext.typeReference);
} else {
token = parser.ensureIdentifier(
token, IdentifierContext.prefixedTypeReference);
token = parser.parseQualifiedRest(
token, IdentifierContext.typeReferenceContinuation);
if (token.isSynthetic && end == typeRefOrPrefix.next) {
// Recovery: Update `end` if a synthetic identifier was inserted.
end = token;
}
}
token = typeArguments.parseArguments(token, parser);
parser.listener.handleType(typeRefOrPrefix, null);
}
}
int endGroupIndex = typeVariableEndGroups.length - 1;
for (Link<Token> t = typeVariableStarters; t.isNotEmpty; t = t.tail) {
token = token.next;
assert(optional('Function', token));
Token functionToken = token;
if (optional("<", token.next)) {
// Skip type parameters, they were parsed above.
token = typeVariableEndGroups[endGroupIndex];
assert(optional('>', token));
}
--endGroupIndex;
token = parser.parseFormalParametersRequiredOpt(
token, MemberKind.GeneralizedFunctionType);
parser.listener.endFunctionType(functionToken, null);
}
// There are two situations in which the [token] != [end]:
// Valid code: identifier `<` identifier `<` identifier `>>`
// where `>>` is replaced by two tokens.
// Invalid code: identifier `<` identifier identifier `>`
// where a synthetic `>` is inserted between the identifiers.
assert(identical(token, end) || optional('>', token));
// During recovery, [token] may be a synthetic that was inserted in the
// middle of the type reference.
end = token;
return token;
}
@override
Token skipType(Token token) {
return end;
}
/// Given `Function` non-identifier, compute the type
/// and return the receiver or one of the [TypeInfo] constants.
TypeInfo computeNoTypeGFT(bool required) {
assert(optional('Function', start));
computeRest(start, required);
if (gftHasReturnType == null) {
return required ? simpleType : noType;
}
assert(end != null);
return this;
}
/// Given void `Function` non-identifier, compute the type
/// and return the receiver or one of the [TypeInfo] constants.
TypeInfo computeVoidGFT(bool required) {
assert(optional('void', start));
assert(optional('Function', start.next));
computeRest(start.next, required);
if (gftHasReturnType == null) {
return voidType;
}
assert(end != null);
return this;
}
/// Given identifier `Function` non-identifier, compute the type
/// and return the receiver or one of the [TypeInfo] constants.
TypeInfo computeIdentifierGFT(bool required) {
assert(isValidTypeReference(start));
assert(optional('Function', start.next));
computeRest(start.next, required);
if (gftHasReturnType == null) {
return simpleType;
}
assert(end != null);
return this;
}
/// Given a builtin, return the receiver so that parseType will report
/// an error for the builtin used as a type.
TypeInfo computeBuiltinOrVarAsType(bool required) {
assert(start.type.isBuiltIn || optional('var', start));
end = typeArguments.skip(start);
computeRest(end.next, required);
assert(end != null);
return this;
}
/// Given identifier `<` ... `>`, compute the type
/// and return the receiver or one of the [TypeInfo] constants.
TypeInfo computeSimpleWithTypeArguments(bool required) {
assert(isValidTypeReference(start));
assert(optional('<', start.next));
assert(typeArguments != noTypeParamOrArg);
end = typeArguments.skip(start);
computeRest(end.next, required);
if (!required && !looksLikeName(end.next) && gftHasReturnType == null) {
return noType;
}
assert(end != null);
return this;
}
/// Given identifier `.` identifier (or `.` identifier or identifier `.`
/// for recovery), compute the type and return the receiver or one of the
/// [TypeInfo] constants.
TypeInfo computePrefixedType(bool required) {
Token token = start;
if (!optional('.', token)) {
assert(token.isKeywordOrIdentifier);
token = token.next;
}
assert(optional('.', token));
if (token.next.isKeywordOrIdentifier) {
token = token.next;
}
end = typeArguments.skip(token);
computeRest(end.next, required);
if (!required && !looksLikeName(end.next) && gftHasReturnType == null) {
return noType;
}
assert(end != null);
return this;
}
void computeRest(Token token, bool required) {
while (optional('Function', token)) {
Token typeVariableStart = token;
// TODO(danrubel): Consider caching TypeParamOrArgInfo
token = computeTypeParamOrArg(token, true).skip(token);
token = token.next;
if (!optional('(', token)) {
break; // Not a function type.
}
token = token.endGroup;
if (token == null) {
break; // Not a function type.
}
if (!required) {
if (!(token.next.isIdentifier || optional('this', token.next))) {
break; // `Function` used as the name in a function declaration.
}
}
assert(optional(')', token));
gftHasReturnType ??= typeVariableStart != start;
typeVariableStarters = typeVariableStarters.prepend(typeVariableStart);
end = token;
token = token.next;
}
}
}
/// See [noTypeParamOrArg].
class NoTypeParamOrArg extends TypeParamOrArgInfo {
const NoTypeParamOrArg();
@override
int get typeArgumentCount => 0;
@override
Token parseArguments(Token token, Parser parser) {
parser.listener.handleNoTypeArguments(token.next);
return token;
}
@override
Token parseVariables(Token token, Parser parser) {
parser.listener.handleNoTypeVariables(token.next);
return token;
}
@override
Token skip(Token token) => token;
}
class SimpleTypeArgument1 extends TypeParamOrArgInfo {
const SimpleTypeArgument1();
@override
bool get isSimpleTypeArgument => true;
@override
int get typeArgumentCount => 1;
@override
TypeInfo get typeInfo => simpleTypeWith1Argument;
@override
Token parseArguments(Token token, Parser parser) {
Token beginGroup = token.next;
assert(optional('<', beginGroup));
Token endGroup = parseEndGroup(beginGroup, beginGroup.next);
Listener listener = parser.listener;
listener.beginTypeArguments(beginGroup);
simpleType.parseType(beginGroup, parser);
parser.listener.endTypeArguments(1, beginGroup, endGroup);
return endGroup;
}
@override
Token parseVariables(Token token, Parser parser) {
Token beginGroup = token.next;
assert(optional('<', beginGroup));
token = beginGroup.next;
Token endGroup = parseEndGroup(beginGroup, token);
Listener listener = parser.listener;
listener.beginTypeVariables(beginGroup);
listener.beginMetadataStar(token);
listener.endMetadataStar(0);
listener.handleIdentifier(token, IdentifierContext.typeVariableDeclaration);
listener.beginTypeVariable(token);
listener.handleTypeVariablesDefined(token, 1);
listener.handleNoType(token);
listener.endTypeVariable(endGroup, 0, null);
listener.endTypeVariables(beginGroup, endGroup);
return endGroup;
}
@override
Token skip(Token token) {
token = token.next;
assert(optional('<', token));
token = token.next;
assert(token.isKeywordOrIdentifier);
return skipEndGroup(token);
}
Token skipEndGroup(Token token) {
token = token.next;
assert(optional('>', token));
return token;
}
Token parseEndGroup(Token beginGroup, Token token) {
token = token.next;
assert(optional('>', token));
return token;
}
}
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 parseEndGroup(Token beginGroup, Token beforeEndGroup) {
Token endGroup = beforeEndGroup.next;
if (!optional('>', endGroup)) {
endGroup = splitGtEq(endGroup);
endGroup.next.setNext(endGroup.next.next);
}
beforeEndGroup.setNext(endGroup);
return endGroup;
}
}
class SimpleTypeArgument1GtGt extends SimpleTypeArgument1 {
const SimpleTypeArgument1GtGt();
@override
TypeInfo get typeInfo => simpleTypeWith1ArgumentGtGt;
Token skipEndGroup(Token token) {
token = token.next;
assert(optional('>>', token));
return splitGtGt(token);
}
Token parseEndGroup(Token beginGroup, Token beforeEndGroup) {
Token endGroup = beforeEndGroup.next;
if (!optional('>', endGroup)) {
endGroup = splitGtGt(endGroup);
endGroup.next.setNext(endGroup.next.next);
}
beforeEndGroup.setNext(endGroup);
return endGroup;
}
}
class ComplexTypeParamOrArgInfo extends TypeParamOrArgInfo {
/// The first token in the type var.
final Token start;
/// If [inDeclaration] is `true`, then this will more aggressively recover
/// given unbalanced `<` `>` and invalid parameters or arguments.
final bool inDeclaration;
@override
int typeArgumentCount;
/// The `>` token which ends the type parameter or argument.
/// This closer may be synthetic, points to the next token in the stream,
/// is only used when skipping over the type parameters or arguments,
/// and may not be part of the token stream.
Token skipEnd;
ComplexTypeParamOrArgInfo(Token token, this.inDeclaration)
: assert(optional('<', token.next)),
assert(inDeclaration != null),
start = token.next;
/// Parse the tokens and return the receiver or [noTypeParamOrArg] if there
/// are no type parameters or arguments. This does not modify the token
/// stream.
TypeParamOrArgInfo compute() {
Token token;
Token next = start;
typeArgumentCount = 0;
while (true) {
TypeInfo typeInfo = computeType(next, true, inDeclaration);
if (typeInfo == noType) {
while (typeInfo == noType && optional('@', next.next)) {
next = skipMetadata(next);
typeInfo = computeType(next, true, inDeclaration);
}
if (typeInfo == noType) {
if (next == start && !inDeclaration && !isCloser(next.next)) {
return noTypeParamOrArg;
}
if (!optional(',', next.next)) {
token = next;
next = token.next;
break;
}
}
assert(typeInfo != noType || optional(',', next.next));
// Fall through to process type (if any) and consume `,`
}
++typeArgumentCount;
token = typeInfo.skipType(next);
next = token.next;
if (optional('extends', next)) {
token = computeType(next, true, inDeclaration).skipType(next);
next = token.next;
}
if (!optional(',', next)) {
skipEnd = splitCloser(next);
if (skipEnd != null) {
return this;
}
if (!inDeclaration) {
return noTypeParamOrArg;
}
// Recovery
if (!looksLikeTypeParamOrArg(inDeclaration, next)) {
break;
}
// Looks like missing comma. Continue looping.
next = token;
}
}
// Recovery
skipEnd = splitCloser(next);
if (skipEnd == null) {
if (optional('(', next)) {
token = next.endGroup;
next = token.next;
}
skipEnd = splitCloser(next);
if (skipEnd == null) {
skipEnd = splitCloser(next.next);
}
if (skipEnd == null) {
skipEnd = syntheticGt(next);
}
}
return this;
}
@override
Token parseArguments(Token token, Parser parser) {
assert(identical(token.next, start));
Token next = start;
parser.listener.beginTypeArguments(start);
int count = 0;
while (true) {
TypeInfo typeInfo = computeType(next, true, inDeclaration);
if (typeInfo == noType) {
// Recovery
while (typeInfo == noType && optional('@', next.next)) {
parser.reportRecoverableErrorWithToken(
next.next, fasta.templateUnexpectedToken);
next = skipMetadata(next);
typeInfo = computeType(next, true, inDeclaration);
}
// Fall through to process type (if any) and consume `,`
}
token = typeInfo.ensureTypeOrVoid(next, parser);
next = token.next;
++count;
if (!optional(',', next)) {
if (parseCloser(token)) {
break;
}
// Recovery
if (!looksLikeTypeParamOrArg(inDeclaration, next)) {
token = parseUnexpectedEnd(token, true, parser);
break;
}
// Missing comma. Report error, insert comma, and continue looping.
next = parseMissingComma(token, parser);
}
}
Token endGroup = token.next;
parser.listener.endTypeArguments(count, start, endGroup);
return endGroup;
}
@override
Token parseVariables(Token token, Parser parser) {
assert(identical(token.next, start));
Token next = start;
Listener listener = parser.listener;
listener.beginTypeVariables(start);
int count = 0;
Link<Token> typeStarts = const Link<Token>();
Link<TypeInfo> superTypeInfos = const Link<TypeInfo>();
while (true) {
token = parser.parseMetadataStar(next);
next = parser.ensureIdentifier(
token, IdentifierContext.typeVariableDeclaration);
token = next;
listener.beginTypeVariable(token);
typeStarts = typeStarts.prepend(token);
next = token.next;
if (optional('extends', next)) {
TypeInfo typeInfo = computeType(next, true, inDeclaration);
token = typeInfo.skipType(next);
next = token.next;
superTypeInfos = superTypeInfos.prepend(typeInfo);
} else {
superTypeInfos = superTypeInfos.prepend(null);
}
++count;
if (!optional(',', next)) {
if (isCloser(token)) {
break;
}
// Recovery
if (!looksLikeTypeParamOrArg(inDeclaration, next)) {
break;
}
// Missing comma. Report error, insert comma, and continue looping.
next = parseMissingComma(token, parser);
}
}
assert(count > 0);
assert(typeStarts.slowLength() == count);
assert(superTypeInfos.slowLength() == count);
listener.handleTypeVariablesDefined(token, count);
token = null;
while (typeStarts.isNotEmpty) {
Token token2 = typeStarts.head;
TypeInfo typeInfo = superTypeInfos.head;
Token extendsOrSuper = null;
Token next2 = token2.next;
if (typeInfo != null) {
assert(optional('extends', next2));
extendsOrSuper = next2;
token2 = typeInfo.ensureTypeNotVoid(next2, parser);
next2 = token2.next;
} else {
assert(!optional('extends', next2));
listener.handleNoType(token2);
}
// Type variables are "completed" in reverse order, so capture the last
// consumed token from the first "completed" type variable.
token ??= token2;
listener.endTypeVariable(next2, --count, extendsOrSuper);
typeStarts = typeStarts.tail;
superTypeInfos = superTypeInfos.tail;
}
if (!parseCloser(token)) {
token = parseUnexpectedEnd(token, false, parser);
}
Token endGroup = token.next;
listener.endTypeVariables(start, endGroup);
return endGroup;
}
Token parseMissingComma(Token token, Parser parser) {
Token next = token.next;
parser.reportRecoverableError(
next, fasta.templateExpectedButGot.withArguments(','));
return parser.rewriter.insertToken(
token, new SyntheticToken(TokenType.COMMA, next.charOffset));
}
Token parseUnexpectedEnd(Token token, bool isArguments, Parser parser) {
Token next = token.next;
bool errorReported = token.isSynthetic || (next.isSynthetic && !next.isEof);
bool typeFollowsExtends = false;
if (optional('extends', next)) {
if (!errorReported) {
parser.reportRecoverableError(
token, fasta.templateExpectedAfterButGot.withArguments('>'));
errorReported = true;
}
token = next;
next = token.next;
typeFollowsExtends = isValidTypeReference(next);
if (parseCloser(token)) {
return token;
}
}
if (typeFollowsExtends ||
optional('dynamic', next) ||
optional('void', next) ||
optional('Function', next)) {
TypeInfo invalidType = computeType(token, true);
if (invalidType != noType) {
if (!errorReported) {
parser.reportRecoverableError(
token, fasta.templateExpectedAfterButGot.withArguments('>'));
errorReported = true;
}
// Parse the type so that the token stream is properly modified,
// but ensure that parser events are ignored by replacing the listener.
final originalListener = parser.listener;
parser.listener = new ForwardingListener();
token = invalidType.parseType(token, parser);
next = token.next;
parser.listener = originalListener;
if (parseCloser(token)) {
return token;
}
}
}
TypeParamOrArgInfo invalidTypeVar =
computeTypeParamOrArg(token, inDeclaration);
if (invalidTypeVar != noTypeParamOrArg) {
if (!errorReported) {
parser.reportRecoverableError(
token, fasta.templateExpectedAfterButGot.withArguments('>'));
errorReported = true;
}
// Parse the type so that the token stream is properly modified,
// but ensure that parser events are ignored by replacing the listener.
final originalListener = parser.listener;
parser.listener = new ForwardingListener();
token = isArguments
? invalidTypeVar.parseArguments(token, parser)
: invalidTypeVar.parseVariables(token, parser);
next = token.next;
parser.listener = originalListener;
if (parseCloser(token)) {
return token;
}
}
if (optional('(', next) && next.endGroup != null) {
if (!errorReported) {
// Only report an error if one has not already been reported.
parser.reportRecoverableError(
token, fasta.templateExpectedAfterButGot.withArguments('>'));
errorReported = true;
}
token = next.endGroup;
next = token.next;
if (parseCloser(token)) {
return token;
}
}
if (!errorReported) {
// Only report an error if one has not already been reported.
parser.reportRecoverableError(
token, fasta.templateExpectedAfterButGot.withArguments('>'));
}
if (parseCloser(next)) {
return next;
}
Token endGroup = syntheticGt(next);
endGroup.setNext(next);
token.setNext(endGroup);
return token;
}
@override
Token skip(Token token) {
assert(skipEnd != null);
return skipEnd;
}
}
/// Return `true` if [token] is one of `>`, `>>`, `>=`, `>>>`, or `>>=`.
bool isCloser(Token token) {
final value = token.stringValue;
return identical(value, '>') ||
identical(value, '>>') ||
identical(value, '>=') ||
identical(value, '>>>') ||
identical(value, '>>=');
}
/// If [beforeCloser].next is one of `>`, `>>`, `>=`, `>>>`, or `>>=`,
/// then update the token stream and return `true`.
bool parseCloser(Token beforeCloser) {
Token unsplit = beforeCloser.next;
Token split = splitCloser(unsplit);
if (split == unsplit) {
return true;
} else if (split == null) {
return false;
}
split.next.setNext(unsplit.next);
beforeCloser.setNext(split);
return true;
}
/// If [closer] is `>` then return it.
/// If [closer] is one of `>>`, `>=`, `>>>`, or `>>=` then split then token
/// and return the leading `>` without updating the token stream.
/// If [closer] is none of the above, then return null;
Token splitCloser(Token closer) {
String value = closer.stringValue;
if (identical(value, '>')) {
return closer;
} else if (identical(value, '>>')) {
return splitGtGt(closer);
} else if (identical(value, '>=')) {
return splitGtEq(closer);
} else if (identical(value, '>>>')) {
return splitGtFromGtGtGt(closer);
} else if (identical(value, '>>=')) {
return splitGtFromGtGtEq(closer);
}
return null;
}