@@ -18,10 +18,16 @@ import cc.{CapturingType, derivedCapturingType}
1818
1919object OrderingConstraint {
2020
21- @ sharable private var id = 0
22- private def nextId =
23- id += 1
24- id
21+ /** If true, use reverse dependencies in `replace` to avoid checking the bounds
22+ * of all parameters in the constraint. This can speed things up, but there are some
23+ * rare corner cases where reverse dependencies miss a parameter. Specifically,
24+ * if a constraint contains a free reference to TypeParam P and afterwards the
25+ * same P is added as a bound variable to the constraint, a backwards link would
26+ * then become necessary at this point but is missing. This causes two CB projects
27+ * to fail when reverse dependencies are checked (parboiled2 and perspective).
28+ * In these rare cases `replace` would behave differently when optimized.
29+ */
30+ final val optimizeReplace = true
2531
2632 type ArrayValuedMap [T ] = SimpleIdentityMap [TypeLambda , Array [T ]]
2733
@@ -145,10 +151,6 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
145151
146152 type This = OrderingConstraint
147153
148- var id = nextId
149- // if id == 118 then
150- // new Error(s"at $id").printStackTrace()
151-
152154 /** A new constraint with given maps and given set of hard typevars */
153155 def newConstraint ( // !!! Dotty problem: Making newConstraint `private` causes -Ytest-pickler failure.
154156 boundsMap : ParamBounds = this .boundsMap,
@@ -294,7 +296,9 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
294296
295297 def update (deps : ReverseDeps , referenced : TypeParamRef ): ReverseDeps =
296298 val prev = deps.at(referenced)
297- deps.updated(referenced, if add then prev + srcParam else prev - srcParam)
299+ val newSet = if add then prev + srcParam else prev - srcParam
300+ if newSet.isEmpty then deps.remove(referenced)
301+ else deps.updated(referenced, newSet)
298302
299303 def traverse (t : Type ) = t match
300304 case param : TypeParamRef =>
@@ -651,41 +655,58 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
651655
652656 var current = this
653657
654- def removeParam (ps : List [TypeParamRef ]) = ps.filterConserve(param ne _)
658+ def removeParamFrom (ps : List [TypeParamRef ]) =
659+ ps.filterConserve(param ne _)
660+
655661 for lo <- lower(param) do
656- current = upperLens.map(this , current, lo, removeParam )
662+ current = upperLens.map(this , current, lo, removeParamFrom )
657663 for hi <- upper(param) do
658- current = lowerLens.map(this , current, hi, removeParam)
659-
660- current.foreachParam { (p, i) =>
661- val other = p.paramRefs(i)
662- if other != param then
663- val oldEntry = current.entry(other)
664- val newEntry = current.ensureNonCyclic(other, oldEntry.substParam(param, replacement))
665- current = boundsLens.update(this , current, other, newEntry)
666- var oldDepEntry = oldEntry
667- var newDepEntry = newEntry
668- replacedTypeVar match
669- case tvar : TypeVar =>
670- if tvar.isInstantiated
671- then
672- // replace is called from TypeVar's instantiateWith,
673- // forget about instantiation for old dependencies
674- oldDepEntry = mapReplacedTypeVarTo(param)(oldDepEntry)
675- else
676- // replace is called from unify,
677- // assume parameter has been replaced for new dependencies
678- // (the actual replacement is done below)
679- newDepEntry = mapReplacedTypeVarTo(replacement)(newDepEntry)
680- case _ =>
681- if oldDepEntry ne newDepEntry then
682- if current eq this then
683- // We can end up here if oldEntry eq newEntry, so posssibly no new constraint
684- // was created, but oldDepEntry ne newDepEntry. In that case we must make
685- // sure we have a new constraint before updating dependencies.
686- current = newConstraint()
687- current.adjustDeps(newDepEntry, oldDepEntry, other)
688- }
664+ current = lowerLens.map(this , current, hi, removeParamFrom)
665+
666+ def replaceParamIn (other : TypeParamRef ) =
667+ val oldEntry = current.entry(other)
668+ val newEntry = current.ensureNonCyclic(other, oldEntry.substParam(param, replacement))
669+ current = boundsLens.update(this , current, other, newEntry)
670+ var oldDepEntry = oldEntry
671+ var newDepEntry = newEntry
672+ replacedTypeVar match
673+ case tvar : TypeVar =>
674+ if tvar.isInstantiated
675+ then
676+ // replace is called from TypeVar's instantiateWith,
677+ // forget about instantiation for old dependencies
678+ oldDepEntry = mapReplacedTypeVarTo(param)(oldDepEntry)
679+ else
680+ // replace is called from unify,
681+ // assume parameter has been replaced for new dependencies
682+ // (the actual replacement is done below)
683+ newDepEntry = mapReplacedTypeVarTo(replacement)(newDepEntry)
684+ case _ =>
685+ if oldDepEntry ne newDepEntry then
686+ if current eq this then
687+ // We can end up here if oldEntry eq newEntry, so posssibly no new constraint
688+ // was created, but oldDepEntry ne newDepEntry. In that case we must make
689+ // sure we have a new constraint before updating dependencies.
690+ current = newConstraint()
691+ current.adjustDeps(newDepEntry, oldDepEntry, other)
692+ end replaceParamIn
693+
694+ if optimizeReplace then
695+ val co = current.coDeps.at(param)
696+ val contra = current.contraDeps.at(param)
697+ current.foreachParam { (p, i) =>
698+ val other = p.paramRefs(i)
699+ entry(other) match
700+ case _ : TypeBounds =>
701+ if co.contains(other) || contra.contains(other) then
702+ replaceParamIn(other)
703+ case _ => replaceParamIn(other)
704+ }
705+ else
706+ current.foreachParam { (p, i) =>
707+ val other = p.paramRefs(i)
708+ if other != param then replaceParamIn(other)
709+ }
689710
690711 current =
691712 if isRemovable(param.binder) then current.remove(param.binder)
0 commit comments