diff --git a/arrow-libs/core/arrow-core/api/android/arrow-core.api b/arrow-libs/core/arrow-core/api/android/arrow-core.api index 4f419e93f28..eefab6744b1 100644 --- a/arrow-libs/core/arrow-core/api/android/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/android/arrow-core.api @@ -913,7 +913,6 @@ public final class arrow/core/raise/DefaultRaise : arrow/core/raise/Raise { public final fun complete ()Z public fun invoke (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun invoke (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun isTraced ()Z public fun raise (Ljava/lang/Object;)Ljava/lang/Void; } @@ -946,6 +945,7 @@ public final class arrow/core/raise/IorRaise : arrow/core/raise/Raise { public final fun getOrAccumulate (Larrow/core/Either;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun invoke (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun invoke (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun isTraced ()Z public fun raise (Ljava/lang/Object;)Ljava/lang/Void; public final fun recover (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; } @@ -960,6 +960,7 @@ public abstract interface class arrow/core/raise/Raise { public abstract fun bindAll-vcjLgH4 (Ljava/util/List;)Ljava/util/List; public abstract fun invoke (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public abstract fun invoke (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun isTraced ()Z public abstract fun raise (Ljava/lang/Object;)Ljava/lang/Void; } @@ -973,6 +974,7 @@ public final class arrow/core/raise/Raise$DefaultImpls { public static fun bindAll-vcjLgH4 (Larrow/core/raise/Raise;Ljava/util/List;)Ljava/util/List; public static fun invoke (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static fun invoke (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun isTraced (Larrow/core/raise/Raise;)Z } public class arrow/core/raise/RaiseAccumulate : arrow/core/raise/Accumulate, arrow/core/raise/Raise { @@ -1101,7 +1103,6 @@ public final class arrow/core/raise/RaiseKt { public static final fun toResult (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun toResult (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun tolerant (Larrow/core/raise/Accumulate;Larrow/core/raise/Raise;)Larrow/core/raise/Accumulate; - 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; @@ -1137,6 +1138,7 @@ public final class arrow/core/raise/ResultRaise : arrow/core/raise/Raise { public final fun bindAllResult (Ljava/util/Set;)Ljava/util/Set; public fun invoke (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun invoke (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun isTraced ()Z public synthetic fun raise (Ljava/lang/Object;)Ljava/lang/Void; public fun raise (Ljava/lang/Throwable;)Ljava/lang/Void; public final fun recover (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; @@ -1171,13 +1173,6 @@ public final class arrow/core/raise/SingletonRaise : arrow/core/raise/Raise { public final fun recover (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; } -public final class arrow/core/raise/Traced : arrow/core/raise/RaiseCancellationException { - public fun (Ljava/lang/Object;Larrow/core/raise/Raise;Larrow/core/raise/Traced;)V - public synthetic fun (Ljava/lang/Object;Larrow/core/raise/Raise;Larrow/core/raise/Traced;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getCause ()Larrow/core/raise/Traced; - public synthetic fun getCause ()Ljava/lang/Throwable; -} - public final class arrow/core/raise/context/RaiseContextualKt { public static final fun bind (Larrow/core/raise/Raise;Larrow/core/Either;)Ljava/lang/Object; public static final fun bind (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/arrow-libs/core/arrow-core/api/arrow-core.klib.api b/arrow-libs/core/arrow-core/api/arrow-core.klib.api index 454d0b1d310..7956b50a478 100644 --- a/arrow-libs/core/arrow-core/api/arrow-core.klib.api +++ b/arrow-libs/core/arrow-core/api/arrow-core.klib.api @@ -89,6 +89,8 @@ final class <#A: kotlin/Any?> arrow.core.raise/IorRaise : arrow.core.raise/Raise final val combineError // arrow.core.raise/IorRaise.combineError|{}combineError[0] final fun (): kotlin/Function2<#A, #A, #A> // arrow.core.raise/IorRaise.combineError.|(){}[0] + final val isTraced // arrow.core.raise/IorRaise.isTraced|{}isTraced[0] + final fun (): kotlin/Boolean // arrow.core.raise/IorRaise.isTraced.|(){}[0] final fun <#A1: kotlin/Any?, #B1: kotlin/Any?> (kotlin.collections/Map<#A1, arrow.core/Either<#A, #B1>>).bindAll(): kotlin.collections/Map<#A1, #B1> // arrow.core.raise/IorRaise.bindAll|bindAll@kotlin.collections.Map<0:0,arrow.core.Either<1:0,0:1>>(){0§;1§}[0] final fun <#A1: kotlin/Any?, #B1: kotlin/Any?> (kotlin.collections/Map<#A1, arrow.core/Ior<#A, #B1>>).bindAll(): kotlin.collections/Map<#A1, #B1> // arrow.core.raise/IorRaise.bindAll|bindAll@kotlin.collections.Map<0:0,arrow.core.Ior<1:0,0:1>>(){0§;1§}[0] @@ -318,9 +320,6 @@ final class <#A: out kotlin/Any?> arrow.core/Some : arrow.core/Option<#A> { // a final class arrow.core.raise/DefaultRaise : arrow.core.raise/Raise { // arrow.core.raise/DefaultRaise|null[0] constructor (kotlin/Boolean) // arrow.core.raise/DefaultRaise.|(kotlin.Boolean){}[0] - final val isTraced // arrow.core.raise/DefaultRaise.isTraced|{}isTraced[0] - final fun (): kotlin/Boolean // arrow.core.raise/DefaultRaise.isTraced.|(){}[0] - final fun complete(): kotlin/Boolean // arrow.core.raise/DefaultRaise.complete|complete(){}[0] final fun raise(kotlin/Any?): kotlin/Nothing // arrow.core.raise/DefaultRaise.raise|raise(kotlin.Any?){}[0] } @@ -328,6 +327,9 @@ final class arrow.core.raise/DefaultRaise : arrow.core.raise/Raise final class arrow.core.raise/ResultRaise : arrow.core.raise/Raise { // arrow.core.raise/ResultRaise|null[0] constructor (arrow.core.raise/Raise) // arrow.core.raise/ResultRaise.|(arrow.core.raise.Raise){}[0] + final val isTraced // arrow.core.raise/ResultRaise.isTraced|{}isTraced[0] + final fun (): kotlin/Boolean // arrow.core.raise/ResultRaise.isTraced.|(){}[0] + final fun <#A1: kotlin/Any?, #B1: kotlin/Any?> (kotlin.collections/Map<#A1, arrow.core/Either>).bindAll(): kotlin.collections/Map<#A1, #B1> // arrow.core.raise/ResultRaise.bindAll|bindAll@kotlin.collections.Map<0:0,arrow.core.Either>(){0§;1§}[0] final fun <#A1: kotlin/Any?, #B1: kotlin/Any?> (kotlin.collections/Map<#A1, kotlin/Result<#B1>>).bindAll(): kotlin.collections/Map<#A1, #B1> // arrow.core.raise/ResultRaise.bindAll|bindAll@kotlin.collections.Map<0:0,kotlin.Result<0:1>>(){0§;1§}[0] final fun <#A1: kotlin/Any?> (arrow.core/Either).bind(): #A1 // arrow.core.raise/ResultRaise.bind|bind@arrow.core.Either(){0§}[0] @@ -346,13 +348,6 @@ final class arrow.core.raise/ResultRaise : arrow.core.raise/Raise (kotlin.coroutines/SuspendFunction1, #A1>).invoke(): #A1 // arrow.core.raise/ResultRaise.invoke|invoke@kotlin.coroutines.SuspendFunction1,0:0>(){0§}[0] } -final class arrow.core.raise/Traced : arrow.core.raise/RaiseCancellationException { // arrow.core.raise/Traced|null[0] - constructor (kotlin/Any?, arrow.core.raise/Raise, arrow.core.raise/Traced? = ...) // arrow.core.raise/Traced.|(kotlin.Any?;arrow.core.raise.Raise;arrow.core.raise.Traced?){}[0] - - final val cause // arrow.core.raise/Traced.cause|{}cause[0] - final fun (): arrow.core.raise/Traced? // arrow.core.raise/Traced.cause.|(){}[0] -} - final value class <#A: kotlin/Any?, #B: kotlin/Any?> arrow.core/AtomicMemoizationCache : arrow.core/MemoizationCache<#A, #B> { // arrow.core/AtomicMemoizationCache|null[0] constructor (arrow.atomic/Atomic> = ...) // arrow.core/AtomicMemoizationCache.|(arrow.atomic.Atomic>){}[0] @@ -721,7 +716,6 @@ final const val arrow.core/NicheAPI // arrow.core/NicheAPI|{}NicheAPI[0] final const val arrow.core/RedundantAPI // arrow.core/RedundantAPI|{}RedundantAPI[0] final fun (): kotlin/String // arrow.core/RedundantAPI.|(){}[0] -final fun (arrow.core.raise/Traced).arrow.core.raise/withCause(arrow.core.raise/Traced): arrow.core.raise/Traced // arrow.core.raise/withCause|withCause@arrow.core.raise.Traced(arrow.core.raise.Traced){}[0] final fun (kotlin/String).arrow.core/escaped(): kotlin/String // arrow.core/escaped|escaped@kotlin.String(){}[0] final fun <#A: kotlin/Any> (kotlin/Function1<#A, kotlin/Boolean>).arrow.core/mapNullable(): kotlin/Function1<#A?, kotlin/Boolean> // arrow.core/mapNullable|mapNullable@kotlin.Function1<0:0,kotlin.Boolean>(){0§}[0] final fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?, #D: kotlin/Any?, #E: kotlin/Any?, #F: kotlin/Any?, #G: kotlin/Any?, #H: kotlin/Any?, #I: kotlin/Any?, #J: kotlin/Any?, #K: kotlin/Any?> (kotlin.sequences/Sequence<#A>).arrow.core/zip(kotlin.sequences/Sequence<#B>, kotlin.sequences/Sequence<#C>, kotlin.sequences/Sequence<#D>, kotlin.sequences/Sequence<#E>, kotlin.sequences/Sequence<#F>, kotlin.sequences/Sequence<#G>, kotlin.sequences/Sequence<#H>, kotlin.sequences/Sequence<#I>, kotlin.sequences/Sequence<#J>, kotlin/Function10<#A, #B, #C, #D, #E, #F, #G, #H, #I, #J, #K>): kotlin.sequences/Sequence<#K> // arrow.core/zip|zip@kotlin.sequences.Sequence<0:0>(kotlin.sequences.Sequence<0:1>;kotlin.sequences.Sequence<0:2>;kotlin.sequences.Sequence<0:3>;kotlin.sequences.Sequence<0:4>;kotlin.sequences.Sequence<0:5>;kotlin.sequences.Sequence<0:6>;kotlin.sequences.Sequence<0:7>;kotlin.sequences.Sequence<0:8>;kotlin.sequences.Sequence<0:9>;kotlin.Function10<0:0,0:1,0:2,0:3,0:4,0:5,0:6,0:7,0:8,0:9,0:10>){0§;1§;2§;3§;4§;5§;6§;7§;8§;9§;10§}[0] diff --git a/arrow-libs/core/arrow-core/api/jvm/arrow-core.api b/arrow-libs/core/arrow-core/api/jvm/arrow-core.api index 4f419e93f28..eefab6744b1 100644 --- a/arrow-libs/core/arrow-core/api/jvm/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/jvm/arrow-core.api @@ -913,7 +913,6 @@ public final class arrow/core/raise/DefaultRaise : arrow/core/raise/Raise { public final fun complete ()Z public fun invoke (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun invoke (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun isTraced ()Z public fun raise (Ljava/lang/Object;)Ljava/lang/Void; } @@ -946,6 +945,7 @@ public final class arrow/core/raise/IorRaise : arrow/core/raise/Raise { public final fun getOrAccumulate (Larrow/core/Either;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun invoke (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun invoke (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun isTraced ()Z public fun raise (Ljava/lang/Object;)Ljava/lang/Void; public final fun recover (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; } @@ -960,6 +960,7 @@ public abstract interface class arrow/core/raise/Raise { public abstract fun bindAll-vcjLgH4 (Ljava/util/List;)Ljava/util/List; public abstract fun invoke (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public abstract fun invoke (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun isTraced ()Z public abstract fun raise (Ljava/lang/Object;)Ljava/lang/Void; } @@ -973,6 +974,7 @@ public final class arrow/core/raise/Raise$DefaultImpls { public static fun bindAll-vcjLgH4 (Larrow/core/raise/Raise;Ljava/util/List;)Ljava/util/List; public static fun invoke (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static fun invoke (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun isTraced (Larrow/core/raise/Raise;)Z } public class arrow/core/raise/RaiseAccumulate : arrow/core/raise/Accumulate, arrow/core/raise/Raise { @@ -1101,7 +1103,6 @@ public final class arrow/core/raise/RaiseKt { public static final fun toResult (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun toResult (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun tolerant (Larrow/core/raise/Accumulate;Larrow/core/raise/Raise;)Larrow/core/raise/Accumulate; - 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; @@ -1137,6 +1138,7 @@ public final class arrow/core/raise/ResultRaise : arrow/core/raise/Raise { public final fun bindAllResult (Ljava/util/Set;)Ljava/util/Set; public fun invoke (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun invoke (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun isTraced ()Z public synthetic fun raise (Ljava/lang/Object;)Ljava/lang/Void; public fun raise (Ljava/lang/Throwable;)Ljava/lang/Void; public final fun recover (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; @@ -1171,13 +1173,6 @@ public final class arrow/core/raise/SingletonRaise : arrow/core/raise/Raise { public final fun recover (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; } -public final class arrow/core/raise/Traced : arrow/core/raise/RaiseCancellationException { - public fun (Ljava/lang/Object;Larrow/core/raise/Raise;Larrow/core/raise/Traced;)V - public synthetic fun (Ljava/lang/Object;Larrow/core/raise/Raise;Larrow/core/raise/Traced;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getCause ()Larrow/core/raise/Traced; - public synthetic fun getCause ()Ljava/lang/Throwable; -} - public final class arrow/core/raise/context/RaiseContextualKt { public static final fun bind (Larrow/core/raise/Raise;Larrow/core/Either;)Ljava/lang/Object; public static final fun bind (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt index 78452d42e99..569e111233c 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt @@ -187,6 +187,9 @@ private class IorAccumulate( fun raiseSingle(e: Error): Nothing = raise.raise(EmptyValue.combine(state.get(), e, combineError)) override fun raise(r: NonEmptyList) = raiseSingle(r.reduce(combineError)) + @ExperimentalTraceApi + override val isTraced: Boolean + get() = raise.isTraced private val raiseAccumulated = RaiseAccumulate.Error { raise.raise(EmptyValue.unbox(state.get())) } @ExperimentalRaiseAccumulateApi @@ -221,6 +224,10 @@ public class SingletonRaise(private val raise: Raise) : Raise { @RaiseDSL override fun raise(r: E): Nothing = raise() + @ExperimentalTraceApi + override val isTraced: Boolean + get() = raise.isTraced + @RaiseDSL public fun ensure(condition: Boolean) { contract { returns() implies condition } 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 9a3fc545692..13ffc29c0e8 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 @@ -126,7 +126,7 @@ public inline fun fold( * This method should never be wrapped in `try`/`catch` as it will not throw any unexpected errors, * it will only result in [CancellationException], or fatal exceptions such as `OutOfMemoryError`. */ -@OptIn(DelicateRaiseApi::class) +@OptIn(DelicateRaiseApi::class, ExperimentalTraceApi::class) // Trace-y things only happen if user opts in elsewhere @JvmName("_fold") public inline fun fold( @BuilderInference block: Raise.() -> A, @@ -140,14 +140,33 @@ public inline fun fold( callsInPlace(recover, AT_MOST_ONCE) callsInPlace(transform, AT_MOST_ONCE) } - val raise = DefaultRaise(false) + return foldTraced(block, catch, { _, e -> recover(e) }, transform, isTraced = false) +} + +@OptIn(DelicateRaiseApi::class) +@ExperimentalTraceApi +@JvmName("_fold") +public inline fun foldTraced( + @BuilderInference block: Raise.() -> A, + catch: (throwable: Throwable) -> B, + recover: (trace: Trace, error: Error) -> B, + transform: (value: A) -> B, + isTraced: Boolean = true, +): B { + contract { + callsInPlace(block, AT_MOST_ONCE) + callsInPlace(catch, AT_MOST_ONCE) + callsInPlace(recover, AT_MOST_ONCE) + callsInPlace(transform, AT_MOST_ONCE) + } + val raise = DefaultRaise(isTraced) return try { val res = block(raise) raise.complete() transform(res) } catch (e: RaiseCancellationException) { raise.complete() - recover(e.raisedOrRethrow(raise)) + recover(Trace(e), e.raisedOrRethrow(raise)) } catch (e: Throwable) { raise.complete() catch(e.nonFatalOrThrow()) @@ -203,39 +222,40 @@ public inline fun Raise.traced( callsInPlace(trace, AT_MOST_ONCE) callsInPlace(block, EXACTLY_ONCE) } - return withErrorTraced({ t, error -> error.also { trace(t, error) } }, block) + return withErrorTraced({ t, error -> error.also { trace(t, error) } }, block = block) } @OptIn(DelicateRaiseApi::class) @ExperimentalTraceApi public inline fun Raise.withErrorTraced( transform: (Trace, OtherError) -> Error, - block: Raise.() -> A + isTraced: Boolean = true, + block: Raise.() -> A, ): A { contract { callsInPlace(transform, AT_MOST_ONCE) callsInPlace(block, EXACTLY_ONCE) } - val nested = DefaultRaise(true) - return try { - block(nested).also { nested.complete() } - } catch (e: Traced) { - nested.complete() - val error = transform(Trace(e), e.raisedOrRethrow(nested)) - // If our outer Raise happens to be traced - // Then we want the stack trace to match the inner one - try { - raise(error) - } catch (rethrown: Traced) { - throw rethrown.withCause(e) - } - } + foldTraced( + block = { return block() }, + catch = { throw it }, + recover = { t, e -> + try { + raise(transform(t, e)) + } catch (rethrown: Traced) { + throw rethrown.withTrace(t) + } + }, + transform = { it }, + isTraced = isTraced + ) } @PublishedApi @DelicateRaiseApi -internal fun Traced.withCause(cause: Traced): Traced = - Traced(raised, raise, cause) +@ExperimentalTraceApi +internal fun Traced.withTrace(trace: Trace): Traced = + if (trace.isTrulyTraced()) Traced(raised, raise, trace) else this /** Returns the raised value, rethrows the CancellationException if not our scope */ @PublishedApi @@ -249,13 +269,16 @@ internal fun CancellationException.raisedOrRethrow(raise: DefaultRaise): R = /** Serves as both purposes of a scope-reference token, and a default implementation for Raise. */ @PublishedApi -internal class DefaultRaise(@PublishedApi internal val isTraced: Boolean) : Raise { +internal class DefaultRaise( + @property:ExperimentalTraceApi + override val isTraced: Boolean +) : Raise { private val isActive = AtomicBoolean(true) @PublishedApi @IgnorableReturnValue internal fun complete(): Boolean = isActive.getAndSet(false) - @OptIn(DelicateRaiseApi::class) + @OptIn(DelicateRaiseApi::class, ExperimentalTraceApi::class) override fun raise(r: Any?): Nothing = when { isActive.value -> throw if (isTraced) Traced(r, this) else NoTrace(r, this) else -> throw RaiseLeakedException() @@ -283,8 +306,8 @@ public expect sealed class RaiseCancellationException( @DelicateRaiseApi internal expect fun NoTrace(raised: Any?, raise: Raise): RaiseCancellationException -@DelicateRaiseApi @PublishedApi -internal class Traced(raised: Any?, raise: Raise, override val cause: Traced? = null) : RaiseCancellationException(raised, raise) +@DelicateRaiseApi @ExperimentalTraceApi @PublishedApi +internal class Traced(raised: Any?, raise: Raise, val trace: Trace? = null) : RaiseCancellationException(raised, raise) private class RaiseLeakedException : IllegalStateException( """ diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Raise.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Raise.kt index caf7b134af8..bc0e5bf028f 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Raise.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Raise.kt @@ -208,6 +208,15 @@ public interface Raise { @RaiseDSL public fun raise(r: Error): Nothing + /** + * Indicates whether this [Raise] context is traced. + * This is used in `withError` to propagate tracing information. + * You should override this property if your [Raise] implementation is based on another. + */ + @ExperimentalTraceApi + public val isTraced: Boolean + get() = false + /** * Invoke an [EagerEffect] inside `this` [Raise] context. * Any _logical failure_ is raised in `this` [Raise] context, @@ -694,6 +703,7 @@ public inline fun Raise.ensureNotNull(value: B?, raise: * * */ +@OptIn(ExperimentalTraceApi::class) @RaiseDSL public inline fun Raise.withError( transform: (OtherError) -> Error, @@ -703,7 +713,7 @@ public inline fun Raise.withError( callsInPlace(block, EXACTLY_ONCE) callsInPlace(transform, AT_MOST_ONCE) } - recover({ return block() }) { raise(transform(it)) } + return withErrorTraced({ _, e -> transform(e) }, this.isTraced, block) } /** diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/RaiseAccumulate.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/RaiseAccumulate.kt index 6a18657988d..9da82fb1933 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/RaiseAccumulate.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/RaiseAccumulate.kt @@ -892,6 +892,10 @@ public open class RaiseAccumulate @ExperimentalRaiseAccumulateApi constru override fun raise(r: Error): Nothing = raiseErrorsWith(r) + @ExperimentalTraceApi + override val isTraced: Boolean + get() = raise.isTraced + public override fun Map>.bindAll(): Map = raise.mapValuesOrAccumulate(this) { it.value.bind() } @@ -1101,6 +1105,10 @@ private class ListAccumulate(private val raise: Raise fun raiseSingle(r: Error): Nothing = raise.raise(NonEmptyList(list + r)) override fun raise(r: NonEmptyList) = raise.raise(NonEmptyList(list + r.all)) + @ExperimentalTraceApi + override val isTraced: Boolean + get() = raise.isTraced + // only valid if list is not empty // errors are never removed from `list`, so once this is valid, it stays valid private val error = Error { raise.raise(NonEmptyList(list)) } 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 d5a93439536..24f826f9b62 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 @@ -15,14 +15,20 @@ public annotation class ExperimentalTraceApi /** Tracing result. Allows to inspect the traces from where raise was called. */ @ExperimentalTraceApi @JvmInline -public value class Trace(private val exception: CancellationException) { +public value class Trace(internal val exception: CancellationException) { + @DelicateRaiseApi + internal fun isTrulyTraced(): Boolean = exception is Traced + + @OptIn(DelicateRaiseApi::class) + internal val innerTrace get() = (exception as? Traced)?.trace + /** * Returns the stacktrace as a [String] * * 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.cause ?: exception).stackTraceToString() + public fun stackTraceToString(): String = stackTraceToStringImpl() /** * Prints the stacktrace. @@ -30,8 +36,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 printStackTrace(): Unit = - (exception.cause ?: exception).printStackTrace() + public fun printStackTrace(): Unit = printStackTraceImpl() /** * Returns the suppressed exceptions that occurred during cancellation of the surrounding coroutines, @@ -40,6 +45,17 @@ public value class Trace(private val exception: CancellationException) { * When consuming a `Resource` fails due to [Raise.raise] it results in `ExitCase.Cancelled`, * if the finalizer then results in a `Throwable` it will be added as a `suppressedException` to the [CancellationException]. */ - public fun suppressedExceptions(): List = - exception.cause?.suppressedExceptions.orEmpty() + exception.suppressedExceptions + public fun suppressedExceptions(): List = buildList { suppressedExceptionsImpl(this@Trace) } +} + +@ExperimentalTraceApi +private tailrec fun Trace.stackTraceToStringImpl(): String = innerTrace?.stackTraceToStringImpl() ?: exception.stackTraceToString() + +@ExperimentalTraceApi +private tailrec fun Trace.printStackTraceImpl(): Unit = innerTrace?.printStackTraceImpl() ?: exception.printStackTrace() + +@ExperimentalTraceApi +private tailrec fun MutableList.suppressedExceptionsImpl(trace: Trace) { + addAll(trace.exception.suppressedExceptions) + trace.innerTrace?.let { return suppressedExceptionsImpl(it) } } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/arrow/core/raise/TraceJvmSpec.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/arrow/core/raise/TraceJvmSpec.kt index 94d3d50c740..7089e455b60 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/arrow/core/raise/TraceJvmSpec.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/arrow/core/raise/TraceJvmSpec.kt @@ -7,7 +7,7 @@ import kotlinx.coroutines.test.runTest @OptIn(ExperimentalTraceApi::class) class TraceJvmSpec { @Test fun canTraceATypedError() = runTest { - either { + val _ = merge { traced({ raise(RuntimeException("")) }) { traced, raised -> // Remove first 2 lines: // arrow.core.raise.RaiseCancellationException @@ -22,4 +22,24 @@ class TraceJvmSpec { } } } + + @Test fun canTraceAMappedError() = runTest { + val _ = merge { + traced({ + withError({ raised: Exception -> + // Remove first line: + // java.lang.RuntimeException: + raised.stackTraceToString().lines().drop(1) + }) { + raise(RuntimeException("")) + } + }) { traced, exceptionTrace -> + // Remove first 2 lines: + // arrow.core.raise.RaiseCancellationException + // at arrow.core.raise.DefaultRaise.raise(Fold.kt:187) + val trace = traced.stackTraceToString().lines().drop(2) + trace shouldBe exceptionTrace + } + } + } } diff --git a/arrow-libs/fx/arrow-fx-coroutines/api/android/arrow-fx-coroutines.api b/arrow-libs/fx/arrow-fx-coroutines/api/android/arrow-fx-coroutines.api index 9dcd49550a3..115f19e3845 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/api/android/arrow-fx-coroutines.api +++ b/arrow-libs/fx/arrow-fx-coroutines/api/android/arrow-fx-coroutines.api @@ -251,6 +251,7 @@ public final class arrow/fx/coroutines/RaiseScope$DefaultImpls { public static fun bindAll-vcjLgH4 (Larrow/fx/coroutines/RaiseScope;Ljava/util/List;)Ljava/util/List; public static fun invoke (Larrow/fx/coroutines/RaiseScope;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static fun invoke (Larrow/fx/coroutines/RaiseScope;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun isTraced (Larrow/fx/coroutines/RaiseScope;)Z } public abstract interface annotation class arrow/fx/coroutines/ResourceDSL : java/lang/annotation/Annotation { diff --git a/arrow-libs/fx/arrow-fx-coroutines/api/jvm/arrow-fx-coroutines.api b/arrow-libs/fx/arrow-fx-coroutines/api/jvm/arrow-fx-coroutines.api index 9dcd49550a3..115f19e3845 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/api/jvm/arrow-fx-coroutines.api +++ b/arrow-libs/fx/arrow-fx-coroutines/api/jvm/arrow-fx-coroutines.api @@ -251,6 +251,7 @@ public final class arrow/fx/coroutines/RaiseScope$DefaultImpls { public static fun bindAll-vcjLgH4 (Larrow/fx/coroutines/RaiseScope;Ljava/util/List;)Ljava/util/List; public static fun invoke (Larrow/fx/coroutines/RaiseScope;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static fun invoke (Larrow/fx/coroutines/RaiseScope;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun isTraced (Larrow/fx/coroutines/RaiseScope;)Z } public abstract interface annotation class arrow/fx/coroutines/ResourceDSL : java/lang/annotation/Annotation {