Skip to content

CC: Give more info when context function parameters leak #20244

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading