[cfe] Use computeRawType in Null Safe UP and DOWN

Closes #43536.

Bug: https://github.com/dart-lang/sdk/issues/43536
Change-Id: I6616739e7ac0eb0be084988cef0460e916a3b448
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/165804
Commit-Queue: Dmitry Stefantsov <dmitryas@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Dmitry Stefantsov 2020-10-07 08:40:34 +00:00 committed by commit-bot@chromium.org
parent 5a442b4f62
commit 3b4fe0dc06
12 changed files with 243 additions and 131 deletions

View file

@ -296,105 +296,6 @@ abstract class TypeConstraintGatherer {
return type == coreTypes.nullType;
}
/// Computes [type] as if declared without nullability markers.
///
/// The algorithm implemented by [_isNullabilityAwareSubtypeMatch] uses the
/// notion of the nullable and the legacy type constructors, that is, int? and
/// int* are applications of the nullable and the legacy type constructors to
/// type int correspondingly. The algorithm requires the ability to peel off
/// the two type constructors from the underlying types, which is done by
/// [_computeRawType].
DartType _computeRawType(DartType type) {
if (type is TypeParameterType) {
if (type.promotedBound == null) {
// The default nullability for library is used when there are no
// nullability markers on the type.
return new TypeParameterType.withDefaultNullabilityForLibrary(
type.parameter, _currentLibrary);
} else {
// Intersection types can't be arguments to the nullable and the legacy
// type constructors, so nothing can be peeled off.
return type;
}
} else if (type == coreTypes.nullType) {
return type;
} else {
// For most types, peeling off the nullability constructors means that
// they become non-nullable.
return type.withDeclaredNullability(Nullability.nonNullable);
}
}
/// Returns true if [type] is declared without nullability markers.
///
/// The algorithm implemented by [_isNullabilityAwareSubtypeMatch] uses the
/// notion of the nullable and the legacy type constructors, that is, int? and
/// int* are applications of the nullable and the legacy type constructors to
/// type int correspondingly. The algorithm requires the ability to detect if
/// a type is raw, that is, if it is declared without any of the two
/// constructors. Method [_isRawTypeParameterType] implements that check for
/// [TypeParameterType]s. For most types, comparing their
/// [DartType.declaredNullability] is enough, but the case of
/// [TypeParameterType]s is more complex for two reasons: (1) intersection
/// types are encoded as [TypeParameterType]s, and they can't be arguments of
/// the nullability constructors, and (2) if a [TypeParameterType] is declared
/// raw, its semantic nullability can be anything from
/// [Nullability.nonNullable], [Nullability.undetermined], and
/// [Nullability.legacy], depending on the bound. [_isRawTypeParameterType]
/// checks if [type] has the same nullability as if it was declared without
/// any nullability markers by the programmer.
bool _isRawTypeParameterType(TypeParameterType type) {
// The default nullability for library is used when there are no nullability
// markers on the type.
return type.promotedBound == null &&
type.declaredNullability ==
new TypeParameterType.withDefaultNullabilityForLibrary(
type.parameter, _currentLibrary)
.declaredNullability;
}
/// Returns true if [type] is an application of the nullable type constructor.
///
/// The algorithm implemented by [_isNullabilityAwareSubtypeMatch] uses the
/// notion of the nullable type constructor, that is, int? is an application
/// of the nullable type constructor to type int. The algorithm requires the
/// ability to detect if a type is an application of the nullable constructor
/// to another type, which is done by [_isNullableTypeConstructorApplication].
bool _isNullableTypeConstructorApplication(DartType type) {
return type.declaredNullability == Nullability.nullable &&
type is! DynamicType &&
type is! VoidType &&
type != coreTypes.nullType;
}
/// Returns true if [type] is an application of the legacy type constructor.
///
/// The algorithm implemented by [_isNullabilityAwareSubtypeMatch] uses the
/// notion of the legacy type constructor, that is, int* is an application of
/// the legacy type constructor to type int. The algorithm requires the
/// ability to detect if a type is an application of the nullable constructor
/// to another type, which is done by [_isNullableTypeConstructorApplication].
bool _isLegacyTypeConstructorApplication(DartType type) {
if (type is TypeParameterType) {
if (type.promotedBound == null) {
// The legacy nullability is considered an application of the legacy
// nullability constructor if it doesn't match the default nullability
// of the type-parameter type for the library.
return type.declaredNullability == Nullability.legacy &&
type.declaredNullability !=
new TypeParameterType.withDefaultNullabilityForLibrary(
type.parameter, _currentLibrary)
.declaredNullability;
} else {
return false;
}
} else if (type is InvalidType) {
return false;
} else {
return type.declaredNullability == Nullability.legacy;
}
}
/// Matches [p] against [q] as a subtype against supertype.
///
/// Returns true if [p] is a subtype of [q] under some constraints, and false
@ -473,7 +374,7 @@ abstract class TypeConstraintGatherer {
//
// Under constraint _ <: X <: Q.
if (p is TypeParameterType &&
_isRawTypeParameterType(p) &&
isTypeParameterTypeWithoutNullabilityMarker(p, _currentLibrary) &&
_parametersToConstrain.contains(p.parameter)) {
_constrainParameterUpper(p.parameter, q);
return true;
@ -483,7 +384,7 @@ abstract class TypeConstraintGatherer {
//
// Under constraint P <: X <: _.
if (q is TypeParameterType &&
_isRawTypeParameterType(q) &&
isTypeParameterTypeWithoutNullabilityMarker(q, _currentLibrary) &&
_parametersToConstrain.contains(q.parameter)) {
_constrainParameterLower(q.parameter, p);
return true;
@ -502,8 +403,11 @@ abstract class TypeConstraintGatherer {
// If P is a legacy type P0* then the match holds under constraint set C:
//
// Only if P0 is a subtype match for Q under constraint set C.
if (_isLegacyTypeConstructorApplication(p)) {
return _isNullabilityAwareSubtypeMatch(_computeRawType(p), q,
if (isLegacyTypeConstructorApplication(p, _currentLibrary)) {
return _isNullabilityAwareSubtypeMatch(
computeTypeWithoutNullabilityMarker(p, _currentLibrary,
nullType: coreTypes.nullType),
q,
constrainSupertype: constrainSupertype);
}
@ -513,11 +417,14 @@ abstract class TypeConstraintGatherer {
// set C.
// Or if P is not dynamic or void and P is a subtype match for Q0? under
// constraint set C.
if (_isLegacyTypeConstructorApplication(q)) {
if (isLegacyTypeConstructorApplication(q, _currentLibrary)) {
final int baseConstraintCount = _protoConstraints.length;
if ((p is DynamicType || p is VoidType) &&
_isNullabilityAwareSubtypeMatch(p, _computeRawType(q),
_isNullabilityAwareSubtypeMatch(
p,
computeTypeWithoutNullabilityMarker(q, _currentLibrary,
nullType: coreTypes.nullType),
constrainSupertype: constrainSupertype)) {
return true;
}
@ -580,12 +487,17 @@ abstract class TypeConstraintGatherer {
// Or if P is a subtype match for Q0 under non-empty constraint set C.
// Or if P is a subtype match for Null under constraint set C.
// Or if P is a subtype match for Q0 under empty constraint set C.
if (_isNullableTypeConstructorApplication(q)) {
if (isNullableTypeConstructorApplication(q, nullType: coreTypes.nullType)) {
final int baseConstraintCount = _protoConstraints.length;
final DartType rawP = _computeRawType(p);
final DartType rawQ = _computeRawType(q);
final DartType rawP = computeTypeWithoutNullabilityMarker(
p, _currentLibrary,
nullType: coreTypes.nullType);
final DartType rawQ = computeTypeWithoutNullabilityMarker(
q, _currentLibrary,
nullType: coreTypes.nullType);
if (_isNullableTypeConstructorApplication(p) &&
if (isNullableTypeConstructorApplication(p,
nullType: coreTypes.nullType) &&
_isNullabilityAwareSubtypeMatch(rawP, rawQ,
constrainSupertype: constrainSupertype)) {
return true;
@ -641,9 +553,12 @@ abstract class TypeConstraintGatherer {
//
// If P0 is a subtype match for Q under constraint set C1.
// And if Null is a subtype match for Q under constraint set C2.
if (_isNullableTypeConstructorApplication(p)) {
if (isNullableTypeConstructorApplication(p, nullType: coreTypes.nullType)) {
final int baseConstraintCount = _protoConstraints.length;
if (_isNullabilityAwareSubtypeMatch(_computeRawType(p), q,
if (_isNullabilityAwareSubtypeMatch(
computeTypeWithoutNullabilityMarker(p, _currentLibrary,
nullType: coreTypes.nullType),
q,
constrainSupertype: constrainSupertype) &&
_isNullabilityAwareSubtypeMatch(coreTypes.nullType, q,
constrainSupertype: constrainSupertype)) {

View file

@ -1069,6 +1069,7 @@ executed
executes
executing
execution
exempt
exercise
exhaustive
exist
@ -2130,6 +2131,7 @@ peek
peel
peeled
peeling
peels
pending
percent
percolate

View file

@ -0,0 +1,13 @@
// 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.
class C<T> {
foo<E extends T>(List<E> list) {
List<E> variable = method(list);
}
List<F> method<F extends T>(List<F> list) => list;
}
main() {}

View file

@ -0,0 +1,14 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
class C<T extends core::Object? = dynamic> extends core::Object {
synthetic constructor •() → self::C<self::C::T%>
;
method foo<generic-covariant-impl E extends self::C::T% = self::C::T%>(core::List<self::C::foo::E%> list) → dynamic
;
method method<generic-covariant-impl F extends self::C::T% = self::C::T%>(core::List<self::C::method::F%> list) → core::List<self::C::method::F%>
;
}
static method main() → dynamic
;

View file

@ -0,0 +1,15 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
class C<T extends core::Object? = dynamic> extends core::Object {
synthetic constructor •() → self::C<self::C::T%>
: super core::Object::•()
;
method foo<generic-covariant-impl E extends self::C::T% = self::C::T%>(core::List<self::C::foo::E%> list) → dynamic {
core::List<self::C::foo::E%> variable = this.{self::C::method}<self::C::foo::E%>(list);
}
method method<generic-covariant-impl F extends self::C::T% = self::C::T%>(core::List<self::C::method::F%> list) → core::List<self::C::method::F%>
return list;
}
static method main() → dynamic {}

View file

@ -0,0 +1,15 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
class C<T extends core::Object? = dynamic> extends core::Object {
synthetic constructor •() → self::C<self::C::T%>
: super core::Object::•()
;
method foo<generic-covariant-impl E extends self::C::T% = self::C::T%>(core::List<self::C::foo::E%> list) → dynamic {
core::List<self::C::foo::E%> variable = this.{self::C::method}<self::C::foo::E%>(list);
}
method method<generic-covariant-impl F extends self::C::T% = self::C::T%>(core::List<self::C::method::F%> list) → core::List<self::C::method::F%>
return list;
}
static method main() → dynamic {}

View file

@ -0,0 +1,6 @@
class C<T> {
foo<E extends T>(List<E> list) {}
List<F> method<F extends T>(List<F> list) => list;
}
main() {}

View file

@ -0,0 +1,6 @@
class C<T> {
List<F> method<F extends T>(List<F> list) => list;
foo<E extends T>(List<E> list) {}
}
main() {}

View file

@ -0,0 +1,15 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
class C<T extends core::Object? = dynamic> extends core::Object {
synthetic constructor •() → self::C<self::C::T%>
: super core::Object::•()
;
method foo<generic-covariant-impl E extends self::C::T% = self::C::T%>(core::List<self::C::foo::E%> list) → dynamic {
core::List<self::C::foo::E%> variable = this.{self::C::method}<self::C::foo::E%>(list);
}
method method<generic-covariant-impl F extends self::C::T% = self::C::T%>(core::List<self::C::method::F%> list) → core::List<self::C::method::F%>
return list;
}
static method main() → dynamic {}

View file

@ -0,0 +1,15 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
class C<T extends core::Object? = dynamic> extends core::Object {
synthetic constructor •() → self::C<self::C::T%>
: super core::Object::•()
;
method foo<generic-covariant-impl E extends self::C::T% = self::C::T%>(core::List<self::C::foo::E%> list) → dynamic {
core::List<self::C::foo::E%> variable = this.{self::C::method}<self::C::foo::E%>(list);
}
method method<generic-covariant-impl F extends self::C::T% = self::C::T%>(core::List<self::C::method::F%> list) → core::List<self::C::method::F%>
return list;
}
static method main() → dynamic {}

View file

@ -396,17 +396,19 @@ mixin StandardBounds {
// without using the nullability of the outermost type. The result uses
// [intersectNullabilities] to compute the resulting type if the subtype
// relation is established.
DartType nonNullableType1 =
type1.withDeclaredNullability(Nullability.nonNullable);
DartType nonNullableType2 =
type2.withDeclaredNullability(Nullability.nonNullable);
if (isSubtypeOf(nonNullableType1, nonNullableType2,
SubtypeCheckMode.withNullabilities)) {
DartType typeWithoutNullabilityMarker1 =
computeTypeWithoutNullabilityMarker(type1, clientLibrary,
nullType: coreTypes.nullType);
DartType typeWithoutNullabilityMarker2 =
computeTypeWithoutNullabilityMarker(type2, clientLibrary,
nullType: coreTypes.nullType);
if (isSubtypeOf(typeWithoutNullabilityMarker1,
typeWithoutNullabilityMarker2, SubtypeCheckMode.withNullabilities)) {
return type1.withDeclaredNullability(intersectNullabilities(
type1.declaredNullability, type2.declaredNullability));
}
if (isSubtypeOf(nonNullableType2, nonNullableType1,
SubtypeCheckMode.withNullabilities)) {
if (isSubtypeOf(typeWithoutNullabilityMarker2,
typeWithoutNullabilityMarker1, SubtypeCheckMode.withNullabilities)) {
return type2.withDeclaredNullability(intersectNullabilities(
type1.declaredNullability, type2.declaredNullability));
}
@ -753,21 +755,23 @@ mixin StandardBounds {
// 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.
InterfaceType nonNonNullableType1 =
type1.withDeclaredNullability(Nullability.nonNullable);
InterfaceType nonNonNullableType2 =
type2.withDeclaredNullability(Nullability.nonNullable);
InterfaceType typeWithoutNullabilityMarker1 =
computeTypeWithoutNullabilityMarker(type1, clientLibrary,
nullType: coreTypes.nullType);
InterfaceType typeWithoutNullabilityMarker2 =
computeTypeWithoutNullabilityMarker(type2, clientLibrary,
nullType: coreTypes.nullType);
if (isSubtypeOf(nonNonNullableType1, nonNonNullableType2,
SubtypeCheckMode.withNullabilities)) {
if (isSubtypeOf(typeWithoutNullabilityMarker1,
typeWithoutNullabilityMarker2, SubtypeCheckMode.withNullabilities)) {
return type2.withDeclaredNullability(
uniteNullabilities(type1.nullability, type2.nullability));
}
// UP(T1, T2) = T1 if T2 <: T1
// Note that both types must be class types at this point.
if (isSubtypeOf(nonNonNullableType2, nonNonNullableType1,
SubtypeCheckMode.withNullabilities)) {
if (isSubtypeOf(typeWithoutNullabilityMarker2,
typeWithoutNullabilityMarker1, SubtypeCheckMode.withNullabilities)) {
return type1.withDeclaredNullability(uniteNullabilities(
type1.declaredNullability, type2.declaredNullability));
}

View file

@ -1035,16 +1035,18 @@ DartType unwrapNullabilityConstructor(DartType type, CoreTypes coreTypes) {
/// Implementation of [unwrapNullabilityConstructor] as a visitor.
///
/// Implementing the function as a visitor makes the necessity of supporting a new implementation of [DartType] visible at compile time.
// TODO(dmitryas): Remove CoreTypes as the second argument when NullType is landed.
/// Implementing the function as a visitor makes the necessity of supporting a
/// new implementation of [DartType] visible at compile time.
// TODO(dmitryas): Remove CoreTypes as the second argument when NullType is
// landed.
class _NullabilityConstructorUnwrapper
implements DartTypeVisitor1<DartType, CoreTypes> {
const _NullabilityConstructorUnwrapper();
@override
DartType defaultDartType(DartType node, CoreTypes coreTypes) {
throw new UnsupportedError(
"Unsupported operation: _NullabilityConstructorUnwrapper(${node.runtimeType})");
throw new UnsupportedError("Unsupported operation: "
"_NullabilityConstructorUnwrapper(${node.runtimeType})");
}
@override
@ -1185,3 +1187,93 @@ class NullabilityAwareTypeVariableEliminator extends ReplacementVisitor {
return super.visitTypeParameterType(node);
}
}
/// Computes [type] as if declared without nullability markers.
///
/// For example, int? and int* are considered applications of the nullable and
/// the legacy type constructors to type int correspondingly.
/// [computeTypeWithoutNullabilityMarker] peels off these type constructors,
/// returning the non-nullable version of type int. In case of
/// [TypeParameterType]s, the result may be either [Nullability.nonNullable] or
/// [Nullability.undetermined], depending on the bound.
DartType computeTypeWithoutNullabilityMarker(
DartType type, Library clientLibrary,
{DartType nullType}) {
assert(nullType != null);
if (type is TypeParameterType) {
if (type.promotedBound == null) {
// The default nullability for library is used when there are no
// nullability markers on the type.
return new TypeParameterType.withDefaultNullabilityForLibrary(
type.parameter, clientLibrary);
} else {
// Intersection types can't be arguments to the nullable and the legacy
// type constructors, so nothing can be peeled off.
return type;
}
} else if (type == nullType) {
return type;
} else {
// For most types, peeling off the nullability constructors means that
// they become non-nullable.
return type.withDeclaredNullability(Nullability.nonNullable);
}
}
/// Returns true if [type] is declared without nullability markers.
///
/// An example of the nullable type constructor application is T? where T is a
/// type parameter. Some examples of types declared without nullability markers
/// are T% and S, where T and S are type parameters such that T extends Object?
/// and S extends Object.
bool isTypeParameterTypeWithoutNullabilityMarker(
TypeParameterType type, Library clientLibrary) {
// The default nullability for library is used when there are no nullability
// markers on the type.
return type.promotedBound == null &&
type.declaredNullability ==
new TypeParameterType.withDefaultNullabilityForLibrary(
type.parameter, clientLibrary)
.declaredNullability;
}
/// Returns true if [type] is an application of the nullable type constructor.
///
/// A type is considered an application of the nullable type constructor if it
/// was declared with the ? marker. Some examples of such types are int?,
/// String?, Object?, and T? where T is a type parameter. Types dynamic, void,
/// and Null are nullable, but aren't considered applications of the nullable
/// type constructor.
bool isNullableTypeConstructorApplication(DartType type, {DartType nullType}) {
assert(nullType != null);
return type.declaredNullability == Nullability.nullable &&
type is! DynamicType &&
type is! VoidType &&
type != nullType;
}
/// Returns true if [type] is an application of the legacy type constructor.
///
/// A type is considered an application of the legacy type constructor if it was
/// declared within a legacy library and is not one of exempt types, such as
/// dynamic or void.
bool isLegacyTypeConstructorApplication(DartType type, Library clientLibrary) {
if (type is TypeParameterType) {
if (type.promotedBound == null) {
// The legacy nullability is considered an application of the legacy
// nullability constructor if it doesn't match the default nullability
// of the type-parameter type for the library.
return type.declaredNullability == Nullability.legacy &&
type.declaredNullability !=
new TypeParameterType.withDefaultNullabilityForLibrary(
type.parameter, clientLibrary)
.declaredNullability;
} else {
return false;
}
} else if (type is InvalidType) {
return false;
} else {
return type.declaredNullability == Nullability.legacy;
}
}