/* * Copyright 2009 The Closure Compiler Authors. * * 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.javascript.jscomp; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.DefinitionsRemover.Definition; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.Node; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * A compiler pass for optimize function return results. Currently this * pass looks for results that are complete unused and rewrite then to be: * "return x()" -->"x(); return" * , but it can easily be * expanded to look for use context to avoid unneeded type coercion: * - "return x.toString()" --> "return x" * - "return !!x" --> "return x" * @author johnlenz@google.com (John Lenz) */ class OptimizeReturns implements OptimizeCalls.CallGraphCompilerPass, CompilerPass { private AbstractCompiler compiler; OptimizeReturns(AbstractCompiler compiler) { this.compiler = compiler; } @Override @VisibleForTesting public void process(Node externs, Node root) { DefinitionUseSiteFinder defFinder = new DefinitionUseSiteFinder(compiler); defFinder.process(externs, root); process(externs, root, defFinder); } @Override public void process( Node externs, Node root, DefinitionUseSiteFinder definitions) { // Find all function nodes whose callers ignore the return values. List<Node> toOptimize = new ArrayList<>(); for (DefinitionSite defSite : definitions.getDefinitionSites()) { if (!defSite.inExterns && !callResultsMaybeUsed(definitions, defSite)) { toOptimize.add(defSite.definition.getRValue()); } } // Optimize the return statements. for (Node node : toOptimize) { rewriteReturns(definitions, node); } } /** * Determines if a function result might be used. A result might be use if: * - Function must is exported. * - The definition is never accessed outside a function call context. */ private static boolean callResultsMaybeUsed( DefinitionUseSiteFinder defFinder, DefinitionSite definitionSite) { Definition definition = definitionSite.definition; // Assume non-function definitions results are used. Node rValue = definition.getRValue(); if (rValue == null || !rValue.isFunction()) { return true; } // Be conservative, don't try to optimize any declaration that isn't as // simple function declaration or assignment. if (!NodeUtil.isSimpleFunctionDeclaration(rValue)) { return true; } if (!defFinder.canModifyDefinition(definition)) { return true; } Collection<UseSite> useSites = defFinder.getUseSites(definition); for (UseSite site : useSites) { // Assume indirect definitions references use the result Node useNodeParent = site.node.getParent(); if (isCall(site)) { Node callNode = useNodeParent; Preconditions.checkState(callNode.isCall()); if (NodeUtil.isExpressionResultUsed(callNode)) { return true; } } else { // Allow a standalone name reference. // var a; if (!useNodeParent.isVar()) { return true; } } } // No possible use of the definition result return false; } /** * For the supplied function node, rewrite all the return expressions so that: * return foo(); * becomes: * foo(); return; * Useless return will be removed later by the peephole optimization passes. */ private void rewriteReturns( final DefinitionUseSiteFinder defFinder, Node fnNode) { Preconditions.checkState(fnNode.isFunction()); final Node body = fnNode.getLastChild(); NodeUtil.visitPostOrder( body, new NodeUtil.Visitor() { @Override public void visit(Node node) { if (node.isReturn() && node.hasOneChild()) { boolean keepValue = NodeUtil.mayHaveSideEffects( node.getFirstChild(), compiler); if (!keepValue) { defFinder.removeReferences(node.getFirstChild()); } Node result = node.removeFirstChild(); if (keepValue) { node.getParent().addChildBefore( IR.exprResult(result).srcref(result), node); } compiler.reportChangeToEnclosingScope(body); } } }, new NodeUtil.MatchShallowStatement()); } /** * Determines if the name node acts as the function name in a call expression. */ private static boolean isCall(UseSite site) { Node node = site.node; Node parent = node.getParent(); return (parent.getFirstChild() == node) && parent.isCall(); } }