Skip to content

After erasure, the tailcalls special _$this variable is ill-typed when in a trait with a self-type #10702

Open
@sjrd

Description

@sjrd

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions