Get the module symbol, given I have the module class, scala macro

瘦欲@ 提交于 2021-01-20 13:08:23

问题


I'm trying to build a simple typeclass IsEnum[T] using a macro.

I use knownDirectSubclasses to get all the direct subclasses if T, ensure T is a sealed trait, and that all subclasses are of case objects (using subSymbol.asClass.isModuleClass && subSymbol.asClass.isCaseClass).

Now I'm trying to build a Seq with the case objects referred by the subclasses.

It's working, using a workaround:

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

But I copied that from some other question, yet it seems hacky and wrong. Why does that work? and is there a cleaner way to achieve that?


回答1:


In 2.13 you can materialize scala.ValueOf

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

Tree will be different

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)

but at runtime it's still Seq(B, C).

In 2.12 shapeless.Witness can be used instead of 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

In Shapeless they use kind of

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

or in our case simply

mkAttributedQualifier(subSymbol.asClass.toType)

but their mkAttributedQualifier also uses downcasting to compiler internals and the tree obtained is like Seq(A.this.B, A.this.C).

Also

Ident(subSymbol.companionSymbol)

seems to work (tree is Seq(B, C)) but .companionSymbol is deprecated (in scaladocs it's written "may return unexpected results for module classes" i.e. for objects).

Following approach similar to the one used by @MateuszKubuszok in his library enumz you can try also

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

and the tree is Seq(App.A.B, App.A.C).

Finally, if you're interested in the tree Seq(B, C) (and not some more complicated tree) it seems you can replace

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

with more conventional

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


来源:https://stackoverflow.com/questions/64121612/get-the-module-symbol-given-i-have-the-module-class-scala-macro

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