Implicit exhaustive whens now have exception in else branch #KT-8700 Fixed

This commit is contained in:
Mikhail Glukhikh
2015-12-04 18:43:44 +03:00
committed by Mikhail Glukhikh
parent 011a9f23b9
commit 7d6ccc40c2
16 changed files with 167 additions and 18 deletions
@@ -3801,8 +3801,9 @@ The "returned" value of try expression with no finally is either the last expres
return StackValue.operation(resultType, new Function1<InstructionAdapter, Unit>() { return StackValue.operation(resultType, new Function1<InstructionAdapter, Unit>() {
@Override @Override
public Unit invoke(InstructionAdapter v) { public Unit invoke(InstructionAdapter v) {
SwitchCodegen switchCodegen = SwitchCodegen switchCodegen = SwitchCodegenUtil.buildAppropriateSwitchCodegenIfPossible(
SwitchCodegenUtil.buildAppropriateSwitchCodegenIfPossible(expression, isStatement, ExpressionCodegen.this); expression, isStatement, isExhaustive(expression, isStatement), ExpressionCodegen.this
);
if (switchCodegen != null) { if (switchCodegen != null) {
switchCodegen.generate(); switchCodegen.generate();
return Unit.INSTANCE; return Unit.INSTANCE;
@@ -3848,9 +3849,7 @@ The "returned" value of try expression with no finally is either the last expres
} }
if (!hasElse && nextCondition != null) { if (!hasElse && nextCondition != null) {
v.mark(nextCondition); v.mark(nextCondition);
if (!isStatement) { putUnitInstanceOntoStackForNonExhaustiveWhen(expression, isStatement);
putUnitInstanceOntoStackForNonExhaustiveWhen(expression);
}
} }
markLineNumber(expression, isStatement); markLineNumber(expression, isStatement);
@@ -3863,14 +3862,24 @@ The "returned" value of try expression with no finally is either the last expres
}); });
} }
private boolean isExhaustive(@NotNull KtWhenExpression whenExpression, boolean isStatement) {
if (isStatement) {
return Boolean.TRUE.equals(bindingContext.get(BindingContext.IMPLICIT_EXHAUSTIVE_WHEN, whenExpression));
}
else {
return Boolean.TRUE.equals(bindingContext.get(BindingContext.EXHAUSTIVE_WHEN, whenExpression));
}
}
public void putUnitInstanceOntoStackForNonExhaustiveWhen( public void putUnitInstanceOntoStackForNonExhaustiveWhen(
@NotNull KtWhenExpression expression @NotNull KtWhenExpression whenExpression,
boolean isStatement
) { ) {
if (Boolean.TRUE.equals(bindingContext.get(BindingContext.EXHAUSTIVE_WHEN, expression))) { if (isExhaustive(whenExpression, isStatement)) {
// when() is supposed to be exhaustive // when() is supposed to be exhaustive
genThrow(v, "kotlin/NoWhenBranchMatchedException", null); genThrow(v, "kotlin/NoWhenBranchMatchedException", null);
} }
else { else if (!isStatement) {
// non-exhaustive when() with no else -> Unit must be expected // non-exhaustive when() with no else -> Unit must be expected
StackValue.putUnitInstance(v); StackValue.putUnitInstance(v);
} }
@@ -30,10 +30,11 @@ public class EnumSwitchCodegen extends SwitchCodegen {
public EnumSwitchCodegen( public EnumSwitchCodegen(
@NotNull KtWhenExpression expression, @NotNull KtWhenExpression expression,
boolean isStatement, boolean isStatement,
boolean isExhaustive,
@NotNull ExpressionCodegen codegen, @NotNull ExpressionCodegen codegen,
@NotNull WhenByEnumsMapping mapping @NotNull WhenByEnumsMapping mapping
) { ) {
super(expression, isStatement, codegen); super(expression, isStatement, isExhaustive, codegen);
this.mapping = mapping; this.mapping = mapping;
} }
@@ -26,9 +26,10 @@ public class IntegralConstantsSwitchCodegen extends SwitchCodegen {
public IntegralConstantsSwitchCodegen( public IntegralConstantsSwitchCodegen(
@NotNull KtWhenExpression expression, @NotNull KtWhenExpression expression,
boolean isStatement, boolean isStatement,
boolean isExhaustive,
@NotNull ExpressionCodegen codegen @NotNull ExpressionCodegen codegen
) { ) {
super(expression, isStatement, codegen); super(expression, isStatement, isExhaustive, codegen);
} }
@Override @Override
@@ -40,9 +40,10 @@ public class StringSwitchCodegen extends SwitchCodegen {
public StringSwitchCodegen( public StringSwitchCodegen(
@NotNull KtWhenExpression expression, @NotNull KtWhenExpression expression,
boolean isStatement, boolean isStatement,
boolean isExhaustive,
@NotNull ExpressionCodegen codegen @NotNull ExpressionCodegen codegen
) { ) {
super(expression, isStatement, codegen); super(expression, isStatement, isExhaustive, codegen);
} }
@Override @Override
@@ -35,6 +35,7 @@ import java.util.*;
abstract public class SwitchCodegen { abstract public class SwitchCodegen {
protected final KtWhenExpression expression; protected final KtWhenExpression expression;
protected final boolean isStatement; protected final boolean isStatement;
protected final boolean isExhaustive;
protected final ExpressionCodegen codegen; protected final ExpressionCodegen codegen;
protected final BindingContext bindingContext; protected final BindingContext bindingContext;
protected final Type subjectType; protected final Type subjectType;
@@ -49,10 +50,11 @@ abstract public class SwitchCodegen {
public SwitchCodegen( public SwitchCodegen(
@NotNull KtWhenExpression expression, boolean isStatement, @NotNull KtWhenExpression expression, boolean isStatement,
@NotNull ExpressionCodegen codegen boolean isExhaustive, @NotNull ExpressionCodegen codegen
) { ) {
this.expression = expression; this.expression = expression;
this.isStatement = isStatement; this.isStatement = isStatement;
this.isExhaustive = isExhaustive;
this.codegen = codegen; this.codegen = codegen;
this.bindingContext = codegen.getBindingContext(); this.bindingContext = codegen.getBindingContext();
@@ -70,7 +72,7 @@ abstract public class SwitchCodegen {
boolean hasElse = expression.getElseExpression() != null; boolean hasElse = expression.getElseExpression() != null;
// if there is no else-entry and it's statement then default --- endLabel // if there is no else-entry and it's statement then default --- endLabel
defaultLabel = (hasElse || !isStatement) ? elseLabel : endLabel; defaultLabel = (hasElse || !isStatement || isExhaustive) ? elseLabel : endLabel;
generateSubject(); generateSubject();
@@ -79,9 +81,9 @@ abstract public class SwitchCodegen {
generateEntries(); generateEntries();
// there is no else-entry but this is not statement, so we should return Unit // there is no else-entry but this is not statement, so we should return Unit
if (!hasElse && !isStatement) { if (!hasElse && (!isStatement || isExhaustive)) {
v.visitLabel(elseLabel); v.visitLabel(elseLabel);
codegen.putUnitInstanceOntoStackForNonExhaustiveWhen(expression); codegen.putUnitInstanceOntoStackForNonExhaustiveWhen(expression, isStatement);
} }
codegen.markLineNumber(expression, isStatement); codegen.markLineNumber(expression, isStatement);
@@ -102,6 +102,7 @@ public class SwitchCodegenUtil {
public static SwitchCodegen buildAppropriateSwitchCodegenIfPossible( public static SwitchCodegen buildAppropriateSwitchCodegenIfPossible(
@NotNull KtWhenExpression expression, @NotNull KtWhenExpression expression,
boolean isStatement, boolean isStatement,
boolean isExhaustive,
@NotNull ExpressionCodegen codegen @NotNull ExpressionCodegen codegen
) { ) {
BindingContext bindingContext = codegen.getBindingContext(); BindingContext bindingContext = codegen.getBindingContext();
@@ -114,15 +115,15 @@ public class SwitchCodegenUtil {
WhenByEnumsMapping mapping = codegen.getBindingContext().get(CodegenBinding.MAPPING_FOR_WHEN_BY_ENUM, expression); WhenByEnumsMapping mapping = codegen.getBindingContext().get(CodegenBinding.MAPPING_FOR_WHEN_BY_ENUM, expression);
if (mapping != null) { if (mapping != null) {
return new EnumSwitchCodegen(expression, isStatement, codegen, mapping); return new EnumSwitchCodegen(expression, isStatement, isExhaustive, codegen, mapping);
} }
if (isIntegralConstantsSwitch(expression, subjectType, bindingContext)) { if (isIntegralConstantsSwitch(expression, subjectType, bindingContext)) {
return new IntegralConstantsSwitchCodegen(expression, isStatement, codegen); return new IntegralConstantsSwitchCodegen(expression, isStatement, isExhaustive, codegen);
} }
if (isStringConstantsSwitch(expression, subjectType, bindingContext)) { if (isStringConstantsSwitch(expression, subjectType, bindingContext)) {
return new StringSwitchCodegen(expression, isStatement, codegen); return new StringSwitchCodegen(expression, isStatement, isExhaustive, codegen);
} }
return null; return null;
@@ -0,0 +1,10 @@
enum class A { V }
fun box(): String {
val a: A = A.V
val b: Boolean
when (a) {
A.V -> b = true
}
return if (b) "OK" else "FAIL"
}
@@ -0,0 +1,8 @@
enum class A { V }
fun box(): String {
val a: A = A.V
when (a) {
A.V -> return "OK"
}
}
@@ -0,0 +1,15 @@
sealed class A {
object B : A()
class C : A()
}
fun box(): String {
val a: A = A.C()
val b: Boolean
when (a) {
A.B -> b = true
is A.C -> b = false
}
return if (!b) "OK" else "FAIL"
}
@@ -0,0 +1,14 @@
enum class Color { RED, GREEN, BLUE }
fun foo(arr: Array<Color>): Color {
loop@ for (color in arr) {
when (color) {
Color.RED -> return color
Color.GREEN -> break@loop
Color.BLUE -> if (arr.size == 1) return color else continue@loop
}
}
return Color.GREEN
}
fun box() = if (foo(arrayOf(Color.BLUE, Color.GREEN)) == Color.GREEN) "OK" else "FAIL"
@@ -0,0 +1,14 @@
enum class A { V }
fun box(): String {
val a: A = A.V
val b: Boolean
when (a) {
A.V -> b = true
}
return if (b) "OK" else "FAIL"
}
// 0 TABLESWITCH
// 1 LOOKUPSWITCH
// 1 ATHROW
@@ -0,0 +1,12 @@
enum class A { V }
fun box(): String {
val a: A = A.V
when (a) {
A.V -> return "OK"
}
}
// 0 TABLESWITCH
// 1 LOOKUPSWITCH
// 1 ATHROW
@@ -0,0 +1,19 @@
sealed class A {
object B : A()
class C : A()
}
fun box(): String {
val a: A = A.C()
val b: Boolean
when (a) {
A.B -> b = true
is A.C -> b = false
}
return if (!b) "OK" else "FAIL"
}
// 0 TABLESWITCH
// 0 LOOKUPSWITCH
// 1 ATHROW
@@ -1039,11 +1039,29 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/when"), Pattern.compile("^(.+)\\.kt$"), true); KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/when"), Pattern.compile("^(.+)\\.kt$"), true);
} }
@TestMetadata("exhaustiveWhenInitialization.kt")
public void testExhaustiveWhenInitialization() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/when/exhaustiveWhenInitialization.kt");
doTest(fileName);
}
@TestMetadata("exhaustiveWhenReturn.kt")
public void testExhaustiveWhenReturn() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/when/exhaustiveWhenReturn.kt");
doTest(fileName);
}
@TestMetadata("integralWhenWithNoInlinedConstants.kt") @TestMetadata("integralWhenWithNoInlinedConstants.kt")
public void testIntegralWhenWithNoInlinedConstants() throws Exception { public void testIntegralWhenWithNoInlinedConstants() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/when/integralWhenWithNoInlinedConstants.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/when/integralWhenWithNoInlinedConstants.kt");
doTest(fileName); doTest(fileName);
} }
@TestMetadata("sealedWhenInitialization.kt")
public void testSealedWhenInitialization() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/when/sealedWhenInitialization.kt");
doTest(fileName);
}
} }
@TestMetadata("compiler/testData/codegen/bytecodeText/whenEnumOptimization") @TestMetadata("compiler/testData/codegen/bytecodeText/whenEnumOptimization")
@@ -8254,6 +8254,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("exhaustiveWhenInitialization.kt")
public void testExhaustiveWhenInitialization() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/when/exhaustiveWhenInitialization.kt");
doTest(fileName);
}
@TestMetadata("exhaustiveWhenReturn.kt")
public void testExhaustiveWhenReturn() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/when/exhaustiveWhenReturn.kt");
doTest(fileName);
}
@TestMetadata("is.kt") @TestMetadata("is.kt")
public void testIs() throws Exception { public void testIs() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/when/is.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/when/is.kt");
@@ -8332,6 +8344,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("sealedWhenInitialization.kt")
public void testSealedWhenInitialization() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/when/sealedWhenInitialization.kt");
doTest(fileName);
}
@TestMetadata("whenArgumentIsEvaluatedOnlyOnce.kt") @TestMetadata("whenArgumentIsEvaluatedOnlyOnce.kt")
public void testWhenArgumentIsEvaluatedOnlyOnce() throws Exception { public void testWhenArgumentIsEvaluatedOnlyOnce() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/when/whenArgumentIsEvaluatedOnlyOnce.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/when/whenArgumentIsEvaluatedOnlyOnce.kt");
@@ -4891,6 +4891,12 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithStdlib/when"), Pattern.compile("^(.+)\\.kt$"), true); KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithStdlib/when"), Pattern.compile("^(.+)\\.kt$"), true);
} }
@TestMetadata("exhaustiveBreakContinue.kt")
public void testExhaustiveBreakContinue() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/when/exhaustiveBreakContinue.kt");
doTestWithStdlib(fileName);
}
@TestMetadata("integralWhenWithNoInlinedConstants.kt") @TestMetadata("integralWhenWithNoInlinedConstants.kt")
public void testIntegralWhenWithNoInlinedConstants() throws Exception { public void testIntegralWhenWithNoInlinedConstants() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/when/integralWhenWithNoInlinedConstants.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/when/integralWhenWithNoInlinedConstants.kt");