Type

Grammar§

Type ::= TypeAuto | TypeVoid | TypeSpecified ;

TypeAuto§

Grammar§

TypeAuto ::= "auto"

Semantics§

A wildcard to denote a type that will be replaced by another one during the semantic pass.

TypeVoid§

Grammar§

TypeVoid ::= "()"

Semantics§

The type for function that return nothing. It has no correpsonding value and cannot be used to declare variables.

TypeSpecified§

Grammar§

TypeSpecified ::= TypeUnambiguous | TypeTuple | TypeModified | TypeRaw ;

Semantics§

All the types, excluding TypeAuto

TypeUnambiguous§

Grammar§

TypeUnambiguous ::= "(" TypeSpecified ")"

Semantics§

A type can be enclosed between parens. If in some case this gives a better readability this also allows to express certain function types.

// no way to express that the type modifiers are not for the return type
var function (): s32*[2] a;
// no way to express that the static storage class is for the function type
var static function()* b;

With the help of parentheses this becomes possible

// The type identifier is a function that returns a s32
// The modified type is a static array of pointer to functions that return s32
var (function(): s32)*[2] a;
// static is for b type, not b itself.
var (static function()*) b;

TypeTuple§

Grammar§

TypeTuple ::= "(" Type ("," Type")* ")"

Semantics§

Define a type made of one or more types.

Expressions of this type are evaluable as a condition, that is “if more than one type”.

TypeRaw§

Grammar§

TypeRaw ::= BasicType | TypeFunction | TypeIdentifier | TypeFrom | TypeEcho ; BasicType ::= "bool" | "u8" | "u16" | "u32" | "u64" | "usize" | "s8" | "s16" | "s32" | "s64" | "ssize" | "f32" | "f64" | "char" ; TypeFunction ::= FunctionDeclaration TypeIdentifier ::= (TypeAppliedGeneric | Identifier) ( "." TypeIdentifier )? TypeFrom ::= FromExpression "." TypeIdentifier TypeEcho ::= EchoExpression

A type is always made of a raw type that can be a keyword (for the builtin types), a function type or finally a chain of identifiers.

bool§

Unsigned 1 bit integer used to represent true and false in logical and relational expressions.

Expressions of this type are evaluable as a condition, that is “if not zero”.

Expressions of this type that are used in arithmetic [BinaryExpression] are exceptionally promoted to the smaller unsigned integral type, u8.

u8 u16 u32 u64 usize§

Unsigned integer types, with respectively a size of 8, 16, 32 or 64 bits. usize has the size of a pointer on the targeted architecture.

Symbols and expressions that have these types are evaluable as a condition, that is “if not zero”.

s8 s16 s32 s64 ssize§

Signed integer types, with respectively a size of 8, 16, 32 or 64 bits. ssize has the size of a pointer on the targeted architecture.

Symbols and expressions that have these types are evaluable as a condition, that is “if not zero”.

char§

Similar to u8 but used type of the StringExpression, so that string manipulation is distinguishable.

TypeFunction§

A function type can be identified using the same syntax as a FunctionDeclaration. The body must not be specified and the name is optional

alias F = function();

Symbols and expressions of these types are not evaluable as a condition.

Function types are usually used with a pointer as modifier.

static function f(){}
var (static function())* fPtr = &f;

TypeIdentifier§

Type identifiers form a chain. Parts are resolved during semantic to either classes, structures, aliases, overloads, enums or units.

struct F { static struct B {} }
var F.B* fbPtr; // pointer to a struct F.B

Even if units are not types, the chain can start with unit identifiers

import a.b;
var a.b.F f; // f resolves to a type declared in a.b

as only the last chain element has to give a type.

If the last part identifier is .expand and that the previous part gave a TypeTuple then the resulting type is the parent tuple with the tuple expanded, non-transitively.

alias T0 = (s16, s32);
alias T1 = (s8, T0, s32); // (s8, (s16, s32), s64)
alias T1 = (s8, T0.expand, s32); // (s8, s16, s32, s64)

The expanssion is not available on TypeTuple literals, only on alias to them.

TypeFrom§

Resolve to the TypeIdentifier in the unit soecified by the FromExpression.

TypeEcho§

This type wraps an EchoExpression. If the expression yields a TypeExpression then it is replaced with the expression type.

Valid compiler echoes are func_t, return, getPointedType, and type.

