Skip to content
Cameron Purdy edited this page Apr 4, 2020 · 3 revisions

The AssertStatement is used to test a condition, and to raise an exception if that condition evaluates to False. For example, if a variable v must not be Null, then the following line of code asserts that v is not Null:

assert v != Null;

The condition is optional; omitting the condition is the same as using the constant False as the condition. That means that the following simple statement will always assert:

assert;

The AssertStatement conveniently allows for both variable declaration and conditional assignment. Consider the following method:

void printNext(Iterator<String> iter)
    {
    @Inject Console console;
    assert String s := iter.next();
    console.println($"next string={s}");
    }

Combined with type inference, an assertion can provide sufficient information to the compiler and to the runtime in order to ensure type safety via implicit narrowing:

String? name = loadName();
assert name != Null;
// name is now known to be a "String", not a "String?"
Int length = name.size;

By default, an assertion will be verified (i.e. "asserted") each time that it is encountered at runtime, but an assertion can be marked to run only during testing, during debugging, run only the first time that it is encountered, or even to run randomly for the purpose of sampling:

keyword execution behavior
assert:once Evaluated only once at runtime, the first time it is encountered
assert:rnd(x) Evaluated on average once every x times it is encountered
assert:test Evaluation only occurs during "test" mode
assert:debug Evaluation only occurs during "debug" mode; assertion results in a breakpoint

(Note that this behavior is different from the default behavior in most languages, which is to disable assertions at runtime in order to avoid any performance penalty caused by checking assertions. The Ecstasy design reflects a conscious effort to prioritize predictability and quality by default over the performance cost of checking assertions. Assertions can be disabled at runtime by explicitly using the assert:test keyword.)

When an assertion fails, it provides helpful runtime information that can be used to understand the cause of the failure after the fact. For example, consider the following assertion:

Int n = foo();
assert n.sign != Negative;

The resulting behavior at runtime will resemble the behavior of the following verbose code:

if (n.sign == Negative)
    {
    throw new IllegalState($"Assertion failed: \"n.sign != Negative\", n={n}, n.sign={n.sign}");
    }

A failed assertion throws an IllegalState exception by default.

keyword failed assertion behavior
assert:arg throws IllegalArgument exception
assert:bounds throws OutOfBounds exception
assert:TODO throws UnsupportedOperation exception
assert:debug debugger break-point

The ConditionList is reachable if the AssertStatement is reachable; a missing ConditionList is treated as if the ConditionList contained a single Condition with the constant value False. The first Condition in the ConditionList is reachable if the ConditionList is reachable. A Condition completes if is reachable and it is not the constant value False. Each subsequent Condition is reachable if the previous Condition completes. The ConditionList completes if the last Condition in the ConditionList completes, or if a reachable Condition short-circuits. The AssertStatement completes if the assertion keyword is assert:once, assert:rnd, assert:test, or assert:debug, or if the ConditionList completes.

Definite assignment rules:

  • The VAS before the ConditionList is the VAS before the assert statement.
  • If the ConditionList can short-circuit, then the VAS at each possible point of short-circuiting is joined with the VAST after the ConditionList.
  • The VAS after the assert statement is the VAST after the ConditionList.
    AssertStatement:
        AssertInstruction ConditionList-opt ;
    
    AssertInstruction:
        assert
        assert:arg
        assert:bounds
        assert:TODO
        assert:once
        assert:rnd( Expression )
        assert:test
        assert:debug

From IfStatement:

    ConditionList:
        Condition
        ConditionList , Condition

    Condition:
        Expression
        OptionalDeclaration ConditionalAssignmentOp Expression
        ( OptionalDeclarationList , OptionalDeclaration ) ConditionalAssignmentOp Expression

    ConditionalAssignmentOp:
        :=
        ?=

And from VariableStatement:

    OptionalDeclarationList:
        OptionalDeclaration
        OptionalDeclarationList , OptionalDeclaration

    OptionalDeclaration:
        Assignable
        VariableTypeExpression Name

    VariableTypeExpression:
        val
        var
        TypeExpression

    Assignable:
        Name
        TernaryExpression . Name
        TernaryExpression ArrayIndexes