[cfe] Adjust type schema closures in UP and DOWN as per spec

See breaking change request
https://github.com/dart-lang/sdk/issues/56466

Closes https://github.com/dart-lang/sdk/issues/56466

Change-Id: I7b04821da92b8b41cb8610e701fa7e7905ff2487
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/375121
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
This commit is contained in:
Chloe Stefantsova
2024-09-02 11:47:49 +00:00
committed by Commit Queue
parent fe88cdb5aa
commit b6b375b2cb
10 changed files with 139 additions and 26 deletions
+11
View File
@@ -58,6 +58,17 @@ number, like `.`, `x`, or the `e` in scientific notation.
### Tools
#### CFE
- **Breaking Change** [#56065][]: The implementation of the UP and
DOWN algorithms in the CFE are changed to match the specification
and the corresponding implementations in the Analyzer. The upper and
lower closures of type schemas are now computed just before they are
passed into the subtype testing procedure instead of at the very
beginning of the UP and DOWN algorithms.
[#56065]: https://github.com/dart-lang/sdk/issues/56065
#### Wasm compiler (dart2wasm)
- The condition `dart.library.js` is now false on conditional imports in
@@ -9,18 +9,28 @@ import 'type_schema.dart' show UnknownType;
import 'type_schema_elimination.dart';
mixin TypeSchemaStandardBounds on StandardBounds {
@override
DartType greatestClosureForLowerBound(DartType typeSchema) {
// - We replace all uses of `T1 <: T2` in the `DOWN` algorithm by `S1 <:
// S2` where `Si` is the greatest closure of `Ti` with respect to `_`.
return greatestClosure(typeSchema, coreTypes.objectNullableRawType,
const NeverType.nonNullable());
}
@override
DartType leastClosureForUpperBound(DartType typeSchema) {
// - We replace all uses of `T1 <: T2` in the `UP` algorithm by `S1 <: S2`
// where `Si` is the least closure of `Ti` with respect to `_`.
return leastClosure(typeSchema, coreTypes.objectNullableRawType,
const NeverType.nonNullable());
}
@override
DartType getNullabilityAwareStandardLowerBoundInternal(
DartType type1, DartType type2) {
// - We add the axiom that `DOWN(T, _) == T` and the symmetric version.
// - We replace all uses of `T1 <: T2` in the `DOWN` algorithm by `S1 <:
// S2` where `Si` is the greatest closure of `Ti` with respect to `_`.
if (type1 is UnknownType) return type2;
if (type2 is UnknownType) return type1;
type1 = greatestClosure(
type1, coreTypes.objectNullableRawType, const NeverType.nonNullable());
type2 = greatestClosure(
type2, coreTypes.objectNullableRawType, const NeverType.nonNullable());
return super.getNullabilityAwareStandardLowerBoundInternal(type1, type2);
}
@@ -43,14 +53,9 @@ mixin TypeSchemaStandardBounds on StandardBounds {
DartType getNullabilityAwareStandardUpperBoundInternal(
DartType type1, DartType type2) {
// - We add the axiom that `UP(T, _) == T` and the symmetric version.
// - We replace all uses of `T1 <: T2` in the `UP` algorithm by `S1 <: S2`
// where `Si` is the least closure of `Ti` with respect to `_`.
if (type1 is UnknownType) return type2;
if (type2 is UnknownType) return type1;
type1 = leastClosure(
type1, coreTypes.objectNullableRawType, const NeverType.nonNullable());
type2 = leastClosure(
type2, coreTypes.objectNullableRawType, const NeverType.nonNullable());
return super.getNullabilityAwareStandardUpperBoundInternal(type1, type2);
}
@@ -0,0 +1,11 @@
// Copyright (c) 2024, 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 A<X extends A<X>> {
A(X x);
}
test<Y extends A<Y>>(Y y) {
A<Object?> a = new A(y);
}
@@ -0,0 +1,12 @@
library;
import self as self;
import "dart:core" as core;
class A<X extends self::A<self::A::X> = self::A<dynamic>> extends core::Object {
constructor •(self::A::X x) → self::A<self::A::X>
: super core::Object::•()
;
}
static method test<Y extends self::A<self::test::Y> = self::A<dynamic>>(self::test::Y y) → dynamic {
self::A<core::Object?> a = new self::A::•<self::test::Y>(y);
}
@@ -0,0 +1,12 @@
library;
import self as self;
import "dart:core" as core;
class A<X extends self::A<self::A::X> = self::A<dynamic>> extends core::Object {
constructor •(self::A::X x) → self::A<self::A::X>
: super core::Object::•()
;
}
static method test<Y extends self::A<self::test::Y> = self::A<dynamic>>(self::test::Y y) → dynamic {
self::A<core::Object?> a = new self::A::•<self::test::Y>(y);
}
@@ -0,0 +1,10 @@
library;
import self as self;
import "dart:core" as core;
class A<X extends self::A<self::A::X> = self::A<dynamic>> extends core::Object {
constructor •(self::A::X x) → self::A<self::A::X>
;
}
static method test<Y extends self::A<self::test::Y> = self::A<dynamic>>(self::test::Y y) → dynamic
;
@@ -0,0 +1,12 @@
library;
import self as self;
import "dart:core" as core;
class A<X extends self::A<self::A::X> = self::A<dynamic>> extends core::Object {
constructor •(self::A::X x) → self::A<self::A::X>
: super core::Object::•()
;
}
static method test<Y extends self::A<self::test::Y> = self::A<dynamic>>(self::test::Y y) → dynamic {
self::A<core::Object?> a = new self::A::•<self::test::Y>(y);
}
@@ -0,0 +1,5 @@
class A<X extends A<X>> {
A(X x);
}
test<Y extends A<Y>>(Y y) {}
@@ -0,0 +1,5 @@
class A<X extends A<X>> {
A(X x);
}
test<Y extends A<Y>>(Y y) {}
+44 -14
View File
@@ -569,15 +569,15 @@ mixin StandardBounds {
// subtype relation is established.
case (_, _)
when isSubtypeOf(
type1WithoutNullabilityMarker,
type2WithoutNullabilityMarker,
greatestClosureForLowerBound(type1WithoutNullabilityMarker),
greatestClosureForLowerBound(type2WithoutNullabilityMarker),
SubtypeCheckMode.withNullabilities):
return type1.withDeclaredNullability(intersectNullabilities(
type1.declaredNullability, type2.declaredNullability));
case (_, _)
when isSubtypeOf(
type2WithoutNullabilityMarker,
type1WithoutNullabilityMarker,
greatestClosureForLowerBound(type2WithoutNullabilityMarker),
greatestClosureForLowerBound(type1WithoutNullabilityMarker),
SubtypeCheckMode.withNullabilities):
return type2.withDeclaredNullability(intersectNullabilities(
type2.declaredNullability, type1.declaredNullability));
@@ -1052,8 +1052,8 @@ mixin StandardBounds {
// type if the subtype relation is established.
case (_, _)
when isSubtypeOf(
typeWithoutNullabilityMarker1,
typeWithoutNullabilityMarker2,
leastClosureForUpperBound(typeWithoutNullabilityMarker1),
leastClosureForUpperBound(typeWithoutNullabilityMarker2),
SubtypeCheckMode.withNullabilities):
// UP(T1, T2) = T2 if T1 <: T2
// Note that both types must be interface or extension types at this
@@ -1062,8 +1062,8 @@ mixin StandardBounds {
uniteNullabilities(type1.nullability, type2.nullability));
case (_, _)
when isSubtypeOf(
typeWithoutNullabilityMarker2,
typeWithoutNullabilityMarker1,
leastClosureForUpperBound(typeWithoutNullabilityMarker2),
leastClosureForUpperBound(typeWithoutNullabilityMarker1),
SubtypeCheckMode.withNullabilities):
// UP(T1, T2) = T1 if T2 <: T1
// Note that both types must be interface or extension types at this
@@ -1699,13 +1699,15 @@ mixin StandardBounds {
// where B1a is the greatest closure of B1 with respect to X1,
// as defined in [inference.md].
if (isSubtypeOf(type1, type2, SubtypeCheckMode.withNullabilities)) {
if (isSubtypeOf(leastClosureForUpperBound(type1),
leastClosureForUpperBound(type2), SubtypeCheckMode.withNullabilities)) {
return type2.withDeclaredNullability(combineNullabilitiesForSubstitution(
inner: type2.nullability,
outer: uniteNullabilities(
type1.declaredNullability, type2.nullability)));
}
if (isSubtypeOf(type2, type1, SubtypeCheckMode.withNullabilities)) {
if (isSubtypeOf(leastClosureForUpperBound(type2),
leastClosureForUpperBound(type1), SubtypeCheckMode.withNullabilities)) {
return type1.withDeclaredNullability(combineNullabilitiesForSubstitution(
inner: type1.declaredNullability,
outer: uniteNullabilities(
@@ -1740,11 +1742,15 @@ mixin StandardBounds {
// where B1a is the greatest closure of B1 with respect to X1,
// as defined in [inference.md].
DartType demoted = type1.left;
if (isSubtypeOf(demoted, type2, SubtypeCheckMode.withNullabilities)) {
if (isSubtypeOf(leastClosureForUpperBound(demoted),
leastClosureForUpperBound(type2), SubtypeCheckMode.withNullabilities)) {
return type2.withDeclaredNullability(uniteNullabilities(
type1.declaredNullability, type2.declaredNullability));
}
if (isSubtypeOf(type2, demoted, SubtypeCheckMode.withNullabilities)) {
if (isSubtypeOf(
leastClosureForUpperBound(type2),
leastClosureForUpperBound(demoted),
SubtypeCheckMode.withNullabilities)) {
return demoted.withDeclaredNullability(uniteNullabilities(
demoted.declaredNullability, type2.declaredNullability));
}
@@ -2065,10 +2071,12 @@ mixin StandardBounds {
// 3. Otherwise return the spec-defined standard upper bound. This will
// be an upper bound, might (or might not) be least, and might
// (or might not) be a well-formed type.
if (isSubtypeOf(type1, type2, SubtypeCheckMode.withNullabilities)) {
if (isSubtypeOf(leastClosureForUpperBound(type1),
leastClosureForUpperBound(type2), SubtypeCheckMode.withNullabilities)) {
return type2;
}
if (isSubtypeOf(type2, type1, SubtypeCheckMode.withNullabilities)) {
if (isSubtypeOf(leastClosureForUpperBound(type2),
leastClosureForUpperBound(type1), SubtypeCheckMode.withNullabilities)) {
return type1;
}
if (identical(type1.classNode, type2.classNode)) {
@@ -2163,4 +2171,26 @@ mixin StandardBounds {
return const DynamicType();
}
}
/// Compute the greatest closure of [typeSchema] for subtyping in DOWN.
///
/// > We add the axiom that DOWN(T, _) == T and the symmetric version.
/// > We replace all uses of T1 <: T2 in the DOWN algorithm by S1 <: S2 where
/// > Si is the greatest closure of Ti with respect to _.
///
/// The specification of using the greatest closure in DOWN can be found at
/// https://github.com/dart-lang/language/blob/main/resources/type-system/inference.md#upper-bound
DartType greatestClosureForLowerBound(DartType typeSchema) => typeSchema;
/// Compute the least closure of [typeSchema] for subtyping in UP.
///
/// Taking closures of type schemas in UP is specified as follows:
///
/// > We add the axiom that UP(T, _) == T and the symmetric version.
/// > We replace all uses of T1 <: T2 in the UP algorithm by S1 <: S2 where Si
/// > is the least closure of Ti with respect to _.
///
/// The specification of using the least closure in UP can be found at
/// https://github.com/dart-lang/language/blob/main/resources/type-system/inference.md#upper-bound
DartType leastClosureForUpperBound(DartType typeSchema) => typeSchema;
}