首页 > 解决方案 > 获取模块符号,假设我有模块类,scala 宏

问题描述

我正在尝试IsEnum[T]使用宏构建一个简单的类型类。

knownDirectSubclasses用来获取所有直接子类 if T, ensureT是一个密封的特征,并且所有子类都是 case 对象(使用subSymbol.asClass.isModuleClass && subSymbol.asClass.isCaseClass)。

现在我正在尝试Seq使用子类引用的案例对象构建一个。

它正在工作,使用一种解决方法:

  Ident(subSymbol.asInstanceOf[scala.reflect.internal.Symbols#Symbol].sourceModule.asInstanceOf[Symbol])

但是我从其他一些问题中复制了它,但它看起来很笨拙和错误。为什么这行得通?有没有更清洁的方法来实现这一目标?

标签: scalaenumsscala-macros

解决方案


在 2.13 中,您可以实现scala.ValueOf

val instanceTree = c.inferImplicitValue(appliedType(typeOf[ValueOf[_]].typeConstructor, subSymbol.asClass.toType))
q"$instanceTree.value"

树会不一样

sealed trait A
object A {
  case object B extends A
  case object C extends A
}
//scalac: Seq(new scala.ValueOf(A.this.B).value, new scala.ValueOf(A.this.C).value)

但在运行时它仍然是Seq(B, C).

在 2.12shapeless.Witness中可以使用代替ValueOf

val instanceTree = c.inferImplicitValue(appliedType(typeOf[Witness.Aux[_]].typeConstructor, subSymbol.asClass.toType))
q"$instanceTree.value"
//scalac: Seq(Witness.mkWitness[App.A.B.type](A.this.B.asInstanceOf[App.A.B.type]).value, Witness.mkWitness[App.A.C.type](A.this.C.asInstanceOf[App.A.C.type]).value)
libraryDependencies += "com.chuusai" %% "shapeless" % "2.4.0-M1" // in 2.3.3 it doesn't work

在Shapeless中,他们使用了一种

subSymbol.asClass.toType match {
  case ref @ TypeRef(_, sym, _) if sym.isModuleClass => mkAttributedQualifier(ref)
}

https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/singletons.scala#L230

或者在我们的例子中

mkAttributedQualifier(subSymbol.asClass.toType)

但他们mkAttributedQualifier也对编译器内部使用向下转换,获得的树就像Seq(A.this.B, A.this.C).

Ident(subSymbol.companionSymbol)

似乎工作(树是Seq(B, C))但.companionSymbol已被弃用(在 scaladocs 中它写成“可能会返回模块类的意外结果”,即对象)。

遵循类似于@MateuszKubuszok在他的库enumz中使用的方法,您也可以尝试

val objectName = symbol.fullName
c.typecheck(c.parse(s"$objectName"))

树是Seq(App.A.B, App.A.C)

最后,如果您对树Seq(B, C)(而不是更复杂的树)感兴趣,您似乎可以替换

Ident(subSymbol.asInstanceOf[scala.reflect.internal.Symbols#Symbol].sourceModule.asInstanceOf[Symbol])

与更传统的

Ident(subSymbol.owner.info.decl(subSymbol.name.toTermName))

推荐阅读