blob: 13a6dc40c068a5582a07f502f457271a161d5e95 [file] [log] [blame]
// Copyright (c) 2019, 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:analyzer/src/summary/format.dart';
import 'package:analyzer/src/summary/idl.dart' show LinkedNode;
import 'package:analyzer/src/summary/idl.dart';
import 'package:analyzer/src/summary/link.dart' as graph
show DependencyWalker, Node;
import 'package:analyzer/src/summary2/builder/source_library_builder.dart';
import 'package:analyzer/src/summary2/linked_bundle_context.dart';
import 'package:analyzer/src/summary2/linked_unit_context.dart';
import 'package:analyzer/src/summary2/reference.dart';
import 'package:analyzer/src/summary2/type_builder.dart';
/// Compute simple-boundedness for all classes and generic types aliases in
/// the source [libraryBuilders]. There might be dependencies between them,
/// so they all should be processed simultaneously.
void computeSimplyBounded(
LinkedBundleContext bundleContext,
Iterable<SourceLibraryBuilder> libraryBuilders,
) {
var walker = SimplyBoundedDependencyWalker(bundleContext);
var nodes = <SimplyBoundedNode>[];
for (var libraryBuilder in libraryBuilders) {
var unitsRef = libraryBuilder.reference.getChild('@unit');
for (var unitRef in unitsRef.children) {
for (var classRef in unitRef.getChild('@class').children) {
var node = walker.getNode(classRef);
nodes.add(node);
}
for (var ref in unitRef.getChild('@typeAlias').children) {
var node = walker.getNode(ref);
nodes.add(node);
}
}
}
for (var node in nodes) {
if (!node.isEvaluated) {
walker.walk(node);
}
LinkedNodeBuilder builder = node._reference.node;
builder.simplyBoundable_isSimplyBounded = node.isSimplyBounded;
}
}
/// The graph walker for evaluating whether types are simply bounded.
class SimplyBoundedDependencyWalker
extends graph.DependencyWalker<SimplyBoundedNode> {
final LinkedBundleContext bundleContext;
final Map<Reference, SimplyBoundedNode> nodeMap = {};
SimplyBoundedDependencyWalker(this.bundleContext);
@override
void evaluate(SimplyBoundedNode v) {
v._evaluate();
}
@override
void evaluateScc(List<SimplyBoundedNode> scc) {
for (var node in scc) {
node._markCircular();
}
}
SimplyBoundedNode getNode(Reference reference) {
var node = nodeMap[reference];
if (node == null) {
if (reference.isClass) {
var parameters = LinkedUnitContext.getTypeParameters(reference.node);
node = SimplyBoundedNode(
this,
reference,
parameters ?? const <LinkedNode>[],
const <LinkedNode>[],
);
} else if (reference.isTypeAlias) {
var parameters = LinkedUnitContext.getTypeParameters(reference.node);
node = SimplyBoundedNode(
this,
reference,
parameters ?? const <LinkedNode>[],
_collectTypedefRhsTypes(reference.node),
);
} else {
throw UnimplementedError('$reference');
}
nodeMap[reference] = node;
}
return node;
}
/// Collects all the type references appearing on the "right hand side" of a
/// typedef.
///
/// The "right hand side" of a typedef is the type appearing after the "="
/// in a new style typedef declaration, or for an old style typedef
/// declaration, the type that *would* appear after the "=" if it were
/// converted to a new style typedef declaration. This means that type
/// parameter declarations and their bounds are not included.
static List<LinkedNode> _collectTypedefRhsTypes(LinkedNode node) {
var kind = node.kind;
if (kind == LinkedNodeKind.functionTypeAlias) {
var types = <LinkedNode>[];
_TypeCollector.addType(
types,
node.functionTypeAlias_returnType,
);
_TypeCollector.visitParameters(
types,
node.functionTypeAlias_formalParameters,
);
return types;
} else if (kind == LinkedNodeKind.genericTypeAlias) {
var types = <LinkedNode>[];
var function = node.genericTypeAlias_functionType;
_TypeCollector.addType(
types,
function.genericFunctionType_returnType,
);
_TypeCollector.visitParameters(
types,
function.genericFunctionType_formalParameters,
);
return types;
} else {
throw StateError('$kind');
}
}
}
/// The graph node used to construct the dependency graph for evaluating
/// whether types are simply bounded.
class SimplyBoundedNode extends graph.Node<SimplyBoundedNode> {
final SimplyBoundedDependencyWalker _walker;
final Reference _reference;
/// The type parameters of the type whose simple-boundedness we check.
final List<LinkedNode> _typeParameters;
/// If the type whose simple-boundedness we check is a typedef, the types
/// appearing in its "right hand side".
final List<LinkedNode> _rhsTypes;
@override
bool isEvaluated = false;
/// After execution of [_evaluate], indicates whether the type is
/// simply bounded.
///
/// Prior to execution of [computeDependencies], `true`.
///
/// Between execution of [computeDependencies] and [_evaluate], `true`
/// indicates that the type is simply bounded only if all of its dependencies
/// are simply bounded; `false` indicates that the type is not simply bounded.
bool isSimplyBounded = true;
SimplyBoundedNode(
this._walker,
this._reference,
this._typeParameters,
this._rhsTypes,
);
@override
List<SimplyBoundedNode> computeDependencies() {
var dependencies = <SimplyBoundedNode>[];
for (var typeParameter in _typeParameters) {
var bound = typeParameter.typeParameter_bound;
if (bound != null) {
if (!_visitType(dependencies, bound, false)) {
// Note: we might consider setting isEvaluated=true here to prevent an
// unnecessary call to SimplyBoundedDependencyWalker.evaluate.
// However, we'd have to be careful to make sure this doesn't violate
// an invariant of the DependencyWalker algorithm, since normally it
// only expects isEvaluated to change during a call to .evaluate or
// .evaluateScc.
isSimplyBounded = false;
return const [];
}
}
}
for (var type in _rhsTypes) {
if (!_visitType(dependencies, type, true)) {
// Note: we might consider setting isEvaluated=true here to prevent an
// unnecessary call to SimplyBoundedDependencyWalker.evaluate.
// However, we'd have to be careful to make sure this doesn't violate
// an invariant of the DependencyWalker algorithm, since normally it
// only expects isEvaluated to change during a call to .evaluate or
// .evaluateScc.
isSimplyBounded = false;
return const [];
}
}
return dependencies;
}
void _evaluate() {
for (var dependency in graph.Node.getDependencies(this)) {
if (!dependency.isSimplyBounded) {
isSimplyBounded = false;
break;
}
}
isEvaluated = true;
}
void _markCircular() {
isSimplyBounded = false;
isEvaluated = true;
}
/// Visits the type specified by [type], storing the [SimplyBoundedNode] for
/// any types it references in [dependencies].
///
/// Return `false` if a type that is known to be not simply bound is found.
///
/// Return `false` if a reference to a type parameter is found, and
/// [allowTypeParameters] is `false`.
///
/// If `false` is returned, further visiting is short-circuited.
///
/// Otherwise `true` is returned.
bool _visitType(List<SimplyBoundedNode> dependencies, LinkedNode type,
bool allowTypeParameters) {
if (type == null) return true;
if (type.kind == LinkedNodeKind.typeName) {
var element = TypeBuilder.typeNameElementIndex(type.typeName_name);
var reference = _walker.bundleContext.referenceOfIndex(element);
if (reference.isTypeParameter) {
return allowTypeParameters;
}
var arguments = type.typeName_typeArguments;
if (arguments == null) {
var graphNode = _walker.nodeMap[reference];
// If not a node being linked, then the flag is already set.
if (graphNode == null) {
if (reference.isClass || reference.isTypeAlias) {
var elementFactory = _walker.bundleContext.elementFactory;
var node = elementFactory.nodeOfReference(reference);
return node.simplyBoundable_isSimplyBounded;
}
return true;
}
dependencies.add(graphNode);
} else {
for (var argument in arguments.typeArgumentList_arguments) {
if (!_visitType(dependencies, argument, allowTypeParameters)) {
return false;
}
}
}
return true;
}
if (type.kind == LinkedNodeKind.genericFunctionType) {
var types = <LinkedNode>[];
_TypeCollector.addType(types, type.genericFunctionType_returnType);
_TypeCollector.visitParameters(
types,
type.genericFunctionType_formalParameters,
);
for (var type in types) {
if (!_visitType(dependencies, type, allowTypeParameters)) {
return false;
}
}
return true;
}
throw UnimplementedError('${type.kind}');
}
}
/// Helper for collecting type annotations in formal parameters.
class _TypeCollector {
static void addType(List<LinkedNode> types, LinkedNode type) {
if (type != null) {
types.add(type);
}
}
static void visitParameter(List<LinkedNode> types, LinkedNode parameter) {
var kind = parameter.kind;
if (kind == LinkedNodeKind.defaultFormalParameter) {
visitParameter(types, parameter.defaultFormalParameter_parameter);
} else if (kind == LinkedNodeKind.functionTypedFormalParameter) {
addType(types, parameter.functionTypedFormalParameter_returnType);
visitParameters(
types,
parameter.functionTypedFormalParameter_formalParameters,
);
} else if (kind == LinkedNodeKind.simpleFormalParameter) {
addType(types, parameter.simpleFormalParameter_type);
} else {
throw UnimplementedError('$kind');
}
}
static void visitParameters(List<LinkedNode> types, LinkedNode parameters) {
assert(parameters.kind == LinkedNodeKind.formalParameterList);
for (var parameter in parameters.formalParameterList_parameters) {
visitParameter(types, parameter);
}
}
}