diff --git a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/SafeContinuationJvm.kt b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/SafeContinuationJvm.kt index 47e5069eea1..0c101298e7e 100644 --- a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/SafeContinuationJvm.kt +++ b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/SafeContinuationJvm.kt @@ -36,7 +36,7 @@ internal actual constructor( while (true) { // lock-free loop val cur = this.result // atomic read when { - cur === UNDECIDED -> if (RESULT.compareAndSet(this, UNDECIDED, result._value)) return + cur === UNDECIDED -> if (RESULT.compareAndSet(this, UNDECIDED, result.value)) return cur === COROUTINE_SUSPENDED -> if (RESULT.compareAndSet(this, COROUTINE_SUSPENDED, RESUMED)) { delegate.resumeWith(result) return @@ -55,7 +55,7 @@ internal actual constructor( } return when { result === RESUMED -> COROUTINE_SUSPENDED // already called continuation, indicate COROUTINE_SUSPENDED upstream - result is Failure -> throw result.exception + result is SuccessOrFailure.Failure -> throw result.exception else -> result // either COROUTINE_SUSPENDED or data } } diff --git a/libraries/stdlib/coroutines/src/kotlin/SuccessOrFailure.kt b/libraries/stdlib/coroutines/src/kotlin/SuccessOrFailure.kt index 1124f69d96d..4a2126c2393 100644 --- a/libraries/stdlib/coroutines/src/kotlin/SuccessOrFailure.kt +++ b/libraries/stdlib/coroutines/src/kotlin/SuccessOrFailure.kt @@ -3,7 +3,7 @@ * that can be found in the license/LICENSE.txt file. */ -// todo: Figure out how to avoid supressing error, move suppressions where they are needed. +// todo: Figure out how to avoid suppressing errors, move suppressions where they are needed. @file:Suppress( "UNCHECKED_CAST", "RedundantVisibilityModifier", @@ -16,98 +16,259 @@ package kotlin import kotlin.internal.InlineOnly +import kotlin.internal.contracts.* import kotlin.jvm.JvmField -@SinceKotlin("1.3") -public inline class SuccessOrFailure @PublishedApi internal constructor(internal val _value: Any?) { +/** + * A discriminated union that encapsulates successful outcome with a value of type [T] + * or a failure with an arbitrary [Throwable] exception. + */ +public inline class SuccessOrFailure @PublishedApi internal constructor( + @PublishedApi internal val value: Any? +) : Serializable { // discovery - public val isSuccess: Boolean get() = _value !is Failure - public val isFailure: Boolean get() = _value is Failure + /** + * Returns `true` if this instance represents successful outcome. + * In this case [isFailure] returns `false`. + */ + public val isSuccess: Boolean get() = value !is Failure + + /** + * Returns `true` if this instance represents failed outcome. + * In this case [isSuccess] returns `false`. + */ + public val isFailure: Boolean get() = value is Failure // value retrieval - public fun getOrThrow(): T = when (_value) { - is Failure -> throw _value.exception - else -> _value as T - } + /** + * Returns the encapsulated value if this instance represents [success][isSuccess] or throws the encapsulated exception + * if it is [failure][isFailure]. + */ + public fun getOrThrow(): T = + when (value) { + is Failure -> throw value.exception + else -> value as T + } - public fun getOrNull(): T? = when (_value) { - is Failure -> null - else -> _value as T - } + /** + * Returns the encapsulated value if this instance represents [success][isSuccess] or `null` + * if it is [failure][isFailure]. + */ + public fun getOrNull(): T? = + when (value) { + is Failure -> null + else -> value as T + } // exception retrieval - public fun exceptionOrNull(): Throwable? = when (_value) { - is Failure -> _value.exception - else -> null + /** + * Returns the encapsulated exception if this instance represents [failure][isFailure] or `null` + * if it is [success][isSuccess]. + */ + public fun exceptionOrNull(): Throwable? = + when (value) { + is Failure -> value.exception + else -> null + } + + // identity + + /** + * Returns `true` if the [other] object is `SuccessOrFailure` that encapsulates an equal value or exception. + */ + public override fun equals(other: Any?): Boolean { + // todo: this is workaround for is/as bugs, rewrite in direct way when fixed + val other = other as? SuccessOrFailure<*> ?: return false + return value == other.value } - // internal API for inline functions + /** + * Returns hashcode of either the encapsulated value or of the exception. + */ + public override fun hashCode(): Int = value?.hashCode() ?: 0 - @PublishedApi internal val exception: Throwable get() = (_value as Failure).exception - @PublishedApi internal val value: T get() = _value as T + /** + * Returns a string representation of the encapsulated value or `Failure(xxx)` string where + * `xxx` is a string representation of the exception. + */ + public override fun toString(): String = value.toString() // companion with constructors + /** + * Companion object for [SuccessOrFailure] class that contains its constructor functions + * [success] and [failure]. + */ public companion object { + /** + * Returns an instance that encapsulates the given [value] as successful value. + */ @InlineOnly public inline fun success(value: T): SuccessOrFailure = SuccessOrFailure(value) + /** + * Returns an instance that encapsulates the given [exception] as failure. + */ @InlineOnly public inline fun failure(exception: Throwable): SuccessOrFailure = - SuccessOrFailure(Failure(exception)) + SuccessOrFailure(Failure(exception)) + } + + @PublishedApi + internal class Failure @PublishedApi internal constructor( + @JvmField + val exception: Throwable + ) : Serializable { + override fun equals(other: Any?): Boolean = other is Failure && exception == other.exception + override fun hashCode(): Int = exception.hashCode() + override fun toString(): String = "Failure($exception)" } } -// top-Level internal failure-marker class -// todo: maybe move it to another kotlin.internal package? -@SinceKotlin("1.3") -@PublishedApi -internal class Failure @PublishedApi internal constructor( - @JvmField - val exception: Throwable -) : Serializable - -@SinceKotlin("1.3") -@InlineOnly public inline fun runOrCatch(block: () -> T): SuccessOrFailure = - try { +/** + * Calls the specified function [block] and returns its encapsulated result if invocation was successful, + * catching and encapsulating any thrown exception as a failure. + */ +@InlineOnly public inline fun runCatching(block: () -> R): SuccessOrFailure { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + return try { SuccessOrFailure.success(block()) } catch (e: Throwable) { SuccessOrFailure.failure(e) } +} + +/** + * Calls the specified function [block] with `this` value as its receiver and returns its encapsulated result + * if invocation was successful, catching and encapsulating any thrown exception as a failure. + */ +@InlineOnly public inline fun T.runCatching(block: T.() -> R): SuccessOrFailure { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + return try { + SuccessOrFailure.success(block()) + } catch (e: Throwable) { + SuccessOrFailure.failure(e) + } +} // -- extensions --- -@SinceKotlin("1.3") -@InlineOnly public inline fun SuccessOrFailure.getOrElse(default: () -> R): R = when { - isFailure -> default() - else -> value -} +/** + * Returns the encapsulated value if this instance represents [success][SuccessOrFailure.isSuccess] or the + * result of [defaultValue] function if it is [failure][SuccessOrFailure.isFailure]. + * + * Note, that an exception thrown by [defaultValue] function is rethrown by this function. + */ +@InlineOnly public inline fun SuccessOrFailure.getOrElse(defaultValue: () -> R): R = + when(value) { + is SuccessOrFailure.Failure -> defaultValue() + else -> value as T + } // transformation -@SinceKotlin("1.3") -@InlineOnly public inline fun SuccessOrFailure.map(block: (T) -> U): SuccessOrFailure = - if (isFailure) this as SuccessOrFailure - else runOrCatch { block(value) } +/** + * Returns the encapsulated result of the given [transform] function applied to encapsulated value + * if this instance represents [success][SuccessOrFailure.isSuccess] or the + * original encapsulated exception if it is [failure][SuccessOrFailure.isFailure]. + * + * Note, that an exception thrown by [transform] function is rethrown by this function. + * See [mapCatching] for an alternative that encapsulates exceptions. + */ +@InlineOnly public inline fun SuccessOrFailure.map(transform: (T) -> R): SuccessOrFailure { + contract { + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) + } + return when(value) { + is SuccessOrFailure.Failure -> SuccessOrFailure(value) // cannot cast here -- casts don't work (todo) + else -> SuccessOrFailure.success(transform(value as T)) + } +} -@SinceKotlin("1.3") -@InlineOnly public inline fun SuccessOrFailure.handle(block: (Throwable) -> U): SuccessOrFailure = - if (isFailure) runOrCatch { block(exception) } - else this +/** + * Returns the encapsulated result of the given [transform] function applied to encapsulated value + * if this instance represents [success][SuccessOrFailure.isSuccess] or the + * original encapsulated exception if it is [failure][SuccessOrFailure.isFailure]. + * + * Any exception thrown by [transform] function is caught, encapsulated as a failure and returned by this function. + * See [map] for an alternative that rethrows exceptions. + */ +@InlineOnly public inline fun SuccessOrFailure.mapCatching(transform: (T) -> R): SuccessOrFailure { + contract { + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) + } + return when(value) { + is SuccessOrFailure.Failure -> SuccessOrFailure(value) // cannot cast here -- casts don't work (todo) + else -> runCatching { transform(value as T) } + } +} + +/** + * Returns the encapsulated result of the given [transform] function applied to encapsulated exception + * if this instance represents [failure][SuccessOrFailure.isFailure] or the + * original encapsulated value if it is [success][SuccessOrFailure.isSuccess]. + * + * Note, that an exception thrown by [transform] function is rethrown by this function. + * See [recoverCatching] for an alternative that encapsulates exceptions. + */ +@InlineOnly public inline fun SuccessOrFailure.recover(transform: (Throwable) -> R): SuccessOrFailure { + contract { + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) + } + return when(value) { + is SuccessOrFailure.Failure -> SuccessOrFailure.success(transform(value.exception)) + else -> this + } +} + +/** + * Returns the encapsulated result of the given [transform] function applied to encapsulated exception + * if this instance represents [failure][SuccessOrFailure.isFailure] or the + * original encapsulated value if it is [success][SuccessOrFailure.isSuccess]. + * + * Any exception thrown by [transform] function is caught, encapsulated as a failure and returned by this function. + * See [recover] for an alternative that rethrows exceptions. + */ +@InlineOnly public inline fun SuccessOrFailure.recoverCatching(transform: (Throwable) -> R): SuccessOrFailure { + contract { + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) + } + val value = value // workaround for inline classes BE bug + return when(value) { + is SuccessOrFailure.Failure -> runCatching { transform(value.exception) } + else -> this + } +} // "peek" onto value/exception and pipe -@SinceKotlin("1.3") -@InlineOnly public inline fun SuccessOrFailure.onFailure(block: (Throwable) -> Unit): SuccessOrFailure { - if (isFailure) block(exception) +/** + * Performs the given [action] on encapsulated value if this instance represents [success][SuccessOrFailure.isSuccess]. + * Returns the original `SuccessOrFailure` unchanged. + */ +@InlineOnly public inline fun SuccessOrFailure.onFailure(action: (Throwable) -> Unit): SuccessOrFailure { + contract { + callsInPlace(action, InvocationKind.AT_MOST_ONCE) + } + if (value is SuccessOrFailure.Failure) action(value.exception) return this } -@SinceKotlin("1.3") -@InlineOnly public inline fun SuccessOrFailure.onSuccess(block: (T) -> Unit): SuccessOrFailure { - if (isSuccess) block(value) +/** + * Performs the given [action] on encapsulated exception if this instance represents [failure][SuccessOrFailure.isFailure]. + * Returns the original `SuccessOrFailure` unchanged. + */ +@InlineOnly public inline fun SuccessOrFailure.onSuccess(action: (T) -> Unit): SuccessOrFailure { + contract { + callsInPlace(action, InvocationKind.AT_MOST_ONCE) + } + if (value !is SuccessOrFailure.Failure) action(value as T) return this }