Skip to content

Commit

Permalink
Add a few more tests of ExtractPrototypeMemberDeclarations and do som…
Browse files Browse the repository at this point in the history
…e small code cleanups.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=175875886
  • Loading branch information
tbreisacher committed Nov 15, 2017
1 parent 1de0f4b commit f8e6576
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 31 deletions.
Expand Up @@ -13,14 +13,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */

package com.google.javascript.jscomp; package com.google.javascript.jscomp;


import static com.google.common.base.Preconditions.checkState;

import com.google.javascript.jscomp.NodeTraversal.AbstractShallowCallback; import com.google.javascript.jscomp.NodeTraversal.AbstractShallowCallback;
import com.google.javascript.rhino.IR; import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Node;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import javax.annotation.Nullable;


/** /**
* When there are multiple prototype member declarations to the same class, * When there are multiple prototype member declarations to the same class,
Expand Down Expand Up @@ -85,7 +87,7 @@ class ExtractPrototypeMemberDeclarations implements CompilerPass {


// The name of variable that will temporary hold the pointer to the prototype // The name of variable that will temporary hold the pointer to the prototype
// object. Of cause, we assume that it'll be renamed by RenameVars. // object. Of cause, we assume that it'll be renamed by RenameVars.
private final String prototypeAlias = "JSCompiler_prototypeAlias"; private static final String PROTOTYPE_ALIAS = "JSCompiler_prototypeAlias";


private final AbstractCompiler compiler; private final AbstractCompiler compiler;


Expand Down Expand Up @@ -150,7 +152,7 @@ private void doExtraction(GatherExtractionInfo info) {
if (pattern == Pattern.USE_GLOBAL_TEMP) { if (pattern == Pattern.USE_GLOBAL_TEMP) {
Node injectionPoint = compiler.getNodeForCodeInsertion(null); Node injectionPoint = compiler.getNodeForCodeInsertion(null);


Node var = NodeUtil.newVarNode(prototypeAlias, null) Node var = NodeUtil.newVarNode(PROTOTYPE_ALIAS, null)
.useSourceInfoIfMissingFromForTree(injectionPoint); .useSourceInfoIfMissingFromForTree(injectionPoint);


injectionPoint.addChildToFront(var); injectionPoint.addChildToFront(var);
Expand All @@ -173,24 +175,23 @@ private void extractInstance(ExtractionInstance instance) {
if (pattern == Pattern.USE_GLOBAL_TEMP) { if (pattern == Pattern.USE_GLOBAL_TEMP) {
// Use the temp variable to hold the prototype. // Use the temp variable to hold the prototype.
Node stmt = Node stmt =
new Node( IR.exprResult(
first.node.getToken(), IR.assign(
IR.assign( IR.name(PROTOTYPE_ALIAS),
IR.name(prototypeAlias), NodeUtil.newQName(
NodeUtil.newQName( compiler,
compiler, className + ".prototype",
className + ".prototype", instance.parent,
instance.parent, className + ".prototype")))
className + ".prototype"))) .useSourceInfoIfMissingFromForTree(first.node);
.useSourceInfoIfMissingFromForTree(first.node);


instance.parent.addChildBefore(stmt, first.node); instance.parent.addChildBefore(stmt, first.node);
compiler.reportChangeToEnclosingScope(stmt); compiler.reportChangeToEnclosingScope(stmt);
} else if (pattern == Pattern.USE_IIFE){ } else if (pattern == Pattern.USE_IIFE) {
Node block = IR.block(); Node block = IR.block();
Node func = IR.function( Node func = IR.function(
IR.name(""), IR.name(""),
IR.paramList(IR.name(prototypeAlias)), IR.paramList(IR.name(PROTOTYPE_ALIAS)),
block); block);


Node call = IR.call(func, Node call = IR.call(func,
Expand All @@ -199,7 +200,7 @@ private void extractInstance(ExtractionInstance instance) {
instance.parent, className + ".prototype")); instance.parent, className + ".prototype"));
call.putIntProp(Node.FREE_CALL, 1); call.putIntProp(Node.FREE_CALL, 1);


Node stmt = new Node(first.node.getToken(), call); Node stmt = IR.exprResult(call);
stmt.useSourceInfoIfMissingFromForTree(first.node); stmt.useSourceInfoIfMissingFromForTree(first.node);
instance.parent.addChildBefore(stmt, first.node); instance.parent.addChildBefore(stmt, first.node);
compiler.reportChangeToEnclosingScope(stmt); compiler.reportChangeToEnclosingScope(stmt);
Expand All @@ -208,7 +209,7 @@ private void extractInstance(ExtractionInstance instance) {
block.addChildToBack(declar.node.detach()); block.addChildToBack(declar.node.detach());
} }
} }
// Go thought each member declaration and replace it with an assignment // Go through each member declaration and replace it with an assignment
// to the prototype variable. // to the prototype variable.
for (PrototypeMemberDeclaration declar : instance.declarations) { for (PrototypeMemberDeclaration declar : instance.declarations) {
replacePrototypeMemberDeclaration(declar); replacePrototypeMemberDeclaration(declar);
Expand All @@ -219,20 +220,17 @@ private void extractInstance(ExtractionInstance instance) {
* Replaces a member declaration to an assignment to the temp prototype * Replaces a member declaration to an assignment to the temp prototype
* object. * object.
*/ */
private void replacePrototypeMemberDeclaration( private void replacePrototypeMemberDeclaration(PrototypeMemberDeclaration declar) {
PrototypeMemberDeclaration declar) {
// x.prototype.y = ... -> t.y = ... // x.prototype.y = ... -> t.y = ...
Node assignment = declar.node.getFirstChild(); Node assignment = declar.node.getFirstChild();
Node lhs = assignment.getFirstChild(); Node lhs = assignment.getFirstChild();
Node name = NodeUtil.newQName( Node name = NodeUtil.newQName(
compiler, compiler,
prototypeAlias + "." + declar.memberName, declar.node, PROTOTYPE_ALIAS + "." + declar.memberName, declar.node,
declar.memberName); declar.memberName);


// Save the full prototype path on the left hand side of the assignment // Save the full prototype path on the left hand side of the assignment for debugging purposes.
// for debugging purposes. // declar.lhs = x.prototype.y so first child of the first child is 'x'.
// declar.lhs = x.prototype.y so first child of the first child
// is 'x'.
Node accessNode = declar.lhs.getFirstFirstChild(); Node accessNode = declar.lhs.getFirstFirstChild();
String originalName = accessNode.getOriginalName(); String originalName = accessNode.getOriginalName();
String className = originalName != null ? originalName : "?"; String className = originalName != null ? originalName : "?";
Expand Down Expand Up @@ -267,12 +265,10 @@ public void visit(NodeTraversal t, Node n, Node parent) {


// Found a good site here. The constructor will computes the chain of // Found a good site here. The constructor will computes the chain of
// declarations that is qualified for extraction. // declarations that is qualified for extraction.
ExtractionInstance instance = ExtractionInstance instance = new ExtractionInstance(prototypeMember, n);
new ExtractionInstance(prototypeMember, n);
cur = instance.declarations.getLast().node; cur = instance.declarations.getLast().node;


// Only add it to our work list if the extraction at this instance // Only add it to our work list if the extraction at this instance makes the code smaller.
// makes the code smaller.
if (instance.isFavorable()) { if (instance.isFavorable()) {
instances.add(instance); instances.add(instance);
totalDelta += instance.delta; totalDelta += instance.delta;
Expand All @@ -281,7 +277,7 @@ public void visit(NodeTraversal t, Node n, Node parent) {
} }


/** /**
* @return <@code true> if the sum of all the extraction instance gain * @return {@code true} if the sum of all the extraction instance gain
* outweighs the overhead of the temp variable declaration. * outweighs the overhead of the temp variable declaration.
*/ */
private boolean shouldExtract() { private boolean shouldExtract() {
Expand All @@ -303,8 +299,7 @@ private ExtractionInstance(PrototypeMemberDeclaration head, Node parent) {


// We can skip over any named functions because they have no effect on // We can skip over any named functions because they have no effect on
// the control flow. In fact, they are lifted to the beginning of the // the control flow. In fact, they are lifted to the beginning of the
// block. This happens a lot when devirtualization breaks the whole // block. This happens a lot when devirtualization breaks the whole chain.
// chain.
if (cur.isFunction()) { if (cur.isFunction()) {
continue; continue;
} }
Expand Down Expand Up @@ -340,6 +335,7 @@ private static class PrototypeMemberDeclaration {
final Node lhs; final Node lhs;


private PrototypeMemberDeclaration(Node lhs, Node node) { private PrototypeMemberDeclaration(Node lhs, Node node) {
checkState(NodeUtil.isExprAssign(node), node);
this.lhs = lhs; this.lhs = lhs;
this.memberName = NodeUtil.getPrototypePropertyName(lhs); this.memberName = NodeUtil.getPrototypePropertyName(lhs);
this.node = node; this.node = node;
Expand Down Expand Up @@ -383,6 +379,7 @@ private static boolean isPrototypePropertyDeclaration(Node n) {
* @return A prototype member declaration representation if there is one * @return A prototype member declaration representation if there is one
* else it returns {@code null}. * else it returns {@code null}.
*/ */
@Nullable
private static PrototypeMemberDeclaration extractDeclaration(Node n) { private static PrototypeMemberDeclaration extractDeclaration(Node n) {
if (!isPrototypePropertyDeclaration(n)) { if (!isPrototypePropertyDeclaration(n)) {
return null; return null;
Expand Down
Expand Up @@ -16,6 +16,8 @@


package com.google.javascript.jscomp; package com.google.javascript.jscomp;


import static com.google.javascript.jscomp.CompilerTestCase.lines;

import com.google.javascript.jscomp.ExtractPrototypeMemberDeclarations.Pattern; import com.google.javascript.jscomp.ExtractPrototypeMemberDeclarations.Pattern;


/** /**
Expand Down Expand Up @@ -56,6 +58,38 @@ public void testNoOpOnEs6Class() {
testSame("export class Example { method1() {} method2() {} }"); testSame("export class Example { method1() {} method2() {} }");
} }


public void testClassDefinedInBlock() {
test(
lines(
"{",
generatePrototypeDeclarations("x", 7),
"}"),
lines(
"var " + TMP + ";",
"{",
TMP + " = x.prototype;",
generateExtractedDeclarations(7),
"}"));
}

public void testClassDefinedInFunction() {
testSame(
lines(
"function f() {",
generatePrototypeDeclarations("x", 7),
"}"));
}

/**
* Currently, this does not run on classes defined in ES6 modules.
*/
// TODO(tbreisacher): Make this work on modules. The initial 'var' needs to go into a non-module
// script node.
public void testEs6Module() {
String importStatement = "import {someValue} from './another_module.js';";
testSame(importStatement + generatePrototypeDeclarations("x", 17));
}

public void testExtractingTwoClassPrototype() { public void testExtractingTwoClassPrototype() {
extract( extract(
generatePrototypeDeclarations("x", 6) + generatePrototypeDeclarations("y", 6), generatePrototypeDeclarations("x", 6) + generatePrototypeDeclarations("y", 6),
Expand Down

0 comments on commit f8e6576

Please sign in to comment.