Skip to content

Generic declarations, instantiation, and conformance

TypeParameters = "<" identifier { "," identifier } ">" .
TypeArguments = "<" Type { "," Type } ">" .
GenericDeclaration = ( "struct" | "class" | "union" | "ref" "union" | "interface" | "func" | "static" )
identifier [ TypeParameters ] .
ClosedType = identifier TypeArguments .

E# generics are reified: a closed instantiation contains its actual type arguments in CLR metadata, rather than erasing them to object. Thus Box<int> and Box<string> are distinct closed types, and fields/methods use their substituted type directly.

struct Pair<A, B> { first: A, second: B }
let p = Pair<int, string> { first: 1, second: "one" }

Type identity includes name and arity. Cell<A> and Cell<A, B> are separate declarations, while a non-generic static Result facet can coexist with struct Result<T, E>.

A generic method has its own type parameters in addition to any on its containing type. Conformance is nominal after type-argument substitution: class Box<T> : IBox<T> implements the closed IBox<T> contract, not every IBox<U>. E# generic interfaces are invariant because the language has no variance declaration syntax; imported BCL interfaces retain their CLR variance.

A generic companion facet repeats the owner’s open parameter list and shares its name and arity:

struct Wrapper<T> { value: T }
static Wrapper<T> {
func ==(left: Wrapper<T>, right: Wrapper<T>) -> bool = left.value == right.value
func !=(left: Wrapper<T>, right: Wrapper<T>) -> bool = left.value != right.value
}

The operator itself cannot declare another type-parameter list. At a closed use such as Wrapper<int>, the owner’s T is substituted into the exact operator signature and the emitted call targets the closed generic host.