diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 9b6217033ede..4f57b5e0ed7a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -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 */ @@ -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) diff --git a/tests/neg-custom-args/captures/effect-swaps.check b/tests/neg-custom-args/captures/effect-swaps.check new file mode 100644 index 000000000000..bda3509645d1 --- /dev/null +++ b/tests/neg-custom-args/captures/effect-swaps.check @@ -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 diff --git a/tests/neg-custom-args/captures/effect-swaps.scala b/tests/neg-custom-args/captures/effect-swaps.scala new file mode 100644 index 000000000000..1d72077bb8da --- /dev/null +++ b/tests/neg-custom-args/captures/effect-swaps.scala @@ -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 diff --git a/tests/neg-custom-args/captures/leaking-iterators.check b/tests/neg-custom-args/captures/leaking-iterators.check index 0481a9a4d9e2..2f47a26e894a 100644 --- a/tests/neg-custom-args/captures/leaking-iterators.check +++ b/tests/neg-custom-args/captures/leaking-iterators.check @@ -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 diff --git a/tests/neg-custom-args/captures/usingLogFile.check b/tests/neg-custom-args/captures/usingLogFile.check index ef0c5d1e77c9..bf5c1dc4f83a 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -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