Skip to content

Commit c8722e4

Browse files
committed
CC: Give more info when context function parameters leak
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.
1 parent fef6576 commit c8722e4

File tree

5 files changed

+98
-7
lines changed

5 files changed

+98
-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,19 @@
1+
-- Error: tests/neg-custom-args/captures/effect-swaps.scala:62:6 -------------------------------------------------------
2+
61 | Result:
3+
62 | Future: // error, escaping label from Result
4+
| ^
5+
|local reference contextual$1 from (using contextual$1: boundary.Label[box Result[box Future[box T^?]^{fr, contextual$1, contextual$1}, box E^?]^?]^):
6+
| box Result[box Future[box T^?]^{fr, contextual$1, contextual$1}, box E^?]^? leaks into outer capture set of type parameter T of method apply in object boundary
7+
63 | fr.await.ok
8+
|--------------------------------------------------------------------------------------------------------------------
9+
|Inline stack trace
10+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
11+
|This location contains code that was inlined from effect-swaps.scala:37
12+
37 | boundary:
13+
| ^^^^^^^^
14+
--------------------------------------------------------------------------------------------------------------------
15+
-- Error: tests/neg-custom-args/captures/effect-swaps.scala:66:11 ------------------------------------------------------
16+
66 | Result.make: // error, escaping label from Result
17+
| ^^^^^^^^^^^
18+
|local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]^):
19+
| 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,68 @@
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+
class Async
18+
class Future[+T]:
19+
this: Future[T]^ =>
20+
def await(using Async^): T = ???
21+
object Future:
22+
def apply[T](op: Async^ ?=> T)(using Async): Future[T]^{op} = ???
23+
24+
abstract class Result[+T, +E]
25+
case class Ok[+T](value: T) extends Result[T, Nothing]
26+
case class Err[+E](value: E) extends Result[Nothing, E]
27+
28+
object Result:
29+
extension [T, E](r: Result[T, E])
30+
31+
/** `_.ok` propagates Err to current Label */
32+
inline def ok(using Label[Result[Nothing, E]]): T = r match
33+
case r: Ok[_] => r.value
34+
case err => break(err.asInstanceOf[Err[E]])
35+
36+
transparent inline def apply[T, E](inline body: Label[Result[T, E]] ?=> T): Result[T, E] =
37+
boundary:
38+
val result = body
39+
Ok(result)
40+
41+
// same as apply, but not an inline method
42+
def make[T, E](body: Label[Result[T, E]] ?=> T): Result[T, E] =
43+
boundary:
44+
val result = body
45+
Ok(result)
46+
47+
end Result
48+
49+
def test[T, E](using Async) =
50+
val good1: List[Future[Result[T, E]]] => Future[Result[List[T], E]] = frs =>
51+
Future:
52+
Result:
53+
frs.map(_.await.ok) // OK
54+
55+
val good2: Result[Future[T], E] => Future[Result[T, E]] = rf =>
56+
Future:
57+
Result:
58+
rf.ok.await // OK, Future argument has type Result[T]
59+
60+
def fail3(fr: Future[Result[T, E]]^) =
61+
Result:
62+
Future: // error, escaping label from Result
63+
fr.await.ok
64+
65+
def fail4(fr: Future[Result[T, E]]^) =
66+
Result.make: // error, escaping label from Result
67+
Future:
68+
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)