Skip to content

Commit 3f52c58

Browse files
committed
Refine instantiation scheme in the case of non-variance
In a non-variant situation, we have to guess, which is difficult. This commit tries to improve guessing. With it, we can compile the either case of the TraverseTest in the collection strawman.
1 parent 733760c commit 3f52c58

File tree

3 files changed

+100
-51
lines changed

3 files changed

+100
-51
lines changed

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

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -230,12 +230,47 @@ trait ConstraintHandling {
230230
}
231231

232232
/** The instance type of `param` in the current constraint (which contains `param`).
233-
* If `fromBelow` is true, the instance type is the lub of the parameter's
234-
* lower bounds; otherwise it is the glb of its upper bounds. However,
235-
* a lower bound instantiation can be a singleton type only if the upper bound
236-
* is also a singleton type.
233+
* The instance type TI is computed depending on the given variance as follows:
234+
*
235+
* If `variance < 0`, `TI` is the glb of `param`'s upper bounds.
236+
* If `variance > 0`, `TI` is the lub of `param`'s lower bounds.
237+
* However, if `TI` would be a singleton type, and the upper bound is
238+
* not a singleton type, `TI` is widened to a non-singleton type.
239+
* If `variance = 0`, let `Lo1` be the lower bound of `param` and let `Lo2` be the
240+
* |-dominator of `Lo1`. If `Lo2` is not more ugly than `Lo1`, we add the
241+
* additional constraint that `param` should be a supertype of `Lo2`.
242+
* We then proceed as if `variance > 0`.
243+
*
244+
* The reason for tweaking the `variance = 0` case is that in this case we have to make
245+
* a guess, since no possible instance type is better than the other. We apply the heuristic
246+
* that usually an |-type is not what is intended. E.g. given two cases of types
247+
* `Some[Int]` and `None`, we'd naturally want `Option[Int]` as the inferred type, not
248+
* `Some[Int] | None`. If `variance > 0` it's OK to infer the smaller `|-type` since
249+
* we can always widen later to the intended type. But in non-variant situations, we
250+
* have to stick to the initial guess.
251+
*
252+
* On the other hand, sometimes the |-dominator is _not_ what we want. For instance, taking
253+
* run/hmap.scala as an example, we have a lower bound
254+
*
255+
* HEntry[$param$5, V] | HEntry[String("name"), V]
256+
*
257+
* Its |-dominator is
258+
*
259+
* HEntry[_ >: $param$11 & String("cat") <: String, V]
260+
*
261+
* If we apply the additional constraint we get compilation failures because while
262+
* we can find implicits for the |-type above, we cannot find implicits for the |-dominator.
263+
* There's no hard criterion what should be considered ugly or not. For now we
264+
* pick the degree of undefinedness of type parameters, i.e. how many type
265+
* parameters are instantiated with wildcard types. Maybe we have to refine this
266+
* in the future.
267+
*
268+
* The whole thing is clearly not nice, and I would love to have a better criterion.
269+
* In principle we are grappling with the fundamental shortcoming that local type inference
270+
* sometimes has to guess what was intended. The more refined our type lattice becomes,
271+
* the harder it is to make a choice.
237272
*/
238-
def instanceType(param: TypeParamRef, fromBelow: Boolean): Type = {
273+
def instanceType(param: TypeParamRef, variance: Int): Type = {
239274
def upperBound = constraint.fullUpperBound(param)
240275
def isSingleton(tp: Type): Boolean = tp match {
241276
case tp: SingletonType => true
@@ -257,22 +292,44 @@ trait ConstraintHandling {
257292
case _ => false
258293
}
259294

260-
// First, solve the constraint.
295+
// First, consider whether we need to constrain with |-dominator
296+
if (variance == 0) {
297+
val lo1 = ctx.typeComparer.bounds(param).lo
298+
val lo2 = ctx.orDominator(lo1)
299+
if (lo1 ne lo2) {
300+
val ugliness = new TypeAccumulator[Int] {
301+
def apply(x: Int, tp: Type) = tp match {
302+
case tp: TypeBounds if !tp.isAlias => apply(apply(x + 1, tp.lo), tp.hi)
303+
case tp: AndOrType => apply(x, tp.tp1) max apply(x, tp.tp2)
304+
case _ => foldOver(x, tp)
305+
}
306+
}
307+
if (ugliness(0, lo2) <= ugliness(0, lo1)) {
308+
constr.println(i"apply additional constr $lo2 <: $param, was $lo1")
309+
lo2 <:< param
310+
}
311+
}
312+
}
313+
314+
// Then, solve the constraint.
315+
val fromBelow = variance >= 0
261316
var inst = approximation(param, fromBelow)
262317

263-
// Then, approximate by (1.) - (3.) and simplify as follows.
264-
// 1. If instance is from below and is a singleton type, yet
318+
// Then, if instance is from below and is a singleton type, yet
265319
// upper bound is not a singleton type, widen the instance.
266320
if (fromBelow && isSingleton(inst) && !isSingleton(upperBound))
267321
inst = inst.widen
268322

323+
// Then, simplify.
269324
inst = inst.simplified
270325

271-
// 2. If instance is from below and is a fully-defined union type, yet upper bound
272-
// is not a union type, approximate the union type from above by an intersection
273-
// of all common base types.
274-
if (fromBelow && isOrType(inst) && isFullyDefined(inst) && !isOrType(upperBound))
326+
// Finally, if the instance is from below and is a fully-defined union type, yet upper bound
327+
// is not a union type, harmonize the union type.
328+
// TODO: See whether we can merge this with the special treatment of dependent params in
329+
// simplified.
330+
if (fromBelow && isOrType(inst) && isFullyDefined(inst) && !isOrType(upperBound)) {
275331
inst = ctx.harmonizeUnion(inst)
332+
}
276333

277334
inst
278335
}

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3073,14 +3073,14 @@ object Types {
30733073
}
30743074

30753075
/** Instantiate variable from the constraints over its `origin`.
3076-
* If `fromBelow` is true, the variable is instantiated to the lub
3076+
* If `variance >= 0` the variable is instantiated to the lub
30773077
* of its lower bounds in the current constraint; otherwise it is
3078-
* instantiated to the glb of its upper bounds. However, a lower bound
3079-
* instantiation can be a singleton type only if the upper bound
3080-
* is also a singleton type.
3078+
* instantiated to the glb of its upper bounds. However, lower bound
3079+
* singleton types and |-types are sometimes widened; for details see
3080+
* TypeComparer#instanceType.
30813081
*/
3082-
def instantiate(fromBelow: Boolean)(implicit ctx: Context): Type =
3083-
instantiateWith(ctx.typeComparer.instanceType(origin, fromBelow))
3082+
def instantiate(variance: Int)(implicit ctx: Context): Type =
3083+
instantiateWith(ctx.typeComparer.instanceType(origin, variance))
30843084

30853085
/** Unwrap to instance (if instantiated) or origin (if not), until result
30863086
* is no longer a TypeVar

compiler/src/dotty/tools/dotc/typer/Inferencing.scala

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ object Inferencing {
7272
* to their upper bound.
7373
*/
7474
private class IsFullyDefinedAccumulator(force: ForceDegree.Value)(implicit ctx: Context) extends TypeAccumulator[Boolean] {
75-
private def instantiate(tvar: TypeVar, fromBelow: Boolean): Type = {
76-
val inst = tvar.instantiate(fromBelow)
75+
private def instantiate(tvar: TypeVar, v: Int): Type = {
76+
val inst = tvar.instantiate(v)
7777
typr.println(i"forced instantiation of ${tvar.origin} = $inst")
7878
inst
7979
}
@@ -84,29 +84,20 @@ object Inferencing {
8484
case tvar: TypeVar
8585
if !tvar.isInstantiated && ctx.typerState.constraint.contains(tvar) =>
8686
force.appliesTo(tvar) && {
87-
if (force.implicitMode) {
88-
// instantiate to or-dominator of lower bound; without this tweak we'd
89-
// fail to find an implicit in situations like this:
90-
//
91-
// def f[T: Ordering](xs: T*) = ...
92-
// f(Some(1), None)
93-
//
94-
// The problem is that there is no implicit Ordering instance for the otherwise inferred
95-
// lower bound of T, which is `Some[Int] | None`.
96-
ctx.orDominator(ctx.typeComparer.bounds(tvar.origin).lo) <:< tvar.origin
97-
}
98-
val direction = instDirection(tvar.origin)
99-
if (direction != 0) {
87+
var constrVariance = instVariance(tvar.origin)
88+
if (variance == 0) constrVariance = constrVariance min 0
89+
if (constrVariance != 0) {
10090
//if (direction > 0) println(s"inst $tvar dir = up")
101-
instantiate(tvar, direction < 0)
91+
instantiate(tvar, constrVariance)
10292
}
10393
else {
104-
val minimize =
105-
force.implicitMode ||
106-
variance >= 0 && !(
107-
force == ForceDegree.noBottom &&
108-
defn.isBottomType(ctx.typeComparer.approximation(tvar.origin, fromBelow = true)))
109-
if (minimize) instantiate(tvar, fromBelow = true)
94+
val effectiveVariance =
95+
if (force.implicitMode) 0
96+
else if (force == ForceDegree.noBottom &&
97+
defn.isBottomType(ctx.typeComparer.approximation(tvar.origin, fromBelow = true)))
98+
-1
99+
else variance
100+
if (effectiveVariance >= 0) instantiate(tvar, effectiveVariance)
110101
else toMaximize = true
111102
}
112103
foldOver(x, tvar)
@@ -119,7 +110,7 @@ object Inferencing {
119110
def apply(x: Unit, tp: Type): Unit = {
120111
tp match {
121112
case tvar: TypeVar if !tvar.isInstantiated =>
122-
instantiate(tvar, fromBelow = false)
113+
instantiate(tvar, -1)
123114
case _ =>
124115
}
125116
foldOver(x, tp)
@@ -184,21 +175,23 @@ object Inferencing {
184175
occurring(tree, boundVars(tree, Nil), Nil)
185176
}
186177

187-
/** The instantiation direction for given poly param computed
178+
/** The preferred instantiation variance for given param ref computed
188179
* from the constraint:
189-
* @return 1 (maximize) if constraint is uniformly from above,
190-
* -1 (minimize) if constraint is uniformly from below,
180+
* @return -1 (maximize) if constraint is uniformly from above,
181+
* +1 (minimize) if constraint is uniformly from below,
191182
* 0 if unconstrained, or constraint is from below and above.
183+
* Note that the instantiation direction of the parameter is the reverse
184+
* of the variance: -1 means maximize, +1 means minimize.
192185
*/
193-
private def instDirection(param: TypeParamRef)(implicit ctx: Context): Int = {
186+
private def instVariance(param: TypeParamRef)(implicit ctx: Context): Int = {
194187
val constrained = ctx.typerState.constraint.fullBounds(param)
195188
val original = param.binder.paramInfos(param.paramNum)
196189
val cmp = ctx.typeComparer
197190
val approxBelow =
198191
if (!cmp.isSubTypeWhenFrozen(constrained.lo, original.lo)) 1 else 0
199192
val approxAbove =
200193
if (!cmp.isSubTypeWhenFrozen(original.hi, constrained.hi)) 1 else 0
201-
approxAbove - approxBelow
194+
approxBelow - approxAbove
202195
}
203196

204197
/** Recursively widen and also follow type declarations and type aliases. */
@@ -277,13 +270,13 @@ object Inferencing {
277270
vs foreachBinding { (tvar, v) =>
278271
if (v != 0) {
279272
typr.println(s"interpolate ${if (v == 1) "co" else "contra"}variant ${tvar.show} in ${tp.show}")
280-
tvar.instantiate(fromBelow = v == 1)
273+
tvar.instantiate(v)
281274
}
282275
}
283276
for (tvar <- constraint.uninstVars)
284277
if (!(vs contains tvar) && qualifies(tvar)) {
285278
typr.println(s"instantiating non-occurring ${tvar.show} in ${tp.show} / $tp")
286-
tvar.instantiate(fromBelow = true)
279+
tvar.instantiate(0)
287280
}
288281
}
289282
if (constraint.uninstVars exists qualifies) interpolate()
@@ -297,12 +290,11 @@ object Inferencing {
297290
val vs = variances(tp, alwaysTrue)
298291
var result: Option[TypeVar] = None
299292
vs foreachBinding { (tvar, v) =>
300-
if (v == 1) tvar.instantiate(fromBelow = false)
301-
else if (v == -1) tvar.instantiate(fromBelow = true)
293+
if (v != 0) tvar.instantiate(-v)
302294
else {
303295
val bounds = ctx.typerState.constraint.fullBounds(tvar.origin)
304296
if (!(bounds.hi <:< bounds.lo)) result = Some(tvar)
305-
tvar.instantiate(fromBelow = false)
297+
tvar.instantiate(-1)
306298
}
307299
}
308300
result

0 commit comments

Comments
 (0)