Support Kotlin suspending functions in MethodParameter
Before this commit, the return type for Kotlin suspending functions (as returned by MethodParameter#getParameterType and MethodParameter#getGenericReturnType methods) was incorrect. This change leverages Kotlin reflection instead of Java one to return the correct type. Closes gh-21058
This commit is contained in:
parent
5938742afd
commit
9302cb2f85
|
|
@ -402,7 +402,9 @@ public class MethodParameter {
|
|||
if (paramType == null) {
|
||||
if (this.parameterIndex < 0) {
|
||||
Method method = getMethod();
|
||||
paramType = (method != null ? method.getReturnType() : void.class);
|
||||
paramType = (method != null ?
|
||||
(KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass()) ?
|
||||
KotlinDelegate.getReturnType(method) : method.getReturnType()) : void.class);
|
||||
}
|
||||
else {
|
||||
paramType = this.executable.getParameterTypes()[this.parameterIndex];
|
||||
|
|
@ -422,7 +424,9 @@ public class MethodParameter {
|
|||
if (paramType == null) {
|
||||
if (this.parameterIndex < 0) {
|
||||
Method method = getMethod();
|
||||
paramType = (method != null ? method.getGenericReturnType() : void.class);
|
||||
paramType = (method != null ?
|
||||
(KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass()) ?
|
||||
KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class);
|
||||
}
|
||||
else {
|
||||
Type[] genericParameterTypes = this.executable.getGenericParameterTypes();
|
||||
|
|
@ -799,6 +803,32 @@ public class MethodParameter {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the generic return type of the method, with support of suspending
|
||||
* functions via Kotlin reflection.
|
||||
*/
|
||||
static private Type getGenericReturnType(Method method) {
|
||||
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
|
||||
if (function != null && function.isSuspend()) {
|
||||
return ReflectJvmMapping.getJavaType(function.getReturnType());
|
||||
}
|
||||
return method.getGenericReturnType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the return type of the method, with support of suspending
|
||||
* functions via Kotlin reflection.
|
||||
*/
|
||||
static private Class<?> getReturnType(Method method) {
|
||||
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
|
||||
if (function != null && function.isSuspend()) {
|
||||
Type paramType = ReflectJvmMapping.getJavaType(function.getReturnType());
|
||||
Class<?> paramClass = ResolvableType.forType(paramType).resolve();
|
||||
Assert.notNull(paramClass, "Type " + paramType + "can't be resolved to a class");
|
||||
return paramClass;
|
||||
}
|
||||
return method.getReturnType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.springframework.core.GenericTypeResolver.resolveReturnTypeArgument
|
||||
import java.lang.reflect.Method
|
||||
|
||||
/**
|
||||
* Tests for Kotlin support in [GenericTypeResolver].
|
||||
*
|
||||
* @author Konrad Kaminski
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class KotlinGenericTypeResolverTests {
|
||||
|
||||
@Test
|
||||
fun methodReturnTypes() {
|
||||
assertEquals(Integer::class.java, resolveReturnTypeArgument(findMethod(MyTypeWithMethods::class.java, "integer")!!,
|
||||
MyInterfaceType::class.java))
|
||||
assertEquals(String::class.java, resolveReturnTypeArgument(findMethod(MyTypeWithMethods::class.java, "string")!!,
|
||||
MyInterfaceType::class.java))
|
||||
assertEquals(null, resolveReturnTypeArgument(findMethod(MyTypeWithMethods::class.java, "raw")!!,
|
||||
MyInterfaceType::class.java))
|
||||
assertEquals(null, resolveReturnTypeArgument(findMethod(MyTypeWithMethods::class.java, "object")!!,
|
||||
MyInterfaceType::class.java))
|
||||
}
|
||||
|
||||
private fun findMethod(clazz: Class<*>, name: String): Method? =
|
||||
clazz.methods.firstOrNull { it.name == name }
|
||||
|
||||
open class MyTypeWithMethods<T> {
|
||||
suspend fun integer(): MyInterfaceType<Int>? = null
|
||||
|
||||
suspend fun string(): MySimpleInterfaceType? = null
|
||||
|
||||
suspend fun `object`(): Any? = null
|
||||
|
||||
suspend fun raw(): MyInterfaceType<*>? = null
|
||||
}
|
||||
|
||||
interface MyInterfaceType<T>
|
||||
|
||||
interface MySimpleInterfaceType: MyInterfaceType<String>
|
||||
|
||||
open class MySimpleTypeWithMethods: MyTypeWithMethods<Int>()
|
||||
}
|
||||
|
|
@ -15,9 +15,14 @@
|
|||
*/
|
||||
package org.springframework.core
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.Assert.*
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.TypeVariable
|
||||
import kotlin.reflect.full.declaredFunctions
|
||||
import kotlin.reflect.jvm.javaMethod
|
||||
|
||||
/**
|
||||
* Tests for Kotlin support in [MethodParameter].
|
||||
|
|
@ -25,6 +30,7 @@ import java.lang.reflect.Method
|
|||
* @author Raman Gupta
|
||||
* @author Sebastien Deleuze
|
||||
* @author Juergen Hoeller
|
||||
* @author Konrad Kaminski
|
||||
*/
|
||||
class KotlinMethodParameterTests {
|
||||
|
||||
|
|
@ -67,6 +73,43 @@ class KotlinMethodParameterTests {
|
|||
assertTrue(MethodParameter(regularClassConstructor, 1).isOptional)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Suspending function return type`() {
|
||||
assertEquals(Number::class.java, returnParameterType("suspendFun"))
|
||||
assertEquals(Number::class.java, returnGenericParameterType("suspendFun"))
|
||||
|
||||
assertEquals(Producer::class.java, returnParameterType("suspendFun2"))
|
||||
assertEquals("org.springframework.core.Producer<? extends java.lang.Number>", returnGenericParameterTypeName("suspendFun2"))
|
||||
|
||||
assertEquals(Wrapper::class.java, returnParameterType("suspendFun3"))
|
||||
assertEquals("org.springframework.core.Wrapper<java.lang.Number>", returnGenericParameterTypeName("suspendFun3"))
|
||||
|
||||
assertEquals(Consumer::class.java, returnParameterType("suspendFun4"))
|
||||
assertEquals("org.springframework.core.Consumer<? super java.lang.Number>", returnGenericParameterTypeName("suspendFun4"))
|
||||
|
||||
assertEquals(Producer::class.java, returnParameterType("suspendFun5"))
|
||||
assertTrue(returnGenericParameterType("suspendFun5") is TypeVariable<*>)
|
||||
assertEquals("org.springframework.core.Producer<? extends java.lang.Number>", returnGenericParameterTypeBoundName("suspendFun5"))
|
||||
|
||||
assertEquals(Wrapper::class.java, returnParameterType("suspendFun6"))
|
||||
assertTrue(returnGenericParameterType("suspendFun6") is TypeVariable<*>)
|
||||
assertEquals("org.springframework.core.Wrapper<java.lang.Number>", returnGenericParameterTypeBoundName("suspendFun6"))
|
||||
|
||||
assertEquals(Consumer::class.java, returnParameterType("suspendFun7"))
|
||||
assertTrue(returnGenericParameterType("suspendFun7") is TypeVariable<*>)
|
||||
assertEquals("org.springframework.core.Consumer<? super java.lang.Number>", returnGenericParameterTypeBoundName("suspendFun7"))
|
||||
|
||||
assertEquals(Object::class.java, returnParameterType("suspendFun8"))
|
||||
assertEquals(Object::class.java, returnGenericParameterType("suspendFun8"))
|
||||
}
|
||||
|
||||
private fun returnParameterType(funName: String) = returnMethodParameter(funName).parameterType
|
||||
private fun returnGenericParameterType(funName: String) = returnMethodParameter(funName).genericParameterType
|
||||
private fun returnGenericParameterTypeName(funName: String) = returnGenericParameterType(funName).typeName
|
||||
private fun returnGenericParameterTypeBoundName(funName: String) = (returnGenericParameterType(funName) as TypeVariable<*>).bounds[0].typeName
|
||||
|
||||
private fun returnMethodParameter(funName: String) =
|
||||
MethodParameter(this::class.declaredFunctions.first { it.name == funName }.javaMethod!!, -1)
|
||||
|
||||
@Suppress("unused_parameter")
|
||||
fun nullable(nullable: String?): Int? = 42
|
||||
|
|
@ -82,4 +125,33 @@ class KotlinMethodParameterTests {
|
|||
@Suppress("unused_parameter")
|
||||
class RegularClass(nonNullable: String, nullable: String?)
|
||||
|
||||
@Suppress("unused", "unused_parameter")
|
||||
suspend fun suspendFun(p1: String): Number = TODO()
|
||||
|
||||
@Suppress("unused", "unused_parameter")
|
||||
suspend fun suspendFun2(p1: String): Producer<Number> = TODO()
|
||||
|
||||
@Suppress("unused", "unused_parameter")
|
||||
suspend fun suspendFun3(p1: String): Wrapper<Number> = TODO()
|
||||
|
||||
@Suppress("unused", "unused_parameter")
|
||||
suspend fun suspendFun4(p1: String): Consumer<Number> = TODO()
|
||||
|
||||
@Suppress("unused", "unused_parameter")
|
||||
suspend fun <T: Producer<Number>> suspendFun5(p1: String): T = TODO()
|
||||
|
||||
@Suppress("unused", "unused_parameter")
|
||||
suspend fun <T: Wrapper<Number>> suspendFun6(p1: String): T = TODO()
|
||||
|
||||
@Suppress("unused", "unused_parameter")
|
||||
suspend fun <T: Consumer<Number>> suspendFun7(p1: String): T = TODO()
|
||||
|
||||
@Suppress("unused", "unused_parameter")
|
||||
suspend fun suspendFun8(p1: String): Any? = TODO()
|
||||
}
|
||||
|
||||
interface Producer<out T>
|
||||
|
||||
interface Wrapper<T>
|
||||
|
||||
interface Consumer<in T>
|
||||
|
|
|
|||
Loading…
Reference in New Issue