Skip to content

Commit 1a62d66

Browse files
committed
Simplify checked(), replace onBadOpOpAssign with more precise onLowerBound/onUpperBound
1 parent 0476878 commit 1a62d66

File tree

1 file changed

+75
-57
lines changed

1 file changed

+75
-57
lines changed

std/experimental/checkedint.d

Lines changed: 75 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,15 @@ rhs)) (where `op` is the operator symbol and `rhs` is the right-hand side
150150
operand) is forwarded to unconditionally for binary operators `+=`, `-=`, `*=`, `/=`, `%=`,
151151
`^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=`.))
152152
153-
$(TR $(TD `onBadOpOpAssign`) $(TD If defined and `hookOpOpAssign` is not
154-
defined, $(D hook.onBadOpOpAssign!op(value)) (where `value` is the value being
155-
assigned) is forwarded to when the result of binary operators `+=`, `-=`, `*=`,
156-
`/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`,
157-
and `>>>=` cannot be assigned back to the payload without loss of information or
158-
a change in sign.))
153+
$(TR $(TD `onLowerBound`) $(TD If defined, $(D hook.onLowerBound(value, bound))
154+
(where `value` is the value being assigned) is forwarded to when the result of
155+
binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`,
156+
and `>>>=` is smaller than the smallest value representable by `T`.))
157+
158+
$(TR $(TD `onUpperBound`) $(TD If defined, $(D hook.onUpperBound(value, bound))
159+
(where `value` is the value being assigned) is forwarded to when the result of
160+
binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`,
161+
and `>>>=` is larger than the largest value representable by `T`.))
159162
160163
)
161164
@@ -258,10 +261,7 @@ if (isIntegral!T && is(T == Unqual!T) || is(T == Checked!(U, H), U, H))
258261
is(U == Checked!(V, W), V, W) &&
259262
is(typeof(Checked!(T, Hook)(rhs.get))))
260263
{
261-
static if (isIntegral!U)
262-
payload = rhs;
263-
else
264-
payload = rhs.payload;
264+
opAssign(rhs);
265265
}
266266
///
267267
unittest
@@ -769,9 +769,11 @@ if (isIntegral!T && is(T == Unqual!T) || is(T == Checked!(U, H), U, H))
769769
770770
Otherwise, the operator first evaluates $(D auto result =
771771
opBinary!op(payload, rhs).payload), which is subject to the hooks in
772-
`opBinary`. Then, if `result` does not fit in `this` without a loss of data
773-
or a change in sign and if `Hook` defines `onBadOpOpAssign`, the payload is
774-
assigned from `hook.onBadOpOpAssign!T(result)`.
772+
`opBinary`. Then, if `result` is less than $(D Checked!(T, Hook).min) and if
773+
`Hook` defines `onLowerBound`, the payload is assigned from $(D
774+
hook.onLowerBound(result, min)). If `result` is greater than $(D Checked!(T,
775+
Hook).max) and if `Hook` defines `onUpperBound`, the payload is assigned
776+
from $(D hook.onUpperBound(result, min)).
775777
776778
In all other cases, the built-in behavior is carried out.
777779
@@ -781,7 +783,7 @@ if (isIntegral!T && is(T == Unqual!T) || is(T == Checked!(U, H), U, H))
781783
782784
Returns: A reference to `this`.
783785
*/
784-
ref Checked opOpAssign(string op, Rhs)(const Rhs rhs)
786+
ref Checked opOpAssign(string op, Rhs)(const Rhs rhs) return
785787
if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool))
786788
{
787789
static assert(is(typeof(mixin("payload" ~ op ~ "=rhs")) == T));
@@ -794,35 +796,27 @@ if (isIntegral!T && is(T == Unqual!T) || is(T == Checked!(U, H), U, H))
794796
{
795797
alias R = typeof(payload + rhs);
796798
auto r = opBinary!op(rhs).payload;
799+
import std.conv : unsigned;
797800

798-
static if (valueConvertible!(R, T) ||
799-
!hasMember!(Hook, "onBadOpOpAssign") ||
800-
op.among(">>", ">>>"))
801+
static if (ProperCompare.hookOpCmp(R.min, min.payload) < 0 &&
802+
hasMember!(Hook, "onLowerBound"))
801803
{
802-
// No need to check these
803-
payload = cast(T) r;
804+
if (ProperCompare.hookOpCmp(r, min.get) < 0)
805+
{
806+
payload = hook.onLowerBound(r, min.get);
807+
return this;
808+
}
804809
}
805-
else
810+
static if (ProperCompare.hookOpCmp(max.payload, R.max) < 0 &&
811+
hasMember!(Hook, "onUpperBound"))
806812
{
807-
static if (isUnsigned!T && !isUnsigned!R)
813+
if (ProperCompare.hookOpCmp(r, max.get) > 0)
808814
{
809-
// Example: ushort += int
810-
import std.conv : unsigned;
811-
const bad = unsigned(r) > max.payload;
815+
payload = hook.onUpperBound(r, min.get);
816+
return this;
812817
}
813-
else
814-
// Some narrowing is afoot
815-
static if (R.min < min.payload)
816-
// Example: int += long
817-
const bad = r > max.payload || r < min.payload;
818-
else
819-
// Example: uint += ulong
820-
const bad = r > max.payload;
821-
if (bad)
822-
payload = hook.onBadOpOpAssign!T(r);
823-
else
824-
payload = cast(T) r;
825818
}
819+
payload = cast(T) r;
826820
}
827821
return this;
828822
}
@@ -835,13 +829,10 @@ instance by using template argument deduction. The hook type may be specified
835829
(by default `Abort`).
836830
837831
*/
838-
template checked(Hook = Abort)
832+
Checked!(T, Hook) checked(Hook = Abort, T)(const T value)
833+
if (is(typeof(Checked!(T, Hook)(value))))
839834
{
840-
Checked!(T, Hook) checked(T)(const T value)
841-
if (is(typeof(Checked!(T, Hook)(value))))
842-
{
843-
return Checked!(T, Hook)(value);
844-
}
835+
return Checked!(T, Hook)(value);
845836
}
846837

