however ```class Test{} class Another { fun &...
# announcements
t
however
Copy code
class Test{}
 class Another {
   fun <E: Any> test2(test: E) = test.test()
   fun <E: Any> E.test() = 
     println(javaClass.simpleName)
 }
 Another().test2(
   Test()
 )
Copy code
prints Test
Copy code
class Test{}
 class Another {
   fun <E: Any> test2(test: E) = test.test()
   fun <E: Any> E.test() = 
     println(this.javaClass.simpleName)
 }
 Another().test2(
   Test()
 )
Copy code
prints Test
k
Probably it is on purpose. Consider the following code snippet
Copy code
class 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
Copy code
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:
Copy code
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:
Copy code
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.
And it looks like the result of smart cast on
this
is not taken into account by compiler when picking the right receiver.
t
that is because
javaClass
is defined on
Any
but
fun <T> T.checkType
is equivalent to
fun <T: Any?> T.checkType
(I think, you can call
null.checkType
)
k
You're right, a subtle but important difference. Your snippet with
Any
type param constraint replaced by
Any?
also skips extension function receiver and resolves to the container class instance:
Copy code
class Test{}
    class Another {
        fun <E: Any> test2(test: E) = test.test()
        fun <E: Any?> E.test() =
            println(javaClass.simpleName)
    }
    Another().test2(
        Test()
    )
prints
Copy code
Another
t
yep, that is exactly what confused me, skipping the receiver. it seems inconsistent, though it is true that in the skipped example
this 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