Skip to content

Referencing a tree of an inner class leads to a ClassCastException #19732

Closed
@lbialy

Description

@lbialy

Compiler version

3.3.1

Minimized code

This is a slightly changed code from https://github.com/lampepfl/dotty-macro-examples/tree/main/defaultParamsInference, two files as in the example:

macro.scala:

package dummy

import scala.quoted.*

trait Defaults[A]:
  def defaults: Map[String, Any]

object Defaults:
  inline def derived[A <: Product]: Defaults[A] = ${ defaultsImpl[A] }

  def defaultsImpl[A <: Product: Type](using Quotes): Expr[Defaults[A]] =
    '{
      new Defaults[A]:
        def defaults: Map[String, Any] = ${ defaultParmasImpl[A] }
    }

inline def defaultParams[T]: Map[String, Any] = ${ defaultParmasImpl[T] }

def defaultParmasImpl[T](using quotes: Quotes, tpe: Type[T]): Expr[Map[String, Any]] =
  import quotes.reflect.*
  TypeRepr.of[T].classSymbol match
    case None => '{ Map.empty[String, Any] }
    case Some(sym) =>
      val comp = sym.companionClass
      val mod = Ref(sym.companionModule)
      val names =
        for p <- sym.caseFields if p.flags.is(Flags.HasDefault)
        yield p.name
      val namesExpr: Expr[List[String]] =
        Expr.ofList(names.map(Expr(_)))

      val body = comp.tree.asInstanceOf[ClassDef].body
      val idents: List[Ref] =
        for
          case deff @ DefDef(name, _, _, _) <- body
          if name.startsWith("$lessinit$greater$default")
        yield mod.select(deff.symbol)
      val typeArgs = TypeRepr.of[T].typeArgs
      val identsExpr: Expr[List[Any]] =
        if typeArgs.isEmpty then Expr.ofList(idents.map(_.asExpr))
        else Expr.ofList(idents.map(_.appliedToTypes(typeArgs).asExpr))

      '{ $namesExpr.zip($identsExpr).toMap }

dummy.scala:

package dummy

case class Person(name: String, address: String = "Zuricch", foo: Int, age: Int = 26)
case class Test3[A, B](as: List[A], bs: List[B] = List.empty)

object Person:
  val x = 10

class Outer:
  case class Inner(a: String, b: Int = 23) derives Defaults

@main def test(): Unit =
  val p1 = Person("John", foo = 10)
  val t1 = Test3(List("string"), List(23.0))
  println(p1)
  println(t1)
  println(defaultParams[Person])
  println(defaultParams[Test3[String, Double]])
  assert(defaultParams[Person] == Map("address" -> "Zuricch", "age" -> 26))

  val outer = Outer()
  println(summon[Defaults[outer.Inner]].defaults)

How to run:

  • place both files in an empty directory
  • run scala-cli run . -S 3.3.1 --server=false

For some reason this does not happen when using the defaultParams macro directly but does happen if the same macro is executed via macro called by derives. Only happens for inner classes, other cases seem to work fine.

Output (click arrow to expand)

-- Error: /Users/lbialy/Projects/foss/tmp/macros/dummy.scala:10:51 -------------
10 |  case class Inner(a: String, b: Int = 23) derives Defaults
   |                                                   ^
   |Exception occurred while executing macro expansion.
   |java.lang.ClassCastException: class dotty.tools.dotc.ast.Trees$This cannot be cast to class dotty.tools.dotc.ast.Trees$RefTree (dotty.tools.dotc.ast.Trees$This and dotty.tools.dotc.ast.Trees$RefTree are in unnamed module of loader 'app')
   |	at scala.quoted.runtime.impl.QuotesImpl.scala$quoted$runtime$impl$QuotesImpl$reflect$Ref$$$_$apply$$anonfun$6(QuotesImpl.scala:445)
   |	at scala.quoted.runtime.impl.QuotesImpl$reflect$.scala$quoted$runtime$impl$QuotesImpl$reflect$$$withDefaultPos(QuotesImpl.scala:2997)
   |	at scala.quoted.runtime.impl.QuotesImpl$reflect$Ref$.apply(QuotesImpl.scala:445)
   |	at scala.quoted.runtime.impl.QuotesImpl$reflect$Ref$.apply(QuotesImpl.scala:443)
   |	at dummy.macro$package$.defaultParmasImpl(macro.scala:25)
   |	at dummy.Defaults$.defaultsImpl$$anonfun$1(macro.scala:14)
   |	at dummy.Defaults$.defaultsImpl$$anonfun$adapted$1(macro.scala:14)
   |	at dotty.tools.dotc.quoted.PickledQuotes$$anon$1.transform(PickledQuotes.scala:110)
   |	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1538)
   |	at dotty.tools.dotc.quoted.PickledQuotes$$anon$1.transform(PickledQuotes.scala:135)
   |	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1570)
   |	at dotty.tools.dotc.quoted.PickledQuotes$$anon$1.transform(PickledQuotes.scala:135)
   |	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform$$anonfun$1(Trees.scala:1605)
   |	at scala.collection.immutable.List.mapConserve(List.scala:472)
   |	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1605)
   |	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transformStats(Trees.scala:1601)
   |	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1574)
   |	at dotty.tools.dotc.quoted.PickledQuotes$$anon$1.transform(PickledQuotes.scala:135)
   |	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1572)
   |	at dotty.tools.dotc.quoted.PickledQuotes$$anon$1.transform(PickledQuotes.scala:135)
   |	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform$$anonfun$1(Trees.scala:1605)
   |	at scala.collection.immutable.List.mapConserve(List.scala:472)
   |	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1605)
   |	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transformStats(Trees.scala:1601)
   |	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transformBlock(Trees.scala:1603)
   |	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1518)
   |	at dotty.tools.dotc.quoted.PickledQuotes$$anon$1.transform(PickledQuotes.scala:135)
   |	at dotty.tools.dotc.quoted.PickledQuotes$.spliceTerms(PickledQuotes.scala:152)
   |	at dotty.tools.dotc.quoted.PickledQuotes$.unpickleTerm(PickledQuotes.scala:88)
   |	at scala.quoted.runtime.impl.QuotesImpl.unpickleExprV2(QuotesImpl.scala:3143)
   |	at dummy.Defaults$.defaultsImpl(macro.scala:14)
   |
   |----------------------------------------------------------------------------
   |Inline stack trace
   |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   |This location contains code that was inlined from macro.scala:9
 9 |  inline def derived[A <: Product]: Defaults[A] = ${ defaultsImpl[A] }
   |                                                  ^^^^^^^^^^^^^^^^^^^^
    ----------------------------------------------------------------------------
1 error found
Compilation failed

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions