diff --git a/compiler/testData/codegen/box/reflection/isInstance/isInstanceCastAndSafeCast.kt b/compiler/testData/codegen/box/reflection/isInstance/isInstanceCastAndSafeCast.kt new file mode 100644 index 00000000000..d9218a8cbf8 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/isInstance/isInstanceCastAndSafeCast.kt @@ -0,0 +1,58 @@ +// WITH_REFLECT + +import kotlin.reflect.KClass +import kotlin.reflect.cast +import kotlin.reflect.safeCast +import kotlin.test.* + +fun testInstance(value: Any?, klass: KClass<*>) { + assertTrue(klass.isInstance(value)) + assertEquals(value, klass.safeCast(value)) + assertEquals(value, klass.cast(value)) +} + +fun testNotInstance(value: Any?, klass: KClass<*>) { + assertFalse(klass.isInstance(value)) + assertNull(klass.safeCast(value)) + try { + klass.cast(value) + fail("Value should not be an instance of $klass: $value") + } + catch (e: Exception) { /* OK */ } +} + +fun box(): String { + testInstance(Any(), Any::class) + testInstance("", String::class) + testInstance("", Any::class) + testNotInstance(Any(), String::class) + testNotInstance(null, Any::class) + testNotInstance(null, String::class) + + testInstance(arrayOf(""), Array::class) + testInstance(arrayOf(""), Array::class) + testNotInstance(arrayOf(Any()), Array::class) + + testInstance(listOf(""), List::class) + testInstance(listOf(""), Collection::class) + // TODO: support MutableList::class (KT-11754) + // testNotInstance(listOf(""), MutableList::class) + + testInstance(42, Int::class) + testInstance(42, Int::class.javaPrimitiveType!!.kotlin) + testInstance(42, Int::class.javaObjectType!!.kotlin) + + testNotInstance(3.14, Int::class) + + // Function types + + testInstance(fun() {}, Function0::class) + testNotInstance(fun() {}, Function1::class) + testNotInstance(fun() {}, Function2::class) + + testNotInstance(::testInstance, Function0::class) + testNotInstance(::testInstance, Function1::class) + testInstance(::testInstance, Function2::class) + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index d7ec15f36d0..f75a5d3c633 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -12235,6 +12235,21 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { } } + @TestMetadata("compiler/testData/codegen/box/reflection/isInstance") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class IsInstance extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInIsInstance() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/isInstance"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("isInstanceCastAndSafeCast.kt") + public void testIsInstanceCastAndSafeCast() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/isInstance/isInstanceCastAndSafeCast.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/reflection/kClassInAnnotation") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/core/builtins/src/kotlin/reflect/KClass.kt b/core/builtins/src/kotlin/reflect/KClass.kt index 310944628b7..72ca333fb57 100644 --- a/core/builtins/src/kotlin/reflect/KClass.kt +++ b/core/builtins/src/kotlin/reflect/KClass.kt @@ -58,6 +58,11 @@ public interface KClass : KDeclarationContainer, KAnnotatedElement, KCl */ public val objectInstance: T? + /** + * Returns `true` if [value] is an instance of this class on a given platform. + */ + public fun isInstance(value: Any?): Boolean + /** * The list of type parameters of this class. This list does *not* include type parameters of outer classes. */ diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectClassUtil.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectClassUtil.kt index dc0ebde4b14..344e7e3e50e 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectClassUtil.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectClassUtil.kt @@ -27,13 +27,29 @@ val Class<*>.safeClassLoader: ClassLoader fun Class<*>.isEnumClassOrSpecializedEnumEntryClass(): Boolean = Enum::class.java.isAssignableFrom(this) -private val WRAPPER_TO_PRIMITIVE = listOf( - Boolean::class, Byte::class, Char::class, Double::class, Float::class, Int::class, Long::class, Short::class -).map { it.javaObjectType to it.javaPrimitiveType }.toMap() +private val PRIMITIVE_CLASSES = + listOf(Boolean::class, Byte::class, Char::class, Double::class, Float::class, Int::class, Long::class, Short::class) +private val WRAPPER_TO_PRIMITIVE = PRIMITIVE_CLASSES.map { it.javaObjectType to it.javaPrimitiveType }.toMap() +private val PRIMITIVE_TO_WRAPPER = PRIMITIVE_CLASSES.map { it.javaPrimitiveType to it.javaObjectType }.toMap() val Class<*>.primitiveByWrapper: Class<*>? get() = WRAPPER_TO_PRIMITIVE[this] +val Class<*>.wrapperByPrimitive: Class<*>? + get() = PRIMITIVE_TO_WRAPPER[this] + +private val FUNCTION_CLASSES = + listOf( + Function0::class.java, Function1::class.java, Function2::class.java, Function3::class.java, Function4::class.java, + Function5::class.java, Function6::class.java, Function7::class.java, Function8::class.java, Function9::class.java, + Function10::class.java, Function11::class.java, Function12::class.java, Function13::class.java, Function14::class.java, + Function15::class.java, Function16::class.java, Function17::class.java, Function18::class.java, Function19::class.java, + Function20::class.java, Function21::class.java, Function22::class.java + ).mapIndexed { i, clazz -> clazz to i }.toMap() + +val Class<*>.functionClassArity: Int? + get() = FUNCTION_CLASSES[this] + /** * NOTE: does not perform a Java -> Kotlin mapping. If this is not expected, consider using KClassImpl#classId instead */ diff --git a/core/reflection.jvm/src/kotlin/reflect/KClasses.kt b/core/reflection.jvm/src/kotlin/reflect/KClasses.kt index 34442668fc9..37c7a17ba77 100644 --- a/core/reflection.jvm/src/kotlin/reflect/KClasses.kt +++ b/core/reflection.jvm/src/kotlin/reflect/KClasses.kt @@ -236,3 +236,29 @@ fun KClass<*>.isSubclassOf(base: KClass<*>): Boolean = */ fun KClass<*>.isSuperclassOf(derived: KClass<*>): Boolean = derived.isSubclassOf(this) + + +/** + * Casts the given [value] to the class represented by this [KClass] object. + * Throws an exception if the value is `null` or if it is not an instance of this class. + * + * @see [KClass.isInstance] + * @see [KClass.safeCast] + */ +@Suppress("UNCHECKED_CAST") +fun KClass.cast(value: Any?): T { + if (!isInstance(value)) throw TypeCastException("Value cannot be cast to $qualifiedName") + return value as T +} + +/** + * Casts the given [value] to the class represented by this [KClass] object. + * Returns `null` if the value is `null` or if it is not an instance of this class. + * + * @see [KClass.isInstance] + * @see [KClass.cast] + */ +@Suppress("UNCHECKED_CAST") +fun KClass.safeCast(value: Any?): T? { + return if (isInstance(value)) value as T else null +} diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt index d25945151ac..5c3f5d8983c 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt @@ -20,6 +20,8 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.load.java.structure.reflect.functionClassArity +import org.jetbrains.kotlin.load.java.structure.reflect.wrapperByPrimitive import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.load.kotlin.reflect.ReflectKotlinClass import org.jetbrains.kotlin.name.ClassId @@ -27,6 +29,7 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies +import kotlin.jvm.internal.TypeIntrinsics import kotlin.reflect.* internal class KClassImpl(override val jClass: Class) : @@ -136,6 +139,14 @@ internal class KClassImpl(override val jClass: Class) : override val objectInstance: T? get() = objectInstance_() + override fun isInstance(value: Any?): Boolean { + // TODO: use Kotlin semantics for mutable/read-only collections once KT-11754 is supported (see TypeIntrinsics) + jClass.functionClassArity?.let { arity -> + return TypeIntrinsics.isFunctionOfArity(value, arity) + } + return (jClass.wrapperByPrimitive ?: jClass).isInstance(value) + } + override val typeParameters: List get() = descriptor.declaredTypeParameters.map(::KTypeParameterImpl) diff --git a/core/runtime.jvm/src/kotlin/jvm/internal/ClassReference.kt b/core/runtime.jvm/src/kotlin/jvm/internal/ClassReference.kt index 465eae2d823..79296e3ed38 100644 --- a/core/runtime.jvm/src/kotlin/jvm/internal/ClassReference.kt +++ b/core/runtime.jvm/src/kotlin/jvm/internal/ClassReference.kt @@ -40,6 +40,8 @@ class ClassReference(override val jClass: Class<*>) : KClass, ClassBasedDec override val objectInstance: Any? get() = error() + override fun isInstance(value: Any?): Boolean = error() + override val typeParameters: List get() = error()