Skip to content

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.

ModifierInstantiableInheritableCLR shape
(none)yesnosealed class — the default; there is no sealed modifier to write
openyesyesregular (non-sealed, non-abstract) class
abstractnoyesabstract class
ref data Tag { init() { } } // sealed by default — no modifier
open ref data Animal { init() { } } // base for derivation
abstract ref data Shape { init() { } } // must be subclassed

open 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.

FormBodyMeaning
func name(...)requiredOrdinary method. Shadowing a virtual parent → ES2120.
virtual func name(...)requiredNew vtable slot; subclasses may override. Only on open/abstract (ES2126 otherwise).
abstract func name(...)noneContract; subclasses must fulfill. Only inside abstract ref data (ES2125 otherwise).
: func name(...)dependsInheritance-participating. The compiler infers the role (below).

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 formHas body?Child abstract?Resolved roleEmits
abstractyesnoFulfillVirtual, reuses the vtable slot
virtualyesnoOverrideVirtual, reuses the slot
abstractnoyesPassThroughcontract stays abstract; no MethodDef on the child
virtualnoyesReAbstractchild 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:

CodeTrigger
ES2121: func body required in a concrete subclass
ES2122no matching virtual/abstract on the chain
ES2123parent member is neither virtual nor abstract
ES2124: used in a class with no inheritance header

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.

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.

Browse inheritance examples →