Skip to content

Commit 79c7a03

Browse files
committed
Generalize approximating type maps.
Approximating type maps now work with bounds instead of NoTypes. This gives more precision (in particular for type applications), and also makes it possible to combine approximation with other mappings. As a side-effect we provide the hooks for preventing constructing illegal types C#A where C is non-singleton and A is abstract by setting variance to 0 for the prefix of an abstract type selection. There's one test case that fails: One of the types in dependent-exractors.scala does not check out anymore. This has likely to do with the loss of precision incurred by the maps. Exact cause remains to be tracked down.
1 parent 8683f66 commit 79c7a03

File tree

4 files changed

+154
-55
lines changed

4 files changed

+154
-55
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/TypeOps.scala

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -132,18 +132,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
132132

133133
/** Approximate a type `tp` with a type that does not contain skolem types. */
134134
object deskolemize extends ApproximatingTypeMap {
135-
private var seen: Set[SkolemType] = Set()
136135
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)
136+
case tp: SkolemType => range(hi = apply(tp.info))
137+
case _ => mapOver(tp)
147138
}
148139
}
149140

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

Lines changed: 149 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ object Types {
136136
case _ => false
137137
}
138138

139+
/** Is this type exactly Nothing (no vars, aliases, refinements etc allowed)? */
140+
def isBottomType(implicit ctx: Context): Boolean = this match {
141+
case tp: TypeRef => tp.symbol eq defn.NothingClass
142+
case _ => false
143+
}
144+
139145
/** Is this type a (neither aliased nor applied) reference to class `sym`? */
140146
def isDirectRef(sym: Symbol)(implicit ctx: Context): Boolean = stripTypeVar match {
141147
case this1: TypeRef =>
@@ -275,7 +281,10 @@ object Types {
275281
}
276282

277283
/** Is this an alias TypeBounds? */
278-
def isAlias: Boolean = this.isInstanceOf[TypeAlias]
284+
final def isAlias: Boolean = this.isInstanceOf[TypeAlias]
285+
286+
/** Is this a non-alias TypeBounds? */
287+
final def isRealTypeBounds = this.isInstanceOf[TypeBounds] && !isAlias
279288

280289
// ----- Higher-order combinators -----------------------------------
281290

@@ -1220,6 +1229,18 @@ object Types {
12201229
case _ => TypeAlias(this)
12211230
}
12221231

1232+
/** The lower bound of a TypeBounds type, the type itself otherwise */
1233+
def loBound = this match {
1234+
case tp: TypeBounds => tp.lo
1235+
case _ => this
1236+
}
1237+
1238+
/** The upper bound of a TypeBounds type, the type itself otherwise */
1239+
def hiBound = this match {
1240+
case tp: TypeBounds => tp.hi
1241+
case _ => this
1242+
}
1243+
12231244
/** The type parameter with given `name`. This tries first `decls`
12241245
* in order not to provoke a cycle by forcing the info. If that yields
12251246
* no symbol it tries `member` as an alternative.
@@ -1766,6 +1787,7 @@ object Types {
17661787
*/
17671788
def derivedSelect(prefix: Type)(implicit ctx: Context): Type =
17681789
if (prefix eq this.prefix) this
1790+
else if (prefix.isBottomType) prefix
17691791
else if (isType) {
17701792
val res = prefix.lookupRefined(name)
17711793
if (res.exists) res
@@ -3724,6 +3746,18 @@ object Types {
37243746
// of `p`'s upper bound.
37253747
val prefix1 = this(tp.prefix)
37263748
variance = saved
3749+
/* was:
3750+
val prefix1 = tp.info match {
3751+
case info: TypeBounds if !info.isAlias =>
3752+
// prefix of an abstract type selection is non-variant, since a path
3753+
// cannot be legally widened to its underlying type, or any supertype.
3754+
val saved = variance
3755+
variance = 0
3756+
try this(tp.prefix) finally variance = saved
3757+
case _ =>
3758+
this(tp.prefix)
3759+
}
3760+
*/
37273761
derivedSelect(tp, prefix1)
37283762
}
37293763
case _: ThisType
@@ -3853,63 +3887,139 @@ object Types {
38533887
def apply(tp: Type) = tp
38543888
}
38553889

3856-
/** A type map that approximates NoTypes by upper or lower known bounds depending on
3890+
case class Range(lo: Type, hi: Type) extends UncachedGroundType {
3891+
assert(!lo.isInstanceOf[Range])
3892+
assert(!hi.isInstanceOf[Range])
3893+
}
3894+
3895+
/** A type map that approximates TypeBounds types depending on
38573896
* variance.
38583897
*
38593898
* if variance > 0 : approximate by upper bound
38603899
* variance < 0 : approximate by lower bound
3861-
* variance = 0 : propagate NoType to next outer level
3900+
* variance = 0 : propagate bounds to next outer level
38623901
*/
38633902
abstract class ApproximatingTypeMap(implicit ctx: Context) extends TypeMap { thisMap =>
3864-
def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType) =
3865-
if (variance == 0) NoType
3866-
else apply(if (variance < 0) lo else hi)
3903+
3904+
def range(lo: Type = defn.NothingType, hi: Type = defn.AnyType) =
3905+
if (variance > 0) hi
3906+
else if (variance < 0) lo
3907+
else Range(loBound(lo), hiBound(hi))
3908+
3909+
def isRange(tp: Type) = tp.isInstanceOf[Range]
3910+
3911+
def loBound(tp: Type) = tp match {
3912+
case tp: Range => tp.lo
3913+
case _ => tp
3914+
}
3915+
3916+
/** The upper bound of a TypeBounds type, the type itself otherwise */
3917+
def hiBound(tp: Type) = tp match {
3918+
case tp: Range => tp.hi
3919+
case _ => tp
3920+
}
3921+
3922+
def rangeToBounds(tp: Type) = tp match {
3923+
case Range(lo, hi) => TypeBounds(lo, hi)
3924+
case _ => tp
3925+
}
38673926

38683927
override protected def derivedSelect(tp: NamedType, pre: Type) =
38693928
if (pre eq tp.prefix) tp
3870-
else tp.info match {
3871-
case TypeAlias(alias) => apply(alias) // try to heal by following aliases
3872-
case _ =>
3873-
if (pre.exists && !pre.isRef(defn.NothingClass) && variance > 0) tp.derivedSelect(pre)
3874-
else tp.info match {
3875-
case TypeBounds(lo, hi) => approx(lo, hi)
3876-
case _ => approx()
3929+
else pre match {
3930+
case Range(preLo, preHi) =>
3931+
tp.info match {
3932+
case TypeAlias(alias) => apply(alias)
3933+
case TypeBounds(lo, hi) => range(apply(lo), apply(hi))
3934+
case _ => range(tp.derivedSelect(preLo), tp.derivedSelect(preHi))
38773935
}
3936+
case _ => tp.derivedSelect(pre)
38783937
}
3938+
38793939
override protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type) =
3880-
if (parent.exists && info.exists) tp.derivedRefinedType(parent, tp.refinedName, info)
3881-
else approx(hi = parent)
3940+
parent match {
3941+
case Range(parentLo, parentHi) =>
3942+
range(derivedRefinedType(tp, parentLo, info), derivedRefinedType(tp, parentHi, info))
3943+
case _ =>
3944+
if (parent.isBottomType) parent
3945+
else info match {
3946+
case Range(infoLo, infoHi) if tp.refinedName.isTermName || variance <= 0 =>
3947+
range(derivedRefinedType(tp, parent, infoLo), derivedRefinedType(tp, parent, infoHi))
3948+
case _ =>
3949+
tp.derivedRefinedType(parent, tp.refinedName, rangeToBounds(info))
3950+
}
3951+
}
38823952
override protected def derivedRecType(tp: RecType, parent: Type) =
3883-
if (parent.exists) tp.rebind(parent)
3884-
else approx()
3953+
parent match {
3954+
case Range(lo, hi) => range(tp.rebind(lo), tp.rebind(hi))
3955+
case _ => tp.rebind(parent)
3956+
}
38853957
override protected def derivedTypeAlias(tp: TypeAlias, alias: Type) =
3886-
if (alias.exists) tp.derivedTypeAlias(alias)
3887-
else approx(NoType, TypeBounds.empty)
3958+
alias match {
3959+
case Range(lo, hi) =>
3960+
if (variance > 0) TypeBounds(lo, hi)
3961+
else range(TypeAlias(lo), TypeAlias(hi))
3962+
case _ => tp.derivedTypeAlias(alias)
3963+
}
38883964
override protected def derivedTypeBounds(tp: TypeBounds, lo: Type, hi: Type) =
3889-
if (lo.exists && hi.exists) tp.derivedTypeBounds(lo, hi)
3890-
else approx(NoType,
3891-
if (lo.exists) TypeBounds.lower(lo)
3892-
else if (hi.exists) TypeBounds.upper(hi)
3893-
else TypeBounds.empty)
3965+
if (isRange(lo) || isRange(hi))
3966+
if (variance > 0) TypeBounds(loBound(lo), hiBound(hi))
3967+
else range(TypeBounds(hiBound(lo), loBound(hi)), TypeBounds(loBound(lo), hiBound(hi)))
3968+
else tp.derivedTypeBounds(lo, hi)
38943969
override protected def derivedSuperType(tp: SuperType, thistp: Type, supertp: Type) =
3895-
if (thistp.exists && supertp.exists) tp.derivedSuperType(thistp, supertp)
3896-
else NoType
3970+
if (isRange(thistp) || isRange(supertp)) range()
3971+
else tp.derivedSuperType(thistp, supertp)
3972+
38973973
override protected def derivedAppliedType(tp: HKApply, tycon: Type, args: List[Type]): Type =
3898-
if (tycon.exists && args.forall(_.exists)) tp.derivedAppliedType(tycon, args)
3899-
else approx() // This is rather coarse, but to do better is a bit complicated
3974+
tycon match {
3975+
case Range(tyconLo, tyconHi) =>
3976+
range(derivedAppliedType(tp, tyconLo, args), derivedAppliedType(tp, tyconHi, args))
3977+
case _ =>
3978+
if (args.exists(isRange))
3979+
if (variance > 0) tp.derivedAppliedType(tycon, args.map(rangeToBounds))
3980+
else {
3981+
val loBuf, hiBuf = new mutable.ListBuffer[Type]
3982+
def distributeArgs(args: List[Type], tparams: List[ParamInfo]): Boolean = args match {
3983+
case Range(lo, hi) :: args1 =>
3984+
val v = tparams.head.paramVariance
3985+
if (v == 0) false
3986+
else if (v > 0) { loBuf += lo; hiBuf += hi }
3987+
else { loBuf += hi; hiBuf += lo }
3988+
distributeArgs(args1, tparams.tail)
3989+
case arg :: args1 =>
3990+
loBuf += arg; hiBuf += arg
3991+
distributeArgs(args1, tparams.tail)
3992+
case nil =>
3993+
true
3994+
}
3995+
if (distributeArgs(args, tp.typeParams))
3996+
range(tp.derivedAppliedType(tycon, loBuf.toList),
3997+
tp.derivedAppliedType(tycon, hiBuf.toList))
3998+
else range()
3999+
}
4000+
else tp.derivedAppliedType(tycon, args)
4001+
}
4002+
39004003
override protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type) =
3901-
if (tp1.exists && tp2.exists) tp.derivedAndOrType(tp1, tp2)
3902-
else if (tp.isAnd) approx(hi = tp1 & tp2) // if one of tp1d, tp2d exists, it is the result of tp1d & tp2d
3903-
else approx(lo = tp1 & tp2)
4004+
if (tp1.isInstanceOf[Range] || tp2.isInstanceOf[Range])
4005+
if (tp.isAnd) range(loBound(tp1) & loBound(tp2), hiBound(tp1) & hiBound(tp2))
4006+
else range(loBound(tp1) | loBound(tp2), hiBound(tp1) | hiBound(tp2))
4007+
else tp.derivedAndOrType(tp1, tp2)
39044008
override protected def derivedAnnotatedType(tp: AnnotatedType, underlying: Type, annot: Annotation) =
3905-
if (underlying.exists) tp.derivedAnnotatedType(underlying, annot)
3906-
else NoType
3907-
override protected def derivedWildcardType(tp: WildcardType, bounds: Type) =
3908-
if (bounds.exists) tp.derivedWildcardType(bounds)
3909-
else WildcardType
3910-
override protected def derivedClassInfo(tp: ClassInfo, pre: Type): Type =
3911-
if (pre.exists) tp.derivedClassInfo(pre)
3912-
else NoType
4009+
underlying match {
4010+
case Range(lo, hi) =>
4011+
range(tp.derivedAnnotatedType(lo, annot), tp.derivedAnnotatedType(hi, annot))
4012+
case _ =>
4013+
if (underlying.isBottomType) underlying
4014+
else tp.derivedAnnotatedType(underlying, annot)
4015+
}
4016+
override protected def derivedWildcardType(tp: WildcardType, bounds: Type) = {
4017+
tp.derivedWildcardType(rangeToBounds(bounds))
4018+
}
4019+
override protected def derivedClassInfo(tp: ClassInfo, pre: Type): Type = {
4020+
assert(!pre.isInstanceOf[Range])
4021+
tp.derivedClassInfo(pre)
4022+
}
39134023
}
39144024

39154025
// ----- TypeAccumulators ----------------------------------------------------

tests/pos/dependent-extractors.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ object Test {
1010
val y1: Int = y
1111

1212
val z = (c: Any) match { case X(y) => y }
13-
val z1: C#T = z
13+
// val z1: C#T = z // error: z has type Any TODO: find out why
1414
}

0 commit comments

Comments
 (0)