JS/RTTI: support safe casts

This commit is contained in:
Alexey Tsvetkov
2015-06-18 16:46:48 +03:00
committed by Alexey Andreev
parent 3fd387d4a6
commit 2b3ebc7e9f
9 changed files with 131 additions and 17 deletions
@@ -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");
@@ -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);
@@ -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"
}