kotlinkotlin-context-receivers

Using context receiver on a delegated property


I have a property delegate using a context receiver:

class LoggingPropertyDelegate<T, V, L : Log>(
    private var value: V,
    private val toLog: T.() -> L
) : ReadWriteProperty<T, V> {
    override fun getValue(thisRef: T, property: KProperty<*>) = value

    context(Logger)
    override fun setValue(thisRef: T, property: KProperty<*>, value: V) {
        this.value = value
        log(toLog(thisRef))
    }
}

But when I try to use it on a property:

var myValue: Int by LoggingPropertyDelegate(0, { InfoLog("Changed to $myValue") })

I get an error that there is no suitable set functions for the delegate. If I remove the context from the method everything works as expected.

Is it not possible to use context receivers on property delegates?


Solution

  • It is possible to use a property delegate that has context receivers. You just need to provide the context receiver in some way.

    First, note that you should put context(Logger) on the delegate class type, not on setValue:

    context(Logger)
    class LoggingPropertyDelegate<T, V, L : Log>(
    

    If myValue is an instance property of some class Foo, then you can do:

    context(Logger)
    class Foo {
        var myValue: Int by LoggingPropertyDelegate(0) { ... }
    }
    

    Note that if Foo is a data class, there seems to be a compiler bug that causes the compiler to crash. Perhaps context receivers are lowered to extra compiler parameters (?)

    And then when instantiating Foo, you will need to provide a Logger:

    val foo = with(someLogger) { Foo() }
    // now you can access foo.myValue
    

    (or instantiate Foo in another function with a Logger context receiver, of course)

    If myValue is a local variable, you can also directly use with to introduce the context receiver instance, in addition to adding a Logger context receiver to the enclosing function.