diff --git a/compiler/testData/codegen/box/reflection/types/classifierIsClass.kt b/compiler/testData/codegen/box/reflection/types/classifierIsClass.kt new file mode 100644 index 00000000000..b632f6bc3a7 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/classifierIsClass.kt @@ -0,0 +1,25 @@ +// WITH_REFLECT + +import kotlin.test.assertEquals + +class Outer { + class Nested + + inner class Inner +} + +fun outer(): Outer = null!! +fun nested(): Outer.Nested = null!! +fun inner(): Outer.Inner = null!! + +fun array(): Array = null!! + +fun box(): String { + assertEquals(Outer::class, ::outer.returnType.classifier) + assertEquals(Outer.Nested::class, ::nested.returnType.classifier) + assertEquals(Outer.Inner::class, ::inner.returnType.classifier) + + assertEquals(Array::class, ::array.returnType.classifier) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/types/classifierIsTypeParameter.kt b/compiler/testData/codegen/box/reflection/types/classifierIsTypeParameter.kt new file mode 100644 index 00000000000..7f2a6f5ecdb --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/classifierIsTypeParameter.kt @@ -0,0 +1,23 @@ +// WITH_REFLECT + +import kotlin.reflect.KTypeParameter +import kotlin.test.* + +class A { + fun foo(): T = null!! + fun bar(): Array? = null!! +} + +fun box(): String { + val t = A::class.members.single { it.name == "foo" }.returnType + assertFalse(t.isMarkedNullable) + val tc = t.classifier + if (tc !is KTypeParameter) fail(tc.toString()) + assertEquals("T", tc.name) + + val u = A::class.members.single { it.name == "bar" }.returnType + assertTrue(u.isMarkedNullable) + assertEquals(Array::class, u.classifier) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/types/classifiersOfBuiltInTypes.kt b/compiler/testData/codegen/box/reflection/types/classifiersOfBuiltInTypes.kt new file mode 100644 index 00000000000..4ad535dde9b --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/classifiersOfBuiltInTypes.kt @@ -0,0 +1,112 @@ +// WITH_REFLECT + +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.test.assertEquals + +fun primitives( + p01: Boolean, + p02: Byte, + p03: Char, + p04: Double, + p05: Float, + p06: Int, + p07: Long, + p08: Short +) {} + +fun nullablePrimitives( + p01: Boolean?, + p02: Byte?, + p03: Char?, + p04: Double?, + p05: Float?, + p06: Int?, + p07: Long?, + p08: Short? +) {} + +fun primitiveArrays( + p01: BooleanArray, + p02: ByteArray, + p03: CharArray, + p04: DoubleArray, + p05: FloatArray, + p06: IntArray, + p07: LongArray, + p08: ShortArray +) {} + +fun others( + p1: Array<*>, + p2: Array, + p3: Array?>, + p4: List<*>, + p5: List?, + p6: Map.Entry, + p7: Unit?, + p8: String, + p9: Nothing +) {} + +inline fun wrapper(): KClass = T::class + +fun check(f: KFunction<*>, vararg expected: KClass<*>) { + val actual = f.parameters.map { it.type.classifier as KClass<*> } + for ((e, a) in expected.toList().zip(actual)) { + assertEquals(e, a, "$e (${e.java}) != $a (${a.java})") + } +} + +fun box(): String { + check( + ::primitives, + Boolean::class, + Byte::class, + Char::class, + Double::class, + Float::class, + Int::class, + Long::class, + Short::class + ) + + check( + ::nullablePrimitives, + wrapper(), + wrapper(), + wrapper(), + wrapper(), + wrapper(), + wrapper(), + wrapper(), + wrapper() + ) + + check( + ::primitiveArrays, + BooleanArray::class, + ByteArray::class, + CharArray::class, + DoubleArray::class, + FloatArray::class, + IntArray::class, + LongArray::class, + ShortArray::class + ) + + check( + ::others, + Array::class, + Array::class, + Array?>::class, + List::class, + List::class, + Map.Entry::class, + Unit::class, + String::class, + Nothing::class + ) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/types/platformTypeClassifier.kt b/compiler/testData/codegen/box/reflection/types/platformTypeClassifier.kt new file mode 100644 index 00000000000..69b9f44556f --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/platformTypeClassifier.kt @@ -0,0 +1,37 @@ +// WITH_REFLECT +// FILE: J.java + +import java.util.List; + +public class J { + public static String string() { + return ""; + } + + public static List list() { + return null; + } + + public static int primitiveInt() { + return 0; + } + + public static Integer wrapperInt() { + return 0; + } +} + +// FILE: K.kt + +import kotlin.reflect.KClass +import kotlin.test.assertEquals + +fun box(): String { + assertEquals(String::class, J::string.returnType.classifier) + assertEquals(List::class, J::list.returnType.classifier) + + assertEquals(Int::class.javaPrimitiveType!!, (J::primitiveInt.returnType.classifier as KClass<*>).java) + assertEquals(Int::class.javaObjectType, (J::wrapperInt.returnType.classifier as KClass<*>).java) + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index a6af9174904..fbbf759c79a 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -12933,6 +12933,30 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/types"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("classifierIsClass.kt") + public void testClassifierIsClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/classifierIsClass.kt"); + doTest(fileName); + } + + @TestMetadata("classifierIsTypeParameter.kt") + public void testClassifierIsTypeParameter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/classifierIsTypeParameter.kt"); + doTest(fileName); + } + + @TestMetadata("classifiersOfBuiltInTypes.kt") + public void testClassifiersOfBuiltInTypes() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/classifiersOfBuiltInTypes.kt"); + doTest(fileName); + } + + @TestMetadata("platformTypeClassifier.kt") + public void testPlatformTypeClassifier() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/platformTypeClassifier.kt"); + doTest(fileName); + } + @TestMetadata("platformTypeNotEqualToKotlinType.kt") public void testPlatformTypeNotEqualToKotlinType() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/platformTypeNotEqualToKotlinType.kt"); diff --git a/core/builtins/src/kotlin/reflect/KType.kt b/core/builtins/src/kotlin/reflect/KType.kt index bf927dba8ff..54053464af4 100644 --- a/core/builtins/src/kotlin/reflect/KType.kt +++ b/core/builtins/src/kotlin/reflect/KType.kt @@ -21,6 +21,14 @@ package kotlin.reflect * or a type parameter of some declaration, plus nullability. */ public interface KType { + /** + * The declaration of the classifier used in this type. + * For example, in the type `List` the classifier would be the [KClass] instance for [List]. + * + * Returns `null` if this type is not denotable in Kotlin, for example if it is an intersection type. + */ + public val classifier: KClassifier? + /** * `true` if this type was marked nullable in the source code. * 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 1e682bec838..dc0ebde4b14 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,6 +27,13 @@ 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() + +val Class<*>.primitiveByWrapper: Class<*>? + get() = WRAPPER_TO_PRIMITIVE[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/jvm/internal/KTypeImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeImpl.kt index 960e756d34a..cdd3ef30f70 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeImpl.kt @@ -16,9 +16,18 @@ package kotlin.reflect.jvm.internal +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import org.jetbrains.kotlin.load.java.structure.reflect.createArrayType +import org.jetbrains.kotlin.load.java.structure.reflect.primitiveByWrapper import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeUtils import java.lang.reflect.Type +import kotlin.reflect.KClass +import kotlin.reflect.KClassifier import kotlin.reflect.KType +import kotlin.reflect.KTypeParameter internal class KTypeImpl( val type: KotlinType, @@ -26,11 +35,49 @@ internal class KTypeImpl( ) : KType { internal val javaType: Type by ReflectProperties.lazySoft(computeJavaType) + override val classifier: KClassifier? + get() = convert(type) + + private fun convert(type: KotlinType): KClassifier? { + val descriptor = type.constructor.declarationDescriptor + when (descriptor) { + is ClassDescriptor -> { + val jClass = descriptor.toJavaClass() ?: return null + if (jClass.isArray) { + // There may be no argument if it's a primitive array (such as IntArray) + val argument = type.arguments.singleOrNull()?.type ?: return KClassImpl(jClass) + + val elementClassifier = convert(argument) + val elementType = when (elementClassifier) { + is KClass<*> -> elementClassifier + is KTypeParameter -> { + // For arrays of type parameters (`Array`) we return the KClass representing `Array` + // since there's no other sensible option + // TODO: return `Array` + Any::class + } + else -> TODO("Arrays of type alias classifiers are not yet supported") + } + return KClassImpl(elementType.java.createArrayType()) + } + + if (!TypeUtils.isNullableType(type)) { + return KClassImpl(jClass.primitiveByWrapper ?: jClass) + } + + return KClassImpl(jClass) + } + is TypeParameterDescriptor -> return KTypeParameterImpl(descriptor) + is TypeAliasDescriptor -> TODO("Type alias classifiers are not yet supported") + else -> return null + } + } + override val isMarkedNullable: Boolean get() = type.isMarkedNullable override fun equals(other: Any?) = - other is KTypeImpl && type.equals(other.type) + other is KTypeImpl && type == other.type override fun hashCode() = type.hashCode()