blob: d5f823a2011657dfbd7375e7a1d10eba25000b5e [file] [log] [blame]
// 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);
}
}
}