Pyromuffin
Pyromuffin

Reputation: 49

Find all package level objects with scala 3 reflection

I'm trying to get a list of Expr[Any] that corresponds to all package level objects. I can find the package symbol itself (by chasing up the symbol owner of any top level object), then iterate through the declaredFields. I can find the top level object ValDef nodes this way, but there is no associated Term in the ValDef. I also can't use Term.select on the package tree to get the declared members because I can't get the tree of of a package symbol.

It seems like it should be possible to get the Term (and not just the ValDef) associated with these top level singleton objects from their symbols somehow, but I can't figure it out.

Basically I would like to do something like this:

inline def packageToucher[T](something : T) : List[Any] = {
    ${ packageToucherImpl('something) } 
}

def packageToucherImpl[T](something : Expr[T])(using Quotes, Type[T]): Expr[List[Any]] = {
    import quotes.reflect.*
    // assume something is a top level object
    val _package = TypeRepr.of[T].typeSymbol.owner 
    val fields = _package.declaredFields
    val packageExpr = _package.tree.asExpr //  assertion failed: Cannot get tree of package symbol
    val packageTree = packageExpr.asTerm
    val fieldExprs : List[Expr[Any]] = fields.map( f => packageTree.select(f).asExpr)

    Expr.ofList(fieldExprs)
}

and an example of usage would be:

// in actions.scala
package potato

object Actions // dummy object for finding package

object yelling extends Action("yell")

object sleeping extends Action("sleep")

object typing extends Action("type")

and in main:

import potato.*
val actions = Macros.packageToucher(Actions) // should be List(Actions, yelling, sleeping, typing)

Any help would be appreciated. I am a bit of a noob when it comes to scala 3 macros.

Thanks!

Upvotes: 2

Views: 287

Answers (1)

Pyromuffin
Pyromuffin

Reputation: 49

Well, after a lot of hard work, trial, and error: I have come to a solution. Perhaps it will be useful to others.

  inline def packageToucher[T](something : T) : List[Any] = {
    ${ packageToucherImpl('something) }
  }

  @experimental // needed for access to Symbol.termRef
  def packageToucherImpl[T](something : Expr[T])(using Quotes, Type[T]): Expr[List[Any]] = {
    import quotes.reflect.*
    // assume something is a top level object
    val _package = TypeRepr.of[T].typeSymbol.owner

    val fields = _package.fieldMembers

    // for some reason, scala generates erased companion objects for Classes that don't have them.
    // Luckily they can be filtered out by checking for the synthetic flag
    val valdefs = fields.filter(f => f.isValDef && !f.flags.is(Flags.Synthetic))

    // here's where the magic happens, we can't access the package's companion object tree directly
    // but we can construct one using AST nodes
    val packageTerm = Ident(_package.companionModule.termRef)
    val exprs = valdefs.map(Select(packageTerm, _).asExpr)

    Expr.ofList(exprs)
  }



Upvotes: 2

Related Questions