| // 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 _fe_analyzer_shared.parser.type_info; |
| |
| import '../scanner/token.dart' show Keyword, Token, TokenType; |
| |
| import '../scanner/token_constants.dart' show IDENTIFIER_TOKEN, KEYWORD_TOKEN; |
| |
| import 'identifier_context.dart'; |
| |
| import 'parser_impl.dart' show Parser; |
| |
| import 'type_info_impl.dart'; |
| |
| import 'util.dart' show isOneOf, optional, optional2; |
| |
| /// [TypeInfo] provides information collected by [computeType] |
| /// about a particular type reference. |
| abstract class TypeInfo { |
| /// Return type info representing the receiver without the trailing `?` |
| /// or the receiver if the receiver does not represent a nullable type. |
| TypeInfo get asNonNullable; |
| |
| /// 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 type references |
| /// or expressions, while `A<T>` only looks like a type reference. |
| bool get couldBeExpression; |
| |
| /// Return true if the receiver has a trailing `?`. |
| bool get isNullable; |
| |
| /// Returns true if the type represents a function type, i.e. something like |
| /// void Function foo(int x); |
| bool get isFunctionType; |
| |
| /// Returns true if the type has type arguments. |
| bool get hasTypeArguments; |
| |
| bool get recovered => false; |
| |
| /// 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 `>>` or `>>>` |
| /// or `>>>=` 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 `>>` or `>>>` |
| /// or `>>>=` 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 `>>` or `>>>` or `>>>=` 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 `>>` or `>>>` or `>>>=` 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 number of type arguments |
| int get typeArgumentCount; |
| |
| bool get recovered => 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 `>>` or `>>>` or `>>>=` 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 `>>` or `>>>` or `>>>=` 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 optional2(Keyword.FUNCTION, token) && |
| (optional2(TokenType.LT, token.next!) || |
| optional2(TokenType.OPEN_PAREN, token.next!)); |
| } |
| |
| bool isPossibleRecordType(Token token) { |
| return optional2(TokenType.OPEN_PAREN, token) && |
| token.endGroup != null && |
| !token.endGroup!.isSynthetic; |
| } |
| |
| bool isValidNonRecordTypeReference(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, 'Function')) || |
| (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, bool acceptKeywordForSimpleType = false]) { |
| Token next = token.next!; |
| if (!isValidNonRecordTypeReference(next) && !isPossibleRecordType(next)) { |
| // As next is not a valid type reference, this is all recovery. |
| 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) |
| ..recovered = true; |
| } |
| } 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) |
| ..recovered = true; |
| } |
| } |
| } else if (required) { |
| // Recovery |
| if (optional('.', next)) { |
| // Looks like prefixed type missing the prefix |
| TypeInfo result = new ComplexTypeInfo( |
| token, computeTypeParamOrArg(next, inDeclaration)) |
| .computePrefixedType(required); |
| if (result is ComplexTypeInfo) result.recovered = true; |
| return result; |
| } else if (optional('var', next) && |
| isOneOf(next.next!, const ['<', ',', '>'])) { |
| return new ComplexTypeInfo( |
| token, computeTypeParamOrArg(next, inDeclaration)) |
| .computeBuiltinOrVarAsType(required) |
| ..recovered = true; |
| } |
| } |
| return noType; |
| } |
| |
| if (optional2(Keyword.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(token, required); |
| } |
| |
| if (isPossibleRecordType(next)) { |
| // ([...]) |
| Token after = next.endGroup!.next!; |
| if (isGeneralizedFunctionType(after)) { |
| // ([...]) `Function` |
| return new ComplexTypeInfo(token, noTypeParamOrArg) |
| .computeRecordTypeGFT(required); |
| } |
| if (optional('?', after) && isGeneralizedFunctionType(after.next!)) { |
| // ([...]) `?` `Function` |
| return new ComplexTypeInfo(token, noTypeParamOrArg) |
| .computeRecordTypeQuestionGFT(required); |
| } |
| return new ComplexTypeInfo(token, noTypeParamOrArg) |
| .computeRecordType(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 (optional2(TokenType.QUESTION, next)) { |
| next = next.next!; |
| if (!isGeneralizedFunctionType(next)) { |
| if ((required || looksLikeName(next)) && |
| typeParamOrArg == simpleTypeArgument1) { |
| // identifier `<` identifier `>` `?` identifier |
| return simpleNullableTypeWith1Argument; |
| } |
| // identifier `<` identifier `>` `?` non-identifier |
| return noType; |
| } |
| } else if (!isGeneralizedFunctionType(next)) { |
| if (required || looksLikeName(next)) { |
| // identifier `<` identifier `>` identifier |
| return typeParamOrArg.typeInfo; |
| } |
| // 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 (optional2(TokenType.PERIOD, next)) { |
| next = next.next!; |
| if (isValidNonRecordTypeReference(next)) { |
| // We've seen identifier `.` identifier |
| typeParamOrArg = computeTypeParamOrArg(next, inDeclaration); |
| next = next.next!; |
| if (typeParamOrArg == noTypeParamOrArg) { |
| if (optional2(TokenType.QUESTION, next)) { |
| next = next.next!; |
| if (!isGeneralizedFunctionType(next)) { |
| if (required || looksLikeName(next)) { |
| // identifier `.` identifier `?` identifier |
| // TODO(danrubel): consider adding PrefixedNullableType |
| // Fall through to build complex type |
| } else { |
| // identifier `.` identifier `?` non-identifier |
| return noType; |
| } |
| } |
| } else { |
| if (!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 (optional2(TokenType.QUESTION, next)) { |
| next = next.next!; |
| if (isGeneralizedFunctionType(next)) { |
| // identifier `?` Function `(` |
| return new ComplexTypeInfo(token, noTypeParamOrArg) |
| .computeIdentifierQuestionGFT(required); |
| } else if (required || looksLikeName(next)) { |
| // identifier `?` |
| return simpleNullableType; |
| } |
| } else if (required || |
| looksLikeName(next) || |
| (acceptKeywordForSimpleType && |
| next.isKeywordOrIdentifier && |
| isOneOf(next.next!, okNextValueInFormalParameter))) { |
| // identifier identifier |
| return simpleType; |
| } |
| return noType; |
| } |
| |
| /// Computes the [TypeInfo] for a variable pattern. |
| /// |
| /// This is similar to [computeType], but has special logic to account for an |
| /// ambiguity that arises in patterns due to the fact that `as` can either be |
| /// an identifier or the operator in a castPattern. |
| TypeInfo computeVariablePatternType(Token token, [bool required = false]) { |
| TypeInfo typeInfo = computeType(token, required); |
| Token afterType = typeInfo.skipType(token); |
| if (!identical(afterType, token)) { |
| Token next = afterType.next!; |
| if (next.isIdentifier) { |
| if (optional('as', next) || optional('when', next)) { |
| // We've seen `TYPE as` or `TYPE when`. `as` is a built-in identifier |
| // and `when` is a pseudo-keyword, so this *could* be a variable |
| // pattern. Or it could be that TYPE should have been parsed as a |
| // pattern. We've decided to resolve the ambiguity by assuming that |
| // TYPE was the pattern, and interpret the `when` or `as` as introducing |
| // a guard or a cast pattern, respectively (see discussion at |
| // https://github.com/dart-lang/sdk/issues/52199). |
| return noType; |
| } |
| } |
| } |
| return typeInfo; |
| } |
| |
| /// 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, bool allowsVariance = false]) { |
| Token beginGroup = token.next!; |
| if (!optional2(TokenType.LT, beginGroup)) { |
| return noTypeParamOrArg; |
| } |
| |
| // identifier `<` `void` `>` and `<` `dynamic` `>` |
| // are handled by ComplexTypeInfo. |
| Token next = beginGroup.next!; |
| if ((next.kind == IDENTIFIER_TOKEN || next.type.isPseudo)) { |
| if (optional2(TokenType.GT, next.next!)) { |
| return simpleTypeArgument1; |
| } else if (optional2(TokenType.GT_GT, next.next!)) { |
| return simpleTypeArgument1GtGt; |
| } else if (optional2(TokenType.GT_EQ, next.next!)) { |
| return simpleTypeArgument1GtEq; |
| } |
| } else if (optional('(', next)) { |
| bool recordType = false; |
| if (isPossibleRecordType(next)) { |
| TypeInfo type = computeType(beginGroup, /* required = */ false); |
| if (type is ComplexTypeInfo && |
| (type.isRecordType || type.gftReturnTypeHasRecordType) && |
| !type.recovered) { |
| // Looks like a record type. |
| recordType = true; |
| } |
| } |
| if (!recordType) { |
| return noTypeParamOrArg; |
| } |
| } |
| |
| // TODO(danrubel): Consider adding additional const for common situations. |
| return new ComplexTypeParamOrArgInfo(token, inDeclaration, allowsVariance) |
| .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 mayFollowTypeArgs(typeArg.skip(token).next!) && !typeArg.recovered |
| ? typeArg |
| : noTypeParamOrArg; |
| } |
| |
| /// The set of identifiers that are illegal to use as the name of a variable in |
| /// a variable pattern, or as the name of an identifier in an identifier |
| /// pattern. |
| const Set<String> illegalPatternIdentifiers = {'when', 'as'}; |
| |
| /// Indicates whether the given [token] is allowed to follow a list of type |
| /// arguments used as a selector after an expression. |
| /// |
| /// This is used for disambiguating constructs like `f(a<b,c>(d))` and |
| /// `f(a<b,c>-d)`. In the case of `f(a<b,c>(d))`, `true` will be returned, |
| /// indicating that the `<` and `>` should be interpreted as delimiting type |
| /// arguments (so one argument is being passed to `f` -- a call to the generic |
| /// function `a`). In the case of `f(a<b,c>-d)`, `false` will be returned, |
| /// indicating that the `<` and `>` should be interpreted as operators (so two |
| /// arguments are being passed to `f`: `a < b` and `c > -d`). |
| bool mayFollowTypeArgs(Token token) { |
| const Set<String> continuationTokens = {'(', '.', '==', '!='}; |
| const Set<String> stopTokens = {')', ']', '}', ';', ':', ','}; |
| const Set<String> tokensThatMayFollowTypeArg = { |
| ...continuationTokens, |
| ...stopTokens |
| }; |
| if (token.type == TokenType.EOF) { |
| // The spec doesn't have anything to say about this case, since an |
| // expression can't occur at the end of a file, but for testing it's to our |
| // advantage to allow EOF after type arguments, so that an isolated `f<x>` |
| // can be parsed as an expression. |
| return true; |
| } |
| return tokensThatMayFollowTypeArg.contains(token.lexeme); |
| } |