Skip to content

Commit

Permalink
Fix #19732: quotes.reflect.Ref incorrectly casting This to RefTree (
Browse files Browse the repository at this point in the history
#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
  • Loading branch information
nicolasstucki authored Mar 14, 2024
2 parents 4859415 + 4c0a644 commit a74470c
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 1 deletion.
9 changes: 8 additions & 1 deletion compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
withDefaultPos(tpd.ref(tp).asInstanceOf[tpd.RefTree])
def apply(sym: Symbol): Ref =
assert(sym.isTerm)
withDefaultPos(tpd.ref(sym).asInstanceOf[tpd.RefTree])
val refTree = tpd.ref(sym) match
case t @ tpd.This(ident) => // not a RefTree, so we need to work around this - issue #19732
// ident in `This` can be a TypeIdent of sym, so we manually prepare the ref here,
// knowing that the owner is actually `This`.
val term = Select(This(sym.owner), sym)
term.asInstanceOf[tpd.RefTree]
case other => other.asInstanceOf[tpd.RefTree]
withDefaultPos(refTree)
end Ref

type Ident = tpd.Ident
Expand Down
1 change: 1 addition & 0 deletions tests/run-macros/i19732.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Map(b -> 23)
42 changes: 42 additions & 0 deletions tests/run-macros/i19732/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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] }
}

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 }
7 changes: 7 additions & 0 deletions tests/run-macros/i19732/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Outer:
case class Inner(a: String, b: Int = 23) derives Defaults

object Test:
def main(args: Array[String]): Unit =
val outer = Outer()
println(summon[Defaults[outer.Inner]].defaults)

0 comments on commit a74470c

Please sign in to comment.