-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce SubclassOptInRequired to the codebase #4115
Conversation
Postponed until Kotlin 2.0 because this feature doesn't work as intended now |
b511268
to
4fb023d
Compare
No idea why the |
Possibly a fluke. After a restart, the tests passed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like we have a mix of concepts here.
We have SubclassOptInRequired
that is "Hey, we are not sure if we won't add new methods/change the contract. Be wary!" (examples: SharedFlow
, maybe Deferred
), and there are "These were never meant to be extended, but they technically might be because of the language restrictions or our omission" (Job
, CancellableContinuation
, ChannelFlow
).
It feels off to have both covered with Brittle*
with WARNING
level.
For the later, I suggest using SubclassOptInRequired(InternalCoroutinesApi::class)
, for the former -- keep it as is, but maybe rename it to something more straightforward -- ExperimentalForInheritanceCoroutinesApi
(or UnstableFor*
).
@@ -13,6 +13,8 @@ import kotlin.coroutines.* | |||
* This class is generally used as a bridge between coroutine-based API and | |||
* asynchronous API that requires an instance of the [Executor]. | |||
*/ | |||
@OptIn(ExperimentalSubclassOptIn::class) | |||
@SubclassOptInRequired(BrittleForInheritanceCoroutinesApi::class) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a stable class with no mentions of unstable inheritance in the doc. Should we really mark it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With CloseableCoroutineDispatcher
being its typealias, I see no way around it.
@@ -173,6 +173,8 @@ import kotlin.coroutines.* | |||
* These implementations ensure that the context preservation property is not violated, and prevent most | |||
* of the developer mistakes related to concurrency, inconsistent flow dispatchers, and cancellation. | |||
*/ | |||
@OptIn(ExperimentalSubclassOptIn::class) | |||
@SubclassOptInRequired(BrittleForInheritanceCoroutinesApi::class) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Frankly, taking into account that Flow
is the way to program and that it has been here for a long time, I doubt we ever can change that, and this warning might be just a headache to opt-in. I suggest we might give up on that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
People who want custom Flow
implementation should use AbstractFlow
instead, so I think it's okay to discourage inheriting from Flow
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still argue that Flow
is neither brittle nor experimental.
In my mental model, it's either Delicate
(because of the invariants that must be preserved) or nothing (because it's quite hard to rationalize it otherwise). WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my mental model, it's certainly brittle, because it's a mistake to try to implement it without using AbstractFlow
. It's not Delicate
because we don't give realistic means of fulfilling the contracts in custom Flow
instances that don't inherit from AbstractFlow
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
by
-delegation to Flow
does happen occasionally (https://grep.app/search?q=%20%3A%20Flow%3C%5B%5E%3E%5D%2B%3E%20by®exp=true), and the following code does emit a warning:
public class MyFlow: Flow<Int> by flowOf(1, 2, 3) {
}
So, Flow
probably shouldn't be marked as InternalForInheritance
, at least, as we may want to make it difficult to opt in to that eventually, but this benign pattern should work without issues.
4fb023d
to
f0f1c2a
Compare
Let's look at it in a case-by-case basis:
To follow the spirit of your suggestion, I introduced another annotation that strongly states that this shouldn't be inherited from and applied it to |
@RequiresOptIn( | ||
level = RequiresOptIn.Level.WARNING, message = | ||
"This is a kotlinx.coroutines API that is not intended to be inherited from, " + | ||
"as the library may handle predefined instances of this in a special manner." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest keeping it consistent: InternalForInheritance
(or InternalForImplementation
) and adding the corresponding disclaimer to the message: No compatibility guarantees are provided. It is recommended to report your use-case of internal API to kotlinx.coroutines issue tracker, so stable API could be provided instead
"Either new methods may be added in the future, which would break the inheritance, " + | ||
"or correctly inheriting from it requires fulfilling contracts that may change in the future." | ||
) | ||
public annotation class BrittleForInheritanceCoroutinesApi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We now have three terms in our glossary -- experimental, brittle and unstable.
I suggest sticking with experimental
as the most straightforward one -- most of our "Not stable for inheritance" mean "as new methods might be added to this interface in the future"
@@ -173,6 +173,8 @@ import kotlin.coroutines.* | |||
* These implementations ensure that the context preservation property is not violated, and prevent most | |||
* of the developer mistakes related to concurrency, inconsistent flow dispatchers, and cancellation. | |||
*/ | |||
@OptIn(ExperimentalSubclassOptIn::class) | |||
@SubclassOptInRequired(BrittleForInheritanceCoroutinesApi::class) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still argue that Flow
is neither brittle nor experimental.
In my mental model, it's either Delicate
(because of the invariants that must be preserved) or nothing (because it's quite hard to rationalize it otherwise). WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mostly just questions, feel free to ignore, if the questions are too silly :)
The initial stage of #3770 Marked: * Job, * Deferred and CompletableDeferred, * Flow, SharedFlow, StateFlow, * CloseableCoroutineDispatcher, * CancellableContinuation, * All of their `public` implementors.
Before this change, there was just one annotation: APIs that can't be inherited from. Now, it's more nuanced: some APIs are never planned to be available for inheritance and are marked as such.
cb1aceb
to
393a486
Compare
bdbd0c7
to
9e65346
Compare
9e65346
to
df15f32
Compare
df15f32
to
8816694
Compare
*/ | ||
@OptIn(ExperimentalSubclassOptIn::class) | ||
@SubclassOptInRequired(markerClass = InternalForInheritanceCoroutinesApi::class) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to be big, I guess.
https://grep.app/search?q=Deferred%3C%5B%5Cw%5D%2B%3E%20by®exp=true it's used in the wild and previously we got reports about : Deferred<> by ..
not working because of our internal type assertions.
The initial stage of #3770
Marked:
public
implementors.