JS: when none of exhaustive when clauses match, generate code that throws exception. See KT-12194

This commit is contained in:
Alexey Andreev
2016-12-27 17:18:47 +03:00
parent 3384c6f603
commit 7f0166623d
7 changed files with 137 additions and 12 deletions
@@ -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);
} }
+4 -1
View File
@@ -33,4 +33,7 @@ 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()
+2
View File
@@ -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"
}