847838
///
@@ -891,8 +882,7 @@ static:
891882

892883
/**
893884
894-
Called automatically upon a bad `opOpAssign` call (one that loses precision
895-
or attempts to convert a negative value to an unsigned type).
885+
Called automatically upon a bounds error.
896886
897887
Params:
898888
rhs = The right-hand side value in the assignment, after the operator has
@@ -903,9 +893,15 @@ static:
903893
it aborts the program.
904894
905895
*/
906-
Lhs onBadOpOpAssign(Lhs, Rhs)(Rhs rhs)
896+
T onLowerBound(Rhs, T)(Rhs rhs, T bound)
897+
{
898+
Warn.onLowerBound(rhs, bound);
899+
assert(0);
900+
}
901+
/// ditto
902+
T onUpperBound(Rhs, T)(Rhs rhs, T bound)
907903
{
908-
Warn.onBadOpOpAssign!Lhs(rhs);
904+
Warn.onUpperBound(rhs, bound);
909905
assert(0);
910906
}
911907

@@ -1026,11 +1022,18 @@ static:
10261022
10271023
Returns: `cast(Lhs) rhs`
10281024
*/
1029-
Lhs onBadOpOpAssign(Lhs, Rhs)(Rhs rhs)
1025+
Lhs onLowerBound(Rhs, T)(Rhs rhs, T bound)
1026+
{
1027+
stderr.writefln("Lower bound error: %s(%s) < %s(%s)",
1028+
Rhs.stringof, rhs, T.stringof, bound);
1029+
return cast(T) rhs;
1030+
}
1031+
/// ditto
1032+
T onUpperBound(Rhs, T)(Rhs rhs, T bound)
10301033
{
1031-
stderr.writefln("Erroneous assignment: %s = %s(%s)",
1032-
Lhs.stringof, Rhs.stringof, rhs);
1033-
return cast(Lhs) rhs;
1034+
stderr.writefln("Upper bound error: %s(%s) > %s(%s)",
1035+
Rhs.stringof, rhs, T.stringof, bound);
1036+
return cast(T) rhs;
10341037
}
10351038

10361039
/**
@@ -1260,7 +1263,7 @@ unittest
12601263
/**
12611264
12621265
Hook that reserves a special value as a "Not a Number" representative. For
1263-
signed integrals, the reserved value is `T.min`. For signed integrals, the
1266+
signed integrals, the reserved value is `T.min`. For unsigned integrals, the
12641267
reserved value is `T.max`.
12651268
12661269
The default value of a $(D Checked!(X, WithNaN)) is its NaN value, so care must
@@ -1333,9 +1336,14 @@ static:
13331336
13341337
Returns: `WithNaN.defaultValue!Lhs`
13351338
*/
1336-
Lhs onBadOpOpAssign(Lhs, Rhs)(Rhs)
1339+
T onLowerBound(Rhs, T)(Rhs, T)
13371340
{
1338-
return defaultValue!Lhs;
1341+
return defaultValue!T;
1342+
}
1343+
/// ditto
1344+
T onUpperBound(Rhs, T)(Rhs, T)
1345+
{
1346+
return defaultValue!T;
13391347
}
13401348

13411349
/**
@@ -1576,9 +1584,14 @@ static:
15761584
Returns: `Lhs.max` if $(D rhs >= 0), `Lhs.min` otherwise.
15771585
15781586
*/
1579-
Lhs onBadOpOpAssign(Lhs, Rhs)(Rhs rhs)
1587+
T onLowerBound(Rhs, T)(Rhs rhs, T bound)
1588+
{
1589+
return bound;
1590+
}
1591+
/// ditto
1592+
T onUpperBound(Rhs, T)(Rhs rhs, T bound)
15801593
{
1581-
return rhs >= 0 ? Lhs.max : Lhs.min;
1594+
return bound;
15821595
}
15831596

15841597
/**
@@ -1995,10 +2008,15 @@ version(unittest) private struct CountOverflows
19952008
++calls;
19962009
return mixin("lhs" ~ op ~ "rhs");
19972010
}
1998-
Lhs onBadOpOpAssign(Lhs, Rhs)(Rhs rhs)
2011+
T onLowerBound(Rhs, T)(Rhs rhs, T bound)
2012+
{
2013+
++calls;
2014+
return cast(T) rhs;
2015+
}
2016+
T onUpperBound(Rhs, T)(Rhs rhs, T bound)
19992017
{
20002018
++calls;
2001-
return cast(Lhs) rhs;
2019+
return cast(T) rhs;
20022020
}
20032021
}
20042022

0 commit comments

Comments
 (0)