@@ -302,25 +302,18 @@ sealed abstract class CaptureSet extends Showable:
302302 case _ => Filtered (asVar, p)
303303
304304 /** Capture set obtained by applying `tm` to all elements of the current capture set
305- * and joining the results. If the current capture set is a variable, the same
306- * transformation is applied to all future additions of new elements.
307- *
308- * Note: We have a problem how we handle the situation where we have a mapped set
309- *
310- * cs2 = tm(cs1)
311- *
312- * and then the propagation solver adds a new element `x` to `cs2`. What do we
313- * know in this case about `cs1`? We can answer this question in a sound way only
314- * if `tm` is a bijection on capture references or it is idempotent on capture references.
315- * (see definition in IdempotentCapRefMap).
316- * If `tm` is a bijection we know that `tm^-1(x)` must be in `cs1`. If `tm` is idempotent
317- * one possible solution is that `x` is in `cs1`, which is what we assume in this case.
318- * That strategy is sound but not complete.
319- *
320- * If `tm` is some other map, we don't know how to handle this case. For now,
321- * we simply refuse to handle other maps. If they do need to be handled,
322- * `OtherMapped` provides some approximation to a solution, but it is neither
323- * sound nor complete.
305+ * and joining the results. If the current capture set is a variable we handle this as
306+ * follows:
307+ * - If the map is a BiTypeMap, the same transformation is applied to all
308+ * future additions of new elements. We try to fuse with previous maps to
309+ * avoid long paths of BiTypeMapped sets.
310+ * - If the map is some other map that maps the current set of elements
311+ * to itself, return the current var. We implicitly assume that the map
312+ * will also map any elements added in the future to themselves. This assumption
313+ * can be tested to hold by setting the ccConfig.checkSkippedMaps setting to true.
314+ * - If the map is some other map that does not map all elements to themselves,
315+ * freeze the current set (i.e. make it porvisionally solved) and return
316+ * the mapped elements as a constant set.
324317 */
325318 def map (tm : TypeMap )(using Context ): CaptureSet =
326319 tm match
@@ -342,16 +335,12 @@ sealed abstract class CaptureSet extends Showable:
342335 this
343336 case _ =>
344337 val mapped = mapRefs(elems, tm, tm.variance)
345- if isConst then
346- if mapped.isConst && mapped.elems == elems && ! mapped.keepAlways then this
347- else mapped
348- else if true || ccConfig.newScheme then
349- if mapped.elems == elems then this
350- else
351- asVar.markSolved(provisional = true )
352- mapped
338+ if mapped.elems == elems then
339+ if ccConfig.checkSkippedMaps && ! isConst then asVar.skippedMaps += tm
340+ this
353341 else
354- Mapped (asVar, tm, tm.variance, mapped)
342+ if ! isConst then asVar.markSolved(provisional = true )
343+ mapped
355344
356345 /** A mapping resulting from substituting parameters of a BindingType to a list of types */
357346 def substParams (tl : BindingType , to : List [Type ])(using Context ) =
@@ -571,6 +560,16 @@ object CaptureSet:
571560 def resetDeps ()(using state : VarState ): Unit =
572561 deps = state.deps(this )
573562
563+ /** Check that all maps recorded in skippedMaps map `elem` to itself
564+ * or something subsumed by it.
565+ */
566+ private def checkSkippedMaps (elem : CaptureRef )(using Context ): Unit =
567+ for tm <- skippedMaps do
568+ val elem1 = tm(elem)
569+ for elem1 <- tm(elem).captureSet.elems do
570+ assert(elem.subsumes(elem1),
571+ i " Skipped map ${tm.getClass} maps newly added $elem to $elem1 in $this" )
572+
574573 final def addThisElem (elem : CaptureRef )(using Context , VarState ): CompareResult =
575574 if isConst || ! recordElemsState() then // Fail if variable is solved or given VarState is frozen
576575 addIfHiddenOrFail(elem)
@@ -588,6 +587,7 @@ object CaptureSet:
588587 // assert(id != 5 || elems.size != 3, this)
589588 val res = (CompareResult .OK /: deps): (r, dep) =>
590589 r.andAlso(dep.tryInclude(normElem, this ))
590+ if ccConfig.checkSkippedMaps && res.isOK then checkSkippedMaps(elem)
591591 res.orElse:
592592 elems -= elem
593593 res.addToTrace(this )
@@ -615,7 +615,7 @@ object CaptureSet:
615615 elem.symbol.ccLevel <= level
616616 case elem : ThisType if level.isDefined =>
617617 elem.cls.ccLevel.nextInner <= level
618- case elem : ParamRef if ! this .isInstanceOf [Mapped | BiMapped ] =>
618+ case elem : ParamRef if ! this .isInstanceOf [BiMapped ] =>
619619 isPartOf(elem.binder.resType)
620620 || {
621621 capt.println(i " LEVEL ERROR $elem for $this" )
@@ -700,6 +700,8 @@ object CaptureSet:
700700 solved = if provisional then ccState.iterCount else Int .MaxValue
701701 deps.foreach(_.propagateSolved(provisional))
702702
703+ var skippedMaps : Set [TypeMap ] = Set .empty
704+
703705 def withDescription (description : String ): this .type =
704706 this .description = this .description.join(" and " , description)
705707 this
@@ -748,8 +750,8 @@ object CaptureSet:
748750 extends Var (owner, initialElems):
749751
750752 // For debugging: A trace where a set was created. Note that logically it would make more
751- // sense to place this variable in Mapped , but that runs afoul of the initializatuon checker.
752- val stack = if debugSets && this .isInstanceOf [Mapped ] then (new Throwable ).getStackTrace().nn.take(20 ) else null
753+ // sense to place this variable in BiMapped , but that runs afoul of the initializatuon checker.
754+ // val stack = if debugSets && this.isInstanceOf[BiMapped ] then (new Throwable).getStackTrace().nn.take(20) else null
753755
754756 /** The variable from which this variable is derived */
755757 def source : Var
@@ -760,7 +762,7 @@ object CaptureSet:
760762 if source.isConst && ! isConst then markSolved(provisional)
761763
762764 // ----------- Longest path recording -------------------------
763-
765+
764766 /** Summarize for set displaying in a path */
765767 def summarize : String = getClass.toString
766768
@@ -779,103 +781,6 @@ object CaptureSet:
779781
780782 end DerivedVar
781783
782- /** A variable that changes when `source` changes, where all additional new elements are mapped
783- * using ∪ { tm(x) | x <- source.elems }.
784- * @param source the original set that is mapped
785- * @param tm the type map, which is assumed to be idempotent on capture refs
786- * (except if ccUnsoundMaps is enabled)
787- * @param variance the assumed variance with which types with capturesets of size >= 2 are approximated
788- * (i.e. co: full capture set, contra: empty set, nonvariant is not allowed.)
789- * @param initial The initial mappings of source's elements at the point the Mapped set is created.
790- */
791- class Mapped private [CaptureSet ]
792- (val source : Var , tm : TypeMap , variance : Int , initial : CaptureSet )(using @ constructorOnly ctx : Context )
793- extends DerivedVar (source.owner, initial.elems):
794- addAsDependentTo(initial) // initial mappings could change by propagation
795-
796- private def mapIsIdempotent = tm.isInstanceOf [IdempotentCaptRefMap ]
797-
798- assert(ccConfig.allowUnsoundMaps || mapIsIdempotent, tm.getClass)
799-
800- private def whereCreated (using Context ): String =
801- if stack == null then " "
802- else i """
803- |Stack trace of variable creation:"
804- | ${stack.mkString(" \n " )}"""
805-
806- override def tryInclude (elem : CaptureRef , origin : CaptureSet )(using Context , VarState ): CompareResult =
807- def propagate : CompareResult =
808- if (origin ne source) && (origin ne initial) && mapIsIdempotent then
809- // `tm` is idempotent, propagate back elems from image set.
810- // This is sound, since we know that for `r in newElems: tm(r) = r`, hence
811- // `r` is _one_ possible solution in `source` that would make an `r` appear in this set.
812- // It's not necessarily the only possible solution, so the scheme is incomplete.
813- source.tryInclude(elem, this )
814- else if ccConfig.allowUnsoundMaps && ! mapIsIdempotent
815- && variance <= 0 && ! origin.isConst && (origin ne initial) && (origin ne source)
816- then
817- // The map is neither a BiTypeMap nor an idempotent type map.
818- // In that case there's no much we can do.
819- // The scheme then does not propagate added elements back to source and rejects adding
820- // elements from variable sources in contra- and non-variant positions. In essence,
821- // we approximate types resulting from such maps by returning a possible super type
822- // from the actual type. But this is neither sound nor complete.
823- report.warning(em " trying to add $elem from unrecognized source $origin of mapped set $this$whereCreated" )
824- CompareResult .Fail (this :: Nil )
825- else
826- CompareResult .OK
827- def propagateIf (cond : Boolean ): CompareResult =
828- if cond then propagate else CompareResult .OK
829-
830- val mapped = extrapolateCaptureRef(elem, tm, variance)
831-
832- def isFixpoint =
833- mapped.isConst && mapped.elems.size == 1 && mapped.elems.contains(elem)
834-
835- def failNoFixpoint =
836- val reason =
837- if variance <= 0 then i " the set's variance is $variance"
838- else i " $elem gets mapped to $mapped, which is not a supercapture. "
839- report.warning(em """ trying to add $elem from unrecognized source $origin of mapped set $this$whereCreated
840- |The reference cannot be added since $reason""" )
841- CompareResult .Fail (this :: Nil )
842-
843- if origin eq source then // elements have to be mapped
844- val added = mapped.elems.filter(! accountsFor(_))
845- addNewElems(added)
846- .andAlso:
847- if mapped.isConst then CompareResult .OK
848- else if mapped.asVar.recordDepsState() then { addAsDependentTo(mapped); CompareResult .OK }
849- else CompareResult .Fail (this :: Nil )
850- .andAlso:
851- propagateIf(! added.isEmpty)
852- else if accountsFor(elem) then
853- CompareResult .OK
854- else if variance > 0 then
855- // we can soundly add nothing to source and `x` to this set
856- addNewElem(elem)
857- else if isFixpoint then
858- // We can soundly add `x` to both this set and source since `f(x) = x`
859- addNewElem(elem).andAlso(propagate)
860- else
861- // we are out of options; fail (which is always sound).
862- failNoFixpoint
863- end tryInclude
864-
865- override def computeApprox (origin : CaptureSet )(using Context ): CaptureSet =
866- if source eq origin then
867- // it's a mapping of origin, so not a superset of `origin`,
868- // therefore don't contribute to the intersection.
869- universal
870- else
871- source.upperApprox(this ).map(tm)
872-
873- override def propagateSolved (provisional : Boolean )(using Context ) =
874- if initial.isConst then super .propagateSolved(provisional)
875-
876- override def toString = s " Mapped $id( $source, elems = $elems) "
877- end Mapped
878-
879784 /** A mapping where the type map is required to be a bijection.
880785 * Parameters as in Mapped.
881786 */
@@ -1127,12 +1032,6 @@ object CaptureSet:
11271032 case _ =>
11281033 false
11291034
1130- /** A TypeMap with the property that every capture reference in the image
1131- * of the map is mapped to itself. I.e. for all capture references r1, r2,
1132- * if M(r1) == r2 then M(r2) == r2.
1133- */
1134- trait IdempotentCaptRefMap extends TypeMap
1135-
11361035 /** A TypeMap that is the identity on capture references */
11371036 trait IdentityCaptRefMap extends TypeMap
11381037
0 commit comments