@@ -236,6 +236,22 @@ extension (tp: Type)
236
236
* (2) all covariant occurrences of cap replaced by `x*`, provided there
237
237
* are no occurrences in `T` at other variances. (1) is standard, whereas
238
238
* (2) is new.
239
+ *
240
+ * For (2), multiple-flipped covariant occurrences of cap won't be replaced.
241
+ * In other words,
242
+ *
243
+ * - For xs: List[File^] ==> List[File^{xs*}], the cap is replaced;
244
+ * - while f: [R] -> (op: File^ => R) -> R remains unchanged.
245
+ *
246
+ * Without this restriction, the signature of functions like withFile:
247
+ *
248
+ * (path: String) -> [R] -> (op: File^ => R) -> R
249
+ *
250
+ * could be refined to
251
+ *
252
+ * (path: String) -> [R] -> (op: File^{withFile*} => R) -> R
253
+ *
254
+ * which is clearly unsound.
239
255
*
240
256
* Why is this sound? Covariant occurrences of cap must represent capabilities
241
257
* that are reachable from `x`, so they are included in the meaning of `{x*}`.
@@ -245,18 +261,27 @@ extension (tp: Type)
245
261
def withReachCaptures (ref : Type )(using Context ): Type =
246
262
object narrowCaps extends TypeMap :
247
263
var ok = true
248
- def apply (t : Type ) = t.dealias match
249
- case t1 @ CapturingType (p, cs) if cs.isUniversal =>
250
- if variance > 0 then
251
- t1.derivedCapturingType(apply(p), ref.reach.singletonCaptureSet)
252
- else
253
- ok = false
254
- t
255
- case _ => t match
256
- case t @ CapturingType (p, cs) =>
257
- t.derivedCapturingType(apply(p), cs) // don't map capture set variables
258
- case t =>
259
- mapOver(t)
264
+
265
+ /** Has the variance been flipped at this point? */
266
+ private var isFlipped : Boolean = false
267
+
268
+ def apply (t : Type ) =
269
+ val saved = isFlipped
270
+ try
271
+ if variance <= 0 then isFlipped = true
272
+ t.dealias match
273
+ case t1 @ CapturingType (p, cs) if cs.isUniversal =>
274
+ if variance > 0 then
275
+ t1.derivedCapturingType(apply(p), if isFlipped then cs else ref.reach.singletonCaptureSet)
276
+ else
277
+ ok = false
278
+ t
279
+ case _ => t match
280
+ case t @ CapturingType (p, cs) =>
281
+ t.derivedCapturingType(apply(p), cs) // don't map capture set variables
282
+ case t =>
283
+ mapOver(t)
284
+ finally isFlipped = saved
260
285
ref match
261
286
case ref : CaptureRef if ref.isTrackableRef =>
262
287
val tp1 = narrowCaps(tp)
0 commit comments