Skip to content

Commit 76178ed

Browse files
committed
Refine FrozenState categorizations
- In ++, use a FrozenSepState in order not to pollute hidden sets - This avoids two spurious separation failures in stdlib
1 parent 0a73a26 commit 76178ed

File tree

8 files changed

+58
-40
lines changed

8 files changed

+58
-40
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import config.Feature
1616
import collection.mutable
1717
import CCState.*
1818
import reporting.Message
19+
import CaptureSet.Frozen
1920

2021
private val Captures: Key[CaptureSet] = Key()
2122

@@ -244,7 +245,7 @@ extension (tp: Type)
244245
* the two capture sets are combined.
245246
*/
246247
def capturing(cs: CaptureSet)(using Context): Type =
247-
if (cs.isAlwaysEmpty || cs.isConst && cs.subCaptures(tp.captureSet, frozen = true).isOK)
248+
if (cs.isAlwaysEmpty || cs.isConst && cs.subCaptures(tp.captureSet, Frozen.All).isOK)
248249
&& !cs.keepAlways
249250
then tp
250251
else tp match

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ trait CaptureRef extends TypeProxy, ValueType:
134134
final def invalidateCaches() =
135135
myCaptureSetRunId = NoRunId
136136

137-
import CaptureSet.{VarState, FrozenSepState}
137+
import CaptureSet.{VarState, FrozenAllState}
138138

139139
/** x subsumes x
140140
* x =:= y ==> x subsumes y
@@ -150,7 +150,7 @@ trait CaptureRef extends TypeProxy, ValueType:
150150
*
151151
* TODO: Move to CaptureSet
152152
*/
153-
final def subsumes(y: CaptureRef)(using ctx: Context, vs: VarState = FrozenSepState): Boolean =
153+
final def subsumes(y: CaptureRef)(using ctx: Context, vs: VarState = FrozenAllState): Boolean =
154154

155155
def subsumingRefs(x: Type, y: Type): Boolean = x match
156156
case x: CaptureRef => y match

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

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ sealed abstract class CaptureSet extends Showable:
142142
* capture set.
143143
*/
144144
protected final def addNewElem(elem: CaptureRef)(using Context, VarState): CompareResult =
145-
if elem.isMaxCapability || summon[VarState].isInstanceOf[FrozenState] then
145+
if elem.isMaxCapability || summon[VarState].isInstanceOf[FrozenVarState] then
146146
addThisElem(elem)
147147
else
148148
addThisElem(elem).orElse:
@@ -173,7 +173,7 @@ sealed abstract class CaptureSet extends Showable:
173173
/** {x} <:< this where <:< is subcapturing, but treating all variables
174174
* as frozen.
175175
*/
176-
def accountsFor(x: CaptureRef)(using ctx: Context, vs: VarState = FrozenSepState): Boolean =
176+
def accountsFor(x: CaptureRef)(using ctx: Context, vs: VarState = FrozenAllState): Boolean =
177177

178178
/** Like `refs.exists(p)`, but testing fresh cap instances in refs last */
179179
def existsElem(refs: SimpleIdentitySet[CaptureRef], p: CaptureRef => Boolean): Boolean =
@@ -191,7 +191,10 @@ sealed abstract class CaptureSet extends Showable:
191191
existsElem(elems, _.subsumes(x))
192192
|| !x.isMaxCapability
193193
&& !x.derivesFrom(defn.Caps_CapSet)
194-
&& x.captureSetOfInfo.subCaptures(this, frozen = true).isOK
194+
&& x.captureSetOfInfo.subCaptures(this)(using ctx,
195+
vs match
196+
case vs: FrozenVarState => vs
197+
case _ => FrozenVarState()).isOK
195198

