Open
Description
Compiler version
3.3.3
Minimized code
repro.scala
//> using scala 3.3.3
import scala.quoted.*
object Macros {
def matchOnImpl[A: Type](a: Expr[A])(using quotes: Quotes): Expr[Unit] = {
import quotes.*, quotes.reflect.*
// workaround to contain @experimental from polluting the whole codebase
object FreshTerm {
private val impl = quotes.reflect.Symbol.getClass.getMethod("freshName", classOf[String])
def generate(prefix: String): String = impl.invoke(quotes.reflect.Symbol, prefix).asInstanceOf[String]
}
extension (sym: Symbol)
def isPublic: Boolean = !sym.isNoSymbol &&
!(sym.flags.is(Flags.Private) || sym.flags.is(Flags.PrivateLocal) || sym.flags.is(Flags.Protected) ||
sym.privateWithin.isDefined || sym.protectedWithin.isDefined)
def isSealed[A: Type]: Boolean =
TypeRepr.of[A].typeSymbol.flags.is(Flags.Sealed)
def extractSealedSubtypes[A: Type]: List[Type[?]] = {
def extractRecursively(sym: Symbol): List[Symbol] =
if sym.flags.is(Flags.Sealed) then sym.children.flatMap(extractRecursively)
else if sym.flags.is(Flags.Enum) then List(sym.typeRef.typeSymbol)
else if sym.flags.is(Flags.Module) then List(sym.typeRef.typeSymbol.moduleClass)
else List(sym)
extractRecursively(TypeRepr.of[A].typeSymbol).distinct.map(typeSymbol =>
typeSymbol.typeRef.asType
)
}
if isSealed[A] then {
val cases = extractSealedSubtypes[A].map { tpe =>
val sym = TypeRepr.of(using tpe).typeSymbol
val bindName = Symbol.newVal(Symbol.spliceOwner, FreshTerm.generate(sym.name.toLowerCase), TypeRepr.of[A],Flags.EmptyFlags, Symbol.noSymbol)
val body = '{ println(${ Expr(sym.name) }) }.asTerm
if sym.flags.is(Flags.Enum | Flags.JavaStatic) then
CaseDef(Bind(bindName, Ident(sym.termRef)), None, body)
else if sym.flags.is(Flags.Module) then
CaseDef(
Bind(bindName, Ident(sym.companionModule.termRef)),
None,
body
)
else
CaseDef(Bind(bindName, Typed(Wildcard(), TypeTree.of(using tpe))), None, body)
}
Match(a.asTerm, cases).asExprOf[Unit]
} else '{ () }
}
inline def matchOn[A](a: A): Unit = ${ matchOnImpl[A]('{ a }) }
}
repro.test.scala
//> using dep org.scalameta::munit:1.0.0-RC1
package test
sealed trait Upper
object Upper {
case object A extends Upper
case object B extends Upper
case object C extends Upper
}
sealed trait lower
object lower {
case object a extends lower
case object b extends lower
case object c extends lower
}
class Test extends munit.FunSuite {
test("should print its own name") {
Macros.matchOn[Upper](Upper.A)
Macros.matchOn[Upper](Upper.B)
Macros.matchOn[Upper](Upper.C)
Macros.matchOn[lower](lower.a)
Macros.matchOn[lower](lower.b)
Macros.matchOn[lower](lower.c)
}
}
scala-cli test .
Output
A$
B$
C$
a$
a$
a$
Expectation
A$
B$
C$
a$
b$
c$
When case object
with lowercased names is used match seem to fall through on the first case
. For the same macro code the behavior is correct if the name of case object starts with an upper case.
I haven't observed such issue with enum
s.