Skip to content

Instantly share code, notes, and snippets.

@rikkimax
Last active January 12, 2024 06:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rikkimax/7864866c2439af7d74a7b0f02723fc3c to your computer and use it in GitHub Desktop.
Save rikkimax/7864866c2439af7d74a7b0f02723fc3c to your computer and use it in GitHub Desktop.

Attribute Contracts

When to Apply

Attribute contracts apply when the type checker fails to coerce and validate a function call, that has function or delegate pointer arguments.

An attribute contract does not change how a function's body and in contract gets typed checked. These get typed checked with the assumption that the stated attributes were valid during the call.

Sets

Sets are automatically generated by the compiler. One for each function parameter that is for a function or delegate pointer, one of the function itself, and one for the return type if its a function or delegate.

Each function parameter set, is accessed from the parameter variable name. The return set is accessed from return. The function's set is accessed from this.

If a function parameter, whose function or delegate pointer also has a parameter that is function or delegate pointer, it may be accessed by parameter.parameter.

Set Operations

Given a set, one of four operations take place upon it.

  1. Assignment. set = a;
  2. Assign the union of. set = union(a, b, c);
  3. Assign the Least Common Denominator (LCD). set = LCD(a, b, c);
  4. Require members in set, if not error. set.require(a, b, c);

The set on the left hand side, may be a sequence that will have the right hand side applied to each.

(a, b) = union(a, b);

Will store the result of union(a, b) into a, then b.

If the return type is not a function or delegate, assigning to the return set will do nothing. Using it as an argument it will be an empty set.

The arguments to any set operation may be another set or an attribute @safe nothrow @nogc pure @throw.

In Built Sets

An in built set is a sequence of all parameters that are functions or delegates, recursively. This may be referenced from __parameters. This will expand automatically into the providing operation for example it will if you provide this to assignment of a union will combine all attributes that each of the function or delegate parameters of the function has.

Set Instances

There are two instances of a given set.

The first is the stated attributes defined in the declaration, this is the default.

The second is the actual attributes, provided by the callee. It is accessed by using set.__input instead of set. This also works for in built sets and parameter of parameter sets.

Input attributes may not be mutated.

Defining a Contract

A contract is defined similarly to a in contract block on a function. It comes before the in block however.

void func()
contract {
    // contract
} do {
    // body
}

It does not get removed during usage of the D interface generator and is valid for functions without a body.

Contract statements are set operations.

You may define intermediary temporary sets. These do not need a special variable declaration.

Default Contract

The default contract, which is provided by the compiler when the contract block is not provided, its duty is to invalidate the attributes in the signature based upon the input functions attributes.

This may be modeled using the following code:

void func(void delegate() nothrow del) nothrow)
contract {
    (this, return, __parameters) = LCD(this, return, __parameters.__input);
} do {
    del();
}

Example

A declaration that binds all three function pointers together for their attributes.

void func(scope void delegate(void delegate() nothrow @nogc toCall) nothrow @nogc del) nothrow @nogc
contract {
    temporary = LCD(this, del.__input);
    (this, __parameters) = temporary;
    del.toCall = temporary;
} do {
    void handle() {
        // does nothing, but thats ok!
    }

    del(&handle);
}

A working function call:

func((/* throw @nogc */ del) /* @throw @gc */ {
    del();
    throw new Exception("Hah!");
});

But if this were to be called inside of a nothrow @nogc function it would fail. Due to the function func having had its signature changed on the callee side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment