Add a piece of analyzer/CFE shared infractructure for language issue 731.
This algorithm will form a piece of the solution to
https://github.com/dart-lang/language/issues/731 (improved inference
for fold etc.). In particular, when performing generic type inference
on an invocation where more than one argument is a closure, it will
determine the order in which type inference should visit the closures
to maximize the chances of producing meaningful assignments of types
to type parameters.
Change-Id: Ifc2202cb713965373981574f276dffbf1b3f48c1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/239364
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/deferred_closure_heuristic.dart b/pkg/_fe_analyzer_shared/lib/src/deferred_closure_heuristic.dart
new file mode 100644
index 0000000..d5f823a
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/lib/src/deferred_closure_heuristic.dart
@@ -0,0 +1,139 @@
+// 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.
+
+import 'package:_fe_analyzer_shared/src/util/dependency_walker.dart';
+
+/// Data structure tracking the type inference dependencies between closures
+/// passed as invocation parameters.
+///
+/// [planClosureReconciliationStages] is used as part of support for
+/// https://github.com/dart-lang/language/issues/731 (improved inference for
+/// fold etc.) to choose the proper order in which to recursively analyze
+/// closures passed as invocation arguments.
+abstract class ClosureDependencies<TypeVariable, Closure> {
+ final List<_ClosureNode<Closure>> _closureNodes = [];
+
+ /// Construct a [ClosureDependencies] object that's prepared to determine the
+ /// order to resolve [closures] for a generic invocation involving the given
+ /// [typeVariables].
+ ClosureDependencies(
+ Iterable<Closure> closures, Iterable<TypeVariable> typeVariables) {
+ Map<TypeVariable, Set<_ClosureNode<Closure>>> closuresDependingOnTypeVar =
+ {};
+ Map<TypeVariable, Set<_ClosureNode<Closure>>> closuresConstrainingTypeVar =
+ {};
+ for (Closure closure in closures) {
+ _ClosureNode<Closure> closureNode = new _ClosureNode<Closure>(closure);
+ _closureNodes.add(closureNode);
+ for (TypeVariable v in typeVarsFreeInClosureArguments(closure)) {
+ (closuresDependingOnTypeVar[v] ??= {}).add(closureNode);
+ }
+ for (TypeVariable v in typeVarsFreeInClosureReturns(closure)) {
+ (closuresConstrainingTypeVar[v] ??= {}).add(closureNode);
+ }
+ }
+ for (TypeVariable typeVariable in typeVariables) {
+ for (_ClosureNode<Closure> closureNode
+ in closuresDependingOnTypeVar[typeVariable] ?? const {}) {
+ closureNode.dependencies
+ .addAll(closuresConstrainingTypeVar[typeVariable] ?? const {});
+ }
+ }
+ }
+
+ /// Computes the order in which to resolve the closures passed to the
+ /// constructor.
+ ///
+ /// Each entry in the returned list represents the set of closures that should
+ /// be visited during a single stage of resolution; after each stage, the
+ /// assignment of actual types to type variables should be refined.
+ ///
+ /// So, for example, if the closures in question are A, B, and C, and the
+ /// returned list is `[{A, B}, {C}]`, then first closures A and B should be
+ /// resolved, then the assignment of actual types to type variables should be
+ /// refined, and then C should be resolved, and then the final assignment of
+ /// actual types to type variables should be computed.
+ List<Set<Closure>> planClosureReconciliationStages() {
+ _DependencyWalker<Closure> walker = new _DependencyWalker<Closure>();
+ for (_ClosureNode<Closure> closureNode in _closureNodes) {
+ walker.walk(closureNode);
+ }
+ return walker.closureReconciliationStages;
+ }
+
+ /// If the type of the parameter corresponding to [closure] is a function
+ /// type, the set of type parameters referred to by the parameter types of
+ /// that parameter. If the type of the parameter is not a function type, an
+ /// empty iterable should be returned.
+ ///
+ /// Should be overridden by the client.
+ Iterable<TypeVariable> typeVarsFreeInClosureArguments(Closure closure);
+
+ /// If the type of the parameter corresponding to [closure] is a function
+ /// type, the set of type parameters referred to by the return type of that
+ /// parameter. If the type of the parameter is not a function type, the set
+ /// type parameters referred to by the type of the parameter should be
+ /// returned.
+ ///
+ /// Should be overridden by the client.
+ Iterable<TypeVariable> typeVarsFreeInClosureReturns(Closure closure);
+}
+
+/// Node type representing a single [Closure] for purposes of walking the
+/// graph of type inference dependencies among closures.
+class _ClosureNode<Closure> extends Node<_ClosureNode<Closure>> {
+ /// The [Closure] being represented by this node.
+ final Closure closure;
+
+ /// If not `null`, the index of the reconciliation stage to which this closure
+ /// has been assigned.
+ int? stageNum;
+
+ /// The nodes for the closures depended on by this closure.
+ final List<_ClosureNode<Closure>> dependencies = [];
+
+ _ClosureNode(this.closure);
+
+ @override
+ bool get isEvaluated => stageNum != null;
+
+ @override
+ List<_ClosureNode<Closure>> computeDependencies() => dependencies;
+}
+
+/// Derived class of [DependencyWalker] capable of walking the graph of type
+/// inference dependencies among closures.
+class _DependencyWalker<Closure>
+ extends DependencyWalker<_ClosureNode<Closure>> {
+ /// The set of closure reconciliation stages accumulated so far.
+ final List<Set<Closure>> closureReconciliationStages = [];
+
+ @override
+ void evaluate(_ClosureNode v) => evaluateScc([v]);
+
+ @override
+ void evaluateScc(List<_ClosureNode> nodes) {
+ int stageNum = 0;
+ for (_ClosureNode node in nodes) {
+ for (_ClosureNode dependency in node.dependencies) {
+ int? dependencyStageNum = dependency.stageNum;
+ if (dependencyStageNum != null && dependencyStageNum >= stageNum) {
+ stageNum = dependencyStageNum + 1;
+ }
+ }
+ }
+ if (closureReconciliationStages.length <= stageNum) {
+ closureReconciliationStages.add({});
+ // `stageNum` can't grow by more than 1 each time `evaluateScc` is called,
+ // so adding one stage is sufficient to make sure the list is now long
+ // enough.
+ assert(stageNum < closureReconciliationStages.length);
+ }
+ Set<Closure> stage = closureReconciliationStages[stageNum];
+ for (_ClosureNode node in nodes) {
+ node.stageNum = stageNum;
+ stage.add(node.closure);
+ }
+ }
+}
diff --git a/pkg/_fe_analyzer_shared/test/deferred_closure_heuristic_test.dart b/pkg/_fe_analyzer_shared/test/deferred_closure_heuristic_test.dart
new file mode 100644
index 0000000..ab4b8f9
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/deferred_closure_heuristic_test.dart
@@ -0,0 +1,138 @@
+// 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.
+
+import 'package:_fe_analyzer_shared/src/deferred_closure_heuristic.dart';
+import 'package:test/test.dart';
+
+main() {
+ test('single', () {
+ // If there is just a single closure and no type variables, it is selected.
+ var f = Closure('f');
+ expect(
+ _TestClosureDeps(typeVars: [], closures: [f])
+ .planClosureReconciliationStages(),
+ [
+ {f}
+ ]);
+ });
+
+ test('simple dependency', () {
+ // If f depends on g, then g is selected first, and then f.
+ var f = Closure('f', argTypes: ['T']);
+ var g = Closure('g', retTypes: ['T']);
+ expect(
+ _TestClosureDeps(typeVars: ['T'], closures: [f, g])
+ .planClosureReconciliationStages(),
+ [
+ {g},
+ {f}
+ ]);
+ });
+
+ test('long chain', () {
+ // If f depends on g and g depends on h, then we do three separate stages:
+ // h, then g, then f.
+ var f = Closure('f', argTypes: ['T']);
+ var g = Closure('g', argTypes: ['U'], retTypes: ['T']);
+ var h = Closure('h', retTypes: ['U']);
+ expect(
+ _TestClosureDeps(typeVars: ['T', 'U'], closures: [f, g, h])
+ .planClosureReconciliationStages(),
+ [
+ {h},
+ {g},
+ {f}
+ ]);
+ });
+
+ test('unrelated closure', () {
+ // Closures that are independent of all the others are inferred during the
+ // first stage.
+ var f = Closure('f', argTypes: ['T']);
+ var g = Closure('g', retTypes: ['T']);
+ var h = Closure('h');
+ expect(
+ _TestClosureDeps(typeVars: ['T', 'U'], closures: [f, g, h])
+ .planClosureReconciliationStages(),
+ [
+ {g, h},
+ {f}
+ ]);
+ });
+
+ test('independent chains', () {
+ // If f depends on g, and h depends on i, then g and i are selected first,
+ // and then f and h.
+ var f = Closure('f', argTypes: ['T']);
+ var g = Closure('g', retTypes: ['T']);
+ var h = Closure('h', argTypes: ['U']);
+ var i = Closure('i', retTypes: ['U']);
+ expect(
+ _TestClosureDeps(typeVars: ['T', 'U'], closures: [f, g, h, i])
+ .planClosureReconciliationStages(),
+ [
+ {g, i},
+ {f, h}
+ ]);
+ });
+
+ test('diamond', () {
+ // Test a diamond dependency shape: f depends on g and h; g and h both
+ // depend on i.
+ var f = Closure('f', argTypes: ['T', 'U']);
+ var g = Closure('g', argTypes: ['V'], retTypes: ['T']);
+ var h = Closure('h', argTypes: ['V'], retTypes: ['U']);
+ var i = Closure('i', retTypes: ['V']);
+ expect(
+ _TestClosureDeps(typeVars: ['T', 'U', 'V'], closures: [f, g, h, i])
+ .planClosureReconciliationStages(),
+ [
+ {i},
+ {g, h},
+ {f}
+ ]);
+ });
+
+ test('cycle', () {
+ // A dependency cycle is inferred all at once.
+ var f = Closure('f', argTypes: ['T']);
+ var g = Closure('g', argTypes: ['U']);
+ var h = Closure('h', argTypes: ['U'], retTypes: ['T']);
+ var i = Closure('i', argTypes: ['T'], retTypes: ['U']);
+ expect(
+ _TestClosureDeps(typeVars: ['T', 'U'], closures: [f, g, h, i])
+ .planClosureReconciliationStages(),
+ [
+ {h, i},
+ {f, g}
+ ]);
+ });
+}
+
+class Closure {
+ final String name;
+ final List<String> argTypes;
+ final List<String> retTypes;
+
+ Closure(this.name, {this.argTypes = const [], this.retTypes = const []});
+
+ @override
+ String toString() => name;
+}
+
+class _TestClosureDeps extends ClosureDependencies<String, Closure> {
+ final List<String> typeVars;
+ final List<Closure> closures;
+
+ _TestClosureDeps({required this.typeVars, required this.closures})
+ : super(closures, typeVars);
+
+ @override
+ Set<String> typeVarsFreeInClosureArguments(Closure closure) =>
+ closure.argTypes.toSet();
+
+ @override
+ Set<String> typeVarsFreeInClosureReturns(Closure closure) =>
+ closure.retTypes.toSet();
+}
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index 5b21bf7..a85eeaf 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -245,6 +245,7 @@
connectivity
consideration
constness
+constraining
consult
consumers
container
@@ -320,6 +321,7 @@
demangle
demangled
dep
+depended
dependency's
deps
dereferencing
@@ -1024,6 +1026,7 @@
recompiling
recompute
recomputed
+reconciliation
reconstruct
recorder
recoveries
@@ -1038,6 +1041,7 @@
reexports
ref
refactoring
+refined
reflect
reflectee
reflective
@@ -1214,6 +1218,7 @@
stability
stacktrace
stacktraces
+stages
stale
stand
starter