Skip to content

Commit f768b0b

Browse files
cushonError Prone Team
authored andcommitted
Fix handling of default cases in arrow switches
And consolidate some reflective workarounds for switch API changes. Fixes #4266 PiperOrigin-RevId: 609484284
1 parent e3725d2 commit f768b0b

File tree

6 files changed

+108
-53
lines changed

6 files changed

+108
-53
lines changed

check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static com.google.common.base.Verify.verify;
2222
import static com.google.common.collect.ImmutableList.toImmutableList;
2323
import static com.google.common.collect.ImmutableSet.toImmutableSet;
24+
import static com.google.common.collect.Iterables.getOnlyElement;
2425
import static com.google.common.collect.Streams.stream;
2526
import static com.google.errorprone.matchers.JUnitMatchers.JUNIT4_RUN_WITH_ANNOTATION;
2627
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
@@ -2754,5 +2755,80 @@ private static boolean hasMatchingMethods(
27542755
return false;
27552756
}
27562757

2758+
private static final Method CASE_TREE_GET_LABELS = getCaseTreeGetLabelsMethod();
2759+
2760+
@Nullable
2761+
private static Method getCaseTreeGetLabelsMethod() {
2762+
try {
2763+
return CaseTree.class.getMethod("getLabels");
2764+
} catch (NoSuchMethodException e) {
2765+
return null;
2766+
}
2767+
}
2768+
2769+
@SuppressWarnings("unchecked") // reflection
2770+
private static List<? extends Tree> getCaseLabels(CaseTree caseTree) {
2771+
if (CASE_TREE_GET_LABELS == null) {
2772+
return ImmutableList.of();
2773+
}
2774+
try {
2775+
return (List<? extends Tree>) CASE_TREE_GET_LABELS.invoke(caseTree);
2776+
} catch (ReflectiveOperationException e) {
2777+
throw new LinkageError(e.getMessage(), e);
2778+
}
2779+
}
2780+
2781+
// getExpression() is being used for compatibility with earlier JDK versions
2782+
@SuppressWarnings("deprecation")
2783+
public static Optional<? extends CaseTree> getSwitchDefault(SwitchTree switchTree) {
2784+
return switchTree.getCases().stream()
2785+
.filter(
2786+
(CaseTree c) -> {
2787+
if (c.getExpression() != null) {
2788+
return false;
2789+
}
2790+
List<? extends Tree> labels = getCaseLabels(c);
2791+
return labels.isEmpty()
2792+
|| (labels.size() == 1
2793+
&& getOnlyElement(labels).getKind().name().equals("DEFAULT_CASE_LABEL"));
2794+
})
2795+
.findFirst();
2796+
}
2797+
2798+
private static final Method CASE_TREE_GET_EXPRESSIONS = getCaseTreeGetExpressionsMethod();
2799+
2800+
@Nullable
2801+
private static Method getCaseTreeGetExpressionsMethod() {
2802+
try {
2803+
return CaseTree.class.getMethod("getExpressions");
2804+
} catch (NoSuchMethodException e) {
2805+
return null;
2806+
}
2807+
}
2808+
2809+
/**
2810+
* Retrieves a stream containing all case expressions, in order, for a given {@code CaseTree}.
2811+
* This method acts as a facade to the {@code CaseTree.getExpressions()} API, falling back to
2812+
* legacy APIs when necessary.
2813+
*/
2814+
@SuppressWarnings({
2815+
"deprecation", // getExpression() is being used for compatibility with earlier JDK versions
2816+
"unchecked", // reflection
2817+
})
2818+
public static Stream<? extends ExpressionTree> getCaseExpressions(CaseTree caseTree) {
2819+
if (!RuntimeVersion.isAtLeast12()) {
2820+
// "default" case gives an empty stream
2821+
return Stream.ofNullable(caseTree.getExpression());
2822+
}
2823+
if (CASE_TREE_GET_EXPRESSIONS == null) {
2824+
return Stream.empty();
2825+
}
2826+
try {
2827+
return ((List<? extends ExpressionTree>) CASE_TREE_GET_EXPRESSIONS.invoke(caseTree)).stream();
2828+
} catch (ReflectiveOperationException e) {
2829+
throw new LinkageError(e.getMessage(), e);
2830+
}
2831+
}
2832+
27572833
private ASTHelpers() {}
27582834
}

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

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,11 @@
2626
import com.google.errorprone.bugpatterns.BugChecker.SwitchTreeMatcher;
2727
import com.google.errorprone.matchers.Description;
2828
import com.google.errorprone.util.ASTHelpers;
29-
import com.google.errorprone.util.RuntimeVersion;
30-
import com.sun.source.tree.CaseTree;
31-
import com.sun.source.tree.ExpressionTree;
3229
import com.sun.source.tree.IdentifierTree;
3330
import com.sun.source.tree.SwitchTree;
3431
import com.sun.tools.javac.code.Type;
35-
import java.util.List;
3632
import java.util.Set;
3733
import java.util.stream.Collectors;
38-
import java.util.stream.Stream;
3934
import javax.lang.model.element.ElementKind;
4035

