JS/RTTI: fixed is-check on nullable reified parameters
#KT-7016 fixed
#KT-8012 fixed
This commit is contained in:
committed by
Alexey Andreev
parent
e1d10abfa1
commit
0b9c041d0a
+2
-1
@@ -59,5 +59,6 @@ var HasMetadata.sideEffects: Boolean by MetadataProperty(default = true)
|
||||
|
||||
enum class TypeCheck {
|
||||
TYPEOF,
|
||||
INSTANCEOF
|
||||
INSTANCEOF,
|
||||
OR_NULL
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+16
-21
@@ -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);
|
||||
|
||||
@@ -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<JsNode>): 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<JsScope>()
|
||||
|
||||
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<JsNode>): 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-8
@@ -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) {
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package foo
|
||||
|
||||
inline fun <reified T> isInstance(x: Any): Boolean =
|
||||
x is T
|
||||
inline fun <reified T> isInstance(x: Any?): Boolean =
|
||||
x is T
|
||||
|
||||
@@ -7,5 +7,9 @@ fun box(): String {
|
||||
assertEquals(true, isInstance<Boolean>(false))
|
||||
assertEquals(false, isInstance<Boolean>("true"))
|
||||
|
||||
assertEquals(true, isInstance<Boolean?>(true), "isInstance<Boolean?>(true)")
|
||||
assertEquals(true, isInstance<Boolean?>(null), "isInstance<Boolean?>(null)")
|
||||
assertEquals(false, isInstance<Boolean?>("true"), "isInstance<Boolean?>(\"true\")")
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -7,5 +7,9 @@ fun box(): String {
|
||||
assertEquals(false, isInstance<Char>(""))
|
||||
assertEquals(false, isInstance<Char>("cc"))
|
||||
|
||||
assertEquals(true, isInstance<Char?>('c'), "isInstance<Char?>('c')")
|
||||
assertEquals(true, isInstance<Char?>(null), "isInstance<Char?>(null)")
|
||||
assertEquals(false, isInstance<Char?>("cc"), "isInstance<Char?>(\"cc\")")
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -17,5 +17,9 @@ fun box(): String {
|
||||
assertEquals(true, isInstance<D>(D()))
|
||||
assertEquals(true, isInstance<C>(D()))
|
||||
|
||||
assertEquals(true, isInstance<D?>(D()), "isInstance<D?>(D())")
|
||||
assertEquals(true, isInstance<D?>(null), "isInstance<D?>(null)")
|
||||
assertEquals(false, isInstance<D?>(C()), "isInstance<D?>(C())")
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -15,5 +15,9 @@ fun box(): String {
|
||||
assertEquals(true, isInstance<Number>(0.0))
|
||||
assertEquals(false, isInstance<Number>("0"))
|
||||
|
||||
assertEquals(true, isInstance<Int?>(0), "isInstance<Int?>(0)")
|
||||
assertEquals(true, isInstance<Int?>(null), "isInstance<Int?>(null)")
|
||||
assertEquals(false, isInstance<Int?>(true), "isInstance<Int?>(true)")
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -6,5 +6,9 @@ fun box(): String {
|
||||
assertEquals(true, isInstance<String>(""))
|
||||
assertEquals(true, isInstance<String>("a"))
|
||||
|
||||
assertEquals(true, isInstance<String?>(""), "isInstance<String?>(\"\")")
|
||||
assertEquals(true, isInstance<String?>(null), "isInstance<String?>(null)")
|
||||
assertEquals(false, isInstance<String?>(10), "isInstance<String?>(10)")
|
||||
|
||||
return "OK"
|
||||
}
|
||||
Reference in New Issue
Block a user