Any other echo command is rejected.

TypeModified§

Grammar§

TypeModified ::= TypePointer | TypeStaticArray | TypeElements | TypeRcArray | TypeSlice | TypeEnumSet ;

Semantics§

Types derived from TypeModified support the IndexExpression.

TypePointer§

Grammar§

TypePointer ::= TypeSpecified "*"

Semantics§

Gives a type that is a pointer to another type.

struct S {}
var S* s;  // s is a pointer to a S instance

Symbols and expressions that have these types are evaluable as a condition, that is “if not null”.

TypeStaticArray§

Grammar§

TypeStaticArray ::= TypeSpecified "[" Expression "]"

Semantics§

Define the type for a fixed length array.

If the modified type is a TypeTuple and if the expression is a RangeExpression then the type is tried as a TypeElements.

The expression must be simplifiable to an IntegerExpression at compile-time.

If this condition is not verified then the type is tried as a TypeEnumSet.

struct S {}
var S*[2] s;  // s is a fixed length vector of two pointers to S instances

Similarly to TypeSlice, static arrays support the .length and .ptr properties but being value types they are syntactic shortcuts that yield expressions that, contrary to TypeSlice, involves no indirections.

TypeElements§

Grammar§

TypeElements ::= TypeSpecified "[" Expression ("," Expression)* "]"

Semantics§

Define a TypeTuple by selecting elements in another one.

The first type must give a TypeTuple.

After evaluation, each expression must either give an IntegerExpression or a RangeExpression.

If an expression gives an IntegerExpression then the type located at the position given by its value is appended to the target TypeTuple.

If an expression gives a RangeExpression then the two sides of the range must give an IntegerExpression. The types located between the position given by the left value to the right value minus one are appended to the target TypeTuple.

alias T0 = (s8, s16, s32, s64);

alias T1 = T0[3,2,1,0];   // s64 s32 s16 s8
alias T2 = T0[1 .. $];    // s16 s32 s64

It is an error to select types using out-of-bounds indexes.

TypeRcArray§

Grammar§

TypeRcArray ::= TypeSpecified "[" "+" "]"

Semantics§

Define the type for a variable length array with automatic reference counting at run-time.

Reference counted arrays are non dereferencable pointer to heap-allocated chunks. They are made of a usize reference count, a usize length, and the variable length content. The fields are respecectively accessible using the .refcount, .length and the .ptr DotExpression properties. The Additional DotExpression properties .decref, .incref give a low-level control of the counting.

Expressions that have this type are evaluable as a condition, that is “if length is not zero”.

The count is automatically incremented

It is automatically decremented

If the count is 0, then the array has no reference, only an owner.

If the count reaches -1, then array is automatically released to the allocator.

When the length of a refcounted array is modified or when elements are appened the array is firstly copied.

The reference that requested the modification is reassigned to the mutated copy.

var s64[+] a = [1,2];   // a refcount == 0
var s64 b;              // b refcount == 0
var s64[+] b = a;       // a & b refcount == 1 and point to the same array
b.length += 2;          // a & b refcount == 0, each point to a different array

Operations on mutable rvalue parameters are done on a distinct copy that’s created on function entry.

function f(u64[+] p)
{
    // hidden `p = p.dup;`
    p.length += 1;
}
var u64[+] v = [0];
f(p);
assert(v.length == 1 && v[0] == 0);

Operations on lvalue parameters transparently affect the reference

function f(var u64[+] p)
{
    p.length += 1;
}
var u64[+] v = [0];
f(p);
assert(v.length == 2);

Operations on const rvalue parameters are limited to the array elements and transparently affect the elements of the reference that was passed. It is however not possible to corrupt the .ptr of the argument.

function f(const u64[+] p)
{
    p[0] = 1;       // OK, does not currupt the original argument .ptr
    p.length += 1;  // NG, would corrupt `v.ptr`
}
var u64[+] v = [0];
f(p);

It is illegal to escape the .ptr of a refcounted array or to assign it to a variable with a longer lifetime.

In the context of the a ReturnStatement this would break a protection that’s setup to prevent local cleanup.

function ensureNullTerminated(char[] str): char*
{
    var char[+] result = str;
    if const usize len = str.length do
    {
        if str[len - 1] != 0 do
            result ~= 0;
    }
    // the following ReturnStatement is actually decomposed in 3 steps
    //
    // 1. evaluate the expression : `var auto r = result.ptr;`
    // 2. stack cleanup           : `result.decref;`
    // 3. real return             : return r;
    return result.ptr;
}

