blob: afd2ed89167a31398d03abe55f48bef2b036f00d [file] [log] [blame]
// Copyright (c) 2020, 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 'package:_fe_analyzer_shared/src/type_inference/type_analyzer_operations.dart'
show Variance;
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_schema.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
/// Helper for checking the subtype relation.
///
/// https://github.com/dart-lang/language
/// See `resources/type-system/subtyping.md`
class SubtypeHelper {
final TypeSystemImpl _typeSystem;
final TypeProviderImpl _typeProvider;
final InterfaceTypeImpl _nullNone;
final InterfaceTypeImpl _objectNone;
final InterfaceTypeImpl _objectQuestion;
SubtypeHelper(this._typeSystem)
: _typeProvider = _typeSystem.typeProvider,
_nullNone = _typeSystem.nullNone,
_objectNone = _typeSystem.objectNone,
_objectQuestion = _typeSystem.objectQuestion;
/// Return `true` if [T0_] is a subtype of [T1_].
bool isSubtypeOf(DartType T0_, DartType T1_) {
// Reflexivity: if `T0` and `T1` are the same type then `T0 <: T1`.
if (identical(T0_, T1_)) {
return true;
}
// `_` is treated as a top and a bottom type during inference.
if (identical(T0_, UnknownInferredType.instance) ||
identical(T1_, UnknownInferredType.instance)) {
return true;
}
// `InvalidType` is treated as a top and a bottom type.
if (identical(T0_, InvalidTypeImpl.instance) ||
identical(T1_, InvalidTypeImpl.instance)) {
return true;
}
var T0 = T0_ as TypeImpl;
var T1 = T1_ as TypeImpl;
var T1_nullability = T1.nullabilitySuffix;
var T0_nullability = T0.nullabilitySuffix;
// Right Top: if `T1` is a top type (i.e. `dynamic`, or `void`, or
// `Object?`) then `T0 <: T1`.
if (identical(T1, DynamicTypeImpl.instance) ||
identical(T1, InvalidTypeImpl.instance) ||
identical(T1, VoidTypeImpl.instance) ||
T1_nullability == NullabilitySuffix.question && T1.isDartCoreObject) {
return true;
}
// Left Top: if `T0` is `dynamic` or `void`,
// then `T0 <: T1` if `Object? <: T1`.
if (identical(T0, DynamicTypeImpl.instance) ||
identical(T0, InvalidTypeImpl.instance) ||
identical(T0, VoidTypeImpl.instance)) {
if (isSubtypeOf(_objectQuestion, T1)) {
return true;
}
}
// Left Bottom: if `T0` is `Never`, then `T0 <: T1`.
if (identical(T0, NeverTypeImpl.instance)) {
return true;
}
// Right Object: if `T1` is `Object` then:
if (T1_nullability == NullabilitySuffix.none && T1.isDartCoreObject) {
// * if `T0` is an unpromoted type variable with bound `B`,
// then `T0 <: T1` iff `B <: Object`.
// * if `T0` is a promoted type variable `X & S`,
// then `T0 <: T1` iff `S <: Object`.
if (T0_nullability == NullabilitySuffix.none &&
T0 is TypeParameterTypeImpl) {
var S = T0.promotedBound;
if (S == null) {
var B = T0.element.bound ?? _objectQuestion;
return isSubtypeOf(B, _objectNone);
} else {
return isSubtypeOf(S, _objectNone);
}
}
// * if `T0` is `FutureOr<S>` for some `S`,
// then `T0 <: T1` iff `S <: Object`
if (T0_nullability == NullabilitySuffix.none &&
T0 is InterfaceTypeImpl &&
T0.isDartAsyncFutureOr) {
return isSubtypeOf(T0.typeArguments[0], T1);
}
// * if `T0` is `Null`, `dynamic`, `void`, or `S?` for any `S`,
// then the subtyping does not hold, the result is false.
if (T0_nullability == NullabilitySuffix.none && T0.isDartCoreNull ||
identical(T0, DynamicTypeImpl.instance) ||
identical(T0, InvalidTypeImpl.instance) ||
identical(T0, VoidTypeImpl.instance) ||
T0_nullability == NullabilitySuffix.question) {
return false;
}
// Extension types require explicit `Object` implementation.
if (T0 is InterfaceTypeImpl && T0.element is ExtensionTypeElement) {
for (var interface in T0.interfaces) {
if (isSubtypeOf(interface, T1_)) {
return true;
}
}
return false;
}
// Otherwise `T0 <: T1` is true.
return true;
}
// Left Null: if `T0` is `Null` then:
if (T0_nullability == NullabilitySuffix.none && T0.isDartCoreNull) {
// * If `T1` is `FutureOr<S>` for some `S`, then the query is true iff
// `Null <: S`.
if (T1_nullability == NullabilitySuffix.none &&
T1 is InterfaceTypeImpl &&
T1.isDartAsyncFutureOr) {
var S = T1.typeArguments[0];
return isSubtypeOf(_nullNone, S);
}
// If `T1` is `Null` or `S?` for some `S`, then the query is true.
if (T1_nullability == NullabilitySuffix.none && T1.isDartCoreNull ||
T1_nullability == NullabilitySuffix.question) {
return true;
}
// * if `T1` is a type variable (promoted or not) the query is false
if (T1 is TypeParameterTypeImpl) {
return false;
}
// Otherwise, the query is false.
return false;
}
// Left FutureOr: if `T0` is `FutureOr<S0>` then:
if (T0_nullability == NullabilitySuffix.none &&
T0 is InterfaceTypeImpl &&
T0.isDartAsyncFutureOr) {
var S0 = T0.typeArguments[0];
// * `T0 <: T1` iff `Future<S0> <: T1` and `S0 <: T1`
if (isSubtypeOf(S0, T1)) {
var FutureS0 = _typeProvider.futureElement.instantiate(
typeArguments: fixedTypeList(S0),
nullabilitySuffix: NullabilitySuffix.none,
);
return isSubtypeOf(FutureS0, T1);
}
return false;
}
// Left Nullable: if `T0` is `S0?` then:
// * `T0 <: T1` iff `S0 <: T1` and `Null <: T1`.
if (T0_nullability == NullabilitySuffix.question) {
var S0 = T0.withNullability(NullabilitySuffix.none);
return isSubtypeOf(S0, T1) && isSubtypeOf(_nullNone, T1);
}
// Type Variable Reflexivity 1: if T0 is a type variable X0 or a promoted
// type variables X0 & S0 and T1 is X0 then:
// * T0 <: T1
if (T0 is TypeParameterTypeImpl &&
T1 is TypeParameterTypeImpl &&
T1.promotedBound == null &&
T0.element == T1.element) {
return true;
}
// Right Promoted Variable: if `T1` is a promoted type variable `X1 & S1`:
// * `T0 <: T1` iff `T0 <: X1` and `T0 <: S1`
if (T1 is TypeParameterTypeImpl) {
var T1_promotedBound = T1.promotedBound;
if (T1_promotedBound != null) {
var X1 = TypeParameterTypeImpl(
element: T1.element,
nullabilitySuffix: T1.nullabilitySuffix,
);
return isSubtypeOf(T0, X1) && isSubtypeOf(T0, T1_promotedBound);
}
}
// Right FutureOr: if `T1` is `FutureOr<S1>` then:
if (T1_nullability == NullabilitySuffix.none &&
T1 is InterfaceTypeImpl &&
T1.isDartAsyncFutureOr) {
var S1 = T1.typeArguments[0];
// `T0 <: T1` iff any of the following hold:
// * either `T0 <: Future<S1>`
var FutureS1 = _typeProvider.futureElement.instantiate(
typeArguments: fixedTypeList(S1),
nullabilitySuffix: NullabilitySuffix.none,
);
if (isSubtypeOf(T0, FutureS1)) {
return true;
}
// * or `T0 <: S1`
if (isSubtypeOf(T0, S1)) {
return true;
}
// * or `T0` is `X0` and `X0` has bound `S0` and `S0 <: T1`
// * or `T0` is `X0 & S0` and `S0 <: T1`
if (T0 is TypeParameterTypeImpl) {
var S0 = T0.promotedBound;
if (S0 != null && isSubtypeOf(S0, T1)) {
return true;
}
var B0 = T0.element.bound;
if (B0 != null && isSubtypeOf(B0, T1)) {
return true;
}
}
// iff
return false;
}
// Right Nullable: if `T1` is `S1?` then:
if (T1_nullability == NullabilitySuffix.question) {
var S1 = T1.withNullability(NullabilitySuffix.none);
// `T0 <: T1` iff any of the following hold:
// * either `T0 <: S1`
if (isSubtypeOf(T0, S1)) {
return true;
}
// * or `T0 <: Null`
if (isSubtypeOf(T0, _nullNone)) {
return true;
}
// or `T0` is `X0` and `X0` has bound `S0` and `S0 <: T1`
// or `T0` is `X0 & S0` and `S0 <: T1`
if (T0 is TypeParameterTypeImpl) {
var S0 = T0.promotedBound;
if (S0 != null && isSubtypeOf(S0, T1)) {
return true;
}
var B0 = T0.element.bound;
if (B0 != null && isSubtypeOf(B0, T1)) {
return true;
}
}
// iff
return false;
}
// Super-Interface: `T0` is an interface type with super-interfaces
// `S0,...Sn`:
// * and `Si <: T1` for some `i`.
if (T0 is InterfaceTypeImpl && T1 is InterfaceTypeImpl) {
return _isInterfaceSubtypeOf(T0, T1);
}
// Left Promoted Variable: `T0` is a promoted type variable `X0 & S0`
// * and `S0 <: T1`
// Left Type Variable Bound: `T0` is a type variable `X0` with bound `B0`
// * and `B0 <: T1`
if (T0 is TypeParameterTypeImpl) {
var S0 = T0.promotedBound;
if (S0 != null && isSubtypeOf(S0, T1)) {
return true;
}
var B0 = T0.element.bound;
if (B0 != null && isSubtypeOf(B0, T1)) {
return true;
}
}
if (T0 is FunctionTypeImpl) {
// Function Type/Function: `T0` is a function type and `T1` is `Function`.
if (T1.isDartCoreFunction) {
return true;
}
if (T1 is FunctionTypeImpl) {
return _isFunctionSubtypeOf(T0, T1);
}
}
if (T0 is RecordTypeImpl) {
// Record Type/Record: `T0` is a record type, and `T1` is `Record`.
if (T1.isDartCoreRecord) {
return true;
}
if (T1 is RecordTypeImpl) {
return _isRecordSubtypeOf(T0, T1);
}
}
return false;
}
bool _interfaceArguments(
InterfaceElement element,
InterfaceType subType,
InterfaceType superType,
) {
List<TypeParameterElement> parameters = element.typeParameters;
List<DartType> subArguments = subType.typeArguments;
List<DartType> superArguments = superType.typeArguments;
assert(subArguments.length == superArguments.length);
assert(parameters.length == subArguments.length);
for (int i = 0; i < subArguments.length; i++) {
var parameter = parameters[i] as TypeParameterElementImpl;
var subArgument = subArguments[i];
var superArgument = superArguments[i];
Variance variance = parameter.variance;
if (variance.isCovariant) {
if (!isSubtypeOf(subArgument, superArgument)) {
return false;
}
} else if (variance.isContravariant) {
if (!isSubtypeOf(superArgument, subArgument)) {
return false;
}
} else if (variance.isInvariant) {
if (!isSubtypeOf(subArgument, superArgument) ||
!isSubtypeOf(superArgument, subArgument)) {
return false;
}
} else {
throw StateError(
'Type parameter $parameter has unknown '
'variance $variance for subtype checking.',
);
}
}
return true;
}
/// Check that [f] is a subtype of [g].
bool _isFunctionSubtypeOf(FunctionType f, FunctionType g) {
var fresh = _typeSystem.relateTypeParameters(f.typeFormals, g.typeFormals);
if (fresh == null) {
return false;
}
f = f.instantiate(fresh.typeParameterTypes);
g = g.instantiate(fresh.typeParameterTypes);
if (!isSubtypeOf(f.returnType, g.returnType)) {
return false;
}
var fParameters = f.parameters;
var gParameters = g.parameters;
var fIndex = 0;
var gIndex = 0;
while (fIndex < fParameters.length && gIndex < gParameters.length) {
var fParameter = fParameters[fIndex];
var gParameter = gParameters[gIndex];
if (fParameter.isRequiredPositional) {
if (gParameter.isRequiredPositional) {
if (isSubtypeOf(gParameter.type, fParameter.type)) {
fIndex++;
gIndex++;
} else {
return false;
}
} else {
return false;
}
} else if (fParameter.isOptionalPositional) {
if (gParameter.isPositional) {
if (isSubtypeOf(gParameter.type, fParameter.type)) {
fIndex++;
gIndex++;
} else {
return false;
}
} else {
return false;
}
} else if (fParameter.isNamed) {
if (gParameter.isNamed) {
var compareNames = fParameter.name.compareTo(gParameter.name);
if (compareNames == 0) {
if (fParameter.isRequiredNamed && !gParameter.isRequiredNamed) {
return false;
} else if (isSubtypeOf(gParameter.type, fParameter.type)) {
fIndex++;
gIndex++;
} else {
return false;
}
} else if (compareNames < 0) {
if (fParameter.isRequiredNamed) {
return false;
} else {
fIndex++;
}
} else {
assert(compareNames > 0);
// The subtype must accept all parameters of the supertype.
return false;
}
} else {
break;
}
}
}
// The supertype must provide all required parameters to the subtype.
while (fIndex < fParameters.length) {
var fParameter = fParameters[fIndex++];
if (fParameter.isRequired) {
return false;
}
}
// The subtype must accept all parameters of the supertype.
assert(fIndex == fParameters.length);
if (gIndex < gParameters.length) {
return false;
}
return true;
}
bool _isInterfaceSubtypeOf(InterfaceType subType, InterfaceType superType) {
// Note: we should never reach `_isInterfaceSubtypeOf` with `i2 == Object`,
// because top types are eliminated before `isSubtypeOf` calls this.
// TODO(scheglov): Replace with assert().
if (identical(subType, superType) || superType.isDartCoreObject) {
return true;
}
// Object cannot subtype anything but itself (handled above).
if (subType.isDartCoreObject) {
return false;
}
var subElement = subType.element;
var superElement = superType.element;
if (identical(subElement, superElement)) {
return _interfaceArguments(superElement, subType, superType);
}
// Classes types cannot subtype `Function` or vice versa.
if (subType.isDartCoreFunction || superType.isDartCoreFunction) {
return false;
}
for (var interface in subElement.allSupertypes) {
if (identical(interface.element, superElement)) {
var substitution = Substitution.fromInterfaceType(subType);
var substitutedInterface =
substitution.substituteType(interface) as InterfaceType;
return _interfaceArguments(
superElement,
substitutedInterface,
superType,
);
}
}
return false;
}
/// Check that [subType] is a subtype of [superType].
bool _isRecordSubtypeOf(RecordType subType, RecordType superType) {
var subPositional = subType.positionalFields;
var superPositional = superType.positionalFields;
if (subPositional.length != superPositional.length) {
return false;
}
var subNamed = subType.namedFields;
var superNamed = superType.namedFields;
if (subNamed.length != superNamed.length) {
return false;
}
for (var i = 0; i < subPositional.length; i++) {
var subField = subPositional[i];
var superField = superPositional[i];
if (!isSubtypeOf(subField.type, superField.type)) {
return false;
}
}
for (var i = 0; i < subNamed.length; i++) {
var subField = subNamed[i];
var superField = superNamed[i];
if (subField.name != superField.name) {
return false;
}
if (!isSubtypeOf(subField.type, superField.type)) {
return false;
}
}
return true;
}
}