Updated SuccessOrFailure class corresponding to KEEP-127 and docs.

This commit is contained in:
Roman Elizarov
2018-07-05 23:49:56 +03:00
committed by Denis Zharkov
parent 33f4a3fcbf
commit 3aa76c58fc
2 changed files with 214 additions and 53 deletions
@@ -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
}
}
@@ -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<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
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 <T> success(value: T): SuccessOrFailure<T> =
SuccessOrFailure(value)
/**
* Returns an instance that encapsulates the given [exception] as failure.
*/
@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?
@SinceKotlin("1.3")
@PublishedApi
internal class Failure @PublishedApi internal constructor(
@JvmField
val exception: Throwable
) : Serializable
@SinceKotlin("1.3")
@InlineOnly public inline fun <T> runOrCatch(block: () -> T): SuccessOrFailure<T> =
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 <R> runCatching(block: () -> R): SuccessOrFailure<R> {
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, 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 ---
@SinceKotlin("1.3")
@InlineOnly public inline fun <R, T : R> SuccessOrFailure<T>.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 <R, T : R> SuccessOrFailure<T>.getOrElse(defaultValue: () -> R): R =
when(value) {
is SuccessOrFailure.Failure -> defaultValue()
else -> value as T
}
// transformation
@SinceKotlin("1.3")
@InlineOnly public inline fun <U, T> SuccessOrFailure<T>.map(block: (T) -> U): SuccessOrFailure<U> =
if (isFailure) this as SuccessOrFailure<U>
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 <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> =
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 <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
@SinceKotlin("1.3")
@InlineOnly public inline fun <T> SuccessOrFailure<T>.onFailure(block: (Throwable) -> Unit): SuccessOrFailure<T> {
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 <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
}
@SinceKotlin("1.3")
@InlineOnly public inline fun <T> SuccessOrFailure<T>.onSuccess(block: (T) -> Unit): SuccessOrFailure<T> {
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 <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
}