Readren
Readren

Reputation: 1240

How to use a `universe.Tree` created and type-checked in one Scala macro execution, in another macro execution?

In the JSON library jsfacile I was able to automatically derive type-classes for types with recursive type references thanx to a buffer where outer executions of a macro store instances of universe.Tree which are used later by inner executions of the same or other macro. This works fine as long as the universe.Tree instance was not type-checked (with the Typers.typecheck method).

The problem is that this forces to type-check the same Tree instance more than one time: one time in the macro execution that created it (after having stored it in the buffer); and more times in each inner macro execution that needs it.

The intention of this question is to find out a way to share the Tree instance between macro executions after it was type-checked; in order to improve compilation speed.

I tried to wrap the type-checked Tree into a universe.Expr[Unit] and migrate it to the mirror of the macro execution that uses it by means of the Expr.in[U <: Universe](otherMirror: Mirror[U]): U # Expr[T] method. But it fails with an internal error:

Internal error: unable to find the outer accessor symbol of class <Name of the class where the macro is expanded>

Any idea?

Upvotes: 0

Views: 220

Answers (1)

Dmytro Mitin
Dmytro Mitin

Reputation: 51658

Generally, typechecking a tree manually and sharing the typed tree among different contexts is a bad idea. See the following example:

import scala.language.experimental.macros
import scala.reflect.macros.whitebox
import scala.collection.mutable

object Macros {
  val mtcCache = mutable.Map[whitebox.Context#Type, whitebox.Context#Tree]()

  trait MyTypeclass[A]

  object MyTypeclass {
    implicit def materialize[A]: MyTypeclass[A] = macro materializeImpl[A]

    def materializeImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
      import c.universe._

      val typA = weakTypeOf[A]

      if (!mtcCache.contains(typA)) 
        mtcCache += (typA -> c.typecheck(q"new MyTypeclass[$typA] {}"))

      mtcCache(typA).asInstanceOf[Tree]
    }
  }
}

import Macros.MyTypeclass

object App { // Internal error: unable to find the outer accessor symbol of object App

  class A { // Internal error: unable to find the outer accessor symbol of class A

    class B { // Internal error: unable to find the outer accessor symbol of class A

      class C {
        implicitly[MyTypeclass[Int]] // new MyTypeclass[Int] {} is created and typechecked here
      }

      implicitly[MyTypeclass[Int]] // cached typed instance is inserted here, this is the reason of above error
    }

    implicitly[MyTypeclass[Int]] // cached typed instance is inserted here, this is the reason of above error
  }

  implicitly[MyTypeclass[Int]] // cached typed instance is inserted here, this is the reason of above error
}

Scala 2.13.3.

With implicitly we put in some places trees with incorrect symbol owner chain.

If you make A, B, C objects then errors disappear (so whether this prevents compilation depends on a luck).

Also if you remove c.typecheck then errors disappear.

Also if we return c.untypecheck(mtcCache(typA).asInstanceOf[Tree]) instead of mtcCache(typA).asInstanceOf[Tree] then errors disappear. But sometimes c.typecheck + c.untypecheck can damage a tree.

So you can try to put both untyped and typed versions of a tree to the cache if you need both but return the untyped one

type CTree = whitebox.Context#Tree
val mtcCache = mutable.Map[whitebox.Context#Type, (CTree, CTree)]()

trait MyTypeclass[A]

object MyTypeclass {
  implicit def materialize[A]: MyTypeclass[A] = macro materializeImpl[A]

  def materializeImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
    import c.universe._

    val typA = weakTypeOf[A]
    val tree = q"new MyTypeclass[$typA] {}"
    if (!mtcCache.contains(typA)) 
      mtcCache += (typA -> (tree, c.typecheck(tree)))

    mtcCache(typA)._1.asInstanceOf[Tree]
  }
}

or if you need typechecking only to trigger the recursion then you can typecheck a tree, put the untyped tree to the cache and return the untyped one

val mtcCache = mutable.Map[whitebox.Context#Type, whitebox.Context#Tree]()

trait MyTypeclass[A]

object MyTypeclass {
  implicit def materialize[A]: MyTypeclass[A] = macro materializeImpl[A]

  def materializeImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
    import c.universe._

    val typA = weakTypeOf[A]
    val tree = q"new MyTypeclass[$typA] {}"
    c.typecheck(tree)
    if (!mtcCache.contains(typA)) mtcCache += (typA -> tree)

    mtcCache(typA).asInstanceOf[Tree]
  }
}

Pull request: https://github.com/readren/json-facile/pull/1

Upvotes: 1

Related Questions