Reputation: 1240
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
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