Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 9a46502

Browse files
authored
Implement FreshCap Handling for Classes and Objects (#24136)
This is a complete implementation of lampepfl/papers#155. It consists of the following major parts: ## Let fresh caps in field types contribute to class captures A fresh in the capture set of a class field now causes a fresh to be added to the capture set of every instance of that class. This caused 18 failures in stdlib of which 2 were significant (the rest was deprecated stuff). So far, we make this compile with the `@caps.unsafe.untrackedCaptures` annotation. #24137 explains what would be needed to fix this in a safer way. ## Add prefixes to fresh caps and relate class and field fresh caps Fresh created in classes now carry a prefix referring to the `this` of the class. The prefix gets mapped by TypeMaps, including `asSeenFrom`. We establish a "covers" relation between a fresh for an object and a fresh for one of its fields. We use the same relation for subsumption. Example: ```scala class A: val f: Ref^ = Ref() val a: A^{cap1} = A() ``` Here the type of `a.f` is `A^{a.cap2}`. Furthermore `cap1`, the cap captured by the type of `a`, both covers and subsumes `a.cap2`. ## Make sure that private fields with inferred types don't capture a fresh cap Fields contribute fresh caps to the class. The problem is what to do with capsets in inferred types of fields. These pose problems of separate compilation. We already demand explicit declarations of capsets of non-private fields. We need to also demand an explicit declaration when a private field has an inferred capset that is not otherwise accounted for in the capset of the enclosing class. That condition can be checked post-cc, when all capsets are known. I.e. it is checked at the same time as when we check non-private fields.
2 parents f747007 + f9cc516 commit 9a46502

Some content is hidden

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

49 files changed

+898
-209
lines changed

‎compiler/src/dotty/tools/dotc/ast/tpd.scala‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
517517
def singleton(tp: Type, needLoad: Boolean = true)(using Context): Tree = tp.dealias match {
518518
case tp: TermRef => ref(tp, needLoad)
519519
case tp: ThisType => This(tp.cls)
520-
case tp: SkolemType => singleton(tp.narrow, needLoad)
520+
case tp: SkolemType => singleton(tp.narrow(), needLoad)
521521
case SuperType(qual, _) => singleton(qual, needLoad)
522522
case ConstantType(value) => Literal(value)
523523
}

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

Lines changed: 99 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import printing.Texts.Text
2323
import reporting.{Message, trace}
2424
import NameOps.isImpureFunction
2525
import annotation.internal.sharable
26+
import collection.immutable
2627

2728
/** Capabilities are members of capture sets. They partially overlap with types
2829
* as shown in the trait hierarchy below.
@@ -147,10 +148,30 @@ object Capabilities:
147148
* @param origin an indication where and why the FreshCap was created, used
148149
* for diagnostics
149150
*/
150-
case class FreshCap (owner: Symbol, origin: Origin)(using @constructorOnly ctx: Context) extends RootCapability:
151-
val hiddenSet = CaptureSet.HiddenSet(owner, this: @unchecked)
151+
case class FreshCap(val prefix: Type)
152+
(val owner: Symbol, val origin: Origin, origHidden: CaptureSet.HiddenSet | Null)
153+
(using @constructorOnly ctx: Context)
154+
extends RootCapability:
155+
val hiddenSet =
156+
if origHidden == null then CaptureSet.HiddenSet(owner, this: @unchecked)
157+
else origHidden
152158
// fails initialization check without the @unchecked
153159

160+
def derivedFreshCap(newPrefix: Type)(using Context): FreshCap =
161+
if newPrefix eq prefix then this
162+
else if newPrefix eq hiddenSet.owningCap.prefix then
163+
hiddenSet.owningCap
164+
else
165+
hiddenSet.derivedCaps
166+
.getOrElseUpdate(newPrefix, FreshCap(newPrefix)(owner, origin, hiddenSet))
167+
168+
/** A map from context owners to skolem TermRefs that were created by ensurePath
169+
* TypeMap's mapCapability.
170+
*/
171+
var skolems: immutable.Map[Symbol, TermRef] = immutable.HashMap.empty
172+
173+
//assert(rootId != 10, i"fresh $prefix, ${ctx.owner}")
174+
154175
/** Is this fresh cap (definitely) classified? If that's the case, the
155176
* classifier cannot be changed anymore.
156177
* We need to distinguish `FreshCap`s that can still be classified from
@@ -167,12 +188,6 @@ object Capabilities:
167188
case _ => false
168189

169190
/** Is this fresh cap at the right level to be able to subsume `ref`?
170-
* Only outer freshes can be subsumed.
171-
* TODO Can we merge this with levelOK? Right now we use two different schemes:
172-
* - For level checking capsets with levelOK: Check that the visibility of the element
173-
* os not properly contained in the captset owner.
174-
* - For level checking elements subsumed by FreshCaps: Check that the widened scope
175-
* (using levelOwner) of the elements contains the owner of the FreshCap.
176191
*/
177192
def acceptsLevelOf(ref: Capability)(using Context): Boolean =
178193
if ccConfig.useFreshLevels && !CCState.collapseFresh then
@@ -203,8 +218,12 @@ object Capabilities:
203218
i"a fresh root capability$classifierStr$originStr"
204219

205220
object FreshCap:
221+
def apply(owner: Symbol, prefix: Type, origin: Origin)(using Context): FreshCap =
222+
new FreshCap(prefix)(owner, origin, null)
223+
def apply(owner: Symbol, origin: Origin)(using Context): FreshCap =
224+
apply(owner, owner.skipWeakOwner.thisType, origin)
206225
def apply(origin: Origin)(using Context): FreshCap =
207-
FreshCap(ctx.owner, origin)
226+
apply(ctx.owner, origin)
208227

209228
/** A root capability associated with a function type. These are conceptually
210229
* existentially quantified over the function's result type.
@@ -441,6 +460,7 @@ object Capabilities:
441460
* the form this.C but their pathroot is still this.C, not this.
442461
*/
443462
final def pathRoot(using Context): Capability = this match
463+
case FreshCap(pre: Capability) => pre.pathRoot
444464
case _: RootCapability => this
445465
case self: DerivedCapability => self.underlying.pathRoot
446466
case self: CoreCapability => self.dealias match
@@ -485,7 +505,13 @@ object Capabilities:
485505
case TermRef(prefix: Capability, _) => prefix.ccOwner
486506
case self: NamedType => self.symbol
487507
case self: DerivedCapability => self.underlying.ccOwner
488-
case self: FreshCap => self.hiddenSet.owner
508+
case self: FreshCap =>
509+
val setOwner = self.hiddenSet.owner
510+
self.prefix match
511+
case prefix: ThisType if setOwner.isTerm && setOwner.owner == prefix.cls =>
512+
setOwner
513+
case prefix: Capability => prefix.ccOwner
514+
case _ => setOwner
489515
case _ /* : GlobalCap | ResultCap | ParamRef */ => NoSymbol
490516

491517
final def visibility(using Context): Symbol = this match
@@ -665,6 +691,8 @@ object Capabilities:
665691

666692
try (this eq y)
667693
|| maxSubsumes(y, canAddHidden = !vs.isOpen)
694+
// if vs is open, we should add new elements to the set containing `this`
695+
// instead of adding them to the hidden set of of `this`.
668696
|| y.match
669697
case y: TermRef =>
670698
y.prefix.match
@@ -732,12 +760,26 @@ object Capabilities:
732760
(this eq y)
733761
|| this.match
734762
case x: FreshCap =>
763+
def classifierOK =
764+
if y.tryClassifyAs(x.hiddenSet.classifier) then true
765+
else
766+
capt.println(i"$y cannot be classified as $x")
767+
false
768+
769+
def prefixAllowsAddHidden: Boolean =
770+
CCState.collapseFresh || x.prefix.match
771+
case NoPrefix => true
772+
case pre: ThisType => x.ccOwner.isContainedIn(pre.cls)
773+
case pre =>
774+
capt.println(i"fresh not open $x, ${x.rootId}, $pre, ${x.ccOwner.skipWeakOwner.thisType}")
775+
false
776+
735777
vs.ifNotSeen(this)(x.hiddenSet.elems.exists(_.subsumes(y)))
778+
|| x.coversFresh(y)
736779
|| x.acceptsLevelOf(y)
737-
&& ( y.tryClassifyAs(x.hiddenSet.classifier)
738-
|| { capt.println(i"$y cannot be classified as $x"); false }
739-
)
780+
&& classifierOK
740781
&& canAddHidden
782+
&& prefixAllowsAddHidden
741783
&& vs.addHidden(x.hiddenSet, y)
742784
case x: ResultCap =>
743785
val result = y match
@@ -800,15 +842,39 @@ object Capabilities:
800842
case _ =>
801843
false
802844
|| x.match
803-
case x: FreshCap if !seen.contains(x) =>
804-
seen.add(x)
805-
x.hiddenSet.exists(recur(_, y))
845+
case x: FreshCap =>
846+
if x.coversFresh(y) then true
847+
else if !seen.contains(x) then
848+
seen.add(x)
849+
x.hiddenSet.exists(recur(_, y))
850+
else false
806851
case Restricted(x1, _) => recur(x1, y)
807852
case _ => false
808853

809854
recur(this, y)
810855
end covers
811856

857+
/** `x eq y` or `x` is a fresh cap, `y` is a fresh cap with prefix
858+
* `p`, and there is a prefix of `p` that contains `x` in its
859+
* capture set.
860+
*/
861+
final def coversFresh(y: Capability)(using Context): Boolean =
862+
(this eq y) || this.match
863+
case x: FreshCap => y match
864+
case y: FreshCap =>
865+
x.origin match
866+
case Origin.InDecl(sym) =>
867+
def occursInPrefix(pre: Type): Boolean = pre match
868+
case pre @ TermRef(pre1, _) =>
869+
pre.symbol == sym
870+
&& pre.info.captureSet.elems.contains(x)
871+
|| occursInPrefix(pre1)
872+
case _ => false
873+
occursInPrefix(y.prefix)
874+
case _ => false
875+
case _ => false
876+
case _ => false
877+
812878
def assumedContainsOf(x: TypeRef)(using Context): SimpleIdentitySet[Capability] =
813879
CaptureSet.assumedContains.getOrElse(x, SimpleIdentitySet.empty)
814880

@@ -857,18 +923,26 @@ object Capabilities:
857923
else if cls2.isSubClass(cls1) then cls2
858924
else defn.NothingClass
859925

860-
def joinClassifiers(cs1: Classifiers, cs2: Classifiers)(using Context): Classifiers =
926+
/** The smallest list D of class symbols in cs1 and cs2 such that
927+
* every class symbol in cs1 and cs2 is a subclass of a class symbol in D
928+
*/
929+
def dominators(cs1: List[ClassSymbol], cs2: List[ClassSymbol])(using Context): List[ClassSymbol] =
861930
// Drop classes that subclass classes of the other set
862931
// @param proper If true, only drop proper subclasses of a class of the other set
863932
def filterSub(cs1: List[ClassSymbol], cs2: List[ClassSymbol], proper: Boolean) =
864933
cs1.filter: cls1 =>
865934
!cs2.exists: cls2 =>
866935
cls1.isSubClass(cls2) && (!proper || cls1 != cls2)
936+
filterSub(cs1, cs2, proper = true) ++ filterSub(cs2, cs1, proper = false)
937+
938+
def joinClassifiers(cs1: Classifiers, cs2: Classifiers)(using Context): Classifiers =
867939
(cs1, cs2) match
868-
case (Unclassified, _) | (_, Unclassified) => Unclassified
869-
case (UnknownClassifier, _) | (_, UnknownClassifier) => UnknownClassifier
940+
case (Unclassified, _) | (_, Unclassified) =>
941+
Unclassified
942+
case (UnknownClassifier, _) | (_, UnknownClassifier) =>
943+
UnknownClassifier
870944
case (ClassifiedAs(cs1), ClassifiedAs(cs2)) =>
871-
ClassifiedAs(filterSub(cs1, cs2, proper =true) ++ filterSub(cs2, cs1, proper =false))
945+
ClassifiedAs(dominators(cs1, cs2))
872946

873947
/** The place of - and cause for - creating a fresh capability. Used for
874948
* error diagnostics
@@ -881,7 +955,7 @@ object Capabilities:
881955
case ResultInstance(methType: Type, meth: Symbol)
882956
case UnapplyInstance(info: MethodType)
883957
case LocalInstance(restpe: Type)
884-
case NewMutable(tp: Type)
958+
case NewInstance(tp: Type)
885959
case NewCapability(tp: Type)
886960
case LambdaExpected(respt: Type)
887961
case LambdaActual(restp: Type)
@@ -911,10 +985,11 @@ object Capabilities:
911985
i" when instantiating argument of unapply with type $info"
912986
case LocalInstance(restpe) =>
913987
i" when instantiating expected result type $restpe of function literal"
914-
case NewMutable(tp) =>
915-
i" when constructing mutable $tp"
988+
case NewInstance(tp) =>
989+
i" when constructing instance $tp"
916990
case NewCapability(tp) =>
917-
i" when constructing Capability instance $tp"
991+
val kind = if tp.derivesFromMutable then "mutable" else "Capability instance"
992+
i" when constructing $kind$tp"
918993
case LambdaExpected(respt) =>
919994
i" when instantiating expected result type $respt of lambda"
920995
case LambdaActual(restp: Type) =>

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import annotation.internal.sharable
1212
import reporting.trace
1313
import printing.{Showable, Printer}
1414
import printing.Texts.*
15-
import util.{SimpleIdentitySet, Property}
15+
import util.{SimpleIdentitySet, Property, EqHashMap}
1616
import typer.ErrorReporting.Addenda
1717
import scala.collection.{mutable, immutable}
1818
import TypeComparer.ErrorNote
@@ -560,8 +560,10 @@ object CaptureSet:
560560
def universal(using Context): Const =
561561
Const(SimpleIdentitySet(GlobalCap))
562562

563+
def fresh(owner: Symbol, prefix: Type, origin: Origin)(using Context): Const =
564+
FreshCap(owner, prefix, origin).singletonCaptureSet
563565
def fresh(origin: Origin)(using Context): Const =
564-
FreshCap(origin).singletonCaptureSet
566+
fresh(ctx.owner, ctx.owner.thisType, origin)
565567

566568
/** The shared capture set `{cap.rd}` */
567569
def shared(using Context): Const =
@@ -964,8 +966,7 @@ object CaptureSet:
964966
case elem: FreshCap
965967
if !nestedOK
966968
&& !elems.contains(elem)
967-
&& !owner.isAnonymousFunction
968-
&& ccConfig.newScheme =>
969+
&& !owner.isAnonymousFunction =>
969970
def fail = i"attempting to add $elem to $this"
970971
def hideIn(fc: FreshCap): Unit =
971972
assert(elem.tryClassifyAs(fc.hiddenSet.classifier), fail)
@@ -990,7 +991,7 @@ object CaptureSet:
990991
case _ => isSubsumed
991992
if !isSubsumed then
992993
if elem.origin != Origin.InDecl(owner) || elem.hiddenSet.isConst then
993-
val fc = newFreshCap(owner, Origin.InDecl(owner))
994+
val fc = FreshCap(owner, Origin.InDecl(owner))
994995
assert(fc.tryClassifyAs(elem.hiddenSet.classifier), fail)
995996
hideIn(fc)
996997
super.includeElem(fc)
@@ -1225,12 +1226,16 @@ object CaptureSet:
12251226

12261227
override def owner = givenOwner
12271228

1229+
/** The FreshCaps generated by derivedFreshCap, indexed by prefix */
1230+
val derivedCaps = new EqHashMap[Type, FreshCap]()
1231+
12281232
//assert(id != 3)
12291233

12301234
description = i"of elements subsumed by a fresh cap in $initialOwner"
12311235

12321236
/** Add element to hidden set. */
12331237
def add(elem: Capability)(using ctx: Context, vs: VarState): Unit =
1238+
assert(elem ne owningCap)
12341239
includeElem(elem)
12351240

12361241
/** Apply function `f` to `elems` while setting `elems` to empty for the
@@ -1387,7 +1392,7 @@ object CaptureSet:
13871392
def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean =
13881393
if hidden.isConst then false
13891394
else
1390-
hidden.add(elem)(using ctx, this)
1395+
if!CCState.collapseFresh thenhidden.add(elem)(using ctx, this)
13911396
true
13921397

13931398
/** If root1 and root2 belong to the same binder but have different originalBinders

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /