Declaration-site variance used in the very common kotlin.Pair
class seems to impact bean resolution and currently prevents bean resolution to work as expected by the end user.
Based on an original report on Twitter, I have created a more focused repro project.
Following test works with class Tuple<A, B>
but fails with class Tuple<out A, out B>
, which is pretty hard to diagnose when the user is using kotlin.Pair
which is provided in Kotlin standard library.
@RunWith(SpringRunner::class)
@ContextConfiguration(classes = [Config::class])
class TypeProjectionTest {
@Inject
private lateinit var ctx: Container<Tuple<String, String>>
@Test
fun testContext() {
}
}
// Variant 1: works when not using declaration-site variance
//class Tuple<A, B>
// Variant 2: Fails with using declaration-site variance
class Tuple<out A, out B>
private interface Container<T>
private class ContainerTuple : Container<Tuple<String, String>>
@Configuration
private open class Config {
@Bean
open fun containerPair() : ContainerTuple {
return ContainerTuple()
}
}
@jhoeller Do you think there is something to refine in our bean resolution algorithm to support this kind of Kotlin declaration-site variance?
Comment From: encircled
In the end in java code it is translated to Tuple<String, String>
and Tuple<? extends String, ? extends String>
, which is not allowed for injection (and should stay so I believe).
The only solution is to have kotlinized version of org.springframework.core.ResolvableType
, where generic type variance would be available and can be considered when looking for the injection candidates.
Comment From: sdeleuze
See also #20494 even more popular use case on collections.
Comment From: sdeleuze
Let's try to tackle that in parallel of #24033 since there may be some links related to type with resolved bounds. The fix implementation should not be tied to Kotlin (but we will introduce Kotlin tests for that).
Comment From: sdeleuze
Reopening as I mixed-up issue numbers in a recent commit message.
Comment From: sdeleuze
I have added the repro as a unit test here.
With declaration-site variance, there is a type mismatch in GenericTypeAwareAutowireCandidateResolver#checkGenericTypeMatch
here because in ResolvableType#isAssignableFrom(ResolvableType, Map<Type, Type>)
:
- The type of fun container() : TupleContainer
is resolved to Tuple<String, String>
- The type of lateinit var container: Container<Tuple<String, String>>
is resolved to Tuple<? extends String, ? extends String>
Tuple<String, String>
is not assignable from Tuple<? extends String, ? extends String>
so the function returns false.
Comment From: sdeleuze
I am waiting a feedback from Kotlin team, so I will move this issue to M3.
Comment From: sdeleuze
Kotlin team is on PTO, so I have to move this issue to RC1 (tentatively).
Comment From: sdeleuze
Kotlin team confirmed my proposal to resolve generic types with variance make sense, ~~should be testable on Java via use-site variance~~. So I will move forward with that strategy and see how it goes.
Comment From: sdeleuze
After a deeper look, it looks like we are going to need to leverage Kotlin reflection in either GenericTypeAwareAutowireCandidateResolver
or ResolvableType
bounds resolution because the Java algorithm looks correct, but there is no declaration-site variance to use for proper comparison.
We need to compare the use-side variance (available on both Java and Kotlin) to declaration-site variance (available only via Kotlin reflection).
The Kotlin team has confirmed there is no way to get a Ktype
from a JVM Type
so this is going to need extra logic to find the information; potentially via the KClass
.
Comment From: sdeleuze
I move this issue to 6.1.x
bucket in order to be able to move forward on CRaC and Coroutines refinements. Since this issue will likely be about Kotlin-specific refinements, it can probably come in a patch release.