Structure of Tuples after typer in Scala compiler plugin

I’m working on a compiler plugin that runs after the typer, and I’ve noticed that Tuples take a particular form

  // User input
  def func(): (Int, String) = ???
  val (a, b) = func()

After typer

  <synthetic> <artifact> private[this] val x$1: (Int, String) = (func(): (Int, String) @unchecked) match {
    case (_1: Int, _2: String)(Int, String)((a @ _), (b @ _)) => scala.Tuple2.apply[Int, String](a, b)
  };
  val a: Int = x$1._1;
  val b: String = x$1._2;

I need the unapplied val names (a and b) associated with the expression that returns the Tuple (func()). Ideally I can do this when I’m processing the val x$1 … tree (single pass, no lookahead). I see a and b in the (seemingly unnecessary and will probably be optimized away?) match.

My question is, can I rely on this structure for capturing those names? This is Scala 2.12.12, I haven’t tried other versions yet.

I don’t think the tuple pattern is special cased. Compare the post-typer trees of the following:

C:\Users\marti\scala>type example.scala
case class MyMatched(x: Int, y: Int)

object Unapplier {
  def unapply(i: Int): Some[(Int, Int)] = Some((i + 1, i + 2))
}

object Example {

  val myTuple = (1, 2)
  val (a, b) = myTuple

  val myMatched = MyMatched(3, 4)
  val MyMatched(c, d) = myMatched

  val Unapplier(e, f) = 7


}
C:\Users\marti\scala>scalac example.scala -Xprint:typer
[[syntax trees at end of                     typer]] // example.scala
package <empty> {
 <snip declarations />
  object Example extends scala.AnyRef {
    def <init>(): Example.type = {
      Example.super.<init>();
      ()
    };
    private[this] val myTuple: (Int, Int) = scala.Tuple2.apply[Int, Int](1, 2);
    <stable> <accessor> def myTuple: (Int, Int) = Example.this.myTuple;
    <synthetic> <artifact> private[this] val x$1: (Int, Int) = (Example.this.myTuple: (Int, Int) @unchecked) match {
      case (_1: Int, _2: Int): (Int, Int)((a @ _), (b @ _)) => scala.Tuple2.apply[Int, Int](a, b)
    };
    private[this] val a: Int = Example.this.x$1._1;
    <stable> <accessor> def a: Int = Example.this.a;
    private[this] val b: Int = Example.this.x$1._2;
    <stable> <accessor> def b: Int = Example.this.b;
    private[this] val myMatched: MyMatched = MyMatched.apply(3, 4);
    <stable> <accessor> def myMatched: MyMatched = Example.this.myMatched;
    <synthetic> <artifact> private[this] val x$2: (Int, Int) = (Example.this.myMatched: MyMatched @unchecked) match {
      case (x: Int, y: Int): MyMatched((c @ _), (d @ _)) => scala.Tuple2.apply[Int, Int](c, d)
    };
    private[this] val c: Int = Example.this.x$2._1;
    <stable> <accessor> def c: Int = Example.this.c;
    private[this] val d: Int = Example.this.x$2._2;
    <stable> <accessor> def d: Int = Example.this.d;
    <synthetic> <artifact> private[this] val x$3: (Int, Int) = (7: Int(7) @unchecked) match {
      case Unapplier.unapply(<unapply-selector>) <unapply> ((e @ _), (f @ _)) => scala.Tuple2.apply[Int, Int](e, f)
    };
    private[this] val e: Int = Example.this.x$3._1;
    <stable> <accessor> def e: Int = Example.this.e;
    private[this] val f: Int = Example.this.x$3._2;
    <stable> <accessor> def f: Int = Example.this.f
  }
}
1 Like

Good point, and thanks.

This is really neat and suggests the pattern I’m applying to Tuples will also work for any arbitrary unapply which is super cool.

1 Like