Skip to content

Commit 0635958

Browse files
committed
Handle levels in constraint solving
- Define a notion of ccNestingLevel, which corresponds to the nesting level of so called "level owners" relative to each other. - The outermost level owner is _root_. - Other level owners are classes that are not staticOwners and methods that are not constructors. - The ccNestingLevel of any symbol is the ccNestingLevel of its closest enclosing level owner, or -1 for NoSymbol. - Capture set variables are created with a level owner. - Capture set variables cannot include elements with higher ccNestingLevels than the variable's owner. - If level-incorrect elements are attempted to be added to a capture set variable, they are instead widened to the underlying capture set.
1 parent 1051563 commit 0635958

File tree

9 files changed

+250
-150
lines changed

9 files changed

+250
-150
lines changed

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

+35
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,41 @@ extension (sym: Symbol)
253253
&& sym != defn.Caps_unsafeBox
254254
&& sym != defn.Caps_unsafeUnbox
255255

256+
/** The owner of the current level. Qualifying owners are
257+
* - methods other than constructors
258+
* - classes, if they are not staticOwners
259+
* - _root_
260+
*/
261+
def levelOwner(using Context): Symbol =
262+
if sym.isStaticOwner then defn.RootClass
263+
else if sym.isClass || sym.is(Method) && !sym.isConstructor then sym
264+
else sym.owner.levelOwner
265+
266+
/** The nesting level of `sym` for the purposes of `cc`,
267+
* -1 for NoSymbol
268+
*/
269+
def ccNestingLevel(using Context): Int =
270+
if sym.exists then
271+
val lowner = sym.levelOwner
272+
val cache = ctx.property(CheckCaptures.NestingLevels).get
273+
cache.getOrElseUpdate(lowner,
274+
if lowner.isRoot then 0 else lowner.owner.ccNestingLevel + 1)
275+
else -1
276+
277+
/** Optionally, the nesting level of `sym` for the purposes of `cc`, provided
278+
* a capture checker is running.
279+
*/
280+
def ccNestingLevelOpt(using Context): Option[Int] =
281+
if ctx.property(CheckCaptures.NestingLevels).isDefined then
282+
Some(ccNestingLevel)
283+
else None
284+
285+
def maxNested(other: Symbol)(using Context): Symbol =
286+
if sym.ccNestingLevel < other.ccNestingLevel then other else sym
287+
288+
def minNested(other: Symbol)(using Context): Symbol =
289+
if sym.ccNestingLevel > other.ccNestingLevel then other else sym
290+
256291
extension (tp: AnnotatedType)
257292
/** Is this a boxed capturing type? */
258293
def isBoxed(using Context): Boolean = tp.annot match

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

+52-17
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import annotation.internal.sharable
1212
import reporting.trace
1313
import printing.{Showable, Printer}
1414
import printing.Texts.*
15-
import util.{SimpleIdentitySet, Property}
15+
import util.{SimpleIdentitySet, Property, optional}, optional.{break, ?}
1616
import util.common.alwaysTrue
1717
import scala.collection.mutable
1818
import config.Config.ccAllowUnsoundMaps
@@ -55,6 +55,11 @@ sealed abstract class CaptureSet extends Showable:
5555
*/
5656
def isAlwaysEmpty: Boolean
5757

58+
/** The level owner in which the set is defined. Sets can only take
59+
* elements with nesting level up to the cc-nestinglevel of owner.
60+
*/
61+
def owner: Symbol
62+
5863
/** Is this capture set definitely non-empty? */
5964
final def isNotEmpty: Boolean = !elems.isEmpty
6065

@@ -123,10 +128,14 @@ sealed abstract class CaptureSet extends Showable:
123128
* as frozen.
124129
*/
125130
def accountsFor(x: CaptureRef)(using Context): Boolean =
126-
reporting.trace(i"$this accountsFor $x, ${x.captureSetOfInfo}?", show = true) {
127-
elems.exists(_.subsumes(x))
128-
|| !x.isRootCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK
129-
}
131+
if comparer.isInstanceOf[ExplainingTypeComparer] then // !!! DEBUG
132+
reporting.trace.force(i"$this accountsFor $x, ${x.captureSetOfInfo}?", show = true):
133+
elems.exists(_.subsumes(x))
134+
|| !x.isRootCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK
135+
else
136+
reporting.trace(i"$this accountsFor $x, ${x.captureSetOfInfo}?", show = true):
137+
elems.exists(_.subsumes(x))
138+
|| !x.isRootCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK
130139

