JS/RTTI: support unsafe casts

#KT-2670 fixed
This commit is contained in:
Alexey Tsvetkov
2015-06-11 15:31:54 +03:00
committed by Alexey Andreev
parent 1d2da9729e
commit 3fd387d4a6
17 changed files with 196 additions and 83 deletions
@@ -36,6 +36,8 @@ var JsFunction.isLocal: Boolean by MetadataProperty(default = false)
var JsParameter.hasDefaultValue: Boolean by MetadataProperty(default = false)
var JsConditional.isUnsafeCast: Boolean by MetadataProperty(default = false)
var JsInvocation.typeCheck: TypeCheck? by MetadataProperty(default = null)
/**
+3
View File
@@ -27,6 +27,9 @@ public class NumberFormatException(message: String? = null) : RuntimeException(m
@library
public class NullPointerException(message: String? = null) : RuntimeException(message) {}
library
public class ClassCastException(message: String? = null) : RuntimeException(message) {}
@library
public class AssertionError(message: String? = null) : Error(message) {}
@@ -41,15 +41,27 @@ public class CastTestGenerated extends AbstractCastTest {
doTest(fileName);
}
@TestMetadata("castToNotNullType.kt")
public void testCastToNotNullType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/castToNotNullType.kt");
@TestMetadata("castToNotNull.kt")
public void testCastToNotNull() throws Exception {
String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/castToNotNull.kt");
doTest(fileName);
}
@TestMetadata("castToNullableType.kt")
public void testCastToNullableType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/castToNullableType.kt");
@TestMetadata("castToNullable.kt")
public void testCastToNullable() throws Exception {
String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/castToNullable.kt");
doTest(fileName);
}
@TestMetadata("reifiedToNotNull.kt")
public void testReifiedToNotNull() throws Exception {
String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/reifiedToNotNull.kt");
doTest(fileName);
}
@TestMetadata("reifiedToNullable.kt")
public void testReifiedToNullable() throws Exception {
String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/reifiedToNullable.kt");
doTest(fileName);
}
@@ -148,6 +148,22 @@ public class DirectiveTestUtils {
private static final DirectiveHandler COUNT_NULLS = new CountNodesDirective<JsNullLiteral>("CHECK_NULLS_COUNT", JsNullLiteral.class);
private static final DirectiveHandler NOT_REFERENCED = new DirectiveHandler("CHECK_NOT_REFERENCED") {
@Override
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
final String reference = arguments.getPositionalArgument(0);
JsVisitor visitor = new RecursiveJsVisitor() {
@Override
public void visitNameRef(@NotNull JsNameRef nameRef) {
assertNotEquals(reference, nameRef.toString());
}
};
visitor.accept(ast);
}
};
private static final DirectiveHandler HAS_INLINE_METADATA = new DirectiveHandler("CHECK_HAS_INLINE_METADATA") {
@Override
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
@@ -178,6 +194,7 @@ public class DirectiveTestUtils {
COUNT_VARS,
COUNT_BREAKS,
COUNT_NULLS,
NOT_REFERENCED,
HAS_INLINE_METADATA,
HAS_NO_INLINE_METADATA
);
@@ -92,6 +92,7 @@ public final class Namer {
public static final String ANOTHER_THIS_PARAMETER_NAME = "$this";
private static final String THROW_NPE_FUN_NAME = "throwNPE";
private static final String THROW_CLASS_CAST_EXCEPTION_FUN_NAME = "throwCCE";
private static final String PROTOTYPE_NAME = "prototype";
private static final String CAPTURED_VAR_FIELD = "v";
@@ -363,6 +364,11 @@ public final class Namer {
return new JsNameRef(THROW_NPE_FUN_NAME, kotlinObject());
}
@NotNull
public JsExpression throwClassCastExceptionFunRef() {
return new JsNameRef(THROW_CLASS_CAST_EXCEPTION_FUN_NAME, kotlinObject());
}
@NotNull
public static JsNameRef kotlin(@NotNull JsName name) {
return fqnWithoutSideEffects(name, kotlinObject());
@@ -36,8 +36,6 @@ import org.jetbrains.kotlin.js.translate.operation.UnaryOperationTranslator;
import org.jetbrains.kotlin.js.translate.reference.*;
import org.jetbrains.kotlin.js.translate.utils.BindingUtils;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
import org.jetbrains.kotlin.resolve.BindingContext;
@@ -392,25 +390,20 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
@Override
@NotNull
public JsNode visitBinaryWithTypeRHSExpression(@NotNull KtBinaryExpressionWithTypeRHS expression,
@NotNull TranslationContext context) {
JsExpression jsExpression = Translation.translateAsExpression(expression.getLeft(), context);
public JsNode visitBinaryWithTypeRHSExpression(
@NotNull KtBinaryExpressionWithTypeRHS expression,
@NotNull TranslationContext context
) {
JsExpression jsExpression;
if (expression.getOperationReference().getReferencedNameElementType() != KtTokens.AS_KEYWORD)
return jsExpression.source(expression);
KtTypeReference right = expression.getRight();
assert right != null;
KotlinType rightType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.TYPE, right);
KotlinType leftType = BindingContextUtils.getTypeNotNull(context.bindingContext(), expression.getLeft());
if (TypeUtils.isNullableType(rightType) || !TypeUtils.isNullableType(leftType)) {
return jsExpression.source(expression);
if (PatternTranslator.isUnsafeCast(expression)) {
jsExpression = PatternTranslator.newInstance(context).translateUnsafeCast(expression);
}
else {
jsExpression = Translation.translateAsExpression(expression.getLeft(), context);
}
// KT-2670
// we actually do not care for types in js
return TranslationUtils.sure(jsExpression, context).source(expression);
return jsExpression.source(expression);
}
private static String getReferencedName(KtSimpleNameExpression expression) {
@@ -16,20 +16,25 @@
package org.jetbrains.kotlin.js.translate.expression;
import com.google.dart.compiler.backend.js.ast.JsConditional;
import com.google.dart.compiler.backend.js.ast.JsExpression;
import com.google.dart.compiler.backend.js.ast.JsInvocation;
import com.google.dart.compiler.backend.js.ast.JsNameRef;
import com.google.dart.compiler.backend.js.ast.metadata.MetadataProperties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.js.patterns.NamePredicate;
import org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt;
import org.jetbrains.kotlin.js.patterns.NamePredicate;
import org.jetbrains.kotlin.js.translate.context.Namer;
import org.jetbrains.kotlin.js.translate.context.TemporaryVariable;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
import org.jetbrains.kotlin.js.translate.general.Translation;
import org.jetbrains.kotlin.js.translate.utils.BindingUtils;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtBinaryExpressionWithTypeRHS;
import org.jetbrains.kotlin.psi.KtExpression;
import org.jetbrains.kotlin.psi.KtIsExpression;
import org.jetbrains.kotlin.psi.KtTypeReference;
@@ -50,6 +55,29 @@ public final class PatternTranslator extends AbstractTranslator {
super(context);
}
public static boolean isUnsafeCast(@NotNull KtBinaryExpressionWithTypeRHS expression) {
return expression.getOperationReference().getReferencedNameElementType() == KtTokens.AS_KEYWORD;
}
@NotNull
public JsExpression translateUnsafeCast(@NotNull KtBinaryExpressionWithTypeRHS expression) {
assert isUnsafeCast(expression): "Expected unsafe cast expression, got " + expression;
KtExpression left = expression.getLeft();
JsExpression expressionToCast = Translation.translateAsExpression(left, context());
TemporaryVariable temporary = context().declareTemporary(expressionToCast);
KtTypeReference typeReference = expression.getRight();
assert typeReference != null: "Unsafe cast must have type reference";
JsExpression isCheck = translateIsCheck(temporary.assignmentExpression(), typeReference);
Namer namer = context().namer();
JsExpression throwCCEFunRef = namer.throwClassCastExceptionFunRef();
JsExpression throwCCE = new JsInvocation(throwCCEFunRef);
JsConditional conditional = new JsConditional(isCheck, temporary.reference(), throwCCE);
MetadataProperties.setUnsafeCast(conditional, true);
return conditional;
}
@NotNull
public JsExpression translateIsExpression(@NotNull KtIsExpression expression) {
JsExpression left = Translation.translateAsExpression(expression.getLeftHandSide(), context());
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.translate.utils
import com.google.dart.compiler.backend.js.ast.*
import com.google.dart.compiler.backend.js.ast.metadata.TypeCheck
import com.google.dart.compiler.backend.js.ast.metadata.isUnsafeCast
import com.google.dart.compiler.backend.js.ast.metadata.typeCheck
import org.jetbrains.kotlin.js.inline.util.IdentitySet
import org.jetbrains.kotlin.js.translate.context.TranslationContext
@@ -50,6 +51,17 @@ private class TypeCheckRewritingVisitor(private val context: TranslationContext)
super.endVisit(x, ctx)
}
override fun endVisit(x: JsConditional, ctx: JsContext<JsNode>) {
val test = x.testExpression
if (x.isUnsafeCast &&
test is JsBinaryOperation &&
test.operator == JsBinaryOperator.ASG
) {
ctx.replaceMe(test.arg2)
}
}
override fun visit(x: JsInvocation, ctx: JsContext<JsNode>): Boolean {
// callee(calleeArgument)(argument)
val callee = x.qualifier as? JsInvocation
@@ -0,0 +1,16 @@
package kotlin
// CHECK_NOT_REFERENCED: Kotlin.isInstanceOf
// CHECK_NOT_REFERENCED: Kotlin.isTypeOf
// CHECK_NOT_REFERENCED: Kotlin.orNull
fun failsClassCast(message: String, fn: ()->Unit) {
try {
fn()
}
catch (e: ClassCastException) {
return
}
throw Exception("Expected ClassCastException to be thrown: message=$message")
}
@@ -43,17 +43,6 @@ fun test(f: () -> Unit) {
}
}
fun fails(f: () -> Unit) {
try {
f()
}
catch(e: Exception) {
return
}
throw Exception("Expected an exception to be thrown from $f")
}
fun box(): String {
val a = A("OK")
@@ -69,6 +58,7 @@ fun box(): String {
test { castNullableToNotNullT<A>(a) }
fails { castNullableToNotNullT<A>(null) }
failsClassCast("castNullableToNotNullT<A>(null)") { castNullableToNotNullT<A>(null) }
return "OK"
}
@@ -0,0 +1,18 @@
package foo
trait A
class AImpl: A
fun test(x: Any?): A = x as A
fun box(): String {
var a: A = AImpl()
assertEquals(a, test(a), "a = AImpl()")
a = object : A {}
assertEquals(a, test(a), "a = object : A{}")
failsClassCast("test(null)") { test(null) }
failsClassCast("test(object{})") { test(object{}) }
return "OK"
}
@@ -1,26 +0,0 @@
package foo
class A
fun checkCastNullableToNotNull(): Boolean {
val a = null
try {
val s = a as A
}
catch (e: Exception) {
return true
}
return false
}
fun checkCastNotNullToNotNull(): Boolean {
val a = A()
var s = a as A
return s == a
}
fun box(): String {
if (!checkCastNullableToNotNull()) return "Failed when try cast Nullable to NotNull"
if (!checkCastNotNullToNotNull()) return "Failed when try cast NotNull to NotNull"
return "OK"
}
@@ -0,0 +1,18 @@
package foo
interface A
class AImpl : A {}
fun test(x: Any?): A? = x as A?
fun box(): String {
var a: A? = AImpl()
assertEquals(a, test(a), "a = AImpl()")
a = object : A {}
assertEquals(a, test(a), "a = object : A{}")
assertEquals(null, test(null), "test(null)")
failsClassCast("test(object{})") { test(object{}) }
return "OK"
}
@@ -1,21 +0,0 @@
package foo
class A
fun <T> Any.cast() = this!! as T
fun box(): String {
val a = null
val s = a as A?
if (s != null) return "Failed when try cast Nullable with null value to Nullable"
val b: A? = A()
val n = b as A?
if (n != b) return "Failed when try cast Nullable with not null value to Nullable"
val c = A()
val m = c as A?
if (m != c) return "Failed when try cast NotNull to Nullable"
return "OK"
}
@@ -0,0 +1,21 @@
package foo
// CHECK_NOT_CALLED: test
trait A
class AImpl: A
inline
fun test<reified T>(x: Any?): T = x as T
fun box(): String {
var a: A = AImpl()
assertEquals(a, test<A>(a), "a = AImpl()")
a = object : A {}
assertEquals(a, test<A>(a), "a = object : A{}")
failsClassCast("test(null)") { test<A>(null) }
failsClassCast("test(object{})") { test<A>(object{}) }
return "OK"
}
@@ -0,0 +1,19 @@
package foo
interface A
class AImpl : A {}
inline
fun test<reified T>(x: Any?): T = x as T
fun box(): String {
var a: A? = AImpl()
assertEquals(a, test<A?>(a), "a = AImpl()")
a = object : A {}
assertEquals(a, test<A?>(a), "a = object : A{}")
assertEquals(null, test<A?>(null), "test(null)")
failsClassCast("test(object{})") { test<A?>(object{}) }
return "OK"
}
+5
View File
@@ -199,6 +199,7 @@
Kotlin.IllegalStateException = createClassNowWithMessage(Kotlin.RuntimeException);
Kotlin.UnsupportedOperationException = createClassNowWithMessage(Kotlin.RuntimeException);
Kotlin.IndexOutOfBoundsException = createClassNowWithMessage(Kotlin.RuntimeException);
Kotlin.ClassCastException = createClassNowWithMessage(Kotlin.RuntimeException);
Kotlin.IOException = createClassNowWithMessage(Kotlin.Exception);
Kotlin.AssertionError = createClassNowWithMessage(Kotlin.Error);
@@ -206,6 +207,10 @@
throw new Kotlin.NullPointerException(message);
};
Kotlin.throwCCE = function () {
throw new Kotlin.ClassCastException("Illegal cast");
};
function throwAbstractFunctionInvocationError(funName) {
return function () {
var message;