Skip to content

Commit 96c956a

Browse files
authored
CC: Give more info when context function parameters leak (#20244)
Previously we had: parameter `$contextual1` leaks into outer capture set of type parameter `T` of method `apply`. We now give info in what type the parameter appeared and who owns the method. It's still not great, but at least we see more info that could tell us about the context.
2 parents b3f0aca + 6e08077 commit 96c956a

File tree

5 files changed

+102
-7
lines changed

5 files changed

+102
-7
lines changed

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

+7-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import Recheck.*
2020
import scala.collection.mutable
2121
import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult}
2222
import StdNames.nme
23-
import NameKinds.{DefaultGetterName, WildcardParamName}
23+
import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
2424
import reporting.trace
2525

2626
/** The capture checker */
@@ -1288,10 +1288,14 @@ class CheckCaptures extends Recheck, SymTransformer:
12881288
val added = widened.filter(isAllowed(_))
12891289
capt.println(i"heal $ref in $cs by widening to $added")
12901290
if !added.subCaptures(cs, frozen = false).isOK then
1291-
val location = if meth.exists then i" of $meth" else ""
1291+
val location = if meth.exists then i" of ${meth.showLocated}" else ""
1292+
val paramInfo =
1293+
if ref.paramName.info.kind.isInstanceOf[UniqueNameKind]
1294+
then i"${ref.paramName} from ${ref.binder}"
1295+
else i"${ref.paramName}"
12921296
val debugSetInfo = if ctx.settings.YccDebug.value then i" $cs" else ""
12931297
report.error(
1294-
i"local reference ${ref.paramName} leaks into outer capture set$debugSetInfo of type parameter $paramName$location",
1298+
i"local reference $paramInfo leaks into outer capture set$debugSetInfo of type parameter $paramName$location",
12951299
tree.srcPos)
12961300
else
12971301
widened.elems.foreach(recur)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps.scala:64:8 ----------------------------------
2+
63 | Result:
3+
64 | Future: // error, escaping label from Result
4+
| ^
5+
| Found: Result.Ok[box Future[box T^?]^{fr, contextual$1}]
6+
| Required: Result[Future[T], Nothing]
7+
65 | fr.await.ok
8+
|--------------------------------------------------------------------------------------------------------------------
9+
|Inline stack trace
10+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
11+
|This location contains code that was inlined from effect-swaps.scala:41
12+
41 | boundary(Ok(body))
13+
| ^^^^^^^^
14+
--------------------------------------------------------------------------------------------------------------------
15+
|
16+
| longer explanation available when compiling with `-explain`
17+
-- Error: tests/neg-custom-args/captures/effect-swaps.scala:68:15 ------------------------------------------------------
18+
68 | Result.make: //lbl ?=> // error, escaping label from Result
19+
| ^^^^^^^^^^^
20+
|local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]^):
21+
| box Future[box T^?]^{fr, contextual$9} leaks into outer capture set of type parameter T of method make in object Result
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import annotation.capability
2+
3+
object boundary:
4+
5+
@capability final class Label[-T]
6+
7+
/** Abort current computation and instead return `value` as the value of
8+
* the enclosing `boundary` call that created `label`.
9+
*/
10+
def break[T](value: T)(using label: Label[T]): Nothing = ???
11+
12+
def apply[T](body: Label[T] ?=> T): T = ???
13+
end boundary
14+
15+
import boundary.{Label, break}
16+
17+
@capability trait Async
18+
object Async:
19+
def blocking[T](body: Async ?=> T): T = ???
20+
21+
class Future[+T]:
22+
this: Future[T]^ =>
23+
def await(using Async): T = ???
24+
object Future:
25+
def apply[T](op: Async ?=> T)(using Async): Future[T]^{op} = ???
26+
27+
enum Result[+T, +E]:
28+
case Ok[+T](value: T) extends Result[T, Nothing]
29+
case Err[+E](error: E) extends Result[Nothing, E]
30+
31+
32+
object Result:
33+
extension [T, E](r: Result[T, E]^)(using Label[Err[E]])
34+
35+
/** `_.ok` propagates Err to current Label */
36+
def ok: T = r match
37+
case Ok(value) => value
38+
case Err(value) => break[Err[E]](Err(value))
39+
40+
transparent inline def apply[T, E](inline body: Label[Result[T, E]] ?=> T): Result[T, E] =
41+
boundary(Ok(body))
42+
43+
// same as apply, but not an inline method
44+
def make[T, E](body: Label[Result[T, E]] ?=> T): Result[T, E] =
45+
boundary(Ok(body))
46+
47+
end Result
48+
49+
def test[T, E](using Async) =
50+
import Result.*
51+
Async.blocking: async ?=>
52+
val good1: List[Future[Result[T, E]]] => Future[Result[List[T], E]] = frs =>
53+
Future:
54+
Result:
55+
frs.map(_.await.ok) // OK
56+
57+
val good2: Result[Future[T], E] => Future[Result[T, E]] = rf =>
58+
Future:
59+
Result:
60+
rf.ok.await // OK, Future argument has type Result[T]
61+
62+
def fail3(fr: Future[Result[T, E]]^) =
63+
Result:
64+
Future: // error, escaping label from Result
65+
fr.await.ok
66+
67+
def fail4[T, E](fr: Future[Result[T, E]]^) =
68+
Result.make: //lbl ?=> // error, escaping label from Result
69+
Future: fut ?=>
70+
fr.await.ok
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
-- Error: tests/neg-custom-args/captures/leaking-iterators.scala:56:2 --------------------------------------------------
22
56 | usingLogFile: log => // error
33
| ^^^^^^^^^^^^
4-
| local reference log leaks into outer capture set of type parameter R of method usingLogFile
4+
| local reference log leaks into outer capture set of type parameter R of method usingLogFile in package cctest
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
-- Error: tests/neg-custom-args/captures/usingLogFile.scala:23:14 ------------------------------------------------------
22
23 | val later = usingLogFile { f => () => f.write(0) } // error
33
| ^^^^^^^^^^^^
4-
| local reference f leaks into outer capture set of type parameter T of method usingLogFile
4+
| local reference f leaks into outer capture set of type parameter T of method usingLogFile in object Test2
55
-- Error: tests/neg-custom-args/captures/usingLogFile.scala:28:23 ------------------------------------------------------
66
28 | private val later2 = usingLogFile { f => Cell(() => f.write(0)) } // error
77
| ^^^^^^^^^^^^
8-
| local reference f leaks into outer capture set of type parameter T of method usingLogFile
8+
| local reference f leaks into outer capture set of type parameter T of method usingLogFile in object Test2
99
-- Error: tests/neg-custom-args/captures/usingLogFile.scala:44:16 ------------------------------------------------------
1010
44 | val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error
1111
| ^^^^^^^^^
12-
| local reference f leaks into outer capture set of type parameter T of method usingFile
12+
| local reference f leaks into outer capture set of type parameter T of method usingFile in object Test3

0 commit comments

Comments
 (0)