Fix a regression with Kotlin generic controllers
This commit reintroduces a more defensive parameter type check that skips KClass casting for generic parameters. Closes gh-32510
This commit is contained in:
parent
e17c3d3be4
commit
1e80694daf
|
|
@ -128,11 +128,9 @@ public abstract class CoroutinesUtils {
|
||||||
Object arg = args[index];
|
Object arg = args[index];
|
||||||
if (!(parameter.isOptional() && arg == null)) {
|
if (!(parameter.isOptional() && arg == null)) {
|
||||||
KType type = parameter.getType();
|
KType type = parameter.getType();
|
||||||
if (!(type.isMarkedNullable() && arg == null)) {
|
if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass<?> kClass
|
||||||
KClass<?> kClass = (KClass<?>) type.getClassifier();
|
&& KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
|
||||||
if (KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
|
arg = KClasses.getPrimaryConstructor(kClass).call(arg);
|
||||||
arg = KClasses.getPrimaryConstructor(kClass).call(arg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
argMap.put(parameter, arg);
|
argMap.put(parameter, arg);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
suspend fun suspendingFunction(value: String): String {
|
||||||
delay(1)
|
delay(1)
|
||||||
return value
|
return value
|
||||||
|
|
@ -293,6 +303,22 @@ class CoroutinesUtilsTests {
|
||||||
return "${this.message}-$limit"
|
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
|
@JvmInline
|
||||||
value class ValueClass(val value: String)
|
value class ValueClass(val value: String)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -317,11 +317,9 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||||
Object arg = args[index];
|
Object arg = args[index];
|
||||||
if (!(parameter.isOptional() && arg == null)) {
|
if (!(parameter.isOptional() && arg == null)) {
|
||||||
KType type = parameter.getType();
|
KType type = parameter.getType();
|
||||||
if (!(type.isMarkedNullable() && arg == null)) {
|
if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass<?> kClass
|
||||||
KClass<?> kClass = (KClass<?>) type.getClassifier();
|
&& KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
|
||||||
if (KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
|
arg = KClasses.getPrimaryConstructor(kClass).call(arg);
|
||||||
arg = KClasses.getPrimaryConstructor(kClass).call(arg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
argMap.put(parameter, arg);
|
argMap.put(parameter, arg);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -25,7 +25,7 @@ import org.springframework.web.testfixture.servlet.MockHttpServletRequest
|
||||||
import org.springframework.web.testfixture.servlet.MockHttpServletResponse
|
import org.springframework.web.testfixture.servlet.MockHttpServletResponse
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kotlin unit tests for {@link InvocableHandlerMethod}.
|
* Kotlin unit tests for [InvocableHandlerMethod].
|
||||||
*
|
*
|
||||||
* @author Sebastien Deleuze
|
* @author Sebastien Deleuze
|
||||||
*/
|
*/
|
||||||
|
|
@ -134,6 +134,14 @@ class InvocableHandlerMethodKotlinTests {
|
||||||
Assertions.assertThat(value).isEqualTo("foo-20")
|
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 {
|
private fun getInvocable(clazz: Class<*>, vararg argTypes: Class<*>): InvocableHandlerMethod {
|
||||||
val method = ResolvableMethod.on(clazz).argTypes(*argTypes).resolveMethod()
|
val method = ResolvableMethod.on(clazz).argTypes(*argTypes).resolveMethod()
|
||||||
val handlerMethod = InvocableHandlerMethod(clazz.constructors.first().newInstance(), method)
|
val handlerMethod = InvocableHandlerMethod(clazz.constructors.first().newInstance(), method)
|
||||||
|
|
@ -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
|
@JvmInline
|
||||||
value class LongValueClass(val value: Long)
|
value class LongValueClass(val value: Long)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -328,11 +328,9 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||||
Object arg = args[index];
|
Object arg = args[index];
|
||||||
if (!(parameter.isOptional() && arg == null)) {
|
if (!(parameter.isOptional() && arg == null)) {
|
||||||
KType type = parameter.getType();
|
KType type = parameter.getType();
|
||||||
if (!(type.isMarkedNullable() && arg == null)) {
|
if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass<?> kClass
|
||||||
KClass<?> kClass = (KClass<?>) type.getClassifier();
|
&& KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
|
||||||
if (KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
|
arg = KClasses.getPrimaryConstructor(kClass).call(arg);
|
||||||
arg = KClasses.getPrimaryConstructor(kClass).call(arg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
argMap.put(parameter, arg);
|
argMap.put(parameter, arg);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -249,6 +249,15 @@ class InvocableHandlerMethodKotlinTests {
|
||||||
assertHandlerResultValue(result, "foo-20")
|
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? {
|
private fun invokeForResult(handler: Any, method: Method, vararg providedArgs: Any): HandlerResult? {
|
||||||
return invoke(handler, method, *providedArgs).block(Duration.ofSeconds(5))
|
return invoke(handler, method, *providedArgs).block(Duration.ofSeconds(5))
|
||||||
|
|
@ -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
|
@JvmInline
|
||||||
value class LongValueClass(val value: Long)
|
value class LongValueClass(val value: Long)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue