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 (paramType == null) {
|
||||||
if (this.parameterIndex < 0) {
|
if (this.parameterIndex < 0) {
|
||||||
Method method = getMethod();
|
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 {
|
else {
|
||||||
paramType = this.executable.getParameterTypes()[this.parameterIndex];
|
paramType = this.executable.getParameterTypes()[this.parameterIndex];
|
||||||
|
|
@ -422,7 +424,9 @@ public class MethodParameter {
|
||||||
if (paramType == null) {
|
if (paramType == null) {
|
||||||
if (this.parameterIndex < 0) {
|
if (this.parameterIndex < 0) {
|
||||||
Method method = getMethod();
|
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 {
|
else {
|
||||||
Type[] genericParameterTypes = this.executable.getGenericParameterTypes();
|
Type[] genericParameterTypes = this.executable.getGenericParameterTypes();
|
||||||
|
|
@ -799,6 +803,32 @@ public class MethodParameter {
|
||||||
}
|
}
|
||||||
return false;
|
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
|
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.Test
|
||||||
import org.junit.Assert.*
|
|
||||||
import java.lang.reflect.Method
|
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].
|
* Tests for Kotlin support in [MethodParameter].
|
||||||
|
|
@ -25,6 +30,7 @@ import java.lang.reflect.Method
|
||||||
* @author Raman Gupta
|
* @author Raman Gupta
|
||||||
* @author Sebastien Deleuze
|
* @author Sebastien Deleuze
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
* @author Konrad Kaminski
|
||||||
*/
|
*/
|
||||||
class KotlinMethodParameterTests {
|
class KotlinMethodParameterTests {
|
||||||
|
|
||||||
|
|
@ -67,6 +73,43 @@ class KotlinMethodParameterTests {
|
||||||
assertTrue(MethodParameter(regularClassConstructor, 1).isOptional)
|
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")
|
@Suppress("unused_parameter")
|
||||||
fun nullable(nullable: String?): Int? = 42
|
fun nullable(nullable: String?): Int? = 42
|
||||||
|
|
@ -82,4 +125,33 @@ class KotlinMethodParameterTests {
|
||||||
@Suppress("unused_parameter")
|
@Suppress("unused_parameter")
|
||||||
class RegularClass(nonNullable: String, nullable: String?)
|
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