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 {
|
enum class TypeCheck {
|
||||||
TYPEOF,
|
TYPEOF,
|
||||||
INSTANCEOF
|
INSTANCEOF,
|
||||||
|
OR_NULL
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,16 +380,27 @@ public final class Namer {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public JsExpression isTypeOf(@NotNull JsExpression type) {
|
public JsExpression isTypeOf(@NotNull JsExpression type) {
|
||||||
JsInvocation invocation = new JsInvocation(kotlin("isTypeOf"), type);
|
return invokeFunctionAndSetTypeCheckMetadata("isTypeOf", type, TypeCheck.TYPEOF);
|
||||||
MetadataProperties.setTypeCheck(invocation, TypeCheck.TYPEOF);
|
|
||||||
MetadataProperties.setSideEffects(invocation, false);
|
|
||||||
return invocation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public JsExpression isInstanceOf(@NotNull JsExpression type) {
|
public JsExpression isInstanceOf(@NotNull JsExpression type) {
|
||||||
JsInvocation invocation = new JsInvocation(kotlin("isInstanceOf"), type);
|
return invokeFunctionAndSetTypeCheckMetadata("isInstanceOf", type, TypeCheck.INSTANCEOF);
|
||||||
MetadataProperties.setTypeCheck(invocation, 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);
|
MetadataProperties.setSideEffects(invocation, false);
|
||||||
return invocation;
|
return invocation;
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-21
@@ -16,18 +16,19 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.js.translate.expression;
|
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.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.kotlin.descriptors.*;
|
import org.jetbrains.kotlin.descriptors.*;
|
||||||
|
import org.jetbrains.kotlin.js.patterns.NamePredicate;
|
||||||
import org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt;
|
import org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt;
|
||||||
import org.jetbrains.kotlin.js.translate.context.Namer;
|
import org.jetbrains.kotlin.js.translate.context.Namer;
|
||||||
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
|
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
|
||||||
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
|
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
|
||||||
import org.jetbrains.kotlin.js.translate.general.Translation;
|
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.BindingUtils;
|
||||||
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
|
|
||||||
import org.jetbrains.kotlin.name.Name;
|
import org.jetbrains.kotlin.name.Name;
|
||||||
import org.jetbrains.kotlin.psi.KtExpression;
|
import org.jetbrains.kotlin.psi.KtExpression;
|
||||||
import org.jetbrains.kotlin.psi.KtIsExpression;
|
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.resolve.DescriptorUtils;
|
||||||
import org.jetbrains.kotlin.types.KotlinType;
|
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.equality;
|
||||||
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.*;
|
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.negated;
|
||||||
|
|
||||||
public final class PatternTranslator extends AbstractTranslator {
|
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) {
|
public JsExpression translateIsCheck(@NotNull JsExpression subject, @NotNull KtTypeReference typeReference) {
|
||||||
KotlinType type = BindingUtils.getTypeByReference(bindingContext(), typeReference);
|
KotlinType type = BindingUtils.getTypeByReference(bindingContext(), typeReference);
|
||||||
JsExpression checkFunReference = getIsTypeCheckCallable(type);
|
JsExpression checkFunReference = getIsTypeCheckCallable(type);
|
||||||
JsInvocation isCheck = new JsInvocation(checkFunReference, subject);
|
return new JsInvocation(checkFunReference, subject);
|
||||||
|
|
||||||
if (isNullable(typeReference)) {
|
|
||||||
return addNullCheck(subject, isCheck);
|
|
||||||
}
|
|
||||||
|
|
||||||
return isCheck;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public JsExpression getIsTypeCheckCallable(@NotNull KotlinType type) {
|
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);
|
JsExpression builtinCheck = getIsTypeCheckCallableForBuiltin(type);
|
||||||
if (builtinCheck != null) return builtinCheck;
|
if (builtinCheck != null) return builtinCheck;
|
||||||
|
|
||||||
@@ -137,15 +141,6 @@ public final class PatternTranslator extends AbstractTranslator {
|
|||||||
return alias;
|
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
|
@NotNull
|
||||||
private JsNameRef getClassNameReference(@NotNull KotlinType type) {
|
private JsNameRef getClassNameReference(@NotNull KotlinType type) {
|
||||||
ClassDescriptor referencedClass = DescriptorUtils.getClassDescriptorForType(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 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.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) {
|
fun expandIsCalls(node: JsNode, context: TranslationContext) {
|
||||||
val visitor = object : JsVisitorWithContextImpl() {
|
TypeCheckRewritingVisitor(context).accept(node)
|
||||||
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()
|
|
||||||
|
|
||||||
val replacement = when (callee?.typeCheck) {
|
private class TypeCheckRewritingVisitor(private val context: TranslationContext) : JsVisitorWithContextImpl() {
|
||||||
TypeCheck.TYPEOF -> typeOfIs(instance!!, type as JsStringLiteral)
|
|
||||||
TypeCheck.INSTANCEOF -> context.namer().isInstanceOf(instance!!, type!!)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (replacement != null) {
|
private val scopes = Stack<JsScope>()
|
||||||
ctx.replaceMe(replacement)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
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});
|
Object.defineProperty(Kotlin.modules, id, {value: declaration});
|
||||||
};
|
};
|
||||||
|
|
||||||
function defineInlineFunction(tag, fun) {
|
Kotlin.defineInlineFunction = function(tag, fun) {
|
||||||
return fun;
|
return fun;
|
||||||
}
|
};
|
||||||
|
|
||||||
Kotlin.defineInlineFunction = defineInlineFunction;
|
Kotlin.isTypeOf = function(type) {
|
||||||
|
|
||||||
Kotlin.isTypeOf = defineInlineFunction('stdlib.kotlin.isTypeOf', function (type) {
|
|
||||||
return function (object) {
|
return function (object) {
|
||||||
return typeof object === type;
|
return typeof object === type;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
Kotlin.isInstanceOf = defineInlineFunction('stdlib.kotlin.isInstanceOf', function (klass) {
|
Kotlin.isInstanceOf = function (klass) {
|
||||||
return function (object) {
|
return function (object) {
|
||||||
return Kotlin.isType(object, klass);
|
return Kotlin.isType(object, klass);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
Kotlin.orNull = function (fn) {
|
||||||
|
return function (object) {
|
||||||
|
return object == null || fn(object);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Kotlin.kotlinModuleMetadata = function (abiVersion, moduleName, data) {
|
Kotlin.kotlinModuleMetadata = function (abiVersion, moduleName, data) {
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package foo
|
package foo
|
||||||
|
|
||||||
inline fun <reified T> isInstance(x: Any): Boolean =
|
inline fun <reified T> isInstance(x: Any?): Boolean =
|
||||||
x is T
|
x is T
|
||||||
@@ -7,5 +7,9 @@ fun box(): String {
|
|||||||
assertEquals(true, isInstance<Boolean>(false))
|
assertEquals(true, isInstance<Boolean>(false))
|
||||||
assertEquals(false, isInstance<Boolean>("true"))
|
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"
|
return "OK"
|
||||||
}
|
}
|
||||||
@@ -7,5 +7,9 @@ fun box(): String {
|
|||||||
assertEquals(false, isInstance<Char>(""))
|
assertEquals(false, isInstance<Char>(""))
|
||||||
assertEquals(false, isInstance<Char>("cc"))
|
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"
|
return "OK"
|
||||||
}
|
}
|
||||||
@@ -17,5 +17,9 @@ fun box(): String {
|
|||||||
assertEquals(true, isInstance<D>(D()))
|
assertEquals(true, isInstance<D>(D()))
|
||||||
assertEquals(true, isInstance<C>(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"
|
return "OK"
|
||||||
}
|
}
|
||||||
@@ -15,5 +15,9 @@ fun box(): String {
|
|||||||
assertEquals(true, isInstance<Number>(0.0))
|
assertEquals(true, isInstance<Number>(0.0))
|
||||||
assertEquals(false, isInstance<Number>("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"
|
return "OK"
|
||||||
}
|
}
|
||||||
@@ -6,5 +6,9 @@ fun box(): String {
|
|||||||
assertEquals(true, isInstance<String>(""))
|
assertEquals(true, isInstance<String>(""))
|
||||||
assertEquals(true, isInstance<String>("a"))
|
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"
|
return "OK"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user