196199
comparer match
197200
case comparer: ExplainingTypeComparer => comparer.traceIndented(debugInfo)(test)
@@ -207,7 +210,7 @@ sealed abstract class CaptureSet extends Showable:
207210
*/
208211
def mightAccountFor(x: CaptureRef)(using Context): Boolean =
209212
reporting.trace(i"$this mightAccountFor $x, ${x.captureSetOfInfo}?", show = true):
210-
elems.exists(_.subsumes(x)(using ctx, FrozenState()))
213+
elems.exists(_.subsumes(x)(using ctx, FrozenVarState()))
211214
|| !x.isMaxCapability
212215
&& {
213216
val elems = x.captureSetOfInfo.elems
@@ -228,8 +231,12 @@ sealed abstract class CaptureSet extends Showable:
228231
* be added when making this test. An attempt to add either
229232
* will result in failure.
230233
*/
231-
final def subCaptures(that: CaptureSet, frozen: Boolean)(using Context): CompareResult =
232-
subCaptures(that)(using ctx, if frozen then FrozenState() else VarState())
234+
final def subCaptures(that: CaptureSet, frozen: Frozen)(using Context): CompareResult =
235+
val state = frozen match
236+
case Frozen.None => VarState()
237+
case Frozen.Vars => FrozenVarState()
238+
case Frozen.All => FrozenAllState
239+
subCaptures(that)(using ctx, state)
233240

234241
/** The subcapturing test, using a given VarState */
235242
private def subCaptures(that: CaptureSet)(using Context, VarState): CompareResult =
@@ -246,16 +253,16 @@ sealed abstract class CaptureSet extends Showable:
246253
* in a frozen state.
247254
*/
248255
def =:= (that: CaptureSet)(using Context): Boolean =
249-
this.subCaptures(that, frozen = true).isOK
250-
&& that.subCaptures(this, frozen = true).isOK
256+
this.subCaptures(that, Frozen.All).isOK
257+
&& that.subCaptures(this, Frozen.All).isOK
251258

252259
/** The smallest capture set (via <:<) that is a superset of both
253260
* `this` and `that`
254261
*/
255262
def ++ (that: CaptureSet)(using Context): CaptureSet =
256-
if this.subCaptures(that, frozen = true).isOK then
263+
if this.subCaptures(that, Frozen.All).isOK then
257264
if that.isAlwaysEmpty && this.keepAlways then this else that
258-
else if that.subCaptures(this, frozen = true).isOK then this
265+
else if that.subCaptures(this, Frozen.All).isOK then this
259266
else if this.isConst && that.isConst then Const(this.elems ++ that.elems)
260267
else Union(this, that)
261268

@@ -267,8 +274,8 @@ sealed abstract class CaptureSet extends Showable:
267274
/** The largest capture set (via <:<) that is a subset of both `this` and `that`
268275
*/
269276
def **(that: CaptureSet)(using Context): CaptureSet =
270-
if this.subCaptures(that, frozen = true).isOK then this
271-
else if that.subCaptures(this, frozen = true).isOK then that
277+
if this.subCaptures(that, Frozen.Vars).isOK then this
278+
else if that.subCaptures(this, Frozen.Vars).isOK then that
272279
else if this.isConst && that.isConst then Const(elemIntersection(this, that))
273280
else Intersection(this, that)
274281

@@ -395,6 +402,12 @@ object CaptureSet:
395402
type Vars = SimpleIdentitySet[Var]
396403
type Deps = SimpleIdentitySet[CaptureSet]
397404

405+
/** An enum indicating a Frozen degree for subCapturing tests */
406+
enum Frozen:
407+
case None // operations are performed in a regular VarState
408+
case Vars // operations are performed in a FrozenVarState
409+
case All // operations are performed in FrozenAllState
410+
398411
@sharable private var varId = 0
399412

400413
/** If set to `true`, capture stack traces that tell us where sets are created */
@@ -538,7 +551,7 @@ object CaptureSet:
538551
else
539552
//if id == 34 then assert(!elem.isUniversalRootCapability)
540553
assert(elem.isTrackableRef, elem)
541-
assert(!this.isInstanceOf[HiddenSet] || summon[VarState] == FrozenSepState, summon[VarState])
554+
assert(!this.isInstanceOf[HiddenSet] || summon[VarState] == FrozenAllState, summon[VarState])
542555
elems += elem
543556
if elem.isRootCapability then
544557
rootAddedHandler()
@@ -1043,7 +1056,7 @@ object CaptureSet:
10431056
def getElems(v: Var): Option[Refs] = elemsMap.get(v)
10441057

10451058
/** Record elements, return whether this was allowed.
1046-
* By default, recording is allowed but the special state FrozenState
1059+
* By default, recording is allowed in regular both not in frozen states.
10471060
* overrides this.
10481061
*/
10491062
def putElems(v: Var, elems: Refs): Boolean = { elemsMap(v) = elems; true }
@@ -1055,14 +1068,14 @@ object CaptureSet:
10551068
def getDeps(v: Var): Option[Deps] = depsMap.get(v)
10561069

10571070
/** Record dependent sets, return whether this was allowed.
1058-
* By default, recording is allowed but the special state FrozenState
1071+
* By default, recording is allowed in regular both not in frozen states.
10591072
* overrides this.
10601073
*/
10611074
def putDeps(v: Var, deps: Deps): Boolean = { depsMap(v) = deps; true }
10621075

10631076
/** Record hidden elements in elemsMap of hidden set `v`,
10641077
* return whether this was allowed. By default, recording is allowed
1065-
* but the special state FrozenSepState overrides this.
1078+
* but the special state FrozenAllState overrides this.
10661079
*/
10671080
def putHidden(v: HiddenSet, elems: Refs): Boolean = { elemsMap(v) = elems; true }
10681081

@@ -1080,21 +1093,22 @@ object CaptureSet:
10801093
else false
10811094
end VarState
10821095

1083-
/** A special state that does not allow to record elements or dependent sets.
1096+
/** A class for states that do not allow to record elements or dependent sets.
10841097
* In effect this means that no new elements or dependent sets can be added
1085-
* in this state (since the previous state cannot be recorded in a snapshot)
1086-
* On the other hand, this state does allow by default Fresh.Cap to subsume arbitary
1087-
* types, which are then recorded in its hidden set.
1098+
* in these states (since the previous state cannot be recorded in a snapshot)
1099+
* On the other hand, these states do allow by default Fresh.Cap instances to
1100+
* subsume arbitary types, which are then recorded in their hidden sets.
10881101
*/
1089-
class FrozenState extends VarState:
1102+
class FrozenVarState extends VarState:
10901103
override def putElems(v: Var, refs: Refs) = false
10911104
override def putDeps(v: Var, deps: Deps) = false
10921105

10931106
@sharable
1094-
/** A frozen state that allows a Fresh.Cap instncce to subsume a
1095-
* reference `r` only if `r` is present in the hidden set of the instance.
1107+
/** A frozen state that allows a Fresh.Cap instancce to subsume a
1108+
* reference `r` only if `r` is already present in the hidden set of the instance.
1109+
* No new references can be added.
10961110
*/
1097-
object FrozenSepState extends FrozenState:
1111+
object FrozenAllState extends FrozenVarState:
10981112
override def putHidden(v: HiddenSet, elems: Refs): Boolean = false
10991113

11001114
@sharable

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import util.{SimpleIdentitySet, EqHashMap, EqHashSet, SrcPos, Property}
1818
import transform.{Recheck, PreRecheck, CapturedVars}
1919
import Recheck.*
2020
import scala.collection.mutable
21-
import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult}
21+
import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult, Frozen}
2222
import CCState.*
2323
import StdNames.nme
2424
import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
@@ -320,7 +320,7 @@ class CheckCaptures extends Recheck, SymTransformer:
320320

