Open tauoverpi opened 2 years ago
Error sets already support narrowing so long as you capture. Example that works today:
test "ErrorSet narrowing" {
try funcA();
}
fn funcA() error{ A, C }!void {
return funcB() catch |err| switch (err) {
// else => err, // error: expected type 'error{C,A}', found 'error{C,A,B}'
else => |e| e,
error.B => {},
};
}
fn funcB() error{ A, B, C }!void {
return error.B;
}
But the same trick does not work on integers:
test "integer narrowing" {
var in: usize = 42;
_ = ∈
const out: u8 = switch (in) {
0...255 => |byte| byte, // error: expected type 'u8', found 'usize'
else => 24,
};
try std.testing.expect(in == out);
}
An observation that with the new panic interface it's possible to disallow integer overflow as an opt-in. The above provides one possible path to prove overflow doesn't occur by forcing the use of assert
or any other way to divert control-flow when it would. There are likely other proposals that are more suitable though.
Related to #12863
Instead of narrowing (creating a new type) one could attach the reduced range to the type within the block and treat any branches outside of that range as dead code without changing the underlying type. Consider the example given:
Here
@TypeOf(narrow) == E
while the range ofnarrow
only includes the laterc
andd
tags. If we changee
to beconst
then the range ofe
within the same scope thatnarrow
is introduced is equivalent but the underlying type remains the same.This can be expanded upon to include other forms of branching such as:
Thus zig can learn the range of a value at compile-time by keeping track of the set/range of possible values that remain within a branch.
By keeping the same underlying type it's possible to operate on integers :
And implicitly cast based on the range:
If the range of a value on the right hand side of a truncating operation is comptime-known then the range of the result is guaranteed to be below the top of the range thus any use of
%
and&
narrows the range of the type. The same applies tocomptime_int
on the right hand side:The same applies for addition and multiplication:
Since the type of the value doesn't change, the value can still be passed safely to functions without casts:
However integers can be narrowed when the range of the value is within the range of a smaller destination type:
Note that if the range is larger than that of the underlying type then overflow is possible.
Operators
Thus the standard operators should now result in:
note: the
range(n...m)
syntax is not part of the proposalExtension
For safety, allow function parameters and return types to express the range desired and throw a compile-error when the range cannot be satisfied:
or maybe as an expression:
However ranges for parameters/return types requires more thought.