// 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. Object? _childrenUnion; Reference.root() : this._(null, ''); Reference._(this.parent, this.name); Iterable get children { var childrenUnion = _childrenUnion; if (childrenUnion == null) return const []; if (childrenUnion is Reference) return [childrenUnion]; return (childrenUnion as Map).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)[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 = {}; childrenUnionAsMap[childrenUnion.name] = childrenUnion; return childrenUnionAsMap[name] = Reference._(this, name); } return (childrenUnion as Map)[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 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; 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 = {}; childrenUnionAsMap[childrenUnion.name] = childrenUnion; childrenUnionAsMap[name] = child; return; } (childrenUnion as Map)[name] ??= child; } }