Skip to content

Commit 0c2ba4e

Browse files
dmitrysulmansdeleuze
authored andcommittedMar 18, 2025
Recursively box/unbox nested inline value classes
See gh-34592 Signed-off-by: Dmitry Sulman <[email protected]>
1 parent c6a9aa5 commit 0c2ba4e

File tree

4 files changed

+91
-15
lines changed

4 files changed

+91
-15
lines changed
 

‎spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java

+21-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.lang.reflect.Method;
2121
import java.util.Arrays;
2222
import java.util.Map;
23+
import java.util.Objects;
2324

2425
import kotlin.Unit;
2526
import kotlin.jvm.JvmClassMappingKt;
@@ -322,11 +323,7 @@ public static Object invokeFunction(Method method, Object target, Object[] args)
322323
KType type = parameter.getType();
323324
if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass<?> kClass
324325
&& KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
325-
KFunction<?> constructor = KClasses.getPrimaryConstructor(kClass);
326-
if (!KCallablesJvm.isAccessible(constructor)) {
327-
KCallablesJvm.setAccessible(constructor, true);
328-
}
329-
arg = constructor.call(arg);
326+
arg = box(kClass, arg);
330327
}
331328
argMap.put(parameter, arg);
332329
}
@@ -341,6 +338,19 @@ public static Object invokeFunction(Method method, Object target, Object[] args)
341338
return (result == Unit.INSTANCE ? null : result);
342339
}
343340

341+
private static Object box(KClass<?> kClass, @Nullable Object arg) {
342+
KFunction<?> constructor = Objects.requireNonNull(KClasses.getPrimaryConstructor(kClass));
343+
KType type = constructor.getParameters().get(0).getType();
344+
if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass<?> parameterClass
345+
&& KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(parameterClass))) {
346+
arg = box(parameterClass, arg);
347+
}
348+
if (!KCallablesJvm.isAccessible(constructor)) {
349+
KCallablesJvm.setAccessible(constructor, true);
350+
}
351+
return constructor.call(arg);
352+
}
353+
344354
private static void handleResult(Object result, SynchronousSink<Object> sink) {
345355
if (KotlinDetector.isInlineClass(result.getClass())) {
346356
try {
@@ -361,7 +371,11 @@ private static void handleResult(Object result, SynchronousSink<Object> sink) {
361371
}
362372

363373
private static Object unbox(Object result) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
364-
return result.getClass().getDeclaredMethod("unbox-impl").invoke(result);
374+
Object unboxed = result.getClass().getDeclaredMethod("unbox-impl").invoke(result);
375+
if (KotlinDetector.isInlineClass(unboxed.getClass())) {
376+
return unbox(unboxed);
377+
}
378+
return unboxed;
365379
}
366380
}
367381

‎spring-web/src/test/kotlin/org/springframework/web/method/support/InvocableHandlerMethodKotlinTests.kt

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -109,12 +109,25 @@ class InvocableHandlerMethodKotlinTests {
109109
Assertions.assertThat(value).isEqualTo(1L)
110110
}
111111

112+
@Test
113+
fun nestedValueClass() {
114+
composite.addResolver(StubArgumentResolver(Long::class.java, 1L))
115+
val value = getInvocable(ValueClassHandler::nestedValueClass.javaMethod!!).invokeForRequest(request, null)
116+
Assertions.assertThat(value).isEqualTo(1L)
117+
}
118+
112119
@Test
113120
fun valueClassReturnValue() {
114121
val value = getInvocable(ValueClassHandler::valueClassReturnValue.javaMethod!!).invokeForRequest(request, null)
115122
Assertions.assertThat(value).isEqualTo("foo")
116123
}
117124

125+
@Test
126+
fun nestedValueClassReturnValue() {
127+
val value = getInvocable(ValueClassHandler::nestedValueClassReturnValue.javaMethod!!).invokeForRequest(request, null)
128+
Assertions.assertThat(value).isEqualTo("foo")
129+
}
130+
118131
@Test
119132
fun resultOfUnitReturnValue() {
120133
val value = getInvocable(ValueClassHandler::resultOfUnitReturnValue.javaMethod!!).invokeForRequest(request, null)
@@ -273,10 +286,14 @@ class InvocableHandlerMethodKotlinTests {
273286

274287
fun valueClassReturnValue() = StringValueClass("foo")
275288

289+
fun nestedValueClassReturnValue() = NestedStringValueClass(StringValueClass("foo"))
290+
276291
fun resultOfUnitReturnValue() = Result.success(Unit)
277292

278293
fun longValueClass(limit: LongValueClass) = limit.value
279294

295+
fun nestedValueClass(limit: ULongValueClass) = limit.value
296+
280297
fun doubleValueClass(limit: DoubleValueClass = DoubleValueClass(3.1)) = limit.value
281298

282299
fun valueClassWithInit(valueClass: ValueClassWithInit) = valueClass
@@ -358,9 +375,15 @@ class InvocableHandlerMethodKotlinTests {
358375
@JvmInline
359376
value class StringValueClass(val value: String)
360377

378+
@JvmInline
379+
value class NestedStringValueClass(val value: StringValueClass)
380+
361381
@JvmInline
362382
value class LongValueClass(val value: Long)
363383

384+
@JvmInline
385+
value class ULongValueClass(val value: ULong)
386+
364387
@JvmInline
365388
value class DoubleValueClass(val value: Double)
366389

‎spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java

+20-6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.ArrayList;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.Objects;
2627
import java.util.stream.Stream;
2728

2829
import kotlin.Unit;
@@ -361,11 +362,7 @@ public static Object invokeFunction(Method method, Object target, Object[] args,
361362
KType type = parameter.getType();
362363
if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass<?> kClass
363364
&& KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
364-
KFunction<?> constructor = KClasses.getPrimaryConstructor(kClass);
365-
if (!KCallablesJvm.isAccessible(constructor)) {
366-
KCallablesJvm.setAccessible(constructor, true);
367-
}
368-
arg = constructor.call(arg);
365+
arg = box(kClass, arg);
369366
}
370367
argMap.put(parameter, arg);
371368
}
@@ -381,6 +378,19 @@ public static Object invokeFunction(Method method, Object target, Object[] args,
381378
}
382379
}
383380

381+
private static Object box(KClass<?> kClass, Object arg) {
382+
KFunction<?> constructor = Objects.requireNonNull(KClasses.getPrimaryConstructor(kClass));
383+
KType type = constructor.getParameters().get(0).getType();
384+
if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass<?> parameterClass
385+
&& KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(parameterClass))) {
386+
arg = box(parameterClass, arg);
387+
}
388+
if (!KCallablesJvm.isAccessible(constructor)) {
389+
KCallablesJvm.setAccessible(constructor, true);
390+
}
391+
return constructor.call(arg);
392+
}
393+
384394
private static void handleResult(Object result, SynchronousSink<Object> sink) {
385395
if (KotlinDetector.isInlineClass(result.getClass())) {
386396
try {
@@ -401,7 +411,11 @@ private static void handleResult(Object result, SynchronousSink<Object> sink) {
401411
}
402412

403413
private static Object unbox(Object result) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
404-
return result.getClass().getDeclaredMethod("unbox-impl").invoke(result);
414+
Object unboxed = result.getClass().getDeclaredMethod("unbox-impl").invoke(result);
415+
if (KotlinDetector.isInlineClass(unboxed.getClass())) {
416+
return unbox(unboxed);
417+
}
418+
return unboxed;
405419
}
406420

407421
}

‎spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/InvocableHandlerMethodKotlinTests.kt

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -205,13 +205,28 @@ class InvocableHandlerMethodKotlinTests {
205205
assertHandlerResultValue(result, "1")
206206
}
207207

208+
@Test
209+
fun nestedValueClass() {
210+
this.resolvers.add(stubResolver(1L, Long::class.java))
211+
val method = ValueClassController::nestedValueClass.javaMethod!!
212+
val result = invoke(ValueClassController(), method,1L)
213+
assertHandlerResultValue(result, "1")
214+
}
215+
208216
@Test
209217
fun valueClassReturnValue() {
210218
val method = ValueClassController::valueClassReturnValue.javaMethod!!
211219
val result = invoke(ValueClassController(), method)
212220
assertHandlerResultValue(result, "foo")
213221
}
214222

223+
@Test
224+
fun nestedValueClassReturnValue() {
225+
val method = ValueClassController::nestedValueClassReturnValue.javaMethod!!
226+
val result = invoke(ValueClassController(), method)
227+
assertHandlerResultValue(result, "foo")
228+
}
229+
215230
@Test
216231
fun resultOfUnitReturnValue() {
217232
val method = ValueClassController::resultOfUnitReturnValue.javaMethod!!
@@ -448,8 +463,12 @@ class InvocableHandlerMethodKotlinTests {
448463

449464
fun valueClass(limit: LongValueClass) = "${limit.value}"
450465

466+
fun nestedValueClass(limit: ULongValueClass) = "${limit.value}"
467+
451468
fun valueClassReturnValue() = StringValueClass("foo")
452469

470+
fun nestedValueClassReturnValue() = NestedStringValueClass(StringValueClass("foo"))
471+
453472
fun resultOfUnitReturnValue() = Result.success(Unit)
454473

455474
fun valueClassWithDefault(limit: DoubleValueClass = DoubleValueClass(3.1)) = "${limit.value}"
@@ -533,9 +552,15 @@ class InvocableHandlerMethodKotlinTests {
533552
@JvmInline
534553
value class StringValueClass(val value: String)
535554

555+
@JvmInline
556+
value class NestedStringValueClass(val value: StringValueClass)
557+
536558
@JvmInline
537559
value class LongValueClass(val value: Long)
538560

561+
@JvmInline
562+
value class ULongValueClass(val value: ULong)
563+
539564
@JvmInline
540565
value class DoubleValueClass(val value: Double)
541566

0 commit comments

Comments
 (0)
Please sign in to comment.