Skip to content

Commit f6f996c

Browse files
committed
Add implied captures in function types
This is an attempt to fix the problem explified in the `delayedRunops*.scala` tests. We can treat it as a baseline that fixes the immediate problem of the interaction of reach capabilities and type variables. There might be better ways to do this by using a proper adapation rule for function types instead of adding implied captures post-hoc.
1 parent c6e3910 commit f6f996c

22 files changed

+200
-30
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,14 @@ extension (tp: Type)
527527
case _ =>
528528
tp
529529

530+
/** Add implied captures as defined by `CaptureSet.addImplied`. */
531+
def withImpliedCaptures(using Context): Type =
532+
if tp.isValueType && !tp.isAlwaysPure then
533+
val implied = CaptureSet.addImplied()(CaptureSet.empty, tp)
534+
if !implied.isAlwaysEmpty then capt.println(i"Add implied $implied to $tp")
535+
tp.capturing(implied)
536+
else tp
537+
530538
def level(using Context): Level =
531539
tp match
532540
case tp: TermRef => tp.symbol.ccLevel

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,39 @@ object CaptureSet:
10681068
.showing(i"Deep capture set of $ref: ${ref1.widen} = $result", capt)
10691069
case _ => ofType(ref.underlying, followResult = true)
10701070

1071+
/** Add captures implied by a type. This means: if we have a contravarint, boxed
1072+
* capability in a function parameter and the capability is either `cap`, or a
1073+
* reach capability, or a capture set variable, add the same capability to the enclosing
1074+
* function arrow. For instance `List[() ->{ops*} Unit] -> Unit` would become
1075+
* `List[() ->{ops*} Unit] ->{ops*} Unit`. This is needed to make
1076+
* the `delayedRunops*.scala` tests produce errors.
1077+
* TODO: Investigate whether we can roll this into a widening rule like
1078+
*
1079+
* List[() ->{cap} Unit] -> Unit <: List[() ->{ops*} Unit] ->{ops*} Unit
1080+
*
1081+
* but not
1082+
*
1083+
* List[() ->{cap} Unit] -> Unit <: List[() ->{ops*} Unit] -> Unit
1084+
*
1085+
* It would mean that a reach capability can no longer be a subtype of `cap`.
1086+
*/
1087+
class addImplied(using Context) extends TypeAccumulator[CaptureSet]:
1088+
var boundVars: Set[CaptureRef] = Set.empty
1089+
def isImplied(tp: CaptureRef) =
1090+
(tp.isRootCapability || tp.isReach || tp.derivesFrom(defn.Caps_CapSet))
1091+
&& !boundVars.contains(tp.stripReach)
1092+
def apply(cs: CaptureSet, t: Type) = t match
1093+
case t @ CapturingType(parent, cs1) =>
1094+
val cs2 = this(cs, parent)
1095+
if variance <= 0 && t.isBoxed then cs2 ++ cs1.filter(isImplied)
1096+
else cs2
1097+
case t: MethodOrPoly =>
1098+
val saved = boundVars
1099+
boundVars ++= t.paramRefs.asInstanceOf[List[CaptureRef]]
1100+
try foldOver(cs, t) finally boundVars = saved
1101+
case _ =>
1102+
foldOver(cs, t)
1103+
10711104
/** Capture set of a type */
10721105
def ofType(tp: Type, followResult: Boolean)(using Context): CaptureSet =
10731106
def recur(tp: Type): CaptureSet = trace(i"ofType $tp, ${tp.getClass} $followResult", show = true):

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1292,7 +1292,7 @@ class CheckCaptures extends Recheck, SymTransformer:
12921292
else
12931293
val widened = improveCaptures(actual.widen.dealiasKeepAnnots, actual)
12941294
val adapted = adaptBoxed(
1295-
widened.withReachCaptures(actual), expected, pos,
1295+
widened.withReachCaptures(actual).withImpliedCaptures, expected, pos,
12961296
covariant = true, alwaysConst = false, boxErrors)
12971297
if adapted eq widened then actual
12981298
else adapted.showing(i"adapt boxed $actual vs $expected = $adapted", capt)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ object SeqView {
212212
override def sorted[B1 >: A](implicit ord1: Ordering[B1]): SeqView[A]^{this} =
213213
if (ord1 == Sorted.this.ord) outer.unsafeAssumePure
214214
else if (ord1.isReverseOf(Sorted.this.ord)) this
215-
else new Sorted(elems, len, ord1)
215+
else new Sorted(elems, len, ord1).asInstanceOf // !!! asInstanceOf needed after adding addImplied widening
216216
}
217217

