-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Misbehavior of macro-generated match when matching case object with lowercase name #20350
Comments
behaviour still present on 3.5.0-RC1-bin-20240508-b10d64e-NIGHTLY |
@hamzaremmal do you think that would be appropriate for a spree? |
Still not sure if this is appropriate for a spree and how hard that would be to fix. |
Sorry @mbovel, I haven't seen the first ping. I don't think it's suitable for a spree. |
It's not a bug in compiler. The actual issue is in macros. This version ( without a branch for module) works fine Fixed version//> 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
CaseDef(Bind(bindName, Typed(Wildcard(), TypeTree.of(using tpe))), None, body)
}
Match(a.asTerm, cases).asExprOf[Unit]
} else '{ () }
} Explanation: The removed from above code: CaseDef(
Bind(bindName, Ident(sym.companionModule.termRef)),
None,
body
) generates the following code: b match
case a$$macro1 @ a => println("a")
case b$$macro1 @ b => println("b") which is equivavlent to Example in scastie - https://scastie.scala-lang.org/s0mcdSvpTqOJ3fPXDpnEtQ |
So in summary, we might have thought that this macro generates this (which would work correctly): (lower.b: lower) match
case amacro @ lower.a => (...)
case bmacro @ lower.b => (...)
case bmacro @ lower.c => (...) but it actually interprets it as this: (lower.b: lower) match
case amacro @ a => (...)
case bmacro @ b => (...)
case bmacro @ c => (...) This would make a lot of sense, as that explains the different behavior of Upper and lower, however the worrying part are the compiler printouts showing what gets generated: lower.b match
{
case a$$macro$5 @ lower.a => println("a$")
case b$$macro$5 @ lower.b => println("b$")
case c$$macro$5 @ lower.c => println("c$")
} which looks correct. |
Created However, it's an
|
Yeah, we did tackle this at some point - I think it was a spree. And basically there's some code branch (I think it was within Quotes) that considers that |
@dos65 @dwijnand Thank you for the explanations! I imagine the best course of action would be to add a check for incorrect/unexpected Idents in -Xcheck-macros (the main fear here is there could be a lot of those in the already published libraries), alternatively we could sanitize Idents into correct forms in Quotes IdentModule.apply() depending on the termRef (this might be unpopular/cause other unexpected issues) |
Compiler version
3.3.3
Minimized code
repro.scala
repro.test.scala
Output
Expectation
When
case object
with lowercased names is used match seem to fall through on the firstcase
. 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.The text was updated successfully, but these errors were encountered: