Reputation: 4769
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()
:
y()
that take extension receiver x
c
, starting with the most recently added, choose the first x.y()
that explicitly takes a dispatch receiver of type c
- note that fun CoroutineScope.foo()
in global scope acts like it has no dispatch receiver, since tests 6 and 7 show that even though scope
is added to the available context list last, Foo
(or Baz
) version of foo()
is used.Given expression y()
:
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 with fed()
)x
in available contexts that define function y()
x
that define function y()
y()
Upvotes: 2
Views: 51
Reputation: 6258
Your declaration:
class Foo {
fun CoroutineScope.foo() {
}
}
defines an extension function foo
of CoroutineScope
in the context of an instance of the class Foo
. This means you can only invoke foo
on an instance of CoroutineScope
if you are in a scope where this
is of the type Foo
.
The first attempt f.foo()
does exactly the opposite. You invoke foo()
on an instance of Foo
and this
references a CoroutineScope
.
The two other examples uses with
to set the this
reference to Foo
and therefor you can invoke foo
on the outer CoroutineScope
instance.
BTW: with(this)
makes no sense, because this
will be set to this
.
Upvotes: 3