Skip to content

Commit a17c6a9

Browse files
committed
More robust level handling
1 parent 555089a commit a17c6a9

File tree

12 files changed

+176
-122
lines changed

12 files changed

+176
-122
lines changed

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

+45-4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import tpd.*
1414
import StdNames.nme
1515
import config.Feature
1616
import collection.mutable
17+
import CCState.*
1718

1819
private val Captures: Key[CaptureSet] = Key()
1920

@@ -64,11 +65,47 @@ class CCState:
6465
*/
6566
var levelError: Option[CaptureSet.CompareResult.LevelError] = None
6667

68+
private var curLevel: Level = outermostLevel
69+
private val symLevel: mutable.Map[Symbol, Int] = mutable.Map()
70+
71+
object CCState:
72+
73+
opaque type Level = Int
74+
75+
val undefinedLevel: Level = -1
76+
77+
val outermostLevel: Level = 0
78+
79+
/** The level of the current environment. Levels start at 0 and increase for
80+
* each nested function or class. -1 means the level is undefined.
81+
*/
82+
def currentLevel(using Context): Level = ccState.curLevel
83+
84+
inline def inNestedLevel[T](inline op: T)(using Context): T =
85+
val ccs = ccState
86+
val saved = ccs.curLevel
87+
ccs.curLevel = ccs.curLevel.nextInner
88+
try op finally ccs.curLevel = saved
89+
90+
inline def inNestedLevelUnless[T](inline p: Boolean)(inline op: T)(using Context): T =
91+
val ccs = ccState
92+
val saved = ccs.curLevel
93+
if !p then ccs.curLevel = ccs.curLevel.nextInner
94+
try op finally ccs.curLevel = saved
95+
96+
extension (x: Level)
97+
def isDefined: Boolean = x >= 0
98+
def <= (y: Level) = (x: Int) <= y
99+
def nextInner: Level = if isDefined then x + 1 else x
100+
101+
extension (sym: Symbol)(using Context)
102+
def ccLevel: Level = ccState.symLevel.getOrElse(sym, -1)
103+
def recordLevel() = ccState.symLevel(sym) = currentLevel
67104
end CCState
68105

69106
/** The currently valid CCState */
70107
def ccState(using Context) =
71-
Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState
108+
Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState1
72109

