From 4cd252d9d5524c5e334363c3f36348d512e7c61e Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Tue, 12 Jul 2016 18:42:46 +0300 Subject: [PATCH] Reflection: add KClass.{supertypes,superclasses,allSupertypes,allSuperclasses} Some things are not implemented yet for allSupertypes, such as KType->Type mapping of types and generic substitution of built-in types --- .../reflection/mapping/types/supertypes.kt | 31 +++++++++++ .../supertypes/builtInClassSupertypes.kt | 42 +++++++++++++++ .../supertypes/genericSubstitution.kt | 25 +++++++++ .../reflection/supertypes/simpleSupertypes.kt | 49 +++++++++++++++++ .../codegen/BlackBoxCodegenTestGenerated.java | 33 ++++++++++++ core/builtins/src/kotlin/reflect/KClass.kt | 5 ++ .../src/kotlin/reflect/KClasses.kt | 53 +++++++++++++++++++ .../kotlin/reflect/jvm/internal/KClassImpl.kt | 20 +++++++ .../src/kotlin/jvm/internal/ClassReference.kt | 8 +-- 9 files changed, 262 insertions(+), 4 deletions(-) create mode 100644 compiler/testData/codegen/box/reflection/mapping/types/supertypes.kt create mode 100644 compiler/testData/codegen/box/reflection/supertypes/builtInClassSupertypes.kt create mode 100644 compiler/testData/codegen/box/reflection/supertypes/genericSubstitution.kt create mode 100644 compiler/testData/codegen/box/reflection/supertypes/simpleSupertypes.kt diff --git a/compiler/testData/codegen/box/reflection/mapping/types/supertypes.kt b/compiler/testData/codegen/box/reflection/mapping/types/supertypes.kt new file mode 100644 index 00000000000..50bea1f050b --- /dev/null +++ b/compiler/testData/codegen/box/reflection/mapping/types/supertypes.kt @@ -0,0 +1,31 @@ +// WITH_REFLECT +// FULL_JDK + +import java.lang.reflect.ParameterizedType +import java.lang.reflect.TypeVariable +import kotlin.reflect.jvm.javaType +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.fail + +open class Klass +interface Interface +interface Interface2 + +class A : Interface, Klass(), Interface2 + +fun box(): String { + val (i, k, i2) = A::class.supertypes.map { it.javaType } + + i as? ParameterizedType ?: fail("Not a parameterized type: $i") + assertEquals(Interface::class.java, i.rawType) + val args = i.actualTypeArguments + assertEquals(String::class.java, args[0], "Not String: ${args[0]}") + assertTrue(args[1].let { it is TypeVariable<*> && it.name == "Z" && it.genericDeclaration == A::class.java }, "Not Z: ${args[1]}") + + assertEquals(Klass::class.java, k) + + assertEquals(Interface2::class.java, i2) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/supertypes/builtInClassSupertypes.kt b/compiler/testData/codegen/box/reflection/supertypes/builtInClassSupertypes.kt new file mode 100644 index 00000000000..5150914adea --- /dev/null +++ b/compiler/testData/codegen/box/reflection/supertypes/builtInClassSupertypes.kt @@ -0,0 +1,42 @@ +// WITH_REFLECT + +import java.io.Serializable +import kotlin.reflect.* +import kotlin.test.assertEquals + +inline fun check(vararg callables: KCallable<*>) { + val types = callables.map { it.returnType } + assertEquals(types, T::class.supertypes) + assertEquals(types.map { it.classifier as KClass<*> }, T::class.superclasses) +} + +inline fun checkAll(vararg callables: KCallable<*>) { + val types = callables.map { it.returnType } + // Calling toSet because the order of returned types/classes is not specified + assertEquals(types.toSet(), T::class.allSupertypes.toSet()) + assertEquals(types.map { it.classifier as KClass<*> }.toSet(), T::class.allSuperclasses.toSet()) +} + +fun comparableOfString(): Comparable = null!! +fun charSequence(): CharSequence = null!! +fun serializable(): Serializable = null!! +fun any(): Any = null!! +fun number(): Number = null!! +fun comparableOfInt(): Comparable = null!! +fun cloneable(): Cloneable = null!! + +fun box(): String { + check() + checkAll() + + check(::comparableOfString, ::charSequence, ::serializable) + checkAll(::comparableOfString, ::charSequence, ::serializable, ::any) + + check(::number, ::comparableOfInt, ::serializable) + checkAll(::number, ::comparableOfInt, ::serializable, ::any) + + check>(::cloneable, ::serializable) + checkAll>(::cloneable, ::serializable, ::any) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/supertypes/genericSubstitution.kt b/compiler/testData/codegen/box/reflection/supertypes/genericSubstitution.kt new file mode 100644 index 00000000000..62b817119b1 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/supertypes/genericSubstitution.kt @@ -0,0 +1,25 @@ +// WITH_REFLECT + +import kotlin.reflect.allSupertypes +import kotlin.test.assertEquals + +interface A +interface B : A +interface C : B +interface D : C + +interface StringList : List + +fun box(): String { + assertEquals( + listOf(String::class, Int::class), + D::class.allSupertypes.single { it.classifier == A::class }.arguments.map { it.type!!.classifier } + ) + + val collectionType = StringList::class.allSupertypes.single { it.classifier == Collection::class } + val arg = collectionType.arguments.single().type!! + // TODO: this does not work currently because for some reason two different instances of TypeParameterDescriptor are created for List + // assertEquals(String::class, arg.classifier) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/supertypes/simpleSupertypes.kt b/compiler/testData/codegen/box/reflection/supertypes/simpleSupertypes.kt new file mode 100644 index 00000000000..c5ffe833636 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/supertypes/simpleSupertypes.kt @@ -0,0 +1,49 @@ +// WITH_REFLECT + +import kotlin.reflect.* +import kotlin.test.assertEquals + +open class Simple +class OneClass : Simple() + +interface Interface +interface Interface2 +class ClassAndTwoInterfaces : Interface, Simple(), Interface2 + +fun any(): Any = null!! +fun simple(): Simple = null!! +fun interface_(): Interface = null!! +fun interface2(): Interface2 = null!! + +fun box(): String { + with(Simple::class) { + assertEquals(listOf(::any.returnType), supertypes) + assertEquals(listOf(Any::class), superclasses) + // Calling toSet because the order of returned types/classes is not specified + assertEquals(setOf(::any.returnType), allSupertypes.toSet()) + assertEquals(setOf(Any::class), allSuperclasses.toSet()) + } + + with (OneClass::class) { + assertEquals(listOf(::simple.returnType), supertypes) + assertEquals(listOf(Simple::class), superclasses) + assertEquals(setOf(::simple.returnType, ::any.returnType), allSupertypes.toSet()) + assertEquals(setOf(Simple::class, Any::class), allSuperclasses.toSet()) + } + + with (Interface::class) { + assertEquals(listOf(::any.returnType), supertypes) + assertEquals(listOf(Any::class), superclasses) + assertEquals(setOf(::any.returnType), allSupertypes.toSet()) + assertEquals(setOf(Any::class), allSuperclasses.toSet()) + } + + with (ClassAndTwoInterfaces::class) { + assertEquals(listOf(::interface_.returnType, ::simple.returnType, ::interface2.returnType), supertypes) + assertEquals(listOf(Interface::class, Simple::class, Interface2::class), superclasses) + assertEquals(setOf(::interface_.returnType, ::simple.returnType, ::interface2.returnType, ::any.returnType), allSupertypes.toSet()) + assertEquals(setOf(Interface::class, Simple::class, Interface2::class, Any::class), allSuperclasses.toSet()) + } + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 7d00e44ed24..ed6f54dc820 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -12473,6 +12473,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("supertypes.kt") + public void testSupertypes() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/supertypes.kt"); + doTest(fileName); + } + @TestMetadata("topLevelFunctions.kt") public void testTopLevelFunctions() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/topLevelFunctions.kt"); @@ -12967,6 +12973,33 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { } } + @TestMetadata("compiler/testData/codegen/box/reflection/supertypes") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Supertypes extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInSupertypes() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/supertypes"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("builtInClassSupertypes.kt") + public void testBuiltInClassSupertypes() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/supertypes/builtInClassSupertypes.kt"); + doTest(fileName); + } + + @TestMetadata("genericSubstitution.kt") + public void testGenericSubstitution() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/supertypes/genericSubstitution.kt"); + doTest(fileName); + } + + @TestMetadata("simpleSupertypes.kt") + public void testSimpleSupertypes() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/supertypes/simpleSupertypes.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/reflection/typeParameters") @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 05112072240..310944628b7 100644 --- a/core/builtins/src/kotlin/reflect/KClass.kt +++ b/core/builtins/src/kotlin/reflect/KClass.kt @@ -63,6 +63,11 @@ public interface KClass : KDeclarationContainer, KAnnotatedElement, KCl */ public val typeParameters: List + /** + * The list of immediate supertypes of this class, in the order they are listed in the source code. + */ + public val supertypes: List + /** * Returns `true` if [other] is a [KClass] instance representing the same class on a given platform. * diff --git a/core/reflection.jvm/src/kotlin/reflect/KClasses.kt b/core/reflection.jvm/src/kotlin/reflect/KClasses.kt index abeaa27b671..fbac678e23e 100644 --- a/core/reflection.jvm/src/kotlin/reflect/KClasses.kt +++ b/core/reflection.jvm/src/kotlin/reflect/KClasses.kt @@ -18,6 +18,9 @@ package kotlin.reflect import org.jetbrains.kotlin.descriptors.ConstructorDescriptor +import org.jetbrains.kotlin.types.TypeSubstitutor +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.utils.DFS import kotlin.reflect.jvm.internal.KClassImpl import kotlin.reflect.jvm.internal.KFunctionImpl import kotlin.reflect.jvm.internal.KTypeImpl @@ -170,3 +173,53 @@ val KClass.declaredMemberExtensionProperties: Collection>() .toList() + + +/** + * Immediate superclasses of this class, in the order they are listed in the source code. + * Includes superclasses and superinterfaces of the class, but does not include the class itself. + */ +val KClass<*>.superclasses: List> + get() = supertypes.mapNotNull { it.classifier as? KClass<*> } + +/** + * All supertypes of this class, including indirect ones, in no particular order. + * There is not more than one type in the returned collection that has any given classifier. + */ +val KClass<*>.allSupertypes: Collection + get() = DFS.dfs( + supertypes, + DFS.Neighbors { current -> + val klass = current.classifier as? KClass<*> ?: throw KotlinReflectionInternalError("Supertype not a class: $current") + val supertypes = klass.supertypes + val typeArguments = current.arguments + if (typeArguments.isEmpty()) supertypes + else TypeSubstitutor.create((current as KTypeImpl).type).let { substitutor -> + supertypes.map { supertype -> + val substituted = substitutor.substitute((supertype as KTypeImpl).type, Variance.INVARIANT) + ?: throw KotlinReflectionInternalError("Type substitution failed: $supertype ($current)") + KTypeImpl(substituted) { + // TODO + TODO("Java type for supertype") + } + } + } + }, + DFS.VisitedWithSet(), + object : DFS.NodeHandlerWithListResult() { + override fun beforeChildren(current: KType): Boolean { + result.add(current) + return true + } + } + ) + +/** + * All superclasses of this class, including indirect ones, in no particular order. + * Includes superclasses and superinterfaces of the class, but does not include the class itself. + * The returned collection does not contain more than one instance of any given class. + */ +val KClass<*>.allSuperclasses: Collection> + get() = allSupertypes.map { supertype -> + supertype.classifier as? KClass<*> ?: throw KotlinReflectionInternalError("Supertype not a class: $supertype") + } 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 c69f85e21d4..d25945151ac 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt @@ -139,6 +139,26 @@ internal class KClassImpl(override val jClass: Class) : override val typeParameters: List get() = descriptor.declaredTypeParameters.map(::KTypeParameterImpl) + override val supertypes: List + get() = descriptor.typeConstructor.supertypes.map { kotlinType -> + KTypeImpl(kotlinType) { + val superClass = kotlinType.constructor.declarationDescriptor + if (superClass !is ClassDescriptor) throw KotlinReflectionInternalError("Supertype not a class: $superClass") + + val superJavaClass = superClass.toJavaClass() + ?: throw KotlinReflectionInternalError("Unsupported superclass of $this: $superClass") + + if (jClass.superclass == superJavaClass) { + jClass.genericSuperclass + } + else { + val index = jClass.interfaces.indexOf(superJavaClass) + if (index < 0) throw KotlinReflectionInternalError("No superclass of $this in Java reflection for $superClass") + jClass.genericInterfaces[index] + } + } + } + override fun equals(other: Any?): Boolean = other is KClassImpl<*> && jClass == other.jClass diff --git a/core/runtime.jvm/src/kotlin/jvm/internal/ClassReference.kt b/core/runtime.jvm/src/kotlin/jvm/internal/ClassReference.kt index cfb5cc0de6d..465eae2d823 100644 --- a/core/runtime.jvm/src/kotlin/jvm/internal/ClassReference.kt +++ b/core/runtime.jvm/src/kotlin/jvm/internal/ClassReference.kt @@ -16,10 +16,7 @@ package kotlin.jvm.internal -import kotlin.reflect.KCallable -import kotlin.reflect.KClass -import kotlin.reflect.KFunction -import kotlin.reflect.KTypeParameter +import kotlin.reflect.* class ClassReference(override val jClass: Class<*>) : KClass, ClassBasedDeclarationContainer { override val simpleName: String? @@ -46,6 +43,9 @@ class ClassReference(override val jClass: Class<*>) : KClass, ClassBasedDec override val typeParameters: List get() = error() + override val supertypes: List + get() = error() + private fun error(): Nothing = throw KotlinReflectionNotSupportedError() override fun equals(other: Any?) =