-
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
Referencing a tree of an inner class leads to a ClassCastException #19732
Comments
Minimised: class Outer:
case class Inner(b: Int = 23) derives Defaults 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
Ref(sym.companionModule)
'{???} With that said, we can also produce a compiler crash in erasure without any class Outer:
case class Inner(b: Int = 23)
@main def test(): Unit =
val outer = Outer()
defaultParams[outer.Inner] import scala.quoted.*
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 }
|
The second minimisation I prepared above is a separate issue, where after getting a symbol of a path dependent TypeRepr we lose information about the prefix there, changing back to a typeref we find out that it is now prefixed by |
Submitted the issue: #19933 |
#19930) In the issue minimisation, `tpd.ref` returns a tpd.This(Ident(Inner$)) tree, where the inner ident is a type ident, which was unexpected. Trying to adjust the `tpd.ref` to return a RefType for that case seems dangerous, so I instead opted for a workaround directly inside the QuotesImpl. At first I wanted to reuse the `tpd.This` tree somehow (or at least to use a correctly retyped `Ident`), but nothing I tried with it worked, so I ended up writing the correct Ref by hand. Fixes #19732
@Kordyjan is this getting a backport to 3.3.x? |
It was backported, can be tried with 3.3.4-RC1 |
Awesome, both besom and scala-yaml have the workaround. I'll test on 3.3.4-RC1. |
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:
dummy.scala:
How to 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 byderives
. Only happens for inner classes, other cases seem to work fine.Output (click arrow to expand)
The text was updated successfully, but these errors were encountered: