blob: 9f96f4829f159dce9c154108045ebbc2b9265960 [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:_fe_analyzer_shared/src/util/dependency_walker.dart' as graph
show DependencyWalker, Node;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/summary2/link.dart';
/// Compute simple-boundedness for all classes and generic types aliases in
/// the [linker]. There might be dependencies between them, so they all should
/// be processed simultaneously.
void computeSimplyBounded(Linker linker) {
var walker = SimplyBoundedDependencyWalker(linker);
var nodes = <SimplyBoundedNode>[];
for (var libraryBuilder in linker.builders.values) {
for (var unit in libraryBuilder.element.units) {
for (var element in unit.classes) {
var node = walker.getNode(element);
nodes.add(node);
}
for (var element in unit.enums) {
var node = walker.getNode(element);
nodes.add(node);
}
for (var element in unit.extensionTypes) {
var node = walker.getNode(element);
nodes.add(node);
}
for (var element in unit.mixins) {
var node = walker.getNode(element);
nodes.add(node);
}
for (var element in unit.typeAliases) {
var node = walker.getNode(element);
nodes.add(node);
}
}
}
for (var node in nodes) {
walker.walk(node);
var node2 = node._node;
if (node2 is ClassDeclaration) {
var element = node2.declaredElement as ClassElementImpl;
element.isSimplyBounded = node.isSimplyBounded;
} else if (node2 is ClassTypeAlias) {
var element = node2.declaredElement as ClassElementImpl;
element.isSimplyBounded = node.isSimplyBounded;
} else if (node2 is EnumDeclaration) {
var element = node2.declaredElement as EnumElementImpl;
element.isSimplyBounded = node.isSimplyBounded;
} else if (node2 is ExtensionTypeDeclaration) {
var element = node2.declaredElement as ExtensionTypeElementImpl;
element.isSimplyBounded = node.isSimplyBounded;
} else if (node2 is GenericTypeAlias) {
var element = node2.declaredElement as TypeAliasElementImpl;
element.isSimplyBounded = node.isSimplyBounded;
} else if (node2 is FunctionTypeAlias) {
var element = node2.declaredElement as TypeAliasElementImpl;
element.isSimplyBounded = node.isSimplyBounded;
} else if (node2 is MixinDeclaration) {
var element = node2.declaredElement as MixinElementImpl;
element.isSimplyBounded = node.isSimplyBounded;
} else {
throw UnimplementedError('${node2.runtimeType}');
}
}
}
/// The graph walker for evaluating whether types are simply bounded.
class SimplyBoundedDependencyWalker
extends graph.DependencyWalker<SimplyBoundedNode> {
final Linker linker;
final Map<Element, SimplyBoundedNode> nodeMap = Map.identity();
SimplyBoundedDependencyWalker(this.linker);
@override
void evaluate(SimplyBoundedNode v) {
v._evaluate();
}
@override
void evaluateScc(List<SimplyBoundedNode> scc) {
for (var node in scc) {
node._markCircular();
}
}
SimplyBoundedNode getNode(Element element) {
var graphNode = nodeMap[element];
if (graphNode == null) {
var node = linker.getLinkingNode(element);
if (node is ClassDeclaration) {
var parameters = node.typeParameters?.typeParameters;
graphNode = SimplyBoundedNode(
this,
node,
parameters ?? const <TypeParameter>[],
const <TypeAnnotation>[],
);
} else if (node is ClassTypeAlias) {
var parameters = node.typeParameters?.typeParameters;
graphNode = SimplyBoundedNode(
this,
node,
parameters ?? const <TypeParameter>[],
const <TypeAnnotation>[],
);
} else if (node is EnumDeclaration) {
var parameters = node.typeParameters?.typeParameters;
graphNode = SimplyBoundedNode(
this,
node,
parameters ?? const <TypeParameter>[],
const <TypeAnnotation>[],
);
} else if (node is ExtensionTypeDeclaration) {
var parameters = node.typeParameters?.typeParameters;
graphNode = SimplyBoundedNode(
this,
node,
parameters ?? const <TypeParameter>[],
const <TypeAnnotation>[],
);
} else if (node is FunctionTypeAlias) {
var parameters = node.typeParameters?.typeParameters;
graphNode = SimplyBoundedNode(
this,
node,
parameters ?? const <TypeParameter>[],
_collectTypedefRhsTypes(node),
);
} else if (node is GenericTypeAlias) {
var parameters = node.typeParameters?.typeParameters;
graphNode = SimplyBoundedNode(
this,
node,
parameters ?? const <TypeParameter>[],
_collectTypedefRhsTypes(node),
);
} else if (node is MixinDeclaration) {
var parameters = node.typeParameters?.typeParameters;
graphNode = SimplyBoundedNode(
this,
node,
parameters ?? const <TypeParameter>[],
const <TypeAnnotation>[],
);
} else {
throw UnimplementedError('(${node.runtimeType}) $node');
}
nodeMap[element] = graphNode;
}
return graphNode;
}
/// 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<TypeAnnotation> _collectTypedefRhsTypes(AstNode node) {
if (node is FunctionTypeAlias) {
var collector = _TypeCollector();
collector.addType(node.returnType);
collector.visitParameters(node.parameters);
return collector.types;
} else if (node is GenericTypeAlias) {
var type = node.type;
var collector = _TypeCollector();
if (type is GenericFunctionType) {
collector.addType(type.returnType);
collector.visitTypeParameters(type.typeParameters);
collector.visitParameters(type.parameters);
} else {
collector.addType(type);
}
return collector.types;
} else {
throw StateError('(${node.runtimeType}) $node');
}
}
}
/// 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 AstNode _node;
/// The type parameters of the type whose simple-boundedness we check.
final List<TypeParameter> _typeParameters;
/// If the type whose simple-boundedness we check is a typedef, the types
/// appearing in its "right hand side".
final List<TypeAnnotation> _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._node,
this._typeParameters,
this._rhsTypes,
);
@override
List<SimplyBoundedNode> computeDependencies() {
var dependencies = <SimplyBoundedNode>[];
for (var typeParameter in _typeParameters) {
var bound = 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, TypeAnnotation type,
bool allowTypeParameters) {
if (type is NamedType) {
var element = type.element;
if (element is TypeParameterElement) {
return allowTypeParameters;
}
var arguments = type.typeArguments;
if (arguments == null) {
var graphNode = _walker.nodeMap[element];
// If not a node being linked, then the flag is already set.
if (graphNode == null) {
if (element is TypeParameterizedElement) {
return element.isSimplyBounded;
}
return true;
}
dependencies.add(graphNode);
} else {
for (var argument in arguments.arguments) {
if (!_visitType(dependencies, argument, allowTypeParameters)) {
return false;
}
}
}
return true;
}
if (type is GenericFunctionType) {
var collector = _TypeCollector();
collector.addType(type.returnType);
collector.visitTypeParameters(type.typeParameters);
collector.visitParameters(type.parameters);
for (var type in collector.types) {
if (!_visitType(dependencies, type, allowTypeParameters)) {
return false;
}
}
return true;
}
if (type is RecordTypeAnnotation) {
for (var field in type.fields) {
if (!_visitType(dependencies, field.type, allowTypeParameters)) {
return false;
}
}
return true;
}
throw UnimplementedError('(${type.runtimeType}) $type');
}
}
/// Helper for collecting type annotations.
class _TypeCollector {
final List<TypeAnnotation> types = [];
void addType(TypeAnnotation? type) {
if (type != null) {
types.add(type);
}
}
void visitParameter(FormalParameter node) {
if (node is DefaultFormalParameter) {
visitParameter(node.parameter);
} else if (node is FieldFormalParameter) {
// The spec does not allow them here, ignore.
} else if (node is FunctionTypedFormalParameter) {
addType(node.returnType);
visitParameters(node.parameters);
} else if (node is SimpleFormalParameter) {
addType(node.type);
} else {
throw UnimplementedError('(${node.runtimeType}) $node');
}
}
void visitParameters(FormalParameterList parameterList) {
for (var parameter in parameterList.parameters) {
visitParameter(parameter);
}
}
void visitTypeParameters(TypeParameterList? node) {
if (node != null) {
for (var typeParameter in node.typeParameters) {
addType(typeParameter.bound);
}
}
}
}