73110
class NoCommonRoot(rs: Symbol*)(using Context) extends Exception(
74111
i"No common capture root nested in ${rs.mkString(" and ")}"
@@ -339,6 +376,12 @@ extension (tp: Type)
339376
case _ =>
340377
tp
341378

379+
def level(using Context): Level =
380+
tp match
381+
case tp: TermRef => tp.symbol.ccLevel
382+
case tp: ThisType => tp.cls.ccLevel.nextInner
383+
case _ => undefinedLevel
384+
342385
extension (cls: ClassSymbol)
343386

344387
def pureBaseClass(using Context): Option[Symbol] =
@@ -423,9 +466,7 @@ extension (sym: Symbol)
423466
|| sym.is(Method, butNot = Accessor)
424467

425468
/** The owner of the current level. Qualifying owners are
426-
* - methods other than constructors and anonymous functions
427-
* - anonymous functions, provided they either define a local
428-
* root of type caps.Capability, or they are the rhs of a val definition.
469+
* - methods, other than accessors
429470
* - classes, if they are not staticOwners
430471
* - _root_
431472
*/

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

+25-32
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import typer.ErrorReporting.Addenda
1717
import TypeComparer.linkOK
1818
import util.common.alwaysTrue
1919
import scala.collection.mutable
20+
import CCState.*
2021

2122
/** A class for capture sets. Capture sets can be constants or variables.
2223
* Capture sets support inclusion constraints <:< where <:< is subcapturing.
@@ -56,10 +57,14 @@ sealed abstract class CaptureSet extends Showable:
5657
*/
5758
def isAlwaysEmpty: Boolean
5859

59-
/** An optional level limit, or NoSymbol if none exists. All elements of the set
60-
* must be in scopes visible from the level limit.
60+
/** An optional level limit, or undefinedLevel if none exists. All elements of the set
61+
* must be at levels equal or smaller than the level of the set, if it is defined.
6162
*/
62-
def levelLimit: Symbol
63+
def level: Level
64+
65+
/** An optional owner, or NoSymbol if none exists. Used for diagnstics
66+
*/
67+
def owner: Symbol
6368

6469
/** Is this capture set definitely non-empty? */
6570
final def isNotEmpty: Boolean = !elems.isEmpty
@@ -242,9 +247,7 @@ sealed abstract class CaptureSet extends Showable:
242247
if this.subCaptures(that, frozen = true).isOK then that
243248
else if that.subCaptures(this, frozen = true).isOK then this
244249
else if this.isConst && that.isConst then Const(this.elems ++ that.elems)
245-
else Var(
246-
this.levelLimit.maxNested(that.levelLimit, onConflict = (sym1, sym2) => sym1),
247-
this.elems ++ that.elems)
250+
else Var(initialElems = this.elems ++ that.elems)
248251
.addAsDependentTo(this).addAsDependentTo(that)
249252

250253
/** The smallest superset (via <:<) of this capture set that also contains `ref`.
@@ -414,7 +417,9 @@ object CaptureSet:
414417

415418
def withDescription(description: String): Const = Const(elems, description)
416419

417-
def levelLimit = NoSymbol
420+
def level = undefinedLevel
421+
422+
def owner = NoSymbol
418423

419424
override def toString = elems.toString
420425
end Const
@@ -434,7 +439,7 @@ object CaptureSet:
434439
end Fluid
435440

436441
/** The subclass of captureset variables with given initial elements */
437-
class Var(directOwner: Symbol, initialElems: Refs = emptySet)(using @constructorOnly ictx: Context) extends CaptureSet:
442+
class Var(override val owner: Symbol = NoSymbol, initialElems: Refs = emptySet, val level: Level = undefinedLevel, underBox: Boolean = false)(using @constructorOnly ictx: Context) extends CaptureSet:
438443

439444
/** A unique identification number for diagnostics */
440445
val id =
@@ -443,9 +448,6 @@ object CaptureSet:
443448

444449
//assert(id != 40)
445450

446-
override val levelLimit =
447-
if directOwner.exists then directOwner.levelOwner else NoSymbol
448-
449451
/** A variable is solved if it is aproximated to a from-then-on constant set. */
450452
private var isSolved: Boolean = false
451453

@@ -519,12 +521,10 @@ object CaptureSet:
519521
private def levelOK(elem: CaptureRef)(using Context): Boolean =
520522
if elem.isRootCapability then !noUniversal
521523
else elem match
522-
case elem: TermRef if levelLimit.exists =>
523-
var sym = elem.symbol
524-
if sym.isLevelOwner then sym = sym.owner
525-
levelLimit.isContainedIn(sym.levelOwner)
526-
case elem: ThisType if levelLimit.exists =>
527-
levelLimit.isContainedIn(elem.cls.levelOwner)
524+
case elem: TermRef if level.isDefined =>
525+
elem.symbol.ccLevel <= level
526+
case elem: ThisType if level.isDefined =>
527+
elem.cls.ccLevel.nextInner <= level
528528
case ReachCapability(elem1) =>
529529
levelOK(elem1)
530530
case MaybeCapability(elem1) =>
@@ -602,8 +602,8 @@ object CaptureSet:
602602
val debugInfo =
603603
if !isConst && ctx.settings.YccDebug.value then ids else ""
604604
val limitInfo =
605-
if ctx.settings.YprintLevel.value && levelLimit.exists
606-
then i"<in $levelLimit>"
605+
if ctx.settings.YprintLevel.value && level.isDefined
606+
then i"<at level ${level.toString}>"
607607
else ""
608608
debugInfo ++ limitInfo
609609

@@ -622,13 +622,6 @@ object CaptureSet:
622622
override def toString = s"Var$id$elems"
623623
end Var
624624

625-
/** Variables that represent refinements of class parameters can have the universal
626-
* capture set, since they represent only what is the result of the constructor.
627-
* Test case: Without that tweak, logger.scala would not compile.
628-
*/
629-
class RefiningVar(directOwner: Symbol)(using Context) extends Var(directOwner):
630-
override def disallowRootCapability(handler: () => Context ?=> Unit)(using Context) = this
631-
632625
/** A variable that is derived from some other variable via a map or filter. */
633626
abstract class DerivedVar(owner: Symbol, initialElems: Refs)(using @constructorOnly ctx: Context)
634627
extends Var(owner, initialElems):
@@ -657,7 +650,7 @@ object CaptureSet:
657650
*/
658651
class Mapped private[CaptureSet]
659652
(val source: Var, tm: TypeMap, variance: Int, initial: CaptureSet)(using @constructorOnly ctx: Context)
660-
extends DerivedVar(source.levelLimit, initial.elems):
653+
extends DerivedVar(source.owner, initial.elems):
661654
addAsDependentTo(initial) // initial mappings could change by propagation
662655

663656
private def mapIsIdempotent = tm.isInstanceOf[IdempotentCaptRefMap]
@@ -754,7 +747,7 @@ object CaptureSet:
754747
*/
755748
final class BiMapped private[CaptureSet]
756749
(val source: Var, bimap: BiTypeMap, initialElems: Refs)(using @constructorOnly ctx: Context)
757-
extends DerivedVar(source.levelLimit, initialElems):
750+
extends DerivedVar(source.owner, initialElems):
758751

759752
override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult =
760753
if origin eq source then
@@ -788,7 +781,7 @@ object CaptureSet:
788781
/** A variable with elements given at any time as { x <- source.elems | p(x) } */
789782
class Filtered private[CaptureSet]
790783
(val source: Var, p: Context ?=> CaptureRef => Boolean)(using @constructorOnly ctx: Context)
791-
extends DerivedVar(source.levelLimit, source.elems.filter(p)):
784+
extends DerivedVar(source.owner, source.elems.filter(p)):
792785

793786
override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult =
794787
if accountsFor(elem) then
@@ -818,7 +811,7 @@ object CaptureSet:
818811
extends Filtered(source, !other.accountsFor(_))
819812

820813
class Intersected(cs1: CaptureSet, cs2: CaptureSet)(using Context)
821-
extends Var(cs1.levelLimit.minNested(cs2.levelLimit), elemIntersection(cs1, cs2)):
814+
extends Var(initialElems = elemIntersection(cs1, cs2)):
822815
addAsDependentTo(cs1)
823816
addAsDependentTo(cs2)
824817
deps += cs1
@@ -908,7 +901,7 @@ object CaptureSet:
908901
if ctx.settings.YccDebug.value then printer.toText(trace, ", ")
909902
else blocking.show
910903
case LevelError(cs: CaptureSet, elem: CaptureRef) =>
911-
Str(i"($elem at wrong level for $cs in ${cs.levelLimit})")
904+
Str(i"($elem at wrong level for $cs at level ${cs.level.toString})")
912905

913906
/** The result is OK */
914907
def isOK: Boolean = this == OK
@@ -1151,6 +1144,6 @@ object CaptureSet:
11511144
i"""
11521145
|
11531146
|Note that reference ${ref}$levelStr
1154-
|cannot be included in outer capture set $cs which is associated with ${cs.levelLimit}"""
1147+
|cannot be included in outer capture set $cs"""
11551148

11561149
end CaptureSet

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

+23-13
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import transform.{Recheck, PreRecheck, CapturedVars}
1919
import Recheck.*
2020
import scala.collection.mutable
2121
import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult}
22+
import CCState.*
2223
import StdNames.nme
2324
import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
2425
import reporting.trace
@@ -191,7 +192,7 @@ class CheckCaptures extends Recheck, SymTransformer:
191192
if Feature.ccEnabled then
192193
super.run
193194

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

196197
class CaptureChecker(ictx: Context) extends Rechecker(ictx):
197198

@@ -311,7 +312,7 @@ class CheckCaptures extends Recheck, SymTransformer:
311312
def capturedVars(sym: Symbol)(using Context): CaptureSet =
312313
myCapturedVars.getOrElseUpdate(sym,
313314
if sym.ownersIterator.exists(_.isTerm)
314-
then CaptureSet.Var(sym.owner)
315+
then CaptureSet.Var(sym.owner, level = sym.ccLevel)
315316
else CaptureSet.empty)
316317

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

589+
override def recheckBlock(tree: Block, pt: Type)(using Context): Type =
590+
inNestedLevel(super.recheckBlock(tree, pt))
591+
588592
override def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean)(using Context): Type =
589593
val cs = capturedVars(tree.meth.symbol)
590594
capt.println(i"typing closure $tree with cvs $cs")
@@ -688,13 +692,14 @@ class CheckCaptures extends Recheck, SymTransformer:
688692
val localSet = capturedVars(sym)
689693
if !localSet.isAlwaysEmpty then
690694
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
695+
inNestedLevel:
696+
try checkInferredResult(super.recheckDefDef(tree, sym), tree)
697+
finally
698+
if !sym.isAnonymousFunction then
699+
// Anonymous functions propagate their type to the enclosing environment
700+
// so it is not in general sound to interpolate their types.
701+
interpolateVarsIn(tree.tpt)
702+
curEnv = saved
698703

699704
override def recheckRHS(rhs: Tree, pt: Type)(using Context): Type =
700705
def avoidMap = new TypeOps.AvoidMap:
@@ -771,7 +776,8 @@ class CheckCaptures extends Recheck, SymTransformer:
771776
checkSubset(thisSet,
772777
CaptureSet.empty.withDescription(i"of pure base class $pureBase"),
773778
selfType.srcPos, cs1description = " captured by this self type")
774-
super.recheckClassDef(tree, impl, cls)
779+
inNestedLevelUnless(cls.is(Module)):
780+
super.recheckClassDef(tree, impl, cls)
775781
finally
776782
curEnv = saved
777783

@@ -824,9 +830,9 @@ class CheckCaptures extends Recheck, SymTransformer:
824830
val saved = curEnv
825831
tree match
826832
case _: RefTree | closureDef(_) if pt.isBoxedCapturing =>
827-
curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner), curEnv)
833+
curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner, level = currentLevel), curEnv)
828834
case _ if tree.hasAttachment(ClosureBodyValue) =>
829-
curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner), curEnv)
835+
curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner, level = currentLevel), curEnv)
830836
case _ =>
831837
val res =
832838
try
@@ -990,7 +996,11 @@ class CheckCaptures extends Recheck, SymTransformer:
990996

991997
inline def inNestedEnv[T](boxed: Boolean)(op: => T): T =
992998
val saved = curEnv
993-
curEnv = Env(curEnv.owner, EnvKind.NestedInOwner, CaptureSet.Var(curEnv.owner), if boxed then null else curEnv)
999+
curEnv = Env(
1000+
curEnv.owner,
1001+
EnvKind.NestedInOwner,
1002+
CaptureSet.Var(curEnv.owner, level = currentLevel),
1003+
if boxed then null else curEnv)
9941004
try op
9951005
finally curEnv = saved
9961006

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

+20-19
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

0 commit comments

Comments
 (0)