Skip to content

Commit 61ba10d

Browse files
authored
Introduce non-nullable types in reactive integrations where appropriate (#3393)
* Introduce non-nullable types in reactive integrations where appropriate * For RxJava2, use them in internal implementations where appropriate * For RxJava3, introduce & Any bound to a generic argument in our extensions to avoid errors in Kotlin 1.8.0 due to non-nullability rx3 annotations being part of generics upper bound. This change went through committee, and all the "unsound" declarations such as "RxSignature<Foo?>" were properly highlighted as a warning that would become an error.
1 parent 7f557e9 commit 61ba10d

File tree

11 files changed

+83
-57
lines changed

11 files changed

+83
-57
lines changed

build.gradle

-2
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,6 @@ configure(subprojects.findAll { !sourceless.contains(it.name) }) {
176176
tasks.withType(AbstractKotlinCompile).all {
177177
kotlinOptions.freeCompilerArgs += OptInPreset.optInAnnotations.collect { "-Xopt-in=" + it }
178178
kotlinOptions.freeCompilerArgs += "-progressive"
179-
// Disable KT-36770 for RxJava2 integration
180-
kotlinOptions.freeCompilerArgs += "-XXLanguage:-ProhibitUsingNullableTypeParameterAgainstNotNullAnnotated"
181179
// Remove null assertions to get smaller bytecode on Android
182180
kotlinOptions.freeCompilerArgs += ["-Xno-param-assertions", "-Xno-receiver-assertions", "-Xno-call-assertions"]
183181
}

reactive/kotlinx-coroutines-rx2/src/RxAwait.kt

+38-12
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,17 @@ import kotlin.coroutines.*
2424
*/
2525
public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine { cont ->
2626
subscribe(object : CompletableObserver {
27-
override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) }
28-
override fun onComplete() { cont.resume(Unit) }
29-
override fun onError(e: Throwable) { cont.resumeWithException(e) }
27+
override fun onSubscribe(d: Disposable) {
28+
cont.disposeOnCancellation(d)
29+
}
30+
31+
override fun onComplete() {
32+
cont.resume(Unit)
33+
}
34+
35+
override fun onError(e: Throwable) {
36+
cont.resumeWithException(e)
37+
}
3038
})
3139
}
3240

@@ -41,13 +49,23 @@ public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine
4149
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this
4250
* function immediately resumes with [CancellationException] and disposes of its subscription.
4351
*/
44-
@Suppress("UNCHECKED_CAST")
4552
public suspend fun <T> MaybeSource<T>.awaitSingleOrNull(): T? = suspendCancellableCoroutine { cont ->
4653
subscribe(object : MaybeObserver<T> {
47-
override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) }
48-
override fun onComplete() { cont.resume(null) }
49-
override fun onSuccess(t: T) { cont.resume(t) }
50-
override fun onError(error: Throwable) { cont.resumeWithException(error) }
54+
override fun onSubscribe(d: Disposable) {
55+
cont.disposeOnCancellation(d)
56+
}
57+
58+
override fun onComplete() {
59+
cont.resume(null)
60+
}
61+
62+
override fun onSuccess(t: T & Any) {
63+
cont.resume(t)
64+
}
65+
66+
override fun onError(error: Throwable) {
67+
cont.resumeWithException(error)
68+
}
5169
})
5270
}
5371

@@ -119,9 +137,17 @@ public suspend fun <T> MaybeSource<T>.awaitOrDefault(default: T): T = awaitSingl
119137
*/
120138
public suspend fun <T> SingleSource<T>.await(): T = suspendCancellableCoroutine { cont ->
121139
subscribe(object : SingleObserver<T> {
122-
override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) }
123-
override fun onSuccess(t: T) { cont.resume(t) }
124-
override fun onError(error: Throwable) { cont.resumeWithException(error) }
140+
override fun onSubscribe(d: Disposable) {
141+
cont.disposeOnCancellation(d)
142+
}
143+
144+
override fun onSuccess(t: T & Any) {
145+
cont.resume(t)
146+
}
147+
148+
override fun onError(error: Throwable) {
149+
cont.resumeWithException(error)
150+
}
125151
})
126152
}
127153

@@ -225,7 +251,7 @@ private suspend fun <T> ObservableSource<T>.awaitOne(
225251
cont.invokeOnCancellation { sub.dispose() }
226252
}
227253

