1

I am having trouble constructing correct Scala (2.13.3) syntax trees at runtime. Suppose we define the following class.

class Foo(x: Int)

I would like to construct the syntax tree for the following line of code.

new Foo(1)

For reference, we can generate the correct syntax tree using ru.reify. We can also type check this tree to confirm it is valid.

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

However, if I cannot figure out how to properly code the same syntax tree from scratch. Below is my initial attempt. I also tried the same thing with TermName instead of TypeName and see the same result.

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

The actualTree is obviously not valid because it doesn't type check. If we inspect the printed output, we can see that the Ident appears to be different between the expectedTree and the actualTree.

From the API, it seems like Ident must hold a Name object, but I cannot figure out which kind of name is required here. Furthermore, the printed output of the expectedTree doesn't indicate that the Ident is holding a Name at all. Is this some other kind of Ident? What would the proper code be to manually construct the AST of new Foo(1)?

EDIT: I was asked to supply information on why quasiquotes and/or reify don't work my use case. The short answer is: this is research tech in automatic programming.

The research task is to synthesize a correct using only a test suite. I am implementing a Scala version of the recently published method called "Code Building Genetic Programming".

I understand the warnings against manual construction in typical reflection use case but I believe the other construction methods require some notion of what the program/AST should be at development time.

Erp12
  • 600
  • 3
  • 16

1 Answers1

1

Besides extractor's apply metod accepting a 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

there is also factory method accepting a Symbol

def Ident(sym: Symbol): Ident

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

Try

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 and reify/splice are preferable ways to construct trees. So you should provide more details why q"new my.package.Foo(1)" and ru.reify { new Foo(1) }.tree are not enough for your use case. Normally it's not necessary to build identical trees, it's enough to build trees that behave as desired (but there are exceptions).

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • 1
    Thanks for the pointer the Ident factory that takes type symbols. I am not sure why my editor didn't find that one. Also, I added an explanation of my use case to the main question. – Erp12 Nov 01 '20 at 02:15