From d6554c66050730640f5a584c5a63acf777e53aaf Mon Sep 17 00:00:00 2001 From: Dmitry Sulman Date: Sat, 29 Mar 2025 16:52:41 +0300 Subject: [PATCH] Recursively boxing Kotlin nested value classes This commit is a follow-up to #34592. It introduces recursive boxing of Kotlin nested value classes in CoroutinesUtils. Signed-off-by: Dmitry Sulman --- .../springframework/core/CoroutinesUtils.java | 21 ++++++++++++++----- .../core/CoroutinesUtilsTests.kt | 17 +++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java index c336fe30edc0..f53847044d8c 100644 --- a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java +++ b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java @@ -19,6 +19,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; +import java.util.Objects; import kotlin.Unit; import kotlin.coroutines.CoroutineContext; @@ -131,11 +132,7 @@ public static Publisher invokeSuspendingFunction( if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass kClass && KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) { - KFunction constructor = KClasses.getPrimaryConstructor(kClass); - if (!KCallablesJvm.isAccessible(constructor)) { - KCallablesJvm.setAccessible(constructor, true); - } - arg = constructor.call(arg); + arg = box(kClass, arg); } argMap.put(parameter, arg); } @@ -161,6 +158,20 @@ public static Publisher invokeSuspendingFunction( return mono; } + private static Object box(KClass kClass, @Nullable Object arg) { + KFunction constructor = Objects.requireNonNull(KClasses.getPrimaryConstructor(kClass)); + KType type = constructor.getParameters().get(0).getType(); + if (!(type.isMarkedNullable() && arg == null) && + type.getClassifier() instanceof KClass parameterClass && + KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(parameterClass))) { + arg = box(parameterClass, arg); + } + if (!KCallablesJvm.isAccessible(constructor)) { + KCallablesJvm.setAccessible(constructor, true); + } + return constructor.call(arg); + } + private static Flux asFlux(Object flow) { return ReactorFlowKt.asFlux(((Flow) flow)); } diff --git a/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt b/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt index 31ebb74927d7..24a98d61ccb6 100644 --- a/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt @@ -199,6 +199,15 @@ class CoroutinesUtilsTests { } } + @Test + fun invokeSuspendingFunctionWithNestedValueClassParameter() { + val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithNestedValueClassParameter") } + val mono = CoroutinesUtils.invokeSuspendingFunction(method, this, "foo", null) as Mono + runBlocking { + Assertions.assertThat(mono.awaitSingle()).isEqualTo("foo") + } + } + @Test fun invokeSuspendingFunctionWithValueClassReturnValue() { val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithValueClassReturnValue") } @@ -328,6 +337,11 @@ class CoroutinesUtilsTests { return value.value } + suspend fun suspendingFunctionWithNestedValueClassParameter(value: NestedValueClass): String { + delay(1) + return value.value.value + } + suspend fun suspendingFunctionWithValueClassReturnValue(): ValueClass { delay(1) return ValueClass("foo") @@ -382,6 +396,9 @@ class CoroutinesUtilsTests { @JvmInline value class ValueClass(val value: String) + @JvmInline + value class NestedValueClass(val value: ValueClass) + @JvmInline value class ValueClassWithInit(val value: String) { init {