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
1
  • I updated my answer with examples of macro annotations to be added in Scala 3.3.0 Commented Mar 22, 2023 at 8:20

1 Answer 1

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

Sign up to request clarification or add additional context in comments.

2 Comments

Why generate a nested type if it cannot be used outisde? I'm struggling to see the point of MacroAnnotations.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.