Skip to content

Commit b5f63df

Browse files
brad4dcopybara-github
authored andcommitted
Use MakeDeclaredNamesUnique in Es6RewriteBlockScopedDeclaration
Replace the logic in Es6RewriteBlockScopedDeclaration that creates unique names for block scoped variable names with a use of MakeDeclaredNamesUnique. 1. Replaces 3 traversals with a a single traversal using MakeDeclaredNamesUnique. 2. Ensures the chosen names are of the kind that can be reverted later if the user has requested transpile-only or no optimization without variable renaming. 3. MakeDeclaredNamesUnique does a better job than the code that was deleted in all but one edge case explicitly pointed out in comments in the unit test. Why is this OK? 1. Transpilation always happens after type checking now. There's no need to maintain original names even on global-ish block-scoped names. (Was that ever really necessary?) 2. We're about to move this pass after Normalization, which may have already forced unique names, so the originals aren't preserved anyway. In fact, when that is done, we can drop the usage of MakeDeclaredNamesUnique from this pass, since Normalization will have already done it. Also in this change: 1. Rely on local variables already having unique names for loop object properties to use. 2. Update the Es6RewriteBlockScopedDeclarationTest to use "LOOP" as a placeholder prefix for the loop object variables in expected output strings. PiperOrigin-RevId: 552536735
1 parent 577b720 commit b5f63df

File tree

4 files changed

+675
-720
lines changed

4 files changed

+675
-720
lines changed

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

Lines changed: 16 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,9 @@
2020
import static com.google.javascript.jscomp.AstFactory.type;
2121

2222
import com.google.common.base.Predicate;
23-
import com.google.common.collect.HashBasedTable;
2423
import com.google.common.collect.HashMultimap;
2524
import com.google.common.collect.LinkedHashMultimap;
2625
import com.google.common.collect.SetMultimap;
27-
import com.google.common.collect.Table;
2826
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
2927
import com.google.javascript.jscomp.NodeTraversal.AbstractPreOrderCallback;
3028
import com.google.javascript.jscomp.colors.StandardColors;
@@ -45,27 +43,20 @@
4543
*
4644
* <p>Note that this must run after Es6RewriteDestructuring, since it does not process destructuring
4745
* let/const declarations at all.
48-
*
49-
* <p>TODO(moz): Try to use MakeDeclaredNamesUnique
5046
*/
5147
public final class Es6RewriteBlockScopedDeclaration extends AbstractPostOrderCallback
5248
implements CompilerPass {
5349

5450
private final AbstractCompiler compiler;
5551
private final AstFactory astFactory;
56-
private final Table<Node, String, String> renameTable = HashBasedTable.create();
5752
private final Set<Node> letConsts = new LinkedHashSet<>();
58-
private final Set<String> undeclaredNames = new LinkedHashSet<>();
59-
private final Set<String> externNames = new LinkedHashSet<>();
6053
private static final FeatureSet transpiledFeatures =
6154
FeatureSet.BARE_MINIMUM.with(Feature.LET_DECLARATIONS, Feature.CONST_DECLARATIONS);
6255
private final UniqueIdSupplier uniqueIdSupplier;
63-
private final boolean astMayHaveUndeclaredVariables;
6456

6557
public Es6RewriteBlockScopedDeclaration(AbstractCompiler compiler) {
6658
this.compiler = compiler;
6759
this.uniqueIdSupplier = compiler.getUniqueIdSupplier();
68-
this.astMayHaveUndeclaredVariables = compiler.getOptions().skipNonTranspilationPasses;
6960
this.astFactory = compiler.createAstFactory();
7061
}
7162

@@ -82,25 +73,24 @@ public void visit(NodeTraversal t, Node n, Node parent) {
8273
}
8374
if (NodeUtil.isNameDeclaration(n)) {
8475
for (Node nameNode = n.getFirstChild(); nameNode != null; nameNode = nameNode.getNext()) {
85-
visitBlockScopedName(t, n, nameNode);
76+
visitBlockScopedNameDeclaration(n, nameNode);
8677
}
8778
} else {
8879
// NOTE: This pass depends on class declarations having been transpiled away
8980
checkState(n.isFunction() || n.isCatch(), "Unexpected declaration node: %s", n);
90-
visitBlockScopedName(t, n, n.getFirstChild());
81+
visitBlockScopedNameDeclaration(n, n.getFirstChild());
9182
}
9283
}
9384

