Description
Not sure this is worth reporting, but it caused some headaches in Scala.js: scala-js/scala-js#3058 (with a wrong fix in scala-js/scala-js#3059) then scala-js/scala-js#3267 (with the "proper" fix in scala-js/scala-js#3272). We'll need the workaround of scala-js/scala-js#3272 forever anyway to keep support for 2.12.{0-4}, but maybe it's worth fixing internally for the future.
Compiling the following code snippet with -Xprint:tailcalls,erasure
, Scala 2.12.4:
import scala.annotation.tailrec
class Parser
trait Helpers { this: Parser =>
@tailrec
final def rec(i: Int): Int = {
if (i == 0) b()
else rec(i - 1)
}
def b(): Int = 42
}
prints:
[[syntax trees at end of tailcalls]] // TraitTailrecSelfType.scala
package <empty> {
class Parser extends Object {
def <init>(): Parser = {
Parser.super.<init>();
()
}
};
abstract trait Helpers extends Object { `this`: Helpers with Parser =>
def /*Helpers*/$init$(): Unit = {
()
};
@scala.annotation.tailrec final def rec(i: Int): Int = {
<synthetic> val _$this: Helpers with Parser = Helpers.this;
_rec(_$this: Helpers with Parser, i: Int){
if (i.==(0))
Helpers.this.b()
else
_rec(Helpers.this, i.-(1).asInstanceOf[Int]()).asInstanceOf[Int]()
}
};
def b(): Int = 42
}
}
[[syntax trees at end of erasure]] // TraitTailrecSelfType.scala
package <empty> {
class Parser extends Object {
def <init>(): Parser = {
Parser.super.<init>();
()
}
};
abstract trait Helpers extends Object { `this`: Parser =>
def /*Helpers*/$init$(): Unit = {
()
};
@scala.annotation.tailrec final def rec(i: Int): Int = {
<synthetic> val _$this: Parser = Helpers.this.$asInstanceOf[Parser]();
_rec(_$this: Parser, i: Int){
(if (i.==(0))
Helpers.this.b()
else
_rec(Helpers.this.$asInstanceOf[Parser](), (i.-(1): Int)): Int)
}
};
def b(): Int = 42
}
}
As you can see, the magical val _$this
is typed as Helpers with Parser
before erasure
, but is then typed as Parser
after erasure. Note that the tree itself is well-typed. However, _$this
is a magical val that is used to encode reassignments to this
in the bytecode. Now, since this is a method of Helpers
, in the bytecode, this
must be of type Helpers
. The type of _$this
should therefore be Helpers
as well, not Parser
.
Note that this leaves a trace up until the bytecode:
$ javap -c Helpers.class
Compiled from "TraitTailrecSelfType.scala"
public interface Helpers {
// omitting everything but rec(int):
public int rec(int);
Code:
0: iload_1
1: iconst_0
2: if_icmpne 14
5: aload_0
6: invokeinterface #22, 1 // InterfaceMethod b:()I
11: goto 26
14: aload_0
15: checkcast #24 // class Parser
18: iload_1
19: iconst_1
20: isub
21: istore_1
22: astore_0
23: goto 0
26: ireturn
}
Note the checkcast #24 // class Parser
, which is totally useless, and exists only because erasure had to adapt this
to Parser
to give it as an argument to the _$this
parameter of the LabelDef
.