Skip to content

Commit 91ee02f

Browse files
committed
Make asSeenFrom use an ApproximatingTypeMap
Supersedes old scheme of dealing with unstable prefixes in non-variant positions.
1 parent 45bfa08 commit 91ee02f

File tree

5 files changed

+103
-135
lines changed

5 files changed

+103
-135
lines changed

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

+57-98
Original file line numberDiff line numberDiff line change
@@ -19,122 +19,81 @@ 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 _ =>
52+
/*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG
53+
tp match {
54+
case tp: NamedType =>
55+
val sym = tp.symbol
56+
if (sym.isStatic) tp
57+
else {
58+
val pre1 = apply(tp.prefix)
59+
if (pre1.isUnsafeNonvariant) {
60+
val safeCtx = ctx.withProperty(TypeOps.findMemberLimit, Some(()))
61+
pre1.member(tp.name)(safeCtx).info match {
62+
case TypeAlias(alias) =>
63+
// try to follow aliases of this will avoid skolemization.
64+
return alias
65+
case _ =>
66+
}
10067
}
68+
derivedSelect(tp, pre1)
10169
}
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)
70+
case tp: ThisType =>
71+
toPrefix(pre, cls, tp.cls)
72+
case _: BoundType | NoPrefix =>
73+
tp
74+
case tp: RefinedType =>
75+
derivedRefinedType(tp, apply(tp.parent), apply(tp.refinedInfo))
76+
case tp: TypeAlias if tp.variance == 1 => // if variance != 1, need to do the variance calculation
77+
derivedTypeAlias(tp, apply(tp.alias))
78+
case _ =>
79+
mapOver(tp)
80+
}
11881
}
11982
}
83+
84+
override def reapply(tp: Type) =
85+
// derives infos have already been subjected to asSeenFrom, hence to need to apply the map again.
86+
tp
12087
}
12188

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

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-
13392
/** Approximate a type `tp` with a type that does not contain skolem types. */
13493
object deskolemize extends ApproximatingTypeMap {
13594
def apply(tp: Type) = /*ctx.traceIndented(i"deskolemize($tp) at $variance", show = true)*/ {
13695
tp match {
137-
case tp: SkolemType => range(hi = atVariance(1)(apply(tp.info)))
96+
case tp: SkolemType => range(tp.bottomType, atVariance(1)(apply(tp.info)))
13897
case _ => mapOver(tp)
13998
}
14099
}

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

+33-33
Original file line numberDiff line numberDiff line change
@@ -283,9 +283,6 @@ object Types {
283283
/** Is this an alias TypeBounds? */
284284
final def isAlias: Boolean = this.isInstanceOf[TypeAlias]
285285

286-
/** Is this a non-alias TypeBounds? */
287-
final def isRealTypeBounds = this.isInstanceOf[TypeBounds] && !isAlias
288-
289286
// ----- Higher-order combinators -----------------------------------
290287

291288
/** Returns true if there is a part of this type that satisfies predicate `p`.
@@ -3744,18 +3741,6 @@ object Types {
37443741
// of `p`'s upper bound.
37453742
val prefix1 = this(tp.prefix)
37463743
variance = saved
3747-
/* was:
3748-
val prefix1 = tp.info match {
3749-
case info: TypeBounds if !info.isAlias =>
3750-
// prefix of an abstract type selection is non-variant, since a path
3751-
// cannot be legally widened to its underlying type, or any supertype.
3752-
val saved = variance
3753-
variance = 0
3754-
try this(tp.prefix) finally variance = saved
3755-
case _ =>
3756-
this(tp.prefix)
3757-
}
3758-
*/
37593744
derivedSelect(tp, prefix1)
37603745
}
37613746
case _: ThisType
@@ -3767,7 +3752,7 @@ object Types {
37673752

37683753
case tp: TypeAlias =>
37693754
val saved = variance
3770-
variance = variance * tp.variance
3755+
variance *= tp.variance
37713756
val alias1 = this(tp.alias)
37723757
variance = saved
37733758
derivedTypeAlias(tp, alias1)
@@ -3902,30 +3887,29 @@ object Types {
39023887
*/
39033888
abstract class ApproximatingTypeMap(implicit ctx: Context) extends TypeMap { thisMap =>
39043889

3905-
def range(lo: Type = defn.NothingType, hi: Type = defn.AnyType) =
3890+
protected def range(lo: Type, hi: Type) =
39063891
if (variance > 0) hi
39073892
else if (variance < 0) lo
3908-
else Range(loBound(lo), hiBound(hi))
3893+
else Range(lower(lo), upper(hi))
39093894

3910-
def isRange(tp: Type) = tp.isInstanceOf[Range]
3895+
private def isRange(tp: Type) = tp.isInstanceOf[Range]
39113896

3912-
def loBound(tp: Type) = tp match {
3897+
private def lower(tp: Type) = tp match {
39133898
case tp: Range => tp.lo
39143899
case _ => tp
39153900
}
39163901

3917-
/** The upper bound of a TypeBounds type, the type itself otherwise */
3918-
def hiBound(tp: Type) = tp match {
3902+
private def upper(tp: Type) = tp match {
39193903
case tp: Range => tp.hi
39203904
case _ => tp
39213905
}
39223906

3923-
def rangeToBounds(tp: Type) = tp match {
3907+
private def rangeToBounds(tp: Type) = tp match {
39243908
case Range(lo, hi) => TypeBounds(lo, hi)
39253909
case _ => tp
39263910
}
39273911

3928-
def atVariance[T](v: Int)(op: => T): T = {
3912+
protected def atVariance[T](v: Int)(op: => T): T = {
39293913
val saved = variance
39303914
variance = v
39313915
try op finally variance = saved
@@ -3935,11 +3919,17 @@ object Types {
39353919
if (pre eq tp.prefix) tp
39363920
else pre match {
39373921
case Range(preLo, preHi) =>
3938-
tp.info match {
3922+
preHi.member(tp.name).info match {
39393923
case TypeAlias(alias) =>
3940-
apply(alias)
3924+
// if H#T = U, then for any x in L..H, x.T =:= U,
3925+
// hence we can replace with U under all variances
3926+
reapply(alias)
39413927
case TypeBounds(lo, hi) =>
3942-
range(atVariance(-1)(apply(lo)), atVariance(1)(apply(hi)))
3928+
range(atVariance(-1)(reapply(lo)), atVariance(1)(reapply(hi)))
3929+
case info: SingletonType =>
3930+
// if H#x: y.type, then for any x in L..H, x.type =:= y.type,
3931+
// hence we can replace with y.type under all variances
3932+
reapply(info)
39433933
case _ =>
39443934
range(tp.derivedSelect(preLo), tp.derivedSelect(preHi))
39453935
}
@@ -3976,12 +3966,12 @@ object Types {
39763966

39773967
override protected def derivedTypeBounds(tp: TypeBounds, lo: Type, hi: Type) =
39783968
if (isRange(lo) || isRange(hi))
3979-
if (variance > 0) TypeBounds(loBound(lo), hiBound(hi))
3980-
else range(TypeBounds(hiBound(lo), loBound(hi)), TypeBounds(loBound(lo), hiBound(hi)))
3969+
if (variance > 0) TypeBounds(lower(lo), upper(hi))
3970+
else range(TypeBounds(upper(lo), lower(hi)), TypeBounds(lower(lo), upper(hi)))
39813971
else tp.derivedTypeBounds(lo, hi)
39823972

39833973
override protected def derivedSuperType(tp: SuperType, thistp: Type, supertp: Type) =
3984-
if (isRange(thistp) || isRange(supertp)) range()
3974+
if (isRange(thistp) || isRange(supertp)) range(thistp.bottomType, thistp.topType)
39853975
else tp.derivedSuperType(thistp, supertp)
39863976

39873977
override protected def derivedAppliedType(tp: HKApply, tycon: Type, args: List[Type]): Type =
@@ -4009,15 +3999,15 @@ object Types {
40093999
if (distributeArgs(args, tp.typeParams))
40104000
range(tp.derivedAppliedType(tycon, loBuf.toList),
40114001
tp.derivedAppliedType(tycon, hiBuf.toList))
4012-
else range()
4002+
else range(tp.bottomType, tp.topType)
40134003
}
40144004
else tp.derivedAppliedType(tycon, args)
40154005
}
40164006

40174007
override protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type) =
40184008
if (tp1.isInstanceOf[Range] || tp2.isInstanceOf[Range])
4019-
if (tp.isAnd) range(loBound(tp1) & loBound(tp2), hiBound(tp1) & hiBound(tp2))
4020-
else range(loBound(tp1) | loBound(tp2), hiBound(tp1) | hiBound(tp2))
4009+
if (tp.isAnd) range(lower(tp1) & lower(tp2), upper(tp1) & upper(tp2))
4010+
else range(lower(tp1) | lower(tp2), upper(tp1) | upper(tp2))
40214011
else tp.derivedAndOrType(tp1, tp2)
40224012

40234013
override protected def derivedAnnotatedType(tp: AnnotatedType, underlying: Type, annot: Annotation) =
@@ -4036,6 +4026,16 @@ object Types {
40364026
assert(!pre.isInstanceOf[Range])
40374027
tp.derivedClassInfo(pre)
40384028
}
4029+
4030+
override protected def derivedLambdaType(tp: LambdaType)(formals: List[tp.PInfo], restpe: Type): Type =
4031+
restpe match {
4032+
case Range(lo, hi) =>
4033+
range(derivedLambdaType(tp)(formals, lo), derivedLambdaType(tp)(formals, hi))
4034+
case _ =>
4035+
tp.derivedLambdaType(tp.paramNames, formals, restpe)
4036+
}
4037+
4038+
protected def reapply(tp: Type): Type = apply(tp)
40394039
}
40404040

40414041
// ----- TypeAccumulators ----------------------------------------------------

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

+11-2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ class PlainPrinter(_ctx: Context) extends Printer {
6868
}
6969
else tp
7070

71+
private def sameBound(lo: Type, hi: Type): Boolean =
72+
try lo =:= hi
73+
catch { case ex: Throwable => false }
74+
75+
private def homogenizeArg(tp: Type) = tp match {
76+
case TypeBounds(lo, hi) if sameBound(lo, hi) => homogenize(hi)
77+
case _ => tp
78+
}
79+
7180
private def selfRecName(n: Int) = s"z$n"
7281

7382
/** Render elements alternating with `sep` string */
@@ -113,9 +122,9 @@ class PlainPrinter(_ctx: Context) extends Printer {
113122
protected def toTextRefinement(rt: RefinedType) =
114123
(refinementNameString(rt) ~ toTextRHS(rt.refinedInfo)).close
115124

116-
protected def argText(arg: Type): Text = arg match {
125+
protected def argText(arg: Type): Text = homogenizeArg(arg) match {
117126
case arg: TypeBounds => "_" ~ toTextGlobal(arg)
118-
case _ => toTextGlobal(arg)
127+
case arg => toTextGlobal(arg)
119128
}
120129

121130
/** The longest sequence of refinement types, starting at given type

compiler/src/dotty/tools/dotc/typer/Typer.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1427,7 +1427,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
14271427
val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1)
14281428
.withType(dummy.nonMemberTermRef)
14291429
checkVariance(impl1)
1430-
if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.typeRef, cdef.namePos)
1430+
if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.thisType, cdef.namePos)
14311431
val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1), cls)
14321432
if (ctx.phase.isTyper && cdef1.tpe.derivesFrom(defn.DynamicClass) && !ctx.dynamicsEnabled) {
14331433
val isRequired = parents1.exists(_.tpe.isRef(defn.DynamicClass))

tests/neg/i1662.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ class Lift {
22
def apply(f: F0) // error
33
class F0
44
object F0 { implicit def f2f0(String): F0 = ??? } // error
5-
(new Lift)("")
5+
(new Lift)("") // error after switch to approximating asSeenFrom
66
}

0 commit comments

Comments
 (0)