Skip to content

Commit dc17409

Browse files
[PatternMatchingInstanceof] Make negated matches optional
This introduces a configuration option that allows one to disable the replacement of casts that are implied by a negated instanceof expression and are not within the same expression as the instanceof. This affects casts in else branches and those below an if statement where the then branch always returns from the method. Casts where the readability benefit of a pattern-maching instanceof is clear, i.e. those within the then branch of an if and those inside the same expression, are still replaced. The default of the option is true, which keeps the current behavior. Fixes #4925
1 parent 1d13006 commit dc17409

File tree

3 files changed

+142
-10
lines changed

3 files changed

+142
-10
lines changed

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

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.google.common.collect.ImmutableList;
3131
import com.google.common.collect.ImmutableSet;
3232
import com.google.errorprone.BugPattern;
33+
import com.google.errorprone.ErrorProneFlags;
3334
import com.google.errorprone.VisitorState;
3435
import com.google.errorprone.bugpatterns.BugChecker.InstanceOfTreeMatcher;
3536
import com.google.errorprone.fixes.SuggestedFix;
@@ -55,6 +56,7 @@
5556
import java.util.HashSet;
5657
import java.util.concurrent.atomic.AtomicBoolean;
5758
import java.util.stream.Stream;
59+
import javax.inject.Inject;
5860
import javax.lang.model.SourceVersion;
5961
import org.jspecify.annotations.Nullable;
6062

