Skip to content

Commit

Permalink
CC: Give more info when context function parameters leak (#20244)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
odersky authored Apr 24, 2024
2 parents b3f0aca + 6e08077 commit 96c956a
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 7 deletions.
10 changes: 7 additions & 3 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import Recheck.*
import scala.collection.mutable
import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult}
import StdNames.nme
import NameKinds.{DefaultGetterName, WildcardParamName}
import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
import reporting.trace

/** The capture checker */
Expand Down Expand Up @@ -1288,10 +1288,14 @@ class CheckCaptures extends Recheck, SymTransformer:
val added = widened.filter(isAllowed(_))
capt.println(i"heal $ref in $cs by widening to $added")
if !added.subCaptures(cs, frozen = false).isOK then
val location = if meth.exists then i" of $meth" else ""
val location = if meth.exists then i" of ${meth.showLocated}" else ""
val paramInfo =
if ref.paramName.info.kind.isInstanceOf[UniqueNameKind]
then i"${ref.paramName} from ${ref.binder}"
else i"${ref.paramName}"
val debugSetInfo = if ctx.settings.YccDebug.value then i" $cs" else ""
report.error(
i"local reference ${ref.paramName} leaks into outer capture set$debugSetInfo of type parameter $paramName$location",
i"local reference $paramInfo leaks into outer capture set$debugSetInfo of type parameter $paramName$location",
tree.srcPos)
else
widened.elems.foreach(recur)
Expand Down
21 changes: 21 additions & 0 deletions tests/neg-custom-args/captures/effect-swaps.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps.scala:64:8 ----------------------------------
63 | Result:
64 | Future: // error, escaping label from Result
| ^
| Found: Result.Ok[box Future[box T^?]^{fr, contextual$1}]
| Required: Result[Future[T], Nothing]
65 | fr.await.ok
|--------------------------------------------------------------------------------------------------------------------
|Inline stack trace
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|This location contains code that was inlined from effect-swaps.scala:41
41 | boundary(Ok(body))
| ^^^^^^^^
--------------------------------------------------------------------------------------------------------------------
|
| longer explanation available when compiling with `-explain`
-- Error: tests/neg-custom-args/captures/effect-swaps.scala:68:15 ------------------------------------------------------
68 | Result.make: //lbl ?=> // error, escaping label from Result
| ^^^^^^^^^^^
|local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]^):
| box Future[box T^?]^{fr, contextual$9} leaks into outer capture set of type parameter T of method make in object Result
70 changes: 70 additions & 0 deletions tests/neg-custom-args/captures/effect-swaps.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import annotation.capability

object boundary:

@capability final class Label[-T]

/** Abort current computation and instead return `value` as the value of
* the enclosing `boundary` call that created `label`.
*/
def break[T](value: T)(using label: Label[T]): Nothing = ???

def apply[T](body: Label[T] ?=> T): T = ???
end boundary

import boundary.{Label, break}

@capability trait Async
object Async:
def blocking[T](body: Async ?=> T): T = ???

class Future[+T]:
this: Future[T]^ =>
def await(using Async): T = ???
object Future:
def apply[T](op: Async ?=> T)(using Async): Future[T]^{op} = ???

enum Result[+T, +E]:
case Ok[+T](value: T) extends Result[T, Nothing]
case Err[+E](error: E) extends Result[Nothing, E]


object Result:
extension [T, E](r: Result[T, E]^)(using Label[Err[E]])

/** `_.ok` propagates Err to current Label */
def ok: T = r match
case Ok(value) => value
case Err(value) => break[Err[E]](Err(value))

transparent inline def apply[T, E](inline body: Label[Result[T, E]] ?=> T): Result[T, E] =
boundary(Ok(body))

// same as apply, but not an inline method
def make[T, E](body: Label[Result[T, E]] ?=> T): Result[T, E] =
boundary(Ok(body))

end Result

def test[T, E](using Async) =
import Result.*
Async.blocking: async ?=>
val good1: List[Future[Result[T, E]]] => Future[Result[List[T], E]] = frs =>
Future:
Result:
frs.map(_.await.ok) // OK

val good2: Result[Future[T], E] => Future[Result[T, E]] = rf =>
Future:
Result:
rf.ok.await // OK, Future argument has type Result[T]

def fail3(fr: Future[Result[T, E]]^) =
Result:
Future: // error, escaping label from Result
fr.await.ok

def fail4[T, E](fr: Future[Result[T, E]]^) =
Result.make: //lbl ?=> // error, escaping label from Result
Future: fut ?=>
fr.await.ok
2 changes: 1 addition & 1 deletion tests/neg-custom-args/captures/leaking-iterators.check
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- Error: tests/neg-custom-args/captures/leaking-iterators.scala:56:2 --------------------------------------------------
56 | usingLogFile: log => // error
| ^^^^^^^^^^^^
| local reference log leaks into outer capture set of type parameter R of method usingLogFile
| local reference log leaks into outer capture set of type parameter R of method usingLogFile in package cctest
6 changes: 3 additions & 3 deletions tests/neg-custom-args/captures/usingLogFile.check
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
-- Error: tests/neg-custom-args/captures/usingLogFile.scala:23:14 ------------------------------------------------------
23 | val later = usingLogFile { f => () => f.write(0) } // error
| ^^^^^^^^^^^^
| local reference f leaks into outer capture set of type parameter T of method usingLogFile
| local reference f leaks into outer capture set of type parameter T of method usingLogFile in object Test2
-- Error: tests/neg-custom-args/captures/usingLogFile.scala:28:23 ------------------------------------------------------
28 | private val later2 = usingLogFile { f => Cell(() => f.write(0)) } // error
| ^^^^^^^^^^^^
| local reference f leaks into outer capture set of type parameter T of method usingLogFile
| local reference f leaks into outer capture set of type parameter T of method usingLogFile in object Test2
-- Error: tests/neg-custom-args/captures/usingLogFile.scala:44:16 ------------------------------------------------------
44 | val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error
| ^^^^^^^^^
| local reference f leaks into outer capture set of type parameter T of method usingFile
| local reference f leaks into outer capture set of type parameter T of method usingFile in object Test3

0 comments on commit 96c956a

Please sign in to comment.