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 03cbaf42be0..d323dfa2576 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 @@ -59,5 +59,6 @@ var HasMetadata.sideEffects: Boolean by MetadataProperty(default = true) enum class TypeCheck { TYPEOF, - INSTANCEOF + INSTANCEOF, + OR_NULL } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java index 4ade2003d99..f8404177b29 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java @@ -380,16 +380,27 @@ public final class Namer { @NotNull public JsExpression isTypeOf(@NotNull JsExpression type) { - JsInvocation invocation = new JsInvocation(kotlin("isTypeOf"), type); - MetadataProperties.setTypeCheck(invocation, TypeCheck.TYPEOF); - MetadataProperties.setSideEffects(invocation, false); - return invocation; + return invokeFunctionAndSetTypeCheckMetadata("isTypeOf", type, TypeCheck.TYPEOF); } @NotNull public JsExpression isInstanceOf(@NotNull JsExpression type) { - JsInvocation invocation = new JsInvocation(kotlin("isInstanceOf"), type); - MetadataProperties.setTypeCheck(invocation, TypeCheck.INSTANCEOF); + return invokeFunctionAndSetTypeCheckMetadata("isInstanceOf", type, TypeCheck.INSTANCEOF); + } + + @NotNull + public JsExpression orNull(@NotNull JsExpression callable) { + return invokeFunctionAndSetTypeCheckMetadata("orNull", callable, TypeCheck.OR_NULL); + } + + @NotNull + private JsExpression invokeFunctionAndSetTypeCheckMetadata( + @NotNull String functionName, + @NotNull JsExpression argument, + @NotNull TypeCheck metadata + ) { + JsInvocation invocation = new JsInvocation(kotlin(functionName), argument); + MetadataProperties.setTypeCheck(invocation, metadata); MetadataProperties.setSideEffects(invocation, false); return invocation; } 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 83679c78eca..7f75bb8584d 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 @@ -16,18 +16,19 @@ package org.jetbrains.kotlin.js.translate.expression; -import com.google.dart.compiler.backend.js.ast.*; +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 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.translate.context.Namer; 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.patterns.NamePredicate; import org.jetbrains.kotlin.js.translate.utils.BindingUtils; -import org.jetbrains.kotlin.js.translate.utils.TranslationUtils; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.KtExpression; import org.jetbrains.kotlin.psi.KtIsExpression; @@ -35,8 +36,8 @@ import org.jetbrains.kotlin.psi.KtTypeReference; import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.types.KotlinType; -import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getTypeByReference; -import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.*; +import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.equality; +import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.negated; public final class PatternTranslator extends AbstractTranslator { @@ -65,17 +66,20 @@ public final class PatternTranslator extends AbstractTranslator { public JsExpression translateIsCheck(@NotNull JsExpression subject, @NotNull KtTypeReference typeReference) { KotlinType type = BindingUtils.getTypeByReference(bindingContext(), typeReference); JsExpression checkFunReference = getIsTypeCheckCallable(type); - JsInvocation isCheck = new JsInvocation(checkFunReference, subject); - - if (isNullable(typeReference)) { - return addNullCheck(subject, isCheck); - } - - return isCheck; + return new JsInvocation(checkFunReference, subject); } @NotNull public JsExpression getIsTypeCheckCallable(@NotNull KotlinType type) { + JsExpression callable = doGetIsTypeCheckCallable(type); + + if (type.isMarkedNullable()) return namer().orNull(callable); + + return callable; + } + + @NotNull + private JsExpression doGetIsTypeCheckCallable(@NotNull KotlinType type) { JsExpression builtinCheck = getIsTypeCheckCallableForBuiltin(type); if (builtinCheck != null) return builtinCheck; @@ -137,15 +141,6 @@ public final class PatternTranslator extends AbstractTranslator { return alias; } - @NotNull - private static JsExpression addNullCheck(@NotNull JsExpression expressionToMatch, @NotNull JsInvocation isCheck) { - return or(TranslationUtils.isNullCheck(expressionToMatch), isCheck); - } - - private boolean isNullable(KtTypeReference typeReference) { - return getTypeByReference(bindingContext(), typeReference).isMarkedNullable(); - } - @NotNull private JsNameRef getClassNameReference(@NotNull KotlinType type) { ClassDescriptor referencedClass = DescriptorUtils.getClassDescriptorForType(type); 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 2f8d9d61a66..25d143f49a5 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 @@ -20,29 +20,63 @@ 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 org.jetbrains.kotlin.js.translate.context.TranslationContext -import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.* +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.or +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.typeOfIs +import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.isNullCheck +import java.util.* fun expandIsCalls(node: JsNode, context: TranslationContext) { - val visitor = object : JsVisitorWithContextImpl() { - override fun visit(x: JsInvocation, ctx: JsContext): Boolean { - val callee = x.qualifier as? JsInvocation - val instance = x.arguments.firstOrNull() - val type = callee?.arguments?.firstOrNull() + TypeCheckRewritingVisitor(context).accept(node) +} - val replacement = when (callee?.typeCheck) { - TypeCheck.TYPEOF -> typeOfIs(instance!!, type as JsStringLiteral) - TypeCheck.INSTANCEOF -> context.namer().isInstanceOf(instance!!, type!!) - else -> null - } +private class TypeCheckRewritingVisitor(private val context: TranslationContext) : JsVisitorWithContextImpl() { - if (replacement != null) { - ctx.replaceMe(replacement) - return false - } + private val scopes = Stack() - return super.visit(x, ctx) - } + override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean { + scopes.push(x.scope) + return super.visit(x, ctx) } - visitor.accept(node) + override fun endVisit(x: JsFunction, ctx: JsContext<*>) { + scopes.pop() + super.endVisit(x, ctx) + } + + override fun visit(x: JsInvocation, ctx: JsContext): Boolean { + // callee(calleeArgument)(argument) + val callee = x.qualifier as? JsInvocation + val calleeArgument = callee?.arguments?.firstOrNull() + val argument = x.arguments.firstOrNull() + + if (callee != null && calleeArgument != null && argument != null) { + val replacement = getReplacement(callee, calleeArgument, argument) + + if (replacement != null) { + ctx.replaceMe(accept(replacement)) + return false + } + } + + return true + } + + private fun getReplacement(callee: JsInvocation, calleeArgument: JsExpression, argument: JsExpression): JsExpression? { + return when (callee.typeCheck) { + // Kotlin.isTypeOf(calleeArgument)(argument) -> typeOf argument === calleeArgument + TypeCheck.TYPEOF -> + typeOfIs(argument, calleeArgument as JsStringLiteral) + + // Kotlin.isInstanceOf(calleeArgument)(argument) -> argument instanceof calleeArgument + TypeCheck.INSTANCEOF -> + context.namer().isInstanceOf(argument, calleeArgument) + + // Kotlin.orNull(calleeArgument)(argument) -> argument === null || calleeArgument(argument) + TypeCheck.OR_NULL -> + or(isNullCheck(argument), JsInvocation(calleeArgument, argument)) + + else -> + null + } + } } diff --git a/js/js.translator/testData/kotlin_lib_ecma5.js b/js/js.translator/testData/kotlin_lib_ecma5.js index 3d97e6b1631..fc790405024 100644 --- a/js/js.translator/testData/kotlin_lib_ecma5.js +++ b/js/js.translator/testData/kotlin_lib_ecma5.js @@ -533,23 +533,27 @@ var Kotlin = {}; Object.defineProperty(Kotlin.modules, id, {value: declaration}); }; - function defineInlineFunction(tag, fun) { + Kotlin.defineInlineFunction = function(tag, fun) { return fun; - } + }; - Kotlin.defineInlineFunction = defineInlineFunction; - - Kotlin.isTypeOf = defineInlineFunction('stdlib.kotlin.isTypeOf', function (type) { + Kotlin.isTypeOf = function(type) { return function (object) { return typeof object === type; } - }); + }; - Kotlin.isInstanceOf = defineInlineFunction('stdlib.kotlin.isInstanceOf', function (klass) { + Kotlin.isInstanceOf = function (klass) { return function (object) { return Kotlin.isType(object, klass); } - }); + }; + + Kotlin.orNull = function (fn) { + return function (object) { + return object == null || fn(object); + } + }; Kotlin.kotlinModuleMetadata = function (abiVersion, moduleName, data) { }; diff --git a/js/js.translator/testData/reified/_commonFiles/isInstance.kt b/js/js.translator/testData/reified/_commonFiles/isInstance.kt index 0ea11339dab..9bbcc9eab1f 100644 --- a/js/js.translator/testData/reified/_commonFiles/isInstance.kt +++ b/js/js.translator/testData/reified/_commonFiles/isInstance.kt @@ -1,4 +1,4 @@ package foo -inline fun isInstance(x: Any): Boolean = - x is T \ No newline at end of file +inline fun isInstance(x: Any?): Boolean = + x is T diff --git a/js/js.translator/testData/reified/cases/isBool.kt b/js/js.translator/testData/reified/cases/isBool.kt index 02f87afbaeb..5663c9e54f6 100644 --- a/js/js.translator/testData/reified/cases/isBool.kt +++ b/js/js.translator/testData/reified/cases/isBool.kt @@ -7,5 +7,9 @@ fun box(): String { assertEquals(true, isInstance(false)) assertEquals(false, isInstance("true")) + assertEquals(true, isInstance(true), "isInstance(true)") + assertEquals(true, isInstance(null), "isInstance(null)") + assertEquals(false, isInstance("true"), "isInstance(\"true\")") + return "OK" } \ No newline at end of file diff --git a/js/js.translator/testData/reified/cases/isChar.kt b/js/js.translator/testData/reified/cases/isChar.kt index 9923f91f663..b43d18ae4eb 100644 --- a/js/js.translator/testData/reified/cases/isChar.kt +++ b/js/js.translator/testData/reified/cases/isChar.kt @@ -7,5 +7,9 @@ fun box(): String { assertEquals(false, isInstance("")) assertEquals(false, isInstance("cc")) + assertEquals(true, isInstance('c'), "isInstance('c')") + assertEquals(true, isInstance(null), "isInstance(null)") + assertEquals(false, isInstance("cc"), "isInstance(\"cc\")") + return "OK" } \ No newline at end of file diff --git a/js/js.translator/testData/reified/cases/isClass.kt b/js/js.translator/testData/reified/cases/isClass.kt index b01f937941b..4ac5659d5d0 100644 --- a/js/js.translator/testData/reified/cases/isClass.kt +++ b/js/js.translator/testData/reified/cases/isClass.kt @@ -17,5 +17,9 @@ fun box(): String { assertEquals(true, isInstance(D())) assertEquals(true, isInstance(D())) + assertEquals(true, isInstance(D()), "isInstance(D())") + assertEquals(true, isInstance(null), "isInstance(null)") + assertEquals(false, isInstance(C()), "isInstance(C())") + return "OK" } \ No newline at end of file diff --git a/js/js.translator/testData/reified/cases/isNumber.kt b/js/js.translator/testData/reified/cases/isNumber.kt index 1adbf2acbe0..ded771ee548 100644 --- a/js/js.translator/testData/reified/cases/isNumber.kt +++ b/js/js.translator/testData/reified/cases/isNumber.kt @@ -15,5 +15,9 @@ fun box(): String { assertEquals(true, isInstance(0.0)) assertEquals(false, isInstance("0")) + assertEquals(true, isInstance(0), "isInstance(0)") + assertEquals(true, isInstance(null), "isInstance(null)") + assertEquals(false, isInstance(true), "isInstance(true)") + return "OK" } \ No newline at end of file diff --git a/js/js.translator/testData/reified/cases/isString.kt b/js/js.translator/testData/reified/cases/isString.kt index e4602ee944b..94a645c478a 100644 --- a/js/js.translator/testData/reified/cases/isString.kt +++ b/js/js.translator/testData/reified/cases/isString.kt @@ -6,5 +6,9 @@ fun box(): String { assertEquals(true, isInstance("")) assertEquals(true, isInstance("a")) + assertEquals(true, isInstance(""), "isInstance(\"\")") + assertEquals(true, isInstance(null), "isInstance(null)") + assertEquals(false, isInstance(10), "isInstance(10)") + return "OK" } \ No newline at end of file