2121import static com .google .common .collect .ImmutableSet .toImmutableSet ;
2222import static com .google .errorprone .BugPattern .SeverityLevel .WARNING ;
2323import static com .google .errorprone .bugpatterns .SwitchUtils .COMPILE_TIME_CONSTANT_MATCHER ;
24+ import static com .google .errorprone .bugpatterns .SwitchUtils .getReferencedLocalVariablesInTree ;
2425import static com .google .errorprone .bugpatterns .SwitchUtils .isEnumValue ;
2526import static com .google .errorprone .bugpatterns .SwitchUtils .renderComments ;
2627import static com .google .errorprone .matchers .Description .NO_MATCH ;
2728import static com .google .errorprone .util .ASTHelpers .constValue ;
2829import static com .google .errorprone .util .ASTHelpers .getStartPosition ;
2930import static com .google .errorprone .util .ASTHelpers .getType ;
31+ import static com .google .errorprone .util .ASTHelpers .isConsideredFinal ;
3032import static com .google .errorprone .util .ASTHelpers .isSubtype ;
3133import static com .google .errorprone .util .ASTHelpers .sameVariable ;
34+ import static com .google .errorprone .util .ASTHelpers .stripParentheses ;
3235import static com .sun .source .tree .Tree .Kind .EXPRESSION_STATEMENT ;
3336import static com .sun .source .tree .Tree .Kind .THROW ;
3437import static java .lang .Math .max ;
@@ -416,6 +419,28 @@ private static boolean switchOnNullWouldImplicitlyThrow(
416419 && cases .stream ().noneMatch (CaseIr ::hasCaseNull );
417420 }
418421
422+ /**
423+ * Determines whether the given case IR has an "unguarded" pattern. As defined in the JLS,
424+ * "unguarded" means that either there is no guard or the guard is the boolean literal `true`.
425+ */
426+ private static boolean isUnguarded (CaseIr caseIr ) {
427+ // Not a pattern
428+ if (caseIr .instanceOfOptional ().isEmpty ()) {
429+ return false ;
430+ }
431+
432+ if (caseIr .guardOptional ().isEmpty ()) {
433+ return true ;
434+ }
435+
436+ // Guard is present and is `true`
437+ ExpressionTree guard = stripParentheses (caseIr .guardOptional ().get ());
438+ return isBooleanLiteral (guard )
439+ && guard instanceof LiteralTree literalTree
440+ && literalTree .getValue () instanceof Boolean b
441+ && b ;
442+ }
443+
419444 /**
420445 * Analyzes the supplied case IRs for a switch statement for issues related default/unconditional
421446 * cases. If deemed necessary, this method injects a `default` and/or `case null` into the
@@ -450,7 +475,7 @@ private Optional<List<CaseIr>> maybeFixDefaultNullAndUnconditional(
450475 .filter (
451476 caseIr ->
452477 caseIr .instanceOfOptional ().isPresent ()
453- && caseIr . guardOptional (). isEmpty ( )
478+ && isUnguarded ( caseIr )
454479 && isSubtype (
455480 getType (subject ),
456481 getType (caseIr .instanceOfOptional ().get ().type ()),
@@ -674,12 +699,13 @@ && switchOnNullWouldImplicitlyThrow(
674699 }
675700
676701 /**
677- * Analyzes the supplied case IRs for duplicate constants (either primitives or enum values). If
702+ * Analyzes the supplied case IRs for duplicate constants (primitives, enum values, or `null` ). If
678703 * any duplicates are found, returns {@code Optional.empty()}.
679704 */
680705 private static Optional <List <CaseIr >> maybeDetectDuplicateConstants (List <CaseIr > cases ) {
681706
682707 Set <Object > seenConstants = new HashSet <>();
708+ boolean seenNull = false ;
683709
684710 for (CaseIr caseIr : cases ) {
685711 if (caseIr .expressionsOptional ().isPresent ()) {
@@ -701,6 +727,13 @@ private static Optional<List<CaseIr>> maybeDetectDuplicateConstants(List<CaseIr>
701727 }
702728 seenConstants .add (sym );
703729 }
730+
731+ if (isNull (expression )) {
732+ if (seenNull ) {
733+ return Optional .empty ();
734+ }
735+ seenNull = true ;
736+ }
704737 }
705738 }
706739 }
@@ -987,6 +1020,12 @@ private Optional<ExpressionTree> validatePredicateForSubject(
9871020 && !b ) {
9881021 return Optional .empty ();
9891022 }
1023+ // A guard cannot reference a local variable that is not final nor effectively final;
1024+ // see JLS 21 §14.11.1.
1025+ if (getReferencedLocalVariablesInTree (rightOperandNoParentheses ).stream ()
1026+ .anyMatch (varSymbol -> !isConsideredFinal (varSymbol ))) {
1027+ return Optional .empty ();
1028+ }
9901029 // Update last case to attach the guard
9911030 cases .set (
9921031 currentCasesSize ,
@@ -1657,7 +1696,7 @@ public static boolean isDominatedBy(
16571696 boolean isPrimitive = getType (constantExpression ).isPrimitive ();
16581697 if (isPrimitive ) {
16591698 // Guarded patterns cannot dominate primitives
1660- if (lhs . guardOptional (). isPresent ( )) {
1699+ if (! isUnguarded ( lhs )) {
16611700 continue ;
16621701 }
16631702 if (lhs .instanceOfOptional ().isPresent ()) {
@@ -1689,7 +1728,7 @@ public static boolean isDominatedBy(
16891728 }
16901729 boolean isEnum = isEnumValue (constantExpression , state );
16911730 if (isEnum ) {
1692- if (lhs . guardOptional (). isPresent ( )) {
1731+ if (! isUnguarded ( lhs )) {
16931732 // Guarded patterns cannot dominate enum values
16941733 continue ;
16951734 }
@@ -1710,7 +1749,7 @@ public static boolean isDominatedBy(
17101749 // RHS must be a reference
17111750 // The rhs-reference code would be needed to support e.g. String literals. It is included
17121751 // for completeness.
1713- if (lhs . guardOptional (). isPresent ( )) {
1752+ if (! isUnguarded ( lhs )) {
17141753 // Guarded patterns cannot dominate references
17151754 continue ;
17161755 }
@@ -1741,7 +1780,7 @@ public static boolean isDominatedBy(
17411780 }
17421781
17431782 // RHS must be a pattern
1744- if (lhs . guardOptional (). isPresent ( )) {
1783+ if (! isUnguarded ( lhs )) {
17451784 // LHS has a guard, so cannot dominate RHS
17461785 return false ;
17471786 }
0 commit comments