Skip to content

Commit fa30d7f

Browse files
committed
Moe robust level handling
1 parent 67ee590 commit fa30d7f

File tree

6 files changed

+90
-44
lines changed

6 files changed

+90
-44
lines changed

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,29 @@ class CCState:
6464
*/
6565
var levelError: Option[CaptureSet.CompareResult.LevelError] = None
6666

67+
private var myLevel: Int = 0
68+
69+
inline def inNestedLevel[T](inline op: T): T =
70+
myLevel += 1
71+
try op finally myLevel -=1
72+
73+
/** The level of the current environment. Levels start at 0 and increase for
74+
* each nested function or class. -1 means the level is undefined.
75+
*/
76+
def currentLevel = myLevel
77+
78+
private val symLevel: mutable.Map[Symbol, Int] = mutable.Map()
79+
80+
def level(sym: Symbol): Int = symLevel.getOrElse(sym, -1)
81+
82+
def recordLevel(sym: Symbol): Unit =
83+
symLevel(sym) = currentLevel
84+
6785
end CCState
6886

6987
/** The currently valid CCState */
7088
def ccState(using Context) =
71-
Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState
89+
Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState1
7290

7391
class NoCommonRoot(rs: Symbol*)(using Context) extends Exception(
7492
i"No common capture root nested in ${rs.mkString(" and ")}"
@@ -339,6 +357,13 @@ extension (tp: Type)
339357
case _ =>
340358
tp
341359

360+
def level(using Context): Int = tp match
361+
case tp: TermRef => ccState.level(tp.symbol)
362+
case tp: ThisType =>
363+
val clsLevel = ccState.level(tp.cls)
364+
if clsLevel == -1 then -1 else clsLevel + 1
365+
case _ => -1
366+
342367
extension (cls: ClassSymbol)
343368

344369
def pureBaseClass(using Context): Option[Symbol] =

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ object CaptureSet:
434434
end Fluid
435435

436436
/** The subclass of captureset variables with given initial elements */
437-
class Var(directOwner: Symbol, initialElems: Refs = emptySet)(using @constructorOnly ictx: Context) extends CaptureSet:
437+
class Var(directOwner: Symbol, initialElems: Refs = emptySet, level: Int = -1, underBox: Boolean = false)(using @constructorOnly ictx: Context) extends CaptureSet:
438438

439439
/** A unique identification number for diagnostics */
440440
val id =
@@ -520,7 +520,12 @@ object CaptureSet:
520520
if elem.isRootCapability then !noUniversal
521521
else elem match
522522
case elem: TermRef if levelLimit.exists =>
523-
levelLimit.isContainedIn(elem.symbol.levelOwner)
523+
var sym = elem.symbol
524+
val now = level == -1 || ccState.level(sym) <= level
525+
val was = levelLimit.isContainedIn(elem.symbol.levelOwner)
526+
if was != now then
527+
println(i"Diff levelOK $sym at ${ccState.level(sym)} for $this at $level in ${directOwner}, now $now")
528+
now
524529
case elem: ThisType if levelLimit.exists =>
525530
levelLimit.isContainedIn(elem.cls.levelOwner)
526531
case ReachCapability(elem1) =>

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

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ class CheckCaptures extends Recheck, SymTransformer:
191191
if Feature.ccEnabled then
192192
super.run
193193

194-
val ccState = new CCState
194+
val ccState1 = new CCState // Dotty problem: Rename to ccState ==> Crash in ExplicitOuter
195195

196196
class CaptureChecker(ictx: Context) extends Rechecker(ictx):
197197

@@ -311,7 +311,7 @@ class CheckCaptures extends Recheck, SymTransformer:
311311
def capturedVars(sym: Symbol)(using Context): CaptureSet =
312312
myCapturedVars.getOrElseUpdate(sym,
313313
if sym.ownersIterator.exists(_.isTerm)
314-
then CaptureSet.Var(sym.owner)
314+
then CaptureSet.Var(sym.owner, level = ccState.level(sym))
315315
else CaptureSet.empty)
316316

317317
/** For all nested environments up to `limit` or a closed environment perform `op`,
@@ -585,6 +585,9 @@ class CheckCaptures extends Recheck, SymTransformer:
585585
tree.srcPos)
586586
super.recheckTypeApply(tree, pt)
587587

588+
override def recheckBlock(tree: Block, pt: Type)(using Context): Type =
589+
ccState.inNestedLevel(super.recheckBlock(tree, pt))
590+
588591
override def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean)(using Context): Type =
589592
val cs = capturedVars(tree.meth.symbol)
590593
capt.println(i"typing closure $tree with cvs $cs")
@@ -688,13 +691,14 @@ class CheckCaptures extends Recheck, SymTransformer:
688691
val localSet = capturedVars(sym)
689692
if !localSet.isAlwaysEmpty then
690693
curEnv = Env(sym, EnvKind.Regular, localSet, curEnv)
691-
try checkInferredResult(super.recheckDefDef(tree, sym), tree)
692-
finally
693-
if !sym.isAnonymousFunction then
694-
// Anonymous functions propagate their type to the enclosing environment
695-
// so it is not in general sound to interpolate their types.
696-
interpolateVarsIn(tree.tpt)
697-
curEnv = saved
694+
ccState.inNestedLevel:
695+
try checkInferredResult(super.recheckDefDef(tree, sym), tree)
696+
finally
697+
if !sym.isAnonymousFunction then
698+
// Anonymous functions propagate their type to the enclosing environment
699+
// so it is not in general sound to interpolate their types.
700+
interpolateVarsIn(tree.tpt)
701+
curEnv = saved
698702

699703
override def recheckRHS(rhs: Tree, pt: Type)(using Context): Type =
700704
def avoidMap = new TypeOps.AvoidMap:
@@ -824,9 +828,9 @@ class CheckCaptures extends Recheck, SymTransformer:
824828
val saved = curEnv
825829
tree match
826830
case _: RefTree | closureDef(_) if pt.isBoxedCapturing =>
827-
curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner), curEnv)
831+
curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner, level = ccState.currentLevel), curEnv)
828832
case _ if tree.hasAttachment(ClosureBodyValue) =>
829-
curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner), curEnv)
833+
curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner, level = ccState.currentLevel), curEnv)
830834
case _ =>
831835
val res =
832836
try
@@ -990,7 +994,11 @@ class CheckCaptures extends Recheck, SymTransformer:
990994

991995
inline def inNestedEnv[T](boxed: Boolean)(op: => T): T =
992996
val saved = curEnv
993-
curEnv = Env(curEnv.owner, EnvKind.NestedInOwner, CaptureSet.Var(curEnv.owner), if boxed then null else curEnv)
997+
curEnv = Env(
998+
curEnv.owner,
999+
EnvKind.NestedInOwner,
1000+
CaptureSet.Var(curEnv.owner, level = ccState.currentLevel),
1001+
if boxed then null else curEnv)
9941002
try op
9951003
finally curEnv = saved
9961004

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

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Handling existentials in CC:
1717
1818
In adapt:
1919
20-
- If an EX is toplevel in expected type, replace with a fresh capture set variable
20+
- If an EX is toplevel in expected type, replace with `cap`.
2121
- If an EX is toplevel in actual type, find a trackable replacement `x` as follows:
2222
+ If actual type is a trackable ref, pick that
2323
+ Otherwise, create a fresh skolem val symbol with currently enclosing
@@ -26,30 +26,31 @@ In adapt:
2626
+ If the EX-bound variable appears only at toplevel, replace it with `x`
2727
+ Otherwise, replace it with `x*`.
2828
29-
In avoidance of a type T:
29+
Delayed packing:
3030
31-
- Replace all co-variant occurrences of locals variables in T (including locally
32-
created EX-skolems) with single fresh EX-bound variable, which wraps T.
33-
- Contravariant occurrences of local variables are approximated by the empty capture set,
34-
as was the case before.
35-
- Invariant occurrences of local variables produce errors, as was the case before.
36-
- Check that no existentially quantified local variable appears under a box.
31+
- When typing a `def` (including an anonymoys function), convert all covariant
32+
toplevel `cap`s to a fresh existential variable wrapping the result type.
3733
38-
The reason it is done this way is that it produces the smallest existential type
39-
wrt the existential type ordering shown below. For instance, consider the type
34+
Level checking and avoidance:
4035
41-
(A^{x}, B^{y})
36+
- Environments, capture refs, and capture set variables carry levels
4237
43-
where `x` and `y` are local. We widen to
38+
+ levels start at 0
39+
+ The level of a block or template statement sequence is one higher than the level of
40+
its environment
41+
+ The level of a TermRef is the level of the environment where its symbol is defined.
42+
+ The level of a ThisType is the level of the statements of the class to which it beloongs.
43+
+ The level of a TermParamRef is currently -1 (i.e. TermParamRefs are not yet checked using this system)
44+
+ The level of a capture set variable is the level of the environment where it is created.
4445
45-
EX a.(A^{a}, B^{a})
46+
- Variables also carry info whether they accept `cap` or not. Variables introduced under a box
47+
don't, the others do.
4648
47-
rather than
48-
49-
EX a.EX b.(A^{a}, A^{b})
50-
51-
In the subtype ordering of existentials the first of these types is a subtype of
52-
the other, but not _vice versa_.
49+
- Capture set variables do not accept elements of level higher than the variable's level
50+
- We use avoidance to heal such cases: If the level-incorrect ref appears
51+
+ covariantly: widen to underlying capture set, reject if that is cap and the variable does not allow it
52+
+ contravariantly: narrow to {}
53+
+ invarianty: reject with error
5354
5455
In cv-computation (markFree):
5556

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

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -410,14 +410,17 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
410410
if isExcluded(meth) then
411411
return
412412

413-
inContext(ctx.withOwner(meth)):
414-
paramss.foreach(traverse)
415-
transformResultType(tpt, meth)
416-
traverse(tree.rhs)
417-
//println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}")
413+
ccState.recordLevel(meth)
414+
ccState.inNestedLevel:
415+
inContext(ctx.withOwner(meth)):
416+
paramss.foreach(traverse)
417+
transformResultType(tpt, meth)
418+
traverse(tree.rhs)
419+
//println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}")
418420

419421
case tree @ ValDef(_, tpt: TypeTree, _) =>
420422
val sym = tree.symbol
423+
ccState.recordLevel(sym)
421424
val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym)
422425
inContext(defCtx):
423426
transformResultType(tpt, sym)
@@ -434,13 +437,17 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
434437
transformTT(arg, boxed = true, exact = false) // type arguments in type applications are boxed
435438

436439
case tree: TypeDef if tree.symbol.isClass =>
440+
ccState.recordLevel(tree.symbol)
437441
inContext(ctx.withOwner(tree.symbol)):
438442
traverseChildren(tree)
439443

440444
case tree @ SeqLiteral(elems, tpt: TypeTree) =>
441445
traverse(elems)
442446
tpt.rememberType(box(transformInferredType(tpt.tpe)))
443447

448+
case tree: Block =>
449+
ccState.inNestedLevel(traverseChildren(tree))
450+
444451
case _ =>
445452
traverseChildren(tree)
446453
postProcess(tree)
@@ -556,7 +563,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
556563
// Infer the self type for the rest, which is all classes without explicit
557564
// self types (to which we also add nested module classes), provided they are
558565
// neither pure, nor are publicily extensible with an unconstrained self type.
559-
CapturingType(cinfo.selfType, CaptureSet.Var(cls))
566+
CapturingType(cinfo.selfType, CaptureSet.Var(cls, level = ccState.currentLevel))
560567
val ps1 = inContext(ctx.withOwner(cls)):
561568
ps.mapConserve(transformExplicitType(_))
562569
if (selfInfo1 ne selfInfo) || (ps1 ne ps) then
@@ -680,11 +687,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
680687
/** Add a capture set variable to `tp` if necessary, or maybe pull out
681688
* an embedded capture set variable from a part of `tp`.
682689
*/
683-
def addVar(tp: Type, owner: Symbol)(using Context): Type =
690+
private def addVar(tp: Type, owner: Symbol)(using Context): Type =
684691
decorate(tp,
685692
addedSet = _.dealias.match
686-
case CapturingType(_, refs) => CaptureSet.Var(owner, refs.elems)
687-
case _ => CaptureSet.Var(owner))
693+
case CapturingType(_, refs) => CaptureSet.Var(owner, refs.elems, level = ccState.currentLevel)
694+
case _ => CaptureSet.Var(owner, level = ccState.currentLevel))
688695

689696
def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit =
690697
setupTraverser(recheckDef).traverse(tree)(using ctx.withPhase(thisPhase))

compiler/src/dotty/tools/dotc/transform/Recheck.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ abstract class Recheck extends Phase, SymTransformer:
266266

267267
def recheckClassDef(tree: TypeDef, impl: Template, sym: ClassSymbol)(using Context): Type =
268268
recheck(impl.constr)
269-
impl.parentsOrDerived.foreach(recheck(_))
269+
impl.parents.foreach(recheck(_))
270270
recheck(impl.self)
271271
recheckStats(impl.body)
272272
sym.typeRef

0 commit comments

Comments
 (0)