@@ -236,6 +236,22 @@ extension (tp: Type)
236236 * (2) all covariant occurrences of cap replaced by `x*`, provided there
237237 * are no occurrences in `T` at other variances. (1) is standard, whereas
238238 * (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.
239255 *
240256 * Why is this sound? Covariant occurrences of cap must represent capabilities
241257 * that are reachable from `x`, so they are included in the meaning of `{x*}`.
@@ -245,18 +261,27 @@ extension (tp: Type)
245261 def withReachCaptures (ref : Type )(using Context ): Type =
246262 object narrowCaps extends TypeMap :
247263 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
260285 ref match
261286 case ref : CaptureRef if ref.isTrackableRef =>
262287 val tp1 = narrowCaps(tp)
0 commit comments