Skip to content

Commit ab8bf5f

Browse files
authored
Decompose Mutable (#24495)
First commit: New doc page for decomposed mutables Todo: - discuss the design - implementation
2 parents f78354a + 895f8fb commit ab8bf5f

File tree

75 files changed

+700
-486
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+700
-486
lines changed

compiler/src/dotty/tools/dotc/cc/Capability.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -441,11 +441,13 @@ object Capabilities:
441441
/** An exclusive capability is a capability that derives
442442
* indirectly from a maximal capability without going through
443443
* a read-only capability or a capability classified as SharedCapability first.
444+
* @param required if true, exclusivity can be obtained by setting the mutability
445+
* status of some capture set variable from Ignored to Writer.
444446
*/
445-
final def isExclusive(using Context): Boolean =
447+
final def isExclusive(required: Boolean = false)(using Context): Boolean =
446448
!isReadOnly
447449
&& !classifier.derivesFrom(defn.Caps_SharedCapability)
448-
&& (isTerminalCapability || captureSetOfInfo.isExclusive)
450+
&& (isTerminalCapability || captureSetOfInfo.isExclusive(required))
449451

450452
/** Similar to isExlusive, but also includes capabilties with capture
451453
* set variables in their info whose status is still open.
@@ -564,8 +566,9 @@ object Capabilities:
564566
case _ => false
565567

566568
def derivesFromCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_Capability)
567-
def derivesFromMutable(using Context): Boolean = derivesFromCapTrait(defn.Caps_Mutable)
569+
def derivesFromStateful(using Context): Boolean = derivesFromCapTrait(defn.Caps_Stateful)
568570
def derivesFromShared(using Context): Boolean = derivesFromCapTrait(defn.Caps_SharedCapability)
571+
def derivesFromUnscoped(using Context): Boolean = derivesFromCapTrait(defn.Caps_Unscoped)
569572

570573
/** The capture set consisting of exactly this reference */
571574
def singletonCaptureSet(using Context): CaptureSet.Const =
@@ -924,7 +927,7 @@ object Capabilities:
924927
* Unclassified : No set exists since some parts of tcs are not classified
925928
* ClassifiedAs(clss: All parts of tcss are classified with classes in clss
926929
*/
927-
enum Classifiers:
930+
enum Classifiers derives CanEqual:
928931
case UnknownClassifier
929932
case Unclassified
930933
case ClassifiedAs(clss: List[ClassSymbol])
@@ -965,7 +968,7 @@ object Capabilities:
965968
/** The place of - and cause for - creating a fresh capability. Used for
966969
* error diagnostics
967970
*/
968-
enum Origin:
971+
enum Origin derives CanEqual:
969972
case InDecl(sym: Symbol)
970973
case TypeArg(tp: Type)
971974
case UnsafeAssumePure

compiler/src/dotty/tools/dotc/cc/CaptureOps.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import tpd.*
1313
import Annotations.Annotation
1414
import CaptureSet.VarState
1515
import Capabilities.*
16-
import Mutability.isMutableType
16+
import Mutability.isStatefulType
1717
import StdNames.nme
1818
import config.Feature
1919
import NameKinds.TryOwnerName
@@ -395,9 +395,9 @@ extension (tp: Type)
395395
false
396396

397397
def derivesFromCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_Capability)
398-
def derivesFromMutable(using Context): Boolean = derivesFromCapTrait(defn.Caps_Mutable)
398+
def derivesFromStateful(using Context): Boolean = derivesFromCapTrait(defn.Caps_Stateful)
399399
def derivesFromShared(using Context): Boolean = derivesFromCapTrait(defn.Caps_SharedCapability)
400-
def derivesFromExclusive(using Context): Boolean = derivesFromCapTrait(defn.Caps_ExclusiveCapability)
400+
def derivesFromUnscoped(using Context): Boolean = derivesFromCapTrait(defn.Caps_Unscoped)
401401

402402
/** Drop @retains annotations everywhere */
403403
def dropAllRetains(using Context): Type = // TODO we should drop retains from inferred types before unpickling
@@ -531,7 +531,11 @@ extension (cls: ClassSymbol)
531531
else defn.AnyClass
532532

533533
def isSeparate(using Context): Boolean =
534-
cls.typeRef.isMutableType
534+
cls.derivesFrom(defn.Caps_Separate)
535+
|| cls.typeRef.isStatefulType
536+
|| cls.paramGetters.exists: getter =>
537+
!getter.is(Private) // Setup makes sure that getters with capture sets are not private
538+
&& getter.hasAnnotation(defn.ConsumeAnnot)
535539

536540
extension (sym: Symbol)
537541

compiler/src/dotty/tools/dotc/cc/CaptureSet.scala

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ sealed abstract class CaptureSet extends Showable:
5959
def mutability_=(x: Mutability): Unit =
6060
myMut = x
6161

62-
/** Mark this capture set as belonging to a Mutable type. Called when a new
62+
/** Mark this capture set as belonging to a Stateful type. Called when a new
6363
* CapturingType is formed. This is different from the setter `mutability_=`
64-
* in that it be defined with different behaviors:
64+
* in that it can be defined with different behaviors:
6565
*
66-
* - set mutability to Mutable (for normal Vars)
66+
* - set mutability to Writer (for normal Vars)
6767
* - take mutability from the set's sources (for DerivedVars)
6868
* - compute mutability on demand based on mutability of elements (for Consts)
6969
*/
70-
def associateWithMutable()(using Context): CaptureSet
70+
def associateWithStateful()(using Context): CaptureSet
7171

7272
/** Is this capture set constant (i.e. not an unsolved capture variable)?
7373
* Solved capture variables count as constant.
@@ -148,15 +148,27 @@ sealed abstract class CaptureSet extends Showable:
148148

149149
final def isAlwaysReadOnly(using Context): Boolean = isConst && isReadOnly
150150

151-
final def isExclusive(using Context): Boolean =
152-
elems.exists(_.isExclusive)
151+
/** Is capture set exclusive? If `required` is true, a variable capture set
152+
* is forced to Writer mutability which makes it exclusive. Otherwise a set
153+
* is exclusive if one of its elements is exclusive.
154+
* Possible issue: If required is true, and the set is a constant, with
155+
* multiple elements that each have a variable capture set, then we make
156+
* the set exclusive by updating the first such variable capture set with
157+
* Ignore mutability to have Write mutability. That makes the effect
158+
* order dependent.
159+
*/
160+
def isExclusive(required: Boolean = false)(using Context): Boolean =
161+
if required && !isConst && mutability == Ignored then
162+
mutability = Writer
163+
mutability == Writer
164+
|| elems.exists(_.isExclusive(required))
153165

154166
/** Similar to isExclusive, but also includes capture set variables
155167
* with unknown status.
156168
*/
157169
final def maybeExclusive(using Context): Boolean = reporting.trace(i"mabe exclusive $this"):
158170
if isConst then elems.exists(_.maybeExclusive)
159-
else mutability != ReadOnly
171+
else mutability != Reader
160172

161173
final def keepAlways: Boolean = this.isInstanceOf[EmptyWithProvenance]
162174

@@ -191,7 +203,7 @@ sealed abstract class CaptureSet extends Showable:
191203
newElems.forall(tryInclude(_, origin))
192204

193205
protected def mutableToReader(origin: CaptureSet)(using Context): Boolean =
194-
if mutability == Mutable then toReader() else true
206+
if mutability == Writer then toReader() else true
195207

196208
/** Add an element to this capture set, assuming it is not already accounted for,
197209
* and omitting any mapping or filtering.
@@ -315,8 +327,8 @@ sealed abstract class CaptureSet extends Showable:
315327
def adaptMutability(that: CaptureSet)(using Context): CaptureSet | Null =
316328
val m1 = this.mutability
317329
val m2 = that.mutability
318-
if m1 == Mutable && m2 == Reader then this.readOnly
319-
else if m1 == Reader && m2 == Mutable then
330+
if m1 == Writer && m2 == Reader then this.readOnly
331+
else if m1 == Reader && m2 == Writer then
320332
if that.toReader() then this else null
321333
else this
322334

@@ -541,14 +553,14 @@ object CaptureSet:
541553
type Vars = SimpleIdentitySet[Var]
542554
type Deps = SimpleIdentitySet[CaptureSet]
543555

544-
enum Mutability:
545-
case Mutable, Reader, Ignored
556+
enum Mutability derives CanEqual:
557+
case Writer, Reader, Ignored
546558

547559
def | (that: Mutability): Mutability =
548560
if this == that then this
549561
else if this == Ignored || that == Ignored then Ignored
550562
else if this == Reader || that == Reader then Reader
551-
else Mutable
563+
else Writer
552564

553565
def & (that: Mutability): Mutability =
554566
if this == that then this
@@ -569,9 +581,9 @@ object CaptureSet:
569581
@sharable // sharable since the set is empty, so mutability won't be set
570582
val empty: CaptureSet.Const = Const(emptyRefs)
571583

572-
/** The empty capture set `{}` of a Mutable type, with Reader status */
573-
@sharable // sharable since the set is empty, so mutability won't be set
574-
val emptyOfMutable: CaptureSet.Const =
584+
/** The empty capture set `{}` of a Stateful type, with Reader status */
585+
@sharable // sharable since the set is empty, so mutability won't be re-set
586+
val emptyOfStateful: CaptureSet.Const =
575587
val cs = Const(emptyRefs)
576588
cs.mutability = Mutability.Reader
577589
cs
@@ -630,15 +642,15 @@ object CaptureSet:
630642

631643
private var isComplete = true
632644

633-
def associateWithMutable()(using Context): CaptureSet =
634-
if elems.isEmpty then emptyOfMutable
645+
def associateWithStateful()(using Context): CaptureSet =
646+
if elems.isEmpty then emptyOfStateful
635647
else
636648
isComplete = false // delay computation of Mutability status
637649
this
638650

639651
override def mutability(using Context): Mutability =
640652
if !isComplete then
641-
myMut = if maybeExclusive then Mutable else Reader
653+
myMut = if maybeExclusive then Writer else Reader
642654
isComplete = true
643655
myMut
644656

@@ -652,7 +664,7 @@ object CaptureSet:
652664
else ""
653665

654666
private def capImpliedByCapability(parent: Type)(using Context): Capability =
655-
if parent.derivesFromMutable then GlobalCap.readOnly else GlobalCap
667+
if parent.derivesFromStateful then GlobalCap.readOnly else GlobalCap
656668

657669
/* The same as {cap} but generated implicitly for references of Capability subtypes.
658670
*/
@@ -665,13 +677,14 @@ object CaptureSet:
665677
* nulls, this provides more lenient checking against compilation units that
666678
* were not yet compiled with capture checking on.
667679
*/
668-
@sharable // sharable since the set is empty, so setMutable is a no-op
680+
@sharable
669681
object Fluid extends Const(emptyRefs):
670682
override def isAlwaysEmpty(using Context) = false
671683
override def addThisElem(elem: Capability)(using Context, VarState) = true
672684
override def toReader()(using Context) = true
673685
override def accountsFor(x: Capability)(using Context)(using VarState): Boolean = true
674686
override def mightAccountFor(x: Capability)(using Context): Boolean = true
687+
override def mutability_=(x: Mutability): Unit = ()
675688
override def toString = "<fluid>"
676689
end Fluid
677690

@@ -727,8 +740,8 @@ object CaptureSet:
727740
*/
728741
var deps: Deps = SimpleIdentitySet.empty
729742

730-
def associateWithMutable()(using Context): CaptureSet =
731-
mutability = Mutable
743+
def associateWithStateful()(using Context): CaptureSet =
744+
mutability = Writer
732745
this
733746

734747
def isConst(using Context) = solved >= ccState.iterationId
@@ -846,7 +859,7 @@ object CaptureSet:
846859
if isConst then failWith(MutAdaptFailure(this))
847860
else
848861
mutability = Reader
849-
TypeComparer.logUndoAction(() => mutability = Mutable)
862+
TypeComparer.logUndoAction(() => mutability = Writer)
850863
deps.forall(_.mutableToReader(this))
851864

852865
private def isPartOf(binder: Type)(using Context): Boolean =
@@ -933,7 +946,7 @@ object CaptureSet:
933946
def markSolved(provisional: Boolean)(using Context): Unit =
934947
solved = if provisional then ccState.iterationId else Int.MaxValue
935948
deps.foreach(_.propagateSolved(provisional))
936-
if mutability == Mutable && !maybeExclusive then mutability = Reader
949+
if mutability == Writer && !maybeExclusive then mutability = Reader
937950

938951

939952
var skippedMaps: Set[TypeMap] = Set.empty
@@ -1046,7 +1059,7 @@ object CaptureSet:
10461059
addAsDependentTo(source)
10471060

10481061
/** Mutability is same as in source, except for readOnly */
1049-
override def associateWithMutable()(using Context): CaptureSet = this
1062+
override def associateWithStateful()(using Context): CaptureSet = this
10501063

10511064
override def mutableToReader(origin: CaptureSet)(using Context): Boolean =
10521065
super.mutableToReader(origin)
@@ -1175,8 +1188,8 @@ object CaptureSet:
11751188
super.mutableToReader(origin)
11761189
&& {
11771190
if (origin eq cs1) || (origin eq cs2) then true
1178-
else if cs1.isConst && cs1.mutability == Mutable then cs2.mutableToReader(this)
1179-
else if cs2.isConst && cs2.mutability == Mutable then cs1.mutableToReader(this)
1191+
else if cs1.isConst && cs1.mutability == Writer then cs2.mutableToReader(this)
1192+
else if cs2.isConst && cs2.mutability == Writer then cs1.mutableToReader(this)
11801193
else true
11811194
}
11821195

@@ -1403,7 +1416,7 @@ object CaptureSet:
14031416
else i"$elem cannot be included in $cs"
14041417
end IncludeFailure
14051418

1406-
/** Failure indicating that a read-only capture set of a mutable type cannot be
1419+
/** Failure indicating that a read-only capture set of a stateful type cannot be
14071420
* widened to an exclusive set.
14081421
* @param cs the exclusive set in question
14091422
* @param lo the lower type of the orginal type comparison, or NoType if not known
@@ -1412,7 +1425,7 @@ object CaptureSet:
14121425
case class MutAdaptFailure(cs: CaptureSet, lo: Type = NoType, hi: Type = NoType) extends Note:
14131426

14141427
def render(using Context): String =
1415-
def ofType(tp: Type) = if tp.exists then i"of the mutable type $tp" else "of a mutable type"
1428+
def ofType(tp: Type) = if tp.exists then i"of the stateful type $tp" else "of a stateful type"
14161429
i"""
14171430
|
14181431
|Note that $cs is an exclusive capture set ${ofType(hi)},

compiler/src/dotty/tools/dotc/cc/CapturingType.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ object CapturingType:
4141
apply(parent1, refs ++ refs1, boxed)
4242
case _ =>
4343
val refs1 =
44-
if parent.derivesFromMutable then refs.associateWithMutable() else refs
44+
if parent.derivesFromStateful then refs.associateWithStateful() else refs
4545
refs1.adoptClassifier(parent.inheritedClassifier)
4646
AnnotatedType(parent, CaptureAnnotation(refs1, boxed)(defn.RetainsAnnot))
4747

0 commit comments

Comments
 (0)