[cfe] Use NNBD-updated least closure in constraint gathering
Closes https://github.com/dart-lang/sdk/issues/47797
Change-Id: I58544e185fc691630d56960d54f0711f4a91b60a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241746
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_schema_environment.dart b/pkg/front_end/lib/src/fasta/type_inference/type_schema_environment.dart
index 4d13bfa..7c3f33b 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/type_schema_environment.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/type_schema_environment.dart
@@ -9,7 +9,12 @@
import 'package:kernel/core_types.dart' show CoreTypes;
import 'package:kernel/type_algebra.dart'
- show FreshTypeParameters, Substitution, getFreshTypeParameters, substitute;
+ show
+ FreshTypeParameters,
+ NullabilityAwareFreeTypeVariableEliminator,
+ Substitution,
+ getFreshTypeParameters,
+ substitute;
import 'package:kernel/type_environment.dart';
@@ -416,14 +421,17 @@
if (!isEmptyContext(returnContextType)) {
if (isConst) {
- returnContextType = new TypeVariableEliminator(
- clientLibrary.isNonNullableByDefault
- ? const NeverType.nonNullable()
- : const NullType(),
- clientLibrary.isNonNullableByDefault
- ? objectNullableRawType
- : objectLegacyRawType)
- .substituteType(returnContextType!);
+ if (clientLibrary.isNonNullableByDefault) {
+ returnContextType = new NullabilityAwareFreeTypeVariableEliminator(
+ bottomType: const NeverType.nonNullable(),
+ topType: objectNullableRawType,
+ topFunctionType: functionRawType(Nullability.nonNullable))
+ .eliminateToLeast(returnContextType!);
+ } else {
+ returnContextType =
+ new TypeVariableEliminator(const NullType(), objectLegacyRawType)
+ .substituteType(returnContextType!);
+ }
}
gatherer.tryConstrainUpper(declaredReturnType!, returnContextType!);
}
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart b/pkg/front_end/testcases/nnbd/issue47795.dart
new file mode 100644
index 0000000..c08ad85
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2022, 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<X> {
+ void m1({List<void Function(X)> xs = const []}) {}
+ void m2({List<void Function<Y extends List<X>>(Y)> xs = const []}) {}
+}
+
+void main() => C().m1();
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.strong.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.strong.expect
new file mode 100644
index 0000000..e9d2bf6
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.strong.expect
@@ -0,0 +1,18 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class C<X extends core::Object? = dynamic> extends core::Object {
+ synthetic constructor •() → self::C<self::C::X%>
+ : super core::Object::•()
+ ;
+ method m1({core::List<(self::C::X%) → void> xs = #C1}) → void {}
+ method m2({covariant-by-class core::List<<Y extends core::List<self::C::X%> = dynamic>(Y) → void> xs = #C2}) → void {}
+}
+static method main() → void
+ return new self::C::•<dynamic>().{self::C::m1}(){({xs: core::List<(dynamic) → void>}) → void};
+
+constants {
+ #C1 = <(core::Object?) → void>[]
+ #C2 = <Never>[]
+}
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.strong.transformed.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.strong.transformed.expect
new file mode 100644
index 0000000..e9d2bf6
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.strong.transformed.expect
@@ -0,0 +1,18 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class C<X extends core::Object? = dynamic> extends core::Object {
+ synthetic constructor •() → self::C<self::C::X%>
+ : super core::Object::•()
+ ;
+ method m1({core::List<(self::C::X%) → void> xs = #C1}) → void {}
+ method m2({covariant-by-class core::List<<Y extends core::List<self::C::X%> = dynamic>(Y) → void> xs = #C2}) → void {}
+}
+static method main() → void
+ return new self::C::•<dynamic>().{self::C::m1}(){({xs: core::List<(dynamic) → void>}) → void};
+
+constants {
+ #C1 = <(core::Object?) → void>[]
+ #C2 = <Never>[]
+}
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.textual_outline.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.textual_outline.expect
new file mode 100644
index 0000000..05de177
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.textual_outline.expect
@@ -0,0 +1,6 @@
+class C<X> {
+ void m1({List<void Function(X)> xs = const []}) {}
+ void m2({List<void Function<Y extends List<X>>(Y)> xs = const []}) {}
+}
+
+void main() => C().m1();
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..05de177
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.textual_outline_modelled.expect
@@ -0,0 +1,6 @@
+class C<X> {
+ void m1({List<void Function(X)> xs = const []}) {}
+ void m2({List<void Function<Y extends List<X>>(Y)> xs = const []}) {}
+}
+
+void main() => C().m1();
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.weak.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.expect
new file mode 100644
index 0000000..e7b237c
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.expect
@@ -0,0 +1,18 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class C<X extends core::Object? = dynamic> extends core::Object {
+ synthetic constructor •() → self::C<self::C::X%>
+ : super core::Object::•()
+ ;
+ method m1({core::List<(self::C::X%) → void> xs = #C1}) → void {}
+ method m2({covariant-by-class core::List<<Y extends core::List<self::C::X%> = dynamic>(Y) → void> xs = #C2}) → void {}
+}
+static method main() → void
+ return new self::C::•<dynamic>().{self::C::m1}(){({xs: core::List<(dynamic) → void>}) → void};
+
+constants {
+ #C1 = <(core::Object?) →* void>[]
+ #C2 = <Never*>[]
+}
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.weak.modular.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.modular.expect
new file mode 100644
index 0000000..e7b237c
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.modular.expect
@@ -0,0 +1,18 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class C<X extends core::Object? = dynamic> extends core::Object {
+ synthetic constructor •() → self::C<self::C::X%>
+ : super core::Object::•()
+ ;
+ method m1({core::List<(self::C::X%) → void> xs = #C1}) → void {}
+ method m2({covariant-by-class core::List<<Y extends core::List<self::C::X%> = dynamic>(Y) → void> xs = #C2}) → void {}
+}
+static method main() → void
+ return new self::C::•<dynamic>().{self::C::m1}(){({xs: core::List<(dynamic) → void>}) → void};
+
+constants {
+ #C1 = <(core::Object?) →* void>[]
+ #C2 = <Never*>[]
+}
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.weak.outline.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.outline.expect
new file mode 100644
index 0000000..1952228
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.outline.expect
@@ -0,0 +1,20 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class C<X extends core::Object? = dynamic> extends core::Object {
+ synthetic constructor •() → self::C<self::C::X%>
+ ;
+ method m1({core::List<(self::C::X%) → void> xs = const <(core::Object?) → void>[]}) → void
+ ;
+ method m2({covariant-by-class core::List<<Y extends core::List<self::C::X%> = dynamic>(Y) → void> xs = const <Never>[]}) → void
+ ;
+}
+static method main() → void
+ ;
+
+
+Extra constant evaluation status:
+Evaluated: ListLiteral @ org-dartlang-testcase:///issue47795.dart:6:40 -> ListConstant(const <void Function(Object?)*>[])
+Evaluated: ListLiteral @ org-dartlang-testcase:///issue47795.dart:7:59 -> ListConstant(const <Never*>[])
+Extra constant evaluation: evaluated: 2, effectively constant: 2
diff --git a/pkg/front_end/testcases/nnbd/issue47795.dart.weak.transformed.expect b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.transformed.expect
new file mode 100644
index 0000000..e7b237c
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/issue47795.dart.weak.transformed.expect
@@ -0,0 +1,18 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class C<X extends core::Object? = dynamic> extends core::Object {
+ synthetic constructor •() → self::C<self::C::X%>
+ : super core::Object::•()
+ ;
+ method m1({core::List<(self::C::X%) → void> xs = #C1}) → void {}
+ method m2({covariant-by-class core::List<<Y extends core::List<self::C::X%> = dynamic>(Y) → void> xs = #C2}) → void {}
+}
+static method main() → void
+ return new self::C::•<dynamic>().{self::C::m1}(){({xs: core::List<(dynamic) → void>}) → void};
+
+constants {
+ #C1 = <(core::Object?) →* void>[]
+ #C2 = <Never*>[]
+}
diff --git a/pkg/kernel/lib/type_algebra.dart b/pkg/kernel/lib/type_algebra.dart
index fe6e356..a7d73a0 100644
--- a/pkg/kernel/lib/type_algebra.dart
+++ b/pkg/kernel/lib/type_algebra.dart
@@ -98,8 +98,10 @@
///
/// Returns `true` if [type] contains a [TypeParameterType] that doesn't refer
/// to an enclosing generic [FunctionType] within [type].
-bool containsFreeTypeVariables(DartType type) {
- return new _FreeTypeVariableVisitor().visit(type);
+bool containsFreeTypeVariables(DartType type,
+ {Set<TypeParameter>? boundVariables}) {
+ return new _FreeTypeVariableVisitor(boundVariables: boundVariables)
+ .visit(type);
}
/// Generates a fresh copy of the given type parameters, with their bounds
@@ -402,6 +404,12 @@
@override
TypeParameter freshTypeParameter(TypeParameter node) {
+ assert(
+ !substitution.containsKey(node),
+ "Function type variables cannot be substituted while still attached "
+ "to the function. Perform substitution on "
+ "`FunctionType.withoutTypeParameters` instead.");
+
TypeParameter fresh = new TypeParameter(node.name)..flags = node.flags;
TypeParameterType typeParameterType = substitution[node] =
new TypeParameterType.forAlphaRenaming(node, fresh);
@@ -661,13 +669,6 @@
// any uses, but does not tell if the resulting function type is distinct.
// Our own use counter will get incremented if something from our
// environment has been used inside the function.
- assert(
- node.typeParameters.every((TypeParameter parameter) =>
- lookup(parameter, true) == null &&
- lookup(parameter, false) == null),
- "Function type variables cannot be substituted while still attached "
- "to the function. Perform substitution on "
- "`FunctionType.withoutTypeParameters` instead.");
_TypeSubstitutor inner =
node.typeParameters.isEmpty ? this : newInnerEnvironment();
int before = this.useCounter;
@@ -919,9 +920,10 @@
}
class _FreeTypeVariableVisitor implements DartTypeVisitor<bool> {
- final Set<TypeParameter> variables = new Set<TypeParameter>();
+ final Set<TypeParameter> boundVariables;
- _FreeTypeVariableVisitor();
+ _FreeTypeVariableVisitor({Set<TypeParameter>? boundVariables})
+ : this.boundVariables = boundVariables ?? <TypeParameter>{};
bool visit(DartType node) => node.accept(this);
@@ -967,22 +969,22 @@
@override
bool visitFunctionType(FunctionType node) {
- variables.addAll(node.typeParameters);
+ boundVariables.addAll(node.typeParameters);
bool result = node.typeParameters.any(handleTypeParameter) ||
node.positionalParameters.any(visit) ||
node.namedParameters.any(visitNamedType) ||
visit(node.returnType);
- variables.removeAll(node.typeParameters);
+ boundVariables.removeAll(node.typeParameters);
return result;
}
@override
bool visitTypeParameterType(TypeParameterType node) {
- return !variables.contains(node.parameter);
+ return !boundVariables.contains(node.parameter);
}
bool handleTypeParameter(TypeParameter node) {
- assert(variables.contains(node));
+ assert(boundVariables.contains(node));
if (node.bound.accept(this)) return true;
// ignore: unnecessary_null_comparison
if (node.defaultType == null) return false;
@@ -1168,27 +1170,18 @@
DartType visitVoidType(VoidType node, CoreTypes coreTypes) => node;
}
-/// Eliminates specified free type parameters in a type.
-///
-/// The algorithm for elimination of type variables is described in
-/// https://github.com/dart-lang/language/pull/957
-class NullabilityAwareTypeVariableEliminator extends ReplacementVisitor {
+abstract class NullabilityAwareTypeVariableEliminatorBase
+ extends ReplacementVisitor {
final DartType bottomType;
final DartType topType;
final DartType topFunctionType;
- final Set<TypeParameter> eliminationTargets;
late bool _isLeastClosure;
- final bool Function(DartType type, bool Function(DartType type) recursor)?
- unhandledTypeHandler;
- NullabilityAwareTypeVariableEliminator(
- {required this.eliminationTargets,
- required this.bottomType,
+ NullabilityAwareTypeVariableEliminatorBase(
+ {required this.bottomType,
required this.topType,
- required this.topFunctionType,
- this.unhandledTypeHandler})
- // ignore: unnecessary_null_comparison
- : assert(eliminationTargets != null),
+ required this.topFunctionType})
+ :
// ignore: unnecessary_null_comparison
assert(bottomType != null),
// ignore: unnecessary_null_comparison
@@ -1196,13 +1189,17 @@
// ignore: unnecessary_null_comparison
assert(topFunctionType != null);
- /// Returns a subtype of [type] for all values of [eliminationTargets].
+ bool containsTypeVariablesToEliminate(DartType type);
+
+ bool isTypeVariableToEliminate(TypeParameter typeParameter);
+
+ /// Returns a subtype of [type] for all variables to be eliminated.
DartType eliminateToLeast(DartType type) {
_isLeastClosure = true;
return type.accept1(this, Variance.covariant) ?? type;
}
- /// Returns a supertype of [type] for all values of [eliminationTargets].
+ /// Returns a supertype of [type] for all variables to be eliminated.
DartType eliminateToGreatest(DartType type) {
_isLeastClosure = false;
return type.accept1(this, Variance.covariant) ?? type;
@@ -1234,8 +1231,7 @@
// - The greatest closure of `S` with respect to `L` is `Function`
if (node.typeParameters.isNotEmpty) {
for (TypeParameter typeParameter in node.typeParameters) {
- if (containsTypeVariable(typeParameter.bound, eliminationTargets,
- unhandledTypeHandler: unhandledTypeHandler)) {
+ if (containsTypeVariablesToEliminate(typeParameter.bound)) {
return getFunctionReplacement(variance);
}
}
@@ -1245,13 +1241,114 @@
@override
DartType? visitTypeParameterType(TypeParameterType node, int variance) {
- if (eliminationTargets.contains(node.parameter)) {
+ if (isTypeVariableToEliminate(node.parameter)) {
return getTypeParameterReplacement(variance);
}
return super.visitTypeParameterType(node, variance);
}
}
+/// Eliminates specified free type parameters in a type.
+///
+/// Use this class when only a specific subset of unbound variables in a type
+/// should be substituted with one of [bottomType], [topType], or
+/// [topFunctionType]. For example, running a
+/// `NullabilityAwareTypeVariableEliminatorBase({T}, Never, Object?,
+/// Function).eliminateToLeast` on type `T Function<S>(S s, R r)` will return
+/// `Never Function<S>(S s, R r)`.
+///
+/// The algorithm for elimination of type variables is described in
+/// https://github.com/dart-lang/language/pull/957
+class NullabilityAwareTypeVariableEliminator
+ extends NullabilityAwareTypeVariableEliminatorBase {
+ final Set<TypeParameter> eliminationTargets;
+ final bool Function(DartType type, bool Function(DartType type) recursor)?
+ unhandledTypeHandler;
+
+ NullabilityAwareTypeVariableEliminator(
+ {required this.eliminationTargets,
+ required DartType bottomType,
+ required DartType topType,
+ required DartType topFunctionType,
+ this.unhandledTypeHandler})
+ // ignore: unnecessary_null_comparison
+ : assert(eliminationTargets != null),
+ // ignore: unnecessary_null_comparison
+ assert(bottomType != null),
+ // ignore: unnecessary_null_comparison
+ assert(topType != null),
+ // ignore: unnecessary_null_comparison
+ assert(topFunctionType != null),
+ super(
+ bottomType: bottomType,
+ topType: topType,
+ topFunctionType: topFunctionType);
+
+ @override
+ bool containsTypeVariablesToEliminate(DartType type) {
+ return containsTypeVariable(type, eliminationTargets,
+ unhandledTypeHandler: unhandledTypeHandler);
+ }
+
+ @override
+ bool isTypeVariableToEliminate(TypeParameter typeParameter) {
+ return eliminationTargets.contains(typeParameter);
+ }
+}
+
+/// Eliminates all free type parameters in a type.
+///
+/// Use this class when all unbound variables in a type should be substituted
+/// with one of [bottomType], [topType], or [topFunctionType]. For example,
+/// running a `NullabilityAwareFreeTypeVariableEliminator(Never, Object?,
+/// Function).eliminateToLeast` on type `T Function<S>(S s, R r)` will return
+/// `Never Function<S>(S s, Object? r)`.
+///
+/// The algorithm for elimination of type variables is described in
+/// https://github.com/dart-lang/language/pull/957
+class NullabilityAwareFreeTypeVariableEliminator
+ extends NullabilityAwareTypeVariableEliminatorBase {
+ Set<TypeParameter> _boundVariables = <TypeParameter>{};
+
+ NullabilityAwareFreeTypeVariableEliminator(
+ {required DartType bottomType,
+ required DartType topType,
+ required DartType topFunctionType})
+ :
+ // ignore: unnecessary_null_comparison
+ assert(bottomType != null),
+ // ignore: unnecessary_null_comparison
+ assert(topType != null),
+ // ignore: unnecessary_null_comparison
+ assert(topFunctionType != null),
+ super(
+ bottomType: bottomType,
+ topType: topType,
+ topFunctionType: topFunctionType);
+
+ @override
+ DartType? visitFunctionType(FunctionType node, int variance) {
+ if (node.typeParameters.isNotEmpty) {
+ _boundVariables.addAll(node.typeParameters);
+ DartType? result = super.visitFunctionType(node, variance);
+ _boundVariables.removeAll(node.typeParameters);
+ return result;
+ } else {
+ return super.visitFunctionType(node, variance);
+ }
+ }
+
+ @override
+ bool containsTypeVariablesToEliminate(DartType type) {
+ return containsFreeTypeVariables(type, boundVariables: _boundVariables);
+ }
+
+ @override
+ bool isTypeVariableToEliminate(TypeParameter typeParameter) {
+ return !_boundVariables.contains(typeParameter);
+ }
+}
+
/// Computes [type] as if declared without nullability markers.
///
/// For example, int? and int* are considered applications of the nullable and