/** * @param name The Name whose properties references should be updated. * @param value The value to use when rewriting. * @param depth The chain depth. * @param newNodes Expression nodes that have been updated. */ private static void rewriteAliasProps(Name name, Node value, int depth, Set<AstChange> newNodes) { if (name.props == null) { return; } Preconditions.checkState(!value.matchesQualifiedName(name.getFullName())); for (Name prop : name.props) { rewriteAliasProps(prop, value, depth + 1, newNodes); List<Ref> refs = new ArrayList<>(prop.getRefs()); for (Ref ref : refs) { Node target = ref.node; for (int i = 0; i <= depth; i++) { if (target.isGetProp()) { target = target.getFirstChild(); } else if (NodeUtil.isObjectLitKey(target)) { // Object literal key definitions are a little trickier, as we // need to find the assignment target Node gparent = target.getParent().getParent(); if (gparent.isAssign()) { target = gparent.getFirstChild(); } else { Preconditions.checkState(NodeUtil.isObjectLitKey(gparent)); target = gparent; } } else { throw new IllegalStateException("unexpected: " + target); } } Preconditions.checkState(target.isGetProp() || target.isName()); target.getParent().replaceChild(target, value.cloneTree()); prop.removeRef(ref); // Rescan the expression root. newNodes.add(new AstChange(ref.module, ref.scope, ref.node)); } } }
/** * For each qualified name N in the global scope, we check if: (a) No ancestor of N is ever * aliased or assigned an unknown value type. (If N = "a.b.c", "a" and "a.b" are never aliased). * (b) N has exactly one write, and it lives in the global scope. (c) N is aliased in a local * scope. (d) N is aliased in global scope * * <p>If (a) is true, then GlobalNamespace must know all the writes to N. If (a) and (b) are true, * then N cannot change during the execution of a local scope. If (a) and (b) and (c) are true, * then the alias can be inlined if the alias obeys the usual rules for how we decide whether a * variable is inlineable. If (a) and (b) and (d) are true, then inline the alias if possible (if * it is assigned exactly once unconditionally). * * @see InlineVariables */ private void inlineAliases(GlobalNamespace namespace) { // Invariant: All the names in the worklist meet condition (a). Deque<Name> workList = new ArrayDeque<>(namespace.getNameForest()); while (!workList.isEmpty()) { Name name = workList.pop(); // Don't attempt to inline a getter or setter property as a variable. if (name.type == Name.Type.GET || name.type == Name.Type.SET) { continue; } if (!name.inExterns && name.globalSets == 1 && name.localSets == 0 && name.aliasingGets > 0) { // {@code name} meets condition (b). Find all of its local aliases // and try to inline them. List<Ref> refs = new ArrayList<>(name.getRefs()); for (Ref ref : refs) { if (ref.type == Type.ALIASING_GET && ref.scope.isLocal()) { // {@code name} meets condition (c). Try to inline it. // TODO(johnlenz): consider picking up new aliases at the end // of the pass instead of immediately like we do for global // inlines. if (inlineAliasIfPossible(name, ref, namespace)) { name.removeRef(ref); } } else if (ref.type == Type.ALIASING_GET && ref.scope.isGlobal() && ref.getTwin() == null) { // ignore aliases in chained assignments if (inlineGlobalAliasIfPossible(name, ref, namespace)) { name.removeRef(ref); } } } } // Check if {@code name} has any aliases left after the // local-alias-inlining above. if ((name.type == Name.Type.OBJECTLIT || name.type == Name.Type.FUNCTION) && name.aliasingGets == 0 && name.props != null) { // All of {@code name}'s children meet condition (a), so they can be // added to the worklist. workList.addAll(name.props); } } }
/** * Attempt to inline an global alias of a global name. This requires that the name is well * defined: assigned unconditionally, assigned exactly once. It is assumed that, the name for * which it is an alias must already meet these same requirements. * * @param alias The alias to inline * @return Whether the alias was inlined. */ private boolean inlineGlobalAliasIfPossible(Name name, Ref alias, GlobalNamespace namespace) { // Ensure that the alias is assigned to global name at that the // declaration. Node aliasParent = alias.node.getParent(); if (aliasParent.isAssign() && NodeUtil.isExecutedExactlyOnce(aliasParent) // We special-case for constructors here, to inline constructor aliases // more aggressively in global scope. // We do this because constructor properties are always collapsed, // so we want to inline the aliases also to avoid breakages. || aliasParent.isName() && name.isConstructor()) { Node lvalue = aliasParent.isName() ? aliasParent : aliasParent.getFirstChild(); if (!lvalue.isQualifiedName()) { return false; } name = namespace.getSlot(lvalue.getQualifiedName()); if (name != null && name.isInlinableGlobalAlias()) { Set<AstChange> newNodes = new LinkedHashSet<>(); List<Ref> refs = new ArrayList<>(name.getRefs()); for (Ref ref : refs) { switch (ref.type) { case SET_FROM_GLOBAL: continue; case DIRECT_GET: case ALIASING_GET: Node newNode = alias.node.cloneTree(); Node node = ref.node; node.getParent().replaceChild(node, newNode); newNodes.add(new AstChange(ref.module, ref.scope, newNode)); name.removeRef(ref); break; default: throw new IllegalStateException(); } } rewriteAliasProps(name, alias.node, 0, newNodes); // just set the original alias to null. aliasParent.replaceChild(alias.node, IR.nullNode()); compiler.reportCodeChange(); // Inlining the variable may have introduced new references // to descendants of {@code name}. So those need to be collected now. namespace.scanNewNodes(newNodes); return true; } } return false; }