flaviut
flaviut

Reputation: 2143

Collecting all identifers in scope

I'm trying to collect all the identifiers that exist in scope by using an implicit macro, but I can't find an elegant way to find them.

val endPos = c.enclosingPosition.endOrPoint
val mNames = c.enclosingMethod
             .collect {case v: ValDef => v}
                 .filter(_.pos.start > endPos)
                 .map {case ValDef(_, name, _, _) => Ident(name)}
val cNames = c.enclosingClass
             .collect {case ValDef(_, name, _, _) => Ident(name)}
val names = mNames ++ cNames

The previous captures all variables, even if they are defined in different methods, but I need a way to filter the ValDefs that occur in methods from cNames. Calling children seemed like it might have worked, but I still get the ValDefs from inside other blocks.

My final goal is to be able to do Logger.trace() and have all the variables in scope logged. So

def test() = {
  val v = 2
  val z = "fdsaf"
  Logger.trace()
}

would log v: 2, z: "fdsaf"

Edit 2:

import c.universe._
val callingPos = c.enclosingPosition
val encMet = Option(c.enclosingMethod)

val classElements = c.enclosingClass
                    .asInstanceOf[ImplDefApi]
                    .impl.children
                    .collect {
    case v: Block => v.asInstanceOf[Tree]
    case v: DefDef => v.asInstanceOf[Tree]
    case v: ValDef => v.asInstanceOf[Tree]
    case v: ModuleDef => v.asInstanceOf[Tree]
}

val classFields = classElements.collect {case v@ValDef(_, name, _, _) if !v.isEmpty => Ident(name)}
val blockFields = classElements.collectFirst{
    case Block(body, ret) if positionIsInList(c)(body) => body.collect {
        case v@ValDef(_, name, _, _) if v.pos precedes callingPos => Ident(name)
    }
}.getOrElse(List())
val methodFields = encMet
                   .map(_.collect {case v@ValDef(_, name, _, _) if v.pos precedes callingPos => Ident(name)})
                   .getOrElse(List())

val names = classFields ++ blockFields ++ methodFields

Works in most cases, but fails on blocks.

Upvotes: 2

Views: 119

Answers (1)

Eugene Burmako
Eugene Burmako

Reputation: 13048

Unfortunately this use case is not something that is supported by Scala macros at the moment. Sure, something more or less working can be whipped up as you've demonstrated (thanks a lot for the examples by the way!), but to the best of my knowledge there's no robust way of achieving your goal.

In the future we might be able to tackle this challenge, but currently there are technical limitations that prevent us from exposing this kind of an API at the moment. Even casting to compiler internals, which sometimes helps in tricky cases, isn't going to help here. Actually, this is exactly the reason why in Scala 2.11 we have deprecated c.enclosingTree-style APIs: https://groups.google.com/forum/#!topic/scala-internals/nf_ooEBn6-k.

If you could elaborate on what you're trying to achieve, I would be glad to help with looking into workarounds that might end up being useful to your use case.

Upvotes: 3

Related Questions