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:
Konrad Kamiński 2017-12-13 16:16:05 +01:00 committed by Sebastien Deleuze
parent 5938742afd
commit 9302cb2f85
3 changed files with 168 additions and 4 deletions

View File

@ -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();
}
}
}

View File

@ -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>()
}

View File

@ -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>