Nordlöw:

The simplest example is probably the constructor for Bound defined something like

Very good, with this you have framed the discussion well :-)


- value range information is propagated to the constructor argument `value`

The problem is, I think this is currently false (even if you call your constructor with just a number literal). I'd like this feature in D, but I don't know how much work it needs to be implemented.

D language is designed to allow you to create data structures in library code able to act a lot like built-in features (so there's a refined operator overloading, opCall, static opCall, and even more unusual things like opDispatch), but there are built-in features that can't yet be reproduced in library code, observe:


void main() {
    int[5] arr;
    auto x = arr[7];
}


dmd 2.066alpha gives a compile error:

test.d(3,14): Error: array index 7 is out of bounds arr[0 .. 5]


In D there is opIndex to overload the [ ], but its arguments are run-time values, so I think currently they have no way to give a compile-time error if you use an index that is known statically to be outside the bounds. So currently you can't reproduce that behavour with library code. Propagating the value range information to the constructor, plus the new __trait(valueRange, exp), allow to solve this problem. And indeed this allows to implement nice ranged values like in Ada, and to do what the Static_Predicate of Ada does.


Another common example of what you currently can't do with library code:

void main() {
    import std.bigint;
    BigInt[] arr = [10, 20];

    import std.numeric;
    alias I10 = Bound!(int, 0, 10);
    I10[] arr = [8, 6, 20];
}


(In theory the compiler should also catch at compile time that bug, because 20 is outside the valid bounds of I10.)


The "enum precondition" I've suggested elsewhere is a generalization of that feature (but it's not a strict subset because it only works with literals, so it's good to have both features), because it manages not just ranges of a single value, but also other literals, like an array:


void foo(int[] arr)
enum in {
    assert(arr.all!(x => x < 10));
} in {
    assert(arr.all!(x => x < 10));
} body {
    // ...
}
void main() {
    foo([10, 20]); // gives compile-time error.
}


This is possible only if the source code of foo is available in the compilation unit. In presence of separated compilation the enum precondition is ignored. So the enum preconditon can't replace regular pre-conditions, they are useful to catch statically only a subset of bugs. The same happens with the idea of propagating range information to the constructors. One way to avoid or mitigate this problem is to leave available the source code of functions with an enum pre-conditions, just like with templates. Perhaps this is not a big problem because enum preconditions and constructor value range propagation are meant to be used mostly in library code, like in Bound integers, etc.

Bye,
bearophile

Reply via email to