Skip to content

Commit 1b0634a

Browse files
committed
Account for added outer refs in the capture sets of classes
1 parent 5bc20ac commit 1b0634a

File tree

2 files changed

+45
-10
lines changed

2 files changed

+45
-10
lines changed

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

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,7 +1089,7 @@ class CheckCaptures extends Recheck, SymTransformer:
10891089

10901090
if actualBoxed eq actual then
10911091
// Only `addOuterRefs` when there is no box adaptation
1092-
expected1 = addOuterRefs(expected1, actual)
1092+
expected1 = addOuterRefs(expected1, actual, tree.srcPos)
10931093
if isCompatible(actualBoxed, expected1) then
10941094
if debugSuccesses then tree match
10951095
case Ident(_) =>
@@ -1130,8 +1130,12 @@ class CheckCaptures extends Recheck, SymTransformer:
11301130
* that are outside `Cls`. These are all accessed through `Cls.this`,
11311131
* so we can assume they are already accounted for by `Ce` and adding
11321132
* them explicitly to `Ce` changes nothing.
1133+
* - To make up for this, we also add these variables to the capture set of `Cls`,
1134+
* so that all instances of `Cls` will capture these outer references.
1135+
* So in a sense we use `{Cls.this}` as a placeholder for certain outer captures.
1136+
* that we needed to be subsumed by `Cls.this`.
11331137
*/
1134-
private def addOuterRefs(expected: Type, actual: Type)(using Context): Type =
1138+
private def addOuterRefs(expected: Type, actual: Type, pos: SrcPos)(using Context): Type =
11351139

11361140
def isPure(info: Type): Boolean = info match
11371141
case info: PolyType => isPure(info.resType)
@@ -1144,19 +1148,40 @@ class CheckCaptures extends Recheck, SymTransformer:
11441148
else isPure(owner.info) && isPureContext(owner.owner, limit)
11451149

11461150
// Augment expeced capture set `erefs` by all references in actual capture
1147-
// set `arefs` that are outside some `this.type` reference in `erefs`
1151+
// set `arefs` that are outside some `C.this.type` reference in `erefs` for an enclosing
1152+
// class `C`. If an added reference is not a ThisType itself, add it to the capture set
1153+
// (i.e. use set) of the `C`. This makes sure that any outer reference implicitly subsumed
1154+
// by `C.this` becomes a capture reference of every instance of `C`.
11481155
def augment(erefs: CaptureSet, arefs: CaptureSet): CaptureSet =
11491156
(erefs /: erefs.elems): (erefs, eref) =>
11501157
eref match
11511158
case eref: ThisType if isPureContext(ctx.owner, eref.cls) =>
1152-
def isOuterRef(aref: Type): Boolean = aref match
1153-
case aref: TermRef =>
1154-
val owner = aref.symbol.owner
1155-
if owner.isClass then isOuterRef(aref.prefix)
1156-
else eref.cls.isProperlyContainedIn(owner)
1159+
1160+
def pathRoot(aref: Type): Type = aref match
1161+
case aref: NamedType if aref.symbol.owner.isClass => pathRoot(aref.prefix)
1162+
case _ => aref
1163+
1164+
def isOuterRef(aref: Type): Boolean = pathRoot(aref) match
1165+
case aref: NamedType => eref.cls.isProperlyContainedIn(aref.symbol.owner)
11571166
case aref: ThisType => eref.cls.isProperlyContainedIn(aref.cls)
11581167
case _ => false
1159-
erefs ++ arefs.filter(isOuterRef)
1168+
1169+
val outerRefs = arefs.filter(isOuterRef)
1170+
1171+
// Include implicitly added outer references in the capture set of the class of `eref`.
1172+
for outerRef <- outerRefs.elems do
1173+
if !erefs.elems.contains(outerRef)
1174+
&& !pathRoot(outerRef).isInstanceOf[ThisType]
1175+
// we don't need to add outer ThisTypes as these are anyway added as path
1176+
// prefixes at the use site. And this exemption is required since capture sets
1177+
// of non-local classes are always empty, so we can't add an outer this to them.
1178+
then
1179+
def provenance =
1180+
i""" of the enclosing class ${eref.cls}.
1181+
|The reference was included since we tried to establish that $arefs <: $erefs"""
1182+
checkElem(outerRef, capturedVars(eref.cls), pos, provenance)
1183+
1184+
erefs ++ outerRefs
11601185
case _ =>
11611186
erefs
11621187

@@ -1341,7 +1366,7 @@ class CheckCaptures extends Recheck, SymTransformer:
13411366
* @param sym symbol of the field definition that is being checked
13421367
*/
13431368
override def checkSubType(actual: Type, expected: Type)(using Context): Boolean =
1344-
val expected1 = alignDependentFunction(addOuterRefs(expected, actual), actual.stripCapturing)
1369+
val expected1 = alignDependentFunction(addOuterRefs(expected, actual, srcPos), actual.stripCapturing)
13451370
val actual1 =
13461371
val saved = curEnv
13471372
try
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class IO
2+
def test(io: IO^) =
3+
class C:
4+
def foo() = () =>
5+
val x: IO^{this} = io
6+
()
7+
val c = new C
8+
val _: C^{io} = c // ok
9+
val _: C = c // error
10+
()

0 commit comments

Comments
 (0)