Skip to content

Fix #2948: Use symbol's info when mapping inherited denotations #2970

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 14 commits into from
Aug 16, 2017
6 changes: 2 additions & 4 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -362,13 +362,11 @@ trait ConstraintHandling {
def pruneLambdaParams(tp: Type) =
if (comparedTypeLambdas.nonEmpty) {
val approx = new ApproximatingTypeMap {
if (fromBelow) variance = -1
def apply(t: Type): Type = t match {
case t @ TypeParamRef(tl: TypeLambda, n) if comparedTypeLambdas contains tl =>
val effectiveVariance = if (fromBelow) -variance else variance
val bounds = tl.paramInfos(n)
if (effectiveVariance > 0) bounds.lo
else if (effectiveVariance < 0) bounds.hi
else NoType
range(bounds.lo, bounds.hi)
case _ =>
mapOver(t)
}
Expand Down
2 changes: 0 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -657,8 +657,6 @@ class Definitions {
def UncheckedStableAnnot(implicit ctx: Context) = UncheckedStableAnnotType.symbol.asClass
lazy val UncheckedVarianceAnnotType = ctx.requiredClassRef("scala.annotation.unchecked.uncheckedVariance")
def UncheckedVarianceAnnot(implicit ctx: Context) = UncheckedVarianceAnnotType.symbol.asClass
lazy val UnsafeNonvariantAnnotType = ctx.requiredClassRef("scala.annotation.internal.UnsafeNonvariant")
def UnsafeNonvariantAnnot(implicit ctx: Context) = UnsafeNonvariantAnnotType.symbol.asClass
lazy val VolatileAnnotType = ctx.requiredClassRef("scala.volatile")
def VolatileAnnot(implicit ctx: Context) = VolatileAnnotType.symbol.asClass
lazy val FieldMetaAnnotType = ctx.requiredClassRef("scala.annotation.meta.field")
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1005,7 +1005,7 @@ object Denotations {
case _ => if (symbol.exists) symbol.owner else NoSymbol
}
if (!owner.membersNeedAsSeenFrom(pre)) this
else derivedSingleDenotation(symbol, info.asSeenFrom(pre, owner))
else derivedSingleDenotation(symbol, symbol.info.asSeenFrom(pre, owner))
}

private def overlaps(fs: FlagSet)(implicit ctx: Context): Boolean = this match {
Expand Down
24 changes: 12 additions & 12 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1537,22 +1537,22 @@ object SymDenotations {
* have existing symbols.
* @param inherited The method is called on a parent class from computeNPMembersNamed
*/
final def nonPrivateMembersNamed(name: Name, inherited: Boolean = false)(implicit ctx: Context): PreDenotation = {
final def nonPrivateMembersNamed(name: Name)(implicit ctx: Context): PreDenotation = {
Stats.record("nonPrivateMembersNamed")
if (Config.cacheMembersNamed) {
var denots: PreDenotation = memberCache lookup name
if (denots == null) {
denots = computeNPMembersNamed(name, inherited)
denots = computeNPMembersNamed(name)
memberCache.enter(name, denots)
} else if (Config.checkCacheMembersNamed) {
val denots1 = computeNPMembersNamed(name, inherited)
val denots1 = computeNPMembersNamed(name)
assert(denots.exists == denots1.exists, s"cache inconsistency: cached: $denots, computed $denots1, name = $name, owner = $this")
}
denots
} else computeNPMembersNamed(name, inherited)
} else computeNPMembersNamed(name)
}

private[core] def computeNPMembersNamed(name: Name, inherited: Boolean)(implicit ctx: Context): PreDenotation = /*>|>*/ Stats.track("computeNPMembersNamed") /*<|<*/ {
private[core] def computeNPMembersNamed(name: Name)(implicit ctx: Context): PreDenotation = /*>|>*/ Stats.track("computeNPMembersNamed") /*<|<*/ {
Stats.record("computeNPMembersNamed after fingerprint")
ensureCompleted()
val ownDenots = info.decls.denotsNamed(name, selectNonPrivate)
Expand All @@ -1564,7 +1564,7 @@ object SymDenotations {
p.symbol.denot match {
case parentd: ClassDenotation =>
denots1 union
parentd.nonPrivateMembersNamed(name, inherited = true)
parentd.nonPrivateMembersNamed(name)
.mapInherited(ownDenots, denots1, thisType)
case _ =>
denots1
Expand Down Expand Up @@ -1768,19 +1768,19 @@ object SymDenotations {
* object that hides a class or object in the scala package of the same name, because
* the behavior would then be unintuitive for such members.
*/
override def computeNPMembersNamed(name: Name, inherited: Boolean)(implicit ctx: Context): PreDenotation =
override def computeNPMembersNamed(name: Name)(implicit ctx: Context): PreDenotation =
packageObj.moduleClass.denot match {
case pcls: ClassDenotation if !pcls.isCompleting =>
if (symbol eq defn.ScalaPackageClass) {
val denots = super.computeNPMembersNamed(name, inherited)
if (denots.exists) denots else pcls.computeNPMembersNamed(name, inherited)
val denots = super.computeNPMembersNamed(name)
if (denots.exists) denots else pcls.computeNPMembersNamed(name)
}
else {
val denots = pcls.computeNPMembersNamed(name, inherited)
if (denots.exists) denots else super.computeNPMembersNamed(name, inherited)
val denots = pcls.computeNPMembersNamed(name)
if (denots.exists) denots else super.computeNPMembersNamed(name)
}
case _ =>
super.computeNPMembersNamed(name, inherited)
super.computeNPMembersNamed(name)
}

/** The union of the member names of the package and the package object */
Expand Down
165 changes: 55 additions & 110 deletions compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,131 +19,76 @@ import ast.tpd._
trait TypeOps { this: Context => // TODO: Make standalone object.

/** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec
* for what this means. Called very often, so the code is optimized heavily.
*
* A tricky aspect is what to do with unstable prefixes. E.g. say we have a class
*
* class C { type T; def f(x: T): T }
*
* and an expression `e` of type `C`. Then computing the type of `e.f` leads
* to the query asSeenFrom(`C`, `(x: T)T`). What should its result be? The
* naive answer `(x: C#T)C#T` is incorrect given that we treat `C#T` as the existential
* `exists(c: C)c.T`. What we need to do instead is to skolemize the existential. So
* the answer would be `(x: c.T)c.T` for some (unknown) value `c` of type `C`.
* `c.T` is expressed in the compiler as a skolem type `Skolem(C)`.
*
* Now, skolemization is messy and expensive, so we want to do it only if we absolutely
* must. Also, skolemizing immediately would mean that asSeenFrom was no longer
* idempotent - each call would return a type with a different skolem.
* Instead we produce an annotated type that marks the prefix as unsafe:
*
* (x: (C @ UnsafeNonvariant)#T)C#T
*
* We also set a global state flag `unsafeNonvariant` to the current run.
* When typing a Select node, typer will check that flag, and if it
* points to the current run will scan the result type of the select for
* @UnsafeNonvariant annotations. If it finds any, it will introduce a skolem
* constant for the prefix and try again.
*
* The scheme is efficient in particular because we expect that unsafe situations are rare;
* most compiles would contain none, so no scanning would be necessary.
* for what this means.
*/
final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type =
asSeenFrom(tp, pre, cls, null)
new AsSeenFromMap(pre, cls).apply(tp)

/** Helper method, taking a map argument which is instantiated only for more
* complicated cases of asSeenFrom.
*/
private def asSeenFrom(tp: Type, pre: Type, cls: Symbol, theMap: AsSeenFromMap): Type = {

/** Map a `C.this` type to the right prefix. If the prefix is unstable and
* the `C.this` occurs in nonvariant or contravariant position, mark the map
* to be unstable.
*/
def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)") /*<|<*/ {
if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass))
tp
else pre match {
case pre: SuperType => toPrefix(pre.thistpe, cls, thiscls)
case _ =>
if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) {
if (theMap != null && theMap.currentVariance <= 0 && !isLegalPrefix(pre)) {
ctx.base.unsafeNonvariant = ctx.runId
pre match {
case AnnotatedType(_, ann) if ann.symbol == defn.UnsafeNonvariantAnnot => pre
case _ => AnnotatedType(pre, Annotation(defn.UnsafeNonvariantAnnot, Nil))
}
}
else pre
}
else if ((pre.termSymbol is Package) && !(thiscls is Package))
toPrefix(pre.select(nme.PACKAGE), cls, thiscls)
else
toPrefix(pre.baseTypeRef(cls).normalizedPrefix, cls.owner, thiscls)
/** The TypeMap handling the asSeenFrom */
class AsSeenFromMap(pre: Type, cls: Symbol) extends ApproximatingTypeMap {

def apply(tp: Type): Type = {

/** Map a `C.this` type to the right prefix. If the prefix is unstable and
* the `C.this` occurs in nonvariant or contravariant position, mark the map
* to be unstable.
*/
def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)") /*<|<*/ {
if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass))
tp
else pre match {
case pre: SuperType => toPrefix(pre.thistpe, cls, thiscls)
case _ =>
if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists)
if (variance <= 0 && !isLegalPrefix(pre)) range(pre.bottomType, pre)
else pre
else if ((pre.termSymbol is Package) && !(thiscls is Package))
toPrefix(pre.select(nme.PACKAGE), cls, thiscls)
else
toPrefix(pre.baseTypeRef(cls).normalizedPrefix, cls.owner, thiscls)
}
}
}

/*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG
tp match {
case tp: NamedType =>
val sym = tp.symbol
if (sym.isStatic) tp
else {
val pre1 = asSeenFrom(tp.prefix, pre, cls, theMap)
if (pre1.isUnsafeNonvariant) {
val safeCtx = ctx.withProperty(TypeOps.findMemberLimit, Some(()))
pre1.member(tp.name)(safeCtx).info match {
case TypeAlias(alias) =>
// try to follow aliases of this will avoid skolemization.
return alias
case _ =>
}
/*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG
tp match {
case tp: NamedType => // inlined for performance; TODO: factor out into inline method
if (tp.symbol.isStatic) tp
else {
val saved = variance
variance = variance max 0
val prefix1 = this(tp.prefix)
variance = saved
derivedSelect(tp, prefix1)
}
tp.derivedSelect(pre1)
}
case tp: ThisType =>
toPrefix(pre, cls, tp.cls)
case _: BoundType | NoPrefix =>
tp
case tp: RefinedType =>
tp.derivedRefinedType(
asSeenFrom(tp.parent, pre, cls, theMap),
tp.refinedName,
asSeenFrom(tp.refinedInfo, pre, cls, theMap))
case tp: TypeAlias if tp.variance == 1 => // if variance != 1, need to do the variance calculation
tp.derivedTypeAlias(asSeenFrom(tp.alias, pre, cls, theMap))
case _ =>
(if (theMap != null) theMap else new AsSeenFromMap(pre, cls))
.mapOver(tp)
case tp: ThisType =>
toPrefix(pre, cls, tp.cls)
case _: BoundType | NoPrefix =>
tp
case tp: RefinedType =>
derivedRefinedType(tp, apply(tp.parent), apply(tp.refinedInfo))
case tp: TypeAlias if tp.variance == 1 => // if variance != 1, need to do the variance calculation
derivedTypeAlias(tp, apply(tp.alias))
case _ =>
mapOver(tp)
}
}
}

override def reapply(tp: Type) =
// derives infos have already been subjected to asSeenFrom, hence to need to apply the map again.
tp
}

private def isLegalPrefix(pre: Type)(implicit ctx: Context) =
pre.isStable || !ctx.phase.isTyper

/** The TypeMap handling the asSeenFrom in more complicated cases */
class AsSeenFromMap(pre: Type, cls: Symbol) extends TypeMap {
def apply(tp: Type) = asSeenFrom(tp, pre, cls, this)

/** A method to export the current variance of the map */
def currentVariance = variance
}

/** Approximate a type `tp` with a type that does not contain skolem types. */
object deskolemize extends ApproximatingTypeMap {
private var seen: Set[SkolemType] = Set()
def apply(tp: Type) = tp match {
case tp: SkolemType =>
if (seen contains tp) NoType
else {
val saved = seen
seen += tp
try approx(hi = tp.info)
finally seen = saved
}
case _ =>
mapOver(tp)
def apply(tp: Type) = /*ctx.traceIndented(i"deskolemize($tp) at $variance", show = true)*/ {
tp match {
case tp: SkolemType => range(tp.bottomType, atVariance(1)(apply(tp.info)))
case _ => mapOver(tp)
}
}
}

Expand Down
Loading