Skip to content
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

Open
MateuszKubuszok opened this issue May 7, 2024 · 1 comment
Labels
area:inline area:metaprogramming:quotes Issues related to quotes and splices itype:bug Spree Suitable for a future Spree

Comments

@MateuszKubuszok
Copy link

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 enums.

@Gedochao
Copy link

Gedochao commented May 9, 2024

behaviour still present on 3.5.0-RC1-bin-20240508-b10d64e-NIGHTLY

@mbovel mbovel added the Spree Suitable for a future Spree label May 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:inline area:metaprogramming:quotes Issues related to quotes and splices itype:bug Spree Suitable for a future Spree
Projects
None yet
Development

No branches or pull requests

3 participants