Skip to content

Commit 097b179

Browse files
committed
Fix universal check for inferred types
Checking against capturing the universal capability now works also for inferred types. Also fix breakage from previous rebase
1 parent 2f0bc12 commit 097b179

File tree

5 files changed

+64
-17
lines changed

5 files changed

+64
-17
lines changed

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -408,17 +408,18 @@ class CheckCaptures extends Recheck, SymTransformer:
408408
tree.symbol.info
409409
case _ =>
410410
NoType
411-
if typeToCheck.exists then
412-
typeToCheck.widenDealias match
413-
case wtp @ CapturingType(parent, refs, _) =>
414-
refs.disallowRootCapability { () =>
415-
val kind = if tree.isInstanceOf[ValDef] then "mutable variable" else "expression"
416-
report.error(
417-
em"""The $kind's type $wtp is not allowed to capture the root capability `*`.
418-
|This usually means that a capability persists longer than its allowed lifetime.""",
419-
tree.srcPos)
420-
}
421-
case _ =>
411+
def checkNotUniversal(tp: Type): Unit = tp.widenDealias match
412+
case wtp @ CapturingType(parent, refs, _) =>
413+
refs.disallowRootCapability { () =>
414+
val kind = if tree.isInstanceOf[ValDef] then "mutable variable" else "expression"
415+
report.error(
416+
em"""The $kind's type $wtp is not allowed to capture the root capability `*`.
417+
|This usually means that a capability persists longer than its allowed lifetime.""",
418+
tree.srcPos)
419+
}
420+
checkNotUniversal(parent)
421+
case _ =>
422+
checkNotUniversal(typeToCheck)
422423
super.recheckFinish(tpe, tree, pt)
423424

424425
/** This method implements the rule outlined in #14390:

docs/_docs/reference/experimental/cc.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ again on access, the capture information "pops out" again. For instance, even th
368368
() => p.fst : {ct} () -> {ct} Int -> String
369369
```
370370
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.
371-
This principle may seem surprising at first, but it is the key to make capture checking concise and practical.
371+
This principle plays an important part in making capture checking concise and practical.
372372

373373
## Escape Checking
374374

@@ -398,7 +398,7 @@ This error message was produced by the following logic:
398398

399399
- The `f` parameter has type `{*} FileOutputStream`, which makes it a capability.
400400
- Therefore, the type of the expression `() => f.write(0)` is `{f} () -> Unit`.
401-
- This makes the whole type of the closure passed to `usingLogFile` the dependent function type
401+
- This makes the type of the whole closure passed to `usingLogFile` the dependent function type
402402
`(f: {*} FileOutputStream) -> {f} () -> Unit`.
403403
- The expected type of the closure is a simple, parametric, impure function type `({*} FileOutputStream) => T`,
404404
for some instantiation of the type variable `T`.

tests/neg-custom-args/captures/usingLogFile.check

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,18 @@
1818
| ^^^^^^^^
1919
| The expression's type {*} () -> Unit is not allowed to capture the root capability `*`.
2020
| This usually means that a capability persists longer than its allowed lifetime.
21+
-- Error: tests/neg-custom-args/captures/usingLogFile.scala:47:27 ------------------------------------------------------
22+
47 | val later = usingLogFile { f => () => f.write(0) } // error
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
24+
| The expression's type {*} () -> Unit is not allowed to capture the root capability `*`.
25+
| This usually means that a capability persists longer than its allowed lifetime.
26+
-- Error: tests/neg-custom-args/captures/usingLogFile.scala:62:25 ------------------------------------------------------
27+
62 | val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error
28+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
29+
| The expression's type {*} (x$0: Int) -> Unit is not allowed to capture the root capability `*`.
30+
| This usually means that a capability persists longer than its allowed lifetime.
31+
-- Error: tests/neg-custom-args/captures/usingLogFile.scala:71:48 ------------------------------------------------------
32+
71 | val later = usingFile("logfile", usingLogger(_, l => () => l.log("test"))) // error
33+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34+
| The expression's type {*} () -> Unit is not allowed to capture the root capability `*`.
35+
| This usually means that a capability persists longer than its allowed lifetime.

tests/neg-custom-args/captures/usingLogFile.scala

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import java.io.FileOutputStream
1+
import java.io.*
22
import annotation.capability
33

44
object Test1:
@@ -36,3 +36,37 @@ object Test2:
3636
usingLogFile { f => later4 = Cell(() => f.write(0)) }
3737
later4.x() // error
3838

39+
object Test3:
40+
41+
def usingLogFile[T](op: ({*} FileOutputStream) => T) =
42+
val logFile = FileOutputStream("log")
43+
val result = op(logFile)
44+
logFile.close()
45+
result
46+
47+
val later = usingLogFile { f => () => f.write(0) } // error
48+
49+
object Test4:
50+
class Logger(f: {*} OutputStream):
51+
def log(msg: String): Unit = ???
52+
53+
def usingFile[T](name: String, op: ({*} OutputStream) => T): T =
54+
val f = new FileOutputStream(name)
55+
val result = op(f)
56+
f.close()
57+
result
58+
59+
val xs: List[Int] = ???
60+
def good = usingFile("out", f => xs.foreach(x => f.write(x)))
61+
def fail =
62+
val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error
63+
later(1)
64+
65+
66+
def usingLogger[T](f: {*} OutputStream, op: ({f} Logger) => T): T =
67+
val logger = Logger(f)
68+
op(logger)
69+
70+
def test =
71+
val later = usingFile("logfile", usingLogger(_, l => () => l.log("test"))) // error
72+
later()

tests/semanticdb/metac.expect

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,9 +2037,6 @@ Occurrences:
20372037
[5:4..5:8): List -> scala/package.List.
20382038
[5:9..5:10): x -> local0
20392039

2040-
Synthetics:
2041-
[5:4..5:8):List => *.apply[Int]
2042-
20432040
expect/MatchType.scala
20442041
----------------------
20452042

0 commit comments

Comments
 (0)