thanksforallthefish
01/27/2020, 8:46 AMclass Test{}
class Another {
fun <E: Any> test2(test: E) = test.test()
fun <E: Any> E.test() =
println(javaClass.simpleName)
}
Another().test2(
Test()
)
prints Test
class Test{}
class Another {
fun <E: Any> test2(test: E) = test.test()
fun <E: Any> E.test() =
println(this.javaClass.simpleName)
}
Another().test2(
Test()
)
prints Test
Kiryushin Andrey
01/27/2020, 4:23 PMclass One {
val propFromTest = "prop from One"
}
class Another {
val propFromAnother = "prop from Another"
fun One.test() {
println(propFromTest)
println(propFromAnother)
}
fun test() {
One().test()
}
}
Another().test()
that prints
prop from One
prop from Another
This experiment shows that omitting this
forces the compiler not just to try the innermost closest receiver but to look upwards through the whole chain of possible receivers and pick the first one containing a member matching the specified identifier. So propFromTest
property gets invoked on the extension function receiver (the instance of One
), while propFromAnother
property gets invoked on the containing class instance (the instance of Another
, one level up in the chain of receivers). If you add a property with the same name both to One
and Another
and try to access it from extension function, you can see that the extension function receiver is the innermost one and takes precedence over the containing class instance. Also when you explicitly write this
, you always get the innermost receiver.
Now to your sample with Any
constraint on type parameter. I have played with it a bit and I believe the logic under the observed behavior is the following. The cases using this
are simple - you just get the innermost receiver, which is the extension function receiver. When this
is omitted, the compiler tries to figure out the target receiver based on the statically known types of all the receivers in the chain.
The javaClass
extension method is defined as following in the Kotlin stdlib:
public inline val <T : Any> T.javaClass: Class<T>
Therefore the compiler needs either a concrete type inheriting from Any
(that is, any concrete type) or a generic type parameter with a constraint : Any
or more restrictive one when picking a receiver object for this function call. When your extension function has Any
constraint on its type parameter, its receiver matches this requirement and is therefore picked by compiler. But when there are no constraints, the requirement is not fulfilled, so the compiler skips this candidate receiver and picks the next one. It may look weird at first sight, cause we know that all objects in Kotlin inherit from Any
, but probably this is the common logic that drives the compiler. All this is only my guess, but it gets confirmed by the following snippet:
fun <T> T.checkType() {
println(this.javaClass.simpleName)
}
fun <T: Any> T.checkType() {
println(this.javaClass.simpleName)
}
Here, the compiler rejects the first function complaining about type T
not being a subtype of Any
, but successfully compiles the second function. Such compiler behavior goes completely in line with your observed behavior and with my guess about its reasons.Kiryushin Andrey
01/27/2020, 4:37 PMthis
is not taken into account by compiler when picking the right receiver.thanksforallthefish
01/27/2020, 5:20 PMjavaClass
is defined on Any
but fun <T> T.checkType
is equivalent to fun <T: Any?> T.checkType
(I think, you can call null.checkType
)Kiryushin Andrey
01/27/2020, 6:00 PMAny
type param constraint replaced by Any?
also skips extension function receiver and resolves to the container class instance:
class Test{}
class Another {
fun <E: Any> test2(test: E) = test.test()
fun <E: Any?> E.test() =
println(javaClass.simpleName)
}
Another().test2(
Test()
)
prints
Another
thanksforallthefish
01/27/2020, 10:07 PMthis is Any?
so javaClass
is not defined, but because I check if (this is Any)
it was overlooked. luckily we had a test and no bug was created, but it was close