diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index d90e7b03e3f9..8684d7316963 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -495,7 +495,27 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas sym.setFlag(Scala2x) if (!(isRefinementClass(sym) || isUnpickleRoot(sym) || sym.is(Scala2Existential))) { val owner = sym.owner - if (owner.isClass) + val canEnter = + owner.isClass && + (!sym.is(TypeParam) || + owner.infoOrCompleter.match + case completer: ClassUnpickler => + // Type parameters seen after class initialization are not + // actually type parameters of the current class but of some + // external class because of the bizarre way in which Scala 2 + // pickles them (see + // https://github.com/scala/scala/blob/aa31e3e6bb945f5d69740d379ede1cd514904109/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala#L181-L197). + // Make sure we don't enter them in the class otherwise the + // compiler will get very confused (testcase in sbt-test/scala2-compat/i12641). + // Note: I don't actually know if these stray type parameters + // can also show up before initialization, if that's the case + // we'll need to study more closely how Scala 2 handles type + // parameter unpickling and try to emulate it. + !completer.isInitialized + case _ => + true) + + if (canEnter) owner.asClass.enter(sym, symScope(owner)) } sym @@ -625,23 +645,30 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas object localMemberUnpickler extends LocalUnpickler class ClassUnpickler(infoRef: Int) extends LocalUnpickler with TypeParamsCompleter { - private def readTypeParams()(using Context): List[TypeSymbol] = { + private var myTypeParams: List[TypeSymbol] = null + + private def readTypeParams()(using Context): Unit = { val tag = readByte() val end = readNat() + readIndex - if (tag == POLYtpe) { - val unusedRestpeRef = readNat() - until(end, () => readSymbolRef()(using ctx)).asInstanceOf[List[TypeSymbol]] - } - else Nil + myTypeParams = + if (tag == POLYtpe) { + val unusedRestpeRef = readNat() + until(end, () => readSymbolRef()(using ctx)).asInstanceOf[List[TypeSymbol]] + } else Nil } - private def loadTypeParams(using Context) = + private def loadTypeParams()(using Context) = atReadPos(index(infoRef), () => readTypeParams()(using ctx)) + /** Have the type params of this class already been unpickled? */ + def isInitialized: Boolean = myTypeParams ne null + /** Force reading type params early, we need them in setClassInfo of subclasses. */ - def init()(using Context): List[TypeSymbol] = loadTypeParams + def init()(using Context): List[TypeSymbol] = + if !isInitialized then loadTypeParams() + myTypeParams override def completerTypeParams(sym: Symbol)(using Context): List[TypeSymbol] = - loadTypeParams + init() } def rootClassUnpickler(start: Coord, cls: Symbol, module: Symbol, infoRef: Int): ClassUnpickler = diff --git a/sbt-test/scala2-compat/i12641/app/App.scala b/sbt-test/scala2-compat/i12641/app/App.scala new file mode 100644 index 000000000000..ad94f2d8ac83 --- /dev/null +++ b/sbt-test/scala2-compat/i12641/app/App.scala @@ -0,0 +1,9 @@ +import cek.Async + + +object demo { + + def test1[F[_]](ev: Async[F]): Unit = ??? + def test2[F[_]](ev: Async[F]): Unit = ??? + +} diff --git a/sbt-test/scala2-compat/i12641/build.sbt b/sbt-test/scala2-compat/i12641/build.sbt new file mode 100644 index 000000000000..433a5e8baddf --- /dev/null +++ b/sbt-test/scala2-compat/i12641/build.sbt @@ -0,0 +1,13 @@ +val scala3Version = sys.props("plugin.scalaVersion") +val scala2Version = sys.props("plugin.scala2Version") + +lazy val lib = project.in(file("lib")) + .settings( + scalaVersion := scala2Version + ) + +lazy val app = project.in(file("app")) + .dependsOn(lib) + .settings( + scalaVersion := scala3Version + ) diff --git a/sbt-test/scala2-compat/i12641/lib/Lib.scala b/sbt-test/scala2-compat/i12641/lib/Lib.scala new file mode 100644 index 000000000000..f58697edf4f8 --- /dev/null +++ b/sbt-test/scala2-compat/i12641/lib/Lib.scala @@ -0,0 +1,29 @@ +package cek + +trait Async[F[_]] + +object Async { + trait WriterTAsync[F[_], L1] + extends Async[({ type LL[A] = WriterT[F, L1, A] })#LL] + with MonadCancel.WriterTMonadCancel[F, L1] { + + override def delegate = super.delegate + } + +} + +case class WriterT[F[_], L0, V]() + +trait MonadError[F[_]] +trait MonadCancel[F[_]] + +object MonadCancel { + + trait WriterTMonadCancel[F[_], L2] + extends MonadCancel[({ type LL[A] = WriterT[F, L2, A] })#LL] { + + def delegate: MonadError[({ type LL[A] = WriterT[F, L2, A] })#LL] = + ??? + + } +} diff --git a/sbt-test/scala2-compat/i12641/test b/sbt-test/scala2-compat/i12641/test new file mode 100644 index 000000000000..19aca297fdcf --- /dev/null +++ b/sbt-test/scala2-compat/i12641/test @@ -0,0 +1 @@ +> app/compile