Unwrap Kotlin inline value classes return values

The result returned by Kotlin reflective invocation of a function
returning an inline value class is wrapped, which makes sense
from Kotlin POV but from a JVM perspective the associated value
and type should be unwrapped to be consistent with what
would happen with a reflective invocation done by Java.

This commit unwraps such result.

Closes gh-33026
This commit is contained in:
Sébastien Deleuze
2024-07-10 18:31:35 +02:00
parent 82c5aa4a48
commit 7617a01f60
6 changed files with 67 additions and 3 deletions

View File

@@ -44,6 +44,7 @@ import kotlinx.coroutines.reactor.ReactorFlowKt;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SynchronousSink;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -145,7 +146,7 @@ public abstract class CoroutinesUtils {
}
return KCallables.callSuspendBy(function, argMap, continuation);
})
.filter(result -> result != Unit.INSTANCE)
.handle(CoroutinesUtils::handleResult)
.onErrorMap(InvocationTargetException.class, InvocationTargetException::getTargetException);
KType returnType = function.getReturnType();
@@ -165,4 +166,22 @@ public abstract class CoroutinesUtils {
return ReactorFlowKt.asFlux(((Flow<?>) flow));
}
private static void handleResult(Object result, SynchronousSink<Object> sink) {
if (result == Unit.INSTANCE) {
sink.complete();
}
else if (KotlinDetector.isInlineClass(result.getClass())) {
try {
sink.next(result.getClass().getDeclaredMethod("unbox-impl").invoke(result));
sink.complete();
}
catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
sink.error(ex);
}
}
else {
sink.next(result);
sink.complete();
}
}
}

View File

@@ -199,6 +199,15 @@ class CoroutinesUtilsTests {
}
}
@Test
fun invokeSuspendingFunctionWithValueClassReturnValue() {
val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithValueClassReturnValue") }
val mono = CoroutinesUtils.invokeSuspendingFunction(method, this, null) as Mono
runBlocking {
Assertions.assertThat(mono.awaitSingle()).isEqualTo("foo")
}
}
@Test
fun invokeSuspendingFunctionWithValueClassWithInitParameter() {
val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithValueClassWithInit") }
@@ -310,6 +319,11 @@ class CoroutinesUtilsTests {
return value.value
}
suspend fun suspendingFunctionWithValueClassReturnValue(): ValueClass {
delay(1)
return ValueClass("foo")
}
suspend fun suspendingFunctionWithValueClassWithInit(value: ValueClassWithInit): String {
delay(1)
return value.value