Skip to content

Commit a74470c

Browse files
Fix #19732: quotes.reflect.Ref incorrectly casting This to RefTree (#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
2 parents 4859415 + 4c0a644 commit a74470c

File tree

4 files changed

+58
-1
lines changed

4 files changed

+58
-1
lines changed

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
468468
withDefaultPos(tpd.ref(tp).asInstanceOf[tpd.RefTree])
469469
def apply(sym: Symbol): Ref =
470470
assert(sym.isTerm)
471-
withDefaultPos(tpd.ref(sym).asInstanceOf[tpd.RefTree])
471+
val refTree = tpd.ref(sym) match
472+
case t @ tpd.This(ident) => // not a RefTree, so we need to work around this - issue #19732
473+
// ident in `This` can be a TypeIdent of sym, so we manually prepare the ref here,
474+
// knowing that the owner is actually `This`.
475+
val term = Select(This(sym.owner), sym)
476+
term.asInstanceOf[tpd.RefTree]
477+
case other => other.asInstanceOf[tpd.RefTree]
478+
withDefaultPos(refTree)
472479
end Ref
473480

474481
type Ident = tpd.Ident

tests/run-macros/i19732.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Map(b -> 23)

tests/run-macros/i19732/Macro_1.scala

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// package dummy
2+
3+
import scala.quoted.*
4+
5+
trait Defaults[A]:
6+
def defaults: Map[String, Any]
7+
8+
object Defaults:
9+
inline def derived[A <: Product]: Defaults[A] = ${ defaultsImpl[A] }
10+
11+
def defaultsImpl[A <: Product: Type](using Quotes): Expr[Defaults[A]] =
12+
'{
13+
new Defaults[A]:
14+
def defaults: Map[String, Any] = ${ defaultParmasImpl[A] }
15+
}
16+
17+
def defaultParmasImpl[T](using quotes: Quotes, tpe: Type[T]): Expr[Map[String, Any]] =
18+
import quotes.reflect.*
19+
20+
TypeRepr.of[T].classSymbol match
21+
case None => '{ Map.empty[String, Any] }
22+
case Some(sym) =>
23+
val comp = sym.companionClass
24+
val mod = Ref(sym.companionModule)
25+
val names =
26+
for p <- sym.caseFields if p.flags.is(Flags.HasDefault)
27+
yield p.name
28+
val namesExpr: Expr[List[String]] =
29+
Expr.ofList(names.map(Expr(_)))
30+
31+
val body = comp.tree.asInstanceOf[ClassDef].body
32+
val idents: List[Ref] =
33+
for
34+
case deff @ DefDef(name, _, _, _) <- body
35+
if name.startsWith("$lessinit$greater$default")
36+
yield mod.select(deff.symbol)
37+
val typeArgs = TypeRepr.of[T].typeArgs
38+
val identsExpr: Expr[List[Any]] =
39+
if typeArgs.isEmpty then Expr.ofList(idents.map(_.asExpr))
40+
else Expr.ofList(idents.map(_.appliedToTypes(typeArgs).asExpr))
41+
42+
'{ $namesExpr.zip($identsExpr).toMap }

tests/run-macros/i19732/Test_2.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class Outer:
2+
case class Inner(a: String, b: Int = 23) derives Defaults
3+
4+
object Test:
5+
def main(args: Array[String]): Unit =
6+
val outer = Outer()
7+
println(summon[Defaults[outer.Inner]].defaults)

0 commit comments

Comments
 (0)