dart-lang / dart-lang/language
Proposal: `sealed` class members
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
## Overview
I recently learned that the `abstract` keyword can be used for class members:
```dart
abstract class Foo {
abstract final Object? a;
abstract Object? b;
}
```
This issue proposes using the `sealed` keyword in a similar way:
```dart
class Foo {
const Foo({this.a});
sealed final Object? a;
}
class Bar extends Foo {
Bar({super.a, this.b});
sealed Object? b;
}
```
Adding `sealed` to a class field prevents the field from being overridden.
```dart
class A {
const A({this.value});
sealed final int? value;
}
class B extends A {
@override
int get value => 42; // error
}
```
Detailed rules (click to expand)
#### Used for class fields
```dart
sealed int value = 42; // error: not in a class declaration
class A {
const A(this.value);
sealed final int value; // OK
void foo() {
sealed int value = 42; // error: does not apply to local variables
}
}
class C {
sealed int i = 0; // OK, can be used for non-final variables
late sealed Object data; // OK, works for "late" values
sealed void foo() {
// OK, can be used for methods
}
sealed int get value => 42; // OK, but getters won't gain benefits
// as described further down
sealed set value(int? newValue) {
// OK, works for setters
}
}
```
#### Does not apply to abstract or static fields
```dart
abstract class A {
abstract sealed Object value; // error: abstract sealed member
sealed String get label; // error: abstract sealed getter
sealed void foo(); // error: abstract sealed method
static sealed A of(BuildContext context) {
// error: static sealed member
}
}
```
#### Don't override inherited `sealed` fields
```dart
class A {
sealed void foo() {
print('I love Dart!');
}
}
class B extends A {
@override
void foo() { // error: cannot override an inherited sealed field
print('hello');
}
}
```
#### Can override non-`sealed` fields
```dart
class A {
const A({this.value});
final Object value;
}
class B implements A {
@override
sealed int value = 0; // OK
}
abstract class C {
Object? get data;
}
class D extends C {
const D({this.data});
@override
sealed final Object data; // OK
}
```
#### Implementing a class with a `sealed` field
A `sealed` class member can be overridden when implementing the class, but the new value must also have the `sealed` modifier. A `sealed` member cannot be replaced with a getter.
```dart
class A {
const A(this.value);
sealed final Object value;
}
class B implements A {
const B({this.value = ''});
@override
sealed final String value; // OK
}
class B implements A {
const B({this.value = ''});
@override
final String value; // error: value must have "sealed" modifier
}
class C implements A {
@override
sealed int value = 0; // OK
}
class D implements A {
@override
sealed int get value => 0; // error: member cannot be changed to getter
}
```
When the class is implemented, a `sealed` getter or method can be overridden by another `sealed` getter/method with no additional restrictions. To prevent overriding, use a `base class`.
```dart
import 'dart:math' as math;
class A {
sealed String get coinFlip {
return math.Random().nextBool() ? 'heads' : 'tails';
}
}
class B implements A {
@override
sealed String get coinFlip => 'always tails'; // OK
}
sealed class C {
sealed String get coinFlip {
// cannot be overridden, unless it's implemented in the same library
return math.Random().nextBool() ? 'heads' : 'tails';
}
}
```
## Benefits
### Type promotion
A `sealed` class member qualifies for type promotion, as if it were a local variable.
```dart
class A {
const A({this.value});
sealed final Object? value;
void foo() {
if (value is String) {
print(value.substring(2));
}
}
}
class B {
B({this.value});
sealed Object? value;
void foo() {
if (value is int) {
value += 3;
}
}
}
```
### Constant class fields
If `foo` is a constant value, and `bar` is a `sealed final` field in its class declaration, then `foo.bar` can be used in a constant context.
```dart
class Fraction {
const Fraction(this.numerator, this.denominator)
: value = numerator / denominator;
sealed final num numerator, denominator;
sealed final double value;
}
const fraction = Fraction(5, 4);
const remainder = fraction.value % 1;
```
## Discussion
This proposal is closely related to #1518, but has a few differences.
#### Advantages of `sealed`
- Uses existing keyword (`stable` could be a variable name)
- Supports type promotion for non-`final` variables
- Allows a class field to be used in a constant context (resolves #299)
#### Advantages of `stable`
- Supports type promotion for getters
- Since a `stable` getter can override a `stable` field, it can be used in place of a `late final` value, allowing a class declaration to keep its `const` constructor (though this could also be achieved via #2464)
Contributor guide
Research direction
Start by reviewing this proposal alongside the related discussions in #1518, #299, and #2464. Determine whether the `sealed` member rules, type-promotion behavior, and constant-context behavior have an agreed design; done would require a decided language specification change rather than a small isolated edit.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100