JS: when none of exhaustive when clauses match, generate code that throws exception. See KT-12194
This commit is contained in:
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.name.Name
|
|||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
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.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
|
||||||
@@ -141,4 +142,16 @@ object CodegenUtil {
|
|||||||
function.returnType != null &&
|
function.returnType != null &&
|
||||||
isReturnTypeOk(function.returnType!!)
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4547,7 +4547,8 @@ The "returned" value of try expression with no finally is either the last expres
|
|||||||
@Override
|
@Override
|
||||||
public Unit invoke(InstructionAdapter v) {
|
public Unit invoke(InstructionAdapter v) {
|
||||||
SwitchCodegen switchCodegen = SwitchCodegenUtil.buildAppropriateSwitchCodegenIfPossible(
|
SwitchCodegen switchCodegen = SwitchCodegenUtil.buildAppropriateSwitchCodegenIfPossible(
|
||||||
expression, isStatement, isExhaustive(expression, isStatement), ExpressionCodegen.this
|
expression, isStatement, CodegenUtil.isExhaustive(bindingContext, expression, isStatement),
|
||||||
|
ExpressionCodegen.this
|
||||||
);
|
);
|
||||||
if (switchCodegen != null) {
|
if (switchCodegen != null) {
|
||||||
switchCodegen.generate();
|
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(
|
public void putUnitInstanceOntoStackForNonExhaustiveWhen(
|
||||||
@NotNull KtWhenExpression whenExpression,
|
@NotNull KtWhenExpression whenExpression,
|
||||||
boolean isStatement
|
boolean isStatement
|
||||||
) {
|
) {
|
||||||
if (isExhaustive(whenExpression, isStatement)) {
|
if (CodegenUtil.isExhaustive(bindingContext, whenExpression, isStatement)) {
|
||||||
// when() is supposed to be exhaustive
|
// when() is supposed to be exhaustive
|
||||||
genThrow(v, "kotlin/NoWhenBranchMatchedException", null);
|
genThrow(v, "kotlin/NoWhenBranchMatchedException", null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,3 +34,6 @@ internal fun arrayIterator(array: dynamic): MutableIterator<dynamic> {
|
|||||||
|
|
||||||
@JsName("PropertyMetadata")
|
@JsName("PropertyMetadata")
|
||||||
internal class PropertyMetadata(val name: String)
|
internal class PropertyMetadata(val name: String)
|
||||||
|
|
||||||
|
@JsName("noWhenBranchMatched")
|
||||||
|
internal fun noWhenBranchMatched(): Nothing = throw NoWhenBranchMatchedException()
|
||||||
@@ -41,3 +41,5 @@ public open class ClassCastException(message: String? = null) : RuntimeException
|
|||||||
public open class AssertionError(message: String? = null) : Error(message)
|
public open class AssertionError(message: String? = null) : Error(message)
|
||||||
|
|
||||||
public open class NoSuchElementException(message: String? = null) : Exception(message)
|
public open class NoSuchElementException(message: String? = null) : Exception(message)
|
||||||
|
|
||||||
|
public open class NoWhenBranchMatchedException(message: String? = null) : RuntimeException(message)
|
||||||
|
|||||||
@@ -3040,6 +3040,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("forWithOneStmWhen.kt")
|
||||||
public void testForWithOneStmWhen() throws Exception {
|
public void testForWithOneStmWhen() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/expression/when/forWithOneStmWhen.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/expression/when/forWithOneStmWhen.kt");
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ package org.jetbrains.kotlin.js.translate.expression;
|
|||||||
import com.google.dart.compiler.backend.js.ast.*;
|
import com.google.dart.compiler.backend.js.ast.*;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
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.context.TranslationContext;
|
||||||
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
|
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
|
||||||
import org.jetbrains.kotlin.js.translate.general.Translation;
|
import org.jetbrains.kotlin.js.translate.general.Translation;
|
||||||
@@ -85,9 +88,21 @@ public final class WhenTranslator extends AbstractTranslator {
|
|||||||
currentIf = nextIf;
|
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;
|
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
|
@NotNull
|
||||||
private static JsStatement translateEntryExpression(
|
private static JsStatement translateEntryExpression(
|
||||||
@NotNull KtWhenEntry entry,
|
@NotNull KtWhenEntry entry,
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
fun <T> checkThrown(x: T, block: (T) -> Any?): Unit? {
|
||||||
|
return try {
|
||||||
|
println((block(x) ?: "").toString())
|
||||||
|
null
|
||||||
|
}
|
||||||
|
catch (e: NoWhenBranchMatchedException) {
|
||||||
|
Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> 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<C>()
|
||||||
|
|
||||||
|
private inline fun createWrongE(): E = js("void 0").unsafeCast<E>()
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user