Skip to content

Commit

Permalink
Keep stack traces in withError
Browse files Browse the repository at this point in the history
  • Loading branch information
serras committed Jan 26, 2025
1 parent b3654bf commit 0f17fe7
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 38 deletions.
16 changes: 16 additions & 0 deletions arrow-libs/core/arrow-core/api/android/arrow-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,10 @@ public final class arrow/core/UtilsKt {
public static final fun mapNullable (Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
}

public final class arrow/core/raise/CancellationExceptionNoTraceKt {
public static final fun copyStacktrace (Ljava/lang/Throwable;Ljava/lang/Throwable;)V
}

public final class arrow/core/raise/DefaultRaise : arrow/core/raise/Raise {
public fun <init> (Z)V
public fun bind (Larrow/core/Either;)Ljava/lang/Object;
Expand Down Expand Up @@ -924,6 +928,11 @@ public final class arrow/core/raise/IorRaise : arrow/core/raise/Raise {
public final fun recover (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
}

public final class arrow/core/raise/NoTrace : arrow/core/raise/RaiseCancellationException {
public fun <init> (Ljava/lang/Object;Larrow/core/raise/Raise;)V
public fun fillInStackTrace ()Ljava/lang/Throwable;
}

public abstract interface class arrow/core/raise/Raise {
public abstract fun bind (Larrow/core/Either;)Ljava/lang/Object;
public abstract fun bind (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
Expand Down Expand Up @@ -1163,3 +1172,10 @@ public final class arrow/core/raise/Trace {
public final synthetic fun unbox-impl ()Ljava/util/concurrent/CancellationException;
}

public final class arrow/core/raise/Traced : arrow/core/raise/RaiseCancellationException {
public fun <init> (Ljava/lang/Object;Larrow/core/raise/Raise;Larrow/core/raise/Traced;)V
public synthetic fun <init> (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;
}

12 changes: 12 additions & 0 deletions arrow-libs/core/arrow-core/api/arrow-core.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,10 @@ final class arrow.core.raise/DefaultRaise : arrow.core.raise/Raise<kotlin/Any?>
final fun raise(kotlin/Any?): kotlin/Nothing // arrow.core.raise/DefaultRaise.raise|raise(kotlin.Any?){}[0]
}

final class arrow.core.raise/NoTrace : arrow.core.raise/RaiseCancellationException { // arrow.core.raise/NoTrace|null[0]
constructor <init>(kotlin/Any?, arrow.core.raise/Raise<kotlin/Any?>) // arrow.core.raise/NoTrace.<init>|<init>(kotlin.Any?;arrow.core.raise.Raise<kotlin.Any?>){}[0]
}

final class arrow.core.raise/ResultRaise : arrow.core.raise/Raise<kotlin/Throwable> { // arrow.core.raise/ResultRaise|null[0]
constructor <init>(arrow.core.raise/Raise<kotlin/Throwable>) // arrow.core.raise/ResultRaise.<init>|<init>(arrow.core.raise.Raise<kotlin.Throwable>){}[0]

Expand All @@ -342,6 +346,13 @@ final class arrow.core.raise/ResultRaise : arrow.core.raise/Raise<kotlin/Throwab
final suspend fun <#A1: kotlin/Any?> (kotlin.coroutines/SuspendFunction1<arrow.core.raise/Raise<kotlin/Throwable>, #A1>).invoke(): #A1 // arrow.core.raise/ResultRaise.invoke|[email protected]<arrow.core.raise.Raise<kotlin.Throwable>,0:0>(){0§<kotlin.Any?>}[0]
}

final class arrow.core.raise/Traced : arrow.core.raise/RaiseCancellationException { // arrow.core.raise/Traced|null[0]
constructor <init>(kotlin/Any?, arrow.core.raise/Raise<kotlin/Any?>, arrow.core.raise/Traced? = ...) // arrow.core.raise/Traced.<init>|<init>(kotlin.Any?;arrow.core.raise.Raise<kotlin.Any?>;arrow.core.raise.Traced?){}[0]

final val cause // arrow.core.raise/Traced.cause|{}cause[0]
final fun <get-cause>(): arrow.core.raise/Traced? // arrow.core.raise/Traced.cause.<get-cause>|<get-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 <init>(arrow.atomic/Atomic<kotlin.collections/Map<#A, #B>> = ...) // arrow.core/AtomicMemoizationCache.<init>|<init>(arrow.atomic.Atomic<kotlin.collections.Map<1:0,1:1>>){}[0]

Expand Down Expand Up @@ -711,6 +722,7 @@ final const val arrow.core/RedundantAPI // arrow.core/RedundantAPI|{}RedundantAP

final fun (arrow.core.raise/Traced).arrow.core.raise/withCause(arrow.core.raise/Traced): arrow.core.raise/Traced // arrow.core.raise/withCause|[email protected](arrow.core.raise.Traced){}[0]
final fun (kotlin/String).arrow.core/escaped(): kotlin/String // arrow.core/escaped|[email protected](){}[0]
final fun (kotlin/Throwable).arrow.core.raise/copyStacktrace(kotlin/Throwable) // arrow.core.raise/copyStacktrace|[email protected](kotlin.Throwable){}[0]
final fun (kotlin/Throwable).arrow.core/nonFatalOrThrow(): kotlin/Throwable // arrow.core/nonFatalOrThrow|[email protected](){}[0]
final fun <#A: kotlin/Any> (kotlin/Function1<#A, kotlin/Boolean>).arrow.core/mapNullable(): kotlin/Function1<#A?, kotlin/Boolean> // arrow.core/mapNullable|[email protected]<0:0,kotlin.Boolean>(){0§<kotlin.Any>}[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|[email protected]<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§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>;3§<kotlin.Any?>;4§<kotlin.Any?>;5§<kotlin.Any?>;6§<kotlin.Any?>;7§<kotlin.Any?>;8§<kotlin.Any?>;9§<kotlin.Any?>;10§<kotlin.Any?>}[0]
Expand Down
16 changes: 16 additions & 0 deletions arrow-libs/core/arrow-core/api/jvm/arrow-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,10 @@ public final class arrow/core/UtilsKt {
public static final fun mapNullable (Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
}

public final class arrow/core/raise/CancellationExceptionNoTraceKt {
public static final fun copyStacktrace (Ljava/lang/Throwable;Ljava/lang/Throwable;)V
}

public final class arrow/core/raise/DefaultRaise : arrow/core/raise/Raise {
public fun <init> (Z)V
public fun bind (Larrow/core/Either;)Ljava/lang/Object;
Expand Down Expand Up @@ -924,6 +928,11 @@ public final class arrow/core/raise/IorRaise : arrow/core/raise/Raise {
public final fun recover (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
}

public final class arrow/core/raise/NoTrace : arrow/core/raise/RaiseCancellationException {
public fun <init> (Ljava/lang/Object;Larrow/core/raise/Raise;)V
public fun fillInStackTrace ()Ljava/lang/Throwable;
}

public abstract interface class arrow/core/raise/Raise {
public abstract fun bind (Larrow/core/Either;)Ljava/lang/Object;
public abstract fun bind (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
Expand Down Expand Up @@ -1163,3 +1172,10 @@ public final class arrow/core/raise/Trace {
public final synthetic fun unbox-impl ()Ljava/util/concurrent/CancellationException;
}

public final class arrow/core/raise/Traced : arrow/core/raise/RaiseCancellationException {
public fun <init> (Ljava/lang/Object;Larrow/core/raise/Raise;Larrow/core/raise/Traced;)V
public synthetic fun <init> (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;
}

Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package arrow.core.raise

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
*/
@OptIn(DelicateRaiseApi::class)
@DelicateRaiseApi
@Suppress(
"EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING",
"SEALED_INHERITOR_IN_DIFFERENT_MODULE"
)
@PublishedApi
internal actual class NoTrace actual constructor(raised: Any?, raise: Raise<Any?>) : RaiseCancellationException(raised, raise) {
override fun fillInStackTrace(): Throwable {
// Prevent Android <= 6.0 bug.
Expand All @@ -19,3 +18,8 @@ internal actual class NoTrace actual constructor(raised: Any?, raise: Raise<Any?
return this
}
}

@PublishedApi
internal actual fun Throwable.copyStacktrace(from: Throwable) {
this.stackTrace = from.stackTrace
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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
Expand Down Expand Up @@ -152,6 +153,59 @@ public inline fun <Error, A, B> fold(
}
}

/**
* Execute the [Raise] context function resulting in [A] or any _logical error_ of type [OtherError],
* and transform any raised [OtherError] into [Error], which is raised to the outer [Raise].
*
* <!--- INCLUDE
* import arrow.core.Either
* import arrow.core.raise.either
* import arrow.core.raise.withError
* import io.kotest.matchers.shouldBe
* -->
* ```kotlin
* fun test() {
* either<Int, String> {
* withError(String::length) {
* raise("failed")
* }
* } shouldBe Either.Left(6)
* }
* ```
* <!--- KNIT example-raise-dsl-11.kt -->
* <!--- TEST lines.isEmpty() -->
*/
@RaiseDSL
@OptIn(DelicateRaiseApi::class)
@Suppress("UNCHECKED_CAST")
public inline fun <Error, OtherError, A> Raise<Error>.withError(
transform: (OtherError) -> Error,
@BuilderInference block: Raise<OtherError>.() -> A
): A {
contract {
callsInPlace(block, EXACTLY_ONCE)
callsInPlace(transform, AT_MOST_ONCE)
}
// recover({ return block(this) }) { raise(transform(it)) }
val raise = DefaultRaise(false)
return try {
block(raise).also { raise.complete() }
} catch (e: RaiseCancellationException) {
raise.complete()
val error = transform(e.raisedOrRethrow(raise))
when {
this is RaiseAccumulate -> raise(error)
e is Traced -> throw Traced(error, this as Raise<Any?>, e.cause).also {
it.copyStacktrace(e)
}
else -> throw NoTrace(error, this as Raise<Any?>)
}
} catch (e: Throwable) {
raise.complete()
throw e.nonFatalOrThrow()
}
}

/**
* Inspect a [Trace] value of [Error].
*
Expand Down Expand Up @@ -264,11 +318,16 @@ public sealed class RaiseCancellationException(

@DelicateRaiseApi
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
@PublishedApi
internal expect class NoTrace(raised: Any?, raise: Raise<Any?>) : RaiseCancellationException

@DelicateRaiseApi
@PublishedApi
internal class Traced(raised: Any?, raise: Raise<Any?>, override val cause: Traced? = null): RaiseCancellationException(raised, raise)

@PublishedApi
internal expect fun Throwable.copyStacktrace(from: Throwable)

private class RaiseLeakedException : IllegalStateException(
"""
'raise' or 'bind' was leaked outside of its context scope.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import arrow.core.recover
import kotlin.coroutines.cancellation.CancellationException
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind.AT_MOST_ONCE
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
import kotlin.contracts.contract
import kotlin.experimental.ExperimentalTypeInference
import kotlin.jvm.JvmMultifileClass
Expand Down Expand Up @@ -629,39 +628,6 @@ public inline fun <Error, B : Any> Raise<Error>.ensureNotNull(value: B?, raise:
return value ?: raise(raise())
}

/**
* Execute the [Raise] context function resulting in [A] or any _logical error_ of type [OtherError],
* and transform any raised [OtherError] into [Error], which is raised to the outer [Raise].
*
* <!--- INCLUDE
* import arrow.core.Either
* import arrow.core.raise.either
* import arrow.core.raise.withError
* import io.kotest.matchers.shouldBe
* -->
* ```kotlin
* fun test() {
* either<Int, String> {
* withError(String::length) {
* raise("failed")
* }
* } shouldBe Either.Left(6)
* }
* ```
* <!--- KNIT example-raise-dsl-11.kt -->
* <!--- TEST lines.isEmpty() -->
*/
@RaiseDSL
public inline fun <Error, OtherError, A> Raise<Error>.withError(
transform: (OtherError) -> Error,
@BuilderInference block: Raise<OtherError>.() -> A
): A {
contract {
callsInPlace(block, EXACTLY_ONCE)
}
recover({ return block(this) }) { raise(transform(it)) }
}

/**
* Execute the [Raise] context function resulting in [A] or any _logical error_ of type [A].
* Does not distinguish between normal results and errors, thus you can consider
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package arrow.core.raise

@OptIn(DelicateRaiseApi::class)
@DelicateRaiseApi
@Suppress(
"EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING",
"SEALED_INHERITOR_IN_DIFFERENT_MODULE"
)
@PublishedApi
internal actual class NoTrace actual constructor(raised: Any?, raise: Raise<Any?>) : RaiseCancellationException(raised, raise)

@PublishedApi
internal actual fun Throwable.copyStacktrace(from: Throwable) { }

0 comments on commit 0f17fe7

Please sign in to comment.