Skip to content

Commit b7d8047

Browse files
committed
A first attempt at level checking for classes
Check that instantiated root variable of a method inside a class is nested in the instance variable of the class. For the moment I was not able to construct a counter example where this makes a difference.
1 parent 7c34545 commit b7d8047

File tree

3 files changed

+56
-4
lines changed

3 files changed

+56
-4
lines changed

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ class CheckCaptures extends Recheck, SymTransformer:
319319
includeCallCaptures(tree.symbol, tree.srcPos)
320320
else
321321
markFree(tree.symbol, tree.srcPos)
322-
instantiateLocalRoots(tree.symbol, NoPrefix, pt):
322+
instantiateLocalRoots(tree.symbol, NoPrefix, pt, tree.srcPos):
323323
super.recheckIdent(tree, pt)
324324

325325
/** A specialized implementation of the selection rule.
@@ -349,7 +349,7 @@ class CheckCaptures extends Recheck, SymTransformer:
349349

350350
val selType = recheckSelection(tree, qualType, name, disambiguate)
351351
val selCs = selType.widen.captureSet
352-
instantiateLocalRoots(tree.symbol, qualType, pt):
352+
instantiateLocalRoots(tree.symbol, qualType, pt, tree.srcPos):
353353
if selCs.isAlwaysEmpty || selType.widen.isBoxedCapturing || qualType.isBoxedCapturing then
354354
selType
355355
else
@@ -370,7 +370,7 @@ class CheckCaptures extends Recheck, SymTransformer:
370370
* - `tp` is the type of a function that gets applied, either as a method
371371
* or as a function value that gets applied.
372372
*/
373-
def instantiateLocalRoots(sym: Symbol, pre: Type, pt: Type)(tp: Type)(using Context): Type =
373+
def instantiateLocalRoots(sym: Symbol, pre: Type, pt: Type, pos: SrcPos)(tp: Type)(using Context): Type =
374374
def canInstantiate =
375375
sym.is(Method, butNot = Accessor)
376376
|| sym.isTerm && defn.isFunctionType(sym.info) && pt == AnySelectionProto
@@ -379,6 +379,16 @@ class CheckCaptures extends Recheck, SymTransformer:
379379
var tp1 = tpw
380380
val rootVar = CaptureRoot.Var(ctx.owner, sym)
381381
if sym.isLevelOwner then
382+
val outerOwner = sym.skipConstructor.owner.levelOwner
383+
if outerOwner.isClass then
384+
val outerRoot = outerOwner.localRoot.termRef
385+
outerRoot.asSeenFrom(pre, sym.owner) match
386+
case outerLimit: CaptureRoot if outerLimit ne outerRoot =>
387+
capt.println(i"constraining $rootVar of $sym by $outerLimit")
388+
if !outerLimit.encloses(rootVar) then
389+
// Should this be an assertion failure instead?
390+
report.error(em"outer instance $outerLimit does not enclose local root $rootVar", pos)
391+
case _ =>
382392
tp1 = mapRoots(sym.localRoot.termRef, rootVar)(tp1)
383393
if tp1 ne tpw then
384394
ccSetup.println(i"INST local $sym: $tp, ${sym.localRoot} = $tp1")
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
def f(x: (() => Unit)): (() => Unit) => (() => Unit) =
2+
def g(y: (() => Unit)): (() => Unit) = x
3+
g
4+
5+
def test1(x: (() => Unit)): Unit =
6+
def test2(y: (() => Unit)) =
7+
val a: (() => Unit) => (() => Unit) = f(y)
8+
a(x) // OK, but should be error
9+
test2(() => ())
10+
11+
def test2(x1: (() => Unit), x2: (() => Unit) => Unit) =
12+
class C1(x1: (() => Unit), xx2: (() => Unit) => Unit):
13+
def c2(y1: (() => Unit), y2: (() => Unit) => Unit): C2^{cap} = C2(y1, y2)
14+
class C2(y1: (() => Unit), y2: (() => Unit) => Unit):
15+
val a: (() => Unit) => (() => Unit) = f(y1)
16+
a(x1) //OK, but should be error
17+
C2(() => (), x => ())
18+
19+
def test3(y1: (() => Unit), y2: (() => Unit) => Unit) =
20+
val cc1 = C1(y1, y2)
21+
val cc2 = cc1.c2(x1, x2)
22+
val cc3: cc1.C2^{cap[test2]} = cc2 // error
23+
24+
def test4(x1: () => Unit) =
25+
class C1:
26+
this: C1^ =>
27+
class C2(z: () => Unit):
28+
this: C2^ =>
29+
val foo: () => Unit = ???
30+
31+
def test5(x2: () => Unit) =
32+
val xx1: C1^{cap[test5]} = C1()
33+
val y1 =
34+
val xx2 = xx1.C2(x1)
35+
val xx3: xx1.C2^{cap[test4]} = xx2 // ok, but dubious
36+
// actual capture set is in test4
37+
// but level constraints would determine that the root should be in test5
38+
// only, there is no root in the set to be mapped
39+
xx2
40+
val f1 = y1.foo
41+
val xx4 = xx1.C2(x2)
42+
val xx5: xx1.C2^{cap[test4]} = xx4 // error
43+

tests/pos-custom-args/captures/nested-classes-2.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,5 @@ def test2(x1: (() => Unit), x2: (() => Unit) => Unit) =
2020
def test3(y1: (() => Unit), y2: (() => Unit) => Unit) =
2121
val cc1/*: C1^{cap[test3]}*/ = C1(y1, y2) // error (but should be OK)
2222
val cc2 = cc1.c2(x1, x2) // error (but should be OK)
23-
()
2423
//val cc3: cc1.C2^{cap[test2]} = cc2
2524

0 commit comments

Comments
 (0)