LEVIATHAN v0.1 · in development

Declarations

Classes & Interfaces

How Leviathan declares reference types: inline and sectional access control, contracts that allocate nothing, multiple inheritance, and the collision rule that decides what happens when two bases name the same member.

Declaring a class

A class optionally takes generic parameters and a comma-separated base list. The body is a brace-delimited member list; a class with no members yet may use the empty-body form.

class Name<T, U> : Base1, Base2 {
    // ...members...
}

class Name;                          // empty-body form

class is Leviathan's reference type: instances have identity and are shared by reference. This is the opposite of struct, the value type covered on Structs & Enums — reach for class when identity matters, struct when it doesn't.

Constructors, just enough to read the examples

A constructor is marked by the new keyword; its name is only a label used to disambiguate overloads, and carries no other meaning. Construction itself has no new at the call site — calling the class name runs the matching constructor.

new Counter() {
    label = "unnamed";
}

// Overload: same 'new' marker, different parameters.
new Counter(string startLabel) {
    label = startLabel;
}

// The label need not match the class name.
new WithValue(string startLabel, int start) {
    label = startLabel;
    value = start;
}
Counter a = Counter();                     // matches the nullary constructor
Counter b = Counter::WithValue("hi", 5);    // explicit selection by label

When two constructors would otherwise collide on parameter shape, first-declared wins and the label is the explicit tiebreaker — the same shape as the field qualification in Distinct below. Full constructor, method, and accessor syntax — including get/set and mutating — lives on Members & Accessors.

Access modifiers

Access may be marked inline, per member, or sectionally with a public: / private: region that applies to every member until the next section marker.

class Account {
    // Inline access modifiers
    public string owner;
    private int pin;

    public:
        string describe() => owner;

    private:
        bool checkPin(int guess) => guess == pin;
}

Interfaces

An interface declares required members but is a contract that allocates nothing. That includes fields — a deliberate exception that treats a field requirement and a method requirement as the same kind of requirement. The implementing class's declaration is the one and only allocating site.

public interface MyInterface {
    int myNumber;
    string myString;
}

class MyInterfacedClass : MyInterface {
    int myNumber;    // the declaration that allocates
    string myString;
}

A requirement left unsatisfied is a compile error.

Why fields never collide across interfaces

Because an interface never allocates, two interfaces requiring the same field create no conflict — the implementing class satisfies both with its single declared slot. The field-collision machinery only ever engages between allocating bases (classes), because an interface never contributes a second slot to begin with.

Multiple inheritance

A class base list may name more than one class, giving Leviathan multiple inheritance of both interface and implementation. Two constructs from the same worked example:

class Counter : Named {
    public string label;
    public distinct int value = 0;      // distinct: survives same-typed collision

    get value() => value;
    set value(int v) value = v;

    public:
        string describe() => label;

        new Counter() { label = "unnamed"; }
        new Counter(string startLabel) { label = startLabel; }
}

class Tag {
    public distinct int value = 99;     // same name+type as Counter.value
    public string note = "tag-note";
}

Widget then inherits from both. Its constructor reaches each base's constructor by qualifying the label with the base's name — Base::Base() applies to this:

class Widget<T> : Counter, Tag {

    public T payload;

    new Widget() {
        Counter::Counter();
        Tag::Tag();

        // Same-typed 'value' kept distinct on both sides:
        //   this.Counter::value == 0
        //   this.Tag::value     == 99
        this.Counter::value = 5;
        this.Tag::value     = 7;

        // 'note' comes only from Tag — no collision, bare access is fine
        note = "widget";
    }
}

The rule that decides whether two same-named inherited members share a slot or not:

Inherited membersOutcome
Same name, same type, from two basesCollision — see Distinct
Same name, different type, from two basesNo collision — both coexist, resolved by type at each use
Same field required by two interfacesNo collision — interfaces allocate nothing, one slot satisfies both

Distinct: resolving collisions

A collision is name + type inherited from two bases. By default a collision collapses to one slot — the later base wins. Marking the field distinct, on either source, keeps separate per-source slots instead, reachable only by qualification:

public distinct int value = 0;

With value declared distinct on both Counter and Tag, Widget holds two independent int slots, each reached through this.Base::member:

this.Counter::value = 5;      // explicit qualification, no collision
this.Tag::value     = 7;

// console.write(value);      // invalid: two int 'value' slots, no type context
A bare read of a distinct-collided member is a compile error

Once a member is distinct-collided, there is no default slot to fall back to — every read or write must qualify which base's slot it means, either via this.Base::member or, from outside the instance, Base::member resolution by static type.

Fields still auto-construct when bare-declared, the same as any field: string s; is "", Array<T> a; is [], int i; is 0 — a distinct field with no initializer follows the same rule as any other.

Covariant interface returns

A method satisfies an interface requirement when its name and parameter canonicals match exactly and its declared class/interface return type is assignable to the required return — a subtype return is enough. T also satisfies a requirement declared as T?. Every other return shape — unions, function types, primitives, and void — must still match exactly.

interface Shape {
    Shape clone();
}

class Circle : Shape {
    // Circle is a subtype of Shape — satisfies the requirement covariantly.
    Circle clone() => Circle::Circle();
}
interface Repository {
    Widget? find(int id);
}

class MemRepository : Repository {
    // T satisfies a required T?
    Widget find(int id) => ...;
}

This relaxation applies only to interface satisfaction — narrowing a return type in a derived class does not override the base class's method the same way. And because a requirement allocates no slot, dispatch still reaches the implementing class's single method; there is nothing extra to resolve at the call site.