131140
/** A more optimistic version of accountsFor, which does not take variable supersets
132141
* of the `x` reference into account. A set might account for `x` if it accounts
@@ -191,7 +200,8 @@ sealed abstract class CaptureSet extends Showable:
191200
if this.subCaptures(that, frozen = true).isOK then that
192201
else if that.subCaptures(this, frozen = true).isOK then this
193202
else if this.isConst && that.isConst then Const(this.elems ++ that.elems)
194-
else Var(this.elems ++ that.elems).addAsDependentTo(this).addAsDependentTo(that)
203+
else Var(this.owner.maxNested(that.owner), this.elems ++ that.elems)
204+
.addAsDependentTo(this).addAsDependentTo(that)
195205

196206
/** The smallest superset (via <:<) of this capture set that also contains `ref`.
197207
*/
@@ -276,7 +286,9 @@ sealed abstract class CaptureSet extends Showable:
276286
if isUniversal then handler()
277287
this
278288

279-
/** Invoke handler on the elements to check wellformedness of the capture set */
289+
/** Invoke handler on the elements to ensure wellformedness of the capture set.
290+
* The handler might add additional elements to the capture set.
291+
*/
280292
def ensureWellformed(handler: List[CaptureRef] => Context ?=> Unit)(using Context): this.type =
281293
handler(elems.toList)
282294
this
@@ -353,6 +365,8 @@ object CaptureSet:
353365

354366
def withDescription(description: String): Const = Const(elems, description)
355367

368+
def owner = NoSymbol
369+
356370
override def toString = elems.toString
357371
end Const
358372

@@ -371,16 +385,23 @@ object CaptureSet:
371385
end Fluid
372386

373387
/** The subclass of captureset variables with given initial elements */
374-
class Var(initialElems: Refs = emptySet) extends CaptureSet:
388+
class Var(directOwner: Symbol, initialElems: Refs = emptySet)(using @constructorOnly ictx: Context) extends CaptureSet:
375389

376390
/** A unique identification number for diagnostics */
377391
val id =
378392
varId += 1
379393
varId
380394

395+
override val owner = directOwner.levelOwner
396+
381397
/** A variable is solved if it is aproximated to a from-then-on constant set. */
382398
private var isSolved: Boolean = false
383399

400+
private var ownLevelCache = -1
401+
private def ownLevel(using Context) =
402+
if ownLevelCache == -1 then ownLevelCache = owner.ccNestingLevel
403+
ownLevelCache
404+
384405
/** The elements currently known to be in the set */
385406
var elems: Refs = initialElems
386407

@@ -425,16 +446,30 @@ object CaptureSet:
425446
deps = state.deps(this)
426447

427448
def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult =
428-
if !isConst && recordElemsState() then
449+
if isConst || !recordElemsState() then
450+
CompareResult.fail(this) // fail if variable is solved or given VarState is frozen
451+
else if levelsOK(newElems) then
429452
elems ++= newElems
430453
if isUniversal then rootAddedHandler()
431454
newElemAddedHandler(newElems.toList)
432455
// assert(id != 5 || elems.size != 3, this)
433456
(CompareResult.OK /: deps) { (r, dep) =>
434457
r.andAlso(dep.tryInclude(newElems, this))
435458
}
436-
else // fail if variable is solved or given VarState is frozen
437-
CompareResult.fail(this)
459+
else widenCaptures(newElems) match
460+
case Some(newElems1) => tryInclude(newElems1, origin)
461+
case None => CompareResult.fail(this)
462+
463+
private def levelsOK(elems: Refs)(using Context): Boolean =
464+
!elems.exists(_.ccNestingLevel > ownLevel)
465+
466+
private def widenCaptures(elems: Refs)(using Context): Option[Refs] =
467+
optional:
468+
(SimpleIdentitySet[CaptureRef]() /: elems): (acc, elem) =>
469+
if elem.ccNestingLevel <= ownLevel then acc + elem
470+
else if elem.isRootCapability then break()
471+
else acc ++ widenCaptures(elem.captureSetOfInfo.elems).?
472+
.showing(i"widen catures ${elems.toList} for $this at $owner = $result", capt)
438473

