Trait Composition for Classes

THIS STRAWMAN IS CURRENTLY NOT IN A COHERENT STATE. Rather, it is mostly a place to collect the support for trait composition, abstract classes, required members, and multiple (trait) inheritance which have been factored out of classes_with_trait_composition.

This strawman shows how to add trait composition to classes_with_trait_composition, as a way to give a clean semantics to multiple inheritance. From the perspective of traits semantics, such multiple inheritance is a straightforward generalization of that single inheritance semantics semantics.

A ClassDeclaration or ClassExpression defines a special constructor function to represent that class. Like a regular function, this constructor function is callable, inherits from Function.prototype, and has a typeof of “function”. Unlike a regular function, it has a [[Class]] of “Class”, has various other internal properties below specific to such constructor functions, and perhaps even directly inherits from Class.prototype which then directly inherits from Function.prototype. From here on, we refer to such a special constructor function as a class.

Class Adjectives

The ClassAdjectives, when present, clarify the role a given class is intended to serve. The abstract adjective indicates that this class is not intended to be directly instantiable. Unresolved requirements in an abstract class are no problem, since the class provides only a partial description.

The const adjective indicates that this class should provide high integrity. An abstract const class enforces non-instantiability — its internal [[Call]] and [[Construct]] methods only throw a TypeError when called. For non-abstract classes and for non-const abstract classes, their internal [[Call]] and [[Construct]] methods directly make the instance they describe, even though these may have unresolved requirements. Const classes are themselves frozen and they bring their class name into scope as a const variable, i.e., a non-assignable variable. For a const class C, C.prototype is also frozen, as are the direct instances of a non-abstract const class.

For a non-abstract class or a non-const abstract class, this constructor code is also the behavior of the class’ internal [[Call]] and [[Construct]] methods.

Errors

Semi-Static Errors

During execution of its ClassBody, a const class definition may fail by throwing a TypeError, in order to report a semi-static error. For a top level class, the initialization time is Program entry time, so such semi-static errors are effectively early errors. A non-abstract const class with unresolved conflicts or requirements reports an early error, so a successful non-abstract const class definition cannot have unresolved conflicts or requirements. For a const class, the class and its prototype are only frozen when the ClassBody exits.

Dynamic Errors

Required prototype properties define a public poisoned property on the prototype of the class on which they appear.

Early Errors

For a const class, every locally declared provided instance member must be “obviously” initialized and every declared required instance property must not be “obviously” initialized or an early SyntaxError is reported.

Chaining

For each built-in conventional constructor – such as Date, Array, and perhaps Function – where the “.call” pattern does not suffice, this class creates an instance that correctly behaves as a proper instance that built-in constructor function, except that they inherits only indirectly from that constructor function’s “prototype” property. Unfortunately, compilers from ES-next to ES5 cannot emulate this behavior by local rewriting.

Adapted Examples

To facilitate comparisons, the examples below are adapted from earlier or similar proposals. Since some of these are also adapted from each other, you may find some redundancy between these examples.

NOTE: At the present time, these examples are only valid for an earlier version of this proposal that made different syntactic choices. Remove this note when these examples are updated.