language
language copied to clipboard
Proposal: `base` class members
Overview
I recently learned that the abstract
keyword can be used for class members:
abstract class Foo {
abstract final Object? a;
abstract Object? b;
}
This issue proposes using the base
keyword in a similar way:
class Foo {
const Foo({this.a});
base final Object? a;
}
class Bar extends Foo {
Bar({super.a, this.b});
base Object? b;
}
When you implement a class, you override all of its fields, whereas when you extend a class, you can choose to override fields individually.
Like how a base
class prevents implementation, a base
field within a class prevents overriding that field.
class A {
const A({this.value});
base final int? value;
}
class B extends A {
@override
int get value => 42; // compile-time error, cannot override a base field
}
Detailed rules (click to expand)
Used for class fields
base int value = 42; // error: not in a class declaration
base class A {
const A(this.value);
base final int value; // OK
void foo() {
base int value = 42; // error: does not apply to local variables
}
}
class B {
const B(this.value);
base final int value; // OK, does not need to be inside a base class
}
class C {
base int i = 0; // OK, can be used for non-final variables
late base Object data; // OK, works for "late" values
base void foo() {
// OK, can be used for methods
}
base int get value => 42; // OK, but getters won't gain benefits
// as described further down
base set value(int? newValue) {
// OK, works for setters
}
}
Does not apply to abstract or static fields
abstract class A {
abstract base Object value; // error: abstract base member
base String get label; // error: abstract base getter
base void foo(); // error: abstract base method
static base A of(BuildContext context) {
// error: static base member
}
}
Don't override inherited base
fields
class A {
base void foo() {
print('I love Dart!');
}
}
class B extends A {
@override
void foo() { // error: cannot override an inherited base field
print('hello');
}
}
Can override non-base
fields
class A {
const A({this.value});
final Object value;
}
class B implements A {
@override
base int value = 0; // OK
}
abstract class C {
Object? get data;
}
class D extends C {
const D({this.data});
@override
base final Object data;
}
Implementing a class with a base
field
A base
class member can be overridden when implementing the class, but the new value must also have the base
modifier. A base
member cannot be replaced with a getter.
class A {
const A(this.value);
base final Object value;
}
class B implements A {
const B({this.value = ''});
@override
base final String value; // OK
}
class B implements A {
const B({this.value = ''});
@override
final String value; // error: value must have "base" modifier
}
class C implements A {
@override
base int value = 0; // OK
}
class D implements A {
@override
base int get value => 0; // error: member cannot be changed to getter
}
When the class is implemented, a base
getter or method can be overridden by another base
getter/method with no additional restrictions. To prevent overriding, use a base class
.
import 'dart:math' as math;
class A {
base String get coinFlip {
return math.Random().nextBool() ? 'heads' : 'tails';
}
}
class B implements A {
@override
base String get coinFlip => 'always tails'; // OK
}
base class C {
base String get coinFlip {
// cannot be overridden, unless it's implemented in the same library
return math.Random().nextBool() ? 'heads' : 'tails';
}
}
Benefits
Type promotion
A base
class member qualifies for type promotion, as if it were a local variable.
class A {
const A({this.value});
base final Object? value;
void foo() {
if (value is String) {
print(value.substring(2));
}
}
}
class B {
B({this.value});
base Object? value;
void foo() {
if (value is int) {
value += 3;
}
}
}
Constant class fields
If foo
is a constant value, and bar
is a base final
field in its class declaration, then foo.bar
can be used in a constant context.
class Fraction {
const Fraction(this.numerator, this.denominator)
: value = numerator / denominator;
base final num numerator, denominator;
base 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 base
- 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
- Can be applied to local, global, and
static
fields - Supports type promotion for getters
- Since a
stable
getter can override astable
field, it can be used in place of alate final
value, allowing a class declaration to keep itsconst
constructor (though this could also be achieved via #2225)