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:
Mike Fairhurst 2019-05-29 22:13:11 +00:00 committed by commit-bot@chromium.org
parent 19a8886a8a
commit 2268c65cbe
3 changed files with 182 additions and 40 deletions

View file

@ -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);
}
}

View file

@ -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

View file

@ -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(