blob: 43d159d95f06ec71d18b30e2f8e29e89ea08c3df [file] [log] [blame]
= GEP-13: Sealed classes
:icons: font
.Metadata
****
[horizontal,options="compact"]
*Number*:: GEP-13
*Title*:: Sealed classes
*Version*:: 1
*Type*:: Feature
*Status*:: Draft
*Leader*:: Paul King
*Created*:: 2021-07-22
*Last modification* :: 2021-07-22
****
== Abstract: Sealed classes
Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them.
By supporting sealed classes and interfaces, the Groovy programming language
can offer an additional mechanism for controlling class hierarchy construction.
=== Motivation
Inheritance is a powerful mechanism for creating hierarchies of related class and interfaces.
Sometimes, it is desirable to restrict the definition of children in such hierarchies.
Modifiers already provide some mechanisms:
* If all of our classes and interfaces are public, this indicates that we want
maximum reuse.
* The `final` modifier offers one mechanism for restricting further inheritance at the method or class level.
It effectively limits all further extension and indicates no further code reuse is desired.
* By making a base class package-private we can limit extension to only classes within
the same package. If an abstract `Shape` class is package-private, I could have
public classes `Square` and `Circle` in the same package. This indicates that we want
code reuse to occur only within the package. While it does limit creation of
new shapes outside the original package, it offers no abstraction for a shape which
could be either a square or circle since `Shape` is not public.
* We can use `protected` visibility to limit access of members strictly to children
but that doesn't help us solve the aforementioned problems like lack of a visible
abstraction for `Shape` in the discussed example.
Sealed classes or interfaces can be public but have an associated list of allowed children.
Classes or interfaces which are not in that list cannot inherit from those sealed types.
This indicates that we want code reuse within the hierarchy but not beyond.
Parent classes in the hierarchy can be made _accessible_, without also making them _extensible_.
This allows hierarchies to be created with maximum reuse within but without having
to defensively code for arbitrary extensions added at a later time.
Such classes are useful in defining Algebraic Data Types (ADTs) and in scenarios where
we might want to reason about whether we have accounted for all possible types, e.g. the
static compiler may wish to give a warning if a switch block doesn't exhaustively
cover all possible types by respective case branches.
==== Initial implementation
* Provide a `@Sealed` marker annotation or AST transform which allows a list of
permitted children to be defined. Use of this annotation will be an incubating
feature subject to change. Explicit use may eventually be discouraged and instead
a keyword, e.g. `sealed` would be encouraged instead. However, the annotation
could be retained to offer support for this feature on earlier JVMs or versions
of Groovy prior to any grammar changes.
* Prohibit extension of JDK16+ sealed classes or annotated `@Sealed` classes.
Likewise for interfaces. This also applies for anonymous inner classes and traits.
* Provide checks in other places where such extension might occur implicitly, e.g.: with `@Delegate`,
when using type coercion, etc.
* Support `non-sealed` or `unsealed` sub-hierarchies. (See JEP-409)
* Allow the permitted subclasses to be inferred automatically just for the case
where the base and all permitted subclasses are in the same file. (See JEP-409)
==== Potential extensions
The following potential extensions are possibly all desirable but
are non-goals for the first implementation:
* Introduce the `sealed` modifier and `permits` clause in the grammar.
* Require that all classes within a sealed hierarchy be compiled at the same time.
* Require that all classes within a sealed hierarchy belong to the same JPMS module.
* Add warnings to the static compiler if a switch is used for a sealed hierarchy
and not all types are exhaustively covered.
* When running on JDK16+, also add sealed class information into the bytecode.
== References and useful links
* https://openjdk.java.net/jeps/360[JEP 360: Sealed Classes (Preview)]
* https://openjdk.java.net/jeps/397[JEP 397: Sealed Classes (Second Preview)]
* https://openjdk.java.net/jeps/409[JEP 409: Sealed Classes]
* https://kotlinlang.org/docs/sealed-classes.html[Sealed Classes] in Kotlin
* https://docs.scala-lang.org/sips/sealed-types.html[Sealed Classes] in Scala
=== Reference implementation
https://github.com/apache/groovy/pull/1606
=== JIRA issues
* https://issues.apache.org/jira/browse/GROOVY-10148[GROOVY-10148: Groovy should not allow classes to extend sealed Java classes]
== Update history
1 (2021-07-22) Initial draft