diff --git a/CHANGELOG.md b/CHANGELOG.md index 930c5153342..3e6f18007e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/pkg/front_end/lib/src/type_inference/standard_bounds.dart b/pkg/front_end/lib/src/type_inference/standard_bounds.dart index da1b542f47c..e265502f1f7 100644 --- a/pkg/front_end/lib/src/type_inference/standard_bounds.dart +++ b/pkg/front_end/lib/src/type_inference/standard_bounds.dart @@ -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); } diff --git a/pkg/front_end/testcases/general/type_schema_in_up_down.dart b/pkg/front_end/testcases/general/type_schema_in_up_down.dart new file mode 100644 index 00000000000..98a393eb004 --- /dev/null +++ b/pkg/front_end/testcases/general/type_schema_in_up_down.dart @@ -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> { + A(X x); +} + +test>(Y y) { + A a = new A(y); +} diff --git a/pkg/front_end/testcases/general/type_schema_in_up_down.dart.strong.expect b/pkg/front_end/testcases/general/type_schema_in_up_down.dart.strong.expect new file mode 100644 index 00000000000..e2bbebf9af2 --- /dev/null +++ b/pkg/front_end/testcases/general/type_schema_in_up_down.dart.strong.expect @@ -0,0 +1,12 @@ +library; +import self as self; +import "dart:core" as core; + +class A = self::A> extends core::Object { + constructor •(self::A::X x) → self::A + : super core::Object::•() + ; +} +static method test = self::A>(self::test::Y y) → dynamic { + self::A a = new self::A::•(y); +} diff --git a/pkg/front_end/testcases/general/type_schema_in_up_down.dart.strong.modular.expect b/pkg/front_end/testcases/general/type_schema_in_up_down.dart.strong.modular.expect new file mode 100644 index 00000000000..e2bbebf9af2 --- /dev/null +++ b/pkg/front_end/testcases/general/type_schema_in_up_down.dart.strong.modular.expect @@ -0,0 +1,12 @@ +library; +import self as self; +import "dart:core" as core; + +class A = self::A> extends core::Object { + constructor •(self::A::X x) → self::A + : super core::Object::•() + ; +} +static method test = self::A>(self::test::Y y) → dynamic { + self::A a = new self::A::•(y); +} diff --git a/pkg/front_end/testcases/general/type_schema_in_up_down.dart.strong.outline.expect b/pkg/front_end/testcases/general/type_schema_in_up_down.dart.strong.outline.expect new file mode 100644 index 00000000000..56bc72cd03f --- /dev/null +++ b/pkg/front_end/testcases/general/type_schema_in_up_down.dart.strong.outline.expect @@ -0,0 +1,10 @@ +library; +import self as self; +import "dart:core" as core; + +class A = self::A> extends core::Object { + constructor •(self::A::X x) → self::A + ; +} +static method test = self::A>(self::test::Y y) → dynamic + ; diff --git a/pkg/front_end/testcases/general/type_schema_in_up_down.dart.strong.transformed.expect b/pkg/front_end/testcases/general/type_schema_in_up_down.dart.strong.transformed.expect new file mode 100644 index 00000000000..e2bbebf9af2 --- /dev/null +++ b/pkg/front_end/testcases/general/type_schema_in_up_down.dart.strong.transformed.expect @@ -0,0 +1,12 @@ +library; +import self as self; +import "dart:core" as core; + +class A = self::A> extends core::Object { + constructor •(self::A::X x) → self::A + : super core::Object::•() + ; +} +static method test = self::A>(self::test::Y y) → dynamic { + self::A a = new self::A::•(y); +} diff --git a/pkg/front_end/testcases/general/type_schema_in_up_down.dart.textual_outline.expect b/pkg/front_end/testcases/general/type_schema_in_up_down.dart.textual_outline.expect new file mode 100644 index 00000000000..5f46318a40f --- /dev/null +++ b/pkg/front_end/testcases/general/type_schema_in_up_down.dart.textual_outline.expect @@ -0,0 +1,5 @@ +class A> { + A(X x); +} + +test>(Y y) {} diff --git a/pkg/front_end/testcases/general/type_schema_in_up_down.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/type_schema_in_up_down.dart.textual_outline_modelled.expect new file mode 100644 index 00000000000..5f46318a40f --- /dev/null +++ b/pkg/front_end/testcases/general/type_schema_in_up_down.dart.textual_outline_modelled.expect @@ -0,0 +1,5 @@ +class A> { + A(X x); +} + +test>(Y y) {} diff --git a/pkg/kernel/lib/src/standard_bounds.dart b/pkg/kernel/lib/src/standard_bounds.dart index 3a7d3d0c465..c215c6cdd88 100644 --- a/pkg/kernel/lib/src/standard_bounds.dart +++ b/pkg/kernel/lib/src/standard_bounds.dart @@ -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; }