JS/RTTI: support unsafe casts
#KT-2670 fixed
This commit is contained in:
committed by
Alexey Andreev
parent
1d2da9729e
commit
3fd387d4a6
+2
@@ -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)
|
||||
|
||||
/**
|
||||
|
||||
@@ -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());
|
||||
|
||||
+11
-18
@@ -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) {
|
||||
|
||||
+29
-1
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user