diff --git a/shared/src/test/scala/CCBehavior.scala b/shared/src/test/scala/CCBehavior.scala new file mode 100644 index 00000000..c6fb30c5 --- /dev/null +++ b/shared/src/test/scala/CCBehavior.scala @@ -0,0 +1,52 @@ +import language.experimental.captureChecking + +import gears.async.AsyncOperations.* +import gears.async.default.given +import gears.async.{Async, AsyncSupport, Future, uninterruptible} + +import java.util.concurrent.CancellationException +import scala.annotation.capability +import scala.concurrent.duration.{Duration, DurationInt} +import scala.util.Success +import scala.util.boundary + +type Result[+T, +E] = Either[E, T] +object Result: + @capability opaque type Label[-T, -E] = boundary.Label[Result[T, E]] + // ^ doesn't work? + + def apply[T, E](body: Label[T, E] ?=> T): Result[T, E] = + boundary(Right(body)) + + extension [U, E](r: Result[U, E]^)(using Label[Nothing, E]^) + def ok: U = r match + case Left(value) => boundary.break(Left(value)) + case Right(value) => value + +class CaptureCheckingBehavior extends munit.FunSuite: + import Result.* + + test("good") { + // don't do this in real code! capturing Async.blocking's Async context across functions is hard to track + Async.blocking: async ?=> + def good1[T, E](frs: List[Future[Result[T, E]]]): Future[Result[List[T], E]]^{async} = + Future: + Result: + frs.map(_.await.ok) + + def good2[T, E](rf: Result[Future[T], E]): Future[Result[T, E]]^{async} = + Future: + Result: + rf.ok.await // OK, Future argument has type Result[T] + + def useless4[T, E](fr: Future[Result[T, E]]) = + fr.await.map(Future(_)) + } + + // test("bad") { + // Async.blocking: async ?=> + // def fail3[T, E](fr: Future[Result[T, E]]): Result[Future[T]^{async}, E] = + // Result: label ?=> + // Future: fut ?=> + // fr.await.ok // error, escaping label from Result + // }