// Copyright (c) 2016, 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. library kernel.canonical_name; import 'ast.dart'; /// A string sequence that identifies a library, class, or member. /// /// Canonical names are organized in a prefix tree. Each node knows its /// parent, children, and the AST node it is currently bound to. /// /// The following schema specifies how the canonical name of a given object /// is defined: /// /// Library: /// URI of library /// /// Class: /// Canonical name of enclosing library /// Name of class /// /// Constructor: /// Canonical name of enclosing class or library /// "@constructors" /// Qualified name /// /// Field: /// Canonical name of enclosing class or library /// "@fields" /// Qualified name /// /// Typedef: /// Canonical name of enclosing class /// "@typedefs" /// Name text /// /// Procedure that is not an accessor or factory: /// Canonical name of enclosing class or library /// "@methods" /// Qualified name /// /// Procedure that is a getter: /// Canonical name of enclosing class or library /// "@getters" /// Qualified name /// /// Procedure that is a setter: /// Canonical name of enclosing class or library /// "@setters" /// Qualified name /// /// Procedure that is a factory: /// Canonical name of enclosing class /// "@factories" /// Qualified name /// /// Qualified name: /// if private: URI of library /// Name text /// /// The "qualified name" allows a member to have a name that is private to /// a library other than the one containing that member. class CanonicalName { CanonicalName _parent; CanonicalName get parent => _parent; final String name; CanonicalName _nonRootTop; Map _children; /// The library, class, or member bound to this name. Reference reference; /// Temporary index used during serialization. int index = -1; CanonicalName._(this._parent, this.name) { assert(name != null); assert(parent != null); _nonRootTop = _parent.isRoot ? this : _parent._nonRootTop; } CanonicalName.root() : _parent = null, _nonRootTop = null, name = ''; bool get isRoot => _parent == null; CanonicalName get nonRootTop => _nonRootTop; Iterable get children => _children?.values ?? const []; Iterable get childrenOrNull => _children?.values; bool hasChild(String name) { return _children != null && _children.containsKey(name); } CanonicalName getChild(String name) { var map = _children ??= {}; return map[name] ??= new CanonicalName._(this, name); } CanonicalName getChildFromUri(Uri uri) { // Note that the Uri class caches its string representation, and all library // URIs will be stringified for serialization anyway, so there is no // significant cost for converting the Uri to a string here. return getChild('$uri'); } CanonicalName getChildFromQualifiedName(Name name) { return name.isPrivate ? getChildFromUri(name.library.importUri).getChild(name.name) : getChild(name.name); } CanonicalName getChildFromMember(Member member) { return getChild(getMemberQualifier(member)) .getChildFromQualifiedName(member.name); } CanonicalName getChildFromFieldWithName(Name name) { return getChild('@fields').getChildFromQualifiedName(name); } CanonicalName getChildFromTypedef(Typedef typedef_) { return getChild('@typedefs').getChild(typedef_.name); } /// Take ownership of a child canonical name and its subtree. /// /// The child name is removed as a child of its current parent and this name /// becomes the new parent. Note that this moves the entire subtree rooted at /// the child. /// /// This method can be used to move subtrees within a canonical name tree or /// else move them between trees. It is safe to call this method if the child /// name is already a child of this name. /// /// The precondition is that this name cannot have a (different) child with /// the same name. void adoptChild(CanonicalName child) { if (child._parent == this) return; if (_children != null && _children.containsKey(child.name)) { throw 'Cannot add a child to $this because this name already has a ' 'child named ${child.name}'; } child._parent.removeChild(child.name); child._parent = this; if (_children == null) _children = {}; _children[child.name] = child; } void removeChild(String name) { _children?.remove(name); } void bindTo(Reference target) { if (reference == target) return; if (reference != null) { throw '$this is already bound'; } if (target.canonicalName != null) { throw 'Cannot bind $this to ${target.node}, target is already bound to ' '${target.canonicalName}'; } target.canonicalName = this; this.reference = target; } void unbind() { if (reference == null) return; assert(reference.canonicalName == this); reference.canonicalName = null; reference = null; } void unbindAll() { unbind(); Iterable children_ = childrenOrNull; if (children_ != null) { for (CanonicalName child in children_) { child.unbindAll(); } } } String toString() => _parent == null ? 'root' : '$parent::$name'; Reference getReference() { return reference ??= (new Reference()..canonicalName = this); } static String getMemberQualifier(Member member) { if (member is Procedure) { if (member.isGetter) return '@getters'; if (member.isSetter) return '@setters'; if (member.isFactory) return '@factories'; return '@methods'; } if (member is Field) { return '@fields'; } if (member is Constructor) { return '@constructors'; } if (member is RedirectingFactoryConstructor) { return '@factories'; } throw 'Unexpected member: $member'; } }