1414
1515package com .google .devtools .j2objc .translate ;
1616
17+ import com .google .common .collect .ImmutableList ;
1718import com .google .devtools .j2objc .ast .AnnotationTypeDeclaration ;
19+ import com .google .devtools .j2objc .ast .Block ;
20+ import com .google .devtools .j2objc .ast .CatchClause ;
1821import com .google .devtools .j2objc .ast .CompilationUnit ;
22+ import com .google .devtools .j2objc .ast .EnhancedForStatement ;
1923import com .google .devtools .j2objc .ast .EnumDeclaration ;
24+ import com .google .devtools .j2objc .ast .ForStatement ;
2025import com .google .devtools .j2objc .ast .LambdaExpression ;
26+ import com .google .devtools .j2objc .ast .MethodDeclaration ;
2127import com .google .devtools .j2objc .ast .RecordDeclaration ;
2228import com .google .devtools .j2objc .ast .SimpleName ;
29+ import com .google .devtools .j2objc .ast .SingleVariableDeclaration ;
2330import com .google .devtools .j2objc .ast .TreeUtil ;
2431import com .google .devtools .j2objc .ast .TypeDeclaration ;
2532import com .google .devtools .j2objc .ast .UnitTreeVisitor ;
33+ import com .google .devtools .j2objc .ast .VariableDeclarationFragment ;
2634import com .google .devtools .j2objc .util .ElementUtil ;
2735import com .google .devtools .j2objc .util .ErrorUtil ;
28- import java .util .ArrayList ;
36+ import com .google .errorprone .annotations .CanIgnoreReturnValue ;
37+ import java .util .ArrayDeque ;
38+ import java .util .Deque ;
2939import java .util .HashSet ;
30- import java .util .List ;
3140import java .util .Set ;
41+ import javax .lang .model .element .Element ;
3242import javax .lang .model .element .ExecutableElement ;
3343import javax .lang .model .element .TypeElement ;
3444import javax .lang .model .element .VariableElement ;
4151@ SuppressWarnings ("UngroupedOverloads" )
4252public class VariableRenamer extends UnitTreeVisitor {
4353
44- private List <Set <String >> fieldNameStack = new ArrayList <>();
45- private Set <TypeElement > renamedTypes = new HashSet <>();
54+ private final Deque <Set <String >> fieldNameStack = new ArrayDeque <>();
55+ private final Set <TypeElement > renamedTypes = new HashSet <>();
56+ // Keep track of the variables and names in a scope so we can rename variables in a
57+ // scope-aware manner; start with a sentinel empty scope.
58+ private final Deque <Scope > scopes = new ArrayDeque <>(ImmutableList .of (new Scope ()));
59+
60+ // Keep track of variables that are in scope and the names that have already been used.
61+ private static class Scope {
62+ private final Set <VariableElement > variables = new HashSet <>();
63+ private final Set <String > usedNames = new HashSet <>();
64+
65+ private Scope (Scope enclosingScope ) {
66+ this .variables .addAll (enclosingScope .variables );
67+ this .usedNames .addAll (enclosingScope .usedNames );
68+ }
69+
70+ private Scope () {}
71+
72+ /**
73+ * Called when a variable reference has been seen.
74+ *
75+ * @return true if its the variable needs renaming, false if it is seen for the first time.
76+ */
77+ private boolean needsRenaming (VariableElement variable ) {
78+ if (!variables .add (variable )) {
79+ // Variable has already been seen in the scope so its name was already resolved.
80+ return false ;
81+ }
82+ return !usedNames .add (ElementUtil .getName (variable ));
83+ }
84+ }
4685
4786 public VariableRenamer (CompilationUnit unit ) {
4887 super (unit );
@@ -102,11 +141,28 @@ private void pushType(TypeElement type) {
102141 for (VariableElement field : fields ) {
103142 fullFieldNames .add (nameTable .getVariableShortName (field ));
104143 }
105- fieldNameStack .add (fullFieldNames );
144+ fieldNameStack .push (fullFieldNames );
145+ // A nested type defines a new nested scope. The used names from the enclosing scope are kept
146+ // since the nested type can reference variables from the enclosing scope.
147+ Scope newScope = enterVariableScope ();
148+ // Mark all the field names as unavailable for renaming.
149+ newScope .usedNames .addAll (fullFieldNames );
106150 }
107151
108152 private void popType () {
109- fieldNameStack .remove (fieldNameStack .size () - 1 );
153+ fieldNameStack .pop ();
154+ scopes .pop ();
155+ }
156+
157+ @ CanIgnoreReturnValue
158+ private Scope enterVariableScope () {
159+ Scope newScope = new Scope (scopes .peek ());
160+ scopes .push (newScope );
161+ return newScope ;
162+ }
163+
164+ private void exitVariableScope () {
165+ scopes .pop ();
110166 }
111167
112168 @ Override
@@ -119,13 +175,8 @@ public void endVisit(SimpleName node) {
119175 // Make sure fields for the declaring type are renamed.
120176 collectAndRenameFields (ElementUtil .getDeclaringClass (var ), new HashSet <VariableElement >());
121177 } else {
122- // Local variable or parameter. Rename if it shares a name with a field.
123- String varName = ElementUtil .getName (var );
124- assert fieldNameStack .size () > 0 ;
125- Set <String > fieldNames = fieldNameStack .get (fieldNameStack .size () - 1 );
126- if (fieldNames .contains (varName )) {
127- nameTable .setVariableName (var , varName + "Arg" );
128- }
178+ // Local variable or parameter.
179+ handleVariable (var );
129180 }
130181 }
131182
@@ -183,4 +234,98 @@ public boolean visit(RecordDeclaration node) {
183234 public void endVisit (RecordDeclaration node ) {
184235 popType ();
185236 }
237+
238+ @ Override
239+ public boolean visit (MethodDeclaration node ) {
240+ enterVariableScope ();
241+ return true ;
242+ }
243+
244+ @ Override
245+ public void endVisit (MethodDeclaration node ) {
246+ exitVariableScope ();
247+ }
248+
249+ @ Override
250+ public boolean visit (Block node ) {
251+ enterVariableScope ();
252+ return true ;
253+ }
254+
255+ @ Override
256+ public void endVisit (Block node ) {
257+ exitVariableScope ();
258+ }
259+
260+ @ Override
261+ public boolean visit (ForStatement node ) {
262+ enterVariableScope ();
263+ return true ;
264+ }
265+
266+ @ Override
267+ public void endVisit (ForStatement node ) {
268+ exitVariableScope ();
269+ }
270+
271+ @ Override
272+ public boolean visit (EnhancedForStatement node ) {
273+ enterVariableScope ();
274+ return true ;
275+ }
276+
277+ @ Override
278+ public void endVisit (EnhancedForStatement node ) {
279+ exitVariableScope ();
280+ }
281+
282+ @ Override
283+ public boolean visit (CatchClause node ) {
284+ enterVariableScope ();
285+ return true ;
286+ }
287+
288+ @ Override
289+ public void endVisit (CatchClause node ) {
290+ exitVariableScope ();
291+ }
292+
293+ @ Override
294+ public boolean visit (SingleVariableDeclaration variableDeclaration ) {
295+ handleVariable (variableDeclaration .getVariableElement ());
296+ return true ;
297+ }
298+
299+ @ Override
300+ public boolean visit (VariableDeclarationFragment variableDeclaration ) {
301+ handleVariable (variableDeclaration .getVariableElement ());
302+ return true ;
303+ }
304+
305+ private void handleVariable (VariableElement variable ) {
306+ if (variable .getKind ().isField ()) {
307+ return ;
308+ }
309+ Scope scope = scopes .peek ();
310+ if (scope .needsRenaming (variable )) {
311+ // Variable needs renaming. Preserve the logic that renames parameters with "Arg" suffix.
312+ String baseName = ElementUtil .getName (variable ) + (isParameter (variable ) ? "Arg" : "" );
313+ String suffix = "" ;
314+ int count = 1 ;
315+ while (scope .usedNames .contains (baseName + suffix )) {
316+ suffix = "_" + count ++;
317+ }
318+ String newName = baseName + suffix ;
319+ nameTable .setVariableName (variable , newName );
320+ scope .usedNames .add (newName );
321+ }
322+ }
323+
324+ private boolean isParameter (VariableElement variable ) {
325+ Element element = variable .getEnclosingElement ();
326+ if (element instanceof ExecutableElement executableElement ) {
327+ return executableElement .getParameters ().contains (variable );
328+ }
329+ return false ;
330+ }
186331}
0 commit comments