Skip to content

Commit 416fadc

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 95efbb1 commit 416fadc

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
@@ -3722,6 +3744,18 @@ object Types {
37223744
// of `p`'s upper bound.
37233745
val prefix1 = this(tp.prefix)
37243746
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+
*/
37253759
derivedSelect(tp, prefix1)
37263760
}
37273761
case _: ThisType
@@ -3851,63 +3885,139 @@ object Types {
38513885
def apply(tp: Type) = tp
38523886
}
38533887

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

38663925
override protected def derivedSelect(tp: NamedType, pre: Type) =
38673926
if (pre eq tp.prefix) tp
3868-
else tp.info match {
3869-
case TypeAlias(alias) => apply(alias) // try to heal by following aliases
3870-
case _ =>
3871-
if (pre.exists && !pre.isRef(defn.NothingClass) && variance > 0) tp.derivedSelect(pre)
3872-
else tp.info match {
3873-
case TypeBounds(lo, hi) => approx(lo, hi)
3874-
case _ => approx()
3927+
else pre match {
3928+
case Range(preLo, preHi) =>
3929+
tp.info match {
3930+
case TypeAlias(alias) => apply(alias)
3931+
case TypeBounds(lo, hi) => range(apply(lo), apply(hi))
3932+
case _ => range(tp.derivedSelect(preLo), tp.derivedSelect(preHi))
38753933
}
3934+
case _ => tp.derivedSelect(pre)
38763935
}
3936+
38773937
override protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type) =
3878-
if (parent.exists && info.exists) tp.derivedRefinedType(parent, tp.refinedName, info)
3879-
else approx(hi = parent)
3938+
parent match {
3939+
case Range(parentLo, parentHi) =>
3940+
range(derivedRefinedType(tp, parentLo, info), derivedRefinedType(tp, parentHi, info))
3941+
case _ =>
3942+
if (parent.isBottomType) parent
3943+
else info match {
3944+
case Range(infoLo, infoHi) if tp.refinedName.isTermName || variance <= 0 =>
3945+
range(derivedRefinedType(tp, parent, infoLo), derivedRefinedType(tp, parent, infoHi))
3946+
case _ =>
3947+
tp.derivedRefinedType(parent, tp.refinedName, rangeToBounds(info))
3948+
}
3949+
}
38803950
override protected def derivedRecType(tp: RecType, parent: Type) =
3881-
if (parent.exists) tp.rebind(parent)
3882-
else approx()
3951+
parent match {
3952+
case Range(lo, hi) => range(tp.rebind(lo), tp.rebind(hi))
3953+
case _ => tp.rebind(parent)
3954+
}
38833955
override protected def derivedTypeAlias(tp: TypeAlias, alias: Type) =
3884-
if (alias.exists) tp.derivedTypeAlias(alias)
3885-
else approx(NoType, TypeBounds.empty)
3956+
alias match {
3957+
case Range(lo, hi) =>
3958+
if (variance > 0) TypeBounds(lo, hi)
3959+
else range(TypeAlias(lo), TypeAlias(hi))
3960+
case _ => tp.derivedTypeAlias(alias)
3961+
}
38863962
override protected def derivedTypeBounds(tp: TypeBounds, lo: Type, hi: Type) =
3887-
if (lo.exists && hi.exists) tp.derivedTypeBounds(lo, hi)
3888-
else approx(NoType,
3889-
if (lo.exists) TypeBounds.lower(lo)
3890-
else if (hi.exists) TypeBounds.upper(hi)
3891-
else TypeBounds.empty)
3963+
if (isRange(lo) || isRange(hi))
3964+
if (variance > 0) TypeBounds(loBound(lo), hiBound(hi))
3965+
else range(TypeBounds(hiBound(lo), loBound(hi)), TypeBounds(loBound(lo), hiBound(hi)))
3966+
else tp.derivedTypeBounds(lo, hi)
38923967
override protected def derivedSuperType(tp: SuperType, thistp: Type, supertp: Type) =
3893-
if (thistp.exists && supertp.exists) tp.derivedSuperType(thistp, supertp)
3894-
else NoType
3968+
if (isRange(thistp) || isRange(supertp)) range()
3969+
else tp.derivedSuperType(thistp, supertp)
3970+
38953971
override protected def derivedAppliedType(tp: HKApply, tycon: Type, args: List[Type]): Type =
3896-
if (tycon.exists && args.forall(_.exists)) tp.derivedAppliedType(tycon, args)
3897-
else approx() // This is rather coarse, but to do better is a bit complicated
3972+
tycon match {
3973+
case Range(tyconLo, tyconHi) =>
3974+
range(derivedAppliedType(tp, tyconLo, args), derivedAppliedType(tp, tyconHi, args))
3975+
case _ =>
3976+
if (args.exists(isRange))
3977+
if (variance > 0) tp.derivedAppliedType(tycon, args.map(rangeToBounds))
3978+
else {
3979+
val loBuf, hiBuf = new mutable.ListBuffer[Type]
3980+
def distributeArgs(args: List[Type], tparams: List[ParamInfo]): Boolean = args match {
3981+
case Range(lo, hi) :: args1 =>
3982+
val v = tparams.head.paramVariance
3983+
if (v == 0) false
3984+
else if (v > 0) { loBuf += lo; hiBuf += hi }
3985+
else { loBuf += hi; hiBuf += lo }
3986+
distributeArgs(args1, tparams.tail)
3987+
case arg :: args1 =>
3988+
loBuf += arg; hiBuf += arg
3989+
distributeArgs(args1, tparams.tail)
3990+
case nil =>
3991+
true
3992+
}
3993+
if (distributeArgs(args, tp.typeParams))
3994+
range(tp.derivedAppliedType(tycon, loBuf.toList),
3995+
tp.derivedAppliedType(tycon, hiBuf.toList))
3996+
else range()
3997+
}
3998+
else tp.derivedAppliedType(tycon, args)
3999+
}
4000+
38984001
override protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type) =
3899-
if (tp1.exists && tp2.exists) tp.derivedAndOrType(tp1, tp2)
3900-
else if (tp.isAnd) approx(hi = tp1 & tp2) // if one of tp1d, tp2d exists, it is the result of tp1d & tp2d
3901-
else approx(lo = tp1 & tp2)
4002+
if (tp1.isInstanceOf[Range] || tp2.isInstanceOf[Range])
4003+
if (tp.isAnd) range(loBound(tp1) & loBound(tp2), hiBound(tp1) & hiBound(tp2))
4004+
else range(loBound(tp1) | loBound(tp2), hiBound(tp1) | hiBound(tp2))
4005+
else tp.derivedAndOrType(tp1, tp2)
39024006
override protected def derivedAnnotatedType(tp: AnnotatedType, underlying: Type, annot: Annotation) =
3903-
if (underlying.exists) tp.derivedAnnotatedType(underlying, annot)
3904-
else NoType
3905-
override protected def derivedWildcardType(tp: WildcardType, bounds: Type) =
3906-
if (bounds.exists) tp.derivedWildcardType(bounds)
3907-
else WildcardType
3908-
override protected def derivedClassInfo(tp: ClassInfo, pre: Type): Type =
3909-
if (pre.exists) tp.derivedClassInfo(pre)
3910-
else NoType
4007+
underlying match {
4008+
case Range(lo, hi) =>
4009+
range(tp.derivedAnnotatedType(lo, annot), tp.derivedAnnotatedType(hi, annot))
4010+
case _ =>
4011+
if (underlying.isBottomType) underlying
4012+
else tp.derivedAnnotatedType(underlying, annot)
4013+
}
4014+
override protected def derivedWildcardType(tp: WildcardType, bounds: Type) = {
4015+
tp.derivedWildcardType(rangeToBounds(bounds))
4016+
}
4017+
override protected def derivedClassInfo(tp: ClassInfo, pre: Type): Type = {
4018+
assert(!pre.isInstanceOf[Range])
4019+
tp.derivedClassInfo(pre)
4020+
}
39114021
}
39124022

39134023
// ----- 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)