Skip to content

Commit 929ba03

Browse files
rishipalcopybara-github
authored andcommitted
Ensure that parameter names generated within the loop do not use the loop object name
Currently, the pass generates parameters with the same name as the loopObjectName. This breaks normalization. Fixing this brings us closer to preserving normalization and moving this pass post normalize. PiperOrigin-RevId: 568363583
1 parent 501ae96 commit 929ba03

File tree

3 files changed

+252
-125
lines changed

3 files changed

+252
-125
lines changed

src/com/google/javascript/jscomp/Es6RewriteBlockScopedDeclaration.java

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ public final class Es6RewriteBlockScopedDeclaration extends AbstractPostOrderCal
5454
FeatureSet.BARE_MINIMUM.with(Feature.LET_DECLARATIONS, Feature.CONST_DECLARATIONS);
5555
private final UniqueIdSupplier uniqueIdSupplier;
5656

57+
private static final String LOOP_PARAM_NAME_PREFIX = "$jscomp$loop_param$";
58+
5759
public Es6RewriteBlockScopedDeclaration(AbstractCompiler compiler) {
5860
this.compiler = compiler;
5961
this.uniqueIdSupplier = compiler.getUniqueIdSupplier();
@@ -470,7 +472,7 @@ private void transformLoopClosure() {
470472
return;
471473
}
472474

473-
createWrapperFunctions();
475+
Set<Node> wrapperFunctions = createWrapperFunctions();
474476

475477
for (Node loopNode : loopObjectMap.keySet()) {
476478
// Introduce objects to reflect the captured scope variables.
@@ -485,6 +487,7 @@ private void transformLoopClosure() {
485487
Node updateLoopObject =
486488
astFactory.createAssign(createLoopObjectNameNode(loopObject), objectLitNextIteration);
487489
Node objectLit =
490+
// This is the only time we generate a loop object variable for a loopNode.
488491
IR.var(createLoopObjectNameNode(loopObject), astFactory.createObjectLit())
489492
.srcrefTree(loopNode);
490493
addNodeBeforeLoop(objectLit, loopNode);
@@ -498,26 +501,82 @@ private void transformLoopClosure() {
498501

499502
changeLoopLocalVariablesToProperties(loopNode, loopObject);
500503
}
504+
505+
// At this point, all local variables in the loop have been changed to property accesses on
506+
// the "loopObject" name. For the wrapper functions that we introduced in the loop, we must
507+
// change the name references in their body to refer to their parameter name instead of the
508+
// "loopObject" name.
509+
// TODO(bradfordcsmith): This is inefficient. We should really choose the names first, then
510+
// make the changes, instead of changing the same variable references twice.
511+
updateNamesInWrapperFunctions(wrapperFunctions);
512+
}
513+
514+
/**
515+
* Before:
516+
*
517+
* <pre>{@code
518+
* var arr = [];
519+
* var LOOP$0 = {};
520+
* var i = 0;
521+
* for (; i < 10; LOOP$0 = {y: LOOP$0.y}, i++) {
522+
* LOOP$0.y = i;
523+
* arr.push((function(LOOP$0$PARAM$1) {
524+
* return function() { return LOOP$0.y; }; <---- must use param name
525+
* })(LOOP$0));
526+
* }
527+
*
528+
* }</pre>
529+
*
530+
* After:
531+
*
532+
* <pre>{@code
533+
* var arr = [];
534+
* var LOOP$0 = {};
535+
* var i = 0;
536+
* for (; i < 10; LOOP$0 = {y: LOOP$0.y}, i++) {
537+
* LOOP$0.y = i;
538+
* arr.push((function(LOOP$0$PARAM$1) {
539+
* return function() { return LOOP$0$PARAM$1.y; }; <--- changed
540+
* })(LOOP$0));
541+
* }
542+
*
543+
* }</pre>
544+
*/
545+
private void updateNamesInWrapperFunctions(Set<Node> wrapperFunctions) {
546+
for (Node func : wrapperFunctions) {
547+
// get the param names here (and the loopObject names from it)
548+
Node paramList = func.getSecondChild();
549+
550+
for (Node param = paramList.getFirstChild(); param != null; param = param.getNext()) {
551+
String loopObjectName =
552+
param.getString().substring(0, param.getString().indexOf(LOOP_PARAM_NAME_PREFIX));
553+
updateNames(/* block */ func.getLastChild(), param, loopObjectName);
554+
}
555+
}
501556
}
502557

503558
/** Create wrapper functions and call them. */
504-
private void createWrapperFunctions() {
559+
private Set<Node> createWrapperFunctions() {
560+
Set<Node> wrapperFunctions = new LinkedHashSet<>();
505561
for (Node functionOrObjectLit : nodesRequiringLoopObjectsClosureMap.keySet()) {
506562
Node returnNode = IR.returnNode();
507563
Set<LoopObject> objects = nodesRequiringLoopObjectsClosureMap.get(functionOrObjectLit);
508-
Node[] objectNames = new Node[objects.size()];
564+
Node[] parameterNames = new Node[objects.size()];
509565
Node[] objectNamesForCall = new Node[objects.size()];
510-
int i = 0;
566+
int loopObjectIndex = 0;
511567
for (LoopObject object : objects) {
512-
objectNames[i] = createLoopObjectNameNode(object);
513-
objectNamesForCall[i] = createLoopObjectNameNode(object);
514-
i++;
568+
// This name for parameter should be unique to preserve normalization
569+
parameterNames[loopObjectIndex] =
570+
createUniqueParameterNameToUseWithinLoop(functionOrObjectLit, object);
571+
// this name must be the same as the loop object name
572+
objectNamesForCall[loopObjectIndex] = createLoopObjectNameNode(object);
573+
loopObjectIndex++;
515574
}
516575

517576
Node iife =
518577
astFactory.createFunction(
519578
"",
520-
IR.paramList(objectNames),
579+
IR.paramList(parameterNames),
521580
IR.block(returnNode),
522581
type(StandardColors.TOP_OBJECT));
523582
compiler.reportChangeToChangeScope(iife);
@@ -531,9 +590,29 @@ private void createWrapperFunctions() {
531590
functionOrObjectLit);
532591
replacement = call.srcrefTreeIfMissing(functionOrObjectLit);
533592
functionOrObjectLit.replaceWith(replacement);
593+
wrapperFunctions.add(iife);
534594
returnNode.addChildToFront(functionOrObjectLit);
535595
compiler.reportChangeToEnclosingScope(replacement);
536596
}
597+
return wrapperFunctions;
598+
}
599+
600+
/*
601+
* Renames all references within the functionOrObjectLit body from the old loopObjectName
602+
* (e.g. "$jscomp$loop$1") to the given loopObjectParamName
603+
* (e.g. $jscomp$loop$1$jscomp$loop_param$m123..456).
604+
*/
605+
private void updateNames(Node node, Node paramName, String loopObjectName) {
606+
607+
if (node.isName() && !node.getString().isEmpty() /* not an anonymous function name */) {
608+
if (node.getString().contains(loopObjectName)) {
609+
node.setString(paramName.getString());
610+
return;
611+
}
612+
}
613+
for (Node child = node.getFirstChild(); child != null; child = child.getNext()) {
614+
updateNames(child, paramName, loopObjectName);
615+
}
537616
}
538617

539618
/**
@@ -692,6 +771,21 @@ private Node createLoopVarReferenceReplacement(
692771
return replacement;
693772
}
694773

774+
/**
775+
* Gets a name other than the loop object name to use for parameter names of the new wrapper
776+
* functions being added in the loop.
777+
*
778+
* <p>For a loop object "$jscomp$loop$0", this creates a unique parameter name like
779+
* "$jscomp$loop$0$jscomp$loop_param$1".
780+
*/
781+
private Node createUniqueParameterNameToUseWithinLoop(Node node, LoopObject loopObject) {
782+
return astFactory.createName(
783+
loopObject.name
784+
+ LOOP_PARAM_NAME_PREFIX
785+
+ uniqueIdSupplier.getUniqueId(compiler.getInput(NodeUtil.getInputId(node))),
786+
type(StandardColors.TOP_OBJECT));
787+
}
788+
695789
private Node createLoopObjectNameNode(LoopObject loopObject) {
696790
return astFactory.createName(loopObject.name, type(StandardColors.TOP_OBJECT));
697791
}

0 commit comments

Comments
 (0)