Skip to content

Commit 2a39069

Browse files
committed
More robust level handling
1 parent 27a3f80 commit 2a39069

File tree

11 files changed

+152
-103
lines changed

11 files changed

+152
-103
lines changed

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

Lines changed: 45 additions & 4 deletions
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

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import util.{SimpleIdentitySet, Property}
1616
import typer.ErrorReporting.Addenda
1717
import util.common.alwaysTrue
1818
import scala.collection.mutable
19+
import CCState.*
1920

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

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

6368
/** Is this capture set definitely non-empty? */
6469
final def isNotEmpty: Boolean = !elems.isEmpty
@@ -239,9 +244,7 @@ sealed abstract class CaptureSet extends Showable:
239244
if this.subCaptures(that, frozen = true).isOK then that
240245
else if that.subCaptures(this, frozen = true).isOK then this
241246
else if this.isConst && that.isConst then Const(this.elems ++ that.elems)
242-
else Var(
243-
this.levelLimit.maxNested(that.levelLimit, onConflict = (sym1, sym2) => sym1),
244-
this.elems ++ that.elems)
247+
else Var(initialElems = this.elems ++ that.elems)
245248
.addAsDependentTo(this).addAsDependentTo(that)
246249

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

412415
def withDescription(description: String): Const = Const(elems, description)
413416

414-
def levelLimit = NoSymbol
417+
def level = undefinedLevel
418+
419+
def owner = NoSymbol
415420

416421
override def toString = elems.toString
417422
end Const
@@ -431,7 +436,7 @@ object CaptureSet:
431436
end Fluid
432437

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

436441
/** A unique identification number for diagnostics */
437442
val id =
@@ -440,9 +445,6 @@ object CaptureSet:
440445

441446
//assert(id != 40)
442447

443-
override val levelLimit =
444-
if directOwner.exists then directOwner.levelOwner else NoSymbol
445-
446448
/** A variable is solved if it is aproximated to a from-then-on constant set. */
447449
private var isSolved: Boolean = false
448450

@@ -516,12 +518,10 @@ object CaptureSet:
516518
private def levelOK(elem: CaptureRef)(using Context): Boolean =
517519
if elem.isRootCapability then !noUniversal
518520
else elem match
519-
case elem: TermRef if levelLimit.exists =>
520-
var sym = elem.symbol
521-
if sym.isLevelOwner then sym = sym.owner
522-
levelLimit.isContainedIn(sym.levelOwner)
523-
case elem: ThisType if levelLimit.exists =>
524-
levelLimit.isContainedIn(elem.cls.levelOwner)
521+
case elem: TermRef if level.isDefined =>
522+
elem.symbol.ccLevel <= level
523+
case elem: ThisType if level.isDefined =>
524+
elem.cls.ccLevel.nextInner <= level
525525
case ReachCapability(elem1) =>
526526
levelOK(elem1)
527527
case MaybeCapability(elem1) =>
@@ -599,8 +599,8 @@ object CaptureSet:
599599
val debugInfo =
600600
if !isConst && ctx.settings.YccDebug.value then ids else ""
601601
val limitInfo =
602-
if ctx.settings.YprintLevel.value && levelLimit.exists
603-
then i"<in $levelLimit>"
602+
if ctx.settings.YprintLevel.value && level.isDefined
603+
then i"<at level ${level.toString}>"
604604
else ""
605605
debugInfo ++ limitInfo
606606

@@ -619,13 +619,6 @@ object CaptureSet:
619619
override def toString = s"Var$id$elems"
620620
end Var
621621

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

660653
private def mapIsIdempotent = tm.isInstanceOf[IdempotentCaptRefMap]
@@ -751,7 +744,7 @@ object CaptureSet:
751744
*/
752745
final class BiMapped private[CaptureSet]
753746
(val source: Var, bimap: BiTypeMap, initialElems: Refs)(using @constructorOnly ctx: Context)
754-
extends DerivedVar(source.levelLimit, initialElems):
747+
extends DerivedVar(source.owner, initialElems):
755748

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

790783
override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult =
791784
if accountsFor(elem) then
@@ -815,7 +808,7 @@ object CaptureSet:
815808
extends Filtered(source, !other.accountsFor(_))
816809

