All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.J2clPass Maven / Gradle / Ivy

There is a newer version: 9.0.8
Show newest version
/*
 * Copyright 2015 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.base.Supplier;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.FunctionInjector.InliningMode;
import com.google.javascript.jscomp.FunctionInjector.Reference;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Rewrites/inlines some J2CL constructs to be more optimizable.
 */
public class J2clPass implements CompilerPass {
  private static final String ALL_CLASS_FILE_NAMES = "*";
  private final AbstractCompiler compiler;
  private final Supplier safeNameIdSupplier;

  private class GetDefineRewriter extends AbstractPostOrderCallback {
    private final Set defines;

    GetDefineRewriter(Set defines) {
      this.defines = defines;
    }

    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (isUtilGetDefineCall(n)) {
        substituteUtilGetDefine(t, n);
      }
    }

    private void substituteUtilGetDefine(NodeTraversal t, Node callNode) {
      Node firstExpr = callNode.getSecondChild();
      Node secondExpr = callNode.getLastChild();

      if (secondExpr != firstExpr) {
        secondExpr.detach();
      } else {
        // There is no secondExpr; default to null.
        secondExpr = IR.nullNode();
      }

      Node replacement = getDefineReplacement(firstExpr, secondExpr);
      replacement.useSourceInfoIfMissingFromForTree(callNode);
      callNode.replaceWith(replacement);
      t.reportCodeChange();
    }

    private Node getDefineReplacement(Node firstExpr, Node secondExpr) {
      if (defines.contains(firstExpr.getString())) {
        Node define = NodeUtil.newQName(compiler, firstExpr.getString());
        Node defineStringValue = NodeUtil.newCallNode(IR.name("String"), define);
        return IR.comma(secondExpr, defineStringValue);
      } else {
        return secondExpr;
      }
    }

    private boolean isUtilGetDefineCall(Node n) {
      return n.isCall() && isUtilGetDefineMethodName(n.getFirstChild().getQualifiedName());
    }

    private boolean isUtilGetDefineMethodName(String fnName) {
      // TODO: Switch this to the filename + property name heuristic which is less brittle.
      return fnName != null && fnName.endsWith(".$getDefine") && fnName.contains("Util");
    }
  }


  /**
   * Collects references to certain function definitions in a certain class and then inlines fully
   * qualified static method calls to those functions anywhere in the program.
   *
   * 

Assumes that the set of provided short function names will not collide with any of the * collected fully qualified function names once the module prefix has been added. */ private class ClassStaticFunctionsInliner { private final String classFileName; private final Set fnNamesToInline; private final InliningMode inliningMode; private final Map fnsToInlineByQualifiedName = new HashMap<>(); private final FunctionInjector injector; private final Node root; private ClassStaticFunctionsInliner( Node root, String classFileName, Set fnNamesToInline, InliningMode inliningMode) { this.root = root; this.classFileName = classFileName; this.fnNamesToInline = fnNamesToInline; this.inliningMode = inliningMode; this.injector = new FunctionInjector(compiler, safeNameIdSupplier, true, true, true); this.injector.setKnownConstants(fnNamesToInline); } private void run() { NodeTraversal.traverseEs6(compiler, root, new FunctionDefsCollector()); NodeTraversal.traverseEs6(compiler, root, new StaticCallInliner()); } private class FunctionDefsCollector implements Callback { @Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { // Only look inside the referenced class file. return !n.isScript() || n.getSourceFileName().endsWith(classFileName) || classFileName.equals(ALL_CLASS_FILE_NAMES); } @Override public void visit(NodeTraversal t, Node n, Node parent) { // If we arrive here then we're already inside the desired script. // Only look at named function declarations if (!n.isAssign() || !n.getLastChild().isFunction()) { return; } // ... that are fully qualified Node qualifiedNameNode = n.getFirstChild(); if (!qualifiedNameNode.isGetProp() || !qualifiedNameNode.isQualifiedName()) { return; } Node fnNode = n.getLastChild(); String qualifiedFnName = qualifiedNameNode.getQualifiedName(); String fnName = qualifiedNameNode.getLastChild().getString(); if (fnNamesToInline.contains(fnName)) { // Then store a reference to it. fnsToInlineByQualifiedName.put(qualifiedFnName, fnNode); } } } private class StaticCallInliner extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { // Only look at method calls if (!n.isCall()) { return; } // ... that are fully qualified Node qualifiedNameNode = n.getFirstChild(); if (!qualifiedNameNode.isGetProp() || !qualifiedNameNode.isQualifiedName()) { return; } // ... and that reference a function definition we want to inline String qualifiedFnName = qualifiedNameNode.getQualifiedName(); String fnName = qualifiedNameNode.getLastChild().getString(); Node fnImpl = fnsToInlineByQualifiedName.get(qualifiedFnName); if (fnImpl == null) { return; } // Otherwise inline the call. Node inlinedCall = injector.inline( new Reference(n, t.getScope(), t.getModule(), inliningMode), fnName, fnImpl); t.getCompiler().reportChangeToEnclosingScope(inlinedCall); } } } public J2clPass(AbstractCompiler compiler) { this.compiler = compiler; this.safeNameIdSupplier = compiler.getUniqueNameIdSupplier(); } @Override public void process(Node externs, Node root) { if (!J2clSourceFileChecker.shouldRunJ2clPasses(compiler)) { return; } /* * Re-writes Util.getDefine to make it work for compiled mode. */ Set defines = new ProcessDefines(compiler, null, true).collectDefines(externs, root).keySet(); NodeTraversal.traverseEs6(compiler, root, new GetDefineRewriter(defines)); /* * Inlines Arrays.$create(), Arrays.$init(), Arrays.$instanceIsOfType(), Arrays.$castTo() and * Casts.$to() so that all references to Object.$isInstance() functions will be fully qualified * and easy to strip. */ inlineFunctionsInFile( root, "Arrays.impl.java.js", ImmutableSet.of("$create", "$init", "$instanceIsOfType", "$castTo", "$stampType"), InliningMode.DIRECT); inlineFunctionsInFile( root, "Casts.impl.java.js", ImmutableSet.of("$to"), InliningMode.DIRECT); /* * Inlines all Interface.$markImplementor(FooClass) metaclass calls so that FooClass and others * like it are not unnecessarily retained and so that static analysis of interface instanceof * calls becomes possible. * * Note that his pass should NOT be restricted to j2cl .java.js files because JavaScript code * implementing Java interfaces (not recommended but widely used in xplat) needs calls to * $markImplementor. * */ inlineFunctionsInFile( root, ALL_CLASS_FILE_NAMES, ImmutableSet.of("$markImplementor"), InliningMode.BLOCK); /* * Inlines class metadata calls so they become optimizable and avoids escaping of constructor. */ inlineFunctionsInFile( root, "Util.impl.java.js", ImmutableSet.of( "$setClassMetadata", "$setClassMetadataForInterface", "$setClassMetadataForEnum", "$setClassMetadataForPrimitive"), InliningMode.BLOCK); } private void inlineFunctionsInFile( Node root, String classFileName, Set fnNamesToInline, InliningMode inliningMode) { new ClassStaticFunctionsInliner(root, classFileName, fnNamesToInline, inliningMode).run(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy