Skip to content

Commit

Permalink
Fix a regression with Kotlin generic controllers
Browse files Browse the repository at this point in the history
This commit reintroduces a more defensive parameter type check that
skips KClass casting for generic parameters.

Closes spring-projectsgh-32510
  • Loading branch information
sdeleuze committed Mar 21, 2024
1 parent e17c3d3 commit 1e80694
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,9 @@ public static Publisher<?> invokeSuspendingFunction(
Object arg = args[index];
if (!(parameter.isOptional() && arg == null)) {
KType type = parameter.getType();
if (!(type.isMarkedNullable() && arg == null)) {
KClass<?> kClass = (KClass<?>) type.getClassifier();
if (KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
arg = KClasses.getPrimaryConstructor(kClass).call(arg);
}
if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass<?> kClass
&& KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
arg = KClasses.getPrimaryConstructor(kClass).call(arg);
}
argMap.put(parameter, arg);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,16 @@ class CoroutinesUtilsTests {
}
}

@Test
fun invokeSuspendingFunctionWithGenericParameter() {
val method = GenericController::class.java.declaredMethods.first { it.name.startsWith("handle") }
val horse = Animal("horse")
val mono = CoroutinesUtils.invokeSuspendingFunction(method, AnimalController(), horse, null) as Mono
runBlocking {
Assertions.assertThat(mono.awaitSingle()).isEqualTo(horse.name)
}
}

suspend fun suspendingFunction(value: String): String {
delay(1)
return value
Expand Down Expand Up @@ -293,6 +303,22 @@ class CoroutinesUtilsTests {
return "${this.message}-$limit"
}

interface Named {
val name: String
}

data class Animal(override val name: String) : Named

abstract class GenericController<T : Named> {

suspend fun handle(named: T): String {
delay(1)
return named.name;
}
}

private class AnimalController : GenericController<Animal>()

@JvmInline
value class ValueClass(val value: String)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,9 @@ public static Object invokeFunction(Method method, Object target, Object[] args)
Object arg = args[index];
if (!(parameter.isOptional() && arg == null)) {
KType type = parameter.getType();
if (!(type.isMarkedNullable() && arg == null)) {
KClass<?> kClass = (KClass<?>) type.getClassifier();
if (KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
arg = KClasses.getPrimaryConstructor(kClass).call(arg);
}
if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass<?> kClass
&& KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
arg = KClasses.getPrimaryConstructor(kClass).call(arg);
}
argMap.put(parameter, arg);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 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.
Expand All @@ -25,7 +25,7 @@ import org.springframework.web.testfixture.servlet.MockHttpServletRequest
import org.springframework.web.testfixture.servlet.MockHttpServletResponse

/**
* Kotlin unit tests for {@link InvocableHandlerMethod}.
* Kotlin unit tests for [InvocableHandlerMethod].
*
* @author Sebastien Deleuze
*/
Expand Down Expand Up @@ -134,6 +134,14 @@ class InvocableHandlerMethodKotlinTests {
Assertions.assertThat(value).isEqualTo("foo-20")
}

@Test
fun genericParameter() {
val horse = Animal("horse")
composite.addResolver(StubArgumentResolver(Animal::class.java, horse))
val value = getInvocable(AnimalHandler::class.java, Named::class.java).invokeForRequest(request, null)
Assertions.assertThat(value).isEqualTo(horse.name)
}

private fun getInvocable(clazz: Class<*>, vararg argTypes: Class<*>): InvocableHandlerMethod {
val method = ResolvableMethod.on(clazz).argTypes(*argTypes).resolveMethod()
val handlerMethod = InvocableHandlerMethod(clazz.constructors.first().newInstance(), method)
Expand Down Expand Up @@ -202,6 +210,19 @@ class InvocableHandlerMethodKotlinTests {
}
}

private abstract class GenericHandler<T : Named> {

fun handle(named: T) = named.name
}

private class AnimalHandler : GenericHandler<Animal>()

interface Named {
val name: String
}

data class Animal(override val name: String) : Named

@JvmInline
value class LongValueClass(val value: Long)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,11 +328,9 @@ public static Object invokeFunction(Method method, Object target, Object[] args,
Object arg = args[index];
if (!(parameter.isOptional() && arg == null)) {
KType type = parameter.getType();
if (!(type.isMarkedNullable() && arg == null)) {
KClass<?> kClass = (KClass<?>) type.getClassifier();
if (KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
arg = KClasses.getPrimaryConstructor(kClass).call(arg);
}
if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass<?> kClass
&& KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
arg = KClasses.getPrimaryConstructor(kClass).call(arg);
}
argMap.put(parameter, arg);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 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.
Expand Down Expand Up @@ -249,6 +249,15 @@ class InvocableHandlerMethodKotlinTests {
assertHandlerResultValue(result, "foo-20")
}

@Test
fun genericParameter() {
val horse = Animal("horse")
this.resolvers.add(stubResolver(horse))
val method = AnimalController::handle.javaMethod!!
val result = invoke(AnimalController(), method, null)
assertHandlerResultValue(result, horse.name)
}


private fun invokeForResult(handler: Any, method: Method, vararg providedArgs: Any): HandlerResult? {
return invoke(handler, method, *providedArgs).block(Duration.ofSeconds(5))
Expand Down Expand Up @@ -379,6 +388,19 @@ class InvocableHandlerMethodKotlinTests {
}
}

private abstract class GenericController<T : Named> {

fun handle(named: T) = named.name
}

private class AnimalController : GenericController<Animal>()

interface Named {
val name: String
}

data class Animal(override val name: String) : Named

@JvmInline
value class LongValueClass(val value: Long)

Expand Down

0 comments on commit 1e80694

Please sign in to comment.