blob: 1aa6d339a50e095f98b6328fc31cd1257e1f462b [file] [log] [blame] [edit]
// 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';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/summary2/library_builder.dart';
import 'package:collection/collection.dart';
/// This class computes which fields are promotable in a library.
class FieldPromotability {
final LibraryBuilder _libraryBuilder;
/// Fields that might be promotable, if not marked unpromotable later.
final List<FieldElementImpl> _potentiallyPromotableFields = [];
/// The set of field names that are not safe to promote.
/// 1. There is a non-final field with this name.
/// 2. There is a concrete getter with this name.
/// 3. Has a `noSuchMethod` forwarder.
final Set<String> _unpromotableFieldNames = {};
/// Getters actually implemented by the key element.
final Map<InterfaceElement, _ImplementedNode> _implementedNodes =
Map.identity();
/// Getters in the interface of the key element.
final Map<InterfaceElement, _InterfaceNode> _interfaceNodes = Map.identity();
/// Information about concrete [InterfaceElement]s.
final List<_InterfaceElementInfo> _concreteInfoList = [];
FieldPromotability(this._libraryBuilder);
void perform() {
for (var unitElement in _libraryBuilder.element.units) {
for (var class_ in unitElement.classes) {
_addInterfaceElement(class_, isAbstract: class_.isAbstract);
}
for (var enum_ in unitElement.enums) {
_addInterfaceElement(enum_, isAbstract: false);
}
for (var mixin_ in unitElement.mixins) {
_addInterfaceElement(mixin_, isAbstract: true);
}
}
var interfaceWalker = _Walker();
var implementedWalker = _Walker();
for (var info in _concreteInfoList) {
// Compute names of getters in the interface.
var interfaceNode = info._interfaceNode;
interfaceWalker.walk(interfaceNode);
var interfaceNames = interfaceNode._transitiveNames!;
// Compute names of actually implemented getters.
var implementedNode = info._implementedNode;
implementedWalker.walk(implementedNode);
var implementedNames = implementedNode._transitiveNames!;
// noSuchMethod forwarders will be generated for getters that are
// in the interface, but not actually implemented; consequently,
// fields with these names are not safe to promote.
for (var name in interfaceNames) {
if (!implementedNames.contains(name)) {
_unpromotableFieldNames.add(name);
}
}
}
for (var field in _potentiallyPromotableFields) {
if (!_unpromotableFieldNames.contains(field.name)) {
field.isPromotable = true;
}
}
}
void _addInterfaceElement(
InterfaceElement element, {
required bool isAbstract,
}) {
var interfaceInfo = _InterfaceElementInfo(this, element,
_getInterfaceNode(element), _getImplementedNode(element));
if (!isAbstract) {
_concreteInfoList.add(interfaceInfo);
}
}
/// Gets or creates the [_ImplementedNode] for [element].
_ImplementedNode _getImplementedNode(InterfaceElement element) =>
_implementedNodes[element] ??= _ImplementedNode(this, element);
/// If the [element] is not in the [_libraryBuilder], returns `null`.
/// Otherwise, invokes [_getImplementedNode].
_ImplementedNode? _getImplementedNodeOrNull(InterfaceElement element) {
if (element.library == _libraryBuilder.element) {
return _getImplementedNode(element);
}
return null;
}
/// Gets or creates the [_InterfaceNode] for [element].
_InterfaceNode _getInterfaceNode(InterfaceElement element) =>
_interfaceNodes[element] ??= _InterfaceNode(this, element);
/// If the [element] is not in the [_libraryBuilder], returns `null`.
/// Otherwise, invokes [_getInterfaceNode].
_InterfaceNode? _getInterfaceNodeOrNull(InterfaceElement element) {
if (element.library == _libraryBuilder.element) {
return _getInterfaceNode(element);
}
return null;
}
}
/// Data structure tracking the set of getters a class concretely implements.
///
/// This data structure extends [_Node] so that we can efficiently walk the
/// superclass chain (without having to worry about circularities) in order to
/// include getters concretely implemented in superclasses and mixins.
class _ImplementedNode extends _Node {
_ImplementedNode(super.fieldPromotability, super.element);
@override
List<_Node> computeDependencies() {
return [_element.supertype, ..._element.mixins]
.whereNotNull()
.map((type) => type.element)
.map(_fieldPromotability._getImplementedNodeOrNull)
.whereNotNull()
.toList();
}
}
/// Information about an [InterfaceElement].
class _InterfaceElementInfo {
final FieldPromotability _fieldPromotability;
final InterfaceElement _element;
final _InterfaceNode _interfaceNode;
final _ImplementedNode _implementedNode;
_InterfaceElementInfo(
this._fieldPromotability,
this._element,
this._interfaceNode,
this._implementedNode,
) {
for (var field in _element.fields) {
field as FieldElementImpl;
_addFieldElement(field);
}
for (var accessor in _element.accessors) {
_addPropertyAccessorElement(accessor);
}
}
void _addFieldElement(FieldElementImpl element) {
if (element.isStatic || element.isSynthetic) {
return;
}
var name = element.name;
if (!name.startsWith('_')) {
return;
}
_interfaceNode._directNames.add(name);
_implementedNode._directNames.add(name);
if (element.isFinal) {
_fieldPromotability._potentiallyPromotableFields.add(element);
} else {
_fieldPromotability._unpromotableFieldNames.add(name);
}
}
void _addPropertyAccessorElement(PropertyAccessorElement element) {
if (!element.isGetter || element.isStatic || element.isSynthetic) {
return;
}
var name = element.name;
if (!name.startsWith('_')) {
return;
}
_interfaceNode._directNames.add(name);
if (!element.isAbstract) {
_fieldPromotability._unpromotableFieldNames.add(name);
_implementedNode._directNames.add(name);
}
}
}
/// Data structure tracking the set of getters in a class's interface.
///
/// This data structure extends [_Node] so that we can efficiently walk the
/// class hierarchy (without having to worry about circularities) in order to
/// include getters defined in superclasses, mixins, and interfaces.
class _InterfaceNode extends _Node {
_InterfaceNode(super.fieldPromotability, super.element);
@override
List<_Node> computeDependencies() {
var directInterfaces = [
_element.supertype,
..._element.mixins,
..._element.interfaces
];
return directInterfaces
.whereNotNull()
.map((type) => type.element)
.map(_fieldPromotability._getInterfaceNodeOrNull)
.whereNotNull()
.toList();
}
}
/// Dependency walker node allowing us to efficiently walk the class hierarchy
/// and accumulate getter names.
abstract class _Node extends Node<_Node> {
/// A reference back to the [FieldPromotability] object.
final FieldPromotability _fieldPromotability;
/// The element represented by this node.
final InterfaceElement _element;
/// The names of getters declared by [_element] directly.
final Set<String> _directNames = {};
/// The names of getters declared by [_element] and its superinterfaces.
Set<String>? _transitiveNames;
_Node(this._fieldPromotability, this._element);
@override
bool get isEvaluated => _transitiveNames != null;
}
class _Walker extends DependencyWalker<_Node> {
@override
void evaluate(_Node v) => evaluateScc([v]);
@override
void evaluateScc(List<_Node> scc) {
var transitiveNames = <String>{};
for (var node in scc) {
transitiveNames.addAll(node._directNames);
for (var dependency in Node.getDependencies(node)) {
var namesFromDependency = dependency._transitiveNames;
if (namesFromDependency != null) {
transitiveNames.addAll(namesFromDependency);
}
}
}
for (var node in scc) {
node._transitiveNames = transitiveNames;
}
}
}