| // Copyright (c) 2016, 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 kernel.type_environment; |
| |
| import 'ast.dart'; |
| import 'class_hierarchy.dart'; |
| import 'core_types.dart'; |
| import 'type_algebra.dart'; |
| |
| import 'src/hierarchy_based_type_environment.dart' |
| show HierarchyBasedTypeEnvironment; |
| import 'src/types.dart'; |
| |
| typedef void ErrorHandler(TreeNode node, String message); |
| |
| abstract class TypeEnvironment extends Types { |
| @override |
| final CoreTypes coreTypes; |
| |
| TypeEnvironment.fromSubclass(this.coreTypes, ClassHierarchyBase base) |
| : super(base); |
| |
| factory TypeEnvironment(CoreTypes coreTypes, ClassHierarchy hierarchy) { |
| return new HierarchyBasedTypeEnvironment(coreTypes, hierarchy); |
| } |
| |
| @override |
| ClassHierarchy get hierarchy; |
| |
| Class get intClass => coreTypes.intClass; |
| Class get numClass => coreTypes.numClass; |
| Class get functionClass => coreTypes.functionClass; |
| Class get objectClass => coreTypes.objectClass; |
| |
| InterfaceType get objectLegacyRawType => coreTypes.objectLegacyRawType; |
| InterfaceType get objectNullableRawType => coreTypes.objectNullableRawType; |
| InterfaceType get functionLegacyRawType => coreTypes.functionLegacyRawType; |
| |
| /// Returns the type `List<E>` with the given [nullability] and [elementType] |
| /// as `E`. |
| InterfaceType listType(DartType elementType, Nullability nullability) { |
| return new InterfaceType( |
| coreTypes.listClass, nullability, <DartType>[elementType]); |
| } |
| |
| /// Returns the type `Set<E>` with the given [nullability] and [elementType] |
| /// as `E`. |
| InterfaceType setType(DartType elementType, Nullability nullability) { |
| return new InterfaceType( |
| coreTypes.setClass, nullability, <DartType>[elementType]); |
| } |
| |
| /// Returns the type `Map<K,V>` with the given [nullability], [key] as `K` |
| /// and [value] is `V`. |
| InterfaceType mapType(DartType key, DartType value, Nullability nullability) { |
| return new InterfaceType( |
| coreTypes.mapClass, nullability, <DartType>[key, value]); |
| } |
| |
| /// Returns the type `Iterable<E>` with the given [nullability] and [type] |
| /// as `E`. |
| InterfaceType iterableType(DartType type, Nullability nullability) { |
| return new InterfaceType( |
| coreTypes.iterableClass, nullability, <DartType>[type]); |
| } |
| |
| /// Returns the type `Stream<E>` with the given [nullability] and [type] |
| /// as `E`. |
| InterfaceType streamType(DartType type, Nullability nullability) { |
| return new InterfaceType( |
| coreTypes.streamClass, nullability, <DartType>[type]); |
| } |
| |
| /// Returns the type `Future<E>` with the given [nullability] and [type] |
| /// as `E`. |
| InterfaceType futureType(DartType type, Nullability nullability) { |
| return new InterfaceType( |
| coreTypes.futureClass, nullability, <DartType>[type]); |
| } |
| |
| DartType _withDeclaredNullability(DartType type, Nullability nullability) { |
| if (type is NullType) return type; |
| return type.withDeclaredNullability( |
| uniteNullabilities(type.declaredNullability, nullability)); |
| } |
| |
| /// Returns the `flatten` of [type] as defined in the spec, which unwraps a |
| /// layer of Future or FutureOr from a type. |
| DartType flatten(DartType t) { |
| // if T is S? then flatten(T) = flatten(S)? |
| // otherwise if T is S* then flatten(T) = flatten(S)* |
| // -- this is preserve with the calls to [_withDeclaredNullability] below. |
| |
| // otherwise if T is FutureOr<S> then flatten(T) = S |
| if (t is FutureOrType) { |
| return _withDeclaredNullability(t.typeArgument, t.declaredNullability); |
| } |
| |
| // otherwise if T <: Future then let S be a type such that T <: Future<S> |
| // and for all R, if T <: Future<R> then S <: R; then flatten(T) = S |
| DartType resolved = _resolveTypeParameterType(t); |
| if (resolved is InterfaceType) { |
| List<DartType>? futureArguments = |
| getTypeArgumentsAsInstanceOf(resolved, coreTypes.futureClass); |
| if (futureArguments != null) { |
| return _withDeclaredNullability(futureArguments.single, t.nullability); |
| } |
| } |
| |
| // otherwise flatten(T) = T |
| return t; |
| } |
| |
| /// Returns the non-type parameter type bound of [type]. |
| DartType _resolveTypeParameterType(DartType type) { |
| while (type is TypeParameterType) { |
| TypeParameterType typeParameterType = type; |
| type = typeParameterType.bound; |
| } |
| return type; |
| } |
| |
| /// Returns the type of the element in the for-in statement [node] with |
| /// [iterableExpressionType] as the static type of the iterable expression. |
| /// |
| /// The [iterableExpressionType] must be a subclass of `Stream` or `Iterable` |
| /// depending on whether `node.isAsync` is `true` or not. |
| DartType forInElementType( |
| ForInStatement node, DartType iterableExpressionType) { |
| // TODO(johnniwinther): Update this to use the type of |
| // `iterable.iterator.current` if inference is updated accordingly. |
| InterfaceType iterableType = |
| _resolveTypeParameterType(iterableExpressionType) as InterfaceType; |
| if (node.isAsync) { |
| List<DartType>? typeArguments = |
| getTypeArgumentsAsInstanceOf(iterableType, coreTypes.streamClass); |
| return typeArguments!.single; |
| } else { |
| List<DartType>? typeArguments = |
| getTypeArgumentsAsInstanceOf(iterableType, coreTypes.iterableClass); |
| return typeArguments!.single; |
| } |
| } |
| |
| /// True if [member] is a binary operator whose return type is defined by |
| /// the both operand types. |
| bool isSpecialCasedBinaryOperator(Procedure member, |
| {bool isNonNullableByDefault: false}) { |
| if (isNonNullableByDefault) { |
| Class? class_ = member.enclosingClass; |
| // TODO(johnniwinther): Do we need to recognize backend implementation |
| // methods? |
| if (class_ == coreTypes.intClass || |
| class_ == coreTypes.numClass || |
| class_ == coreTypes.doubleClass) { |
| String name = member.name.text; |
| return name == '+' || |
| name == '-' || |
| name == '*' || |
| name == 'remainder' || |
| name == '%'; |
| } |
| } else { |
| Class? class_ = member.enclosingClass; |
| if (class_ == coreTypes.intClass || class_ == coreTypes.numClass) { |
| String name = member.name.text; |
| return name == '+' || |
| name == '-' || |
| name == '*' || |
| name == 'remainder' || |
| name == '%'; |
| } |
| } |
| return false; |
| } |
| |
| /// True if [member] is a ternary operator whose return type is defined by |
| /// the least upper bound of the operand types. |
| bool isSpecialCasedTernaryOperator(Procedure member, |
| {bool isNonNullableByDefault: false}) { |
| if (isNonNullableByDefault) { |
| Class? class_ = member.enclosingClass; |
| if (class_ == coreTypes.intClass || class_ == coreTypes.numClass) { |
| String name = member.name.text; |
| return name == 'clamp'; |
| } |
| } |
| return false; |
| } |
| |
| /// Returns the static return type of a special cased binary operator |
| /// (see [isSpecialCasedBinaryOperator]) given the static type of the |
| /// operands. |
| DartType getTypeOfSpecialCasedBinaryOperator(DartType type1, DartType type2, |
| {bool isNonNullableByDefault: false}) { |
| if (isNonNullableByDefault) { |
| // Let e be an expression of one of the forms e1 + e2, e1 - e2, e1 * e2, |
| // e1 % e2 or e1.remainder(e2), where the static type of e1 is a non-Never |
| // type T and T <: num, and where the static type of e2 is S and S is |
| // assignable to num. Then: |
| if (type1 is! NeverType && |
| isSubtypeOf(type1, coreTypes.numNonNullableRawType, |
| SubtypeCheckMode.withNullabilities) && |
| type2 is DynamicType || |
| isSubtypeOf(type2, coreTypes.numNonNullableRawType, |
| SubtypeCheckMode.withNullabilities)) { |
| if (isSubtypeOf(type1, coreTypes.doubleNonNullableRawType, |
| SubtypeCheckMode.withNullabilities)) { |
| // If T <: double then the static type of e is double. This includes S |
| // being dynamic or Never. |
| return coreTypes.doubleNonNullableRawType; |
| } else if (type2 is! NeverType && |
| isSubtypeOf(type2, coreTypes.doubleNonNullableRawType, |
| SubtypeCheckMode.withNullabilities)) { |
| // If S <: double and not S <:Never, then the static type of e is |
| // double. |
| return coreTypes.doubleNonNullableRawType; |
| } else if (isSubtypeOf(type1, coreTypes.intNonNullableRawType, |
| SubtypeCheckMode.withNullabilities) && |
| type2 is! NeverType && |
| isSubtypeOf(type2, coreTypes.intNonNullableRawType, |
| SubtypeCheckMode.withNullabilities)) { |
| // If T <: int , S <: int and not S <: Never, then the static type of |
| // e is int. |
| return coreTypes.intNonNullableRawType; |
| } else if (type2 is! NeverType && |
| isSubtypeOf(type2, type1, SubtypeCheckMode.withNullabilities)) { |
| // Otherwise the static type of e is num. |
| return coreTypes.numNonNullableRawType; |
| } |
| } |
| // Otherwise the static type of e is num. |
| return coreTypes.numNonNullableRawType; |
| } else { |
| type1 = _resolveTypeParameterType(type1); |
| type2 = _resolveTypeParameterType(type2); |
| |
| if (type1 == type2) return type1; |
| |
| if (type1 is InterfaceType && type2 is InterfaceType) { |
| if (type1.classNode == type2.classNode) { |
| return type1; |
| } |
| if (type1.classNode == coreTypes.doubleClass || |
| type2.classNode == coreTypes.doubleClass) { |
| return coreTypes.doubleRawType(type1.nullability); |
| } |
| } |
| |
| return coreTypes.numRawType(type1.nullability); |
| } |
| } |
| |
| DartType getTypeOfSpecialCasedTernaryOperator( |
| DartType type1, DartType type2, DartType type3, Library clientLibrary) { |
| if (clientLibrary.isNonNullableByDefault) { |
| // Let e be a normal invocation of the form e1.clamp(e2, e3), where the |
| // static types of e1, e2 and e3 are T1, T2 and T3 respectively, and where |
| // T1, T2, and T3 are all non-Never subtypes of num. Then: |
| if (type1 is! NeverType && type2 is! NeverType && type3 is! NeverType |
| /* We skip the check that all types are subtypes of num because, if |
| not, we'll compute the static type to be num, anyway.*/ |
| ) { |
| if (isSubtypeOf(type1, coreTypes.intNonNullableRawType, |
| SubtypeCheckMode.withNullabilities) && |
| isSubtypeOf(type2, coreTypes.intNonNullableRawType, |
| SubtypeCheckMode.withNullabilities) && |
| isSubtypeOf(type3, coreTypes.intNonNullableRawType, |
| SubtypeCheckMode.withNullabilities)) { |
| // If T1, T2 and T3 are all subtypes of int, the static type of e is |
| // int. |
| return coreTypes.intNonNullableRawType; |
| } else if (isSubtypeOf(type1, coreTypes.doubleNonNullableRawType, |
| SubtypeCheckMode.withNullabilities) && |
| isSubtypeOf(type2, coreTypes.doubleNonNullableRawType, |
| SubtypeCheckMode.withNullabilities) && |
| isSubtypeOf(type3, coreTypes.doubleNonNullableRawType, |
| SubtypeCheckMode.withNullabilities)) { |
| // If T1, T2 and T3 are all subtypes of double, the static type of e |
| // is double. |
| return coreTypes.doubleNonNullableRawType; |
| } |
| } |
| // Otherwise the static type of e is num. |
| return coreTypes.numNonNullableRawType; |
| } |
| return coreTypes.numRawType(type1.nullability); |
| } |
| |
| /// Returns the possibly abstract interface member of [class_] with the given |
| /// [name]. |
| /// |
| /// If [setter] is `false`, only fields, methods, and getters with that name |
| /// will be found. If [setter] is `true`, only non-final fields and setters |
| /// will be found. |
| /// |
| /// If multiple members with that name are inherited and not overridden, the |
| /// member from the first declared supertype is returned. |
| Member? getInterfaceMember(Class cls, Name name, {bool setter: false}); |
| } |
| |
| /// Tri-state logical result of a nullability-aware subtype check. |
| class IsSubtypeOf { |
| /// Internal value constructed via [IsSubtypeOf.never]. |
| /// |
| /// The integer values of [_valueNever], [_valueOnlyIfIgnoringNullabilities], |
| /// and [_valueAlways] are important for the implementations of [_andValues], |
| /// [_all], and [and]. They should be kept in sync. |
| static const int _valueNever = 0; |
| |
| /// Internal value constructed via [IsSubtypeOf.onlyIfIgnoringNullabilities]. |
| static const int _valueOnlyIfIgnoringNullabilities = 1; |
| |
| /// Internal value constructed via [IsSubtypeOf.always]. |
| static const int _valueAlways = 3; |
| |
| static const List<IsSubtypeOf> _all = const <IsSubtypeOf>[ |
| const IsSubtypeOf.never(), |
| const IsSubtypeOf.onlyIfIgnoringNullabilities(), |
| // There's no value for this index so we use `IsSubtypeOf.never()` as a |
| // dummy value. |
| const IsSubtypeOf.never(), |
| const IsSubtypeOf.always() |
| ]; |
| |
| /// Combines results of subtype checks on parts into the overall result. |
| /// |
| /// It's an implementation detail for [and]. See the comment on [and] for |
| /// more details and examples. Both [value1] and [value2] should be chosen |
| /// from [_valueNever], [_valueOnlyIfIgnoringNullabilities], and |
| /// [_valueAlways]. The method produces the result which is one of |
| /// [_valueNever], [_valueOnlyIfIgnoringNullabilities], and [_valueAlways]. |
| static int _andValues(int value1, int value2) => value1 & value2; |
| |
| /// Combines results of the checks on alternatives into the overall result. |
| /// |
| /// It's an implementation detail for [or]. See the comment on [or] for more |
| /// details and examples. Both [value1] and [value2] should be chosen from |
| /// [_valueNever], [_valueOnlyIfIgnoringNullabilities], and [_valueAlways]. |
| /// The method produces the result which is one of [_valueNever], |
| /// [_valueOnlyIfIgnoringNullabilities], and [_valueAlways]. |
| static int _orValues(int value1, int value2) => value1 | value2; |
| |
| /// The only state of an [IsSubtypeOf] object. |
| final int _value; |
| |
| final DartType? subtype; |
| |
| final DartType? supertype; |
| |
| const IsSubtypeOf._internal(int value, this.subtype, this.supertype) |
| : _value = value; |
| |
| /// Subtype check succeeds in both modes. |
| const IsSubtypeOf.always() : this._internal(_valueAlways, null, null); |
| |
| /// Subtype check succeeds only if the nullability markers are ignored. |
| /// |
| /// It is assumed that if a subtype check succeeds for two types in full-NNBD |
| /// mode, it also succeeds for those two types if the nullability markers on |
| /// the types and all of their sub-terms are ignored (that is, in the pre-NNBD |
| /// mode). By contraposition, if a subtype check fails for two types when the |
| /// nullability markers are ignored, it should also fail for those types in |
| /// full-NNBD mode. |
| const IsSubtypeOf.onlyIfIgnoringNullabilities( |
| {DartType? subtype, DartType? supertype}) |
| : this._internal(_valueOnlyIfIgnoringNullabilities, subtype, supertype); |
| |
| /// Subtype check fails in both modes. |
| const IsSubtypeOf.never() : this._internal(_valueNever, null, null); |
| |
| /// Checks if two types are in relation based solely on their nullabilities. |
| /// |
| /// This is useful on its own if the types are known to be the same modulo the |
| /// nullability attribute, but mostly it's useful to combine the result from |
| /// [IsSubtypeOf.basedSolelyOnNullabilities] via [and] with the partial |
| /// results obtained from other type parts. For example, the overall result |
| /// for `List<int>? <: List<num>*` can be computed as `Ra.and(Rn)` where `Ra` |
| /// is the result of a subtype check on the arguments `int` and `num`, and |
| /// `Rn` is the result of [IsSubtypeOf.basedSolelyOnNullabilities] on the |
| /// types `List<int>?` and `List<num>*`. |
| factory IsSubtypeOf.basedSolelyOnNullabilities( |
| DartType subtype, DartType supertype) { |
| if (subtype is InvalidType) { |
| if (supertype is InvalidType) { |
| return const IsSubtypeOf.always(); |
| } |
| return new IsSubtypeOf.onlyIfIgnoringNullabilities( |
| subtype: subtype, supertype: supertype); |
| } |
| if (supertype is InvalidType) { |
| return new IsSubtypeOf.onlyIfIgnoringNullabilities( |
| subtype: subtype, supertype: supertype); |
| } |
| |
| if (subtype.isPotentiallyNullable && supertype.isPotentiallyNonNullable) { |
| // It's a special case to test X% <: X%, FutureOr<X%> <: FutureOr<X%>, |
| // FutureOr<FutureOr<X%>> <: FutureOr<FutureOr<X%>>, etc, where X is a |
| // type parameter. In that case, the nullabilities of the subtype and the |
| // supertype are related, that is, they are both nullable or non-nullable |
| // at run time. |
| if (subtype.nullability == Nullability.undetermined && |
| supertype.nullability == Nullability.undetermined) { |
| DartType unwrappedSubtype = subtype; |
| DartType unwrappedSupertype = supertype; |
| while (unwrappedSubtype is FutureOrType) { |
| unwrappedSubtype = unwrappedSubtype.typeArgument; |
| } |
| while (unwrappedSupertype is FutureOrType) { |
| unwrappedSupertype = unwrappedSupertype.typeArgument; |
| } |
| if (unwrappedSubtype.nullability == unwrappedSupertype.nullability) { |
| // The relationship between the types must be established elsewhere. |
| return const IsSubtypeOf.always(); |
| } |
| } |
| return new IsSubtypeOf.onlyIfIgnoringNullabilities( |
| subtype: subtype, supertype: supertype); |
| } |
| return const IsSubtypeOf.always(); |
| } |
| |
| /// Combines results for the type parts into the overall result for the type. |
| /// |
| /// For example, the result of `A<B1, C1> <: A<B2, C2>` can be computed from |
| /// the results of the checks `B1 <: B2` and `C1 <: C2`. Using the binary |
| /// outcome of the checks, the combination of the check results on parts is |
| /// simply done via `&&`, and [and] is the analog to `&&` for the ternary |
| /// outcome. So, in the example above the overall result is computed as |
| /// `Rb.and(Rc)` where `Rb` is the result of `B1 <: B2`, `Rc` is the result |
| /// of `C1 <: C2`. |
| IsSubtypeOf and(IsSubtypeOf other) { |
| int resultValue = _andValues(_value, other._value); |
| if (resultValue == IsSubtypeOf._valueOnlyIfIgnoringNullabilities) { |
| // If the type mismatch is due to nullabilities, the mismatching parts are |
| // remembered in either 'this' or [other]. In that case we need to return |
| // exactly one of those objects, so that the information about mismatching |
| // parts is propagated upwards. |
| if (_value == IsSubtypeOf._valueOnlyIfIgnoringNullabilities) { |
| return this; |
| } else { |
| assert(other._value == IsSubtypeOf._valueOnlyIfIgnoringNullabilities); |
| return other; |
| } |
| } else { |
| return _all[resultValue]; |
| } |
| } |
| |
| /// Shorts the computation of [and] if `this` is [IsSubtypeOf.never]. |
| /// |
| /// Use this instead of [and] for optimization in case the argument to [and] |
| /// is, for example, a potentially expensive subtype check. Unlike [and], |
| /// [andSubtypeCheckFor] will immediately return if `this` was constructed as |
| /// [IsSubtypeOf.never] because the right-hand side will not change the |
| /// overall result anyway. |
| IsSubtypeOf andSubtypeCheckFor( |
| DartType subtype, DartType supertype, Types tester) { |
| if (_value == _valueNever) return this; |
| return this |
| .and(tester.performNullabilityAwareSubtypeCheck(subtype, supertype)); |
| } |
| |
| /// Combines results of the checks on alternatives into the overall result. |
| /// |
| /// For example, the result of `T <: FutureOr<S>` can be computed from the |
| /// results of the checks `T <: S` and `T <: Future<S>`. Using the binary |
| /// outcome of the checks, the combination of the check results on parts is |
| /// simply done via logical "or", and [or] is the analog to "or" for the |
| /// ternary outcome. So, in the example above the overall result is computed |
| /// as `Rs.or(Rf)` where `Rs` is the result of `T <: S`, `Rf` is the result of |
| /// `T <: Future<S>`. |
| IsSubtypeOf or(IsSubtypeOf other) { |
| int resultValue = _orValues(_value, other._value); |
| if (resultValue == IsSubtypeOf._valueOnlyIfIgnoringNullabilities) { |
| // If the type mismatch is due to nullabilities, the mismatching parts are |
| // remembered in either 'this' or [other]. In that case we need to return |
| // exactly one of those objects, so that the information about mismatching |
| // parts is propagated upwards. |
| if (_value == IsSubtypeOf._valueOnlyIfIgnoringNullabilities) { |
| return this; |
| } else { |
| assert(other._value == IsSubtypeOf._valueOnlyIfIgnoringNullabilities); |
| return other; |
| } |
| } else { |
| return _all[resultValue]; |
| } |
| } |
| |
| /// Shorts the computation of [or] if `this` is [IsSubtypeOf.always]. |
| /// |
| /// Use this instead of [or] for optimization in case the argument to [or] is, |
| /// for example, a potentially expensive subtype check. Unlike [or], |
| /// [orSubtypeCheckFor] will immediately return if `this` was constructed |
| /// as [IsSubtypeOf.always] because the right-hand side will not change the |
| /// overall result anyway. |
| IsSubtypeOf orSubtypeCheckFor( |
| DartType subtype, DartType supertype, Types tester) { |
| if (_value == _valueAlways) return this; |
| return this |
| .or(tester.performNullabilityAwareSubtypeCheck(subtype, supertype)); |
| } |
| |
| bool isSubtypeWhenIgnoringNullabilities() { |
| return _value != _valueNever; |
| } |
| |
| bool isSubtypeWhenUsingNullabilities() { |
| return _value == _valueAlways; |
| } |
| |
| @override |
| String toString() { |
| switch (_value) { |
| case _valueAlways: |
| return "IsSubtypeOf.always"; |
| case _valueNever: |
| return "IsSubtypeOf.never"; |
| case _valueOnlyIfIgnoringNullabilities: |
| return "IsSubtypeOf.onlyIfIgnoringNullabilities"; |
| } |
| return "IsSubtypeOf.<unknown value '${_value}'>"; |
| } |
| } |
| |
| enum SubtypeCheckMode { |
| withNullabilities, |
| ignoringNullabilities, |
| } |
| |
| abstract class StaticTypeCache { |
| DartType getExpressionType(Expression node, StaticTypeContext context); |
| |
| DartType getForInIteratorType(ForInStatement node, StaticTypeContext context); |
| |
| DartType getForInElementType(ForInStatement node, StaticTypeContext context); |
| } |
| |
| class StaticTypeCacheImpl implements StaticTypeCache { |
| late Map<Expression, DartType> _expressionTypes = {}; |
| late Map<ForInStatement, DartType> _forInIteratorTypes = {}; |
| late Map<ForInStatement, DartType> _forInElementTypes = {}; |
| |
| @override |
| DartType getExpressionType(Expression node, StaticTypeContext context) { |
| return _expressionTypes[node] ??= node.getStaticTypeInternal(context); |
| } |
| |
| @override |
| DartType getForInIteratorType( |
| ForInStatement node, StaticTypeContext context) { |
| return _forInIteratorTypes[node] ??= node.getIteratorTypeInternal(context); |
| } |
| |
| @override |
| DartType getForInElementType(ForInStatement node, StaticTypeContext context) { |
| return _forInElementTypes[node] ??= node.getElementTypeInternal(context); |
| } |
| } |
| |
| /// Context object needed for computing `Expression.getStaticType`. |
| /// |
| /// The [StaticTypeContext] provides access to the [TypeEnvironment] and the |
| /// current 'this type' as well as determining the nullability state of the |
| /// enclosing library. |
| abstract class StaticTypeContext { |
| /// The [TypeEnvironment] used for the static type computation. |
| /// |
| /// This provides access to the core types and the class hierarchy. |
| TypeEnvironment get typeEnvironment; |
| |
| /// The static type of a `this` expression. |
| InterfaceType? get thisType; |
| |
| /// Creates a static type context for computing static types in the body |
| /// of [member]. |
| factory StaticTypeContext(Member member, TypeEnvironment typeEnvironment, |
| {StaticTypeCache cache}) = StaticTypeContextImpl; |
| |
| /// Creates a static type context for computing static types of annotations |
| /// in [library]. |
| factory StaticTypeContext.forAnnotations( |
| Library library, TypeEnvironment typeEnvironment, |
| {StaticTypeCache cache}) = StaticTypeContextImpl.forAnnotations; |
| |
| /// The [Nullability] used for non-nullable types. |
| /// |
| /// For opt out libraries this is [Nullability.legacy]. |
| Nullability get nonNullable; |
| |
| /// The [Nullability] used for nullable types. |
| /// |
| /// For opt out libraries this is [Nullability.legacy]. |
| Nullability get nullable; |
| |
| /// Return `true` if the current library is opted in to non-nullable by |
| /// default. |
| bool get isNonNullableByDefault; |
| |
| /// Returns the mode under which the current library was compiled. |
| NonNullableByDefaultCompiledMode get nonNullableByDefaultCompiledMode; |
| |
| /// Returns the static type of [node]. |
| DartType getExpressionType(Expression node); |
| |
| /// Returns the static type of the iterator in for-in statement [node]. |
| DartType getForInIteratorType(ForInStatement node); |
| |
| /// Returns the static type of the element in for-in statement [node]. |
| DartType getForInElementType(ForInStatement node); |
| } |
| |
| class StaticTypeContextImpl implements StaticTypeContext { |
| /// The [TypeEnvironment] used for the static type computation. |
| /// |
| /// This provides access to the core types and the class hierarchy. |
| @override |
| final TypeEnvironment typeEnvironment; |
| |
| /// The library in which the static type is computed. |
| /// |
| /// The `library.isNonNullableByDefault` property is used to determine the |
| /// nullabilities of the static types. |
| final Library _library; |
| |
| /// The static type of a `this` expression. |
| @override |
| final InterfaceType? thisType; |
| |
| final StaticTypeCache? _cache; |
| |
| /// Creates a static type context for computing static types in the body |
| /// of [member]. |
| StaticTypeContextImpl(Member member, this.typeEnvironment, |
| {StaticTypeCache? cache}) |
| : _library = member.enclosingLibrary, |
| thisType = member.enclosingClass?.getThisType( |
| typeEnvironment.coreTypes, member.enclosingLibrary.nonNullable), |
| _cache = cache; |
| |
| /// Creates a static type context for computing static types of annotations |
| /// in [library]. |
| StaticTypeContextImpl.forAnnotations(this._library, this.typeEnvironment, |
| {StaticTypeCache? cache}) |
| : thisType = null, |
| _cache = cache; |
| |
| /// The [Nullability] used for non-nullable types. |
| /// |
| /// For opt out libraries this is [Nullability.legacy]. |
| @override |
| Nullability get nonNullable => _library.nonNullable; |
| |
| /// The [Nullability] used for nullable types. |
| /// |
| /// For opt out libraries this is [Nullability.legacy]. |
| @override |
| Nullability get nullable => _library.nullable; |
| |
| /// Return `true` if the current library is opted in to non-nullable by |
| /// default. |
| @override |
| bool get isNonNullableByDefault => _library.isNonNullableByDefault; |
| |
| /// Returns the mode under which the current library was compiled. |
| @override |
| NonNullableByDefaultCompiledMode get nonNullableByDefaultCompiledMode => |
| _library.nonNullableByDefaultCompiledMode; |
| |
| @override |
| DartType getExpressionType(Expression node) { |
| if (_cache != null) { |
| return _cache!.getExpressionType(node, this); |
| } else { |
| return node.getStaticTypeInternal(this); |
| } |
| } |
| |
| @override |
| DartType getForInIteratorType(ForInStatement node) { |
| if (_cache != null) { |
| return _cache!.getForInIteratorType(node, this); |
| } else { |
| return node.getIteratorTypeInternal(this); |
| } |
| } |
| |
| @override |
| DartType getForInElementType(ForInStatement node) { |
| if (_cache != null) { |
| return _cache!.getForInElementType(node, this); |
| } else { |
| return node.getElementTypeInternal(this); |
| } |
| } |
| } |
| |
| /// Implementation of [StaticTypeContext] that update its state when entering |
| /// and leaving libraries and members. |
| abstract class StatefulStaticTypeContext implements StaticTypeContext { |
| @override |
| final TypeEnvironment typeEnvironment; |
| |
| /// Creates a [StatefulStaticTypeContext] that supports entering multiple |
| /// libraries and/or members successively. |
| factory StatefulStaticTypeContext.stacked(TypeEnvironment typeEnvironment) = |
| _StackedStatefulStaticTypeContext; |
| |
| /// Creates a [StatefulStaticTypeContext] that only supports entering one |
| /// library and/or member at a time. |
| factory StatefulStaticTypeContext.flat(TypeEnvironment typeEnvironment) = |
| _FlatStatefulStaticTypeContext; |
| |
| StatefulStaticTypeContext._internal(this.typeEnvironment); |
| |
| /// Updates the [nonNullable] and [thisType] to match static type context for |
| /// the member [node]. |
| /// |
| /// This should be called before computing static types on the body of member |
| /// [node]. |
| void enterMember(Member node); |
| |
| /// Reverts the [nonNullable] and [thisType] values to the previous state. |
| /// |
| /// This should be called after computing static types on the body of member |
| /// [node]. |
| void leaveMember(Member node); |
| |
| /// Updates the [nonNullable] and [thisType] to match static type context for |
| /// the library [node]. |
| /// |
| /// This should be called before computing static types on annotations in the |
| /// library [node]. |
| void enterLibrary(Library node); |
| |
| /// Reverts the [nonNullable] and [thisType] values to the previous state. |
| /// |
| /// This should be called after computing static types on annotations in the |
| /// library [node]. |
| void leaveLibrary(Library node); |
| } |
| |
| /// Implementation of [StatefulStaticTypeContext] that only supports entering |
| /// one library and/or at a time. |
| class _FlatStatefulStaticTypeContext extends StatefulStaticTypeContext { |
| Library? _currentLibrary; |
| Member? _currentMember; |
| |
| _FlatStatefulStaticTypeContext(TypeEnvironment typeEnvironment) |
| : super._internal(typeEnvironment); |
| |
| Library get _library { |
| Library? library = _currentLibrary ?? _currentMember?.enclosingLibrary; |
| assert(library != null, |
| "No library currently associated with StaticTypeContext."); |
| return library!; |
| } |
| |
| @override |
| InterfaceType? get thisType { |
| assert(_currentMember != null, |
| "No member currently associated with StaticTypeContext."); |
| return _currentMember?.enclosingClass?.getThisType( |
| typeEnvironment.coreTypes, |
| _currentMember!.enclosingLibrary.nonNullable); |
| } |
| |
| @override |
| Nullability get nonNullable => _library.nonNullable; |
| |
| @override |
| Nullability get nullable => _library.nullable; |
| |
| @override |
| bool get isNonNullableByDefault => _library.isNonNullableByDefault; |
| |
| @override |
| NonNullableByDefaultCompiledMode get nonNullableByDefaultCompiledMode => |
| _library.nonNullableByDefaultCompiledMode; |
| |
| /// Updates the [nonNullable] and [thisType] to match static type context for |
| /// the member [node]. |
| /// |
| /// This should be called before computing static types on the body of member |
| /// [node]. |
| /// |
| /// Only one member can be entered at a time. |
| @override |
| void enterMember(Member node) { |
| assert(_currentMember == null, "Already in context of $_currentMember"); |
| _currentMember = node; |
| } |
| |
| /// Reverts the [nonNullable] and [thisType] values to the previous state. |
| /// |
| /// This should be called after computing static types on the body of member |
| /// [node]. |
| @override |
| void leaveMember(Member node) { |
| assert( |
| _currentMember == node, |
| "Inconsistent static type context stack: " |
| "Trying to leave $node but current is ${_currentMember}."); |
| _currentMember = null; |
| } |
| |
| /// Updates the [nonNullable] and [thisType] to match static type context for |
| /// the library [node]. |
| /// |
| /// This should be called before computing static types on annotations in the |
| /// library [node]. |
| /// |
| /// Only one library can be entered at a time, and not while a member is |
| /// entered through [enterMember]. |
| @override |
| void enterLibrary(Library node) { |
| assert(_currentLibrary == null, "Already in context of $_currentLibrary"); |
| assert(_currentMember == null, "Already in context of $_currentMember"); |
| _currentLibrary = node; |
| } |
| |
| /// Reverts the [nonNullable] and [thisType] values to the previous state. |
| /// |
| /// This should be called after computing static types on annotations in the |
| /// library [node]. |
| @override |
| void leaveLibrary(Library node) { |
| assert( |
| _currentLibrary == node, |
| "Inconsistent static type context stack: " |
| "Trying to leave $node but current is ${_currentLibrary}."); |
| _currentLibrary = null; |
| } |
| |
| @override |
| DartType getExpressionType(Expression node) => |
| node.getStaticTypeInternal(this); |
| |
| @override |
| DartType getForInIteratorType(ForInStatement node) => |
| node.getIteratorTypeInternal(this); |
| |
| @override |
| DartType getForInElementType(ForInStatement node) => |
| node.getElementTypeInternal(this); |
| } |
| |
| /// Implementation of [StatefulStaticTypeContext] that use a stack to change |
| /// state when entering and leaving libraries and members. |
| class _StackedStatefulStaticTypeContext extends StatefulStaticTypeContext { |
| final List<_StaticTypeContextState> _contextStack = |
| <_StaticTypeContextState>[]; |
| |
| _StackedStatefulStaticTypeContext(TypeEnvironment typeEnvironment) |
| : super._internal(typeEnvironment); |
| |
| Library get _library { |
| assert(_contextStack.isNotEmpty, |
| "No library currently associated with StaticTypeContext."); |
| return _contextStack.last._library; |
| } |
| |
| @override |
| InterfaceType? get thisType { |
| assert(_contextStack.isNotEmpty, |
| "No this type currently associated with StaticTypeContext."); |
| return _contextStack.last._thisType; |
| } |
| |
| @override |
| Nullability get nonNullable => _library.nonNullable; |
| |
| @override |
| Nullability get nullable => _library.nullable; |
| |
| @override |
| bool get isNonNullableByDefault => _library.isNonNullableByDefault; |
| |
| @override |
| NonNullableByDefaultCompiledMode get nonNullableByDefaultCompiledMode => |
| _library.nonNullableByDefaultCompiledMode; |
| |
| /// Updates the [library] and [thisType] to match static type context for |
| /// the member [node]. |
| /// |
| /// This should be called before computing static types on the body of member |
| /// [node]. |
| @override |
| void enterMember(Member node) { |
| _contextStack.add(new _StaticTypeContextState( |
| node, |
| node.enclosingLibrary, |
| node.enclosingClass?.getThisType( |
| typeEnvironment.coreTypes, node.enclosingLibrary.nonNullable))); |
| } |
| |
| /// Reverts the [library] and [thisType] values to the previous state. |
| /// |
| /// This should be called after computing static types on the body of member |
| /// [node]. |
| @override |
| void leaveMember(Member node) { |
| _StaticTypeContextState state = _contextStack.removeLast(); |
| assert( |
| state._node == node, |
| "Inconsistent static type context stack: " |
| "Trying to leave $node but current is ${state._node}."); |
| } |
| |
| /// Updates the [library] and [thisType] to match static type context for |
| /// the library [node]. |
| /// |
| /// This should be called before computing static types on annotations in the |
| /// library [node]. |
| @override |
| void enterLibrary(Library node) { |
| _contextStack.add(new _StaticTypeContextState(node, node, null)); |
| } |
| |
| /// Reverts the [library] and [thisType] values to the previous state. |
| /// |
| /// This should be called after computing static types on annotations in the |
| /// library [node]. |
| @override |
| void leaveLibrary(Library node) { |
| _StaticTypeContextState state = _contextStack.removeLast(); |
| assert( |
| state._node == node, |
| "Inconsistent static type context stack: " |
| "Trying to leave $node but current is ${state._node}."); |
| } |
| |
| @override |
| DartType getExpressionType(Expression node) => |
| node.getStaticTypeInternal(this); |
| |
| @override |
| DartType getForInIteratorType(ForInStatement node) => |
| node.getIteratorTypeInternal(this); |
| |
| @override |
| DartType getForInElementType(ForInStatement node) => |
| node.getElementTypeInternal(this); |
| } |
| |
| class _StaticTypeContextState { |
| final TreeNode _node; |
| final Library _library; |
| final InterfaceType? _thisType; |
| |
| _StaticTypeContextState(this._node, this._library, this._thisType); |
| } |