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 org.reactivestreams.Publisher;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.publisher.SynchronousSink;
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
@ -145,7 +146,7 @@ public abstract class CoroutinesUtils {
|
||||||
}
|
}
|
||||||
return KCallables.callSuspendBy(function, argMap, continuation);
|
return KCallables.callSuspendBy(function, argMap, continuation);
|
||||||
})
|
})
|
||||||
.filter(result -> result != Unit.INSTANCE)
|
.handle(CoroutinesUtils::handleResult)
|
||||||
.onErrorMap(InvocationTargetException.class, InvocationTargetException::getTargetException);
|
.onErrorMap(InvocationTargetException.class, InvocationTargetException::getTargetException);
|
||||||
|
|
||||||
KType returnType = function.getReturnType();
|
KType returnType = function.getReturnType();
|
||||||
|
@ -165,4 +166,22 @@ public abstract class CoroutinesUtils {
|
||||||
return ReactorFlowKt.asFlux(((Flow<?>) flow));
|
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
|
@Test
|
||||||
fun invokeSuspendingFunctionWithValueClassWithInitParameter() {
|
fun invokeSuspendingFunctionWithValueClassWithInitParameter() {
|
||||||
val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithValueClassWithInit") }
|
val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithValueClassWithInit") }
|
||||||
|
@ -310,6 +319,11 @@ class CoroutinesUtilsTests {
|
||||||
return value.value
|
return value.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun suspendingFunctionWithValueClassReturnValue(): ValueClass {
|
||||||
|
delay(1)
|
||||||
|
return ValueClass("foo")
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun suspendingFunctionWithValueClassWithInit(value: ValueClassWithInit): String {
|
suspend fun suspendingFunctionWithValueClassWithInit(value: ValueClassWithInit): String {
|
||||||
delay(1)
|
delay(1)
|
||||||
return value.value
|
return value.value
|
||||||
|
|
|
@ -299,7 +299,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@SuppressWarnings({"deprecation", "DataFlowIssue"})
|
@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);
|
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
|
||||||
// For property accessors
|
// For property accessors
|
||||||
if (function == null) {
|
if (function == null) {
|
||||||
|
@ -332,6 +332,9 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Object result = function.callBy(argMap);
|
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);
|
return (result == Unit.INSTANCE ? null : result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,6 +104,12 @@ class InvocableHandlerMethodKotlinTests {
|
||||||
Assertions.assertThat(value).isEqualTo(1L)
|
Assertions.assertThat(value).isEqualTo(1L)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun valueClassReturnValue() {
|
||||||
|
val value = getInvocable(ValueClassHandler::valueClassReturnValue.javaMethod!!).invokeForRequest(request, null)
|
||||||
|
Assertions.assertThat(value).isEqualTo("foo")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun valueClassDefaultValue() {
|
fun valueClassDefaultValue() {
|
||||||
composite.addResolver(StubArgumentResolver(Double::class.java))
|
composite.addResolver(StubArgumentResolver(Double::class.java))
|
||||||
|
@ -200,6 +206,9 @@ class InvocableHandlerMethodKotlinTests {
|
||||||
|
|
||||||
private class ValueClassHandler {
|
private class ValueClassHandler {
|
||||||
|
|
||||||
|
fun valueClassReturnValue() =
|
||||||
|
StringValueClass("foo")
|
||||||
|
|
||||||
fun longValueClass(limit: LongValueClass) =
|
fun longValueClass(limit: LongValueClass) =
|
||||||
limit.value
|
limit.value
|
||||||
|
|
||||||
|
@ -246,6 +255,9 @@ class InvocableHandlerMethodKotlinTests {
|
||||||
|
|
||||||
data class Animal(override val name: String) : Named
|
data class Animal(override val name: String) : Named
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class StringValueClass(val value: String)
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class LongValueClass(val value: Long)
|
value class LongValueClass(val value: Long)
|
||||||
|
|
||||||
|
|
|
@ -325,7 +325,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||||
@Nullable
|
@Nullable
|
||||||
@SuppressWarnings({"deprecation", "DataFlowIssue"})
|
@SuppressWarnings({"deprecation", "DataFlowIssue"})
|
||||||
public static Object invokeFunction(Method method, Object target, Object[] args, boolean isSuspendingFunction,
|
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) {
|
if (isSuspendingFunction) {
|
||||||
Object coroutineContext = exchange.getAttribute(COROUTINE_CONTEXT_ATTRIBUTE);
|
Object coroutineContext = exchange.getAttribute(COROUTINE_CONTEXT_ATTRIBUTE);
|
||||||
|
@ -369,6 +369,9 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Object result = function.callBy(argMap);
|
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);
|
return (result == Unit.INSTANCE ? null : result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,6 +205,13 @@ class InvocableHandlerMethodKotlinTests {
|
||||||
assertHandlerResultValue(result, "1")
|
assertHandlerResultValue(result, "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun valueClassReturnValue() {
|
||||||
|
val method = ValueClassController::valueClassReturnValue.javaMethod!!
|
||||||
|
val result = invoke(ValueClassController(), method,)
|
||||||
|
assertHandlerResultValue(result, "foo")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun valueClassWithDefaultValue() {
|
fun valueClassWithDefaultValue() {
|
||||||
this.resolvers.add(stubResolver(null, Double::class.java))
|
this.resolvers.add(stubResolver(null, Double::class.java))
|
||||||
|
@ -376,6 +383,9 @@ class InvocableHandlerMethodKotlinTests {
|
||||||
fun valueClass(limit: LongValueClass) =
|
fun valueClass(limit: LongValueClass) =
|
||||||
"${limit.value}"
|
"${limit.value}"
|
||||||
|
|
||||||
|
fun valueClassReturnValue() =
|
||||||
|
StringValueClass("foo")
|
||||||
|
|
||||||
fun valueClassWithDefault(limit: DoubleValueClass = DoubleValueClass(3.1)) =
|
fun valueClassWithDefault(limit: DoubleValueClass = DoubleValueClass(3.1)) =
|
||||||
"${limit.value}"
|
"${limit.value}"
|
||||||
|
|
||||||
|
@ -420,6 +430,9 @@ class InvocableHandlerMethodKotlinTests {
|
||||||
|
|
||||||
data class Animal(override val name: String) : Named
|
data class Animal(override val name: String) : Named
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class StringValueClass(val value: String)
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class LongValueClass(val value: Long)
|
value class LongValueClass(val value: Long)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue