Skip to content

Commit 63d8546

Browse files
authored
Merge pull request #6299 from dotty-staging/fix-#6288
Fix #6288: Allow Singletons in Unions
2 parents aa06e94 + b940f11 commit 63d8546

18 files changed

+373
-100
lines changed

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

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -272,40 +272,31 @@ trait ConstraintHandling[AbstractContext] {
272272
}
273273
}
274274

275-
/** Widen inferred type `tp` with upper bound `bound`, according to the following rules:
276-
* 1. If `tp` is a singleton type, yet `bound` is not a singleton type, nor a subtype
277-
* of `scala.Singleton`, widen `tp`.
278-
* 2. If `tp` is a union type, yet upper bound is not a union type,
279-
* approximate the union type from above by an intersection of all common base types.
275+
/** Widen inferred type `inst` with upper `bound`, according to the following rules:
276+
* 1. If `inst` is a singleton type, or a union containing some singleton types,
277+
* widen (all) the singleton type(s), provied the result is a subtype of `bound`
278+
* (i.e. `inst.widenSingletons <:< bound` succeeds with satisfiable constraint)
279+
* 2. If `inst` is a union type, approximate the union type from above by an intersection
280+
* of all common base types, provied the result is a subtype of `bound`.
281+
*
282+
* Don't do these widenings if `bound` is a subtype of `scala.Singleton`.
280283
*
281284
* At this point we also drop the @Repeated annotation to avoid inferring type arguments with it,
282285
* as those could leak the annotation to users (see run/inferred-repeated-result).
283286
*/
284-
def widenInferred(tp: Type, bound: Type)(implicit actx: AbstractContext): Type = {
285-
def isMultiSingleton(tp: Type): Boolean = tp.stripAnnots match {
286-
case tp: SingletonType => true
287-
case AndType(tp1, tp2) => isMultiSingleton(tp1) | isMultiSingleton(tp2)
288-
case OrType(tp1, tp2) => isMultiSingleton(tp1) & isMultiSingleton(tp2)
289-
case tp: TypeRef => isMultiSingleton(tp.info.hiBound)
290-
case tp: TypeVar => isMultiSingleton(tp.underlying)
291-
case tp: TypeParamRef => isMultiSingleton(bounds(tp).hi)
292-
case _ => false
287+
def widenInferred(inst: Type, bound: Type)(implicit actx: AbstractContext): Type = {
288+
def widenOr(tp: Type) = {
289+
val tpw = tp.widenUnion
290+
if ((tpw ne tp) && tpw <:< bound) tpw else tp
293291
}
294-
def isOrType(tp: Type): Boolean = tp.dealias match {
295-
case tp: OrType => true
296-
case tp: RefinedOrRecType => isOrType(tp.parent)
297-
case AndType(tp1, tp2) => isOrType(tp1) | isOrType(tp2)
298-
case WildcardType(bounds: TypeBounds) => isOrType(bounds.hi)
299-
case _ => false
292+
def widenSingle(tp: Type) = {
293+
val tpw = tp.widenSingletons
294+
if ((tpw ne tp) && tpw <:< bound) tpw else tp
300295
}
301-
def widenOr(tp: Type) =
302-
if (isOrType(tp) && !isOrType(bound)) tp.widenUnion
303-
else tp
304-
def widenSingle(tp: Type) =
305-
if (isMultiSingleton(tp) && !isMultiSingleton(bound) &&
306-
!isSubTypeWhenFrozen(bound, defn.SingletonType)) tp.widen
307-
else tp
308-
widenOr(widenSingle(tp)).dropRepeatedAnnot
296+
val wideInst =
297+
if (isSubTypeWhenFrozen(bound, defn.SingletonType)) inst
298+
else widenOr(widenSingle(inst))
299+
wideInst.dropRepeatedAnnot
309300
}
310301

311302
/** The instance type of `param` in the current constraint (which contains `param`).
@@ -316,7 +307,7 @@ trait ConstraintHandling[AbstractContext] {
316307
*/
317308
def instanceType(param: TypeParamRef, fromBelow: Boolean)(implicit actx: AbstractContext): Type = {
318309
val inst = approximation(param, fromBelow).simplified
319-
if (fromBelow) widenInferred(inst, constraint.fullUpperBound(param)) else inst
310+
if (fromBelow) widenInferred(inst, param) else inst
320311
}
321312

322313
/** Constraint `c1` subsumes constraint `c2`, if under `c2` as constraint we have

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

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,15 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
418418
case _ =>
419419
false
420420
}
421-
joinOK || recur(tp11, tp2) && recur(tp12, tp2)
421+
def widenOK =
422+
(tp2.widenSingletons eq tp2) &&
423+
(tp1.widenSingletons ne tp1) &&
424+
recur(tp1.widenSingletons, tp2)
425+
426+
if (tp2.atoms.nonEmpty && canCompare(tp2.atoms))
427+
tp1.atoms.nonEmpty && tp1.atoms.subsetOf(tp2.atoms)
428+
else
429+
widenOK || joinOK || recur(tp11, tp2) && recur(tp12, tp2)
422430
case tp1: MatchType =>
423431
val reduced = tp1.reduced
424432
if (reduced.exists) recur(reduced, tp2) else thirdTry
@@ -573,10 +581,28 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
573581
}
574582
compareTypeLambda
575583
case OrType(tp21, tp22) =>
576-
val tp1a = tp1.widenDealiasKeepRefiningAnnots
584+
if (tp2.atoms.nonEmpty && canCompare(tp2.atoms))
585+
return tp1.atoms.nonEmpty && tp1.atoms.subsetOf(tp2.atoms) ||
586+
tp1.isRef(NothingClass)
587+
588+
// The next clause handles a situation like the one encountered in i2745.scala.
589+
// We have:
590+
//
591+
// x: A | B, x.type <:< A | X where X is a type variable
592+
//
593+
// We should instantiate X to B instead of x.type or A | B. To do this, we widen
594+
// the LHS to A | B and recur *without indicating that this is a lowApprox*. The
595+
// latter point is important since otherwise we would not get to instantiate X.
596+
// If that succeeds, fine. If not we continue and hit the `either` below.
597+
// That second path is important to handle comparisons with unions of singletons,
598+
// as in `1 <:< 1 | 2`.
599+
val tp1w = tp1.widen
600+
if ((tp1w ne tp1) && recur(tp1w, tp2))
601+
return true
602+
603+
val tp1a = tp1.dealiasKeepRefiningAnnots
577604
if (tp1a ne tp1)
578-
// Follow the alias; this might avoid truncating the search space in the either below
579-
// Note that it's safe to widen here because singleton types cannot be part of `|`.
605+
// Follow the alias; this might lead to an OrType on the left which needs to be split
580606
return recur(tp1a, tp2)
581607

582608
// Rewrite T1 <: (T211 & T212) | T22 to T1 <: (T211 | T22) and T1 <: (T212 | T22)
@@ -1038,6 +1064,23 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
10381064
else None
10391065
}
10401066

1067+
/** Check whether we can compare the given set of atoms with another to determine
1068+
* a subtype test between OrTypes. There is one situation where this is not
1069+
* the case, which has to do with SkolemTypes. TreeChecker sometimes expects two
1070+
* types to be equal that have different skolems. To account for this, we identify
1071+
* two different skolems in all phases `p`, where `p.isTyper` is false.
1072+
* But in that case comparing two sets of atoms that contain skolems
1073+
* for equality would give the wrong result, so we should not use the sets
1074+
* for comparisons.
1075+
*/
1076+
def canCompare(atoms: Set[Type]): Boolean =
1077+
ctx.phase.isTyper || {
1078+
val hasSkolems = new ExistsAccumulator(_.isInstanceOf[SkolemType]) {
1079+
override def stopAtStatic = true
1080+
}
1081+
!atoms.exists(hasSkolems(false, _))
1082+
}
1083+
10411084
/** Subtype test for corresponding arguments in `args1`, `args2` according to
10421085
* variances in type parameters `tparams2`.
10431086
*
@@ -1499,30 +1542,38 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
14991542
/** The greatest lower bound of a list types */
15001543
final def glb(tps: List[Type]): Type = ((AnyType: Type) /: tps)(glb)
15011544

1545+
def widenInUnions(implicit ctx: Context): Boolean = ctx.scala2Mode || ctx.erasedTypes
1546+
15021547
/** The least upper bound of two types
15031548
* @param canConstrain If true, new constraints might be added to simplify the lub.
15041549
* @note We do not admit singleton types in or-types as lubs.
15051550
*/
15061551
def lub(tp1: Type, tp2: Type, canConstrain: Boolean = false): Type = /*>|>*/ trace(s"lub(${tp1.show}, ${tp2.show}, canConstrain=$canConstrain)", subtyping, show = true) /*<|<*/ {
1507-
if (tp1 eq tp2) tp1
1508-
else if (!tp1.exists) tp1
1509-
else if (!tp2.exists) tp2
1510-
else if ((tp1 isRef AnyClass) || (tp1 isRef AnyKindClass) || (tp2 isRef NothingClass)) tp1
1511-
else if ((tp2 isRef AnyClass) || (tp2 isRef AnyKindClass) || (tp1 isRef NothingClass)) tp2
1512-
else {
1513-
val t1 = mergeIfSuper(tp1, tp2, canConstrain)
1514-
if (t1.exists) t1
1515-
else {
1516-
val t2 = mergeIfSuper(tp2, tp1, canConstrain)
1517-
if (t2.exists) t2
1518-
else {
1519-
val tp1w = tp1.widen
1520-
val tp2w = tp2.widen
1521-
if ((tp1 ne tp1w) || (tp2 ne tp2w)) lub(tp1w, tp2w)
1522-
else orType(tp1w, tp2w) // no need to check subtypes again
1523-
}
1552+
if (tp1 eq tp2) return tp1
1553+
if (!tp1.exists) return tp1
1554+
if (!tp2.exists) return tp2
1555+
if ((tp1 isRef AnyClass) || (tp1 isRef AnyKindClass) || (tp2 isRef NothingClass)) return tp1
1556+
if ((tp2 isRef AnyClass) || (tp2 isRef AnyKindClass) || (tp1 isRef NothingClass)) return tp2
1557+
val atoms1 = tp1.atoms
1558+
if (atoms1.nonEmpty && !widenInUnions) {
1559+
val atoms2 = tp2.atoms
1560+
if (atoms2.nonEmpty) {
1561+
if (atoms1.subsetOf(atoms2)) return tp2
1562+
if (atoms2.subsetOf(atoms1)) return tp1
1563+
if ((atoms1 & atoms2).isEmpty) return orType(tp1, tp2)
15241564
}
15251565
}
1566+
val t1 = mergeIfSuper(tp1, tp2, canConstrain)
1567+
if (t1.exists) return t1
1568+
1569+
val t2 = mergeIfSuper(tp2, tp1, canConstrain)
1570+
if (t2.exists) return t2
1571+
1572+
def widen(tp: Type) = if (widenInUnions) tp.widen else tp.widenIfUnstable
1573+
val tp1w = widen(tp1)
1574+
val tp2w = widen(tp2)
1575+
if ((tp1 ne tp1w) || (tp2 ne tp2w)) lub(tp1w, tp2w)
1576+
else orType(tp1w, tp2w) // no need to check subtypes again
15261577
}
15271578

15281579
/** The least upper bound of a list of types */
@@ -2213,6 +2264,11 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
22132264
super.isSubType(tp1, tp2, approx)
22142265
}
22152266

2267+
override def recur(tp1: Type, tp2: Type): Boolean =
2268+
traceIndented(s"${show(tp1)} <:< ${show(tp2)} recur ${if (frozenConstraint) " frozen" else ""}") {
2269+
super.recur(tp1, tp2)
2270+
}
2271+
22162272
override def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean =
22172273
traceIndented(s"hasMatchingMember(${show(tp1)} . $name, ${show(tp2.refinedInfo)}), member = ${show(tp1.member(name).info)}") {
22182274
super.hasMatchingMember(name, tp1, tp2)

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

Lines changed: 79 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,11 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
131131
* class A extends C[A] with D
132132
* class B extends C[B] with D with E
133133
*
134-
* we approximate `A | B` by `C[A | B] with D`
134+
* we approximate `A | B` by `C[A | B] with D`.
135+
*
136+
* Before we do that, we try to find a common non-class supertype of T1 | ... | Tn
137+
* in a "best effort", ad-hoc way by selectively widening types in `T1, ..., Tn`
138+
* and stopping if the resulting union simplifies to a type that is not a disjunction.
135139
*/
136140
def orDominator(tp: Type): Type = {
137141

@@ -188,29 +192,83 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
188192
case _ => false
189193
}
190194

195+
// Step 1: Get RecTypes and ErrorTypes out of the way,
191196
tp1 match {
192-
case tp1: RecType =>
193-
tp1.rebind(approximateOr(tp1.parent, tp2))
194-
case tp1: TypeProxy if !isClassRef(tp1) =>
195-
orDominator(tp1.superType | tp2)
196-
case err: ErrorType =>
197-
err
197+
case tp1: RecType => return tp1.rebind(approximateOr(tp1.parent, tp2))
198+
case err: ErrorType => return err
198199
case _ =>
199-
tp2 match {
200-
case tp2: RecType =>
201-
tp2.rebind(approximateOr(tp1, tp2.parent))
202-
case tp2: TypeProxy if !isClassRef(tp2) =>
203-
orDominator(tp1 | tp2.superType)
204-
case err: ErrorType =>
205-
err
206-
case _ =>
207-
val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect)
208-
val doms = dominators(commonBaseClasses, Nil)
209-
def baseTp(cls: ClassSymbol): Type =
210-
tp.baseType(cls).mapReduceOr(identity)(mergeRefinedOrApplied)
211-
doms.map(baseTp).reduceLeft(AndType.apply)
212-
}
213200
}
201+
tp2 match {
202+
case tp2: RecType => return tp2.rebind(approximateOr(tp1, tp2.parent))
203+
case err: ErrorType => return err
204+
case _ =>
205+
}
206+
207+
// Step 2: Try to widen either side. This is tricky and incomplete.
208+
// An illustration is in test pos/padTo.scala: Here we need to compute the join of
209+
//
210+
// `A | C` under the constraints `B >: A` and `C <: B`
211+
//
212+
// where `A, B, C` are type parameters.
213+
// Widening `A` to its upper bound would give `Any | C`, i.e. `Any`.
214+
// But widening `C` first would give `A | B` and then `B`.
215+
// So we need to widen `C` first. But how to decide this in general?
216+
// In the algorithm below, we try to widen both sides (once), and then proceed as follows:
217+
//
218+
// 2.0. If no widening succeeds, proceed with step 3.
219+
// 2.1. If only one widening succeeds, continue with that one.
220+
// 2.2. If the two widened types are in a subtype relationship, continue with the smaller one.
221+
// 2.3. If exactly one of the two types is a singleton type, continue with the widened singleton type.
222+
// 2.4. If the widened tp2 is a supertype of tp1, return widened tp2.
223+
// 2.5. If the widened tp1 is a supertype of tp2, return widened tp1.
224+
// 2.6. Otherwise, continue with widened tp1
225+
//
226+
// At steps 4-6 we lose possible solutions, since we have to make an
227+
// arbitrary choice which side to widen. A better solution would look at
228+
// the constituents of each operand (if the operand is an OrType again) and
229+
// try to widen them selectively in turn. But this might lead to a combinatorial
230+
// explosion of possibilities.
231+
//
232+
// Another approach could be to store information contained in lower bounds
233+
// on both sides. So if `B >: A` we'd also record that `A <: B` and therefore
234+
// widening `A` would yield `B` instead of `Any`, so we'd still be on the right track.
235+
// This looks feasible if lower bounds are type parameters, but tricky if they
236+
// are something else. We'd have to extract the strongest possible
237+
// constraint over all type parameters that is implied by a lower bound.
238+
// This looks related to an algorithmic problem arising in GADT matching.
239+
//
240+
// However, this alone is still not enough. There are other sources of incompleteness,
241+
// for instance arising from mis-aligned refinements.
242+
val tp1w = tp1 match {
243+
case tp1: TypeProxy if !isClassRef(tp1) => tp1.superType.widenExpr
244+
case _ => tp1
245+
}
246+
val tp2w = tp2 match {
247+
case tp2: TypeProxy if !isClassRef(tp2) => tp2.superType.widenExpr
248+
case _ => tp2
249+
}
250+
if ((tp1w ne tp1) || (tp2w ne tp2)) {
251+
val isSingle1 = tp1.isInstanceOf[SingletonType]
252+
val isSingle2 = tp2.isInstanceOf[SingletonType]
253+
return {
254+
if (tp2w eq tp2) orDominator(tp1w | tp2) // 2.1
255+
else if (tp1w eq tp1) orDominator(tp1 | tp2w) // 2.1
256+
else if (tp1w frozen_<:< tp2w) orDominator(tp1w | tp2) // 2.2
257+
else if (tp2w frozen_<:< tp1w) orDominator(tp1 | tp2w) // 2.2
258+
else if (isSingle1 && !isSingle2) orDominator(tp1w | tp2) // 2.3
259+
else if (isSingle2 && !isSingle1) orDominator(tp1 | tp2w) // 2.3
260+
else if (tp1 frozen_<:< tp2w) tp2w // 2.4
261+
else if (tp2 frozen_<:< tp1w) tp1w // 2.5
262+
else orDominator(tp1w | tp2) // 2.6
263+
}
264+
}
265+
266+
// Step 3: Intersect base classes of both sides
267+
val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect)
268+
val doms = dominators(commonBaseClasses, Nil)
269+
def baseTp(cls: ClassSymbol): Type =
270+
tp.baseType(cls).mapReduceOr(identity)(mergeRefinedOrApplied)
271+
doms.map(baseTp).reduceLeft(AndType.apply)
214272
}
215273

216274
tp match {

0 commit comments

Comments
 (0)