Updated SuccessOrFailure class corresponding to KEEP-127 and docs.
This commit is contained in:
committed by
Denis Zharkov
parent
33f4a3fcbf
commit
3aa76c58fc
@@ -36,7 +36,7 @@ internal actual constructor(
|
|||||||
while (true) { // lock-free loop
|
while (true) { // lock-free loop
|
||||||
val cur = this.result // atomic read
|
val cur = this.result // atomic read
|
||||||
when {
|
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)) {
|
cur === COROUTINE_SUSPENDED -> if (RESULT.compareAndSet(this, COROUTINE_SUSPENDED, RESUMED)) {
|
||||||
delegate.resumeWith(result)
|
delegate.resumeWith(result)
|
||||||
return
|
return
|
||||||
@@ -55,7 +55,7 @@ internal actual constructor(
|
|||||||
}
|
}
|
||||||
return when {
|
return when {
|
||||||
result === RESUMED -> COROUTINE_SUSPENDED // already called continuation, indicate COROUTINE_SUSPENDED upstream
|
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
|
else -> result // either COROUTINE_SUSPENDED or data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* that can be found in the license/LICENSE.txt file.
|
* 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(
|
@file:Suppress(
|
||||||
"UNCHECKED_CAST",
|
"UNCHECKED_CAST",
|
||||||
"RedundantVisibilityModifier",
|
"RedundantVisibilityModifier",
|
||||||
@@ -16,98 +16,259 @@
|
|||||||
package kotlin
|
package kotlin
|
||||||
|
|
||||||
import kotlin.internal.InlineOnly
|
import kotlin.internal.InlineOnly
|
||||||
|
import kotlin.internal.contracts.*
|
||||||
import kotlin.jvm.JvmField
|
import kotlin.jvm.JvmField
|
||||||
|
|
||||||
@SinceKotlin("1.3")
|
/**
|
||||||
public inline class SuccessOrFailure<out T> @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<out T> @PublishedApi internal constructor(
|
||||||
|
@PublishedApi internal val value: Any?
|
||||||
|
) : Serializable {
|
||||||
// discovery
|
// 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
|
// value retrieval
|
||||||
|
|
||||||
public fun getOrThrow(): T = when (_value) {
|
/**
|
||||||
is Failure -> throw _value.exception
|
* Returns the encapsulated value if this instance represents [success][isSuccess] or throws the encapsulated exception
|
||||||
else -> _value as T
|
* 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
|
* Returns the encapsulated value if this instance represents [success][isSuccess] or `null`
|
||||||
else -> _value as T
|
* if it is [failure][isFailure].
|
||||||
}
|
*/
|
||||||
|
public fun getOrNull(): T? =
|
||||||
|
when (value) {
|
||||||
|
is Failure -> null
|
||||||
|
else -> value as T
|
||||||
|
}
|
||||||
|
|
||||||
// exception retrieval
|
// exception retrieval
|
||||||
|
|
||||||
public fun exceptionOrNull(): Throwable? = when (_value) {
|
/**
|
||||||
is Failure -> _value.exception
|
* Returns the encapsulated exception if this instance represents [failure][isFailure] or `null`
|
||||||
else -> 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 with constructors
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Companion object for [SuccessOrFailure] class that contains its constructor functions
|
||||||
|
* [success] and [failure].
|
||||||
|
*/
|
||||||
public companion object {
|
public companion object {
|
||||||
|
/**
|
||||||
|
* Returns an instance that encapsulates the given [value] as successful value.
|
||||||
|
*/
|
||||||
@InlineOnly public inline fun <T> success(value: T): SuccessOrFailure<T> =
|
@InlineOnly public inline fun <T> success(value: T): SuccessOrFailure<T> =
|
||||||
SuccessOrFailure(value)
|
SuccessOrFailure(value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance that encapsulates the given [exception] as failure.
|
||||||
|
*/
|
||||||
@InlineOnly public inline fun <T> failure(exception: Throwable): SuccessOrFailure<T> =
|
@InlineOnly public inline fun <T> failure(exception: Throwable): SuccessOrFailure<T> =
|
||||||
SuccessOrFailure<T>(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?
|
* Calls the specified function [block] and returns its encapsulated result if invocation was successful,
|
||||||
@SinceKotlin("1.3")
|
* catching and encapsulating any thrown exception as a failure.
|
||||||
@PublishedApi
|
*/
|
||||||
internal class Failure @PublishedApi internal constructor(
|
@InlineOnly public inline fun <R> runCatching(block: () -> R): SuccessOrFailure<R> {
|
||||||
@JvmField
|
contract {
|
||||||
val exception: Throwable
|
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||||
) : Serializable
|
}
|
||||||
|
return try {
|
||||||
@SinceKotlin("1.3")
|
|
||||||
@InlineOnly public inline fun <T> runOrCatch(block: () -> T): SuccessOrFailure<T> =
|
|
||||||
try {
|
|
||||||
SuccessOrFailure.success(block())
|
SuccessOrFailure.success(block())
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
SuccessOrFailure.failure(e)
|
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, R> T.runCatching(block: T.() -> R): SuccessOrFailure<R> {
|
||||||
|
contract {
|
||||||
|
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
SuccessOrFailure.success(block())
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
SuccessOrFailure.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -- extensions ---
|
// -- extensions ---
|
||||||
|
|
||||||
@SinceKotlin("1.3")
|
/**
|
||||||
@InlineOnly public inline fun <R, T : R> SuccessOrFailure<T>.getOrElse(default: () -> R): R = when {
|
* Returns the encapsulated value if this instance represents [success][SuccessOrFailure.isSuccess] or the
|
||||||
isFailure -> default()
|
* result of [defaultValue] function if it is [failure][SuccessOrFailure.isFailure].
|
||||||
else -> value
|
*
|
||||||
}
|
* Note, that an exception thrown by [defaultValue] function is rethrown by this function.
|
||||||
|
*/
|
||||||
|
@InlineOnly public inline fun <R, T : R> SuccessOrFailure<T>.getOrElse(defaultValue: () -> R): R =
|
||||||
|
when(value) {
|
||||||
|
is SuccessOrFailure.Failure -> defaultValue()
|
||||||
|
else -> value as T
|
||||||
|
}
|
||||||
|
|
||||||
// transformation
|
// transformation
|
||||||
|
|
||||||
@SinceKotlin("1.3")
|
/**
|
||||||
@InlineOnly public inline fun <U, T> SuccessOrFailure<T>.map(block: (T) -> U): SuccessOrFailure<U> =
|
* Returns the encapsulated result of the given [transform] function applied to encapsulated value
|
||||||
if (isFailure) this as SuccessOrFailure<U>
|
* if this instance represents [success][SuccessOrFailure.isSuccess] or the
|
||||||
else runOrCatch { block(value) }
|
* 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 <R, T> SuccessOrFailure<T>.map(transform: (T) -> R): SuccessOrFailure<R> {
|
||||||
|
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 <U, T: U> SuccessOrFailure<T>.handle(block: (Throwable) -> U): SuccessOrFailure<U> =
|
* Returns the encapsulated result of the given [transform] function applied to encapsulated value
|
||||||
if (isFailure) runOrCatch { block(exception) }
|
* if this instance represents [success][SuccessOrFailure.isSuccess] or the
|
||||||
else this
|
* 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 <R, T> SuccessOrFailure<T>.mapCatching(transform: (T) -> R): SuccessOrFailure<R> {
|
||||||
|
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 <R, T: R> SuccessOrFailure<T>.recover(transform: (Throwable) -> R): SuccessOrFailure<R> {
|
||||||
|
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 <R, T: R> SuccessOrFailure<T>.recoverCatching(transform: (Throwable) -> R): SuccessOrFailure<R> {
|
||||||
|
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
|
// "peek" onto value/exception and pipe
|
||||||
|
|
||||||
@SinceKotlin("1.3")
|
/**
|
||||||
@InlineOnly public inline fun <T> SuccessOrFailure<T>.onFailure(block: (Throwable) -> Unit): SuccessOrFailure<T> {
|
* Performs the given [action] on encapsulated value if this instance represents [success][SuccessOrFailure.isSuccess].
|
||||||
if (isFailure) block(exception)
|
* Returns the original `SuccessOrFailure` unchanged.
|
||||||
|
*/
|
||||||
|
@InlineOnly public inline fun <T> SuccessOrFailure<T>.onFailure(action: (Throwable) -> Unit): SuccessOrFailure<T> {
|
||||||
|
contract {
|
||||||
|
callsInPlace(action, InvocationKind.AT_MOST_ONCE)
|
||||||
|
}
|
||||||
|
if (value is SuccessOrFailure.Failure) action(value.exception)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@SinceKotlin("1.3")
|
/**
|
||||||
@InlineOnly public inline fun <T> SuccessOrFailure<T>.onSuccess(block: (T) -> Unit): SuccessOrFailure<T> {
|
* Performs the given [action] on encapsulated exception if this instance represents [failure][SuccessOrFailure.isFailure].
|
||||||
if (isSuccess) block(value)
|
* Returns the original `SuccessOrFailure` unchanged.
|
||||||
|
*/
|
||||||
|
@InlineOnly public inline fun <T> SuccessOrFailure<T>.onSuccess(action: (T) -> Unit): SuccessOrFailure<T> {
|
||||||
|
contract {
|
||||||
|
callsInPlace(action, InvocationKind.AT_MOST_ONCE)
|
||||||
|
}
|
||||||
|
if (value !is SuccessOrFailure.Failure) action(value as T)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user