blob: 7102e1bddae3adf8a13a1055f4b3c1b737ab39ee [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 generic
/// invocation parameters.
///
/// [planReconciliationStages] 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
/// function literals passed as invocation arguments.
abstract class FunctionLiteralDependencies<TypeVariable, ParamInfo,
DeferredParamInfo extends ParamInfo> {
final List<_Node<ParamInfo>> _paramNodes = [];
/// Construct a [FunctionLiteralDependencies] object that's prepared to
/// determine the order to resolve [deferredParams] for a generic invocation
/// involving the given [typeVariables].
///
/// [unDeferredParams] should contain information about any parameters
/// corresponding to arguments that have already been type inferred.
FunctionLiteralDependencies(
Iterable<DeferredParamInfo> deferredParams,
Iterable<TypeVariable> typeVariables,
Iterable<ParamInfo> unDeferredParams) {
Map<TypeVariable, Set<_Node<ParamInfo>>> paramsDependingOnTypeVar = {};
Map<TypeVariable, Set<_Node<ParamInfo>>> paramsConstrainingTypeVar = {};
int deferredParamIndex = 0;
for (DeferredParamInfo param in deferredParams) {
_Node<ParamInfo> paramNode =
new _Node<ParamInfo>(param, deferredParamIndex: deferredParamIndex++);
_paramNodes.add(paramNode);
for (TypeVariable v in typeVarsFreeInParamParams(param)) {
(paramsDependingOnTypeVar[v] ??= {}).add(paramNode);
}
for (TypeVariable v in typeVarsFreeInParamReturns(param)) {
(paramsConstrainingTypeVar[v] ??= {}).add(paramNode);
}
}
for (ParamInfo param in unDeferredParams) {
_Node<ParamInfo> paramNode =
new _Node<ParamInfo>(param, deferredParamIndex: null);
_paramNodes.add(paramNode);
// Note: for un-deferred parameters, we only care about
// typeVarsFreeInParamReturns, because these parameters have already been
// analyzed, so they can't depend on other parameters.
for (TypeVariable v in typeVarsFreeInParamReturns(param)) {
(paramsConstrainingTypeVar[v] ??= {}).add(paramNode);
}
}
for (TypeVariable typeVariable in typeVariables) {
for (_Node<ParamInfo> paramNode
in paramsDependingOnTypeVar[typeVariable] ?? const {}) {
paramNode.dependencies
.addAll(paramsConstrainingTypeVar[typeVariable] ?? const {});
}
}
}
/// Computes the order in which to resolve the deferred parameters passed to
/// the constructor.
///
/// Each entry in the returned list represents the set of parameters whose
/// corresponding arguments should be visited during a single stage of
/// resolution; after each stage, the assignment of actual types to type
/// variables should be refined. The list of parameters in each stage is
/// sorted to match the order of the `deferredParams` node passed to the
/// constructor.
///
/// So, for example, if the parameters in question are A, B, and C, and the
/// returned list is `[[A, B], [C]]`, then first parameters 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.
///
/// Note that the first stage may be empty; when this happens, it means that
/// the assignment of actual types to type variables should be refined before
/// doing any visiting.
List<List<DeferredParamInfo>> planReconciliationStages() {
_DependencyWalker<ParamInfo, DeferredParamInfo> walker =
new _DependencyWalker<ParamInfo, DeferredParamInfo>();
for (_Node<ParamInfo> paramNode in _paramNodes) {
walker.walk(paramNode);
}
List<_Node<ParamInfo>> _sortStage(List<_Node<ParamInfo>> stage) {
stage.sort((a, b) => a.deferredParamIndex! - b.deferredParamIndex!);
return stage;
}
return [
for (List<_Node<ParamInfo>> stage in walker.reconciliationStages)
[
for (_Node<ParamInfo> node in _sortStage(stage))
node.param as DeferredParamInfo
]
];
}
/// If the type of the parameter corresponding to [param] 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> typeVarsFreeInParamParams(DeferredParamInfo param);
/// If the type of the parameter corresponding to [param] 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> typeVarsFreeInParamReturns(ParamInfo param);
}
/// Derived class of [DependencyWalker] capable of walking the graph of type
/// inference dependencies among parameters.
class _DependencyWalker<ParamInfo, DeferredParamInfo extends ParamInfo>
extends DependencyWalker<_Node<ParamInfo>> {
/// The set of reconciliation stages accumulated so far.
final List<List<_Node<ParamInfo>>> reconciliationStages = [];
@override
void evaluate(_Node<ParamInfo> v) => evaluateScc([v]);
@override
void evaluateScc(List<_Node<ParamInfo>> nodes) {
int stageNum = 0;
for (_Node<ParamInfo> node in nodes) {
for (_Node<ParamInfo> dependency in node.dependencies) {
int? dependencyStageNum = dependency.stageNum;
if (dependencyStageNum != null && dependencyStageNum >= stageNum) {
stageNum = dependencyStageNum + 1;
}
}
}
if (reconciliationStages.length <= stageNum) {
reconciliationStages.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 < reconciliationStages.length);
}
List<_Node<ParamInfo>> stage = reconciliationStages[stageNum];
for (_Node<ParamInfo> node in nodes) {
node.stageNum = stageNum;
if (node.deferredParamIndex != null) {
stage.add(node);
}
}
}
}
/// Node type representing a single parameter for purposes of walking the graph
/// of type inference dependencies among parameters.
class _Node<ParamInfo> extends Node<_Node<ParamInfo>> {
/// The [ParamInfo] represented by this node.
final ParamInfo param;
/// If not `null`, the index of the reconciliation stage to which this
/// parameter has been assigned.
int? stageNum;
/// The nodes for the parameters depended on by this parameter.
final List<_Node<ParamInfo>> dependencies = [];
/// If this node represents a deferred parameter, the index of it in the list
/// of deferred parameters used to construct [FunctionLiteralDependencies].
final int? deferredParamIndex;
_Node(this.param, {required this.deferredParamIndex});
@override
bool get isEvaluated => stageNum != null;
@override
List<_Node<ParamInfo>> computeDependencies() => dependencies;
}