Scala 3 macro to create enum
Asked Answered
W

1

1

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
Woodring answered 17/9, 2021 at 18:1 Comment(1)
I updated my answer with examples of macro annotations to be added in Scala 3.3.0Overthrust
O
1

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 =>
//  }
//})

Macro Annotations in Scala 3

How to generate a class in Dotty with macro?

How to generate parameterless constructor at compile time using scala 3 macro?

Method Override with Scala 3 Macros

Overthrust answered 25/9, 2021 at 19:40 Comment(2)
Why generate a nested type if it cannot be used outisde? I'm struggling to see the point of MacroAnnotations.Lind
@Lind #76001721Overthrust

© 2022 - 2024 — McMap. All rights reserved.