Skip to content

Commit

Permalink
Introduces Util.$getDefine for J2CL to get @define properties.
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=123171684
  • Loading branch information
gkdn authored and blickly committed May 25, 2016
1 parent 016769e commit 467c5b7
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 16 deletions.
84 changes: 74 additions & 10 deletions src/com/google/javascript/jscomp/J2clPass.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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;
Expand All @@ -29,20 +30,62 @@

/**
* Rewrites/inlines some J2CL constructs to be more optimizable.
*
* <p>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.
*
* <p>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.
*/
public class J2clPass implements CompilerPass {
private static final String ALL_CLASS_FILE_NAMES = "*";
private final AbstractCompiler compiler;
private final Supplier<String> safeNameIdSupplier;

private class GetDefineRewriter extends AbstractPostOrderCallback {
private Set<String> defines;

GetDefineRewriter(Set<String> defines) {
this.defines = defines;
}

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

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

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

Node replacement = getDefineReplacement(firstExpr, secondExpr);
replacement.useSourceInfoIfMissingFromForTree(callNode);
callNode.getParent().replaceChild(callNode, replacement);
compiler.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) {
return fnName != null && fnName.endsWith("Util.$getDefine");
}
}

/**
* 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.
Expand Down Expand Up @@ -146,6 +189,17 @@ public J2clPass(AbstractCompiler compiler) {

@Override
public void process(Node externs, Node root) {
/*
* Re-writes Util.getDefine to make it work for compiled mode.
*/
Set<String> defines = new ProcessDefines(compiler, null).collectDefines(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,
"vmbootstrap/Arrays.impl.js",
Expand All @@ -156,6 +210,18 @@ public void process(Node externs, Node root) {
"vmbootstrap/Casts.impl.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.
*/
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,
"nativebootstrap/Util.impl.js",
Expand All @@ -165,8 +231,6 @@ public void process(Node externs, Node root) {
"$setClassMetadataForEnum",
"$setClassMetadataForPrimitive"),
InliningMode.BLOCK);
inlineFunctionsInFile(
root, ALL_CLASS_FILE_NAMES, ImmutableSet.of("$markImplementor"), InliningMode.BLOCK);
}

private void inlineFunctionsInFile(
Expand Down
12 changes: 6 additions & 6 deletions src/com/google/javascript/jscomp/ProcessDefines.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,7 @@ ProcessDefines injectNamespace(GlobalNamespace namespace) {

@Override
public void process(Node externs, Node root) {
if (namespace == null) {
namespace = new GlobalNamespace(compiler, root);
}
overrideDefines(collectDefines(root, namespace));
overrideDefines(collectDefines(root));
}

private void overrideDefines(Map<String, DefineInfo> allDefines) {
Expand Down Expand Up @@ -171,8 +168,11 @@ private boolean isValidDefineType(JSTypeExpression expression) {
* each one.
* @return A map of {@link DefineInfo} structures, keyed by name.
*/
private Map<String, DefineInfo> collectDefines(Node root,
GlobalNamespace namespace) {
Map<String, DefineInfo> collectDefines(Node root) {
if (namespace == null) {
namespace = new GlobalNamespace(compiler, root);
}

// Find all the global names with a @define annotation
List<Name> allDefines = new ArrayList<>();
for (Name name : namespace.getNameIndex().values()) {
Expand Down
15 changes: 15 additions & 0 deletions test/com/google/javascript/jscomp/J2clPassTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ protected CompilerPass getProcessor(final Compiler compiler) {
return new J2clPass(compiler);
}

public void testUtilGetDefine() {
String defineAbc = "var a={}; a.b={}; /** @define {boolean} */ a.b.c = true;\n";
test(
defineAbc + "nativebootstrap.Util.$getDefine('a.b.c', 'def');",
defineAbc + "('def', String(a.b.c));");
test(
defineAbc + "nativebootstrap.Util.$getDefine('a.b.c');",
defineAbc + "(null, String(a.b.c));");
}

public void testUtilGetDefine_notDefined() {
test("nativebootstrap.Util.$getDefine('not.defined');", "null;");
test("nativebootstrap.Util.$getDefine('not.defined', 'def');", "'def';");
}

public void testQualifiedInlines() {
// Arrays functions.
test(
Expand Down

0 comments on commit 467c5b7

Please sign in to comment.