Skip to content

Commit

Permalink
Updates OptimizeArgumentsArray to support ES6 parameter syntaxes.
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=233651786
  • Loading branch information
nreid260 authored and EatingW committed Feb 13, 2019
1 parent dc76072 commit c24053e
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 67 deletions.
129 changes: 77 additions & 52 deletions src/com/google/javascript/jscomp/OptimizeArgumentsArray.java
Expand Up @@ -16,17 +16,20 @@


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


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


import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback; import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
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.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Deque; import java.util.Deque;
import java.util.List; import java.util.List;
import javax.annotation.Nullable;


/** /**
* Optimization for functions that have {@code var_args} or access the * Optimization for functions that have {@code var_args} or access the
Expand Down Expand Up @@ -182,30 +185,22 @@ public void visit(NodeTraversal traversal, Node node, Node parent) {
* @param scope scope of the function * @param scope scope of the function
*/ */
private void tryReplaceArguments(Scope scope) { private void tryReplaceArguments(Scope scope) {
// Number of parameter that can be accessed without using the arguments // Find the number of parameters that can be accessed without using `arguments`.
// array.
Node parametersList = NodeUtil.getFunctionParameters(scope.getRootNode()); Node parametersList = NodeUtil.getFunctionParameters(scope.getRootNode());
int numParameters = parametersList.getChildCount();
checkState(parametersList.isParamList(), parametersList); checkState(parametersList.isParamList(), parametersList);
int numParameters = parametersList.getChildCount();


// We want to guess what the highest index that has been access from the // Determine the highest index that is used to make an access on `arguments`. By default, assume
// arguments array. We will guess that it does not use anything index higher // that the value is the number of parameters to the function.
// than the named parameter list first until we see other wise. int highestIndex = getHighestIndex(numParameters - 1);
int highestIndex = numParameters - 1;
highestIndex = getHighestIndex(highestIndex);
if (highestIndex < 0) { if (highestIndex < 0) {
return; return;
} }


// Number of extra arguments we need. ImmutableSortedMap<Integer, String> argNames =
// For example: function() { arguments[3] } access index 3 so assembleParamNames(parametersList, highestIndex + 1);
// it will need 4 extra named arguments to changed into: changeMethodSignature(argNames, parametersList);
// function(a,b,c,d) { d }. changeBody(argNames);
int numExtraArgs = highestIndex - numParameters + 1;

ImmutableList<String> extraArgNames = getNewNames(numExtraArgs);
changeMethodSignature(extraArgNames, parametersList);
changeBody(numParameters, extraArgNames, parametersList);
} }


/** /**
Expand Down Expand Up @@ -264,59 +259,89 @@ private int getHighestIndex(int highestIndex) {
} }


/** /**
* Inserts new formal parameters into the method's signature based on the given list of names. * Inserts new formal parameters into the method's signature based on the given set of names.
* *
* <p>Example: function() --> function(r0, r1, r2) * <p>Example: function() --> function(r0, r1, r2)
* *
* @param extraArgNames names for all new formal parameters * @param argNames maps param index to param name, if the param with that index has a name.
* @param parametersList node representing the function signature * @param paramList node representing the function signature
*/ */
private void changeMethodSignature(ImmutableList<String> extraArgNames, Node parametersList) { private void changeMethodSignature(ImmutableSortedMap<Integer, String> argNames, Node paramList) {
if (extraArgNames.isEmpty()) { ImmutableSortedMap<Integer, String> newParams = argNames.tailMap(paramList.getChildCount());
return; for (String name : newParams.values()) {
paramList.addChildToBack(IR.name(name).useSourceInfoIfMissingFrom(paramList));
} }
for (String name : extraArgNames) { if (!newParams.isEmpty()) {
parametersList.addChildToBack(IR.name(name).useSourceInfoIfMissingFrom(parametersList)); compiler.reportChangeToEnclosingScope(paramList);
} }
compiler.reportChangeToEnclosingScope(parametersList);
} }


/** Performs the replacement of arguments[x] -> a if x is known. */ /**
private void changeBody( * Performs the replacement of arguments[x] -> a if x is known.
int numNamedParameter, ImmutableList<String> argNames, Node parametersList) { *
* @param argNames maps param index to param name, if the param with that index has a name.
*/
private void changeBody(ImmutableMap<Integer, String> argNames) {
for (Node ref : currentArgumentsAccess) { for (Node ref : currentArgumentsAccess) {
Node index = ref.getNext(); Node index = ref.getNext();
Node grandParent = ref.getGrandparent();
Node parent = ref.getParent(); Node parent = ref.getParent();
int value = (int) index.getDouble(); // This was validated earlier.


// Skip if it is unknown. @Nullable String name = argNames.get(value);
if (!index.isNumber()) { if (name == null) {
continue; continue;
} }
int value = (int) index.getDouble();


// Unnamed parameter. Node newName = IR.name(name).useSourceInfoIfMissingFrom(parent);
if (value >= numNamedParameter) { parent.replaceWith(newName);
Node newName = IR.name(argNames.get(value - numNamedParameter)); // TODO(nickreid): See if we can do this fewer times. The accesses may be in different scopes.
grandParent.replaceChild(parent, newName); compiler.reportChangeToEnclosingScope(newName);
compiler.reportChangeToEnclosingScope(newName);
} else {
// Here, for no apparent reason, the user is accessing a named parameter
// with arguments[idx]. We can replace it with the actual name for them.
Node name = parametersList.getChildAtIndex(value);
Node newName = IR.name(name.getString());
grandParent.replaceChild(parent, newName);
compiler.reportChangeToEnclosingScope(newName);
}
} }
} }


