blob: 880de4ba4a208d8b4d169e4b2a193d0f96da1252 [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/dart/element/element.dart';
import 'package:meta/meta.dart';
/// Indirection between a name and the corresponding [ElementImpl].
///
/// References are organized in a prefix tree.
/// Each reference knows its parent, children, and the [ElementImpl].
///
/// Library:
/// URI of library
///
/// Class:
/// Reference of the enclosing library
/// "@class"
/// Name of the class
///
/// Method:
/// Reference of the enclosing class
/// "@method"
/// Name of the method
///
/// There is only one reference object per [ElementImpl].
class Reference {
/// The name of the container used for duplicate declarations.
static const _defName = '@def';
/// The parent of this reference, or `null` if the root.
Reference? parent;
/// The simple name of the reference in its [parent].
String name;
/// The corresponding [ElementImpl], or `null` if a named container.
ElementImpl? element;
/// Temporary index used during serialization and linking.
int? index;
// null, Reference or Map<String, Reference>.
Object? _childrenUnion;
Reference.root() : this._(null, '');
Reference._(this.parent, this.name);
Iterable<Reference> get children {
var childrenUnion = _childrenUnion;
if (childrenUnion == null) return const [];
if (childrenUnion is Reference) return [childrenUnion];
return (childrenUnion as Map<String, Reference>).values;
}
@visibleForTesting
Object? get childrenUnionForTesting => _childrenUnion;
/// The name of the element that this reference represents.
///
/// Normally, this is [name]. But in case of duplicate declarations, such
/// as augmentations (which is allowed by the specification), or invalid
/// code, the actual name is the name of the parent of the duplicates
/// container `@def`.
String get elementName {
if (parent?.name == _defName) {
return parent!.parent!.name;
}
return name;
}
bool get isLibrary => parent?.isRoot == true;
bool get isPrefix => parent?.name == '@prefix';
bool get isRoot => parent == null;
bool get isSetter => parent?.name == '@setter';
/// The parent that is not a container like `@method`.
///
/// Usually this is the parent of the parent.
/// @class::A::@method::foo -> @class::A
///
/// But if this is a duplicates, we go two more levels up.
/// @class::A::@method::foo::@def::0 -> @class::A
Reference get parentNotContainer {
// Should be `@method`, `@constructor`, etc.
var containerInParent = parent!;
// Skip the duplicates container.
if (containerInParent.name == _defName) {
containerInParent = containerInParent.parent!.parent!;
}
return containerInParent.parent!;
}
/// Return the child with the given name, or `null` if does not exist.
Reference? operator [](String name) {
var childrenUnion = _childrenUnion;
if (childrenUnion == null) return null;
if (childrenUnion is Reference) {
if (childrenUnion.name == name) return childrenUnion;
return null;
}
return (childrenUnion as Map<String, Reference>)[name];
}
/// Adds a new child with the given [name].
///
/// This method should be used when a new declaration of an element with
/// this name is processed. If there is no existing child with this name,
/// this method works exactly as [getChild]. If there is a duplicate, which
/// should happen rarely, an intermediate `@def` container is added, the
/// existing child is transferred to it and renamed to `0`, then a new child
/// is added with name `1`. Additional duplicate children get names `2`, etc.
Reference addChild(String name) {
var child = Reference._(null, name);
addChildReference(name, child);
return child;
}
/// Transfers [child] to this parent.
void addChildReference(String name, Reference child) {
child.parent = this;
var existing = this[name];
// If not a duplicate.
if (existing == null) {
_addChild(name, child);
return;
}
var def = existing[_defName];
// If no duplicates container yet.
if (def == null) {
removeChild(name); // existing
def = getChild(name).getChild(_defName);
existing.parent = def;
existing.name = '0';
def._addChild(existing.name, existing);
}
// Add a new child to the duplicates container.
child.parent = def;
child.name = '${def.children.length}';
def._addChild(child.name, child);
}
/// Return the child with the given name, create if does not exist yet.
Reference getChild(String name) {
var childrenUnion = _childrenUnion;
if (childrenUnion == null) {
// 0 -> 1 children.
return _childrenUnion = Reference._(this, name);
}
if (childrenUnion is Reference) {
if (childrenUnion.name == name) return childrenUnion;
// 1 -> 2 children.
var childrenUnionAsMap = _childrenUnion = <String, Reference>{};
childrenUnionAsMap[childrenUnion.name] = childrenUnion;
return childrenUnionAsMap[name] = Reference._(this, name);
}
return (childrenUnion as Map<String, Reference>)[name] ??= Reference._(
this,
name,
);
}
/// Returns children with the given name.
/// Usually returns zero or one child.
/// But in case of duplicates will return more than one.
List<Reference> getChildrenByName(String name) {
var result = this[name];
// No such child yet.
if (result == null) {
return const [];
}
// Maybe has the container with duplicates.
if (result[_defName] case var defContainer?) {
return defContainer.children.toList();
}
// Should be the only child with such name.
return [result];
}
Reference? removeChild(String name) {
var childrenUnion = _childrenUnion;
if (childrenUnion == null) return null;
if (childrenUnion is Reference) {
if (childrenUnion.name == name) {
// 1 -> 0 children.
_childrenUnion = null;
return childrenUnion;
}
return null;
}
var childrenUnionAsMap = childrenUnion as Map<String, Reference>;
var result = childrenUnionAsMap.remove(name);
if (childrenUnionAsMap.length == 1) {
// 2 -> 1 children.
_childrenUnion = childrenUnionAsMap.values.single;
}
return result;
}
@override
String toString() => parent == null ? 'root' : '$parent::$name';
void _addChild(String name, Reference child) {
var childrenUnion = _childrenUnion;
if (childrenUnion == null) {
// 0 -> 1 children.
_childrenUnion = child;
return;
}
if (childrenUnion is Reference) {
// 1 -> 2 children.
var childrenUnionAsMap = _childrenUnion = <String, Reference>{};
childrenUnionAsMap[childrenUnion.name] = childrenUnion;
childrenUnionAsMap[name] = child;
return;
}
(childrenUnion as Map<String, Reference>)[name] ??= child;
}
}