In this situation manual increment of the reference count will preserve the memory but will also create a leak.

Only local refcounted arrays are automatically managed.

Static and members must be freed using destructors.

struct S
{
    var u64[+] member;
    @destructor function destroy()
    {
        member.decref;
    }
}

A refcounted array can be copied using the dup DotExpression property. The initial count of a dup is -1, so a dup should always be assigned to a lvalue.

A refcounted array can be constructed using a [CallExppression] on a NewExpression. The call must have exactly one argument, which sets the length of the leftmost dim.

var A a = (new u64[+];)(10);
assert(a.length == 10);

Cycles between refcounted arrays are undefined behavior

var auto a = (new u64[+])(1);
a = a; // cycle
assert(a.length == 1); // fails, a.length is undefined

TypeSlice§

Grammar§

TypeSlice ::= TypeSpecified "[" "]"

Semantics§

The type of a bounds checked view on a a pointer, a static array, or a rc array, and with a stack allocated payload.

function test(char* ptr; usize length)
{
    var char[] slice = ptr[0 .. length];
    while slice.length != 0 do
        slice = slice[1 .. slice.length];
}

Slices are represented using a payload made of a usize length and a TypePointer ptr. They are respecectively accessible using the .length and the .ptr DotExpression properties.

Expressions of this type are evaluable as a condition that is “if length is not zero”.

TypeSlice does not allocate, does not require managment but has limited capabilities. A variable of TypeSlice

Note on lifetime§

While it is not disallowed to declare member of type TypeSlice, as valid use cases may be possible, it should be noted that this can easily cause lifetime issues.

In the following example member may have a longer life time than the source p memory, causing invalid reads, either because of reallocated or free’d, out-of-scope stack memory.

struct S
{
    var char[] member;
    function f(char[] p)
    {
        member = p;
    }
}

It is preferable to use TypeRcArray members so that memory remains alive at least a long as the aggregate instance.

struct S
{
    var char[+] member; // keep alive for the instance

    // better if called with an arg that's rc array
    function f1(char[+] p)
    {
        member = p;
    }

    // alternatively
    function f2(char[] p)
    {
        member = p;
    }

    @destructor function destroy()
    {
        member.decref;
    }
}

TypeEnumSet§

Grammar§

TypeEnumSet ::= TypeSpecified "[" Expression "]"

Semantics§

If a TypeStaticArray is for a bool and that the expression that gives the vector length resolves to an EnumDeclaration then the type becomes TypeEnumSet.

This type defines a fixed-length bit vector. The vector size is defined by the EnumDeclaration type. Its members must be ordered, and the last member value must no exceed the vector bit size.

The expressions allowed on TypeEnumSet are limited to

enum Direction
{
    N, E, S, W
}

alias Directions = bool[Direction];

@unittest function t1()
{
    Directions dir;
    dir += Direction.N;
    dir += Direction.W;
    assert(Direction.N in dir);
    assert(dir[Direction.W]);
    assert(Directions.max == [N,E,S,W]); // 1 + 2 + 4 + 8
}

Note that the members value is never used, instead their rank is used to left shift 1.

The membership of the value used in TypeEnumSet operations is checked during compilation, so that only literals are accepted when using an extended enum.

enum Base {b1}

enum Extended : Base {b2}

function test()
{
    var bool[Base] bb;
    bb[Extended.b1] = true; // OK
    var Extended e;
    bb[e] = true;           // NG, membership is not verified
}

Other internal types§

TypeNull§

The initial type of the NullExpression.

It is generally cast to to another type so it is not required to name it.

// null alone is of TypeNull but of type char* after implicit conversion
var char* v = null;

However TypeNull can be specified by two ways

  1. By inference as function return type:
function f(): auto // is replaced by the internal TypeNull
{
    return null;
}
  1. Using a TypeEcho
function f(): echo(type, null)
{
    return null;
}

TypeReturn§

A context specific type allowing functions to optionally return lvalues, see TypeReturn.

TypeDeclared§

The declaration that a TypeIdentifier resolve to is always associated to an internal type : ClassDeclaration is linked to a TypeClass, EnumDeclaration to a TypeEnum, and so on.