9485
@Override
9586
public void process(Node externs, Node root) {
96-
if (this.astMayHaveUndeclaredVariables) {
97-
// If we are only transpiling, we may have undefined variables in the code.
98-
NodeTraversal.traverse(compiler, root, new CollectUndeclaredNames());
99-
}
100-
// Record names declared in externs to prevent collisions when declaring vars from let/const.
101-
this.externNames.addAll(NodeUtil.collectExternVariableNames(compiler, externs));
87+
// Make all declared names unique, so we can safely just switch 'let' or 'const' to 'var' in all
88+
// non-loop cases.
89+
MakeDeclaredNamesUnique renamer = MakeDeclaredNamesUnique.builder().build();
90+
NodeTraversal.traverseRoots(compiler, renamer, externs, root);
91+
// - Gather a list of let & const variables
92+
// - Also add `= void 0` to any that are not initialized.
10293
NodeTraversal.traverse(compiler, root, this);
103-
NodeTraversal.traverse(compiler, root, new Es6RenameReferences(renameTable));
10494
LoopClosureTransformer transformer = new LoopClosureTransformer();
10595
NodeTraversal.traverse(compiler, root, transformer);
10696
transformer.transformLoopClosure();
@@ -113,8 +103,7 @@ public void process(Node externs, Node root) {
113103
*
114104
* <p>Also normalizes declarations with no initializer in a loop to be initialized to undefined.
115105
*/
116-
private void visitBlockScopedName(NodeTraversal t, Node decl, Node nameNode) {
117-
Scope scope = t.getScope();
106+
private void visitBlockScopedNameDeclaration(Node decl, Node nameNode) {
118107
Node parent = decl.getParent();
119108
// Normalize "let x;" to "let x = undefined;" if in a loop, since we later convert x
120109
// to be $jscomp$loop$0.x and want to reset the property to undefined every loop iteration.
@@ -126,26 +115,6 @@ && inLoop(decl)) {
126115
nameNode.addChildToFront(undefined);
127116
compiler.reportChangeToEnclosingScope(undefined);
128117
}
129-
130-
String oldName = nameNode.getString();
131-
Scope hoistScope = scope.getClosestHoistScope();
132-
if (scope != hoistScope) {
133-
String newName = oldName;
134-
if (hoistScope.hasSlot(oldName)
135-
|| undeclaredNames.contains(oldName)
136-
|| externNames.contains(oldName)) {
137-
do {
138-
newName = oldName + "$" + uniqueIdSupplier.getUniqueId(t.getInput());
139-
} while (hoistScope.hasSlot(newName));
140-
nameNode.setString(newName);
141-
compiler.reportChangeToEnclosingScope(nameNode);
142-
Node scopeRoot = scope.getRootNode();
143-
renameTable.put(scopeRoot, oldName, newName);
144-
}
145-
Var oldVar = scope.getVar(oldName);
146-
scope.undeclare(oldVar);
147-
hoistScope.declare(newName, nameNode, oldVar.getInput());
148-
}
149118
}
150119

151120
/**
@@ -219,35 +188,17 @@ private void rewriteDeclsToVars() {
219188
}
220189
}
221190

222-
/**
223-
* Records undeclared names and aggressively rename possible references to them. Eg: In "{ let
224-
* inner; } use(inner);", we rename the let declared variable.
225-
*/
226-
private class CollectUndeclaredNames extends AbstractPostOrderCallback {
227-
228-
@Override
229-
public void visit(NodeTraversal t, Node n, Node parent) {
230-
if (n.isName() && !t.getScope().hasSlot(n.getString())) {
231-
undeclaredNames.add(n.getString());
232-
}
233-
}
234-
}
235-
236191
/** Transforms let/const declarations captured by loop closures. */
237192
private class LoopClosureTransformer extends AbstractPreOrderCallback {
238193

239194
private static final String LOOP_OBJECT_NAME = "$jscomp$loop";
240-
private static final String LOOP_OBJECT_PROPERTY_NAME = "$jscomp$loop$prop$";
241195
private final Map<Node, LoopObject> loopObjectMap = new LinkedHashMap<>();
242196

243197
private final SetMultimap<Node, LoopObject> nodesRequiringLoopObjectsClosureMap =
244198
LinkedHashMultimap.create();
245199
private final SetMultimap<Node, String> nodesHandledForLoopObjectClosure =
246200
HashMultimap.create();
247201
private final SetMultimap<Var, Node> referenceMap = LinkedHashMultimap.create();
248-
// Maps from a var to a unique property name for that var
249-
// e.g. 'i' -> '$jscomp$loop$prop$i$0'
250-
private final Map<Var, String> propertyNameMap = new LinkedHashMap<>();
251202

252203
@Override
253204
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
@@ -322,9 +273,7 @@ public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
322273
LoopObject object =
323274
loopObjectMap.computeIfAbsent(
324275
loopNode, (Node k) -> new LoopObject(createUniqueObjectName(t.getInput())));
325-
String newPropertyName = createUniquePropertyName(var);
326276
object.vars.add(var);
327-
propertyNameMap.put(var, newPropertyName);
328277
nodesRequiringLoopObjectsClosureMap.put(nodeToWrapInClosure, object);
329278
}
330279
return true;
@@ -334,13 +283,11 @@ private String createUniqueObjectName(CompilerInput input) {
334283
return LOOP_OBJECT_NAME + "$" + uniqueIdSupplier.getUniqueId(input);
335284
}
336285

337-
// Normalization guarantees unique variable names so generating a unique ID is necessary
338-
// because this pass runs after normalization.
339-
private String createUniquePropertyName(Var var) {
340-
return LOOP_OBJECT_PROPERTY_NAME
341-
+ var.getName()
342-
+ "$"
343-
+ uniqueIdSupplier.getUniqueId(var.getInput());
286+
private String getLoopObjPropName(Var var) {
287+
// NOTE: var.getName() would be wrong here, because it will still contain the original
288+
// and possibly non-unique name for the variable. However, the name node itself will already
289+
// have the new and guaranteed-globally-unique name.
290+
return var.getNameNode().getString();
344291
}
345292

346293
private void transformLoopClosure() {
@@ -445,7 +392,7 @@ private void changeVanillaForLoopHeader(Node loopNode, Node updateLoopObject) {
445392
*/
446393
private void changeLoopLocalVariablesToProperties(Node loopNode, LoopObject loopObject) {
447394
for (Var var : loopObject.vars) {
448-
String newPropertyName = propertyNameMap.get(var);
395+
String newPropertyName = getLoopObjPropName(var);
449396
for (Node reference : referenceMap.get(var)) {
450397
// for-of loops are transpiled away before this pass runs
451398
checkState(!loopNode.isForOf(), loopNode);
@@ -546,7 +493,7 @@ private void assignLoopVarToLoopObjectProperty(
546493
/** Rename all variables in the loop object to properties */
547494
private void renameVarsToProperties(LoopObject loopObject, Node objectLitNextIteration) {
548495
for (Var var : loopObject.vars) {
549-
String newPropertyName = propertyNameMap.get(var);
496+
String newPropertyName = getLoopObjPropName(var);
550497
objectLitNextIteration.addChildToBack(
551498
astFactory.createStringKey(
552499
newPropertyName,

0 commit comments

Comments
 (0)