Dotty: Cannot rewrite recursive call: it targets a supertype

Created on 26 May 2020  路  5Comments  路  Source: lampepfl/dotty

Minimized code

import scala.annotation.tailrec

trait Iterator { def hasNext: Boolean }

object Iterable {
  val i1: Iterator = ???
  val iterator = new Iterator {
    @tailrec def hasNext: Boolean =
      if (!i1.hasNext) false
      else {/* i1.next; */ hasNext}
  }
}

Output

[error] 10 |      if (!i1.hasNext) false
[error]    |           ^^^^^^^^^^
[error]    |           Cannot rewrite recursive call: it targets a supertype

Expectation

compile successfully as in scala 2

Notes

  • Scala 2 compile to while/for loop:
public final class Iterable$$anon.Iterable$$anon$1 implements Iterator {
    public boolean hasNext() {
        while($outer.Iterable$$i1.hasNext()) ;
        return false;
    }
    private final Iterable $outer;
...
  • When I remove the @tailrec, dotty also compile to while/for loop:
private static final class Iterable$$anon$1 implements Iterator {
  private final Iterable $outer;

  public boolean hasNext() {
      for (Iterator iterator = (Iterator)this; iterator.Iterable$_$$anon$$$outer().Iterable$$i1.hasNext(); iterator = iterator) {}
      return false;
  }
...

It means that dotty can get rid of all calls to the method. So it should NOT throw compile errors when we add @tailrec?

bug

All 5 comments

Both dotty and scalac do not rewrite the recursive call i1.hasNext. I think it's reasonable for @tailrec to tell you when this happen, and so I would just remove @tailrec from the method call.

I think only the second hasNext call (in else clause) is recursive.
Or not? 馃榿

To be precise, we don't know statically whether it's a recursive call to the same hasNext method or not, it depends on the runtime class of i1, so @tailrec conservatively disallows it.

Note that @tailrec does not require recursive calls to be on the same class instance, for example this is valid:

class Test {
  @tailrec
  final def foo(cond: Boolean, that: Test): Boolean =
    if (cond)
      that.foo(!cond, this)
    else
      cond
}

The call to that.foo is considered to be a recursive call to foo with a different receiver.

Closing as "working as intended"

Was this page helpful?
0 / 5 - 0 ratings

Related issues

odersky picture odersky  路  28Comments

odersky picture odersky  路  126Comments

TomasMikula picture TomasMikula  路  28Comments

megri picture megri  路  29Comments

odersky picture odersky  路  27Comments