Skip to content

Add restricted capabilities x.only[C] #23485

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2268,10 +2268,6 @@ object desugar {
Annotated(
AppliedTypeTree(ref(defn.SeqType), t),
New(ref(defn.RepeatedAnnot.typeRef), Nil :: Nil))
else if op.name == nme.CC_REACH then
Annotated(t, New(ref(defn.ReachCapabilityAnnot.typeRef), Nil :: Nil))
else if op.name == nme.CC_READONLY then
Annotated(t, New(ref(defn.ReadOnlyCapabilityAnnot.typeRef), Nil :: Nil))
else
assert(ctx.mode.isExpr || ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive), ctx.mode)
Select(t, op.name)
Expand Down
9 changes: 9 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,15 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
annot.putAttachment(RetainsAnnot, ())
Annotated(parent, annot)

def makeReachAnnot()(using Context): Tree =
New(ref(defn.ReachCapabilityAnnot.typeRef), Nil :: Nil)

def makeReadOnlyAnnot()(using Context): Tree =
New(ref(defn.ReadOnlyCapabilityAnnot.typeRef), Nil :: Nil)

def makeOnlyAnnot(qid: Tree)(using Context) =
New(AppliedTypeTree(ref(defn.OnlyCapabilityAnnot.typeRef), qid :: Nil), Nil :: Nil)

def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(using Context): DefDef =
DefDef(nme.CONSTRUCTOR, joinParams(tparams, vparamss), TypeTree(), rhs)

Expand Down
197 changes: 175 additions & 22 deletions compiler/src/dotty/tools/dotc/cc/Capability.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ import annotation.internal.sharable
* | +-- SetCapability -----+-- TypeRef
* | +-- TypeParamRef
* |
* +-- DerivedCapability -+-- ReadOnly
* +-- Reach
* +-- DerivedCapability -+-- Reach
* +-- Only
* +-- ReadOnly
* +-- Maybe
*
* All CoreCapabilities are Types, or, more specifically instances of TypeProxy.
Expand Down Expand Up @@ -96,9 +97,18 @@ object Capabilities:
* but they can wrap reach capabilities. We have
* (x?).readOnly = (x.rd)?
*/
case class ReadOnly(underlying: ObjectCapability | RootCapability | Reach)
extends DerivedCapability:
assert(!underlying.isInstanceOf[Maybe])
case class ReadOnly(underlying: ObjectCapability | RootCapability | Reach | Restricted)
extends DerivedCapability

/** The restricted capability `x.only[C]`. We have {x.only[C]} <: {x}.
*
* Restricted capabilities cannot wrap maybe capabilities or read-only capabilities
* but they can wrap reach capabilities. We have
* (x?).restrict[T] = (x.restrict[T])?
* (x.rd).restrict[T] = (x.restrict[T]).rd
*/
case class Restricted(underlying: ObjectCapability | RootCapability | Reach, cls: ClassSymbol)
extends DerivedCapability

/** If `x` is a capability, its reach capability `x*`. `x*` stands for all
* capabilities reachable through `x`.
Expand All @@ -109,11 +119,11 @@ object Capabilities:
*
* Reach capabilities cannot wrap read-only capabilities or maybe capabilities.
* We have
* (x.rd).reach = x*.rd
* (x.rd)? = (x*)?
* (x?).reach = (x.reach)?
* (x.rd).reach = (x.reach).rd
* (x.only[T]).reach = (x*).only[T]
*/
case class Reach(underlying: ObjectCapability) extends DerivedCapability:
assert(!underlying.isInstanceOf[Maybe | ReadOnly])
case class Reach(underlying: ObjectCapability) extends DerivedCapability

/** The global root capability referenced as `caps.cap`
* `cap` does not subsume other capabilities, except in arguments of
Expand All @@ -124,6 +134,7 @@ object Capabilities:
def descr(using Context) = "the universal root capability"
override val maybe = Maybe(this)
override val readOnly = ReadOnly(this)
override def restrict(cls: ClassSymbol)(using Context) = Restricted(this, cls)
override def reach = unsupported("cap.reach")
override def singletonCaptureSet(using Context) = CaptureSet.universal
override def captureSetOfInfo(using Context) = singletonCaptureSet
Expand Down Expand Up @@ -242,19 +253,29 @@ object Capabilities:
/** A trait for references in CaptureSets. These can be NamedTypes, ThisTypes or ParamRefs,
* as well as three kinds of AnnotatedTypes representing readOnly, reach, and maybe capabilities.
* If there are several annotations they come with an order:
* `*` first, `.rd` next, `?` last.
* `*` first, `.only` next, `.rd` next, `?` last.
*/
trait Capability extends Showable:

