@@ -18,6 +18,11 @@ import cc.{CapturingType, derivedCapturingType}
1818
1919object OrderingConstraint {
2020
21+ @ sharable private var id = 0
22+ private def nextId =
23+ id += 1
24+ id
25+
2126 type ArrayValuedMap [T ] = SimpleIdentityMap [TypeLambda , Array [T ]]
2227
2328 /** The type of `OrderingConstraint#boundsMap` */
@@ -140,6 +145,10 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
140145
141146 type This = OrderingConstraint
142147
148+ var id = nextId
149+ // if id == 118 then
150+ // new Error(s"at $id").printStackTrace()
151+
143152 /** A new constraint with given maps and given set of hard typevars */
144153 def newConstraint ( // !!! Dotty problem: Making newConstraint `private` causes -Ytest-pickler failure.
145154 boundsMap : ParamBounds = this .boundsMap,
@@ -245,8 +254,40 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
245254 // .showing(i"outer depends on $tv with ${tvdeps.toList}%, % = $result")
246255 if co then test(coDeps, upperLens) else test(contraDeps, lowerLens)
247256
257+ /** Modify traversals in two respects:
258+ * - when encountering an application C[Ts], where C is a type variable or parameter
259+ * that has an instantiation in this constraint, assume the type parameters of
260+ * the instantiation instead of the type parameters of C when traversing the
261+ * arguments Ts. That can make a difference for the variance in which an argument
262+ * is traversed. Example constraint:
263+ *
264+ * constrainded types: C[X], A
265+ * A >: C[B]
266+ * C := Option
267+ *
268+ * Here, B is traversed with variance +1 instead of 0. Test case: pos/t3152.scala
269+ *
270+ * - When typing a prefx, don't avoid negative variances. This matters only for the
271+ * corner case where a parameter is instantiated to Nothing (see comment in
272+ * TypeAccumulator#applyToPrefix). When determining instantiation directions
273+ * (which is what dependency variances are for), it can be ignored.
274+ */
275+ private trait ConstraintAwareTraversal [T ] extends TypeAccumulator [T ]:
276+ override def tyconTypeParams (tp : AppliedType )(using Context ): List [ParamInfo ] =
277+ def tparams (tycon : Type ): List [ParamInfo ] = tycon match
278+ case tycon : TypeVar if ! tycon.isInstantiated => tparams(tycon.origin)
279+ case tycon : TypeParamRef =>
280+ entry(tycon) match
281+ case _ : TypeBounds => tp.tyconTypeParams
282+ case tycon1 if tycon1.typeParams.nonEmpty => tycon1.typeParams
283+ case _ => tp.tyconTypeParams
284+ case _ => tp.tyconTypeParams
285+ tparams(tp.tycon)
286+ override def applyToPrefix (x : T , tp : NamedType ): T =
287+ this (x, tp.prefix)
288+
248289 private class Adjuster (srcParam : TypeParamRef )(using Context )
249- extends TypeTraverser , ConstraintAwareTraversal :
290+ extends TypeTraverser , ConstraintAwareTraversal [ Unit ] :
250291
251292 var add : Boolean = compiletime.uninitialized
252293 val seen = util.HashSet [LazyRef ]()
@@ -257,9 +298,12 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
257298
258299 def traverse (t : Type ) = t match
259300 case param : TypeParamRef =>
260- if contains(param) then
261- if variance >= 0 then coDeps = update(coDeps, param)
262- if variance <= 0 then contraDeps = update(contraDeps, param)
301+ entry(param) match
302+ case _ : TypeBounds =>
303+ if variance >= 0 then coDeps = update(coDeps, param)
304+ if variance <= 0 then contraDeps = update(contraDeps, param)
305+ case tp =>
306+ traverse(tp)
263307 case tp : LazyRef =>
264308 if ! seen.contains(tp) then
265309 seen += tp
@@ -287,39 +331,40 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
287331 * and/or prefix of `bound`, just add the new parts of `bound`.
288332 * @param isLower `bound` and `prevBound` are lower bounds
289333 */
290- def adjustDelta (bound : Type , prevBound : Type , isLower : Boolean ): Boolean =
291- if bound eq prevBound then true
334+ def adjustDelta (bound : Type , prevBound : Type , isLower : Boolean , baseCase : => Boolean ): Boolean =
335+ if bound eq prevBound then
336+ baseCase
292337 else bound match
293338 case bound : AndOrType =>
294- adjustDelta(bound.tp1, prevBound, isLower) && {
339+ adjustDelta(bound.tp1, prevBound, isLower, baseCase ) && {
295340 adjustReferenced(bound.tp2, isLower, add = true )
296341 true
297342 }
298343 case _ => false
299344
300- /** Adjust dependencies to account for the delta of previous bound `prevBound `
301- * and new bound `bound `.
302- * @param isLower `bound` and `prevBound` are lower bounds
345+ /** Adjust dependencies to account for the delta of previous bounds `prevBounds `
346+ * and new bounds `bounds `.
347+ * @param add true if the bounds are added, false if they are removed
303348 */
304- def adjustBounds (bound : Type , prevBound : Type , isLower : Boolean ) =
305- if ! adjustDelta(bound, prevBound, isLower) then
306- adjustReferenced(prevBound, isLower, add = false )
307- adjustReferenced(bound, isLower, add = true )
349+ def adjustBounds (bounds : TypeBounds , add : Boolean ) =
350+ adjustReferenced(bounds.lo, isLower = true , add)
351+ adjustReferenced(bounds.hi, isLower = false , add)
308352
309353 entry match
310- case TypeBounds (lo, hi) =>
354+ case entry @ TypeBounds (lo, hi) =>
311355 prevEntry match
312- case TypeBounds (plo, phi) =>
313- adjustBounds(lo, plo, isLower = true )
314- adjustBounds(hi, phi, isLower = false )
356+ case prevEntry @ TypeBounds (plo, phi) =>
357+ if ! adjustDelta(lo, plo, isLower = true ,
358+ adjustDelta(hi, phi, isLower = false , true ))
359+ then
360+ adjustBounds(prevEntry, add = false )
361+ adjustBounds(entry, add = true )
315362 case _ =>
316- adjustReferenced(lo, isLower = true , add = true )
317- adjustReferenced(hi, isLower = false , add = true )
363+ adjustBounds(entry, add = true )
318364 case _ =>
319365 prevEntry match
320- case TypeBounds (plo, phi) =>
321- adjustReferenced(plo, isLower = true , add = false )
322- adjustReferenced(phi, isLower = false , add = false )
366+ case prevEntry : TypeBounds =>
367+ adjustBounds(prevEntry, add = false )
323368 case _ =>
324369 dropDeps(srcParam)
325370 this
@@ -408,6 +453,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
408453 }
409454
410455 def add (poly : TypeLambda , tvars : List [TypeVar ])(using Context ): This = {
456+ checkWellFormed() // TODO: drop
411457 assert(! contains(poly))
412458 val nparams = poly.paramNames.length
413459 val entries1 = new Array [Type ](nparams * 2 )
@@ -563,7 +609,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
563609 boundsLens.update(this , current, param, newEntry).adjustDeps(newEntry, oldEntry, param)
564610
565611 private def updateEntry (current : This , param : TypeParamRef , newEntry : Type )(using Context ): This = {
566- // println(i"update $param to $tp in $current")
612+ // println(i"update $param to $newEntry in $current")
567613 if Config .checkNoWildcardsInConstraint then assert(! newEntry.containsWildcardTypes)
568614 var current1 = updateEntryNoOrdering(current, param, newEntry, current.entry(param))
569615 newEntry match {
@@ -596,12 +642,12 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
596642 else
597643 assert(replacement.isValueTypeOrLambda)
598644
599- val droppedTypeVar = typeVarOfParam(param)
645+ val replacedTypeVar = typeVarOfParam(param)
646+ // println(i"replace $param, $replacedTypeVar with $replacement in $this")
600647
601- // println(i"replace $param, $droppedTypeVar with $replacement in $this")
602- val dropTypeVar = new TypeMap :
648+ def mapReplacedTypeVarTo (to : Type ) = new TypeMap :
603649 override def apply (t : Type ): Type =
604- if t.exists && (t eq droppedTypeVar) then param else mapOver(t)
650+ if (t eq replacedTypeVar) && t.exists then to else mapOver(t)
605651
606652 var current = this
607653
@@ -616,7 +662,29 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
616662 if other != param then
617663 val oldEntry = current.entry(other)
618664 val newEntry = current.ensureNonCyclic(other, oldEntry.substParam(param, replacement))
619- current = updateEntryNoOrdering(current, other, newEntry, dropTypeVar(oldEntry))
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)
620688 }
621689
622690 current =
@@ -703,6 +771,26 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
703771 assert(tvar.origin == param, i " mismatch $tvar, $param" )
704772 case _ =>
705773
774+ def occursAtToplevel (param : TypeParamRef , inst : Type )(using Context ): Boolean =
775+ def occurs (tp : Type )(using Context ): Boolean = tp match
776+ case tp : AndOrType =>
777+ occurs(tp.tp1) || occurs(tp.tp2)
778+ case tp : TypeParamRef =>
779+ (tp eq param) || entry(tp).match
780+ case NoType => false
781+ case TypeBounds (lo, hi) => (lo eq hi) && occurs(lo)
782+ case inst => occurs(inst)
783+ case tp : TypeVar =>
784+ occurs(tp.underlying)
785+ case TypeBounds (lo, hi) =>
786+ occurs(lo) || occurs(hi)
787+ case _ =>
788+ val tp1 = tp.dealias
789+ (tp1 ne tp) && occurs(tp1)
790+
791+ occurs(inst)
792+ end occursAtToplevel
793+
706794// ---------- Exploration --------------------------------------------------------
707795
708796 def domainLambdas : List [TypeLambda ] = boundsMap.keys
@@ -755,7 +843,60 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
755843
756844// ---------- Checking -----------------------------------------------
757845
846+ /** Depending on Config settngs, check that there are no cycles and that
847+ * reverse depenecies are correct.
848+ */
758849 def checkWellFormed ()(using Context ): this .type =
850+
851+ /** Check that each dependency A -> B in coDeps and contraDeps corresponds to
852+ * a reference to A at the right variance in the entry of B.
853+ */
854+ def checkBackward (deps : ReverseDeps , depsName : String , v : Int )(using Context ): Unit =
855+ deps.foreachBinding { (param, params) =>
856+ for srcParam <- params do
857+ assert(contains(srcParam) && occursAtVariance(param, v, in = entry(srcParam)),
858+ i " wrong $depsName backwards reference $param -> $srcParam in $thisConstraint" )
859+ }
860+
861+ /** A type traverser that checks that all references bound in the constraint
862+ * are accounted for in coDeps and/or contraDeps.
863+ */
864+ def checkForward (srcParam : TypeParamRef )(using Context ) =
865+ new TypeTraverser with ConstraintAwareTraversal [Unit ]:
866+ val seen = util.HashSet [LazyRef ]()
867+ def traverse (t : Type ): Unit = t match
868+ case param : TypeParamRef if param ne srcParam =>
869+ def check (deps : ReverseDeps , directDeps : List [TypeParamRef ], depsName : String ) =
870+ assert(deps.at(param).contains(srcParam) || directDeps.contains(srcParam),
871+ i " missing $depsName backwards reference $param -> $srcParam in $thisConstraint" )
872+ entry(param) match
873+ case _ : TypeBounds =>
874+ if variance >= 0 then check(contraDeps, upper(param), " contra" )
875+ if variance <= 0 then check(coDeps, lower(param), " co" )
876+ case tp =>
877+ traverse(tp)
878+ case tp : LazyRef =>
879+ if ! seen.contains(tp) then
880+ seen += tp
881+ traverse(tp.ref)
882+ case _ => traverseChildren(t)
883+
884+ /** Does `param` occur at variance `v` or else at variance 0 in entry `in`? */
885+ def occursAtVariance (param : TypeParamRef , v : Int , in : Type )(using Context ): Boolean =
886+ val test = new TypeAccumulator [Boolean ] with ConstraintAwareTraversal [Boolean ]:
887+ def apply (x : Boolean , t : Type ): Boolean =
888+ if x then true
889+ else t match
890+ case t : TypeParamRef =>
891+ entry(t) match
892+ case _ : TypeBounds =>
893+ t == param && (variance == 0 || variance == v)
894+ case e =>
895+ apply(x, e)
896+ case _ =>
897+ foldOver(x, t)
898+ test(false , in)
899+
759900 if Config .checkConstraintsNonCyclic then
760901 domainParams.foreach { param =>
761902 val inst = entry(param)
@@ -764,38 +905,14 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
764905 assert(! occursAtToplevel(param, inst),
765906 s " cyclic bound for $param: ${inst.show} in ${this .show}" )
766907 }
767- if Config .checkConstraintDeps then
768- def checkDeps (deps : ReverseDeps ) = ()/*
769- deps.foreachBinding { (tv, tvs) =>
770- for tv1 <- tvs do
771- assert(!tv1.instanceOpt.exists, i"$this")
772- }*/
773- checkDeps(coDeps)
774- checkDeps(contraDeps)
908+ if Config .checkConstraintDeps || ctx.settings.YcheckConstraintDeps .value then
909+ checkBackward(coDeps, " co" , - 1 )
910+ checkBackward(contraDeps, " contra" , + 1 )
911+ domainParams.foreach(p => if contains(p) then checkForward(p).traverse(entry(p)))
912+
775913 this
776914 end checkWellFormed
777915
778- def occursAtToplevel (param : TypeParamRef , inst : Type )(using Context ): Boolean =
779-
780- def occurs (tp : Type )(using Context ): Boolean = tp match
781- case tp : AndOrType =>
782- occurs(tp.tp1) || occurs(tp.tp2)
783- case tp : TypeParamRef =>
784- (tp eq param) || entry(tp).match
785- case NoType => false
786- case TypeBounds (lo, hi) => (lo eq hi) && occurs(lo)
787- case inst => occurs(inst)
788- case tp : TypeVar =>
789- occurs(tp.underlying)
790- case TypeBounds (lo, hi) =>
791- occurs(lo) || occurs(hi)
792- case _ =>
793- val tp1 = tp.dealias
794- (tp1 ne tp) && occurs(tp1)
795-
796- occurs(inst)
797- end occursAtToplevel
798-
799916 override def checkClosed ()(using Context ): Unit =
800917
801918 def isFreeTypeParamRef (tp : Type ) = tp match
0 commit comments