From d04a40020e61b32940b12e24a67ebd5b1ce22c9a Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Thu, 23 Feb 2023 17:48:41 +0100 Subject: [PATCH 01/17] Add tracing --- .../kotlin/arrow/core/raise/Fold.kt | 83 +++++++++++++++++-- .../arrow/core/raise/RaiseAccumulate.kt | 1 - .../kotlin/arrow/core/raise/Traced.kt | 37 +++++++++ 3 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt 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 2b8ecc71bd7..4b23f160457 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 @@ -1,10 +1,12 @@ @file:JvmMultifileClass @file:JvmName("RaiseKt") @file:OptIn(ExperimentalTypeInference::class, ExperimentalContracts::class) + package arrow.core.raise import arrow.atomic.AtomicBoolean import arrow.core.nonFatalOrThrow +import arrow.core.Either import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind.AT_MOST_ONCE import kotlin.contracts.InvocationKind.EXACTLY_ONCE @@ -95,7 +97,7 @@ public inline fun fold( callsInPlace(recover, AT_MOST_ONCE) callsInPlace(transform, AT_MOST_ONCE) } - val raise = DefaultRaise() + val raise = DefaultRaise(false) return try { val res = program(raise) raise.complete() @@ -109,24 +111,93 @@ public inline fun fold( } } +public fun Effect.traced(recover: Raise.(traces: Traced) -> Unit): Effect = + effect { traced({ bind() }, recover) } + +public fun EagerEffect.traced(recover: Raise.(traces: Traced) -> Unit): EagerEffect = + eagerEffect { traced({ bind() }, recover) } + +/** + * Inspect a [Traced] value of [R]. + * + * Tracing [R] can be useful to know where certain errors, or failures are coming from. + * Let's say you have a `DomainError`, but it might be raised from many places in the project. + * + * You would have to manually _trace_ where this error is coming from, + * instead [Traced] offers you ways to inspect the actual stacktrace of where the raised value occurred. + * + * Beware that tracing can only track the [Raise.bind] or [Raise.raise] call that resulted in the [R] value, + * and not any location of where the [R], or [Either.Left] value was created. + * + * ```kotlin + * public fun main() { + * val error = effect { raise("error") } + * error.traced { (trace, _: String) -> trace.printStackTrace() } + * .fold({ require(it == "error") }, { error("impossible") }) + * } + * ``` + * ```text + * arrow.core.continuations.RaiseCancellationException: Raised Continuation + * at arrow.core.continuations.DefaultRaise.raise(Fold.kt:77) + * at MainKtKt$main$error$1.invoke(MainKt.kt:6) + * at MainKtKt$main$error$1.invoke(MainKt.kt:6) + * at arrow.core.continuations.Raise$DefaultImpls.bind(Raise.kt:22) + * at arrow.core.continuations.DefaultRaise.bind(Fold.kt:74) + * at arrow.core.continuations.Effect__TracingKt$traced$2.invoke(Traced.kt:46) + * at arrow.core.continuations.Effect__TracingKt$traced$2.invoke(Traced.kt:46) + * at arrow.core.continuations.Effect__FoldKt.fold(Fold.kt:92) + * at arrow.core.continuations.Effect.fold(Unknown Source) + * at MainKtKt.main(MainKt.kt:8) + * at MainKtKt.main(MainKt.kt) + * ``` + * + * NOTE: + * This implies a performance penalty of creating a stacktrace when calling [Raise.raise], + * but **this only occurs** when composing `traced`. + * The stacktrace creation is disabled if no `traced` calls are made within the function composition. + */ +public inline fun Raise.traced( + @BuilderInference program: Raise.() -> A, + recover: Raise.(traces: Traced) -> Unit, +): A { + val nested = DefaultRaise(true) + return try { + program.invoke(nested) + } catch (e: RaiseCancellationExceptionNoTrace) { + val r: R = e.raisedOrRethrow(nested) + recover(Traced(e, r)) + raise(r) + } +} + /** Returns the raised value, rethrows the CancellationException if not our scope */ @PublishedApi @Suppress("UNCHECKED_CAST") internal fun CancellationException.raisedOrRethrow(raise: DefaultRaise): R = - if (this is RaiseCancellationException && this.raise === raise) raised as R - else throw this + when { + this is RaiseCancellationExceptionNoTrace && this.raise === raise -> raised as R + this is RaiseCancellationException && this.raise === raise -> raised as R + else -> throw this + } /** Serves as both purposes of a scope-reference token, and a default implementation for Raise. */ @PublishedApi -internal class DefaultRaise : Raise { +internal class DefaultRaise(private val isTraced: Boolean) : Raise { private val isActive = AtomicBoolean(true) + @PublishedApi internal fun complete(): Boolean = isActive.getAndSet(false) - override fun raise(r: Any?): Nothing = - if (isActive.value) throw RaiseCancellationException(r, this) else throw RaiseLeakedException() + override fun raise(r: Any?): Nothing = when { + isActive.value && !isTraced -> throw RaiseCancellationExceptionNoTrace(r, this) + isActive.value && !isTraced -> throw RaiseCancellationException(r, this) + else -> throw RaiseLeakedException() + } } /** CancellationException is required to cancel coroutines when raising from within them. */ +private class RaiseCancellationExceptionNoTrace(val raised: Any?, val raise: Raise) : + CancellationExceptionNoTrace() + private class RaiseCancellationException(val raised: Any?, val raise: Raise) : CancellationExceptionNoTrace() public expect open class CancellationExceptionNoTrace() : CancellationException 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 431960c0d05..5a0aca0c572 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 @@ -21,7 +21,6 @@ import kotlin.experimental.ExperimentalTypeInference import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName - /** * Accumulate the errors from running both [action1] and [action2] * using the given [semigroup]. diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt new file mode 100644 index 00000000000..bd56887a8b7 --- /dev/null +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt @@ -0,0 +1,37 @@ +@file:JvmMultifileClass +@file:JvmName("RaiseKt") +package arrow.core.raise + +import kotlin.coroutines.cancellation.CancellationException +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName + +/** Tracing result of `R`. Allows to inspect `R`, and the traces from where it was raised. */ +public data class Traced(private val exception: CancellationException, private val raised: R) { + /** + * 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.stackTraceToString() + + /** + * Prints the stacktrace. + * + * 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.printStackTrace() + + /** + * Returns the suppressed exceptions that occurred during cancellation of the surrounding coroutines, + * + * For example when working with `Resource`, or `bracket`: + * 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.suppressedExceptions +} From 3b108cde94e73c278ce4c1d2192918bc545cf99b Mon Sep 17 00:00:00 2001 From: nomisRev Date: Thu, 23 Feb 2023 16:54:54 +0000 Subject: [PATCH 02/17] Update API files --- arrow-libs/core/arrow-core/api/arrow-core.api | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/arrow-libs/core/arrow-core/api/arrow-core.api b/arrow-libs/core/arrow-core/api/arrow-core.api index 20eb0450c2a..3e1709c7ffb 100644 --- a/arrow-libs/core/arrow-core/api/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/arrow-core.api @@ -3201,7 +3201,7 @@ public class arrow/core/raise/CancellationExceptionNoTrace : java/util/concurren } public final class arrow/core/raise/DefaultRaise : arrow/core/raise/Raise { - public fun ()V + public fun (Z)V public fun attempt (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun bind (Larrow/core/Either;)Ljava/lang/Object; public fun bind (Larrow/core/Option;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; @@ -3456,6 +3456,9 @@ public final class arrow/core/raise/RaiseKt { public static final fun toResult (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 traced (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function1; + public static final fun traced (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function2; public static final fun zipOrAccumulate (Larrow/core/raise/Raise;Larrow/typeclasses/Semigroup;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/Function10;)Ljava/lang/Object; public static final fun zipOrAccumulate (Larrow/core/raise/Raise;Larrow/typeclasses/Semigroup;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;Larrow/typeclasses/Semigroup;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; @@ -3528,6 +3531,18 @@ public final class arrow/core/raise/ResultRaise : arrow/core/raise/Raise { public final synthetic fun unbox-impl ()Larrow/core/raise/Raise; } +public final class arrow/core/raise/Traced { + public fun (Ljava/util/concurrent/CancellationException;Ljava/lang/Object;)V + public final fun copy (Ljava/util/concurrent/CancellationException;Ljava/lang/Object;)Larrow/core/raise/Traced; + public static synthetic fun copy$default (Larrow/core/raise/Traced;Ljava/util/concurrent/CancellationException;Ljava/lang/Object;ILjava/lang/Object;)Larrow/core/raise/Traced; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun printStackTrace ()V + public final fun stackTraceToString ()Ljava/lang/String; + public final fun suppressedExceptions ()Ljava/util/List; + public fun toString ()Ljava/lang/String; +} + public abstract interface class arrow/typeclasses/Monoid : arrow/typeclasses/Semigroup { public static final field Companion Larrow/typeclasses/Monoid$Companion; public static fun Boolean ()Larrow/typeclasses/Monoid; From 209248f8a625ed9b86dd2fee3924f756384f8bc3 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Thu, 23 Feb 2023 18:25:03 +0100 Subject: [PATCH 03/17] Retrigger CI From 04d0616ae80394c181bdfd1071c1519224b60a11 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Fri, 10 Mar 2023 23:59:33 +0100 Subject: [PATCH 04/17] Add couple tests, and support nesting tracing calls --- .../kotlin/arrow/core/raise/Builders.kt | 11 ++-- .../kotlin/arrow/core/raise/Fold.kt | 27 +++----- .../kotlin/arrow/core/raise/Traced.kt | 6 +- .../kotlin/arrow/core/raise/TraceSpec.kt | 64 +++++++++++++++++++ 4 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/TraceSpec.kt 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 1781ffac2b8..d3f43c579fe 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 @@ -57,10 +57,9 @@ public inline fun ior(semigroup: Semigroup, @BuilderInference block: I public typealias Null = Nothing? @JvmInline -public value class NullableRaise(private val cont: Raise) : Raise { +public value class NullableRaise(private val raise: Raise) : Raise by raise { @RaiseDSL public fun ensure(value: Boolean): Unit = ensure(value) { null } - override fun raise(r: Nothing?): Nothing = cont.raise(r) public fun Option.bind(): B = bind { raise(null) } public fun B?.bind(): B { @@ -75,14 +74,12 @@ public value class NullableRaise(private val cont: Raise) : Raise { } @JvmInline -public value class ResultRaise(private val cont: Raise) : Raise { - override fun raise(r: Throwable): Nothing = cont.raise(r) +public value class ResultRaise(private val raise: Raise) : Raise by raise { public fun Result.bind(): B = fold(::identity) { raise(it) } } @JvmInline -public value class OptionRaise(private val cont: Raise) : Raise { - override fun raise(r: None): Nothing = cont.raise(r) +public value class OptionRaise(private val raise: Raise) : Raise by raise { public fun Option.bind(): B = bind { raise(None) } public fun ensure(value: Boolean): Unit = ensure(value) { None } @@ -96,7 +93,7 @@ public class IorRaise @PublishedApi internal constructor( semigroup: Semigroup, private val state: Atomic>, private val raise: Raise, -) : Raise, Semigroup by semigroup { +) : Raise by raise, Semigroup by semigroup { override fun raise(r: E): Nothing = raise.raise(combine(r)) 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 4b23f160457..a635712637e 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 @@ -9,7 +9,6 @@ import arrow.core.nonFatalOrThrow import arrow.core.Either import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind.AT_MOST_ONCE -import kotlin.contracts.InvocationKind.EXACTLY_ONCE import kotlin.contracts.contract import kotlin.coroutines.cancellation.CancellationException import kotlin.experimental.ExperimentalTypeInference @@ -77,7 +76,6 @@ public inline fun fold( transform: (value: A) -> B, ): B { contract { - callsInPlace(program, EXACTLY_ONCE) callsInPlace(recover, AT_MOST_ONCE) callsInPlace(transform, AT_MOST_ONCE) } @@ -92,7 +90,6 @@ public inline fun fold( transform: (value: A) -> B, ): B { contract { - callsInPlace(program, EXACTLY_ONCE) callsInPlace(error, AT_MOST_ONCE) callsInPlace(recover, AT_MOST_ONCE) callsInPlace(transform, AT_MOST_ONCE) @@ -111,12 +108,6 @@ public inline fun fold( } } -public fun Effect.traced(recover: Raise.(traces: Traced) -> Unit): Effect = - effect { traced({ bind() }, recover) } - -public fun EagerEffect.traced(recover: Raise.(traces: Traced) -> Unit): EagerEffect = - eagerEffect { traced({ bind() }, recover) } - /** * Inspect a [Traced] value of [R]. * @@ -156,17 +147,19 @@ public fun EagerEffect.traced(recover: Raise.(traces: Traced) * but **this only occurs** when composing `traced`. * The stacktrace creation is disabled if no `traced` calls are made within the function composition. */ +@ExperimentalTraceApi public inline fun Raise.traced( @BuilderInference program: Raise.() -> A, - recover: Raise.(traces: Traced) -> Unit, + trace: (traced: Traced) -> Unit ): A { - val nested = DefaultRaise(true) + val itOuterTraced = this is DefaultRaise && isTraced + val nested = if (this is DefaultRaise && isTraced) this else DefaultRaise(true) return try { program.invoke(nested) - } catch (e: RaiseCancellationExceptionNoTrace) { + } catch (e: RaiseCancellationException) { val r: R = e.raisedOrRethrow(nested) - recover(Traced(e, r)) - raise(r) + trace(Traced(e, r)) + if (itOuterTraced) throw e else raise(r) } } @@ -182,14 +175,14 @@ 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(private val isTraced: Boolean) : Raise { +internal class DefaultRaise(@PublishedApi internal val isTraced: Boolean) : Raise { private val isActive = AtomicBoolean(true) @PublishedApi internal fun complete(): Boolean = isActive.getAndSet(false) override fun raise(r: Any?): Nothing = when { isActive.value && !isTraced -> throw RaiseCancellationExceptionNoTrace(r, this) - isActive.value && !isTraced -> throw RaiseCancellationException(r, this) + isActive.value && isTraced -> throw RaiseCancellationException(r, this) else -> throw RaiseLeakedException() } } @@ -198,7 +191,7 @@ internal class DefaultRaise(private val isTraced: Boolean) : Raise { private class RaiseCancellationExceptionNoTrace(val raised: Any?, val raise: Raise) : CancellationExceptionNoTrace() -private class RaiseCancellationException(val raised: Any?, val raise: Raise) : CancellationExceptionNoTrace() +private class RaiseCancellationException(val raised: Any?, val raise: Raise) : CancellationException() public expect open class CancellationExceptionNoTrace() : CancellationException diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt index bd56887a8b7..1ed3e8b36fb 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt @@ -6,8 +6,12 @@ import kotlin.coroutines.cancellation.CancellationException import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName +@RequiresOptIn("This API is experimental, and may change in the future.") +public annotation class ExperimentalTraceApi + /** Tracing result of `R`. Allows to inspect `R`, and the traces from where it was raised. */ -public data class Traced(private val exception: CancellationException, private val raised: R) { +@ExperimentalTraceApi +public class Traced(private val exception: CancellationException, public val raised: R) { /** * Returns the stacktrace as a [String] * 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 new file mode 100644 index 00000000000..79f84480768 --- /dev/null +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/TraceSpec.kt @@ -0,0 +1,64 @@ +package arrow.core.raise + +import arrow.core.right +import arrow.typeclasses.Semigroup +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe +import io.kotest.property.Arb +import io.kotest.property.arbitrary.int +import io.kotest.property.arbitrary.string +import io.kotest.property.checkAll +import kotlinx.coroutines.CompletableDeferred + +@OptIn(ExperimentalTraceApi::class) +class TraceSpec : StringSpec({ + "trace is empty when no errors" { + checkAll(Arb.int()) { i -> + either { + traced({ i }) { unreachable() } + } shouldBe i.right() + } + } + + "trace is empty with exception" { + checkAll(Arb.string()) { msg -> + val error = RuntimeException(msg) + shouldThrow { + either { + traced({ throw error }) { unreachable() } + } + }.message shouldBe msg + } + } + + "Can trace a typed error" { + either { + traced({ raise(RuntimeException("")) }) { traced -> + // Remove first 2 lines: + // arrow.core.raise.RaiseCancellationException + // at arrow.core.raise.DefaultRaise.raise(Fold.kt:187) + val trace = traced.stackTraceToString().lines().drop(2) + + // Remove first line: + // java.lang.RuntimeException: + val exceptionTrace = traced.raised.stackTraceToString().lines().drop(1) + + trace shouldBe exceptionTrace + } + } + } + + "nested tracing - identity" { + val inner = CompletableDeferred() + ior(Semigroup.string()) { + traced({ + traced({ raise("") }) { traced -> + inner.complete(traced.stackTraceToString()) + } + }) { traced -> + inner.await() shouldBe traced.stackTraceToString() + } + } + } +}) From a75658f6220bc71a1e86020d2f3807be143c8793 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Sat, 11 Mar 2023 09:57:05 +0100 Subject: [PATCH 05/17] Make fix after merge, and revert delegation in ior builder --- .../src/commonMain/kotlin/arrow/core/raise/Builders.kt | 6 +----- .../src/commonTest/kotlin/arrow/core/raise/TraceSpec.kt | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) 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 57b6faac92b..195b599d92d 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 @@ -13,11 +13,7 @@ import arrow.core.Option import arrow.core.Some import arrow.core.getOrElse import arrow.core.identity -import arrow.core.orElse -import arrow.typeclasses.Semigroup -import arrow.typeclasses.SemigroupDeprecation import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind.EXACTLY_ONCE import kotlin.contracts.contract import kotlin.experimental.ExperimentalTypeInference import kotlin.jvm.JvmInline @@ -85,7 +81,7 @@ public class IorRaise @PublishedApi internal constructor( private val combineError: (E, E) -> E, private val state: Atomic>, private val raise: Raise, -) : Raise by raise { +) : Raise { override fun raise(r: E): Nothing = raise.raise(combine(r)) 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 79f84480768..6c04507b80a 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 @@ -51,7 +51,7 @@ class TraceSpec : StringSpec({ "nested tracing - identity" { val inner = CompletableDeferred() - ior(Semigroup.string()) { + ior(String::plus) { traced({ traced({ raise("") }) { traced -> inner.complete(traced.stackTraceToString()) From efd6fc81c37e52c04622d27a60881c9971bb1e10 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Sat, 11 Mar 2023 11:25:17 +0100 Subject: [PATCH 06/17] Move trace comparison test to JVM only --- .../kotlin/arrow/core/raise/Fold.kt | 5 ++++ .../kotlin/arrow/core/raise/TraceSpec.kt | 18 -------------- .../raise/CancellationExceptionNoTrace.kt | 2 +- .../raise/CancellationExceptionNoTrace.kt | 2 +- .../kotlin/arrow/core/raise/TraceJvmSpec.kt | 24 +++++++++++++++++++ .../raise/CancellationExceptionNoTrace.kt | 2 +- 6 files changed, 32 insertions(+), 21 deletions(-) create mode 100644 arrow-libs/core/arrow-core/src/jvmTest/kotlin/arrow/core/raise/TraceJvmSpec.kt 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 a635712637e..302ae241b82 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 @@ -203,3 +203,8 @@ private class RaiseLeakedException : IllegalStateException( See: Effect documentation for additional information. """.trimIndent() ) + +internal const val RaiseCancellationExceptionCaptured: String = + "kotlin.coroutines.cancellation.CancellationException should never get cancelled. Always re-throw it if captured." + + "This swallows the exception of Arrow's Raise, and leads to unexpected behavior." + + "When working with Arrow prefer Either.catch or arrow.core.raise.catch to automatically rethrow CancellationException." 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 6c04507b80a..840be3ae88f 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 @@ -1,7 +1,6 @@ package arrow.core.raise import arrow.core.right -import arrow.typeclasses.Semigroup import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe @@ -32,23 +31,6 @@ class TraceSpec : StringSpec({ } } - "Can trace a typed error" { - either { - traced({ raise(RuntimeException("")) }) { traced -> - // Remove first 2 lines: - // arrow.core.raise.RaiseCancellationException - // at arrow.core.raise.DefaultRaise.raise(Fold.kt:187) - val trace = traced.stackTraceToString().lines().drop(2) - - // Remove first line: - // java.lang.RuntimeException: - val exceptionTrace = traced.raised.stackTraceToString().lines().drop(1) - - trace shouldBe exceptionTrace - } - } - } - "nested tracing - identity" { val inner = CompletableDeferred() ior(String::plus) { diff --git a/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt b/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt index 83d1a6a624d..b9f09182c31 100644 --- a/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt +++ b/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt @@ -2,4 +2,4 @@ package arrow.core.raise import kotlin.coroutines.cancellation.CancellationException -public actual open class CancellationExceptionNoTrace : CancellationException("Raised Continuation") +public actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) diff --git a/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt b/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt index 3b25b1c4b9e..053ba0b2e13 100644 --- a/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt +++ b/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt @@ -6,7 +6,7 @@ import kotlin.coroutines.cancellation.CancellationException * Inspired by KotlinX Coroutines: * https://github.com/Kotlin/kotlinx.coroutines/blob/3788889ddfd2bcfedbff1bbca10ee56039e024a2/kotlinx-coroutines-core/jvm/src/Exceptions.kt#L29 */ -public actual open class CancellationExceptionNoTrace : CancellationException("Raised Continuation") { +public actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) { override fun fillInStackTrace(): Throwable { // Prevent Android <= 6.0 bug. stackTrace = emptyArray() 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 new file mode 100644 index 00000000000..133db395ba1 --- /dev/null +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/arrow/core/raise/TraceJvmSpec.kt @@ -0,0 +1,24 @@ +package arrow.core.raise + +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe + +@OptIn(ExperimentalTraceApi::class) +class TraceJvmSpec : StringSpec({ + "Can trace a typed error" { + either { + traced({ raise(RuntimeException("")) }) { traced -> + // Remove first 2 lines: + // arrow.core.raise.RaiseCancellationException + // at arrow.core.raise.DefaultRaise.raise(Fold.kt:187) + val trace = traced.stackTraceToString().lines().drop(2) + + // Remove first line: + // java.lang.RuntimeException: + val exceptionTrace = traced.raised.stackTraceToString().lines().drop(1) + + trace shouldBe exceptionTrace + } + } + } +}) diff --git a/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt b/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt index 83d1a6a624d..b9f09182c31 100644 --- a/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt +++ b/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt @@ -2,4 +2,4 @@ package arrow.core.raise import kotlin.coroutines.cancellation.CancellationException -public actual open class CancellationExceptionNoTrace : CancellationException("Raised Continuation") +public actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) From 2766fcc90b08cbe5fe70e0c010dcec922f972c71 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Sat, 11 Mar 2023 21:22:13 +0100 Subject: [PATCH 07/17] Fix test --- .../kotlin/arrow/core/raise/StructuredConcurrencySpec.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/StructuredConcurrencySpec.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/StructuredConcurrencySpec.kt index dc4f36c7061..5ce8a987981 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/StructuredConcurrencySpec.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/StructuredConcurrencySpec.kt @@ -54,7 +54,7 @@ class StructuredConcurrencySpec : StringSpec({ }.fold(::identity) { fail("Should never be here") } shouldBe "hello" withTimeout(2.seconds) { - cancelled.await().shouldNotBeNull().message shouldBe "Raised Continuation" + cancelled.await().shouldNotBeNull().message shouldBe RaiseCancellationExceptionCaptured } } From 4d22cf1af68882ff43481c4995a245e8fbfbfae2 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Sun, 12 Mar 2023 12:45:24 +0100 Subject: [PATCH 08/17] Test review suggestions --- arrow-libs/core/arrow-core/api/arrow-core.api | 27 ++++++++------- .../kotlin/arrow/core/raise/Fold.kt | 33 +++++++++---------- .../arrow/core/raise/{Traced.kt => Trace.kt} | 4 ++- .../kotlin/arrow/core/raise/TraceSpec.kt | 12 +++---- .../raise/CancellationExceptionNoTrace.kt | 6 +++- .../raise/CancellationExceptionNoTrace.kt | 12 +++++-- .../kotlin/arrow/core/raise/TraceJvmSpec.kt | 8 ++--- .../raise/CancellationExceptionNoTrace.kt | 6 +++- 8 files changed, 63 insertions(+), 45 deletions(-) rename arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/{Traced.kt => Trace.kt} (91%) diff --git a/arrow-libs/core/arrow-core/api/arrow-core.api b/arrow-libs/core/arrow-core/api/arrow-core.api index 668f3f0330c..65e86b86928 100644 --- a/arrow-libs/core/arrow-core/api/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/arrow-core.api @@ -3231,11 +3231,6 @@ public final class arrow/core/continuations/result { public final fun invoke-gIAlu-s (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } -public class arrow/core/raise/CancellationExceptionNoTrace : java/util/concurrent/CancellationException { - public fun ()V - public fun fillInStackTrace ()Ljava/lang/Throwable; -} - public final class arrow/core/raise/DefaultRaise : arrow/core/raise/Raise { public fun (Z)V public fun attempt (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -3505,7 +3500,7 @@ public final class arrow/core/raise/RaiseKt { public static final fun toResult (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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/Function1;)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 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; 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/Function7;)Ljava/lang/Object; @@ -3574,12 +3569,20 @@ public final class arrow/core/raise/ResultRaise : arrow/core/raise/Raise { public final synthetic fun unbox-impl ()Larrow/core/raise/Raise; } -public final class arrow/core/raise/Traced { - public fun (Ljava/util/concurrent/CancellationException;Ljava/lang/Object;)V - public final fun getRaised ()Ljava/lang/Object; - public final fun printStackTrace ()V - public final fun stackTraceToString ()Ljava/lang/String; - public final fun suppressedExceptions ()Ljava/util/List; +public final class arrow/core/raise/Trace { + public static final synthetic fun box-impl (Ljava/util/concurrent/CancellationException;)Larrow/core/raise/Trace; + public static fun constructor-impl (Ljava/util/concurrent/CancellationException;)Ljava/util/concurrent/CancellationException; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/util/concurrent/CancellationException;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/util/concurrent/CancellationException;Ljava/util/concurrent/CancellationException;)Z + public fun hashCode ()I + public static fun hashCode-impl (Ljava/util/concurrent/CancellationException;)I + public static final fun printStackTrace-impl (Ljava/util/concurrent/CancellationException;)V + public static final fun stackTraceToString-impl (Ljava/util/concurrent/CancellationException;)Ljava/lang/String; + public static final fun suppressedExceptions-impl (Ljava/util/concurrent/CancellationException;)Ljava/util/List; + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/util/concurrent/CancellationException;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/util/concurrent/CancellationException; } public abstract interface class arrow/typeclasses/Monoid : arrow/typeclasses/Semigroup { 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 302ae241b82..8a26bef21b8 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 @@ -109,13 +109,13 @@ public inline fun fold( } /** - * Inspect a [Traced] value of [R]. + * Inspect a [Trace] value of [R]. * * Tracing [R] can be useful to know where certain errors, or failures are coming from. * Let's say you have a `DomainError`, but it might be raised from many places in the project. * * You would have to manually _trace_ where this error is coming from, - * instead [Traced] offers you ways to inspect the actual stacktrace of where the raised value occurred. + * instead [Trace] offers you ways to inspect the actual stacktrace of where the raised value occurred. * * Beware that tracing can only track the [Raise.bind] or [Raise.raise] call that resulted in the [R] value, * and not any location of where the [R], or [Either.Left] value was created. @@ -150,7 +150,7 @@ public inline fun fold( @ExperimentalTraceApi public inline fun Raise.traced( @BuilderInference program: Raise.() -> A, - trace: (traced: Traced) -> Unit + trace: (traced: Trace, R) -> Unit ): A { val itOuterTraced = this is DefaultRaise && isTraced val nested = if (this is DefaultRaise && isTraced) this else DefaultRaise(true) @@ -158,7 +158,7 @@ public inline fun Raise.traced( program.invoke(nested) } catch (e: RaiseCancellationException) { val r: R = e.raisedOrRethrow(nested) - trace(Traced(e, r)) + trace(Trace(e), r) if (itOuterTraced) throw e else raise(r) } } @@ -168,7 +168,6 @@ public inline fun Raise.traced( @Suppress("UNCHECKED_CAST") internal fun CancellationException.raisedOrRethrow(raise: DefaultRaise): R = when { - this is RaiseCancellationExceptionNoTrace && this.raise === raise -> raised as R this is RaiseCancellationException && this.raise === raise -> raised as R else -> throw this } @@ -180,20 +179,20 @@ internal class DefaultRaise(@PublishedApi internal val isTraced: Boolean) : Rais @PublishedApi internal fun complete(): Boolean = isActive.getAndSet(false) - override fun raise(r: Any?): Nothing = when { - isActive.value && !isTraced -> throw RaiseCancellationExceptionNoTrace(r, this) - isActive.value && isTraced -> throw RaiseCancellationException(r, this) - else -> throw RaiseLeakedException() - } + override fun raise(r: Any?): Nothing = + if (isActive.value) throw RaiseCancellationException(r, this, isTraced) else throw RaiseLeakedException() } /** CancellationException is required to cancel coroutines when raising from within them. */ -private class RaiseCancellationExceptionNoTrace(val raised: Any?, val raise: Raise) : - CancellationExceptionNoTrace() - -private class RaiseCancellationException(val raised: Any?, val raise: Raise) : CancellationException() - -public expect open class CancellationExceptionNoTrace() : CancellationException +internal expect open class RaiseCancellationException constructor( + raised: Any?, + raise: Raise, + isTraced: Boolean +) : CancellationException { + internal val raised: Any? + internal val raise: Raise + internal val isTraced: Boolean +} private class RaiseLeakedException : IllegalStateException( """ @@ -205,6 +204,6 @@ private class RaiseLeakedException : IllegalStateException( ) internal const val RaiseCancellationExceptionCaptured: String = - "kotlin.coroutines.cancellation.CancellationException should never get cancelled. Always re-throw it if captured." + + "kotlin.coroutines.cancellation.CancellationException should never get captured, always re-throw it if captured." + "This swallows the exception of Arrow's Raise, and leads to unexpected behavior." + "When working with Arrow prefer Either.catch or arrow.core.raise.catch to automatically rethrow CancellationException." diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt similarity index 91% rename from arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt rename to arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt index 1ed3e8b36fb..62afef71f96 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt @@ -3,6 +3,7 @@ package arrow.core.raise import kotlin.coroutines.cancellation.CancellationException +import kotlin.jvm.JvmInline import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -11,7 +12,8 @@ public annotation class ExperimentalTraceApi /** Tracing result of `R`. Allows to inspect `R`, and the traces from where it was raised. */ @ExperimentalTraceApi -public class Traced(private val exception: CancellationException, public val raised: R) { +@JvmInline +public value class Trace @PublishedApi internal constructor(private val exception: CancellationException) { /** * Returns the stacktrace as a [String] * 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 840be3ae88f..4b2c47d510f 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 @@ -15,7 +15,7 @@ class TraceSpec : StringSpec({ "trace is empty when no errors" { checkAll(Arb.int()) { i -> either { - traced({ i }) { unreachable() } + traced({ i }) { _,_ -> unreachable() } } shouldBe i.right() } } @@ -25,7 +25,7 @@ class TraceSpec : StringSpec({ val error = RuntimeException(msg) shouldThrow { either { - traced({ throw error }) { unreachable() } + traced({ throw error }) { _, _ -> unreachable() } } }.message shouldBe msg } @@ -35,11 +35,11 @@ class TraceSpec : StringSpec({ val inner = CompletableDeferred() ior(String::plus) { traced({ - traced({ raise("") }) { traced -> - inner.complete(traced.stackTraceToString()) + traced({ raise("") }) { trace,_ -> + inner.complete(trace.stackTraceToString()) } - }) { traced -> - inner.await() shouldBe traced.stackTraceToString() + }) { trace,_ -> + inner.await() shouldBe trace.stackTraceToString() } } } diff --git a/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt b/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt index b9f09182c31..dfe2c82fb98 100644 --- a/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt +++ b/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt @@ -2,4 +2,8 @@ package arrow.core.raise import kotlin.coroutines.cancellation.CancellationException -public actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) +internal actual open class RaiseCancellationException actual constructor( + internal actual val raised: Any?, + internal actual val raise: Raise, + internal actual val isTraced: Boolean +) : CancellationException(if (!isTraced) RaiseCancellationExceptionCaptured else "") diff --git a/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt b/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt index 053ba0b2e13..19d038ef0d4 100644 --- a/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt +++ b/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt @@ -6,11 +6,17 @@ import kotlin.coroutines.cancellation.CancellationException * Inspired by KotlinX Coroutines: * https://github.com/Kotlin/kotlinx.coroutines/blob/3788889ddfd2bcfedbff1bbca10ee56039e024a2/kotlinx-coroutines-core/jvm/src/Exceptions.kt#L29 */ -public actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) { - override fun fillInStackTrace(): Throwable { +internal actual open class RaiseCancellationException actual constructor( + internal actual val raised: Any?, + internal actual val raise: Raise, + internal actual val isTraced: Boolean +) : CancellationException(if (!isTraced) RaiseCancellationExceptionCaptured else "") { + override fun fillInStackTrace(): Throwable = + if (isTraced) super.fillInStackTrace() + else { // Prevent Android <= 6.0 bug. stackTrace = emptyArray() // We don't need stacktrace on shift, it hurts performance. - return this + this } } 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 133db395ba1..b5fe770dee7 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,17 +7,17 @@ import io.kotest.matchers.shouldBe class TraceJvmSpec : StringSpec({ "Can trace a typed error" { either { - traced({ raise(RuntimeException("")) }) { traced -> + traced({ raise(RuntimeException("")) }) { trace, raised -> // Remove first 2 lines: // arrow.core.raise.RaiseCancellationException // at arrow.core.raise.DefaultRaise.raise(Fold.kt:187) - val trace = traced.stackTraceToString().lines().drop(2) + val stackTrace = trace.stackTraceToString().lines().drop(2) // Remove first line: // java.lang.RuntimeException: - val exceptionTrace = traced.raised.stackTraceToString().lines().drop(1) + val exceptionStackTrace = raised.stackTraceToString().lines().drop(1) - trace shouldBe exceptionTrace + stackTrace shouldBe exceptionStackTrace } } } diff --git a/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt b/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt index b9f09182c31..dfe2c82fb98 100644 --- a/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt +++ b/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt @@ -2,4 +2,8 @@ package arrow.core.raise import kotlin.coroutines.cancellation.CancellationException -public actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) +internal actual open class RaiseCancellationException actual constructor( + internal actual val raised: Any?, + internal actual val raise: Raise, + internal actual val isTraced: Boolean +) : CancellationException(if (!isTraced) RaiseCancellationExceptionCaptured else "") From 9693f1b41327cbf93713dd1bd7477fdcfe68ba33 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Thu, 16 Mar 2023 15:07:26 +0100 Subject: [PATCH 09/17] Revert "Test review suggestions" This reverts commit 4d22cf1af68882ff43481c4995a245e8fbfbfae2. --- arrow-libs/core/arrow-core/api/arrow-core.api | 27 +++++++-------- .../kotlin/arrow/core/raise/Fold.kt | 33 ++++++++++--------- .../arrow/core/raise/{Trace.kt => Traced.kt} | 4 +-- .../kotlin/arrow/core/raise/TraceSpec.kt | 12 +++---- .../raise/CancellationExceptionNoTrace.kt | 6 +--- .../raise/CancellationExceptionNoTrace.kt | 12 ++----- .../kotlin/arrow/core/raise/TraceJvmSpec.kt | 8 ++--- .../raise/CancellationExceptionNoTrace.kt | 6 +--- 8 files changed, 45 insertions(+), 63 deletions(-) rename arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/{Trace.kt => Traced.kt} (91%) diff --git a/arrow-libs/core/arrow-core/api/arrow-core.api b/arrow-libs/core/arrow-core/api/arrow-core.api index 65e86b86928..668f3f0330c 100644 --- a/arrow-libs/core/arrow-core/api/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/arrow-core.api @@ -3231,6 +3231,11 @@ public final class arrow/core/continuations/result { public final fun invoke-gIAlu-s (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public class arrow/core/raise/CancellationExceptionNoTrace : java/util/concurrent/CancellationException { + public fun ()V + public fun fillInStackTrace ()Ljava/lang/Throwable; +} + public final class arrow/core/raise/DefaultRaise : arrow/core/raise/Raise { public fun (Z)V public fun attempt (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -3500,7 +3505,7 @@ public final class arrow/core/raise/RaiseKt { public static final fun toResult (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 traced (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; 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/Function7;)Ljava/lang/Object; @@ -3569,20 +3574,12 @@ public final class arrow/core/raise/ResultRaise : arrow/core/raise/Raise { public final synthetic fun unbox-impl ()Larrow/core/raise/Raise; } -public final class arrow/core/raise/Trace { - public static final synthetic fun box-impl (Ljava/util/concurrent/CancellationException;)Larrow/core/raise/Trace; - public static fun constructor-impl (Ljava/util/concurrent/CancellationException;)Ljava/util/concurrent/CancellationException; - public fun equals (Ljava/lang/Object;)Z - public static fun equals-impl (Ljava/util/concurrent/CancellationException;Ljava/lang/Object;)Z - public static final fun equals-impl0 (Ljava/util/concurrent/CancellationException;Ljava/util/concurrent/CancellationException;)Z - public fun hashCode ()I - public static fun hashCode-impl (Ljava/util/concurrent/CancellationException;)I - public static final fun printStackTrace-impl (Ljava/util/concurrent/CancellationException;)V - public static final fun stackTraceToString-impl (Ljava/util/concurrent/CancellationException;)Ljava/lang/String; - public static final fun suppressedExceptions-impl (Ljava/util/concurrent/CancellationException;)Ljava/util/List; - public fun toString ()Ljava/lang/String; - public static fun toString-impl (Ljava/util/concurrent/CancellationException;)Ljava/lang/String; - public final synthetic fun unbox-impl ()Ljava/util/concurrent/CancellationException; +public final class arrow/core/raise/Traced { + public fun (Ljava/util/concurrent/CancellationException;Ljava/lang/Object;)V + public final fun getRaised ()Ljava/lang/Object; + public final fun printStackTrace ()V + public final fun stackTraceToString ()Ljava/lang/String; + public final fun suppressedExceptions ()Ljava/util/List; } public abstract interface class arrow/typeclasses/Monoid : arrow/typeclasses/Semigroup { 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 8a26bef21b8..302ae241b82 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 @@ -109,13 +109,13 @@ public inline fun fold( } /** - * Inspect a [Trace] value of [R]. + * Inspect a [Traced] value of [R]. * * Tracing [R] can be useful to know where certain errors, or failures are coming from. * Let's say you have a `DomainError`, but it might be raised from many places in the project. * * You would have to manually _trace_ where this error is coming from, - * instead [Trace] offers you ways to inspect the actual stacktrace of where the raised value occurred. + * instead [Traced] offers you ways to inspect the actual stacktrace of where the raised value occurred. * * Beware that tracing can only track the [Raise.bind] or [Raise.raise] call that resulted in the [R] value, * and not any location of where the [R], or [Either.Left] value was created. @@ -150,7 +150,7 @@ public inline fun fold( @ExperimentalTraceApi public inline fun Raise.traced( @BuilderInference program: Raise.() -> A, - trace: (traced: Trace, R) -> Unit + trace: (traced: Traced) -> Unit ): A { val itOuterTraced = this is DefaultRaise && isTraced val nested = if (this is DefaultRaise && isTraced) this else DefaultRaise(true) @@ -158,7 +158,7 @@ public inline fun Raise.traced( program.invoke(nested) } catch (e: RaiseCancellationException) { val r: R = e.raisedOrRethrow(nested) - trace(Trace(e), r) + trace(Traced(e, r)) if (itOuterTraced) throw e else raise(r) } } @@ -168,6 +168,7 @@ public inline fun Raise.traced( @Suppress("UNCHECKED_CAST") internal fun CancellationException.raisedOrRethrow(raise: DefaultRaise): R = when { + this is RaiseCancellationExceptionNoTrace && this.raise === raise -> raised as R this is RaiseCancellationException && this.raise === raise -> raised as R else -> throw this } @@ -179,20 +180,20 @@ internal class DefaultRaise(@PublishedApi internal val isTraced: Boolean) : Rais @PublishedApi internal fun complete(): Boolean = isActive.getAndSet(false) - override fun raise(r: Any?): Nothing = - if (isActive.value) throw RaiseCancellationException(r, this, isTraced) else throw RaiseLeakedException() + override fun raise(r: Any?): Nothing = when { + isActive.value && !isTraced -> throw RaiseCancellationExceptionNoTrace(r, this) + isActive.value && isTraced -> throw RaiseCancellationException(r, this) + else -> throw RaiseLeakedException() + } } /** CancellationException is required to cancel coroutines when raising from within them. */ -internal expect open class RaiseCancellationException constructor( - raised: Any?, - raise: Raise, - isTraced: Boolean -) : CancellationException { - internal val raised: Any? - internal val raise: Raise - internal val isTraced: Boolean -} +private class RaiseCancellationExceptionNoTrace(val raised: Any?, val raise: Raise) : + CancellationExceptionNoTrace() + +private class RaiseCancellationException(val raised: Any?, val raise: Raise) : CancellationException() + +public expect open class CancellationExceptionNoTrace() : CancellationException private class RaiseLeakedException : IllegalStateException( """ @@ -204,6 +205,6 @@ private class RaiseLeakedException : IllegalStateException( ) internal const val RaiseCancellationExceptionCaptured: String = - "kotlin.coroutines.cancellation.CancellationException should never get captured, always re-throw it if captured." + + "kotlin.coroutines.cancellation.CancellationException should never get cancelled. Always re-throw it if captured." + "This swallows the exception of Arrow's Raise, and leads to unexpected behavior." + "When working with Arrow prefer Either.catch or arrow.core.raise.catch to automatically rethrow CancellationException." 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/Traced.kt similarity index 91% rename from arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt rename to arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt index 62afef71f96..1ed3e8b36fb 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/Traced.kt @@ -3,7 +3,6 @@ package arrow.core.raise import kotlin.coroutines.cancellation.CancellationException -import kotlin.jvm.JvmInline import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -12,8 +11,7 @@ public annotation class ExperimentalTraceApi /** Tracing result of `R`. Allows to inspect `R`, and the traces from where it was raised. */ @ExperimentalTraceApi -@JvmInline -public value class Trace @PublishedApi internal constructor(private val exception: CancellationException) { +public class Traced(private val exception: CancellationException, public val raised: R) { /** * Returns the stacktrace as a [String] * 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 4b2c47d510f..840be3ae88f 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 @@ -15,7 +15,7 @@ class TraceSpec : StringSpec({ "trace is empty when no errors" { checkAll(Arb.int()) { i -> either { - traced({ i }) { _,_ -> unreachable() } + traced({ i }) { unreachable() } } shouldBe i.right() } } @@ -25,7 +25,7 @@ class TraceSpec : StringSpec({ val error = RuntimeException(msg) shouldThrow { either { - traced({ throw error }) { _, _ -> unreachable() } + traced({ throw error }) { unreachable() } } }.message shouldBe msg } @@ -35,11 +35,11 @@ class TraceSpec : StringSpec({ val inner = CompletableDeferred() ior(String::plus) { traced({ - traced({ raise("") }) { trace,_ -> - inner.complete(trace.stackTraceToString()) + traced({ raise("") }) { traced -> + inner.complete(traced.stackTraceToString()) } - }) { trace,_ -> - inner.await() shouldBe trace.stackTraceToString() + }) { traced -> + inner.await() shouldBe traced.stackTraceToString() } } } diff --git a/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt b/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt index dfe2c82fb98..b9f09182c31 100644 --- a/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt +++ b/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt @@ -2,8 +2,4 @@ package arrow.core.raise import kotlin.coroutines.cancellation.CancellationException -internal actual open class RaiseCancellationException actual constructor( - internal actual val raised: Any?, - internal actual val raise: Raise, - internal actual val isTraced: Boolean -) : CancellationException(if (!isTraced) RaiseCancellationExceptionCaptured else "") +public actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) diff --git a/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt b/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt index 19d038ef0d4..053ba0b2e13 100644 --- a/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt +++ b/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt @@ -6,17 +6,11 @@ import kotlin.coroutines.cancellation.CancellationException * Inspired by KotlinX Coroutines: * https://github.com/Kotlin/kotlinx.coroutines/blob/3788889ddfd2bcfedbff1bbca10ee56039e024a2/kotlinx-coroutines-core/jvm/src/Exceptions.kt#L29 */ -internal actual open class RaiseCancellationException actual constructor( - internal actual val raised: Any?, - internal actual val raise: Raise, - internal actual val isTraced: Boolean -) : CancellationException(if (!isTraced) RaiseCancellationExceptionCaptured else "") { - override fun fillInStackTrace(): Throwable = - if (isTraced) super.fillInStackTrace() - else { +public actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) { + override fun fillInStackTrace(): Throwable { // Prevent Android <= 6.0 bug. stackTrace = emptyArray() // We don't need stacktrace on shift, it hurts performance. - this + return this } } 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 b5fe770dee7..133db395ba1 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,17 +7,17 @@ import io.kotest.matchers.shouldBe class TraceJvmSpec : StringSpec({ "Can trace a typed error" { either { - traced({ raise(RuntimeException("")) }) { trace, raised -> + traced({ raise(RuntimeException("")) }) { traced -> // Remove first 2 lines: // arrow.core.raise.RaiseCancellationException // at arrow.core.raise.DefaultRaise.raise(Fold.kt:187) - val stackTrace = trace.stackTraceToString().lines().drop(2) + val trace = traced.stackTraceToString().lines().drop(2) // Remove first line: // java.lang.RuntimeException: - val exceptionStackTrace = raised.stackTraceToString().lines().drop(1) + val exceptionTrace = traced.raised.stackTraceToString().lines().drop(1) - stackTrace shouldBe exceptionStackTrace + trace shouldBe exceptionTrace } } } diff --git a/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt b/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt index dfe2c82fb98..b9f09182c31 100644 --- a/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt +++ b/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt @@ -2,8 +2,4 @@ package arrow.core.raise import kotlin.coroutines.cancellation.CancellationException -internal actual open class RaiseCancellationException actual constructor( - internal actual val raised: Any?, - internal actual val raise: Raise, - internal actual val isTraced: Boolean -) : CancellationException(if (!isTraced) RaiseCancellationExceptionCaptured else "") +public actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) From b2f01e75f980e3a4cc2c5102bf27e3ae66c206bd Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Thu, 16 Mar 2023 15:23:53 +0100 Subject: [PATCH 10/17] Hide exceptions from public API, and turn traced into 2 param method --- arrow-libs/core/arrow-core/api/arrow-core.api | 12 +++--------- .../src/commonMain/kotlin/arrow/core/raise/Fold.kt | 10 +++++----- .../kotlin/arrow/core/raise/{Traced.kt => Trace.kt} | 2 +- .../commonTest/kotlin/arrow/core/raise/TraceSpec.kt | 8 ++++---- .../arrow/core/raise/CancellationExceptionNoTrace.kt | 2 +- .../arrow/core/raise/CancellationExceptionNoTrace.kt | 2 +- .../jvmTest/kotlin/arrow/core/raise/TraceJvmSpec.kt | 4 ++-- .../arrow/core/raise/CancellationExceptionNoTrace.kt | 2 +- 8 files changed, 18 insertions(+), 24 deletions(-) rename arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/{Traced.kt => Trace.kt} (94%) diff --git a/arrow-libs/core/arrow-core/api/arrow-core.api b/arrow-libs/core/arrow-core/api/arrow-core.api index 668f3f0330c..672dde5a56e 100644 --- a/arrow-libs/core/arrow-core/api/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/arrow-core.api @@ -3231,11 +3231,6 @@ public final class arrow/core/continuations/result { public final fun invoke-gIAlu-s (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } -public class arrow/core/raise/CancellationExceptionNoTrace : java/util/concurrent/CancellationException { - public fun ()V - public fun fillInStackTrace ()Ljava/lang/Throwable; -} - public final class arrow/core/raise/DefaultRaise : arrow/core/raise/Raise { public fun (Z)V public fun attempt (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -3505,7 +3500,7 @@ public final class arrow/core/raise/RaiseKt { public static final fun toResult (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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/Function1;)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 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; 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/Function7;)Ljava/lang/Object; @@ -3574,9 +3569,8 @@ public final class arrow/core/raise/ResultRaise : arrow/core/raise/Raise { public final synthetic fun unbox-impl ()Larrow/core/raise/Raise; } -public final class arrow/core/raise/Traced { - public fun (Ljava/util/concurrent/CancellationException;Ljava/lang/Object;)V - public final fun getRaised ()Ljava/lang/Object; +public final class arrow/core/raise/Trace { + public fun (Ljava/util/concurrent/CancellationException;)V public final fun printStackTrace ()V public final fun stackTraceToString ()Ljava/lang/String; public final fun suppressedExceptions ()Ljava/util/List; 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 302ae241b82..dc29bc84960 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 @@ -109,13 +109,13 @@ public inline fun fold( } /** - * Inspect a [Traced] value of [R]. + * Inspect a [Trace] value of [R]. * * Tracing [R] can be useful to know where certain errors, or failures are coming from. * Let's say you have a `DomainError`, but it might be raised from many places in the project. * * You would have to manually _trace_ where this error is coming from, - * instead [Traced] offers you ways to inspect the actual stacktrace of where the raised value occurred. + * instead [Trace] offers you ways to inspect the actual stacktrace of where the raised value occurred. * * Beware that tracing can only track the [Raise.bind] or [Raise.raise] call that resulted in the [R] value, * and not any location of where the [R], or [Either.Left] value was created. @@ -150,7 +150,7 @@ public inline fun fold( @ExperimentalTraceApi public inline fun Raise.traced( @BuilderInference program: Raise.() -> A, - trace: (traced: Traced) -> Unit + trace: (traced: Trace, R) -> Unit ): A { val itOuterTraced = this is DefaultRaise && isTraced val nested = if (this is DefaultRaise && isTraced) this else DefaultRaise(true) @@ -158,7 +158,7 @@ public inline fun Raise.traced( program.invoke(nested) } catch (e: RaiseCancellationException) { val r: R = e.raisedOrRethrow(nested) - trace(Traced(e, r)) + trace(Trace(e), r) if (itOuterTraced) throw e else raise(r) } } @@ -193,7 +193,7 @@ private class RaiseCancellationExceptionNoTrace(val raised: Any?, val raise: Rai private class RaiseCancellationException(val raised: Any?, val raise: Raise) : CancellationException() -public expect open class CancellationExceptionNoTrace() : CancellationException +internal expect open class CancellationExceptionNoTrace() : CancellationException private class RaiseLeakedException : IllegalStateException( """ diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt similarity index 94% rename from arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt rename to arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt index 1ed3e8b36fb..79c246d38ad 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Traced.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt @@ -11,7 +11,7 @@ public annotation class ExperimentalTraceApi /** Tracing result of `R`. Allows to inspect `R`, and the traces from where it was raised. */ @ExperimentalTraceApi -public class Traced(private val exception: CancellationException, public val raised: R) { +public class Trace(private val exception: CancellationException) { /** * Returns the stacktrace as a [String] * 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 840be3ae88f..7a488a1f2f7 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 @@ -15,7 +15,7 @@ class TraceSpec : StringSpec({ "trace is empty when no errors" { checkAll(Arb.int()) { i -> either { - traced({ i }) { unreachable() } + traced({ i }) { _,_ -> unreachable() } } shouldBe i.right() } } @@ -25,7 +25,7 @@ class TraceSpec : StringSpec({ val error = RuntimeException(msg) shouldThrow { either { - traced({ throw error }) { unreachable() } + traced({ throw error }) { _,_ -> unreachable() } } }.message shouldBe msg } @@ -35,10 +35,10 @@ class TraceSpec : StringSpec({ val inner = CompletableDeferred() ior(String::plus) { traced({ - traced({ raise("") }) { traced -> + traced({ raise("") }) { traced, _ -> inner.complete(traced.stackTraceToString()) } - }) { traced -> + }) { traced, _ -> inner.await() shouldBe traced.stackTraceToString() } } diff --git a/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt b/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt index b9f09182c31..747d2adac12 100644 --- a/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt +++ b/arrow-libs/core/arrow-core/src/jsMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt @@ -2,4 +2,4 @@ package arrow.core.raise import kotlin.coroutines.cancellation.CancellationException -public actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) +internal actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) diff --git a/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt b/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt index 053ba0b2e13..226c21392d6 100644 --- a/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt +++ b/arrow-libs/core/arrow-core/src/jvmMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt @@ -6,7 +6,7 @@ import kotlin.coroutines.cancellation.CancellationException * Inspired by KotlinX Coroutines: * https://github.com/Kotlin/kotlinx.coroutines/blob/3788889ddfd2bcfedbff1bbca10ee56039e024a2/kotlinx-coroutines-core/jvm/src/Exceptions.kt#L29 */ -public actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) { +internal actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) { override fun fillInStackTrace(): Throwable { // Prevent Android <= 6.0 bug. stackTrace = emptyArray() 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 133db395ba1..caaf88af7ff 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 io.kotest.matchers.shouldBe class TraceJvmSpec : StringSpec({ "Can trace a typed error" { either { - traced({ raise(RuntimeException("")) }) { traced -> + traced({ raise(RuntimeException("")) }) { traced, raised -> // Remove first 2 lines: // arrow.core.raise.RaiseCancellationException // at arrow.core.raise.DefaultRaise.raise(Fold.kt:187) @@ -15,7 +15,7 @@ class TraceJvmSpec : StringSpec({ // Remove first line: // java.lang.RuntimeException: - val exceptionTrace = traced.raised.stackTraceToString().lines().drop(1) + val exceptionTrace = raised.stackTraceToString().lines().drop(1) trace shouldBe exceptionTrace } diff --git a/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt b/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt index b9f09182c31..747d2adac12 100644 --- a/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt +++ b/arrow-libs/core/arrow-core/src/nativeMain/kotlin/arrow/core/raise/CancellationExceptionNoTrace.kt @@ -2,4 +2,4 @@ package arrow.core.raise import kotlin.coroutines.cancellation.CancellationException -public actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) +internal actual open class CancellationExceptionNoTrace : CancellationException(RaiseCancellationExceptionCaptured) From e3bb2d8b4f78d42a8df46a935506e89e7b29bf89 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Thu, 16 Mar 2023 16:52:44 +0100 Subject: [PATCH 11/17] Apply suggestions from code review Co-authored-by: Alejandro Serrano --- .../src/commonMain/kotlin/arrow/core/raise/Fold.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 dc29bc84960..94515120ac7 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 @@ -152,7 +152,7 @@ public inline fun Raise.traced( @BuilderInference program: Raise.() -> A, trace: (traced: Trace, R) -> Unit ): A { - val itOuterTraced = this is DefaultRaise && isTraced + val isOuterTraced = this is DefaultRaise && isTraced val nested = if (this is DefaultRaise && isTraced) this else DefaultRaise(true) return try { program.invoke(nested) @@ -168,8 +168,7 @@ public inline fun Raise.traced( @Suppress("UNCHECKED_CAST") internal fun CancellationException.raisedOrRethrow(raise: DefaultRaise): R = when { - this is RaiseCancellationExceptionNoTrace && this.raise === raise -> raised as R - this is RaiseCancellationException && this.raise === raise -> raised as R + this.raise === raise && (this is RaiseCancellationExceptionNoTrace || this is RaiseCancellationException) -> raised as R else -> throw this } @@ -181,8 +180,7 @@ internal class DefaultRaise(@PublishedApi internal val isTraced: Boolean) : Rais @PublishedApi internal fun complete(): Boolean = isActive.getAndSet(false) override fun raise(r: Any?): Nothing = when { - isActive.value && !isTraced -> throw RaiseCancellationExceptionNoTrace(r, this) - isActive.value && isTraced -> throw RaiseCancellationException(r, this) + isActive.value -> throw if (isTraced) RaiseCancellationException(r, this) else RaiseCancellationExceptionNoTrace(r, this) else -> throw RaiseLeakedException() } } From e58cdb3daee09a1cdcc233413f6080d693ba9e1f Mon Sep 17 00:00:00 2001 From: franciscodr Date: Thu, 16 Mar 2023 18:53:07 +0100 Subject: [PATCH 12/17] Fix compilation errors --- .../src/commonMain/kotlin/arrow/core/raise/Fold.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 94515120ac7..f83b9f9808c 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 @@ -159,7 +159,7 @@ public inline fun Raise.traced( } catch (e: RaiseCancellationException) { val r: R = e.raisedOrRethrow(nested) trace(Trace(e), r) - if (itOuterTraced) throw e else raise(r) + if (isOuterTraced) throw e else raise(r) } } @@ -168,7 +168,8 @@ public inline fun Raise.traced( @Suppress("UNCHECKED_CAST") internal fun CancellationException.raisedOrRethrow(raise: DefaultRaise): R = when { - this.raise === raise && (this is RaiseCancellationExceptionNoTrace || this is RaiseCancellationException) -> raised as R + this is RaiseCancellationExceptionNoTrace && this.raise === raise -> raised as R + this is RaiseCancellationException && this.raise === raise -> raised as R else -> throw this } From b1079477e39ae774613596885e89462d682945e4 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Thu, 16 Mar 2023 19:31:36 +0100 Subject: [PATCH 13/17] Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt Co-authored-by: Youssef Shoaib --- .../arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 79c246d38ad..61e1f316591 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 @@ -11,7 +11,7 @@ public annotation class ExperimentalTraceApi /** Tracing result of `R`. Allows to inspect `R`, and the traces from where it was raised. */ @ExperimentalTraceApi -public class Trace(private val exception: CancellationException) { +@JvmInline value class Trace(private val exception: CancellationException) { /** * Returns the stacktrace as a [String] * From f6bc835f429bbc8aa82a2d0ff4538262d1f990d2 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Thu, 16 Mar 2023 19:52:02 +0100 Subject: [PATCH 14/17] Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt --- .../arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 61e1f316591..e679ad803a3 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 @@ -11,7 +11,8 @@ public annotation class ExperimentalTraceApi /** Tracing result of `R`. Allows to inspect `R`, and the traces from where it was raised. */ @ExperimentalTraceApi -@JvmInline value class Trace(private val exception: CancellationException) { +@JvmInline +public value class Trace(private val exception: CancellationException) { /** * Returns the stacktrace as a [String] * From 1594d4797c29a4e015fdd027124a0d37e0263ce0 Mon Sep 17 00:00:00 2001 From: nomisRev Date: Thu, 16 Mar 2023 18:56:11 +0000 Subject: [PATCH 15/17] Update API files --- arrow-libs/core/arrow-core/api/arrow-core.api | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/arrow-libs/core/arrow-core/api/arrow-core.api b/arrow-libs/core/arrow-core/api/arrow-core.api index 672dde5a56e..65e86b86928 100644 --- a/arrow-libs/core/arrow-core/api/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/arrow-core.api @@ -3570,10 +3570,19 @@ public final class arrow/core/raise/ResultRaise : arrow/core/raise/Raise { } public final class arrow/core/raise/Trace { - public fun (Ljava/util/concurrent/CancellationException;)V - public final fun printStackTrace ()V - public final fun stackTraceToString ()Ljava/lang/String; - public final fun suppressedExceptions ()Ljava/util/List; + public static final synthetic fun box-impl (Ljava/util/concurrent/CancellationException;)Larrow/core/raise/Trace; + public static fun constructor-impl (Ljava/util/concurrent/CancellationException;)Ljava/util/concurrent/CancellationException; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/util/concurrent/CancellationException;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/util/concurrent/CancellationException;Ljava/util/concurrent/CancellationException;)Z + public fun hashCode ()I + public static fun hashCode-impl (Ljava/util/concurrent/CancellationException;)I + public static final fun printStackTrace-impl (Ljava/util/concurrent/CancellationException;)V + public static final fun stackTraceToString-impl (Ljava/util/concurrent/CancellationException;)Ljava/lang/String; + public static final fun suppressedExceptions-impl (Ljava/util/concurrent/CancellationException;)Ljava/util/List; + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/util/concurrent/CancellationException;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/util/concurrent/CancellationException; } public abstract interface class arrow/typeclasses/Monoid : arrow/typeclasses/Semigroup { From ee8e7afc6e3d84f8bfe353a4347962cb37974967 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Thu, 16 Mar 2023 21:05:25 +0100 Subject: [PATCH 16/17] Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt Co-authored-by: Youssef Shoaib --- .../arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e679ad803a3..cb6eb7fde6d 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 @@ -9,7 +9,7 @@ import kotlin.jvm.JvmName @RequiresOptIn("This API is experimental, and may change in the future.") public annotation class ExperimentalTraceApi -/** Tracing result of `R`. Allows to inspect `R`, and the traces from where it was raised. */ +/** Tracing result. Allows to inspect the traces from where raise was called. */ @ExperimentalTraceApi @JvmInline public value class Trace(private val exception: CancellationException) { From 9208d8ce730139c8439714c3010a91e2a7ecbdf9 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Thu, 16 Mar 2023 21:22:27 +0100 Subject: [PATCH 17/17] Add import @JvmInline --- .../arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt | 1 + 1 file changed, 1 insertion(+) 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 cb6eb7fde6d..33d303bb41a 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 @@ -3,6 +3,7 @@ package arrow.core.raise import kotlin.coroutines.cancellation.CancellationException +import kotlin.jvm.JvmInline import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName