Skip to content

Commit 302c5f9

Browse files
committed
Scala2Unpickler: Don't trust the owner field of tparams
When a type parameter of a class external to the current pickle is referenced, Scala 2 will sometimes pickle it as if it was a type parameter of the current class. When we unpickle such a type parameter on our side, we enter it in the class, this leads the compiler to believe the class has more type parameters than it actually has. This doesn't happen in Scala 2 itself because it never enters unpickled type parameters in the class that owns them (it must be recreating them in some way, but I don't know how). It would be interesting to dig deeper to understand exactly how Scala 2 handles type parameter unpickling so we could emulate it given that this is not the first time we run into trouble here (cf #12120), but for now we fix the problem with the following heuristic: once we've loaded all the type parameters of a class (which we do eagerly via `ClassUnpickler#init()`), we simply stop from being entered any subsequent type parameter we see for this class. Time will tell if this is good enough. While I was touching that code, I also made ClassCompleter#completerTypeParams idempotent as one would expect it to be. Fixes #12641.
1 parent 56abade commit 302c5f9

File tree

1 file changed

+37
-10
lines changed

1 file changed

+37
-10
lines changed

compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala

+37-10
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,27 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
495495
sym.setFlag(Scala2x)
496496
if (!(isRefinementClass(sym) || isUnpickleRoot(sym) || sym.is(Scala2Existential))) {
497497
val owner = sym.owner
498-
if (owner.isClass)
498+
val canEnter =
499+
owner.isClass &&
500+
(!sym.is(TypeParam) ||
501+
owner.infoOrCompleter.match
502+
case completer: ClassUnpickler =>
503+
// Type parameters seen after class initialization are not
504+
// actually type parameters of the current class but of some
505+
// external class because of the bizarre way in which Scala 2
506+
// pickles them (see
507+
// https://github.com/scala/scala/blob/aa31e3e6bb945f5d69740d379ede1cd514904109/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala#L181-L197).
508+
// Make sure we don't enter them in the class otherwise the
509+
// compiler will get very confused (testcase in sbt-test/scala2-compat/i12641).
510+
// Note: I don't actually know if these stray type parameters
511+
// can also show up before initialization, if that's the case
512+
// we'll need to study more closely how Scala 2 handles type
513+
// parameter unpickling and try to emulate it.
514+
!completer.isInitialized
515+
case _ =>
516+
true)
517+
518+
if (canEnter)
499519
owner.asClass.enter(sym, symScope(owner))
500520
}
501521
sym
@@ -625,23 +645,30 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
625645
object localMemberUnpickler extends LocalUnpickler
626646

627647
class ClassUnpickler(infoRef: Int) extends LocalUnpickler with TypeParamsCompleter {
628-
private def readTypeParams()(using Context): List[TypeSymbol] = {
648+
private var myTypeParams: List[TypeSymbol] = null
649+
650+
private def readTypeParams()(using Context): Unit = {
629651
val tag = readByte()
630652
val end = readNat() + readIndex
631-
if (tag == POLYtpe) {
632-
val unusedRestpeRef = readNat()
633-
until(end, () => readSymbolRef()(using ctx)).asInstanceOf[List[TypeSymbol]]
634-
}
635-
else Nil
653+
myTypeParams =
654+
if (tag == POLYtpe) {
655+
val unusedRestpeRef = readNat()
656+
until(end, () => readSymbolRef()(using ctx)).asInstanceOf[List[TypeSymbol]]
657+
} else Nil
636658
}
637-
private def loadTypeParams(using Context) =
659+
private def loadTypeParams()(using Context) =
638660
atReadPos(index(infoRef), () => readTypeParams()(using ctx))
639661

662+
/** Have the type params of this class already been unpickled? */
663+
def isInitialized: Boolean = myTypeParams ne null
664+
640665
/** Force reading type params early, we need them in setClassInfo of subclasses. */
641-
def init()(using Context): List[TypeSymbol] = loadTypeParams
666+
def init()(using Context): List[TypeSymbol] =
667+
if !isInitialized then loadTypeParams()
668+
myTypeParams
642669

643670
override def completerTypeParams(sym: Symbol)(using Context): List[TypeSymbol] =
644-
loadTypeParams
671+
init()
645672
}
646673

647674
def rootClassUnpickler(start: Coord, cls: Symbol, module: Symbol, infoRef: Int): ClassUnpickler =

0 commit comments

Comments
 (0)