diff --git a/compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaClassViaKotlinReflection.kt b/compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaClassViaKotlinReflection.kt new file mode 100644 index 00000000000..9c4dd79674d --- /dev/null +++ b/compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaClassViaKotlinReflection.kt @@ -0,0 +1,21 @@ +// WITH_REFLECT +// FILE: Base.java +public sealed class Base permits O, K {} + +// FILE: O.java +public final class O extends Base {} + +// FILE: K.java +public non-sealed class K extends Base {} + +// FILE: main.kt + +fun box(): String { + val klass = Base::class + if (!klass.isSealed) return "Error: Base is not sealed" + if (klass.isAbstract) return "Error: Base is not abstract" + return klass.sealedSubclasses + .joinToString("") { it.simpleName ?: "_No name provided_" } + .takeIf { it.isNotBlank() } + ?: "_No sealed subclasses found_" +} diff --git a/compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaInterfaceViaKotlinReflection.kt b/compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaInterfaceViaKotlinReflection.kt new file mode 100644 index 00000000000..a8e92e00acc --- /dev/null +++ b/compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaInterfaceViaKotlinReflection.kt @@ -0,0 +1,21 @@ +// WITH_REFLECT +// FILE: Base.java +public sealed interface Base permits O, K {} + +// FILE: O.java +public non-sealed interface O extends Base {} + +// FILE: K.java +public non-sealed interface K extends Base {} + +// FILE: main.kt + +fun box(): String { + val klass = Base::class + if (!klass.isSealed) return "Error: Base is not sealed" + if (klass.isAbstract) return "Error: Base is abstract" + return klass.sealedSubclasses + .joinToString("") { it.simpleName ?: "_No name provided_" } + .takeIf { it.isNotBlank() } + ?: "_No sealed subclasses found_" +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxModernJdkCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxModernJdkCodegenTestGenerated.java index 9680cafe029..8968c350e3d 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxModernJdkCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxModernJdkCodegenTestGenerated.java @@ -146,6 +146,18 @@ public class BlackBoxModernJdkCodegenTestGenerated extends AbstractBlackBoxCodeg public void testSealedJavaClassViaJavaReflection() throws Exception { runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaClassViaJavaReflection.kt"); } + + @Test + @TestMetadata("sealedJavaClassViaKotlinReflection.kt") + public void testSealedJavaClassViaKotlinReflection() throws Exception { + runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaClassViaKotlinReflection.kt"); + } + + @Test + @TestMetadata("sealedJavaInterfaceViaKotlinReflection.kt") + public void testSealedJavaInterfaceViaKotlinReflection() throws Exception { + runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaInterfaceViaKotlinReflection.kt"); + } } @Nested diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxModernJdkCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxModernJdkCodegenTestGenerated.java index 1cbdac21a17..7d0d20484c2 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxModernJdkCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxModernJdkCodegenTestGenerated.java @@ -146,6 +146,18 @@ public class IrBlackBoxModernJdkCodegenTestGenerated extends AbstractIrBlackBoxC public void testSealedJavaClassViaJavaReflection() throws Exception { runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaClassViaJavaReflection.kt"); } + + @Test + @TestMetadata("sealedJavaClassViaKotlinReflection.kt") + public void testSealedJavaClassViaKotlinReflection() throws Exception { + runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaClassViaKotlinReflection.kt"); + } + + @Test + @TestMetadata("sealedJavaInterfaceViaKotlinReflection.kt") + public void testSealedJavaInterfaceViaKotlinReflection() throws Exception { + runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaInterfaceViaKotlinReflection.kt"); + } } @Nested diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/descriptors/runtime/structure/ReflectJavaClass.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/descriptors/runtime/structure/ReflectJavaClass.kt index a8606dfb2ac..923ebc950f0 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/descriptors/runtime/structure/ReflectJavaClass.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/descriptors/runtime/structure/ReflectJavaClass.kt @@ -123,10 +123,12 @@ class ReflectJavaClass( get() = false override val isSealed: Boolean - get() = false + get() = Java16SealedRecordLoader.loadIsSealed(klass) ?: false override val permittedTypes: Collection - get() = emptyList() + get() = Java16SealedRecordLoader.loadGetPermittedSubclasses(klass) + ?.map(::ReflectJavaClassifierType) + ?: emptyList() override fun equals(other: Any?) = other is ReflectJavaClass && klass == other.klass @@ -134,3 +136,47 @@ class ReflectJavaClass( override fun toString() = this::class.java.name + ": " + klass } + +private object Java16SealedRecordLoader { + class Cache( + val isSealed: Method?, + val getPermittedSubclasses: Method?, + ) + + private var _cache: Cache? = null + + private fun buildCache(): Cache { + val clazz = Class::class.java + + return try { + Cache( + clazz.getMethod("isSealed"), + clazz.getMethod("getPermittedSubclasses"), + ) + } catch (e: NoSuchMethodException) { + Cache(null, null) + } + } + + private fun initCache(): Cache { + var cache = this._cache + if (cache == null) { + cache = buildCache() + this._cache = cache + } + return cache + } + + fun loadIsSealed(clazz: Class<*>): Boolean? { + val cache = initCache() + val isSealed = cache.isSealed ?: return null + return isSealed.invoke(clazz) as Boolean + } + + fun loadGetPermittedSubclasses(clazz: Class<*>): Array>? { + val cache = initCache() + val getPermittedSubclasses = cache.getPermittedSubclasses ?: return null + @Suppress("UNCHECKED_CAST") + return getPermittedSubclasses.invoke(clazz) as Array> + } +}