/** Generates {@code count} unique names (for synthesized parameters). */ /**
private ImmutableList<String> getNewNames(int count) { * Generates a {@link Map} from argument indices to parameter names.
ImmutableList.Builder<String> builder = ImmutableList.builder(); *
for (; count > 0; count--) { * <p>A {@link Map} is used because the sequence may be sparse in the case that there is an
builder.add(paramPrefix + uniqueId++); * anonymous param, such as a destructuring param. There may also be fewer returned names than
* {@code maxCount} if there is a rest param, since no additional params may be synthesized.
*
* @param maxCount The maximum number of argument names in the returned map.
*/
private ImmutableSortedMap<Integer, String> assembleParamNames(Node paramList, int maxCount) {
checkArgument(paramList.isParamList(), paramList);

ImmutableSortedMap.Builder<Integer, String> builder = ImmutableSortedMap.naturalOrder();
int index = 0;

// Collect all existing param names first...
for (Node param = paramList.getFirstChild(); param != null; param = param.getNext()) {
switch (param.getToken()) {
case NAME:
builder.put(index, param.getString());
break;

case REST:
return builder.build();

case DEFAULT_VALUE:
// `arguments` doesn't consider default values. It holds exactly the provided args.
case OBJECT_PATTERN:
case ARRAY_PATTERN:
// Patterns have no names to substitute into the body.
break;

default:
throw new IllegalArgumentException(param.toString());
}

index++;
} }
// ... then synthesize any additional param names.
for (; index < maxCount; index++) {
builder.put(index, paramPrefix + uniqueId++);
}

return builder.build(); return builder.build();
} }
} }
44 changes: 29 additions & 15 deletions test/com/google/javascript/jscomp/OptimizeArgumentsArrayTest.java
Expand Up @@ -16,7 +16,6 @@


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


import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.JUnit4; import org.junit.runners.JUnit4;
Expand Down Expand Up @@ -305,27 +304,42 @@ public void testUnusualArgumentsUsage() {
} }


@Test @Test
@Ignore public void testUseArguments_withDefaultValue() {
public void testUseArgumentsToAccessParamWithDefault() { // `arguments` doesn't consider default values. It holds exaclty the provided args.
// TODO(b/123256727): fix this case. right now the pass crashes testSame("function f(x = 0) { arguments[0]; }");
test("function f(x = 0) { arguments[0]; }", "function f(x = 0) { x; }");
test(
"function f(x = 0) { arguments[1]; }", //
"function f(x = 0, p0) { p0; }");
} }


@Test @Test
@Ignore public void testUseArguments_withRestParam() {
public void testUseArgumentsWithRestParam() { test(
// TODO(b/123256727): fix this case. right now the pass crashes "function f(x, ...rest) { arguments[0]; }", //
test("function f(x, ...rest) { arguments[1]; }", "function f(...rest) { rest[0]; }"); "function f(x, ...rest) { x; }");


test("function f(x, ...rest) { arguments[2]; }", "function f(...rest) { rest[1]; }"); // We could possibly do better here by referencing through `rest` instead, but the additional
// complexity of tracking and validating the rest parameter isn't worth it.
testSame("function f(x, ...rest) { arguments[1]; }");
testSame("function f(x, ...rest) { arguments[2]; }"); // Don't synthesize params after a rest.
} }


@Test @Test
@Ignore public void testUseArguments_withArrayDestructuringParam() {
public void testUseArgumentsWithDestructuringParam() {
// TODO(b/123256727): fix this case. right now the pass crashes
test("function f([x, y]) { arguments[1]; }", "function f([x, y], p0) { p0; }");

testSame("function f([x, y]) { arguments[0]; }"); testSame("function f([x, y]) { arguments[0]; }");

test(
"function f([x, y]) { arguments[1]; }", //
"function f([x, y], p0) { p0; }");
}

@Test
public void testUseArguments_withObjectDestructuringParam() {
test(
"function f({x: y}) { arguments[1]; }", //
"function f({x: y}, p0) { p0; }");

testSame("function f({x: y}) { arguments[0]; }");
} }
} }

0 comments on commit c24053e

Please sign in to comment.