Skip to content

Commit 831da36

Browse files
mirkoalicastroError Prone Team
authored andcommitted
Fixes #5553 : Skip UnnecessaryOptionalGet when lambda parameter is unnamed
`UnnecessaryOptionalGet` suggested replacing `.get()` with the lambda parameter name, but the parameter `_` (unnamed variable, Java 22+) produces uncompilable code since `_` cannot be referenced. Changes: - Added an early return when the lambda parameter source is `_` - Added a test guarded by `assume().that(Runtime.version().feature()).isAtLeast(22)` Fixes #5553 Fixes #5649 COPYBARA_INTEGRATE_REVIEW=#5649 from mirkoalicastro:mirkoalicastro/fix-unnecessary-optional-get-unnamed-variable bdcd042 PiperOrigin-RevId: 899107954
1 parent 7c4b040 commit 831da36

5 files changed

Lines changed: 94 additions & 52 deletions

File tree

check_api/src/main/java/com/google/errorprone/fixes/SuggestedFixes.java

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
import java.util.Comparator;
123123
import java.util.Deque;
124124
import java.util.EnumSet;
125+
import java.util.HashSet;
125126
import java.util.Iterator;
126127
import java.util.List;
127128
import java.util.ListIterator;
@@ -743,11 +744,20 @@ public static SuggestedFix renameVariable(
743744
// and a tree without an end position for earlier versions.
744745
int typeEndPos = tree.getType() != null ? state.getEndPosition(tree.getType()) : -1;
745746
int searchOffset = typeEndPos == -1 ? 0 : (typeEndPos - startPos);
746-
int pos = startPos + state.getSourceForNode(tree).indexOf(name, searchOffset);
747-
return SuggestedFix.builder()
748-
.replace(pos, pos + name.length(), replacement)
749-
.merge(renameVariableUsages(tree, replacement, state))
750-
.build();
747+
SuggestedFix.Builder fix = SuggestedFix.builder();
748+
state.getOffsetTokens(startPos + searchOffset, state.getEndPosition(tree)).stream()
749+
.filter(
750+
token ->
751+
switch (token.kind()) {
752+
// VariableTree#getName is empty for unnamed _ variables
753+
case UNDERSCORE -> name.isEmpty();
754+
case IDENTIFIER -> token.name().contentEquals(name);
755+
default -> false;
756+
})
757+
.findFirst()
758+
.ifPresent(token -> fix.replace(token.pos(), token.endPos(), replacement));
759+
fix.merge(renameVariableUsages(tree, replacement, state));
760+
return fix.build();
751761
}
752762

753763
/**
@@ -1861,5 +1871,30 @@ public static Visibility from(Tree tree) {
18611871
}
18621872
}
18631873

1874+
public static VariableNamer variableNamer(VisitorState state) {
1875+
return new VariableNamer(state);
1876+
}
1877+
1878+
/** Helper class for avoiding variable name shadowing. */
1879+
public static class VariableNamer {
1880+
private final Set<String> idents;
1881+
1882+
private VariableNamer(VisitorState state) {
1883+
this.idents =
1884+
FindIdentifiers.findAllIdents(state).stream()
1885+
.map(s -> s.getSimpleName().toString())
1886+
.collect(toCollection(HashSet::new));
1887+
}
1888+
1889+
public String avoidShadowing(String name) {
1890+
for (int i = 1; ; i++) {
1891+
String n = i == 1 ? name : (name + i);
1892+
if (idents.add(n)) {
1893+
return n;
1894+
}
1895+
}
1896+
}
1897+
}
1898+
18641899
private SuggestedFixes() {}
18651900
}

core/src/main/java/com/google/errorprone/bugpatterns/AssertThrowsMinimizer.java

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import static com.google.errorprone.util.ASTHelpers.getType;
3232
import static com.google.errorprone.util.ASTHelpers.isCheckedExceptionType;
3333
import static com.google.errorprone.util.ASTHelpers.isSubtype;
34-
import static java.util.stream.Collectors.toCollection;
3534

