1

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?

alturkovic
  • 990
  • 8
  • 31

1 Answers1

2

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.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • I thought only the caller would need to "pass" the context when using the variable, I didn't think of declaring it on my class :) Thanks! – alturkovic Jan 20 '23 at 12:48
  • This seemed to resolve the issue as IntelliJ no longer showed an error, but when I tried to run it, it did not work, it breaks the Kotlin compiler. I posted the complete solution here: https://pastebin.com/erfdsLZx and stack trace here: https://pastebin.com/9guWH0UP – alturkovic Jan 20 '23 at 14:06
  • @alturkovic That is most definitely a compiler bug. Looks like I really should hit "compile" before posting lol. But the fix is simple - just move `context(Logger)` to the property delegate class (see the edit). – Sweeper Jan 20 '23 at 22:21
  • I don't think moving the `context(Logger)` to the class instead of the `setValue` works either: https://pastebin.com/g7WQu2PB – alturkovic Jan 21 '23 at 12:41
  • @alturkovic After some investigation, it seems like the compiler bug is related to `Example` being a data class. If you change `Example` to a regular class, it works. I recommend reporting this bug on https://youtrack.jetbrains.com/issues/KT. – Sweeper Jan 21 '23 at 12:58
  • 1
    @alturkovic See https://pl.kotl.in/EvWUtwjT6 – Sweeper Jan 21 '23 at 13:03