Skip to content

Commit

Permalink
Merge pull request #390 from Kotlin/pikalova/subclassOptInRequired
Browse files Browse the repository at this point in the history
Update proposals/subclass-opt-in-required.md
  • Loading branch information
BlondeHex authored Sep 14, 2024
2 parents 6d2e403 + c950cf0 commit ad1eb2e
Showing 1 changed file with 85 additions and 2 deletions.
87 changes: 85 additions & 2 deletions proposals/subclass-opt-in-required.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

* **Type**: Design proposal
* **Author**: Vsevolod Tolstopyatov
* **Contributors**: Mikhail Glukhikh, Vsevolod Tolstopyatov, Roman Elizarov, Ilya Gorbunov
* **Contributors**: Mikhail Glukhikh, Vsevolod Tolstopyatov, Roman Elizarov, Ilya Gorbunov, Anastasia Pikalova,
Stanislav Ruban

* **Status**: Implemented in Kotlin 1.8.0 as experimental
* **Discussion**: [KEEP-320](https://github.com/Kotlin/KEEP/issues/320)

Expand Down Expand Up @@ -101,12 +103,16 @@ Other alternatives are:

* `RequireInheritanceOptiIn`
* `SubclassesRequireOptIn`
* `SubclassRequiresOptIn`
* `InheritanceRequiresOptIn`
* Various attempts to leverage the notion of `sealed` and `open`:
* `SemiOpen` and `SemiSealed`
* `OptInToOpen` and `OptInToSubclass`

`SubclassOptInRequired` was chosen as the most appropriate and likely the most familiar for developers
to grasp from at first glance.
The name indicates that subclasses must opt in to the specified opt-in marker(s).
`Required` highlights this obligation more effectively than `Requires`.

### SubclassOptInRequired marker contagiousness (lexical scopes)

Expand Down Expand Up @@ -184,8 +190,85 @@ may be used within its body or signatures (`UnstableApi` types or overridden met
between
opting-in into extension and opting-in into overall uses.

### Design considerations
Although one of the goals of this proposal is consistency with the existing `OptIn` API,
`SubclassOptInRequired` doesn't support passing annotation arguments, unlike opt-in marker annotations.
For example:
```kotlin
@RequiresOptIn
annotation class ExperimentalAPI(val message: String)

@ExperimentalAPI("Some message")
class ExperimentalA

// Unable to set 'message'
@SubclassOptInRequired(ExperimentalAPI::class)
open class ExperimentalB
```
It's allowed to pass a custom message as an annotation argument in `ExperimentalAPI`,
but this is not possible with `SubclassOptInRequired`.
This design limitation is considered minor
because no significant use cases or valid scenarios for annotation arguments in experimental annotations have been identified.

### Alternative approaches
1. `@RequiresOptIn` injects a new `scope` parameter with the default value `ALL` to an experimental annotation.
```kotlin
@RequiresOptIn // injects 'scope' param
annotation class Ann

@Ann(scope = Scope.Inheritance)
open class Foo
```
The design was rejected due to concerns about potential clashes between explicitly declared and injected parameters.


2. Users can define a special annotation parameter named `scope`.
```kotlin
@RequiresOptIn
annotation class Ann(val scope: Scope = Scope.All)

@Ann(scope = Scope.Inheritance)
open class Foo
```
The design was rejected because it creates an implicit contract between the compiler logic,
the parameter names, and the presence or absence of the @RequiresOptIn annotation.


3. Pass annotation instances as arguments to the `@SubclassOptInRequired` annotation.
```kotlin
@RequiresOptIn
annotation class Ann(val message: String)

@SubclassOptInRequired(@Ann("message"))
open class Foo
```
The design was rejected because the `Annotation` type, which is common to all annotations,
cannot be used as an annotation parameter type.


4. Add the `scope` parameter to the `RequiresOptIn` annotation.
```kotlin
@RequiresOptIn(scope = Scope.All)
annotation class PoisonAll(val message: String)

@RequiresOptIn(scope = Scope.Inheritance)
annotation class PoisonOnlySubclasses(val message: String)
```
The design was rejected because it limits the ability to use the same experimental annotation marker for different scopes:
either marking the entire API as unstable or marking only inheritance as unstable.

5. Consider using a single marker with `@Repeatable` instead of vararg for `@SubclassOptInRequired`
During the design process, we also considered an alternative approach:
instead of allowing `@SubclassOptInRequired` to accept multiple markers via the `vararg` parameter (similar to `@OptIn`),
we explored the possibility of designing the annotation to accept only one marker and making `@SubclassOptInRequired` `@Repeatable`.
The design was rejected for two key reasons.
First, introducing a repeatable annotation would break consistency with the existing `@OptIn` annotation.
Maintaining consistency between these annotations is crucial for ensuring a predictable user experience.
Second, we want to avoid introducing `@Repeatable` annotations unless absolutely necessary,
as they tend to complicate code readability and increase the mental load for developers.

### Status and timeline

The feature is available since Kotlin 2.0.0 as experimental (it itself requires an opt-in
into `ExperimentalSubclassOptIn`)
and is expected to be promoted to stable in Kotlin 2.1.0.
and stable in Kotlin 2.1.0.

0 comments on commit ad1eb2e

Please sign in to comment.