Skip to content

Commit fa85416

Browse files
authored
Add path support for capture checking (scala#21445)
2 parents 0c5a377 + ad58323 commit fa85416

27 files changed

+478
-137
lines changed

Diff for: compiler/src/dotty/tools/dotc/ast/untpd.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -524,8 +524,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
524524
def makeRetaining(parent: Tree, refs: List[Tree], annotName: TypeName)(using Context): Annotated =
525525
Annotated(parent, New(scalaAnnotationDot(annotName), List(refs)))
526526

527-
def makeCapsOf(id: Ident)(using Context): Tree =
528-
TypeApply(Select(scalaDot(nme.caps), nme.capsOf), id :: Nil)
527+
def makeCapsOf(tp: RefTree)(using Context): Tree =
528+
TypeApply(Select(scalaDot(nme.caps), nme.capsOf), tp :: Nil)
529529

530530
def makeCapsBound()(using Context): Tree =
531531
makeRetaining(

Diff for: compiler/src/dotty/tools/dotc/cc/CaptureOps.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ extension (tp: Type)
194194
true
195195
case tp: TermRef =>
196196
((tp.prefix eq NoPrefix)
197-
|| tp.symbol.is(ParamAccessor) && tp.prefix.isThisTypeOf(tp.symbol.owner)
197+
|| tp.symbol.isField && !tp.symbol.isStatic && tp.prefix.isTrackableRef
198198
|| tp.isRootCapability
199199
) && !tp.symbol.isOneOf(UnstableValueFlags)
200200
case tp: TypeRef =>

Diff for: compiler/src/dotty/tools/dotc/cc/CaptureRef.scala

+40-15
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,19 @@ trait CaptureRef extends TypeProxy, ValueType:
6161
case tp: TermParamRef => tp.underlying.derivesFrom(defn.Caps_Exists)
6262
case _ => false
6363

64-
/** Normalize reference so that it can be compared with `eq` for equality */
65-
final def normalizedRef(using Context): CaptureRef = this match
66-
case tp @ AnnotatedType(parent: CaptureRef, annot) if tp.isTrackableRef =>
67-
tp.derivedAnnotatedType(parent.normalizedRef, annot)
68-
case tp: TermRef if tp.isTrackableRef =>
69-
tp.symbol.termRef
70-
case _ => this
64+
// With the support of pathes, we don't need to normalize the `TermRef`s anymore.
65+
// /** Normalize reference so that it can be compared with `eq` for equality */
66+
// final def normalizedRef(using Context): CaptureRef = this match
67+
// case tp @ AnnotatedType(parent: CaptureRef, annot) if tp.isTrackableRef =>
68+
// tp.derivedAnnotatedType(parent.normalizedRef, annot)
69+
// case tp: TermRef if tp.isTrackableRef =>
70+
// tp.symbol.termRef
71+
// case _ => this
7172

7273
/** The capture set consisting of exactly this reference */
7374
final def singletonCaptureSet(using Context): CaptureSet.Const =
7475
if mySingletonCaptureSet == null then
75-
mySingletonCaptureSet = CaptureSet(this.normalizedRef)
76+
mySingletonCaptureSet = CaptureSet(this)
7677
mySingletonCaptureSet.uncheckedNN
7778

7879
/** The capture set of the type underlying this reference */
@@ -97,27 +98,51 @@ trait CaptureRef extends TypeProxy, ValueType:
9798
* x subsumes y ==> x* subsumes y, x subsumes y?
9899
* x subsumes y ==> x* subsumes y*, x? subsumes y?
99100
* x: x1.type /\ x1 subsumes y ==> x subsumes y
101+
* TODO: Document path cases
100102
*/
101103
final def subsumes(y: CaptureRef)(using Context): Boolean =
104+
105+
def subsumingRefs(x: Type, y: Type): Boolean = x match
106+
case x: CaptureRef => y match
107+
case y: CaptureRef => x.subsumes(y)
108+
case _ => false
109+
case _ => false
110+
111+
def viaInfo(info: Type)(test: Type => Boolean): Boolean = info.match
112+
case info: SingletonCaptureRef => test(info)
113+
case info: AndType => viaInfo(info.tp1)(test) || viaInfo(info.tp2)(test)
114+
case info: OrType => viaInfo(info.tp1)(test) && viaInfo(info.tp2)(test)
115+
case _ => false
116+
102117
(this eq y)
103118
|| this.isRootCapability
104119
|| y.match
105120
case y: TermRef =>
106-
(y.prefix eq this)
107-
|| y.info.match
108-
case y1: SingletonCaptureRef => this.subsumes(y1)
121+
y.prefix.match
122+
case ypre: CaptureRef =>
123+
this.subsumes(ypre)
124+
|| this.match
125+
case x @ TermRef(xpre: CaptureRef, _) if x.symbol == y.symbol =>
126+
// To show `{x.f} <:< {y.f}`, it is important to prove `x` and `y`
127+
// are equvalent, which means `x =:= y` in terms of subtyping,
128+
// not just `{x} =:= {y}` in terms of subcapturing.
129+
// It is possible to construct two singleton types `x` and `y`,
130+
// which subsume each other, but are not equal references.
131+
// See `tests/neg-custom-args/captures/path-prefix.scala` for example.
132+
withMode(Mode.IgnoreCaptures) {TypeComparer.isSameRef(xpre, ypre)}
133+
case _ =>
134+
false
109135
case _ => false
136+
|| viaInfo(y.info)(subsumingRefs(this, _))
110137
case MaybeCapability(y1) => this.stripMaybe.subsumes(y1)
111138
case _ => false
112139
|| this.match
113140
case ReachCapability(x1) => x1.subsumes(y.stripReach)
114-
case x: TermRef =>
115-
x.info match
116-
case x1: SingletonCaptureRef => x1.subsumes(y)
117-
case _ => false
141+
case x: TermRef => viaInfo(x.info)(subsumingRefs(_, y))
118142
case x: TermParamRef => subsumesExistentially(x, y)
119143
case x: TypeRef => assumedContainsOf(x).contains(y)
120144
case _ => false
145+
end subsumes
121146

122147
def assumedContainsOf(x: TypeRef)(using Context): SimpleIdentitySet[CaptureRef] =
123148
CaptureSet.assumedContains.getOrElse(x, SimpleIdentitySet.empty)

Diff for: compiler/src/dotty/tools/dotc/cc/CaptureSet.scala

+6-2
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ object CaptureSet:
374374

375375
def apply(elems: CaptureRef*)(using Context): CaptureSet.Const =
376376
if elems.isEmpty then empty
377-
else Const(SimpleIdentitySet(elems.map(_.normalizedRef.ensuring(_.isTrackableRef))*))
377+
else Const(SimpleIdentitySet(elems.map(_.ensuring(_.isTrackableRef))*))
378378

379379
def apply(elems: Refs)(using Context): CaptureSet.Const =
380380
if elems.isEmpty then empty else Const(elems)
@@ -508,7 +508,11 @@ object CaptureSet:
508508
!noUniversal
509509
else elem match
510510
case elem: TermRef if level.isDefined =>
511-
elem.symbol.ccLevel <= level
511+
elem.prefix match
512+
case prefix: CaptureRef =>
513+
levelOK(prefix)
514+
case _ =>
515+
elem.symbol.ccLevel <= level
512516
case elem: ThisType if level.isDefined =>
513517
elem.cls.ccLevel.nextInner <= level
514518
case ReachCapability(elem1) =>

0 commit comments

Comments
 (0)