@@ -131,7 +131,11 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
131
131
* class A extends C[A] with D
132
132
* class B extends C[B] with D with E
133
133
*
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.
135
139
*/
136
140
def orDominator (tp : Type ): Type = {
137
141
@@ -188,29 +192,82 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
188
192
case _ => false
189
193
}
190
194
195
+ // Step 1: Get RecTypes and ErrorTypes out of the way,
191
196
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
198
199
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
- }
213
200
}
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)
214
271
}
215
272
216
273
tp match {
0 commit comments