Scala 2.13 - lambdalift transform two trees with the same structure to different bytecode

I’m writing macro that extracts all guards from match case in form of List[A => Boolean] (repository: GitHub - Lifeblossom/scala-macro-bug-report). It looks like this

import scala.reflect.macros.whitebox

class MacroImpl(val c: whitebox.Context) {

  import c.universe._

  def derive[T: WeakTypeTag](pf: MacroImpl.this.c.Tree): Tree = {

    val functions = pf
      .collect { case c: CaseDef => c}
      .map {
      case CaseDef(literal: Literal, EmptyTree, _) =>
        q"(t: ${weakTypeTag[T]}) => t == ${literal}"
      case CaseDef(Bind(term, Ident(termNames.WILDCARD)), guard, _) =>
        q"(${term.toTermName}: ${weakTypeTag[T]}) => ${guard}"

      case cf =>
        throw new RuntimeException(
          s"Cannot handle case [${cf}]. Raw tree [${showRaw(cf)}]"
        )
    }

    println(s"RES: [${functions}]")
    println(s"TREE 1: ${showRaw(functions(0))}")
    println(s"TREE 2: ${showRaw(functions(1))}")
    println(functions(0).equalsStructure(functions(1)))

    q"Seq(..${functions})"
  }
}

I’m testing it with following test:

object Test {
  val functions = Macro.magicMatch[String, String]({
    case "abc"           => "Case 1"
    case t if t == "abc" => "Case 2"
    case "abc"           => "Case 3"
    case t if t == "abc" => "Case 4"
  })
}

Every returned A => Boolean function has the same tree: Function(List(ValDef(Modifiers(PARAM), TermName("t"), TypeTree(), EmptyTree)), Apply(Select(Ident(TermName("t")), TermName("$eq$eq")), List(Literal(Constant("abc"))))) which transforms to the same bytecode i.e

      final <artifact> def $anonfun$functions(t: String): Boolean = t.==("abc");
      ((t: String) => $anonfun$functions(t))
    }, {
      final <artifact> def $anonfun$functions(t: String): Boolean = t.==("abc");
      ((t: String) => $anonfun$functions(t))
    }, {
      final <artifact> def $anonfun$functions(t: String): Boolean = t.==("abc");
      ((t: String) => $anonfun$functions(t))
    }, {
      final <artifact> def $anonfun$functions(t: String): Boolean = t.==("abc");
      ((t: String) => $anonfun$functions(t))
    }}));

after uncurry stage. But after lambdalift they differ

    final <artifact> private[this] def $anonfun$functions$1(t: String): Boolean = t.==("abc");
    final <artifact> private[this] def $anonfun$functions$2(t$1: String, t: String): Boolean = t$1.==("abc");
    final <artifact> private[this] def $anonfun$functions$3(t: String): Boolean = t.==("abc");
    final <artifact> private[this] def $anonfun$functions$4(t$2: String, t: String): Boolean = t$2.==("abc")

while in theory it shouldn’t cause any problems (calls are changed accordingly) the whole compilation fails on

[error] Error while emitting Test.scala
[error] value t

Does it mean that despite having being the same, trees extracted from case t if t == "abc" has some hidden properties that only lambdalift uses?

Symbols have “owners”, generally an enclosing element, so if a tree is transformed “unhygienically”, the owner chain is broken. Lambda lift is where the breakage is commonly noticed.