Skip to content

Commit 0827546

Browse files
authored
Standardize on log-based undo (#23357)
Drop the snapshot scheme in VarState. The previous scheme did leak instantiations from failed subtype comparisons, since a subtype comparison might have several subCaptures tests. If a first subCaptures test succeeds then anything it instantiated is made permanent, even if a subsequent test and the whole subType comparison fails. The change needed updates in several neg test files where we find that fewer things are instantiated in error messages.
2 parents 1b5138d + 2dea57d commit 0827546

21 files changed

+118
-206
lines changed

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

Lines changed: 46 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,7 @@ sealed abstract class CaptureSet extends Showable:
178178

179179
/** Try to include all element in `refs` to this capture set. */
180180
protected final def tryInclude(newElems: Refs, origin: CaptureSet)(using Context, VarState): Boolean =
181-
TypeComparer.inNestedLevel:
182-
// Run in nested level so that a error notes for a failure here can be
183-
// cancelled in case the whole comparison succeeds.
184-
// We do this here because all nested tryInclude and subCaptures calls go
185-
// through this method.
186-
newElems.forall(tryInclude(_, origin))
181+
newElems.forall(tryInclude(_, origin))
187182

188183
protected def mutableToReader(origin: CaptureSet)(using Context): Boolean =
189184
if mutability == Mutable then toReader() else true
@@ -284,16 +279,14 @@ sealed abstract class CaptureSet extends Showable:
284279

285280
/** The subcapturing test, using a given VarState */
286281
final def subCaptures(that: CaptureSet)(using ctx: Context, vs: VarState = VarState()): Boolean =
287-
val this1 = this.adaptMutability(that)
288-
if this1 == null then false
289-
else if this1 ne this then
290-
capt.println(i"WIDEN ro $this with ${this.mutability} <:< $that with ${that.mutability} to $this1")
291-
this1.subCaptures(that, vs)
292-
else if that.tryInclude(elems, this) then
293-
addDependent(that)
294-
else
295-
varState.rollBack()
296-
false
282+
TypeComparer.inNestedLevel:
283+
val this1 = this.adaptMutability(that)
284+
if this1 == null then false
285+
else if this1 ne this then
286+
capt.println(i"WIDEN ro $this with ${this.mutability} <:< $that with ${that.mutability} to $this1")
287+
this1.subCaptures(that, vs)
288+
else
289+
that.tryInclude(elems, this) && addDependent(that)
297290

298291
/** Two capture sets are considered =:= equal if they mutually subcapture each other
299292
* in a frozen state.
@@ -662,30 +655,6 @@ object CaptureSet:
662655

663656
var description: String = ""
664657

665-
/** Record current elements in given VarState provided it does not yet
666-
* contain an entry for this variable.
667-
*/
668-
private def recordElemsState()(using VarState): Boolean =
669-
varState.getElems(this) match
670-
case None => varState.putElems(this, elems)
671-
case _ => true
672-
673-
/** Record current dependent sets in given VarState provided it does not yet
674-
* contain an entry for this variable.
675-
*/
676-
private[CaptureSet] def recordDepsState()(using VarState): Boolean =
677-
varState.getDeps(this) match
678-
case None => varState.putDeps(this, deps)
679-
case _ => true
680-
681-
/** Reset elements to what was recorded in `state` */
682-
def resetElems()(using state: VarState): Unit =
683-
elems = state.elems(this)
684-
685-
/** Reset dependent sets to what was recorded in `state` */
686-
def resetDeps()(using state: VarState): Unit =
687-
deps = state.deps(this)
688-
689658
/** Check that all maps recorded in skippedMaps map `elem` to itself
690659
* or something subsumed by it.
691660
*/
@@ -695,16 +664,28 @@ object CaptureSet:
695664
assert(elem.subsumes(elem1),
696665
i"Skipped map ${tm.getClass} maps newly added $elem to $elem1 in $this")
697666

667+
protected def includeElem(elem: Capability)(using Context): Unit =
668+
if !elems.contains(elem) then
669+
elems += elem
670+
TypeComparer.logUndoAction: () =>
671+
elems -= elem
672+
673+
def includeDep(cs: CaptureSet)(using Context): Unit =
674+
if !deps.contains(cs) then
675+
deps += cs
676+
TypeComparer.logUndoAction: () =>
677+
deps -= cs
678+
698679
final def addThisElem(elem: Capability)(using Context, VarState): Boolean =
699-
if isConst || !recordElemsState() then // Fail if variable is solved or given VarState is frozen
680+
if isConst || !varState.canRecord then // Fail if variable is solved or given VarState is frozen
700681
addIfHiddenOrFail(elem)
701682
else if !levelOK(elem) then
702683
failWith(IncludeFailure(this, elem, levelError = true)) // or `elem` is not visible at the level of the set.
703684
else
704685
// id == 108 then assert(false, i"trying to add $elem to $this")
705686
assert(elem.isWellformed, elem)
706687
assert(!this.isInstanceOf[HiddenSet] || summon[VarState].isSeparating, summon[VarState])
707-
elems += elem
688+
includeElem(elem)
708689
if isBadRoot(rootLimit, elem) then
709690
rootAddedHandler()
710691
newElemAddedHandler(elem)
@@ -772,7 +753,7 @@ object CaptureSet:
772753
(cs eq this)
773754
|| cs.isUniversal
774755
|| isConst
775-
|| recordDepsState() && { deps += cs; true }
756+
|| varState.canRecord && { includeDep(cs); true }
776757

777758
override def disallowRootCapability(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): this.type =
778759
rootLimit = upto
@@ -1126,7 +1107,7 @@ object CaptureSet:
11261107
if alias ne this then alias.add(elem)
11271108
else
11281109
def addToElems() =
1129-
elems += elem
1110+
includeElem(elem)
11301111
deps.foreach: dep =>
11311112
assert(dep != this)
11321113
vs.addHidden(dep.asInstanceOf[HiddenSet], elem)
@@ -1142,7 +1123,7 @@ object CaptureSet:
11421123
deps = SimpleIdentitySet(elem.hiddenSet)
11431124
else
11441125
addToElems()
1145-
elem.hiddenSet.deps += this
1126+
elem.hiddenSet.includeDep(this)
11461127
case _ =>
11471128
addToElems()
11481129

@@ -1238,41 +1219,15 @@ object CaptureSet:
12381219
*/
12391220
class VarState:
12401221

1241-
/** A map from captureset variables to their elements at the time of the snapshot. */
1242-
private val elemsMap: util.EqHashMap[Var, Refs] = new util.EqHashMap
1243-
1244-
/** A map from captureset variables to their dependent sets at the time of the snapshot. */
1245-
private val depsMap: util.EqHashMap[Var, Deps] = new util.EqHashMap
1246-
12471222
/** A map from ResultCap values to other ResultCap values. If two result values
12481223
* `a` and `b` are unified, then `eqResultMap(a) = b` and `eqResultMap(b) = a`.
12491224
*/
12501225
private var eqResultMap: util.SimpleIdentityMap[ResultCap, ResultCap] = util.SimpleIdentityMap.empty
12511226

1252-
/** A snapshot of the `eqResultMap` value at the start of a VarState transaction */
1253-
private var eqResultSnapshot: util.SimpleIdentityMap[ResultCap, ResultCap] | Null = null
1254-
1255-
/** The recorded elements of `v` (it's required that a recording was made) */
1256-
def elems(v: Var): Refs = elemsMap(v)
1257-
1258-
/** Optionally the recorded elements of `v`, None if nothing was recorded for `v` */
1259-
def getElems(v: Var): Option[Refs] = elemsMap.get(v)
1260-
12611227
/** Record elements, return whether this was allowed.
12621228
* By default, recording is allowed in regular but not in frozen states.
12631229
*/
1264-
def putElems(v: Var, elems: Refs): Boolean = { elemsMap(v) = elems; true }
1265-
1266-
/** The recorded dependent sets of `v` (it's required that a recording was made) */
1267-
def deps(v: Var): Deps = depsMap(v)
1268-
1269-
/** Optionally the recorded dependent sets of `v`, None if nothing was recorded for `v` */
1270-
def getDeps(v: Var): Option[Deps] = depsMap.get(v)
1271-
1272-
/** Record dependent sets, return whether this was allowed.
1273-
* By default, recording is allowed in regular but not in frozen states.
1274-
*/
1275-
def putDeps(v: Var, deps: Deps): Boolean = { depsMap(v) = deps; true }
1230+
def canRecord: Boolean = true
12761231

12771232
/** Does this state allow additions of elements to capture set variables? */
12781233
def isOpen = true
@@ -1283,11 +1238,6 @@ object CaptureSet:
12831238
* but the special state VarState.Separate overrides this.
12841239
*/
12851240
def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean =
1286-
elemsMap.get(hidden) match
1287-
case None =>
1288-
elemsMap(hidden) = hidden.elems
1289-
depsMap(hidden) = hidden.deps
1290-
case _ =>
12911241
hidden.add(elem)(using ctx, this)
12921242
true
12931243

@@ -1311,17 +1261,13 @@ object CaptureSet:
13111261
&& eqResultMap(c1) == null
13121262
&& eqResultMap(c2) == null
13131263
&& {
1314-
if eqResultSnapshot == null then eqResultSnapshot = eqResultMap
13151264
eqResultMap = eqResultMap.updated(c1, c2).updated(c2, c1)
1265+
TypeComparer.logUndoAction: () =>
1266+
eqResultMap.remove(c1)
1267+
eqResultMap.remove(c2)
13161268
true
13171269
}
13181270

1319-
/** Roll back global state to what was recorded in this VarState */
1320-
def rollBack(): Unit =
1321-
elemsMap.keysIterator.foreach(_.resetElems()(using this))
1322-
depsMap.keysIterator.foreach(_.resetDeps()(using this))
1323-
if eqResultSnapshot != null then eqResultMap = eqResultSnapshot.nn
1324-
13251271
private var seen: util.EqHashSet[Capability] = new util.EqHashSet
13261272

13271273
/** Run test `pred` unless `ref` was seen in an enclosing `ifNotSeen` operation */
@@ -1341,8 +1287,7 @@ object CaptureSet:
13411287
* subsume arbitary types, which are then recorded in their hidden sets.
13421288
*/
13431289
class Closed extends VarState:
1344-
override def putElems(v: Var, refs: Refs) = false
1345-
override def putDeps(v: Var, deps: Deps) = false
1290+
override def canRecord = false
13461291
override def isOpen = false
13471292
override def toString = "closed varState"
13481293

@@ -1366,23 +1311,29 @@ object CaptureSet:
13661311
*/
13671312
def HardSeparate(using Context): Separating = ccState.HardSeparate
13681313

1369-
/** A special state that turns off recording of elements. Used only
1370-
* in `addSub` to prevent cycles in recordings. Instantiated in ccState.Unrecorded.
1371-
*/
1372-
class Unrecorded extends VarState:
1373-
override def putElems(v: Var, refs: Refs) = true
1374-
override def putDeps(v: Var, deps: Deps) = true
1375-
override def rollBack(): Unit = ()
1314+
/** A mixin trait that overrides the addHidden and unify operations to
1315+
* not depend in state. */
1316+
trait Stateless extends VarState:
1317+
1318+
/** Allow adding hidden elements, but don't store them */
13761319
override def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean = true
1320+
1321+
/** Don't allow to unify result caps */
1322+
override def unify(c1: ResultCap, c2: ResultCap)(using Context): Boolean = false
1323+
end Stateless
1324+
1325+
/** An open state that turns off recording of hidden elements (but allows
1326+
* adding them). Used in `addAsDependentTo`. Instantiated in ccState.Unrecorded.
1327+
*/
1328+
class Unrecorded extends VarState, Stateless:
13771329
override def toString = "unrecorded varState"
13781330

13791331
def Unrecorded(using Context): Unrecorded = ccState.Unrecorded
13801332

13811333
/** A closed state that turns off recording of hidden elements (but allows
13821334
* adding them). Used in `mightAccountFor`. Instantiated in ccState.ClosedUnrecorded.
13831335
*/
1384-
class ClosedUnrecorded extends Closed:
1385-
override def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean = true
1336+
class ClosedUnrecorded extends Closed, Stateless:
13861337
override def toString = "closed unrecorded varState"
13871338

13881339
def ClosedUnrecorded(using Context): ClosedUnrecorded = ccState.ClosedUnrecorded

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
5353
needsGc = false
5454
maxErrorLevel = -1
5555
errorNotes = Nil
56-
logSize = 0
56+
undoLog.clear()
5757
if Config.checkTypeComparerReset then checkReset()
5858

5959
private var pendingSubTypes: util.MutableSet[(Type, Type)] | Null = null
@@ -63,8 +63,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
6363
private var maxErrorLevel: Int = -1
6464
protected var errorNotes: List[(Int, ErrorNote)] = Nil
6565

66-
private val undoLog = mutable.ArrayBuffer[() => Unit]()
67-
private var logSize = 0
66+
val undoLog = mutable.ArrayBuffer[() => Unit]()
6867

6968
private var needsGc = false
7069

@@ -1588,18 +1587,20 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
15881587
undoLog(i)()
15891588
i += 1
15901589
undoLog.takeInPlace(prevSize)
1590+
assert(undoLog.size == prevSize)
15911591

15921592
// begin recur
15931593
if tp2 eq NoType then false
15941594
else if tp1 eq tp2 then true
15951595
else
15961596
val savedCstr = constraint
15971597
val savedGadt = ctx.gadt
1598-
val savedLogSize = logSize
1598+
val savedLogSize = undoLog.size
15991599
inline def restore() =
16001600
state.constraint = savedCstr
16011601
ctx.gadtState.restore(savedGadt)
16021602
if undoLog.size != savedLogSize then
1603+
//println(i"ROLLBACK $tp1 <:< $tp2")
16031604
rollBack(savedLogSize)
16041605
val savedSuccessCount = successCount
16051606
try
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt-depfun.scala:11:43 ----------------------------------
2-
11 | val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error // error: separatioon
2+
11 | val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error
33
| ^^^^^^^
44
| Found: Str^{} ->{ac, y, z} Str^{y, z}
55
| Required: Str^{y, z} => Str^{y, z}
66
|
77
| where: => refers to a fresh root capability in the type of value dc
88
|
99
| longer explanation available when compiling with `-explain`
10-
-- Error: tests/neg-custom-args/captures/capt-depfun.scala:11:24 -------------------------------------------------------
11-
11 | val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error // error: separatioon
12-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
13-
| Separation failure: value dc's type Str^{y, z} => Str^{y, z} hides parameters y and z.
14-
| The parameters need to be annotated with @consume to allow this.

tests/neg-custom-args/captures/capt-depfun.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ class Str
88
def f(y: Cap, z: Cap) =
99
def g(): C @retains[y.type | z.type] = ???
1010
val ac: ((x: Cap) => Str @retains[x.type] => Str @retains[x.type]) = ???
11-
val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error // error: separatioon
11+
val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error

tests/neg-custom-args/captures/depfun-reach.check

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,3 @@
2727
| =>² refers to a fresh root capability in the type of value b
2828
|
2929
| longer explanation available when compiling with `-explain`
30-
-- Error: tests/neg-custom-args/captures/depfun-reach.scala:18:17 ------------------------------------------------------
31-
18 | : (xs: List[(X, () ->{io} Unit)]) => List[() ->{} Unit] = // error
32-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33-
|Separation failure: method foo's result type (xs: List[(X, () ->{io} Unit)]) => List[() -> Unit] hides parameter op.
34-
|The parameter needs to be annotated with @consume to allow this.

tests/neg-custom-args/captures/depfun-reach.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def test(io: Object^, async: Object^) =
1515
compose(op)
1616

1717
def foo[X](op: (xs: List[(X, () ->{io} Unit)]) => List[() ->{xs*} Unit])
18-
: (xs: List[(X, () ->{io} Unit)]) => List[() ->{} Unit] = // error
18+
: (xs: List[(X, () ->{io} Unit)]) => List[() ->{} Unit] =
1919
op // error
2020

2121
def boom(op: List[(() ->{async} Unit, () ->{io} Unit)]): List[() ->{} Unit] =

tests/neg-custom-args/captures/effect-swaps-explicit.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,21 @@
1717
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:69:10 ------------------------
1818
69 | Future: fut ?=> // error, type mismatch
1919
| ^
20-
|Found: (contextual$9: boundary.Label[Result[Future[T^?]^?, E^?]^?]^) ?->{fr, async} Future[T^?]^{fr, contextual$9}
21-
|Required: (boundary.Label[Result[Future[T^?]^{fr, async}, E^?]]^) ?=> Future[T^?]^{fr, async}
20+
|Found: (contextual$9: boundary.Label[Result[Future[T^?]^?, E^?]^?]^?) ?->{fr, async} Future[T^?]^{fr, contextual$9}
21+
|Required: (boundary.Label[Result[Future[T^?]^?, E^?]]^) ?=> Future[T^?]^?
2222
|
2323
|where: ?=> refers to a fresh root capability created in method fail4 when checking argument to parameter body of method make
2424
| ^ refers to the universal root capability
2525
|
2626
|Note that reference contextual$9.type
27-
|cannot be included in outer capture set {fr, async}
27+
|cannot be included in outer capture set ?
2828
70 | fr.await.ok
2929
|
3030
| longer explanation available when compiling with `-explain`
3131
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:73:35 ------------------------
3232
73 | Result.make[Future[T], E]: lbl ?=> // error: type mismatch
3333
| ^
34-
|Found: (lbl: boundary.Label[Result[Future[T^?]^?, E^?]^?]^) ?->{fr, async} Future[T^?]^{fr, lbl}
34+
|Found: (lbl: boundary.Label[Result[Future[T^?]^?, E^?]^?]^?) ?->{fr, async} Future[T^?]^{fr, lbl}
3535
|Required: (boundary.Label[Result[Future[T], E]]^) ?=> Future[T]
3636
|
3737
|where: ?=> refers to a fresh root capability created in method fail5 when checking argument to parameter body of method make

tests/neg-custom-args/captures/effect-swaps.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,21 @@
1717
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps.scala:69:10 ---------------------------------
1818
69 | Future: fut ?=> // error, type mismatch
1919
| ^
20-
|Found: (contextual$9: boundary.Label[Result[Future[T^?]^?, E^?]^?]^{cap.rd}) ?->{fr, async} Future[T^?]^{fr, contextual$9}
21-
|Required: (boundary.Label[Result[Future[T^?]^{fr, async}, E^?]]^{cap.rd}) ?=> Future[T^?]^{fr, async}
20+
|Found: (contextual$9: boundary.Label[Result[Future[T^?]^?, E^?]^?]^?) ?->{fr, async} Future[T^?]^{fr, contextual$9}
21+
|Required: (boundary.Label[Result[Future[T^?]^?, E^?]]^{cap.rd}) ?=> Future[T^?]^?
2222
|
2323
|where: ?=> refers to a fresh root capability created in method fail4 when checking argument to parameter body of method make
2424
| cap is the universal root capability
2525
|
2626
|Note that reference contextual$9.type
27-
|cannot be included in outer capture set {fr, async}
27+
|cannot be included in outer capture set ?
2828
70 | fr.await.ok
2929
|
3030
| longer explanation available when compiling with `-explain`
3131
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps.scala:73:35 ---------------------------------
3232
73 | Result.make[Future[T], E]: lbl ?=> // error: type mismatch
3333
| ^
34-
|Found: (lbl: boundary.Label[Result[Future[T^?]^?, E^?]^?]^{cap.rd}) ?->{fr, async} Future[T^?]^{fr, lbl}
34+
|Found: (lbl: boundary.Label[Result[Future[T^?]^?, E^?]^?]^?) ?->{fr, async} Future[T^?]^{fr, lbl}
3535
|Required: (boundary.Label[Result[Future[T], E]]^{cap.rd}) ?=> Future[T]
3636
|
3737
|where: ?=> refers to a fresh root capability created in method fail5 when checking argument to parameter body of method make

0 commit comments

Comments
 (0)