Mark tail-recursive functions as such

Always annotate your tail-recursive function with @annotation.tailrec.

Reason

This will let the compiler know that you expect your function to be tail-recursive, and allow compilation to fail should it not be.

This is important, because there are scenarios where you’d expect a function to be tail-recursive but it really isn’t. Take, for example:

class Foo {
  def sum(cur: List[Int], acc: Int): Int = cur match {
    case h :: t => sum(t, acc + h)
    case _      => acc
  }
}

This looks tail-recursive - sum calls itself in tail position, after all. Turns out, however, that it’s not:

class Foo {
  @annotation.tailrec
  def sum(cur: List[Int], acc: Int): Int = cur match {
    case h :: t => sum(t, acc + h)
    case _      => acc
  }
}
// error: could not optimize @tailrec annotated method sum: it is neither private nor final so can be overridden
//   final def sum(cur: List[Int], acc: Int): Int = cur match {
//         ^

Thanks to that error message, we can fix the problem and make sum propery tail-recursive:

class Foo {
  @annotation.tailrec
  final def sum(cur: List[Int], acc: Int): Int = cur match {
    case h :: t => sum(t, acc + h)
    case _      => acc
  }
}

Checked by

This rule is not checked by any linter that I know of.