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 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)
|
var JsInvocation.typeCheck: TypeCheck? by MetadataProperty(default = null)
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,30 @@ public class CastTestGenerated extends AbstractCastTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("smartCastInExtensionFunction.kt")
|
||||||
public void testSmartCastInExtensionFunction() throws Exception {
|
public void testSmartCastInExtensionFunction() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/smartCastInExtensionFunction.kt");
|
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;
|
JsExpression jsExpression;
|
||||||
|
|
||||||
if (PatternTranslator.isUnsafeCast(expression)) {
|
if (PatternTranslator.isCastExpression(expression)) {
|
||||||
jsExpression = PatternTranslator.newInstance(context).translateUnsafeCast(expression);
|
jsExpression = PatternTranslator.newInstance(context).translateCastExpression(expression);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
jsExpression = Translation.translateAsExpression(expression.getLeft(), context);
|
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.JsInvocation;
|
||||||
import com.google.dart.compiler.backend.js.ast.JsNameRef;
|
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.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.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.kotlin.descriptors.*;
|
import org.jetbrains.kotlin.descriptors.*;
|
||||||
@@ -55,26 +57,32 @@ public final class PatternTranslator extends AbstractTranslator {
|
|||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isUnsafeCast(@NotNull KtBinaryExpressionWithTypeRHS expression) {
|
public static boolean isCastExpression(@NotNull KtBinaryExpressionWithTypeRHS expression) {
|
||||||
return expression.getOperationReference().getReferencedNameElementType() == KtTokens.AS_KEYWORD;
|
return isSafeCast(expression) || isUnsafeCast(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public JsExpression translateUnsafeCast(@NotNull KtBinaryExpressionWithTypeRHS expression) {
|
public JsExpression translateCastExpression(@NotNull JetBinaryExpressionWithTypeRHS expression) {
|
||||||
assert isUnsafeCast(expression): "Expected unsafe cast expression, got " + expression;
|
assert isCastExpression(expression): "Expected cast expression, got " + expression;
|
||||||
KtExpression left = expression.getLeft();
|
JetExpression left = expression.getLeft();
|
||||||
JsExpression expressionToCast = Translation.translateAsExpression(left, context());
|
JsExpression expressionToCast = Translation.translateAsExpression(left, context());
|
||||||
TemporaryVariable temporary = context().declareTemporary(expressionToCast);
|
TemporaryVariable temporary = context().declareTemporary(expressionToCast);
|
||||||
|
|
||||||
KtTypeReference typeReference = expression.getRight();
|
JetTypeReference typeReference = expression.getRight();
|
||||||
assert typeReference != null: "Unsafe cast must have type reference";
|
assert typeReference != null: "Cast expression must have type reference";
|
||||||
JsExpression isCheck = translateIsCheck(temporary.assignmentExpression(), typeReference);
|
JsExpression isCheck = translateIsCheck(temporary.assignmentExpression(), typeReference);
|
||||||
|
JsExpression onFail;
|
||||||
|
|
||||||
Namer namer = context().namer();
|
if (isSafeCast(expression)) {
|
||||||
JsExpression throwCCEFunRef = namer.throwClassCastExceptionFunRef();
|
onFail = JsLiteral.NULL;
|
||||||
JsExpression throwCCE = new JsInvocation(throwCCEFunRef);
|
}
|
||||||
JsConditional conditional = new JsConditional(isCheck, temporary.reference(), throwCCE);
|
else {
|
||||||
MetadataProperties.setUnsafeCast(conditional, true);
|
JsExpression throwCCEFunRef = context().namer().throwClassCastExceptionFunRef();
|
||||||
|
onFail = new JsInvocation(throwCCEFunRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
JsConditional conditional = new JsConditional(isCheck, temporary.reference(), onFail);
|
||||||
|
MetadataPackage.setIsCastExpression(conditional, true);
|
||||||
return conditional;
|
return conditional;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,4 +193,12 @@ public final class PatternTranslator extends AbstractTranslator {
|
|||||||
public JsExpression translateExpressionForExpressionPattern(@NotNull KtExpression patternExpression) {
|
public JsExpression translateExpressionForExpressionPattern(@NotNull KtExpression patternExpression) {
|
||||||
return Translation.translateAsExpression(patternExpression, context());
|
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.*
|
||||||
import com.google.dart.compiler.backend.js.ast.metadata.TypeCheck
|
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 com.google.dart.compiler.backend.js.ast.metadata.typeCheck
|
||||||
import org.jetbrains.kotlin.js.inline.util.IdentitySet
|
import org.jetbrains.kotlin.js.inline.util.IdentitySet
|
||||||
import org.jetbrains.kotlin.js.translate.context.TranslationContext
|
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>) {
|
override fun endVisit(x: JsConditional, ctx: JsContext<JsNode>) {
|
||||||
val test = x.testExpression
|
val test = x.testExpression
|
||||||
|
|
||||||
if (x.isUnsafeCast &&
|
if (x.isCastExpression &&
|
||||||
test is JsBinaryOperation &&
|
test is JsBinaryOperation &&
|
||||||
test.operator == JsBinaryOperator.ASG
|
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