diff --git a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt index 4427204fce2..021cf758878 100644 --- a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt +++ b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt @@ -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) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/CastTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/CastTestGenerated.java index 4ec43632889..0df6bfd3f07 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/CastTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/CastTestGenerated.java @@ -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"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java index 94c1109d3cd..9931ed26c3e 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java @@ -396,8 +396,8 @@ public final class ExpressionVisitor extends TranslatorVisitor { ) { 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); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java index ece9e967ff0..ec96e96573c 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java @@ -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; + } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/expandIsCalls.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/expandIsCalls.kt index 598019e4a95..7535502a7f6 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/expandIsCalls.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/expandIsCalls.kt @@ -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) { val test = x.testExpression - if (x.isUnsafeCast && + if (x.isCastExpression && test is JsBinaryOperation && test.operator == JsBinaryOperator.ASG ) { diff --git a/js/js.translator/testData/expression/cast/cases/safeCastToNotNull.kt b/js/js.translator/testData/expression/cast/cases/safeCastToNotNull.kt new file mode 100644 index 00000000000..96995603cae --- /dev/null +++ b/js/js.translator/testData/expression/cast/cases/safeCastToNotNull.kt @@ -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" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/cast/cases/safeCastToNullable.kt b/js/js.translator/testData/expression/cast/cases/safeCastToNullable.kt new file mode 100644 index 00000000000..d29d9e60bb7 --- /dev/null +++ b/js/js.translator/testData/expression/cast/cases/safeCastToNullable.kt @@ -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" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/cast/cases/safeCastToReifiedNotNull.kt b/js/js.translator/testData/expression/cast/cases/safeCastToReifiedNotNull.kt new file mode 100644 index 00000000000..ea19f6efd7a --- /dev/null +++ b/js/js.translator/testData/expression/cast/cases/safeCastToReifiedNotNull.kt @@ -0,0 +1,21 @@ +package foo + +// CHECK_NOT_CALLED: castTo + +class A +class B + +inline +fun Any?.castTo(): T? = this as? T + +fun box(): String { + val a: Any? = A() + val nil: Any? = null + val b: Any? = B() + + assertEquals(a, a.castTo(), "a") + assertEquals(null, nil.castTo(), "nil") + assertEquals(null, b.castTo(), "b") + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/cast/cases/safeCastToReifiedNullable.kt b/js/js.translator/testData/expression/cast/cases/safeCastToReifiedNullable.kt new file mode 100644 index 00000000000..7e9ecebd5df --- /dev/null +++ b/js/js.translator/testData/expression/cast/cases/safeCastToReifiedNullable.kt @@ -0,0 +1,21 @@ +package foo + +// CHECK_NOT_CALLED: castTo + +class A +class B + +inline +fun Any?.castTo(): T? = this as? T? + +fun box(): String { + val a: Any? = A() + val nil: Any? = null + val b: Any? = B() + + assertEquals(a, a.castTo(), "a") + assertEquals(null, nil.castTo(), "nil") + assertEquals(null, b.castTo(), "b") + + return "OK" +} \ No newline at end of file