JS/RTTI: support safe casts
This commit is contained in:
committed by
Alexey Andreev
parent
3fd387d4a6
commit
2b3ebc7e9f
+1
-1
@@ -36,7 +36,7 @@ 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 JsConditional.isCastExpression: Boolean by MetadataProperty(default = false)
|
||||
|
||||
var JsInvocation.typeCheck: TypeCheck? by MetadataProperty(default = null)
|
||||
|
||||
|
||||
@@ -65,6 +65,30 @@ public class CastTestGenerated extends AbstractCastTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("safeCastToNotNull.kt")
|
||||
public void testSafeCastToNotNull() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/safeCastToNotNull.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("safeCastToNullable.kt")
|
||||
public void testSafeCastToNullable() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/safeCastToNullable.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("safeCastToReifiedNotNull.kt")
|
||||
public void testSafeCastToReifiedNotNull() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/safeCastToReifiedNotNull.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("safeCastToReifiedNullable.kt")
|
||||
public void testSafeCastToReifiedNullable() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/safeCastToReifiedNullable.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("smartCastInExtensionFunction.kt")
|
||||
public void testSmartCastInExtensionFunction() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/smartCastInExtensionFunction.kt");
|
||||
|
||||
+2
-2
@@ -396,8 +396,8 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
|
||||
) {
|
||||
JsExpression jsExpression;
|
||||
|
||||
if (PatternTranslator.isUnsafeCast(expression)) {
|
||||
jsExpression = PatternTranslator.newInstance(context).translateUnsafeCast(expression);
|
||||
if (PatternTranslator.isCastExpression(expression)) {
|
||||
jsExpression = PatternTranslator.newInstance(context).translateCastExpression(expression);
|
||||
}
|
||||
else {
|
||||
jsExpression = Translation.translateAsExpression(expression.getLeft(), context);
|
||||
|
||||
+28
-12
@@ -21,6 +21,8 @@ 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 com.google.dart.compiler.backend.js.ast.*;
|
||||
import com.google.dart.compiler.backend.js.ast.metadata.MetadataPackage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
@@ -55,26 +57,32 @@ public final class PatternTranslator extends AbstractTranslator {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public static boolean isUnsafeCast(@NotNull KtBinaryExpressionWithTypeRHS expression) {
|
||||
return expression.getOperationReference().getReferencedNameElementType() == KtTokens.AS_KEYWORD;
|
||||
public static boolean isCastExpression(@NotNull KtBinaryExpressionWithTypeRHS expression) {
|
||||
return isSafeCast(expression) || isUnsafeCast(expression);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JsExpression translateUnsafeCast(@NotNull KtBinaryExpressionWithTypeRHS expression) {
|
||||
assert isUnsafeCast(expression): "Expected unsafe cast expression, got " + expression;
|
||||
KtExpression left = expression.getLeft();
|
||||
public JsExpression translateCastExpression(@NotNull JetBinaryExpressionWithTypeRHS expression) {
|
||||
assert isCastExpression(expression): "Expected cast expression, got " + expression;
|
||||
JetExpression 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";
|
||||
JetTypeReference typeReference = expression.getRight();
|
||||
assert typeReference != null: "Cast expression must have type reference";
|
||||
JsExpression isCheck = translateIsCheck(temporary.assignmentExpression(), typeReference);
|
||||
JsExpression onFail;
|
||||
|
||||
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);
|
||||
if (isSafeCast(expression)) {
|
||||
onFail = JsLiteral.NULL;
|
||||
}
|
||||
else {
|
||||
JsExpression throwCCEFunRef = context().namer().throwClassCastExceptionFunRef();
|
||||
onFail = new JsInvocation(throwCCEFunRef);
|
||||
}
|
||||
|
||||
JsConditional conditional = new JsConditional(isCheck, temporary.reference(), onFail);
|
||||
MetadataPackage.setIsCastExpression(conditional, true);
|
||||
return conditional;
|
||||
}
|
||||
|
||||
@@ -185,4 +193,12 @@ public final class PatternTranslator extends AbstractTranslator {
|
||||
public JsExpression translateExpressionForExpressionPattern(@NotNull KtExpression patternExpression) {
|
||||
return Translation.translateAsExpression(patternExpression, context());
|
||||
}
|
||||
|
||||
private static boolean isSafeCast(@NotNull JetBinaryExpressionWithTypeRHS expression) {
|
||||
return expression.getOperationReference().getReferencedNameElementType() == JetTokens.AS_SAFE;
|
||||
}
|
||||
|
||||
private static boolean isUnsafeCast(@NotNull JetBinaryExpressionWithTypeRHS expression) {
|
||||
return expression.getOperationReference().getReferencedNameElementType() == JetTokens.AS_KEYWORD;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +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.isCastExpression
|
||||
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
|
||||
@@ -54,7 +54,7 @@ private class TypeCheckRewritingVisitor(private val context: TranslationContext)
|
||||
override fun endVisit(x: JsConditional, ctx: JsContext<JsNode>) {
|
||||
val test = x.testExpression
|
||||
|
||||
if (x.isUnsafeCast &&
|
||||
if (x.isCastExpression &&
|
||||
test is JsBinaryOperation &&
|
||||
test.operator == JsBinaryOperator.ASG
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package foo
|
||||
|
||||
class A
|
||||
class B
|
||||
|
||||
fun box(): String {
|
||||
val a: Any? = A()
|
||||
val nil: Any? = null
|
||||
val b: Any? = B()
|
||||
|
||||
assertEquals(a, a as? A, "a")
|
||||
assertEquals(null, nil as? A, "nil")
|
||||
assertEquals(null, b as? A, "b")
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package foo
|
||||
|
||||
class A
|
||||
class B
|
||||
|
||||
fun box(): String {
|
||||
val a: Any? = A()
|
||||
val nil: Any? = null
|
||||
val b: Any? = B()
|
||||
|
||||
assertEquals(a, a as? A?, "a")
|
||||
assertEquals(null, nil as? A?, "nil")
|
||||
assertEquals(null, b as? A?, "b")
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package foo
|
||||
|
||||
// CHECK_NOT_CALLED: castTo
|
||||
|
||||
class A
|
||||
class B
|
||||
|
||||
inline
|
||||
fun Any?.castTo<reified T>(): T? = this as? T
|
||||
|
||||
fun box(): String {
|
||||
val a: Any? = A()
|
||||
val nil: Any? = null
|
||||
val b: Any? = B()
|
||||
|
||||
assertEquals(a, a.castTo<A>(), "a")
|
||||
assertEquals(null, nil.castTo<A>(), "nil")
|
||||
assertEquals(null, b.castTo<A>(), "b")
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package foo
|
||||
|
||||
// CHECK_NOT_CALLED: castTo
|
||||
|
||||
class A
|
||||
class B
|
||||
|
||||
inline
|
||||
fun Any?.castTo<reified T>(): T? = this as? T?
|
||||
|
||||
fun box(): String {
|
||||
val a: Any? = A()
|
||||
val nil: Any? = null
|
||||
val b: Any? = B()
|
||||
|
||||
assertEquals(a, a.castTo<A>(), "a")
|
||||
assertEquals(null, nil.castTo<A>(), "nil")
|
||||
assertEquals(null, b.castTo<A>(), "b")
|
||||
|
||||
return "OK"
|
||||
}
|
||||
Reference in New Issue
Block a user