/* * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.template.soy.passes; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.template.soy.exprtree.AbstractExprNodeVisitor; import com.google.template.soy.exprtree.ExprNode; import com.google.template.soy.exprtree.ExprNode.ParentExprNode; import com.google.template.soy.exprtree.ExprRootNode; import com.google.template.soy.exprtree.VarRefNode; import com.google.template.soy.soytree.AbstractSoyNodeVisitor; import com.google.template.soy.soytree.LetNode; import com.google.template.soy.soytree.SoyFileNode; import com.google.template.soy.soytree.SoyFileSetNode; import com.google.template.soy.soytree.SoyNode; import com.google.template.soy.soytree.SoyNode.ConditionalBlockNode; import com.google.template.soy.soytree.SoyNode.ExprHolderNode; import com.google.template.soy.soytree.SoyNode.LocalVarBlockNode; import com.google.template.soy.soytree.SoyNode.LocalVarNode; import com.google.template.soy.soytree.SoyNode.LoopNode; import com.google.template.soy.soytree.SoyNode.MsgBlockNode; import com.google.template.soy.soytree.SoyNode.ParentSoyNode; import com.google.template.soy.soytree.SoyNode.SplitLevelTopNode; import com.google.template.soy.soytree.TemplateNode; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * Visitor for building a map from each node to its list of dependees (ordered by nearest dependee * first). The map built by this visitor is useful in optimization passes that move nodes within the * tree. In the context of this pass, a dependee is a node that appears earlier in the depth-first * traversal, and must remain in the same traversal relationship with the depender after any * modifications to the tree, in order to preserve correctness. * * <p>Important: Do not use outside of Soy code (treat as superpackage-private). * * <p>{@link #exec} must be called on a {@code TemplateNode} or an ancestor of a {@code * TemplateNode}. * * <p>More specifically, a dependee may be an ancestor of the depender or an older sibling of either * the depender or an ancestor of the depender. In the case of a dependee that is an ancestor of the * depender, the depender must not be moved outside of the subtree of the dependee. In the case of a * dependee that is an older sibling of either the depender or an ancestor of the depender, this * same relationship must be maintained when the depender is moved within the tree. * * <p>Dependees are one of: (a) TemplateNode: Its nodes must remain in its subtree. (b) * SplitLevelTopNode: Its immediate children (the bottom level) must remain under it. (c) * ConditionalBlockNode other than LoopNode: It may never be executed, and we can't guarantee that * any of the references made in a node's subtree will be defined in the case that the block is not * executed, and furthermore, even if the node's subtree has no references, we don't want to * needlessly process the node's subtree when the block is not executed. We make an exception for * LoopNodes because we don't want to lose the ability to pull invariants out of loops. (d) * LocalVarNode: Its subtree or younger siblings may reference its local variable. (e) MsgBlockNode: * Any change to its immediate children would change the message to be translated, which would be * incorrect. * */ public final class BuildAllDependeesMapVisitor extends AbstractSoyNodeVisitor<Map<SoyNode, List<SoyNode>>> { /** Stack of frames containing lists of nodes that may be dependees (for the current template). */ private Deque<List<SoyNode>> potentialDependeeFrames; /** * Map from each node to the list of all its dependees, ordered by distance from the depender * (nearest dependee is first). */ private Map<SoyNode, List<SoyNode>> allDependeesMap; @Override public Map<SoyNode, List<SoyNode>> exec(SoyNode node) { Preconditions.checkArgument( node instanceof SoyFileSetNode || node instanceof SoyFileNode || node instanceof TemplateNode); allDependeesMap = Maps.newHashMap(); visit(node); return allDependeesMap; } // ----------------------------------------------------------------------------------------------- // Implementations for specific nodes. @Override protected void visitSoyFileSetNode(SoyFileSetNode node) { visitChildren(node); } @Override protected void visitSoyFileNode(SoyFileNode node) { visitChildren(node); } @Override protected void visitTemplateNode(TemplateNode node) { potentialDependeeFrames = new ArrayDeque<>(); // Note: Add to potential dependees while visiting children because descendents can't be moved // out of the template. potentialDependeeFrames.push(Lists.<SoyNode>newArrayList(node)); visitChildren(node); potentialDependeeFrames.pop(); } @Override protected void visitLetNode(LetNode node) { visitSoyNode(node); // Note: Add to potential dependees before visiting younger siblings because it defines an // inline local variable. potentialDependeeFrames.peek().add(node); } // ----------------------------------------------------------------------------------------------- // Fallback implementation. @Override protected void visitSoyNode(SoyNode node) { // ------ Recurse. ------ // Note: We must recurse first, because a parent's children must have already been processed // (thus their mappings are already in allDependeesMap) when the parent is processed. if (node instanceof ParentSoyNode<?>) { List<SoyNode> newPotentialDependeeFrame = Lists.newArrayList(); if (node instanceof TemplateNode || node instanceof SplitLevelTopNode<?> || node instanceof ConditionalBlockNode || node instanceof LocalVarBlockNode || node instanceof MsgBlockNode) { // This node is a potential dependee for descendants. newPotentialDependeeFrame.add(node); } potentialDependeeFrames.push(newPotentialDependeeFrame); visitChildren((ParentSoyNode<?>) node); potentialDependeeFrames.pop(); } // ------ Process this node. ------ // Find the top-level refs within expressions in this node, if any. Set<String> topLevelRefs; if (node instanceof ExprHolderNode) { topLevelRefs = Sets.newHashSet(); for (ExprRootNode expr : ((ExprHolderNode) node).getExprList()) { topLevelRefs.addAll(new GetTopLevelRefsInExprVisitor().exec(expr)); } } else { topLevelRefs = null; } // Add mappings for this node to allDependeesMap. List<SoyNode> allDependees = Lists.newArrayList(); for (List<SoyNode> potentialDependeeFrame : potentialDependeeFrames) { // We must iterate in reverse if we wish to encounter the dependees in distance order (nearest // dependee first). for (int i = potentialDependeeFrame.size() - 1; i >= 0; i--) { SoyNode potentialDependee = potentialDependeeFrame.get(i); if (isDependent(potentialDependee, node, topLevelRefs)) { allDependees.add(potentialDependee); } } } allDependeesMap.put(node, allDependees); if (allDependees.isEmpty()) { throw new AssertionError(); } } // ----------------------------------------------------------------------------------------------- // Helpers. /** * Helper for visitSoyNode() to determine whether a given node is dependent on a given potential * dependee. * * @param potentialDependee The potential dependee node. * @param node The node to check to see if it's a depender of the potential dependee. * @param topLevelRefs The top-level references made by this node. Does not include references * made by its descendents. May be either empty or null if there are no references. * @return True if {@code node} is dependent on {@code potentialDependee}. */ private boolean isDependent( SoyNode potentialDependee, SoyNode node, @Nullable Set<String> topLevelRefs) { if (potentialDependee instanceof TemplateNode || (potentialDependee instanceof ConditionalBlockNode && !(potentialDependee instanceof LoopNode))) { // A node can never be moved outside of its template, nor outside of any conditionally // executed block that contains it (we make an exception for loops). return true; } if (node.getParent() == potentialDependee && (potentialDependee instanceof SplitLevelTopNode<?> || potentialDependee instanceof MsgBlockNode)) { // The bottom level of a split-level structure cannot be moved. Also, the immediate children // of a MsgBlockNode cannot be moved because they define the message for translation. return true; } if (potentialDependee instanceof LocalVarNode) { // Check whether this node depends on the local var. if (topLevelRefs != null && topLevelRefs.contains(((LocalVarNode) potentialDependee).getVarName())) { return true; } // Check whether any child depends on the local var. if (node instanceof ParentSoyNode<?>) { for (SoyNode child : ((ParentSoyNode<?>) node).getChildren()) { List<SoyNode> allDependeesOfChild = allDependeesMap.get(child); if (allDependeesOfChild == null) { throw new AssertionError("Child has not been visited."); } if (allDependeesOfChild.contains(potentialDependee)) { return true; } } } } // If got past all the checks, then this node is not dependent on this potential dependee. return false; } // ----------------------------------------------------------------------------------------------- // Helper to retrieve top-level references from a Soy expression. /** * Helper for getTopLevelRefsInExpr() to get top-level references from a Soy V2 expression. * Returns the set of top-level references in the given expression. */ private static final class GetTopLevelRefsInExprVisitor extends AbstractExprNodeVisitor<Set<String>> { private Set<String> topLevelRefs; @Override public Set<String> exec(ExprNode node) { topLevelRefs = Sets.newHashSet(); visit(node); return topLevelRefs; } @Override protected void visitVarRefNode(VarRefNode node) { topLevelRefs.add(node.getName()); } @Override protected void visitExprNode(ExprNode node) { if (node instanceof ParentExprNode) { visitChildren((ParentExprNode) node); } } } }