blob: 5320dafa47fe96ec39bb1f6f9e2c634d03979ba4 [file] [log] [blame]
// 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);
}
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);
}