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