4136
/** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */
@@ -58,7 +53,7 @@ public Description matchSwitch(SwitchTree tree, VisitorState state) {
5853
}
5954
ImmutableSet<String> handled =
6055
tree.getCases().stream()
61-
.flatMap(MissingCasesInEnumSwitch::getExpressions)
56+
.flatMap(ASTHelpers::getCaseExpressions)
6257
.filter(IdentifierTree.class::isInstance)
6358
.map(e -> ((IdentifierTree) e).getName().toString())
6459
.collect(toImmutableSet());
@@ -93,19 +88,4 @@ private static String buildMessage(Set<String> unhandled) {
9388
}
9489
return message.toString();
9590
}
96-
97-
@SuppressWarnings("unchecked")
98-
private static Stream<? extends ExpressionTree> getExpressions(CaseTree caseTree) {
99-
try {
100-
if (RuntimeVersion.isAtLeast12()) {
101-
return ((List<? extends ExpressionTree>)
102-
CaseTree.class.getMethod("getExpressions").invoke(caseTree))
103-
.stream();
104-
} else {
105-
return Stream.of(caseTree.getExpression());
106-
}
107-
} catch (ReflectiveOperationException e) {
108-
throw new LinkageError(e.getMessage(), e);
109-
}
110-
}
11191
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static com.google.common.collect.Iterables.getLast;
2020
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
2121
import static com.google.errorprone.matchers.Description.NO_MATCH;
22+
import static com.google.errorprone.util.ASTHelpers.getSwitchDefault;
2223

