Skip to content

Commit 9f8d126

Browse files
committed
Avoid recursion when transforming types used in Holes
Previously, the AvoidMap used in getPicklableHoleType would try to replace recursive type parameters obtained via F-bounds) with LazyRefs, which would expand to include another LazyRef, etc. Now we explicitly guard against recursing over already visited TypeRefs, instead replacing them with RecTypes (which are safe to pickle).
1 parent 7328cd6 commit 9f8d126

File tree

2 files changed

+52
-1
lines changed

2 files changed

+52
-1
lines changed

compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,47 @@ class PickleQuotes extends MacroTransform {
199199
ctx.typeAssigner.assignType(untpd.TypeDef(local.name, hole), local).withSpan(typeArg.span)
200200
}
201201

202-
/** Avoid all non-static types except those defined in the quote. */
202+
/** Avoid all non-static types except those defined in the quote.
203+
* Also avoid recursive LazyRef references that can be generated by TypeBounds.
204+
*/
203205
private def getPicklableHoleType(tpe: Type, isStagedClasses: Symbol => Boolean)(using Context) =
204206
new TypeOps.AvoidMap {
207+
val processedTypeRefs = util.HashSet[Type]()
205208
def toAvoid(tp: NamedType) = !isStagedClasses(tp.typeSymbol) && !isStaticPrefix(tp)
209+
override def apply(tp: Type) = tp match
210+
case typeRef: TypeRef if toAvoid(typeRef) =>
211+
if processedTypeRefs.contains(typeRef) then typeRef
212+
else
213+
typeRef.info match
214+
case TypeBounds(lo, hi) =>
215+
val typeRefRemapper = (recType: RecType) => new TypeMap {
216+
def apply(t: Type) = t match
217+
case lr: LazyRef if lr.ref == typeRef => recType.recThis
218+
case tr: TypeRef if tr == typeRef => recType.recThis
219+
case lr: LazyRef => lr
220+
case _ => mapOver(t)
221+
}
222+
processedTypeRefs.add(typeRef)
223+
val newLo =
224+
RecType.closeOver(typeRefRemapper(_).apply(atVariance(-variance)(apply(lo))))
225+
val newHi =
226+
apply(hi) match
227+
case Range(l, h) =>
228+
Range(
229+
RecType.closeOver(typeRefRemapper(_).apply(l)),
230+
RecType.closeOver(typeRefRemapper(_).apply(h))
231+
)
232+
case other =>
233+
RecType.closeOver(typeRefRemapper(_).apply(other))
234+
processedTypeRefs.remove(typeRef)
235+
range(newLo, newHi)
236+
case _ =>
237+
super.apply(tp)
238+
case lazyRef: LazyRef =>
239+
if processedTypeRefs.contains(lazyRef.ref) then lazyRef
240+
else super.apply(lazyRef)
241+
case _ =>
242+
super.apply(tp)
206243
}.apply(tpe)
207244
}
208245

tests/pos/i22996.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import scala.quoted.*
2+
3+
trait Appendable[T]
4+
trait Format[T] { def empty: T }
5+
6+
object FormatMacros {
7+
8+
def test[T <: Appendable[T]: Type, F <: Format[T]: Type](f: Expr[F])(using Quotes): Expr[T] =
9+
'{ $f.empty }
10+
11+
def test2[T <: Appendable[T2]: Type, T2 <: Appendable[T], F <: Format[T]: Type](f: Expr[F])(using Quotes): Expr[T] =
12+
'{ $f.empty }
13+
14+
}

0 commit comments

Comments
 (0)