diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index 66aa17b959f4..6bd3792cc091 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -408,17 +408,18 @@ class CheckCaptures extends Recheck, SymTransformer: tree.symbol.info case _ => NoType - if typeToCheck.exists then - typeToCheck.widenDealias match - case wtp @ CapturingType(parent, refs, _) => - refs.disallowRootCapability { () => - val kind = if tree.isInstanceOf[ValDef] then "mutable variable" else "expression" - report.error( - em"""The $kind's type $wtp is not allowed to capture the root capability `*`. - |This usually means that a capability persists longer than its allowed lifetime.""", - tree.srcPos) - } - case _ => + def checkNotUniversal(tp: Type): Unit = tp.widenDealias match + case wtp @ CapturingType(parent, refs, _) => + refs.disallowRootCapability { () => + val kind = if tree.isInstanceOf[ValDef] then "mutable variable" else "expression" + report.error( + em"""The $kind's type $wtp is not allowed to capture the root capability `*`. + |This usually means that a capability persists longer than its allowed lifetime.""", + tree.srcPos) + } + checkNotUniversal(parent) + case _ => + checkNotUniversal(typeToCheck) super.recheckFinish(tpe, tree, pt) /** This method implements the rule outlined in #14390: diff --git a/docs/_docs/reference/experimental/cc.md b/docs/_docs/reference/experimental/cc.md index 592d410a4502..c8fccad0bc4e 100644 --- a/docs/_docs/reference/experimental/cc.md +++ b/docs/_docs/reference/experimental/cc.md @@ -368,7 +368,7 @@ again on access, the capture information "pops out" again. For instance, even th () => p.fst : {ct} () -> {ct} Int -> String ``` In other words, references to capabilities "tunnel through" in generic instantiations from creation to access; they do not affect the capture set of the enclosing generic data constructor applications. -This principle may seem surprising at first, but it is the key to make capture checking concise and practical. +This principle plays an important part in making capture checking concise and practical. ## Escape Checking @@ -398,7 +398,7 @@ This error message was produced by the following logic: - The `f` parameter has type `{*} FileOutputStream`, which makes it a capability. - Therefore, the type of the expression `() => f.write(0)` is `{f} () -> Unit`. - - This makes the whole type of the closure passed to `usingLogFile` the dependent function type + - This makes the type of the whole closure passed to `usingLogFile` the dependent function type `(f: {*} FileOutputStream) -> {f} () -> Unit`. - The expected type of the closure is a simple, parametric, impure function type `({*} FileOutputStream) => T`, for some instantiation of the type variable `T`. diff --git a/tests/neg-custom-args/captures/usingLogFile.check b/tests/neg-custom-args/captures/usingLogFile.check index b292b230b742..aa1c90f9ea4c 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -18,3 +18,18 @@ | ^^^^^^^^ | The expression's type {*} () -> Unit is not allowed to capture the root capability `*`. | This usually means that a capability persists longer than its allowed lifetime. +-- Error: tests/neg-custom-args/captures/usingLogFile.scala:47:27 ------------------------------------------------------ +47 | val later = usingLogFile { f => () => f.write(0) } // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | The expression's type {*} () -> Unit is not allowed to capture the root capability `*`. + | This usually means that a capability persists longer than its allowed lifetime. +-- Error: tests/neg-custom-args/captures/usingLogFile.scala:62:25 ------------------------------------------------------ +62 | val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | The expression's type {*} (x$0: Int) -> Unit is not allowed to capture the root capability `*`. + | This usually means that a capability persists longer than its allowed lifetime. +-- Error: tests/neg-custom-args/captures/usingLogFile.scala:71:48 ------------------------------------------------------ +71 | val later = usingFile("logfile", usingLogger(_, l => () => l.log("test"))) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | The expression's type {*} () -> Unit is not allowed to capture the root capability `*`. + | This usually means that a capability persists longer than its allowed lifetime. diff --git a/tests/neg-custom-args/captures/usingLogFile.scala b/tests/neg-custom-args/captures/usingLogFile.scala index 206986e0bb22..20426c43779a 100644 --- a/tests/neg-custom-args/captures/usingLogFile.scala +++ b/tests/neg-custom-args/captures/usingLogFile.scala @@ -1,4 +1,4 @@ -import java.io.FileOutputStream +import java.io.* import annotation.capability object Test1: @@ -36,3 +36,37 @@ object Test2: usingLogFile { f => later4 = Cell(() => f.write(0)) } later4.x() // error +object Test3: + + def usingLogFile[T](op: ({*} FileOutputStream) => T) = + val logFile = FileOutputStream("log") + val result = op(logFile) + logFile.close() + result + + val later = usingLogFile { f => () => f.write(0) } // error + +object Test4: + class Logger(f: {*} OutputStream): + def log(msg: String): Unit = ??? + + def usingFile[T](name: String, op: ({*} OutputStream) => T): T = + val f = new FileOutputStream(name) + val result = op(f) + f.close() + result + + val xs: List[Int] = ??? + def good = usingFile("out", f => xs.foreach(x => f.write(x))) + def fail = + val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error + later(1) + + + def usingLogger[T](f: {*} OutputStream, op: ({f} Logger) => T): T = + val logger = Logger(f) + op(logger) + + def test = + val later = usingFile("logfile", usingLogger(_, l => () => l.log("test"))) // error + later() diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index cad281110d9c..633b06e71ba2 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -2037,9 +2037,6 @@ Occurrences: [5:4..5:8): List -> scala/package.List. [5:9..5:10): x -> local0 -Synthetics: -[5:4..5:8):List => *.apply[Int] - expect/MatchType.scala ----------------------