Skip to content

Commit cfde2d1

Browse files
committed
Cleaner binary compatibility solution
1 parent eeeb99c commit cfde2d1

File tree

4 files changed

+90
-95
lines changed

4 files changed

+90
-95
lines changed

Diff for: kotlinx-coroutines-core/api/kotlinx-coroutines-core.api

-5
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,6 @@ public abstract interface class kotlinx/coroutines/CopyableThrowable {
160160
}
161161

162162
public final class kotlinx/coroutines/CoroutineContextKt {
163-
public static final synthetic fun newCoroutineContext (Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
164-
public static final synthetic fun newCoroutineContext (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
165-
}
166-
167-
public final class kotlinx/coroutines/CoroutineContext_commonKt {
168163
public static final fun newCoroutineContext (Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
169164
public static final fun newCoroutineContext (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
170165
}

Diff for: kotlinx-coroutines-core/common/src/CoroutineContext.common.kt

-78
Original file line numberDiff line numberDiff line change
@@ -83,81 +83,3 @@ internal object UndispatchedMarker: CoroutineContext.Element, CoroutineContext.K
8383
override val key: CoroutineContext.Key<*>
8484
get() = this
8585
}
86-
87-
/**
88-
* Creates a context for a new coroutine. It installs [Dispatchers.Default] when no other dispatcher or
89-
* [ContinuationInterceptor] is specified and
90-
*/
91-
@ExperimentalCoroutinesApi
92-
public fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
93-
val combined = foldCopies(coroutineContext, context, true)
94-
val debug = wrapContextWithDebug(combined)
95-
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
96-
debug + Dispatchers.Default else debug
97-
}
98-
99-
/**
100-
* Creates a context for coroutine builder functions that do not launch a new coroutine, e.g. [withContext].
101-
* @suppress
102-
*/
103-
@InternalCoroutinesApi
104-
public fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext {
105-
/*
106-
* Fast-path: we only have to copy/merge if 'addedContext' (which typically has one or two elements)
107-
* contains copyable elements.
108-
*/
109-
if (!addedContext.hasCopyableElements()) return this + addedContext
110-
return foldCopies(this, addedContext, false)
111-
}
112-
113-
private fun CoroutineContext.hasCopyableElements(): Boolean =
114-
fold(false) { result, it -> result || it is CopyableThreadContextElement<*> }
115-
116-
/**
117-
* Folds two contexts properly applying [CopyableThreadContextElement] rules when necessary.
118-
* The rules are the following:
119-
* - If neither context has CTCE, the sum of two contexts is returned
120-
* - Every CTCE from the left-hand side context that does not have a matching (by key) element from right-hand side context
121-
* is [copied][CopyableThreadContextElement.copyForChild] if [isNewCoroutine] is `true`.
122-
* - Every CTCE from the left-hand side context that has a matching element in the right-hand side context is [merged][CopyableThreadContextElement.mergeForChild]
123-
* - Every CTCE from the right-hand side context that hasn't been merged is copied
124-
* - Everything else is added to the resulting context as is.
125-
*/
126-
private fun foldCopies(originalContext: CoroutineContext, appendContext: CoroutineContext, isNewCoroutine: Boolean): CoroutineContext {
127-
// Do we have something to copy left-hand side?
128-
val hasElementsLeft = originalContext.hasCopyableElements()
129-
val hasElementsRight = appendContext.hasCopyableElements()
130-
131-
// Nothing to fold, so just return the sum of contexts
132-
if (!hasElementsLeft && !hasElementsRight) {
133-
return originalContext + appendContext
134-
}
135-
136-
var leftoverContext = appendContext
137-
val folded = originalContext.fold<CoroutineContext>(EmptyCoroutineContext) { result, element ->
138-
if (element !is CopyableThreadContextElement<*>) return@fold result + element
139-
// Will this element be overwritten?
140-
val newElement = leftoverContext[element.key]
141-
// No, just copy it
142-
if (newElement == null) {
143-
// For 'withContext'-like builders we do not copy as the element is not shared
144-
return@fold result + if (isNewCoroutine) element.copyForChild() else element
145-
}
146-
// Yes, then first remove the element from append context
147-
leftoverContext = leftoverContext.minusKey(element.key)
148-
// Return the sum
149-
@Suppress("UNCHECKED_CAST")
150-
return@fold result + (element as CopyableThreadContextElement<Any?>).mergeForChild(newElement)
151-
}
152-
153-
if (hasElementsRight) {
154-
leftoverContext = leftoverContext.fold<CoroutineContext>(EmptyCoroutineContext) { result, element ->
155-
// We're appending new context element -- we have to copy it, otherwise it may be shared with others
156-
if (element is CopyableThreadContextElement<*>) {
157-
return@fold result + element.copyForChild()
158-
}
159-
return@fold result + element
160-
}
161-
}
162-
return folded + leftoverContext
163-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// This file should be a part of `CoroutineContext.common.kt`, but adding `JvmName` to that fails: KT-75248
2+
@file:JvmName("CoroutineContextKt")
3+
@file:JvmMultifileClass
4+
package kotlinx.coroutines
5+
6+
import kotlin.coroutines.ContinuationInterceptor
7+
import kotlin.coroutines.CoroutineContext
8+
import kotlin.coroutines.EmptyCoroutineContext
9+
import kotlin.jvm.JvmMultifileClass
10+
import kotlin.jvm.JvmName
11+
12+
/**
13+
* Creates a context for a new coroutine. It installs [Dispatchers.Default] when no other dispatcher or
14+
* [ContinuationInterceptor] is specified and
15+
*/
16+
@ExperimentalCoroutinesApi
17+
public fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
18+
val combined = foldCopies(coroutineContext, context, true)
19+
val debug = wrapContextWithDebug(combined)
20+
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
21+
debug + Dispatchers.Default else debug
22+
}
23+
24+
/**
25+
* Creates a context for coroutine builder functions that do not launch a new coroutine, e.g. [withContext].
26+
* @suppress
27+
*/
28+
@InternalCoroutinesApi
29+
public fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext {
30+
/*
31+
* Fast-path: we only have to copy/merge if 'addedContext' (which typically has one or two elements)
32+
* contains copyable elements.
33+
*/
34+
if (!addedContext.hasCopyableElements()) return this + addedContext
35+
return foldCopies(this, addedContext, false)
36+
}
37+
38+
private fun CoroutineContext.hasCopyableElements(): Boolean =
39+
fold(false) { result, it -> result || it is CopyableThreadContextElement<*> }
40+
41+
/**
42+
* Folds two contexts properly applying [CopyableThreadContextElement] rules when necessary.
43+
* The rules are the following:
44+
* - If neither context has CTCE, the sum of two contexts is returned
45+
* - Every CTCE from the left-hand side context that does not have a matching (by key) element from right-hand side context
46+
* is [copied][CopyableThreadContextElement.copyForChild] if [isNewCoroutine] is `true`.
47+
* - Every CTCE from the left-hand side context that has a matching element in the right-hand side context is [merged][CopyableThreadContextElement.mergeForChild]
48+
* - Every CTCE from the right-hand side context that hasn't been merged is copied
49+
* - Everything else is added to the resulting context as is.
50+
*/
51+
private fun foldCopies(originalContext: CoroutineContext, appendContext: CoroutineContext, isNewCoroutine: Boolean): CoroutineContext {
52+
// Do we have something to copy left-hand side?
53+
val hasElementsLeft = originalContext.hasCopyableElements()
54+
val hasElementsRight = appendContext.hasCopyableElements()
55+
56+
// Nothing to fold, so just return the sum of contexts
57+
if (!hasElementsLeft && !hasElementsRight) {
58+
return originalContext + appendContext
59+
}
60+
61+
var leftoverContext = appendContext
62+
val folded = originalContext.fold<CoroutineContext>(EmptyCoroutineContext) { result, element ->
63+
if (element !is CopyableThreadContextElement<*>) return@fold result + element
64+
// Will this element be overwritten?
65+
val newElement = leftoverContext[element.key]
66+
// No, just copy it
67+
if (newElement == null) {
68+
// For 'withContext'-like builders we do not copy as the element is not shared
69+
return@fold result + if (isNewCoroutine) element.copyForChild() else element
70+
}
71+
// Yes, then first remove the element from append context
72+
leftoverContext = leftoverContext.minusKey(element.key)
73+
// Return the sum
74+
@Suppress("UNCHECKED_CAST")
75+
return@fold result + (element as CopyableThreadContextElement<Any?>).mergeForChild(newElement)
76+
}
77+
78+
if (hasElementsRight) {
79+
leftoverContext = leftoverContext.fold<CoroutineContext>(EmptyCoroutineContext) { result, element ->
80+
// We're appending new context element -- we have to copy it, otherwise it may be shared with others
81+
if (element is CopyableThreadContextElement<*>) {
82+
return@fold result + element.copyForChild()
83+
}
84+
return@fold result + element
85+
}
86+
}
87+
return folded + leftoverContext
88+
}

Diff for: kotlinx-coroutines-core/jvm/src/CoroutineContext.kt

+2-12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@file:JvmName("CoroutineContextKt")
2+
@file:JvmMultifileClass
13
package kotlinx.coroutines
24

35
import kotlinx.coroutines.internal.*
@@ -50,15 +52,3 @@ internal data class CoroutineId(
5052
Thread.currentThread().name = oldState
5153
}
5254
}
53-
54-
@Deprecated(level = DeprecationLevel.HIDDEN, message = "Binary compatibility with 1.10.0 and earlier, was experimental")
55-
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
56-
@kotlin.internal.LowPriorityInOverloadResolution
57-
public fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext =
58-
newCoroutineContext(context)
59-
60-
@Deprecated(level = DeprecationLevel.HIDDEN, message = "Binary compatibility with 1.10.0 and earlier, was internal")
61-
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
62-
@kotlin.internal.LowPriorityInOverloadResolution
63-
public fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext =
64-
newCoroutineContext(addedContext)

0 commit comments

Comments
 (0)