321321
/** Assert subcapturing `cs1 <: cs2` (available for debugging, otherwise unused) */
322322
def assertSub(cs1: CaptureSet, cs2: CaptureSet)(using Context) =
323-
assert(cs1.subCaptures(cs2, frozen = false).isOK, i"$cs1 is not a subset of $cs2")
323+
assert(cs1.subCaptures(cs2, Frozen.None).isOK, i"$cs1 is not a subset of $cs2")
324324

325325
/** If `res` is not CompareResult.OK, report an error */
326326
def checkOK(res: CompareResult, prefix: => String, pos: SrcPos, provenance: => String = "")(using Context): Unit =
@@ -334,15 +334,15 @@ class CheckCaptures extends Recheck, SymTransformer:
334334
/** Check subcapturing `{elem} <: cs`, report error on failure */
335335
def checkElem(elem: CaptureRef, cs: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context) =
336336
checkOK(
337-
elem.singletonCaptureSet.subCaptures(cs, frozen = false),
337+
elem.singletonCaptureSet.subCaptures(cs, Frozen.None),
338338
i"$elem cannot be referenced here; it is not",
339339
pos, provenance)
340340

341341
/** Check subcapturing `cs1 <: cs2`, report error on failure */
342342
def checkSubset(cs1: CaptureSet, cs2: CaptureSet, pos: SrcPos,
343343
provenance: => String = "", cs1description: String = "")(using Context) =
344344
checkOK(
345-
cs1.subCaptures(cs2, frozen = false),
345+
cs1.subCaptures(cs2, Frozen.None),
346346
if cs1.elems.size == 1 then i"reference ${cs1.elems.toList.head}$cs1description is not"
347347
else i"references $cs1$cs1description are not all",
348348
pos, provenance)
@@ -1390,7 +1390,7 @@ class CheckCaptures extends Recheck, SymTransformer:
13901390
val cs = actual.captureSet
13911391
if covariant then cs ++ leaked
13921392
else
1393-
if !leaked.subCaptures(cs, frozen = false).isOK then
1393+
if !leaked.subCaptures(cs, Frozen.None).isOK then
13941394
report.error(
13951395
em"""$expected cannot be box-converted to ${actual.capturing(leaked)}
13961396
|since the additional capture set $leaked resulted from box conversion is not allowed in $actual""", pos)
@@ -1713,7 +1713,7 @@ class CheckCaptures extends Recheck, SymTransformer:
17131713
val widened = ref.captureSetOfInfo
17141714
val added = widened.filter(isAllowed(_))
17151715
capt.println(i"heal $ref in $cs by widening to $added")
1716-
if !added.subCaptures(cs, frozen = false).isOK then
1716+
if !added.subCaptures(cs, Frozen.None).isOK then
17171717
val location = if meth.exists then i" of ${meth.showLocated}" else ""
17181718
val paramInfo =
17191719
if ref.paramName.info.kind.isInstanceOf[UniqueNameKind]

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2889,10 +2889,13 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
28892889
end inverse
28902890
end MapExistentials
28912891

