diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index 4c0df4f03d56..7b58cb43faa7 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -130,6 +130,12 @@ jar { } } +kotlin { + compilerOptions { + freeCompilerArgs.addAll("-Xcontext-parameters") + } +} + test { // Make sure the classes dir is used on the test classpath (required by ResourceTests). // When test fixtures are involved, the JAR is used by default. diff --git a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java index 020a00064c98..324ee6c09228 100644 --- a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java +++ b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java @@ -130,7 +130,7 @@ public static Publisher invokeSuspendingFunction( for (KParameter parameter : function.getParameters()) { switch (parameter.getKind()) { case INSTANCE -> argMap.put(parameter, target); - case VALUE, EXTENSION_RECEIVER -> { + case VALUE, EXTENSION_RECEIVER, CONTEXT -> { Object arg = args[index]; if (!(parameter.isOptional() && arg == null)) { KType type = parameter.getType(); diff --git a/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt b/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt index 0f3f4312d2c3..3f8aec8e92a0 100644 --- a/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt @@ -273,6 +273,26 @@ class CoroutinesUtilsTests { Assertions.assertThat(mono.awaitSingleOrNull()).isEqualTo("foo-20") } + @Test + fun invokeSuspendingFunctionWithContextParameter() { + val method = CoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithContextParameter", + CustomException::class.java, Continuation::class.java) + val mono = CoroutinesUtils.invokeSuspendingFunction(method, this, CustomException("foo")) as Mono + runBlocking { + Assertions.assertThat(mono.awaitSingleOrNull()).isEqualTo("foo") + } + } + + @Test + fun invokeSuspendingFunctionWithContextParameterAndParameter() { + val method = CoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithContextParameterAndParameter", + CustomException::class.java, Int::class.java, Continuation::class.java) + val mono = CoroutinesUtils.invokeSuspendingFunction(method, this, CustomException("foo"), 20) as Mono + runBlocking { + Assertions.assertThat(mono.awaitSingleOrNull()).isEqualTo("foo-20") + } + } + @Test suspend fun invokeSuspendingFunctionWithGenericParameter() { val method = GenericController::class.java.declaredMethods.first { it.name.startsWith("handle") } @@ -388,6 +408,18 @@ class CoroutinesUtilsTests { return "${this.message}-$limit" } + context(value: CustomException) + suspend fun suspendingFunctionWithContextParameter(): String { + delay(1) + return "${value.message}" + } + + context(value: CustomException) + suspend fun suspendingFunctionWithContextParameterAndParameter(limit: Int): String { + delay(1) + return "${value.message}-$limit" + } + interface Named { val name: String } diff --git a/spring-web/spring-web.gradle b/spring-web/spring-web.gradle index df797daae639..6863bd3ba416 100644 --- a/spring-web/spring-web.gradle +++ b/spring-web/spring-web.gradle @@ -104,3 +104,9 @@ dependencies { testRuntimeOnly("org.glassfish:jakarta.el") testRuntimeOnly("org.hibernate.validator:hibernate-validator") } + +kotlin { + compilerOptions { + freeCompilerArgs.addAll("-Xcontext-parameters") + } +} \ No newline at end of file diff --git a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java index 1a1ef3a011ad..b3b12cb12091 100644 --- a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java @@ -312,7 +312,7 @@ private static class KotlinDelegate { for (KParameter parameter : function.getParameters()) { switch (parameter.getKind()) { case INSTANCE -> argMap.put(parameter, target); - case VALUE, EXTENSION_RECEIVER -> { + case VALUE, EXTENSION_RECEIVER, CONTEXT -> { Object arg = args[index]; if (!(parameter.isOptional() && arg == null)) { KType type = parameter.getType(); diff --git a/spring-web/src/test/kotlin/org/springframework/web/method/support/InvocableHandlerMethodKotlinTests.kt b/spring-web/src/test/kotlin/org/springframework/web/method/support/InvocableHandlerMethodKotlinTests.kt index a3dbd8f91a43..c8bea27c81c9 100644 --- a/spring-web/src/test/kotlin/org/springframework/web/method/support/InvocableHandlerMethodKotlinTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/web/method/support/InvocableHandlerMethodKotlinTests.kt @@ -260,6 +260,22 @@ class InvocableHandlerMethodKotlinTests { Assertions.assertThat(value).isEqualTo("foo-20") } + @Test + fun contextParameter() { + composite.addResolver(StubArgumentResolver(CustomException::class.java, CustomException("foo"))) + val value = getInvocable(ReflectionUtils.findMethod(ContextParameterHandler::class.java, "handle", CustomException::class.java)!!).invokeForRequest(request, null) + Assertions.assertThat(value).isEqualTo("foo") + } + + @Test + fun contextParameterWithParameter() { + composite.addResolver(StubArgumentResolver(CustomException::class.java, CustomException("foo"))) + composite.addResolver(StubArgumentResolver(Int::class.java, 20)) + val value = getInvocable(ReflectionUtils.findMethod(ContextParameterHandler::class.java, "handleWithParameter", CustomException::class.java, Int::class.java)!!) + .invokeForRequest(request, null) + Assertions.assertThat(value).isEqualTo("foo-20") + } + @Test fun genericParameter() { val horse = Animal("horse") @@ -381,6 +397,19 @@ class InvocableHandlerMethodKotlinTests { } } + private class ContextParameterHandler { + + context(exception: CustomException) + fun handle(): String { + return "${exception.message}" + } + + context(exception: CustomException) + fun handleWithParameter(limit: Int): String { + return "${exception.message}-$limit" + } + } + private abstract class GenericHandler { fun handle(named: T) = named.name