817810
class Intersected(cs1: CaptureSet, cs2: CaptureSet)(using Context)
818-
extends Var(cs1.levelLimit.minNested(cs2.levelLimit), elemIntersection(cs1, cs2)):
811+
extends Var(initialElems = elemIntersection(cs1, cs2)):
819812
addAsDependentTo(cs1)
820813
addAsDependentTo(cs2)
821814
deps += cs1
@@ -905,7 +898,7 @@ object CaptureSet:
905898
if ctx.settings.YccDebug.value then printer.toText(trace, ", ")
906899
else blocking.show
907900
case LevelError(cs: CaptureSet, elem: CaptureRef) =>
908-
Str(i"($elem at wrong level for $cs in ${cs.levelLimit})")
901+
Str(i"($elem at wrong level for $cs at level ${cs.level.toString})")
909902

910903
/** The result is OK */
911904
def isOK: Boolean = this == OK
@@ -1148,6 +1141,6 @@ object CaptureSet:
11481141
i"""
11491142
|
11501143
|Note that reference ${ref}$levelStr
1151-
|cannot be included in outer capture set $cs which is associated with ${cs.levelLimit}"""
1144+
|cannot be included in outer capture set $cs"""
11521145

11531146
end CaptureSet

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

Lines changed: 19 additions & 13 deletions
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`,
@@ -592,6 +593,9 @@ class CheckCaptures extends Recheck, SymTransformer:
592593
tree.srcPos)
593594
super.recheckTypeApply(tree, pt)
594595

596+
override def recheckBlock(tree: Block, pt: Type)(using Context): Type =
597+
inNestedLevel(super.recheckBlock(tree, pt))
598+
595599
override def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean)(using Context): Type =
596600
val cs = capturedVars(tree.meth.symbol)
597601
capt.println(i"typing closure $tree with cvs $cs")
@@ -695,13 +699,14 @@ class CheckCaptures extends Recheck, SymTransformer:
695699
val localSet = capturedVars(sym)
696700
if !localSet.isAlwaysEmpty then
697701
curEnv = Env(sym, EnvKind.Regular, localSet, curEnv)
698-
try checkInferredResult(super.recheckDefDef(tree, sym), tree)
699-
finally
700-
if !sym.isAnonymousFunction then
701-
// Anonymous functions propagate their type to the enclosing environment
702-
// so it is not in general sound to interpolate their types.
703-
interpolateVarsIn(tree.tpt)
704-
curEnv = saved
702+
inNestedLevel:
703+
try checkInferredResult(super.recheckDefDef(tree, sym), tree)
704+
finally
705+
if !sym.isAnonymousFunction then
706+
// Anonymous functions propagate their type to the enclosing environment
707+
// so it is not in general sound to interpolate their types.
708+
interpolateVarsIn(tree.tpt)
709+
curEnv = saved
705710

706711
/** If val or def definition with inferred (result) type is visible
707712
* in other compilation units, check that the actual inferred type
@@ -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

@@ -823,9 +829,9 @@ class CheckCaptures extends Recheck, SymTransformer:
823829
val saved = curEnv
824830
tree match
825831
case _: RefTree | closureDef(_) if pt.isBoxedCapturing =>
826-
curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner), curEnv)
832+
curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner, level = currentLevel), curEnv)
827833
case _ if tree.hasAttachment(ClosureBodyValue) =>
828-
curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner), curEnv)
834+
curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner, level = currentLevel), curEnv)
829835
case _ =>
830836
val res =
831837
try
@@ -995,7 +1001,7 @@ class CheckCaptures extends Recheck, SymTransformer:
9951001
val saved = curEnv
9961002
curEnv = Env(
9971003
curEnv.owner, EnvKind.NestedInOwner,
998-
CaptureSet.Var(curEnv.owner),
1004+
CaptureSet.Var(curEnv.owner, level = currentLevel),
9991005
if boxed then null else curEnv)
10001006
try
10011007
val (eargs, eres) = expected.dealias.stripCapturing match

0 commit comments

Comments
 (0)