Scala Macro get value for term name

試著忘記壹切 提交于 2019-12-06 02:49:15

问题


I have a following code:

usage.scala

object Test extends App {
   import Macros._

   val f = 1
   Macros.get(f) 
}

macros.scala

import language.experimental.macros
import scala.reflect.macros.Context

object Macros {
  def get(a: Int) = macro getImpl

  def getImpl(c: Context)(a: c.Expr[Int]) = {
    import c.universe._

     println(showRaw(a))
  }
}    

It return:

Expr(Select(This(newTypeName("Test")), newTermName("f")))

How to extract from termName("f") a 1 value ? It's possible with macros?


回答1:


In general - no. f could be abstract or defined as external call or user input or random or one of many other cases.

But in some particular cases you could get it. You know almost all that compiler knows!

Take a look at c.enclosingClass:

object Macros {
  def get(a: Int) = macro getImpl

  def getImpl(c: Context)(a: c.Expr[Int]) = {
    import c.universe._
    println(showRaw(c.enclosingClass))
    c.Expr[Unit](Literal(Constant(())))
  }
}    

object Test { val f = 1; Macros.get(f) }
// ModuleDef(Modifiers(), $line55.$read.$iw.$iw.$iw.$iw.Test, Template(List(Select(Ident(scala), newTypeName("AnyRef"))), emptyValDef, List(DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(newTypeName("Test")), tpnme.EMPTY), nme.CONSTRUCTOR), List())), Literal(Constant(())))), ValDef(Modifiers(), newTermName("f "), TypeTree(), Literal(Constant(1))), Apply(Select(Ident(newTermName("Macros")), newTermName("get")), List(Ident(newTermName("f")))))))

Interesting part here is ValDef(Modifiers(), newTermName("f "), TypeTree(), Literal(Constant(1))).

We have to extract it:

object Macros {
  def get(a: Int) = macro getImpl

  def getImpl(c: Context)(a: c.Expr[Int]) = {
    import c.universe._

    val (enclosing, name) = a.tree match {
      case Select(This(enclosing), name) => enclosing -> name
      case _ => c.abort(c.enclosingPosition, "Not a `this` memver")
    }
    val impl = c.enclosingClass match {
      case impl: ImplDef if impl.name.toString == enclosing.toString => impl
      case impl: ImplDef => c.abort(c.enclosingPosition, "Should search in another parent")
      case _ => c.abort(c.enclosingPosition, "Not an `ImplDef`")
    }
    val body = impl.children.collect{
      case Template(_, _, body) => body
    } match {
      case Seq(body) => body
      case _ => c.abort(c.enclosingPosition, "Should be a single template.")
    }
    val rhss = body.collect{
      case ValDef(_, valName, _, rhs) if valName.toString == name.toString => rhs
    }
    val rhs = rhss match {
      case Seq(rhs) => rhs
      case Seq() => c.abort(c.enclosingPosition, "Not found. Maybe it's a DefDef or somethong else")
      case _ => c.abort(c.enclosingPosition, "Some other error.")
    }
    val res = rhs match {
      case Literal(Constant(i: Int)) => i
      case Literal(Constant(_)) => c.abort(c.enclosingPosition, "Literal, but not an Int.")
      case _ => c.abort(c.enclosingPosition, "Implemented not as literal.")
    }
    println(s"Int value in this particular case: $res")
    c.Expr[Any](Literal(Constant(res)))
  }
}   

Result:

object Test { val f = 1; Macros.get(f) }
// Int value in this particular case: 1

So we have a value of f in compile time.

I'm pretty sure this is not what you expected.



来源:https://stackoverflow.com/questions/20805160/scala-macro-get-value-for-term-name

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!