2892+
protected def frozenDegree(frozen: Boolean) =
2893+
if frozen then CaptureSet.Frozen.Vars else CaptureSet.Frozen.None
2894+
28922895
protected def subCaptures(refs1: CaptureSet, refs2: CaptureSet, frozen: Boolean)(using Context): CaptureSet.CompareResult =
28932896
try
28942897
if assocExistentials.isEmpty then
2895-
refs1.subCaptures(refs2, frozen)
2898+
refs1.subCaptures(refs2, frozenDegree(frozen))
28962899
else
28972900
val mapped = refs1.map(MapExistentials(assocExistentials))
28982901
if mapped.elems.exists(Existential.isBadExistential)
@@ -2903,15 +2906,15 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
29032906
throw ex
29042907

29052908
protected def subCapturesMapped(refs1: CaptureSet, refs2: CaptureSet, frozen: Boolean)(using Context): CaptureSet.CompareResult =
2906-
refs1.subCaptures(refs2, frozen)
2909+
refs1.subCaptures(refs2, frozenDegree(frozen))
29072910

29082911
/** Is the boxing status of tp1 and tp2 the same, or alternatively, is
29092912
* the capture sets `refs1` of `tp1` a subcapture of the empty set?
29102913
* In the latter case, boxing status does not matter.
29112914
*/
29122915
protected def sameBoxed(tp1: Type, tp2: Type, refs1: CaptureSet)(using Context): Boolean =
29132916
(tp1.isBoxedCapturing == tp2.isBoxedCapturing)
2914-
|| refs1.subCaptures(CaptureSet.empty, frozenConstraint).isOK
2917+
|| refs1.subCaptures(CaptureSet.empty, frozenDegree(frozenConstraint)).isOK
29152918

29162919
// ----------- Diagnostics --------------------------------------------------
29172920

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import typer.Inferencing.*
1919
import typer.IfBottom
2020
import reporting.TestingReporter
2121
import cc.{CapturingType, derivedCapturingType, CaptureSet, captureSet, isBoxed, isBoxedCapturing}
22-
import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap}
22+
import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap, Frozen}
2323

2424
import scala.annotation.internal.sharable
2525
import scala.annotation.threadUnsafe
@@ -161,7 +161,7 @@ object TypeOps:
161161
TypeComparer.lub(simplify(l, theMap), simplify(r, theMap), isSoft = tp.isSoft)
162162
case tp @ CapturingType(parent, refs) =>
163163
if !ctx.mode.is(Mode.Type)
164-
&& refs.subCaptures(parent.captureSet, frozen = true).isOK
164+
&& refs.subCaptures(parent.captureSet, Frozen.All).isOK
165165
&& (tp.isBoxed || !parent.isBoxedCapturing)
166166
// fuse types with same boxed status and outer boxed with any type
167167
then

scala2-library-cc/src/scala/collection/IndexedSeqView.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ object IndexedSeqView {
136136

137137
@SerialVersionUID(3L)
138138
class Concat[A](prefix: SomeIndexedSeqOps[A]^, suffix: SomeIndexedSeqOps[A]^)
139-
extends SeqView.Concat[A](prefix, caps.unsafe.unsafeAssumeSeparate(suffix)) with IndexedSeqView[A]
139+
extends SeqView.Concat[A](prefix, suffix) with IndexedSeqView[A]
140140

141141
@SerialVersionUID(3L)
142142
class Take[A](underlying: SomeIndexedSeqOps[A]^, n: Int)

scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ private[mutable] object CheckedIndexedSeqView {
7575

7676
@SerialVersionUID(3L)
7777
class Concat[A](prefix: SomeIndexedSeqOps[A]^, suffix: SomeIndexedSeqOps[A]^)(protected val mutationCount: () => Int)
78-
extends IndexedSeqView.Concat[A](prefix, caps.unsafe.unsafeAssumeSeparate(suffix)) with CheckedIndexedSeqView[A]
78+
extends IndexedSeqView.Concat[A](prefix, suffix) with CheckedIndexedSeqView[A]
7979

8080
@SerialVersionUID(3L)
8181
class Take[A](underlying: SomeIndexedSeqOps[A]^, n: Int)(protected val mutationCount: () => Int)

0 commit comments

Comments
 (0)