【问题标题】:How to recognize an anonymous class in a Scala macro?如何识别 Scala 宏中的匿名类?
【发布时间】:2020-10-28 00:05:30
【问题描述】:

我有一个枚举类成员的宏。我想扩展宏,以便它通过在表单中​​枚举到任何类成员中递归地工作:

    object obj {
      var name = "value"
      var nested = new {
        var x = 0
      }
    }

在转换到宏之前我使用的运行时反射中,对我来说效果很好的相应测试是symbol.info.widen =:= typeOf[AnyRef],但是这不能与宏一起使用,因为在这种情况下,类型不是 AnyRef,而是它的子类(细化) .

当我将类型打印到控制台时,我得到例如:

AnyRef{def x: Int; def x_=(x$1: Int): Unit}

当我列出所有基类时,我得到:

List(<refinement of AnyRef>, class Object, class Any)

我不能使用测试<:< typeOf[AnyRef],因为几乎任何东西都可以通过这样的测试。

我该如何测试?

这是函数的反射版本,工作正常:

  def listMembersNested_A(m: Any): Seq[(String, Any)] = {
    import scala.reflect.runtime.currentMirror
    import scala.reflect.runtime.universe._

    val anyMirror = currentMirror.reflect(m)
    val members = currentMirror.classSymbol(m.getClass).toType.members
    val items = for {
      symbol <- members
      if symbol.isTerm && !symbol.isMethod && !symbol.isModule
    } yield {
      val field = anyMirror.reflectField(symbol.asTerm)
      symbol.name.decodedName.toString.trim -> (if (symbol.info.widen =:= typeOf[AnyRef]) {
        listMembersNested_A(field.get)
      } else {
        field.get
      })
    }
    items.toSeq
  }

它的宏对应物(它是一个具体化宏):

    def impl[O: c.WeakTypeTag](c: blackbox.Context): c.Expr[ListMembersNested[O]] = {
      import c.universe._

      val O = weakTypeOf[O]

      val dive = O.members.sorted.collect {
        case f if f.isMethod && f.asMethod.paramLists.isEmpty && f.asMethod.isGetter =>
          val fName = f.name.decodedName.toString
          if (f.info.widen =:= typeOf[AnyRef]) { /// <<<<<< this does not work
            q"$fName -> listMembersNested(t.$f)"
          } else {
            q"$fName -> t.$f"
          }
      }
      val r = q" Seq(..$dive)"
      val membersExpr = c.Expr[Seq[(String, Any)]](r)

      reify {
        new ListMembersNested[O] {
          def listMembers(t: O) = membersExpr.splice
        }
      }
    }

【问题讨论】:

  • 如果我调用运行时反射版本listMembersNested_A(obj),那么它会产生List((nested,pckg.App$obj$$anon$1@7ae42ce3), (name,value)),即在您的if-else 中,两次都选择了分支field.get,并且永远不会选择分支listMembersNested_A(field.get)。这是预期的行为吗?
  • 你的宏版本listMembers(obj)(我定义了def listMembers[O](t: O)(implicit lmn: ListMembersNested[O]): Seq[(String, Any)] = lmn.listMembers(t))产生Seq("name".$minus$greater(t.name), "nested".$minus$greater(t.nested))。所以我看不出运行时反射版本的工作方式和宏版本的工作方式之间的区别。
  • "如果我调用运行时反射版本" 这真的很奇怪。在我的项目中调试函数时,它为我输入listMembersNested_A 递归,然后生成的结果是“List((nested,List((x,0))), (name,value))”。当我在 Scastie 尝试这个时,我得到的结果和你一样。这两者都适用于 Scala 2.12.12
  • 只有当obj 被定义为函数中的局部变量时,我才会得到我所描述的行为。当它是另一个对象/类的成员时,我会得到您所描述的行为。当我在 ScalaTest 中调试它时,它对我来说是一个局部变量。

标签: scala reflection scala-macros


【解决方案1】:

在你的宏中尝试用类型匹配的模式替换if-else

val anyRefType = typeOf[AnyRef]

f.info match {
  case NullaryMethodType(RefinedType(List(`anyRefType`), _)) =>
    q"$fName -> listMembersNested(t.$f)"
  case _ =>
    q"$fName -> t.$f"
}

【讨论】:

  • 太棒了。这真的有效。在阅读RefinedType ScalaDoc 之后,我虽然可以进行更具体的NullaryMethodType(RefinedType(List(AnyRef), _)) 匹配,但不知何故似乎不再匹配。
  • 我现在明白了。我需要匹配typeOf[AnyRef],而不是简单的AnyRef
  • @Suma 如果更方便也可以匹配f.info.resultType match { case RefinedType(List(`anyRefType`), _) =&gt; ...
猜你喜欢
  • 1970-01-01
  • 2012-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多