WIP
diff --git a/compiler/testData/codegen/boxJvm/reflection/annotations/subclassOptInRequired.kt b/compiler/testData/codegen/boxJvm/reflection/annotations/subclassOptInRequired.kt index 2585084..3d66b01 100644 --- a/compiler/testData/codegen/boxJvm/reflection/annotations/subclassOptInRequired.kt +++ b/compiler/testData/codegen/boxJvm/reflection/annotations/subclassOptInRequired.kt
@@ -30,11 +30,11 @@ // @SubclassOptInRequired should be findable via findAnnotation val ann = ExperimentalBase::class.findAnnotation<SubclassOptInRequired>() assertNotNull(ann, "@SubclassOptInRequired not found on ExperimentalBase") - assertEquals(ExperimentalFeature::class, ann.markerClass) + assertEquals<Any>(ExperimentalFeature::class, ann.markerClass) // TODO: Interesting. Type inference fails here without <Any> but FE shows that it is redundant val ann2 = AnotherBase::class.findAnnotation<SubclassOptInRequired>() assertNotNull(ann2) - assertEquals(AnotherExperimentalApi::class, ann2.markerClass) + assertEquals<Any>(AnotherExperimentalApi::class, ann2.markerClass) // hasAnnotation should return true assertTrue(ExperimentalBase::class.hasAnnotation<SubclassOptInRequired>()) @@ -48,7 +48,7 @@ val annotations = ExperimentalBase::class.annotations val found = annotations.filterIsInstance<SubclassOptInRequired>() assertEquals(1, found.size) - assertEquals(ExperimentalFeature::class, found.single().markerClass) + assertEquals<Any>(ExperimentalFeature::class, found.single().markerClass) // Class modifiers are unaffected by the annotation assertTrue(ExperimentalBase::class.isAbstract)
diff --git a/compiler/testData/codegen/boxJvm/reflection/builtins/arrayMemberPropertiesAndFunctions.kt b/compiler/testData/codegen/boxJvm/reflection/builtins/arrayMemberPropertiesAndFunctions.kt new file mode 100644 index 0000000..3da5e9a --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/builtins/arrayMemberPropertiesAndFunctions.kt
@@ -0,0 +1,30 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// Tests that array types expose their 'size' memberProperty and inherited +// Object methods via memberFunctions. + +import kotlin.reflect.full.* +import kotlin.test.* + +fun box(): String { + val arrayClasses = listOf( + Array::class, IntArray::class, LongArray::class, ShortArray::class, + ByteArray::class, FloatArray::class, DoubleArray::class, + BooleanArray::class, CharArray::class + ) + + for (klass in arrayClasses) { + // memberProperties must include 'size' + assertTrue(klass.memberProperties.any { it.name == "size" }, + "${klass.simpleName}::class.memberProperties must include 'size'") + + // memberFunctions must include the inherited Object methods + val fnNames = klass.memberFunctions.map { it.name }.toSet() + for (name in listOf("equals", "hashCode", "toString")) { + assertTrue(name in fnNames, + "${klass.simpleName}::class.memberFunctions must include '$name'") + } + } + + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/builtins/primitiveAndBooleanMemberFunctions.kt b/compiler/testData/codegen/boxJvm/reflection/builtins/primitiveAndBooleanMemberFunctions.kt new file mode 100644 index 0000000..721ef22 --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/builtins/primitiveAndBooleanMemberFunctions.kt
@@ -0,0 +1,30 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// Tests that Boolean and Char expose their full set of member functions via reflection. + +import kotlin.reflect.full.* +import kotlin.test.* + +fun box(): String { + // Boolean has compareTo, and, or, xor, not, toInt, equals, hashCode, toString + val boolFns = Boolean::class.memberFunctions.map { it.name }.toSet() + assertTrue("compareTo" in boolFns, "Boolean must have compareTo()") + assertTrue("and" in boolFns, "Boolean must have and()") + assertTrue("or" in boolFns, "Boolean must have or()") + assertTrue("not" in boolFns, "Boolean must have not()") + + // Char has compareTo, plus(Int), minus(Char/Int), toInt/toLong/etc., and range operators + val charFns = Char::class.memberFunctions.map { it.name }.toSet() + assertTrue("compareTo" in charFns, "Char must have compareTo()") + assertTrue("plus" in charFns, "Char must have plus()") + assertTrue("minus" in charFns, "Char must have minus()") + + // Both must include Object methods + for ((klass, fns) in listOf(Boolean::class to boolFns, Char::class to charFns)) { + assertTrue("equals" in fns, "${klass.simpleName} must have equals()") + assertTrue("hashCode" in fns, "${klass.simpleName} must have hashCode()") + assertTrue("toString" in fns, "${klass.simpleName} must have toString()") + } + + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/builtins/primitiveConstructorAnnotations.kt b/compiler/testData/codegen/boxJvm/reflection/builtins/primitiveConstructorAnnotations.kt new file mode 100644 index 0000000..6e30079 --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/builtins/primitiveConstructorAnnotations.kt
@@ -0,0 +1,18 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// Tests that annotations on primitive-type constructors are accessible and empty. + +import kotlin.reflect.full.* +import kotlin.test.* + +fun box(): String { + for (klass in listOf( + Int::class, Long::class, Short::class, Byte::class, + Float::class, Double::class, Boolean::class, Char::class + )) { + val ctor = klass.constructors.single() + assertEquals(emptyList(), ctor.annotations, + "${klass.simpleName} constructor should have no annotations") + } + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/call/javaAnnotationElementCallability.kt b/compiler/testData/codegen/boxJvm/reflection/call/javaAnnotationElementCallability.kt new file mode 100644 index 0000000..2530e8a --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/call/javaAnnotationElementCallability.kt
@@ -0,0 +1,58 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// FILE: CallableAnnotation.java +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface CallableAnnotation { + String name() default "default-name"; + int version() default 1; + boolean enabled() default true; +} + +// FILE: box.kt +// Tests that Java annotation element accessor properties are callable via reflection, +// including via KProperty0.getter.call() and KProperty0.call(). + +import kotlin.reflect.* +import kotlin.reflect.full.* +import kotlin.reflect.jvm.* +import kotlin.test.* + +fun box(): String { + val annClass = CallableAnnotation::class + + // Static properties represent annotation elements + val staticProps = annClass.staticProperties + assertTrue(staticProps.isNotEmpty(), + "CallableAnnotation must have staticProperties for its elements, got: ${staticProps.map { it.name }}") + + val nameProp = staticProps.firstOrNull { it.name == "name" } + val versionProp = staticProps.firstOrNull { it.name == "version" } + val enabledProp = staticProps.firstOrNull { it.name == "enabled" } + + // Check the properties exist + assertNotNull(nameProp, "CallableAnnotation must have 'name' static property") + assertNotNull(versionProp, "CallableAnnotation must have 'version' static property") + assertNotNull(enabledProp, "CallableAnnotation must have 'enabled' static property") + + // Call the default value accessors — they must not throw + val nameDefault = runCatching { nameProp.call() } + .getOrElse { return "Fail: calling 'name' threw ${it::class.simpleName}: ${it.message}" } + val versionDefault = runCatching { versionProp.call() } + .getOrElse { return "Fail: calling 'version' threw ${it::class.simpleName}: ${it.message}" } + val enabledDefault = runCatching { enabledProp.call() } + .getOrElse { return "Fail: calling 'enabled' threw ${it::class.simpleName}: ${it.message}" } + + assertEquals("default-name", nameDefault, "name default should be 'default-name'") + assertEquals(1, versionDefault, "version default should be 1") + assertEquals(true, enabledDefault, "enabled default should be true") + + // Also verify via getter.call() + val nameViaGetter = runCatching { nameProp.getter.call() } + .getOrElse { return "Fail: name getter.call() threw ${it::class.simpleName}: ${it.message}" } + assertEquals("default-name", nameViaGetter) + + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/constructors/multipleConstructorsPrimaryOrder.kt b/compiler/testData/codegen/boxJvm/reflection/constructors/multipleConstructorsPrimaryOrder.kt new file mode 100644 index 0000000..db7661d --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/constructors/multipleConstructorsPrimaryOrder.kt
@@ -0,0 +1,61 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// Tests that when a class has multiple constructors, primaryConstructor correctly +// identifies the source-declared primary constructor (not some arbitrary secondary one). + +import kotlin.reflect.* +import kotlin.reflect.full.* +import kotlin.test.* + +class MultiCtorClass(val x: Int, val y: String) { + constructor(x: Int) : this(x, "") + constructor() : this(0, "") + constructor(y: String) : this(0, y) + constructor(x: Int, y: String, extra: Boolean) : this(x, if (extra) y.uppercase() else y) +} + +class SingleCtorClass(val id: Int) + +class NoArgClass { + val value = 42 +} + +fun box(): String { + // The primary constructor of MultiCtorClass is (Int, String) + val primary = MultiCtorClass::class.primaryConstructor + ?: return "Fail: MultiCtorClass has no primaryConstructor" + + val primaryParamNames = primary.parameters.map { it.name } + assertEquals(listOf("x", "y"), primaryParamNames, + "primaryConstructor should be (x: Int, y: String), got params: $primaryParamNames") + + // The primary constructor should be findable in the constructors list + val ctors = MultiCtorClass::class.constructors.toList() + assertEquals(5, ctors.size, "Expected 5 constructors") + assertTrue(primary in ctors, "primaryConstructor must be in constructors list") + + // Calling the primary constructor via reflection should work + val instance = primary.call(7, "hello") + assertEquals(7, instance.x) + assertEquals("hello", instance.y) + + // callBy with only the required params (no defaults here) also works + val instance2 = primary.callBy(mapOf( + primary.parameters[0] to 3, + primary.parameters[1] to "world" + )) + assertEquals(3, instance2.x) + assertEquals("world", instance2.y) + + // SingleCtorClass: only one constructor, which is the primary + val singlePrimary = SingleCtorClass::class.primaryConstructor + ?: return "Fail: SingleCtorClass has no primaryConstructor" + assertEquals(listOf("id"), singlePrimary.parameters.map { it.name }) + + // NoArgClass: primary constructor has zero parameters + val noArgPrimary = NoArgClass::class.primaryConstructor + ?: return "Fail: NoArgClass has no primaryConstructor" + assertEquals(0, noArgPrimary.parameters.size) + + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/mapping/companionExtensionStaticVisibility.kt b/compiler/testData/codegen/boxJvm/reflection/mapping/companionExtensionStaticVisibility.kt new file mode 100644 index 0000000..ba3d6ce --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/mapping/companionExtensionStaticVisibility.kt
@@ -0,0 +1,50 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// LANGUAGE: +CompanionBlocksAndExtensions +// Tests that companion extensions declared at top-level are visible as static members +// of the file facade class via reflection. + +import kotlin.reflect.full.* +import kotlin.test.* + +class Widget(val id: Int) { + companion { + val defaultId: Int = 0 + fun create(): Widget = Widget(defaultId) + } +} + +companion val Widget.label: String get() = "widget-${Widget.defaultId}" +companion fun Widget.describe(): String = "Widget[default=${Widget.defaultId}]" +companion fun Widget.withId(n: Int): Widget = Widget(n) + +fun box(): String { + // Companion BLOCK members are on Widget::class as staticFunctions/staticProperties + val widgetStatic = Widget::class.staticFunctions.map { it.name }.toSet() + assertTrue("create" in widgetStatic, "create should be in Widget::class.staticFunctions") + + // Companion EXTENSIONS are on the file facade class + val facadeKlass = Class.forName("CompanionExtensionStaticVisibilityKt").kotlin + + val facadeStaticFns = facadeKlass.staticFunctions.map { it.name }.toSet() + assertTrue(facadeStaticFns.isNotEmpty(), + "File facade must have static functions for companion extensions, got: $facadeStaticFns") + assertTrue("describe" in facadeStaticFns, + "describe() companion extension must be in facade staticFunctions, got: $facadeStaticFns") + assertTrue("withId" in facadeStaticFns, + "withId() companion extension must be in facade staticFunctions, got: $facadeStaticFns") + + val facadeStaticProps = facadeKlass.staticProperties.map { it.name }.toSet() + assertTrue("label" in facadeStaticProps, + "label companion extension property must be in facade staticProperties, got: $facadeStaticProps") + + // Companion extensions are callable via the facade + val describeFn = facadeKlass.staticFunctions.first { it.name == "describe" } + assertEquals("Widget[default=0]", describeFn.call()) + + val withIdFn = facadeKlass.staticFunctions.first { it.name == "withId" } + val w = withIdFn.call(42) as Widget + assertEquals(42, w.id) + + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/mapping/genericJavaClassJavaType.kt b/compiler/testData/codegen/boxJvm/reflection/mapping/genericJavaClassJavaType.kt new file mode 100644 index 0000000..f971d15 --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/mapping/genericJavaClassJavaType.kt
@@ -0,0 +1,49 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// FILE: GenericJavaHolder.java +public class GenericJavaHolder { + public static class Box<T> { + public T get() { return null; } + public void set(T value) {} + } + public static class Pair<A, B> { + public A first() { return null; } + public B second() { return null; } + } +} + +// FILE: box.kt +// Tests that KType.javaType correctly includes type parameters for generic Java classes. + +import kotlin.reflect.jvm.javaType +import kotlin.reflect.full.* +import kotlin.test.* + +fun box(): String { + // Box<T>.get() → return type T; represented as a TypeVariable "T" + val getMethod = GenericJavaHolder.Box::class.memberFunctions.first { it.name == "get" } + val getJavaType = getMethod.returnType.javaType.typeName + assertEquals("T", getJavaType, + "Box.get() return javaType should be 'T', got: $getJavaType") + + // Box<T>.set(T) → parameter type T + val setMethod = GenericJavaHolder.Box::class.memberFunctions.first { it.name == "set" } + val setParamType = setMethod.valueParameters.first().type.javaType.typeName + assertEquals("T", setParamType, + "Box.set() parameter javaType should be 'T', got: $setParamType") + + // Pair<A, B>.first() → A, second() → B (distinct type variables) + val firstMethod = GenericJavaHolder.Pair::class.memberFunctions.first { it.name == "first" } + assertEquals("A", firstMethod.returnType.javaType.typeName, + "Pair.first() return javaType should be 'A'") + + val secondMethod = GenericJavaHolder.Pair::class.memberFunctions.first { it.name == "second" } + assertEquals("B", secondMethod.returnType.javaType.typeName, + "Pair.second() return javaType should be 'B'") + + // For the class itself as a type argument, javaType should include the parameter + val boxSupertype = GenericJavaHolder.Box::class.supertypes.firstOrNull() + // supertypes just shows Any here, but declaring class via members is tested above + + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/mapping/javaAnnotationElementJavaType.kt b/compiler/testData/codegen/boxJvm/reflection/mapping/javaAnnotationElementJavaType.kt new file mode 100644 index 0000000..89fb813 --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/mapping/javaAnnotationElementJavaType.kt
@@ -0,0 +1,50 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// FILE: SampleAnnotation.java +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface SampleAnnotation { + String text() default ""; + int count() default 0; + Class<?> type() default Object.class; + String[] tags() default {}; + boolean flag() default false; +} + +// FILE: box.kt +// Tests that KType.javaType on Java annotation element parameter types is accessible +// and returns the correct Java types. + +import kotlin.reflect.jvm.javaType +import kotlin.reflect.full.* +import kotlin.test.* + +fun box(): String { + val ctor = SampleAnnotation::class.constructors.single() + + val paramsByName = ctor.parameters.associateBy { it.name } + + // Each element type must be accessible via javaType without throwing + val expectedJavaTypes = mapOf( + "text" to "java.lang.String", + "count" to "int", + "type" to "java.lang.Class", // Class<?> erases to Class + "tags" to "[Ljava.lang.String;", + "flag" to "boolean" + ) + + for ((name, expectedType) in expectedJavaTypes) { + val param = paramsByName[name] + ?: return "Fail: annotation element '$name' not found in constructor parameters" + + val javaType = runCatching { param.type.javaType.typeName } + .getOrElse { return "Fail: javaType for '$name' threw ${it::class.simpleName}: ${it.message}" } + + assertTrue(javaType.startsWith(expectedType) || javaType == expectedType, + "Annotation element '$name': expected javaType starting with '$expectedType', got: $javaType") + } + + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/mapping/javaEnumConstructorParameterNames.kt b/compiler/testData/codegen/boxJvm/reflection/mapping/javaEnumConstructorParameterNames.kt new file mode 100644 index 0000000..5b9935b --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/mapping/javaEnumConstructorParameterNames.kt
@@ -0,0 +1,43 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// FILE: ColorEnum.java +public enum ColorEnum { + RED(255, 0, 0), + GREEN(0, 255, 0), + BLUE(0, 0, 255); + + private final int r, g, b; + ColorEnum(int r, int g, int b) { this.r = r; this.g = g; this.b = b; } + public int getR() { return r; } + public int getG() { return g; } + public int getB() { return b; } +} + +// FILE: box.kt +// Tests that Java enum constructor parameters are reflected with their original +// field names, not generic positional names like arg0, arg1, arg2. + +import kotlin.reflect.full.* +import kotlin.test.* + +fun box(): String { + val ctor = ColorEnum::class.constructors.single() + val paramNames = ctor.parameters.map { it.name } + + assertEquals(3, paramNames.size, + "ColorEnum constructor should have 3 parameters, got: $paramNames") + + // The parameter names must be the actual field names, not arg0/arg1/arg2 + for (name in paramNames) { + assertFalse(name?.startsWith("arg") == true, + "Expected field name, got positional placeholder: paramNames=$paramNames") + assertNotNull(name, + "Constructor parameter name must not be null: paramNames=$paramNames") + } + + // Must be the field names in declaration order + assertEquals(listOf("r", "g", "b"), paramNames, + "Constructor parameter names should match field names r, g, b") + + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/mapping/javaFunctionInterfaceImplementorTypes.kt b/compiler/testData/codegen/boxJvm/reflection/mapping/javaFunctionInterfaceImplementorTypes.kt new file mode 100644 index 0000000..7ef8474 --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/mapping/javaFunctionInterfaceImplementorTypes.kt
@@ -0,0 +1,50 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// FILE: KotlinFunctionImplementor.java +import kotlin.jvm.functions.Function1; +import kotlin.jvm.functions.Function2; + +public class KotlinFunctionImplementor { + public static class IntToString implements Function1<Integer, String> { + @Override public String invoke(Integer value) { return value.toString(); } + } + public static class StringAndIntToBoolean implements Function2<String, Integer, Boolean> { + @Override public Boolean invoke(String s, Integer n) { return s.length() == n; } + } +} + +// FILE: box.kt +// Tests that a Java class implementing a Kotlin functional interface has its invoke +// parameter and return types reflected as proper Kotlin types (non-nullable, not platform). + +import kotlin.reflect.full.* +import kotlin.test.* + +fun box(): String { + // Function1<Integer, String> implementor: invoke(Integer) → String + val invoke1 = KotlinFunctionImplementor.IntToString::class + .memberFunctions.first { it.name == "invoke" && !it.isAbstract } + + val paramType = invoke1.valueParameters.first().type.toString() + assertFalse(paramType.endsWith("!"), + "invoke parameter type should not be a platform type, got: $paramType") + assertEquals("kotlin.Int", paramType, + "invoke parameter should be kotlin.Int (not kotlin.Int! or java.lang.Integer)") + + val returnType = invoke1.returnType.toString() + assertFalse(returnType.endsWith("!"), + "invoke return type should not be a platform type, got: $returnType") + assertEquals("kotlin.String", returnType, + "invoke return should be kotlin.String (not kotlin.String!)") + + // Function2<String, Integer, Boolean> implementor + val invoke2 = KotlinFunctionImplementor.StringAndIntToBoolean::class + .memberFunctions.first { it.name == "invoke" && !it.isAbstract } + + assertEquals(2, invoke2.valueParameters.size) + assertEquals("kotlin.String", invoke2.valueParameters[0].type.toString()) + assertEquals("kotlin.Int", invoke2.valueParameters[1].type.toString()) + assertEquals("kotlin.Boolean", invoke2.returnType.toString()) + + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/mapping/javaFunctionInterfaceInvokeFakeOverride.kt b/compiler/testData/codegen/boxJvm/reflection/mapping/javaFunctionInterfaceInvokeFakeOverride.kt new file mode 100644 index 0000000..3aacfe7 --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/mapping/javaFunctionInterfaceInvokeFakeOverride.kt
@@ -0,0 +1,35 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// FILE: SingleInvokeImpl.java +import kotlin.jvm.functions.Function1; + +public class SingleInvokeImpl implements Function1<String, Integer> { + @Override + public Integer invoke(String value) { return value.length(); } +} + +// FILE: box.kt +// Tests that a Java class implementing a Kotlin Function1 has exactly one non-abstract +// 'invoke' in memberFunctions, and that its parameter has a name (not null). + +import kotlin.reflect.full.* +import kotlin.test.* + +fun box(): String { + val invokeFns = SingleInvokeImpl::class.memberFunctions.filter { it.name == "invoke" } + + // There should be exactly one concrete (non-abstract) invoke + val concreteInvokes = invokeFns.filter { !it.isAbstract } + assertEquals(1, concreteInvokes.size, + "Expected exactly 1 concrete invoke, got: ${concreteInvokes.map { "${it.name} abstract=${it.isAbstract} params=${it.parameters.map { p -> p.name }}" }}") + + val invoke = concreteInvokes.single() + // The VALUE parameter must have a non-null name + val valueParam = invoke.valueParameters.singleOrNull() + ?: return "Fail: invoke should have exactly 1 value parameter" + + assertNotNull(valueParam.name, + "invoke value parameter name must not be null, got: ${valueParam.name}") + + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/mapping/jvmExposeBoxed.kt b/compiler/testData/codegen/boxJvm/reflection/mapping/jvmExposeBoxed.kt index c954228..b011488 100644 --- a/compiler/testData/codegen/boxJvm/reflection/mapping/jvmExposeBoxed.kt +++ b/compiler/testData/codegen/boxJvm/reflection/mapping/jvmExposeBoxed.kt
@@ -1,6 +1,7 @@ // TARGET_BACKEND: JVM // WITH_REFLECT // WITH_STDLIB +@file:OptIn(ExperimentalStdlibApi::class) import kotlin.jvm.JvmExposeBoxed import kotlin.reflect.*
diff --git a/compiler/testData/codegen/boxJvm/reflection/mapping/jvmStaticInCompanionBlock.kt b/compiler/testData/codegen/boxJvm/reflection/mapping/jvmStaticInCompanionBlock.kt deleted file mode 100644 index 28104e9..0000000 --- a/compiler/testData/codegen/boxJvm/reflection/mapping/jvmStaticInCompanionBlock.kt +++ /dev/null
@@ -1,77 +0,0 @@ -// TARGET_BACKEND: JVM -// WITH_REFLECT -// LANGUAGE: +CompanionBlocksAndExtensions - -import kotlin.reflect.* -import kotlin.reflect.full.* -import kotlin.reflect.jvm.* -import kotlin.test.* - -class WithJvmStaticInCompanionBlock { - companion { - // @JvmStatic has no effect inside companion blocks (members are already static), - // but the annotation should still be reflected on the callable. - @JvmStatic - fun alreadyStatic(): String = "static" - - // Without @JvmStatic for comparison - fun plainStatic(): String = "plain" - - @JvmStatic - val staticProp: Int = 42 - } -} - -class WithJvmStaticInCompanionObject(val x: Int) { - companion object { - @JvmStatic - fun fromObject(): String = "from-object" - - fun notJvmStatic(): String = "not-static" - } -} - -fun box(): String { - // Companion block: both @JvmStatic and plain functions appear in staticFunctions - val blockFns = WithJvmStaticInCompanionBlock::class.staticFunctions.associateBy { it.name } - assertNotNull(blockFns["alreadyStatic"], "alreadyStatic should be in staticFunctions") - assertNotNull(blockFns["plainStatic"], "plainStatic should be in staticFunctions") - - // @JvmStatic annotation is reflected on the callable - val annotated = blockFns["alreadyStatic"]!! - val jvmStaticAnn = annotated.annotations.firstOrNull { it.annotationClass.simpleName == "JvmStatic" } - assertNotNull(jvmStaticAnn, "@JvmStatic should appear in annotations of the function") - - // Plain companion block function has no @JvmStatic annotation - val plain = blockFns["plainStatic"]!! - val plainJvmStatic = plain.annotations.firstOrNull { it.annotationClass.simpleName == "JvmStatic" } - assertNull(plainJvmStatic, "plainStatic should not have @JvmStatic annotation") - - // Both are callable without an instance (static — no INSTANCE parameter) - assertEquals(emptyList(), annotated.parameters.filter { it.kind == KParameter.Kind.INSTANCE }) - assertEquals(emptyList(), plain.parameters.filter { it.kind == KParameter.Kind.INSTANCE }) - assertEquals("static", annotated.call()) - assertEquals("plain", plain.call()) - - // @JvmStatic property in companion block - val staticProp = WithJvmStaticInCompanionBlock::class.staticProperties.firstOrNull { it.name == "staticProp" } - assertNotNull(staticProp, "staticProp should be in staticProperties") - assertEquals(42, staticProp.call()) - val propJvmStatic = staticProp.annotations.firstOrNull { it.annotationClass.simpleName == "JvmStatic" } - assertNotNull(propJvmStatic, "@JvmStatic should appear in property annotations") - - // Companion OBJECT with @JvmStatic: function appears both as static and as member of companion - // The @JvmStatic function is in staticFunctions of the outer class - val objStaticFns = WithJvmStaticInCompanionObject::class.staticFunctions.map { it.name }.toSet() - assertTrue("fromObject" in objStaticFns, - "@JvmStatic in companion object should appear in outer class staticFunctions: $objStaticFns") - assertFalse("notJvmStatic" in objStaticFns, - "Non-@JvmStatic companion function should NOT appear in outer class staticFunctions: $objStaticFns") - - // Verify javaMethod is accessible for the @JvmStatic companion block function - val javaMethod = annotated.javaMethod - assertNotNull(javaMethod, "javaMethod should be accessible for @JvmStatic companion block function") - assertEquals("alreadyStatic", javaMethod.name) - - return "OK" -}
diff --git a/compiler/testData/codegen/boxJvm/reflection/modifiers/inlineModifiers.kt b/compiler/testData/codegen/boxJvm/reflection/modifiers/inlineModifiers.kt index d7470c6..e902fff 100644 --- a/compiler/testData/codegen/boxJvm/reflection/modifiers/inlineModifiers.kt +++ b/compiler/testData/codegen/boxJvm/reflection/modifiers/inlineModifiers.kt
@@ -12,7 +12,7 @@ inline fun <reified T> reifiedInline(): String = T::class.simpleName ?: "?" inline fun <reified T, reified R : T> multiReifiedInline(x: T): R? = x as? R inline fun withBothModifiers(crossinline cross: () -> String, noinline noin: () -> Int): String = cross() + noin() - noinline fun notInline(x: Int): Int = x + fun notInline(x: Int): Int = x infix fun infixFun(other: Int): Int = other operator fun plus(other: InlineSubject): InlineSubject = this
diff --git a/compiler/testData/codegen/boxJvm/reflection/modifiers/sealedInterfaceWithValueClasses.kt b/compiler/testData/codegen/boxJvm/reflection/modifiers/sealedInterfaceWithValueClasses.kt index 67c59d7..bf83ed7 100644 --- a/compiler/testData/codegen/boxJvm/reflection/modifiers/sealedInterfaceWithValueClasses.kt +++ b/compiler/testData/codegen/boxJvm/reflection/modifiers/sealedInterfaceWithValueClasses.kt
@@ -28,7 +28,7 @@ // sealedSubclasses contains all 5 variants val subclasses = Measurement::class.sealedSubclasses assertEquals(5, subclasses.size) - val names = subclasses.map { it.simpleName }.sorted() + val names = subclasses.map { it.simpleName.orEmpty() }.sorted() assertEquals(listOf("Combined", "Distance", "Duration", "Empty", "Weight"), names) // Value class variants: isValue=true, isData=false
diff --git a/compiler/testData/codegen/boxJvm/reflection/properties/kotlinEnumEntriesProperty.kt b/compiler/testData/codegen/boxJvm/reflection/properties/kotlinEnumEntriesProperty.kt index c735b28..db02bbc4 100644 --- a/compiler/testData/codegen/boxJvm/reflection/properties/kotlinEnumEntriesProperty.kt +++ b/compiler/testData/codegen/boxJvm/reflection/properties/kotlinEnumEntriesProperty.kt
@@ -17,7 +17,7 @@ enum class Singleton { ONLY } -private fun <E : Enum<E>> checkEntriesProperty(enumClass: KClass<E>, expectedCount: Int) { +private inline fun <reified E : Enum<E>> checkEntriesProperty(enumClass: KClass<E>, expectedCount: Int) { val prop = enumClass.members.single { it.name == "entries" } .also { assertTrue(it is KProperty0<*>, "entries should be KProperty0") } as KProperty0<*>
diff --git a/compiler/testData/codegen/boxJvm/reflection/supertypes/genericSealedHierarchy.kt b/compiler/testData/codegen/boxJvm/reflection/supertypes/genericSealedHierarchy.kt index 49266ee..ae4b1fa 100644 --- a/compiler/testData/codegen/boxJvm/reflection/supertypes/genericSealedHierarchy.kt +++ b/compiler/testData/codegen/boxJvm/reflection/supertypes/genericSealedHierarchy.kt
@@ -29,7 +29,7 @@ // sealedSubclasses val eitherSubs = Either::class.sealedSubclasses assertEquals(3, eitherSubs.size) - val names = eitherSubs.map { it.simpleName }.sorted() + val names = eitherSubs.map { it.simpleName.orEmpty() }.sorted() assertEquals(listOf("Empty", "Left", "Right"), names) // Left implements Either<L, Nothing> — type arguments in supertype
diff --git a/compiler/testData/codegen/boxJvm/reflection/supertypes/javaCollectionSupertypesNoDuplicates.kt b/compiler/testData/codegen/boxJvm/reflection/supertypes/javaCollectionSupertypesNoDuplicates.kt new file mode 100644 index 0000000..fceee15 --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/supertypes/javaCollectionSupertypesNoDuplicates.kt
@@ -0,0 +1,41 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// Tests that Java collection class supertypes do not contain duplicate entries. +// Each Kotlin-mapped supertype (MutableList, MutableSet, etc.) must appear at most once. + +import kotlin.reflect.* +import kotlin.reflect.full.* +import kotlin.test.* + +fun checkNoDuplicates(klass: kotlin.reflect.KClass<*>) { + val supertypes = klass.supertypes + val typeStrings = supertypes.map { it.toString() } + val duplicates = typeStrings.groupBy { it }.filter { it.value.size > 1 }.keys + assertTrue(duplicates.isEmpty(), + "${klass.simpleName}::class.supertypes contains duplicates: $duplicates\nAll supertypes: $typeStrings") +} + +fun box(): String { + // LinkedList implements List + Deque; must not have MutableList twice + checkNoDuplicates(java.util.LinkedList::class) + + // ArrayList also maps to MutableList + checkNoDuplicates(java.util.ArrayList::class) + + // HashSet maps to MutableSet + checkNoDuplicates(java.util.HashSet::class) + + // TreeSet maps to MutableSet + NavigableSet etc. + checkNoDuplicates(java.util.TreeSet::class) + + // HashMap maps to MutableMap + checkNoDuplicates(java.util.HashMap::class) + + // Verify the specific LinkedList count: MutableList must appear exactly once + val linkedListSupertypes = java.util.LinkedList::class.supertypes.map { it.toString() } + val mutableListCount = linkedListSupertypes.count { it.contains("MutableList") } + assertEquals(1, mutableListCount, + "LinkedList supertypes must contain MutableList exactly once, got: $linkedListSupertypes") + + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/supertypes/sealedInterfaceSupertypes.kt b/compiler/testData/codegen/boxJvm/reflection/supertypes/sealedInterfaceSupertypes.kt index c29e917..8e622cb 100644 --- a/compiler/testData/codegen/boxJvm/reflection/supertypes/sealedInterfaceSupertypes.kt +++ b/compiler/testData/codegen/boxJvm/reflection/supertypes/sealedInterfaceSupertypes.kt
@@ -57,7 +57,7 @@ // sealedSubclasses of JsonElement val subs = JsonElement::class.sealedSubclasses assertEquals(4, subs.size) - val subNames = subs.map { it.simpleName }.sorted() + val subNames = subs.map { it.simpleName.orEmpty() }.sorted() assertEquals(listOf("JsonArray", "JsonNull", "JsonNumber", "JsonString"), subNames) // superclasses (KClass not KType form)
diff --git a/compiler/testData/codegen/boxJvm/reflection/supertypes/suspendFunctionalInterfaceMemberFunctions.kt b/compiler/testData/codegen/boxJvm/reflection/supertypes/suspendFunctionalInterfaceMemberFunctions.kt new file mode 100644 index 0000000..c73c911 --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/supertypes/suspendFunctionalInterfaceMemberFunctions.kt
@@ -0,0 +1,45 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// Tests that classes implementing suspend functional interfaces expose their inherited +// member functions (invoke, equals, hashCode, toString) via memberFunctions. + +import kotlin.reflect.full.* +import kotlin.test.* + +class SuspendUnit : suspend () -> Unit { + override suspend fun invoke() {} +} + +class SuspendStringProducer : suspend () -> String { + override suspend fun invoke(): String = "hello" +} + +class SuspendMapper : suspend (Int) -> String { + override suspend fun invoke(p: Int): String = p.toString() +} + +fun checkMemberFunctions(klass: kotlin.reflect.KClass<*>, label: String) { + val fns = klass.memberFunctions.map { it.name }.toSet() + + // Must have invoke + assertTrue("invoke" in fns, + "$label: memberFunctions must include 'invoke', got: $fns") + + // Must have the standard Object methods + for (name in listOf("equals", "hashCode", "toString")) { + assertTrue(name in fns, + "$label: memberFunctions must include '$name', got: $fns") + } +} + +fun box(): String { + checkMemberFunctions(SuspendUnit::class, "SuspendUnit") + checkMemberFunctions(SuspendStringProducer::class, "SuspendStringProducer") + checkMemberFunctions(SuspendMapper::class, "SuspendMapper") + + // Verify invoke is callable + val invoker = SuspendStringProducer::class.memberFunctions.first { it.name == "invoke" } + assertTrue(invoker.isSuspend) + + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/supertypes/throwablePrintStackTraceFakeOverride.kt b/compiler/testData/codegen/boxJvm/reflection/supertypes/throwablePrintStackTraceFakeOverride.kt new file mode 100644 index 0000000..1466f86 --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/supertypes/throwablePrintStackTraceFakeOverride.kt
@@ -0,0 +1,37 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// Tests that Throwable, Exception, and Error expose printStackTrace(PrintStream) +// as a properly-typed member function via reflection. + +import kotlin.reflect.full.* +import kotlin.test.* + +fun checkPrintStackTrace(klass: kotlin.reflect.KClass<*>, label: String) { + val fns = klass.memberFunctions.filter { it.name == "printStackTrace" } + + // Must have at least one printStackTrace overload + assertTrue(fns.isNotEmpty(), + "$label must have at least one printStackTrace() in memberFunctions") + + // Must have a no-arg overload + assertTrue(fns.any { it.valueParameters.isEmpty() }, + "$label must have printStackTrace() with no parameters") + + // Must have a PrintStream overload + val withParam = fns.firstOrNull { it.valueParameters.size == 1 } + assertNotNull(withParam, + "$label must have printStackTrace(PrintStream) overload, found: ${fns.map { it.parameters.map { p -> p.type } }}") + + // The PrintStream overload's parameter must be typed as PrintStream, not generic Any + val paramType = withParam.valueParameters.single().type.toString() + assertTrue(paramType.contains("PrintStream"), + "$label.printStackTrace(PrintStream) parameter type must contain 'PrintStream', got: $paramType") +} + +fun box(): String { + checkPrintStackTrace(Throwable::class, "Throwable") + checkPrintStackTrace(Exception::class, "Exception") + checkPrintStackTrace(java.lang.Error::class, "Error") + checkPrintStackTrace(RuntimeException::class, "RuntimeException") + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/typeParameters/enumFakeOverrideTypeSubstitution.kt b/compiler/testData/codegen/boxJvm/reflection/typeParameters/enumFakeOverrideTypeSubstitution.kt new file mode 100644 index 0000000..2ac0189 --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/typeParameters/enumFakeOverrideTypeSubstitution.kt
@@ -0,0 +1,45 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// Tests that fake override methods inherited from Enum<E> have their type parameter E +// correctly substituted with the concrete enum type in javaType. + +import kotlin.reflect.full.* +import kotlin.reflect.jvm.javaType +import kotlin.test.* + +enum class Season { SPRING, SUMMER, AUTUMN, WINTER } +enum class Weekday(val abbreviation: String) { + MON("Mon"), TUE("Tue"), WED("Wed"), THU("Thu"), FRI("Fri") +} + +fun checkEnumFakeOverrides(enumClass: kotlin.reflect.KClass<out Enum<*>>, simpleName: String) { + val fns = enumClass.memberFunctions.associateBy { it.name } + + // getDeclaringClass() is inherited from Enum<E>; return type should be Class<EnumType> + val getDeclaringClass = fns["getDeclaringClass"] + if (getDeclaringClass != null) { + val jt = getDeclaringClass.returnType.javaType.typeName + assertTrue( + jt.contains(simpleName) || jt == "java.lang.Class<${enumClass.java.name}>", + "$simpleName.getDeclaringClass() return javaType should reference $simpleName, got: $jt" + ) + assertFalse(jt == "java.lang.Class<E>" || jt.endsWith("<E>"), + "$simpleName.getDeclaringClass() must not use raw type variable E, got: $jt") + } + + // compareTo(E other) — parameter should be the concrete enum type, not E + val compareTo = fns["compareTo"] + if (compareTo != null) { + val paramType = compareTo.valueParameters.firstOrNull()?.type?.javaType?.typeName + if (paramType != null) { + assertFalse(paramType == "E", + "$simpleName.compareTo() parameter must not be raw 'E', got: $paramType") + } + } +} + +fun box(): String { + checkEnumFakeOverrides(Season::class, "Season") + checkEnumFakeOverrides(Weekday::class, "Weekday") + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/typeParameters/javaSubclassFakeOverrideTypeSubstitution.kt b/compiler/testData/codegen/boxJvm/reflection/typeParameters/javaSubclassFakeOverrideTypeSubstitution.kt new file mode 100644 index 0000000..a1a1d47 --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/typeParameters/javaSubclassFakeOverrideTypeSubstitution.kt
@@ -0,0 +1,51 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// FILE: TypedJavaBase.java +public class TypedJavaBase<T> { + public T value() { return null; } + public void setValue(T t) {} + public java.util.List<T> getList() { return null; } +} + +// FILE: box.kt +// Tests that a Kotlin class extending a generic Java class has its inherited fake override +// methods with type parameters correctly substituted with the concrete bound type. + +import kotlin.reflect.full.* +import kotlin.reflect.jvm.javaType +import kotlin.test.* + +class KotlinExtendsTyped : TypedJavaBase<String>() +class KotlinExtendsTypedInt : TypedJavaBase<Int>() + +fun box(): String { + // value() return type should be String, not T + val valueFn = KotlinExtendsTyped::class.memberFunctions.firstOrNull { it.name == "value" } + ?: return "Fail: 'value' not found in KotlinExtendsTyped.memberFunctions" + + val returnJavaType = valueFn.returnType.javaType.typeName + assertFalse(returnJavaType == "T", + "KotlinExtendsTyped.value() return javaType must not be raw 'T', got: $returnJavaType") + assertEquals("java.lang.String", returnJavaType, + "KotlinExtendsTyped.value() return javaType should be java.lang.String") + + // setValue(T) parameter type should be String + val setValueFn = KotlinExtendsTyped::class.memberFunctions.firstOrNull { it.name == "setValue" } + if (setValueFn != null) { + val paramType = setValueFn.valueParameters.firstOrNull()?.type?.javaType?.typeName + if (paramType != null) { + assertFalse(paramType == "T", + "KotlinExtendsTyped.setValue() param must not be 'T', got: $paramType") + } + } + + // For Int bound: value() should return int/Integer + val intValueFn = KotlinExtendsTypedInt::class.memberFunctions.firstOrNull { it.name == "value" } + if (intValueFn != null) { + val intReturnType = intValueFn.returnType.javaType.typeName + assertFalse(intReturnType == "T", + "KotlinExtendsTypedInt.value() return must not be raw 'T', got: $intReturnType") + } + + return "OK" +}
diff --git a/compiler/testData/codegen/boxJvm/reflection/types/javaWildcardTypeStructure.kt b/compiler/testData/codegen/boxJvm/reflection/types/javaWildcardTypeStructure.kt new file mode 100644 index 0000000..e762f05 --- /dev/null +++ b/compiler/testData/codegen/boxJvm/reflection/types/javaWildcardTypeStructure.kt
@@ -0,0 +1,74 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT +// FILE: WildcardMethods.java +import java.util.*; + +public class WildcardMethods { + public static List<? extends Number> boundedOut() { return null; } + public static List<? super Integer> boundedIn() { return null; } + public static List<?> unbounded() { return null; } + public static Map<String, ? extends Number> mapBounded(){ return null; } +} + +// FILE: box.kt +// Tests structural properties of reflected types for Java wildcard-typed methods: +// correct classifier, argument count, and variance projections. + +import kotlin.reflect.* +import kotlin.reflect.full.* +import kotlin.reflect.jvm.javaType +import kotlin.test.* + +fun box(): String { + val fns = WildcardMethods::class.staticFunctions.associateBy { it.name } + + // boundedOut(): List<? extends Number> + // → classifier should be some List type, with 1 argument of OUT variance + val boundedOut = fns["boundedOut"] ?: return "Fail: boundedOut not found" + val boundedOutType = boundedOut.returnType + val boundedOutClassifier = boundedOutType.classifier as? KClass<*> + assertNotNull(boundedOutClassifier, "boundedOut return type must have a KClass classifier") + assertTrue(boundedOutClassifier.qualifiedName?.contains("List") == true, + "boundedOut return type classifier must be a List, got: ${boundedOutClassifier.qualifiedName}") + assertEquals(1, boundedOutType.arguments.size, + "boundedOut return type must have 1 type argument") + val boundedOutArg = boundedOutType.arguments.single() + assertEquals(KVariance.OUT, boundedOutArg.variance, + "boundedOut type argument must be OUT (covariant)") + val boundedOutArgType = boundedOutArg.type + assertNotNull(boundedOutArgType, "boundedOut type argument must not be star") + assertTrue(boundedOutArgType.toString().contains("Number"), + "boundedOut type argument must be Number, got: $boundedOutArgType") + + // boundedIn(): List<? super Integer> + // → 1 argument of IN variance + val boundedIn = fns["boundedIn"] ?: return "Fail: boundedIn not found" + val boundedInType = boundedIn.returnType + assertEquals(1, boundedInType.arguments.size) + val boundedInArg = boundedInType.arguments.single() + assertEquals(KVariance.IN, boundedInArg.variance, + "boundedIn type argument must be IN (contravariant)") + assertTrue(boundedInArg.type?.toString()?.contains("Int") == true, + "boundedIn type argument must contain Int, got: ${boundedInArg.type}") + + // unbounded(): List<?> + // → 1 star-projection argument + val unbounded = fns["unbounded"] ?: return "Fail: unbounded not found" + val unboundedType = unbounded.returnType + assertEquals(1, unboundedType.arguments.size) + val unboundedArg = unboundedType.arguments.single() + assertNull(unboundedArg.variance, "unbounded type argument must be a star projection (null variance)") + assertNull(unboundedArg.type, "unbounded type argument must be a star projection (null type)") + + // mapBounded(): Map<String, ? extends Number> + // → 2 arguments; second is OUT + val mapBounded = fns["mapBounded"] ?: return "Fail: mapBounded not found" + val mapBoundedType = mapBounded.returnType + assertEquals(2, mapBoundedType.arguments.size, + "mapBounded return type must have 2 type arguments") + val mapSecondArg = mapBoundedType.arguments[1] + assertEquals(KVariance.OUT, mapSecondArg.variance, + "mapBounded second type argument must be OUT") + + return "OK" +}