Skip to content

Commit bf08193

Browse files
T45Ksdeleuze
authored andcommitted
Support Kotlin value classes as suspending function arguments
Similar to gh-31698 but for Coroutines. See gh-31846
1 parent 503ccb5 commit bf08193

File tree

2 files changed

+30
-1
lines changed

2 files changed

+30
-1
lines changed

spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.lang.reflect.InvocationTargetException;
2020
import java.lang.reflect.Method;
21+
import java.lang.reflect.Modifier;
2122
import java.util.Map;
2223
import java.util.Objects;
2324

@@ -40,6 +41,7 @@
4041
import kotlinx.coroutines.reactor.MonoKt;
4142
import kotlinx.coroutines.reactor.ReactorFlowKt;
4243
import org.reactivestreams.Publisher;
44+
import org.springframework.util.ReflectionUtils;
4345
import reactor.core.publisher.Flux;
4446
import reactor.core.publisher.Mono;
4547

@@ -55,6 +57,9 @@
5557
*/
5658
public abstract class CoroutinesUtils {
5759

60+
private static final ReflectionUtils.MethodFilter boxImplFilter =
61+
(method -> method.isSynthetic() && Modifier.isStatic(method.getModifiers()) && method.getName().equals("box-impl"));
62+
5863
/**
5964
* Convert a {@link Deferred} instance to a {@link Mono}.
6065
*/
@@ -115,7 +120,14 @@ public static Publisher<?> invokeSuspendingFunction(CoroutineContext context, Me
115120
case INSTANCE -> argMap.put(parameter, target);
116121
case VALUE -> {
117122
if (!parameter.isOptional() || args[index] != null) {
118-
argMap.put(parameter, args[index]);
123+
if (parameter.getType().getClassifier() instanceof KClass<?> kClass && kClass.isValue()) {
124+
Class<?> javaClass = JvmClassMappingKt.getJavaClass(kClass);
125+
Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(javaClass, boxImplFilter);
126+
Assert.state(methods.length == 1, "Unable to find a single box-impl synthetic static method in " + javaClass.getName());
127+
argMap.put(parameter, ReflectionUtils.invokeMethod(methods[0], null, args[index]));
128+
} else {
129+
argMap.put(parameter, args[index]);
130+
}
119131
}
120132
index++;
121133
}

spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,15 @@ class CoroutinesUtilsTests {
145145
}
146146
}
147147

148+
@Test
149+
fun invokeSuspendingFunctionWithValueClassParameter() {
150+
val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithValueClass") }
151+
val mono = CoroutinesUtils.invokeSuspendingFunction(method, this, "foo", null) as Mono
152+
runBlocking {
153+
Assertions.assertThat(mono.awaitSingle()).isEqualTo("foo")
154+
}
155+
}
156+
148157
suspend fun suspendingFunction(value: String): String {
149158
delay(1)
150159
return value
@@ -172,4 +181,12 @@ class CoroutinesUtilsTests {
172181
return null
173182
}
174183

184+
suspend fun suspendingFunctionWithValueClass(value: ValueClass): String {
185+
delay(1)
186+
return value.value
187+
}
188+
189+
@JvmInline
190+
value class ValueClass(val value: String)
191+
175192
}

0 commit comments

Comments
 (0)