Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
sjrd opened this issue Jan 27, 2018 · 1 comment
Labels

Comments

@sjrd
Copy link
Member

sjrd commented Jan 27, 2018

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.

@sjrd
Copy link
Member Author

sjrd commented Jan 27, 2018

The fix could be as simple as replacing currentClass.typeOfThis by currentClass.tpe on this line:
https://github.com/scala/scala/blob/3995c7e7c73862c900e221b2753f724ee472ccfb/src/compiler/scala/tools/nsc/transform/TailCalls.scala#L129
(with precautions for type constructors, I guess)

@smarter smarter added the erasure label Feb 6, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants