@@ -44,12 +44,24 @@ import Capabilities.*
4444 */
4545sealed abstract class CaptureSet extends Showable :
4646 import CaptureSet .*
47+ import Mutability .*
4748
4849 /** The elements of this capture set. For capture variables,
4950 * the elements known so far.
5051 */
5152 def elems : Refs
5253
54+ protected var myMut : Mutability = Ignored
55+
56+ /** The access kind of this CaptureSet. */
57+ def mutability (using Context ): Mutability = myMut
58+
59+ def mutability_= (x : Mutability ): Unit =
60+ myMut = x
61+
62+ /** Mark this capture set as belonging to a Mutable type. */
63+ def setMutable ()(using Context ): Unit
64+
5365 /** Is this capture set constant (i.e. not an unsolved capture variable)?
5466 * Solved capture variables count as constant.
5567 */
@@ -127,6 +139,13 @@ sealed abstract class CaptureSet extends Showable:
127139 final def isExclusive (using Context ): Boolean =
128140 elems.exists(_.isExclusive)
129141
142+ /** Similar to isExlusive, but also includes capture set variables
143+ * with unknown status.
144+ */
145+ final def maybeExclusive (using Context ): Boolean = reporting.trace(i " mabe exclusive $this" ):
146+ if isConst then elems.exists(_.maybeExclusive)
147+ else mutability != ReadOnly
148+
130149 final def keepAlways : Boolean = this .isInstanceOf [EmptyWithProvenance ]
131150
132151 def failWith (fail : TypeComparer .ErrorNote )(using Context ): false =
@@ -164,6 +183,9 @@ sealed abstract class CaptureSet extends Showable:
164183 // through this method.
165184 newElems.forall(tryInclude(_, origin))
166185
186+ protected def mutableToReader (origin : CaptureSet )(using Context ): Boolean =
187+ if mutability == Mutable then toReader() else true
188+
167189 /** Add an element to this capture set, assuming it is not already accounted for,
168190 * and omitting any mapping or filtering.
169191 *
@@ -188,6 +210,8 @@ sealed abstract class CaptureSet extends Showable:
188210 */
189211 protected def addThisElem (elem : Capability )(using Context , VarState ): Boolean
190212
213+ protected def toReader ()(using Context ): Boolean
214+
191215 protected def addIfHiddenOrFail (elem : Capability )(using ctx : Context , vs : VarState ): Boolean =
192216 elems.exists(_.maxSubsumes(elem, canAddHidden = true ))
193217 || failWith(IncludeFailure (this , elem))
@@ -258,7 +282,12 @@ sealed abstract class CaptureSet extends Showable:
258282
259283 /** The subcapturing test, using a given VarState */
260284 final def subCaptures (that : CaptureSet )(using ctx : Context , vs : VarState = VarState ()): Boolean =
261- if that.tryInclude(elems, this ) then
285+ val this1 = this .adaptMutability(that)
286+ if this1 == null then false
287+ else if this1 ne this then
288+ capt.println(i " WIDEN ro $this with ${this .mutability} <:< $that with ${that.mutability} to $this1" )
289+ this1.subCaptures(that, vs)
290+ else if that.tryInclude(elems, this ) then
262291 addDependent(that)
263292 else
264293 varState.rollBack()
@@ -271,6 +300,14 @@ sealed abstract class CaptureSet extends Showable:
271300 this .subCaptures(that, VarState .Separate )
272301 && that.subCaptures(this , VarState .Separate )
273302
303+ def adaptMutability (that : CaptureSet )(using Context ): CaptureSet | Null =
304+ val m1 = this .mutability
305+ val m2 = that.mutability
306+ if m1 == Mutable && m2 == Reader then this .readOnly
307+ else if m1 == Reader && m2 == Mutable then
308+ if that.toReader() then this else null
309+ else this
310+
274311 /** The smallest capture set (via <:<) that is a superset of both
275312 * `this` and `that`
276313 */
@@ -372,7 +409,10 @@ sealed abstract class CaptureSet extends Showable:
372409
373410 def maybe (using Context ): CaptureSet = map(MaybeMap ())
374411
375- def readOnly (using Context ): CaptureSet = map(ReadOnlyMap ())
412+ def readOnly (using Context ): CaptureSet =
413+ val res = map(ReadOnlyMap ())
414+ if mutability != Ignored then res.mutability = Reader
415+ res
376416
377417 /** A bad root `elem` is inadmissible as a member of this set. What is a bad roots depends
378418 * on the value of `rootLimit`.
@@ -445,6 +485,25 @@ object CaptureSet:
445485 type Vars = SimpleIdentitySet [Var ]
446486 type Deps = SimpleIdentitySet [CaptureSet ]
447487
488+ enum Mutability :
489+ case Mutable , Reader , Ignored
490+
491+ def | (that : Mutability ): Mutability =
492+ if this == that then this
493+ else if this == Ignored || that == Ignored then Ignored
494+ else if this == Reader || that == Reader then Reader
495+ else Mutable
496+
497+ def & (that : Mutability ): Mutability =
498+ if this == that then this
499+ else if this == Ignored then that
500+ else if that == Ignored then this
501+ else if this == Reader then that
502+ else this
503+
504+ end Mutability
505+ import Mutability .*
506+
448507 /** If set to `true`, capture stack traces that tell us where sets are created */
449508 private final val debugSets = false
450509
@@ -496,6 +555,8 @@ object CaptureSet:
496555 false
497556 }
498557
558+ def toReader ()(using Context ) = false
559+
499560 def addDependent (cs : CaptureSet )(using Context , VarState ) = true
500561
501562 def upperApprox (origin : CaptureSet )(using Context ): CaptureSet = this
@@ -506,6 +567,17 @@ object CaptureSet:
506567
507568 def owner = NoSymbol
508569
570+ private var isComplete = true
571+
572+ def setMutable ()(using Context ): Unit =
573+ isComplete = false // delay computation of Mutability status
574+
575+ override def mutability (using Context ): Mutability =
576+ if ! isComplete then
577+ myMut = if maybeExclusive then Mutable else Reader
578+ isComplete = true
579+ myMut
580+
509581 override def toString = elems.toString
510582 end Const
511583
@@ -524,6 +596,7 @@ object CaptureSet:
524596 object Fluid extends Const (emptyRefs):
525597 override def isAlwaysEmpty (using Context ) = false
526598 override def addThisElem (elem : Capability )(using Context , VarState ) = true
599+ override def toReader ()(using Context ) = true
527600 override def accountsFor (x : Capability )(using Context )(using VarState ): Boolean = true
528601 override def mightAccountFor (x : Capability )(using Context ): Boolean = true
529602 override def toString = " <fluid>"
@@ -563,6 +636,9 @@ object CaptureSet:
563636 */
564637 var deps : Deps = SimpleIdentitySet .empty
565638
639+ def setMutable ()(using Context ): Unit =
640+ mutability = Mutable
641+
566642 def isConst (using Context ) = solved >= ccState.iterationId
567643 def isAlwaysEmpty (using Context ) = isConst && elems.isEmpty
568644 def isProvisionallySolved (using Context ): Boolean = solved > 0 && solved != Int .MaxValue
@@ -640,6 +716,13 @@ object CaptureSet:
640716 case note : IncludeFailure => note.addToTrace(this )
641717 res
642718
719+ final def toReader ()(using Context ) =
720+ if isConst then false // TODO add error note when failing?
721+ else
722+ mutability = Reader
723+ TypeComparer .logUndoAction(() => mutability = Mutable )
724+ deps.forall(_.mutableToReader(this ))
725+
643726 private def isPartOf (binder : Type )(using Context ): Boolean =
644727 val find = new TypeAccumulator [Boolean ]:
645728 def apply (b : Boolean , t : Type ) =
@@ -744,6 +827,8 @@ object CaptureSet:
744827 def markSolved (provisional : Boolean )(using Context ): Unit =
745828 solved = if provisional then ccState.iterationId else Int .MaxValue
746829 deps.foreach(_.propagateSolved(provisional))
830+ if mutability == Mutable && ! maybeExclusive then mutability = Reader
831+
747832
748833 var skippedMaps : Set [TypeMap ] = Set .empty
749834
@@ -803,8 +888,14 @@ object CaptureSet:
803888 /** The variable from which this variable is derived */
804889 def source : Var
805890
891+ mutability = source.mutability
892+
806893 addAsDependentTo(source)
807894
895+ override def mutableToReader (origin : CaptureSet )(using Context ): Boolean =
896+ super .mutableToReader(origin)
897+ && ((origin eq source) || source.mutableToReader(this ))
898+
808899 override def propagateSolved (provisional : Boolean )(using Context ) =
809900 if source.isConst && ! isConst then markSolved(provisional)
810901
@@ -904,6 +995,7 @@ object CaptureSet:
904995 extends Var (initialElems = cs1.elems ++ cs2.elems):
905996 addAsDependentTo(cs1)
906997 addAsDependentTo(cs2)
998+ mutability = cs1.mutability | cs2.mutability
907999
9081000 override def tryInclude (elem : Capability , origin : CaptureSet )(using Context , VarState ): Boolean =
9091001 if accountsFor(elem) then true
@@ -918,6 +1010,15 @@ object CaptureSet:
9181010 else res
9191011 else res
9201012
1013+ override def mutableToReader (origin : CaptureSet )(using Context ): Boolean =
1014+ super .mutableToReader(origin)
1015+ && {
1016+ if (origin eq cs1) || (origin eq cs2) then true
1017+ else if cs1.isConst && cs1.mutability == Mutable then cs2.mutableToReader(this )
1018+ else if cs2.isConst && cs2.mutability == Mutable then cs1.mutableToReader(this )
1019+ else true
1020+ }
1021+
9211022 override def propagateSolved (provisional : Boolean )(using Context ) =
9221023 if cs1.isConst && cs2.isConst && ! isConst then markSolved(provisional)
9231024 end Union
@@ -928,6 +1029,7 @@ object CaptureSet:
9281029 addAsDependentTo(cs2)
9291030 deps += cs1
9301031 deps += cs2
1032+ mutability = cs1.mutability & cs2.mutability
9311033
9321034 override def tryInclude (elem : Capability , origin : CaptureSet )(using Context , VarState ): Boolean =
9331035 val inIntersection =
@@ -940,6 +1042,11 @@ object CaptureSet:
9401042 && ((origin eq cs1) || cs1.tryInclude(elem, this ))
9411043 && ((origin eq cs2) || cs2.tryInclude(elem, this ))
9421044
1045+ override def mutableToReader (origin : CaptureSet )(using Context ): Boolean =
1046+ super .mutableToReader(origin)
1047+ && ((origin eq cs1) || cs1.mutableToReader(this ))
1048+ && ((origin eq cs2) || cs2.mutableToReader(this ))
1049+
9431050 override def computeApprox (origin : CaptureSet )(using Context ): CaptureSet =
9441051 if (origin eq cs1) || (origin eq cs2) then
9451052 // it's a combination of origin with some other set, so not a superset of `origin`,
0 commit comments