@@ -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,82 @@ 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+ // 0. If no widening succeeds, proceed with step 3.
219+ // 1. If only one widening succeeds, pick that one.
220+ // 2. If the two widened types are in a subtype relationship, pick the smaller one.
221+ // 3. If exactly one of the two types is a singleton type, pick that one.
222+ // 4. If the widened tp1 is a supertype of tp2, pick widened tp1.
223+ // 5. If the widened tp2 is a supertype of tp1, pick widened tp2.
224+ // 6. Otherwise, pick 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)
255+ else if (tp1w eq tp1) orDominator(tp1 | tp2w)
256+ else if (tp1w frozen_<:< tp2w) orDominator(tp1w | tp2)
257+ else if (tp2w frozen_<:< tp1w) orDominator(tp1 | tp2w)
258+ else if (isSingle1 && ! isSingle2) orDominator(tp1w | tp2)
259+ else if (isSingle2 && ! isSingle1) orDominator(tp1 | tp2w)
260+ else if (tp1 frozen_<:< tp2w) tp2w
261+ else orDominator(tp1w | tp2)
262+ }
263+ }
264+
265+ // Step 3: Intersect base classes of both sides
266+ val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect)
267+ val doms = dominators(commonBaseClasses, Nil )
268+ def baseTp (cls : ClassSymbol ): Type =
269+ tp.baseType(cls).mapReduceOr(identity)(mergeRefinedOrApplied)
270+ doms.map(baseTp).reduceLeft(AndType .apply)
214271 }
215272
216273 tp match {
0 commit comments