diff --git a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/CodegenUtil.kt b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/CodegenUtil.kt index a1c31a9293f..16f84691b05 100644 --- a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/CodegenUtil.kt +++ b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/CodegenUtil.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.types.KotlinType @@ -141,4 +142,16 @@ object CodegenUtil { function.returnType != null && isReturnTypeOk(function.returnType!!) } + + + @JvmStatic + fun BindingContext.isExhaustive(whenExpression: KtWhenExpression, isStatement: Boolean): Boolean { + val slice = if (isStatement && !whenExpression.isUsedAsExpression(this)) { + BindingContext.IMPLICIT_EXHAUSTIVE_WHEN + } + else { + BindingContext.EXHAUSTIVE_WHEN + } + return this[slice, whenExpression] == true + } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index b90afe33244..269bb31e07e 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -4547,7 +4547,8 @@ The "returned" value of try expression with no finally is either the last expres @Override public Unit invoke(InstructionAdapter v) { SwitchCodegen switchCodegen = SwitchCodegenUtil.buildAppropriateSwitchCodegenIfPossible( - expression, isStatement, isExhaustive(expression, isStatement), ExpressionCodegen.this + expression, isStatement, CodegenUtil.isExhaustive(bindingContext, expression, isStatement), + ExpressionCodegen.this ); if (switchCodegen != null) { switchCodegen.generate(); @@ -4607,20 +4608,11 @@ The "returned" value of try expression with no finally is either the last expres }); } - private boolean isExhaustive(@NotNull KtWhenExpression whenExpression, boolean isStatement) { - if (isStatement && !BindingContextUtilsKt.isUsedAsExpression(whenExpression, bindingContext)) { - 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( @NotNull KtWhenExpression whenExpression, boolean isStatement ) { - if (isExhaustive(whenExpression, isStatement)) { + if (CodegenUtil.isExhaustive(bindingContext, whenExpression, isStatement)) { // when() is supposed to be exhaustive genThrow(v, "kotlin/NoWhenBranchMatchedException", null); } diff --git a/js/js.libraries/src/core/builtins.kt b/js/js.libraries/src/core/builtins.kt index e51b56a1137..3b80da96653 100644 --- a/js/js.libraries/src/core/builtins.kt +++ b/js/js.libraries/src/core/builtins.kt @@ -33,4 +33,7 @@ internal fun arrayIterator(array: dynamic): MutableIterator { } @JsName("PropertyMetadata") -internal class PropertyMetadata(val name: String) \ No newline at end of file +internal class PropertyMetadata(val name: String) + +@JsName("noWhenBranchMatched") +internal fun noWhenBranchMatched(): Nothing = throw NoWhenBranchMatchedException() \ No newline at end of file diff --git a/js/js.libraries/src/core/exceptions.kt b/js/js.libraries/src/core/exceptions.kt index d50cffb996d..4d305f75ac7 100644 --- a/js/js.libraries/src/core/exceptions.kt +++ b/js/js.libraries/src/core/exceptions.kt @@ -41,3 +41,5 @@ public open class ClassCastException(message: String? = null) : RuntimeException public open class AssertionError(message: String? = null) : Error(message) public open class NoSuchElementException(message: String? = null) : Exception(message) + +public open class NoWhenBranchMatchedException(message: String? = null) : RuntimeException(message) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 333648c4ba5..e41dd188d51 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -3040,6 +3040,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } + @TestMetadata("exhaustiveCheckException.kt") + public void testExhaustiveCheckException() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/expression/when/exhaustiveCheckException.kt"); + doTest(fileName); + } + @TestMetadata("forWithOneStmWhen.kt") public void testForWithOneStmWhen() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/expression/when/forWithOneStmWhen.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java index b96c15c448c..f594039b882 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java @@ -19,6 +19,9 @@ package org.jetbrains.kotlin.js.translate.expression; import com.google.dart.compiler.backend.js.ast.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.backend.common.CodegenUtil; +import org.jetbrains.kotlin.builtins.KotlinBuiltIns; +import org.jetbrains.kotlin.js.translate.context.Namer; import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.general.AbstractTranslator; import org.jetbrains.kotlin.js.translate.general.Translation; @@ -85,9 +88,21 @@ public final class WhenTranslator extends AbstractTranslator { currentIf = nextIf; } } + + if (currentIf != null && currentIf.getElseStatement() == null && isExhaustive()) { + JsExpression noWhenMatchedInvocation = new JsInvocation(JsAstUtils.pureFqn("noWhenBranchMatched", Namer.kotlinObject())); + currentIf.setElseStatement(JsAstUtils.asSyntheticStatement(noWhenMatchedInvocation)); + } + return resultIf != null ? resultIf : JsLiteral.NULL; } + private boolean isExhaustive() { + KotlinType type = bindingContext().getType(whenExpression); + boolean isStatement = type != null && KotlinBuiltIns.isUnit(type) && !type.isMarkedNullable(); + return CodegenUtil.isExhaustive(bindingContext(), whenExpression, isStatement); + } + @NotNull private static JsStatement translateEntryExpression( @NotNull KtWhenEntry entry, diff --git a/js/js.translator/testData/box/expression/when/exhaustiveCheckException.kt b/js/js.translator/testData/box/expression/when/exhaustiveCheckException.kt new file mode 100644 index 00000000000..4cdbeeee9bb --- /dev/null +++ b/js/js.translator/testData/box/expression/when/exhaustiveCheckException.kt @@ -0,0 +1,94 @@ +fun checkThrown(x: T, block: (T) -> Any?): Unit? { + return try { + println((block(x) ?: "").toString()) + null + } + catch (e: NoWhenBranchMatchedException) { + Unit + } +} + +fun checkNotThrown(x: T, block: (T) -> Any?): Unit? { + return try { + println((block(x) ?: "").toString()) + Unit + } + catch (e: NoWhenBranchMatchedException) { + null + } +} + +sealed class C { + class X : C() + + class Y : C() +} + +enum class E { + X, Y +} + +private inline fun createWrongC(): C = js("void 0").unsafeCast() + +private inline fun createWrongE(): E = js("void 0").unsafeCast() + +fun box(): String { + checkThrown(createWrongC()) { + when (it) { + is C.X -> 0 + is C.Y -> 1 + } + } ?: return "fail1" + + checkNotThrown(createWrongC()) { + when (it) { + is C.X -> 0 + else -> 1 + } + } ?: return "fail2" + + checkThrown(createWrongE()) { + when (it) { + E.X -> 0 + E.Y -> 1 + } + } ?: return "fail3" + + checkNotThrown(createWrongE()) { + when (it) { + E.X -> 0 + else -> 1 + } + } ?: return "fail4" + + checkNotThrown(createWrongC()) { + when (it) { + is C.X -> {} + is C.Y -> {} + } + Unit + } ?: return "fail5" + + checkNotThrown(createWrongC()) { + when (it) { + is C.X -> {} + is C.Y -> {} + } + } ?: return "fail6" + + checkNotThrown(createWrongC()) { + when (it) { + is C.X -> Unit + is C.Y -> Unit + } + } ?: return "fail7" + + checkThrown(createWrongC()) { + when (it) { + is C.X -> Unit + is C.Y -> null as Unit? + } + } ?: return "fail8" + + return "OK" +} \ No newline at end of file