Skip to content

Commit 4ba95e0

Browse files
authored
Introduce CompletableDeferred.asDeferred that prevents downcasting (#4410)
Fixes #4408
1 parent 381b2eb commit 4ba95e0

File tree

3 files changed

+33
-0
lines changed

3 files changed

+33
-0
lines changed

kotlinx-coroutines-core/api/kotlinx-coroutines-core.api

+1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ public final class kotlinx/coroutines/CompletableDeferredKt {
124124
public static final fun CompletableDeferred (Ljava/lang/Object;)Lkotlinx/coroutines/CompletableDeferred;
125125
public static final fun CompletableDeferred (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/CompletableDeferred;
126126
public static synthetic fun CompletableDeferred$default (Lkotlinx/coroutines/Job;ILjava/lang/Object;)Lkotlinx/coroutines/CompletableDeferred;
127+
public static final fun asDeferred (Lkotlinx/coroutines/CompletableDeferred;)Lkotlinx/coroutines/Deferred;
127128
public static final fun completeWith (Lkotlinx/coroutines/CompletableDeferred;Ljava/lang/Object;)Z
128129
}
129130

kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api

+1
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,7 @@ final fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/StateFlow<#A>).kotlinx.coro
907907
final fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/StateFlow<#A>).kotlinx.coroutines.flow/onSubscription(kotlin.coroutines/SuspendFunction1<kotlinx.coroutines.flow/FlowCollector<#A>, kotlin/Unit>): kotlinx.coroutines.flow/StateFlow<#A> // kotlinx.coroutines.flow/onSubscription|[email protected]<0:0>(kotlin.coroutines.SuspendFunction1<kotlinx.coroutines.flow.FlowCollector<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
908908
final fun <#A: kotlin/Any?> (kotlinx.coroutines.selects/SelectBuilder<#A>).kotlinx.coroutines.selects/onTimeout(kotlin.time/Duration, kotlin.coroutines/SuspendFunction0<#A>) // kotlinx.coroutines.selects/onTimeout|[email protected]<0:0>(kotlin.time.Duration;kotlin.coroutines.SuspendFunction0<0:0>){0§<kotlin.Any?>}[0]
909909
final fun <#A: kotlin/Any?> (kotlinx.coroutines.selects/SelectBuilder<#A>).kotlinx.coroutines.selects/onTimeout(kotlin/Long, kotlin.coroutines/SuspendFunction0<#A>) // kotlinx.coroutines.selects/onTimeout|[email protected]<0:0>(kotlin.Long;kotlin.coroutines.SuspendFunction0<0:0>){0§<kotlin.Any?>}[0]
910+
final fun <#A: kotlin/Any?> (kotlinx.coroutines/CompletableDeferred<#A>).kotlinx.coroutines/asDeferred(): kotlinx.coroutines/Deferred<#A> // kotlinx.coroutines/asDeferred|[email protected]<0:0>(){0§<kotlin.Any?>}[0]
910911
final fun <#A: kotlin/Any?> (kotlinx.coroutines/CompletableDeferred<#A>).kotlinx.coroutines/completeWith(kotlin/Result<#A>): kotlin/Boolean // kotlinx.coroutines/completeWith|[email protected]<0:0>(kotlin.Result<0:0>){0§<kotlin.Any?>}[0]
911912
final fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines.channels/broadcast(kotlin.coroutines/CoroutineContext = ..., kotlin/Int = ..., kotlinx.coroutines/CoroutineStart = ..., kotlin/Function1<kotlin/Throwable?, kotlin/Unit>? = ..., kotlin.coroutines/SuspendFunction1<kotlinx.coroutines.channels/ProducerScope<#A>, kotlin/Unit>): kotlinx.coroutines.channels/BroadcastChannel<#A> // kotlinx.coroutines.channels/broadcast|[email protected](kotlin.coroutines.CoroutineContext;kotlin.Int;kotlinx.coroutines.CoroutineStart;kotlin.Function1<kotlin.Throwable?,kotlin.Unit>?;kotlin.coroutines.SuspendFunction1<kotlinx.coroutines.channels.ProducerScope<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
912913
final fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines.channels/produce(kotlin.coroutines/CoroutineContext = ..., kotlin/Int = ..., kotlin.coroutines/SuspendFunction1<kotlinx.coroutines.channels/ProducerScope<#A>, kotlin/Unit>): kotlinx.coroutines.channels/ReceiveChannel<#A> // kotlinx.coroutines.channels/produce|[email protected](kotlin.coroutines.CoroutineContext;kotlin.Int;kotlin.coroutines.SuspendFunction1<kotlinx.coroutines.channels.ProducerScope<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]

kotlinx-coroutines-core/common/src/CompletableDeferred.kt

+31
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,37 @@ public fun <T> CompletableDeferred(parent: Job? = null): CompletableDeferred<T>
6969
@Suppress("FunctionName")
7070
public fun <T> CompletableDeferred(value: T): CompletableDeferred<T> = CompletableDeferredImpl<T>(null).apply { complete(value) }
7171

72+
/**
73+
* Creates a view of this [CompletableDeferred] as a [Deferred], which prevents downcasting to a completable version.
74+
*
75+
* ```
76+
* class MyClass(val scope: CoroutineScope) {
77+
* // can be completed
78+
* private val actualDeferred: CompletableDeferred<String> = CompletableDeferred()
79+
*
80+
* // can not be completed from outside
81+
* public val operationCompleted: Deferred<String> = actualDeferred.asDeferred()
82+
*
83+
* fun startOperation() = scope.launch {
84+
* // do some work
85+
* delay(2.seconds)
86+
* actualDeferred.complete("Done")
87+
* }
88+
* }
89+
*
90+
* // (myClass.operationCompleted as CompletableDeferred<*>) will fail
91+
* ```
92+
*/
93+
@ExperimentalCoroutinesApi
94+
public fun <T> CompletableDeferred<T>.asDeferred(): Deferred<T> = ReadonlyDeferred(this)
95+
96+
@OptIn(InternalForInheritanceCoroutinesApi::class)
97+
private class ReadonlyDeferred<T>(
98+
val deferred: CompletableDeferred<T>,
99+
) : Deferred<T> by deferred {
100+
override fun toString(): String = "ReadonlyDeferred($deferred)"
101+
}
102+
72103
/**
73104
* Concrete implementation of [CompletableDeferred].
74105
*/

0 commit comments

Comments
 (0)