Skip to content

Commit 249433c

Browse files
committed
Keep or types
Don't replace them by their dominators, unless one of the following holds: - language:Scala2 mode is on - we are at the point of findMember selection - we compare with a higher-kinded application This means approximateUnion is now split into harmonizeUnion and orDominator which each implement one of the former's two functionalities.
1 parent 1ec9cbb commit 249433c

File tree

5 files changed

+110
-82
lines changed

5 files changed

+110
-82
lines changed

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ trait ConstraintHandling {
3535
/** If the constraint is frozen we cannot add new bounds to the constraint. */
3636
protected var frozenConstraint = false
3737

38+
protected var alwaysFluid = false
39+
40+
/** Perform `op` in a mode where all attempts to set `frozen` to true are ignored */
41+
def fluidly[T](op: => T): T = {
42+
val saved = alwaysFluid
43+
alwaysFluid = true
44+
try op finally alwaysFluid = saved
45+
}
46+
3847
/** We are currently comparing lambdas. Used as a flag for
3948
* optimization: when `false`, no need to do an expensive `pruneLambdaParams`
4049
*/
@@ -126,14 +135,14 @@ trait ConstraintHandling {
126135

127136
final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = {
128137
val saved = frozenConstraint
129-
frozenConstraint = true
138+
frozenConstraint = !alwaysFluid
130139
try isSubType(tp1, tp2)
131140
finally frozenConstraint = saved
132141
}
133142

134143
final def isSameTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = {
135144
val saved = frozenConstraint
136-
frozenConstraint = true
145+
frozenConstraint = !alwaysFluid
137146
try isSameType(tp1, tp2)
138147
finally frozenConstraint = saved
139148
}
@@ -219,7 +228,7 @@ trait ConstraintHandling {
219228
// is not a union type, approximate the union type from above by an intersection
220229
// of all common base types.
221230
if (fromBelow && isOrType(inst) && isFullyDefined(inst) && !isOrType(upperBound))
222-
inst = inst.approximateUnion
231+
inst = ctx.harmonizeUnion(inst)
223232

224233
// 3. If instance is from below, and upper bound has open named parameters
225234
// make sure the instance has all named parameters of the bound.

src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,6 @@ object StdNames {
427427
val isEmpty: N = "isEmpty"
428428
val isInstanceOf_ : N = "isInstanceOf"
429429
val java: N = "java"
430-
val keepUnions: N = "keepUnions"
431430
val key: N = "key"
432431
val lang: N = "lang"
433432
val length: N = "length"

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

Lines changed: 76 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -173,100 +173,124 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
173173
}
174174

175175
/** Approximate union type by intersection of its dominators.
176-
* See Type#approximateUnion for an explanation.
176+
* That is, replace a union type Tn | ... | Tn
177+
* by the smallest intersection type of base-class instances of T1,...,Tn.
178+
* Example: Given
179+
*
180+
* trait C[+T]
181+
* trait D
182+
* class A extends C[A] with D
183+
* class B extends C[B] with D with E
184+
*
185+
* we approximate `A | B` by `C[A | B] with D`
177186
*/
178-
def approximateUnion(tp: Type): Type = {
187+
def orDominator(tp: Type): Type = {
188+
179189
/** a faster version of cs1 intersect cs2 */
180190
def intersect(cs1: List[ClassSymbol], cs2: List[ClassSymbol]): List[ClassSymbol] = {
181191
val cs2AsSet = new util.HashSet[ClassSymbol](100)
182192
cs2.foreach(cs2AsSet.addEntry)
183193
cs1.filter(cs2AsSet.contains)
184194
}
195+
185196
/** The minimal set of classes in `cs` which derive all other classes in `cs` */
186197
def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = (cs: @unchecked) match {
187198
case c :: rest =>
188199
val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu
189200
if (cs == c.baseClasses) accu1 else dominators(rest, accu1)
190201
}
202+
203+
def mergeRefined(tp1: Type, tp2: Type): Type = {
204+
def fail = throw new AssertionError(i"Failure to join alternatives $tp1 and $tp2")
205+
tp1 match {
206+
case tp1 @ RefinedType(parent1, name1, rinfo1) =>
207+
tp2 match {
208+
case RefinedType(parent2, `name1`, rinfo2) =>
209+
tp1.derivedRefinedType(
210+
mergeRefined(parent1, parent2), name1, rinfo1 | rinfo2)
211+
case _ => fail
212+
}
213+
case tp1 @ TypeRef(pre1, name1) =>
214+
tp2 match {
215+
case tp2 @ TypeRef(pre2, `name1`) =>
216+
tp1.derivedSelect(pre1 | pre2)
217+
case _ => fail
218+
}
219+
case _ => fail
220+
}
221+
}
222+
191223
def approximateOr(tp1: Type, tp2: Type): Type = {
192224
def isClassRef(tp: Type): Boolean = tp match {
193225
case tp: TypeRef => tp.symbol.isClass
194226
case tp: RefinedType => isClassRef(tp.parent)
195227
case _ => false
196228
}
197229

198-
/** If `tp1` and `tp2` are typebounds, try to make one fit into the other
199-
* or to make them equal, by instantiating uninstantiated type variables.
200-
*/
201-
def homogenizedUnion(tp1: Type, tp2: Type): Type = {
202-
tp1 match {
203-
case tp1: TypeBounds =>
204-
tp2 match {
205-
case tp2: TypeBounds =>
206-
def fitInto(tp1: TypeBounds, tp2: TypeBounds): Unit = {
207-
val nestedCtx = ctx.fresh.setNewTyperState
208-
if (tp2.boundsInterval.contains(tp1.boundsInterval)(nestedCtx))
209-
nestedCtx.typerState.commit()
210-
}
211-
fitInto(tp1, tp2)
212-
fitInto(tp2, tp1)
213-
case _ =>
214-
}
215-
case _ =>
216-
}
217-
tp1 | tp2
218-
}
219-
220-
tp1 match {
221-
case tp1: RefinedType =>
222-
tp2 match {
223-
case tp2: RefinedType if tp1.refinedName == tp2.refinedName =>
224-
return tp1.derivedRefinedType(
225-
approximateUnion(OrType(tp1.parent, tp2.parent)),
226-
tp1.refinedName,
227-
homogenizedUnion(tp1.refinedInfo, tp2.refinedInfo))
228-
//.ensuring { x => println(i"approx or $tp1 | $tp2 = $x\n constr = ${ctx.typerState.constraint}"); true } // DEBUG
229-
case _ =>
230-
}
231-
case _ =>
232-
}
233-
234230
tp1 match {
235231
case tp1: RecType =>
236232
tp1.rebind(approximateOr(tp1.parent, tp2))
237233
case tp1: TypeProxy if !isClassRef(tp1) =>
238-
approximateUnion(tp1.superType | tp2)
234+
orDominator(tp1.superType | tp2)
239235
case _ =>
240236
tp2 match {
241237
case tp2: RecType =>
242238
tp2.rebind(approximateOr(tp1, tp2.parent))
243239
case tp2: TypeProxy if !isClassRef(tp2) =>
244-
approximateUnion(tp1 | tp2.superType)
240+
orDominator(tp1 | tp2.superType)
245241
case _ =>
246242
val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect)
247243
val doms = dominators(commonBaseClasses, Nil)
248-
def baseTp(cls: ClassSymbol): Type =
249-
if (tp1.typeParams.nonEmpty) tp.baseTypeRef(cls)
250-
else tp.baseTypeWithArgs(cls)
244+
def baseTp(cls: ClassSymbol): Type = {
245+
val base =
246+
if (tp1.typeParams.nonEmpty) tp.baseTypeRef(cls)
247+
else tp.baseTypeWithArgs(cls)
248+
base.mapReduceOr(identity)(mergeRefined)
249+
}
251250
doms.map(baseTp).reduceLeft(AndType.apply)
252251
}
253252
}
254253
}
255-
if (ctx.featureEnabled(defn.LanguageModuleClass, nme.keepUnions)) tp
256-
else tp match {
254+
255+
tp match {
257256
case tp: OrType =>
258-
approximateOr(tp.tp1, tp.tp2) // Maybe refactor using liftToRec?
259-
case tp @ AndType(tp1, tp2) =>
260-
tp derived_& (approximateUnion(tp1), approximateUnion(tp2))
261-
case tp: RefinedType =>
262-
tp.derivedRefinedType(approximateUnion(tp.parent), tp.refinedName, tp.refinedInfo)
263-
case tp: RecType =>
264-
tp.rebind(approximateUnion(tp.parent))
257+
approximateOr(tp.tp1, tp.tp2)
265258
case _ =>
266259
tp
267260
}
268261
}
269262

263+
/** Given a disjunction T1 | ... | Tn of types with potentially embedded
264+
* type variables, constrain type variables further if this eliminates
265+
* some of the branches of the disjunction. Do this also for disjunctions
266+
* embedded in intersections, as parents in refinements, and in recursive types.
267+
*
268+
* For instance, if `A` is an unconstrained type variable, then
269+
*
270+
* ArrayBuffer[Int] | ArrayBuffer[A]
271+
*
272+
* is approximated by constraining `A` to be =:= to `Int` and returning `ArrayBuffer[Int]`
273+
* instead of `ArrayBuffer[_ >: Int | A <: Int & A]`
274+
*/
275+
def harmonizeUnion(tp: Type): Type = tp match {
276+
case tp: OrType =>
277+
joinIfScala2(typeComparer.fluidly(tp.tp1 | tp.tp2))
278+
case tp @ AndType(tp1, tp2) =>
279+
tp derived_& (harmonizeUnion(tp1), harmonizeUnion(tp2))
280+
case tp: RefinedType =>
281+
tp.derivedRefinedType(harmonizeUnion(tp.parent), tp.refinedName, tp.refinedInfo)
282+
case tp: RecType =>
283+
tp.rebind(harmonizeUnion(tp.parent))
284+
case _ =>
285+
tp
286+
}
287+
288+
/** Under -language:Scala2: Replace or-types with their joins */
289+
private def joinIfScala2(tp: Type) = tp match {
290+
case tp: OrType if scala2Mode => tp.join
291+
case _ => tp
292+
}
293+
270294
/** Not currently needed:
271295
*
272296
def liftToRec(f: (Type, Type) => Type)(tp1: Type, tp2: Type)(implicit ctx: Context) = {

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

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -436,8 +436,12 @@ object Types {
436436
tp.cls.findMember(name, pre, excluded)
437437
case AndType(l, r) =>
438438
goAnd(l, r)
439-
case OrType(l, r) =>
440-
goOr(l, r)
439+
case tp: OrType =>
440+
// we need to keep the invariant that `pre <: tp`. Branch `union-types-narrow-prefix`
441+
// achieved that by narrowing `pre` to each alternative, but it led to merge errors in
442+
// lots of places. The present strategy is instead of widen `tp` using `join` to be a
443+
// supertype of `pre`.
444+
go(tp.join)
441445
case tp: JavaArrayType =>
442446
defn.ObjectType.findMember(name, pre, excluded)
443447
case ErrorType =>
@@ -556,7 +560,6 @@ object Types {
556560
def goAnd(l: Type, r: Type) = {
557561
go(l) & (go(r), pre, safeIntersection = ctx.pendingMemberSearches.contains(name))
558562
}
559-
def goOr(l: Type, r: Type) = go(l) | (go(r), pre)
560563

561564
{ val recCount = ctx.findMemberCount + 1
562565
ctx.findMemberCount = recCount
@@ -1218,28 +1221,6 @@ object Types {
12181221
*/
12191222
def simplified(implicit ctx: Context) = ctx.simplify(this, null)
12201223

1221-
/** Approximations of union types: We replace a union type Tn | ... | Tn
1222-
* by the smallest intersection type of baseclass instances of T1,...,Tn.
1223-
* Example: Given
1224-
*
1225-
* trait C[+T]
1226-
* trait D
1227-
* class A extends C[A] with D
1228-
* class B extends C[B] with D with E
1229-
*
1230-
* we approximate `A | B` by `C[A | B] with D`
1231-
*
1232-
* As a second measure we also homogenize refinements containing
1233-
* type variables. For instance, if `A` is an instantiatable type variable,
1234-
* then
1235-
*
1236-
* ArrayBuffer[Int] | ArrayBuffer[A]
1237-
*
1238-
* is approximated by instantiating `A` to `Int` and returning `ArrayBuffer[Int]`
1239-
* instead of `ArrayBuffer[_ >: Int | A <: Int & A]`
1240-
*/
1241-
def approximateUnion(implicit ctx: Context) = ctx.approximateUnion(this)
1242-
12431224
/** customized hash code of this type.
12441225
* NotCached for uncached types. Cached types
12451226
* compute hash and use it as the type's hashCode.
@@ -2220,9 +2201,24 @@ object Types {
22202201
}
22212202

22222203
abstract case class OrType(tp1: Type, tp2: Type) extends CachedGroundType with AndOrType {
2204+
22232205
assert(tp1.isInstanceOf[ValueType] && tp2.isInstanceOf[ValueType])
22242206
def isAnd = false
22252207

2208+
private[this] var myJoin: Type = _
2209+
private[this] var myJoinPeriod: Period = Nowhere
2210+
2211+
/** Replace or type by the closest non-or type above it */
2212+
def join(implicit ctx: Context): Type = {
2213+
if (myJoinPeriod != ctx.period) {
2214+
myJoin = ctx.orDominator(this)
2215+
core.println(i"join of $this == $myJoin")
2216+
assert(myJoin != this)
2217+
myJoinPeriod = ctx.period
2218+
}
2219+
myJoin
2220+
}
2221+
22262222
def derivedOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type =
22272223
if ((tp1 eq this.tp1) && (tp2 eq this.tp2)) this
22282224
else OrType.make(tp1, tp2)

src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,7 @@ class Namer { typer: Typer =>
884884
// definition is inline (i.e. final in Scala2).
885885
def widenRhs(tp: Type): Type = tp.widenTermRefExpr match {
886886
case tp: ConstantType if isInline => tp
887-
case _ => tp.widen.approximateUnion
887+
case _ => ctx.harmonizeUnion(tp.widen)
888888
}
889889

890890
// Replace aliases to Unit by Unit itself. If we leave the alias in

0 commit comments

Comments
 (0)