Calling tailrec function at end of boolean expression

Hi,
I have 2 identical tailrec functions.
Compiler gives warning for one but accepts the other one.

// Compiler warning: A function is marked as tail-recursive but no tail calls are found
tailrec fun foo(): Boolean = Random.nextBoolean() && foo()

// Same as above, but accepted by compiler as tail-recursive
tailrec fun bar(): Boolean = if(Random.nextBoolean()) {
    bar()
} else {
    false
}

To be eligible for the tailrec modifier, a function must call itself as the last operation it performs.

Why is tailrec function calling itself at the end of boolean expression not treated as the last operation?

This looks like a rare corner case, I guess the compiler looks for a line like return foo(), which strictly speaking your first example doesn’t have. Yes it can be rweritten as such but it’s not necessarily obvious. I suspect this to be rare enough that it won’t be worth fixing it.

2 Likes

Because the final call && consumes the return value of the inner called foo which strikes against tail recursive functions.
Theoretically, we can rewrite any function as tailrec function, but I think it is better for the compiler not to be too clever here. Especially when tailrec is someday extended to have more than one function in circularity, annotating accidentally a function as tailrec or refactor a function marked as tail rec may lead to unnecessary compile performance degradation.

Basically what @sighoya said. Written differently:

tailrec fun foo(): Boolean = and(Random.nextBoolean(), foo())

It’s easier to see that foo isn’t the last call

@sighoya @benwoodworth There is no final && or and call.

The && operator “short circuits”. Once it evaluates the first argument, it either returns that immediately as the result or evaluates the second argument for the result.

@vihang.patil Looks like this issue is already getting tracked as KT-24151 and it affects all the short circuiting operators.

3 Likes