Rename SuccessOrFailure to Result and hide Failure from ABI

* The members of Result are isSuccess, isFailure, exceptionOrNull, getOrNull
* The rest of API is implemented via inline-only extensions
* There are two internal functions to hide detailed mechanics of an internal
  Result.Failure class: createFailure and throwOnFailure
* Result.toString is explicit: either Success(v) or Failure(x)

See KT-26538
This commit is contained in:
Roman Elizarov
2018-09-09 11:34:31 +03:00
parent 69ee88871f
commit e2713501ce
81 changed files with 968 additions and 612 deletions
@@ -79,7 +79,7 @@ private class ExperimentalContinuationMigration<T>(val continuation: Continuatio
private class ContinuationMigration<T>(val continuation: ExperimentalContinuation<T>): Continuation<T> {
override val context: CoroutineContext = continuation.context.toCoroutineContext()
override fun resumeWith(result: SuccessOrFailure<T>) {
override fun resumeWith(result: Result<T>) {
result
.onSuccess { continuation.resume(it) }
.onFailure { continuation.resumeWithException(it) }
@@ -17,5 +17,5 @@ internal expect class SafeContinuation<in T> : Continuation<T> {
internal fun getOrThrow(): Any?
override val context: CoroutineContext
override fun resumeWith(result: SuccessOrFailure<T>): Unit
override fun resumeWith(result: Result<T>): Unit
}
@@ -23,7 +23,7 @@ internal actual constructor(
private var result: Any? = initialResult
public actual override fun resumeWith(result: SuccessOrFailure<T>) {
public actual override fun resumeWith(result: Result<T>) {
val cur = this.result
when {
cur === UNDECIDED -> {
@@ -46,7 +46,7 @@ internal actual constructor(
val result = this.result
return when {
result === RESUMED -> COROUTINE_SUSPENDED // already called continuation, indicate COROUTINE_SUSPENDED upstream
result is SuccessOrFailure.Failure -> throw result.exception
result is Result.Failure -> throw result.exception
else -> result // either COROUTINE_SUSPENDED or data
}
}
@@ -25,7 +25,7 @@ internal abstract class CoroutineImpl(private val resultContinuation: Continuati
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted_ = it }
override fun resumeWith(result: SuccessOrFailure<Any?>) {
override fun resumeWith(result: Result<Any?>) {
var current = this
var currentResult: Any? = result.getOrNull()
var currentException: Throwable? = result.exceptionOrNull()
@@ -84,7 +84,7 @@ internal object CompletedContinuation : Continuation<Any?> {
override val context: CoroutineContext
get() = error("This continuation is already complete")
override fun resumeWith(result: SuccessOrFailure<Any?>) {
override fun resumeWith(result: Result<Any?>) {
error("This continuation is already complete")
}
@@ -34,7 +34,7 @@ internal actual constructor(
)
}
public actual override fun resumeWith(result: SuccessOrFailure<T>) {
public actual override fun resumeWith(result: Result<T>) {
while (true) { // lock-free loop
val cur = this.result // atomic read
when {
@@ -57,7 +57,7 @@ internal actual constructor(
}
return when {
result === RESUMED -> COROUTINE_SUSPENDED // already called continuation, indicate COROUTINE_SUSPENDED upstream
result is SuccessOrFailure.Failure -> throw result.exception
result is Result.Failure -> throw result.exception
else -> result // either COROUTINE_SUSPENDED or data
}
}
@@ -162,7 +162,7 @@ private inline fun <T> createCoroutineFromSuspendFunction(
object : RestrictedContinuationImpl(completion as Continuation<Any?>) {
private var label = 0
override fun invokeSuspend(result: SuccessOrFailure<Any?>): Any? =
override fun invokeSuspend(result: Result<Any?>): Any? =
when (label) {
0 -> {
label = 1
@@ -180,7 +180,7 @@ private inline fun <T> createCoroutineFromSuspendFunction(
object : ContinuationImpl(completion as Continuation<Any?>, context) {
private var label = 0
override fun invokeSuspend(result: SuccessOrFailure<Any?>): Any? =
override fun invokeSuspend(result: Result<Any?>): Any? =
when (label) {
0 -> {
label = 1
@@ -18,7 +18,7 @@ internal abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
// This implementation is final. This fact is used to unroll resumeWith recursion.
public final override fun resumeWith(result: SuccessOrFailure<Any?>) {
public final override fun resumeWith(result: Result<Any?>) {
// Invoke "resume" debug probe only once, even if previous frames are "resumed" in the loop below, too
probeCoroutineResumed(this)
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
@@ -27,13 +27,13 @@ internal abstract class BaseContinuationImpl(
while (true) {
with(current) {
val completion = completion!! // fail fast when trying to resume continuation without completion
val outcome: SuccessOrFailure<Any?> =
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
SuccessOrFailure.success(outcome)
Result.success(outcome)
} catch (exception: Throwable) {
SuccessOrFailure.failure(exception)
Result.failure(exception)
}
releaseIntercepted() // this state machine instance is terminating
if (completion is BaseContinuationImpl) {
@@ -49,7 +49,7 @@ internal abstract class BaseContinuationImpl(
}
}
protected abstract fun invokeSuspend(result: SuccessOrFailure<Any?>): Any?
protected abstract fun invokeSuspend(result: Result<Any?>): Any?
protected open fun releaseIntercepted() {
// does nothing here, overridden in ContinuationImpl
@@ -124,7 +124,7 @@ internal object CompletedContinuation : Continuation<Any?> {
override val context: CoroutineContext
get() = error("This continuation is already complete")
override fun resumeWith(result: SuccessOrFailure<Any?>) {
override fun resumeWith(result: Result<Any?>) {
error("This continuation is already complete")
}
@@ -24,9 +24,9 @@ private class RunSuspend : Continuation<Unit> {
override val context: CoroutineContext
get() = EmptyCoroutineContext
var result: SuccessOrFailure<Unit>? = null
var result: Result<Unit>? = null
override fun resumeWith(result: SuccessOrFailure<Unit>) = synchronized(this) {
override fun resumeWith(result: Result<Unit>) = synchronized(this) {
this.result = result
(this as Object).notifyAll()
}
@@ -33,7 +33,7 @@ private class MyContinuation : BaseContinuationImpl(null) {
var label = 0
override fun invokeSuspend(result: SuccessOrFailure<Any?>): Any? = null
override fun invokeSuspend(result: Result<Any?>): Any? = null
}
class DebugMetadataTest {
@@ -7,7 +7,7 @@ package test.kotlin
import kotlin.test.*
class SuccessOrFailureTest {
class ResultTest {
@Test
fun testRunCatchingSuccess() {
val ok = runCatching { "OK" }
@@ -22,17 +22,17 @@ class SuccessOrFailureTest {
@Test
fun testConstructedSuccess() {
val ok = SuccessOrFailure.success("OK")
val ok = Result.success("OK")
checkSuccess(ok, "OK", true)
}
@Test
fun testConstructedFailure() {
val fail = SuccessOrFailure.failure<Unit>(IllegalStateException("F"))
val fail = Result.failure<Unit>(IllegalStateException("F"))
checkFailure(fail, "F", true)
}
private fun <T> checkSuccess(ok: SuccessOrFailure<T>, v: T, topLevel: Boolean = false) {
private fun <T> checkSuccess(ok: Result<T>, v: T, topLevel: Boolean = false) {
assertTrue(ok.isSuccess)
assertFalse(ok.isFailure)
assertEquals(v, ok.getOrThrow())
@@ -44,7 +44,7 @@ class SuccessOrFailureTest {
assertEquals("V:$v", ok.fold({ "V:$it" }, { "EX:$it" }))
assertEquals(null, ok.exceptionOrNull())
assertEquals(null, ok.fold(onSuccess = { null }, onFailure = { it }))
assertEquals(v.toString(), ok.toString())
assertEquals("Success($v)", ok.toString())
assertEquals(ok, ok)
if (topLevel) {
checkSuccess(ok.map { 42 }, 42)
@@ -62,7 +62,7 @@ class SuccessOrFailureTest {
assertEquals(0, fCnt)
}
private fun <T> checkFailure(fail: SuccessOrFailure<T>, msg: String, topLevel: Boolean = false) {
private fun <T> checkFailure(fail: Result<T>, msg: String, topLevel: Boolean = false) {
assertFalse(fail.isSuccess)
assertTrue(fail.isFailure)
assertFails { fail.getOrThrow() }
@@ -41,7 +41,7 @@ class TestDispatcher(
inner class DispatchedContinuation<T>(val delegate: Continuation<T>) : Continuation<T> {
override val context: CoroutineContext = delegate.context
override fun resumeWith(result: SuccessOrFailure<T>) {
override fun resumeWith(result: Result<T>) {
executor.execute {
delegate.resumeWith(result)
}
@@ -0,0 +1,328 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("UNCHECKED_CAST", "RedundantVisibilityModifier")
package kotlin
import kotlin.contracts.*
import kotlin.internal.InlineOnly
import kotlin.jvm.JvmField
/**
* A discriminated union that encapsulates successful outcome with a value of type [T]
* or a failure with an arbitrary [Throwable] exception.
*/
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
public inline class Result<out T> @PublishedApi internal constructor(
@PublishedApi
internal val value: Any?
) : Serializable {
// discovery
/**
* 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 & exception retrieval
/**
* Returns the encapsulated value if this instance represents [success][Result.isSuccess] or `null`
* if it is [failure][Result.isFailure].
*
* This function is shorthand for `getOrElse { null }` (see [getOrElse]) or
* `fold(onSuccess = { it }, onFailure = { null })` (see [fold]).
*/
public fun getOrNull(): T? =
when (value) {
is Failure -> null
else -> value as T
}
/**
* Returns the encapsulated exception if this instance represents [failure][isFailure] or `null`
* if it is [success][isSuccess].
*
* This function is shorthand for `fold(onSuccess = { null }, onFailure = { it })` (see [fold]).
*/
public fun exceptionOrNull(): Throwable? =
when (value) {
is Failure -> value.exception
else -> null
}
/**
* Returns a string `Success(v)` if this instance represents [success][Result.isSuccess]
* where `v` is a string representation of the value or a string `Failure(x)` if
* it is [failure][isFailure] where `x` is a string representation of the exception.
*/
public override fun toString(): String =
when (value) {
is Failure -> value.toString() // "Failure($exception)"
else -> "Success($value)"
}
// companion with constructors
/**
* Companion object for [Result] 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): Result<T> =
Result(value)
/**
* Returns an instance that encapsulates the given [exception] as failure.
*/
@InlineOnly public inline fun <T> failure(exception: Throwable): Result<T> =
Result(createFailure(exception))
}
internal class Failure(
@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)"
}
}
/**
* Creates an instance of internal marker [Result.Failure] class to
* make sure that this class is not exposed in ABI.
*/
@PublishedApi
internal fun createFailure(exception: Throwable): Any =
Result.Failure(exception)
/**
* Throws exception if the result is failure. This internal function minimizes
* inlined bytecode for [getOrThrow] and makes sure that in the future we can
* add some exception-augmenting logic here (if needed).
*/
@PublishedApi
internal fun Result<*>.throwOnFailure() {
if (value is Result.Failure) throw value.exception
}
/**
* 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): Result<R> {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return try {
Result.success(block())
} catch (e: Throwable) {
Result.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): Result<R> {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return try {
Result.success(block())
} catch (e: Throwable) {
Result.failure(e)
}
}
// -- extensions ---
/**
* Returns the encapsulated value if this instance represents [success][Result.isSuccess] or throws the encapsulated exception
* if it is [failure][Result.isFailure].
*
* This function is shorthand for `getOrElse { throw it }` (see [getOrElse]).
*/
@InlineOnly
public inline fun <T> Result<T>.getOrThrow(): T {
throwOnFailure()
return value as T
}
/**
* Returns the encapsulated value if this instance represents [success][Result.isSuccess] or the
* result of [onFailure] function for encapsulated exception if it is [failure][Result.isFailure].
*
* Note, that an exception thrown by [onFailure] function is rethrown by this function.
*
* This function is shorthand for `fold(onSuccess = { it }, onFailure = onFailure)` (see [fold]).
*/
@InlineOnly
public inline fun <R, T : R> Result<T>.getOrElse(onFailure: (exception: Throwable) -> R): R {
contract {
callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
}
return when(val exception = exceptionOrNull()) {
null -> value as T
else -> onFailure(exception)
}
}
/**
* Returns the encapsulated value if this instance represents [success][Result.isSuccess] or the
* [defaultValue] if it is [failure][Result.isFailure].
*
* This function is shorthand for `getOrElse { defaultValue }` (see [getOrElse]).
*/
@InlineOnly
public inline fun <R, T : R> Result<T>.getOrDefault(defaultValue: R): R {
if (isFailure) return defaultValue
return value as T
}
/**
* Returns the the result of [onSuccess] for encapsulated value if this instance represents [success][Result.isSuccess]
* or the result of [onFailure] function for encapsulated exception if it is [failure][Result.isFailure].
*
* Note, that an exception thrown by [onSuccess] or by [onFailure] function is rethrown by this function.
*/
@InlineOnly
public inline fun <R, T> Result<T>.fold(
onSuccess: (value: T) -> R,
onFailure: (exception: Throwable) -> R
): R {
contract {
callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE)
callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
}
return when(val exception = exceptionOrNull()) {
null -> onSuccess(value as T)
else -> onFailure(exception)
}
}
// transformation
/**
* Returns the encapsulated result of the given [transform] function applied to encapsulated value
* if this instance represents [success][Result.isSuccess] or the
* original encapsulated exception if it is [failure][Result.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> Result<T>.map(transform: (value: T) -> R): Result<R> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
return when(val exception = exceptionOrNull()) {
null -> Result.success(transform(value as T))
else -> Result(value)
}
}
/**
* Returns the encapsulated result of the given [transform] function applied to encapsulated value
* if this instance represents [success][Result.isSuccess] or the
* original encapsulated exception if it is [failure][Result.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> Result<T>.mapCatching(transform: (value: T) -> R): Result<R> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
return when(val exception = exceptionOrNull()) {
null -> runCatching { transform(value as T) }
else -> Result(value)
}
}
/**
* Returns the encapsulated result of the given [transform] function applied to encapsulated exception
* if this instance represents [failure][Result.isFailure] or the
* original encapsulated value if it is [success][Result.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> Result<T>.recover(transform: (exception: Throwable) -> R): Result<R> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
return when(val exception = exceptionOrNull()) {
null -> this
else -> Result.success(transform(exception))
}
}
/**
* Returns the encapsulated result of the given [transform] function applied to encapsulated exception
* if this instance represents [failure][Result.isFailure] or the
* original encapsulated value if it is [success][Result.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> Result<T>.recoverCatching(transform: (exception: Throwable) -> R): Result<R> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
val value = value // workaround for inline classes BE bug
return when(val exception = exceptionOrNull()) {
null -> this
else -> runCatching { transform(exception) }
}
}
// "peek" onto value/exception and pipe
/**
* Performs the given [action] on encapsulated value if this instance represents [success][Result.isSuccess].
* Returns the original `Result` unchanged.
*/
@InlineOnly
public inline fun <T> Result<T>.onFailure(action: (exception: Throwable) -> Unit): Result<T> {
contract {
callsInPlace(action, InvocationKind.AT_MOST_ONCE)
}
exceptionOrNull()?.let { action(it) }
return this
}
/**
* Performs the given [action] on encapsulated exception if this instance represents [failure][Result.isFailure].
* Returns the original `Result` unchanged.
*/
@InlineOnly
public inline fun <T> Result<T>.onSuccess(action: (value: T) -> Unit): Result<T> {
contract {
callsInPlace(action, InvocationKind.AT_MOST_ONCE)
}
if (isSuccess) action(value as T)
return this
}
// -------------------
@@ -7,6 +7,7 @@ package kotlin.coroutines
import kotlin.coroutines.intrinsics.*
import kotlin.internal.InlineOnly
import kotlin.jvm.JvmName
/**
* Interface representing a continuation after a suspension point that returns value of type `T`.
@@ -23,7 +24,7 @@ public interface Continuation<in T> {
* Resumes the execution of the corresponding coroutine passing successful or failed [result] as the
* return value of the last suspension point.
*/
public fun resumeWith(result: SuccessOrFailure<T>)
public fun resumeWith(result: Result<T>)
}
/**
@@ -41,7 +42,7 @@ public annotation class RestrictsSuspension
*/
@SinceKotlin("1.3")
@InlineOnly public inline fun <T> Continuation<T>.resume(value: T): Unit =
resumeWith(SuccessOrFailure.success(value))
resumeWith(Result.success(value))
/**
* Resumes the execution of the corresponding coroutine so that the [exception] is re-thrown right after the
@@ -49,7 +50,7 @@ public annotation class RestrictsSuspension
*/
@SinceKotlin("1.3")
@InlineOnly public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
resumeWith(SuccessOrFailure.failure(exception))
resumeWith(Result.failure(exception))
/**
@@ -58,13 +59,13 @@ public annotation class RestrictsSuspension
@SinceKotlin("1.3")
@InlineOnly public inline fun <T> Continuation(
context: CoroutineContext,
crossinline resumeWith: (SuccessOrFailure<T>) -> Unit
crossinline resumeWith: (Result<T>) -> Unit
): Continuation<T> =
object : Continuation<T> {
override val context: CoroutineContext
get() = context
override fun resumeWith(result: SuccessOrFailure<T>) =
override fun resumeWith(result: Result<T>) =
resumeWith(result)
}
@@ -172,7 +172,7 @@ private class SequenceBuilderIterator<T> : SequenceBuilder<T>(), Iterator<T>, Co
}
// Completion continuation implementation
override fun resumeWith(result: SuccessOrFailure<Unit>) {
override fun resumeWith(result: Result<Unit>) {
result.getOrThrow() // just rethrow exception if it is there
state = State_Done
}
@@ -45,7 +45,7 @@ class CoroutinesReferenceValuesTest {
bad.startCoroutine(object : Continuation<BadClass> {
override val context: CoroutineContext = EmptyCoroutineContext
override fun resumeWith(result_: SuccessOrFailure<BadClass>) {
override fun resumeWith(result_: Result<BadClass>) {
assertTrue(result == null)
result = result_.getOrThrow()
}