Skip to content

Commit ab71be7

Browse files
authored
Simplify Level Checking and Fix Errors (#23934)
Drop the separate level number infrastructure (which was too coarse anyway) and base level checking exclsuively on owners.
2 parents 9bd7774 + de35cbb commit ab71be7

27 files changed

+360
-278
lines changed

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

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -21,34 +21,6 @@ class CCState:
2121
*/
2222
val approxWarnings: mutable.ListBuffer[Message] = mutable.ListBuffer()
2323

24-
// ------ Level handling ---------------------------
25-
26-
private var curLevel: Level = outermostLevel
27-
28-
/** The level of the current environment. Levels start at 0 and increase for
29-
* each nested function or class. -1 means the level is undefined.
30-
*/
31-
def currentLevel(using Context): Level = curLevel
32-
33-
/** Perform `op` in the next inner level */
34-
inline def inNestedLevel[T](inline op: T)(using Context): T =
35-
val saved = curLevel
36-
curLevel = curLevel.nextInner
37-
try op finally curLevel = saved
38-
39-
/** Perform `op` in the next inner level unless `p` holds. */
40-
inline def inNestedLevelUnless[T](inline p: Boolean)(inline op: T)(using Context): T =
41-
val saved = curLevel
42-
if !p then curLevel = curLevel.nextInner
43-
try op finally curLevel = saved
44-
45-
/** A map recording the level of a symbol */
46-
private val mySymLevel: mutable.Map[Symbol, Level] = mutable.Map()
47-
48-
def symLevel(sym: Symbol): Level = mySymLevel.getOrElse(sym, undefinedLevel)
49-
50-
def recordLevel(sym: Symbol)(using Context): Unit = mySymLevel(sym) = curLevel
51-
5224
// ------ BiTypeMap adjustment -----------------------
5325

5426
private var myMapFutureElems = true
@@ -117,17 +89,6 @@ class CCState:
11789

11890
object CCState:
11991

120-
opaque type Level = Int
121-
122-
val undefinedLevel: Level = -1
123-
124-
val outermostLevel: Level = 0
125-
126-
extension (x: Level)
127-
def isDefined: Boolean = x >= 0
128-
def <= (y: Level) = (x: Int) <= y
129-
def nextInner: Level = if isDefined then x + 1 else x
130-
13192
/** If we are currently in capture checking or setup, and `mt` is a method
13293
* type that is not a prefix of a curried method, perform `op` assuming
13394
* a fresh enclosing existential scope `mt`, otherwise perform `op` directly.

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

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import CaptureSet.VarState
1616
import Annotations.Annotation
1717
import Flags.*
1818
import config.Printers.capt
19-
import CCState.{Level, undefinedLevel}
2019
import annotation.constructorOnly
2120
import ast.tpd
2221
import printing.{Printer, Showable}
@@ -168,6 +167,12 @@ object Capabilities:
168167
case _ => false
169168

170169
/** Is this fresh cap at the right level to be able to subsume `ref`?
170+
* Only outer freshes can be subsumed.
171+
* TODO Can we merge this with levelOK? Right now we use two different schemes:
172+
* - For level checking capsets with levelOK: Check that the visibility of the element
173+
* os not properly contained in the captset owner.
174+
* - For level checking elements subsumed by FreshCaps: Check that the widened scope
175+
* (using levelOwner) of the elements contains the owner of the FreshCap.
171176
*/
172177
def acceptsLevelOf(ref: Capability)(using Context): Boolean =
173178
if ccConfig.useFreshLevels && !CCState.collapseFresh then
@@ -483,19 +488,26 @@ object Capabilities:
483488
case self: FreshCap => self.hiddenSet.owner
484489
case _ /* : GlobalCap | ResultCap | ParamRef */ => NoSymbol
485490

491+
final def visibility(using Context): Symbol = this match
492+
case self: FreshCap => adjustOwner(ccOwner)
493+
case _ =>
494+
val vis = ccOwner
495+
if vis.is(Param) then vis.owner else vis
496+
486497
/** The symbol that represents the level closest-enclosing ccOwner.
487498
* Symbols representing levels are
488499
* - class symbols, but not inner (non-static) module classes
489500
* - method symbols, but not accessors or constructors
490501
*/
491502
final def levelOwner(using Context): Symbol =
492-
def adjust(owner: Symbol): Symbol =
493-
if !owner.exists
494-
|| owner.isClass && (!owner.is(Flags.Module) || owner.isStatic)
495-
|| owner.is(Flags.Method, butNot = Flags.Accessor) && !owner.isConstructor
496-
then owner
497-
else adjust(owner.owner)
498-
adjust(ccOwner)
503+
adjustOwner(ccOwner)
504+
505+
private def adjustOwner(owner: Symbol)(using Context): Symbol =
506+
if !owner.exists
507+
|| owner.isClass && (!owner.is(Flags.Module) || owner.isStatic)
508+
|| owner.is(Flags.Method, butNot = Flags.Accessor)
509+
then owner
510+
else adjustOwner(owner.owner)
499511

500512
/** Tests whether the capability derives from capability class `cls`. */
501513
def derivesFromCapTrait(cls: ClassSymbol)(using Context): Boolean = this match
@@ -1033,7 +1045,13 @@ object Capabilities:
10331045
override def mapCapability(c: Capability, deep: Boolean) = c match
10341046
case c @ ResultCap(binder) =>
10351047
if localBinders.contains(binder) then c // keep bound references
1036-
else seen.getOrElseUpdate(c, FreshCap(origin)) // map free references to FreshCap
1048+
else
1049+
// Create a fresh skolem that does not subsume anything
1050+
def freshSkolem =
1051+
val c = FreshCap(origin)
1052+
c.hiddenSet.markSolved(provisional = false)
1053+
c
1054+
seen.getOrElseUpdate(c, freshSkolem) // map free references to FreshCap
10371055
case _ => super.mapCapability(c, deep)
10381056
end subst
10391057

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

Lines changed: 37 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,6 @@ sealed abstract class CaptureSet extends Showable:
7676
/** Is this set provisionally solved, so that another cc run might unfreeze it? */
7777
def isProvisionallySolved(using Context): Boolean
7878

79-
/** An optional level limit, or undefinedLevel if none exists. All elements of the set
80-
* must be at levels equal or smaller than the level of the set, if it is defined.
81-
*/
82-
def level: Level
83-
8479
/** An optional owner, or NoSymbol if none exists. Used for diagnstics
8580
*/
8681
def owner: Symbol
@@ -394,7 +389,13 @@ sealed abstract class CaptureSet extends Showable:
394389
if mappedElems == elems then this
395390
else Const(mappedElems)
396391
else if ccState.mapFutureElems then
397-
def unfused = BiMapped(asVar, tm, mappedElems)
392+
def unfused =
393+
if debugVars then
394+
try BiMapped(asVar, tm, mappedElems)
395+
catch case ex: AssertionError =>
396+
println(i"error while mapping $this")
397+
throw ex
398+
else BiMapped(asVar, tm, mappedElems)
398399
this match
399400
case self: BiMapped => self.bimap.fuse(tm) match
400401
case Some(fused: BiTypeMap) => BiMapped(self.source, fused, mappedElems)
@@ -601,8 +602,6 @@ object CaptureSet:
601602

602603
def withDescription(description: String): Const = Const(elems, description)
603604

604-
def level = undefinedLevel
605-
606605
def owner = NoSymbol
607606

608607
def dropEmpties()(using Context) = this
@@ -652,8 +651,12 @@ object CaptureSet:
652651
override def toString = "<fluid>"
653652
end Fluid
654653

654+
/** If true emit info when var with id debugTarget is created or gets a new element */
655+
inline val debugVars = false
656+
inline val debugTarget = 1745
657+
655658
/** The subclass of captureset variables with given initial elements */
656-
class Var(initialOwner: Symbol = NoSymbol, initialElems: Refs = emptyRefs, val level: Level = undefinedLevel, underBox: Boolean = false)(using @constructorOnly ictx: Context) extends CaptureSet:
659+
class Var(initialOwner: Symbol = NoSymbol, initialElems: Refs = emptyRefs, underBox: Boolean = false)(using /*@constructorOnly*/ ictx: Context) extends CaptureSet:
657660

658661
override def owner = initialOwner
659662

@@ -678,8 +681,15 @@ object CaptureSet:
678681
/** The elements currently known to be in the set */
679682
protected var myElems: Refs = initialElems
680683

684+
if debugVars && id == debugTarget then
685+
println(i"###INIT ELEMS of $id to $initialElems")
686+
assert(false)
687+
681688
def elems: Refs = myElems
682-
def elems_=(refs: Refs): Unit = myElems = refs
689+
def elems_=(refs: Refs): Unit =
690+
if debugVars && id == debugTarget then
691+
println(i"###SET ELEMS of $id to $refs")
692+
myElems = refs
683693

684694
/** The sets currently known to be dependent sets (i.e. new additions to this set
685695
* are propagated to these dependent sets.)
@@ -762,6 +772,8 @@ object CaptureSet:
762772

763773
protected def includeElem(elem: Capability)(using Context): Unit =
764774
if !elems.contains(elem) then
775+
if debugVars && id == debugTarget then
776+
println(i"###INCLUDE $elem in $this")
765777
elems += elem
766778
TypeComparer.logUndoAction: () =>
767779
elems -= elem
@@ -815,37 +827,17 @@ object CaptureSet:
815827
find(false, binder)
816828

817829
def levelOK(elem: Capability)(using Context): Boolean = elem match
818-
case _: FreshCap =>
819-
!level.isDefined
820-
|| ccState.symLevel(elem.ccOwner) <= level
821-
|| {
822-
capt.println(i"LEVEL ERROR $elem cannot be included in $this of $owner")
823-
false
824-
}
825830
case elem @ ResultCap(binder) =>
826831
rootLimit == null && (this.isInstanceOf[BiMapped] || isPartOf(binder.resType))
827832
case GlobalCap =>
828833
rootLimit == null
829-
case elem: TermRef if level.isDefined =>
830-
elem.prefix match
831-
case prefix: Capability =>
832-
levelOK(prefix)
833-
case _ =>
834-
ccState.symLevel(elem.symbol) <= level
835-
case elem: ThisType if level.isDefined =>
836-
ccState.symLevel(elem.cls).nextInner <= level
837-
case elem: ParamRef if !this.isInstanceOf[BiMapped] =>
838-
isPartOf(elem.binder.resType)
839-
|| {
840-
capt.println(
841-
i"""LEVEL ERROR $elem for $this
842-
|elem binder = ${elem.binder}""")
843-
false
844-
}
845-
case elem: DerivedCapability =>
846-
levelOK(elem.underlying)
834+
case elem: ParamRef =>
835+
this.isInstanceOf[BiMapped] || isPartOf(elem.binder.resType)
847836
case _ =>
848-
true
837+
if owner.exists then
838+
val elemVis = elem.visibility
839+
!elemVis.isProperlyContainedIn(owner)
840+
else true
849841

850842
def addDependent(cs: CaptureSet)(using Context, VarState): Boolean =
851843
(cs eq this)
@@ -926,15 +918,9 @@ object CaptureSet:
926918
*/
927919
override def optionalInfo(using Context): String =
928920
for vars <- ctx.property(ShownVars) do vars += this
929-
val debugInfo =
930-
if !ctx.settings.YccDebug.value then ""
931-
else if isConst then ids ++ "(solved)"
932-
else ids
933-
val limitInfo =
934-
if ctx.settings.YprintLevel.value && level.isDefined
935-
then i"<at level ${level.toString}>"
936-
else ""
937-
debugInfo ++ limitInfo
921+
if !ctx.settings.YccDebug.value then ""
922+
else if isConst then ids ++ "(solved)"
923+
else ids
938924

939925
/** Used for diagnostics and debugging: A string that traces the creation
940926
* history of a variable by following source links. Each variable on the
@@ -1206,6 +1192,7 @@ object CaptureSet:
12061192
if alias ne this then alias.add(elem)
12071193
else
12081194
def addToElems() =
1195+
assert(!isConst)
12091196
includeElem(elem)
12101197
deps.foreach: dep =>
12111198
assert(dep != this)
@@ -1335,7 +1322,7 @@ object CaptureSet:
13351322
override def toText(printer: Printer): Text =
13361323
inContext(printer.printerContext):
13371324
if levelError then
1338-
i"($elem at wrong level for $cs at level ${cs.level.toString})"
1325+
i"($elem at wrong level for $cs in ${cs.owner.showLocated})"
13391326
else
13401327
if ctx.settings.YccDebug.value
13411328
then i"$elem cannot be included in $trace"
@@ -1378,8 +1365,10 @@ object CaptureSet:
13781365
* but the special state VarState.Separate overrides this.
13791366
*/
13801367
def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean =
1381-
hidden.add(elem)(using ctx, this)
1382-
true
1368+
if hidden.isConst then false
1369+
else
1370+
hidden.add(elem)(using ctx, this)
1371+
true
13831372

13841373
/** If root1 and root2 belong to the same binder but have different originalBinders
13851374
* it means that one of the roots was mapped to the binder of the other by a

0 commit comments

Comments
 (0)