in pkg/compiler/lib/src/elements/types.dart [1893:2162]
bool _subtypeHelper(DartType s, DartType t,
{bool allowPotentialSubtypes = false,
bool assumeInstantiations = false}) {
assert(allowPotentialSubtypes || !assumeInstantiations);
// TODO(fishythefish): Add constraint solving for potential subtypes.
if (allowPotentialSubtypes) return true;
/// Based on
/// https://github.com/dart-lang/language/blob/master/resources/type-system/subtyping.md.
/// See also [_isSubtype] in `dart:_rti`.
bool _isSubtype(DartType s, DartType t, _Assumptions env) {
// Reflexivity:
if (s == t) return true;
if (env != null &&
s is FunctionTypeVariable &&
t is FunctionTypeVariable &&
env.isAssumed(s, t)) return true;
if (s is AnyType) return true;
// Right Top:
if (isTopType(t)) return true;
// Left Top:
if (isStrongTopType(s)) return false;
// Left Bottom:
if (isBottomType(s)) return true;
// Left Type Variable Bound 1:
if (s is TypeVariableType) {
if (_isSubtype(getTypeVariableBound(s.element), t, env)) return true;
}
if (s is FunctionTypeVariable) {
if (_isSubtype(s._bound, t, env)) return true;
}
// Left Null:
// Note: Interchanging the Left Null and Right Object rules allows us to
// reduce casework.
if (!useLegacySubtyping && s.isNull) {
if (t is FutureOrType) {
return _isSubtype(s, t.typeArgument, env);
}
return t.isNull || t is LegacyType || t is NullableType;
}
// Right Object:
if (!useLegacySubtyping && t.isObject) {
if (s is FutureOrType) {
return _isSubtype(s.typeArgument, t, env);
}
if (s is LegacyType) {
return _isSubtype(s.baseType, t, env);
}
return s is! NullableType;
}
// Left Legacy:
if (s is LegacyType) {
return _isSubtype(s.baseType, t, env);
}
// Right Legacy:
if (t is LegacyType) {
// Note that to convert `T*` to `T?`, we can't just say `t._toNullable`.
// The resulting type `T?` may be normalizable (e.g. if `T` is `Never`).
return _isSubtype(
s, useLegacySubtyping ? t.baseType : nullableType(t), env);
}
// Left FutureOr:
if (s is FutureOrType) {
DartType typeArgument = s.typeArgument;
return _isSubtype(typeArgument, t, env) &&
_isSubtype(commonElements.futureType(typeArgument), t, env);
}
// Left Nullable:
if (s is NullableType) {
return (useLegacySubtyping ||
_isSubtype(commonElements.nullType, t, env)) &&
_isSubtype(s.baseType, t, env);
}
// Type Variable Reflexivity 1 is subsumed by Reflexivity and therefore
// elided.
// Type Variable Reflexivity 2 does not apply because we do not represent
// promoted type variables.
// Right Promoted Variable does not apply because we do not represent
// promoted type variables.
// Right FutureOr:
if (t is FutureOrType) {
DartType typeArgument = t.typeArgument;
return _isSubtype(s, typeArgument, env) ||
_isSubtype(s, commonElements.futureType(typeArgument), env);
}
// Right Nullable:
if (t is NullableType) {
return (!useLegacySubtyping &&
_isSubtype(s, commonElements.nullType, env)) ||
_isSubtype(s, t.baseType, env);
}
// Left Promoted Variable does not apply because we do not represent
// promoted type variables.
// Left Type Variable Bound 2:
if (s is TypeVariableType) return false;
if (s is FunctionTypeVariable) return false;
// Function Type/Function:
if (s is FunctionType && t == commonElements.functionType) {
return true;
}
// Positional Function Types + Named Function Types:
// TODO(fishythefish): Disallow JavaScriptFunction as a subtype of
// function types using features inaccessible from JavaScript.
if (t is FunctionType) {
if (s == commonElements.jsJavaScriptFunctionType) return true;
if (s is FunctionType) {
List<FunctionTypeVariable> sTypeVariables = s.typeVariables;
List<FunctionTypeVariable> tTypeVariables = t.typeVariables;
int length = tTypeVariables.length;
if (length != sTypeVariables.length) return false;
env ??= _Assumptions();
env.assumePairs(sTypeVariables, tTypeVariables);
try {
for (int i = 0; i < length; i++) {
DartType sBound = sTypeVariables[i].bound;
DartType tBound = tTypeVariables[i].bound;
if (!_isSubtype(sBound, tBound, env) ||
!_isSubtype(tBound, sBound, env)) {
return false;
}
}
if (!_isSubtype(s.returnType, t.returnType, env)) return false;
List<DartType> sRequiredPositional = s.parameterTypes;
List<DartType> tRequiredPositional = t.parameterTypes;
int sRequiredPositionalLength = sRequiredPositional.length;
int tRequiredPositionalLength = tRequiredPositional.length;
if (sRequiredPositionalLength > tRequiredPositionalLength) {
return false;
}
int requiredPositionalDelta =
tRequiredPositionalLength - sRequiredPositionalLength;
List<DartType> sOptionalPositional = s.optionalParameterTypes;
List<DartType> tOptionalPositional = t.optionalParameterTypes;
int sOptionalPositionalLength = sOptionalPositional.length;
int tOptionalPositionalLength = tOptionalPositional.length;
if (sRequiredPositionalLength + sOptionalPositionalLength <
tRequiredPositionalLength + tOptionalPositionalLength) {
return false;
}
for (int i = 0; i < sRequiredPositionalLength; i++) {
if (!_isSubtype(
tRequiredPositional[i], sRequiredPositional[i], env)) {
return false;
}
}
for (int i = 0; i < requiredPositionalDelta; i++) {
if (!_isSubtype(
tRequiredPositional[sRequiredPositionalLength + i],
sOptionalPositional[i],
env)) {
return false;
}
}
for (int i = 0; i < tOptionalPositionalLength; i++) {
if (!_isSubtype(tOptionalPositional[i],
sOptionalPositional[requiredPositionalDelta + i], env)) {
return false;
}
}
List<String> sNamed = s.namedParameters;
List<String> tNamed = t.namedParameters;
Set<String> sRequiredNamed = s.requiredNamedParameters;
Set<String> tRequiredNamed = t.requiredNamedParameters;
List<DartType> sNamedTypes = s.namedParameterTypes;
List<DartType> tNamedTypes = t.namedParameterTypes;
int sNamedLength = sNamed.length;
int tNamedLength = tNamed.length;
int sIndex = 0;
for (int tIndex = 0; tIndex < tNamedLength; tIndex++) {
String tName = tNamed[tIndex];
while (true) {
if (sIndex >= sNamedLength) return false;
String sName = sNamed[sIndex++];
int comparison = sName.compareTo(tName);
if (comparison > 0) return false;
bool sIsRequired =
!useLegacySubtyping && sRequiredNamed.contains(sName);
if (comparison < 0) {
if (sIsRequired) return false;
continue;
}
bool tIsRequired =
!useLegacySubtyping && tRequiredNamed.contains(tName);
if (sIsRequired && !tIsRequired) return false;
if (!_isSubtype(
tNamedTypes[tIndex], sNamedTypes[sIndex - 1], env))
return false;
break;
}
}
if (!useLegacySubtyping) {
while (sIndex < sNamedLength) {
if (sRequiredNamed.contains(sNamed[sIndex++])) return false;
}
}
return true;
} finally {
if (length > 0) env.forgetPairs(sTypeVariables, tTypeVariables);
}
}
return false;
}
// Interface Compositionality + Super-Interface:
if (s is InterfaceType) {
if (t is InterfaceType) {
InterfaceType instance =
s.element == t.element ? s : asInstanceOf(s, t.element);
if (instance == null) return false;
List<DartType> sArgs = instance.typeArguments;
List<DartType> tArgs = t.typeArguments;
List<Variance> variances = getTypeVariableVariances(t.element);
assert(sArgs.length == tArgs.length);
assert(tArgs.length == variances.length);
for (int i = 0; i < variances.length; i++) {
switch (variances[i]) {
case Variance.legacyCovariant:
case Variance.covariant:
if (!_isSubtype(sArgs[i], tArgs[i], env)) return false;
break;
case Variance.contravariant:
if (!_isSubtype(tArgs[i], sArgs[i], env)) return false;
break;
case Variance.invariant:
if (!_isSubtype(sArgs[i], tArgs[i], env) ||
!_isSubtype(tArgs[i], sArgs[i], env)) return false;
break;
default:
throw StateError(
"Invalid variance ${variances[i]} used for subtype check.");
}
}
return true;
}
return false;
}
return false;
}
return _isSubtype(s, t, null);
}