Recursively box/unbox nested inline value classes

See gh-34592
Signed-off-by: Dmitry Sulman <dmitry.sulman@gmail.com>
This commit is contained in:
Dmitry Sulman 2025-03-13 14:57:57 +02:00 committed by Sébastien Deleuze
parent c6a9aa59a3
commit 0c2ba4e38e
4 changed files with 91 additions and 15 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,6 +20,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import kotlin.Unit;
import kotlin.jvm.JvmClassMappingKt;
@ -322,11 +323,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
KType type = parameter.getType();
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);
}
@ -341,6 +338,19 @@ public class InvocableHandlerMethod extends HandlerMethod {
return (result == Unit.INSTANCE ? null : result);
}
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 void handleResult(Object result, SynchronousSink<Object> sink) {
if (KotlinDetector.isInlineClass(result.getClass())) {
try {
@ -361,7 +371,11 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
private static Object unbox(Object result) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
return result.getClass().getDeclaredMethod("unbox-impl").invoke(result);
Object unboxed = result.getClass().getDeclaredMethod("unbox-impl").invoke(result);
if (KotlinDetector.isInlineClass(unboxed.getClass())) {
return unbox(unboxed);
}
return unboxed;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -109,12 +109,25 @@ class InvocableHandlerMethodKotlinTests {
Assertions.assertThat(value).isEqualTo(1L)
}
@Test
fun nestedValueClass() {
composite.addResolver(StubArgumentResolver(Long::class.java, 1L))
val value = getInvocable(ValueClassHandler::nestedValueClass.javaMethod!!).invokeForRequest(request, null)
Assertions.assertThat(value).isEqualTo(1L)
}
@Test
fun valueClassReturnValue() {
val value = getInvocable(ValueClassHandler::valueClassReturnValue.javaMethod!!).invokeForRequest(request, null)
Assertions.assertThat(value).isEqualTo("foo")
}
@Test
fun nestedValueClassReturnValue() {
val value = getInvocable(ValueClassHandler::nestedValueClassReturnValue.javaMethod!!).invokeForRequest(request, null)
Assertions.assertThat(value).isEqualTo("foo")
}
@Test
fun resultOfUnitReturnValue() {
val value = getInvocable(ValueClassHandler::resultOfUnitReturnValue.javaMethod!!).invokeForRequest(request, null)
@ -273,10 +286,14 @@ class InvocableHandlerMethodKotlinTests {
fun valueClassReturnValue() = StringValueClass("foo")
fun nestedValueClassReturnValue() = NestedStringValueClass(StringValueClass("foo"))
fun resultOfUnitReturnValue() = Result.success(Unit)
fun longValueClass(limit: LongValueClass) = limit.value
fun nestedValueClass(limit: ULongValueClass) = limit.value
fun doubleValueClass(limit: DoubleValueClass = DoubleValueClass(3.1)) = limit.value
fun valueClassWithInit(valueClass: ValueClassWithInit) = valueClass
@ -358,9 +375,15 @@ class InvocableHandlerMethodKotlinTests {
@JvmInline
value class StringValueClass(val value: String)
@JvmInline
value class NestedStringValueClass(val value: StringValueClass)
@JvmInline
value class LongValueClass(val value: Long)
@JvmInline
value class ULongValueClass(val value: ULong)
@JvmInline
value class DoubleValueClass(val value: Double)

View File

@ -23,6 +23,7 @@ import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import kotlin.Unit;
@ -361,11 +362,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
KType type = parameter.getType();
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);
}
@ -381,6 +378,19 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
}
private static Object box(KClass<?> kClass, 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 void handleResult(Object result, SynchronousSink<Object> sink) {
if (KotlinDetector.isInlineClass(result.getClass())) {
try {
@ -401,7 +411,11 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
private static Object unbox(Object result) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
return result.getClass().getDeclaredMethod("unbox-impl").invoke(result);
Object unboxed = result.getClass().getDeclaredMethod("unbox-impl").invoke(result);
if (KotlinDetector.isInlineClass(unboxed.getClass())) {
return unbox(unboxed);
}
return unboxed;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -205,6 +205,14 @@ class InvocableHandlerMethodKotlinTests {
assertHandlerResultValue(result, "1")
}
@Test
fun nestedValueClass() {
this.resolvers.add(stubResolver(1L, Long::class.java))
val method = ValueClassController::nestedValueClass.javaMethod!!
val result = invoke(ValueClassController(), method,1L)
assertHandlerResultValue(result, "1")
}
@Test
fun valueClassReturnValue() {
val method = ValueClassController::valueClassReturnValue.javaMethod!!
@ -212,6 +220,13 @@ class InvocableHandlerMethodKotlinTests {
assertHandlerResultValue(result, "foo")
}
@Test
fun nestedValueClassReturnValue() {
val method = ValueClassController::nestedValueClassReturnValue.javaMethod!!
val result = invoke(ValueClassController(), method)
assertHandlerResultValue(result, "foo")
}
@Test
fun resultOfUnitReturnValue() {
val method = ValueClassController::resultOfUnitReturnValue.javaMethod!!
@ -448,8 +463,12 @@ class InvocableHandlerMethodKotlinTests {
fun valueClass(limit: LongValueClass) = "${limit.value}"
fun nestedValueClass(limit: ULongValueClass) = "${limit.value}"
fun valueClassReturnValue() = StringValueClass("foo")
fun nestedValueClassReturnValue() = NestedStringValueClass(StringValueClass("foo"))
fun resultOfUnitReturnValue() = Result.success(Unit)
fun valueClassWithDefault(limit: DoubleValueClass = DoubleValueClass(3.1)) = "${limit.value}"
@ -533,9 +552,15 @@ class InvocableHandlerMethodKotlinTests {
@JvmInline
value class StringValueClass(val value: String)
@JvmInline
value class NestedStringValueClass(val value: StringValueClass)
@JvmInline
value class LongValueClass(val value: Long)
@JvmInline
value class ULongValueClass(val value: ULong)
@JvmInline
value class DoubleValueClass(val value: Double)