diff --git a/arrow-libs/core/arrow-core/api/arrow-core.api b/arrow-libs/core/arrow-core/api/arrow-core.api index 816517114e..e9212941bf 100644 --- a/arrow-libs/core/arrow-core/api/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/arrow-core.api @@ -3647,6 +3647,7 @@ public final class arrow/core/raise/RaiseKt { public static final fun toValidated (Lkotlin/jvm/functions/Function1;)Larrow/core/Validated; public static final fun toValidated (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun traced (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; + public static final fun withCause (Larrow/core/raise/Traced;Larrow/core/raise/Traced;)Larrow/core/raise/Traced; public static final fun withError (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun zipOrAccumulate (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function9;)Ljava/lang/Object; public static final fun zipOrAccumulate (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function8;)Ljava/lang/Object; diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Fold.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Fold.kt index e6487699af..a4729445bf 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Fold.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Fold.kt @@ -155,6 +155,7 @@ public inline fun fold( * [Raise] context barrier. */ @JvmName("_foldUnsafe") +@OptIn(DelicateRaiseApi::class) public inline fun foldUnsafe( @BuilderInference block: Raise.() -> A, catch: (throwable: Throwable) -> B, @@ -171,7 +172,7 @@ public inline fun foldUnsafe( val res = block(raise) raise.complete() transform(res) - } catch (e: CancellationException) { + } catch (e: RaiseCancellationException) { raise.complete() recover(e.raisedOrRethrow(raise)) } catch (e: Throwable) { @@ -219,24 +220,37 @@ public inline fun foldUnsafe( * but **this only occurs** when composing `traced`. * The stacktrace creation is disabled if no `traced` calls are made within the function composition. */ +@OptIn(DelicateRaiseApi::class) @ExperimentalTraceApi public inline fun Raise.traced( @BuilderInference block: Raise.() -> A, trace: (trace: Trace, error: Error) -> Unit ): A { - val isOuterTraced = this is DefaultRaise && isTraced - val nested: DefaultRaise = if (isOuterTraced) this as DefaultRaise else DefaultRaise(true) + val nested = DefaultRaise(true) return try { - block.invoke(nested) - } catch (e: CancellationException) { + block(nested).also { nested.complete() } + } catch (e: Traced) { + nested.complete() val r: Error = e.raisedOrRethrow(nested) trace(Trace(e), r) - if (isOuterTraced) throw e else raise(r) + // If our outer Raise happens to be traced + // Then we want the stack trace to match the inner one + try { + raise(r) + } catch (rethrown: Traced) { + throw rethrown.withCause(e) + } } } +@PublishedApi +@DelicateRaiseApi +internal fun Traced.withCause(cause: Traced): Traced = + Traced(raised, raise, cause) + /** Returns the raised value, rethrows the CancellationException if not our scope */ @PublishedApi +@DelicateRaiseApi @Suppress("UNCHECKED_CAST") internal fun CancellationException.raisedOrRethrow(raise: DefaultRaise): R = when { @@ -251,6 +265,7 @@ internal class DefaultRaise(@PublishedApi internal val isTraced: Boolean) : Rais @PublishedApi internal fun complete(): Boolean = isActive.getAndSet(false) + @OptIn(DelicateRaiseApi::class) override fun raise(r: Any?): Nothing = when { isActive.value -> throw if (isTraced) Traced(r, this) else NoTrace(r, this) else -> throw RaiseLeakedException() @@ -277,7 +292,7 @@ public sealed class RaiseCancellationException( internal expect class NoTrace(raised: Any?, raise: Raise) : RaiseCancellationException @DelicateRaiseApi -internal class Traced(raised: Any?, raise: Raise): RaiseCancellationException(raised, raise) +internal class Traced(raised: Any?, raise: Raise, override val cause: Traced? = null): RaiseCancellationException(raised, raise) private class RaiseLeakedException : IllegalStateException( """ diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt index 6306dcd6ec..d5a9343953 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt @@ -22,7 +22,7 @@ public value class Trace(private val exception: CancellationException) { * Note, the first line in the stacktrace will be the `RaiseCancellationException`. * The users call to `raise` can found in the_second line of the stacktrace. */ - public fun stackTraceToString(): String = exception.stackTraceToString() + public fun stackTraceToString(): String = (exception.cause ?: exception).stackTraceToString() /** * Prints the stacktrace. @@ -31,7 +31,7 @@ public value class Trace(private val exception: CancellationException) { * The users call to `raise` can found in the_second line of the stacktrace. */ public fun printStackTrace(): Unit = - exception.printStackTrace() + (exception.cause ?: exception).printStackTrace() /** * Returns the suppressed exceptions that occurred during cancellation of the surrounding coroutines, @@ -41,5 +41,5 @@ public value class Trace(private val exception: CancellationException) { * if the finalizer then results in a `Throwable` it will be added as a `suppressedException` to the [CancellationException]. */ public fun suppressedExceptions(): List = - exception.suppressedExceptions + exception.cause?.suppressedExceptions.orEmpty() + exception.suppressedExceptions } diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/TraceSpec.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/TraceSpec.kt index 7a488a1f2f..1e3058acbf 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/TraceSpec.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/TraceSpec.kt @@ -43,4 +43,14 @@ class TraceSpec : StringSpec({ } } } + + "nested tracing - different types" { + either { + traced({ + traced ({ + raise(Unit) + }) { _, _ -> unreachable() } + }) { _, unit -> unit shouldBe Unit } + } + } })