9090import java .util .stream .Stream ;
9191import javax .inject .Inject ;
9292import javax .lang .model .element .ElementKind ;
93+ import javax .lang .model .type .TypeKind ;
9394import org .jspecify .annotations .Nullable ;
9495
9596/** Checks for chains of if statements that may be converted to a switch. */
@@ -113,6 +114,9 @@ public final class IfChainToSwitch extends BugChecker implements IfTreeMatcher {
113114 "java.lang.Integer" ,
114115 "java.lang.String" );
115116
117+ // Non-reference types that are allowed for switch subjects, as specified in JLS 21 §14.11.
118+ private static final ImmutableSet <TypeKind > ALLOWED_SWITCH_SUBJECT_NON_REFERENCE_TYPES =
119+ ImmutableSet .of (TypeKind .CHAR , TypeKind .BYTE , TypeKind .SHORT , TypeKind .INT );
116120 private final boolean enableMain ;
117121 private final boolean enableSafe ;
118122 private final int maxChainLength ;
@@ -729,6 +733,18 @@ private static Optional<List<CaseIr>> maybeDetectDuplicateConstants(List<CaseIr>
729733 // Check for compile-time constants
730734 Object constant = constValue (expression );
731735 if (constant != null ) {
736+ // Canonicialize constants (by widening) to detect identical values across various
737+ // (boxed) primitive types
738+ constant =
739+ switch (constant ) {
740+ case Float f -> (double ) f ;
741+ case Double d -> d ;
742+ case Number n -> n .longValue (); // primitive integer type
743+ case Boolean bool -> bool ;
744+ case Character c -> (long ) c ;
745+ default -> constant ;
746+ };
747+
732748 if (seenConstants .contains (constant )) {
733749 return Optional .empty ();
734750 }
@@ -1412,6 +1428,20 @@ private Optional<ExpressionTree> validateCompileTimeConstantForSubject(
14121428 return Optional .empty ();
14131429 }
14141430
1431+ // The variable being switched on must also be one of the following types; this is
1432+ // an outer bound (relaxed in preview features, which this checker does not yet support)
1433+ if (subject .isPresent ()) {
1434+ var switchExpressionType = getType (subject .get ());
1435+ boolean validSwitchExpressionType =
1436+ switchExpressionType != null // Should never be null
1437+ && (switchExpressionType .isReference ()
1438+ || ALLOWED_SWITCH_SUBJECT_NON_REFERENCE_TYPES .contains (
1439+ switchExpressionType .getKind ()));
1440+ if (!validSwitchExpressionType ) {
1441+ return Optional .empty ();
1442+ }
1443+ }
1444+
14151445 boolean addDefault = hasElse && !hasElseIf ;
14161446 int previousCaseEndPosition =
14171447 cases .isEmpty ()
@@ -1425,7 +1455,7 @@ private Optional<ExpressionTree> validateCompileTimeConstantForSubject(
14251455 /* guardOptional= */ Optional .empty (),
14261456 /* expressionsOptional= */ Optional .of (ImmutableList .of (compileTimeConstant )),
14271457 /* arrowRhsOptional= */ arrowRhsOptional ,
1428- /* caseSourceCodeRange= */ Range .openClosed (previousCaseEndPosition , caseEndPosition )));
1458+ /* caseSourceCodeRange= */ Range .closedOpen (previousCaseEndPosition , caseEndPosition )));
14291459 if (addDefault ) {
14301460 previousCaseEndPosition =
14311461 cases .isEmpty ()
@@ -1439,7 +1469,7 @@ private Optional<ExpressionTree> validateCompileTimeConstantForSubject(
14391469 /* guardOptional= */ Optional .empty (),
14401470 /* expressionsOptional= */ Optional .empty (),
14411471 /* arrowRhsOptional= */ elseOptional ,
1442- /* caseSourceCodeRange= */ Range .openClosed (
1472+ /* caseSourceCodeRange= */ Range .closedOpen (
14431473 previousCaseEndPosition ,
14441474 elseOptional .isPresent ()
14451475 ? getStartPosition (elseOptional .get ())
0 commit comments