scalascala-macrosdottyscala-3

Scala 3 Tasty Reflection Macro: CyclicReference


I'm trying to access the parameters of a method that is being implemented as a macro.

object Macros {
    def impl()(using Quotes): Expr[Unit] = {    
        import quotes.reflect._
        val params: List[List[ValDef]] = {
            def nearestEnclosingMethodParams(owner: Symbol): List[List[ValDef]] =
                owner match {
                    case defSym if defSym.isDefDef =>
                        defSym.tree.asInstanceOf[DefDef].paramss
                    case _ =>
                        nearestEnclosingMethod(owner.owner)
                }
            nearestEnclosingMethodParams(Symbol.spliceOwner)
        }
        println(params) // I would do something useful with params names and types here
        '{()}
    }
}

The call-site could look something like:

object Test {
    def foo(a: String, b: Int) = Foo.impl
    @main def run(): Unit = {
        val x = foo("blah", 24)
        ()
    }
}

object Foo {
    inline def impl = ${ Macros.impl() }
}

For now, I'm getting a CyclicReference error when the macro expands, upon defSym.tree. I understand that defSym.tree is cyclic because it includes the code of the currently expanding macro, but I still need to access a "tree" version of the method definition to access its name and parameters, without the method's body. How can I get that information without cycling?


Solution

  • This issue was fixed in the recently released version of Scala 3.0.0-RC1.

    using defSym.tree in the def foo(a: String, b: Int) = Foo.impl example now works just fine and just uses an EmptyTree for the macro body. You can easily extract any information you need from the method argument, or type hierarchy where foo is defined, which was my use-case.

    DefDef(
        foo,
        List(
            List(
                ValDef(
                    a,
                    TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class scala)),object Predef),type String)],
                    EmptyTree
                ), 
                ValDef(
                    b,
                    TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int)],
                    EmptyTree
                )
            )
        ),
        TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Unit)],
        EmptyTree
    )