diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index d8f7dd32c9bd..2e49e7ffce72 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -125,6 +125,18 @@ trait TypeAssigner { else qualType + def maybeSkolemizePrefix(tree: untpd.Tree, qualType: Type, name: Name)(using Context): Type = + if name.isTermName && !TypeOps.isLegalPrefix(qualType) then + tree.getAttachment(QualSkolem) match + case Some(skolem) if skolem.info == qualType => + skolem + case _ => + val skolem = QualSkolemType(qualType) + tree.putAttachment(QualSkolem, skolem) + skolem + else + qualType + /** The type of the selection `tree`, where `qual1` is the typed qualifier part. */ def selectionType(tree: untpd.RefTree, qual1: Tree)(using Context): Type = val qualType = @@ -155,7 +167,7 @@ trait TypeAssigner { // is casted to T[] by javac. Since the return type of Array[T]#clone() is Array[T], // this is exactly what Erasure will do. case _ => - val pre = maybeSkolemizePrefix(qualType, name) + val pre = maybeSkolemizePrefix(tree, qualType, name) val mbr = if ctx.isJava then // don't look in the companion class here if qual is a module, @@ -621,6 +633,11 @@ object TypeAssigner extends TypeAssigner: */ private[dotc] val SkolemizedArgs = new Property.StickyKey[Map[tpd.Tree, SkolemType]] + /** An attachment on a selection holding the `QualSkolemType` used to type it. + * Sticky so that copies of the tree (e.g. while inlining) keep the same skolem. + */ + private[dotc] val QualSkolem = new Property.StickyKey[QualSkolemType] + def seqLitType(tree: untpd.SeqLiteral, elemType: Type)(using Context) = tree match case tree: untpd.JavaSeqLiteral => defn.ArrayOf(elemType) case _ => if ctx.erasedTypes then defn.SeqType else defn.SeqType.appliedTo(elemType) diff --git a/compiler/test/dotc/run-test-pickling.excludelist b/compiler/test/dotc/run-test-pickling.excludelist index 6aa8a67982b3..05f772a17cc2 100644 --- a/compiler/test/dotc/run-test-pickling.excludelist +++ b/compiler/test/dotc/run-test-pickling.excludelist @@ -52,5 +52,9 @@ typeCheckErrors.scala i18150.scala i22968.scala +# Unstable-prefix selection: typer skolemizes, unpickling recomputes without skolem +# so info goes from Box[(Owner#w : Int)] to Box[? <: (Owner#w : Int)]. Separate fix later. +i26681.scala + # Pickling differences with local parameters export forwarders of methods with into parameters. But their external type is the same Parser.scala diff --git a/tests/neg/i26681b.scala b/tests/neg/i26681b.scala new file mode 100644 index 000000000000..2e3aa19f22f5 --- /dev/null +++ b/tests/neg/i26681b.scala @@ -0,0 +1,16 @@ +// https://github.com/scala/scala3/issues/26681 +class Box[T](val x: T) + +class Owner: + val w: Int = 8 + val b: Box[w.type] = new Box(w) + def get: Box[w.type] = new Box(w) + def set(bb: Box[w.type]): Unit = () + +def unstable: Owner = new Owner + +def same[T](a: Box[T], b: Box[T]): Unit = () + +def t1 = same(unstable.b, unstable.b) // error +def t2 = same(unstable.get, unstable.get) // error +def t3 = unstable.set(unstable.get) // error diff --git a/tests/run/i26681.check b/tests/run/i26681.check new file mode 100644 index 000000000000..79ebd0860f49 --- /dev/null +++ b/tests/run/i26681.check @@ -0,0 +1,2 @@ +ok +ok diff --git a/tests/run/i26681.scala b/tests/run/i26681.scala new file mode 100644 index 000000000000..5f299659f94b --- /dev/null +++ b/tests/run/i26681.scala @@ -0,0 +1,19 @@ +// https://github.com/scala/scala3/issues/26681 +class Box[T] + +class Owner: + val w: Int = 8 + val b: Box[w.type] = new Box[w.type] + +def use[T](b: Box[T]): String = "ok" + +def unstable: Owner = new Owner + +transparent inline def f(inline rhs: Any): Any = + inline rhs match + case r: Box[t] => use[t](r) + +@main def Test = + println(f(unstable.b)) + val o = unstable + println(f(o.b))