Skip to content

Commit 076df81

Browse files
authored
Merge pull request #2945 from dotty-staging/change-avoid-approx
Implement avoid in terms of AproximatingTypeMap
2 parents 9f5711f + 491fe97 commit 076df81

File tree

7 files changed

+121
-97
lines changed

7 files changed

+121
-97
lines changed

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -631,12 +631,6 @@ object Contexts {
631631
* of underlying during a controlled operation exists. */
632632
private[core] val pendingUnderlying = new mutable.HashSet[Type]
633633

634-
/** A flag that some unsafe nonvariant instantiation was encountered
635-
* in this run. Used as a shortcut to a avoid scans of types in
636-
* Typer.typedSelect.
637-
*/
638-
private[dotty] var unsafeNonvariant: RunId = NoRunId
639-
640634
/** A map from ErrorType to associated message computation. We use this map
641635
* instead of storing message computations directly in ErrorTypes in order
642636
* to avoid space leaks - the message computation usually captures a context.

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

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
2929

3030
def apply(tp: Type): Type = {
3131

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-
*/
32+
/** Map a `C.this` type to the right prefix. If the prefix is unstable, and
33+
* the current variance is <= 0, return a range.
34+
*/
3635
def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)") /*<|<*/ {
3736
if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass))
3837
tp
@@ -50,16 +49,11 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
5049
}
5150

