Hi! Sorry if this is the wrong channel of if it's ...
# compiler
s
Hi! Sorry if this is the wrong channel of if it's been asked before, but I just noticed smart-casts of
(() -> Unit)?
doesn't always allow calling in the “natural” way. Is this a known issue? (Kotlin 1.6.0.)
Here's the code in text, for simplicity:
Copy code
class Runner(private val run: (() -> Unit)?) {
    fun works() {
        if (run == null) return

        run.invoke()
    }

    fun fails() {
        if (run == null) return

        run()
    }
}
Interestingly, it acts as expected if the lambda is not a class property, as this here works:
Copy code
fun runner(run: (() -> Unit)?) {
    if (run == null) return

    run()
}
A playground link: https://pl.kotl.in/qPSCNha93
d
seems like bug
f
This was always the case and is nothing new, so I wouldn't say bug. The compiler could probably be extended to support this, but there might be good reasons to not do it. 🤷 For your particular case this would work nicely as well:
Copy code
run?.invoke() ?: return
d
The
?
in your workaround is not needed, which is part of the issue.
f
The problem is that the compiler needs to insert the smart cast, in order for it to do so it needs it to be stand-alone. My guess is that the smart cast processing is done in the same iteration as the unsugaring of calls (don't quote me on that, I don't know) and that it requires an explicit call because of that.
Copy code
fun f() {
    if (run == null) return
    run() // requires smart cast, but doesn't work in any Kotlin version because of operator sugar
}
The alternative I gave for the particular example would be:
Copy code
fun f() {
    run?.invoke() ?: return
}
This would work in the example case, but not in the general case (obviously). A general solution to fix this:
Copy code
fun f() {
    // snip
    run?.let { run ->
        // snip
        run()
        // snip
    }
    // snip
}
You can actually see the smart cast in the screenshot, look at the
works
function and the green background color of
run
.
d
So why does the
works
compile?
f
Because it's not using any sugar, the
run.invoke()
in
works
gets transpiled to
(run as () -> Unit).invoke()
. (Well, smart casted.)
s
But why would this work, then?
Copy code
fun runner(run: (() -> Unit)?) {
    if (run == null) return

    run()
}
This is smart-cast + sugar, the only difference is we're dealing with a function parameter and not a class property:
b
f
I really don't know and the generated bytecode (IntelliJ Kotlin tools) is equivalent too. 🤷
Copy code
L2
    LINENUMBER 6 L2
    ALOAD 0
    GETFIELD Test.run : Lkotlin/jvm/functions/Function0;
    INVOKEINTERFACE kotlin/jvm/functions/Function0.invoke ()Ljava/lang/Object; (itf)
    POP
   L3
    LINENUMBER 7 L3
    ALOAD 0
    GETFIELD Test.run : Lkotlin/jvm/functions/Function0;
    INVOKEINTERFACE kotlin/jvm/functions/Function0.invoke ()Ljava/lang/Object; (itf)
    POP
   L4
    LINENUMBER 8 L4
    ALOAD 1
    INVOKEINTERFACE kotlin/jvm/functions/Function0.invoke ()Ljava/lang/Object; (itf)
    POP
   L5
    LINENUMBER 9 L5
    ALOAD 1
    INVOKEINTERFACE kotlin/jvm/functions/Function0.invoke ()Ljava/lang/Object; (itf)
    POP
1
Voted for the issue, very inconsistent behavior.
s
Thanks for the issue link! You found what I could not. 👍