Inheritance
Inheritance exists on ref data only, and is opt-in, sealed by default — the opposite of C#‘s
“inheritable unless sealed”. data, choice, and enum do not participate.
Class modifiers
Section titled “Class modifiers”| Modifier | Instantiable | Inheritable | CLR shape |
|---|---|---|---|
| (none) | yes | no | sealed class — the default; there is no sealed modifier to write |
open | yes | yes | regular (non-sealed, non-abstract) class |
abstract | no | yes | abstract class |
ref data Tag { init() { } } // sealed by default — no modifieropen ref data Animal { init() { } } // base for derivationabstract ref data Shape { init() { } } // must be subclassedopen is the one shape that is both instantiable and inheritable — and E#‘s take on OO is that a
class should be either abstract or sealed, never both at once. So open is slated to become an
explicit .esproj opt-in, disabled by default: the language steers you toward abstract (a base you
extend) or the sealed default (a leaf you instantiate), and the both-at-once middle is something you
enable deliberately — explicitness over habit — rather than reach for by default.
Member forms
Section titled “Member forms”| Form | Body | Meaning |
|---|---|---|
func name(...) | required | Ordinary method. Shadowing a virtual parent → ES2120. |
virtual func name(...) | required | New vtable slot; subclasses may override. Only on open/abstract (ES2126 otherwise). |
abstract func name(...) | none | Contract; subclasses must fulfill. Only inside abstract ref data (ES2125 otherwise). |
: func name(...) | depends | Inheritance-participating. The compiler infers the role (below). |
: func role inference
Section titled “: func role inference”When the compiler walks the chain and finds a matching virtual/abstract parent, it infers the role
from parent form × this body × child kind:
| Parent form | Has body? | Child abstract? | Resolved role | Emits |
|---|---|---|---|---|
abstract | yes | no | Fulfill | Virtual, reuses the vtable slot |
virtual | yes | no | Override | Virtual, reuses the slot |
abstract | no | yes | PassThrough | contract stays abstract; no MethodDef on the child |
virtual | no | yes | ReAbstract | child removes the body (rare) |
abstract ref data Shape { init() { } abstract func area() -> int}ref data Square : Shape { side: int init(s: int) : base() { self.side = s } : func area() -> int { return self.side * self.side } // Fulfill}Diagnostics around : func:
| Code | Trigger |
|---|---|
| ES2121 | : func body required in a concrete subclass |
| ES2122 | no matching virtual/abstract on the chain |
| ES2123 | parent member is neither virtual nor abstract |
| ES2124 | : used in a class with no inheritance header |
Base-constructor chaining
Section titled “Base-constructor chaining”A subclass init chains to the base with : base(...) between the parameter list and the body:
open ref data Animal { species: string, init(s: string) { self.species = s } }ref data Dog : Animal { name: string, init(n: string) : base("dog") { self.name = n } }The compiler resolves : base(args) against the base init and emits the matching .ctor call before
field-default initialization. An argument-count mismatch is ES2128. Inherited
fields and methods resolve transparently through the chain; an overridden virtual dispatches via
callvirt.
Interfaces and base classes together
Section titled “Interfaces and base classes together”ref data Renderer : IDrawable, IDescribable { ... } — multiple interfaces are comma-separated. When the
: list includes a base class, the base class must be the first entry. Interface conformance itself
is nominal — see Types → interface.