diff --git a/compiler/testData/codegen/boxWithJava/reflection/nestedClasses/J.java b/compiler/testData/codegen/boxWithJava/reflection/nestedClasses/J.java new file mode 100644 index 00000000000..212f25c946d --- /dev/null +++ b/compiler/testData/codegen/boxWithJava/reflection/nestedClasses/J.java @@ -0,0 +1,10 @@ +public class J { + public class Inner {} + + public static class Nested {} + + private static class PrivateNested {} + + // This anonymous class should not appear in 'nestedClasses' + private final Object o = new Object() {}; +} diff --git a/compiler/testData/codegen/boxWithJava/reflection/nestedClasses/K.kt b/compiler/testData/codegen/boxWithJava/reflection/nestedClasses/K.kt new file mode 100644 index 00000000000..d3672290a74 --- /dev/null +++ b/compiler/testData/codegen/boxWithJava/reflection/nestedClasses/K.kt @@ -0,0 +1,7 @@ +import kotlin.test.assertEquals + +fun box(): String { + assertEquals(listOf("Inner", "Nested", "PrivateNested"), J::class.nestedClasses.map { it.simpleName!! }.toSortedList()) + + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/classes/nestedClasses.kt b/compiler/testData/codegen/boxWithStdlib/reflection/classes/nestedClasses.kt new file mode 100644 index 00000000000..0e70d36541e --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/classes/nestedClasses.kt @@ -0,0 +1,58 @@ +// FULL_JDK + +import kotlin.reflect.KClass +import kotlin.reflect.jvm.* +import kotlin.test.assertEquals + +class A { + companion object {} + inner class Inner + class Nested + private class PrivateNested +} + +fun nestedNames(c: KClass<*>) = c.nestedClasses.map { it.simpleName ?: throw AssertionError("Unnamed class: ${it.java}") }.toSortedList() + +fun box(): String { + // Kotlin class without nested classes + assertEquals(emptyList(), nestedNames(A.Inner::class)) + // Kotlin class with nested classes + assertEquals(listOf("Companion", "Inner", "Nested", "PrivateNested"), nestedNames(A::class)) + + // Java class without nested classes + assertEquals(emptyList(), nestedNames(Error::class)) + // Java class with nested classes + assertEquals(listOf("State", "UncaughtExceptionHandler"), nestedNames(Thread::class)) + + // Built-ins + assertEquals(emptyList(), nestedNames(Array::class)) + assertEquals(emptyList(), nestedNames(CharSequence::class)) + assertEquals(listOf("Companion"), nestedNames(String::class)) + + assertEquals(emptyList(), nestedNames(Collection::class)) + assertEquals(emptyList(), nestedNames(MutableCollection::class)) + assertEquals(emptyList(), nestedNames(List::class)) + assertEquals(emptyList(), nestedNames(MutableList::class)) + assertEquals(listOf("Entry"), nestedNames(Map::class)) + assertEquals(emptyList(), nestedNames(Map.Entry::class)) + assertEquals(emptyList(), nestedNames(MutableMap.MutableEntry::class)) + + // TODO: should be MutableEntry. Currently we do not distinguish between Map and MutableMap. + assertEquals(listOf("Entry"), nestedNames(MutableMap::class)) + + // Primitives + for (primitive in listOf(Byte::class, Double::class, Float::class, Int::class, Long::class, Short::class, Char::class)) { + assertEquals(listOf("Companion"), nestedNames(primitive)) + } + assertEquals(emptyList(), nestedNames(Boolean::class)) + + // Primitive arrays + for (primitiveArray in listOf( + ByteArray::class, DoubleArray::class, FloatArray::class, IntArray::class, + LongArray::class, ShortArray::class, CharArray::class, BooleanArray::class + )) { + assertEquals(emptyList(), nestedNames(primitiveArray)) + } + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithJavaCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithJavaCodegenTestGenerated.java index 00128d87b81..71739e7cbe9 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithJavaCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithJavaCodegenTestGenerated.java @@ -339,6 +339,12 @@ public class BlackBoxWithJavaCodegenTestGenerated extends AbstractBlackBoxCodege doTestWithJava(fileName); } + @TestMetadata("nestedClasses") + public void testNestedClasses() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/reflection/nestedClasses/"); + doTestWithJava(fileName); + } + @TestMetadata("noConflictOnKotlinGetterAndJavaField") public void testNoConflictOnKotlinGetterAndJavaField() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/reflection/noConflictOnKotlinGetterAndJavaField/"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java index 010ffa0c704..bb927520144 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java @@ -3030,6 +3030,12 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode doTestWithStdlib(fileName); } + @TestMetadata("nestedClasses.kt") + public void testNestedClasses() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/classes/nestedClasses.kt"); + doTestWithStdlib(fileName); + } + @TestMetadata("objectInstance.kt") public void testObjectInstance() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/classes/objectInstance.kt"); diff --git a/core/builtins/src/kotlin/reflect/KClass.kt b/core/builtins/src/kotlin/reflect/KClass.kt index d59938f460f..442b90fe799 100644 --- a/core/builtins/src/kotlin/reflect/KClass.kt +++ b/core/builtins/src/kotlin/reflect/KClass.kt @@ -49,6 +49,11 @@ public interface KClass : KDeclarationContainer, KAnnotatedElement { */ public val constructors: Collection> + /** + * All classes declared inside this class. This includes both inner and static nested classes. + */ + public val nestedClasses: Collection> + /** * The instance of the object declaration, or `null` if this class is not an object declaration. */ diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt index 833f330ae3a..7a9123ff0e0 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt @@ -40,7 +40,7 @@ private val TYPES_ELIGIBLE_FOR_SIMPLE_VISIT = setOf( ) public class ReflectKotlinClass private constructor( - private val klass: Class<*>, + internal val klass: Class<*>, private val classHeader: KotlinClassHeader ) : KotlinJvmBinaryClass { 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 044554ae6f3..d9416cb16ae 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt @@ -16,15 +16,20 @@ package kotlin.reflect.jvm.internal -import org.jetbrains.kotlin.descriptors.ClassKind -import org.jetbrains.kotlin.descriptors.ConstructorDescriptor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.descriptors.PropertyDescriptor +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.reflect.tryLoadClass +import org.jetbrains.kotlin.load.java.sources.JavaSourceElement +import org.jetbrains.kotlin.load.java.structure.reflect.ReflectJavaClass +import org.jetbrains.kotlin.load.java.structure.reflect.safeClassLoader +import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement +import org.jetbrains.kotlin.load.kotlin.reflect.ReflectKotlinClass import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.platform.JavaToKotlinClassMap +import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.scopes.JetScope import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies import kotlin.reflect.KCallable @@ -111,6 +116,27 @@ class KClassImpl(override val jClass: Class) : KDeclarationContainer KFunctionImpl(this, it) as KFunction } + override val nestedClasses: Collection> + get() = descriptor.unsubstitutedInnerClassesScope.getAllDescriptors().map { nestedClass -> + val source = (nestedClass as DeclarationDescriptorWithSource).source + when (source) { + is KotlinJvmBinarySourceElement -> + (source.binaryClass as ReflectKotlinClass).klass + is JavaSourceElement -> + (source.javaElement as ReflectJavaClass).element + SourceElement.NO_SOURCE -> { + // If neither a Kotlin class nor a Java class, it must be a built-in + val classId = JavaToKotlinClassMap.INSTANCE.mapKotlinToJava(DescriptorUtils.getFqName(nestedClass)) + ?: throw KotlinReflectionInternalError("Class with no source must be a built-in: $nestedClass") + val packageName = classId.packageFqName.asString() + val className = classId.relativeClassName.asString().replace('.', '$') + // All pseudo-classes like String.Companion must be accessible from the current class loader + javaClass.safeClassLoader.tryLoadClass("$packageName.$className") + } + else -> throw KotlinReflectionInternalError("Unsupported class: $nestedClass (source = $source)") + } + }.filterNotNull().map { KClassImpl(it) } + @suppress("UNCHECKED_CAST") override val objectInstance: T? by ReflectProperties.lazy { val descriptor = descriptor diff --git a/core/runtime.jvm/src/kotlin/jvm/internal/ClassReference.kt b/core/runtime.jvm/src/kotlin/jvm/internal/ClassReference.kt index 7b8da59b3e5..87409cce9e2 100644 --- a/core/runtime.jvm/src/kotlin/jvm/internal/ClassReference.kt +++ b/core/runtime.jvm/src/kotlin/jvm/internal/ClassReference.kt @@ -33,6 +33,9 @@ public class ClassReference(override val jClass: Class<*>) : KClass, ClassB override val constructors: Collection> get() = error() + override val nestedClasses: Collection> + get() = error() + override val annotations: List get() = error()