From afdb947eb0b6a9eee9b70eb5f7506ab0c62c374e Mon Sep 17 00:00:00 2001 From: Flavio Brasil Date: Tue, 15 Oct 2024 09:18:01 -0700 Subject: [PATCH] core: require AllowUnsafe in KyoApp.* methods (#748) --- README.md | 6 +- .../shared/src/main/scala/kyo/KyoApp.scala | 188 +++++++++--------- .../src/test/scala/kyo/KyoAppTest.scala | 6 +- 3 files changed, 104 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 35b6ca82f..0ecc9b288 100644 --- a/README.md +++ b/README.md @@ -442,11 +442,13 @@ val a: Int < IO = // Avoid! Run the application with a specific timeout val b: Int = - KyoApp.run(2.minutes)(a) + import AllowUnsafe.embrace.danger + KyoApp.Unsafe.run(2.minutes)(a) // Avoid! Run the application without specifying a timeout val c: Int = - KyoApp.run(a) + import AllowUnsafe.embrace.danger + KyoApp.Unsafe.run(a) ``` ## Core Effects diff --git a/kyo-core/shared/src/main/scala/kyo/KyoApp.scala b/kyo-core/shared/src/main/scala/kyo/KyoApp.scala index aaa1f07a6..0133adf40 100644 --- a/kyo-core/shared/src/main/scala/kyo/KyoApp.scala +++ b/kyo-core/shared/src/main/scala/kyo/KyoApp.scala @@ -15,6 +15,7 @@ abstract class KyoApp extends KyoApp.Base[KyoApp.Effects]: def clock: Clock = Clock.live override protected def handle[A: Flat](v: A < KyoApp.Effects)(using Frame): Unit = + import AllowUnsafe.embrace.danger v.map { v => if (()).equals(v) then () else Console.println(v) @@ -22,8 +23,9 @@ abstract class KyoApp extends KyoApp.Base[KyoApp.Effects]: Clock.let(clock), Random.let(random), Log.let(log), - KyoApp.run + KyoApp.Unsafe.run ) + end handle end KyoApp object KyoApp: @@ -56,96 +58,98 @@ object KyoApp: /** The combined effect type used by KyoApp. */ type Effects = Async & Resource & Abort[Throwable] - /** Attempts to run an effect with a specified timeout. - * - * Note: This method is unsafe and should only be used as the entrypoint of an application. - * - * @param timeout - * The maximum duration to wait for the effect to complete. - * @param v - * The effect to run. - * @param ev - * Evidence that A is Flat. - * @param frame - * The implicit Frame. - * @return - * A Result containing either the computed value or a Throwable. - */ - def attempt[A: Flat](timeout: Duration)(v: A < Effects)(using Frame): Result[Throwable, A] = - import AllowUnsafe.embrace.danger - IO.Unsafe.run(runFiber(timeout)(v).block(timeout)).eval - - /** Runs an effect with a specified timeout, throwing an exception if it fails. - * - * Note: This method is unsafe and should only be used as the entrypoint of an application. - * - * @param timeout - * The maximum duration to wait for the effect to complete. - * @param v - * The effect to run. - * @param ev - * Evidence that A is Flat. - * @param frame - * The implicit Frame. - * @return - * The computed value of type A. - * @throws Throwable - * if the effect fails or times out. - */ - def run[A: Flat](timeout: Duration)(v: A < Effects)(using Frame): A = - attempt(timeout)(v).getOrThrow - - /** Runs an effect with an infinite timeout. - * - * Note: This method is unsafe and should only be used as the entrypoint of an application. - * - * @param v - * The effect to run. - * @param ev - * Evidence that A is Flat. - * @param frame - * The implicit Frame. - * @return - * The computed value of type A. - * @throws Throwable - * if the effect fails. - */ - def run[A: Flat](v: A < Effects)(using Frame): A = - run(Duration.Infinity)(v) - - /** Creates a Fiber to run an effect with an infinite timeout. - * - * Note: This method is unsafe and should only be used as the entrypoint of an application. - * - * @param v - * The effect to run. - * @param ev - * Evidence that A is Flat. - * @param frame - * The implicit Frame. - * @return - * A Fiber representing the running effect. - */ - def runFiber[A: Flat](v: A < Effects)(using Frame): Fiber[Throwable, A] = - runFiber(Duration.Infinity)(v) - - /** Creates a Fiber to run an effect with a specified timeout. - * - * Note: This method is unsafe and should only be used as the entrypoint of an application. - * - * @param timeout - * The maximum duration to wait for the effect to complete. - * @param v - * The effect to run. - * @param ev - * Evidence that A is Flat. - * @param frame - * The implicit Frame. - * @return - * A Fiber representing the running effect. - */ - def runFiber[A: Flat](timeout: Duration)(v: A < Effects)(using Frame): Fiber[Throwable, A] = - import AllowUnsafe.embrace.danger - v.pipe(Resource.run, Async.run, IO.Unsafe.run).eval + /** WARNING: Low-level API meant for integrations, libraries, and performance-sensitive code. See AllowUnsafe for more details. */ + object Unsafe: + + /** Attempts to run an effect with a specified timeout. + * + * Note: This method is unsafe and should only be used as the entrypoint of an application. + * + * @param timeout + * The maximum duration to wait for the effect to complete. + * @param v + * The effect to run. + * @param ev + * Evidence that A is Flat. + * @param frame + * The implicit Frame. + * @return + * A Result containing either the computed value or a Throwable. + */ + def attempt[A: Flat](timeout: Duration)(v: A < Effects)(using Frame, AllowUnsafe): Result[Throwable, A] = + IO.Unsafe.run(runFiber(timeout)(v).block(timeout)).eval + + /** Runs an effect with a specified timeout, throwing an exception if it fails. + * + * Note: This method is unsafe and should only be used as the entrypoint of an application. + * + * @param timeout + * The maximum duration to wait for the effect to complete. + * @param v + * The effect to run. + * @param ev + * Evidence that A is Flat. + * @param frame + * The implicit Frame. + * @return + * The computed value of type A. + * @throws Throwable + * if the effect fails or times out. + */ + def run[A: Flat](timeout: Duration)(v: A < Effects)(using Frame, AllowUnsafe): A = + attempt(timeout)(v).getOrThrow + + /** Runs an effect with an infinite timeout. + * + * Note: This method is unsafe and should only be used as the entrypoint of an application. + * + * @param v + * The effect to run. + * @param ev + * Evidence that A is Flat. + * @param frame + * The implicit Frame. + * @return + * The computed value of type A. + * @throws Throwable + * if the effect fails. + */ + def run[A: Flat](v: A < Effects)(using Frame, AllowUnsafe): A = + run(Duration.Infinity)(v) + + /** Creates a Fiber to run an effect with an infinite timeout. + * + * Note: This method is unsafe and should only be used as the entrypoint of an application. + * + * @param v + * The effect to run. + * @param ev + * Evidence that A is Flat. + * @param frame + * The implicit Frame. + * @return + * A Fiber representing the running effect. + */ + def runFiber[A: Flat](v: A < Effects)(using Frame, AllowUnsafe): Fiber[Throwable, A] = + runFiber(Duration.Infinity)(v) + + /** Creates a Fiber to run an effect with a specified timeout. + * + * Note: This method is unsafe and should only be used as the entrypoint of an application. + * + * @param timeout + * The maximum duration to wait for the effect to complete. + * @param v + * The effect to run. + * @param ev + * Evidence that A is Flat. + * @param frame + * The implicit Frame. + * @return + * A Fiber representing the running effect. + */ + def runFiber[A: Flat](timeout: Duration)(v: A < Effects)(using Frame, AllowUnsafe): Fiber[Throwable, A] = + v.pipe(Resource.run, Async.timeout(timeout), Async.run, IO.Unsafe.run).eval + end Unsafe end KyoApp diff --git a/kyo-core/shared/src/test/scala/kyo/KyoAppTest.scala b/kyo-core/shared/src/test/scala/kyo/KyoAppTest.scala index 431050242..454f1bea4 100644 --- a/kyo-core/shared/src/test/scala/kyo/KyoAppTest.scala +++ b/kyo-core/shared/src/test/scala/kyo/KyoAppTest.scala @@ -41,7 +41,8 @@ class KyoAppTest extends Test: _ <- Async.run(()) yield 1 - assert(KyoApp.run(Duration.Infinity)(run) == 1) + import AllowUnsafe.embrace.danger + assert(KyoApp.Unsafe.run(Duration.Infinity)(run) == 1) } "failing effects" taggedAs jvmOnly in { def run: Unit < KyoApp.Effects = @@ -51,7 +52,8 @@ class KyoAppTest extends Test: _ <- Abort.fail(new RuntimeException("Aborts!")) yield () - KyoApp.attempt(Duration.Infinity)(run) match + import AllowUnsafe.embrace.danger + KyoApp.Unsafe.attempt(Duration.Infinity)(run) match case Result.Fail(exception) => assert(exception.getMessage == "Aborts!") case _ => fail("Unexpected Success...") }