Skip to content

Fix #5279: Be more careful where we create symbolic refs from named ones #5287

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 54 additions & 9 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1917,6 +1917,10 @@ object Types {
||
lastSymbol.infoOrCompleter.isInstanceOf[ErrorType]
||
!sym.exists
||
!lastSymbol.exists
||
sym.isPackageObject // package objects can be visited before we get around to index them
||
sym.owner != lastSymbol.owner &&
Expand Down Expand Up @@ -2070,17 +2074,36 @@ object Types {
else this

/** A reference like this one, but with the given denotation, if it exists.
* If the symbol of `denot` is the same as the current symbol, the denotation
* is re-used, otherwise a new one is created.
* Returns a new named type with the denotation's symbol if that symbol exists, and
* one of the following alternatives applies:
* 1. The current designator is a symbol and the symbols differ, or
* 2. The current designator is a name and the new symbolic named type
* does not have a currently known denotation.
* 3. The current designator is a name and the new symbolic named type
* has the same info as the current info
* Otherwise the current denotation is overwritten with the given one.
*
* Note: (2) and (3) are a "lock in mechanism" where a reference with a name as
* designator can turn into a symbolic reference.
*
* Note: This is a subtle dance to keep the balance between going to symbolic
* references as much as we can (since otherwise we'd risk getting cycles)
* and to still not lose any type info in the denotation (since symbolic
* references often recompute their info directly from the symbol's info).
* A test case is neg/opaque-self-encoding.scala.
*/
final def withDenot(denot: Denotation)(implicit ctx: Context): ThisType =
if (denot.exists) {
val adapted = withSym(denot.symbol)
if (adapted ne this) adapted.withDenot(denot).asInstanceOf[ThisType]
else {
setDenot(denot)
this
}
val result =
if (adapted.eq(this)
|| designator.isInstanceOf[Symbol]
|| !adapted.denotationIsCurrent
|| adapted.info.eq(denot.info))
adapted
else this
result.setDenot(denot)
result.asInstanceOf[ThisType]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess adapted.eq(this) is a performance tweak, semantically we can drop it without changing the result.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact.

}
else // don't assign NoDenotation, we might need to recover later. Test case is pos/avoid.scala.
this
Expand Down Expand Up @@ -2181,6 +2204,11 @@ object Types {
override protected def designator_=(d: Designator): Unit = myDesignator = d

override def underlying(implicit ctx: Context): Type = info

/** Hook that can be called from creation methods in TermRef and TypeRef */
def validated(implicit ctx: Context): this.type = {
this
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validated is not used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I do use if occasionally for debugging, and decided to leave it in for others to use as well if needed.

}

final class CachedTermRef(prefix: Type, designator: Designator, hc: Int) extends TermRef(prefix, designator) {
Expand All @@ -2197,6 +2225,23 @@ object Types {
private def assertUnerased()(implicit ctx: Context) =
if (Config.checkUnerased) assert(!ctx.phase.erasedTypes)

/** The designator to be used for a named type creation with given prefix, name, and denotation.
* This is the denotation's symbol, if it exists and the prefix is not the this type
* of the class owning the symbol. The reason for the latter qualification is that
* when re-computing the denotation of a `this.<symbol>` reference we read the
* type directly off the symbol. But the given denotation might contain a more precise
* type than what can be computed from the symbol's info. We have to create in this case
* a reference with a name as designator so that the denotation will be correctly updated in
* the future. See also NamedType#withDenot. Test case is neg/opaque-self-encoding.scala.
*/
private def designatorFor(prefix: Type, name: Name, denot: Denotation)(implicit ctx: Context): Designator = {
val sym = denot.symbol
if (sym.exists && (prefix.eq(NoPrefix) || prefix.ne(sym.owner.thisType)))
sym
else
name
}

object NamedType {
def isType(desig: Designator)(implicit ctx: Context): Boolean = desig match {
case sym: Symbol => sym.isType
Expand All @@ -2220,7 +2265,7 @@ object Types {
* from the denotation's symbol if the latter exists, or else it is the given name.
*/
def apply(prefix: Type, name: TermName, denot: Denotation)(implicit ctx: Context): TermRef =
apply(prefix, if (denot.symbol.exists) denot.symbol.asTerm else name).withDenot(denot)
apply(prefix, designatorFor(prefix, name, denot)).withDenot(denot)
}

object TypeRef {
Expand All @@ -2233,7 +2278,7 @@ object Types {
* from the denotation's symbol if the latter exists, or else it is the given name.
*/
def apply(prefix: Type, name: TypeName, denot: Denotation)(implicit ctx: Context): TypeRef =
apply(prefix, if (denot.symbol.exists) denot.symbol.asType else name).withDenot(denot)
apply(prefix, designatorFor(prefix, name, denot)).withDenot(denot)
}

// --- Other SingletonTypes: ThisType/SuperType/ConstantType ---------------------------
Expand Down
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -185,16 +185,16 @@ class TreePickler(pickler: TastyPickler) {
writeByte(if (tpe.isType) TYPEREFdirect else TERMREFdirect)
pickleSymRef(sym)
}
else if (isLocallyDefined(sym)) {
writeByte(if (tpe.isType) TYPEREFsymbol else TERMREFsymbol)
pickleSymRef(sym); pickleType(tpe.prefix)
}
else tpe.designator match {
case name: Name =>
writeByte(if (tpe.isType) TYPEREF else TERMREF)
pickleName(name); pickleType(tpe.prefix)
case sym: Symbol =>
pickleExternalRef(sym)
if (isLocallyDefined(sym)) {
writeByte(if (tpe.isType) TYPEREFsymbol else TERMREFsymbol)
pickleSymRef(sym); pickleType(tpe.prefix)
}
else pickleExternalRef(sym)
}
case tpe: ThisType =>
if (tpe.cls.is(Flags.Package) && !tpe.cls.isEffectiveRoot) {
Expand Down
10 changes: 10 additions & 0 deletions tests/neg/opaque-self-encoding.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
trait TS { type TX = Int }
trait TT { self: { type TX = Int } =>
type TX
def lift(x: Int): TX = x
}

class Test {
val t = new TT {}
t.lift(1): Int // error
}
15 changes: 15 additions & 0 deletions tests/pos/refinements.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
trait TS { type TX = Int }
trait TT { self: { type TX = Int } =>
type TX
def lift(x: Int): TX = x
}

// A more direct version

trait UU {
type UX
val u: UX
val x: this.type & { type UX = Int }
val y: Int = x.u
val z: x.UX = y
}