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