Skip to content

Commit aa2d344

Browse files
committed
Make reach refinement shallow
1 parent 2d2beb1 commit aa2d344

File tree

2 files changed

+55
-12
lines changed

2 files changed

+55
-12
lines changed

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

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import language.experimental.captureChecking
2+
trait IO
3+
def test1(): Unit =
4+
val f: IO^ => IO^ = x => x
5+
val g: IO^ => IO^{f*} = f // error
6+
def test2(): Unit =
7+
val f: [R] -> (IO^ => R) -> R = ???
8+
val g: [R] -> (IO^{f*} => R) -> R = f // error
9+
def test3(): Unit =
10+
val f: [R] -> (IO^ -> R) -> R = ???
11+
val g: [R] -> (IO^{f*} -> R) -> R = f // error
12+
def test4(): Unit =
13+
val xs: List[IO^] = ???
14+
val ys: List[IO^{xs*}] = xs // ok
15+
def test5(): Unit =
16+
val f: [R] -> (IO^ -> R) -> IO^ = ???
17+
val g: [R] -> (IO^ -> R) -> IO^{f*} = f // ok
18+
val h: [R] -> (IO^{f*} -> R) -> IO^ = f // error

0 commit comments

Comments
 (0)