3635
import com.google.common.base.CaseFormat;
3736
import com.google.common.collect.ImmutableList;
@@ -47,7 +46,6 @@
4746
import com.google.errorprone.matchers.Description;
4847
import com.google.errorprone.matchers.Matcher;
4948
import com.google.errorprone.predicates.TypePredicates;
50-
import com.google.errorprone.util.FindIdentifiers;
5149
import com.sun.source.tree.BlockTree;
5250
import com.sun.source.tree.ExpressionStatementTree;
5351
import com.sun.source.tree.ExpressionTree;
@@ -67,10 +65,7 @@
6765
import com.sun.tools.javac.code.Symbol.MethodSymbol;
6866
import com.sun.tools.javac.code.Type;
6967
import com.sun.tools.javac.code.Types;
70-
import java.util.HashSet;
7168
import java.util.Optional;
72-
import java.util.Set;
73-
import java.util.stream.IntStream;
7469
import java.util.stream.Stream;
7570
import javax.inject.Inject;
7671
import javax.lang.model.element.ElementKind;
@@ -197,8 +192,9 @@ public Description matchMethod(MethodTree tree, VisitorState state) {
197192
}
198193

199194
// update the tree path so VariableName considers the method parameters
200-
VariableNamer variableNamer =
201-
new VariableNamer(state.withPath(new TreePath(state.getPath(), toFix.getFirst().runnable)));
195+
SuggestedFixes.VariableNamer variableNamer =
196+
SuggestedFixes.variableNamer(
197+
state.withPath(new TreePath(state.getPath(), toFix.getFirst().runnable)));
202198
for (AssertThrows current : toFix) {
203199
StringBuilder hoistedVariables = new StringBuilder();
204200
for (Hoist hoist : current.toHoist) {
@@ -367,25 +363,4 @@ private static boolean isCheckedException(Type exception, VisitorState state) {
367363
.forClass(
368364
TypePredicates.isDescendantOf(
369365
"com.google.android.gms.tagmanager.internal.type.AbstractType")));
370-
371-
private static class VariableNamer {
372-
private final Set<String> idents;
373-
374-
VariableNamer(VisitorState state) {
375-
this.idents =
376-
FindIdentifiers.findAllIdents(state).stream()
377-
.map(s -> s.getSimpleName().toString())
378-
.collect(toCollection(HashSet::new));
379-
}
380-
381-
// Stolen from PatternMatchingInstanceof
382-
// TODO: cushon - add to SuggestedFixes?
383-
private String avoidShadowing(String name) {
384-
return IntStream.iterate(1, i -> i + 1)
385-
.mapToObj(i -> i == 1 ? name : (name + i))
386-
.filter(n -> idents.add(n))
387-
.findFirst()
388-
.get();
389-
}
390-
}
391366
}

core/src/main/java/com/google/errorprone/bugpatterns/PatternMatchingInstanceof.java

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package com.google.errorprone.bugpatterns;
1818

1919
import static com.google.common.base.Ascii.toLowerCase;
20-
import static com.google.common.collect.ImmutableSet.toImmutableSet;
2120
import static com.google.common.collect.Streams.stream;
2221
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
2322
import static com.google.errorprone.fixes.SuggestedFix.mergeFixes;
@@ -36,8 +35,8 @@
3635
import com.google.errorprone.bugpatterns.threadsafety.ConstantExpressions;
3736
import com.google.errorprone.bugpatterns.threadsafety.ConstantExpressions.ConstantExpression;
3837
import com.google.errorprone.fixes.SuggestedFix;
38+
import com.google.errorprone.fixes.SuggestedFixes;
3939
import com.google.errorprone.matchers.Description;
40-
import com.google.errorprone.util.FindIdentifiers;
4140
import com.google.errorprone.util.Reachability;
4241
import com.sun.source.tree.BinaryTree;
4342
import com.sun.source.tree.BlockTree;
@@ -56,7 +55,6 @@
5655
import com.sun.tools.javac.code.Type;
5756
import com.sun.tools.javac.code.TypeTag;
5857
import java.util.HashSet;
59-
import java.util.stream.IntStream;
6058
import java.util.stream.Stream;
6159
import javax.inject.Inject;
6260
import javax.lang.model.SourceVersion;
@@ -167,24 +165,12 @@ private static String generateVariableName(Type targetType, VisitorState state)
167165
String simpleName = IdentifierNames.fixInitialisms(targetType.tsym.getSimpleName().toString());
168166
String lowerFirstLetter = toLowerCase(String.valueOf(simpleName.charAt(0)));
169167
String camelCased = lowerFirstLetter + simpleName.substring(1);
168+
SuggestedFixes.VariableNamer variableNamer = SuggestedFixes.variableNamer(state);
170169
if (SourceVersion.isKeyword(camelCased)
171170
|| (unboxed != null && unboxed.getTag() != TypeTag.NONE)) {
172-
return avoidShadowing(lowerFirstLetter, state);
171+
return variableNamer.avoidShadowing(lowerFirstLetter);
173172
}
174-
return avoidShadowing(camelCased, state);
175-
}
176-
177-
// TODO: cushon - add to SuggestedFixes?
178-
private static String avoidShadowing(String name, VisitorState state) {
179-
var idents =
180-
FindIdentifiers.findAllIdents(state).stream()
181-
.map(s -> s.getSimpleName().toString())
182-
.collect(toImmutableSet());
183-
return IntStream.iterate(1, i -> i + 1)
184-
.mapToObj(i -> i == 1 ? name : (name + i))
185-
.filter(n -> !idents.contains(n))
186-
.findFirst()
187-
.get();
173+
return variableNamer.avoidShadowing(camelCased);
188174
}
189175

