Skip to content
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

Proposal: Sealed class inheritance #22

Merged
merged 2 commits into from
Jul 12, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions proposals/sealed-class-inheritance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Sealed class inheritance

* **Type**: Design proposal
* **Author**: Stanislav Erokhin
* **Contributors**: Andrey Breslav, Alexander Udalov
* **Status**: Under consideration
* **Prototype**: Implemented

## Feedback

Discussion of this proposal is held in [this issue](https://github.com/Kotlin/KEEP/issues/29).

## Summary
In kotlin 1.0 all direct subclasses for a sealed class should be declared inside it.
For example:
```kotlin
sealed class A {
class B: A()
}
```

For some cases such limitation is not convenient. (see use cases below)

Proposal: allow top-level subclasses for a top-level sealed class in the same file.



## Motivation / use cases

- Nicer names for subclasses
- It's painful to create complex sealed class hierarchy -- nested level is too big
- >7 votes on [KT-11573](https://youtrack.jetbrains.com/issue/KT-11573): Support sealed class inheritors in the same file

## Implementation details

### Compiler checks

For non top-level sealed class all subclasses should be declared inside such class.
So, for such classes nothing changes.

Let us describe changes for top-level sealed classes.
Suppose that we have top-level class `A`.
For every class `B` which has class `A` in supertypes, we should check:

- if `B` is a top-level class, then we should check that `A` and `B` are declared in same file.
- otherwise we should check that `B` is declared inside class `A`.

Examples:
```kotlin
// FILE: 1.kt
sealed class A {
class B : A() { // B is declared inside A -- ok
class C: A() // C is declared inside A -- ok
}
}

class D : A() { // D and A are declared in same file -- ok

class E : A() // E is declared outside A -- error
}

// FILE: 2.kt
class F: A() // F and A are declared in different files -- error
```

### Exhaustive `when` check

Suppose we have `when` with parameter `a` where `a` is an instance of sealed class `A`.
Example:
```kotlin
fun foo(a: A) = when(a) {
is B -> 1
is C -> 2
}
```
In such code compiler should check that `when` is exhaustive, i.e. all branches are presented.
To do this, we want to collect all direct subclasses of sealed class `A`.
So we should collect all classes inside and, if class `A` is top-level, collect all classes in the same package.
After this we should choose from them only direct subclasses of class `A`.

As we see above, all direct subclasses of sealed classes will be declared in same file with corresponding sealed class.
Because of this, it is impossible to add direct subclass of class `A` without recompilation of class `A`.

### Bytecode generation

In bytecode we should generate special synthetic constructors for class A, which will be called from direct subclasses constructors. Actually, the same sythetic constructors are generated for sealed classes in kotlin 1.0, so we should reuse corresponding algorithm.

**Future improvements:**

- Store information about direct subclasses in binary metadata for corresponding sealed class. [KT-12795](https://youtrack.jetbrains.com/issue/KT-12795)

## Open questions

- Should we allow non top-level subclasses?
```kotlin
sealed class A

class B {
class C: A()
}
```

- Should we allow subclasses of a sealed class on the same level?
```kotlin
class A {
sealed B
class C: B()
}
```

- Should we allow subclasses of a sealed class in other files?
- Can we provide a way to restrict inheritors explicitly?
```
sealed(B, C) class A

class B : A()
class C : A()
```