Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4336,6 +4336,182 @@ public void foo(Object o) {
.doTest();
}

// ---------------------------------------------------------------------------------------------
// Reproducers for suspected bugs in IfChainToSwitch. All of the tests in this section fail as
// of the time of writing; each one documents the behavior that the checker *should* have.
// ---------------------------------------------------------------------------------------------

@Test
public void ifChain_instanceOfOrIntConstant_noCrash() {
// BUG (crash): `validateInstanceOfForSubject` records the source range of the synthesized
// `default` case as [start(else), *end*(else)), whereas every other case uses
// [.., start(else)). When a subsequent disjunct is a compile-time constant, the next case's
// range is computed as [end(else), start(else)), and `Range.closedOpen` throws
// `IllegalArgumentException: Invalid range`.

helper
.addSourceLines(
"Test.java",
"""
class Test {
public void foo(Integer x) {
if (x instanceof Number || x == 5) {
System.out.println("a");
} else {
System.out.println("b");
}
}
}
""")
.setArgs(ENABLE_MAIN, DISABLE_SAFE, MIN_CHAIN_LENGTH_3);
// TODO: add doTest()
}

@Test
public void ifChain_instanceOfOrEnumConstant_noCrash() {
// BUG (crash): same root cause as above, but the invalid range is constructed in
// `validateEnumPredicateForSubject` instead.
helper
.addSourceLines(
"Test.java",
"""
class Test {
public void foo(Object o) {
if (o instanceof String || o == Suit.HEART) {
System.out.println("a");
} else {
System.out.println("b");
}
}
}
""")
.setArgs(ENABLE_MAIN, DISABLE_SAFE, MIN_CHAIN_LENGTH_3);
// TODO: add doTest()
}

@Test
public void ifChain_conditionalExpressionConstant_noError() {
// BUG (does not compile): `COMPILE_TIME_CONSTANT_MATCHER` accepts `flag ? 1 : 2` (it only
// requires the two branches to be constant, not the condition), but a `case` label requires a
// constant expression as defined by JLS 21 15.29. The suggested fix produces
// `case (flag ? 1 : 2) ->`, which javac rejects with "constant expression required".
helper
.addSourceLines(
"Test.java",
"""
class Test {
public void foo(int x, boolean flag) {
// BUG: Diagnostic contains: This if-chain may be converted into a switch
if (x == (flag ? 1 : 2)) {
System.out.println("a");
} else if (x == 3) {
System.out.println("b");
} else if (x == 4) {
System.out.println("c");
}
}
}
""")
.setArgs(ENABLE_MAIN, DISABLE_SAFE, MIN_CHAIN_LENGTH_3)
.doTest();
}

@Test
public void ifChain_compileTimeConstantParameter_noError() {
// BUG (does not compile): `COMPILE_TIME_CONSTANT_MATCHER` also accepts a final parameter
// annotated with `@CompileTimeConstant`, which is not a constant variable, so the suggested
// fix produces `case c ->` and javac reports "constant expression required".
helper
.addSourceLines(
"Test.java",
"""
import com.google.errorprone.annotations.CompileTimeConstant;

class Test {
public void foo(int x, @CompileTimeConstant final int c) {
// BUG: Diagnostic contains: This if-chain may be converted into a switch
if (x == c) {
System.out.println("a");
} else if (x == 3) {
System.out.println("b");
} else if (x == 4) {
System.out.println("c");
}
}
}
""")
.setArgs(ENABLE_MAIN, DISABLE_SAFE, MIN_CHAIN_LENGTH_3)
.doTest();
}

@Test
public void ifChain_exhaustiveEnumWithTrailingStatements_keepsTrailingStatements() {
// BUG (does not compile): when every enum constant is handled, no `default` is emitted. A
// switch *statement* over an enum is not "enhanced" (JLS 21 14.11.2), so javac considers it
// able to complete normally even when it is exhaustive. The checker's own reachability
// analysis disagrees and deletes the statements following the switch, so the generated method
// fails to compile with "missing return statement".
helper
.addSourceLines(
"Test.java",
"""
class Test {
public int foo(Suit s) {
// BUG: Diagnostic contains: This if-chain may be converted into a switch
if (s == Suit.HEART) {
return 1;
} else if (s == Suit.SPADE) {
return 2;
} else if (s == Suit.DIAMOND) {
return 3;
} else if (s == Suit.CLUB) {
return 4;
}
System.out.println("not reached today, but reachable as far as javac knows");
throw new AssertionError();
}
}
""")
.setArgs(ENABLE_MAIN, DISABLE_SAFE, MIN_CHAIN_LENGTH_3)
.doTest();
}

@Test
public void ifChain_exhaustiveEnumInConstructor_keepsBlankFinalAssignment() {
// BUG (invalid code generated): the checker will delete the last two statements, the second of
// which is needed to initialize the blank final field `x`. (Failing to initialize a blank
// final field results in a compile-time error.)
helper
.addSourceLines(
"Test.java",
"""
class Test {
final int x;

Test(Suit s) {
// BUG: Diagnostic contains: This if-chain may be converted into a switch
if (s == Suit.HEART) {
x = 1;
return;
} else if (s == Suit.SPADE) {
x = 2;
return;
} else if (s == Suit.DIAMOND) {
x = 3;
return;
} else if (s == Suit.CLUB) {
x = 4;
return;
}
System.out.println("not reached");
x = 0;
}
}
""")
.setArgs(ENABLE_MAIN, DISABLE_SAFE, MIN_CHAIN_LENGTH_3)
.doTest();
}

/** Substitute underscore for {@code unused} variables, if supported. */
private static String maybeChangeToUnnamedVariable(String s) {
if (Runtime.version().feature() >= 22) {
Expand Down
Loading