5251
/*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG
52+
// One `case ThisType` is specific to asSeenFrom, all other cases are inlined for performance
5353
tp match {
54-
case tp: NamedType => // inlined for performance; TODO: factor out into inline method
54+
case tp: NamedType =>
5555
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)
62-
}
56+
else derivedSelect(tp, atVariance(variance max 0)(this(tp.prefix)))
6357
case tp: ThisType =>
6458
toPrefix(pre, cls, tp.cls)
6559
case _: BoundType | NoPrefix =>

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

Lines changed: 78 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3679,14 +3679,26 @@ object Types {
36793679

36803680
// ----- TypeMaps --------------------------------------------------------------------
36813681

3682-
abstract class TypeMap(implicit protected val ctx: Context) extends (Type => Type) { thisMap =>
3682+
/** Common base class of TypeMap and TypeAccumulator */
3683+
abstract class VariantTraversal {
3684+
protected[core] var variance = 1
3685+
3686+
@inline protected def atVariance[T](v: Int)(op: => T): T = {
3687+
val saved = variance
3688+
variance = v
3689+
val res = op
3690+
variance = saved
3691+
res
3692+
}
3693+
}
3694+
3695+
abstract class TypeMap(implicit protected val ctx: Context)
3696+
extends VariantTraversal with (Type => Type) { thisMap =>
36833697

36843698
protected def stopAtStatic = true
36853699

36863700
def apply(tp: Type): Type
36873701

3688-
protected[core] var variance = 1
3689-
36903702
protected def derivedSelect(tp: NamedType, pre: Type): Type =
36913703
tp.derivedSelect(pre)
36923704
protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type): Type =
@@ -3724,16 +3736,13 @@ object Types {
37243736
case tp: NamedType =>
37253737
if (stopAtStatic && tp.symbol.isStatic) tp
37263738
else {
3727-
val saved = variance
3728-
variance = variance max 0
3739+
val prefix1 = atVariance(variance max 0)(this(tp.prefix))
37293740
// A prefix is never contravariant. Even if say `p.A` is used in a contravariant
37303741
// context, we cannot assume contravariance for `p` because `p`'s lower
37313742
// bound might not have a binding for `A` (e.g. the lower bound could be `Nothing`).
37323743
// By contrast, covariance does translate to the prefix, since we have that
37333744
// if `p <: q` then `p.A <: q.A`, and well-formedness requires that `A` is a member
37343745
// of `p`'s upper bound.
3735-
val prefix1 = this(tp.prefix)
3736-
variance = saved
37373746
derivedSelect(tp, prefix1)
37383747
}
37393748
case _: ThisType
@@ -3744,11 +3753,7 @@ object Types {
37443753
derivedRefinedType(tp, this(tp.parent), this(tp.refinedInfo))
37453754

37463755
case tp: TypeAlias =>
3747-
val saved = variance
3748-
variance *= tp.variance
3749-
val alias1 = this(tp.alias)
3750-
variance = saved
3751-
derivedTypeAlias(tp, alias1)
3756+
derivedTypeAlias(tp, atVariance(variance * tp.variance)(this(tp.alias)))
37523757

37533758
case tp: TypeBounds =>
37543759
variance = -variance
@@ -3764,12 +3769,8 @@ object Types {
37643769
if (inst.exists) apply(inst) else tp
37653770

37663771
case tp: HKApply =>
3767-
def mapArg(arg: Type, tparam: ParamInfo): Type = {
3768-
val saved = variance
3769-
variance *= tparam.paramVariance
3770-
try this(arg)
3771-
finally variance = saved
3772-
}
3772+
def mapArg(arg: Type, tparam: ParamInfo): Type =
3773+
atVariance(variance * tparam.paramVariance)(this(arg))
37733774
derivedAppliedType(tp, this(tp.tycon),
37743775
tp.args.zipWithConserve(tp.typeParams)(mapArg))
37753776

@@ -3894,20 +3895,14 @@ object Types {
38943895
case _ => tp
38953896
}
38963897

3897-
protected def atVariance[T](v: Int)(op: => T): T = {
3898-
val saved = variance
3899-
variance = v
3900-
try op finally variance = saved
3901-
}
3902-
3903-
/** Derived selection.
3904-
* @pre the (upper bound of) prefix `pre` has a member named `tp.name`.
3898+
/** Try to widen a named type to its info relative to given prefix `pre`, where possible.
3899+
* The possible cases are listed inline in the code. Return `default` if no widening is
3900+
* possible.
39053901
*/
3906-
override protected def derivedSelect(tp: NamedType, pre: Type) =
3907-
if (pre eq tp.prefix) tp
3908-
else pre match {
3909-
case Range(preLo, preHi) =>
3910-
preHi.member(tp.name).info.widenExpr match {
3902+
def tryWiden(tp: NamedType, pre: Type)(default: => Type): Type =
3903+
pre.member(tp.name) match {
3904+
case d: SingleDenotation =>
3905+
d.info match {
39113906
case TypeAlias(alias) =>
39123907
// if H#T = U, then for any x in L..H, x.T =:= U,
39133908
// hence we can replace with U under all variances
@@ -3921,9 +3916,21 @@ object Types {
39213916
// hence we can replace with y.type under all variances
39223917
reapply(info)
39233918
case _ =>
3924-
range(tp.derivedSelect(preLo), tp.derivedSelect(preHi))
3919+
default
39253920
}
3926-
case _ => tp.derivedSelect(pre)
3921+
case _ => default
3922+
}
3923+
3924+
/** Derived selection.
3925+
* @pre the (upper bound of) prefix `pre` has a member named `tp.name`.
3926+
*/
3927+
override protected def derivedSelect(tp: NamedType, pre: Type) =
3928+
if (pre eq tp.prefix) tp
3929+
else pre match {
3930+
case Range(preLo, preHi) =>
3931+
tryWiden(tp, preHi)(range(tp.derivedSelect(preLo), tp.derivedSelect(preHi)))
3932+
case _ =>
3933+
tp.derivedSelect(pre)
39273934
}
39283935

39293936
override protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type) =
@@ -3932,20 +3939,30 @@ object Types {
39323939
case Range(parentLo, parentHi) =>
39333940
range(derivedRefinedType(tp, parentLo, info), derivedRefinedType(tp, parentHi, info))
39343941
case _ =>
3942+
def propagate(lo: Type, hi: Type) =
3943+
range(derivedRefinedType(tp, parent, lo), derivedRefinedType(tp, parent, hi))
39353944
if (parent.isBottomType) parent
39363945
else info match {
3946+
case Range(infoLo: TypeBounds, infoHi: TypeBounds) =>
3947+
assert(variance == 0)
3948+
val v1 = infoLo.variance
3949+
val v2 = infoHi.variance
3950+
// There's some weirdness coming from the way aliases can have variance
3951+
// If infoLo and infoHi are both aliases with the same non-zero variance
3952+
// we can propagate to a range of the refined types. If they are both
3953+
// non-alias ranges we know that infoLo <:< infoHi and therefore we can
3954+
// propagate to refined types with infoLo and infoHi as bounds.
3955+
// In all other cases, Nothing..Any is the only interval that contains
3956+
// the range. i966.scala is a test case.
3957+
if (v1 > 0 && v2 > 0) propagate(infoLo, infoHi)
3958+
else if (v1 < 0 && v2 < 0) propagate(infoHi, infoLo)
3959+
else if (!infoLo.isAlias && !infoHi.isAlias) propagate(infoLo, infoHi)
3960+
else range(tp.bottomType, tp.topType)
3961+
// Using `parent` instead of `tp.topType` would be better for normal refinements,
3962+
// but it would also turn *-types into hk-types, which is not what we want.
3963+
// We should revisit this point in case we represent applied types not as refinements anymore.
39373964
case Range(infoLo, infoHi) =>
3938-
def propagate(lo: Type, hi: Type) =
3939-
range(derivedRefinedType(tp, parent, lo), derivedRefinedType(tp, parent, hi))
3940-
tp.refinedInfo match {
3941-
case rinfo: TypeBounds =>
3942-
val v = if (rinfo.isAlias) rinfo.variance * variance else variance
3943-
if (v > 0) tp.derivedRefinedType(parent, tp.refinedName, rangeToBounds(info))
3944-
else if (v < 0) propagate(infoHi, infoLo)
3945-
else range(tp.bottomType, tp.topType)
3946-
case _ =>
3947-
propagate(infoLo, infoHi)
3948-
}
3965+
propagate(infoLo, infoHi)
39493966
case _ =>
39503967
tp.derivedRefinedType(parent, tp.refinedName, info)
39513968
}
@@ -3987,6 +4004,13 @@ object Types {
39874004
if (variance > 0) tp.derivedAppliedType(tycon, args.map(rangeToBounds))
39884005
else {
39894006
val loBuf, hiBuf = new mutable.ListBuffer[Type]
4007+
// Given `C[A1, ..., An]` where sone A's are ranges, try to find
4008+
// non-range arguments L1, ..., Ln and H1, ..., Hn such that
4009+
// C[L1, ..., Ln] <: C[H1, ..., Hn] by taking the right limits of
4010+
// ranges that appear in as co- or contravariant arguments.
4011+
// Fail for non-variant argument ranges.
4012+
// If successful, the L-arguments are in loBut, the H-arguments in hiBuf.
4013+
// @return operation succeeded for all arguments.
39904014
def distributeArgs(args: List[Type], tparams: List[ParamInfo]): Boolean = args match {
39914015
case Range(lo, hi) :: args1 =>
39924016
val v = tparams.head.paramVariance
@@ -4006,13 +4030,14 @@ object Types {
40064030
range(tp.derivedAppliedType(tycon, loBuf.toList),
40074031
tp.derivedAppliedType(tycon, hiBuf.toList))
40084032
else range(tp.bottomType, tp.topType)
4033+
// TODO: can we give a better bound than `topType`?
40094034
}
40104035
}
40114036
else tp.derivedAppliedType(tycon, args)
40124037
}
40134038

40144039
override protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type) =
4015-
if (tp1.isInstanceOf[Range] || tp2.isInstanceOf[Range])
4040+
if (isRange(tp1) || isRange(tp2))
40164041
if (tp.isAnd) range(lower(tp1) & lower(tp2), upper(tp1) & upper(tp2))
40174042
else range(lower(tp1) | lower(tp2), upper(tp1) | upper(tp2))
40184043
else tp.derivedAndOrType(tp1, tp2)
@@ -4030,7 +4055,9 @@ object Types {
40304055
}
40314056

40324057
override protected def derivedClassInfo(tp: ClassInfo, pre: Type): Type = {
4033-
assert(!pre.isInstanceOf[Range])
4058+
assert(!isRange(pre))
4059+
// we don't know what to do here; this case has to be handled in subclasses
4060+
// (typically by handling ClassInfo's specially, in case they can be encountered).
40344061
tp.derivedClassInfo(pre)
40354062
}
40364063

@@ -4058,23 +4085,17 @@ object Types {
40584085

40594086
// ----- TypeAccumulators ----------------------------------------------------
40604087

4061-
abstract class TypeAccumulator[T](implicit protected val ctx: Context) extends ((T, Type) => T) {
4088+
abstract class TypeAccumulator[T](implicit protected val ctx: Context)
4089+
extends VariantTraversal with ((T, Type) => T) {
40624090

40634091
protected def stopAtStatic = true
40644092

40654093
def apply(x: T, tp: Type): T
40664094

40674095
protected def applyToAnnot(x: T, annot: Annotation): T = x // don't go into annotations
40684096

4069-
protected var variance = 1
4070-
4071-
protected final def applyToPrefix(x: T, tp: NamedType) = {
4072-
val saved = variance
4073-
variance = variance max 0 // see remark on NamedType case in TypeMap
4074-
val result = this(x, tp.prefix)
4075-
variance = saved
4076-
result
4077-
}
4097+
protected final def applyToPrefix(x: T, tp: NamedType) =
4098+
atVariance(variance max 0)(this(x, tp.prefix)) // see remark on NamedType case in TypeMap
40784099

40794100
def foldOver(x: T, tp: Type): T = tp match {
40804101
case tp: TypeRef =>
@@ -4095,13 +4116,7 @@ object Types {
40954116
this(this(x, tp.parent), tp.refinedInfo)
40964117

40974118
case bounds @ TypeBounds(lo, hi) =>
4098-
if (lo eq hi) {
4099-
val saved = variance
4100-
variance = variance * bounds.variance
4101-
val result = this(x, lo)
4102-
variance = saved
4103-
result
4104-
}
4119+
if (lo eq hi) atVariance(variance * bounds.variance)(this(x, lo))
41054120
else {
41064121
variance = -variance
41074122
val y = this(x, lo)

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import typer.ImportInfo
1212
import config.Config
1313
import java.lang.Integer.toOctalString
1414
import config.Config.summarizeDepth
15+
import scala.util.control.NonFatal
1516
import scala.annotation.switch
1617

1718
class PlainPrinter(_ctx: Context) extends Printer {
@@ -69,11 +70,11 @@ class PlainPrinter(_ctx: Context) extends Printer {
6970
else tp
7071

7172
private def sameBound(lo: Type, hi: Type): Boolean =
72-
try lo =:= hi
73-
catch { case ex: Throwable => false }
73+
try ctx.typeComparer.isSameTypeWhenFrozen(lo, hi)
74+
catch { case NonFatal(ex) => false }
7475

7576
private def homogenizeArg(tp: Type) = tp match {
76-
case TypeBounds(lo, hi) if sameBound(lo, hi) => homogenize(hi)
77+
case TypeBounds(lo, hi) if homogenizedView && sameBound(lo, hi) => homogenize(hi)
7778
case _ => tp
7879
}
7980

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

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ trait TypeAssigner {
4646
val parentType = info.parentsWithArgs.reduceLeft(ctx.typeComparer.andType(_, _))
4747
def addRefinement(parent: Type, decl: Symbol) = {
4848
val inherited =
49-
parentType.findMember(decl.name, info.cls.thisType, Private)
49+
parentType.findMember(decl.name, info.cls.thisType, excluded = Private)
5050
.suchThat(decl.matches(_))
5151
val inheritedInfo = inherited.info
5252
if (inheritedInfo.exists && decl.info <:< inheritedInfo && !(inheritedInfo <:< decl.info)) {
@@ -88,7 +88,7 @@ trait TypeAssigner {
8888
case info => range(tp.info.bottomType, apply(info))
8989
}
9090
case tp: TypeRef if toAvoid(tp.symbol) =>
91-
val avoided = tp.info match {
91+
tp.info match {
9292
case TypeAlias(alias) =>
9393
apply(alias)
9494
case TypeBounds(lo, hi) =>
@@ -98,7 +98,6 @@ trait TypeAssigner {
9898
case _ =>
9999
range(tp.bottomType, tp.topType) // should happen only in error cases
100100
}
101-
avoided
102101
case tp: ThisType if toAvoid(tp.cls) =>
103102
range(tp.bottomType, apply(classBound(tp.cls.classInfo)))
104103
case tp: TypeVar if ctx.typerState.constraint.contains(tp) =>
@@ -109,18 +108,26 @@ trait TypeAssigner {
109108
mapOver(tp)
110109
}
111110

112-
/** Two deviations from standard derivedSelect:
113-
* 1. The teh approximation result is a singleton references C#x.type, we
111+
/** Three deviations from standard derivedSelect:
112+
* 1. We first try a widening conversion to the type's info with
113+
* the original prefix. Since the original prefix is known to
114+
* be a subtype of the returned prefix, this can improve results.
115+
* 2. IThen, if the approximation result is a singleton reference C#x.type, we
114116
* replace by the widened type, which is usually more natural.
115-
* 2. We need to handle the case where the prefix type does not have a member
116-
* named `tp.name` anymmore.
117+
* 3. Finally, we need to handle the case where the prefix type does not have a member
118+
* named `tp.name` anymmore. In that case, we need to fall back to Bot..Top.
117119
*/
118120
override def derivedSelect(tp: NamedType, pre: Type) =
119-
if (pre eq tp.prefix) tp
120-
else if (tp.isTerm && variance > 0 && !pre.isInstanceOf[SingletonType])
121-
apply(tp.info.widenExpr)
122-
else if (upper(pre).member(tp.name).exists) super.derivedSelect(tp, pre)
123-
else range(tp.bottomType, tp.topType)
121+
if (pre eq tp.prefix)
122+
tp
123+
else tryWiden(tp, tp.prefix) {
124+
if (tp.isTerm && variance > 0 && !pre.isInstanceOf[SingletonType])
125+
apply(tp.info.widenExpr)
126+
else if (upper(pre).member(tp.name).exists)
127+
super.derivedSelect(tp, pre)
128+
else
129+
range(tp.bottomType, tp.topType)
130+
}
124131
}
125132

126133
widenMap(tp)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
624624
else pt.notApplied
625625
val expr1 = typedExpr(tree.expr, ept)(exprCtx)
626626
ensureNoLocalRefs(
627-
assignType(cpy.Block(tree)(stats1, expr1), stats1, expr1), pt, localSyms(stats1))
627+
cpy.Block(tree)(stats1, expr1).withType(expr1.tpe), pt, localSyms(stats1))
628628
}
629629

630630
def escapingRefs(block: Tree, localSyms: => List[Symbol])(implicit ctx: Context): collection.Set[NamedType] = {

0 commit comments

Comments
 (0)