From 0eb7c4496129b7f5316bf2df506b64078bbed662 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Wed, 3 Aug 2022 08:56:37 +0200 Subject: [PATCH] Fix #2779 (#2783) * Replace usage startCoroutineUninterceptedOrReturn with createCoroutineUnintercepted(..).resume(Unit) in FoldContinuation --- .../kotlin/arrow/core/continuations/Effect.kt | 15 ++------ .../arrow/core/continuations/EffectScope.kt | 5 +-- .../arrow/core/continuations/EffectSpec.kt | 37 +++++++++++++++++++ 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/Effect.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/Effect.kt index 4152a05e116..a66f6563786 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/Effect.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/Effect.kt @@ -11,7 +11,7 @@ import arrow.core.nonFatalOrThrow import kotlin.coroutines.Continuation import kotlin.coroutines.CoroutineContext import kotlin.coroutines.cancellation.CancellationException -import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED +import kotlin.coroutines.intrinsics.createCoroutineUnintercepted import kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn import kotlin.coroutines.resume @@ -568,7 +568,7 @@ import kotlin.coroutines.resume * ``` * */ -public sealed interface Effect { +public interface Effect { /** * Runs the suspending computation by creating a [Continuation], and running the `fold` function * over the computation. @@ -717,10 +717,7 @@ internal class FoldContinuation( result.fold(parent::resume) { throwable -> if (throwable is Suspend && token == throwable.token) { val f: suspend () -> B = { throwable.recover(throwable.shifted) as B } - when (val res = f.startCoroutineUninterceptedOrReturn(parent)) { - COROUTINE_SUSPENDED -> Unit - else -> parent.resume(res as B) - } + f.createCoroutineUnintercepted(parent).resume(Unit) } else parent.resumeWith(result) } } @@ -758,11 +755,7 @@ internal class FoldContinuation( */ public fun effect(f: suspend EffectScope.() -> A): Effect = DefaultEffect(f) -@Deprecated( - "This will be removed in Arrow 2.0", - level = DeprecationLevel.WARNING -) -internal class DefaultEffect(val f: suspend EffectScope.() -> A) : Effect { +private class DefaultEffect(val f: suspend EffectScope.() -> A) : Effect { // We create a `Token` for fold Continuation, so we can properly differentiate between nested // folds override suspend fun fold(recover: suspend (R) -> B, transform: suspend (A) -> B): B = diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EffectScope.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EffectScope.kt index b566dbeec8c..8a6a5b46466 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EffectScope.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EffectScope.kt @@ -56,10 +56,7 @@ public interface EffectScope { * ``` * */ - public suspend fun Effect.bind(): B = - when (this) { - is DefaultEffect -> f(this@EffectScope) - } + public suspend fun Effect.bind(): B = fold(this@EffectScope::shift, ::identity) /** * Runs the [EagerEffect] to finish, returning [B] or [shift] in case of [R], diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/continuations/EffectSpec.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/continuations/EffectSpec.kt index 7fe46a309a6..81f62f911e1 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/continuations/EffectSpec.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/continuations/EffectSpec.kt @@ -5,6 +5,7 @@ import arrow.core.identity import arrow.core.left import arrow.core.right import io.kotest.assertions.fail +import io.kotest.common.runBlocking import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe import io.kotest.property.Arb @@ -286,6 +287,42 @@ class EffectSpec : ) shouldBe Failure(msg) } } + + "#2779 - handleErrorWith does not make nested Continuations hang" { + checkAll(Arb.string()) { error -> + val failed: Effect = effect { + withContext(Dispatchers.Default) {} + shift(error) + } + + val newError: Effect, Int> = + failed.handleErrorWith { str -> + effect { shift(str.reversed().toList()) } + } + + newError.toEither() shouldBe Either.Left(error.reversed().toList()) + } + } + + "#2779 - bind nested in fold does not make nested Continuations hang" { + checkAll(Arb.string()) { error -> + val failed: Effect = effect { + withContext(Dispatchers.Default) {} + shift(error) + } + + val newError: Effect, Int> = + effect { + failed.fold({ r -> + effect, Int> { + shift(r.reversed().toList()) + }.bind() + }, ::identity) + } + + newError.toEither() shouldBe Either.Left(error.reversed().toList()) + } + } }) private data class Failure(val msg: String)