Skip to content

Commit 6421ba6

Browse files
authored
Merge pull request #5287 from dotty-staging/fix-#5279
Fix #5279: Be more careful where we create symbolic refs from named ones
2 parents cd3cfce + 378ddce commit 6421ba6

File tree

4 files changed

+84
-14
lines changed

4 files changed

+84
-14
lines changed

compiler/src/dotty/tools/dotc/core/Types.scala

+54-9
Original file line numberDiff line numberDiff line change
@@ -1917,6 +1917,10 @@ object Types {
19171917
||
19181918
lastSymbol.infoOrCompleter.isInstanceOf[ErrorType]
19191919
||
1920+
!sym.exists
1921+
||
1922+
!lastSymbol.exists
1923+
||
19201924
sym.isPackageObject // package objects can be visited before we get around to index them
19211925
||
19221926
sym.owner != lastSymbol.owner &&
@@ -2079,17 +2083,36 @@ object Types {
20792083
else this
20802084

20812085
/** A reference like this one, but with the given denotation, if it exists.
2082-
* If the symbol of `denot` is the same as the current symbol, the denotation
2083-
* is re-used, otherwise a new one is created.
2086+
* Returns a new named type with the denotation's symbol if that symbol exists, and
2087+
* one of the following alternatives applies:
2088+
* 1. The current designator is a symbol and the symbols differ, or
2089+
* 2. The current designator is a name and the new symbolic named type
2090+
* does not have a currently known denotation.
2091+
* 3. The current designator is a name and the new symbolic named type
2092+
* has the same info as the current info
2093+
* Otherwise the current denotation is overwritten with the given one.
2094+
*
2095+
* Note: (2) and (3) are a "lock in mechanism" where a reference with a name as
2096+
* designator can turn into a symbolic reference.
2097+
*
2098+
* Note: This is a subtle dance to keep the balance between going to symbolic
2099+
* references as much as we can (since otherwise we'd risk getting cycles)
2100+
* and to still not lose any type info in the denotation (since symbolic
2101+
* references often recompute their info directly from the symbol's info).
2102+
* A test case is neg/opaque-self-encoding.scala.
20842103
*/
20852104
final def withDenot(denot: Denotation)(implicit ctx: Context): ThisType =
20862105
if (denot.exists) {
20872106
val adapted = withSym(denot.symbol)
2088-
if (adapted ne this) adapted.withDenot(denot).asInstanceOf[ThisType]
2089-
else {
2090-
setDenot(denot)
2091-
this
2092-
}
2107+
val result =
2108+
if (adapted.eq(this)
2109+
|| designator.isInstanceOf[Symbol]
2110+
|| !adapted.denotationIsCurrent
2111+
|| adapted.info.eq(denot.info))
2112+
adapted
2113+
else this
2114+
result.setDenot(denot)
2115+
result.asInstanceOf[ThisType]
20932116
}
20942117
else // don't assign NoDenotation, we might need to recover later. Test case is pos/avoid.scala.
20952118
this
@@ -2190,6 +2213,11 @@ object Types {
21902213
override protected def designator_=(d: Designator): Unit = myDesignator = d
21912214

21922215
override def underlying(implicit ctx: Context): Type = info
2216+
2217+
/** Hook that can be called from creation methods in TermRef and TypeRef */
2218+
def validated(implicit ctx: Context): this.type = {
2219+
this
2220+
}
21932221
}
21942222

21952223
final class CachedTermRef(prefix: Type, designator: Designator, hc: Int) extends TermRef(prefix, designator) {
@@ -2206,6 +2234,23 @@ object Types {
22062234
private def assertUnerased()(implicit ctx: Context) =
22072235
if (Config.checkUnerased) assert(!ctx.phase.erasedTypes)
22082236

2237+
/** The designator to be used for a named type creation with given prefix, name, and denotation.
2238+
* This is the denotation's symbol, if it exists and the prefix is not the this type
2239+
* of the class owning the symbol. The reason for the latter qualification is that
2240+
* when re-computing the denotation of a `this.<symbol>` reference we read the
2241+
* type directly off the symbol. But the given denotation might contain a more precise
2242+
* type than what can be computed from the symbol's info. We have to create in this case
2243+
* a reference with a name as designator so that the denotation will be correctly updated in
2244+
* the future. See also NamedType#withDenot. Test case is neg/opaque-self-encoding.scala.
2245+
*/
2246+
private def designatorFor(prefix: Type, name: Name, denot: Denotation)(implicit ctx: Context): Designator = {
2247+
val sym = denot.symbol
2248+
if (sym.exists && (prefix.eq(NoPrefix) || prefix.ne(sym.owner.thisType)))
2249+
sym
2250+
else
2251+
name
2252+
}
2253+
22092254
object NamedType {
22102255
def isType(desig: Designator)(implicit ctx: Context): Boolean = desig match {
22112256
case sym: Symbol => sym.isType
@@ -2229,7 +2274,7 @@ object Types {
22292274
* from the denotation's symbol if the latter exists, or else it is the given name.
22302275
*/
22312276
def apply(prefix: Type, name: TermName, denot: Denotation)(implicit ctx: Context): TermRef =
2232-
apply(prefix, if (denot.symbol.exists) denot.symbol.asTerm else name).withDenot(denot)
2277+
apply(prefix, designatorFor(prefix, name, denot)).withDenot(denot)
22332278
}
22342279

22352280
object TypeRef {
@@ -2242,7 +2287,7 @@ object Types {
22422287
* from the denotation's symbol if the latter exists, or else it is the given name.
22432288
*/
22442289
def apply(prefix: Type, name: TypeName, denot: Denotation)(implicit ctx: Context): TypeRef =
2245-
apply(prefix, if (denot.symbol.exists) denot.symbol.asType else name).withDenot(denot)
2290+
apply(prefix, designatorFor(prefix, name, denot)).withDenot(denot)
22462291
}
22472292

22482293
// --- Other SingletonTypes: ThisType/SuperType/ConstantType ---------------------------

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

+5-5
Original file line numberDiff line numberDiff line change
@@ -185,16 +185,16 @@ class TreePickler(pickler: TastyPickler) {
185185
writeByte(if (tpe.isType) TYPEREFdirect else TERMREFdirect)
186186
pickleSymRef(sym)
187187
}
188-
else if (isLocallyDefined(sym)) {
189-
writeByte(if (tpe.isType) TYPEREFsymbol else TERMREFsymbol)
190-
pickleSymRef(sym); pickleType(tpe.prefix)
191-
}
192188
else tpe.designator match {
193189
case name: Name =>
194190
writeByte(if (tpe.isType) TYPEREF else TERMREF)
195191
pickleName(name); pickleType(tpe.prefix)
196192
case sym: Symbol =>
197-
pickleExternalRef(sym)
193+
if (isLocallyDefined(sym)) {
194+
writeByte(if (tpe.isType) TYPEREFsymbol else TERMREFsymbol)
195+
pickleSymRef(sym); pickleType(tpe.prefix)
196+
}
197+
else pickleExternalRef(sym)
198198
}
199199
case tpe: ThisType =>
200200
if (tpe.cls.is(Flags.Package) && !tpe.cls.isEffectiveRoot) {

tests/neg/opaque-self-encoding.scala

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
trait TS { type TX = Int }
2+
trait TT { self: { type TX = Int } =>
3+
type TX
4+
def lift(x: Int): TX = x
5+
}
6+
7+
class Test {
8+
val t = new TT {}
9+
t.lift(1): Int // error
10+
}

tests/pos/refinements.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
trait TS { type TX = Int }
2+
trait TT { self: { type TX = Int } =>
3+
type TX
4+
def lift(x: Int): TX = x
5+
}
6+
7+
// A more direct version
8+
9+
trait UU {
10+
type UX
11+
val u: UX
12+
val x: this.type & { type UX = Int }
13+
val y: Int = x.u
14+
val z: x.UX = y
15+
}

0 commit comments

Comments
 (0)