package yamin
typealias Foo = () -> Unit
fun main() {
bar {
baz()
}
}
fun bar(foo: Foo) {
foo()
}
context(Foo)
fun baz() {
//
}
I tried to use a lambda type for the context of a function, which seems to be fine at this point but when I tried to call that function in that context, or at least what I think is that context I'm failing, and Kotlin's compiler is showing me this error for baz()
:
No required context receiver found: Cxt { context((yamin.Foo /* = () -> kotlin.Unit */)) public fun baz(): kotlin.Unit defined in yamin in file Main.kt[SimpleFunctionDescriptorImpl@7b5e305f] }
Maybe I misunderstood Kotlin's context receivers or I'm expecting something that it's not designed for. I just want to create a function that can only be called in certain context and in this example only in a certain lambda.
CodePudding user response:
As it is right now, your baz
can be called like this:
val someFoo: Foo = {}
with(someFoo) {
baz()
}
By using with
, I bring an instance of () -> Unit
into the context, so someFoo
becomes a context receiver with which I can call baz
. In baz
, I can then access this someFoo
by using this@Foo
. This is how context receivers are supposed to work.
If you want baz
to only be able to be called in bar
's lambda, bar
's lambda needs to provide the context receiver using a receiver parameter, similar to how with
's lambda is declared T.() -> R
.
object Foo
fun main() {
bar {
baz()
}
}
fun bar(foo: Foo.() -> Unit) {
Foo.foo()
}
context(Foo)
fun baz() {
// ...
}
I changed the actual context here to just an object
, because as far as I can see, there is nothing special about bar
's lambda. Of course, if you want, you can make it provide extra information to baz
by changing Foo
to a class that does contain data.
Note that this does not prevent someone from doing:
with(Foo) {
baz()
}
It is quite hard to prevent against this without adding another layer outside of bar
, like:
// note that bar is no longer a global function
import bar.Foo.Companion.bar
class Foo private constructor() {
companion object {
fun bar(foo: Foo.() -> Unit) {
Foo().foo()
}
}
}
context(Foo)
fun baz() {
// ...
}
You can also do something simple like this prevents it from happening from outside the package:
sealed interface FooInterface
private object Foo: FooInterface
fun bar(foo: FooInterface.() -> Unit) {
Foo.foo()
}
context(FooInterface)
fun baz() {
// ...
}
CodePudding user response:
To make a function that can only be called in a certain scope, give the function itself a receiver. Instead of typealias Foo = () -> Unit
, you would use something like typealias Foo = MyReceiver.() -> Unit
.
You can do this with a regular extension receiver, but it's also possible with context receivers.
Here's a simple version that works for me:
typealias Foo = BarScope.() -> Unit
fun main() {
bar {
baz()
}
}
object BarScope
fun bar(foo: Foo) {
BarScope.foo()
}
context(BarScope)
fun baz() {
}