private var myCaptureSet: CaptureSet | Null = uninitialized
private var myCaptureSetValid: Validity = invalid
private var captureSetValid: Validity = invalid
private var mySingletonCaptureSet: CaptureSet.Const | Null = null
private var myDerived: List[DerivedCapability] = Nil
private var myClassifiers: Classifiers = UnknownClassifier
private var classifiersValid: Validity = invalid

protected def cached[C <: DerivedCapability](newRef: C): C =
def recur(refs: List[DerivedCapability]): C = refs match
case ref :: refs1 =>
if ref.getClass == newRef.getClass then ref.asInstanceOf[C] else recur(refs1)
val exists = ref match
case Restricted(_, cls) =>
newRef match
case Restricted(_, newCls) => cls == newCls
case _ => false
case _ =>
ref.getClass == newRef.getClass
if exists then ref.asInstanceOf[C]
else recur(refs1)
case Nil =>
myDerived = newRef :: myDerived
newRef
Expand All @@ -267,11 +288,21 @@ object Capabilities:
def readOnly: ReadOnly | Maybe = this match
case Maybe(ref1) => Maybe(ref1.readOnly)
case self: ReadOnly => self
case self: (ObjectCapability | RootCapability | Reach) => cached(ReadOnly(self))

def reach: Reach | ReadOnly | Maybe = this match
case self: (ObjectCapability | RootCapability | Reach | Restricted) => cached(ReadOnly(self))

def restrict(cls: ClassSymbol)(using Context): Restricted | ReadOnly | Maybe = this match
case Maybe(ref1) => Maybe(ref1.restrict(cls))
case ReadOnly(ref1) => ReadOnly(ref1.restrict(cls).asInstanceOf[Restricted])
case self @ Restricted(ref1, prevCls) =>
val combinedCls = leastClassifier(prevCls, cls)
if combinedCls == prevCls then self
else cached(Restricted(ref1, combinedCls))
case self: (ObjectCapability | RootCapability | Reach) => cached(Restricted(self, cls))

def reach: Reach | Restricted | ReadOnly | Maybe = this match
case Maybe(ref1) => Maybe(ref1.reach)
case ReadOnly(ref1) => ReadOnly(ref1.reach.asInstanceOf[Reach])
case ReadOnly(ref1) => ReadOnly(ref1.reach.asInstanceOf[Reach | Restricted])
case Restricted(ref1, cls) => Restricted(ref1.reach.asInstanceOf[Reach], cls)
case self: Reach => self
case self: ObjectCapability => cached(Reach(self))

Expand All @@ -285,6 +316,12 @@ object Capabilities:
case tp: SetCapability => tp.captureSetOfInfo.isReadOnly
case _ => this ne stripReadOnly

final def restriction(using Context): Symbol = this match
case Restricted(_, cls) => cls
case ReadOnly(ref1) => ref1.restriction
case Maybe(ref1) => ref1.restriction
case _ => NoSymbol

/** Is this a reach reference of the form `x*` or a readOnly or maybe variant
* of a reach reference?
*/
Expand All @@ -299,9 +336,20 @@ object Capabilities:
case Maybe(ref1) => ref1.stripReadOnly.maybe
case _ => this

/** Drop restrictions with clss `cls` or a superclass of `cls` */
final def stripRestricted(cls: ClassSymbol)(using Context): Capability = this match
case Restricted(ref1, cls1) if cls.isSubClass(cls1) => ref1
case ReadOnly(ref1) => ref1.stripRestricted(cls).readOnly
case Maybe(ref1) => ref1.stripRestricted(cls).maybe
case _ => this

final def stripRestricted(using Context): Capability =
stripRestricted(defn.NothingClass)

final def stripReach(using Context): Capability = this match
case Reach(ref1) => ref1
case ReadOnly(ref1) => ref1.stripReach.readOnly
case Restricted(ref1, cls) => ref1.stripReach.restrict(cls)
case Maybe(ref1) => ref1.stripReach.maybe
case _ => this