@@ -64,6 +66,14 @@
6466
summary = "This code can be simplified to use a pattern-matching instanceof.")
6567
public final class PatternMatchingInstanceof extends BugChecker implements InstanceOfTreeMatcher {
6668

69+
private final boolean enableNegatedMatches;
70+
71+
@Inject
72+
PatternMatchingInstanceof(ErrorProneFlags flags) {
73+
this.enableNegatedMatches =
74+
flags.getBoolean("PatternMatchingInstanceof:EnableNegatedMatches").orElse(true);
75+
}
76+
6777
@Override
6878
public Description matchInstanceOf(InstanceOfTree instanceOfTree, VisitorState state) {
6979
if (!supportsPatternMatchingInstanceof(state.context)) {
@@ -162,7 +172,7 @@ private static String generateVariableName(Type targetType, VisitorState state)
162172
}
163173

164174
/** Finds trees which are implied by the {@code instanceOfTree}. */
165-
private static ImmutableList<Tree> findImpliedStatements(
175+
private ImmutableList<Tree> findImpliedStatements(
166176
InstanceOfTree tree, VisitorState state) {
167177
Tree last = tree;
168178
boolean negated = false;
@@ -198,15 +208,18 @@ private static ImmutableList<Tree> findImpliedStatements(
198208
return impliedStatements.build();
199209
}
200210
if (negated) {
201-
if (ifTree.getElseStatement() != null) {
202-
impliedStatements.add(ifTree.getElseStatement());
203-
}
204-
if (!Reachability.canCompleteNormally(ifTree.getThenStatement())) {
205-
var pparent = parentPath.getParentPath().getLeaf();
206-
if (pparent instanceof BlockTree blockTree) {
207-
var index = blockTree.getStatements().indexOf(ifTree);
208-
impliedStatements.addAll(
209-
blockTree.getStatements().subList(index + 1, blockTree.getStatements().size()));
211+
if (enableNegatedMatches) {
212+
if (ifTree.getElseStatement() != null) {
213+
impliedStatements.add(ifTree.getElseStatement());
214+
}
215+
if (!Reachability.canCompleteNormally(ifTree.getThenStatement())) {
216+
var pparent = parentPath.getParentPath().getLeaf();
217+
if (pparent instanceof BlockTree blockTree) {
218+
var index = blockTree.getStatements().indexOf(ifTree);
219+
impliedStatements.addAll(
220+
blockTree.getStatements().subList(
221+
index + 1, blockTree.getStatements().size()));
222+
}
210223
}
211224
}
212225
} else {

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

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,36 @@ void test(Object o) {
5757
.doTest();
5858
}
5959

60+
@Test
61+
public void positive_disabledNegation() {
62+
helper
63+
.addInputLines(
64+
"Test.java",
65+
"""
66+
class Test {
67+
void test(Object o) {
68+
if (o instanceof Test) {
69+
Test test = (Test) o;
70+
test(test);
71+
}
72+
}
73+
}
74+
""")
75+
.addOutputLines(
76+
"Test.java",
77+
"""
78+
class Test {
79+
void test(Object o) {
80+
if (o instanceof Test test) {
81+
test(test);
82+
}
83+
}
84+
}
85+
""")
86+
.setArgs("-XepOpt:PatternMatchingInstanceof:EnableNegatedMatches=false")
87+
.doTest();
88+
}
89+
6090
@Test
6191
public void negatedIf() {
6292
helper
@@ -88,6 +118,27 @@ void test(Object o) {
88118
.doTest();
89119
}
90120

121+
@Test
122+
public void negatedIf_disabledNegation() {
123+
helper
124+
.addInputLines(
125+
"Test.java",
126+
"""
127+
class Test {
128+
void test(Object o) {
129+
if (!(o instanceof Test)) {
130+
} else {
131+
Test test = (Test) o;
132+
test(test);
133+
}
134+
}
135+
}
136+
""")
137+
.expectUnchanged()
138+
.setArgs("-XepOpt:PatternMatchingInstanceof:EnableNegatedMatches=false")
139+
.doTest();
140+
}
141+
91142
@Test
92143
public void negatedIf_withOrs() {
93144
helper
@@ -150,6 +201,27 @@ void test(Object o) {
150201
.doTest();
151202
}
152203

204+
@Test
205+
public void negatedIfWithReturn_disabledNegation() {
206+
helper
207+
.addInputLines(
208+
"Test.java",
209+
"""
210+
class Test {
211+
void test(Object o) {
212+
if (!(o instanceof Test)) {
213+
return;
214+
}
215+
Test test = (Test) o;
216+
test(test);
217+
}
218+
}
219+
""")
220+
.expectUnchanged()
221+
.setArgs("-XepOpt:PatternMatchingInstanceof:EnableNegatedMatches=false")
222+
.doTest();
223+
}
224+
153225
@Test
154226
public void negatedIf_butNoDefiniteReturn_noFinding() {
155227
helper
@@ -581,6 +653,47 @@ public boolean equals(Object o) {
581653
.doTest();
582654
}
583655

656+
@Test
657+
public void withinIfCondition_andUsedAfter_disabledNegation() {
658+
helper
659+
.addInputLines(
660+
"Test.java",
661+
"""
662+
class Test {
663+
private final int x = 0;
664+
private final int y = 1;
665+
666+
@Override
667+
public boolean equals(Object o) {
668+
if (!(o instanceof Test) || ((Test) o).x != this.x) {
669+
return false;
670+
}
671+
Test other = (Test) o;
672+
return other.y == this.y;
673+
}
674+
}
675+
""")
676+
.addOutputLines(
677+
"Test.java",
678+
"""
679+
class Test {
680+
private final int x = 0;
681+
private final int y = 1;
682+
683+
@Override
684+
public boolean equals(Object o) {
685+
if (!(o instanceof Test test) || test.x != this.x) {
686+
return false;
687+
}
688+
Test other = (Test) o;
689+
return other.y == this.y;
690+
}
691+
}
692+
""")
693+
.setArgs("-XepOpt:PatternMatchingInstanceof:EnableNegatedMatches=false")
694+
.doTest();
695+
}
696+
584697
@Test
585698
public void conditionalExpression() {
586699
helper

docs/bugpattern/PatternMatchingInstanceof.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,11 @@ void handle(Object o) {
2424
}
2525
```
2626

27+
If the flag `-XepOpt:PatternMatchingInstanceof:EnableNegatedMatches=false` is used,
28+
only casts that are in the same expression as the instanceof
29+
or in the block that starts with the instanceof are rewritten,
30+
but casts inside else branches or below an if statement with a negated instanceof
31+
are left unchanged.
32+
2733
For more information on pattern matching and `instanceof`, see
2834
[Pattern Matching for the instanceof Operator](https://docs.oracle.com/en/java/javase/21/language/pattern-matching-instanceof.html)

0 commit comments

Comments
 (0)