439474
def addDependent(cs: CaptureSet)(using Context, VarState): CompareResult =
440475
if (cs eq this) || cs.isUniversal || isConst then
@@ -519,8 +554,8 @@ object CaptureSet:
519554
end Var
520555

521556
/** A variable that is derived from some other variable via a map or filter. */
522-
abstract class DerivedVar(initialElems: Refs)(using @constructorOnly ctx: Context)
523-
extends Var(initialElems):
557+
abstract class DerivedVar(owner: Symbol, initialElems: Refs)(using @constructorOnly ctx: Context)
558+
extends Var(owner, initialElems):
524559

525560
// For debugging: A trace where a set was created. Note that logically it would make more
526561
// sense to place this variable in Mapped, but that runs afoul of the initializatuon checker.
@@ -546,7 +581,7 @@ object CaptureSet:
546581
*/
547582
class Mapped private[CaptureSet]
548583
(val source: Var, tm: TypeMap, variance: Int, initial: CaptureSet)(using @constructorOnly ctx: Context)
549-
extends DerivedVar(initial.elems):
584+
extends DerivedVar(source.owner, initial.elems):
550585
addAsDependentTo(initial) // initial mappings could change by propagation
551586

552587
private def mapIsIdempotent = tm.isInstanceOf[IdempotentCaptRefMap]
@@ -612,7 +647,7 @@ object CaptureSet:
612647
*/
613648
final class BiMapped private[CaptureSet]
614649
(val source: Var, bimap: BiTypeMap, initialElems: Refs)(using @constructorOnly ctx: Context)
615-
extends DerivedVar(initialElems):
650+
extends DerivedVar(source.owner, initialElems):
616651

617652
override def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult =
618653
if origin eq source then
@@ -642,7 +677,7 @@ object CaptureSet:
642677
/** A variable with elements given at any time as { x <- source.elems | p(x) } */
643678
class Filtered private[CaptureSet]
644679
(val source: Var, p: Context ?=> CaptureRef => Boolean)(using @constructorOnly ctx: Context)
645-
extends DerivedVar(source.elems.filter(p)):
680+
extends DerivedVar(source.owner, source.elems.filter(p)):
646681

647682
override def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult =
648683
val filtered = newElems.filter(p)
@@ -673,7 +708,7 @@ object CaptureSet:
673708
extends Filtered(source, !other.accountsFor(_))
674709

675710
class Intersected(cs1: CaptureSet, cs2: CaptureSet)(using Context)
676-
extends Var(elemIntersection(cs1, cs2)):
711+
extends Var(cs1.owner.minNested(cs2.owner), elemIntersection(cs1, cs2)):
677712
addAsDependentTo(cs1)
678713
addAsDependentTo(cs2)
679714
deps += cs1

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

+36-34
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ object CheckCaptures:
189189
/** Attachment key for bodies of closures, provided they are values */
190190
val ClosureBodyValue = Property.Key[Unit]
191191

192+
/** Attachment key for the nesting level cache */
193+
val NestingLevels = Property.Key[mutable.HashMap[Symbol, Int]]
194+
192195
class CheckCaptures extends Recheck, SymTransformer:
193196
thisPhase =>
194197

@@ -263,7 +266,8 @@ class CheckCaptures extends Recheck, SymTransformer:
263266
report.error(em"$header included in allowed capture set ${res.blocking}", pos)
264267

265268
/** The current environment */
266-
private var curEnv: Env = Env(NoSymbol, EnvKind.Regular, CaptureSet.empty, null)
269+
private var curEnv: Env = inContext(ictx):
270+
Env(defn.RootClass, EnvKind.Regular, CaptureSet.empty, null)
267271

