@@ -302,25 +302,18 @@ sealed abstract class CaptureSet extends Showable:
302
302
case _ => Filtered (asVar, p)
303
303
304
304
/** 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.
324
317
*/
325
318
def map (tm : TypeMap )(using Context ): CaptureSet =
326
319
tm match
@@ -342,16 +335,12 @@ sealed abstract class CaptureSet extends Showable:
342
335
this
343
336
case _ =>
344
337
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
353
341
else
354
- Mapped (asVar, tm, tm.variance, mapped)
342
+ if ! isConst then asVar.markSolved(provisional = true )
343
+ mapped
355
344
356
345
/** A mapping resulting from substituting parameters of a BindingType to a list of types */
357
346
def substParams (tl : BindingType , to : List [Type ])(using Context ) =
@@ -571,6 +560,16 @@ object CaptureSet:
571
560
def resetDeps ()(using state : VarState ): Unit =
572
561
deps = state.deps(this )
573
562
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
+
574
573
final def addThisElem (elem : CaptureRef )(using Context , VarState ): CompareResult =
575
574
if isConst || ! recordElemsState() then // Fail if variable is solved or given VarState is frozen
576
575
addIfHiddenOrFail(elem)
@@ -588,6 +587,7 @@ object CaptureSet:
588
587
// assert(id != 5 || elems.size != 3, this)
589
588
val res = (CompareResult .OK /: deps): (r, dep) =>
590
589
r.andAlso(dep.tryInclude(normElem, this ))
590
+ if ccConfig.checkSkippedMaps && res.isOK then checkSkippedMaps(elem)
591
591
res.orElse:
592
592
elems -= elem
593
593
res.addToTrace(this )
@@ -615,7 +615,7 @@ object CaptureSet:
615
615
elem.symbol.ccLevel <= level
616
616
case elem : ThisType if level.isDefined =>
617
617
elem.cls.ccLevel.nextInner <= level
618
- case elem : ParamRef if ! this .isInstanceOf [Mapped | BiMapped ] =>
618
+ case elem : ParamRef if ! this .isInstanceOf [BiMapped ] =>
619
619
isPartOf(elem.binder.resType)
620
620
|| {
621
621
capt.println(i " LEVEL ERROR $elem for $this" )
@@ -700,6 +700,8 @@ object CaptureSet:
700
700
solved = if provisional then ccState.iterCount else Int .MaxValue
701
701
deps.foreach(_.propagateSolved(provisional))
702
702
703
+ var skippedMaps : Set [TypeMap ] = Set .empty
704
+
703
705
def withDescription (description : String ): this .type =
704
706
this .description = this .description.join(" and " , description)
705
707
this
@@ -748,8 +750,8 @@ object CaptureSet:
748
750
extends Var (owner, initialElems):
749
751
750
752
// 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
753
755
754
756
/** The variable from which this variable is derived */
755
757
def source : Var
@@ -760,7 +762,7 @@ object CaptureSet:
760
762
if source.isConst && ! isConst then markSolved(provisional)
761
763
762
764
// ----------- Longest path recording -------------------------
763
-
765
+
764
766
/** Summarize for set displaying in a path */
765
767
def summarize : String = getClass.toString
766
768
@@ -779,103 +781,6 @@ object CaptureSet:
779
781
780
782
end DerivedVar
781
783
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
-
879
784
/** A mapping where the type map is required to be a bijection.
880
785
* Parameters as in Mapped.
881
786
*/
@@ -1127,12 +1032,6 @@ object CaptureSet:
1127
1032
case _ =>
1128
1033
false
1129
1034
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
-
1136
1035
/** A TypeMap that is the identity on capture references */
1137
1036
trait IdentityCaptRefMap extends TypeMap
1138
1037
0 commit comments