Consider:
class Foo {
fun CoroutineScope.foo() {
}
}
class Bar {
val f = Foo()
fun CoroutineScope.bar() { // this could also be "suspend fun bar = coroutineScope {"
f.foo() // unresolved reference
with (f) {
foo() // OK
}
with (f) {
with(this) {
foo() // OK
}
}
}
}
It seems like the first attempt at f.foo() should infer the CoroutineScope receiver specified on bar(). It seems it doesn't; but in an attempt to understand receivers better, does anyone have an explanation as to why?
Edit
After looking over some docs (specifically the "Declaring extensions as members") and Rene's response, I tried a few more things:
import kotlinx.coroutines.*
class Foo {
fun CoroutineScope.foo() { println("Foo.foo")}
fun Baz.fed(){ println("Foo.fed") }
}
class Baz {
fun CoroutineScope.foo() { println("Baz.foo") }
fun Foo.fed(){ println("Baz.fed") }
}
fun CoroutineScope.foo() { println("CoroutineScope.foo") }
fun foo() { println("Global.foo") }
fun bar(scope: CoroutineScope) {
val f = Foo()
val b = Baz()
println ("Test 1")
foo() // foo() from Global
scope.foo() // foo() from CoroutineScope
//f.foo() // unresolved reference
println ("\nTest 2")
with(scope) {
foo() // foo() from CoroutineScope
//f.foo() // unresolved reference
}
println ("\nTest 3")
with(f) {
scope.foo() // foo() from Foo
foo() // foo() from Global
}
println ("\nTest 4")
with(scope) {
with (f) {
foo() // foo() from Foo
scope.foo() // foo() from Foo
}
}
println ("\nTest 5")
with(f) {
with (scope) {
foo() // foo() from Foo
scope.foo() // foo() from Foo
}
}
println ("\nTest 6")
with(b) {
with(f) {
with (scope) {
foo() // foo() from Foo
fed() // fed() from Baz
}
}
}
println ("\nTest 7")
with(f) {
with(b) {
with (scope) {
foo() // foo() from Baz
fed() // fed() from Foo
}
}
}
}
fun main() = runBlocking {
bar(this)
}
It's interesting to see that when both contexts are made available via with, it is able to figure out which one is the dispatch context and which the extension context, regardless of what order they are provided. But if you specify the extension context directly like f.bar(), it will only look for versions of bar with extension receiver of type Foo, or a direct member of Foo (I'm still a bit hazy about how it views dispatch and extension receivers for a function that is simply defined in the class definition). So it seems the logic is something like:
Given expression x.y():
- Find all functions
y()that take extension receiverx - For each receiver available
c, starting with the most recently added, choose the firstx.y()that explicitly takes a dispatch receiver of typec- note thatfun CoroutineScope.foo()in global scope acts like it has no dispatch receiver, since tests 6 and 7 show that even thoughscopeis added to the available context list last,Foo(orBaz) version offoo()is used.
Given expression y():
- Try to find an
x.y()with both a dispatch receiver and extension receiver (x) defined and in the list of available contexts. (note: it chooses the most recently added extension receiver first, then tries to find a matching dispatch receiver (see test 6 and 7 withfed()) - If #1 returned nothing, choose most recently added context with dispatch receiver
xin available contexts that define functiony() - Still nothing? Choose the most recently added context with extension receiver
xthat define functiony() - Fall back to global version of
y()