268272
private val myCapturedVars: util.EqHashMap[Symbol, CaptureSet] = EqHashMap()
269273

@@ -272,7 +276,8 @@ class CheckCaptures extends Recheck, SymTransformer:
272276
*/
273277
def capturedVars(sym: Symbol)(using Context) =
274278
myCapturedVars.getOrElseUpdate(sym,
275-
if sym.ownersIterator.exists(_.isTerm) then CaptureSet.Var()
279+
if sym.ownersIterator.exists(_.isTerm) then
280+
CaptureSet.Var(if sym.isConstructor then sym.owner.owner else sym.owner)
276281
else CaptureSet.empty)
277282

278283
/** For all nested environments up to `limit` or a closed environment perform `op`,
@@ -655,9 +660,9 @@ class CheckCaptures extends Recheck, SymTransformer:
655660
val saved = curEnv
656661
tree match
657662
case _: RefTree | closureDef(_) if pt.isBoxedCapturing =>
658-
curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(), curEnv)
663+
curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner), curEnv)
659664
case _ if tree.hasAttachment(ClosureBodyValue) =>
660-
curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(), curEnv)
665+
curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner), curEnv)
661666
case _ =>
662667
val res =
663668
try super.recheck(tree, pt)
@@ -674,13 +679,10 @@ class CheckCaptures extends Recheck, SymTransformer:
674679
* of simulated boxing and unboxing.
675680
*/
676681
override def recheckFinish(tpe: Type, tree: Tree, pt: Type)(using Context): Type =
677-
val typeToCheck = tree match
678-
case _: Ident | _: Select | _: Apply | _: TypeApply if tree.symbol.unboxesResult =>
679-
tpe
680-
case _: Try =>
681-
tpe
682-
case _ =>
683-
NoType
682+
def needsUniversalCheck = tree match
683+
case _: RefTree | _: Apply | _: TypeApply => tree.symbol.unboxesResult
684+
case _: Try => true
685+
case _ => false
684686
def checkNotUniversal(tp: Type): Unit = tp.widenDealias match
685687
case wtp @ CapturingType(parent, refs) =>
686688
refs.disallowRootCapability { () =>
@@ -691,8 +693,10 @@ class CheckCaptures extends Recheck, SymTransformer:
691693
}
692694
checkNotUniversal(parent)
693695
case _ =>
694-
if !allowUniversalInBoxed then checkNotUniversal(typeToCheck)
696+
if !allowUniversalInBoxed && needsUniversalCheck then
697+
checkNotUniversal(tpe)
695698
super.recheckFinish(tpe, tree, pt)
699+
end recheckFinish
696700

697701
// ------------------ Adaptation -------------------------------------
698702
//
@@ -782,6 +786,12 @@ class CheckCaptures extends Recheck, SymTransformer:
782786
*/
783787
def adaptBoxed(actual: Type, expected: Type, pos: SrcPos, alwaysConst: Boolean = false)(using Context): Type =
784788

