0

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?

Pᴇʜ
  • 56,719
  • 10
  • 49
  • 73
caeus
  • 3,084
  • 1
  • 22
  • 36
  • 2
    If you cannot use enumeratum (which provides `Enum` type class for its own types) I implemented `Enum` type class which works for: enumeratum, enumaration, Java enums and sealed hierarchies of case objects - https://github.com/scalalandio/enumz . You can take a look at macros there if you are interested. – Mateusz Kubuszok Sep 29 '20 at 14:39
  • 1
    @MateuszKubuszok I guess manual parsing/typechecking can be replaced with `Ident(subSymbol.owner.info.decl(subSymbol.name.toTermName))`. – Dmytro Mitin Sep 30 '20 at 19:59
  • 1
    Good to know, thanks! – Mateusz Kubuszok Sep 30 '20 at 23:01
  • The shortest is `Ident(subSymbol.asClass.module)`. – Dmytro Mitin Sep 18 '22 at 13:20

1 Answers1

3

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/*ModuleSymbol*/])

with more conventional

Ident(subSymbol.owner.info.decl(subSymbol.name.toTermName)/*.asModule*/)

or (the shortest option)

Ident(subSymbol.asClass.module/*.asModule*/)
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66