RFC: Value range propagation for if-else

bearophile via Digitalmars-d digitalmars-d at puremagic.com
Wed Jun 18 15:08:06 PDT 2014


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


More information about the Digitalmars-d mailing list