218218
@volatile private[this] var evaluated = false
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops2.scala:10:35 -------------------------------
2+
10 | app[List[() ->{ops*} Unit], Unit](ops, runOps) // error
3+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
| Found: () ->{ops*} Unit
5+
| Required: () -> Unit
6+
|
7+
| longer explanation available when compiling with `-explain`
8+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops2.scala:18:36 -------------------------------
9+
18 | app2[List[() => Unit], Unit](ops, runOps: List[() => Unit] -> Unit) // error
10+
| ^^^^^^
11+
| Found: (ops: List[box () ->? Unit]^?) ->? Unit
12+
| Required: (ops: List[box () => Unit]) -> Unit
13+
|
14+
| longer explanation available when compiling with `-explain`
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import language.experimental.captureChecking
2+
3+
def runOps(ops: List[() => Unit]): Unit =
4+
ops.foreach(op => op())
5+
6+
def app[T, U](x: T, op: T => U): () ->{op} U =
7+
() => op(x)
8+
9+
def unsafeRunOps(ops: List[() => Unit]): () ->{} Unit =
10+
app[List[() ->{ops*} Unit], Unit](ops, runOps) // error
11+
12+
def app2[T, U](x: T, op: T => U): () ->{op} U =
13+
() =>
14+
def y: T = x
15+
op(y)
16+
17+
def unsafeRunOps2(ops: List[() => Unit]): () -> Unit =
18+
app2[List[() => Unit], Unit](ops, runOps: List[() => Unit] -> Unit) // error
19+
20+
21+
22+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops3.scala:10:41 -------------------------------
2+
10 | app[List[() ->{ops*} Unit], Unit](ops, runOps) // error
3+
| ^^^^^^
4+
| Found: (ops: List[box () ->? Unit]^?) ->? Unit
5+
| Required: (ops: List[box () ->{ops²*} Unit]) -> Unit
6+
|
7+
| where: ops is a reference to a value parameter
8+
| ops² is a parameter in method unsafeRunOps
9+
|
10+
| longer explanation available when compiling with `-explain`
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import language.experimental.captureChecking
2+
3+
def runOps(ops: List[() => Unit]): Unit =
4+
ops.foreach(op => op())
5+
6+
def app[T, U](x: T, op: T -> U): () ->{} U =
7+
() => op(x)
8+
9+
def unsafeRunOps(ops: List[() => Unit]): () ->{} Unit =
10+
app[List[() ->{ops*} Unit], Unit](ops, runOps) // error
11+
12+
13+
14+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops4.scala:11:4 --------------------------------
2+
11 | runOps[C]: List[() ->{C^} Unit] ->{C^} Unit) // error
3+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
| Found: List[box () ->{C^} Unit] ->{C^} Unit
5+
| Required: List[box () ->{C^} Unit] -> Unit
6+
|
7+
| longer explanation available when compiling with `-explain`
8+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops4.scala:15:43 -------------------------------
9+
15 | app[List[() ->{C^} Unit], Unit](ops, rops[C]) // error
10+
| ^^^^^^^
11+
| Found: (ops: List[box () ->{C^} Unit]) ->{C^} Unit
12+
| Required: (ops: List[box () ->{C^} Unit]) -> Unit
13+
|
14+
| longer explanation available when compiling with `-explain`
15+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops4.scala:18:39 -------------------------------
16+
18 | app[List[() ->{C^} Unit], Unit](ops, runOps) // error
17+
| ^^^^^^
18+
| Found: (ops: List[box () ->? Unit]^?) ->? Unit
19+
| Required: (ops: List[box () ->{C^} Unit]) -> Unit
20+
|
21+
| longer explanation available when compiling with `-explain`
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import language.experimental.captureChecking
2+
3+
def runOps[C^](ops: List[() ->{C^} Unit]): Unit =
4+
ops.foreach(op => op())
5+
6+
def app[T, U](x: T, op: T -> U): () ->{} U =
7+
() => op(x)
8+
9+
def unsafeRunOps[C^](ops: List[() ->{C^} Unit]): () ->{} Unit =
10+
app[List[() ->{C^} Unit], Unit](ops,
11+
runOps[C]: List[() ->{C^} Unit] ->{C^} Unit) // error
12+
13+
def unsafeRunOps2[C^](ops: List[() ->{C^} Unit]): () ->{} Unit =
14+
def rops[D^]: (ops: List[() ->{D^} Unit]) -> Unit = ???
15+
app[List[() ->{C^} Unit], Unit](ops, rops[C]) // error
16+
17+
def unsafeRunOps3[C^](ops: List[() ->{C^} Unit]): () ->{} Unit =
18+
app[List[() ->{C^} Unit], Unit](ops, runOps) // error
19+
20+
21+
22+
23+
24+

0 commit comments

Comments
 (0)