Expand Down Expand Up @@ -425,7 +473,7 @@ object Capabilities:

def derivesFromCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_Capability)
def derivesFromMutable(using Context): Boolean = derivesFromCapTrait(defn.Caps_Mutable)
def derivesFromSharedCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_SharedCapability)
def derivesFromSharable(using Context): Boolean = derivesFromCapTrait(defn.Caps_Sharable)

/** The capture set consisting of exactly this reference */
def singletonCaptureSet(using Context): CaptureSet.Const =
Expand All @@ -435,7 +483,7 @@ object Capabilities:

/** The capture set of the type underlying this reference */
def captureSetOfInfo(using Context): CaptureSet =
if myCaptureSetValid == currentId then myCaptureSet.nn
if captureSetValid == currentId then myCaptureSet.nn
else if myCaptureSet.asInstanceOf[AnyRef] eq CaptureSet.Pending then CaptureSet.empty
else
myCaptureSet = CaptureSet.Pending
Expand All @@ -447,11 +495,76 @@ object Capabilities:
myCaptureSet = null
else
myCaptureSet = computed
myCaptureSetValid = currentId
captureSetValid = currentId
computed

/** The transitive classifiers of this capability. */
def transClassifiers(using Context): Classifiers =
def toClassifiers(cls: ClassSymbol): Classifiers =
if cls == defn.AnyClass then Unclassified
else ClassifiedAs(cls :: Nil)
if classifiersValid != currentId then
myClassifiers = this match
case self: FreshCap =>
toClassifiers(self.hiddenSet.classifier)
case self: RootCapability =>
Unclassified
case Restricted(_, cls) =>
assert(cls != defn.AnyClass)
if cls == defn.NothingClass then ClassifiedAs(Nil)
else ClassifiedAs(cls :: Nil)
case ReadOnly(ref1) =>
ref1.transClassifiers
case Maybe(ref1) =>
ref1.transClassifiers
case Reach(_) =>
captureSetOfInfo.transClassifiers
case self: CoreCapability =>
joinClassifiers(toClassifiers(self.classifier), captureSetOfInfo.transClassifiers)
if myClassifiers != UnknownClassifier then
classifiersValid == currentId
myClassifiers
end transClassifiers

def tryClassifyAs(cls: ClassSymbol)(using Context): Boolean =
cls == defn.AnyClass
|| this.match
case self: FreshCap =>
self.hiddenSet.tryClassifyAs(cls)
case self: RootCapability =>
true
case Restricted(_, cls1) =>
assert(cls != defn.AnyClass)
cls1.isSubClass(cls)
case ReadOnly(ref1) =>
ref1.tryClassifyAs(cls)
case Maybe(ref1) =>
ref1.tryClassifyAs(cls)
case Reach(_) =>
captureSetOfInfo.tryClassifyAs(cls)
case self: CoreCapability =>
self.classifier.isSubClass(cls)
&& captureSetOfInfo.tryClassifyAs(cls)

def isKnownClassifiedAs(cls: ClassSymbol)(using Context): Boolean =
transClassifiers match
case ClassifiedAs(cs) => cs.forall(_.isSubClass(cls))
case _ => false

def isKnownEmpty(using Context): Boolean = this match
case Restricted(ref1, cls) =>
val isEmpty = ref1.transClassifiers match
case ClassifiedAs(cs) =>
cs.forall(c => leastClassifier(c, cls) == defn.NothingClass)
case _ => false
isEmpty || ref1.isKnownEmpty
case ReadOnly(ref1) => ref1.isKnownEmpty
case Maybe(ref1) => ref1.isKnownEmpty
case _ => false

def invalidateCaches() =
myCaptureSetValid = invalid
captureSetValid = invalid
classifiersValid = invalid

