| // 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; |
| |
| import '../../scanner/token.dart' show Token, TokenType; |
| |
| import '../scanner/token_constants.dart' show IDENTIFIER_TOKEN, KEYWORD_TOKEN; |
| |
| import 'parser.dart' show Parser; |
| |
| import 'type_info_impl.dart'; |
| |
| import 'util.dart' show isOneOf, optional; |
| |
| /// [TypeInfo] provides information collected by [computeType] |
| /// about a particular type reference. |
| abstract class TypeInfo { |
| /// Return `true` if the tokens comprising the type represented by the |
| /// receiver could be interpreted as a valid standalone expression. |
| /// For example, `A` or `A.b` could be interpreted as a type references |
| /// or as expressions, while `A<T>` only looks like a type reference. |
| bool get couldBeExpression; |
| |
| /// Call this function when the token after [token] must be a type (not void). |
| /// This function will call the appropriate event methods on the [Parser]'s |
| /// listener to handle the type, inserting a synthetic type reference if |
| /// necessary. This may modify the token stream when parsing `>>` in valid |
| /// code or during recovery. |
| Token ensureTypeNotVoid(Token token, Parser parser); |
| |
| /// Call this function when the token after [token] must be a type or void. |
| /// This function will call the appropriate event methods on the [Parser]'s |
| /// listener to handle the type, inserting a synthetic type reference if |
| /// necessary. This may modify the token stream when parsing `>>` in valid |
| /// code or during recovery. |
| Token ensureTypeOrVoid(Token token, Parser parser); |
| |
| /// Call this function to parse an optional type (not void) after [token]. |
| /// This function will call the appropriate event methods on the [Parser]'s |
| /// listener to handle the type. This may modify the token stream |
| /// when parsing `>>` in valid code or during recovery. |
| Token parseTypeNotVoid(Token token, Parser parser); |
| |
| /// Call this function to parse an optional type or void after [token]. |
| /// This function will call the appropriate event methods on the [Parser]'s |
| /// listener to handle the type. This may modify the token stream |
| /// when parsing `>>` in valid code or during recovery. |
| Token parseType(Token token, Parser parser); |
| |
| /// Call this function with the [token] before the type to obtain |
| /// the last token in the type. If there is no type, then this method |
| /// will return [token]. This does not modify the token stream. |
| Token skipType(Token token); |
| } |
| |
| /// [TypeParamOrArgInfo] provides information collected by |
| /// [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 |
| /// when parsing `>>` in valid code or during recovery. |
| Token parseArguments(Token token, Parser parser); |
| |
| /// Call this function to parse optional type parameters |
| /// (also known as type variables) after [token]. |
| /// This function will call the appropriate event methods on the [Parser]'s |
| /// listener to handle the parameters. This may modify the token stream |
| /// when parsing `>>` in valid code or during recovery. |
| Token parseVariables(Token token, Parser parser); |
| |
| /// Call this function with the [token] before the type var to obtain |
| /// the last token in the type var. If there is no type var, then this method |
| /// will return [token]. This does not modify the token stream. |
| Token skip(Token token); |
| } |
| |
| /// [NoType] is a specialized [TypeInfo] returned by [computeType] when |
| /// there is no type information in the source. |
| const TypeInfo noType = const NoType(); |
| |
| /// [NoTypeParamOrArg] is a specialized [TypeParamOrArgInfo] returned by |
| /// [computeTypeParamOrArg] when no type parameters or arguments are found. |
| const TypeParamOrArgInfo noTypeParamOrArg = const NoTypeParamOrArg(); |
| |
| /// [VoidType] is a specialized [TypeInfo] returned by [computeType] when |
| /// `void` appears in the source. |
| const TypeInfo voidType = const VoidType(); |
| |
| bool isGeneralizedFunctionType(Token token) { |
| return optional('Function', token) && |
| (optional('<', token.next) || optional('(', token.next)); |
| } |
| |
| bool isValidTypeReference(Token token) { |
| int kind = token.kind; |
| if (IDENTIFIER_TOKEN == kind) return true; |
| if (KEYWORD_TOKEN == kind) { |
| TokenType type = token.type; |
| String value = type.lexeme; |
| return type.isPseudo || |
| (type.isBuiltIn && optional('.', token.next)) || |
| (identical(value, 'dynamic')) || |
| (identical(value, 'void')); |
| } |
| return false; |
| } |
| |
| /// Called by the parser to obtain information about a possible type reference |
| /// that follows [token]. This does not modify the token stream. |
| /// |
| /// If [inDeclaration] is `true`, then this will more aggressively recover |
| /// given unbalanced `<` `>` and invalid parameters or arguments. |
| TypeInfo computeType(final Token token, bool required, |
| [bool inDeclaration = false]) { |
| Token next = token.next; |
| if (!isValidTypeReference(next)) { |
| if (next.type.isBuiltIn) { |
| TypeParamOrArgInfo typeParamOrArg = |
| computeTypeParamOrArg(next, inDeclaration); |
| if (typeParamOrArg != noTypeParamOrArg) { |
| // Recovery: built-in `<` ... `>` |
| if (required || looksLikeName(typeParamOrArg.skip(next).next)) { |
| return new ComplexTypeInfo(token, typeParamOrArg) |
| .computeBuiltinOrVarAsType(required); |
| } |
| } else if (required || isGeneralizedFunctionType(next.next)) { |
| String value = next.stringValue; |
| if ((!identical('get', value) && |
| !identical('set', value) && |
| !identical('factory', value) && |
| !identical('operator', value) && |
| !(identical('typedef', value) && next.next.isIdentifier))) { |
| return new ComplexTypeInfo(token, typeParamOrArg) |
| .computeBuiltinOrVarAsType(required); |
| } |
| } |
| } else if (required) { |
| // Recovery |
| if (optional('.', next)) { |
| // Looks like prefixed type missing the prefix |
| return new ComplexTypeInfo( |
| token, computeTypeParamOrArg(next, inDeclaration)) |
| .computePrefixedType(required); |
| } else if (optional('var', next) && |
| isOneOf(next.next, const ['<', ',', '>'])) { |
| return new ComplexTypeInfo( |
| token, computeTypeParamOrArg(next, inDeclaration)) |
| .computeBuiltinOrVarAsType(required); |
| } |
| } |
| return noType; |
| } |
| |
| if (optional('void', next)) { |
| next = next.next; |
| if (isGeneralizedFunctionType(next)) { |
| // `void` `Function` ... |
| return new ComplexTypeInfo(token, noTypeParamOrArg) |
| .computeVoidGFT(required); |
| } |
| // `void` |
| return voidType; |
| } |
| |
| if (isGeneralizedFunctionType(next)) { |
| // `Function` ... |
| return new ComplexTypeInfo(token, noTypeParamOrArg) |
| .computeNoTypeGFT(required); |
| } |
| |
| // We've seen an identifier. |
| |
| TypeParamOrArgInfo typeParamOrArg = |
| computeTypeParamOrArg(next, inDeclaration); |
| if (typeParamOrArg != noTypeParamOrArg) { |
| if (typeParamOrArg.isSimpleTypeArgument) { |
| // We've seen identifier `<` identifier `>` |
| next = typeParamOrArg.skip(next).next; |
| if (!isGeneralizedFunctionType(next)) { |
| if (required || looksLikeName(next)) { |
| // identifier `<` identifier `>` identifier |
| return typeParamOrArg.typeInfo; |
| } else { |
| // identifier `<` identifier `>` non-identifier |
| return noType; |
| } |
| } |
| } |
| // TODO(danrubel): Consider adding a const for |
| // identifier `<` identifier `,` identifier `>` |
| // if that proves to be a common case. |
| |
| // identifier `<` ... `>` |
| return new ComplexTypeInfo(token, typeParamOrArg) |
| .computeSimpleWithTypeArguments(required); |
| } |
| |
| assert(typeParamOrArg == noTypeParamOrArg); |
| next = next.next; |
| if (optional('.', next)) { |
| next = next.next; |
| if (isValidTypeReference(next)) { |
| // We've seen identifier `.` identifier |
| typeParamOrArg = computeTypeParamOrArg(next, inDeclaration); |
| next = next.next; |
| if (typeParamOrArg == noTypeParamOrArg && |
| !isGeneralizedFunctionType(next)) { |
| if (required || looksLikeName(next)) { |
| // identifier `.` identifier identifier |
| return prefixedType; |
| } else { |
| // identifier `.` identifier non-identifier |
| return noType; |
| } |
| } |
| // identifier `.` identifier |
| return new ComplexTypeInfo(token, typeParamOrArg) |
| .computePrefixedType(required); |
| } |
| // identifier `.` non-identifier |
| if (required) { |
| typeParamOrArg = computeTypeParamOrArg(token.next.next, inDeclaration); |
| return new ComplexTypeInfo(token, typeParamOrArg) |
| .computePrefixedType(required); |
| } |
| return noType; |
| } |
| |
| assert(typeParamOrArg == noTypeParamOrArg); |
| if (isGeneralizedFunctionType(next)) { |
| // identifier `Function` |
| return new ComplexTypeInfo(token, noTypeParamOrArg) |
| .computeIdentifierGFT(required); |
| } |
| |
| if (required || looksLikeName(next)) { |
| // identifier identifier |
| return simpleType; |
| } |
| return noType; |
| } |
| |
| /// Called by the parser to obtain information about a possible group of type |
| /// parameters or type arguments that follow [token]. |
| /// This does not modify the token stream. |
| /// |
| /// If [inDeclaration] is `true`, then this will more aggressively recover |
| /// given unbalanced `<` `>` and invalid parameters or arguments. |
| TypeParamOrArgInfo computeTypeParamOrArg(Token token, |
| [bool inDeclaration = false]) { |
| Token beginGroup = token.next; |
| if (!optional('<', beginGroup)) { |
| return noTypeParamOrArg; |
| } |
| |
| // identifier `<` `void` `>` and `<` `dynamic` `>` |
| // are handled by ComplexTypeInfo. |
| Token next = beginGroup.next; |
| if ((next.kind == IDENTIFIER_TOKEN || next.type.isPseudo)) { |
| if (optional('>', next.next)) { |
| return simpleTypeArgument1; |
| } else if (optional('>>', next.next)) { |
| return simpleTypeArgument1GtGt; |
| } else if (optional('>=', next.next)) { |
| return simpleTypeArgument1GtEq; |
| } |
| } else if (optional('(', next)) { |
| return noTypeParamOrArg; |
| } |
| |
| // TODO(danrubel): Consider adding additional const for common situations. |
| return new ComplexTypeParamOrArgInfo(token, inDeclaration).compute(); |
| } |
| |
| /// Called by the parser to obtain information about a possible group of type |
| /// type arguments that follow [token] and that are followed by '('. |
| /// Returns the type arguments if [token] matches '<' type (',' type)* '>' '(', |
| /// and otherwise returns [noTypeParamOrArg]. The final '(' is not part of the |
| /// grammar construct `typeArguments`, but it is required here such that type |
| /// arguments in generic method invocations can be recognized, and as few as |
| /// possible other constructs will pass (e.g., 'a < C, D > 3'). |
| TypeParamOrArgInfo computeMethodTypeArguments(Token token) { |
| TypeParamOrArgInfo typeArg = computeTypeParamOrArg(token); |
| return optional('(', typeArg.skip(token).next) ? typeArg : noTypeParamOrArg; |
| } |