Skip to content

Commit 9c4b88d

Browse files
authored
Merge pull request #2970 from dotty-staging/fix-#2948
Fix #2948: Use symbol's info when mapping inherited denotations
2 parents 79ce6c0 + 0c11d6d commit 9c4b88d

File tree

13 files changed

+354
-293
lines changed

13 files changed

+354
-293
lines changed

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -362,13 +362,11 @@ trait ConstraintHandling {
362362
def pruneLambdaParams(tp: Type) =
363363
if (comparedTypeLambdas.nonEmpty) {
364364
val approx = new ApproximatingTypeMap {
365+
if (fromBelow) variance = -1
365366
def apply(t: Type): Type = t match {
366367
case t @ TypeParamRef(tl: TypeLambda, n) if comparedTypeLambdas contains tl =>
367-
val effectiveVariance = if (fromBelow) -variance else variance
368368
val bounds = tl.paramInfos(n)
369-
if (effectiveVariance > 0) bounds.lo
370-
else if (effectiveVariance < 0) bounds.hi
371-
else NoType
369+
range(bounds.lo, bounds.hi)
372370
case _ =>
373371
mapOver(t)
374372
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,8 +657,6 @@ class Definitions {
657657
def UncheckedStableAnnot(implicit ctx: Context) = UncheckedStableAnnotType.symbol.asClass
658658
lazy val UncheckedVarianceAnnotType = ctx.requiredClassRef("scala.annotation.unchecked.uncheckedVariance")
659659
def UncheckedVarianceAnnot(implicit ctx: Context) = UncheckedVarianceAnnotType.symbol.asClass
660-
lazy val UnsafeNonvariantAnnotType = ctx.requiredClassRef("scala.annotation.internal.UnsafeNonvariant")
661-
def UnsafeNonvariantAnnot(implicit ctx: Context) = UnsafeNonvariantAnnotType.symbol.asClass
662660
lazy val VolatileAnnotType = ctx.requiredClassRef("scala.volatile")
663661
def VolatileAnnot(implicit ctx: Context) = VolatileAnnotType.symbol.asClass
664662
lazy val FieldMetaAnnotType = ctx.requiredClassRef("scala.annotation.meta.field")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1005,7 +1005,7 @@ object Denotations {
10051005
case _ => if (symbol.exists) symbol.owner else NoSymbol
10061006
}
10071007
if (!owner.membersNeedAsSeenFrom(pre)) this
1008-
else derivedSingleDenotation(symbol, info.asSeenFrom(pre, owner))
1008+
else derivedSingleDenotation(symbol, symbol.info.asSeenFrom(pre, owner))
10091009
}
10101010

10111011
private def overlaps(fs: FlagSet)(implicit ctx: Context): Boolean = this match {

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,22 +1540,22 @@ object SymDenotations {
15401540
* have existing symbols.
15411541
* @param inherited The method is called on a parent class from computeNPMembersNamed
15421542
*/
1543-
final def nonPrivateMembersNamed(name: Name, inherited: Boolean = false)(implicit ctx: Context): PreDenotation = {
1543+
final def nonPrivateMembersNamed(name: Name)(implicit ctx: Context): PreDenotation = {
15441544
Stats.record("nonPrivateMembersNamed")
15451545
if (Config.cacheMembersNamed) {
15461546
var denots: PreDenotation = memberCache lookup name
15471547
if (denots == null) {
1548-
denots = computeNPMembersNamed(name, inherited)
1548+
denots = computeNPMembersNamed(name)
15491549
memberCache.enter(name, denots)
15501550
} else if (Config.checkCacheMembersNamed) {
1551-
val denots1 = computeNPMembersNamed(name, inherited)
1551+
val denots1 = computeNPMembersNamed(name)
15521552
assert(denots.exists == denots1.exists, s"cache inconsistency: cached: $denots, computed $denots1, name = $name, owner = $this")
15531553
}
15541554
denots
1555-
} else computeNPMembersNamed(name, inherited)
1555+
} else computeNPMembersNamed(name)
15561556
}
15571557

1558-
private[core] def computeNPMembersNamed(name: Name, inherited: Boolean)(implicit ctx: Context): PreDenotation = /*>|>*/ Stats.track("computeNPMembersNamed") /*<|<*/ {
1558+
private[core] def computeNPMembersNamed(name: Name)(implicit ctx: Context): PreDenotation = /*>|>*/ Stats.track("computeNPMembersNamed") /*<|<*/ {
15591559
Stats.record("computeNPMembersNamed after fingerprint")
15601560
ensureCompleted()
15611561
val ownDenots = info.decls.denotsNamed(name, selectNonPrivate)
@@ -1567,7 +1567,7 @@ object SymDenotations {
15671567
p.symbol.denot match {
15681568
case parentd: ClassDenotation =>
15691569
denots1 union
1570-
parentd.nonPrivateMembersNamed(name, inherited = true)
1570+
parentd.nonPrivateMembersNamed(name)
15711571
.mapInherited(ownDenots, denots1, thisType)
15721572
case _ =>
15731573
denots1
@@ -1771,19 +1771,19 @@ object SymDenotations {
17711771
* object that hides a class or object in the scala package of the same name, because
17721772
* the behavior would then be unintuitive for such members.
17731773
*/
1774-
override def computeNPMembersNamed(name: Name, inherited: Boolean)(implicit ctx: Context): PreDenotation =
1774+
override def computeNPMembersNamed(name: Name)(implicit ctx: Context): PreDenotation =
17751775
packageObj.moduleClass.denot match {
17761776
case pcls: ClassDenotation if !pcls.isCompleting =>
17771777
if (symbol eq defn.ScalaPackageClass) {
1778-
val denots = super.computeNPMembersNamed(name, inherited)
1779-
if (denots.exists) denots else pcls.computeNPMembersNamed(name, inherited)
1778+
val denots = super.computeNPMembersNamed(name)
1779+
if (denots.exists) denots else pcls.computeNPMembersNamed(name)
17801780
}
17811781
else {
1782-
val denots = pcls.computeNPMembersNamed(name, inherited)
1783-
if (denots.exists) denots else super.computeNPMembersNamed(name, inherited)
1782+
val denots = pcls.computeNPMembersNamed(name)
1783+
if (denots.exists) denots else super.computeNPMembersNamed(name)
17841784
}
17851785
case _ =>
1786-
super.computeNPMembersNamed(name, inherited)
1786+
super.computeNPMembersNamed(name)
17871787
}
17881788

17891789
/** The union of the member names of the package and the package object */

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

Lines changed: 55 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -19,131 +19,76 @@ import ast.tpd._
1919
trait TypeOps { this: Context => // TODO: Make standalone object.
2020

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

54-
/** Helper method, taking a map argument which is instantiated only for more
55-
* complicated cases of asSeenFrom.
56-
*/
57-
private def asSeenFrom(tp: Type, pre: Type, cls: Symbol, theMap: AsSeenFromMap): Type = {
58-
59-
/** Map a `C.this` type to the right prefix. If the prefix is unstable and
60-
* the `C.this` occurs in nonvariant or contravariant position, mark the map
61-
* to be unstable.
62-
*/
63-
def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)") /*<|<*/ {
64-
if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass))
65-
tp
66-
else pre match {
67-
case pre: SuperType => toPrefix(pre.thistpe, cls, thiscls)
68-
case _ =>
69-
if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) {
70-
if (theMap != null && theMap.currentVariance <= 0 && !isLegalPrefix(pre)) {
71-
ctx.base.unsafeNonvariant = ctx.runId
72-
pre match {
73-
case AnnotatedType(_, ann) if ann.symbol == defn.UnsafeNonvariantAnnot => pre
74-
case _ => AnnotatedType(pre, Annotation(defn.UnsafeNonvariantAnnot, Nil))
75-
}
76-
}
77-
else pre
78-
}
79-
else if ((pre.termSymbol is Package) && !(thiscls is Package))
80-
toPrefix(pre.select(nme.PACKAGE), cls, thiscls)
81-
else
82-
toPrefix(pre.baseTypeRef(cls).normalizedPrefix, cls.owner, thiscls)
27+
/** The TypeMap handling the asSeenFrom */
28+
class AsSeenFromMap(pre: Type, cls: Symbol) extends ApproximatingTypeMap {
29+
30+
def apply(tp: Type): Type = {
31+
32+
/** Map a `C.this` type to the right prefix. If the prefix is unstable and
33+
* the `C.this` occurs in nonvariant or contravariant position, mark the map
34+
* to be unstable.
35+
*/
36+
def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)") /*<|<*/ {
37+
if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass))
38+
tp
39+
else pre match {
40+
case pre: SuperType => toPrefix(pre.thistpe, cls, thiscls)
41+
case _ =>
42+
if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists)
43+
if (variance <= 0 && !isLegalPrefix(pre)) range(pre.bottomType, pre)
44+
else pre
45+
else if ((pre.termSymbol is Package) && !(thiscls is Package))
46+
toPrefix(pre.select(nme.PACKAGE), cls, thiscls)
47+
else
48+
toPrefix(pre.baseTypeRef(cls).normalizedPrefix, cls.owner, thiscls)
49+
}
8350
}
84-
}
8551

86-
/*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG
87-
tp match {
88-
case tp: NamedType =>
89-
val sym = tp.symbol
90-
if (sym.isStatic) tp
91-
else {
92-
val pre1 = asSeenFrom(tp.prefix, pre, cls, theMap)
93-
if (pre1.isUnsafeNonvariant) {
94-
val safeCtx = ctx.withProperty(TypeOps.findMemberLimit, Some(()))
95-
pre1.member(tp.name)(safeCtx).info match {
96-
case TypeAlias(alias) =>
97-
// try to follow aliases of this will avoid skolemization.
98-
return alias
99-
case _ =>
100-
}
52+
/*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG
53+
tp match {
54+
case tp: NamedType => // inlined for performance; TODO: factor out into inline method
55+
if (tp.symbol.isStatic) tp
56+
else {
57+
val saved = variance
58+
variance = variance max 0
59+
val prefix1 = this(tp.prefix)
60+
variance = saved
61+
derivedSelect(tp, prefix1)
10162
}
102-
tp.derivedSelect(pre1)
103-
}
104-
case tp: ThisType =>
105-
toPrefix(pre, cls, tp.cls)
106-
case _: BoundType | NoPrefix =>
107-
tp
108-
case tp: RefinedType =>
109-
tp.derivedRefinedType(
110-
asSeenFrom(tp.parent, pre, cls, theMap),
111-
tp.refinedName,
112-
asSeenFrom(tp.refinedInfo, pre, cls, theMap))
113-
case tp: TypeAlias if tp.variance == 1 => // if variance != 1, need to do the variance calculation
114-
tp.derivedTypeAlias(asSeenFrom(tp.alias, pre, cls, theMap))
115-
case _ =>
116-
(if (theMap != null) theMap else new AsSeenFromMap(pre, cls))
117-
.mapOver(tp)
63+
case tp: ThisType =>
64+
toPrefix(pre, cls, tp.cls)
65+
case _: BoundType | NoPrefix =>
66+
tp
67+
case tp: RefinedType =>
68+
derivedRefinedType(tp, apply(tp.parent), apply(tp.refinedInfo))
69+
case tp: TypeAlias if tp.variance == 1 => // if variance != 1, need to do the variance calculation
70+
derivedTypeAlias(tp, apply(tp.alias))
71+
case _ =>
72+
mapOver(tp)
73+
}
11874
}
11975
}
76+
77+
override def reapply(tp: Type) =
78+
// derives infos have already been subjected to asSeenFrom, hence to need to apply the map again.
79+
tp
12080
}
12181

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

125-
/** The TypeMap handling the asSeenFrom in more complicated cases */
126-
class AsSeenFromMap(pre: Type, cls: Symbol) extends TypeMap {
127-
def apply(tp: Type) = asSeenFrom(tp, pre, cls, this)
128-
129-
/** A method to export the current variance of the map */
130-
def currentVariance = variance
131-
}
132-
13385
/** Approximate a type `tp` with a type that does not contain skolem types. */
13486
object deskolemize extends ApproximatingTypeMap {
135-
private var seen: Set[SkolemType] = Set()
136-
def apply(tp: Type) = tp match {
137-
case tp: SkolemType =>
138-
if (seen contains tp) NoType
139-
else {
140-
val saved = seen
141-
seen += tp
142-
try approx(hi = tp.info)
143-
finally seen = saved
144-
}
145-
case _ =>
146-
mapOver(tp)
87+
def apply(tp: Type) = /*ctx.traceIndented(i"deskolemize($tp) at $variance", show = true)*/ {
88+
tp match {
89+
case tp: SkolemType => range(tp.bottomType, atVariance(1)(apply(tp.info)))
90+
case _ => mapOver(tp)
91+
}
14792
}
14893
}
14994

0 commit comments

Comments
 (0)