mirror of
https://github.com/dart-lang/sdk
synced 2024-10-04 19:29:35 +00:00
First subtyping rules for nullable types
Change-Id: Ibaaea75f8b5e83f1fb7039332093121854108da8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103548 Commit-Queue: Mike Fairhurst <mfairhurst@google.com> Reviewed-by: Paul Berry <paulberry@google.com>
This commit is contained in:
parent
19a8886a8a
commit
2268c65cbe
|
@ -349,8 +349,10 @@ class DeferredFunctionTypeImpl extends _FunctionTypeImplLazy {
|
|||
|
||||
DeferredFunctionTypeImpl(this._computeElement, String name,
|
||||
List<DartType> typeArguments, bool isInstantiated,
|
||||
{NullabilitySuffix nullabilitySuffix = NullabilitySuffix.star})
|
||||
: super._(null, name, null, typeArguments, null, null, isInstantiated,
|
||||
{NullabilitySuffix nullabilitySuffix = NullabilitySuffix.star,
|
||||
FunctionTypedElement computedElement})
|
||||
: _computedElement = computedElement,
|
||||
super._(null, name, null, typeArguments, null, null, isInstantiated,
|
||||
nullabilitySuffix: nullabilitySuffix);
|
||||
|
||||
@override
|
||||
|
@ -367,6 +369,7 @@ class DeferredFunctionTypeImpl extends _FunctionTypeImplLazy {
|
|||
if (this.nullabilitySuffix == nullabilitySuffix) return this;
|
||||
return DeferredFunctionTypeImpl(
|
||||
_computeElement, name, typeArguments, isInstantiated,
|
||||
computedElement: _computedElement,
|
||||
nullabilitySuffix: nullabilitySuffix);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,9 @@ int _getTopiness(DartType t) {
|
|||
|
||||
bool _isBottom(DartType t) {
|
||||
return t.isBottom ||
|
||||
t.isDartCoreNull ||
|
||||
// TODO(mfairhurst): Remove the exception treating Null* as Top.
|
||||
t.isDartCoreNull &&
|
||||
(t as TypeImpl).nullabilitySuffix == NullabilitySuffix.star ||
|
||||
identical(t, UnknownInferredType.instance);
|
||||
}
|
||||
|
||||
|
@ -60,7 +62,8 @@ bool _isTop(DartType t) {
|
|||
return _isTop((t as InterfaceType).typeArguments[0]);
|
||||
}
|
||||
return t.isDynamic ||
|
||||
t.isObject ||
|
||||
(t.isObject &&
|
||||
(t as TypeImpl).nullabilitySuffix != NullabilitySuffix.none) ||
|
||||
t.isVoid ||
|
||||
identical(t, UnknownInferredType.instance);
|
||||
}
|
||||
|
@ -559,12 +562,23 @@ class Dart2TypeSystem extends TypeSystem {
|
|||
p1.isCovariant && isSubtypeOf(p1.type, p2.type);
|
||||
}
|
||||
|
||||
/// Check if [_t1] is a subtype of [_t2].
|
||||
///
|
||||
/// Partially updated to reflect
|
||||
/// https://github.com/dart-lang/language/blob/da5adf7eb5f2d479069d8660ed7ca7b230098510/resources/type-system/subtyping.md
|
||||
///
|
||||
/// However, it does not correllate 1:1 and does not specialize Null vs Never
|
||||
/// cases. It also is not guaranteed to be exactly accurate vs the "spec"
|
||||
/// because it has slightly different order of operations. These should be
|
||||
/// brought in line or proven equivalent.
|
||||
@override
|
||||
bool isSubtypeOf(DartType t1, DartType t2) {
|
||||
bool isSubtypeOf(DartType _t1, DartType _t2) {
|
||||
var t1 = _t1 as TypeImpl;
|
||||
var t2 = _t2 as TypeImpl;
|
||||
|
||||
if (identical(t1, t2)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The types are void, dynamic, bottom, interface types, function types,
|
||||
// FutureOr<T> and type parameters.
|
||||
//
|
||||
|
@ -583,10 +597,36 @@ class Dart2TypeSystem extends TypeSystem {
|
|||
return false;
|
||||
}
|
||||
|
||||
// TODO(mfairhurst): Convert Null to Never?, to simplify the algorithm, and
|
||||
// remove this check.
|
||||
if (t1.isDartCoreNull) {
|
||||
if (t2.nullabilitySuffix != NullabilitySuffix.none) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle T1? <: T2
|
||||
if (t1.nullabilitySuffix == NullabilitySuffix.question) {
|
||||
if (t2.nullabilitySuffix == NullabilitySuffix.none) {
|
||||
// If T2 is not FutureOr<S2>, then subtype is false.
|
||||
if (!t2.isDartAsyncFutureOr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// T1? <: FutureOr<T2> is true if T2 is S2?.
|
||||
// TODO(mfairhurst): handle T1? <: FutureOr<dynamic>, etc.
|
||||
if (t2 is InterfaceTypeImpl &&
|
||||
(t2.typeArguments[0] as TypeImpl).nullabilitySuffix ==
|
||||
NullabilitySuffix.none) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle FutureOr<T> union type.
|
||||
if (t1 is InterfaceType && t1.isDartAsyncFutureOr) {
|
||||
if (t1 is InterfaceTypeImpl && t1.isDartAsyncFutureOr) {
|
||||
var t1TypeArg = t1.typeArguments[0];
|
||||
if (t2 is InterfaceType && t2.isDartAsyncFutureOr) {
|
||||
if (t2 is InterfaceTypeImpl && t2.isDartAsyncFutureOr) {
|
||||
var t2TypeArg = t2.typeArguments[0];
|
||||
// FutureOr<A> <: FutureOr<B> iff A <: B
|
||||
return isSubtypeOf(t1TypeArg, t2TypeArg);
|
||||
|
@ -598,7 +638,7 @@ class Dart2TypeSystem extends TypeSystem {
|
|||
return isSubtypeOf(t1Future, t2) && isSubtypeOf(t1TypeArg, t2);
|
||||
}
|
||||
|
||||
if (t2 is InterfaceType && t2.isDartAsyncFutureOr) {
|
||||
if (t2 is InterfaceTypeImpl && t2.isDartAsyncFutureOr) {
|
||||
// given t2 is Future<A> | A, then:
|
||||
// t1 <: (Future<A> | A) iff t1 <: Future<A> or t1 <: A
|
||||
var t2TypeArg = t2.typeArguments[0];
|
||||
|
@ -610,23 +650,26 @@ class Dart2TypeSystem extends TypeSystem {
|
|||
// T is not dynamic or object (handled above)
|
||||
// True if T == S
|
||||
// Or true if bound of S is S' and S' <: T
|
||||
if (t1 is TypeParameterType) {
|
||||
if (t2 is TypeParameterType &&
|
||||
if (t1 is TypeParameterTypeImpl) {
|
||||
if (t2 is TypeParameterTypeImpl &&
|
||||
t1.definition == t2.definition &&
|
||||
_typeParameterBoundsSubtype(t1.bound, t2.bound, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DartType bound = t1.element.bound;
|
||||
return bound == null
|
||||
? false
|
||||
: _typeParameterBoundsSubtype(bound, t2, false);
|
||||
}
|
||||
|
||||
if (t2 is TypeParameterType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We've eliminated void, dynamic, bottom, type parameters, and FutureOr.
|
||||
// The only cases are the combinations of interface type and function type.
|
||||
// We've eliminated void, dynamic, bottom, type parameters, FutureOr,
|
||||
// nullable, and legacy nullable types. The only cases are the combinations
|
||||
// of interface type and function type.
|
||||
|
||||
// A function type can only subtype an interface type if
|
||||
// the interface type is Function
|
||||
|
@ -637,11 +680,11 @@ class Dart2TypeSystem extends TypeSystem {
|
|||
if (t1 is InterfaceType && t2 is FunctionType) return false;
|
||||
|
||||
// Two interface types
|
||||
if (t1 is InterfaceType && t2 is InterfaceType) {
|
||||
if (t1 is InterfaceTypeImpl && t2 is InterfaceTypeImpl) {
|
||||
return _isInterfaceSubtypeOf(t1, t2, null);
|
||||
}
|
||||
|
||||
return _isFunctionSubtypeOf(t1, t2);
|
||||
return _isFunctionSubtypeOf(t1 as FunctionType, t2 as FunctionType);
|
||||
}
|
||||
|
||||
/// Given a [type] T that may have an unknown type `?`, returns a type
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
// Tests related to the [TypeSystem] class.
|
||||
|
||||
import 'package:analyzer/dart/analysis/features.dart';
|
||||
import 'package:analyzer/dart/ast/standard_ast_factory.dart' show astFactory;
|
||||
import 'package:analyzer/dart/ast/token.dart' show Keyword;
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
|
@ -32,6 +33,7 @@ main() {
|
|||
defineReflectiveTests(ConstraintMatchingTest);
|
||||
defineReflectiveTests(StrongAssignabilityTest);
|
||||
defineReflectiveTests(StrongSubtypingTest);
|
||||
defineReflectiveTests(NonNullableSubtypingTest);
|
||||
defineReflectiveTests(StrongGenericFunctionInferenceTest);
|
||||
defineReflectiveTests(StrongLeastUpperBoundTest);
|
||||
defineReflectiveTests(StrongGreatestLowerBoundTest);
|
||||
|
@ -78,6 +80,7 @@ abstract class BoundTestBase {
|
|||
InterfaceType get intType => typeProvider.intType;
|
||||
InterfaceType get iterableType => typeProvider.iterableType;
|
||||
InterfaceType get listType => typeProvider.listType;
|
||||
InterfaceType get nullType => typeProvider.nullType;
|
||||
InterfaceType get numType => typeProvider.numType;
|
||||
InterfaceType get objectType => typeProvider.objectType;
|
||||
InterfaceType get stringType => typeProvider.stringType;
|
||||
|
@ -849,6 +852,94 @@ abstract class LeastUpperBoundTestBase extends BoundTestBase {
|
|||
}
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class NonNullableSubtypingTest extends StrongSubtypingTestBase {
|
||||
@override
|
||||
void setUp() {
|
||||
typeProvider = AnalysisContextFactory.contextWithCoreAndOptions(
|
||||
new AnalysisOptionsImpl()
|
||||
..contextFeatures = FeatureSet.forTesting(
|
||||
additionalFeatures: [Feature.non_nullable]),
|
||||
resourceProvider: new MemoryResourceProvider())
|
||||
.typeProvider;
|
||||
|
||||
// TypeSystem should use the context type provider.
|
||||
typeSystem = new Dart2TypeSystem(typeProvider);
|
||||
|
||||
LibraryElement coreLibrary = typeProvider.objectType.element.library;
|
||||
LibraryElement asyncLibrary = typeProvider.streamType.element.library;
|
||||
|
||||
// Get a non-nullable type provider for convience during the test.
|
||||
typeProvider = new NonNullableTypeProvider(coreLibrary, asyncLibrary);
|
||||
}
|
||||
|
||||
void test_int_nullableTypes() {
|
||||
List<DartType> equivalents = <DartType>[
|
||||
intType,
|
||||
_star(intType),
|
||||
];
|
||||
List<DartType> supertypes = <DartType>[
|
||||
_question(intType),
|
||||
objectType,
|
||||
_question(objectType),
|
||||
];
|
||||
List<DartType> unrelated = <DartType>[doubleType, nullType];
|
||||
_checkGroups(intType,
|
||||
equivalents: equivalents, supertypes: supertypes, unrelated: unrelated);
|
||||
}
|
||||
|
||||
void test_intQuestion_nullableTypes() {
|
||||
List<DartType> equivalents = <DartType>[
|
||||
_question(intType),
|
||||
_star(intType),
|
||||
];
|
||||
List<DartType> subtypes = <DartType>[
|
||||
intType,
|
||||
nullType,
|
||||
];
|
||||
List<DartType> supertypes = <DartType>[
|
||||
_question(numType),
|
||||
_star(numType),
|
||||
_question(objectType),
|
||||
_star(objectType),
|
||||
];
|
||||
List<DartType> unrelated = <DartType>[doubleType, numType, objectType];
|
||||
_checkGroups(_question(intType),
|
||||
equivalents: equivalents,
|
||||
supertypes: supertypes,
|
||||
unrelated: unrelated,
|
||||
subtypes: subtypes);
|
||||
}
|
||||
|
||||
void test_intStar_nullableTypes() {
|
||||
List<DartType> equivalents = <DartType>[
|
||||
intType,
|
||||
_question(intType),
|
||||
_star(intType),
|
||||
];
|
||||
List<DartType> subtypes = <DartType>[nullType];
|
||||
List<DartType> supertypes = <DartType>[
|
||||
numType,
|
||||
_question(numType),
|
||||
_star(numType),
|
||||
objectType,
|
||||
_question(objectType),
|
||||
];
|
||||
List<DartType> unrelated = <DartType>[doubleType];
|
||||
_checkGroups(_star(intType),
|
||||
equivalents: equivalents,
|
||||
supertypes: supertypes,
|
||||
unrelated: unrelated,
|
||||
subtypes: subtypes);
|
||||
}
|
||||
|
||||
DartType _question(DartType dartType) =>
|
||||
(dartType as TypeImpl).withNullability(NullabilitySuffix.question);
|
||||
|
||||
DartType _star(DartType dartType) =>
|
||||
(dartType as TypeImpl).withNullability(NullabilitySuffix.star);
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class StrongAssignabilityTest extends AbstractTypeSystemTest {
|
||||
void test_isAssignableTo_bottom_isBottom() {
|
||||
|
@ -1852,29 +1943,7 @@ class StrongLeastUpperBoundTest extends LeastUpperBoundTestBase {
|
|||
}
|
||||
|
||||
@reflectiveTest
|
||||
class StrongSubtypingTest {
|
||||
TypeProvider typeProvider;
|
||||
TypeSystem typeSystem;
|
||||
|
||||
DartType get bottomType => typeProvider.bottomType;
|
||||
InterfaceType get doubleType => typeProvider.doubleType;
|
||||
DartType get dynamicType => typeProvider.dynamicType;
|
||||
InterfaceType get functionType => typeProvider.functionType;
|
||||
InterfaceType get futureOrType => typeProvider.futureOrType;
|
||||
InterfaceType get intType => typeProvider.intType;
|
||||
InterfaceType get listType => typeProvider.listType;
|
||||
InterfaceType get numType => typeProvider.numType;
|
||||
InterfaceType get objectType => typeProvider.objectType;
|
||||
InterfaceType get stringType => typeProvider.stringType;
|
||||
DartType get voidType => VoidTypeImpl.instance;
|
||||
|
||||
void setUp() {
|
||||
typeProvider = AnalysisContextFactory.contextWithCore(
|
||||
resourceProvider: new MemoryResourceProvider())
|
||||
.typeProvider;
|
||||
typeSystem = new Dart2TypeSystem(typeProvider);
|
||||
}
|
||||
|
||||
class StrongSubtypingTest extends StrongSubtypingTestBase {
|
||||
void test_bottom_isBottom() {
|
||||
DartType interfaceType = ElementFactory.classElement2('A', []).type;
|
||||
List<DartType> equivalents = <DartType>[bottomType];
|
||||
|
@ -2184,6 +2253,31 @@ class StrongSubtypingTest {
|
|||
];
|
||||
_checkGroups(voidType, equivalents: equivalents, subtypes: subtypes);
|
||||
}
|
||||
}
|
||||
|
||||
class StrongSubtypingTestBase {
|
||||
TypeProvider typeProvider;
|
||||
TypeSystem typeSystem;
|
||||
|
||||
DartType get bottomType => typeProvider.bottomType;
|
||||
InterfaceType get doubleType => typeProvider.doubleType;
|
||||
DartType get dynamicType => typeProvider.dynamicType;
|
||||
InterfaceType get functionType => typeProvider.functionType;
|
||||
InterfaceType get futureOrType => typeProvider.futureOrType;
|
||||
InterfaceType get intType => typeProvider.intType;
|
||||
InterfaceType get listType => typeProvider.listType;
|
||||
DartType get nullType => typeProvider.nullType;
|
||||
InterfaceType get numType => typeProvider.numType;
|
||||
InterfaceType get objectType => typeProvider.objectType;
|
||||
InterfaceType get stringType => typeProvider.stringType;
|
||||
DartType get voidType => VoidTypeImpl.instance;
|
||||
|
||||
void setUp() {
|
||||
typeProvider = AnalysisContextFactory.contextWithCore(
|
||||
resourceProvider: new MemoryResourceProvider())
|
||||
.typeProvider;
|
||||
typeSystem = new Dart2TypeSystem(typeProvider);
|
||||
}
|
||||
|
||||
void _checkEquivalent(DartType type1, DartType type2) {
|
||||
_checkIsSubtypeOf(type1, type2);
|
||||
|
@ -2218,7 +2312,8 @@ class StrongSubtypingTest {
|
|||
}
|
||||
|
||||
void _checkIsNotSubtypeOf(DartType type1, DartType type2) {
|
||||
expect(typeSystem.isSubtypeOf(type1, type2), false);
|
||||
expect(typeSystem.isSubtypeOf(type1, type2), false,
|
||||
reason: '$type1 was not supposed to be a subtype of $type2');
|
||||
}
|
||||
|
||||
void _checkIsStrictSubtypeOf(DartType type1, DartType type2) {
|
||||
|
@ -2227,7 +2322,8 @@ class StrongSubtypingTest {
|
|||
}
|
||||
|
||||
void _checkIsSubtypeOf(DartType type1, DartType type2) {
|
||||
expect(typeSystem.isSubtypeOf(type1, type2), true);
|
||||
expect(typeSystem.isSubtypeOf(type1, type2), true,
|
||||
reason: '$type1 is not a subtype of $type2');
|
||||
}
|
||||
|
||||
void _checkLattice(
|
||||
|
|
Loading…
Reference in a new issue