Integer Ranges and Overflow Prevention

As part of Lumi goal #1 the compiler will enforce code that is without any integer overflow (or underflow).

Integer Ranges

In Lumi all integers are declared with explicit range Int{minimum:maximum} and the compiler enforces that it will always contain values inside this legal range.

For example, the compiler will not allow these assignments:

func compute(Int{0:20} x)->(Int{-10:10} y)
   y := 20  ; error - clearly out of range
   y := x  ; error - may be out of range

Assigning a constant or an integer with overlapped range are naturally legal, for example:

func compute(Int{-10:10} x)->(Int{-20:20} y)
   y := 10  ; legal - inside legal range
   y := x  ; legal - all legal values of x are overlapped by y range

Integer Arithmetic

The result of any arithmetic operation is an integer with a new range based on the operator. For example:

func compute(Int{6:12} x, Int{0:100} y)
    x + y  ; return range is Int{6:112}
    x - y  ; return range is Int{-12:94}
    x * y  ; return range is Int{0:1200}
    x div y  ; return range is Int{0:16}
    x mod y  ; return range is Int{0:11}

Compile Time Overflow Prevention

The compiler will not allow operation that may result in an overflow (or and underflow). For example:

func compute(Uint64 a, Uint64 b)
    a + b  ; error - potential overflow
    -a  ; error - potential underflow

Run-Time Overflow Checking

It is possible to check for an overflow in run-time using ! or ? together with one of + - * operators. For example:

~~~ raises an error when overflow detected ~~~
func ! raising-compute(Uint64 x, Uint64 y)->(Uint64 z)
    z := x +! y
    z := x -! y
    z := x *! y

func handling-compute(Uint64 a, Uint64 b)->(Uint64 z)
    if-error z := x +? y
        z := 0
    if-error z := x -? y
        z := 0
    if-error z := x *? y
        z := 0

Efficient Native Wraparound

Utilizing native overhead-free wraparound is supported by using wraparound keyword. The result of native wraparound is naturally limited only for the ranges that native wraparound is guaranteed to be supported: Uint8 Uint16 Uint32 Uint64 (Int{0:255} Int{0:65535} Int{0:4294967295} Int{0:18446744073709551615}). For other ranges wraparound must be done manually using ((value - min) mod (max - min + 1)) + min for example.

wraparound can be used as a single unary operator before assignment or together with one of += -= *= assignment operators:

func compute(Uint32 x)->(Uint32 y)
    y := wraparound x + 1
    y := compute(wraparound x - 1)
    y wraparound+= 1
    y wraparound-= 1
    y wraparound*= 1

The result range of using wraparound as above is the same as the target assignee range (Uint32 in the examples above). This means that the assigned range must always be one of Uint8 Uint16 Uint32 Uint64.

wraparound can also be used together with + - * operators:

func compute(Int{0:1000000} x, Uint64 y)->(Uint32 z)
    z := x wraparound+ y
    z := x wraparound- y
    z := x wraparound* y

The result range of using wraparound as above is the minimal from Uint8 Uint16 Uint32 Uint64 that overlaps the left operand (Uint32 from x in the examples above). This means that the left operand can be any unsigned range.

Clamping

Clamping allows shrinking an integer range to a smaller range Int{min:max} by converting any value larger than max to max and smaller than min to min. This can be done automatically using clamp keyword. Clamping is not overhead-free because the checking and converting must be done at run-time.

clamp can be used as a single unary operator before assignment or together with one of += -= *= assignment operators:

func compute(Uint32 x)->(Uint32 y)
    y := clamp x + 1
    y := compute(clamp x - 1)
    y clamp+= 1
    y clamp-= 1
    y clamp*= 1

Using clamp as above will clamp the result to the range of the target assignee (Uint32 in the examples above).

clamp can also be used together with + - * operators:

func compute(Uint32 x, Uint64 y)->(Uint32 z)
    z := x clamp+ y
    z := x clamp- y
    z := x clamp* y

Using clamp as above will clamp the result to the range of the left operand (Uint32 from x in the examples above).

On assignment it is possible to raise an error instead of clamping using ! or ? together with clamp. Whenever a value is too small or big for the assignee target range - instead of setting min or max an error is raised. For example:

~~~ raises an error when clamping ~~~
func ! raising-compute(Uint32 x)->(Uint32 y)
    y := clamp! x + 1
    y := raising-compute(clamp! x - 1)

func handling-compute(Uint32 x)->(Uint32 y)
    if-error y := clamp? x + 1
        y := 0

Sequences Index Integer Range

planned - not supported in TL5

It is planned to support a special range that is bound to a sequence and can only hold values that are legal indices to the sequence.

It may look like this:

func example(Array{Char} array)->(Char result)
    var Int{array} index
    result := array[index]  ; no need to check index at run-time