|
2 | 2 |
|
3 | 3 | * **Type**: Design proposal
|
4 | 4 | * **Author**: Vsevolod Tolstopyatov
|
5 |
| -* **Contributors**: Mikhail Glukhikh, Vsevolod Tolstopyatov, Roman Elizarov, Ilya Gorbunov |
| 5 | +* **Contributors**: Mikhail Glukhikh, Vsevolod Tolstopyatov, Roman Elizarov, Ilya Gorbunov, Anastasia Pikalova, |
| 6 | + Stanislav Ruban |
| 7 | + |
6 | 8 | * **Status**: Implemented in Kotlin 1.8.0 as experimental
|
7 | 9 | * **Discussion**: [KEEP-320](https://github.com/Kotlin/KEEP/issues/320)
|
8 | 10 |
|
@@ -101,12 +103,16 @@ Other alternatives are:
|
101 | 103 |
|
102 | 104 | * `RequireInheritanceOptiIn`
|
103 | 105 | * `SubclassesRequireOptIn`
|
| 106 | + * `SubclassRequiresOptIn` |
| 107 | + * `InheritanceRequiresOptIn` |
104 | 108 | * Various attempts to leverage the notion of `sealed` and `open`:
|
105 | 109 | * `SemiOpen` and `SemiSealed`
|
106 | 110 | * `OptInToOpen` and `OptInToSubclass`
|
107 | 111 |
|
108 | 112 | `SubclassOptInRequired` was chosen as the most appropriate and likely the most familiar for developers
|
109 | 113 | to grasp from at first glance.
|
| 114 | +The name indicates that subclasses must opt in. |
| 115 | +`Required` highlights this obligation more effectively than `Requires`. |
110 | 116 |
|
111 | 117 | ### SubclassOptInRequired marker contagiousness (lexical scopes)
|
112 | 118 |
|
@@ -184,8 +190,76 @@ may be used within its body or signatures (`UnstableApi` types or overridden met
|
184 | 190 | between
|
185 | 191 | opting-in into extension and opting-in into overall uses.
|
186 | 192 |
|
| 193 | +### Design downsides |
| 194 | +Although one of the goals of this proposal is consistency with the existing `OptIn` API, |
| 195 | +`SubclassOptInRequired` doesn't support passing annotation arguments, unlike experimental annotations. |
| 196 | +For example: |
| 197 | +```kotlin |
| 198 | +@RequiresOptIn |
| 199 | +annotation class ExperimentalAPI(val message: String) |
| 200 | + |
| 201 | +@ExperimentalAPI("Some message") |
| 202 | +class ExperimentalA |
| 203 | + |
| 204 | +// Unable to set 'message' |
| 205 | +@SubclassOptInRequired(ExperimentalAPI::class) |
| 206 | +open class ExperimentalB |
| 207 | +``` |
| 208 | +It's allowed to pass a custom message as an annotation argument in `ExperimentalAPI`, |
| 209 | +but this is not possible with `SubclassOptInRequired`. |
| 210 | +This design limitation is considered minor |
| 211 | +because no significant use cases or valid scenarios for annotation arguments in experimental annotations have been identified. |
| 212 | + |
| 213 | +### Alternative approaches |
| 214 | +1. `@RequiresOptIn` injects a new `scope` parameter with the default value `ALL` to an experimental annotation. |
| 215 | + ```kotlin |
| 216 | + @RequiresOptIn // injects 'scope' param |
| 217 | + annotation class Ann |
| 218 | + |
| 219 | + @Ann(scope = Scope.Inheritance) |
| 220 | + open class Foo |
| 221 | + ``` |
| 222 | + The design was rejected due to concerns about preserving source compatibility with explicitly declared and injected parameters. |
| 223 | + |
| 224 | + |
| 225 | +2. Users can define a special annotation parameter named `scope`. |
| 226 | + ```kotlin |
| 227 | + @RequiresOptIn |
| 228 | + annotation class Ann(val scope: Scope = Scope.All) |
| 229 | + |
| 230 | + @Ann(scope = Scope.Inheritance) |
| 231 | + open class Foo |
| 232 | + ``` |
| 233 | + The design was rejected because it creates an implicit contract between the compiler logic and the parameter names, |
| 234 | + leading to potential fragility dependencies. |
| 235 | + |
| 236 | + |
| 237 | +3. Pass annotation instances as arguments to the `@SubclassOptInRequired` annotation. |
| 238 | + ```kotlin |
| 239 | + @RequiresOptIn |
| 240 | + annotation class Ann(val message: String) |
| 241 | + |
| 242 | + @SubclassOptInRequired(@Ann("message")) |
| 243 | + open class Foo |
| 244 | + ``` |
| 245 | + The design was rejected because the `Annotation` type, which is common to all annotations, |
| 246 | + cannot be used as an annotation parameter type. |
| 247 | + |
| 248 | + |
| 249 | +4. Add the `scope` parameter to the `RequiresOptIn` annotation. |
| 250 | + ```kotlin |
| 251 | + @RequiresOptIn(scope = Scope.All) |
| 252 | + annotation class PoisonAll(val message: String) |
| 253 | + |
| 254 | + @RequiresOptIn(scope = Scope.Inheritance) |
| 255 | + annotation class PoisonOnlySubclasses(val message: String) |
| 256 | + ``` |
| 257 | + This design was rejected because it limits the ability to use the same experimental annotation marker for different scopes: |
| 258 | + either marking the entire API as unstable or marking only inheritance as unstable. |
| 259 | + |
| 260 | + |
187 | 261 | ### Status and timeline
|
188 | 262 |
|
189 | 263 | The feature is available since Kotlin 2.0.0 as experimental (it itself requires an opt-in
|
190 | 264 | into `ExperimentalSubclassOptIn`)
|
191 |
| -and is expected to be promoted to stable in Kotlin 2.1.0. |
| 265 | +and stable in Kotlin 2.1.0. |
0 commit comments