Skip to content

Commit 31d9a83

Browse files
committed
Make asSeenFrom use an ApproximatingTypeMap
Supersedes old scheme of dealing with unstable prefixes in non-variant positions.
1 parent 34a3fe2 commit 31d9a83

File tree

5 files changed

+103
-135
lines changed

5 files changed

+103
-135
lines changed

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

Lines changed: 57 additions & 98 deletions
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

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,6 @@ object Types {
286286
/** Is this an alias TypeBounds? */
287287
final def isAlias: Boolean = this.isInstanceOf[TypeAlias]
288288

289-
/** Is this a non-alias TypeBounds? */
290-
final def isRealTypeBounds = this.isInstanceOf[TypeBounds] && !isAlias
291-
292289
// ----- Higher-order combinators -----------------------------------
293290

294291
/** Returns true if there is a part of this type that satisfies predicate `p`.
@@ -3747,18 +3744,6 @@ object Types {
37473744
// of `p`'s upper bound.
37483745
val prefix1 = this(tp.prefix)
37493746
variance = saved
3750-
/* was:
3751-
val prefix1 = tp.info match {
3752-
case info: TypeBounds if !info.isAlias =>
3753-
// prefix of an abstract type selection is non-variant, since a path
3754-
// cannot be legally widened to its underlying type, or any supertype.
3755-
val saved = variance
3756-
variance = 0
3757-
try this(tp.prefix) finally variance = saved
3758-
case _ =>
3759-
this(tp.prefix)
3760-
}
3761-
*/
37623747
derivedSelect(tp, prefix1)
37633748
}
37643749
case _: ThisType
@@ -3770,7 +3755,7 @@ object Types {
37703755

37713756
case tp: TypeAlias =>
37723757
val saved = variance
3773-
variance = variance * tp.variance
3758+
variance *= tp.variance
37743759
val alias1 = this(tp.alias)
37753760
variance = saved
37763761
derivedTypeAlias(tp, alias1)
@@ -3905,30 +3890,29 @@ object Types {
39053890
*/
39063891
abstract class ApproximatingTypeMap(implicit ctx: Context) extends TypeMap { thisMap =>
39073892

3908-
def range(lo: Type = defn.NothingType, hi: Type = defn.AnyType) =
3893+
protected def range(lo: Type, hi: Type) =
39093894
if (variance > 0) hi
39103895
else if (variance < 0) lo
3911-
else Range(loBound(lo), hiBound(hi))
3896+
else Range(lower(lo), upper(hi))
39123897

3913-
def isRange(tp: Type) = tp.isInstanceOf[Range]
3898+
private def isRange(tp: Type) = tp.isInstanceOf[Range]
39143899

3915-
def loBound(tp: Type) = tp match {
3900+
private def lower(tp: Type) = tp match {
39163901
case tp: Range => tp.lo
39173902
case _ => tp
39183903
}
39193904

3920-
/** The upper bound of a TypeBounds type, the type itself otherwise */
3921-
def hiBound(tp: Type) = tp match {
3905+
private def upper(tp: Type) = tp match {
39223906
case tp: Range => tp.hi
39233907
case _ => tp
39243908
}
39253909

3926-
def rangeToBounds(tp: Type) = tp match {
3910+
private def rangeToBounds(tp: Type) = tp match {
39273911
case Range(lo, hi) => TypeBounds(lo, hi)
39283912
case _ => tp
39293913
}
39303914

3931-
def atVariance[T](v: Int)(op: => T): T = {
3915+
protected def atVariance[T](v: Int)(op: => T): T = {
39323916
val saved = variance
39333917
variance = v
39343918
try op finally variance = saved
@@ -3938,11 +3922,17 @@ object Types {
39383922
if (pre eq tp.prefix) tp
39393923
else pre match {
39403924
case Range(preLo, preHi) =>
3941-
tp.info match {
3925+
preHi.member(tp.name).info match {
39423926
case TypeAlias(alias) =>
3943-
apply(alias)
3927+
// if H#T = U, then for any x in L..H, x.T =:= U,
3928+
// hence we can replace with U under all variances
3929+
reapply(alias)
39443930
case TypeBounds(lo, hi) =>
3945-
range(atVariance(-1)(apply(lo)), atVariance(1)(apply(hi)))
3931+
range(atVariance(-1)(reapply(lo)), atVariance(1)(reapply(hi)))
3932+
case info: SingletonType =>
3933+
// if H#x: y.type, then for any x in L..H, x.type =:= y.type,
3934+
// hence we can replace with y.type under all variances
3935+
reapply(info)
39463936
case _ =>
39473937
range(tp.derivedSelect(preLo), tp.derivedSelect(preHi))
39483938
}
@@ -3979,12 +3969,12 @@ object Types {
39793969

39803970
override protected def derivedTypeBounds(tp: TypeBounds, lo: Type, hi: Type) =
39813971
if (isRange(lo) || isRange(hi))
3982-
if (variance > 0) TypeBounds(loBound(lo), hiBound(hi))
3983-
else range(TypeBounds(hiBound(lo), loBound(hi)), TypeBounds(loBound(lo), hiBound(hi)))
3972+
if (variance > 0) TypeBounds(lower(lo), upper(hi))
3973+
else range(TypeBounds(upper(lo), lower(hi)), TypeBounds(lower(lo), upper(hi)))
39843974
else tp.derivedTypeBounds(lo, hi)
39853975

39863976
override protected def derivedSuperType(tp: SuperType, thistp: Type, supertp: Type) =
3987-
if (isRange(thistp) || isRange(supertp)) range()
3977+
if (isRange(thistp) || isRange(supertp)) range(thistp.bottomType, thistp.topType)
39883978
else tp.derivedSuperType(thistp, supertp)
39893979

39903980
override protected def derivedAppliedType(tp: HKApply, tycon: Type, args: List[Type]): Type =
@@ -4012,15 +4002,15 @@ object Types {
40124002
if (distributeArgs(args, tp.typeParams))
40134003
range(tp.derivedAppliedType(tycon, loBuf.toList),
40144004
tp.derivedAppliedType(tycon, hiBuf.toList))
4015-
else range()
4005+
else range(tp.bottomType, tp.topType)
40164006
}
40174007
else tp.derivedAppliedType(tycon, args)
40184008
}
40194009

40204010
override protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type) =
40214011
if (tp1.isInstanceOf[Range] || tp2.isInstanceOf[Range])
4022-
if (tp.isAnd) range(loBound(tp1) & loBound(tp2), hiBound(tp1) & hiBound(tp2))
4023-
else range(loBound(tp1) | loBound(tp2), hiBound(tp1) | hiBound(tp2))
4012+
if (tp.isAnd) range(lower(tp1) & lower(tp2), upper(tp1) & upper(tp2))
4013+
else range(lower(tp1) | lower(tp2), upper(tp1) | upper(tp2))
40244014
else tp.derivedAndOrType(tp1, tp2)
40254015

40264016
override protected def derivedAnnotatedType(tp: AnnotatedType, underlying: Type, annot: Annotation) =
@@ -4039,6 +4029,16 @@ object Types {
40394029
assert(!pre.isInstanceOf[Range])
40404030
tp.derivedClassInfo(pre)
40414031
}
4032+
4033+
override protected def derivedLambdaType(tp: LambdaType)(formals: List[tp.PInfo], restpe: Type): Type =
4034+
restpe match {
4035+
case Range(lo, hi) =>
4036+
range(derivedLambdaType(tp)(formals, lo), derivedLambdaType(tp)(formals, hi))
4037+
case _ =>
4038+
tp.derivedLambdaType(tp.paramNames, formals, restpe)
4039+
}
4040+
4041+
protected def reapply(tp: Type): Type = apply(tp)
40424042
}
40434043

40444044
// ----- TypeAccumulators ----------------------------------------------------

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

Lines changed: 11 additions & 2 deletions
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

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

tests/neg/i1662.scala

Lines changed: 1 addition & 1 deletion
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)