diff --git a/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java b/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java index 1c196bb6048..7882a03e7ca 100644 --- a/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java +++ b/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java @@ -21,6 +21,7 @@ import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Streams.stream; import static com.google.errorprone.matchers.JUnitMatchers.JUNIT4_RUN_WITH_ANNOTATION; import static com.google.errorprone.matchers.Matchers.isSubtypeOf; @@ -2754,5 +2755,80 @@ private static boolean hasMatchingMethods( return false; } + private static final Method CASE_TREE_GET_LABELS = getCaseTreeGetLabelsMethod(); + + @Nullable + private static Method getCaseTreeGetLabelsMethod() { + try { + return CaseTree.class.getMethod("getLabels"); + } catch (NoSuchMethodException e) { + return null; + } + } + + @SuppressWarnings("unchecked") // reflection + private static List getCaseLabels(CaseTree caseTree) { + if (CASE_TREE_GET_LABELS == null) { + return ImmutableList.of(); + } + try { + return (List) CASE_TREE_GET_LABELS.invoke(caseTree); + } catch (ReflectiveOperationException e) { + throw new LinkageError(e.getMessage(), e); + } + } + + // getExpression() is being used for compatibility with earlier JDK versions + @SuppressWarnings("deprecation") + public static Optional getSwitchDefault(SwitchTree switchTree) { + return switchTree.getCases().stream() + .filter( + (CaseTree c) -> { + if (c.getExpression() != null) { + return false; + } + List labels = getCaseLabels(c); + return labels.isEmpty() + || (labels.size() == 1 + && getOnlyElement(labels).getKind().name().equals("DEFAULT_CASE_LABEL")); + }) + .findFirst(); + } + + private static final Method CASE_TREE_GET_EXPRESSIONS = getCaseTreeGetExpressionsMethod(); + + @Nullable + private static Method getCaseTreeGetExpressionsMethod() { + try { + return CaseTree.class.getMethod("getExpressions"); + } catch (NoSuchMethodException e) { + return null; + } + } + + /** + * Retrieves a stream containing all case expressions, in order, for a given {@code CaseTree}. + * This method acts as a facade to the {@code CaseTree.getExpressions()} API, falling back to + * legacy APIs when necessary. + */ + @SuppressWarnings({ + "deprecation", // getExpression() is being used for compatibility with earlier JDK versions + "unchecked", // reflection + }) + public static Stream getCaseExpressions(CaseTree caseTree) { + if (!RuntimeVersion.isAtLeast12()) { + // "default" case gives an empty stream + return Stream.ofNullable(caseTree.getExpression()); + } + if (CASE_TREE_GET_EXPRESSIONS == null) { + return Stream.empty(); + } + try { + return ((List) CASE_TREE_GET_EXPRESSIONS.invoke(caseTree)).stream(); + } catch (ReflectiveOperationException e) { + throw new LinkageError(e.getMessage(), e); + } + } + private ASTHelpers() {} } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/MissingCasesInEnumSwitch.java b/core/src/main/java/com/google/errorprone/bugpatterns/MissingCasesInEnumSwitch.java index c05bd7dd4c2..1d9ce7d08c2 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/MissingCasesInEnumSwitch.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/MissingCasesInEnumSwitch.java @@ -26,16 +26,11 @@ import com.google.errorprone.bugpatterns.BugChecker.SwitchTreeMatcher; import com.google.errorprone.matchers.Description; import com.google.errorprone.util.ASTHelpers; -import com.google.errorprone.util.RuntimeVersion; -import com.sun.source.tree.CaseTree; -import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.SwitchTree; import com.sun.tools.javac.code.Type; -import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.lang.model.element.ElementKind; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @@ -58,7 +53,7 @@ public Description matchSwitch(SwitchTree tree, VisitorState state) { } ImmutableSet handled = tree.getCases().stream() - .flatMap(MissingCasesInEnumSwitch::getExpressions) + .flatMap(ASTHelpers::getCaseExpressions) .filter(IdentifierTree.class::isInstance) .map(e -> ((IdentifierTree) e).getName().toString()) .collect(toImmutableSet()); @@ -93,19 +88,4 @@ private static String buildMessage(Set unhandled) { } return message.toString(); } - - @SuppressWarnings("unchecked") - private static Stream getExpressions(CaseTree caseTree) { - try { - if (RuntimeVersion.isAtLeast12()) { - return ((List) - CaseTree.class.getMethod("getExpressions").invoke(caseTree)) - .stream(); - } else { - return Stream.of(caseTree.getExpression()); - } - } catch (ReflectiveOperationException e) { - throw new LinkageError(e.getMessage(), e); - } - } } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/MissingDefault.java b/core/src/main/java/com/google/errorprone/bugpatterns/MissingDefault.java index d3129dd9f19..07fe35f2f59 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/MissingDefault.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/MissingDefault.java @@ -19,6 +19,7 @@ import static com.google.common.collect.Iterables.getLast; import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; import static com.google.errorprone.matchers.Description.NO_MATCH; +import static com.google.errorprone.util.ASTHelpers.getSwitchDefault; import com.google.common.collect.Iterables; import com.google.errorprone.BugPattern; @@ -52,8 +53,7 @@ public Description matchSwitch(SwitchTree tree, VisitorState state) { // by MissingCasesInEnumSwitch return NO_MATCH; } - Optional maybeDefault = - tree.getCases().stream().filter(c -> c.getExpression() == null).findFirst(); + Optional maybeDefault = getSwitchDefault(tree); if (!maybeDefault.isPresent()) { Description.Builder description = buildDescription(tree); if (!tree.getCases().isEmpty()) { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java b/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java index f6e527d5b9b..76919adee26 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java @@ -22,6 +22,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; import static com.google.errorprone.matchers.Description.NO_MATCH; +import static com.google.errorprone.util.ASTHelpers.getCaseExpressions; import static com.google.errorprone.util.ASTHelpers.getStartPosition; import static com.google.errorprone.util.ASTHelpers.getSymbol; import static com.sun.source.tree.Tree.Kind.BLOCK; @@ -45,7 +46,6 @@ import com.google.errorprone.matchers.Matchers; import com.google.errorprone.util.ASTHelpers; import com.google.errorprone.util.Reachability; -import com.google.errorprone.util.RuntimeVersion; import com.google.errorprone.util.SourceVersion; import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.BlockTree; @@ -77,7 +77,6 @@ import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; -import java.util.stream.Stream; import javax.inject.Inject; import javax.lang.model.element.ElementKind; @@ -206,11 +205,11 @@ private static AnalysisResult analyzeSwitchTree(SwitchTree switchTree, VisitorSt // One-pass scan through each case in switch for (int caseIndex = 0; caseIndex < cases.size(); caseIndex++) { CaseTree caseTree = cases.get(caseIndex); - boolean isDefaultCase = (getExpressions(caseTree).count() == 0); + boolean isDefaultCase = (getCaseExpressions(caseTree).count() == 0); hasDefaultCase |= isDefaultCase; // Accumulate enum values included in this case handledEnumValues.addAll( - getExpressions(caseTree) + getCaseExpressions(caseTree) .filter(IdentifierTree.class::isInstance) .map(expressionTree -> ((IdentifierTree) expressionTree).getName().toString()) .collect(toImmutableSet())); @@ -954,30 +953,7 @@ private static String removeFallThruLines(String comments) { /** Prints source for all expressions in a given {@code case}, separated by commas. */ private static String printCaseExpressions(CaseTree caseTree, VisitorState state) { - return getExpressions(caseTree).map(state::getSourceForNode).collect(joining(", ")); - } - - /** - * Retrieves a stream containing all case expressions, in order, for a given {@code CaseTree}. - * This method acts as a facade to the {@code CaseTree.getExpressions()} API, falling back to - * legacy APIs when necessary. - */ - @SuppressWarnings("unchecked") - private static Stream getExpressions(CaseTree caseTree) { - try { - if (RuntimeVersion.isAtLeast12()) { - return ((List) - CaseTree.class.getMethod("getExpressions").invoke(caseTree)) - .stream(); - } else { - // "default" case gives an empty stream - return caseTree.getExpression() == null - ? Stream.empty() - : Stream.of(caseTree.getExpression()); - } - } catch (ReflectiveOperationException e) { - throw new LinkageError(e.getMessage(), e); - } + return getCaseExpressions(caseTree).map(state::getSourceForNode).collect(joining(", ")); } /** diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/SwitchDefault.java b/core/src/main/java/com/google/errorprone/bugpatterns/SwitchDefault.java index ee6b05588d2..bf114cc9711 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/SwitchDefault.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/SwitchDefault.java @@ -20,6 +20,7 @@ import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION; import static com.google.errorprone.matchers.Description.NO_MATCH; import static com.google.errorprone.util.ASTHelpers.getStartPosition; +import static com.google.errorprone.util.ASTHelpers.getSwitchDefault; import com.google.errorprone.BugPattern; import com.google.errorprone.VisitorState; @@ -44,8 +45,7 @@ public class SwitchDefault extends BugChecker implements SwitchTreeMatcher { @Override public Description matchSwitch(SwitchTree tree, VisitorState state) { - Optional maybeDefault = - tree.getCases().stream().filter(c -> c.getExpression() == null).findAny(); + Optional maybeDefault = getSwitchDefault(tree); if (!maybeDefault.isPresent()) { return NO_MATCH; } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/SwitchDefaultTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/SwitchDefaultTest.java index 7c11cf4d03b..14215648b78 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/SwitchDefaultTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/SwitchDefaultTest.java @@ -291,4 +291,27 @@ public void newNotation_changeOrder() { "}") .doTest(); } + + @Test + public void arrowSwitch_noDefault() { + assumeTrue(RuntimeVersion.isAtLeast21()); + compilationHelper + .addSourceLines( + "Foo.java", // + "sealed interface Foo {", + " final class Bar implements Foo {}", + " final class Baz implements Foo {}", + "}") + .addSourceLines( + "Test.java", + "class Test {", + " void f(Foo i) {", + " switch (i) {", + " case Foo.Bar bar -> {}", + " case Foo.Baz baz -> {}", + " }", + " }", + "}") + .doTest(); + } }