I'm wondering if it is possible to write a macro in Scala 3 that take a set of strings and turn it into an enum type with with those strings as cases?
For example, I would like to write a class with an internal type generated from the input element:
import scala.quoted.*
class Example(myEnumElements:Seq[String]) {
inline def buildEnum(inline elts:Seq[String]): Unit = ${ buildEnumType('elts) }
def buildEnumType(e: Expr[Seq[String]])(using Quotes, Type[Seq]): Expr[Unit] = '{
enum MyEnum:
???
}
}
...
// Possibly in another file?
val example = Example(Seq("A","B","C"))
def someConvenienceFunction(e:example.MyEnum) = e match
case A => "apple"
case B => "banana"
case C => "cranberry"
...
// Possibly in another file?
someConvenienceFunction(example.A) // "apple"
someConvenienceFunction(example.D) // compile error
Scala 3 macros are currently only def macros. They are not for generating classes, enums etc. Even if you define an enum inside buildEnumType
it will be visible only inside the block {...}
that buildEnum
call expands into.
Try to use code generation instead.
How to generate a class in Dotty with macro?
https://users.scala-lang.org/t/macro-annotations-replacement-in-scala-3/7374
How to create variables with macros in Scala (Scala 2)
Resolving variables in scope modified by Scala macro (Scala 2)
Starting from Scala 3.3.0-RC2, there appeared macro annotations (implemented by Nicolas Stucki).
Macro annotation (part 1) https://github.com/lampepfl/dotty/pull/16392
Macro annotations class modifications (part 2) https://github.com/lampepfl/dotty/pull/16454
Enable returning classes from MacroAnnotations (part 3) https://github.com/lampepfl/dotty/pull/16534
New definitions are not visible from outside the macro expansion.
The macro annotation generating "enum" (a seled trait and case objects extending the trait) should be the following:
build.sbt
scalaVersion := "3.3.0-RC3"
import scala.annotation.{MacroAnnotation, experimental}
import scala.quoted.*
object Macros {
@experimental
class buildEnum extends MacroAnnotation:
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
import quotes.reflect.*
extension (symb: Symbol)
def setFlags(flags: Flags): Symbol =
given dotty.tools.dotc.core.Contexts.Context =
quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
symb.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol]
.denot.setFlag(flags.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet])
symb
tree match
case ClassDef(name, constr, parents, selfOpt, body) =>
val parents = List(TypeTree.of[Any])
val cls = Symbol.newClass(tree.symbol, "MyEnum", parents.map(_.tpe), decls = _ => Nil, selfType = None)
.setFlags(Flags.Trait | Flags.Sealed)
val clsDef = ClassDef(cls, parents, body = Nil)
def mkObject(name: String): (ValDef, ClassDef) =
val modParents = List(TypeTree.of[Any], Inferred(cls.typeRef))
val mod = Symbol.newModule(tree.symbol, name, modFlags = Flags.EmptyFlags, clsFlags = Flags.EmptyFlags,
modParents.map(_.tpe), decls = _ => Nil, privateWithin = Symbol.noSymbol)
.setFlags(Flags.Case)
//val modCls = mod.moduleClass
ClassDef.module(mod, modParents, body = Nil)
val enumTrees = clsDef :: List("A", "B", "C").map(mkObject(_).toList).reduce(_ ++ _)
val res = List(ClassDef.copy(tree)(name, constr, parents, selfOpt, body ++ enumTrees))
println(res.map(_.show))
res
case _ =>
report.errorAndAbort("@modifyObj can annotate only classes")
import Macros.buildEnum
import scala.quoted.*
import scala.annotation.experimental
object App:
@buildEnum @experimental
class Example(myEnumElements:Seq[String])
//scalac: List(@scala.annotation.experimental @Macros6.buildEnum class Example(myEnumElements: scala.Seq[scala.Predef.String]) extends scala.Any {
// sealed trait MyEnum extends scala.Any
// object A extends scala.Any with Example.this.MyEnum { this: Example.this.A.type =>
// }
// object B extends scala.Any with Example.this.MyEnum { this: Example.this.B.type =>
// }
// object C extends scala.Any with Example.this.MyEnum { this: Example.this.C.type =>
// }
//})
How to generate a class in Dotty with macro?
How to generate parameterless constructor at compile time using scala 3 macro?