blob: 98dc7c93628f2de286da0f0fd28f20d852e158ca [file] [log] [blame] [edit]
// 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 'non_null.dart';
mixin StandardBounds {
ClassHierarchyBase get hierarchy;
bool isSubtypeOf(DartType subtype, DartType supertype);
bool areMutualSubtypes(DartType s, DartType t);
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.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;
// 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(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 _getStandardLowerBound(type1, type2);
}
DartType _getStandardLowerBound(DartType type1, DartType type2) {
// DOWN(T, T) = T.
if (type1 == type2) return type1;
return getStandardLowerBoundInternal(type1, type2);
}
DartType getStandardLowerBoundInternal(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 getStandardLowerBoundInternal(typedefType1.unalias, type2);
case (_, TypedefType typedefType2):
return getStandardLowerBoundInternal(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 (_, _) when coreTypes.isNull(type1) && coreTypes.isNull(type2):
return morebottom(type1, type2) ? type1 : type2;
case (NullType(), DartType(declaredNullability: Nullability.nullable)):
case (
NeverType(nullability: Nullability.nullable),
DartType(declaredNullability: Nullability.nullable)
):
case (_, DartType(declaredNullability: Nullability.nullable))
when coreTypes.isNull(type1):
return type1;
case (NullType(), _):
case (NeverType(nullability: Nullability.nullable), _):
case (_, _) when coreTypes.isNull(type1):
return const NeverType.nonNullable();
case (DartType(declaredNullability: Nullability.nullable), NullType()):
case (
DartType(declaredNullability: Nullability.nullable),
NeverType(nullability: Nullability.nullable)
):
case (DartType(declaredNullability: Nullability.nullable), _)
when coreTypes.isNull(type2):
return type2;
case (_, NullType()):
case (_, NeverType(nullability: Nullability.nullable)):
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 _getStandardLowerBound(type1, type2WithoutNullabilityMarker);
case (_, _)
when !isType1WithoutNullabilityMarker &&
isType2WithoutNullabilityMarker:
return _getStandardLowerBound(type1WithoutNullabilityMarker, type2);
case (_, _)
when isNullableTypeConstructorApplication(type1) &&
isNullableTypeConstructorApplication(type2):
return _getStandardLowerBound(
type1WithoutNullabilityMarker, type2WithoutNullabilityMarker)
.withDeclaredNullability(Nullability.nullable);
case (FunctionType functionType1, FunctionType functionType2):
return _getFunctionStandardLowerBound(functionType1, functionType2);
case (RecordType recordType1, RecordType recordType2):
return _getRecordStandardLowerBound(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)):
return type1.withDeclaredNullability(intersectNullabilities(
type1.declaredNullability, type2.declaredNullability));
case (_, _)
when isSubtypeOf(
greatestClosureForLowerBound(type2WithoutNullabilityMarker),
greatestClosureForLowerBound(type1WithoutNullabilityMarker)):
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: "
"getStandardUpperBoundInternal("
"${type1.runtimeType}, ${type2.runtimeType}"
")");
}
}
/// 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 _getStandardUpperBound(type1, type2);
}
DartType _getStandardUpperBound(DartType type1, DartType type2) {
// UP(T, T) = T
if (type1 == type2) return type1;
return getStandardUpperBoundInternal(type1, type2);
}
DartType getStandardUpperBoundInternal(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 getStandardUpperBoundInternal(typedefType1.unalias, type2);
case (_, TypedefType typedefType2):
return getStandardUpperBoundInternal(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 _getIntersectionStandardUpperBound(intersectionType1, type2);
case (_, IntersectionType intersectionType2):
return _getIntersectionStandardUpperBound(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 (_, _) when coreTypes.isNull(type1) && coreTypes.isNull(type2):
return morebottom(type1, type2) ? type2 : type1;
case (NullType(), _):
case (NeverType(nullability: Nullability.nullable), _):
case (_, _) when coreTypes.isNull(type1):
return type2.withDeclaredNullability(Nullability.nullable);
case (_, NullType()):
case (_, NeverType(nullability: Nullability.nullable)):
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 _getStandardUpperBound(
computeTypeWithoutNullabilityMarker(type1),
computeTypeWithoutNullabilityMarker(type2))
.withDeclaredNullability(Nullability.nullable);
case (TypeParameterType typeParameterType1, _):
return _getTypeVariableStandardUpperBound(type1, type2,
bound1: typeParameterType1.bound,
nominalEliminationTarget: typeParameterType1.parameter);
case (StructuralParameterType structuralParameterType1, _):
return _getTypeVariableStandardUpperBound(type1, type2,
bound1: structuralParameterType1.bound,
structuralEliminationTarget: structuralParameterType1.parameter);
case (_, TypeParameterType typeParameterType2):
return _getTypeVariableStandardUpperBound(type2, type1,
bound1: typeParameterType2.bound,
nominalEliminationTarget: typeParameterType2.parameter);
case (_, StructuralParameterType structuralParameterType2):
return _getTypeVariableStandardUpperBound(type2, type1,
bound1: structuralParameterType2.bound,
structuralEliminationTarget: structuralParameterType2.parameter);
case (FunctionType functionType1, FunctionType functionType2):
return _getFunctionStandardUpperBound(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 _getStandardUpperBound(
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 _getStandardUpperBound(
type1, coreTypes.objectNonNullableRawType);
case (RecordType recordType1, RecordType recordType2):
return _getRecordStandardUpperBound(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 _getStandardUpperBound(
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 _getStandardUpperBound(
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)):
// 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)):
// 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] =
_getStandardLowerBound(leftArguments[i], rightArguments[i]);
} else if (variance == Variance.invariant) {
if (!areMutualSubtypes(leftArguments[i], rightArguments[i])) {
return _getLegacyLeastUpperBound(
typeDeclarationType1, typeDeclarationType2);
}
} else {
typeArguments[i] =
_getStandardUpperBound(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: "
"getStandardUpperBoundInternal("
"${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 _getFunctionStandardLowerBound(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))) {
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] = _getStandardUpperBound(
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, _getStandardUpperBound(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 = _getStandardLowerBound(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 _getRecordStandardLowerBound(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) => _getStandardLowerBound(r1.positional[i], r2.positional[i]));
List<NamedType> named = new List<NamedType>.generate(
namedLength,
(i) => new NamedType(r1.named[i].name,
_getStandardLowerBound(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 _getFunctionStandardUpperBound(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))) {
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] = _getStandardLowerBound(
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,
_getStandardLowerBound(
named1.type,
instantiator != null
? instantiator.substitute(named2.type)
: named2.type),
isRequired: named1.isRequired || named2.isRequired));
++i;
++j;
}
}
}
DartType returnType = _getStandardUpperBound(
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 _getRecordStandardUpperBound(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) => _getStandardUpperBound(r1.positional[i], r2.positional[i]));
List<NamedType> named = new List<NamedType>.generate(
namedLength,
(i) => new NamedType(r1.named[i].name,
_getStandardUpperBound(r1.named[i].type, r2.named[i].type)));
return new RecordType(positional, named,
uniteNullabilities(r1.declaredNullability, r2.declaredNullability));
}
DartType _getTypeVariableStandardUpperBound(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))) {
return type2.withDeclaredNullability(combineNullabilitiesForSubstitution(
inner: type2.nullability,
outer: uniteNullabilities(
type1.declaredNullability, type2.nullability)));
}
if (isSubtypeOf(
leastClosureForUpperBound(type2), leastClosureForUpperBound(type1))) {
return type1.withDeclaredNullability(combineNullabilitiesForSubstitution(
inner: type1.declaredNullability,
outer: uniteNullabilities(
type1.declaredNullability, type2.nullability)));
}
TypeParameterEliminator eliminator = new TypeParameterEliminator(
structuralEliminationTargets: {
if (structuralEliminationTarget != null) structuralEliminationTarget
},
nominalEliminationTargets: {
if (nominalEliminationTarget != null) nominalEliminationTarget
},
coreTypes: coreTypes,
unhandledTypeHandler: (type, recursor) => false);
DartType result =
_getStandardUpperBound(eliminator.eliminateToGreatest(bound1), type2);
return result.withDeclaredNullability(combineNullabilitiesForSubstitution(
inner: result.declaredNullability,
outer:
uniteNullabilities(bound1.declaredNullability, type2.nullability)));
}
DartType _getIntersectionStandardUpperBound(
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))) {
return type2.withDeclaredNullability(uniteNullabilities(
type1.declaredNullability, type2.declaredNullability));
}
if (isSubtypeOf(
leastClosureForUpperBound(type2), leastClosureForUpperBound(demoted))) {
return demoted.withDeclaredNullability(uniteNullabilities(
demoted.declaredNullability, type2.declaredNullability));
}
TypeParameterEliminator eliminator = new TypeParameterEliminator(
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 _getStandardUpperBound(
eliminator.eliminateToGreatest(type1.right), type2)
.withDeclaredNullability(resultingNullability);
}
/// 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;
}