228-
override fun onNext(t: T) {
254+
override fun onNext(t: T & Any) {
229255
when (mode) {
230256
Mode.FIRST, Mode.FIRST_OR_DEFAULT -> {
231257
if (!seenValue) {

reactive/kotlinx-coroutines-rx2/src/RxChannel.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ private class SubscriptionChannel<T> :
6060
_subscription.value = sub
6161
}
6262

63-
override fun onSuccess(t: T) {
63+
override fun onSuccess(t: T & Any) {
6464
trySend(t)
6565
close(cause = null)
6666
}
6767

68-
override fun onNext(t: T) {
68+
override fun onNext(t: T & Any) {
6969
trySend(t) // Safe to ignore return value here, expectedly racing with cancellation
7070
}
7171

@@ -80,15 +80,15 @@ private class SubscriptionChannel<T> :
8080

8181
/** @suppress */
8282
@Deprecated(message = "Deprecated in the favour of Flow", level = DeprecationLevel.HIDDEN) // ERROR in 1.4.0, HIDDEN in 1.6.0
83-
public fun <T> ObservableSource<T>.openSubscription(): ReceiveChannel<T> {
83+
public fun <T> ObservableSource<T & Any>.openSubscription(): ReceiveChannel<T> {
8484
val channel = SubscriptionChannel<T>()
8585
subscribe(channel)
8686
return channel
8787
}
8888

8989
/** @suppress */
9090
@Deprecated(message = "Deprecated in the favour of Flow", level = DeprecationLevel.HIDDEN) // ERROR in 1.4.0, HIDDEN in 1.6.0
91-
public fun <T> MaybeSource<T>.openSubscription(): ReceiveChannel<T> {
91+
public fun <T> MaybeSource<T & Any>.openSubscription(): ReceiveChannel<T> {
9292
val channel = SubscriptionChannel<T>()
9393
subscribe(channel)
9494
return channel

reactive/kotlinx-coroutines-rx2/src/RxObservable.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ private class RxObservableCoroutine<T : Any>(
7777
processResFunc = RxObservableCoroutine<*>::processResultSelectSend as ProcessResultFunction
7878
)
7979

80-
@Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER")
80+
@Suppress("UNUSED_PARAMETER")
8181
private fun registerSelectForSend(select: SelectInstance<*>, element: Any?) {
8282
// Try to acquire the mutex and complete in the registration phase.
8383
if (mutex.tryLock()) {
@@ -113,7 +113,7 @@ private class RxObservableCoroutine<T : Any>(
113113
}
114114
}
115115

116-
public override suspend fun send(element: T) {
116+
override suspend fun send(element: T) {
117117
mutex.lock()
118118
doLockedNext(element)?.let { throw it }
119119
}

reactive/kotlinx-coroutines-rx3/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ compileKotlin {
2323
tasks.withType(DokkaTaskPartial.class) {
2424
dokkaSourceSets.configureEach {
2525
externalDocumentationLink {
26-
url.set(new URL('http://reactivex.io/RxJava/3.x/javadoc/'))
26+
url.set(new URL('https://reactivex.io/RxJava/3.x/javadoc/'))
2727
packageListUrl.set(projectDir.toPath().resolve("package.list").toUri().toURL())
2828
}
2929
}

reactive/kotlinx-coroutines-rx3/src/RxAwait.kt

+25-21
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,11 @@ public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine
4141
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this
4242
* function immediately resumes with [CancellationException] and disposes of its subscription.
4343
*/
44-
@Suppress("UNCHECKED_CAST")
45-
public suspend fun <T> MaybeSource<T>.awaitSingleOrNull(): T? = suspendCancellableCoroutine { cont ->
46-
subscribe(object : MaybeObserver<T> {
44+
public suspend fun <T> MaybeSource<T & Any>.awaitSingleOrNull(): T? = suspendCancellableCoroutine { cont ->
45+
subscribe(object : MaybeObserver<T & Any> {
4746
override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) }
4847
override fun onComplete() { cont.resume(null) }
49-
override fun onSuccess(t: T) { cont.resume(t) }
48+
override fun onSuccess(t: T & Any) { cont.resume(t) }
5049
override fun onError(error: Throwable) { cont.resumeWithException(error) }
5150
})
5251
}
@@ -61,7 +60,7 @@ public suspend fun <T> MaybeSource<T>.awaitSingleOrNull(): T? = suspendCancellab
6160
*
6261
* @throws NoSuchElementException if no elements were produced by this [MaybeSource].
6362
*/
64-
public suspend fun <T> MaybeSource<T>.awaitSingle(): T = awaitSingleOrNull() ?: throw NoSuchElementException()
63+
public suspend fun <T> MaybeSource<T & Any>.awaitSingle(): T = awaitSingleOrNull() ?: throw NoSuchElementException()
6564

6665
/**
6766
* Awaits for completion of the maybe without blocking a thread.
@@ -84,7 +83,7 @@ public suspend fun <T> MaybeSource<T>.awaitSingle(): T = awaitSingleOrNull() ?:
8483
level = DeprecationLevel.ERROR,
8584
replaceWith = ReplaceWith("this.awaitSingleOrNull()")
8685
) // Warning since 1.5, error in 1.6, hidden in 1.7
87-
public suspend fun <T> MaybeSource<T>.await(): T? = awaitSingleOrNull()
86+
public suspend fun <T> MaybeSource<T & Any>.await(): T? = awaitSingleOrNull()
8887

8988
/**
9089
* Awaits for completion of the maybe without blocking a thread.
@@ -107,7 +106,7 @@ public suspend fun <T> MaybeSource<T>.await(): T? = awaitSingleOrNull()
107106
level = DeprecationLevel.ERROR,
108107
replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: default")
109108
) // Warning since 1.5, error in 1.6, hidden in 1.7
110-
public suspend fun <T> MaybeSource<T>.awaitOrDefault(default: T): T = awaitSingleOrNull() ?: default
109+
public suspend fun <T> MaybeSource<T & Any>.awaitOrDefault(default: T): T = awaitSingleOrNull() ?: default
111110

112111
// ------------------------ SingleSource ------------------------
113112

@@ -119,10 +118,10 @@ public suspend fun <T> MaybeSource<T>.awaitOrDefault(default: T): T = awaitSingl
119118
* If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
120119
* function immediately disposes of its subscription and resumes with [CancellationException].
121120
*/
122-
public suspend fun <T> SingleSource<T>.await(): T = suspendCancellableCoroutine { cont ->
123-
subscribe(object : SingleObserver<T> {
121+
public suspend fun <T> SingleSource<T & Any>.await(): T = suspendCancellableCoroutine { cont ->
122+
subscribe(object : SingleObserver<T & Any> {
124123
override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) }
125-
override fun onSuccess(t: T) { cont.resume(t) }
124+
override fun onSuccess(t: T & Any) { cont.resume(t) }
126125
override fun onError(error: Throwable) { cont.resumeWithException(error) }
127126
})
128127
}
@@ -139,7 +138,8 @@ public suspend fun <T> SingleSource<T>.await(): T = suspendCancellableCoroutine
139138
*
140139
* @throws NoSuchElementException if the observable does not emit any value
141140
*/
142-
public suspend fun <T> ObservableSource<T>.awaitFirst(): T = awaitOne(Mode.FIRST)
141+
@Suppress("UNCHECKED_CAST")
142+
public suspend fun <T> ObservableSource<T & Any>.awaitFirst(): T = awaitOne(Mode.FIRST) as T
143143

144144
/**
145145
* Awaits the first value from the given [Observable], or returns the [default] value if none is emitted, without
@@ -150,7 +150,9 @@ public suspend fun <T> ObservableSource<T>.awaitFirst(): T = awaitOne(Mode.FIRST
150150
* If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
151151
* function immediately disposes of its subscription and resumes with [CancellationException].
152152
*/
153-
public suspend fun <T> ObservableSource<T>.awaitFirstOrDefault(default: T): T = awaitOne(Mode.FIRST_OR_DEFAULT, default)
153+
@Suppress("UNCHECKED_CAST")
154+
public suspend fun <T> ObservableSource<T & Any>.awaitFirstOrDefault(default: T): T =
155+
awaitOne(Mode.FIRST_OR_DEFAULT, default) as T
154156

155157
/**
156158
* Awaits the first value from the given [Observable], or returns `null` if none is emitted, without blocking the
@@ -161,7 +163,7 @@ public suspend fun <T> ObservableSource<T>.awaitFirstOrDefault(default: T): T =
161163
* If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
162164
* function immediately disposes of its subscription and resumes with [CancellationException].
163165
*/
164-
public suspend fun <T> ObservableSource<T>.awaitFirstOrNull(): T? = awaitOne(Mode.FIRST_OR_DEFAULT)
166+
public suspend fun <T> ObservableSource<T & Any>.awaitFirstOrNull(): T? = awaitOne(Mode.FIRST_OR_DEFAULT)
165167

166168
/**
167169
* Awaits the first value from the given [Observable], or calls [defaultValue] to get a value if none is emitted,
@@ -172,7 +174,7 @@ public suspend fun <T> ObservableSource<T>.awaitFirstOrNull(): T? = awaitOne(Mod
172174
* If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
173175
* function immediately disposes of its subscription and resumes with [CancellationException].
174176
*/
175-
public suspend fun <T> ObservableSource<T>.awaitFirstOrElse(defaultValue: () -> T): T =
177+
public suspend fun <T> ObservableSource<T & Any>.awaitFirstOrElse(defaultValue: () -> T): T =
176178
awaitOne(Mode.FIRST_OR_DEFAULT) ?: defaultValue()
177179

178180
/**
@@ -185,7 +187,8 @@ public suspend fun <T> ObservableSource<T>.awaitFirstOrElse(defaultValue: () ->
185187
*
186188
* @throws NoSuchElementException if the observable does not emit any value
187189
*/
188-
public suspend fun <T> ObservableSource<T>.awaitLast(): T = awaitOne(Mode.LAST)
190+
@Suppress("UNCHECKED_CAST")
191+
public suspend fun <T> ObservableSource<T & Any>.awaitLast(): T = awaitOne(Mode.LAST) as T
189192

190193
/**
191194
* Awaits the single value from the given observable without blocking the thread and returns the resulting value, or,
@@ -198,26 +201,27 @@ public suspend fun <T> ObservableSource<T>.awaitLast(): T = awaitOne(Mode.LAST)
198201
* @throws NoSuchElementException if the observable does not emit any value
199202
* @throws IllegalArgumentException if the observable emits more than one value
200203
*/
201-
public suspend fun <T> ObservableSource<T>.awaitSingle(): T = awaitOne(Mode.SINGLE)
204+
@Suppress("UNCHECKED_CAST")
205+
public suspend fun <T> ObservableSource<T & Any>.awaitSingle(): T = awaitOne(Mode.SINGLE) as T
202206

203207
// ------------------------ private ------------------------
204208

205209
internal fun CancellableContinuation<*>.disposeOnCancellation(d: Disposable) =
206210
invokeOnCancellation { d.dispose() }
207211

208-
private enum class Mode(val s: String) {
212+
private enum class Mode(@JvmField val s: String) {
209213
FIRST("awaitFirst"),
210214
FIRST_OR_DEFAULT("awaitFirstOrDefault"),
211215
LAST("awaitLast"),
212216
SINGLE("awaitSingle");
213217
override fun toString(): String = s
214218
}
215219

216-
private suspend fun <T> ObservableSource<T>.awaitOne(
220+
private suspend fun <T> ObservableSource<T & Any>.awaitOne(
217221
mode: Mode,
218222
default: T? = null
219-
): T = suspendCancellableCoroutine { cont ->
220-
subscribe(object : Observer<T> {
223+
): T? = suspendCancellableCoroutine { cont ->
224+
subscribe(object : Observer<T & Any> {
221225
private lateinit var subscription: Disposable
222226
private var value: T? = null
223227
private var seenValue = false
@@ -227,7 +231,7 @@ private suspend fun <T> ObservableSource<T>.awaitOne(
227231
cont.invokeOnCancellation { sub.dispose() }
228232
}
229233

230-
override fun onNext(t: T) {
234+
override fun onNext(t: T & Any) {
231235
when (mode) {
232236
Mode.FIRST, Mode.FIRST_OR_DEFAULT -> {
233237
if (!seenValue) {

reactive/kotlinx-coroutines-rx3/src/RxChannel.kt

+7-8
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import kotlinx.coroutines.flow.*
1919
* [MaybeSource] doesn't have a corresponding [Flow] adapter, so it should be transformed to [Observable] first.
2020
*/
2121
@PublishedApi
22-
internal fun <T> MaybeSource<T>.openSubscription(): ReceiveChannel<T> {
22+
internal fun <T> MaybeSource<T & Any>.openSubscription(): ReceiveChannel<T> {
2323
val channel = SubscriptionChannel<T>()
2424
subscribe(channel)
2525
return channel
@@ -33,7 +33,7 @@ internal fun <T> MaybeSource<T>.openSubscription(): ReceiveChannel<T> {
3333
* [ObservableSource] doesn't have a corresponding [Flow] adapter, so it should be transformed to [Observable] first.
3434
*/
3535
@PublishedApi
36-
internal fun <T> ObservableSource<T>.openSubscription(): ReceiveChannel<T> {
36+
internal fun <T> ObservableSource<T & Any>.openSubscription(): ReceiveChannel<T> {
3737
val channel = SubscriptionChannel<T>()
3838
subscribe(channel)
3939
return channel
@@ -45,7 +45,7 @@ internal fun <T> ObservableSource<T>.openSubscription(): ReceiveChannel<T> {
4545
* If [action] throws an exception at some point or if the [MaybeSource] raises an error, the exception is rethrown from
4646
* [collect].
4747
*/
48-
public suspend inline fun <T> MaybeSource<T>.collect(action: (T) -> Unit): Unit =
48+
public suspend inline fun <T> MaybeSource<T & Any>.collect(action: (T) -> Unit): Unit =
4949
openSubscription().consumeEach(action)
5050

5151
/**
@@ -54,12 +54,11 @@ public suspend inline fun <T> MaybeSource<T>.collect(action: (T) -> Unit): Unit
5454
* If [action] throws an exception at some point, the subscription is cancelled, and the exception is rethrown from
5555
* [collect]. Also, if the [ObservableSource] signals an error, that error is rethrown from [collect].
5656
*/
57-
public suspend inline fun <T> ObservableSource<T>.collect(action: (T) -> Unit): Unit =
58-
openSubscription().consumeEach(action)
57+
public suspend inline fun <T> ObservableSource<T & Any>.collect(action: (T) -> Unit): Unit = openSubscription().consumeEach(action)
5958

6059
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
6160
private class SubscriptionChannel<T> :
62-
LinkedListChannel<T>(null), Observer<T>, MaybeObserver<T>
61+
LinkedListChannel<T>(null), Observer<T & Any>, MaybeObserver<T & Any>
6362
{
6463
private val _subscription = atomic<Disposable?>(null)
6564

@@ -73,12 +72,12 @@ private class SubscriptionChannel<T> :
7372
_subscription.value = sub
7473
}
7574

76-
override fun onSuccess(t: T) {
75+
override fun onSuccess(t: T & Any) {
7776
trySend(t)
7877
close(cause = null)
7978
}
8079

81-
override fun onNext(t: T) {
80+
override fun onNext(t: T & Any) {
8281
trySend(t) // Safe to ignore return value here, expectedly racing with cancellation
8382
}
8483

reactive/kotlinx-coroutines-rx3/src/RxConvert.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public fun Job.asCompletable(context: CoroutineContext): Completable = rxComplet
4242
*
4343
* @param context -- the coroutine context from which the resulting maybe is going to be signalled
4444
*/
45-
public fun <T> Deferred<T?>.asMaybe(context: CoroutineContext): Maybe<T> = rxMaybe(context) {
45+
public fun <T> Deferred<T?>.asMaybe(context: CoroutineContext): Maybe<T & Any> = rxMaybe(context) {
4646
this@asMaybe.await()
4747
}
4848

reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import kotlin.coroutines.*
2020
public fun <T> rxMaybe(
2121
context: CoroutineContext = EmptyCoroutineContext,
2222
block: suspend CoroutineScope.() -> T?
23-
): Maybe<T> {
23+
): Maybe<T & Any> {
2424
require(context[Job] === null) { "Maybe context cannot contain job in it." +
2525
"Its lifecycle should be managed via Disposable handle. Had $context" }
2626
return rxMaybeInternal(GlobalScope, context, block)
@@ -30,7 +30,7 @@ private fun <T> rxMaybeInternal(
3030
scope: CoroutineScope, // support for legacy rxMaybe in scope
3131
context: CoroutineContext,
3232
block: suspend CoroutineScope.() -> T?
33-
): Maybe<T> = Maybe.create { subscriber ->
33+
): Maybe<T & Any> = Maybe.create { subscriber ->
3434
val newContext = scope.newCoroutineContext(context)
3535
val coroutine = RxMaybeCoroutine(newContext, subscriber)
3636
subscriber.setCancellable(RxCancellable(coroutine))
@@ -39,7 +39,7 @@ private fun <T> rxMaybeInternal(
3939

4040
private class RxMaybeCoroutine<T>(
4141
parentContext: CoroutineContext,
42-
private val subscriber: MaybeEmitter<T>
42+
private val subscriber: MaybeEmitter<T & Any>
4343
) : AbstractCoroutine<T>(parentContext, false, true) {
4444
override fun onCompleted(value: T) {
4545
try {

0 commit comments

Comments
 (0)