| // Copyright (c) 2019, 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. |
| |
| import 'dart:math' as math; |
| |
| import '../ast.dart'; |
| import '../class_hierarchy.dart'; |
| import '../core_types.dart'; |
| import '../type_algebra.dart'; |
| import '../type_environment.dart'; |
| import 'non_null.dart'; |
| |
| mixin StandardBounds { |
| ClassHierarchyBase get hierarchy; |
| |
| bool isSubtypeOf(DartType subtype, DartType supertype, SubtypeCheckMode mode); |
| |
| bool areMutualSubtypes(DartType s, DartType t, SubtypeCheckMode mode); |
| |
| CoreTypes get coreTypes => hierarchy.coreTypes; |
| |
| /// Checks the value of the MORETOP predicate for [s] and [t]. |
| /// |
| /// For the definition of MORETOP see the following: |
| /// https://github.com/dart-lang/language/blob/master/resources/type-system/upper-lower-bounds.md#helper-predicates |
| bool moretop(DartType s, DartType t) { |
| assert(coreTypes.isTop(s) || coreTypes.isObject(s)); |
| assert(coreTypes.isTop(t) || coreTypes.isObject(t)); |
| |
| switch ((s, t)) { |
| // MORETOP(void, T) = true. |
| case (VoidType(), _): |
| return true; |
| |
| // MORETOP(S, void) = false. |
| case (_, VoidType()): |
| return false; |
| |
| // MORETOP(dynamic, T) = true. |
| case (DynamicType(), _): |
| return true; |
| |
| // MORETOP(S, dynamic) = false. |
| case (_, DynamicType()): |
| return false; |
| |
| // MORETOP(Object, T) = true. |
| case ( |
| InterfaceType( |
| classNode: Class sClassNode, |
| declaredNullability: Nullability.nonNullable |
| ), |
| _ |
| ) |
| when sClassNode == coreTypes.objectClass: |
| return true; |
| |
| // MORETOP(S, Object) = false. |
| case ( |
| _, |
| InterfaceType( |
| classNode: Class tClassNode, |
| declaredNullability: Nullability.nonNullable |
| ) |
| ) |
| when tClassNode == coreTypes.objectClass: |
| return false; |
| |
| // MORETOP(S*, T*) = MORETOP(S, T). |
| case ( |
| DartType(declaredNullability: Nullability.legacy), |
| DartType(declaredNullability: Nullability.legacy) |
| ): |
| DartType nonNullableS = |
| s.withDeclaredNullability(Nullability.nonNullable); |
| assert(!identical(s, nonNullableS)); |
| DartType nonNullableT = |
| t.withDeclaredNullability(Nullability.nonNullable); |
| assert(!identical(t, nonNullableT)); |
| return moretop(nonNullableS, nonNullableT); |
| |
| // MORETOP(S, T*) = true. |
| case ( |
| DartType(declaredNullability: Nullability.nonNullable), |
| DartType(declaredNullability: Nullability.legacy) |
| ): |
| return true; |
| |
| // MORETOP(S*, T) = false. |
| case ( |
| DartType(declaredNullability: Nullability.legacy), |
| DartType(declaredNullability: Nullability.nonNullable) |
| ): |
| return false; |
| |
| // MORETOP(S?, T?) == MORETOP(S, T). |
| case ( |
| DartType(declaredNullability: Nullability.nullable), |
| DartType(declaredNullability: Nullability.nullable) |
| ): |
| DartType nonNullableS = |
| s.withDeclaredNullability(Nullability.nonNullable); |
| assert(!identical(s, nonNullableS)); |
| DartType nonNullableT = |
| t.withDeclaredNullability(Nullability.nonNullable); |
| assert(!identical(t, nonNullableT)); |
| return moretop(nonNullableS, nonNullableT); |
| |
| // MORETOP(S, T?) = true. |
| case ( |
| DartType(declaredNullability: Nullability.nonNullable), |
| DartType(declaredNullability: Nullability.nullable) |
| ): |
| return true; |
| |
| // MORETOP(S?, T) = false. |
| case ( |
| DartType(declaredNullability: Nullability.nullable), |
| DartType(declaredNullability: Nullability.nonNullable) |
| ): |
| return false; |
| |
| // TODO(cstefantsova): Update the following after the spec is updated. |
| case ( |
| DartType(declaredNullability: Nullability.nullable), |
| DartType(declaredNullability: Nullability.legacy) |
| ): |
| return true; |
| |
| // TODO(cstefantsova): Update the following after the spec is updated. |
| case ( |
| DartType(declaredNullability: Nullability.legacy), |
| DartType(declaredNullability: Nullability.nullable) |
| ): |
| return false; |
| |
| // MORETOP(FutureOr<S>, FutureOr<T>) = MORETOP(S, T). |
| case ( |
| FutureOrType( |
| typeArgument: DartType sTypeArgument, |
| declaredNullability: Nullability.nonNullable |
| ), |
| FutureOrType( |
| typeArgument: DartType tTypeArgument, |
| declaredNullability: Nullability.nonNullable |
| ) |
| ): |
| return moretop(sTypeArgument, tTypeArgument); |
| |
| case (InterfaceType(), _): |
| case (_, InterfaceType()): |
| case (ExtensionType(), _): |
| case (_, ExtensionType()): |
| case (FunctionType(), _): |
| case (_, FunctionType()): |
| case (RecordType(), _): |
| case (_, RecordType()): |
| case (NeverType(), _): |
| case (_, NeverType()): |
| case (NullType(), _): |
| case (_, NullType()): |
| case (FutureOrType(), _): |
| case (_, FutureOrType()): |
| case (TypeParameterType(), _): |
| case (_, TypeParameterType()): |
| case (StructuralParameterType(), _): |
| case (_, StructuralParameterType()): |
| case (IntersectionType(), _): |
| case (_, IntersectionType()): |
| case (TypedefType(), _): |
| case (_, TypedefType()): |
| case (InvalidType(), _): |
| case (_, InvalidType()): |
| case (AuxiliaryType(), _): |
| throw new UnsupportedError("moretop($s, $t)"); |
| } |
| } |
| |
| /// Checks the value of the MOREBOTTOM predicate for [s] and [t]. |
| /// |
| /// For the definition of MOREBOTTOM see the following: |
| /// https://github.com/dart-lang/language/blob/master/resources/type-system/upper-lower-bounds.md#helper-predicates |
| bool morebottom(DartType s, DartType t) { |
| assert(coreTypes.isBottom(s) || coreTypes.isNull(s)); |
| assert(coreTypes.isBottom(t) || coreTypes.isNull(t)); |
| |
| switch ((s, t)) { |
| // MOREBOTTOM(Never, T) = true. |
| case (NeverType(declaredNullability: Nullability.nonNullable), _): |
| return true; |
| |
| // MOREBOTTOM(S, Never) = false. |
| case (_, NeverType(declaredNullability: Nullability.nonNullable)): |
| return false; |
| |
| // MOREBOTTOM(Null, T) = true. |
| case (NullType(), _): |
| return true; |
| |
| // MOREBOTTOM(S, Null) = false. |
| case (_, NullType()): |
| return false; |
| |
| // MOREBOTTOM(S?, T?) = MOREBOTTOM(S, T). |
| case ( |
| DartType(declaredNullability: Nullability.nullable), |
| DartType(declaredNullability: Nullability.nullable) |
| ): |
| DartType nonNullableS = |
| s.withDeclaredNullability(Nullability.nonNullable); |
| assert(s != nonNullableS); |
| DartType nonNullableT = |
| t.withDeclaredNullability(Nullability.nonNullable); |
| assert(t != nonNullableT); |
| return morebottom(nonNullableS, nonNullableT); |
| |
| // MOREBOTTOM(S, T?) = true. |
| case ( |
| DartType(declaredNullability: Nullability.nonNullable), |
| DartType(declaredNullability: Nullability.nullable) |
| ): |
| return true; |
| |
| // MOREBOTTOM(S?, T) = false. |
| case ( |
| DartType(declaredNullability: Nullability.nullable), |
| DartType(declaredNullability: Nullability.nonNullable) |
| ): |
| return false; |
| |
| // MOREBOTTOM(S*, T*) = MOREBOTTOM(S, T) |
| case ( |
| DartType(declaredNullability: Nullability.legacy), |
| DartType(declaredNullability: Nullability.legacy) |
| ): |
| DartType nonNullableS = |
| s.withDeclaredNullability(Nullability.nonNullable); |
| assert(s != nonNullableS); |
| DartType nonNullableT = |
| t.withDeclaredNullability(Nullability.nonNullable); |
| assert(t != nonNullableT); |
| return morebottom(nonNullableS, nonNullableT); |
| |
| // MOREBOTTOM(S, T*) = true. |
| case ( |
| DartType(declaredNullability: Nullability.nonNullable), |
| DartType(declaredNullability: Nullability.legacy) |
| ): |
| return true; |
| |
| // MOREBOTTOM(S*, T) = false. |
| case ( |
| DartType(declaredNullability: Nullability.legacy), |
| DartType(declaredNullability: Nullability.nonNullable) |
| ): |
| return false; |
| |
| // TODO(cstefantsova): Update the following after the spec is updated. |
| case ( |
| DartType(declaredNullability: Nullability.nullable), |
| DartType(declaredNullability: Nullability.legacy) |
| ): |
| return true; |
| |
| // TODO(cstefantsova): Update the following after the spec is updated. |
| case ( |
| DartType(declaredNullability: Nullability.legacy), |
| DartType(declaredNullability: Nullability.nullable) |
| ): |
| return false; |
| |
| // MOREBOTTOM(X&S, Y&T) = MOREBOTTOM(S, T). |
| case ( |
| IntersectionType(right: DartType sRight), |
| IntersectionType(right: DartType tRight) |
| ): |
| return morebottom(sRight, tRight); |
| |
| // MOREBOTTOM(X&S, T) = true. |
| case (IntersectionType(), _): |
| return true; |
| |
| // MOREBOTTOM(S, X&T) = false. |
| case (_, IntersectionType()): |
| return false; |
| |
| // MOREBOTTOM(X extends S, Y extends T) = MOREBOTTOM(S, T). |
| case ( |
| TypeParameterType(parameter: TypeParameter sParameter), |
| TypeParameterType(parameter: TypeParameter tParameter) |
| ): |
| return morebottom(sParameter.bound, tParameter.bound); |
| |
| case (DynamicType(), _): |
| case (_, DynamicType()): |
| case (VoidType(), _): |
| case (_, VoidType()): |
| case (NeverType(), _): |
| case (_, NeverType()): |
| case (FunctionType(), _): |
| case (_, FunctionType()): |
| case (TypedefType(), _): |
| case (_, TypedefType()): |
| case (FutureOrType(), _): |
| case (_, FutureOrType()): |
| case (TypeParameterType(), _): |
| case (_, TypeParameterType()): |
| case (StructuralParameterType(), _): |
| case (_, StructuralParameterType()): |
| case (RecordType(), _): |
| case (_, RecordType()): |
| case (InterfaceType(), _): |
| case (_, InterfaceType()): |
| case (ExtensionType(), _): |
| case (_, ExtensionType()): |
| case (InvalidType(), _): |
| case (_, InvalidType()): |
| case (AuxiliaryType(), _): |
| throw new UnsupportedError("morebottom($s, $t)"); |
| } |
| } |
| |
| /// Computes the standard lower bound of [type1] and [type2]. |
| /// |
| /// Standard lower bound is a lower bound function that imposes an |
| /// ordering on the top types `void`, `dynamic`, and `object`. This function |
| /// additionally handles the unknown type that appears during type inference. |
| DartType getStandardLowerBound(DartType type1, DartType type2) { |
| if (type1 is InvalidType || type2 is InvalidType) { |
| return const InvalidType(); |
| } |
| return _getNullabilityAwareStandardLowerBound(type1, type2); |
| } |
| |
| DartType _getNullabilityAwareStandardLowerBound( |
| DartType type1, DartType type2) { |
| // DOWN(T, T) = T. |
| if (type1 == type2) return type1; |
| |
| return getNullabilityAwareStandardLowerBoundInternal(type1, type2); |
| } |
| |
| DartType getNullabilityAwareStandardLowerBoundInternal( |
| DartType type1, DartType type2) { |
| DartType type1WithoutNullabilityMarker = |
| computeTypeWithoutNullabilityMarker(type1); |
| DartType type2WithoutNullabilityMarker = |
| computeTypeWithoutNullabilityMarker(type2); |
| bool isType1WithoutNullabilityMarker = |
| isTypeWithoutNullabilityMarker(type1); |
| bool isType2WithoutNullabilityMarker = |
| isTypeWithoutNullabilityMarker(type2); |
| |
| switch ((type1, type2)) { |
| case (InvalidType(), _): |
| case (_, InvalidType()): |
| return const InvalidType(); |
| |
| case (TypedefType typedefType1, _): |
| return getNullabilityAwareStandardLowerBoundInternal( |
| typedefType1.unalias, type2); |
| case (_, TypedefType typedefType2): |
| return getNullabilityAwareStandardLowerBoundInternal( |
| type1, typedefType2.unalias); |
| |
| // DOWN(T1, T2) where TOP(T1) and TOP(T2) = |
| // T1 if MORETOP(T2, T1) |
| // T2 otherwise |
| // DOWN(T1, T2) = T2 if TOP(T1) |
| // DOWN(T1, T2) = T1 if TOP(T2) |
| case (DynamicType(), DynamicType()): |
| case (DynamicType(), VoidType()): |
| case (VoidType(), DynamicType()): |
| case (VoidType(), VoidType()): |
| case (_, _) when coreTypes.isTop(type1) && coreTypes.isTop(type2): |
| return moretop(type2, type1) ? type1 : type2; |
| case (DynamicType(), _): |
| case (VoidType(), _): |
| case (_, _) when coreTypes.isTop(type1): |
| return type2; |
| case (_, DynamicType()): |
| case (_, VoidType()): |
| case (_, _) when coreTypes.isTop(type2): |
| return type1; |
| |
| // DOWN(T1, T2) where BOTTOM(T1) and BOTTOM(T2) = |
| // T1 if MOREBOTTOM(T1, T2) |
| // T2 otherwise |
| // DOWN(T1, T2) = T2 if BOTTOM(T2) |
| // DOWN(T1, T2) = T1 if BOTTOM(T1) |
| case ( |
| NeverType(nullability: Nullability.nonNullable), |
| NeverType(nullability: Nullability.nonNullable) |
| ): |
| case (_, _) when coreTypes.isBottom(type1) && coreTypes.isBottom(type2): |
| return morebottom(type1, type2) ? type1 : type2; |
| case (NeverType(nullability: Nullability.nonNullable), _): |
| case (_, _) when coreTypes.isBottom(type1): |
| return type1; |
| case (_, NeverType(nullability: Nullability.nonNullable)): |
| case (_, _) when coreTypes.isBottom(type2): |
| return type2; |
| |
| // DOWN(T1, T2) where NULL(T1) and NULL(T2) = |
| // T1 if MOREBOTTOM(T1, T2) |
| // T2 otherwise |
| // DOWN(Null, T2) = |
| // Null if Null <: T2 |
| // Never otherwise |
| // DOWN(T1, Null) = |
| // Null if Null <: T1 |
| // Never otherwise |
| case (NullType(), NullType()): |
| case ( |
| NeverType(nullability: Nullability.nullable), |
| NeverType(nullability: Nullability.nullable) |
| ): |
| case ( |
| NeverType(nullability: Nullability.nullable), |
| NeverType(nullability: Nullability.legacy) |
| ): |
| case ( |
| NeverType(nullability: Nullability.legacy), |
| NeverType(nullability: Nullability.nullable) |
| ): |
| case ( |
| NeverType(nullability: Nullability.legacy), |
| NeverType(nullability: Nullability.legacy) |
| ): |
| case (_, _) when coreTypes.isNull(type1) && coreTypes.isNull(type2): |
| return morebottom(type1, type2) ? type1 : type2; |
| case (NullType(), DartType(declaredNullability: Nullability.nullable)): |
| case (NullType(), DartType(declaredNullability: Nullability.legacy)): |
| case ( |
| NeverType(nullability: Nullability.nullable), |
| DartType(declaredNullability: Nullability.nullable) |
| ): |
| case ( |
| NeverType(nullability: Nullability.nullable), |
| DartType(declaredNullability: Nullability.legacy) |
| ): |
| case ( |
| NeverType(nullability: Nullability.legacy), |
| DartType(declaredNullability: Nullability.nullable) |
| ): |
| case ( |
| NeverType(nullability: Nullability.legacy), |
| DartType(declaredNullability: Nullability.legacy) |
| ): |
| case (_, DartType(declaredNullability: Nullability.nullable)) |
| when coreTypes.isNull(type1): |
| case (_, DartType(declaredNullability: Nullability.legacy)) |
| when coreTypes.isNull(type1): |
| return type1; |
| case (NullType(), _): |
| case (NeverType(nullability: Nullability.nullable), _): |
| case (NeverType(nullability: Nullability.legacy), _): |
| case (_, _) when coreTypes.isNull(type1): |
| return const NeverType.nonNullable(); |
| case (DartType(declaredNullability: Nullability.nullable), NullType()): |
| case (DartType(declaredNullability: Nullability.legacy), NullType()): |
| case ( |
| DartType(declaredNullability: Nullability.nullable), |
| NeverType(nullability: Nullability.nullable) |
| ): |
| case ( |
| DartType(declaredNullability: Nullability.legacy), |
| NeverType(nullability: Nullability.nullable) |
| ): |
| case ( |
| DartType(declaredNullability: Nullability.nullable), |
| NeverType(nullability: Nullability.legacy) |
| ): |
| case ( |
| DartType(declaredNullability: Nullability.legacy), |
| NeverType(nullability: Nullability.legacy) |
| ): |
| case (DartType(declaredNullability: Nullability.nullable), _) |
| when coreTypes.isNull(type2): |
| case (DartType(declaredNullability: Nullability.legacy), _) |
| when coreTypes.isNull(type2): |
| return type2; |
| case (_, NullType()): |
| case (_, NeverType(nullability: Nullability.nullable)): |
| case (_, NeverType(nullability: Nullability.legacy)): |
| case (_, _) when coreTypes.isNull(type2): |
| return const NeverType.nonNullable(); |
| |
| // DOWN(T1, T2) where OBJECT(T1) and OBJECT(T2) = |
| // T1 if MORETOP(T2, T1) |
| // T2 otherwise |
| // DOWN(T1, T2) where OBJECT(T1) = |
| // T2 if T2 is non-nullable |
| // NonNull(T2) if NonNull(T2) is non-nullable |
| // Never otherwise |
| // DOWN(T1, T2) where OBJECT(T2) = |
| // T1 if T1 is non-nullable |
| // NonNull(T1) if NonNull(T1) is non-nullable |
| // Never otherwise |
| case (_, _) when coreTypes.isObject(type1) && coreTypes.isObject(type2): |
| return moretop(type2, type1) ? type1 : type2; |
| case (_, DartType(nullability: Nullability.nonNullable)) |
| when coreTypes.isObject(type1): |
| return type2; |
| case (_, _) when coreTypes.isObject(type1): |
| if (computeNonNull(type2) case DartType nonNullType2 |
| when nonNullType2.nullability == Nullability.nonNullable) { |
| return nonNullType2; |
| } else { |
| return const NeverType.nonNullable(); |
| } |
| case (DartType(nullability: Nullability.nonNullable), _) |
| when coreTypes.isObject(type2): |
| return type1; |
| case (_, _) when coreTypes.isObject(type2): |
| if (computeNonNull(type1) case DartType nonNullType1 |
| when nonNullType1.nullability == Nullability.nonNullable) { |
| return nonNullType1; |
| } else { |
| return const NeverType.nonNullable(); |
| } |
| |
| // DOWN(T1*, T2*) = S* where S is DOWN(T1, T2) |
| // DOWN(T1*, T2?) = S* where S is DOWN(T1, T2) |
| // DOWN(T1?, T2*) = S* where S is DOWN(T1, T2) |
| // DOWN(T1*, T2) = S where S is DOWN(T1, T2) |
| // DOWN(T1, T2*) = S where S is DOWN(T1, T2) |
| // DOWN(T1?, T2?) = S? where S is DOWN(T1, T2) |
| // DOWN(T1?, T2) = S where S is DOWN(T1, T2) |
| // DOWN(T1, T2?) = S where S is DOWN(T1, T2) |
| case (_, _) |
| when isType1WithoutNullabilityMarker && |
| !isType2WithoutNullabilityMarker: |
| return _getNullabilityAwareStandardLowerBound( |
| type1, type2WithoutNullabilityMarker); |
| case (_, _) |
| when !isType1WithoutNullabilityMarker && |
| isType2WithoutNullabilityMarker: |
| return _getNullabilityAwareStandardLowerBound( |
| type1WithoutNullabilityMarker, type2); |
| case (_, _) |
| when isLegacyTypeConstructorApplication(type1) || |
| isLegacyTypeConstructorApplication(type2): |
| return _getNullabilityAwareStandardLowerBound( |
| type1WithoutNullabilityMarker, type2WithoutNullabilityMarker) |
| .withDeclaredNullability(Nullability.legacy); |
| case (_, _) |
| when isNullableTypeConstructorApplication(type1) && |
| isNullableTypeConstructorApplication(type2): |
| return _getNullabilityAwareStandardLowerBound( |
| type1WithoutNullabilityMarker, type2WithoutNullabilityMarker) |
| .withDeclaredNullability(Nullability.nullable); |
| |
| case (FunctionType functionType1, FunctionType functionType2): |
| return _getNullabilityAwareFunctionStandardLowerBound( |
| functionType1, functionType2); |
| |
| case (RecordType recordType1, RecordType recordType2): |
| return _getNullabilityAwareRecordStandardLowerBound( |
| recordType1, recordType2); |
| |
| // DOWN(T1, T2) = T1 if T1 <: T2. |
| // DOWN(T1, T2) = T2 if T2 <: T1. |
| // |
| // We use the non-nullable variants of the two types to determine T1 <: |
| // T2 without using the nullability of the outermost type. The result |
| // uses [intersectNullabilities] to compute the resulting type if the |
| // subtype relation is established. |
| case (_, _) |
| when isSubtypeOf( |
| greatestClosureForLowerBound(type1WithoutNullabilityMarker), |
| greatestClosureForLowerBound(type2WithoutNullabilityMarker), |
| SubtypeCheckMode.withNullabilities): |
| return type1.withDeclaredNullability(intersectNullabilities( |
| type1.declaredNullability, type2.declaredNullability)); |
| case (_, _) |
| when isSubtypeOf( |
| greatestClosureForLowerBound(type2WithoutNullabilityMarker), |
| greatestClosureForLowerBound(type1WithoutNullabilityMarker), |
| SubtypeCheckMode.withNullabilities): |
| return type2.withDeclaredNullability(intersectNullabilities( |
| type2.declaredNullability, type1.declaredNullability)); |
| |
| // See |
| // https://github.com/dart-lang/sdk/issues/37439#issuecomment-519654959. |
| // |
| // GLB(FutureOr<A>, FutureOr<B>) == FutureOr<GLB(A, B)> |
| case ( |
| FutureOrType(typeArgument: DartType t1), |
| FutureOrType(typeArgument: DartType t2) |
| ): |
| DartType argument = getStandardLowerBound(t1, t2); |
| return new FutureOrType(argument, argument.declaredNullability); |
| // GLB(FutureOr<A>, Future<B>) == Future<GLB(A, B)> |
| case ( |
| FutureOrType(typeArgument: DartType t1), |
| InterfaceType( |
| classNode: Class classNode2, |
| typeArguments: [DartType t2] |
| ) |
| ) |
| when classNode2 == coreTypes.futureClass: |
| return new InterfaceType( |
| coreTypes.futureClass, |
| intersectNullabilities( |
| type1.declaredNullability, type2.declaredNullability), |
| <DartType>[getStandardLowerBound(t1, t2)]); |
| // GLB(FutureOr<A>, B) == GLB(A, B) |
| case (FutureOrType(typeArgument: DartType t1), DartType t2): |
| return getStandardLowerBound(t1, t2); |
| |
| // The if-statement below handles the following rule: |
| // GLB(A, FutureOr<B>) == GLB(FutureOr<B>, A) |
| // It's broken down into sub-cases instead of making a recursive call |
| // to avoid making the checks that were already made above. Note that |
| // at this point it's not possible for type1 to be a FutureOr. |
| // |
| // GLB(Future<A>, FutureOr<B>) == Future<GLB(B, A)> |
| case ( |
| InterfaceType( |
| classNode: Class classNode1, |
| typeArguments: [DartType t1] |
| ), |
| FutureOrType(typeArgument: DartType t2) |
| ) |
| when classNode1 == coreTypes.futureClass: |
| return new InterfaceType( |
| coreTypes.futureClass, |
| intersectNullabilities( |
| type1.declaredNullability, type2.declaredNullability), |
| <DartType>[getStandardLowerBound(t2, t1)]); |
| // GLB(A, FutureOr<B>) == GLB(B, A) |
| case (DartType t1, FutureOrType(typeArgument: DartType t2)): |
| return getStandardLowerBound(t2, t1); |
| |
| // DOWN(T1, T2) = Never otherwise. |
| case (InterfaceType(), _): |
| case (_, InterfaceType()): |
| case (RecordType(), _): |
| case (_, RecordType()): |
| case (ExtensionType(), _): |
| case (_, ExtensionType()): |
| case (TypeParameterType(), _): |
| case (_, TypeParameterType()): |
| case (StructuralParameterType(), _): |
| case (_, StructuralParameterType()): |
| case (IntersectionType(), _): |
| case (_, IntersectionType()): |
| return NeverType.fromNullability(combineNullabilitiesForSubstitution( |
| inner: Nullability.nonNullable, |
| outer: intersectNullabilities( |
| type1.declaredNullability, type2.declaredNullability))); |
| |
| case (NeverType(nullability: Nullability.undetermined), _): |
| case (_, NeverType(nullability: Nullability.undetermined)): |
| throw new StateError("Unsupported nullability for NeverType: " |
| "'${Nullability.undetermined}'."); |
| |
| case (AuxiliaryType(), _): |
| case (_, AuxiliaryType()): |
| throw new StateError("Unsupported type combination: " |
| "getNullabilityAwareStandardUpperBoundInternal(" |
| "${type1.runtimeType}, ${type2.runtimeType}" |
| ")"); |
| } |
| } |
| |
| DartType getNullabilityObliviousStandardLowerBoundInternal( |
| DartType type1, DartType type2) { |
| // SLB(void, T) = SLB(T, void) = T. |
| if (type1 is VoidType) { |
| return type2; |
| } |
| if (type2 is VoidType) { |
| return type1; |
| } |
| |
| // SLB(dynamic, T) = SLB(T, dynamic) = T if T is not void. |
| if (type1 is DynamicType) { |
| return type2; |
| } |
| if (type2 is DynamicType) { |
| return type1; |
| } |
| |
| // SLB(Object, T) = SLB(T, Object) = T if T is not void or dynamic. |
| if (type1 == coreTypes.objectLegacyRawType) { |
| return type2; |
| } |
| if (type2 == coreTypes.objectLegacyRawType) { |
| return type1; |
| } |
| |
| // SLB(bottom, T) = SLB(T, bottom) = bottom. |
| if (type1 is NullType) return type1; |
| if (type2 is NullType) return type2; |
| |
| // Function types have structural lower bounds. |
| if (type1 is FunctionType && type2 is FunctionType) { |
| return _getNullabilityObliviousFunctionStandardLowerBound(type1, type2); |
| } |
| |
| // Otherwise, the lower bounds of two types is one of them it if it is a |
| // subtype of the other. |
| if (isSubtypeOf(type1, type2, SubtypeCheckMode.ignoringNullabilities)) { |
| return type1; |
| } |
| |
| if (isSubtypeOf(type2, type1, SubtypeCheckMode.ignoringNullabilities)) { |
| return type2; |
| } |
| |
| // See |
| // https://github.com/dart-lang/sdk/issues/37439#issuecomment-519654959. |
| if (type1 is FutureOrType) { |
| if (type2 is FutureOrType) { |
| // GLB(FutureOr<A>, FutureOr<B>) == FutureOr<GLB(A, B)> |
| DartType argument = |
| getStandardLowerBound(type1.typeArgument, type2.typeArgument); |
| return new FutureOrType(argument, argument.declaredNullability); |
| } |
| if (type2 is InterfaceType && type2.classNode == coreTypes.futureClass) { |
| // GLB(FutureOr<A>, Future<B>) == Future<GLB(A, B)> |
| return new InterfaceType( |
| coreTypes.futureClass, |
| intersectNullabilities( |
| type1.declaredNullability, type2.declaredNullability), |
| <DartType>[ |
| getStandardLowerBound(type1.typeArgument, type2.typeArguments[0]) |
| ]); |
| } |
| // GLB(FutureOr<A>, B) == GLB(A, B) |
| return getStandardLowerBound(type1.typeArgument, type2); |
| } |
| // The if-statement below handles the following rule: |
| // GLB(A, FutureOr<B>) == GLB(FutureOr<B>, A) |
| // It's broken down into sub-cases instead of making a recursive call to |
| // avoid making the checks that were already made above. Note that at this |
| // point it's not possible for type1 to be a FutureOr. |
| if (type2 is FutureOrType) { |
| if (type1 is FutureOrType) { |
| // GLB(Future<A>, FutureOr<B>) == Future<GLB(B, A)> |
| return new InterfaceType( |
| coreTypes.futureClass, |
| intersectNullabilities( |
| type1.declaredNullability, type2.declaredNullability), |
| <DartType>[ |
| getStandardLowerBound(type2.typeArgument, type1.typeArgument) |
| ]); |
| } |
| // GLB(A, FutureOr<B>) == GLB(B, A) |
| return getStandardLowerBound(type2.typeArgument, type1); |
| } |
| |
| // No subtype relation, so the lower bound is bottom. |
| return const NeverType.nonNullable(); |
| } |
| |
| /// Computes the standard upper bound of two types. |
| /// |
| /// Standard upper bound is an upper bound function that imposes an ordering |
| /// on the top types 'void', 'dynamic', and `object`. This function |
| /// additionally handles the unknown type that appears during type inference. |
| DartType getStandardUpperBound(DartType type1, DartType type2) { |
| if (type1 is InvalidType || type2 is InvalidType) { |
| return const InvalidType(); |
| } |
| return _getNullabilityAwareStandardUpperBound(type1, type2); |
| } |
| |
| DartType _getNullabilityAwareStandardUpperBound( |
| DartType type1, DartType type2) { |
| // UP(T, T) = T |
| if (type1 == type2) return type1; |
| |
| return getNullabilityAwareStandardUpperBoundInternal(type1, type2); |
| } |
| |
| DartType getNullabilityAwareStandardUpperBoundInternal( |
| DartType type1, DartType type2) { |
| DartType typeWithoutNullabilityMarker1 = |
| computeTypeWithoutNullabilityMarker(type1); |
| DartType typeWithoutNullabilityMarker2 = |
| computeTypeWithoutNullabilityMarker(type2); |
| |
| switch ((type1, type2)) { |
| case (InvalidType(), _): |
| case (_, InvalidType()): |
| return const InvalidType(); |
| |
| case (TypedefType typedefType1, _): |
| return getNullabilityAwareStandardUpperBoundInternal( |
| typedefType1.unalias, type2); |
| case (_, TypedefType typedefType2): |
| return getNullabilityAwareStandardUpperBoundInternal( |
| type1, typedefType2.unalias); |
| |
| // UP(T1, T2) where TOP(T1) and TOP(T2) = |
| // T1 if MORETOP(T1, T2) |
| // T2 otherwise |
| // UP(T1, T2) = T1 if TOP(T1) |
| // UP(T1, T2) = T2 if TOP(T2) |
| case (DynamicType(), DynamicType()): |
| case (DynamicType(), VoidType()): |
| case (VoidType(), DynamicType()): |
| case (VoidType(), VoidType()): |
| case (_, _) when coreTypes.isTop(type1) && coreTypes.isTop(type2): |
| return moretop(type1, type2) ? type1 : type2; |
| case (DynamicType(), _): |
| case (VoidType(), _): |
| case (_, _) when coreTypes.isTop(type1): |
| return type1; |
| case (_, DynamicType()): |
| case (_, VoidType()): |
| case (_, _) when coreTypes.isTop(type2): |
| return type2; |
| |
| // UP(T1, T2) where BOTTOM(T1) and BOTTOM(T2) = |
| // T2 if MOREBOTTOM(T1, T2) |
| // T1 otherwise |
| // UP(T1, T2) = T2 if BOTTOM(T1) |
| // UP(T1, T2) = T1 if BOTTOM(T2) |
| case ( |
| NeverType(nullability: Nullability.nonNullable), |
| NeverType(nullability: Nullability.nonNullable) |
| ): |
| case (_, _) when coreTypes.isBottom(type1) && coreTypes.isBottom(type2): |
| return morebottom(type1, type2) ? type2 : type1; |
| case (NeverType(nullability: Nullability.nonNullable), _): |
| case (_, _) when coreTypes.isBottom(type1): |
| return type2; |
| case (_, NeverType(nullability: Nullability.nonNullable)): |
| case (_, _) when coreTypes.isBottom(type2): |
| return type1; |
| |
| case (IntersectionType intersectionType1, _): |
| return _getNullabilityAwareIntersectionStandardUpperBound( |
| intersectionType1, type2); |
| case (_, IntersectionType intersectionType2): |
| return _getNullabilityAwareIntersectionStandardUpperBound( |
| intersectionType2, type1); |
| |
| // UP(T1, T2) where NULL(T1) and NULL(T2) = |
| // T2 if MOREBOTTOM(T1, T2) |
| // T1 otherwise |
| // UP(T1, T2) where NULL(T1) = |
| // T2 if T2 is nullable |
| // T2? otherwise |
| // UP(T1, T2) where NULL(T2) = |
| // T1 if T1 is nullable |
| // T1? otherwise |
| case (NullType(), NullType()): |
| case ( |
| NeverType(nullability: Nullability.nullable), |
| NeverType(nullability: Nullability.nullable) |
| ): |
| case ( |
| NeverType(nullability: Nullability.nullable), |
| NeverType(nullability: Nullability.legacy) |
| ): |
| case ( |
| NeverType(nullability: Nullability.legacy), |
| NeverType(nullability: Nullability.nullable) |
| ): |
| case ( |
| NeverType(nullability: Nullability.legacy), |
| NeverType(nullability: Nullability.legacy) |
| ): |
| case (_, _) when coreTypes.isNull(type1) && coreTypes.isNull(type2): |
| return morebottom(type1, type2) ? type2 : type1; |
| case (NullType(), _): |
| case (NeverType(nullability: Nullability.nullable), _): |
| case (NeverType(nullability: Nullability.legacy), _): |
| case (_, _) when coreTypes.isNull(type1): |
| return type2.withDeclaredNullability(Nullability.nullable); |
| case (_, NullType()): |
| case (_, NeverType(nullability: Nullability.nullable)): |
| case (_, NeverType(nullability: Nullability.legacy)): |
| case (_, _) when coreTypes.isNull(type2): |
| return type1.withDeclaredNullability(Nullability.nullable); |
| |
| // UP(T1, T2) where OBJECT(T1) and OBJECT(T2) = |
| // T1 if MORETOP(T1, T2) |
| // T2 otherwise |
| // UP(T1, T2) where OBJECT(T1) = |
| // T1 if T2 is non-nullable |
| // T1? otherwise |
| // UP(T1, T2) where OBJECT(T2) = |
| // T2 if T1 is non-nullable |
| // T2? otherwise |
| case (_, _) when coreTypes.isObject(type1) && coreTypes.isObject(type2): |
| return moretop(type1, type2) ? type1 : type2; |
| case (_, _) |
| when coreTypes.isObject(type1) && |
| type2.nullability == Nullability.nonNullable: |
| return type1; |
| case (_, _) when coreTypes.isObject(type1): |
| return type1.withDeclaredNullability(Nullability.nullable); |
| case (_, _) |
| when coreTypes.isObject(type2) && |
| type1.nullability == Nullability.nonNullable: |
| return type2; |
| case (_, _) when coreTypes.isObject(type2): |
| return type2.withDeclaredNullability(Nullability.nullable); |
| |
| // UP(T1*, T2*) = S* where S is UP(T1, T2) |
| // UP(T1*, T2?) = S? where S is UP(T1, T2) |
| // UP(T1?, T2*) = S? where S is UP(T1, T2) |
| // UP(T1*, T2) = S* where S is UP(T1, T2) |
| // UP(T1, T2*) = S* where S is UP(T1, T2) |
| // UP(T1?, T2?) = S? where S is UP(T1, T2) |
| // UP(T1?, T2) = S? where S is UP(T1, T2) |
| // UP(T1, T2?) = S? where S is UP(T1, T2) |
| case (_, _) when isNullableTypeConstructorApplication(type1): |
| case (_, _) when isNullableTypeConstructorApplication(type2): |
| return _getNullabilityAwareStandardUpperBound( |
| computeTypeWithoutNullabilityMarker(type1), |
| computeTypeWithoutNullabilityMarker(type2)) |
| .withDeclaredNullability(Nullability.nullable); |
| case (_, _) when isLegacyTypeConstructorApplication(type1): |
| case (_, _) when isLegacyTypeConstructorApplication(type2): |
| return _getNullabilityAwareStandardUpperBound( |
| computeTypeWithoutNullabilityMarker(type1), |
| computeTypeWithoutNullabilityMarker(type2)) |
| .withDeclaredNullability(Nullability.legacy); |
| |
| case (TypeParameterType typeParameterType1, _): |
| return _getNullabilityAwareTypeVariableStandardUpperBound(type1, type2, |
| bound1: typeParameterType1.bound, |
| nominalEliminationTarget: typeParameterType1.parameter); |
| case (StructuralParameterType structuralParameterType1, _): |
| return _getNullabilityAwareTypeVariableStandardUpperBound(type1, type2, |
| bound1: structuralParameterType1.bound, |
| structuralEliminationTarget: structuralParameterType1.parameter); |
| case (_, TypeParameterType typeParameterType2): |
| return _getNullabilityAwareTypeVariableStandardUpperBound(type2, type1, |
| bound1: typeParameterType2.bound, |
| nominalEliminationTarget: typeParameterType2.parameter); |
| case (_, StructuralParameterType structuralParameterType2): |
| return _getNullabilityAwareTypeVariableStandardUpperBound(type2, type1, |
| bound1: structuralParameterType2.bound, |
| structuralEliminationTarget: structuralParameterType2.parameter); |
| |
| case (FunctionType functionType1, FunctionType functionType2): |
| return _getNullabilityAwareFunctionStandardUpperBound( |
| functionType1, functionType2); |
| case (FunctionType(), InterfaceType interfaceType2) |
| when interfaceType2.classNode == coreTypes.functionClass: |
| // UP(T Function<...>(...), Function) = Function |
| return coreTypes.functionRawType(uniteNullabilities( |
| type1.declaredNullability, type2.declaredNullability)); |
| case (FunctionType(), _): |
| // UP(T Function<...>(...), T2) = UP(Object, T2) |
| return _getNullabilityAwareStandardUpperBound( |
| coreTypes.objectNonNullableRawType, type2); |
| case (InterfaceType interfaceType1, FunctionType()) |
| when interfaceType1.classNode == coreTypes.functionClass: |
| // UP(Function, T Function<...>(...)) = Function |
| return coreTypes.functionRawType(uniteNullabilities( |
| type1.declaredNullability, type2.declaredNullability)); |
| case (_, FunctionType()): |
| // UP(T1, T Function<...>(...)) = UP(T1, Object) |
| return _getNullabilityAwareStandardUpperBound( |
| type1, coreTypes.objectNonNullableRawType); |
| |
| case (RecordType recordType1, RecordType recordType2): |
| return _getNullabilityAwareRecordStandardUpperBound( |
| recordType1, recordType2); |
| case (RecordType(), InterfaceType interfaceType2) |
| when interfaceType2.classNode == coreTypes.recordClass: |
| // UP(Record(...), Record) = Record |
| return coreTypes.recordRawType(uniteNullabilities( |
| type1.declaredNullability, type2.declaredNullability)); |
| case (RecordType(), _): |
| // UP(Record(...), T2) = UP(Object, T2) |
| return _getNullabilityAwareStandardUpperBound( |
| coreTypes.objectNonNullableRawType, type2); |
| case (InterfaceType interfaceType1, RecordType()) |
| when interfaceType1.classNode == coreTypes.recordClass: |
| // UP(Record, Record(...)) = Record |
| return coreTypes.recordRawType(uniteNullabilities( |
| type1.declaredNullability, type2.declaredNullability)); |
| case (_, RecordType()): |
| // UP(T1, Record(...)) = UP(T1, Object) |
| return _getNullabilityAwareStandardUpperBound( |
| type1, coreTypes.objectNonNullableRawType); |
| |
| // UP(FutureOr<T1>, FutureOr<T2>) = FutureOr<T3> where T3 = UP(T1, T2) |
| // UP(Future<T1>, FutureOr<T2>) = FutureOr<T3> where T3 = UP(T1, T2) |
| // UP(FutureOr<T1>, Future<T2>) = FutureOr<T3> where T3 = UP(T1, T2) |
| // UP(T1, FutureOr<T2>) = FutureOr<T3> where T3 = UP(T1, T2) |
| // UP(FutureOr<T1>, T2) = FutureOr<T3> where T3 = UP(T1, T2) |
| case ( |
| FutureOrType(typeArgument: DartType t1), |
| FutureOrType(typeArgument: DartType t2) |
| ): |
| case ( |
| FutureOrType(typeArgument: DartType t1), |
| InterfaceType( |
| typeArguments: [DartType t2], |
| classNode: Class classNode2 |
| ) |
| ) |
| when classNode2 == coreTypes.futureClass: |
| case (FutureOrType(typeArgument: DartType t1), DartType t2): |
| case ( |
| InterfaceType( |
| typeArguments: [DartType t1], |
| classNode: Class classNode1 |
| ), |
| FutureOrType(typeArgument: DartType t2) |
| ) |
| when classNode1 == coreTypes.futureClass: |
| case (DartType t1, FutureOrType(typeArgument: DartType t2)): |
| return new FutureOrType( |
| getStandardUpperBound(t1, t2), |
| uniteNullabilities( |
| type1.declaredNullability, type2.declaredNullability)); |
| |
| // We use the non-nullable variants of the two interfaces types to |
| // determine T1 <: T2 without using the nullability of the outermost |
| // type. The result uses [uniteNullabilities] to compute the resulting |
| // type if the subtype relation is established. |
| case (_, _) |
| when isSubtypeOf( |
| leastClosureForUpperBound(typeWithoutNullabilityMarker1), |
| leastClosureForUpperBound(typeWithoutNullabilityMarker2), |
| SubtypeCheckMode.withNullabilities): |
| // UP(T1, T2) = T2 if T1 <: T2 |
| // Note that both types must be interface or extension types at this |
| // point. |
| return type2.withDeclaredNullability( |
| uniteNullabilities(type1.nullability, type2.nullability)); |
| case (_, _) |
| when isSubtypeOf( |
| leastClosureForUpperBound(typeWithoutNullabilityMarker2), |
| leastClosureForUpperBound(typeWithoutNullabilityMarker1), |
| SubtypeCheckMode.withNullabilities): |
| // UP(T1, T2) = T1 if T2 <: T1 |
| // Note that both types must be interface or extension types at this |
| // point. |
| return type1.withDeclaredNullability(uniteNullabilities( |
| type1.declaredNullability, type2.declaredNullability)); |
| |
| case ( |
| TypeDeclarationType typeDeclarationType1, |
| TypeDeclarationType typeDeclarationType2 |
| ): |
| if (typeDeclarationType1.typeDeclarationReference == |
| typeDeclarationType2.typeDeclarationReference) { |
| // UP(C<T0, ..., Tn>, C<S0, ..., Sn>) = C<R0,..., Rn> where Ri is |
| // UP(Ti, Si) |
| |
| TypeDeclaration typeDeclaration = |
| typeDeclarationType1.typeDeclaration; |
| List<TypeParameter> typeParameters = typeDeclaration.typeParameters; |
| List<DartType> leftArguments = typeDeclarationType1.typeArguments; |
| List<DartType> rightArguments = typeDeclarationType2.typeArguments; |
| int n = typeParameters.length; |
| List<DartType> typeArguments = new List<DartType>.of(leftArguments); |
| for (int i = 0; i < n; ++i) { |
| Variance variance = typeParameters[i].variance; |
| if (variance == Variance.contravariant) { |
| typeArguments[i] = _getNullabilityAwareStandardLowerBound( |
| leftArguments[i], rightArguments[i]); |
| } else if (variance == Variance.invariant) { |
| if (!areMutualSubtypes(leftArguments[i], rightArguments[i], |
| SubtypeCheckMode.withNullabilities)) { |
| return _getLegacyLeastUpperBound( |
| typeDeclarationType1, typeDeclarationType2); |
| } |
| } else { |
| typeArguments[i] = _getNullabilityAwareStandardUpperBound( |
| leftArguments[i], rightArguments[i]); |
| } |
| } |
| switch (typeDeclaration) { |
| case Class(): |
| return new InterfaceType( |
| typeDeclaration, |
| uniteNullabilities( |
| type1.declaredNullability, type2.declaredNullability), |
| typeArguments); |
| case ExtensionTypeDeclaration(): |
| return new ExtensionType( |
| typeDeclaration, |
| uniteNullabilities( |
| type1.declaredNullability, type2.declaredNullability), |
| typeArguments); |
| } |
| } else { |
| // UP(C0<T0, ..., Tn>, C1<S0, ..., Sk>) |
| // = least upper bound of two interfaces as in Dart 1. |
| return _getLegacyLeastUpperBound( |
| typeDeclarationType1, typeDeclarationType2); |
| } |
| |
| case (NeverType(nullability: Nullability.undetermined), _): |
| case (_, NeverType(nullability: Nullability.undetermined)): |
| throw new StateError("Unsupported nullability for NeverType: " |
| "'${Nullability.undetermined}'."); |
| |
| case (AuxiliaryType(), _): |
| case (_, AuxiliaryType()): |
| throw new StateError("Unsupported type combination: " |
| "getNullabilityAwareStandardUpperBoundInternal(" |
| "${type1.runtimeType}, ${type2.runtimeType}" |
| ")"); |
| } |
| } |
| |
| DartType _getLegacyLeastUpperBound( |
| TypeDeclarationType type1, TypeDeclarationType type2) { |
| if (type1 is InterfaceType && type2 is InterfaceType) { |
| return hierarchy.getLegacyLeastUpperBound(type1, type2); |
| } else if (type1 is ExtensionType || type2 is ExtensionType) { |
| // This mimics the legacy least upper bound implementation for regular |
| // classes, where the least upper bound is found as the single common |
| // supertype with the highest class hierarchy depth. |
| |
| // TODO(johnniwinther): Move this computation to [ClassHierarchyBase] and |
| // cache it there. |
| // TODO(johnniwinther): Handle non-extension type supertypes. |
| Map<ExtensionTypeDeclaration, int> extensionTypeDeclarationDepth = {}; |
| |
| int computeExtensionTypeDeclarationDepth( |
| ExtensionTypeDeclaration extensionTypeDeclaration, |
| List<InterfaceType> superInterfaceType) { |
| int? depth = extensionTypeDeclarationDepth[extensionTypeDeclaration]; |
| if (depth == null) { |
| int maxDepth = 0; |
| for (DartType implemented in extensionTypeDeclaration.implements) { |
| if (implemented is ExtensionType) { |
| int supertypeDepth = computeExtensionTypeDeclarationDepth( |
| implemented.extensionTypeDeclaration, superInterfaceType); |
| if (supertypeDepth >= maxDepth) { |
| maxDepth = supertypeDepth + 1; |
| } |
| } else if (implemented is InterfaceType) { |
| superInterfaceType.add(implemented); |
| } |
| } |
| depth = extensionTypeDeclarationDepth[extensionTypeDeclaration] = |
| maxDepth; |
| } |
| return depth; |
| } |
| |
| // TODO(johnniwinther): Handle non-extension type supertypes. |
| void computeSuperTypes(ExtensionType type, List<ExtensionType> supertypes, |
| List<InterfaceType> superInterfaceTypes) { |
| computeExtensionTypeDeclarationDepth( |
| type.extensionTypeDeclaration, superInterfaceTypes); |
| supertypes.add(type); |
| for (DartType implemented in type.extensionTypeDeclaration.implements) { |
| if (implemented is ExtensionType) { |
| ExtensionType supertype = |
| hierarchy.getExtensionTypeAsInstanceOfExtensionTypeDeclaration( |
| type, implemented.extensionTypeDeclaration)!; |
| computeSuperTypes(supertype, supertypes, superInterfaceTypes); |
| } |
| } |
| } |
| |
| List<ExtensionType> supertypes1 = []; |
| List<InterfaceType> superInterfaceTypes1 = []; |
| if (type1 is ExtensionType) { |
| computeSuperTypes(type1, supertypes1, superInterfaceTypes1); |
| } else { |
| type1 as InterfaceType; |
| superInterfaceTypes1 = <InterfaceType>[type1]; |
| } |
| List<ExtensionType> supertypes2 = []; |
| List<InterfaceType> superInterfaceTypes2 = []; |
| if (type2 is ExtensionType) { |
| computeSuperTypes(type2, supertypes2, superInterfaceTypes2); |
| } else { |
| type2 as InterfaceType; |
| superInterfaceTypes2 = <InterfaceType>[type2]; |
| } |
| |
| Set<ExtensionType> set = supertypes1.toSet()..retainAll(supertypes2); |
| Map<int, List<ExtensionType>> commonSupertypesByDepth = {}; |
| for (ExtensionType type in set) { |
| (commonSupertypesByDepth[extensionTypeDeclarationDepth[ |
| type.extensionTypeDeclaration]!] ??= []) |
| .add(type); |
| } |
| int maxDepth = -1; |
| ExtensionType? candidate; |
| for (MapEntry<int, List<ExtensionType>> entry |
| in commonSupertypesByDepth.entries) { |
| if (entry.key > maxDepth && entry.value.length == 1) { |
| maxDepth = entry.key; |
| candidate = entry.value.single; |
| } |
| } |
| if (candidate != null) { |
| return candidate; |
| } |
| |
| return hierarchy.getLegacyLeastUpperBoundFromSupertypeLists( |
| type1, type2, superInterfaceTypes1, superInterfaceTypes2); |
| } |
| if (type1 is ExtensionType && type1.isPotentiallyNullable || |
| type2 is ExtensionType && type2.isPotentiallyNullable) { |
| return coreTypes.objectNullableRawType; |
| } else { |
| return coreTypes.objectRawType( |
| uniteNullabilities(type1.nullability, type2.nullability)); |
| } |
| } |
| |
| /// Computes the nullability-aware lower bound of two function types. |
| /// |
| /// The algorithm is defined as follows: |
| /// DOWN( |
| /// <X0 extends B00, ..., Xm extends B0m>(P00, ..., P0k) -> T0, |
| /// <X0 extends B10, ..., Xm extends B1m>(P10, ..., P1l) -> T1) |
| /// = |
| /// <X0 extends B20, ..., Xm extends B2m>(P20, ..., P2q) -> R0 |
| /// if: |
| /// each B0i and B1i are equal types (syntactically), |
| /// q is max(k, l), |
| /// R0 is DOWN(T0, T1), |
| /// B2i is B0i, |
| /// P2i is UP(P0i, P1i) for i <= than min(k, l), |
| /// P2i is P0i for k < i <= q, |
| /// P2i is P1i for l < i <= q, and |
| /// P2i is optional if P0i or P1i is optional. |
| /// |
| /// DOWN( |
| /// <X0 extends B00, ..., Xm extends B0m>(P00, ..., P0k, Named0) -> T0, |
| /// <X0 extends B10, ..., Xm extends B1m>(P10, ..., P1k, Named1) -> T1) |
| /// = |
| /// <X0 extends B20, ..., Xm extends B2m>(P20, ..., P2k, Named2) -> R0 |
| /// if: |
| /// each B0i and B1i are equal types (syntactically), |
| /// R0 is DOWN(T0, T1), |
| /// B2i is B0i, |
| /// P2i is UP(P0i, P1i), |
| /// Named2 contains R2i xi for each xi in both Named0 and Named1, |
| /// where R0i xi is in Named0, |
| /// where R1i xi is in Named1, |
| /// and R2i is UP(R0i, R1i), |
| /// and R2i xi is required if xi is required in both Named0 and Named1, |
| /// Named2 contains R0i xi for each xi in Named0 and not Named1, |
| /// where xi is optional in Named2, |
| /// Named2 contains R1i xi for each xi in Named1 and not Named0, and |
| /// where xi is optional in Named2. |
| /// DOWN(T Function<...>(...), S Function<...>(...)) = Never otherwise. |
| DartType _getNullabilityAwareFunctionStandardLowerBound( |
| FunctionType f, FunctionType g) { |
| bool haveNamed = |
| f.namedParameters.isNotEmpty || g.namedParameters.isNotEmpty; |
| bool haveOptionalPositional = |
| f.requiredParameterCount < f.positionalParameters.length || |
| g.requiredParameterCount < g.positionalParameters.length; |
| |
| // The fallback result for whenever the following rule applies: |
| // DOWN(T Function<...>(...), S Function<...>(...)) = Never otherwise. |
| final DartType fallbackResult = NeverType.fromNullability( |
| intersectNullabilities(f.declaredNullability, g.declaredNullability)); |
| |
| if (haveNamed && haveOptionalPositional) return fallbackResult; |
| if (haveNamed && |
| f.positionalParameters.length != g.positionalParameters.length) { |
| return fallbackResult; |
| } |
| |
| int m = f.typeParameters.length; |
| bool boundsMatch = false; |
| if (g.typeParameters.length == m) { |
| boundsMatch = true; |
| if (m != 0) { |
| List<DartType> fParametersAsArguments = [ |
| for (StructuralParameter parameter in f.typeParameters) |
| new StructuralParameterType.withDefaultNullability(parameter) |
| ]; |
| FunctionTypeInstantiator instantiator = |
| FunctionTypeInstantiator.fromInstantiation( |
| g, fParametersAsArguments); |
| for (int i = 0; i < m && boundsMatch; ++i) { |
| // TODO(cstefantsova): Figure out if a procedure for syntactic |
| // equality should be used instead. |
| if (!areMutualSubtypes( |
| f.typeParameters[i].bound, |
| instantiator.substitute(g.typeParameters[i].bound), |
| SubtypeCheckMode.withNullabilities)) { |
| boundsMatch = false; |
| } |
| } |
| g = instantiator.substitute(g.withoutTypeParameters) as FunctionType; |
| } |
| } |
| if (!boundsMatch) return fallbackResult; |
| int maxPos = |
| math.max(f.positionalParameters.length, g.positionalParameters.length); |
| int minPos = |
| math.min(f.positionalParameters.length, g.positionalParameters.length); |
| |
| List<StructuralParameter> typeParameters = f.typeParameters; |
| |
| List<DartType> positionalParameters = |
| new List<DartType>.filled(maxPos, dummyDartType); |
| for (int i = 0; i < minPos; ++i) { |
| positionalParameters[i] = _getNullabilityAwareStandardUpperBound( |
| f.positionalParameters[i], g.positionalParameters[i]); |
| } |
| for (int i = minPos; i < f.positionalParameters.length; ++i) { |
| positionalParameters[i] = f.positionalParameters[i]; |
| } |
| for (int i = minPos; i < g.positionalParameters.length; ++i) { |
| positionalParameters[i] = g.positionalParameters[i]; |
| } |
| |
| List<NamedType> namedParameters = <NamedType>[]; |
| { |
| // Assuming that the named parameters of both types are sorted |
| // lexicographically. |
| int i = 0; |
| int j = 0; |
| while (i < f.namedParameters.length && j < g.namedParameters.length) { |
| NamedType named1 = f.namedParameters[i]; |
| NamedType named2 = g.namedParameters[j]; |
| int order = named1.name.compareTo(named2.name); |
| NamedType named; |
| if (order < 0) { |
| named = new NamedType(named1.name, named1.type, isRequired: false); |
| ++i; |
| } else if (order > 0) { |
| named = !named2.isRequired |
| ? named2 |
| : new NamedType(named2.name, named2.type, isRequired: false); |
| ++j; |
| } else { |
| named = new NamedType(named1.name, |
| _getNullabilityAwareStandardUpperBound(named1.type, named2.type), |
| isRequired: named1.isRequired && named2.isRequired); |
| ++i; |
| ++j; |
| } |
| namedParameters.add(named); |
| } |
| while (i < f.namedParameters.length) { |
| NamedType named1 = f.namedParameters[i]; |
| namedParameters.add(!named1.isRequired |
| ? named1 |
| : new NamedType(named1.name, named1.type, isRequired: false)); |
| ++i; |
| } |
| while (j < g.namedParameters.length) { |
| NamedType named2 = g.namedParameters[j]; |
| namedParameters |
| .add(new NamedType(named2.name, named2.type, isRequired: false)); |
| ++j; |
| } |
| } |
| |
| DartType returnType = |
| _getNullabilityAwareStandardLowerBound(f.returnType, g.returnType); |
| |
| return new FunctionType(positionalParameters, returnType, |
| intersectNullabilities(f.declaredNullability, g.declaredNullability), |
| namedParameters: namedParameters, |
| typeParameters: typeParameters, |
| requiredParameterCount: |
| math.min(f.requiredParameterCount, g.requiredParameterCount)); |
| } |
| |
| /// Computes the nullability-aware lower bound of two record types. |
| /// |
| /// The algorithm is defined as follows: |
| /// DOWN((P00, ..., P0k, Named0), (P10, ..., P1k, Named1)) = |
| /// (P20, ..., P2k, Named2) |
| /// if: |
| /// P2i is DOWN(P0i, P1i), |
| /// Named0 contains R0i xi |
| /// if Named1 contains R1i xi |
| /// Named1 contains R1i xi |
| /// if Named0 contains R0i xi |
| /// Named2 contains exactly R2i xi |
| /// for each xi in both Named0 and Named1 |
| /// where R0i xi is in Named0 |
| /// where R1i xi is in Named1 |
| /// and R2i is UP(R0i, R1i) |
| /// DOWN(Record(...), Record(...)) = Never otherwise. |
| DartType _getNullabilityAwareRecordStandardLowerBound( |
| RecordType r1, RecordType r2) { |
| // The fallback result for whenever the following rule applies: |
| // DOWN(Record(...), Record(...)) = Never otherwise. |
| late final DartType fallbackResult = NeverType.fromNullability( |
| intersectNullabilities(r1.declaredNullability, r2.declaredNullability)); |
| |
| if (r1.positional.length != r2.positional.length || |
| r1.named.length != r2.named.length) { |
| return fallbackResult; |
| } |
| |
| int positionalLength = r1.positional.length; |
| int namedLength = r1.named.length; |
| |
| for (int i = 0; i < namedLength; i++) { |
| if (r1.named[i].name != r2.named[i].name) { |
| return fallbackResult; |
| } |
| } |
| |
| List<DartType> positional = new List<DartType>.generate( |
| positionalLength, |
| (i) => _getNullabilityAwareStandardLowerBound( |
| r1.positional[i], r2.positional[i])); |
| |
| List<NamedType> named = new List<NamedType>.generate( |
| namedLength, |
| (i) => new NamedType( |
| r1.named[i].name, |
| _getNullabilityAwareStandardLowerBound( |
| r1.named[i].type, r2.named[i].type))); |
| |
| return new RecordType(positional, named, |
| intersectNullabilities(r1.declaredNullability, r2.declaredNullability)); |
| } |
| |
| /// Computes the nullability-aware lower bound of two function types. |
| /// |
| /// UP( |
| /// <X0 extends B00, ... Xm extends B0m>(P00, ... P0k) -> T0, |
| /// <X0 extends B10, ... Xm extends B1m>(P10, ... P1l) -> T1) |
| /// = |
| /// <X0 extends B20, ..., Xm extends B2m>(P20, ..., P2q) -> R0 |
| /// if: |
| /// each B0i and B1i are equal types (syntactically) |
| /// Both have the same number of required positional parameters |
| /// q is min(k, l) |
| /// R0 is UP(T0, T1) |
| /// B2i is B0i |
| /// P2i is DOWN(P0i, P1i) |
| /// UP( |
| /// <X0 extends B00, ... Xm extends B0m>(P00, ... P0k, Named0) -> T0, |
| /// <X0 extends B10, ... Xm extends B1m>(P10, ... P1k, Named1) -> T1) |
| /// = |
| /// <X0 extends B20, ..., Xm extends B2m>(P20, ..., P2k, Named2) -> R0 |
| /// if: |
| /// each B0i and B1i are equal types (syntactically) |
| /// All positional parameters are required |
| /// R0 is UP(T0, T1) |
| /// B2i is B0i |
| /// P2i is DOWN(P0i, P1i) |
| /// Named0 contains R0i xi |
| /// if R1i xi is a required named parameter in Named1 |
| /// Named1 contains R1i xi |
| /// if R0i xi is a required named parameter in Named0 |
| /// Named2 contains exactly R2i xi |
| /// for each xi in both Named0 and Named1 |
| /// where R0i xi is in Named0 |
| /// where R1i xi is in Named1 |
| /// and R2i is DOWN(R0i, R1i) |
| /// and R2i xi is required |
| /// if xi is required in either Named0 or Named1 |
| /// UP(T Function<...>(...), S Function<...>(...)) = Function otherwise |
| DartType _getNullabilityAwareFunctionStandardUpperBound( |
| FunctionType f, FunctionType g) { |
| bool haveNamed = |
| f.namedParameters.isNotEmpty || g.namedParameters.isNotEmpty; |
| bool haveOptionalPositional = |
| f.requiredParameterCount < f.positionalParameters.length || |
| g.requiredParameterCount < g.positionalParameters.length; |
| |
| // The return value for whenever the following applies: |
| // UP(T Function<...>(...), S Function<...>(...)) = Function otherwise |
| final DartType fallbackResult = coreTypes.functionRawType( |
| uniteNullabilities(f.declaredNullability, g.declaredNullability)); |
| |
| if (haveNamed && haveOptionalPositional) return fallbackResult; |
| if (!haveNamed && f.requiredParameterCount != g.requiredParameterCount) { |
| return fallbackResult; |
| } |
| // Here we perform a quick check on the function types to figure out if we |
| // can compute a non-trivial upper bound for them. The check isn't merged |
| // with the computation of the non-trivial upper bound itself to avoid |
| // performing unnecessary computations. |
| if (haveNamed) { |
| if (f.positionalParameters.length != g.positionalParameters.length) { |
| return fallbackResult; |
| } |
| // Assuming that the named parameters are sorted lexicographically in |
| // both type1 and type2. |
| int i = 0; |
| int j = 0; |
| while (i < f.namedParameters.length && j < g.namedParameters.length) { |
| NamedType named1 = f.namedParameters[i]; |
| NamedType named2 = g.namedParameters[j]; |
| int order = named1.name.compareTo(named2.name); |
| if (order < 0) { |
| if (named1.isRequired) return fallbackResult; |
| ++i; |
| } else if (order > 0) { |
| if (named2.isRequired) return fallbackResult; |
| ++j; |
| } else { |
| ++i; |
| ++j; |
| } |
| } |
| while (i < f.namedParameters.length) { |
| if (f.namedParameters[i].isRequired) return fallbackResult; |
| ++i; |
| } |
| while (j < g.namedParameters.length) { |
| if (g.namedParameters[j].isRequired) return fallbackResult; |
| ++j; |
| } |
| } |
| |
| int m = f.typeParameters.length; |
| bool boundsMatch = false; |
| FunctionTypeInstantiator? instantiator; |
| if (g.typeParameters.length == m) { |
| boundsMatch = true; |
| if (m != 0) { |
| List<DartType> fTypeParametersAsTypes = [ |
| for (StructuralParameter parameter in f.typeParameters) |
| new StructuralParameterType.withDefaultNullability(parameter) |
| ]; |
| instantiator = FunctionTypeInstantiator.fromIterables( |
| g.typeParameters, fTypeParametersAsTypes); |
| for (int i = 0; i < m && boundsMatch; ++i) { |
| // TODO(cstefantsova): Figure out if a procedure for syntactic |
| // equality should be used instead. |
| if (!areMutualSubtypes( |
| f.typeParameters[i].bound, |
| instantiator.substitute(g.typeParameters[i].bound), |
| SubtypeCheckMode.withNullabilities)) { |
| boundsMatch = false; |
| } |
| } |
| } |
| } |
| if (!boundsMatch) return fallbackResult; |
| int minPos = |
| math.min(f.positionalParameters.length, g.positionalParameters.length); |
| |
| List<StructuralParameter> typeParameters = f.typeParameters; |
| |
| List<DartType> positionalParameters = |
| new List<DartType>.filled(minPos, dummyDartType); |
| for (int i = 0; i < minPos; ++i) { |
| positionalParameters[i] = _getNullabilityAwareStandardLowerBound( |
| f.positionalParameters[i], |
| instantiator != null |
| ? instantiator.substitute(g.positionalParameters[i]) |
| : g.positionalParameters[i]); |
| } |
| |
| List<NamedType> namedParameters = <NamedType>[]; |
| { |
| // Assuming that the named parameters of both types are sorted |
| // lexicographically. |
| int i = 0; |
| int j = 0; |
| while (i < f.namedParameters.length && j < g.namedParameters.length) { |
| NamedType named1 = f.namedParameters[i]; |
| NamedType named2 = g.namedParameters[j]; |
| int order = named1.name.compareTo(named2.name); |
| if (order < 0) { |
| ++i; |
| } else if (order > 0) { |
| ++j; |
| } else { |
| namedParameters.add(new NamedType( |
| named1.name, |
| _getNullabilityAwareStandardLowerBound( |
| named1.type, |
| instantiator != null |
| ? instantiator.substitute(named2.type) |
| : named2.type), |
| isRequired: named1.isRequired || named2.isRequired)); |
| ++i; |
| ++j; |
| } |
| } |
| } |
| |
| DartType returnType = _getNullabilityAwareStandardUpperBound( |
| f.returnType, |
| instantiator != null |
| ? instantiator.substitute(g.returnType) |
| : g.returnType); |
| |
| return new FunctionType(positionalParameters, returnType, |
| uniteNullabilities(f.declaredNullability, g.declaredNullability), |
| namedParameters: namedParameters, |
| typeParameters: typeParameters, |
| requiredParameterCount: f.requiredParameterCount); |
| } |
| |
| /// Computes the nullability-aware lower bound of two record types. |
| /// |
| /// UP((P00, ... P0k, Named0), (P10, ... P1k, Named1)) = |
| /// (P20, ..., P2k, Named2) |
| /// if: |
| /// P2i is UP(P0i, P1i) |
| /// Named0 contains R0i xi |
| /// if Named1 contains R1i xi |
| /// Named1 contains R1i xi |
| /// if Named0 contains R0i xi |
| /// Named2 contains exactly R2i xi |
| /// for each xi in both Named0 and Named1 |
| /// where R0i xi is in Named0 |
| /// where R1i xi is in Named1 |
| /// and R2i is UP(R0i, R1i) |
| /// UP(Record(...), Record(...)) = Record otherwise |
| DartType _getNullabilityAwareRecordStandardUpperBound( |
| RecordType r1, RecordType r2) { |
| // The return value for whenever the following applies: |
| // UP(Record(...), Record(...)) = Record otherwise |
| late final DartType fallbackResult = coreTypes.recordRawType( |
| uniteNullabilities(r1.declaredNullability, r2.declaredNullability)); |
| |
| // Here we perform a quick check on the function types to figure out if we |
| // can compute a non-trivial upper bound for them. |
| if (r1.positional.length != r2.positional.length || |
| r1.named.length != r2.named.length) { |
| return fallbackResult; |
| } |
| |
| int positionalLength = r1.positional.length; |
| int namedLength = r1.named.length; |
| |
| for (int i = 0; i < namedLength; i++) { |
| // The named parameters of record types are assumed to be sorted |
| // lexicographically. |
| if (r1.named[i].name != r2.named[i].name) { |
| return fallbackResult; |
| } |
| } |
| |
| List<DartType> positional = new List<DartType>.generate( |
| positionalLength, |
| (i) => _getNullabilityAwareStandardUpperBound( |
| r1.positional[i], r2.positional[i])); |
| |
| List<NamedType> named = new List<NamedType>.generate( |
| namedLength, |
| (i) => new NamedType( |
| r1.named[i].name, |
| _getNullabilityAwareStandardUpperBound( |
| r1.named[i].type, r2.named[i].type))); |
| |
| return new RecordType(positional, named, |
| uniteNullabilities(r1.declaredNullability, r2.declaredNullability)); |
| } |
| |
| DartType _getNullabilityAwareTypeVariableStandardUpperBound( |
| DartType type1, DartType type2, |
| {required DartType bound1, |
| TypeParameter? nominalEliminationTarget, |
| StructuralParameter? structuralEliminationTarget}) { |
| assert(type1 is TypeParameterType || type1 is StructuralParameterType); |
| assert(nominalEliminationTarget != null && |
| structuralEliminationTarget == null || |
| nominalEliminationTarget == null && |
| structuralEliminationTarget != null); |
| |
| // UP(X1 extends B1, T2) = |
| // T2 if X1 <: T2 |
| // otherwise X1 if T2 <: X1 |
| // otherwise UP(B1a, T2) |
| // where B1a is the greatest closure of B1 with respect to X1, |
| // as defined in [inference.md]. |
| |
| if (isSubtypeOf(leastClosureForUpperBound(type1), |
| leastClosureForUpperBound(type2), SubtypeCheckMode.withNullabilities)) { |
| return type2.withDeclaredNullability(combineNullabilitiesForSubstitution( |
| inner: type2.nullability, |
| outer: uniteNullabilities( |
| type1.declaredNullability, type2.nullability))); |
| } |
| if (isSubtypeOf(leastClosureForUpperBound(type2), |
| leastClosureForUpperBound(type1), SubtypeCheckMode.withNullabilities)) { |
| return type1.withDeclaredNullability(combineNullabilitiesForSubstitution( |
| inner: type1.declaredNullability, |
| outer: uniteNullabilities( |
| type1.declaredNullability, type2.nullability))); |
| } |
| NullabilityAwareTypeParameterEliminator eliminator = |
| new NullabilityAwareTypeParameterEliminator( |
| structuralEliminationTargets: { |
| if (structuralEliminationTarget != null) structuralEliminationTarget |
| }, |
| nominalEliminationTargets: { |
| if (nominalEliminationTarget != null) nominalEliminationTarget |
| }, |
| coreTypes: coreTypes, |
| unhandledTypeHandler: (type, recursor) => false); |
| DartType result = _getNullabilityAwareStandardUpperBound( |
| eliminator.eliminateToGreatest(bound1), type2); |
| return result.withDeclaredNullability(combineNullabilitiesForSubstitution( |
| inner: result.declaredNullability, |
| outer: |
| uniteNullabilities(bound1.declaredNullability, type2.nullability))); |
| } |
| |
| DartType _getNullabilityAwareIntersectionStandardUpperBound( |
| IntersectionType type1, DartType type2) { |
| // UP(X1 & B1, T2) = |
| // T2 if X1 <: T2 |
| // otherwise X1 if T2 <: X1 |
| // otherwise UP(B1a, T2) |
| // where B1a is the greatest closure of B1 with respect to X1, |
| // as defined in [inference.md]. |
| DartType demoted = type1.left; |
| if (isSubtypeOf(leastClosureForUpperBound(demoted), |
| leastClosureForUpperBound(type2), SubtypeCheckMode.withNullabilities)) { |
| return type2.withDeclaredNullability(uniteNullabilities( |
| type1.declaredNullability, type2.declaredNullability)); |
| } |
| if (isSubtypeOf( |
| leastClosureForUpperBound(type2), |
| leastClosureForUpperBound(demoted), |
| SubtypeCheckMode.withNullabilities)) { |
| return demoted.withDeclaredNullability(uniteNullabilities( |
| demoted.declaredNullability, type2.declaredNullability)); |
| } |
| NullabilityAwareTypeParameterEliminator eliminator = |
| new NullabilityAwareTypeParameterEliminator( |
| structuralEliminationTargets: {}, |
| nominalEliminationTargets: {type1.left.parameter}, |
| coreTypes: coreTypes, |
| unhandledTypeHandler: (type, recursor) => false); |
| Nullability resultingNullability = |
| uniteNullabilities(type1.right.declaredNullability, type2.nullability); |
| |
| // If the resulting nullability is [Nullability.undetermined], one of the |
| // types can be nullable at run time. The upper bound is supposed to be a |
| // supertype to both of the types under all conditions, so we interpret the |
| // undetermined case as [Nullability.nullable]. |
| resultingNullability = resultingNullability == Nullability.undetermined |
| ? Nullability.nullable |
| : resultingNullability; |
| |
| return _getNullabilityAwareStandardUpperBound( |
| eliminator.eliminateToGreatest(type1.right), type2) |
| .withDeclaredNullability(resultingNullability); |
| } |
| |
| DartType getNullabilityObliviousStandardUpperBoundInternal( |
| DartType type1, DartType type2) { |
| // SUB(void, T) = SUB(T, void) = void. |
| if (type1 is VoidType) { |
| return type1; |
| } |
| if (type2 is VoidType) { |
| return type2; |
| } |
| |
| // SUB(dynamic, T) = SUB(T, dynamic) = dynamic if T is not void. |
| if (type1 is DynamicType) { |
| return type1; |
| } |
| if (type2 is DynamicType) { |
| return type2; |
| } |
| |
| // SUB(Object, T) = SUB(T, Object) = Object if T is not void or dynamic. |
| if (type1 == coreTypes.objectLegacyRawType) { |
| return type1; |
| } |
| if (type2 == coreTypes.objectLegacyRawType) { |
| return type2; |
| } |
| |
| // SUB(bottom, T) = SUB(T, bottom) = T. |
| if (type1 is NullType) return type2; |
| if (type2 is NullType) return type1; |
| |
| if (type1 is TypeParameterType || type2 is TypeParameterType) { |
| return _getNullabilityObliviousTypeParameterStandardUpperBound( |
| type1, type2); |
| } |
| |
| // The standard upper bound of a function type and an interface type T is |
| // the standard upper bound of Function and T. |
| if (type1 is FunctionType && |
| (type2 is InterfaceType || type2 is FutureOrType)) { |
| type1 = coreTypes.functionLegacyRawType; |
| } |
| if (type2 is FunctionType && |
| (type1 is InterfaceType || type2 is FutureOrType)) { |
| type2 = coreTypes.functionLegacyRawType; |
| } |
| |
| // At this point type1 and type2 should both either be interface types or |
| // function types. |
| if (type1 is InterfaceType && type2 is InterfaceType) { |
| return _getInterfaceStandardUpperBound(type1, type2); |
| } |
| |
| if (type1 is FunctionType && type2 is FunctionType) { |
| return _getNullabilityObliviousFunctionStandardUpperBound(type1, type2); |
| } |
| |
| // UP(FutureOr<T1>, FutureOr<T2>) = FutureOr<T3> where T3 = UP(T1, T2) |
| // UP(Future<T1>, FutureOr<T2>) = FutureOr<T3> where T3 = UP(T1, T2) |
| // UP(FutureOr<T1>, Future<T2>) = FutureOr<T3> where T3 = UP(T1, T2) |
| // UP(T1, FutureOr<T2>) = FutureOr<T3> where T3 = UP(T1, T2) |
| // UP(FutureOr<T1>, T2) = FutureOr<T3> where T3 = UP(T1, T2) |
| if (type1 is FutureOrType) { |
| DartType t1 = type1.typeArgument; |
| DartType t2; |
| if (type2 is InterfaceType && type2.classNode == coreTypes.futureClass) { |
| t2 = type2.typeArguments.single; |
| } else if (type2 is FutureOrType) { |
| t2 = type2.typeArgument; |
| } else { |
| t2 = type2; |
| } |
| return new FutureOrType( |
| getStandardUpperBound(t1, t2), |
| uniteNullabilities( |
| type1.declaredNullability, type2.declaredNullability)); |
| } else if (type2 is FutureOrType) { |
| DartType t2 = type2.typeArgument; |
| DartType t1; |
| if (type1 is InterfaceType && type1.classNode == coreTypes.futureClass) { |
| t1 = type1.typeArguments.single; |
| } else { |
| t1 = type1; |
| } |
| return new FutureOrType( |
| getStandardUpperBound(t1, t2), |
| uniteNullabilities( |
| type1.declaredNullability, type2.declaredNullability)); |
| } |
| |
| if (type1 is InvalidType || type2 is InvalidType) { |
| return const InvalidType(); |
| } |
| |
| // Should never happen. As a defensive measure, return the dynamic type. |
| assert(false, "type1 = $type1; type2 = $type2"); |
| return const DynamicType(); |
| } |
| |
| /// Compute the standard lower bound of function types [f] and [g]. |
| /// |
| /// The spec rules for SLB on function types, informally, are pretty simple: |
| /// |
| /// - If a parameter is required in both, it stays required. |
| /// |
| /// - If a positional parameter is optional or missing in one, it becomes |
| /// optional. (This is because we're trying to build a function type which |
| /// is a subtype of both [f] and [g], meaning it accepts all possible |
| /// inputs that [f] and [g] accept.) |
| /// |
| /// - Named parameters are unioned together. |
| /// |
| /// - For any parameter that exists in both functions, use the SUB of them as |
| /// the resulting parameter type. |
| /// |
| /// - Use the SLB of their return types. |
| DartType _getNullabilityObliviousFunctionStandardLowerBound( |
| FunctionType f, FunctionType g) { |
| // TODO(rnystrom,paulberry): Right now, this assumes f and g do not have |
| // any type parameters. Revisit that in the presence of generic methods. |
| |
| // Calculate the SUB of each corresponding pair of parameters. |
| int totalPositional = |
| math.max(f.positionalParameters.length, g.positionalParameters.length); |
| List<DartType> positionalParameters = |
| new List<DartType>.filled(totalPositional, dummyDartType); |
| for (int i = 0; i < totalPositional; i++) { |
| if (i < f.positionalParameters.length) { |
| DartType fType = f.positionalParameters[i]; |
| if (i < g.positionalParameters.length) { |
| DartType gType = g.positionalParameters[i]; |
| positionalParameters[i] = getStandardUpperBound(fType, gType); |
| } else { |
| positionalParameters[i] = fType; |
| } |
| } else { |
| positionalParameters[i] = g.positionalParameters[i]; |
| } |
| } |
| |
| // Parameters that are required in both functions are required in the |
| // result. Parameters that are optional or missing in either end up |
| // optional. |
| int requiredParameterCount = |
| math.min(f.requiredParameterCount, g.requiredParameterCount); |
| bool hasPositional = requiredParameterCount < totalPositional; |
| |
| // Union the named parameters together. |
| List<NamedType> namedParameters = []; |
| { |
| int i = 0; |
| int j = 0; |
| while (true) { |
| if (i < f.namedParameters.length) { |
| if (j < g.namedParameters.length) { |
| String fName = f.namedParameters[i].name; |
| String gName = g.namedParameters[j].name; |
| int order = fName.compareTo(gName); |
| if (order < 0) { |
| namedParameters.add(f.namedParameters[i++]); |
| } else if (order > 0) { |
| namedParameters.add(g.namedParameters[j++]); |
| } else { |
| namedParameters.add(new NamedType( |
| fName, |
| getStandardUpperBound(f.namedParameters[i++].type, |
| g.namedParameters[j++].type))); |
| } |
| } else { |
| namedParameters.addAll(f.namedParameters.skip(i)); |
| break; |
| } |
| } else { |
| namedParameters.addAll(g.namedParameters.skip(j)); |
| break; |
| } |
| } |
| } |
| bool hasNamed = namedParameters.isNotEmpty; |
| |
| // Edge case. Dart does not support functions with both optional positional |
| // and named parameters. If we would synthesize that, give up. |
| if (hasPositional && hasNamed) { |
| return const NeverType.nonNullable(); |
| } |
| |
| // Calculate the SLB of the return type. |
| DartType returnType = getStandardLowerBound(f.returnType, g.returnType); |
| return new FunctionType(positionalParameters, returnType, |
| intersectNullabilities(f.declaredNullability, g.declaredNullability), |
| namedParameters: namedParameters, |
| requiredParameterCount: requiredParameterCount); |
| } |
| |
| /// Compute the standard upper bound of function types [f] and [g]. |
| /// |
| /// The rules for SUB on function types, informally, are pretty simple: |
| /// |
| /// - If the functions don't have the same number of required parameters, |
| /// always return `Function`. |
| /// |
| /// - Discard any optional named or positional parameters the two types do |
| /// not have in common. |
| /// |
| /// - Compute the SLB of each corresponding pair of parameter types, and the |
| /// SUB of the return types. Return a function type with those types. |
| DartType _getNullabilityObliviousFunctionStandardUpperBound( |
| FunctionType f, FunctionType g) { |
| // TODO(rnystrom): Right now, this assumes f and g do not have any type |
| // parameters. Revisit that in the presence of generic methods. |
| |
| // If F and G differ in their number of required parameters, then the |
| // standard upper bound of F and G is Function. |
| // TODO(paulberry): We could do better here, e.g.: |
| // SUB(([int]) -> void, (int) -> void) = (int) -> void |
| if (f.requiredParameterCount != g.requiredParameterCount) { |
| return new InterfaceType( |
| coreTypes.functionClass, |
| uniteNullabilities(f.declaredNullability, g.declaredNullability), |
| const <DynamicType>[]); |
| } |
| int requiredParameterCount = f.requiredParameterCount; |
| |
| // Calculate the SLB of each corresponding pair of parameters. |
| // Ignore any extra optional positional parameters if one has more than the |
| // other. |
| int totalPositional = |
| math.min(f.positionalParameters.length, g.positionalParameters.length); |
| List<DartType> positionalParameters = |
| new List<DartType>.filled(totalPositional, dummyDartType); |
| for (int i = 0; i < totalPositional; i++) { |
| positionalParameters[i] = getStandardLowerBound( |
| f.positionalParameters[i], g.positionalParameters[i]); |
| } |
| |
| // Intersect the named parameters. |
| List<NamedType> namedParameters = []; |
| { |
| int i = 0; |
| int j = 0; |
| while (true) { |
| if (i < f.namedParameters.length) { |
| if (j < g.namedParameters.length) { |
| String fName = f.namedParameters[i].name; |
| String gName = g.namedParameters[j].name; |
| int order = fName.compareTo(gName); |
| if (order < 0) { |
| i++; |
| } else if (order > 0) { |
| j++; |
| } else { |
| namedParameters.add(new NamedType( |
| fName, |
| getStandardLowerBound(f.namedParameters[i++].type, |
| g.namedParameters[j++].type))); |
| } |
| } else { |
| break; |
| } |
| } else { |
| break; |
| } |
| } |
| } |
| |
| // Calculate the SUB of the return type. |
| DartType returnType = getStandardUpperBound(f.returnType, g.returnType); |
| return new FunctionType(positionalParameters, returnType, |
| uniteNullabilities(f.declaredNullability, g.declaredNullability), |
| namedParameters: namedParameters, |
| requiredParameterCount: requiredParameterCount); |
| } |
| |
| DartType _getInterfaceStandardUpperBound( |
| InterfaceType type1, InterfaceType type2) { |
| // This currently does not implement a very complete standard upper bound |
| // algorithm, but handles a couple of the very common cases that are |
| // causing pain in real code. The current algorithm is: |
| // 1. If either of the types is a supertype of the other, return it. |
| // This is in fact the best result in this case. |
| // 2. If the two types have the same class element and is implicitly or |
| // explicitly covariant, then take the pointwise standard upper bound of |
| // the type arguments. This is again the best result, except that the |
| // recursive calls may not return the true standard upper bounds. The |
| // result is guaranteed to be a well-formed type under the assumption |
| // that the input types were well-formed (and assuming that the |
| // recursive calls return well-formed types). |
| // If the variance of the type parameter is contravariant, we take the |
| // standard lower bound of the type arguments. If the variance of the |
| // type parameter is invariant, we verify if the type arguments satisfy |
| // subtyping in both directions, then choose a bound. |
| // 3. Otherwise return the spec-defined standard upper bound. This will |
| // be an upper bound, might (or might not) be least, and might |
| // (or might not) be a well-formed type. |
| if (isSubtypeOf(leastClosureForUpperBound(type1), |
| leastClosureForUpperBound(type2), SubtypeCheckMode.withNullabilities)) { |
| return type2; |
| } |
| if (isSubtypeOf(leastClosureForUpperBound(type2), |
| leastClosureForUpperBound(type1), SubtypeCheckMode.withNullabilities)) { |
| return type1; |
| } |
| if (identical(type1.classNode, type2.classNode)) { |
| List<DartType> tArgs1 = type1.typeArguments; |
| List<DartType> tArgs2 = type2.typeArguments; |
| List<TypeParameter> tParams = type1.classNode.typeParameters; |
| |
| assert(tArgs1.length == tArgs2.length); |
| assert(tArgs1.length == tParams.length); |
| List<DartType> tArgs = new List.filled(tArgs1.length, dummyDartType); |
| for (int i = 0; i < tArgs1.length; i++) { |
| if (tParams[i].variance == Variance.contravariant) { |
| tArgs[i] = getStandardLowerBound(tArgs1[i], tArgs2[i]); |
| } else if (tParams[i].variance == Variance.invariant) { |
| if (!areMutualSubtypes( |
| tArgs1[i], tArgs2[i], SubtypeCheckMode.withNullabilities)) { |
| // No bound will be valid, find bound at the interface level. |
| return hierarchy.getLegacyLeastUpperBound(type1, type2); |
| } |
| // TODO (kallentu) : Fix asymmetric bounds behavior for invariant |
| // type parameters. |
| tArgs[i] = tArgs1[i]; |
| } else { |
| tArgs[i] = getStandardUpperBound(tArgs1[i], tArgs2[i]); |
| } |
| } |
| return new InterfaceType( |
| type1.classNode, |
| uniteNullabilities( |
| type1.declaredNullability, type2.declaredNullability), |
| tArgs); |
| } |
| return hierarchy.getLegacyLeastUpperBound(type1, type2); |
| } |
| |
| DartType _getNullabilityObliviousTypeParameterStandardUpperBound( |
| DartType type1, DartType type2) { |
| // This currently just implements a simple standard upper bound to |
| // handle some common cases. It also avoids some termination issues |
| // with the naive spec algorithm. The standard upper bound of two types |
| // (at least one of which is a type parameter) is computed here as: |
| // 1. If either type is a supertype of the other, return it. |
| // 2. If the first type is a type parameter, replace it with its bound, |
| // with recursive occurrences of itself replaced with Object. |
| // The second part of this should ensure termination. Informally, |
| // each type variable instantiation in one of the arguments to the |
| // standard upper bound algorithm now strictly reduces the number |
| // of bound variables in scope in that argument position. |
| // 3. If the second type is a type parameter, do the symmetric operation |
| // to #2. |
| // |
| // It's not immediately obvious why this is symmetric in the case that both |
| // of them are type parameters. For #1, symmetry holds since subtype is |
| // antisymmetric. For #2, it's clearly not symmetric if upper bounds of |
| // bottom are allowed. Ignoring this (for various reasons, not least of |
| // which that there's no way to write it), there's an informal argument |
| // (that might even be right) that you will always either end up expanding |
| // both of them or else returning the same result no matter which order you |
| // expand them in. A key observation is that identical(expand(type1), |
| // type2) => subtype(type1, type2) and hence the contra-positive. |
| // |
| // TODO(leafp): Think this through and figure out what's the right |
| // definition. Be careful about termination. |
| // |
| // I suspect in general a reasonable algorithm is to expand the innermost |
| // type variable first. Alternatively, you could probably choose to treat |
| // it as just an instance of the interface type upper bound problem, with |
| // the "inheritance" chain extended by the bounds placed on the variables. |
| if (isSubtypeOf(type1, type2, SubtypeCheckMode.ignoringNullabilities)) { |
| return type2; |
| } |
| if (isSubtypeOf(type2, type1, SubtypeCheckMode.ignoringNullabilities)) { |
| return type1; |
| } |
| if (type1 is TypeParameterType) { |
| // TODO(paulberry): Analyzer collapses simple bounds in one step, i.e. |
| // for C<T extends U, U extends List>, T gets resolved directly to List. |
| // Do we need to replicate that behavior? |
| return getStandardUpperBound( |
| Substitution.fromMap({type1.parameter: coreTypes.objectLegacyRawType}) |
| .substituteType(type1.parameter.bound), |
| type2); |
| } else if (type2 is TypeParameterType) { |
| return getStandardUpperBound( |
| type1, |
| Substitution.fromMap({type2.parameter: coreTypes.objectLegacyRawType}) |
| .substituteType(type2.parameter.bound)); |
| } else { |
| // We should only be called when at least one of the types is a |
| // TypeParameterType |
| assert(false); |
| return const DynamicType(); |
| } |
| } |
| |
| /// Compute the greatest closure of [typeSchema] for subtyping in DOWN. |
| /// |
| /// > We add the axiom that DOWN(T, _) == T and the symmetric version. |
| /// > We replace all uses of T1 <: T2 in the DOWN algorithm by S1 <: S2 where |
| /// > Si is the greatest closure of Ti with respect to _. |
| /// |
| /// The specification of using the greatest closure in DOWN can be found at |
| /// https://github.com/dart-lang/language/blob/main/resources/type-system/inference.md#upper-bound |
| DartType greatestClosureForLowerBound(DartType typeSchema) => typeSchema; |
| |
| /// Compute the least closure of [typeSchema] for subtyping in UP. |
| /// |
| /// Taking closures of type schemas in UP is specified as follows: |
| /// |
| /// > We add the axiom that UP(T, _) == T and the symmetric version. |
| /// > We replace all uses of T1 <: T2 in the UP algorithm by S1 <: S2 where Si |
| /// > is the least closure of Ti with respect to _. |
| /// |
| /// The specification of using the least closure in UP can be found at |
| /// https://github.com/dart-lang/language/blob/main/resources/type-system/inference.md#upper-bound |
| DartType leastClosureForUpperBound(DartType typeSchema) => typeSchema; |
| } |