Skip to content

Commit 7ba3198

Browse files
committed
Fix #7458: Refine typevar interpolation
If there are several type variables to instantiate in an interpolation call, instantiate first those type variables that do not tighten the constraint for other instantiatable type variables. The following code from i7458.scala requires this new scheme to compile. ``` type Tr[+V1, +O1 <: V1] def [V2, O2 <: V2](tr: Tr[V2, O2]) sl: Tr[V2, O2] = ??? def as[V3, O3 <: V3](tr: Tr[V3, O3]) : Tr[V3, O3] = tr.sl ``` Interestingly, adding `()` to the parameter list of `sl` makes the problem disappear even without the fix. The reason is that in this case interpolation is suspended to a later point since expressions of method type are not interpolated.
1 parent ca6b1c6 commit 7ba3198

File tree

2 files changed

+53
-4
lines changed

2 files changed

+53
-4
lines changed

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

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ trait Inferencing { this: Typer =>
389389
if ((ownedVars ne locked) && !ownedVars.isEmpty) {
390390
val qualifying = ownedVars -- locked
391391
if (!qualifying.isEmpty) {
392-
typr.println(i"interpolate $tree: ${tree.tpe.widen} in $state, owned vars = ${state.ownedVars.toList}%, %, previous = ${locked.toList}%, % / ${state.constraint}")
392+
typr.println(i"interpolate $tree: ${tree.tpe.widen} in $state, owned vars = ${state.ownedVars.toList}%, %, qualifying = ${qualifying.toList}%, %, previous = ${locked.toList}%, % / ${state.constraint}")
393393
val resultAlreadyConstrained =
394394
tree.isInstanceOf[Apply] || tree.tpe.isInstanceOf[MethodOrPoly]
395395
if (!resultAlreadyConstrained)
@@ -421,22 +421,68 @@ trait Inferencing { this: Typer =>
421421
// val y: List[List[String]] = List(List(1))
422422
val hasUnreportedErrors = state.reporter.hasUnreportedErrors
423423
def constraint = state.constraint
424+
type InstantiateQueue = mutable.ListBuffer[(TypeVar, Boolean)]
425+
val toInstantiate = new InstantiateQueue
424426
for (tvar <- qualifying)
425-
if (!tvar.isInstantiated && state.constraint.contains(tvar)) {
427+
if (!tvar.isInstantiated && constraint.contains(tvar)) {
426428
// Needs to be checked again, since previous interpolations could already have
427429
// instantiated `tvar` through unification.
428430
val v = vs(tvar)
429431
if (v == null) {
430432
typr.println(i"interpolate non-occurring $tvar in $state in $tree: $tp, fromBelow = ${tvar.hasLowerBound}, $constraint")
431-
tvar.instantiate(fromBelow = tvar.hasLowerBound)
433+
toInstantiate += ((tvar, tvar.hasLowerBound))
432434
}
433435
else if (!hasUnreportedErrors)
434436
if (v.intValue != 0) {
435437
typr.println(i"interpolate $tvar in $state in $tree: $tp, fromBelow = ${v.intValue == 1}, $constraint")
436-
tvar.instantiate(fromBelow = v.intValue == 1)
438+
toInstantiate += ((tvar, v.intValue == 1))
437439
}
438440
else typr.println(i"no interpolation for nonvariant $tvar in $state")
439441
}
442+
443+
/** Instantiate all type variables in `buf` in the indicated directions.
444+
* If a type variable A is instantiated from below, and there is another
445+
* type variable B in `buf` that is known to be smaller than A, wait and
446+
* instantiate all other type variables before trying to instantiate A again.
447+
* Dually, wait instantiating a type variable from above as long as it has
448+
* upper bounds in `buf`.
449+
*
450+
* This is done to avoid loss of precision when forming unions. An example
451+
* is in i7558.scala:
452+
*
453+
* type Tr[+V1, +O1 <: V1]
454+
* def [V2, O2 <: V2](tr: Tr[V2, O2]) sl: Tr[V2, O2] = ???
455+
* def as[V3, O3 <: V3](tr: Tr[V3, O3]) : Tr[V3, O3] = tr.sl
456+
*
457+
* Here we interpolate at some point V2 and O2 given the constraint
458+
*
459+
* V2 >: V3, O2 >: O3, O2 <: V2
460+
*
461+
* where O3 and V3 are type refs with O3 <: V3.
462+
* If we interpolate V2 first to V3 | O2, the widenUnion algorithm will
463+
* instantiate O2 to V3, leading to the final constraint
464+
*
465+
* V2 := V3, O2 := V3
466+
*
467+
* But if we instantiate O2 first to O3, and V2 next to V3, we get the
468+
* more flexible instantiation
469+
*
470+
* V2 := V3, O2 := O3
471+
*/
472+
def doInstantiate(buf: InstantiateQueue): Unit =
473+
if buf.nonEmpty then
474+
val suspended = new InstantiateQueue
475+
while buf.nonEmpty do
476+
val first @ (tvar, fromBelow) = buf.head
477+
buf.dropInPlace(1)
478+
val suspend = buf.exists{ (following, _) =>
479+
if fromBelow then constraint.isLess(following.origin, tvar.origin)
480+
else constraint.isLess(following.origin, tvar.origin)
481+
}
482+
if suspend then suspended += first else tvar.instantiate(fromBelow)
483+
doInstantiate(suspended)
484+
end doInstantiate
485+
doInstantiate(toInstantiate)
440486
}
441487
}
442488
tree

tests/pos/i7458.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
type Tr[+V1, +O1 <: V1]
2+
def [V2, O2 <: V2](tr: Tr[V2, O2]) sl: Tr[V2, O2] = ???
3+
def as[V3, O3 <: V3](tr: Tr[V3, O3]) : Tr[V3, O3] = tr.sl

0 commit comments

Comments
 (0)