Commit: 2cb2de63e10d54be519dcb107fe35e2a9e3f56b9 [2cb2de63e1]
Parents: bef86df163
Author: Eugene Burmako <xeno.by@gmail.com>
Date: 17 August 2013 09:29:28 GMT+02:00
Commit Date: 17 August 2013 10:27:30 GMT+02:00
Labels: HEAD topic/patmat

SI-5903 extractor macros

Establishes a pattern that can be used to implement extractor macros
that give the programmer control over signatures of unapplications
at compile-time.

=== The pattern ===

In a nutshell, given an unapply method (for simplicity, in this
example the scrutinee is of a concrete type, but it's also possible
to have the extractor be polymorphic, as demonstrated in the tests):

  def unapply(x: SomeType) = ???

One can write a macro that generates extraction signatures for unapply
on per-call basis, using the target of the calls (c.prefix) and the type
of the scrutinee (that comes with x), and then communicate these signatures
to the typechecker.

For example, here's how one can define a macro that simply passes
the scrutinee back to the pattern match:

  def unapply(x: SomeType) = macro impl
  def impl(c: Context)(x: c.Tree) = {
    q"""
      new {
        class Match(x: SomeType) {
          def isEmpty = false
          def get = x
        }
        def unapply(x: SomeType) = new Match(x)
      }.unapply($x)
    """
  }

In addition to the the matcher, which implements domain-specific
matching logic, there's quite a bit of boilerplate here, and
every part of it looks necessary to arrange a non-frustrating dialogue
with the typer. Maybe something better can be done in this department,
but I can't see how, without introducing modifications to the typechecker.

Even though the pattern uses structural types, somehow no reflective calls
are being generated (as verified by -Xlog-reflective-calls and then
by manual examination of the produced code). That's a mystery to me, but
that's also good news, since that means that extractor macros aren't
going to induce performance penalties.

Almost. Unfortunately, I couldn't turn matchers into value classes
because one can't declare them local. Nevertheless, I'm leaving a canary
in place (neg/t5903e) that will let us know once this restriction is lifted.

=== Use cases ===

In particular, the pattern can be used to implement shapeshifting
pattern matchers for string interpolators without resorting to dirty
tricks. For example, quasiquote unapplications can be unhardcoded now:

def doTypedApply(tree: Tree, fun0: Tree, args: List[Tree], ...) = {
  ...
  fun.tpe match {
    case ExtractorType(unapply) if mode.inPatternMode =>
      // this hardcode in Typers.scala is no longer necessary
      if (unapply == QuasiquoteClass_api_unapply) macroExpandUnapply(...)
      else doTypedUnapply(tree, fun0, fun, args, mode, pt)
  }
}

Rough implementation strategy would involve destructuring c.prefix,
analyzing parts of StringContext and generating an appropriate matcher.

=== Implementation details ===

No modifications to core logic of typer or patmat are necessary,
as we're just piggybacking on https://github.com/scala/scala/pull/2835.

The only minor change I introduced is a guard against misbehaving
extractor macros that don't conform to the pattern (e.g. expand into
blocks or whatever else). Without the guard we'd crash with an NPE,
with the guard we get a sane compilation error.