【问题标题】:How to manually construct an Scala runtime AST that instantiates a Class?如何手动构建实例化类的 Scala 运行时 AST?
【发布时间】:2021-02-12 14:55:17
【问题描述】:

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

class Foo(x: Int)

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

new Foo(1)

作为参考,我们可以使用ru.reify 生成正确的语法树。我们也可以输入 check 这棵树来确认它是有效的。

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 显然无效,因为它不进行类型检查。如果我们检查打印输出,我们可以看到IdentexpectedTreeactualTree 之间似乎不同。

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

编辑: 我被要求提供有关为什么 quasiquotes 和/或 reify 对我的用例不起作用的信息。简短的回答是:这是自动编程的研究技术。

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

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

【问题讨论】:

    标签: scala reflection abstract-syntax-tree scala-reflect


    【解决方案1】:

    除了提取器的 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
    

    Quasiquotes 和 reify/splicepreferable ways 来构造树。因此,您应该提供更多详细信息,为什么 q"new my.package.Foo(1)"ru.reify { new Foo(1) }.tree 不足以满足您的用例。通常不需要构建相同的树,构建行为符合要求的树就足够了(但也有例外)。

    【讨论】:

    • 感谢 Ident 工厂提供类型符号的指针。我不知道为什么我的编辑没有找到那个。此外,我在主要问题中添加了对我的用例的解释。
    猜你喜欢
    • 1970-01-01
    • 2011-04-22
    • 1970-01-01
    • 2019-09-24
    • 2022-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-25
    相关资源
    最近更新 更多