Skip to content

Commit e5987d6

Browse files
committed
Check all top-level covariant capture sets in checkNotUniversal
Fixes #21401
1 parent a2c53a1 commit e5987d6

File tree

3 files changed

+47
-11
lines changed

3 files changed

+47
-11
lines changed

Diff for: compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

+18-11
Original file line numberDiff line numberDiff line change
@@ -978,21 +978,28 @@ class CheckCaptures extends Recheck, SymTransformer:
978978
case _: RefTree | _: Apply | _: TypeApply => tree.symbol.unboxesResult
979979
case _: Try => true
980980
case _ => false
981-
def checkNotUniversal(tp: Type): Unit = tp.widenDealias match
982-
case wtp @ CapturingType(parent, refs) =>
983-
refs.disallowRootCapability { () =>
984-
report.error(
985-
em"""The expression's type $wtp is not allowed to capture the root capability `cap`.
986-
|This usually means that a capability persists longer than its allowed lifetime.""",
987-
tree.srcPos)
988-
}
989-
checkNotUniversal(parent)
990-
case _ =>
981+
982+
object checkNotUniversal extends TypeTraverser:
983+
def traverse(tp: Type) =
984+
tp.dealias match
985+
case wtp @ CapturingType(parent, refs) =>
986+
if variance > 0 then
987+
refs.disallowRootCapability: () =>
988+
def part = if wtp eq tpe.widen then "" else i" in its part $wtp"
989+
report.error(
990+
em"""The expression's type ${tpe.widen} is not allowed to capture the root capability `cap`$part.
991+
|This usually means that a capability persists longer than its allowed lifetime.""",
992+
tree.srcPos)
993+
if !wtp.isBoxed then traverse(parent)
994+
case tp =>
995+
traverseChildren(tp)
996+
991997
if !ccConfig.useSealed
992998
&& !tpe.hasAnnotation(defn.UncheckedCapturesAnnot)
993999
&& needsUniversalCheck
1000+
&& tpe.widen.isValueType
9941001
then
995-
checkNotUniversal(tpe)
1002+
checkNotUniversal.traverse(tpe.widen)
9961003
super.recheckFinish(tpe, tree, pt)
9971004
end recheckFinish
9981005

Diff for: tests/neg-custom-args/captures/i21401.check

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- Error: tests/neg-custom-args/captures/i21401.scala:15:22 ------------------------------------------------------------
2+
15 | val a = usingIO[IO^](x => x) // error: The expression's type IO^ is not allowed to capture the root capability `cap`
3+
| ^^^^^^^^^^^^^^^^^^^^
4+
| The expression's type box IO^ is not allowed to capture the root capability `cap`.
5+
| This usually means that a capability persists longer than its allowed lifetime.
6+
-- Error: tests/neg-custom-args/captures/i21401.scala:16:70 ------------------------------------------------------------
7+
16 | val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error: The expression's type Res is not allowed to capture the root capability `cap` in its part box IO^
8+
| ^^^^^^^^^^^^^^^^^^^
9+
| The expression's type Res is not allowed to capture the root capability `cap` in its part box IO^.
10+
| This usually means that a capability persists longer than its allowed lifetime.

Diff for: tests/neg-custom-args/captures/i21401.scala

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import language.experimental.captureChecking
2+
3+
trait IO:
4+
def println(s: String): Unit
5+
def usingIO[R](op: IO^ => R): R = ???
6+
7+
case class Boxed[+T](unbox: T)
8+
9+
type Res = [R, X <: Boxed[IO^] -> R] -> (op: X) -> R
10+
def mkRes(x: IO^): Res =
11+
[R, X <: Boxed[IO^] -> R] => (op: X) =>
12+
val op1: Boxed[IO^] -> R = op
13+
op1(Boxed[IO^](x))
14+
def test2() =
15+
val a = usingIO[IO^](x => x) // error: The expression's type IO^ is not allowed to capture the root capability `cap`
16+
val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error: The expression's type Res is not allowed to capture the root capability `cap` in its part box IO^
17+
val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x)
18+
val y: IO^{x*} = x.unbox
19+
y.println("boom")

0 commit comments

Comments
 (0)