Skip to content

Commit

Permalink
Check all top-level covariant capture sets in checkNotUniversal (#21428)
Browse files Browse the repository at this point in the history
Fixes #21401
  • Loading branch information
Linyxus authored Aug 23, 2024
2 parents ff6f50a + e5987d6 commit 5101daf
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 11 deletions.
29 changes: 18 additions & 11 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -978,21 +978,28 @@ class CheckCaptures extends Recheck, SymTransformer:
case _: RefTree | _: Apply | _: TypeApply => tree.symbol.unboxesResult
case _: Try => true
case _ => false
def checkNotUniversal(tp: Type): Unit = tp.widenDealias match
case wtp @ CapturingType(parent, refs) =>
refs.disallowRootCapability { () =>
report.error(
em"""The expression's type $wtp is not allowed to capture the root capability `cap`.
|This usually means that a capability persists longer than its allowed lifetime.""",
tree.srcPos)
}
checkNotUniversal(parent)
case _ =>

object checkNotUniversal extends TypeTraverser:
def traverse(tp: Type) =
tp.dealias match
case wtp @ CapturingType(parent, refs) =>
if variance > 0 then
refs.disallowRootCapability: () =>
def part = if wtp eq tpe.widen then "" else i" in its part $wtp"
report.error(
em"""The expression's type ${tpe.widen} is not allowed to capture the root capability `cap`$part.
|This usually means that a capability persists longer than its allowed lifetime.""",
tree.srcPos)
if !wtp.isBoxed then traverse(parent)
case tp =>
traverseChildren(tp)

if !ccConfig.useSealed
&& !tpe.hasAnnotation(defn.UncheckedCapturesAnnot)
&& needsUniversalCheck
&& tpe.widen.isValueType
then
checkNotUniversal(tpe)
checkNotUniversal.traverse(tpe.widen)
super.recheckFinish(tpe, tree, pt)
end recheckFinish

Expand Down
10 changes: 10 additions & 0 deletions tests/neg-custom-args/captures/i21401.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- Error: tests/neg-custom-args/captures/i21401.scala:15:22 ------------------------------------------------------------
15 | val a = usingIO[IO^](x => x) // error: The expression's type IO^ is not allowed to capture the root capability `cap`
| ^^^^^^^^^^^^^^^^^^^^
| The expression's type box IO^ is not allowed to capture the root capability `cap`.
| This usually means that a capability persists longer than its allowed lifetime.
-- Error: tests/neg-custom-args/captures/i21401.scala:16:70 ------------------------------------------------------------
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^
| ^^^^^^^^^^^^^^^^^^^
| The expression's type Res is not allowed to capture the root capability `cap` in its part box IO^.
| This usually means that a capability persists longer than its allowed lifetime.
19 changes: 19 additions & 0 deletions tests/neg-custom-args/captures/i21401.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import language.experimental.captureChecking

trait IO:
def println(s: String): Unit
def usingIO[R](op: IO^ => R): R = ???

case class Boxed[+T](unbox: T)

type Res = [R, X <: Boxed[IO^] -> R] -> (op: X) -> R
def mkRes(x: IO^): Res =
[R, X <: Boxed[IO^] -> R] => (op: X) =>
val op1: Boxed[IO^] -> R = op
op1(Boxed[IO^](x))
def test2() =
val a = usingIO[IO^](x => x) // error: The expression's type IO^ is not allowed to capture the root capability `cap`
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^
val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x)
val y: IO^{x*} = x.unbox
y.println("boom")

0 comments on commit 5101daf

Please sign in to comment.