如何手动构造实例化类的Scala运行时AST?

问题描述

我在运行时无法构建正确的Scala(2.13.3)语法树。假设我们定义以下类。

class Foo(x: Int)

我想为下面的代码行构建语法树。

new Foo(1)

作为参考,我们可以使用ru.reify生成正确的语法树。我们还可以输入检查此树以确认其有效。

val expectedTree = ru.reify {
    new Foo(1)
}

println(ru.showRaw(expectedTree))
// Apply(
//     Select(
//         New(Ident(my.package.Foo)),<-- What does Ident hold here?
//         termNames.CONSTRUCTOR
//     ),//     List(
//         Literal(Constant(1))
//     )
// )

val toolBox = mirror.mkToolBox()
toolBox.typecheck(expectedTree).tpe
// my.package.Foo

但是,如果我不知道如何从头开始正确地编码相同的语法树。以下是我的初步尝试。我还使用TermName而不是TypeName尝试了同样的方法,并看到了相同的结果。

import ru._

val actualTree = Apply(
    Select(
        New(Ident(TypeName("my.package.Foo"))),termNames.CONSTRUCTOR
    ),List(
        Literal(Constant(1))
    )
)

println(ru.showRaw(actualTree))
// Apply(
//     Select(
//         New(Ident(TypeName("my.package.Foo"))),<-- Ident holds a TypeName
//         termNames.CONSTRUCTOR
//     ),//     List(
//         Literal(Constant(1))
//     )
// )

val toolBox = mirror.mkToolBox()
toolBox.typecheck(actualTree).tpe
// ToolBoxError: reflective typecheck has Failed: not found: type my.package.Foo

actualTree显然无效,因为它没有键入check。如果检查打印的输出,我们可以看到IdentexpectedTree间的actualTree似乎有所不同。

从API看来,Ident必须包含一个Name对象,但是在这里我无法确定需要哪种名称。此外,expectedTree的打印输出并不表示Ident完全持有Name。这是另一种Ident吗?手动构造new Foo(1)的AST的正确代码是什么?

编辑: 我被要求提供有关为什么准引用和/或验证不起作用的信息。简短的答案是:这是自动编程的研究技术。

研究任务是仅使用测试套件来综合正确性。我正在实现the recently published method called "Code Building Genetic Programming"的Scala版本。

我理解在典型的反射用例中关于手动构建的警告,但我相信其他构建方法需要对程序/ AST在开发时应具有的概念有所了解。

解决方法

除了提取器的apply方法接受Name

abstract class IdentExtractor {
  def apply(name: Name): Ident
  //...
}

https://github.com/scala/scala/blob/2.13.x/src/reflect/scala/reflect/api/Trees.scala#L1787

还有一种工厂方法接受Symbol

def Ident(sym: Symbol): Ident

https://github.com/scala/scala/blob/2.13.x/src/reflect/scala/reflect/api/Trees.scala#L2237

尝试

val actualTree = Apply(
  Select(
    New(Ident(typeOf[Foo].typeSymbol)),termNames.CONSTRUCTOR
  ),List(
    Literal(Constant(1))
  )
)

println(ru.showRaw(actualTree)) 
//Apply(Select(New(Ident(my.package.Foo)),termNames.CONSTRUCTOR),List(Literal(Constant(1))))

println(toolbox.typecheck(actualTree).tpe) // my.package.Foo

准引号和reify / splicepreferable ways来构造树。因此,您应该提供更多详细信息,为什么q"new my.package.Foo(1)"ru.reify { new Foo(1) }.tree不足以满足您的用例。通常,没有必要构建相同的树,而足以构建表现理想的树(但有例外)。