如何在另一个宏执行中使用在一个 Scala 宏执行中创建和类型检查的 `universe.Tree`?

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

在 JSON 库中 jsfacile 我能够自动派生类型-类 用于具有递归类型引用的类型 thanx 到缓冲区,宏的外部执行存储实例universe.Tree 稍后由相同或其他宏的内部执行使用。 只要未对 universe.Tree 实例进行类型检查(使用 Typers.typecheck 方法),此方法就可以正常工作。

问题在于,这会强制对同一个 Tree 实例进行多次类型检查:一次在创建它的宏执行中(在将其存储在缓冲区中之后);在每个需要它的内部宏执行中执行更多次。

这个问题的目的是找到一种方法,在进行类型检查后,在宏执行之间共享 Tree 实例;为了提高编译速度。

我尝试将类型检查的 Tree 包装成 universe.Expr[Unit] 并通过 Expr.in[U <: Universe](otherMirror: Mirror[U]): U # Expr[T] 方法将其迁移到使用它的宏执行的镜像。 但它因内部错误而失败:

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

有什么想法吗?

一般来说,手动对树进行类型检查并在不同的上下文中共享类型树是一个坏主意。请参阅以下示例:

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.

implicitly 中,我们在一些地方放置了符号所有者链不正确的树。

如果你制作 ABC 对象,那么错误就会消失(所以这是否会阻止编译取决于运气)。

此外,如果您删除 c.typecheck,错误就会消失。

此外,如果我们 return c.untypecheck(mtcCache(typA).asInstanceOf[Tree]) 而不是 mtcCache(typA).asInstanceOf[Tree] 那么错误就会消失。但有时 c.typecheck + c.untypecheck 会损坏一棵树。

所以你可以尝试将树的非类型化版本和类型化版本都放入缓存中,如果你需要两者,但 return 非类型化版本

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]
  }
}

或者,如果您只需要类型检查来触发递归,那么您可以对树进行类型检查,将未类型化的树放入缓存,然后 return 未类型化的树

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]
  }
}

拉取请求:https://github.com/readren/json-facile/pull/1