bug icon indicating copy to clipboard operation
bug copied to clipboard

Missing @tailrec error with recursive call targeting supertype

Open smarter opened this issue 6 years ago • 7 comments

The following outputs an error as expected:

import scala.annotation.tailrec

trait Outer { self =>
  def hi(cond: Boolean): Boolean

  trait Inner extends Outer {
    @tailrec
    final def hi(cond: Boolean): Boolean =
      self.hi(cond)
  }
}
try/super.scala:9: error: could not optimize @tailrec annotated method hi: it contains a recursive call targeting a supertype
      self.hi(cond)
           ^
one error found

The following also outputs an error:

import scala.annotation.tailrec

trait Outer { self =>
  def hi(cond: Boolean): Boolean

  trait Inner extends Outer {
    @tailrec
    final def hi(cond: Boolean): Boolean =
      self.hi(cond) && cond
  }
}
try/super.scala:8: error: could not optimize @tailrec annotated method hi: it contains a recursive call not in tail position
    final def hi(cond: Boolean): Boolean =
              ^
one error found

But this compiles without error:

import scala.annotation.tailrec

trait Outer { self =>
  def hi(cond: Boolean): Boolean

  trait Inner extends Outer {
    @tailrec
    final def hi(cond: Boolean): Boolean =
      self.hi(cond) && hi(cond)
  }
}

smarter avatar Mar 20 '19 18:03 smarter

I think it's open to interpretation whether Scala 2 or 3 is right here.

For instance, this example compiles in both 2 and 3:

import scala.annotation.tailrec

trait HiHo {
  final def ho(cond: Boolean): Boolean = hi(cond)
  @tailrec
  final def hi(cond: Boolean): Boolean =
    ho(cond) && hi(cond)
}

Essentially it's the same thing: there are 2 branches, one of which can be tailrec optimized and one which cannot. And actually this example can be proven to eventually stackoverflow while self.hi(cond) could be perfectly safe.

Jasper-M avatar Mar 28 '24 09:03 Jasper-M

I don't think @tailrec can handle mutual recursion

joroKr21 avatar Mar 28 '24 09:03 joroKr21

Indeed it can't, but basically my point is that scala 2 accepts @tailrec as long as there is a self-recursive call that can be optimized. Whether or not there are other method calls in there doesn't matter. Scala 3 does the same thing except when one of those other method calls looks like a recursive call but targets a supertype.

In both cases the compiler manages to optimize an actual tail recursive call. And in both cases that other method call is not guaranteed to be either safe or unsafe.

Jasper-M avatar Mar 28 '24 09:03 Jasper-M

Ahh I see what you mean, that makes sense 👍

joroKr21 avatar Mar 28 '24 09:03 joroKr21

Not sure what the context is (that motivated commenting), but this is one of a few tailrec tickets I'm addressing (today or so). There is also a ticket on mutual recursion of local methods. Hopefully after coffee and sunrise, the comments will also make sense to me.

som-snytt avatar Mar 28 '24 13:03 som-snytt

We don't really know if the super method is recursive or not.

joroKr21 avatar Mar 28 '24 13:03 joroKr21

The preference on the dotty ticket for x => f() was to warn conservatively for any possibility of recursivity.

som-snytt avatar Mar 28 '24 14:03 som-snytt