Description
Compiler version
3.4.0
(also validated on near latest local build 3.5.0-RC1-bin-SNAPSHOT-nonbootstrapped-git-9a5b9b4
Minimized code
This compiles:
@tailrec
def foo(): Unit =
def bar(): Unit =
if (???)
foo()
else
bar()
bar()
This does not:
@tailrec
def foo(): Unit =
def bar(): Unit =
if (???)
foo()
bar()
Output
These should no compile, as there are no tail recursive calls within the outer @tailrec
annotated block. It has erroneously detected that this is ok, due to the rewrite of the inner bar
method. This should not compile, and if this were not annotated with @tailrec
, the outer tailLabel
should not be generated.
Looking into the one that does compile, it generates an unutilised label testLabel2[Unit]:
, and fails to warn about having no tail
l recursion optimised calls on the to level method:
@tailrec def foo(): Unit =
{
while <empty> do
tailLabel2[Unit]:
return
{
def bar(): Unit =
{
while <empty> do
tailLabel1[Unit]:
return
if ???() then Test.foo() else
(return[tailLabel1] ()):Unit
}
bar()
}
}
Expectation
For the behaviour between these two (weather this is an acceptable place to recur or not) to be consistent between these cases - or in the alternative - to support multi tiered tail recursion.
Notes
This compiles (and indeed rewrites to a loop) if the inner method is declared as inline. Potentially a rule that within annotated @tailrec
methods, any inner methods that call the top level method are handled as inline
for simple uses directly within this method?
I was having a play around, and while complex, it does look feasible that we could have some sort of rewrite that processes this as a single larger rewrite. What would people thing of optimiseing to something like this:
import scala.annotation.tailrec
import scala.util.boundary
import scala.util.boundary.break
object Test extends App {
//println(a(0))
println(a_tr(0))
var count = 0
def f(tNum: Int): Boolean = {
count = count + 1
if (count > 100000) {
throw new Exception("Infinitely looping")
}
tNum == 4 || tNum == 1
}
@tailrec
def a(i: Int): Int = {
@tailrec
def b(j: Int): Int = {
if (f(1)) {
a(j + 1)
} else if (f(2)) {
b(j + 1)
} else {
j
}
}
if (f(3)) {
a(i + 1)
} else if (f(4)) {
b(i + 1)
} else {
i
}
}
//identical to a, but with tail recursion logic applied
def a_tr(i: Int): Int = {
class AExit
class ATail
var aRes: Int | Null = null
boundary[AExit] {
var aIState = i
while (true) {
boundary[ATail] {
while (true) {
def b(j: Int): Int = {
var bJState = i
while (true) {
if (f(1)) {
aIState = bJState + 1
break(new ATail)
} else if (f(2)) {
bJState = bJState + 1
} else {
aRes = bJState
break(new AExit())
}
}
???
}
if (f(3)) {
aIState = aIState + 1
} else if (f(4)) {
b(aIState + 1)
} else {
aRes = aIState
break(new AExit)
}
}
???
}
}
???
}
aRes.nn
}
}