Implementation of exhaustive whens in control flow analysis.

Now control flow analyzer knows when is exhaustive.
If despite of this all conditions are accidentally false, jump to error is generated.
A set of tests (diagnostic, control flow). #KT-5113 Fixed. #KT-6046 Fixed. #KT-1882 Fixed.
This commit is contained in:
Mikhail Glukhikh
2015-05-12 19:20:31 +03:00
parent c4aa6d01a9
commit 0d7f649cc2
16 changed files with 367 additions and 1 deletions
@@ -1354,6 +1354,11 @@ public class JetControlFlowProcessor {
if (!isElse) {
builder.bindLabel(nextLabel);
// For the last entry of exhaustive when,
// attempt to jump further should lead to error, not to "done"
if (!iterator.hasNext() && WhenChecker.isWhenExhaustive(expression, trace)) {
builder.jumpToError(expression);
}
}
}
builder.bindLabel(doneLabel);
@@ -98,7 +98,7 @@ public final class WhenChecker {
return notEmpty;
}
private static boolean isWhenExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
public static boolean isWhenExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
JetType type = whenSubjectType(expression, trace.getBindingContext());
if (type == null) return false;
ClassDescriptor classDescriptor = getClassDescriptorOfTypeIfEnum(type);
@@ -0,0 +1,46 @@
== foo ==
fun foo(flag: Boolean): Int {
when (flag) {
true -> return 1
false -> return 0
}
}
---------------------
L0:
1 <START>
v(flag: Boolean)
magic[FAKE_INITIALIZER](flag: Boolean) -> <v0>
w(flag|<v0>)
2 mark({ when (flag) { true -> return 1 false -> return 0 } })
mark(when (flag) { true -> return 1 false -> return 0 })
r(flag) -> <v1>
mark(true -> return 1)
mark(true)
r(true) -> <v2>
magic[EQUALS_IN_WHEN_CONDITION](true|<v1>, <v2>) -> <v3>
jmp?(L4|<v3>) NEXT:[mark(false -> return 0), r(1) -> <v4>]
L3 ['when' entry body]:
r(1) -> <v4>
ret(*|<v4>) L1 NEXT:[<END>]
- jmp(L2) NEXT:[merge(when (flag) { true -> return 1 false -> return 0 }|!<v8>, !<v9>) -> <v10>] PREV:[]
L4 [next 'when' entry]:
mark(false -> return 0) PREV:[jmp?(L4|<v3>)]
mark(false)
r(false) -> <v5>
magic[EQUALS_IN_WHEN_CONDITION](false|<v1>, <v5>) -> <v6>
jmp?(L6|<v6>) NEXT:[jmp(error), r(0) -> <v7>]
L5 ['when' entry body]:
r(0) -> <v7>
ret(*|<v7>) L1 NEXT:[<END>]
- jmp(L2) NEXT:[merge(when (flag) { true -> return 1 false -> return 0 }|!<v8>, !<v9>) -> <v10>] PREV:[]
L6 [next 'when' entry]:
jmp(error) NEXT:[<ERROR>] PREV:[jmp?(L6|<v6>)]
L2 [after 'when' expression]:
- merge(when (flag) { true -> return 1 false -> return 0 }|!<v8>, !<v9>) -> <v10> PREV:[]
L1:
1 <END> NEXT:[<SINK>] PREV:[ret(*|<v4>) L1, ret(*|<v7>) L1]
error:
<ERROR> PREV:[jmp(error)]
sink:
<SINK> PREV:[<ERROR>, <END>]
=====================
@@ -0,0 +1,6 @@
fun foo(flag: Boolean): Int {
when (flag) {
true -> return 1
false -> return 0
}
}
@@ -0,0 +1,21 @@
== foo ==
fun foo(flag: Boolean): Int {
when (flag) {
true -> return 1
false -> return 0
}
}
---------------------
<v0>: Boolean NEW: magic[FAKE_INITIALIZER](flag: Boolean) -> <v0>
flag <v1>: * NEW: r(flag) -> <v1>
true <v2>: * NEW: r(true) -> <v2>
true <v3>: * NEW: magic[EQUALS_IN_WHEN_CONDITION](true|<v1>, <v2>) -> <v3>
1 <v4>: Int NEW: r(1) -> <v4>
return 1 !<v8>: *
false <v5>: * NEW: r(false) -> <v5>
false <v6>: * NEW: magic[EQUALS_IN_WHEN_CONDITION](false|<v1>, <v5>) -> <v6>
0 <v7>: Int NEW: r(0) -> <v7>
return 0 !<v9>: *
when (flag) { true -> return 1 false -> return 0 } <v10>: * NEW: merge(when (flag) { true -> return 1 false -> return 0 }|!<v8>, !<v9>) -> <v10>
{ when (flag) { true -> return 1 false -> return 0 } } <v10>: * COPY
=====================
@@ -0,0 +1,15 @@
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
fun foo(dir: Direction): Int {
val res: Int
// See KT-6046: res is always initialized
when (dir) {
Direction.NORTH -> res = 1
Direction.SOUTH -> res = 2
Direction.WEST -> res = 3
Direction.EAST -> res = 4
}
return res
}
@@ -0,0 +1,57 @@
package
internal fun foo(/*0*/ dir: Direction): kotlin.Int
internal final enum class Direction : kotlin.Enum<Direction> {
public enum entry NORTH : Direction {
private constructor NORTH()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Direction): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry SOUTH : Direction {
private constructor SOUTH()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Direction): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry WEST : Direction {
private constructor WEST()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Direction): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry EAST : Direction {
private constructor EAST()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Direction): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
private constructor Direction()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Direction): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
// Static members
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): Direction
public final /*synthesized*/ fun values(): kotlin.Array<Direction>
}
@@ -0,0 +1,14 @@
enum class MyEnum {
A, B
}
fun foo(x: MyEnum?): Int {
val y: Int
// See KT-6046: y is always initialized
when (x) {
MyEnum.A -> y = 1
MyEnum.B -> y = 2
null -> y = 0
}
return y
}
@@ -0,0 +1,37 @@
package
internal fun foo(/*0*/ x: MyEnum?): kotlin.Int
internal final enum class MyEnum : kotlin.Enum<MyEnum> {
public enum entry A : MyEnum {
private constructor A()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry B : MyEnum {
private constructor B()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
private constructor MyEnum()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
// Static members
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): MyEnum
public final /*synthesized*/ fun values(): kotlin.Array<MyEnum>
}
@@ -0,0 +1,13 @@
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
fun foo(dir: Direction): Int {
when (dir) {
Direction.NORTH -> return 1
Direction.SOUTH -> return 2
Direction.WEST -> return 3
Direction.EAST -> return 4
}
// See KT-1882: no return is needed at the end
}
@@ -0,0 +1,57 @@
package
internal fun foo(/*0*/ dir: Direction): kotlin.Int
internal final enum class Direction : kotlin.Enum<Direction> {
public enum entry NORTH : Direction {
private constructor NORTH()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Direction): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry SOUTH : Direction {
private constructor SOUTH()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Direction): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry WEST : Direction {
private constructor WEST()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Direction): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry EAST : Direction {
private constructor EAST()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Direction): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
private constructor Direction()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Direction): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
// Static members
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): Direction
public final /*synthesized*/ fun values(): kotlin.Array<Direction>
}
@@ -0,0 +1,16 @@
// See KT-5113
enum class E {
A,
B
}
class Outer(e: E) {
private val prop: Int
init {
when(e ) {
// When is exhaustive, property is always initialized
E.A -> prop = 1
E.B -> prop = 2
}
}
}
@@ -0,0 +1,43 @@
package
internal final enum class E : kotlin.Enum<E> {
public enum entry A : E {
private constructor A()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: E): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry B : E {
private constructor B()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: E): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
private constructor E()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: E): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
// Static members
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): E
public final /*synthesized*/ fun values(): kotlin.Array<E>
}
internal final class Outer {
public constructor Outer(/*0*/ e: E)
private final val prop: kotlin.Int
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -219,6 +219,12 @@ public class ControlFlowTestGenerated extends AbstractControlFlowTest {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/cfg/controlStructures/whenConditions.kt");
doTest(fileName);
}
@TestMetadata("whenExhaustive.kt")
public void testWhenExhaustive() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/cfg/controlStructures/whenExhaustive.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/cfg/conventions")
@@ -221,6 +221,12 @@ public class PseudoValueTestGenerated extends AbstractPseudoValueTest {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/cfg/controlStructures/whenConditions.kt");
doTest(fileName);
}
@TestMetadata("whenExhaustive.kt")
public void testWhenExhaustive() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/cfg/controlStructures/whenExhaustive.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/cfg/conventions")
@@ -12921,6 +12921,24 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("ExhaustiveInitialization.kt")
public void testExhaustiveInitialization() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveInitialization.kt");
doTest(fileName);
}
@TestMetadata("ExhaustiveNullable.kt")
public void testExhaustiveNullable() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveNullable.kt");
doTest(fileName);
}
@TestMetadata("ExhaustiveReturn.kt")
public void testExhaustiveReturn() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveReturn.kt");
doTest(fileName);
}
@TestMetadata("kt4434.kt")
public void testKt4434() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/kt4434.kt");
@@ -12975,6 +12993,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("PropertyNotInitialized.kt")
public void testPropertyNotInitialized() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/PropertyNotInitialized.kt");
doTest(fileName);
}
@TestMetadata("When.kt")
public void testWhen() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/When.kt");