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:
parent
82c5aa4a48
commit
7617a01f60
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -299,7 +299,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
|||
|
||||
@Nullable
|
||||
@SuppressWarnings({"deprecation", "DataFlowIssue"})
|
||||
public static Object invokeFunction(Method method, Object target, Object[] args) throws InvocationTargetException, IllegalAccessException {
|
||||
public static Object invokeFunction(Method method, Object target, Object[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
|
||||
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
|
||||
// For property accessors
|
||||
if (function == null) {
|
||||
|
@ -332,6 +332,9 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
|||
}
|
||||
}
|
||||
Object result = function.callBy(argMap);
|
||||
if (result != null && KotlinDetector.isInlineClass(result.getClass())) {
|
||||
return result.getClass().getDeclaredMethod("unbox-impl").invoke(result);
|
||||
}
|
||||
return (result == Unit.INSTANCE ? null : result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,6 +104,12 @@ class InvocableHandlerMethodKotlinTests {
|
|||
Assertions.assertThat(value).isEqualTo(1L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun valueClassReturnValue() {
|
||||
val value = getInvocable(ValueClassHandler::valueClassReturnValue.javaMethod!!).invokeForRequest(request, null)
|
||||
Assertions.assertThat(value).isEqualTo("foo")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun valueClassDefaultValue() {
|
||||
composite.addResolver(StubArgumentResolver(Double::class.java))
|
||||
|
@ -200,6 +206,9 @@ class InvocableHandlerMethodKotlinTests {
|
|||
|
||||
private class ValueClassHandler {
|
||||
|
||||
fun valueClassReturnValue() =
|
||||
StringValueClass("foo")
|
||||
|
||||
fun longValueClass(limit: LongValueClass) =
|
||||
limit.value
|
||||
|
||||
|
@ -246,6 +255,9 @@ class InvocableHandlerMethodKotlinTests {
|
|||
|
||||
data class Animal(override val name: String) : Named
|
||||
|
||||
@JvmInline
|
||||
value class StringValueClass(val value: String)
|
||||
|
||||
@JvmInline
|
||||
value class LongValueClass(val value: Long)
|
||||
|
||||
|
|
|
@ -325,7 +325,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
|||
@Nullable
|
||||
@SuppressWarnings({"deprecation", "DataFlowIssue"})
|
||||
public static Object invokeFunction(Method method, Object target, Object[] args, boolean isSuspendingFunction,
|
||||
ServerWebExchange exchange) throws InvocationTargetException, IllegalAccessException {
|
||||
ServerWebExchange exchange) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
|
||||
|
||||
if (isSuspendingFunction) {
|
||||
Object coroutineContext = exchange.getAttribute(COROUTINE_CONTEXT_ATTRIBUTE);
|
||||
|
@ -369,6 +369,9 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
|||
}
|
||||
}
|
||||
Object result = function.callBy(argMap);
|
||||
if (result != null && KotlinDetector.isInlineClass(result.getClass())) {
|
||||
return result.getClass().getDeclaredMethod("unbox-impl").invoke(result);
|
||||
}
|
||||
return (result == Unit.INSTANCE ? null : result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -205,6 +205,13 @@ class InvocableHandlerMethodKotlinTests {
|
|||
assertHandlerResultValue(result, "1")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun valueClassReturnValue() {
|
||||
val method = ValueClassController::valueClassReturnValue.javaMethod!!
|
||||
val result = invoke(ValueClassController(), method,)
|
||||
assertHandlerResultValue(result, "foo")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun valueClassWithDefaultValue() {
|
||||
this.resolvers.add(stubResolver(null, Double::class.java))
|
||||
|
@ -376,6 +383,9 @@ class InvocableHandlerMethodKotlinTests {
|
|||
fun valueClass(limit: LongValueClass) =
|
||||
"${limit.value}"
|
||||
|
||||
fun valueClassReturnValue() =
|
||||
StringValueClass("foo")
|
||||
|
||||
fun valueClassWithDefault(limit: DoubleValueClass = DoubleValueClass(3.1)) =
|
||||
"${limit.value}"
|
||||
|
||||
|
@ -420,6 +430,9 @@ class InvocableHandlerMethodKotlinTests {
|
|||
|
||||
data class Animal(override val name: String) : Named
|
||||
|
||||
@JvmInline
|
||||
value class StringValueClass(val value: String)
|
||||
|
||||
@JvmInline
|
||||
value class LongValueClass(val value: Long)
|
||||
|
||||
|
|
Loading…
Reference in New Issue