Skip to content

Commit ab23a91

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 09fd394 commit ab23a91

File tree

3 files changed

+142
-14
lines changed

3 files changed

+142
-14
lines changed

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

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.common.collect.ImmutableList;
3030
import com.google.common.collect.ImmutableSet;
3131
import com.google.errorprone.BugPattern;
32+
import com.google.errorprone.ErrorProneFlags;
3233
import com.google.errorprone.VisitorState;
3334
import com.google.errorprone.bugpatterns.BugChecker.InstanceOfTreeMatcher;
3435
import com.google.errorprone.bugpatterns.threadsafety.ConstantExpressions;
@@ -64,9 +65,13 @@
6465
public final class PatternMatchingInstanceof extends BugChecker implements InstanceOfTreeMatcher {
6566
private final ConstantExpressions constantExpressions;
6667

68+
private final boolean enableNegatedMatches;
69+
6770
@Inject
68-
PatternMatchingInstanceof(ConstantExpressions constantExpressions) {
71+
PatternMatchingInstanceof(ConstantExpressions constantExpressions, ErrorProneFlags flags) {
6972
this.constantExpressions = constantExpressions;
73+
this.enableNegatedMatches =
74+
flags.getBoolean("PatternMatchingInstanceof:EnableNegatedMatches").orElse(true);
7075
}
7176

7277
@Override
@@ -169,7 +174,7 @@ private static String generateVariableName(Type targetType, VisitorState state)
169174
}
170175

171176
/** Finds trees which are implied by the {@code instanceOfTree}. */
172-
private static ImmutableList<Tree> findImpliedStatements(
177+
private ImmutableList<Tree> findImpliedStatements(
173178
InstanceOfTree tree, VisitorState state) {
174179
Tree last = tree;
175180
boolean negated = false;
@@ -204,19 +209,23 @@ private static ImmutableList<Tree> findImpliedStatements(
204209
if (ifTree.getCondition() != last) {
205210
return impliedStatements.build();
206211
}
207-
StatementTree positiveBranch =
208-
negated ? ifTree.getElseStatement() : ifTree.getThenStatement();
209-
if (positiveBranch != null) {
210-
impliedStatements.add(positiveBranch);
211-
}
212-
StatementTree negativeBranch =
213-
negated ? ifTree.getThenStatement() : ifTree.getElseStatement();
214-
if (negativeBranch != null && !Reachability.canCompleteNormally(negativeBranch)) {
215-
if (parentPath.getParentPath().getLeaf() instanceof BlockTree blockTree) {
216-
var index = blockTree.getStatements().indexOf(ifTree);
217-
impliedStatements.addAll(
218-
blockTree.getStatements().subList(index + 1, blockTree.getStatements().size()));
212+
if (enableNegatedMatches) {
213+
StatementTree positiveBranch =
214+
negated ? ifTree.getElseStatement() : ifTree.getThenStatement();
215+
if (positiveBranch != null) {
216+
impliedStatements.add(positiveBranch);
217+
}
218+
StatementTree negativeBranch =
219+
negated ? ifTree.getThenStatement() : ifTree.getElseStatement();
220+
if (negativeBranch != null && !Reachability.canCompleteNormally(negativeBranch)) {
221+
if (parentPath.getParentPath().getLeaf() instanceof BlockTree blockTree) {
222+
var index = blockTree.getStatements().indexOf(ifTree);
223+
impliedStatements.addAll(
224+
blockTree.getStatements().subList(index + 1, blockTree.getStatements().size()));
225+
}
219226
}
227+
} else if (!negated) {
228+
impliedStatements.add(ifTree.getThenStatement());
220229
}
221230
return impliedStatements.build();
222231
}

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 withinIf_elseCannotCompleteNormally_variableInScopeForStatementsAfter() {
93144
helper
@@ -187,6 +238,27 @@ void test(Object o) {
187238
.doTest();
188239
}
189240

241+
@Test
242+
public void negatedIfWithReturn_disabledNegation() {
243+
helper
244+
.addInputLines(
245+
"Test.java",
246+
"""
247+
class Test {
248+
void test(Object o) {
249+
if (!(o instanceof Test)) {
250+
return;
251+
}
252+
Test test = (Test) o;
253+
test(test);
254+
}
255+
}
256+
""")
257+
.expectUnchanged()
258+
.setArgs("-XepOpt:PatternMatchingInstanceof:EnableNegatedMatches=false")
259+
.doTest();
260+
}
261+
190262
@Test
191263
public void negatedIf_butNoDefiniteReturn_noFinding() {
192264
helper
@@ -618,6 +690,47 @@ public boolean equals(Object o) {
618690
.doTest();
619691
}
620692

693+
@Test
694+
public void withinIfCondition_andUsedAfter_disabledNegation() {
695+
helper
696+
.addInputLines(
697+
"Test.java",
698+
"""
699+
class Test {
700+
private final int x = 0;
701+
private final int y = 1;
702+
703+
@Override
704+
public boolean equals(Object o) {
705+
if (!(o instanceof Test) || ((Test) o).x != this.x) {
706+
return false;
707+
}
708+
Test other = (Test) o;
709+
return other.y == this.y;
710+
}
711+
}
712+
""")
713+
.addOutputLines(
714+
"Test.java",
715+
"""
716+
class Test {
717+
private final int x = 0;
718+
private final int y = 1;
719+
720+
@Override
721+
public boolean equals(Object o) {
722+
if (!(o instanceof Test test) || test.x != this.x) {
723+
return false;
724+
}
725+
Test other = (Test) o;
726+
return other.y == this.y;
727+
}
728+
}
729+
""")
730+
.setArgs("-XepOpt:PatternMatchingInstanceof:EnableNegatedMatches=false")
731+
.doTest();
732+
}
733+
621734
@Test
622735
public void conditionalExpression() {
623736
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)