2324
import com.google.common.collect.Iterables;
2425
import com.google.errorprone.BugPattern;
@@ -52,8 +53,7 @@ public Description matchSwitch(SwitchTree tree, VisitorState state) {
5253
// by MissingCasesInEnumSwitch
5354
return NO_MATCH;
5455
}
55-
Optional<? extends CaseTree> maybeDefault =
56-
tree.getCases().stream().filter(c -> c.getExpression() == null).findFirst();
56+
Optional<? extends CaseTree> maybeDefault = getSwitchDefault(tree);
5757
if (!maybeDefault.isPresent()) {
5858
Description.Builder description = buildDescription(tree);
5959
if (!tree.getCases().isEmpty()) {

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

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static com.google.common.collect.Iterables.getOnlyElement;
2323
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
2424
import static com.google.errorprone.matchers.Description.NO_MATCH;
25+
import static com.google.errorprone.util.ASTHelpers.getCaseExpressions;
2526
import static com.google.errorprone.util.ASTHelpers.getStartPosition;
2627
import static com.google.errorprone.util.ASTHelpers.getSymbol;
2728
import static com.sun.source.tree.Tree.Kind.BLOCK;
@@ -45,7 +46,6 @@
4546
import com.google.errorprone.matchers.Matchers;
4647
import com.google.errorprone.util.ASTHelpers;
4748
import com.google.errorprone.util.Reachability;
48-
import com.google.errorprone.util.RuntimeVersion;
4949
import com.google.errorprone.util.SourceVersion;
5050
import com.sun.source.tree.AssignmentTree;
5151
import com.sun.source.tree.BlockTree;
@@ -77,7 +77,6 @@
7777
import java.util.Optional;
7878
import java.util.Set;
7979
import java.util.regex.Pattern;
80-
import java.util.stream.Stream;
8180
import javax.inject.Inject;
8281
import javax.lang.model.element.ElementKind;
8382

@@ -206,11 +205,11 @@ private static AnalysisResult analyzeSwitchTree(SwitchTree switchTree, VisitorSt
206205
// One-pass scan through each case in switch
207206
for (int caseIndex = 0; caseIndex < cases.size(); caseIndex++) {
208207
CaseTree caseTree = cases.get(caseIndex);
209-
boolean isDefaultCase = (getExpressions(caseTree).count() == 0);
208+
boolean isDefaultCase = (getCaseExpressions(caseTree).count() == 0);
210209
hasDefaultCase |= isDefaultCase;
211210
// Accumulate enum values included in this case
212211
handledEnumValues.addAll(
213-
getExpressions(caseTree)
212+
getCaseExpressions(caseTree)
214213
.filter(IdentifierTree.class::isInstance)
215214
.map(expressionTree -> ((IdentifierTree) expressionTree).getName().toString())
216215
.collect(toImmutableSet()));
@@ -954,30 +953,7 @@ private static String removeFallThruLines(String comments) {
954953

955954
/** Prints source for all expressions in a given {@code case}, separated by commas. */
956955
private static String printCaseExpressions(CaseTree caseTree, VisitorState state) {
957-
return getExpressions(caseTree).map(state::getSourceForNode).collect(joining(", "));
958-
}
959-
960-
/**
961-
* Retrieves a stream containing all case expressions, in order, for a given {@code CaseTree}.
962-
* This method acts as a facade to the {@code CaseTree.getExpressions()} API, falling back to
963-
* legacy APIs when necessary.
964-
*/
965-
@SuppressWarnings("unchecked")
966-
private static Stream<? extends ExpressionTree> getExpressions(CaseTree caseTree) {
967-
try {
968-
if (RuntimeVersion.isAtLeast12()) {
969-
return ((List<? extends ExpressionTree>)
970-
CaseTree.class.getMethod("getExpressions").invoke(caseTree))
971-
.stream();
972-
} else {
973-
// "default" case gives an empty stream
974-
return caseTree.getExpression() == null
975-
? Stream.empty()
976-
: Stream.of(caseTree.getExpression());
977-
}
978-
} catch (ReflectiveOperationException e) {
979-
throw new LinkageError(e.getMessage(), e);
980-
}
956+
return getCaseExpressions(caseTree).map(state::getSourceForNode).collect(joining(", "));
981957
}
982958

983959
/**

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
2121
import static com.google.errorprone.matchers.Description.NO_MATCH;
2222
import static com.google.errorprone.util.ASTHelpers.getStartPosition;
23+
import static com.google.errorprone.util.ASTHelpers.getSwitchDefault;
2324

2425
import com.google.errorprone.BugPattern;
2526
import com.google.errorprone.VisitorState;
@@ -44,8 +45,7 @@ public class SwitchDefault extends BugChecker implements SwitchTreeMatcher {
4445

4546
@Override
4647
public Description matchSwitch(SwitchTree tree, VisitorState state) {
47-
Optional<? extends CaseTree> maybeDefault =
48-
tree.getCases().stream().filter(c -> c.getExpression() == null).findAny();
48+
Optional<? extends CaseTree> maybeDefault = getSwitchDefault(tree);
4949
if (!maybeDefault.isPresent()) {
5050
return NO_MATCH;
5151
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,4 +291,27 @@ public void newNotation_changeOrder() {
291291
"}")
292292
.doTest();
293293
}
294+
295+
@Test
296+
public void arrowSwitch_noDefault() {
297+
assumeTrue(RuntimeVersion.isAtLeast21());
298+
compilationHelper
299+
.addSourceLines(
300+
"Foo.java", //
301+
"sealed interface Foo {",
302+
" final class Bar implements Foo {}",
303+
" final class Baz implements Foo {}",
304+
"}")
305+
.addSourceLines(
306+
"Test.java",
307+
"class Test {",
308+
" void f(Foo i) {",
309+
" switch (i) {",
310+
" case Foo.Bar bar -> {}",
311+
" case Foo.Baz baz -> {}",
312+
" }",
313+
" }",
314+
"}")
315+
.doTest();
316+
}
294317
}

0 commit comments

Comments
 (0)