Skip to content

Commit 8796e4a

Browse files
committed
Don't interpolate type variables if doing so would narrow the constraint
1 parent 4dea9a7 commit 8796e4a

File tree

4 files changed

+74
-32
lines changed

4 files changed

+74
-32
lines changed

compiler/src/dotty/tools/dotc/config/Config.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ object Config {
2222
*/
2323
inline val checkConstraintsNonCyclic = false
2424

25+
inline val checkConstraintDeps = false
26+
2527
/** Check that each constraint resulting from a subtype test
2628
* is satisfiable. Also check that a type variable instantiation
2729
* satisfies its constraints.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ abstract class Constraint extends Showable {
189189
def dependsOn(tv: TypeVar, except: TypeVars, co: Boolean)(using Context): Boolean
190190

191191
/** Check that no constrained parameter contains itself as a bound */
192-
def checkNonCyclic()(using Context): this.type
192+
def checkWellFormed()(using Context): this.type
193193

194194
/** Does `param` occur at the toplevel in `tp` ?
195195
* Toplevel means: the type itself or a factor in some

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
433433
todos.dropInPlace(1)
434434
i += 1
435435
}
436-
current.checkNonCyclic()
436+
current.checkWellFormed()
437437
}
438438

439439
// ---------- Updates ------------------------------------------------------------
@@ -572,10 +572,10 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
572572

573573
/** The public version of `updateEntry`. Guarantees that there are no cycles */
574574
def updateEntry(param: TypeParamRef, tp: Type)(using Context): This =
575-
updateEntry(this, param, ensureNonCyclic(param, tp)).checkNonCyclic()
575+
updateEntry(this, param, ensureNonCyclic(param, tp)).checkWellFormed()
576576

577577
def addLess(param1: TypeParamRef, param2: TypeParamRef, direction: UnificationDirection)(using Context): This =
578-
order(this, param1, param2, direction).checkNonCyclic()
578+
order(this, param1, param2, direction).checkWellFormed()
579579

580580
// ---------- Replacements and Removals -------------------------------------
581581

@@ -585,7 +585,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
585585
*/
586586
def replace(param: TypeParamRef, tp: Type)(using Context): OrderingConstraint =
587587
val replacement = tp.dealiasKeepAnnots.stripTypeVar
588-
if param == replacement then this.checkNonCyclic()
588+
if param == replacement then this.checkWellFormed()
589589
else
590590
assert(replacement.isValueTypeOrLambda)
591591
var current =
@@ -607,7 +607,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
607607
current = upperLens.map(this, current, p, i, removeParam)
608608
}
609609
current.dropDeps(typeVarOfParam(param))
610-
current.checkNonCyclic()
610+
current.checkWellFormed()
611611
end replace
612612

613613
def remove(pt: TypeLambda)(using Context): This = {
@@ -620,8 +620,8 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
620620
}
621621
val hardVars1 = pt.paramRefs.foldLeft(hardVars)((hvs, param) => hvs - typeVarOfParam(param))
622622
newConstraint(boundsMap.remove(pt), removeFromOrdering(lowerMap), removeFromOrdering(upperMap), hardVars1)
623-
.checkNonCyclic()
624623
.adjustDeps(boundsMap(pt).nn, add = false)
624+
.checkWellFormed()
625625
}
626626

627627
def isRemovable(pt: TypeLambda): Boolean = {
@@ -655,7 +655,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
655655
current = upperLens.map(this, current, p, i, _.map(subst))
656656
}
657657
constr.println(i"renamed $this to $current")
658-
current.checkNonCyclic()
658+
current.checkWellFormed()
659659

660660
def isHard(tv: TypeVar) = hardVars.contains(tv)
661661

@@ -739,7 +739,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
739739

740740
// ---------- Checking -----------------------------------------------
741741

742-
def checkNonCyclic()(using Context): this.type =
742+
def checkWellFormed()(using Context): this.type =
743743
if Config.checkConstraintsNonCyclic then
744744
domainParams.foreach { param =>
745745
val inst = entry(param)
@@ -748,6 +748,14 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
748748
assert(!occursAtToplevel(param, inst),
749749
s"cyclic bound for $param: ${inst.show} in ${this.show}")
750750
}
751+
if Config.checkConstraintDeps then
752+
def checkDeps(deps: TypeVarDeps) =
753+
deps.foreachBinding { (tv, tvs) =>
754+
for tv1 <- tvs do
755+
assert(!tv1.instanceOpt.exists, i"$this")
756+
}
757+
checkDeps(coDeps)
758+
checkDeps(contraDeps)
751759
this
752760

753761
def occursAtToplevel(param: TypeParamRef, inst: Type)(using Context): Boolean =

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

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,31 @@ trait Inferencing { this: Typer =>
640640
typr.println(i"no interpolation for nonvariant $tvar in $state")
641641
)
642642

