Skip to content

Commit

Permalink
Fix #2779 (#2783)
Browse files Browse the repository at this point in the history
* Replace usage startCoroutineUninterceptedOrReturn with createCoroutineUnintercepted(..).resume(Unit) in FoldContinuation
  • Loading branch information
nomisRev committed Aug 3, 2022
1 parent 737fa39 commit 0eb7c44
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 15 deletions.
Expand Up @@ -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
Expand Down Expand Up @@ -568,7 +568,7 @@ import kotlin.coroutines.resume
* ```
* <!--- KNIT example-effect-guide-13.kt -->
*/
public sealed interface Effect<out R, out A> {
public interface Effect<out R, out A> {
/**
* Runs the suspending computation by creating a [Continuation], and running the `fold` function
* over the computation.
Expand Down Expand Up @@ -717,10 +717,7 @@ internal class FoldContinuation<B>(
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)
}
}
Expand Down Expand Up @@ -758,11 +755,7 @@ internal class FoldContinuation<B>(
*/
public fun <R, A> effect(f: suspend EffectScope<R>.() -> A): Effect<R, A> = DefaultEffect(f)

@Deprecated(
"This will be removed in Arrow 2.0",
level = DeprecationLevel.WARNING
)
internal class DefaultEffect<R, A>(val f: suspend EffectScope<R>.() -> A) : Effect<R, A> {
private class DefaultEffect<R, A>(val f: suspend EffectScope<R>.() -> A) : Effect<R, A> {
// We create a `Token` for fold Continuation, so we can properly differentiate between nested
// folds
override suspend fun <B> fold(recover: suspend (R) -> B, transform: suspend (A) -> B): B =
Expand Down
Expand Up @@ -56,10 +56,7 @@ public interface EffectScope<in R> {
* ```
* <!--- KNIT example-effect-scope-02.kt -->
*/
public suspend fun <B> Effect<R, B>.bind(): B =
when (this) {
is DefaultEffect -> f(this@EffectScope)
}
public suspend fun <B> Effect<R, B>.bind(): B = fold(this@EffectScope::shift, ::identity)

/**
* Runs the [EagerEffect] to finish, returning [B] or [shift] in case of [R],
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -286,6 +287,42 @@ class EffectSpec :
) shouldBe Failure(msg)
}
}

"#2779 - handleErrorWith does not make nested Continuations hang" {
checkAll(Arb.string()) { error ->
val failed: Effect<String, Int> = effect {
withContext(Dispatchers.Default) {}
shift(error)
}

val newError: Effect<List<Char>, 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<String, Int> = effect {
withContext(Dispatchers.Default) {}
shift(error)
}

val newError: Effect<List<Char>, Int> =
effect {
failed.fold({ r ->
effect<List<Char>, Int> {
shift(r.reversed().toList())
}.bind()
}, ::identity)
}

newError.toEither() shouldBe Either.Left(error.reversed().toList())
}
}
})

private data class Failure(val msg: String)
Expand Down

0 comments on commit 0eb7c44

Please sign in to comment.