190176
/** Finds trees which are implied by the {@code instanceOfTree}. */

core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryOptionalGet.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.errorprone.VisitorState;
2626
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
2727
import com.google.errorprone.fixes.SuggestedFix;
28+
import com.google.errorprone.fixes.SuggestedFixes;
2829
import com.google.errorprone.matchers.Description;
2930
import com.google.errorprone.matchers.Matcher;
3031
import com.sun.source.tree.ExpressionTree;
@@ -83,14 +84,22 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState
8384
return Description.NO_MATCH;
8485
}
8586
VariableTree arg = getOnlyElement(lambdaExpressionTree.getParameters());
87+
SuggestedFixes.VariableNamer variableNamer = SuggestedFixes.variableNamer(state);
8688
SuggestedFix.Builder fix = SuggestedFix.builder();
89+
String replacement;
90+
if (arg.getName().isEmpty()) {
91+
replacement = variableNamer.avoidShadowing("value");
92+
fix.merge(SuggestedFixes.renameVariable(arg, replacement, state));
93+
} else {
94+
replacement = arg.getName().toString();
95+
}
8796
new TreeScanner<Void, VisitorState>() {
8897
@Override
8998
public Void visitMethodInvocation(
9099
MethodInvocationTree methodInvocationTree, VisitorState visitorState) {
91100
if (OPTIONAL_GET.matches(methodInvocationTree, visitorState)
92101
&& sameVariable(getReceiver(tree), getReceiver(methodInvocationTree))) {
93-
fix.replace(methodInvocationTree, state.getSourceForNode(arg));
102+
fix.replace(methodInvocationTree, replacement);
94103
}
95104
return super.visitMethodInvocation(methodInvocationTree, visitorState);
96105
}

core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryOptionalGetTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package com.google.errorprone.bugpatterns;
1818

19+
import static com.google.common.truth.TruthJUnit.assume;
20+
1921
import com.google.errorprone.BugCheckerRefactoringTestHelper;
2022
import org.junit.Test;
2123
import org.junit.runner.RunWith;
@@ -77,6 +79,7 @@ public class Test {
7779
private void home() {
7880
Optional<String> op = Optional.of("hello");
7981
op.transform(x -> Long.parseLong(op.get()));
82+
op.transform((String x) -> Long.parseLong(op.get()));
8083
}
8184
}
8285
""")
@@ -89,6 +92,7 @@ public class Test {
8992
private void home() {
9093
Optional<String> op = Optional.of("hello");
9194
op.transform(x -> Long.parseLong(x));
95+
op.transform((String x) -> Long.parseLong(x));
9296
}
9397
}
9498
""")
@@ -388,4 +392,37 @@ private void home() {
388392
""")
389393
.doTest();
390394
}
395+
396+
@Test
397+
public void unnamedVariable_withGet_warning() {
398+
assume().that(Runtime.version().feature()).isAtLeast(22);
399+
refactoringTestHelper
400+
.addInputLines(
401+
"Test.java",
402+
"""
403+
import java.util.Optional;
404+
405+
public class Test {
406+
private void home() {
407+
Optional<String> op = Optional.of("hello");
408+
op.ifPresent(_ -> System.out.println(op.get()));
409+
op.ifPresent((String _) -> System.out.println(op.get()));
410+
}
411+
}
412+
""")
413+
.addOutputLines(
414+
"Test.java",
415+
"""
416+
import java.util.Optional;
417+
418+
public class Test {
419+
private void home() {
420+
Optional<String> op = Optional.of("hello");
421+
op.ifPresent(value -> System.out.println(value));
422+
op.ifPresent((String value) -> System.out.println(value));
423+
}
424+
}
425+
""")
426+
.doTest();
427+
}
391428
}

0 commit comments

Comments
 (0)