643+
def typeVarsIn(xs: collection.Seq[(TypeVar, Int)]): TypeVars =
644+
xs.foldLeft(SimpleIdentitySet.empty: TypeVars)((tvs, tvi) => tvs + tvi._1)
645+
646+
def filterByDeps(tvs0: List[(TypeVar, Int)]): List[(TypeVar, Int)] = {
647+
val excluded = typeVarsIn(tvs0)
648+
def step(tvs: List[(TypeVar, Int)]): List[(TypeVar, Int)] = tvs match
649+
case tvs @ (hd @ (tvar, v)) :: tvs1 =>
650+
def aboveOK = !constraint.dependsOn(tvar, excluded, co = true)
651+
def belowOK = !constraint.dependsOn(tvar, excluded, co = false)
652+
if v == 0 && !aboveOK then
653+
step((tvar, 1) :: tvs1)
654+
else if v == 0 && !belowOK then
655+
step((tvar, -1) :: tvs1)
656+
else if v == -1 && !aboveOK || v == 1 && !belowOK then
657+
println(i"drop $tvar, $v in $tp, $pt, qualifying = ${qualifying.toList}, tvs0 = ${tvs0.toList}%, %, excluded = ${excluded.toList}, $constraint")
658+
step(tvs1)
659+
else
660+
tvs.derivedCons(hd, step(tvs1))
661+
case Nil =>
662+
Nil
663+
val tvs1 = step(tvs0)
664+
if tvs1 eq tvs0 then tvs1 else filterByDeps(tvs1)
665+
}//.showing(i"filter $tvs0 in $constraint = $result")
666+
end filterByDeps
667+
643668
/** Instantiate all type variables in `buf` in the indicated directions.
644669
* If a type variable A is instantiated from below, and there is another
645670
* type variable B in `buf` that is known to be smaller than A, wait and
@@ -669,38 +694,45 @@ trait Inferencing { this: Typer =>
669694
*
670695
* V2 := V3, O2 := O3
671696
*/
672-
def doInstantiate(buf: InstantiateQueue): Unit =
673-
val varsToInstantiate = buf.foldLeft(SimpleIdentitySet.empty: TypeVars) {
674-
case (tvs, (tv, _)) => tvs + tv
675-
}
676-
if buf.nonEmpty then
677-
val suspended = new InstantiateQueue
678-
while buf.nonEmpty do
679-
val first @ (tvar, v) = buf.head
697+
def doInstantiate(tvs: List[(TypeVar, Int)]): Unit =
698+
def excluded = typeVarsIn(tvs)
699+
def tryInstantiate(tvs: List[(TypeVar, Int)]): List[(TypeVar, Int)] = tvs match
700+
case (hd @ (tvar, v)) :: tvs1 =>
680701
val fromBelow =
681702
if v == 0 then
682-
val aboveOK = !constraint.dependsOn(tvar, varsToInstantiate, co = true)
683-
val belowOK = !constraint.dependsOn(tvar, varsToInstantiate, co = false)
703+
val aboveOK = true // !constraint.dependsOn(tvar, excluded, co = true, track = true)
704+
val belowOK = true // !constraint.dependsOn(tvar, excluded, co = false, track = true)
705+
assert(aboveOK, i"$tvar, excluded = ${excluded.toList}, $constraint")
706+
assert(belowOK, i"$tvar, excluded = ${excluded.toList}, $constraint")
684707
if aboveOK == belowOK then tvar.hasLowerBound
685708
else belowOK
686709
else
687710
v == 1
688711
typr.println(
689-
i"interpolate${if v == 0 then "non-occurring" else ""} $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint")
690-
buf.dropInPlace(1)
691-
if !tvar.isInstantiated then
692-
val suspend = buf.exists{ (following, _) =>
693-
if fromBelow then
694-
constraint.isLess(following.origin, tvar.origin)
695-
else
696-
constraint.isLess(tvar.origin, following.origin)
712+
i"interpolate${if v == 0 then " non-occurring" else ""} $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint")
713+
if tvar.isInstantiated then
714+
tryInstantiate(tvs1)
715+
else
716+
val suspend = tvs1.exists{ (following, _) =>
717+
if fromBelow
718+
then constraint.isLess(following.origin, tvar.origin)
719+
else constraint.isLess(tvar.origin, following.origin)
697720
}
698-
if suspend then suspended += first else tvar.instantiate(fromBelow)
699-
end if
700-
end while
701-
doInstantiate(suspended)
721+
if suspend then
722+
typr.println(i"suspended: $hd")
723+
hd :: tryInstantiate(tvs1)
724+
else
725+
tvar.instantiate(fromBelow)
726+
tryInstantiate(tvs1)
727+
case Nil => Nil
728+
if tvs.nonEmpty then doInstantiate(tryInstantiate(tvs))
702729
end doInstantiate
703-
doInstantiate(toInstantiate)
730+
val toInst = toInstantiate.toList
731+
if toInst.nonEmpty then
732+
typr.println(i"interpolating $toInst for $tp/$pt in $constraint")
733+
val filtered = filterByDeps(toInst)
734+
typr.println(i"filtered $filtered")
735+
doInstantiate(filtered)
704736
}
705737
}
706738
tree

0 commit comments

Comments
 (0)