789+
inline def inNestedEnv[T](boxed: Boolean)(op: => T): T =
790+
val saved = curEnv
791+
curEnv = Env(curEnv.owner, EnvKind.NestedInOwner, CaptureSet.Var(curEnv.owner), if boxed then null else curEnv)
792+
try op
793+
finally curEnv = saved
794+
785795
/** Adapt function type `actual`, which is `aargs -> ares` (possibly with dependencies)
786796
* to `expected` type.
787797
* It returns the adapted type along with a capture set consisting of the references
@@ -791,10 +801,7 @@ class CheckCaptures extends Recheck, SymTransformer:
791801
def adaptFun(actual: Type, aargs: List[Type], ares: Type, expected: Type,
792802
covariant: Boolean, boxed: Boolean,
793803
reconstruct: (List[Type], Type) => Type): (Type, CaptureSet) =
794-
val saved = curEnv
795-
curEnv = Env(curEnv.owner, EnvKind.NestedInOwner, CaptureSet.Var(), if boxed then null else curEnv)
796-
797-
try
804+
inNestedEnv(boxed):
798805
val (eargs, eres) = expected.dealias.stripCapturing match
799806
case defn.FunctionOf(eargs, eres, _) => (eargs, eres)
800807
case expected: MethodType => (expected.paramInfos, expected.resType)
@@ -808,8 +815,6 @@ class CheckCaptures extends Recheck, SymTransformer:
808815
else reconstruct(aargs1, ares1)
809816

810817
(resTp, curEnv.captured)
811-
finally
812-
curEnv = saved
813818

814819
/** Adapt type function type `actual` to the expected type.
815820
* @see [[adaptFun]]
@@ -818,10 +823,7 @@ class CheckCaptures extends Recheck, SymTransformer:
818823
actual: Type, ares: Type, expected: Type,
819824
covariant: Boolean, boxed: Boolean,
820825
reconstruct: Type => Type): (Type, CaptureSet) =
821-
val saved = curEnv
822-
curEnv = Env(curEnv.owner, EnvKind.NestedInOwner, CaptureSet.Var(), if boxed then null else curEnv)
823-
824-
try
826+
inNestedEnv(boxed):
825827
val eres = expected.dealias.stripCapturing match
826828
case RefinedType(_, _, rinfo: PolyType) => rinfo.resType
827829
case expected: PolyType => expected.resType
@@ -834,8 +836,6 @@ class CheckCaptures extends Recheck, SymTransformer:
834836
else reconstruct(ares1)
835837

836838
(resTp, curEnv.captured)
837-
finally
838-
curEnv = saved
839839
end adaptTypeFun
840840

841841
def adaptInfo(actual: Type, expected: Type, covariant: Boolean): String =
@@ -977,16 +977,16 @@ class CheckCaptures extends Recheck, SymTransformer:
977977
traverseChildren(t)
978978

979979
override def checkUnit(unit: CompilationUnit)(using Context): Unit =
980-
Setup(preRecheckPhase, thisPhase, recheckDef)(ctx.compilationUnit.tpdTree)
981-
//println(i"SETUP:\n${Recheck.addRecheckedTypes.transform(ctx.compilationUnit.tpdTree)}")
982-
withCaptureSetsExplained {
983-
super.checkUnit(unit)
984-
checkOverrides.traverse(unit.tpdTree)
985-
checkSelfTypes(unit.tpdTree)
986-
postCheck(unit.tpdTree)
987-
if ctx.settings.YccDebug.value then
988-
show(unit.tpdTree) // this does not print tree, but makes its variables visible for dependency printing
989-
}
980+
inContext(ctx.withProperty(NestingLevels, Some(new mutable.HashMap[Symbol, Int]))):
981+
Setup(preRecheckPhase, thisPhase, this)(ctx.compilationUnit.tpdTree)
982+
//println(i"SETUP:\n${Recheck.addRecheckedTypes.transform(ctx.compilationUnit.tpdTree)}")
983+
withCaptureSetsExplained:
984+
super.checkUnit(unit)
985+
checkOverrides.traverse(unit.tpdTree)
986+
checkSelfTypes(unit.tpdTree)
987+
postCheck(unit.tpdTree)
988+
if ctx.settings.YccDebug.value then
989+
show(unit.tpdTree) // this does not print tree, but makes its variables visible for dependency printing
990990

991991
/** Check that self types of subclasses conform to self types of super classes.
992992
* (See comment below how this is achieved). The check assumes that classes
@@ -1139,6 +1139,8 @@ class CheckCaptures extends Recheck, SymTransformer:
11391139
|| // non-local symbols cannot have inferred types since external capture types are not inferred
11401140
isLocal // local symbols still need explicit types if
11411141
&& !sym.owner.is(Trait) // they are defined in a trait, since we do OverridingPairs checking before capture inference
1142+
||
1143+
sym.allOverriddenSymbols.nonEmpty
11421144
def isNotPureThis(ref: CaptureRef) = ref match {
11431145
case ref: ThisType => !ref.cls.isPureClass
11441146
case _ => true

0 commit comments

Comments
 (0)