Declarations
Multiple inheritance
Combine several base classes, how same-named fields and methods collapse or stay separate, and how to reach one base's member by qualification.
since 0.1.0-alpha.1linuxwindowswasm
Description
A class may list more than one base class:
class Both : Left, Right { }
Both receives the fields and methods of both bases. When two bases declare a member with the same
name, what happens depends on whether the declarations have the same type:
- If the name and type are the same, the two declarations collide, and the outcome has three
cases. The same declaration reached along two paths is one slot. One declaration that
overrides the other along a single inheritance chain is one slot, and the more-derived
declaration wins whatever the order of the bases. Any other pair, two unrelated
declarations, is a compile error. Resolve it by marking one declaration
distinct(two slots are kept, seelang.distinct) or by restating the member in the derived class (one slot). - Methods whose parameter types differ are overloads and coexist; a use is resolved by its arguments. Fields of one name with different types are not allowed (see Rules).
- A field marked
distinctis never collapsed; seelang.distinct.
A method declared in the class itself overrides the inherited ones. The inherited methods of a
particular base can still be called by qualifying them with the base's name:
this.Left::who().
Restating a colliding member chooses one slot
class Left { int value = 1; string tag = "L"; string who() => "left"; }
class Right { int value = 2; string who() => "right"; }
class Both : Left, Right {
int value = 3;
string who() => "both";
string each() => this.Left::who() + " and " + this.Right::who();
}
Both b = Both();
console.writeln(b.value);
console.writeln(b.who());
console.writeln(b.tag);
b.value = 9;
Left l = b;
Right r = b;
console.writeln(l.value);
console.writeln(r.value);
console.writeln(b.each());
3
both
L
9
9
left and right
Left and Right each declare value and who, and neither is derived from the other, so Both
must choose: it restates both. value exists once in Both, and a write through b is seen
through both the Left and the Right view. tag has no collision and comes from Left. The
bases' own versions of who are still reachable by qualifying them.
Rules
-
Same-named members collide only when their types also match. Two declarations that reach a class through its bases are one slot when they are the same declaration (reached along two paths) or when one overrides the other along a single chain: the more-derived declaration wins, in either order of the bases. Two unrelated declarations are a compile error, reported once at the class's base list:
error: 'hp : int' reaches 'Leaf' from two unrelated declarations, 'Race::hp' and 'Job::hp'; mark one `distinct` to keep both slots, or declare 'hp' in 'Leaf' to chooseTwo declarations that override a common declaration separately and then meet in one class are the competing-overrides variant, which is reported the same way ("through two competing overrides of 'Unit::hp'"). A class that restates the member, or marks one declaration
distinct, has no collision. Interfaces take no part: their requirements allocate nothing. -
A class holds one field per name. Two bases declaring one field name with different types are a compile error unless one of them is
distinct:error: 'v' reaches 'Z' as two fields of different types, 'X::v : int' and 'Y::v : string'; a class holds one field per name — rename one, or mark one `distinct` to keep both slotsA
distinctmember declared once in a generic class cannot be inherited through two instantiations of that class:error: 'v' reaches 'L' twice from one declaration with different types, 'G::v : int' and 'G::v : string'; a class cannot inherit a `distinct` member through two instantiations of one generic class -
Methods that share a name but differ in parameter types coexist as overloads, and a use is resolved by its arguments.
-
A field declared
distinctkeeps its own slot, separate from any same-named field of another base. Where two such slots are visible, a read must be qualified (object.Base::name) and an unqualified read is a compile error; where one is visible, the bare name reaches it. Seelang.distinct. -
A member declared
constin one base and non-constin another base, with the same name and type, is ambiguous and a compile error. Resolve it withdistinctor by restating the field in the derived class. The same holds forreadonlyagainst non-readonly. -
this.Base::member(orobject.Base::member) names the member asBasesees it, for both fields and methods. Where the bare call would be unambiguous, a qualified method call is bound statically to the named base's method: it does not see an override declared further down. Where the bare call would be ambiguous betweendistinctpaths, it selects that base's path and dispatches on the runtime class (seelang.distinct). -
A field may be qualified with the class that declares it or with any class that inherits it along that path.
-
A class that inherits from the same class along two paths holds one copy of that class's fields,
distinctor not; qualifying through either path names the same slot. -
A class may also list interfaces among its bases; see
lang.interfaces.
Examples
A class reached along two paths holds one copy of its fields, and a qualified call is bound to the base that is named, whatever overrides exist below it:
Shared ancestors and qualified calls
class Base { int n = 1; }
class L : Base { }
class R : Base { }
class D : L, R { }
D d = D();
d.n = 5;
L l = d;
R r = d;
console.writeln(l.n);
console.writeln(r.n);
class Top { string who() => "top"; }
class Mid : Top { string who() => "mid"; }
class Leaf : Mid {
string who() => "leaf";
string viaTop() => this.Top::who();
string viaMid() => this.Mid::who();
}
Leaf f = Leaf();
console.writeln(f.viaTop());
console.writeln(f.viaMid());
console.writeln(f.who());
5
5
top
mid
leaf
Fields of one name with different types cannot be combined:
One name, two types
class A { int x = 1; }
class B { string x = "s"; }
class C : A, B { }
// error: 'x' reaches 'C' as two fields of different types, 'A::x : int' and 'B::x : string';
// a class holds one field per name — rename one, or mark one `distinct` to keep both slots
A const and a non-const declaration of the same name and type must be reconciled:
Mixed const and non-const
class A { const int x = 1; }
class B { int x = 2; }
class C : A, B { }
// error: 'x : int' is declared both `const` and non-const across bases;
// mark `distinct` or match the declarations
Notes
The limits that apply to distinct members in a combined object are listed in the Notes of
lang.distinct.
See also
- Classes — Declare reference types with fields, methods, constructors and accessors, with generics and inheritance from one or more base classes.
- distinct fields — Keep same-named fields of different bases in separate slots, reached by qualification.
- Fields — Declare the data a class or struct holds, with optional initializers, and how a field without an initializer gets its default value.
- Interfaces — Declare the members a class must provide, including fields, and let any class satisfy them by declaring those members.