/** x subsumes x
* x =:= y ==> x subsumes y
Expand Down Expand Up @@ -505,6 +618,7 @@ object Capabilities:
|| viaInfo(y.info)(subsumingRefs(this, _))
case Maybe(y1) => this.stripMaybe.subsumes(y1)
case ReadOnly(y1) => this.stripReadOnly.subsumes(y1)
case Restricted(y1, cls) => this.stripRestricted(cls).subsumes(y1)
case y: TypeRef if y.derivesFrom(defn.Caps_CapSet) =>
// The upper and lower bounds don't have to be in the form of `CapSet^{...}`.
// They can be other capture set variables, which are bounded by `CapSet`,
Expand All @@ -519,6 +633,7 @@ object Capabilities:
case _ => false
|| this.match
case Reach(x1) => x1.subsumes(y.stripReach)
case Restricted(x1, cls) => y.isKnownClassifiedAs(cls) && x1.subsumes(y)
case x: TermRef => viaInfo(x.info)(subsumingRefs(_, y))
case x: TypeRef if assumedContainsOf(x).contains(y) => true
case x: TypeRef if x.derivesFrom(defn.Caps_CapSet) =>
Expand Down Expand Up @@ -559,12 +674,15 @@ object Capabilities:

vs.ifNotSeen(this)(x.hiddenSet.elems.exists(_.subsumes(y)))
|| levelOK
&& ( y.tryClassifyAs(x.hiddenSet.classifier)
|| { capt.println(i"$y cannot be classified as $x"); false }
)
&& canAddHidden
&& vs.addHidden(x.hiddenSet, y)
case x: ResultCap =>
val result = y match
case y: ResultCap => vs.unify(x, y)
case _ => y.derivesFromSharedCapability
case _ => y.derivesFromSharable
if !result then
TypeComparer.addErrorNote(CaptureSet.ExistentialSubsumesFailure(x, y))
result
Expand All @@ -574,11 +692,12 @@ object Capabilities:
case _: ResultCap => false
case _: FreshCap if CCState.collapseFresh => true
case _ =>
y.derivesFromSharedCapability
y.derivesFromSharable
|| canAddHidden && vs != VarState.HardSeparate && CCState.capIsRoot
case _ =>
y match
case ReadOnly(y1) => this.stripReadOnly.maxSubsumes(y1, canAddHidden)
case Restricted(y1, cls) => this.stripRestricted(cls).maxSubsumes(y1, canAddHidden)
case _ => false

/** `x covers y` if we should retain `y` when computing the overlap of
Expand Down Expand Up @@ -623,13 +742,47 @@ object Capabilities:
val c1 = c.underlying.toType
c match
case _: ReadOnly => ReadOnlyCapability(c1)
case Restricted(_, cls) => OnlyCapability(c1, cls)
case _: Reach => ReachCapability(c1)
case _: Maybe => MaybeCapability(c1)
case _ => c1

def toText(printer: Printer): Text = printer.toTextCapability(this)
end Capability

/** Result type of `transClassifiers`. Interprete as follows:
* UnknownClassifier: No list could be computed since some capture sets
* are still unsolved variables
* Unclassified : No set exists since some parts of tcs are not classified
* ClassifiedAs(clss: All parts of tcss are classified with classes in clss
*/
enum Classifiers:
case UnknownClassifier
case Unclassified
case ClassifiedAs(clss: List[ClassSymbol])

export Classifiers.{UnknownClassifier, Unclassified, ClassifiedAs}

/** The least classifier between `cls1` and `cls2`, which are either
* AnyClass, NothingClass, or a class directly extending caps.Classifier.
* @return if oen of cls1, cls2 is a subclass of the other, the subclass
* otherwise NothingClass (which is a subclass of all classes)
*/
def leastClassifier(cls1: ClassSymbol, cls2: ClassSymbol)(using Context): ClassSymbol =
if cls1.isSubClass(cls2) then cls1
else if cls2.isSubClass(cls1) then cls2
else defn.NothingClass

def joinClassifiers(cs1: Classifiers, cs2: Classifiers)(using Context): Classifiers =
// Drop classes that subclass classes of the other set
def filterSub(cs1: List[ClassSymbol], cs2: List[ClassSymbol]) =
cs1.filter(cls1 => !cs2.exists(cls2 => cls1.isSubClass(cls2)))
(cs1, cs2) match
case (Unclassified, _) | (_, Unclassified) => Unclassified
case (UnknownClassifier, _) | (_, UnknownClassifier) => UnknownClassifier
case (ClassifiedAs(cs1), ClassifiedAs(cs2)) =>
ClassifiedAs(filterSub(cs1, cs2) ++ filterSub(cs2, cs1))

/** The place of - and cause for - creating a fresh capability. Used for
* error diagnostics
*/
Expand Down
Loading
Loading