【问题标题】:Get an scala.MatchError: f (of class scala.reflect.internal.Trees$Ident) when providing a lambda assigned to a val在提供分配给 val 的 lambda 时获取 scala.MatchError: f (of class scala.reflect.internal.Trees$Ident)
【发布时间】:2022-09-28 15:07:10
【问题描述】:

我有点像发现一个用例的宏,在这个用例中,我试图从函数中提取 lambda arg 名称。为此,我定义了这个类(假设在模块 A 中):

object MacroTest {

  def getLambdaArgNames[A, B](f: A => B): String = macro getLambdaArgNamesImpl[A, B]

  def getLambdaArgNamesImpl[A, B](c: Context)(f: c.Expr[A => B]): c.Expr[String] = {
    import c.universe._
    val Function(args, body) = f.tree
    val names = args.map(_.name)
    val argNames = names.mkString(\", \")
    val constant = Literal(Constant(argNames))
    c.Expr[String](q\"$constant\")
  }

现在在另一个模块中,我正在尝试编写一个单元,例如检查传递给 lambda 的参数名称:

class TestSomething extends AnyFreeSpec with Matchers {
  \"test\" in {
    val f = (e1: Expr[Int]) => e1 === 3
    val argNames = MacroTest.getLambdaArgNames(f)
    println(argNames)
    assert(argNames === \"e1\")
  }
}

但是这段代码不能编译,因为: scala.MatchError: f (of class scala.reflect.internal.Trees$Ident)

但是,如果我直接将 lambda 传递给像 MacroTest.getLambdaArgNames((e1: Expr[Int]) => e1 === 3) 这样的函数,它就可以工作,所以我对导致代码无法编译的原因非常迷茫。

任何可能的解决方案来解决这个问题?

    标签: scala scala-macros


    【解决方案1】:

    这与Scala macro inspect tree for anonymous function 中的问题相同,您在其中评论过:您确实需要将 lambda 本身传递给宏,正如您所说

    如果我直接将 lambda 传递给像 MacroTest.getLambdaArgNames((e1: Expr[Int]) => e1 === 3) 这样的函数,它正在工作

    当您编写MacroTest.getLambdaArgNames(f) 时,AST 参数(getLambdaArgNamesImpl 中的f: c.Expr[A => B])只存储标识符f,而 lambda 的 AST 不会存储在任何地方。

    或者,您可以将 lambda 的 AST 存储在 f 中,而不是 lambda 本身:

    val f = q"(e1: Expr[Int]) => e1 === 3"
    

    in Scala 2,

    val f = '{ (e1: Expr[Int]) => e1 === 3 }
    

    in Scala 3,然后使getLambdaArgNames成为正常功能。

    【讨论】:

    • 所以如果我理解得很好,有没有办法直接从 lambda 本身获取 lambda 的来源?
    • 是的。编译后信息根本不会存储在任何地方(有些语言可以,例如 JavaScript,但不是 Scala)。也许对于这种特定情况,您可以改用反射,但这取决于我目前不知道的编译细节,并且可以在 Scala 版本之间轻松更改。
    【解决方案2】:

    尝试使用Traverser 的方法

    def getLambdaArgNamesImpl[A, B](c: blackbox.Context)(f: c.Expr[A => B]): c.Expr[String] = {
      import c.universe._
    
      val arguments = f.tree match {
        case Function(args, body) => args
    
        case _ =>
          var rhs: Option[Tree] = None
    
          val traverser = new Traverser {
            override def traverse(tree: Tree): Unit = {
              tree match {
                case q"$_ val f: $_ = $expr"
                  if tree.symbol == f.tree.symbol ||
                    (tree.symbol.isTerm && tree.symbol.asTerm.getter == f.tree.symbol) =>
                  rhs = Some(expr)
                case _ => super.traverse(tree)
              }
            }
          }
    
          c.enclosingRun.units.foreach(unit => traverser.traverse(unit.body))
    
          rhs match {
            case Some(Function(args, body)) => args
            case _ => c.abort(c.enclosingPosition, "can't find definition of val f")
          }
      }
    
      val names = arguments.map(_.name)
      val argNames = names.mkString(", ")
      val constant = Literal(Constant(argNames))
      c.Expr[String](q"$constant")
    }
    

    f 是局部变量时,情况tree.symbol == f.tree.symbol 匹配

    class TestSomething extends AnyFreeSpec with Matchers {
      "test" in {
        val f = (e1: Expr[Int]) => e1 === 3
        ...
    

    f 是类中的字段时,案例tree.symbol.asTerm.getter == f.tree.symbol 匹配

    class TestSomething extends AnyFreeSpec with Matchers {
      val f = (e1: Expr[Int]) => e1 === 3
      "test" in {
        ...
    

    Def Macro, pass parameter from a value

    Creating a method definition tree from a method symbol and a body

    Scala macro how to convert a MethodSymbol to DefDef with parameter default values?

    How to get the runtime value of parameter passed to a Scala macro?

    【讨论】:

    • 如果您将 lambda 直接提供给函数,它仍然有效吗?
    • @alifirat 你是对的。不是。固定的。
    • @alifirat 当然,如果您做的事情比单个作业更复杂,例如像val f0 = (e1: Expr[Int]) => e1 === 3; val f = f0; getLambdaArgNames(f) 然后它会再次中断。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    • 1970-01-01
    • 2018-03-05
    • 1970-01-01
    • 1970-01-01
    • 2018-08-23
    相关资源
    最近更新 更多