xref: /netbsd-src/external/gpl3/gcc.old/dist/libphobos/src/std/experimental/checkedint.d (revision 627f7eb200a4419d89b531d55fccd2ee3ffdcde0)
1*627f7eb2Smrg /**
2*627f7eb2Smrg $(SCRIPT inhibitQuickIndex = 1;)
3*627f7eb2Smrg 
4*627f7eb2Smrg This module defines facilities for efficient checking of integral operations
5*627f7eb2Smrg against overflow, casting with loss of precision, unexpected change of sign,
6*627f7eb2Smrg etc. The checking (and possibly correction) can be done at operation level, for
7*627f7eb2Smrg example $(LREF opChecked)$(D !"+"(x, y, overflow)) adds two integrals `x` and
8*627f7eb2Smrg `y` and sets `overflow` to `true` if an overflow occurred. The flag `overflow`
9*627f7eb2Smrg (a `bool` passed by reference) is not touched if the operation succeeded, so the
10*627f7eb2Smrg same flag can be reused for a sequence of operations and tested at the end.
11*627f7eb2Smrg 
12*627f7eb2Smrg Issuing individual checked operations is flexible and efficient but often
13*627f7eb2Smrg tedious. The $(LREF Checked) facility offers encapsulated integral wrappers that
14*627f7eb2Smrg do all checking internally and have configurable behavior upon erroneous
15*627f7eb2Smrg results. For example, `Checked!int` is a type that behaves like `int` but aborts
16*627f7eb2Smrg execution immediately whenever involved in an operation that produces the
17*627f7eb2Smrg arithmetically wrong result. The accompanying convenience function $(LREF
18*627f7eb2Smrg checked) uses type deduction to convert a value `x` of integral type `T` to
19*627f7eb2Smrg `Checked!T` by means of `checked(x)`. For example:
20*627f7eb2Smrg 
21*627f7eb2Smrg ---
22*627f7eb2Smrg void main()
23*627f7eb2Smrg {
24*627f7eb2Smrg     import std.experimental.checkedint, std.stdio;
25*627f7eb2Smrg     writeln((checked(5) + 7).get); // 12
26*627f7eb2Smrg     writeln((checked(10) * 1000 * 1000 * 1000).get); // Overflow
27*627f7eb2Smrg }
28*627f7eb2Smrg ---
29*627f7eb2Smrg 
30*627f7eb2Smrg Similarly, $(D checked(-1) > uint(0)) aborts execution (even though the built-in
31*627f7eb2Smrg comparison $(D int(-1) > uint(0)) is surprisingly true due to language's
32*627f7eb2Smrg conversion rules modeled after C). Thus, `Checked!int` is a virtually drop-in
33*627f7eb2Smrg replacement for `int` useable in debug builds, to be replaced by `int` in
34*627f7eb2Smrg release mode if efficiency demands it.
35*627f7eb2Smrg 
36*627f7eb2Smrg `Checked`  has customizable behavior with the help of a second type parameter,
37*627f7eb2Smrg `Hook`. Depending on what methods `Hook` defines, core operations on the
38*627f7eb2Smrg underlying integral may be verified for overflow or completely redefined. If
39*627f7eb2Smrg `Hook` defines no method at all and carries no state, there is no change in
40*627f7eb2Smrg behavior, i.e. $(D Checked!(int, void)) is a wrapper around `int` that adds no
41*627f7eb2Smrg customization at all.
42*627f7eb2Smrg 
43*627f7eb2Smrg This module provides a few predefined hooks (below) that add useful behavior to
44*627f7eb2Smrg `Checked`:
45*627f7eb2Smrg 
46*627f7eb2Smrg $(BOOKTABLE ,
47*627f7eb2Smrg     $(TR $(TD $(LREF Abort)) $(TD
48*627f7eb2Smrg         fails every incorrect operation with a message to $(REF
49*627f7eb2Smrg         stderr, std, stdio) followed by a call to `assert(0)`. It is the default
50*627f7eb2Smrg         second parameter, i.e. `Checked!short` is the same as
51*627f7eb2Smrg         $(D Checked!(short, Abort)).
52*627f7eb2Smrg     ))
53*627f7eb2Smrg     $(TR $(TD $(LREF Throw)) $(TD
54*627f7eb2Smrg         fails every incorrect operation by throwing an exception.
55*627f7eb2Smrg     ))
56*627f7eb2Smrg     $(TR $(TD $(LREF Warn)) $(TD
57*627f7eb2Smrg         prints incorrect operations to $(REF stderr, std, stdio)
58*627f7eb2Smrg         but otherwise preserves the built-in behavior.
59*627f7eb2Smrg     ))
60*627f7eb2Smrg     $(TR $(TD $(LREF ProperCompare)) $(TD
61*627f7eb2Smrg         fixes the comparison operators `==`, `!=`, `<`, `<=`, `>`, and `>=`
62*627f7eb2Smrg         to return correct results in all circumstances,
63*627f7eb2Smrg         at a slight cost in efficiency. For example,
64*627f7eb2Smrg         $(D Checked!(uint, ProperCompare)(1) > -1) is `true`,
65*627f7eb2Smrg         which is not the case for the built-in comparison. Also, comparing
66*627f7eb2Smrg         numbers for equality with floating-point numbers only passes if the
67*627f7eb2Smrg         integral can be converted to the floating-point number precisely,
68*627f7eb2Smrg         so as to preserve transitivity of equality.
69*627f7eb2Smrg     ))
70*627f7eb2Smrg     $(TR $(TD $(LREF WithNaN)) $(TD
71*627f7eb2Smrg         reserves a special "Not a Number" (NaN) value akin to the homonym value
72*627f7eb2Smrg         reserved for floating-point values. Once a $(D Checked!(X, WithNaN))
73*627f7eb2Smrg         gets this special value, it preserves and propagates it until
74*627f7eb2Smrg         reassigned. $(LREF isNaN) can be used to query whether the object
75*627f7eb2Smrg         is not a number.
76*627f7eb2Smrg     ))
77*627f7eb2Smrg     $(TR $(TD $(LREF Saturate)) $(TD
78*627f7eb2Smrg         implements saturating arithmetic, i.e. $(D Checked!(int, Saturate))
79*627f7eb2Smrg         "stops" at `int.max` for all operations that would cause an `int` to
80*627f7eb2Smrg         overflow toward infinity, and at `int.min` for all operations that would
81*627f7eb2Smrg         correspondingly overflow toward negative infinity.
82*627f7eb2Smrg     ))
83*627f7eb2Smrg )
84*627f7eb2Smrg 
85*627f7eb2Smrg 
86*627f7eb2Smrg These policies may be used alone, e.g. $(D Checked!(uint, WithNaN)) defines a
87*627f7eb2Smrg `uint`-like type that reaches a stable NaN state for all erroneous operations.
88*627f7eb2Smrg They may also be "stacked" on top of each other, owing to the property that a
89*627f7eb2Smrg checked integral emulates an actual integral, which means another checked
90*627f7eb2Smrg integral can be built on top of it. Some combinations of interest include:
91*627f7eb2Smrg 
92*627f7eb2Smrg $(BOOKTABLE ,
93*627f7eb2Smrg     $(TR $(TD $(D Checked!(Checked!int, ProperCompare))))
94*627f7eb2Smrg     $(TR $(TD
95*627f7eb2Smrg defines an `int` with fixed
96*627f7eb2Smrg comparison operators that will fail with `assert(0)` upon overflow. (Recall that
97*627f7eb2Smrg `Abort` is the default policy.) The order in which policies are combined is
98*627f7eb2Smrg important because the outermost policy (`ProperCompare` in this case) has the
99*627f7eb2Smrg first crack at intercepting an operator. The converse combination $(D
100*627f7eb2Smrg Checked!(Checked!(int, ProperCompare))) is meaningless because `Abort` will
101*627f7eb2Smrg intercept comparison and will fail without giving `ProperCompare` a chance to
102*627f7eb2Smrg intervene.
103*627f7eb2Smrg     ))
104*627f7eb2Smrg     $(TR $(TD))
105*627f7eb2Smrg     $(TR $(TDNW $(D Checked!(Checked!(int, ProperCompare), WithNaN))))
106*627f7eb2Smrg     $(TR $(TD
107*627f7eb2Smrg defines an `int`-like
108*627f7eb2Smrg type that supports a NaN value. For values that are not NaN, comparison works
109*627f7eb2Smrg properly. Again the composition order is important; $(D Checked!(Checked!(int,
110*627f7eb2Smrg WithNaN), ProperCompare)) does not have good semantics because `ProperCompare`
111*627f7eb2Smrg intercepts comparisons before the numbers involved are tested for NaN.
112*627f7eb2Smrg     ))
113*627f7eb2Smrg )
114*627f7eb2Smrg 
115*627f7eb2Smrg The hook's members are looked up statically in a Design by Introspection manner
116*627f7eb2Smrg and are all optional. The table below illustrates the members that a hook type
117*627f7eb2Smrg may define and their influence over the behavior of the `Checked` type using it.
118*627f7eb2Smrg In the table, `hook` is an alias for `Hook` if the type `Hook` does not
119*627f7eb2Smrg introduce any state, or an object of type `Hook` otherwise.
120*627f7eb2Smrg 
121*627f7eb2Smrg $(TABLE ,
122*627f7eb2Smrg $(TR $(TH `Hook` member) $(TH Semantics in $(D Checked!(T, Hook)))
123*627f7eb2Smrg )
124*627f7eb2Smrg $(TR $(TD `defaultValue`) $(TD If defined, `Hook.defaultValue!T` is used as the
125*627f7eb2Smrg default initializer of the payload.)
126*627f7eb2Smrg )
127*627f7eb2Smrg $(TR $(TD `min`) $(TD If defined, `Hook.min!T` is used as the minimum value of
128*627f7eb2Smrg the payload.)
129*627f7eb2Smrg )
130*627f7eb2Smrg $(TR $(TD `max`) $(TD If defined, `Hook.max!T` is used as the maximum value of
131*627f7eb2Smrg the payload.)
132*627f7eb2Smrg )
133*627f7eb2Smrg $(TR $(TD `hookOpCast`) $(TD If defined, `hook.hookOpCast!U(get)` is forwarded
134*627f7eb2Smrg to unconditionally when the payload is to be cast to type `U`.)
135*627f7eb2Smrg )
136*627f7eb2Smrg $(TR $(TD `onBadCast`) $(TD If defined and `hookOpCast` is $(I not) defined,
137*627f7eb2Smrg `onBadCast!U(get)` is forwarded to when the payload is to be cast to type `U`
138*627f7eb2Smrg and the cast would lose information or force a change of sign.)
139*627f7eb2Smrg )
140*627f7eb2Smrg $(TR $(TD `hookOpEquals`) $(TD If defined, $(D hook.hookOpEquals(get, rhs)) is
141*627f7eb2Smrg forwarded to unconditionally when the payload is compared for equality against
142*627f7eb2Smrg value `rhs` of integral, floating point, or Boolean type.)
143*627f7eb2Smrg )
144*627f7eb2Smrg $(TR $(TD `hookOpCmp`) $(TD If defined, $(D hook.hookOpCmp(get, rhs)) is
145*627f7eb2Smrg forwarded to unconditionally when the payload is compared for ordering against
146*627f7eb2Smrg value `rhs` of integral, floating point, or Boolean type.)
147*627f7eb2Smrg )
148*627f7eb2Smrg $(TR $(TD `hookOpUnary`) $(TD If defined, `hook.hookOpUnary!op(get)` (where `op`
149*627f7eb2Smrg is the operator symbol) is forwarded to for unary operators `-` and `~`. In
150*627f7eb2Smrg addition, for unary operators `++` and `--`, `hook.hookOpUnary!op(payload)` is
151*627f7eb2Smrg called, where `payload` is a reference to the value wrapped by `Checked` so the
152*627f7eb2Smrg hook can change it.)
153*627f7eb2Smrg )
154*627f7eb2Smrg $(TR $(TD `hookOpBinary`) $(TD If defined, $(D hook.hookOpBinary!op(get, rhs))
155*627f7eb2Smrg (where `op` is the operator symbol and `rhs` is the right-hand side operand) is
156*627f7eb2Smrg forwarded to unconditionally for binary operators `+`,  `-`, `*`, `/`, `%`,
157*627f7eb2Smrg `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.)
158*627f7eb2Smrg )
159*627f7eb2Smrg $(TR $(TD `hookOpBinaryRight`) $(TD If defined, $(D
160*627f7eb2Smrg hook.hookOpBinaryRight!op(lhs, get)) (where `op` is the operator symbol and
161*627f7eb2Smrg `lhs` is the left-hand side operand) is forwarded to unconditionally for binary
162*627f7eb2Smrg operators `+`,  `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.)
163*627f7eb2Smrg )
164*627f7eb2Smrg $(TR $(TD `onOverflow`) $(TD If defined, `hook.onOverflow!op(get)` is forwarded
165*627f7eb2Smrg to for unary operators that overflow but only if `hookOpUnary` is not defined.
166*627f7eb2Smrg Unary `~` does not overflow; unary `-` overflows only when the most negative
167*627f7eb2Smrg value of a signed type is negated, and the result of the hook call is returned.
168*627f7eb2Smrg When the increment or decrement operators overflow, the payload is assigned the
169*627f7eb2Smrg result of `hook.onOverflow!op(get)`. When a binary operator overflows, the
170*627f7eb2Smrg result of $(D hook.onOverflow!op(get, rhs)) is returned, but only if `Hook` does
171*627f7eb2Smrg not define `hookOpBinary`.)
172*627f7eb2Smrg )
173*627f7eb2Smrg $(TR $(TD `hookOpOpAssign`) $(TD If defined, $(D hook.hookOpOpAssign!op(payload,
174*627f7eb2Smrg rhs)) (where `op` is the operator symbol and `rhs` is the right-hand side
175*627f7eb2Smrg operand) is forwarded to unconditionally for binary operators `+=`,  `-=`, `*=`, `/=`, `%=`,
176*627f7eb2Smrg `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=`.)
177*627f7eb2Smrg )
178*627f7eb2Smrg $(TR $(TD `onLowerBound`) $(TD If defined, $(D hook.onLowerBound(value, bound))
179*627f7eb2Smrg (where `value` is the value being assigned) is forwarded to when the result of
180*627f7eb2Smrg binary operators `+=`,  `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`,
181*627f7eb2Smrg and `>>>=` is smaller than the smallest value representable by `T`.)
182*627f7eb2Smrg )
183*627f7eb2Smrg $(TR $(TD `onUpperBound`) $(TD If defined, $(D hook.onUpperBound(value, bound))
184*627f7eb2Smrg (where `value` is the value being assigned) is forwarded to when the result of
185*627f7eb2Smrg binary operators `+=`,  `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`,
186*627f7eb2Smrg and `>>>=` is larger than the largest value representable by `T`.)
187*627f7eb2Smrg )
188*627f7eb2Smrg )
189*627f7eb2Smrg 
190*627f7eb2Smrg */
191*627f7eb2Smrg module std.experimental.checkedint;
192*627f7eb2Smrg import std.traits : isFloatingPoint, isIntegral, isNumeric, isUnsigned, Unqual;
193*627f7eb2Smrg 
194*627f7eb2Smrg ///
195*627f7eb2Smrg @system unittest
196*627f7eb2Smrg {
concatAndAdd(int[]a,int[]b,int offset)197*627f7eb2Smrg     int[] concatAndAdd(int[] a, int[] b, int offset)
198*627f7eb2Smrg     {
199*627f7eb2Smrg         // Aborts on overflow on size computation
200*627f7eb2Smrg         auto r = new int[(checked(a.length) + b.length).get];
201*627f7eb2Smrg         // Aborts on overflow on element computation
202*627f7eb2Smrg         foreach (i; 0 .. a.length)
203*627f7eb2Smrg             r[i] = (a[i] + checked(offset)).get;
204*627f7eb2Smrg         foreach (i; 0 .. b.length)
205*627f7eb2Smrg             r[i + a.length] = (b[i] + checked(offset)).get;
206*627f7eb2Smrg         return r;
207*627f7eb2Smrg     }
208*627f7eb2Smrg     assert(concatAndAdd([1, 2, 3], [4, 5], -1) == [0, 1, 2, 3, 4]);
209*627f7eb2Smrg }
210*627f7eb2Smrg 
211*627f7eb2Smrg /**
212*627f7eb2Smrg Checked integral type wraps an integral `T` and customizes its behavior with the
213*627f7eb2Smrg help of a `Hook` type. The type wrapped must be one of the predefined integrals
214*627f7eb2Smrg (unqualified), or another instance of `Checked`.
215*627f7eb2Smrg */
216*627f7eb2Smrg struct Checked(T, Hook = Abort)
217*627f7eb2Smrg if (isIntegral!T || is(T == Checked!(U, H), U, H))
218*627f7eb2Smrg {
219*627f7eb2Smrg     import std.algorithm.comparison : among;
220*627f7eb2Smrg     import std.experimental.allocator.common : stateSize;
221*627f7eb2Smrg     import std.traits : hasMember;
222*627f7eb2Smrg 
223*627f7eb2Smrg     /**
224*627f7eb2Smrg     The type of the integral subject to checking.
225*627f7eb2Smrg     */
226*627f7eb2Smrg     alias Representation = T;
227*627f7eb2Smrg 
228*627f7eb2Smrg     // state {
229*627f7eb2Smrg     static if (hasMember!(Hook, "defaultValue"))
230*627f7eb2Smrg         private T payload = Hook.defaultValue!T;
231*627f7eb2Smrg     else
232*627f7eb2Smrg         private T payload;
233*627f7eb2Smrg     /**
234*627f7eb2Smrg     `hook` is a member variable if it has state, or an alias for `Hook`
235*627f7eb2Smrg     otherwise.
236*627f7eb2Smrg     */
237*627f7eb2Smrg     static if (stateSize!Hook > 0) Hook hook;
238*627f7eb2Smrg     else alias hook = Hook;
239*627f7eb2Smrg     // } state
240*627f7eb2Smrg 
241*627f7eb2Smrg     // get
242*627f7eb2Smrg     /**
243*627f7eb2Smrg     Returns a copy of the underlying value.
244*627f7eb2Smrg     */
getChecked245*627f7eb2Smrg     auto get() inout { return payload; }
246*627f7eb2Smrg     ///
247*627f7eb2Smrg     @safe unittest
248*627f7eb2Smrg     {
249*627f7eb2Smrg         auto x = checked(ubyte(42));
250*627f7eb2Smrg         static assert(is(typeof(x.get()) == ubyte));
251*627f7eb2Smrg         assert(x.get == 42);
252*627f7eb2Smrg         const y = checked(ubyte(42));
253*627f7eb2Smrg         static assert(is(typeof(y.get()) == const ubyte));
254*627f7eb2Smrg         assert(y.get == 42);
255*627f7eb2Smrg     }
256*627f7eb2Smrg 
257*627f7eb2Smrg     /**
258*627f7eb2Smrg     Defines the minimum and maximum. These values are hookable by defining
259*627f7eb2Smrg     `Hook.min` and/or `Hook.max`.
260*627f7eb2Smrg     */
261*627f7eb2Smrg     static if (hasMember!(Hook, "min"))
262*627f7eb2Smrg     {
263*627f7eb2Smrg         enum Checked!(T, Hook) min = Checked!(T, Hook)(Hook.min!T);
264*627f7eb2Smrg         ///
265*627f7eb2Smrg         @system unittest
266*627f7eb2Smrg         {
267*627f7eb2Smrg             assert(Checked!short.min == -32768);
268*627f7eb2Smrg             assert(Checked!(short, WithNaN).min == -32767);
269*627f7eb2Smrg             assert(Checked!(uint, WithNaN).max == uint.max - 1);
270*627f7eb2Smrg         }
271*627f7eb2Smrg     }
272*627f7eb2Smrg     else
273*627f7eb2Smrg         enum Checked!(T, Hook) min = Checked(T.min);
274*627f7eb2Smrg     /// ditto
275*627f7eb2Smrg     static if (hasMember!(Hook, "max"))
276*627f7eb2Smrg         enum Checked!(T, Hook) max = Checked(Hook.max!T);
277*627f7eb2Smrg     else
278*627f7eb2Smrg         enum Checked!(T, Hook) max = Checked(T.max);
279*627f7eb2Smrg 
280*627f7eb2Smrg     /**
281*627f7eb2Smrg     Constructor taking a value properly convertible to the underlying type. `U`
282*627f7eb2Smrg     may be either an integral that can be converted to `T` without a loss, or
283*627f7eb2Smrg     another `Checked` instance whose representation may be in turn converted to
284*627f7eb2Smrg     `T` without a loss.
285*627f7eb2Smrg     */
286*627f7eb2Smrg     this(U)(U rhs)
287*627f7eb2Smrg     if (valueConvertible!(U, T) ||
288*627f7eb2Smrg         !isIntegral!T && is(typeof(T(rhs))) ||
289*627f7eb2Smrg         is(U == Checked!(V, W), V, W) &&
290*627f7eb2Smrg             is(typeof(Checked!(T, Hook)(rhs.get))))
291*627f7eb2Smrg     {
292*627f7eb2Smrg         static if (isIntegral!U)
293*627f7eb2Smrg             payload = rhs;
294*627f7eb2Smrg         else
295*627f7eb2Smrg             payload = rhs.payload;
296*627f7eb2Smrg     }
297*627f7eb2Smrg     ///
298*627f7eb2Smrg     @system unittest
299*627f7eb2Smrg     {
300*627f7eb2Smrg         auto a = checked(42L);
301*627f7eb2Smrg         assert(a == 42);
302*627f7eb2Smrg         auto b = Checked!long(4242); // convert 4242 to long
303*627f7eb2Smrg         assert(b == 4242);
304*627f7eb2Smrg     }
305*627f7eb2Smrg 
306*627f7eb2Smrg     /**
307*627f7eb2Smrg     Assignment operator. Has the same constraints as the constructor.
308*627f7eb2Smrg     */
309*627f7eb2Smrg     void opAssign(U)(U rhs) if (is(typeof(Checked!(T, Hook)(rhs))))
310*627f7eb2Smrg     {
311*627f7eb2Smrg         static if (isIntegral!U)
312*627f7eb2Smrg             payload = rhs;
313*627f7eb2Smrg         else
314*627f7eb2Smrg             payload = rhs.payload;
315*627f7eb2Smrg     }
316*627f7eb2Smrg     ///
317*627f7eb2Smrg     @system unittest
318*627f7eb2Smrg     {
319*627f7eb2Smrg         Checked!long a;
320*627f7eb2Smrg         a = 42L;
321*627f7eb2Smrg         assert(a == 42);
322*627f7eb2Smrg         a = 4242;
323*627f7eb2Smrg         assert(a == 4242);
324*627f7eb2Smrg     }
325*627f7eb2Smrg 
326*627f7eb2Smrg     // opCast
327*627f7eb2Smrg     /**
328*627f7eb2Smrg     Casting operator to integral, `bool`, or floating point type. If `Hook`
329*627f7eb2Smrg     defines `hookOpCast`, the call immediately returns
330*627f7eb2Smrg     `hook.hookOpCast!U(get)`. Otherwise, casting to `bool` yields $(D
331*627f7eb2Smrg     get != 0) and casting to another integral that can represent all
332*627f7eb2Smrg     values of `T` returns `get` promoted to `U`.
333*627f7eb2Smrg 
334*627f7eb2Smrg     If a cast to a floating-point type is requested and `Hook` defines
335*627f7eb2Smrg     `onBadCast`, the cast is verified by ensuring $(D get == cast(T)
336*627f7eb2Smrg     U(get)). If that is not `true`, `hook.onBadCast!U(get)` is returned.
337*627f7eb2Smrg 
338*627f7eb2Smrg     If a cast to an integral type is requested and `Hook` defines `onBadCast`,
339*627f7eb2Smrg     the cast is verified by ensuring `get` and $(D cast(U)
340*627f7eb2Smrg     get) are the same arithmetic number. (Note that `int(-1)` and
341*627f7eb2Smrg     `uint(1)` are different values arithmetically although they have the same
342*627f7eb2Smrg     bitwise representation and compare equal by language rules.) If the numbers
343*627f7eb2Smrg     are not arithmetically equal, `hook.onBadCast!U(get)` is
344*627f7eb2Smrg     returned.
345*627f7eb2Smrg 
346*627f7eb2Smrg     */
347*627f7eb2Smrg     U opCast(U, this _)()
348*627f7eb2Smrg     if (isIntegral!U || isFloatingPoint!U || is(U == bool))
349*627f7eb2Smrg     {
350*627f7eb2Smrg         static if (hasMember!(Hook, "hookOpCast"))
351*627f7eb2Smrg         {
352*627f7eb2Smrg             return hook.hookOpCast!U(payload);
353*627f7eb2Smrg         }
354*627f7eb2Smrg         else static if (is(U == bool))
355*627f7eb2Smrg         {
356*627f7eb2Smrg             return payload != 0;
357*627f7eb2Smrg         }
358*627f7eb2Smrg         else static if (valueConvertible!(T, U))
359*627f7eb2Smrg         {
360*627f7eb2Smrg             return payload;
361*627f7eb2Smrg         }
362*627f7eb2Smrg         // may lose bits or precision
363*627f7eb2Smrg         else static if (!hasMember!(Hook, "onBadCast"))
364*627f7eb2Smrg         {
365*627f7eb2Smrg             return cast(U) payload;
366*627f7eb2Smrg         }
367*627f7eb2Smrg         else
368*627f7eb2Smrg         {
369*627f7eb2Smrg             if (isUnsigned!T || !isUnsigned!U ||
370*627f7eb2Smrg                     T.sizeof > U.sizeof || payload >= 0)
371*627f7eb2Smrg             {
372*627f7eb2Smrg                 auto result = cast(U) payload;
373*627f7eb2Smrg                 // If signedness is different, we need additional checks
374*627f7eb2Smrg                 if (result == payload &&
375*627f7eb2Smrg                         (!isUnsigned!T || isUnsigned!U || result >= 0))
376*627f7eb2Smrg                     return result;
377*627f7eb2Smrg             }
378*627f7eb2Smrg             return hook.onBadCast!U(payload);
379*627f7eb2Smrg         }
380*627f7eb2Smrg     }
381*627f7eb2Smrg     ///
382*627f7eb2Smrg     @system unittest
383*627f7eb2Smrg     {
384*627f7eb2Smrg         assert(cast(uint) checked(42) == 42);
385*627f7eb2Smrg         assert(cast(uint) checked!WithNaN(-42) == uint.max);
386*627f7eb2Smrg     }
387*627f7eb2Smrg 
388*627f7eb2Smrg     // opEquals
389*627f7eb2Smrg     /**
390*627f7eb2Smrg     Compares `this` against `rhs` for equality. If `Hook` defines
391*627f7eb2Smrg     `hookOpEquals`, the function forwards to $(D
392*627f7eb2Smrg     hook.hookOpEquals(get, rhs)). Otherwise, the result of the
393*627f7eb2Smrg     built-in operation $(D get == rhs) is returned.
394*627f7eb2Smrg 
395*627f7eb2Smrg     If `U` is also an instance of `Checked`, both hooks (left- and right-hand
396*627f7eb2Smrg     side) are introspected for the method `hookOpEquals`. If both define it,
397*627f7eb2Smrg     priority is given to the left-hand side.
398*627f7eb2Smrg 
399*627f7eb2Smrg     */
400*627f7eb2Smrg     bool opEquals(U, this _)(U rhs)
401*627f7eb2Smrg     if (isIntegral!U || isFloatingPoint!U || is(U == bool) ||
402*627f7eb2Smrg         is(U == Checked!(V, W), V, W) && is(typeof(this == rhs.payload)))
403*627f7eb2Smrg     {
404*627f7eb2Smrg         static if (is(U == Checked!(V, W), V, W))
405*627f7eb2Smrg         {
406*627f7eb2Smrg             alias R = typeof(payload + rhs.payload);
407*627f7eb2Smrg             static if (is(Hook == W))
408*627f7eb2Smrg             {
409*627f7eb2Smrg                 // Use the lhs hook if there
410*627f7eb2Smrg                 return this == rhs.payload;
411*627f7eb2Smrg             }
412*627f7eb2Smrg             else static if (valueConvertible!(T, R) && valueConvertible!(V, R))
413*627f7eb2Smrg             {
414*627f7eb2Smrg                 return payload == rhs.payload;
415*627f7eb2Smrg             }
416*627f7eb2Smrg             else static if (hasMember!(Hook, "hookOpEquals"))
417*627f7eb2Smrg             {
418*627f7eb2Smrg                 return hook.hookOpEquals(payload, rhs.payload);
419*627f7eb2Smrg             }
420*627f7eb2Smrg             else static if (hasMember!(W, "hookOpEquals"))
421*627f7eb2Smrg             {
422*627f7eb2Smrg                 return rhs.hook.hookOpEquals(rhs.payload, payload);
423*627f7eb2Smrg             }
424*627f7eb2Smrg             else
425*627f7eb2Smrg             {
426*627f7eb2Smrg                 return payload == rhs.payload;
427*627f7eb2Smrg             }
428*627f7eb2Smrg         }
429*627f7eb2Smrg         else static if (hasMember!(Hook, "hookOpEquals"))
430*627f7eb2Smrg             return hook.hookOpEquals(payload, rhs);
431*627f7eb2Smrg         else static if (isIntegral!U || isFloatingPoint!U || is(U == bool))
432*627f7eb2Smrg             return payload == rhs;
433*627f7eb2Smrg     }
434*627f7eb2Smrg 
435*627f7eb2Smrg     ///
436*627f7eb2Smrg     static if (is(T == int) && is(Hook == void)) @safe unittest
437*627f7eb2Smrg     {
438*627f7eb2Smrg         static struct MyHook
439*627f7eb2Smrg         {
440*627f7eb2Smrg             static bool thereWereErrors;
hookOpEqualsChecked::MyHook441*627f7eb2Smrg             static bool hookOpEquals(L, R)(L lhs, R rhs)
442*627f7eb2Smrg             {
443*627f7eb2Smrg                 if (lhs != rhs) return false;
444*627f7eb2Smrg                 static if (isUnsigned!L && !isUnsigned!R)
445*627f7eb2Smrg                 {
446*627f7eb2Smrg                     if (lhs > 0 && rhs < 0) thereWereErrors = true;
447*627f7eb2Smrg                 }
448*627f7eb2Smrg                 else static if (isUnsigned!R && !isUnsigned!L)
449*627f7eb2Smrg                     if (lhs < 0 && rhs > 0) thereWereErrors = true;
450*627f7eb2Smrg                 // Preserve built-in behavior.
451*627f7eb2Smrg                 return true;
452*627f7eb2Smrg             }
453*627f7eb2Smrg         }
454*627f7eb2Smrg         auto a = checked!MyHook(-42);
455*627f7eb2Smrg         assert(a == uint(-42));
456*627f7eb2Smrg         assert(MyHook.thereWereErrors);
457*627f7eb2Smrg         MyHook.thereWereErrors = false;
458*627f7eb2Smrg         assert(checked!MyHook(uint(-42)) == -42);
459*627f7eb2Smrg         assert(MyHook.thereWereErrors);
460*627f7eb2Smrg         static struct MyHook2
461*627f7eb2Smrg         {
hookOpEqualsChecked::MyHook2462*627f7eb2Smrg             static bool hookOpEquals(L, R)(L lhs, R rhs)
463*627f7eb2Smrg             {
464*627f7eb2Smrg                 return lhs == rhs;
465*627f7eb2Smrg             }
466*627f7eb2Smrg         }
467*627f7eb2Smrg         MyHook.thereWereErrors = false;
468*627f7eb2Smrg         assert(checked!MyHook2(uint(-42)) == a);
469*627f7eb2Smrg         // Hook on left hand side takes precedence, so no errors
470*627f7eb2Smrg         assert(!MyHook.thereWereErrors);
471*627f7eb2Smrg     }
472*627f7eb2Smrg 
473*627f7eb2Smrg     // opCmp
474*627f7eb2Smrg     /**
475*627f7eb2Smrg 
476*627f7eb2Smrg     Compares `this` against `rhs` for ordering. If `Hook` defines `hookOpCmp`,
477*627f7eb2Smrg     the function forwards to $(D hook.hookOpCmp(get, rhs)). Otherwise, the
478*627f7eb2Smrg     result of the built-in comparison operation is returned.
479*627f7eb2Smrg 
480*627f7eb2Smrg     If `U` is also an instance of `Checked`, both hooks (left- and right-hand
481*627f7eb2Smrg     side) are introspected for the method `hookOpCmp`. If both define it,
482*627f7eb2Smrg     priority is given to the left-hand side.
483*627f7eb2Smrg 
484*627f7eb2Smrg     */
485*627f7eb2Smrg     auto opCmp(U, this _)(const U rhs) //const pure @safe nothrow @nogc
486*627f7eb2Smrg     if (isIntegral!U || isFloatingPoint!U || is(U == bool))
487*627f7eb2Smrg     {
488*627f7eb2Smrg         static if (hasMember!(Hook, "hookOpCmp"))
489*627f7eb2Smrg         {
490*627f7eb2Smrg             return hook.hookOpCmp(payload, rhs);
491*627f7eb2Smrg         }
492*627f7eb2Smrg         else static if (valueConvertible!(T, U) || valueConvertible!(U, T))
493*627f7eb2Smrg         {
494*627f7eb2Smrg             return payload < rhs ? -1 : payload > rhs;
495*627f7eb2Smrg         }
496*627f7eb2Smrg         else static if (isFloatingPoint!U)
497*627f7eb2Smrg         {
498*627f7eb2Smrg             U lhs = payload;
499*627f7eb2Smrg             return lhs < rhs ? U(-1.0)
500*627f7eb2Smrg                 : lhs > rhs ? U(1.0)
501*627f7eb2Smrg                 : lhs == rhs ? U(0.0) : U.init;
502*627f7eb2Smrg         }
503*627f7eb2Smrg         else
504*627f7eb2Smrg         {
505*627f7eb2Smrg             return payload < rhs ? -1 : payload > rhs;
506*627f7eb2Smrg         }
507*627f7eb2Smrg     }
508*627f7eb2Smrg 
509*627f7eb2Smrg     /// ditto
opCmpChecked510*627f7eb2Smrg     auto opCmp(U, Hook1, this _)(Checked!(U, Hook1) rhs)
511*627f7eb2Smrg     {
512*627f7eb2Smrg         alias R = typeof(payload + rhs.payload);
513*627f7eb2Smrg         static if (valueConvertible!(T, R) && valueConvertible!(U, R))
514*627f7eb2Smrg         {
515*627f7eb2Smrg             return payload < rhs.payload ? -1 : payload > rhs.payload;
516*627f7eb2Smrg         }
517*627f7eb2Smrg         else static if (is(Hook == Hook1))
518*627f7eb2Smrg         {
519*627f7eb2Smrg             // Use the lhs hook
520*627f7eb2Smrg             return this.opCmp(rhs.payload);
521*627f7eb2Smrg         }
522*627f7eb2Smrg         else static if (hasMember!(Hook, "hookOpCmp"))
523*627f7eb2Smrg         {
524*627f7eb2Smrg             return hook.hookOpCmp(get, rhs.get);
525*627f7eb2Smrg         }
526*627f7eb2Smrg         else static if (hasMember!(Hook1, "hookOpCmp"))
527*627f7eb2Smrg         {
528*627f7eb2Smrg             return -rhs.hook.hookOpCmp(rhs.payload, get);
529*627f7eb2Smrg         }
530*627f7eb2Smrg         else
531*627f7eb2Smrg         {
532*627f7eb2Smrg             return payload < rhs.payload ? -1 : payload > rhs.payload;
533*627f7eb2Smrg         }
534*627f7eb2Smrg     }
535*627f7eb2Smrg 
536*627f7eb2Smrg     ///
537*627f7eb2Smrg     static if (is(T == int) && is(Hook == void)) @safe unittest
538*627f7eb2Smrg     {
539*627f7eb2Smrg         static struct MyHook
540*627f7eb2Smrg         {
541*627f7eb2Smrg             static bool thereWereErrors;
hookOpCmpChecked::MyHook542*627f7eb2Smrg             static int hookOpCmp(L, R)(L lhs, R rhs)
543*627f7eb2Smrg             {
544*627f7eb2Smrg                 static if (isUnsigned!L && !isUnsigned!R)
545*627f7eb2Smrg                 {
546*627f7eb2Smrg                     if (rhs < 0 && rhs >= lhs)
547*627f7eb2Smrg                         thereWereErrors = true;
548*627f7eb2Smrg                 }
549*627f7eb2Smrg                 else static if (isUnsigned!R && !isUnsigned!L)
550*627f7eb2Smrg                 {
551*627f7eb2Smrg                     if (lhs < 0 && lhs >= rhs)
552*627f7eb2Smrg                         thereWereErrors = true;
553*627f7eb2Smrg                 }
554*627f7eb2Smrg                 // Preserve built-in behavior.
555*627f7eb2Smrg                 return lhs < rhs ? -1 : lhs > rhs;
556*627f7eb2Smrg             }
557*627f7eb2Smrg         }
558*627f7eb2Smrg         auto a = checked!MyHook(-42);
559*627f7eb2Smrg         assert(a > uint(42));
560*627f7eb2Smrg         assert(MyHook.thereWereErrors);
561*627f7eb2Smrg         static struct MyHook2
562*627f7eb2Smrg         {
hookOpCmpChecked::MyHook2563*627f7eb2Smrg             static int hookOpCmp(L, R)(L lhs, R rhs)
564*627f7eb2Smrg             {
565*627f7eb2Smrg                 // Default behavior
566*627f7eb2Smrg                 return lhs < rhs ? -1 : lhs > rhs;
567*627f7eb2Smrg             }
568*627f7eb2Smrg         }
569*627f7eb2Smrg         MyHook.thereWereErrors = false;
570*627f7eb2Smrg         assert(Checked!(uint, MyHook2)(uint(-42)) <= a);
571*627f7eb2Smrg         //assert(Checked!(uint, MyHook2)(uint(-42)) >= a);
572*627f7eb2Smrg         // Hook on left hand side takes precedence, so no errors
573*627f7eb2Smrg         assert(!MyHook.thereWereErrors);
574*627f7eb2Smrg         assert(a <= Checked!(uint, MyHook2)(uint(-42)));
575*627f7eb2Smrg         assert(MyHook.thereWereErrors);
576*627f7eb2Smrg     }
577*627f7eb2Smrg 
578*627f7eb2Smrg     // For coverage
579*627f7eb2Smrg     static if (is(T == int) && is(Hook == void)) @system unittest
580*627f7eb2Smrg     {
581*627f7eb2Smrg         assert(checked(42) <= checked!void(42));
582*627f7eb2Smrg         assert(checked!void(42) <= checked(42u));
583*627f7eb2Smrg         assert(checked!void(42) <= checked!(void*)(42u));
584*627f7eb2Smrg     }
585*627f7eb2Smrg 
586*627f7eb2Smrg     // opUnary
587*627f7eb2Smrg     /**
588*627f7eb2Smrg 
589*627f7eb2Smrg     Defines unary operators `+`, `-`, `~`, `++`, and `--`. Unary `+` is not
590*627f7eb2Smrg     overridable and always has built-in behavior (returns `this`). For the
591*627f7eb2Smrg     others, if `Hook` defines `hookOpUnary`, `opUnary` forwards to $(D
592*627f7eb2Smrg     Checked!(typeof(hook.hookOpUnary!op(get)),
593*627f7eb2Smrg     Hook)(hook.hookOpUnary!op(get))).
594*627f7eb2Smrg 
595*627f7eb2Smrg     If `Hook` does not define `hookOpUnary` but defines `onOverflow`, `opUnary`
596*627f7eb2Smrg     forwards to `hook.onOverflow!op(get)` in case an overflow occurs.
597*627f7eb2Smrg     For `++` and `--`, the payload is assigned from the result of the call to
598*627f7eb2Smrg     `onOverflow`.
599*627f7eb2Smrg 
600*627f7eb2Smrg     Note that unary `-` is considered to overflow if `T` is a signed integral of
601*627f7eb2Smrg     32 or 64 bits and is equal to the most negative value. This is because that
602*627f7eb2Smrg     value has no positive negation.
603*627f7eb2Smrg 
604*627f7eb2Smrg     */
605*627f7eb2Smrg     auto opUnary(string op, this _)()
606*627f7eb2Smrg     if (op == "+" || op == "-" || op == "~")
607*627f7eb2Smrg     {
608*627f7eb2Smrg         static if (op == "+")
609*627f7eb2Smrg             return Checked(this); // "+" is not hookable
610*627f7eb2Smrg         else static if (hasMember!(Hook, "hookOpUnary"))
611*627f7eb2Smrg         {
612*627f7eb2Smrg             auto r = hook.hookOpUnary!op(payload);
613*627f7eb2Smrg             return Checked!(typeof(r), Hook)(r);
614*627f7eb2Smrg         }
615*627f7eb2Smrg         else static if (op == "-" && isIntegral!T && T.sizeof >= 4 &&
616*627f7eb2Smrg                 !isUnsigned!T && hasMember!(Hook, "onOverflow"))
617*627f7eb2Smrg         {
618*627f7eb2Smrg             static assert(is(typeof(-payload) == typeof(payload)));
619*627f7eb2Smrg             bool overflow;
620*627f7eb2Smrg             import core.checkedint : negs;
621*627f7eb2Smrg             auto r = negs(payload, overflow);
622*627f7eb2Smrg             if (overflow) r = hook.onOverflow!op(payload);
623*627f7eb2Smrg             return Checked(r);
624*627f7eb2Smrg         }
625*627f7eb2Smrg         else
626*627f7eb2Smrg             return Checked(mixin(op ~ "payload"));
627*627f7eb2Smrg     }
628*627f7eb2Smrg 
629*627f7eb2Smrg     /// ditto
630*627f7eb2Smrg     ref Checked opUnary(string op)() return
631*627f7eb2Smrg     if (op == "++" || op == "--")
632*627f7eb2Smrg     {
633*627f7eb2Smrg         static if (hasMember!(Hook, "hookOpUnary"))
634*627f7eb2Smrg             hook.hookOpUnary!op(payload);
635*627f7eb2Smrg         else static if (hasMember!(Hook, "onOverflow"))
636*627f7eb2Smrg         {
637*627f7eb2Smrg             static if (op == "++")
638*627f7eb2Smrg             {
639*627f7eb2Smrg                 if (payload == max.payload)
640*627f7eb2Smrg                     payload = hook.onOverflow!"++"(payload);
641*627f7eb2Smrg                 else
642*627f7eb2Smrg                     ++payload;
643*627f7eb2Smrg             }
644*627f7eb2Smrg             else
645*627f7eb2Smrg             {
646*627f7eb2Smrg                 if (payload == min.payload)
647*627f7eb2Smrg                     payload = hook.onOverflow!"--"(payload);
648*627f7eb2Smrg                 else
649*627f7eb2Smrg                     --payload;
650*627f7eb2Smrg             }
651*627f7eb2Smrg         }
652*627f7eb2Smrg         else
653*627f7eb2Smrg             mixin(op ~ "payload;");
654*627f7eb2Smrg         return this;
655*627f7eb2Smrg     }
656*627f7eb2Smrg 
657*627f7eb2Smrg     ///
658*627f7eb2Smrg     static if (is(T == int) && is(Hook == void)) @safe unittest
659*627f7eb2Smrg     {
660*627f7eb2Smrg         static struct MyHook
661*627f7eb2Smrg         {
662*627f7eb2Smrg             static bool thereWereErrors;
663*627f7eb2Smrg             static L hookOpUnary(string x, L)(L lhs)
664*627f7eb2Smrg             {
665*627f7eb2Smrg                 if (x == "-" && lhs == -lhs) thereWereErrors = true;
666*627f7eb2Smrg                 return -lhs;
667*627f7eb2Smrg             }
668*627f7eb2Smrg         }
669*627f7eb2Smrg         auto a = checked!MyHook(long.min);
670*627f7eb2Smrg         assert(a == -a);
671*627f7eb2Smrg         assert(MyHook.thereWereErrors);
672*627f7eb2Smrg         auto b = checked!void(42);
673*627f7eb2Smrg         assert(++b == 43);
674*627f7eb2Smrg     }
675*627f7eb2Smrg 
676*627f7eb2Smrg     // opBinary
677*627f7eb2Smrg     /**
678*627f7eb2Smrg 
679*627f7eb2Smrg     Defines binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`,
680*627f7eb2Smrg     and `>>>`. If `Hook` defines `hookOpBinary`, `opBinary` forwards to $(D
681*627f7eb2Smrg     Checked!(typeof(hook.hookOpBinary!op(get, rhs)),
682*627f7eb2Smrg     Hook)(hook.hookOpBinary!op(get, rhs))).
683*627f7eb2Smrg 
684*627f7eb2Smrg     If `Hook` does not define `hookOpBinary` but defines `onOverflow`,
685*627f7eb2Smrg     `opBinary` forwards to `hook.onOverflow!op(get, rhs)` in case an
686*627f7eb2Smrg     overflow occurs.
687*627f7eb2Smrg 
688*627f7eb2Smrg     If two `Checked` instances are involved in a binary operation and both
689*627f7eb2Smrg     define `hookOpBinary`, the left-hand side hook has priority. If both define
690*627f7eb2Smrg     `onOverflow`, a compile-time error occurs.
691*627f7eb2Smrg 
692*627f7eb2Smrg     */
693*627f7eb2Smrg     auto opBinary(string op, Rhs)(const Rhs rhs)
694*627f7eb2Smrg     if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool))
695*627f7eb2Smrg     {
696*627f7eb2Smrg         return opBinaryImpl!(op, Rhs, typeof(this))(rhs);
697*627f7eb2Smrg     }
698*627f7eb2Smrg 
699*627f7eb2Smrg     /// ditto
700*627f7eb2Smrg     auto opBinary(string op, Rhs)(const Rhs rhs) const
701*627f7eb2Smrg     if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool))
702*627f7eb2Smrg     {
703*627f7eb2Smrg         return opBinaryImpl!(op, Rhs, typeof(this))(rhs);
704*627f7eb2Smrg     }
705*627f7eb2Smrg 
706*627f7eb2Smrg     private auto opBinaryImpl(string op, Rhs, this _)(const Rhs rhs)
707*627f7eb2Smrg     {
708*627f7eb2Smrg         alias R = typeof(mixin("payload" ~ op ~ "rhs"));
709*627f7eb2Smrg         static assert(is(typeof(mixin("payload" ~ op ~ "rhs")) == R));
710*627f7eb2Smrg         static if (isIntegral!R) alias Result = Checked!(R, Hook);
711*627f7eb2Smrg         else alias Result = R;
712*627f7eb2Smrg 
713*627f7eb2Smrg         static if (hasMember!(Hook, "hookOpBinary"))
714*627f7eb2Smrg         {
715*627f7eb2Smrg             auto r = hook.hookOpBinary!op(payload, rhs);
716*627f7eb2Smrg             return Checked!(typeof(r), Hook)(r);
717*627f7eb2Smrg         }
718*627f7eb2Smrg         else static if (is(Rhs == bool))
719*627f7eb2Smrg         {
720*627f7eb2Smrg             return mixin("this" ~ op ~ "ubyte(rhs)");
721*627f7eb2Smrg         }
722*627f7eb2Smrg         else static if (isFloatingPoint!Rhs)
723*627f7eb2Smrg         {
724*627f7eb2Smrg             return mixin("payload" ~ op ~ "rhs");
725*627f7eb2Smrg         }
726*627f7eb2Smrg         else static if (hasMember!(Hook, "onOverflow"))
727*627f7eb2Smrg         {
728*627f7eb2Smrg             bool overflow;
729*627f7eb2Smrg             auto r = opChecked!op(payload, rhs, overflow);
730*627f7eb2Smrg             if (overflow) r = hook.onOverflow!op(payload, rhs);
731*627f7eb2Smrg             return Result(r);
732*627f7eb2Smrg         }
733*627f7eb2Smrg         else
734*627f7eb2Smrg         {
735*627f7eb2Smrg             // Default is built-in behavior
736*627f7eb2Smrg             return Result(mixin("payload" ~ op ~ "rhs"));
737*627f7eb2Smrg         }
738*627f7eb2Smrg     }
739*627f7eb2Smrg 
740*627f7eb2Smrg     /// ditto
741*627f7eb2Smrg     auto opBinary(string op, U, Hook1)(Checked!(U, Hook1) rhs)
742*627f7eb2Smrg     {
743*627f7eb2Smrg         return opBinaryImpl2!(op, U, Hook1, typeof(this))(rhs);
744*627f7eb2Smrg     }
745*627f7eb2Smrg 
746*627f7eb2Smrg     /// ditto
747*627f7eb2Smrg     auto opBinary(string op, U, Hook1)(Checked!(U, Hook1) rhs) const
748*627f7eb2Smrg     {
749*627f7eb2Smrg         return opBinaryImpl2!(op, U, Hook1, typeof(this))(rhs);
750*627f7eb2Smrg     }
751*627f7eb2Smrg 
752*627f7eb2Smrg     private
753*627f7eb2Smrg     auto opBinaryImpl2(string op, U, Hook1, this _)(Checked!(U, Hook1) rhs)
754*627f7eb2Smrg     {
755*627f7eb2Smrg         alias R = typeof(get + rhs.payload);
756*627f7eb2Smrg         static if (valueConvertible!(T, R) && valueConvertible!(U, R) ||
757*627f7eb2Smrg             is(Hook == Hook1))
758*627f7eb2Smrg         {
759*627f7eb2Smrg             // Delegate to lhs
760*627f7eb2Smrg             return mixin("this" ~ op ~ "rhs.payload");
761*627f7eb2Smrg         }
762*627f7eb2Smrg         else static if (hasMember!(Hook, "hookOpBinary"))
763*627f7eb2Smrg         {
764*627f7eb2Smrg             return hook.hookOpBinary!op(payload, rhs);
765*627f7eb2Smrg         }
766*627f7eb2Smrg         else static if (hasMember!(Hook1, "hookOpBinary"))
767*627f7eb2Smrg         {
768*627f7eb2Smrg             // Delegate to rhs
769*627f7eb2Smrg             return mixin("this.payload" ~ op ~ "rhs");
770*627f7eb2Smrg         }
771*627f7eb2Smrg         else static if (hasMember!(Hook, "onOverflow") &&
772*627f7eb2Smrg             !hasMember!(Hook1, "onOverflow"))
773*627f7eb2Smrg         {
774*627f7eb2Smrg             // Delegate to lhs
775*627f7eb2Smrg             return mixin("this" ~ op ~ "rhs.payload");
776*627f7eb2Smrg         }
777*627f7eb2Smrg         else static if (hasMember!(Hook1, "onOverflow") &&
778*627f7eb2Smrg             !hasMember!(Hook, "onOverflow"))
779*627f7eb2Smrg         {
780*627f7eb2Smrg             // Delegate to rhs
781*627f7eb2Smrg             return mixin("this.payload" ~ op ~ "rhs");
782*627f7eb2Smrg         }
783*627f7eb2Smrg         else
784*627f7eb2Smrg         {
785*627f7eb2Smrg             static assert(0, "Conflict between lhs and rhs hooks," ~
786*627f7eb2Smrg                 " use .get on one side to disambiguate.");
787*627f7eb2Smrg         }
788*627f7eb2Smrg     }
789*627f7eb2Smrg 
790*627f7eb2Smrg     static if (is(T == int) && is(Hook == void)) @system unittest
791*627f7eb2Smrg     {
792*627f7eb2Smrg         const a = checked(42);
793*627f7eb2Smrg         assert(a + 1 == 43);
794*627f7eb2Smrg         assert(a + checked(uint(42)) == 84);
795*627f7eb2Smrg         assert(checked(42) + checked!void(42u) == 84);
796*627f7eb2Smrg         assert(checked!void(42) + checked(42u) == 84);
797*627f7eb2Smrg 
798*627f7eb2Smrg         static struct MyHook
799*627f7eb2Smrg         {
800*627f7eb2Smrg             static uint tally;
801*627f7eb2Smrg             static auto hookOpBinary(string x, L, R)(L lhs, R rhs)
802*627f7eb2Smrg             {
803*627f7eb2Smrg                 ++tally;
804*627f7eb2Smrg                 return mixin("lhs" ~ x ~ "rhs");
805*627f7eb2Smrg             }
806*627f7eb2Smrg         }
807*627f7eb2Smrg         assert(checked!MyHook(42) + checked(42u) == 84);
808*627f7eb2Smrg         assert(checked!void(42) + checked!MyHook(42u) == 84);
809*627f7eb2Smrg         assert(MyHook.tally == 2);
810*627f7eb2Smrg     }
811*627f7eb2Smrg 
812*627f7eb2Smrg     // opBinaryRight
813*627f7eb2Smrg     /**
814*627f7eb2Smrg 
815*627f7eb2Smrg     Defines binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`,
816*627f7eb2Smrg     `>>`, and `>>>` for the case when a built-in numeric or Boolean type is on
817*627f7eb2Smrg     the left-hand side, and a `Checked` instance is on the right-hand side.
818*627f7eb2Smrg 
819*627f7eb2Smrg     */
820*627f7eb2Smrg     auto opBinaryRight(string op, Lhs)(const Lhs lhs)
821*627f7eb2Smrg     if (isIntegral!Lhs || isFloatingPoint!Lhs || is(Lhs == bool))
822*627f7eb2Smrg     {
823*627f7eb2Smrg         return opBinaryRightImpl!(op, Lhs, typeof(this))(lhs);
824*627f7eb2Smrg     }
825*627f7eb2Smrg 
826*627f7eb2Smrg     /// ditto
827*627f7eb2Smrg     auto opBinaryRight(string op, Lhs)(const Lhs lhs) const
828*627f7eb2Smrg     if (isIntegral!Lhs || isFloatingPoint!Lhs || is(Lhs == bool))
829*627f7eb2Smrg     {
830*627f7eb2Smrg         return opBinaryRightImpl!(op, Lhs, typeof(this))(lhs);
831*627f7eb2Smrg     }
832*627f7eb2Smrg 
833*627f7eb2Smrg     private auto opBinaryRightImpl(string op, Lhs, this _)(const Lhs lhs)
834*627f7eb2Smrg     {
835*627f7eb2Smrg         static if (hasMember!(Hook, "hookOpBinaryRight"))
836*627f7eb2Smrg         {
837*627f7eb2Smrg             auto r = hook.hookOpBinaryRight!op(lhs, payload);
838*627f7eb2Smrg             return Checked!(typeof(r), Hook)(r);
839*627f7eb2Smrg         }
840*627f7eb2Smrg         else static if (hasMember!(Hook, "hookOpBinary"))
841*627f7eb2Smrg         {
842*627f7eb2Smrg             auto r = hook.hookOpBinary!op(lhs, payload);
843*627f7eb2Smrg             return Checked!(typeof(r), Hook)(r);
844*627f7eb2Smrg         }
845*627f7eb2Smrg         else static if (is(Lhs == bool))
846*627f7eb2Smrg         {
847*627f7eb2Smrg             return mixin("ubyte(lhs)" ~ op ~ "this");
848*627f7eb2Smrg         }
849*627f7eb2Smrg         else static if (isFloatingPoint!Lhs)
850*627f7eb2Smrg         {
851*627f7eb2Smrg             return mixin("lhs" ~ op ~ "payload");
852*627f7eb2Smrg         }
853*627f7eb2Smrg         else static if (hasMember!(Hook, "onOverflow"))
854*627f7eb2Smrg         {
855*627f7eb2Smrg             bool overflow;
856*627f7eb2Smrg             auto r = opChecked!op(lhs, T(payload), overflow);
857*627f7eb2Smrg             if (overflow) r = hook.onOverflow!op(42);
858*627f7eb2Smrg             return Checked!(typeof(r), Hook)(r);
859*627f7eb2Smrg         }
860*627f7eb2Smrg         else
861*627f7eb2Smrg         {
862*627f7eb2Smrg             // Default is built-in behavior
863*627f7eb2Smrg             auto r = mixin("lhs" ~ op ~ "T(payload)");
864*627f7eb2Smrg             return Checked!(typeof(r), Hook)(r);
865*627f7eb2Smrg         }
866*627f7eb2Smrg     }
867*627f7eb2Smrg 
868*627f7eb2Smrg     static if (is(T == int) && is(Hook == void)) @system unittest
869*627f7eb2Smrg     {
870*627f7eb2Smrg         assert(1 + checked(1) == 2);
871*627f7eb2Smrg         static uint tally;
872*627f7eb2Smrg         static struct MyHook
873*627f7eb2Smrg         {
874*627f7eb2Smrg             static auto hookOpBinaryRight(string x, L, R)(L lhs, R rhs)
875*627f7eb2Smrg             {
876*627f7eb2Smrg                 ++tally;
877*627f7eb2Smrg                 return mixin("lhs" ~ x ~ "rhs");
878*627f7eb2Smrg             }
879*627f7eb2Smrg         }
880*627f7eb2Smrg         assert(1 + checked!MyHook(1) == 2);
881*627f7eb2Smrg         assert(tally == 1);
882*627f7eb2Smrg 
883*627f7eb2Smrg         immutable x1 = checked(1);
884*627f7eb2Smrg         assert(1 + x1 == 2);
885*627f7eb2Smrg         immutable x2 = checked!MyHook(1);
886*627f7eb2Smrg         assert(1 + x2 == 2);
887*627f7eb2Smrg         assert(tally == 2);
888*627f7eb2Smrg     }
889*627f7eb2Smrg 
890*627f7eb2Smrg     // opOpAssign
891*627f7eb2Smrg     /**
892*627f7eb2Smrg 
893*627f7eb2Smrg     Defines operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`,
894*627f7eb2Smrg     `<<=`, `>>=`, and `>>>=`.
895*627f7eb2Smrg 
896*627f7eb2Smrg     If `Hook` defines `hookOpOpAssign`, `opOpAssign` forwards to
897*627f7eb2Smrg     `hook.hookOpOpAssign!op(payload, rhs)`, where `payload` is a reference to
898*627f7eb2Smrg     the internally held data so the hook can change it.
899*627f7eb2Smrg 
900*627f7eb2Smrg     Otherwise, the operator first evaluates $(D auto result =
901*627f7eb2Smrg     opBinary!op(payload, rhs).payload), which is subject to the hooks in
902*627f7eb2Smrg     `opBinary`. Then, if `result` is less than $(D Checked!(T, Hook).min) and if
903*627f7eb2Smrg     `Hook` defines `onLowerBound`, the payload is assigned from $(D
904*627f7eb2Smrg     hook.onLowerBound(result, min)). If `result` is greater than $(D Checked!(T,
905*627f7eb2Smrg     Hook).max) and if `Hook` defines `onUpperBound`, the payload is assigned
906*627f7eb2Smrg     from $(D hook.onUpperBound(result, min)).
907*627f7eb2Smrg 
908*627f7eb2Smrg     In all other cases, the built-in behavior is carried out.
909*627f7eb2Smrg 
910*627f7eb2Smrg     Params:
911*627f7eb2Smrg     op = The operator involved (without the `"="`, e.g. `"+"` for `"+="` etc)
912*627f7eb2Smrg     rhs = The right-hand side of the operator (left-hand side is `this`)
913*627f7eb2Smrg 
914*627f7eb2Smrg     Returns: A reference to `this`.
915*627f7eb2Smrg     */
916*627f7eb2Smrg     ref Checked opOpAssign(string op, Rhs)(const Rhs rhs) return
917*627f7eb2Smrg     if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool))
918*627f7eb2Smrg     {
919*627f7eb2Smrg         static assert(is(typeof(mixin("payload" ~ op ~ "=rhs")) == T));
920*627f7eb2Smrg 
921*627f7eb2Smrg         static if (hasMember!(Hook, "hookOpOpAssign"))
922*627f7eb2Smrg         {
923*627f7eb2Smrg             hook.hookOpOpAssign!op(payload, rhs);
924*627f7eb2Smrg         }
925*627f7eb2Smrg         else
926*627f7eb2Smrg         {
927*627f7eb2Smrg             alias R = typeof(get + rhs);
928*627f7eb2Smrg             auto r = opBinary!op(rhs).get;
929*627f7eb2Smrg             import std.conv : unsigned;
930*627f7eb2Smrg 
931*627f7eb2Smrg             static if (ProperCompare.hookOpCmp(R.min, min.get) < 0 &&
932*627f7eb2Smrg                 hasMember!(Hook, "onLowerBound"))
933*627f7eb2Smrg             {
934*627f7eb2Smrg                 if (ProperCompare.hookOpCmp(r, min.get) < 0)
935*627f7eb2Smrg                 {
936*627f7eb2Smrg                     // Example: Checked!uint(1) += int(-3)
937*627f7eb2Smrg                     payload = hook.onLowerBound(r, min.get);
938*627f7eb2Smrg                     return this;
939*627f7eb2Smrg                 }
940*627f7eb2Smrg             }
941*627f7eb2Smrg             static if (ProperCompare.hookOpCmp(max.get, R.max) < 0 &&
942*627f7eb2Smrg                 hasMember!(Hook, "onUpperBound"))
943*627f7eb2Smrg             {
944*627f7eb2Smrg                 if (ProperCompare.hookOpCmp(r, max.get) > 0)
945*627f7eb2Smrg                 {
946*627f7eb2Smrg                     // Example: Checked!uint(1) += long(uint.max)
947*627f7eb2Smrg                     payload = hook.onUpperBound(r, max.get);
948*627f7eb2Smrg                     return this;
949*627f7eb2Smrg                 }
950*627f7eb2Smrg             }
951*627f7eb2Smrg             payload = cast(T) r;
952*627f7eb2Smrg         }
953*627f7eb2Smrg         return this;
954*627f7eb2Smrg     }
955*627f7eb2Smrg 
956*627f7eb2Smrg     ///
957*627f7eb2Smrg     static if (is(T == int) && is(Hook == void)) @safe unittest
958*627f7eb2Smrg     {
959*627f7eb2Smrg         static struct MyHook
960*627f7eb2Smrg         {
961*627f7eb2Smrg             static bool thereWereErrors;
962*627f7eb2Smrg             static T onLowerBound(Rhs, T)(Rhs rhs, T bound)
963*627f7eb2Smrg             {
964*627f7eb2Smrg                 thereWereErrors = true;
965*627f7eb2Smrg                 return bound;
966*627f7eb2Smrg             }
967*627f7eb2Smrg             static T onUpperBound(Rhs, T)(Rhs rhs, T bound)
968*627f7eb2Smrg             {
969*627f7eb2Smrg                 thereWereErrors = true;
970*627f7eb2Smrg                 return bound;
971*627f7eb2Smrg             }
972*627f7eb2Smrg         }
973*627f7eb2Smrg         auto x = checked!MyHook(byte.min);
974*627f7eb2Smrg         x -= 1;
975*627f7eb2Smrg         assert(MyHook.thereWereErrors);
976*627f7eb2Smrg         MyHook.thereWereErrors = false;
977*627f7eb2Smrg         x = byte.max;
978*627f7eb2Smrg         x += 1;
979*627f7eb2Smrg         assert(MyHook.thereWereErrors);
980*627f7eb2Smrg     }
981*627f7eb2Smrg }
982*627f7eb2Smrg 
983*627f7eb2Smrg /**
984*627f7eb2Smrg 
985*627f7eb2Smrg Convenience function that turns an integral into the corresponding `Checked`
986*627f7eb2Smrg instance by using template argument deduction. The hook type may be specified
987*627f7eb2Smrg (by default `Abort`).
988*627f7eb2Smrg 
989*627f7eb2Smrg */
990*627f7eb2Smrg Checked!(T, Hook) checked(Hook = Abort, T)(const T value)
991*627f7eb2Smrg if (is(typeof(Checked!(T, Hook)(value))))
992*627f7eb2Smrg {
993*627f7eb2Smrg     return Checked!(T, Hook)(value);
994*627f7eb2Smrg }
995*627f7eb2Smrg 
996*627f7eb2Smrg ///
997*627f7eb2Smrg @system unittest
998*627f7eb2Smrg {
999*627f7eb2Smrg     static assert(is(typeof(checked(42)) == Checked!int));
1000*627f7eb2Smrg     assert(checked(42) == Checked!int(42));
1001*627f7eb2Smrg     static assert(is(typeof(checked!WithNaN(42)) == Checked!(int, WithNaN)));
1002*627f7eb2Smrg     assert(checked!WithNaN(42) == Checked!(int, WithNaN)(42));
1003*627f7eb2Smrg }
1004*627f7eb2Smrg 
1005*627f7eb2Smrg // get
1006*627f7eb2Smrg @safe unittest
1007*627f7eb2Smrg {
1008*627f7eb2Smrg     void test(T)()
1009*627f7eb2Smrg     {
1010*627f7eb2Smrg         assert(Checked!(T, void)(ubyte(22)).get == 22);
1011*627f7eb2Smrg     }
1012*627f7eb2Smrg     test!ubyte;
1013*627f7eb2Smrg     test!(const ubyte);
1014*627f7eb2Smrg     test!(immutable ubyte);
1015*627f7eb2Smrg }
1016*627f7eb2Smrg 
1017*627f7eb2Smrg // Abort
1018*627f7eb2Smrg /**
1019*627f7eb2Smrg 
1020*627f7eb2Smrg Force all integral errors to fail by printing an error message to `stderr` and
1021*627f7eb2Smrg then abort the program. `Abort` is the default second argument for `Checked`.
1022*627f7eb2Smrg 
1023*627f7eb2Smrg */
1024*627f7eb2Smrg struct Abort
1025*627f7eb2Smrg {
1026*627f7eb2Smrg static:
1027*627f7eb2Smrg     /**
1028*627f7eb2Smrg 
1029*627f7eb2Smrg     Called automatically upon a bad cast (one that loses precision or attempts
1030*627f7eb2Smrg     to convert a negative value to an unsigned type). The source type is `Src`
1031*627f7eb2Smrg     and the destination type is `Dst`.
1032*627f7eb2Smrg 
1033*627f7eb2Smrg     Params:
1034*627f7eb2Smrg     src = The source of the cast
1035*627f7eb2Smrg 
1036*627f7eb2Smrg     Returns: Nominally the result is the desired value of the cast operation,
1037*627f7eb2Smrg     which will be forwarded as the result of the cast. For `Abort`, the
1038*627f7eb2Smrg     function never returns because it aborts the program.
1039*627f7eb2Smrg 
1040*627f7eb2Smrg     */
1041*627f7eb2Smrg     Dst onBadCast(Dst, Src)(Src src)
1042*627f7eb2Smrg     {
1043*627f7eb2Smrg         Warn.onBadCast!Dst(src);
1044*627f7eb2Smrg         assert(0);
1045*627f7eb2Smrg     }
1046*627f7eb2Smrg 
1047*627f7eb2Smrg     /**
1048*627f7eb2Smrg 
1049*627f7eb2Smrg     Called automatically upon a bounds error.
1050*627f7eb2Smrg 
1051*627f7eb2Smrg     Params:
1052*627f7eb2Smrg     rhs = The right-hand side value in the assignment, after the operator has
1053*627f7eb2Smrg     been evaluated
1054*627f7eb2Smrg     bound = The value of the bound being violated
1055*627f7eb2Smrg 
1056*627f7eb2Smrg     Returns: Nominally the result is the desired value of the operator, which
1057*627f7eb2Smrg     will be forwarded as result. For `Abort`, the function never returns because
1058*627f7eb2Smrg     it aborts the program.
1059*627f7eb2Smrg 
1060*627f7eb2Smrg     */
1061*627f7eb2Smrg     T onLowerBound(Rhs, T)(Rhs rhs, T bound)
1062*627f7eb2Smrg     {
1063*627f7eb2Smrg         Warn.onLowerBound(rhs, bound);
1064*627f7eb2Smrg         assert(0);
1065*627f7eb2Smrg     }
1066*627f7eb2Smrg     /// ditto
1067*627f7eb2Smrg     T onUpperBound(Rhs, T)(Rhs rhs, T bound)
1068*627f7eb2Smrg     {
1069*627f7eb2Smrg         Warn.onUpperBound(rhs, bound);
1070*627f7eb2Smrg         assert(0);
1071*627f7eb2Smrg     }
1072*627f7eb2Smrg 
1073*627f7eb2Smrg     /**
1074*627f7eb2Smrg 
1075*627f7eb2Smrg     Called automatically upon a comparison for equality. In case of a erroneous
1076*627f7eb2Smrg     comparison (one that would make a signed negative value appear equal to an
1077*627f7eb2Smrg     unsigned positive value), this hook issues `assert(0)` which terminates the
1078*627f7eb2Smrg     application.
1079*627f7eb2Smrg 
1080*627f7eb2Smrg     Params:
1081*627f7eb2Smrg     lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of
1082*627f7eb2Smrg       the operator is `Checked!int`
1083*627f7eb2Smrg     rhs = The right-hand side type involved in the operator
1084*627f7eb2Smrg 
1085*627f7eb2Smrg     Returns: Upon a correct comparison, returns the result of the comparison.
1086*627f7eb2Smrg     Otherwise, the function terminates the application so it never returns.
1087*627f7eb2Smrg 
1088*627f7eb2Smrg     */
1089*627f7eb2Smrg     static bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs)
1090*627f7eb2Smrg     {
1091*627f7eb2Smrg         bool error;
1092*627f7eb2Smrg         auto result = opChecked!"=="(lhs, rhs, error);
1093*627f7eb2Smrg         if (error)
1094*627f7eb2Smrg         {
1095*627f7eb2Smrg             Warn.hookOpEquals(lhs, rhs);
1096*627f7eb2Smrg             assert(0);
1097*627f7eb2Smrg         }
1098*627f7eb2Smrg         return result;
1099*627f7eb2Smrg     }
1100*627f7eb2Smrg 
1101*627f7eb2Smrg     /**
1102*627f7eb2Smrg 
1103*627f7eb2Smrg     Called automatically upon a comparison for ordering using one of the
1104*627f7eb2Smrg     operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e.
1105*627f7eb2Smrg     it would make a signed negative value appear greater than or equal to an
1106*627f7eb2Smrg     unsigned positive value), then application is terminated with `assert(0)`.
1107*627f7eb2Smrg     Otherwise, the three-state result is returned (positive if $(D lhs > rhs),
1108*627f7eb2Smrg     negative if $(D lhs < rhs), `0` otherwise).
1109*627f7eb2Smrg 
1110*627f7eb2Smrg     Params:
1111*627f7eb2Smrg     lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of
1112*627f7eb2Smrg       the operator is `Checked!int`
1113*627f7eb2Smrg     rhs = The right-hand side type involved in the operator
1114*627f7eb2Smrg 
1115*627f7eb2Smrg     Returns: For correct comparisons, returns a positive integer if $(D lhs >
1116*627f7eb2Smrg     rhs), a negative integer if  $(D lhs < rhs), `0` if the two are equal. Upon
1117*627f7eb2Smrg     a mistaken comparison such as $(D int(-1) < uint(0)), the function never
1118*627f7eb2Smrg     returns because it aborts the program.
1119*627f7eb2Smrg 
1120*627f7eb2Smrg     */
1121*627f7eb2Smrg     int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs)
1122*627f7eb2Smrg     {
1123*627f7eb2Smrg         bool error;
1124*627f7eb2Smrg         auto result = opChecked!"cmp"(lhs, rhs, error);
1125*627f7eb2Smrg         if (error)
1126*627f7eb2Smrg         {
1127*627f7eb2Smrg             Warn.hookOpCmp(lhs, rhs);
1128*627f7eb2Smrg             assert(0);
1129*627f7eb2Smrg         }
1130*627f7eb2Smrg         return result;
1131*627f7eb2Smrg     }
1132*627f7eb2Smrg 
1133*627f7eb2Smrg     /**
1134*627f7eb2Smrg 
1135*627f7eb2Smrg     Called automatically upon an overflow during a unary or binary operation.
1136*627f7eb2Smrg 
1137*627f7eb2Smrg     Params:
1138*627f7eb2Smrg     x = The operator, e.g. `-`
1139*627f7eb2Smrg     lhs = The left-hand side (or sole) argument
1140*627f7eb2Smrg     rhs = The right-hand side type involved in the operator
1141*627f7eb2Smrg 
1142*627f7eb2Smrg     Returns: Nominally the result is the desired value of the operator, which
1143*627f7eb2Smrg     will be forwarded as result. For `Abort`, the function never returns because
1144*627f7eb2Smrg     it aborts the program.
1145*627f7eb2Smrg 
1146*627f7eb2Smrg     */
1147*627f7eb2Smrg     typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs)
1148*627f7eb2Smrg     {
1149*627f7eb2Smrg         Warn.onOverflow!x(lhs);
1150*627f7eb2Smrg         assert(0);
1151*627f7eb2Smrg     }
1152*627f7eb2Smrg     /// ditto
1153*627f7eb2Smrg     typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs)
1154*627f7eb2Smrg     {
1155*627f7eb2Smrg         Warn.onOverflow!x(lhs, rhs);
1156*627f7eb2Smrg         assert(0);
1157*627f7eb2Smrg     }
1158*627f7eb2Smrg }
1159*627f7eb2Smrg 
1160*627f7eb2Smrg @system unittest
1161*627f7eb2Smrg {
1162*627f7eb2Smrg     void test(T)()
1163*627f7eb2Smrg     {
1164*627f7eb2Smrg         Checked!(int, Abort) x;
1165*627f7eb2Smrg         x = 42;
1166*627f7eb2Smrg         auto x1 = cast(T) x;
1167*627f7eb2Smrg         assert(x1 == 42);
1168*627f7eb2Smrg         //x1 += long(int.max);
1169*627f7eb2Smrg     }
1170*627f7eb2Smrg     test!short;
1171*627f7eb2Smrg     test!(const short);
1172*627f7eb2Smrg     test!(immutable short);
1173*627f7eb2Smrg }
1174*627f7eb2Smrg 
1175*627f7eb2Smrg 
1176*627f7eb2Smrg // Throw
1177*627f7eb2Smrg /**
1178*627f7eb2Smrg 
1179*627f7eb2Smrg Force all integral errors to fail by throwing an exception of type
1180*627f7eb2Smrg `Throw.CheckFailure`. The message coming with the error is similar to the one
1181*627f7eb2Smrg printed by `Warn`.
1182*627f7eb2Smrg 
1183*627f7eb2Smrg */
1184*627f7eb2Smrg struct Throw
1185*627f7eb2Smrg {
1186*627f7eb2Smrg     /**
1187*627f7eb2Smrg     Exception type thrown upon any failure.
1188*627f7eb2Smrg     */
1189*627f7eb2Smrg     static class CheckFailure : Exception
1190*627f7eb2Smrg     {
1191*627f7eb2Smrg         this(T...)(string f, T vals)
1192*627f7eb2Smrg         {
1193*627f7eb2Smrg             import std.format : format;
1194*627f7eb2Smrg             super(format(f, vals));
1195*627f7eb2Smrg         }
1196*627f7eb2Smrg     }
1197*627f7eb2Smrg 
1198*627f7eb2Smrg     /**
1199*627f7eb2Smrg 
1200*627f7eb2Smrg     Called automatically upon a bad cast (one that loses precision or attempts
1201*627f7eb2Smrg     to convert a negative value to an unsigned type). The source type is `Src`
1202*627f7eb2Smrg     and the destination type is `Dst`.
1203*627f7eb2Smrg 
1204*627f7eb2Smrg     Params:
1205*627f7eb2Smrg     src = The source of the cast
1206*627f7eb2Smrg 
1207*627f7eb2Smrg     Returns: Nominally the result is the desired value of the cast operation,
1208*627f7eb2Smrg     which will be forwarded as the result of the cast. For `Throw`, the
1209*627f7eb2Smrg     function never returns because it throws an exception.
1210*627f7eb2Smrg 
1211*627f7eb2Smrg     */
1212*627f7eb2Smrg     static Dst onBadCast(Dst, Src)(Src src)
1213*627f7eb2Smrg     {
1214*627f7eb2Smrg         throw new CheckFailure("Erroneous cast: cast(%s) %s(%s)",
1215*627f7eb2Smrg             Dst.stringof, Src.stringof, src);
1216*627f7eb2Smrg     }
1217*627f7eb2Smrg 
1218*627f7eb2Smrg     /**
1219*627f7eb2Smrg 
1220*627f7eb2Smrg     Called automatically upon a bounds error.
1221*627f7eb2Smrg 
1222*627f7eb2Smrg     Params:
1223*627f7eb2Smrg     rhs = The right-hand side value in the assignment, after the operator has
1224*627f7eb2Smrg     been evaluated
1225*627f7eb2Smrg     bound = The value of the bound being violated
1226*627f7eb2Smrg 
1227*627f7eb2Smrg     Returns: Nominally the result is the desired value of the operator, which
1228*627f7eb2Smrg     will be forwarded as result. For `Throw`, the function never returns because
1229*627f7eb2Smrg     it throws.
1230*627f7eb2Smrg 
1231*627f7eb2Smrg     */
1232*627f7eb2Smrg     static T onLowerBound(Rhs, T)(Rhs rhs, T bound)
1233*627f7eb2Smrg     {
1234*627f7eb2Smrg         throw new CheckFailure("Lower bound error: %s(%s) < %s(%s)",
1235*627f7eb2Smrg             Rhs.stringof, rhs, T.stringof, bound);
1236*627f7eb2Smrg     }
1237*627f7eb2Smrg     /// ditto
1238*627f7eb2Smrg     static T onUpperBound(Rhs, T)(Rhs rhs, T bound)
1239*627f7eb2Smrg     {
1240*627f7eb2Smrg         throw new CheckFailure("Upper bound error: %s(%s) > %s(%s)",
1241*627f7eb2Smrg             Rhs.stringof, rhs, T.stringof, bound);
1242*627f7eb2Smrg     }
1243*627f7eb2Smrg 
1244*627f7eb2Smrg     /**
1245*627f7eb2Smrg 
1246*627f7eb2Smrg     Called automatically upon a comparison for equality. Throws upon an
1247*627f7eb2Smrg     erroneous comparison (one that would make a signed negative value appear
1248*627f7eb2Smrg     equal to an unsigned positive value).
1249*627f7eb2Smrg 
1250*627f7eb2Smrg     Params:
1251*627f7eb2Smrg     lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of
1252*627f7eb2Smrg       the operator is `Checked!int`
1253*627f7eb2Smrg     rhs = The right-hand side type involved in the operator
1254*627f7eb2Smrg 
1255*627f7eb2Smrg     Returns: The result of the comparison.
1256*627f7eb2Smrg 
1257*627f7eb2Smrg     Throws: `CheckFailure` if the comparison is mathematically erroneous.
1258*627f7eb2Smrg 
1259*627f7eb2Smrg     */
1260*627f7eb2Smrg     static bool hookOpEquals(L, R)(L lhs, R rhs)
1261*627f7eb2Smrg     {
1262*627f7eb2Smrg         bool error;
1263*627f7eb2Smrg         auto result = opChecked!"=="(lhs, rhs, error);
1264*627f7eb2Smrg         if (error)
1265*627f7eb2Smrg         {
1266*627f7eb2Smrg             throw new CheckFailure("Erroneous comparison: %s(%s) == %s(%s)",
1267*627f7eb2Smrg                 L.stringof, lhs, R.stringof, rhs);
1268*627f7eb2Smrg         }
1269*627f7eb2Smrg         return result;
1270*627f7eb2Smrg     }
1271*627f7eb2Smrg 
1272*627f7eb2Smrg     /**
1273*627f7eb2Smrg 
1274*627f7eb2Smrg     Called automatically upon a comparison for ordering using one of the
1275*627f7eb2Smrg     operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e.
1276*627f7eb2Smrg     it would make a signed negative value appear greater than or equal to an
1277*627f7eb2Smrg     unsigned positive value), throws a `Throw.CheckFailure` exception.
1278*627f7eb2Smrg     Otherwise, the three-state result is returned (positive if $(D lhs > rhs),
1279*627f7eb2Smrg     negative if $(D lhs < rhs), `0` otherwise).
1280*627f7eb2Smrg 
1281*627f7eb2Smrg     Params:
1282*627f7eb2Smrg     lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of
1283*627f7eb2Smrg       the operator is `Checked!int`
1284*627f7eb2Smrg     rhs = The right-hand side type involved in the operator
1285*627f7eb2Smrg 
1286*627f7eb2Smrg     Returns: For correct comparisons, returns a positive integer if $(D lhs >
1287*627f7eb2Smrg     rhs), a negative integer if  $(D lhs < rhs), `0` if the two are equal.
1288*627f7eb2Smrg 
1289*627f7eb2Smrg     Throws: Upon a mistaken comparison such as $(D int(-1) < uint(0)), the
1290*627f7eb2Smrg     function never returns because it throws a `Throw.CheckedFailure` exception.
1291*627f7eb2Smrg 
1292*627f7eb2Smrg     */
1293*627f7eb2Smrg     static int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs)
1294*627f7eb2Smrg     {
1295*627f7eb2Smrg         bool error;
1296*627f7eb2Smrg         auto result = opChecked!"cmp"(lhs, rhs, error);
1297*627f7eb2Smrg         if (error)
1298*627f7eb2Smrg         {
1299*627f7eb2Smrg             throw new CheckFailure("Erroneous ordering comparison: %s(%s) and %s(%s)",
1300*627f7eb2Smrg                 Lhs.stringof, lhs, Rhs.stringof, rhs);
1301*627f7eb2Smrg         }
1302*627f7eb2Smrg         return result;
1303*627f7eb2Smrg     }
1304*627f7eb2Smrg 
1305*627f7eb2Smrg     /**
1306*627f7eb2Smrg 
1307*627f7eb2Smrg     Called automatically upon an overflow during a unary or binary operation.
1308*627f7eb2Smrg 
1309*627f7eb2Smrg     Params:
1310*627f7eb2Smrg     x = The operator, e.g. `-`
1311*627f7eb2Smrg     lhs = The left-hand side (or sole) argument
1312*627f7eb2Smrg     rhs = The right-hand side type involved in the operator
1313*627f7eb2Smrg 
1314*627f7eb2Smrg     Returns: Nominally the result is the desired value of the operator, which
1315*627f7eb2Smrg     will be forwarded as result. For `Throw`, the function never returns because
1316*627f7eb2Smrg     it throws an exception.
1317*627f7eb2Smrg 
1318*627f7eb2Smrg     */
1319*627f7eb2Smrg     static typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs)
1320*627f7eb2Smrg     {
1321*627f7eb2Smrg         throw new CheckFailure("Overflow on unary operator: %s%s(%s)",
1322*627f7eb2Smrg             x, Lhs.stringof, lhs);
1323*627f7eb2Smrg     }
1324*627f7eb2Smrg     /// ditto
1325*627f7eb2Smrg     static typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs)
1326*627f7eb2Smrg     {
1327*627f7eb2Smrg         throw new CheckFailure("Overflow on binary operator: %s(%s) %s %s(%s)",
1328*627f7eb2Smrg             Lhs.stringof, lhs, x, Rhs.stringof, rhs);
1329*627f7eb2Smrg     }
1330*627f7eb2Smrg }
1331*627f7eb2Smrg 
1332*627f7eb2Smrg ///
1333*627f7eb2Smrg @safe unittest
1334*627f7eb2Smrg {
1335*627f7eb2Smrg     void test(T)()
1336*627f7eb2Smrg     {
1337*627f7eb2Smrg         Checked!(int, Throw) x;
1338*627f7eb2Smrg         x = 42;
1339*627f7eb2Smrg         auto x1 = cast(T) x;
1340*627f7eb2Smrg         assert(x1 == 42);
1341*627f7eb2Smrg         x = T.max + 1;
1342*627f7eb2Smrg         import std.exception : assertThrown, assertNotThrown;
1343*627f7eb2Smrg         assertThrown(cast(T) x);
1344*627f7eb2Smrg         x = x.max;
1345*627f7eb2Smrg         assertThrown(x += 42);
1346*627f7eb2Smrg         assertThrown(x += 42L);
1347*627f7eb2Smrg         x = x.min;
1348*627f7eb2Smrg         assertThrown(-x);
1349*627f7eb2Smrg         assertThrown(x -= 42);
1350*627f7eb2Smrg         assertThrown(x -= 42L);
1351*627f7eb2Smrg         x = -1;
1352*627f7eb2Smrg         assertNotThrown(x == -1);
1353*627f7eb2Smrg         assertThrown(x == uint(-1));
1354*627f7eb2Smrg         assertNotThrown(x <= -1);
1355*627f7eb2Smrg         assertThrown(x <= uint(-1));
1356*627f7eb2Smrg     }
1357*627f7eb2Smrg     test!short;
1358*627f7eb2Smrg     test!(const short);
1359*627f7eb2Smrg     test!(immutable short);
1360*627f7eb2Smrg }
1361*627f7eb2Smrg 
1362*627f7eb2Smrg // Warn
1363*627f7eb2Smrg /**
1364*627f7eb2Smrg Hook that prints to `stderr` a trace of all integral errors, without affecting
1365*627f7eb2Smrg default behavior.
1366*627f7eb2Smrg */
1367*627f7eb2Smrg struct Warn
1368*627f7eb2Smrg {
1369*627f7eb2Smrg     import std.stdio : stderr;
1370*627f7eb2Smrg static:
1371*627f7eb2Smrg     /**
1372*627f7eb2Smrg 
1373*627f7eb2Smrg     Called automatically upon a bad cast from `src` to type `Dst` (one that
1374*627f7eb2Smrg     loses precision or attempts to convert a negative value to an unsigned
1375*627f7eb2Smrg     type).
1376*627f7eb2Smrg 
1377*627f7eb2Smrg     Params:
1378*627f7eb2Smrg     src = The source of the cast
1379*627f7eb2Smrg     Dst = The target type of the cast
1380*627f7eb2Smrg 
1381*627f7eb2Smrg     Returns: `cast(Dst) src`
1382*627f7eb2Smrg 
1383*627f7eb2Smrg     */
1384*627f7eb2Smrg     Dst onBadCast(Dst, Src)(Src src)
1385*627f7eb2Smrg     {
1386*627f7eb2Smrg         stderr.writefln("Erroneous cast: cast(%s) %s(%s)",
1387*627f7eb2Smrg             Dst.stringof, Src.stringof, src);
1388*627f7eb2Smrg         return cast(Dst) src;
1389*627f7eb2Smrg     }
1390*627f7eb2Smrg 
1391*627f7eb2Smrg     /**
1392*627f7eb2Smrg 
1393*627f7eb2Smrg     Called automatically upon a bad `opOpAssign` call (one that loses precision
1394*627f7eb2Smrg     or attempts to convert a negative value to an unsigned type).
1395*627f7eb2Smrg 
1396*627f7eb2Smrg     Params:
1397*627f7eb2Smrg     rhs = The right-hand side value in the assignment, after the operator has
1398*627f7eb2Smrg     been evaluated
1399*627f7eb2Smrg     bound = The bound being violated
1400*627f7eb2Smrg 
1401*627f7eb2Smrg     Returns: `cast(Lhs) rhs`
1402*627f7eb2Smrg     */
1403*627f7eb2Smrg     Lhs onLowerBound(Rhs, T)(Rhs rhs, T bound)
1404*627f7eb2Smrg     {
1405*627f7eb2Smrg         stderr.writefln("Lower bound error: %s(%s) < %s(%s)",
1406*627f7eb2Smrg             Rhs.stringof, rhs, T.stringof, bound);
1407*627f7eb2Smrg         return cast(T) rhs;
1408*627f7eb2Smrg     }
1409*627f7eb2Smrg     /// ditto
1410*627f7eb2Smrg     T onUpperBound(Rhs, T)(Rhs rhs, T bound)
1411*627f7eb2Smrg     {
1412*627f7eb2Smrg         stderr.writefln("Upper bound error: %s(%s) > %s(%s)",
1413*627f7eb2Smrg             Rhs.stringof, rhs, T.stringof, bound);
1414*627f7eb2Smrg         return cast(T) rhs;
1415*627f7eb2Smrg     }
1416*627f7eb2Smrg 
1417*627f7eb2Smrg     /**
1418*627f7eb2Smrg 
1419*627f7eb2Smrg     Called automatically upon a comparison for equality. In case of an Erroneous
1420*627f7eb2Smrg     comparison (one that would make a signed negative value appear equal to an
1421*627f7eb2Smrg     unsigned positive value), writes a warning message to `stderr` as a side
1422*627f7eb2Smrg     effect.
1423*627f7eb2Smrg 
1424*627f7eb2Smrg     Params:
1425*627f7eb2Smrg     lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of
1426*627f7eb2Smrg       the operator is `Checked!int`
1427*627f7eb2Smrg     rhs = The right-hand side type involved in the operator
1428*627f7eb2Smrg 
1429*627f7eb2Smrg     Returns: In all cases the function returns the built-in result of $(D lhs ==
1430*627f7eb2Smrg     rhs).
1431*627f7eb2Smrg 
1432*627f7eb2Smrg     */
1433*627f7eb2Smrg     bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs)
1434*627f7eb2Smrg     {
1435*627f7eb2Smrg         bool error;
1436*627f7eb2Smrg         auto result = opChecked!"=="(lhs, rhs, error);
1437*627f7eb2Smrg         if (error)
1438*627f7eb2Smrg         {
1439*627f7eb2Smrg             stderr.writefln("Erroneous comparison: %s(%s) == %s(%s)",
1440*627f7eb2Smrg                 Lhs.stringof, lhs, Rhs.stringof, rhs);
1441*627f7eb2Smrg             return lhs == rhs;
1442*627f7eb2Smrg         }
1443*627f7eb2Smrg         return result;
1444*627f7eb2Smrg     }
1445*627f7eb2Smrg 
1446*627f7eb2Smrg     ///
1447*627f7eb2Smrg     @system unittest
1448*627f7eb2Smrg     {
1449*627f7eb2Smrg         auto x = checked!Warn(-42);
1450*627f7eb2Smrg         // Passes
1451*627f7eb2Smrg         assert(x == -42);
1452*627f7eb2Smrg         // Passes but prints a warning
1453*627f7eb2Smrg         // assert(x == uint(-42));
1454*627f7eb2Smrg     }
1455*627f7eb2Smrg 
1456*627f7eb2Smrg     /**
1457*627f7eb2Smrg 
1458*627f7eb2Smrg     Called automatically upon a comparison for ordering using one of the
1459*627f7eb2Smrg     operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e.
1460*627f7eb2Smrg     it would make a signed negative value appear greater than or equal to an
1461*627f7eb2Smrg     unsigned positive value), then a warning message is printed to `stderr`.
1462*627f7eb2Smrg 
1463*627f7eb2Smrg     Params:
1464*627f7eb2Smrg     lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of
1465*627f7eb2Smrg       the operator is `Checked!int`
1466*627f7eb2Smrg     rhs = The right-hand side type involved in the operator
1467*627f7eb2Smrg 
1468*627f7eb2Smrg     Returns: In all cases, returns $(D lhs < rhs ? -1 : lhs > rhs). The result
1469*627f7eb2Smrg     is  not autocorrected in case of an erroneous comparison.
1470*627f7eb2Smrg 
1471*627f7eb2Smrg     */
1472*627f7eb2Smrg     int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs)
1473*627f7eb2Smrg     {
1474*627f7eb2Smrg         bool error;
1475*627f7eb2Smrg         auto result = opChecked!"cmp"(lhs, rhs, error);
1476*627f7eb2Smrg         if (error)
1477*627f7eb2Smrg         {
1478*627f7eb2Smrg             stderr.writefln("Erroneous ordering comparison: %s(%s) and %s(%s)",
1479*627f7eb2Smrg                 Lhs.stringof, lhs, Rhs.stringof, rhs);
1480*627f7eb2Smrg             return lhs < rhs ? -1 : lhs > rhs;
1481*627f7eb2Smrg         }
1482*627f7eb2Smrg         return result;
1483*627f7eb2Smrg     }
1484*627f7eb2Smrg 
1485*627f7eb2Smrg     ///
1486*627f7eb2Smrg     @system unittest
1487*627f7eb2Smrg     {
1488*627f7eb2Smrg         auto x = checked!Warn(-42);
1489*627f7eb2Smrg         // Passes
1490*627f7eb2Smrg         assert(x <= -42);
1491*627f7eb2Smrg         // Passes but prints a warning
1492*627f7eb2Smrg         // assert(x <= uint(-42));
1493*627f7eb2Smrg     }
1494*627f7eb2Smrg 
1495*627f7eb2Smrg     /**
1496*627f7eb2Smrg 
1497*627f7eb2Smrg     Called automatically upon an overflow during a unary or binary operation.
1498*627f7eb2Smrg 
1499*627f7eb2Smrg     Params:
1500*627f7eb2Smrg     x = The operator involved
1501*627f7eb2Smrg     Lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of
1502*627f7eb2Smrg       the operator is `Checked!int`
1503*627f7eb2Smrg     Rhs = The right-hand side type involved in the operator
1504*627f7eb2Smrg 
1505*627f7eb2Smrg     Returns: $(D mixin(x ~ "lhs")) for unary, $(D mixin("lhs" ~ x ~ "rhs")) for
1506*627f7eb2Smrg     binary
1507*627f7eb2Smrg 
1508*627f7eb2Smrg     */
1509*627f7eb2Smrg     typeof(~Lhs()) onOverflow(string x, Lhs)(ref Lhs lhs)
1510*627f7eb2Smrg     {
1511*627f7eb2Smrg         stderr.writefln("Overflow on unary operator: %s%s(%s)",
1512*627f7eb2Smrg             x, Lhs.stringof, lhs);
1513*627f7eb2Smrg         return mixin(x ~ "lhs");
1514*627f7eb2Smrg     }
1515*627f7eb2Smrg     /// ditto
1516*627f7eb2Smrg     typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs)
1517*627f7eb2Smrg     {
1518*627f7eb2Smrg         stderr.writefln("Overflow on binary operator: %s(%s) %s %s(%s)",
1519*627f7eb2Smrg             Lhs.stringof, lhs, x, Rhs.stringof, rhs);
1520*627f7eb2Smrg         return mixin("lhs" ~ x ~ "rhs");
1521*627f7eb2Smrg     }
1522*627f7eb2Smrg }
1523*627f7eb2Smrg 
1524*627f7eb2Smrg ///
1525*627f7eb2Smrg @system unittest
1526*627f7eb2Smrg {
1527*627f7eb2Smrg     auto x = checked!Warn(42);
1528*627f7eb2Smrg     short x1 = cast(short) x;
1529*627f7eb2Smrg     //x += long(int.max);
1530*627f7eb2Smrg     auto y = checked!Warn(cast(const int) 42);
1531*627f7eb2Smrg     short y1 = cast(const byte) y;
1532*627f7eb2Smrg }
1533*627f7eb2Smrg 
1534*627f7eb2Smrg // ProperCompare
1535*627f7eb2Smrg /**
1536*627f7eb2Smrg 
1537*627f7eb2Smrg Hook that provides arithmetically correct comparisons for equality and ordering.
1538*627f7eb2Smrg Comparing an object of type $(D Checked!(X, ProperCompare)) against another
1539*627f7eb2Smrg integral (for equality or ordering) ensures that no surprising conversions from
1540*627f7eb2Smrg signed to unsigned integral occur before the comparison. Using $(D Checked!(X,
1541*627f7eb2Smrg ProperCompare)) on either side of a comparison for equality against a
1542*627f7eb2Smrg floating-point number makes sure the integral can be properly converted to the
1543*627f7eb2Smrg floating point type, thus making sure equality is transitive.
1544*627f7eb2Smrg 
1545*627f7eb2Smrg */
1546*627f7eb2Smrg struct ProperCompare
1547*627f7eb2Smrg {
1548*627f7eb2Smrg     /**
1549*627f7eb2Smrg     Hook for `==` and `!=` that ensures comparison against integral values has
1550*627f7eb2Smrg     the behavior expected by the usual arithmetic rules. The built-in semantics
1551*627f7eb2Smrg     yield surprising behavior when comparing signed values against unsigned
1552*627f7eb2Smrg     values for equality, for example $(D uint.max == -1) or $(D -1_294_967_296 ==
1553*627f7eb2Smrg     3_000_000_000u). The call $(D hookOpEquals(x, y)) returns `true` if and only
1554*627f7eb2Smrg     if `x` and `y` represent the same arithmetic number.
1555*627f7eb2Smrg 
1556*627f7eb2Smrg     If one of the numbers is an integral and the other is a floating-point
1557*627f7eb2Smrg     number, $(D hookOpEquals(x, y)) returns `true` if and only if the integral
1558*627f7eb2Smrg     can be converted exactly (without approximation) to the floating-point
1559*627f7eb2Smrg     number. This is in order to preserve transitivity of equality: if $(D
1560*627f7eb2Smrg     hookOpEquals(x, y)) and $(D hookOpEquals(y, z)) then $(D hookOpEquals(y,
1561*627f7eb2Smrg     z)), in case `x`, `y`, and `z` are a mix of integral and floating-point
1562*627f7eb2Smrg     numbers.
1563*627f7eb2Smrg 
1564*627f7eb2Smrg     Params:
1565*627f7eb2Smrg     lhs = The left-hand side of the comparison for equality
1566*627f7eb2Smrg     rhs = The right-hand side of the comparison for equality
1567*627f7eb2Smrg 
1568*627f7eb2Smrg     Returns:
1569*627f7eb2Smrg     The result of the comparison, `true` if the values are equal
1570*627f7eb2Smrg     */
1571*627f7eb2Smrg     static bool hookOpEquals(L, R)(L lhs, R rhs)
1572*627f7eb2Smrg     {
1573*627f7eb2Smrg         alias C = typeof(lhs + rhs);
1574*627f7eb2Smrg         static if (isFloatingPoint!C)
1575*627f7eb2Smrg         {
1576*627f7eb2Smrg             static if (!isFloatingPoint!L)
1577*627f7eb2Smrg             {
1578*627f7eb2Smrg                 return hookOpEquals(rhs, lhs);
1579*627f7eb2Smrg             }
1580*627f7eb2Smrg             else static if (!isFloatingPoint!R)
1581*627f7eb2Smrg             {
1582*627f7eb2Smrg                 static assert(isFloatingPoint!L && !isFloatingPoint!R);
1583*627f7eb2Smrg                 auto rhs1 = C(rhs);
1584*627f7eb2Smrg                 return lhs == rhs1 && cast(R) rhs1 == rhs;
1585*627f7eb2Smrg             }
1586*627f7eb2Smrg             else
1587*627f7eb2Smrg                 return lhs == rhs;
1588*627f7eb2Smrg         }
1589*627f7eb2Smrg         else
1590*627f7eb2Smrg         {
1591*627f7eb2Smrg             bool error;
1592*627f7eb2Smrg             auto result = opChecked!"=="(lhs, rhs, error);
1593*627f7eb2Smrg             if (error)
1594*627f7eb2Smrg             {
1595*627f7eb2Smrg                 // Only possible error is a wrong "true"
1596*627f7eb2Smrg                 return false;
1597*627f7eb2Smrg             }
1598*627f7eb2Smrg             return result;
1599*627f7eb2Smrg         }
1600*627f7eb2Smrg     }
1601*627f7eb2Smrg 
1602*627f7eb2Smrg     /**
1603*627f7eb2Smrg     Hook for `<`, `<=`, `>`, and `>=` that ensures comparison against integral
1604*627f7eb2Smrg     values has the behavior expected by the usual arithmetic rules. The built-in
1605*627f7eb2Smrg     semantics yield surprising behavior when comparing signed values against
1606*627f7eb2Smrg     unsigned values, for example $(D 0u < -1). The call $(D hookOpCmp(x, y))
1607*627f7eb2Smrg     returns `-1` if and only if `x` is smaller than `y` in abstract arithmetic
1608*627f7eb2Smrg     sense.
1609*627f7eb2Smrg 
1610*627f7eb2Smrg     If one of the numbers is an integral and the other is a floating-point
1611*627f7eb2Smrg     number, $(D hookOpEquals(x, y)) returns a floating-point number that is `-1`
1612*627f7eb2Smrg     if `x < y`, `0` if `x == y`, `1` if `x > y`, and `NaN` if the floating-point
1613*627f7eb2Smrg     number is `NaN`.
1614*627f7eb2Smrg 
1615*627f7eb2Smrg     Params:
1616*627f7eb2Smrg     lhs = The left-hand side of the comparison for ordering
1617*627f7eb2Smrg     rhs = The right-hand side of the comparison for ordering
1618*627f7eb2Smrg 
1619*627f7eb2Smrg     Returns:
1620*627f7eb2Smrg     The result of the comparison (negative if $(D lhs < rhs), positive if $(D
1621*627f7eb2Smrg     lhs > rhs), `0` if the values are equal)
1622*627f7eb2Smrg     */
1623*627f7eb2Smrg     static auto hookOpCmp(L, R)(L lhs, R rhs)
1624*627f7eb2Smrg     {
1625*627f7eb2Smrg         alias C = typeof(lhs + rhs);
1626*627f7eb2Smrg         static if (isFloatingPoint!C)
1627*627f7eb2Smrg         {
1628*627f7eb2Smrg             return lhs < rhs
1629*627f7eb2Smrg                 ? C(-1)
1630*627f7eb2Smrg                 : lhs > rhs ? C(1) : lhs == rhs ? C(0) : C.init;
1631*627f7eb2Smrg         }
1632*627f7eb2Smrg         else
1633*627f7eb2Smrg         {
1634*627f7eb2Smrg             static if (!valueConvertible!(L, C) || !valueConvertible!(R, C))
1635*627f7eb2Smrg             {
1636*627f7eb2Smrg                 static assert(isUnsigned!C);
1637*627f7eb2Smrg                 static assert(isUnsigned!L != isUnsigned!R);
1638*627f7eb2Smrg                 if (!isUnsigned!L && lhs < 0)
1639*627f7eb2Smrg                     return -1;
1640*627f7eb2Smrg                 if (!isUnsigned!R && rhs < 0)
1641*627f7eb2Smrg                     return 1;
1642*627f7eb2Smrg             }
1643*627f7eb2Smrg             return lhs < rhs ? -1 : lhs > rhs;
1644*627f7eb2Smrg         }
1645*627f7eb2Smrg     }
1646*627f7eb2Smrg }
1647*627f7eb2Smrg 
1648*627f7eb2Smrg ///
1649*627f7eb2Smrg @safe unittest
1650*627f7eb2Smrg {
1651*627f7eb2Smrg     alias opEqualsProper = ProperCompare.hookOpEquals;
1652*627f7eb2Smrg     assert(opEqualsProper(42, 42));
1653*627f7eb2Smrg     assert(opEqualsProper(42.0, 42.0));
1654*627f7eb2Smrg     assert(opEqualsProper(42u, 42));
1655*627f7eb2Smrg     assert(opEqualsProper(42, 42u));
1656*627f7eb2Smrg     assert(-1 == 4294967295u);
1657*627f7eb2Smrg     assert(!opEqualsProper(-1, 4294967295u));
1658*627f7eb2Smrg     assert(!opEqualsProper(const uint(-1), -1));
1659*627f7eb2Smrg     assert(!opEqualsProper(uint(-1), -1.0));
1660*627f7eb2Smrg     assert(3_000_000_000U == -1_294_967_296);
1661*627f7eb2Smrg     assert(!opEqualsProper(3_000_000_000U, -1_294_967_296));
1662*627f7eb2Smrg }
1663*627f7eb2Smrg 
1664*627f7eb2Smrg @safe unittest
1665*627f7eb2Smrg {
1666*627f7eb2Smrg     alias opCmpProper = ProperCompare.hookOpCmp;
1667*627f7eb2Smrg     assert(opCmpProper(42, 42) == 0);
1668*627f7eb2Smrg     assert(opCmpProper(42, 42.0) == 0);
1669*627f7eb2Smrg     assert(opCmpProper(41, 42.0) < 0);
1670*627f7eb2Smrg     assert(opCmpProper(42, 41.0) > 0);
1671*627f7eb2Smrg     import std.math : isNaN;
1672*627f7eb2Smrg     assert(isNaN(opCmpProper(41, double.init)));
1673*627f7eb2Smrg     assert(opCmpProper(42u, 42) == 0);
1674*627f7eb2Smrg     assert(opCmpProper(42, 42u) == 0);
1675*627f7eb2Smrg     assert(opCmpProper(-1, uint(-1)) < 0);
1676*627f7eb2Smrg     assert(opCmpProper(uint(-1), -1) > 0);
1677*627f7eb2Smrg     assert(opCmpProper(-1.0, -1) == 0);
1678*627f7eb2Smrg }
1679*627f7eb2Smrg 
1680*627f7eb2Smrg @safe unittest
1681*627f7eb2Smrg {
1682*627f7eb2Smrg     auto x1 = Checked!(uint, ProperCompare)(42u);
1683*627f7eb2Smrg     assert(x1.get < -1);
1684*627f7eb2Smrg     assert(x1 > -1);
1685*627f7eb2Smrg }
1686*627f7eb2Smrg 
1687*627f7eb2Smrg // WithNaN
1688*627f7eb2Smrg /**
1689*627f7eb2Smrg 
1690*627f7eb2Smrg Hook that reserves a special value as a "Not a Number" representative. For
1691*627f7eb2Smrg signed integrals, the reserved value is `T.min`. For signed integrals, the
1692*627f7eb2Smrg reserved value is `T.max`.
1693*627f7eb2Smrg 
1694*627f7eb2Smrg The default value of a $(D Checked!(X, WithNaN)) is its NaN value, so care must
1695*627f7eb2Smrg be taken that all variables are explicitly initialized. Any arithmetic and logic
1696*627f7eb2Smrg operation involving at least on NaN becomes NaN itself. All of $(D a == b), $(D
1697*627f7eb2Smrg a < b), $(D a > b), $(D a <= b), $(D a >= b) yield `false` if at least one of
1698*627f7eb2Smrg `a` and `b` is NaN.
1699*627f7eb2Smrg 
1700*627f7eb2Smrg */
1701*627f7eb2Smrg struct WithNaN
1702*627f7eb2Smrg {
1703*627f7eb2Smrg static:
1704*627f7eb2Smrg     /**
1705*627f7eb2Smrg     The default value used for values not explicitly initialized. It is the NaN
1706*627f7eb2Smrg     value, i.e. `T.min` for signed integrals and `T.max` for unsigned integrals.
1707*627f7eb2Smrg     */
1708*627f7eb2Smrg     enum T defaultValue(T) = T.min == 0 ? T.max : T.min;
1709*627f7eb2Smrg     /**
1710*627f7eb2Smrg     The maximum value representable is $(D T.max) for signed integrals, $(D
1711*627f7eb2Smrg     T.max - 1) for unsigned integrals. The minimum value representable is $(D
1712*627f7eb2Smrg     T.min + 1) for signed integrals, $(D 0) for unsigned integrals.
1713*627f7eb2Smrg     */
1714*627f7eb2Smrg     enum T max(T) = cast(T) (T.min == 0 ? T.max - 1 : T.max);
1715*627f7eb2Smrg     /// ditto
1716*627f7eb2Smrg     enum T min(T) = cast(T) (T.min == 0 ? T(0) : T.min + 1);
1717*627f7eb2Smrg 
1718*627f7eb2Smrg     /**
1719*627f7eb2Smrg     If `rhs` is `WithNaN.defaultValue!Rhs`, returns
1720*627f7eb2Smrg     `WithNaN.defaultValue!Lhs`. Otherwise, returns $(D cast(Lhs) rhs).
1721*627f7eb2Smrg 
1722*627f7eb2Smrg     Params:
1723*627f7eb2Smrg     rhs = the value being cast (`Rhs` is the first argument to `Checked`)
1724*627f7eb2Smrg     Lhs = the target type of the cast
1725*627f7eb2Smrg 
1726*627f7eb2Smrg     Returns: The result of the cast operation.
1727*627f7eb2Smrg     */
1728*627f7eb2Smrg     Lhs hookOpCast(Lhs, Rhs)(Rhs rhs)
1729*627f7eb2Smrg     {
1730*627f7eb2Smrg         static if (is(Lhs == bool))
1731*627f7eb2Smrg         {
1732*627f7eb2Smrg             return rhs != defaultValue!Rhs && rhs != 0;
1733*627f7eb2Smrg         }
1734*627f7eb2Smrg         else static if (valueConvertible!(Rhs, Lhs))
1735*627f7eb2Smrg         {
1736*627f7eb2Smrg             return rhs != defaultValue!Rhs ? Lhs(rhs) : defaultValue!Lhs;
1737*627f7eb2Smrg         }
1738*627f7eb2Smrg         else
1739*627f7eb2Smrg         {
1740*627f7eb2Smrg             // Not value convertible, only viable option is rhs fits within the
1741*627f7eb2Smrg             // bounds of Lhs
1742*627f7eb2Smrg             static if (ProperCompare.hookOpCmp(Rhs.min, Lhs.min) < 0)
1743*627f7eb2Smrg             {
1744*627f7eb2Smrg                 // Example: hookOpCast!short(int(42)), hookOpCast!uint(int(42))
1745*627f7eb2Smrg                 if (ProperCompare.hookOpCmp(rhs, Lhs.min) < 0)
1746*627f7eb2Smrg                     return defaultValue!Lhs;
1747*627f7eb2Smrg             }
1748*627f7eb2Smrg             static if (ProperCompare.hookOpCmp(Rhs.max, Lhs.max) > 0)
1749*627f7eb2Smrg             {
1750*627f7eb2Smrg                 // Example: hookOpCast!int(uint(42))
1751*627f7eb2Smrg                 if (ProperCompare.hookOpCmp(rhs, Lhs.max) > 0)
1752*627f7eb2Smrg                     return defaultValue!Lhs;
1753*627f7eb2Smrg             }
1754*627f7eb2Smrg             return cast(Lhs) rhs;
1755*627f7eb2Smrg         }
1756*627f7eb2Smrg     }
1757*627f7eb2Smrg 
1758*627f7eb2Smrg     ///
1759*627f7eb2Smrg     @safe unittest
1760*627f7eb2Smrg     {
1761*627f7eb2Smrg         auto x = checked!WithNaN(422);
1762*627f7eb2Smrg         assert((cast(ubyte) x) == 255);
1763*627f7eb2Smrg         x = checked!WithNaN(-422);
1764*627f7eb2Smrg         assert((cast(byte) x) == -128);
1765*627f7eb2Smrg         assert(cast(short) x == -422);
1766*627f7eb2Smrg         assert(cast(bool) x);
1767*627f7eb2Smrg         x = x.init; // set back to NaN
1768*627f7eb2Smrg         assert(x != true);
1769*627f7eb2Smrg         assert(x != false);
1770*627f7eb2Smrg     }
1771*627f7eb2Smrg 
1772*627f7eb2Smrg     /**
1773*627f7eb2Smrg 
1774*627f7eb2Smrg     Returns `false` if $(D lhs == WithNaN.defaultValue!Lhs), $(D lhs == rhs)
1775*627f7eb2Smrg     otherwise.
1776*627f7eb2Smrg 
1777*627f7eb2Smrg     Params:
1778*627f7eb2Smrg     lhs = The left-hand side of the comparison (`Lhs` is the first argument to
1779*627f7eb2Smrg     `Checked`)
1780*627f7eb2Smrg     rhs = The right-hand side of the comparison
1781*627f7eb2Smrg 
1782*627f7eb2Smrg     Returns: `lhs != WithNaN.defaultValue!Lhs && lhs == rhs`
1783*627f7eb2Smrg     */
1784*627f7eb2Smrg     bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs)
1785*627f7eb2Smrg     {
1786*627f7eb2Smrg         return lhs != defaultValue!Lhs && lhs == rhs;
1787*627f7eb2Smrg     }
1788*627f7eb2Smrg 
1789*627f7eb2Smrg     /**
1790*627f7eb2Smrg 
1791*627f7eb2Smrg     If $(D lhs == WithNaN.defaultValue!Lhs), returns `double.init`. Otherwise,
1792*627f7eb2Smrg     has the same semantics as the default comparison.
1793*627f7eb2Smrg 
1794*627f7eb2Smrg     Params:
1795*627f7eb2Smrg     lhs = The left-hand side of the comparison (`Lhs` is the first argument to
1796*627f7eb2Smrg     `Checked`)
1797*627f7eb2Smrg     rhs = The right-hand side of the comparison
1798*627f7eb2Smrg 
1799*627f7eb2Smrg     Returns: `double.init` if `lhs == WitnNaN.defaultValue!Lhs`, `-1.0` if $(D
1800*627f7eb2Smrg     lhs < rhs), `0.0` if $(D lhs == rhs), `1.0` if $(D lhs > rhs).
1801*627f7eb2Smrg 
1802*627f7eb2Smrg     */
1803*627f7eb2Smrg     double hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs)
1804*627f7eb2Smrg     {
1805*627f7eb2Smrg         if (lhs == defaultValue!Lhs) return double.init;
1806*627f7eb2Smrg         return lhs < rhs
1807*627f7eb2Smrg             ? -1.0
1808*627f7eb2Smrg             : lhs > rhs ? 1.0 : lhs == rhs ? 0.0 : double.init;
1809*627f7eb2Smrg     }
1810*627f7eb2Smrg 
1811*627f7eb2Smrg     ///
1812*627f7eb2Smrg     @safe unittest
1813*627f7eb2Smrg     {
1814*627f7eb2Smrg         Checked!(int, WithNaN) x;
1815*627f7eb2Smrg         assert(!(x < 0) && !(x > 0) && !(x == 0));
1816*627f7eb2Smrg         x = 1;
1817*627f7eb2Smrg         assert(x > 0 && !(x < 0) && !(x == 0));
1818*627f7eb2Smrg     }
1819*627f7eb2Smrg 
1820*627f7eb2Smrg     /**
1821*627f7eb2Smrg     Defines hooks for unary operators `-`, `~`, `++`, and `--`.
1822*627f7eb2Smrg 
1823*627f7eb2Smrg     For `-` and `~`, if $(D v == WithNaN.defaultValue!T), returns
1824*627f7eb2Smrg     `WithNaN.defaultValue!T`. Otherwise, the semantics is the same as for the
1825*627f7eb2Smrg     built-in operator.
1826*627f7eb2Smrg 
1827*627f7eb2Smrg     For `++` and `--`, if $(D v == WithNaN.defaultValue!Lhs) or the operation
1828*627f7eb2Smrg     would result in an overflow, sets `v` to `WithNaN.defaultValue!T`.
1829*627f7eb2Smrg     Otherwise, the semantics is the same as for the built-in operator.
1830*627f7eb2Smrg 
1831*627f7eb2Smrg     Params:
1832*627f7eb2Smrg     x = The operator symbol
1833*627f7eb2Smrg     v = The left-hand side of the comparison (`T` is the first argument to
1834*627f7eb2Smrg     `Checked`)
1835*627f7eb2Smrg 
1836*627f7eb2Smrg     Returns: $(UL $(LI For $(D x == "-" || x == "~"): If  $(D v ==
1837*627f7eb2Smrg     WithNaN.defaultValue!T), the function returns `WithNaN.defaultValue!T`.
1838*627f7eb2Smrg     Otherwise it returns the normal result of the operator.) $(LI For $(D x ==
1839*627f7eb2Smrg     "++" || x == "--"): The function returns `void`.))
1840*627f7eb2Smrg 
1841*627f7eb2Smrg     */
1842*627f7eb2Smrg     auto hookOpUnary(string x, T)(ref T v)
1843*627f7eb2Smrg     {
1844*627f7eb2Smrg         static if (x == "-" || x == "~")
1845*627f7eb2Smrg         {
1846*627f7eb2Smrg             return v != defaultValue!T ? mixin(x ~ "v") : v;
1847*627f7eb2Smrg         }
1848*627f7eb2Smrg         else static if (x == "++")
1849*627f7eb2Smrg         {
1850*627f7eb2Smrg             static if (defaultValue!T == T.min)
1851*627f7eb2Smrg             {
1852*627f7eb2Smrg                 if (v != defaultValue!T)
1853*627f7eb2Smrg                 {
1854*627f7eb2Smrg                     if (v == T.max) v = defaultValue!T;
1855*627f7eb2Smrg                     else ++v;
1856*627f7eb2Smrg                 }
1857*627f7eb2Smrg             }
1858*627f7eb2Smrg             else
1859*627f7eb2Smrg             {
1860*627f7eb2Smrg                 static assert(defaultValue!T == T.max);
1861*627f7eb2Smrg                 if (v != defaultValue!T) ++v;
1862*627f7eb2Smrg             }
1863*627f7eb2Smrg         }
1864*627f7eb2Smrg         else static if (x == "--")
1865*627f7eb2Smrg         {
1866*627f7eb2Smrg             if (v != defaultValue!T) --v;
1867*627f7eb2Smrg         }
1868*627f7eb2Smrg     }
1869*627f7eb2Smrg 
1870*627f7eb2Smrg     ///
1871*627f7eb2Smrg     @safe unittest
1872*627f7eb2Smrg     {
1873*627f7eb2Smrg         Checked!(int, WithNaN) x;
1874*627f7eb2Smrg         ++x;
1875*627f7eb2Smrg         assert(x.isNaN);
1876*627f7eb2Smrg         x = 1;
1877*627f7eb2Smrg         assert(!x.isNaN);
1878*627f7eb2Smrg         x = -x;
1879*627f7eb2Smrg         ++x;
1880*627f7eb2Smrg         assert(!x.isNaN);
1881*627f7eb2Smrg     }
1882*627f7eb2Smrg 
1883*627f7eb2Smrg     @safe unittest // for coverage
1884*627f7eb2Smrg     {
1885*627f7eb2Smrg         Checked!(uint, WithNaN) y;
1886*627f7eb2Smrg         ++y;
1887*627f7eb2Smrg         assert(y.isNaN);
1888*627f7eb2Smrg     }
1889*627f7eb2Smrg 
1890*627f7eb2Smrg     /**
1891*627f7eb2Smrg     Defines hooks for binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`,
1892*627f7eb2Smrg      `^`, `<<`, `>>`, and `>>>` for cases where a `Checked` object is the
1893*627f7eb2Smrg     left-hand side operand. If $(D lhs == WithNaN.defaultValue!Lhs), returns
1894*627f7eb2Smrg     $(D WithNaN.defaultValue!(typeof(lhs + rhs))) without evaluating the
1895*627f7eb2Smrg     operand. Otherwise, evaluates the operand. If evaluation does not overflow,
1896*627f7eb2Smrg     returns the result. Otherwise, returns $(D WithNaN.defaultValue!(typeof(lhs
1897*627f7eb2Smrg     + rhs))).
1898*627f7eb2Smrg 
1899*627f7eb2Smrg     Params:
1900*627f7eb2Smrg     x = The operator symbol
1901*627f7eb2Smrg     lhs = The left-hand side operand (`Lhs` is the first argument to `Checked`)
1902*627f7eb2Smrg     rhs = The right-hand side operand
1903*627f7eb2Smrg 
1904*627f7eb2Smrg     Returns: If $(D lhs != WithNaN.defaultValue!Lhs) and the operator does not
1905*627f7eb2Smrg     overflow, the function returns the same result as the built-in operator. In
1906*627f7eb2Smrg     all other cases, returns $(D WithNaN.defaultValue!(typeof(lhs + rhs))).
1907*627f7eb2Smrg     */
1908*627f7eb2Smrg     auto hookOpBinary(string x, L, R)(L lhs, R rhs)
1909*627f7eb2Smrg     {
1910*627f7eb2Smrg         alias Result = typeof(lhs + rhs);
1911*627f7eb2Smrg         if (lhs != defaultValue!L)
1912*627f7eb2Smrg         {
1913*627f7eb2Smrg             bool error;
1914*627f7eb2Smrg             auto result = opChecked!x(lhs, rhs, error);
1915*627f7eb2Smrg             if (!error) return result;
1916*627f7eb2Smrg         }
1917*627f7eb2Smrg         return defaultValue!Result;
1918*627f7eb2Smrg     }
1919*627f7eb2Smrg 
1920*627f7eb2Smrg     ///
1921*627f7eb2Smrg     @safe unittest
1922*627f7eb2Smrg     {
1923*627f7eb2Smrg         Checked!(int, WithNaN) x;
1924*627f7eb2Smrg         assert((x + 1).isNaN);
1925*627f7eb2Smrg         x = 100;
1926*627f7eb2Smrg         assert(!(x + 1).isNaN);
1927*627f7eb2Smrg     }
1928*627f7eb2Smrg 
1929*627f7eb2Smrg     /**
1930*627f7eb2Smrg     Defines hooks for binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`,
1931*627f7eb2Smrg      `^`, `<<`, `>>`, and `>>>` for cases where a `Checked` object is the
1932*627f7eb2Smrg     right-hand side operand. If $(D rhs == WithNaN.defaultValue!Rhs), returns
1933*627f7eb2Smrg     $(D WithNaN.defaultValue!(typeof(lhs + rhs))) without evaluating the
1934*627f7eb2Smrg     operand. Otherwise, evaluates the operand. If evaluation does not overflow,
1935*627f7eb2Smrg     returns the result. Otherwise, returns $(D WithNaN.defaultValue!(typeof(lhs
1936*627f7eb2Smrg     + rhs))).
1937*627f7eb2Smrg 
1938*627f7eb2Smrg     Params:
1939*627f7eb2Smrg     x = The operator symbol
1940*627f7eb2Smrg     lhs = The left-hand side operand
1941*627f7eb2Smrg     rhs = The right-hand side operand (`Rhs` is the first argument to `Checked`)
1942*627f7eb2Smrg 
1943*627f7eb2Smrg     Returns: If $(D rhs != WithNaN.defaultValue!Rhs) and the operator does not
1944*627f7eb2Smrg     overflow, the function returns the same result as the built-in operator. In
1945*627f7eb2Smrg     all other cases, returns $(D WithNaN.defaultValue!(typeof(lhs + rhs))).
1946*627f7eb2Smrg     */
1947*627f7eb2Smrg     auto hookOpBinaryRight(string x, L, R)(L lhs, R rhs)
1948*627f7eb2Smrg     {
1949*627f7eb2Smrg         alias Result = typeof(lhs + rhs);
1950*627f7eb2Smrg         if (rhs != defaultValue!R)
1951*627f7eb2Smrg         {
1952*627f7eb2Smrg             bool error;
1953*627f7eb2Smrg             auto result = opChecked!x(lhs, rhs, error);
1954*627f7eb2Smrg             if (!error) return result;
1955*627f7eb2Smrg         }
1956*627f7eb2Smrg         return defaultValue!Result;
1957*627f7eb2Smrg     }
1958*627f7eb2Smrg     ///
1959*627f7eb2Smrg     @safe unittest
1960*627f7eb2Smrg     {
1961*627f7eb2Smrg         Checked!(int, WithNaN) x;
1962*627f7eb2Smrg         assert((1 + x).isNaN);
1963*627f7eb2Smrg         x = 100;
1964*627f7eb2Smrg         assert(!(1 + x).isNaN);
1965*627f7eb2Smrg     }
1966*627f7eb2Smrg 
1967*627f7eb2Smrg     /**
1968*627f7eb2Smrg 
1969*627f7eb2Smrg     Defines hooks for binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`,
1970*627f7eb2Smrg     `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=` for cases where a `Checked`
1971*627f7eb2Smrg     object is the left-hand side operand. If $(D lhs ==
1972*627f7eb2Smrg     WithNaN.defaultValue!Lhs), no action is carried. Otherwise, evaluates the
1973*627f7eb2Smrg     operand. If evaluation does not overflow and fits in `Lhs` without loss of
1974*627f7eb2Smrg     information or change of sign, sets `lhs` to the result. Otherwise, sets
1975*627f7eb2Smrg     `lhs` to `WithNaN.defaultValue!Lhs`.
1976*627f7eb2Smrg 
1977*627f7eb2Smrg     Params:
1978*627f7eb2Smrg     x = The operator symbol (without the `=`)
1979*627f7eb2Smrg     lhs = The left-hand side operand (`Lhs` is the first argument to `Checked`)
1980*627f7eb2Smrg     rhs = The right-hand side operand
1981*627f7eb2Smrg 
1982*627f7eb2Smrg     Returns: `void`
1983*627f7eb2Smrg     */
1984*627f7eb2Smrg     void hookOpOpAssign(string x, L, R)(ref L lhs, R rhs)
1985*627f7eb2Smrg     {
1986*627f7eb2Smrg         if (lhs == defaultValue!L)
1987*627f7eb2Smrg             return;
1988*627f7eb2Smrg         bool error;
1989*627f7eb2Smrg         auto temp = opChecked!x(lhs, rhs, error);
1990*627f7eb2Smrg         lhs = error
1991*627f7eb2Smrg             ? defaultValue!L
1992*627f7eb2Smrg             : hookOpCast!L(temp);
1993*627f7eb2Smrg     }
1994*627f7eb2Smrg 
1995*627f7eb2Smrg     ///
1996*627f7eb2Smrg     @safe unittest
1997*627f7eb2Smrg     {
1998*627f7eb2Smrg         Checked!(int, WithNaN) x;
1999*627f7eb2Smrg         x += 4;
2000*627f7eb2Smrg         assert(x.isNaN);
2001*627f7eb2Smrg         x = 0;
2002*627f7eb2Smrg         x += 4;
2003*627f7eb2Smrg         assert(!x.isNaN);
2004*627f7eb2Smrg         x += int.max;
2005*627f7eb2Smrg         assert(x.isNaN);
2006*627f7eb2Smrg     }
2007*627f7eb2Smrg }
2008*627f7eb2Smrg 
2009*627f7eb2Smrg ///
2010*627f7eb2Smrg @safe unittest
2011*627f7eb2Smrg {
2012*627f7eb2Smrg     auto x1 = Checked!(int, WithNaN)();
2013*627f7eb2Smrg     assert(x1.isNaN);
2014*627f7eb2Smrg     assert(x1.get == int.min);
2015*627f7eb2Smrg     assert(x1 != x1);
2016*627f7eb2Smrg     assert(!(x1 < x1));
2017*627f7eb2Smrg     assert(!(x1 > x1));
2018*627f7eb2Smrg     assert(!(x1 == x1));
2019*627f7eb2Smrg     ++x1;
2020*627f7eb2Smrg     assert(x1.isNaN);
2021*627f7eb2Smrg     assert(x1.get == int.min);
2022*627f7eb2Smrg     --x1;
2023*627f7eb2Smrg     assert(x1.isNaN);
2024*627f7eb2Smrg     assert(x1.get == int.min);
2025*627f7eb2Smrg     x1 = 42;
2026*627f7eb2Smrg     assert(!x1.isNaN);
2027*627f7eb2Smrg     assert(x1 == x1);
2028*627f7eb2Smrg     assert(x1 <= x1);
2029*627f7eb2Smrg     assert(x1 >= x1);
2030*627f7eb2Smrg     static assert(x1.min == int.min + 1);
2031*627f7eb2Smrg     x1 += long(int.max);
2032*627f7eb2Smrg }
2033*627f7eb2Smrg 
2034*627f7eb2Smrg /**
2035*627f7eb2Smrg Queries whether a $(D Checked!(T, WithNaN)) object is not a number (NaN).
2036*627f7eb2Smrg 
2037*627f7eb2Smrg Params: x = the `Checked` instance queried
2038*627f7eb2Smrg 
2039*627f7eb2Smrg Returns: `true` if `x` is a NaN, `false` otherwise
2040*627f7eb2Smrg */
2041*627f7eb2Smrg bool isNaN(T)(const Checked!(T, WithNaN) x)
2042*627f7eb2Smrg {
2043*627f7eb2Smrg     return x.get == x.init.get;
2044*627f7eb2Smrg }
2045*627f7eb2Smrg 
2046*627f7eb2Smrg ///
2047*627f7eb2Smrg @safe unittest
2048*627f7eb2Smrg {
2049*627f7eb2Smrg     auto x1 = Checked!(int, WithNaN)();
2050*627f7eb2Smrg     assert(x1.isNaN);
2051*627f7eb2Smrg     x1 = 1;
2052*627f7eb2Smrg     assert(!x1.isNaN);
2053*627f7eb2Smrg     x1 = x1.init;
2054*627f7eb2Smrg     assert(x1.isNaN);
2055*627f7eb2Smrg }
2056*627f7eb2Smrg 
2057*627f7eb2Smrg @safe unittest
2058*627f7eb2Smrg {
2059*627f7eb2Smrg     void test1(T)()
2060*627f7eb2Smrg     {
2061*627f7eb2Smrg         auto x1 = Checked!(T, WithNaN)();
2062*627f7eb2Smrg         assert(x1.isNaN);
2063*627f7eb2Smrg         assert(x1.get == int.min);
2064*627f7eb2Smrg         assert(x1 != x1);
2065*627f7eb2Smrg         assert(!(x1 < x1));
2066*627f7eb2Smrg         assert(!(x1 > x1));
2067*627f7eb2Smrg         assert(!(x1 == x1));
2068*627f7eb2Smrg         assert(x1.get == int.min);
2069*627f7eb2Smrg         auto x2 = Checked!(T, WithNaN)(42);
2070*627f7eb2Smrg         assert(!x2.isNaN);
2071*627f7eb2Smrg         assert(x2 == x2);
2072*627f7eb2Smrg         assert(x2 <= x2);
2073*627f7eb2Smrg         assert(x2 >= x2);
2074*627f7eb2Smrg         static assert(x2.min == T.min + 1);
2075*627f7eb2Smrg     }
2076*627f7eb2Smrg     test1!int;
2077*627f7eb2Smrg     test1!(const int);
2078*627f7eb2Smrg     test1!(immutable int);
2079*627f7eb2Smrg 
2080*627f7eb2Smrg     void test2(T)()
2081*627f7eb2Smrg     {
2082*627f7eb2Smrg         auto x1 = Checked!(T, WithNaN)();
2083*627f7eb2Smrg         assert(x1.get == T.min);
2084*627f7eb2Smrg         assert(x1 != x1);
2085*627f7eb2Smrg         assert(!(x1 < x1));
2086*627f7eb2Smrg         assert(!(x1 > x1));
2087*627f7eb2Smrg         assert(!(x1 == x1));
2088*627f7eb2Smrg         ++x1;
2089*627f7eb2Smrg         assert(x1.get == T.min);
2090*627f7eb2Smrg         --x1;
2091*627f7eb2Smrg         assert(x1.get == T.min);
2092*627f7eb2Smrg         x1 = 42;
2093*627f7eb2Smrg         assert(x1 == x1);
2094*627f7eb2Smrg         assert(x1 <= x1);
2095*627f7eb2Smrg         assert(x1 >= x1);
2096*627f7eb2Smrg         static assert(x1.min == T.min + 1);
2097*627f7eb2Smrg         x1 += long(T.max);
2098*627f7eb2Smrg     }
2099*627f7eb2Smrg     test2!int;
2100*627f7eb2Smrg }
2101*627f7eb2Smrg 
2102*627f7eb2Smrg @safe unittest
2103*627f7eb2Smrg {
2104*627f7eb2Smrg     alias Smart(T) = Checked!(Checked!(T, ProperCompare), WithNaN);
2105*627f7eb2Smrg     Smart!int x1;
2106*627f7eb2Smrg     assert(x1 != x1);
2107*627f7eb2Smrg     x1 = -1;
2108*627f7eb2Smrg     assert(x1 < 1u);
2109*627f7eb2Smrg     auto x2 = Smart!(const int)(42);
2110*627f7eb2Smrg }
2111*627f7eb2Smrg 
2112*627f7eb2Smrg // Saturate
2113*627f7eb2Smrg /**
2114*627f7eb2Smrg 
2115*627f7eb2Smrg Hook that implements $(I saturation), i.e. any arithmetic operation that would
2116*627f7eb2Smrg overflow leaves the result at its extreme value (`min` or `max` depending on the
2117*627f7eb2Smrg direction of the overflow).
2118*627f7eb2Smrg 
2119*627f7eb2Smrg Saturation is not sticky; if a value reaches its saturation value, another
2120*627f7eb2Smrg operation may take it back to normal range.
2121*627f7eb2Smrg 
2122*627f7eb2Smrg */
2123*627f7eb2Smrg struct Saturate
2124*627f7eb2Smrg {
2125*627f7eb2Smrg static:
2126*627f7eb2Smrg     /**
2127*627f7eb2Smrg 
2128*627f7eb2Smrg     Implements saturation for operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`,
2129*627f7eb2Smrg     and `>>>=`. This hook is called if the result of the binary operation does
2130*627f7eb2Smrg     not fit in `Lhs` without loss of information or a change in sign.
2131*627f7eb2Smrg 
2132*627f7eb2Smrg     Params:
2133*627f7eb2Smrg     Rhs = The right-hand side type in the assignment, after the operation has
2134*627f7eb2Smrg     been computed
2135*627f7eb2Smrg     bound = The bound being violated
2136*627f7eb2Smrg 
2137*627f7eb2Smrg     Returns: `Lhs.max` if $(D rhs >= 0), `Lhs.min` otherwise.
2138*627f7eb2Smrg 
2139*627f7eb2Smrg     */
2140*627f7eb2Smrg     T onLowerBound(Rhs, T)(Rhs rhs, T bound)
2141*627f7eb2Smrg     {
2142*627f7eb2Smrg         return bound;
2143*627f7eb2Smrg     }
2144*627f7eb2Smrg     /// ditto
2145*627f7eb2Smrg     T onUpperBound(Rhs, T)(Rhs rhs, T bound)
2146*627f7eb2Smrg     {
2147*627f7eb2Smrg         return bound;
2148*627f7eb2Smrg     }
2149*627f7eb2Smrg     ///
2150*627f7eb2Smrg     @safe unittest
2151*627f7eb2Smrg     {
2152*627f7eb2Smrg         auto x = checked!Saturate(short(100));
2153*627f7eb2Smrg         x += 33000;
2154*627f7eb2Smrg         assert(x == short.max);
2155*627f7eb2Smrg         x -= 70000;
2156*627f7eb2Smrg         assert(x == short.min);
2157*627f7eb2Smrg     }
2158*627f7eb2Smrg 
2159*627f7eb2Smrg     /**
2160*627f7eb2Smrg 
2161*627f7eb2Smrg     Implements saturation for operators `+`, `-` (unary and binary), `*`, `/`,
2162*627f7eb2Smrg     `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.
2163*627f7eb2Smrg 
2164*627f7eb2Smrg     For unary `-`, `onOverflow` is called if $(D lhs == Lhs.min) and `Lhs` is a
2165*627f7eb2Smrg     signed type. The function returns `Lhs.max`.
2166*627f7eb2Smrg 
2167*627f7eb2Smrg     For binary operators, the result is as follows: $(UL $(LI `Lhs.max` if the
2168*627f7eb2Smrg     result overflows in the positive direction, on division by `0`, or on
2169*627f7eb2Smrg     shifting right by a negative value) $(LI `Lhs.min` if the result overflows
2170*627f7eb2Smrg     in the negative direction) $(LI `0` if `lhs` is being shifted left by a
2171*627f7eb2Smrg     negative value, or shifted right by a large positive value))
2172*627f7eb2Smrg 
2173*627f7eb2Smrg     Params:
2174*627f7eb2Smrg     x = The operator involved in the `opAssign` operation
2175*627f7eb2Smrg     Lhs = The left-hand side of the operator (`Lhs` is the first argument to
2176*627f7eb2Smrg     `Checked`)
2177*627f7eb2Smrg     Rhs = The right-hand side type in the operator
2178*627f7eb2Smrg 
2179*627f7eb2Smrg     Returns: The saturated result of the operator.
2180*627f7eb2Smrg 
2181*627f7eb2Smrg     */
2182*627f7eb2Smrg     typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs)
2183*627f7eb2Smrg     {
2184*627f7eb2Smrg         static assert(x == "-" || x == "++" || x == "--");
2185*627f7eb2Smrg         return x == "--" ? Lhs.min : Lhs.max;
2186*627f7eb2Smrg     }
2187*627f7eb2Smrg     /// ditto
2188*627f7eb2Smrg     typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs)
2189*627f7eb2Smrg     {
2190*627f7eb2Smrg         static if (x == "+")
2191*627f7eb2Smrg             return rhs >= 0 ? Lhs.max : Lhs.min;
2192*627f7eb2Smrg         else static if (x == "*")
2193*627f7eb2Smrg             return (lhs >= 0) == (rhs >= 0) ? Lhs.max : Lhs.min;
2194*627f7eb2Smrg         else static if (x == "^^")
2195*627f7eb2Smrg             return lhs > 0 || !(rhs & 1) ? Lhs.max : Lhs.min;
2196*627f7eb2Smrg         else static if (x == "-")
2197*627f7eb2Smrg             return rhs >= 0 ? Lhs.min : Lhs.max;
2198*627f7eb2Smrg         else static if (x == "/" || x == "%")
2199*627f7eb2Smrg             return Lhs.max;
2200*627f7eb2Smrg         else static if (x == "<<")
2201*627f7eb2Smrg             return rhs >= 0 ? Lhs.max : 0;
2202*627f7eb2Smrg         else static if (x == ">>" || x == ">>>")
2203*627f7eb2Smrg             return rhs >= 0 ? 0 : Lhs.max;
2204*627f7eb2Smrg         else
2205*627f7eb2Smrg             static assert(false);
2206*627f7eb2Smrg     }
2207*627f7eb2Smrg     ///
2208*627f7eb2Smrg     @safe unittest
2209*627f7eb2Smrg     {
2210*627f7eb2Smrg         assert(checked!Saturate(int.max) + 1 == int.max);
2211*627f7eb2Smrg         assert(checked!Saturate(100) ^^ 10 == int.max);
2212*627f7eb2Smrg         assert(checked!Saturate(-100) ^^ 10 == int.max);
2213*627f7eb2Smrg         assert(checked!Saturate(100) / 0 == int.max);
2214*627f7eb2Smrg         assert(checked!Saturate(100) << -1 == 0);
2215*627f7eb2Smrg         assert(checked!Saturate(100) << 33 == int.max);
2216*627f7eb2Smrg         assert(checked!Saturate(100) >> -1 == int.max);
2217*627f7eb2Smrg         assert(checked!Saturate(100) >> 33 == 0);
2218*627f7eb2Smrg     }
2219*627f7eb2Smrg }
2220*627f7eb2Smrg 
2221*627f7eb2Smrg ///
2222*627f7eb2Smrg @safe unittest
2223*627f7eb2Smrg {
2224*627f7eb2Smrg     auto x = checked!Saturate(int.max);
2225*627f7eb2Smrg     ++x;
2226*627f7eb2Smrg     assert(x == int.max);
2227*627f7eb2Smrg     --x;
2228*627f7eb2Smrg     assert(x == int.max - 1);
2229*627f7eb2Smrg     x = int.min;
2230*627f7eb2Smrg     assert(-x == int.max);
2231*627f7eb2Smrg     x -= 42;
2232*627f7eb2Smrg     assert(x == int.min);
2233*627f7eb2Smrg     assert(x * -2 == int.max);
2234*627f7eb2Smrg }
2235*627f7eb2Smrg 
2236*627f7eb2Smrg /*
2237*627f7eb2Smrg Yields `true` if `T1` is "value convertible" (by C's "value preserving" rule,
2238*627f7eb2Smrg see $(HTTP c-faq.com/expr/preservingrules.html)) to `T2`, where the two are
2239*627f7eb2Smrg integral types. That is, all of values in `T1` are also in `T2`. For example
2240*627f7eb2Smrg `int` is value convertible to `long` but not to `uint` or `ulong`.
2241*627f7eb2Smrg */
2242*627f7eb2Smrg private enum valueConvertible(T1, T2) = isIntegral!T1 && isIntegral!T2 &&
2243*627f7eb2Smrg     is(T1 : T2) && (
2244*627f7eb2Smrg         isUnsigned!T1 == isUnsigned!T2 || // same signedness
2245*627f7eb2Smrg         !isUnsigned!T2 && T2.sizeof > T1.sizeof // safely convertible
2246*627f7eb2Smrg     );
2247*627f7eb2Smrg 
2248*627f7eb2Smrg /**
2249*627f7eb2Smrg 
2250*627f7eb2Smrg Defines binary operations with overflow checking for any two integral types.
2251*627f7eb2Smrg The result type obeys the language rules (even when they may be
2252*627f7eb2Smrg counterintuitive), and `overflow` is set if an overflow occurs (including
2253*627f7eb2Smrg inadvertent change of signedness, e.g. `-1` is converted to `uint`).
2254*627f7eb2Smrg Conceptually the behavior is:
2255*627f7eb2Smrg 
2256*627f7eb2Smrg $(OL $(LI Perform the operation in infinite precision)
2257*627f7eb2Smrg $(LI If the infinite-precision result fits in the result type, return it and
2258*627f7eb2Smrg do not touch `overflow`)
2259*627f7eb2Smrg $(LI Otherwise, set `overflow` to `true` and return an unspecified value)
2260*627f7eb2Smrg )
2261*627f7eb2Smrg 
2262*627f7eb2Smrg The implementation exploits properties of types and operations to minimize
2263*627f7eb2Smrg additional work.
2264*627f7eb2Smrg 
2265*627f7eb2Smrg Params:
2266*627f7eb2Smrg x = The binary operator involved, e.g. `/`
2267*627f7eb2Smrg lhs = The left-hand side of the operator
2268*627f7eb2Smrg rhs = The right-hand side of the operator
2269*627f7eb2Smrg overflow = The overflow indicator (assigned `true` in case there's an error)
2270*627f7eb2Smrg 
2271*627f7eb2Smrg Returns:
2272*627f7eb2Smrg The result of the operation, which is the same as the built-in operator
2273*627f7eb2Smrg */
2274*627f7eb2Smrg typeof(mixin(x == "cmp" ? "0" : ("L() " ~ x ~ " R()")))
2275*627f7eb2Smrg opChecked(string x, L, R)(const L lhs, const R rhs, ref bool overflow)
2276*627f7eb2Smrg if (isIntegral!L && isIntegral!R)
2277*627f7eb2Smrg {
2278*627f7eb2Smrg     static if (x == "cmp")
2279*627f7eb2Smrg         alias Result = int;
2280*627f7eb2Smrg     else
2281*627f7eb2Smrg         alias Result = typeof(mixin("L() " ~ x ~ " R()"));
2282*627f7eb2Smrg 
2283*627f7eb2Smrg     import core.checkedint : addu, adds, subs, muls, subu, mulu;
2284*627f7eb2Smrg     import std.algorithm.comparison : among;
2285*627f7eb2Smrg     static if (x == "==")
2286*627f7eb2Smrg     {
2287*627f7eb2Smrg         alias C = typeof(lhs + rhs);
2288*627f7eb2Smrg         static if (valueConvertible!(L, C) && valueConvertible!(R, C))
2289*627f7eb2Smrg         {
2290*627f7eb2Smrg             // Values are converted to R before comparison, cool.
2291*627f7eb2Smrg             return lhs == rhs;
2292*627f7eb2Smrg         }
2293*627f7eb2Smrg         else
2294*627f7eb2Smrg         {
2295*627f7eb2Smrg             static assert(isUnsigned!C);
2296*627f7eb2Smrg             static assert(isUnsigned!L != isUnsigned!R);
2297*627f7eb2Smrg             if (lhs != rhs) return false;
2298*627f7eb2Smrg             // R(lhs) and R(rhs) have the same bit pattern, yet may be
2299*627f7eb2Smrg             // different due to signedness change.
2300*627f7eb2Smrg             static if (!isUnsigned!R)
2301*627f7eb2Smrg             {
2302*627f7eb2Smrg                 if (rhs >= 0)
2303*627f7eb2Smrg                     return true;
2304*627f7eb2Smrg             }
2305*627f7eb2Smrg             else
2306*627f7eb2Smrg             {
2307*627f7eb2Smrg                 if (lhs >= 0)
2308*627f7eb2Smrg                     return true;
2309*627f7eb2Smrg             }
2310*627f7eb2Smrg             overflow = true;
2311*627f7eb2Smrg             return true;
2312*627f7eb2Smrg         }
2313*627f7eb2Smrg     }
2314*627f7eb2Smrg     else static if (x == "cmp")
2315*627f7eb2Smrg     {
2316*627f7eb2Smrg         alias C = typeof(lhs + rhs);
2317*627f7eb2Smrg         static if (!valueConvertible!(L, C) || !valueConvertible!(R, C))
2318*627f7eb2Smrg         {
2319*627f7eb2Smrg             static assert(isUnsigned!C);
2320*627f7eb2Smrg             static assert(isUnsigned!L != isUnsigned!R);
2321*627f7eb2Smrg             if (!isUnsigned!L && lhs < 0)
2322*627f7eb2Smrg             {
2323*627f7eb2Smrg                 overflow = true;
2324*627f7eb2Smrg                 return -1;
2325*627f7eb2Smrg             }
2326*627f7eb2Smrg             if (!isUnsigned!R && rhs < 0)
2327*627f7eb2Smrg             {
2328*627f7eb2Smrg                 overflow = true;
2329*627f7eb2Smrg                 return 1;
2330*627f7eb2Smrg             }
2331*627f7eb2Smrg         }
2332*627f7eb2Smrg         return lhs < rhs ? -1 : lhs > rhs;
2333*627f7eb2Smrg     }
2334*627f7eb2Smrg     else static if (x.among("<<", ">>", ">>>"))
2335*627f7eb2Smrg     {
2336*627f7eb2Smrg         // Handle shift separately from all others. The test below covers
2337*627f7eb2Smrg         // negative rhs as well.
2338*627f7eb2Smrg         import std.conv : unsigned;
2339*627f7eb2Smrg         if (unsigned(rhs) > 8 * Result.sizeof) goto fail;
2340*627f7eb2Smrg         return mixin("lhs" ~ x ~ "rhs");
2341*627f7eb2Smrg     }
2342*627f7eb2Smrg     else static if (x.among("&", "|", "^"))
2343*627f7eb2Smrg     {
2344*627f7eb2Smrg         // Nothing to check
2345*627f7eb2Smrg         return mixin("lhs" ~ x ~ "rhs");
2346*627f7eb2Smrg     }
2347*627f7eb2Smrg     else static if (x == "^^")
2348*627f7eb2Smrg     {
2349*627f7eb2Smrg         // Exponentiation is weird, handle separately
2350*627f7eb2Smrg         return pow(lhs, rhs, overflow);
2351*627f7eb2Smrg     }
2352*627f7eb2Smrg     else static if (valueConvertible!(L, Result) &&
2353*627f7eb2Smrg             valueConvertible!(R, Result))
2354*627f7eb2Smrg     {
2355*627f7eb2Smrg         static if (L.sizeof < Result.sizeof && R.sizeof < Result.sizeof &&
2356*627f7eb2Smrg             x.among("+", "-", "*"))
2357*627f7eb2Smrg         {
2358*627f7eb2Smrg             // No checks - both are value converted and result is in range
2359*627f7eb2Smrg             return mixin("lhs" ~ x ~ "rhs");
2360*627f7eb2Smrg         }
2361*627f7eb2Smrg         else static if (x == "+")
2362*627f7eb2Smrg         {
2363*627f7eb2Smrg             static if (isUnsigned!Result) alias impl = addu;
2364*627f7eb2Smrg             else alias impl = adds;
2365*627f7eb2Smrg             return impl(Result(lhs), Result(rhs), overflow);
2366*627f7eb2Smrg         }
2367*627f7eb2Smrg         else static if (x == "-")
2368*627f7eb2Smrg         {
2369*627f7eb2Smrg             static if (isUnsigned!Result) alias impl = subu;
2370*627f7eb2Smrg             else alias impl = subs;
2371*627f7eb2Smrg             return impl(Result(lhs), Result(rhs), overflow);
2372*627f7eb2Smrg         }
2373*627f7eb2Smrg         else static if (x == "*")
2374*627f7eb2Smrg         {
2375*627f7eb2Smrg             static if (!isUnsigned!L && !isUnsigned!R &&
2376*627f7eb2Smrg                 is(L == Result))
2377*627f7eb2Smrg             {
2378*627f7eb2Smrg                 if (lhs == Result.min && rhs == -1) goto fail;
2379*627f7eb2Smrg             }
2380*627f7eb2Smrg             static if (isUnsigned!Result) alias impl = mulu;
2381*627f7eb2Smrg             else alias impl = muls;
2382*627f7eb2Smrg             return impl(Result(lhs), Result(rhs), overflow);
2383*627f7eb2Smrg         }
2384*627f7eb2Smrg         else static if (x == "/" || x == "%")
2385*627f7eb2Smrg         {
2386*627f7eb2Smrg             static if (!isUnsigned!L && !isUnsigned!R &&
2387*627f7eb2Smrg                 is(L == Result) && x == "/")
2388*627f7eb2Smrg             {
2389*627f7eb2Smrg                 if (lhs == Result.min && rhs == -1) goto fail;
2390*627f7eb2Smrg             }
2391*627f7eb2Smrg             if (rhs == 0) goto fail;
2392*627f7eb2Smrg             return mixin("lhs" ~ x ~ "rhs");
2393*627f7eb2Smrg         }
2394*627f7eb2Smrg         else static assert(0, x);
2395*627f7eb2Smrg     }
2396*627f7eb2Smrg     else // Mixed signs
2397*627f7eb2Smrg     {
2398*627f7eb2Smrg         static assert(isUnsigned!Result);
2399*627f7eb2Smrg         static assert(isUnsigned!L != isUnsigned!R);
2400*627f7eb2Smrg         static if (x == "+")
2401*627f7eb2Smrg         {
2402*627f7eb2Smrg             static if (!isUnsigned!L)
2403*627f7eb2Smrg             {
2404*627f7eb2Smrg                 if (lhs < 0)
2405*627f7eb2Smrg                     return subu(Result(rhs), Result(-lhs), overflow);
2406*627f7eb2Smrg             }
2407*627f7eb2Smrg             else static if (!isUnsigned!R)
2408*627f7eb2Smrg             {
2409*627f7eb2Smrg                 if (rhs < 0)
2410*627f7eb2Smrg                     return subu(Result(lhs), Result(-rhs), overflow);
2411*627f7eb2Smrg             }
2412*627f7eb2Smrg             return addu(Result(lhs), Result(rhs), overflow);
2413*627f7eb2Smrg         }
2414*627f7eb2Smrg         else static if (x == "-")
2415*627f7eb2Smrg         {
2416*627f7eb2Smrg             static if (!isUnsigned!L)
2417*627f7eb2Smrg             {
2418*627f7eb2Smrg                 if (lhs < 0) goto fail;
2419*627f7eb2Smrg             }
2420*627f7eb2Smrg             else static if (!isUnsigned!R)
2421*627f7eb2Smrg             {
2422*627f7eb2Smrg                 if (rhs < 0)
2423*627f7eb2Smrg                     return addu(Result(lhs), Result(-rhs), overflow);
2424*627f7eb2Smrg             }
2425*627f7eb2Smrg             return subu(Result(lhs), Result(rhs), overflow);
2426*627f7eb2Smrg         }
2427*627f7eb2Smrg         else static if (x == "*")
2428*627f7eb2Smrg         {
2429*627f7eb2Smrg             static if (!isUnsigned!L)
2430*627f7eb2Smrg             {
2431*627f7eb2Smrg                 if (lhs < 0) goto fail;
2432*627f7eb2Smrg             }
2433*627f7eb2Smrg             else static if (!isUnsigned!R)
2434*627f7eb2Smrg             {
2435*627f7eb2Smrg                 if (rhs < 0) goto fail;
2436*627f7eb2Smrg             }
2437*627f7eb2Smrg             return mulu(Result(lhs), Result(rhs), overflow);
2438*627f7eb2Smrg         }
2439*627f7eb2Smrg         else static if (x == "/" || x == "%")
2440*627f7eb2Smrg         {
2441*627f7eb2Smrg             static if (!isUnsigned!L)
2442*627f7eb2Smrg             {
2443*627f7eb2Smrg                 if (lhs < 0 || rhs == 0) goto fail;
2444*627f7eb2Smrg             }
2445*627f7eb2Smrg             else static if (!isUnsigned!R)
2446*627f7eb2Smrg             {
2447*627f7eb2Smrg                 if (rhs <= 0) goto fail;
2448*627f7eb2Smrg             }
2449*627f7eb2Smrg             return mixin("Result(lhs)" ~ x ~ "Result(rhs)");
2450*627f7eb2Smrg         }
2451*627f7eb2Smrg         else static assert(0, x);
2452*627f7eb2Smrg     }
2453*627f7eb2Smrg     debug assert(false);
2454*627f7eb2Smrg fail:
2455*627f7eb2Smrg     overflow = true;
2456*627f7eb2Smrg     return Result(0);
2457*627f7eb2Smrg }
2458*627f7eb2Smrg 
2459*627f7eb2Smrg ///
2460*627f7eb2Smrg @safe unittest
2461*627f7eb2Smrg {
2462*627f7eb2Smrg     bool overflow;
2463*627f7eb2Smrg     assert(opChecked!"+"(const short(1), short(1), overflow) == 2 && !overflow);
2464*627f7eb2Smrg     assert(opChecked!"+"(1, 1, overflow) == 2 && !overflow);
2465*627f7eb2Smrg     assert(opChecked!"+"(1, 1u, overflow) == 2 && !overflow);
2466*627f7eb2Smrg     assert(opChecked!"+"(-1, 1u, overflow) == 0 && !overflow);
2467*627f7eb2Smrg     assert(opChecked!"+"(1u, -1, overflow) == 0 && !overflow);
2468*627f7eb2Smrg }
2469*627f7eb2Smrg 
2470*627f7eb2Smrg ///
2471*627f7eb2Smrg @safe unittest
2472*627f7eb2Smrg {
2473*627f7eb2Smrg     bool overflow;
2474*627f7eb2Smrg     assert(opChecked!"-"(1, 1, overflow) == 0 && !overflow);
2475*627f7eb2Smrg     assert(opChecked!"-"(1, 1u, overflow) == 0 && !overflow);
2476*627f7eb2Smrg     assert(opChecked!"-"(1u, -1, overflow) == 2 && !overflow);
2477*627f7eb2Smrg     assert(opChecked!"-"(-1, 1u, overflow) == 0 && overflow);
2478*627f7eb2Smrg }
2479*627f7eb2Smrg 
2480*627f7eb2Smrg @safe unittest
2481*627f7eb2Smrg {
2482*627f7eb2Smrg     bool overflow;
2483*627f7eb2Smrg     assert(opChecked!"*"(2, 3, overflow) == 6 && !overflow);
2484*627f7eb2Smrg     assert(opChecked!"*"(2, 3u, overflow) == 6 && !overflow);
2485*627f7eb2Smrg     assert(opChecked!"*"(1u, -1, overflow) == 0 && overflow);
2486*627f7eb2Smrg     //assert(mul(-1, 1u, overflow) == uint.max - 1 && overflow);
2487*627f7eb2Smrg }
2488*627f7eb2Smrg 
2489*627f7eb2Smrg @safe unittest
2490*627f7eb2Smrg {
2491*627f7eb2Smrg     bool overflow;
2492*627f7eb2Smrg     assert(opChecked!"/"(6, 3, overflow) == 2 && !overflow);
2493*627f7eb2Smrg     assert(opChecked!"/"(6, 3, overflow) == 2 && !overflow);
2494*627f7eb2Smrg     assert(opChecked!"/"(6u, 3, overflow) == 2 && !overflow);
2495*627f7eb2Smrg     assert(opChecked!"/"(6, 3u, overflow) == 2 && !overflow);
2496*627f7eb2Smrg     assert(opChecked!"/"(11, 0, overflow) == 0 && overflow);
2497*627f7eb2Smrg     overflow = false;
2498*627f7eb2Smrg     assert(opChecked!"/"(6u, 0, overflow) == 0 && overflow);
2499*627f7eb2Smrg     overflow = false;
2500*627f7eb2Smrg     assert(opChecked!"/"(-6, 2u, overflow) == 0 && overflow);
2501*627f7eb2Smrg     overflow = false;
2502*627f7eb2Smrg     assert(opChecked!"/"(-6, 0u, overflow) == 0 && overflow);
2503*627f7eb2Smrg     overflow = false;
2504*627f7eb2Smrg     assert(opChecked!"cmp"(0u, -6, overflow) == 1 && overflow);
2505*627f7eb2Smrg     overflow = false;
2506*627f7eb2Smrg     assert(opChecked!"|"(1, 2, overflow) == 3 && !overflow);
2507*627f7eb2Smrg }
2508*627f7eb2Smrg 
2509*627f7eb2Smrg /*
2510*627f7eb2Smrg Exponentiation function used by the implementation of operator `^^`.
2511*627f7eb2Smrg */
2512*627f7eb2Smrg private pure @safe nothrow @nogc
2513*627f7eb2Smrg auto pow(L, R)(const L lhs, const R rhs, ref bool overflow)
2514*627f7eb2Smrg if (isIntegral!L && isIntegral!R)
2515*627f7eb2Smrg {
2516*627f7eb2Smrg     if (rhs <= 1)
2517*627f7eb2Smrg     {
2518*627f7eb2Smrg         if (rhs == 0) return 1;
2519*627f7eb2Smrg         static if (!isUnsigned!R)
2520*627f7eb2Smrg             return rhs == 1
2521*627f7eb2Smrg                 ? lhs
2522*627f7eb2Smrg                 : (rhs == -1 && (lhs == 1 || lhs == -1)) ? lhs : 0;
2523*627f7eb2Smrg         else
2524*627f7eb2Smrg             return lhs;
2525*627f7eb2Smrg     }
2526*627f7eb2Smrg 
2527*627f7eb2Smrg     typeof(lhs ^^ rhs) b = void;
2528*627f7eb2Smrg     static if (!isUnsigned!L && isUnsigned!(typeof(b)))
2529*627f7eb2Smrg     {
2530*627f7eb2Smrg         // Need to worry about mixed-sign stuff
2531*627f7eb2Smrg         if (lhs < 0)
2532*627f7eb2Smrg         {
2533*627f7eb2Smrg             if (rhs & 1)
2534*627f7eb2Smrg             {
2535*627f7eb2Smrg                 if (lhs < 0) overflow = true;
2536*627f7eb2Smrg                 return 0;
2537*627f7eb2Smrg             }
2538*627f7eb2Smrg             b = -lhs;
2539*627f7eb2Smrg         }
2540*627f7eb2Smrg         else
2541*627f7eb2Smrg         {
2542*627f7eb2Smrg             b = lhs;
2543*627f7eb2Smrg         }
2544*627f7eb2Smrg     }
2545*627f7eb2Smrg     else
2546*627f7eb2Smrg     {
2547*627f7eb2Smrg         b = lhs;
2548*627f7eb2Smrg     }
2549*627f7eb2Smrg     if (b == 1) return 1;
2550*627f7eb2Smrg     if (b == -1) return (rhs & 1) ? -1 : 1;
2551*627f7eb2Smrg     if (rhs > 63)
2552*627f7eb2Smrg     {
2553*627f7eb2Smrg         overflow = true;
2554*627f7eb2Smrg         return 0;
2555*627f7eb2Smrg     }
2556*627f7eb2Smrg 
2557*627f7eb2Smrg     assert((b > 1 || b < -1) && rhs > 1);
2558*627f7eb2Smrg     return powImpl(b, cast(uint) rhs, overflow);
2559*627f7eb2Smrg }
2560*627f7eb2Smrg 
2561*627f7eb2Smrg // Inspiration: http://www.stepanovpapers.com/PAM.pdf
2562*627f7eb2Smrg pure @safe nothrow @nogc
2563*627f7eb2Smrg private T powImpl(T)(T b, uint e, ref bool overflow)
2564*627f7eb2Smrg if (isIntegral!T && T.sizeof >= 4)
2565*627f7eb2Smrg {
2566*627f7eb2Smrg     assert(e > 1);
2567*627f7eb2Smrg 
2568*627f7eb2Smrg     import core.checkedint : muls, mulu;
2569*627f7eb2Smrg     static if (isUnsigned!T) alias mul = mulu;
2570*627f7eb2Smrg     else alias mul = muls;
2571*627f7eb2Smrg 
2572*627f7eb2Smrg     T r = b;
2573*627f7eb2Smrg     --e;
2574*627f7eb2Smrg     // Loop invariant: r * (b ^^ e) is the actual result
2575*627f7eb2Smrg     for (;; e /= 2)
2576*627f7eb2Smrg     {
2577*627f7eb2Smrg         if (e % 2)
2578*627f7eb2Smrg         {
2579*627f7eb2Smrg             r = mul(r, b, overflow);
2580*627f7eb2Smrg             if (e == 1) break;
2581*627f7eb2Smrg         }
2582*627f7eb2Smrg         b = mul(b, b, overflow);
2583*627f7eb2Smrg     }
2584*627f7eb2Smrg     return r;
2585*627f7eb2Smrg }
2586*627f7eb2Smrg 
2587*627f7eb2Smrg @safe unittest
2588*627f7eb2Smrg {
2589*627f7eb2Smrg     static void testPow(T)(T x, uint e)
2590*627f7eb2Smrg     {
2591*627f7eb2Smrg         bool overflow;
2592*627f7eb2Smrg         assert(opChecked!"^^"(T(0), 0, overflow) == 1);
2593*627f7eb2Smrg         assert(opChecked!"^^"(-2, T(0), overflow) == 1);
2594*627f7eb2Smrg         assert(opChecked!"^^"(-2, T(1), overflow) == -2);
2595*627f7eb2Smrg         assert(opChecked!"^^"(-1, -1, overflow) == -1);
2596*627f7eb2Smrg         assert(opChecked!"^^"(-2, 1, overflow) == -2);
2597*627f7eb2Smrg         assert(opChecked!"^^"(-2, -1, overflow) == 0);
2598*627f7eb2Smrg         assert(opChecked!"^^"(-2, 4u, overflow) == 16);
2599*627f7eb2Smrg         assert(!overflow);
2600*627f7eb2Smrg         assert(opChecked!"^^"(-2, 3u, overflow) == 0);
2601*627f7eb2Smrg         assert(overflow);
2602*627f7eb2Smrg         overflow = false;
2603*627f7eb2Smrg         assert(opChecked!"^^"(3, 64u, overflow) == 0);
2604*627f7eb2Smrg         assert(overflow);
2605*627f7eb2Smrg         overflow = false;
2606*627f7eb2Smrg         foreach (uint i; 0 .. e)
2607*627f7eb2Smrg         {
2608*627f7eb2Smrg             assert(opChecked!"^^"(x, i, overflow) == x ^^ i);
2609*627f7eb2Smrg             assert(!overflow);
2610*627f7eb2Smrg         }
2611*627f7eb2Smrg         assert(opChecked!"^^"(x, e, overflow) == x ^^ e);
2612*627f7eb2Smrg         assert(overflow);
2613*627f7eb2Smrg     }
2614*627f7eb2Smrg 
2615*627f7eb2Smrg     testPow!int(3, 21);
2616*627f7eb2Smrg     testPow!uint(3, 21);
2617*627f7eb2Smrg     testPow!long(3, 40);
2618*627f7eb2Smrg     testPow!ulong(3, 41);
2619*627f7eb2Smrg }
2620*627f7eb2Smrg 
2621*627f7eb2Smrg version (unittest) private struct CountOverflows
2622*627f7eb2Smrg {
2623*627f7eb2Smrg     uint calls;
2624*627f7eb2Smrg     auto onOverflow(string op, Lhs)(Lhs lhs)
2625*627f7eb2Smrg     {
2626*627f7eb2Smrg         ++calls;
2627*627f7eb2Smrg         return mixin(op ~ "lhs");
2628*627f7eb2Smrg     }
2629*627f7eb2Smrg     auto onOverflow(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs)
2630*627f7eb2Smrg     {
2631*627f7eb2Smrg         ++calls;
2632*627f7eb2Smrg         return mixin("lhs" ~ op ~ "rhs");
2633*627f7eb2Smrg     }
2634*627f7eb2Smrg     T onLowerBound(Rhs, T)(Rhs rhs, T bound)
2635*627f7eb2Smrg     {
2636*627f7eb2Smrg         ++calls;
2637*627f7eb2Smrg         return cast(T) rhs;
2638*627f7eb2Smrg     }
2639*627f7eb2Smrg     T onUpperBound(Rhs, T)(Rhs rhs, T bound)
2640*627f7eb2Smrg     {
2641*627f7eb2Smrg         ++calls;
2642*627f7eb2Smrg         return cast(T) rhs;
2643*627f7eb2Smrg     }
2644*627f7eb2Smrg }
2645*627f7eb2Smrg 
2646*627f7eb2Smrg version (unittest) private struct CountOpBinary
2647*627f7eb2Smrg {
2648*627f7eb2Smrg     uint calls;
2649*627f7eb2Smrg     auto hookOpBinary(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs)
2650*627f7eb2Smrg     {
2651*627f7eb2Smrg         ++calls;
2652*627f7eb2Smrg         return mixin("lhs" ~ op ~ "rhs");
2653*627f7eb2Smrg     }
2654*627f7eb2Smrg }
2655*627f7eb2Smrg 
2656*627f7eb2Smrg // opBinary
2657*627f7eb2Smrg @nogc nothrow pure @safe unittest
2658*627f7eb2Smrg {
2659*627f7eb2Smrg     auto x = Checked!(const int, void)(42), y = Checked!(immutable int, void)(142);
2660*627f7eb2Smrg     assert(x + y == 184);
2661*627f7eb2Smrg     assert(x + 100 == 142);
2662*627f7eb2Smrg     assert(y - x == 100);
2663*627f7eb2Smrg     assert(200 - x == 158);
2664*627f7eb2Smrg     assert(y * x == 142 * 42);
2665*627f7eb2Smrg     assert(x / 1 == 42);
2666*627f7eb2Smrg     assert(x % 20 == 2);
2667*627f7eb2Smrg 
2668*627f7eb2Smrg     auto x1 = Checked!(int, CountOverflows)(42);
2669*627f7eb2Smrg     assert(x1 + 0 == 42);
2670*627f7eb2Smrg     assert(x1 + false == 42);
2671*627f7eb2Smrg     assert(is(typeof(x1 + 0.5) == double));
2672*627f7eb2Smrg     assert(x1 + 0.5 == 42.5);
2673*627f7eb2Smrg     assert(x1.hook.calls == 0);
2674*627f7eb2Smrg     assert(x1 + int.max == int.max + 42);
2675*627f7eb2Smrg     assert(x1.hook.calls == 1);
2676*627f7eb2Smrg     assert(x1 * 2 == 84);
2677*627f7eb2Smrg     assert(x1.hook.calls == 1);
2678*627f7eb2Smrg     assert(x1 / 2 == 21);
2679*627f7eb2Smrg     assert(x1.hook.calls == 1);
2680*627f7eb2Smrg     assert(x1 % 20 == 2);
2681*627f7eb2Smrg     assert(x1.hook.calls == 1);
2682*627f7eb2Smrg     assert(x1 << 2 == 42 << 2);
2683*627f7eb2Smrg     assert(x1.hook.calls == 1);
2684*627f7eb2Smrg     assert(x1 << 42 == x1.get << x1.get);
2685*627f7eb2Smrg     assert(x1.hook.calls == 2);
2686*627f7eb2Smrg     x1 = int.min;
2687*627f7eb2Smrg     assert(x1 - 1 == int.max);
2688*627f7eb2Smrg     assert(x1.hook.calls == 3);
2689*627f7eb2Smrg 
2690*627f7eb2Smrg     auto x2 = Checked!(int, CountOpBinary)(42);
2691*627f7eb2Smrg     assert(x2 + 1 == 43);
2692*627f7eb2Smrg     assert(x2.hook.calls == 1);
2693*627f7eb2Smrg 
2694*627f7eb2Smrg     auto x3 = Checked!(uint, CountOverflows)(42u);
2695*627f7eb2Smrg     assert(x3 + 1 == 43);
2696*627f7eb2Smrg     assert(x3.hook.calls == 0);
2697*627f7eb2Smrg     assert(x3 - 1 == 41);
2698*627f7eb2Smrg     assert(x3.hook.calls == 0);
2699*627f7eb2Smrg     assert(x3 + (-42) == 0);
2700*627f7eb2Smrg     assert(x3.hook.calls == 0);
2701*627f7eb2Smrg     assert(x3 - (-42) == 84);
2702*627f7eb2Smrg     assert(x3.hook.calls == 0);
2703*627f7eb2Smrg     assert(x3 * 2 == 84);
2704*627f7eb2Smrg     assert(x3.hook.calls == 0);
2705*627f7eb2Smrg     assert(x3 * -2 == -84);
2706*627f7eb2Smrg     assert(x3.hook.calls == 1);
2707*627f7eb2Smrg     assert(x3 / 2 == 21);
2708*627f7eb2Smrg     assert(x3.hook.calls == 1);
2709*627f7eb2Smrg     assert(x3 / -2 == 0);
2710*627f7eb2Smrg     assert(x3.hook.calls == 2);
2711*627f7eb2Smrg     assert(x3 ^^ 2 == 42 * 42);
2712*627f7eb2Smrg     assert(x3.hook.calls == 2);
2713*627f7eb2Smrg 
2714*627f7eb2Smrg     auto x4 = Checked!(int, CountOverflows)(42);
2715*627f7eb2Smrg     assert(x4 + 1 == 43);
2716*627f7eb2Smrg     assert(x4.hook.calls == 0);
2717*627f7eb2Smrg     assert(x4 + 1u == 43);
2718*627f7eb2Smrg     assert(x4.hook.calls == 0);
2719*627f7eb2Smrg     assert(x4 - 1 == 41);
2720*627f7eb2Smrg     assert(x4.hook.calls == 0);
2721*627f7eb2Smrg     assert(x4 * 2 == 84);
2722*627f7eb2Smrg     assert(x4.hook.calls == 0);
2723*627f7eb2Smrg     x4 = -2;
2724*627f7eb2Smrg     assert(x4 + 2u == 0);
2725*627f7eb2Smrg     assert(x4.hook.calls == 0);
2726*627f7eb2Smrg     assert(x4 * 2u == -4);
2727*627f7eb2Smrg     assert(x4.hook.calls == 1);
2728*627f7eb2Smrg 
2729*627f7eb2Smrg     auto x5 = Checked!(int, CountOverflows)(3);
2730*627f7eb2Smrg     assert(x5 ^^ 0 == 1);
2731*627f7eb2Smrg     assert(x5 ^^ 1 == 3);
2732*627f7eb2Smrg     assert(x5 ^^ 2 == 9);
2733*627f7eb2Smrg     assert(x5 ^^ 3 == 27);
2734*627f7eb2Smrg     assert(x5 ^^ 4 == 81);
2735*627f7eb2Smrg     assert(x5 ^^ 5 == 81 * 3);
2736*627f7eb2Smrg     assert(x5 ^^ 6 == 81 * 9);
2737*627f7eb2Smrg }
2738*627f7eb2Smrg 
2739*627f7eb2Smrg // opBinaryRight
2740*627f7eb2Smrg @nogc nothrow pure @safe unittest
2741*627f7eb2Smrg {
2742*627f7eb2Smrg     auto x1 = Checked!(int, CountOverflows)(42);
2743*627f7eb2Smrg     assert(1 + x1 == 43);
2744*627f7eb2Smrg     assert(true + x1 == 43);
2745*627f7eb2Smrg     assert(0.5 + x1 == 42.5);
2746*627f7eb2Smrg     auto x2 = Checked!(int, void)(42);
2747*627f7eb2Smrg     assert(x1 + x2 == 84);
2748*627f7eb2Smrg     assert(x2 + x1   == 84);
2749*627f7eb2Smrg }
2750*627f7eb2Smrg 
2751*627f7eb2Smrg // opOpAssign
2752*627f7eb2Smrg @safe unittest
2753*627f7eb2Smrg {
2754*627f7eb2Smrg     auto x1 = Checked!(int, CountOverflows)(3);
2755*627f7eb2Smrg     assert((x1 += 2) == 5);
2756*627f7eb2Smrg     x1 *= 2_000_000_000L;
2757*627f7eb2Smrg     assert(x1.hook.calls == 1);
2758*627f7eb2Smrg     x1 *= -2_000_000_000L;
2759*627f7eb2Smrg     assert(x1.hook.calls == 2);
2760*627f7eb2Smrg 
2761*627f7eb2Smrg     auto x2 = Checked!(ushort, CountOverflows)(ushort(3));
2762*627f7eb2Smrg     assert((x2 += 2) == 5);
2763*627f7eb2Smrg     assert(x2.hook.calls == 0);
2764*627f7eb2Smrg     assert((x2 += ushort.max) == cast(ushort) (ushort(5) + ushort.max));
2765*627f7eb2Smrg     assert(x2.hook.calls == 1);
2766*627f7eb2Smrg 
2767*627f7eb2Smrg     auto x3 = Checked!(uint, CountOverflows)(3u);
2768*627f7eb2Smrg     x3 *= ulong(2_000_000_000);
2769*627f7eb2Smrg     assert(x3.hook.calls == 1);
2770*627f7eb2Smrg }
2771*627f7eb2Smrg 
2772*627f7eb2Smrg // opAssign
2773*627f7eb2Smrg @safe unittest
2774*627f7eb2Smrg {
2775*627f7eb2Smrg     Checked!(int, void) x;
2776*627f7eb2Smrg     x = 42;
2777*627f7eb2Smrg     assert(x.get == 42);
2778*627f7eb2Smrg     x = x;
2779*627f7eb2Smrg     assert(x.get == 42);
2780*627f7eb2Smrg     x = short(43);
2781*627f7eb2Smrg     assert(x.get == 43);
2782*627f7eb2Smrg     x = ushort(44);
2783*627f7eb2Smrg     assert(x.get == 44);
2784*627f7eb2Smrg }
2785*627f7eb2Smrg 
2786*627f7eb2Smrg @safe unittest
2787*627f7eb2Smrg {
2788*627f7eb2Smrg     static assert(!is(typeof(Checked!(short, void)(ushort(42)))));
2789*627f7eb2Smrg     static assert(!is(typeof(Checked!(int, void)(long(42)))));
2790*627f7eb2Smrg     static assert(!is(typeof(Checked!(int, void)(ulong(42)))));
2791*627f7eb2Smrg     assert(Checked!(short, void)(short(42)).get == 42);
2792*627f7eb2Smrg     assert(Checked!(int, void)(ushort(42)).get == 42);
2793*627f7eb2Smrg }
2794*627f7eb2Smrg 
2795*627f7eb2Smrg // opCast
2796*627f7eb2Smrg @nogc nothrow pure @safe unittest
2797*627f7eb2Smrg {
2798*627f7eb2Smrg     static assert(is(typeof(cast(float) Checked!(int, void)(42)) == float));
2799*627f7eb2Smrg     assert(cast(float) Checked!(int, void)(42) == 42);
2800*627f7eb2Smrg 
2801*627f7eb2Smrg     assert(is(typeof(cast(long) Checked!(int, void)(42)) == long));
2802*627f7eb2Smrg     assert(cast(long) Checked!(int, void)(42) == 42);
2803*627f7eb2Smrg     static assert(is(typeof(cast(long) Checked!(uint, void)(42u)) == long));
2804*627f7eb2Smrg     assert(cast(long) Checked!(uint, void)(42u) == 42);
2805*627f7eb2Smrg 
2806*627f7eb2Smrg     auto x = Checked!(int, void)(42);
2807*627f7eb2Smrg     if (x) {} else assert(0);
2808*627f7eb2Smrg     x = 0;
2809*627f7eb2Smrg     if (x) assert(0);
2810*627f7eb2Smrg 
2811*627f7eb2Smrg     static struct Hook1
2812*627f7eb2Smrg     {
2813*627f7eb2Smrg         uint calls;
2814*627f7eb2Smrg         Dst hookOpCast(Dst, Src)(Src value)
2815*627f7eb2Smrg         {
2816*627f7eb2Smrg             ++calls;
2817*627f7eb2Smrg             return 42;
2818*627f7eb2Smrg         }
2819*627f7eb2Smrg     }
2820*627f7eb2Smrg     auto y = Checked!(long, Hook1)(long.max);
2821*627f7eb2Smrg     assert(cast(int) y == 42);
2822*627f7eb2Smrg     assert(cast(uint) y == 42);
2823*627f7eb2Smrg     assert(y.hook.calls == 2);
2824*627f7eb2Smrg 
2825*627f7eb2Smrg     static struct Hook2
2826*627f7eb2Smrg     {
2827*627f7eb2Smrg         uint calls;
2828*627f7eb2Smrg         Dst onBadCast(Dst, Src)(Src value)
2829*627f7eb2Smrg         {
2830*627f7eb2Smrg             ++calls;
2831*627f7eb2Smrg             return 42;
2832*627f7eb2Smrg         }
2833*627f7eb2Smrg     }
2834*627f7eb2Smrg     auto x1 = Checked!(uint, Hook2)(100u);
2835*627f7eb2Smrg     assert(cast(ushort) x1 == 100);
2836*627f7eb2Smrg     assert(cast(short) x1 == 100);
2837*627f7eb2Smrg     assert(cast(float) x1 == 100);
2838*627f7eb2Smrg     assert(cast(double) x1 == 100);
2839*627f7eb2Smrg     assert(cast(real) x1 == 100);
2840*627f7eb2Smrg     assert(x1.hook.calls == 0);
2841*627f7eb2Smrg     assert(cast(int) x1 == 100);
2842*627f7eb2Smrg     assert(x1.hook.calls == 0);
2843*627f7eb2Smrg     x1 = uint.max;
2844*627f7eb2Smrg     assert(cast(int) x1 == 42);
2845*627f7eb2Smrg     assert(x1.hook.calls == 1);
2846*627f7eb2Smrg 
2847*627f7eb2Smrg     auto x2 = Checked!(int, Hook2)(-100);
2848*627f7eb2Smrg     assert(cast(short) x2 == -100);
2849*627f7eb2Smrg     assert(cast(ushort) x2 == 42);
2850*627f7eb2Smrg     assert(cast(uint) x2 == 42);
2851*627f7eb2Smrg     assert(cast(ulong) x2 == 42);
2852*627f7eb2Smrg     assert(x2.hook.calls == 3);
2853*627f7eb2Smrg }
2854*627f7eb2Smrg 
2855*627f7eb2Smrg // opEquals
2856*627f7eb2Smrg @nogc nothrow pure @safe unittest
2857*627f7eb2Smrg {
2858*627f7eb2Smrg     assert(Checked!(int, void)(42) == 42L);
2859*627f7eb2Smrg     assert(42UL == Checked!(int, void)(42));
2860*627f7eb2Smrg 
2861*627f7eb2Smrg     static struct Hook1
2862*627f7eb2Smrg     {
2863*627f7eb2Smrg         uint calls;
2864*627f7eb2Smrg         bool hookOpEquals(Lhs, Rhs)(const Lhs lhs, const Rhs rhs)
2865*627f7eb2Smrg         {
2866*627f7eb2Smrg             ++calls;
2867*627f7eb2Smrg             return lhs != rhs;
2868*627f7eb2Smrg         }
2869*627f7eb2Smrg     }
2870*627f7eb2Smrg     auto x1 = Checked!(int, Hook1)(100);
2871*627f7eb2Smrg     assert(x1 != Checked!(long, Hook1)(100));
2872*627f7eb2Smrg     assert(x1.hook.calls == 1);
2873*627f7eb2Smrg     assert(x1 != 100u);
2874*627f7eb2Smrg     assert(x1.hook.calls == 2);
2875*627f7eb2Smrg 
2876*627f7eb2Smrg     static struct Hook2
2877*627f7eb2Smrg     {
2878*627f7eb2Smrg         uint calls;
2879*627f7eb2Smrg         bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs)
2880*627f7eb2Smrg         {
2881*627f7eb2Smrg             ++calls;
2882*627f7eb2Smrg             return false;
2883*627f7eb2Smrg         }
2884*627f7eb2Smrg     }
2885*627f7eb2Smrg     auto x2 = Checked!(int, Hook2)(-100);
2886*627f7eb2Smrg     assert(x2 != x1);
2887*627f7eb2Smrg     // For coverage: lhs has no hookOpEquals, rhs does
2888*627f7eb2Smrg     assert(Checked!(uint, void)(100u) != x2);
2889*627f7eb2Smrg     // For coverage: different types, neither has a hookOpEquals
2890*627f7eb2Smrg     assert(Checked!(uint, void)(100u) == Checked!(int, void*)(100));
2891*627f7eb2Smrg     assert(x2.hook.calls == 0);
2892*627f7eb2Smrg     assert(x2 != -100);
2893*627f7eb2Smrg     assert(x2.hook.calls == 1);
2894*627f7eb2Smrg     assert(x2 != cast(uint) -100);
2895*627f7eb2Smrg     assert(x2.hook.calls == 2);
2896*627f7eb2Smrg     x2 = 100;
2897*627f7eb2Smrg     assert(x2 != cast(uint) 100);
2898*627f7eb2Smrg     assert(x2.hook.calls == 3);
2899*627f7eb2Smrg     x2 = -100;
2900*627f7eb2Smrg 
2901*627f7eb2Smrg     auto x3 = Checked!(uint, Hook2)(100u);
2902*627f7eb2Smrg     assert(x3 != 100);
2903*627f7eb2Smrg     x3 = uint.max;
2904*627f7eb2Smrg     assert(x3 != -1);
2905*627f7eb2Smrg 
2906*627f7eb2Smrg     assert(x2 != x3);
2907*627f7eb2Smrg }
2908*627f7eb2Smrg 
2909*627f7eb2Smrg // opCmp
2910*627f7eb2Smrg @nogc nothrow pure @safe unittest
2911*627f7eb2Smrg {
2912*627f7eb2Smrg     Checked!(int, void) x;
2913*627f7eb2Smrg     assert(x <= x);
2914*627f7eb2Smrg     assert(x < 45);
2915*627f7eb2Smrg     assert(x < 45u);
2916*627f7eb2Smrg     assert(x > -45);
2917*627f7eb2Smrg     assert(x < 44.2);
2918*627f7eb2Smrg     assert(x > -44.2);
2919*627f7eb2Smrg     assert(!(x < double.init));
2920*627f7eb2Smrg     assert(!(x > double.init));
2921*627f7eb2Smrg     assert(!(x <= double.init));
2922*627f7eb2Smrg     assert(!(x >= double.init));
2923*627f7eb2Smrg 
2924*627f7eb2Smrg     static struct Hook1
2925*627f7eb2Smrg     {
2926*627f7eb2Smrg         uint calls;
2927*627f7eb2Smrg         int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs)
2928*627f7eb2Smrg         {
2929*627f7eb2Smrg             ++calls;
2930*627f7eb2Smrg             return 0;
2931*627f7eb2Smrg         }
2932*627f7eb2Smrg     }
2933*627f7eb2Smrg     auto x1 = Checked!(int, Hook1)(42);
2934*627f7eb2Smrg     assert(!(x1 < 43u));
2935*627f7eb2Smrg     assert(!(43u < x1));
2936*627f7eb2Smrg     assert(x1.hook.calls == 2);
2937*627f7eb2Smrg 
2938*627f7eb2Smrg     static struct Hook2
2939*627f7eb2Smrg     {
2940*627f7eb2Smrg         uint calls;
2941*627f7eb2Smrg         int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs)
2942*627f7eb2Smrg         {
2943*627f7eb2Smrg             ++calls;
2944*627f7eb2Smrg             return ProperCompare.hookOpCmp(lhs, rhs);
2945*627f7eb2Smrg         }
2946*627f7eb2Smrg     }
2947*627f7eb2Smrg     auto x2 = Checked!(int, Hook2)(-42);
2948*627f7eb2Smrg     assert(x2 < 43u);
2949*627f7eb2Smrg     assert(43u > x2);
2950*627f7eb2Smrg     assert(x2.hook.calls == 2);
2951*627f7eb2Smrg     x2 = 42;
2952*627f7eb2Smrg     assert(x2 > 41u);
2953*627f7eb2Smrg 
2954*627f7eb2Smrg     auto x3 = Checked!(uint, Hook2)(42u);
2955*627f7eb2Smrg     assert(x3 > 41);
2956*627f7eb2Smrg     assert(x3 > -41);
2957*627f7eb2Smrg }
2958*627f7eb2Smrg 
2959*627f7eb2Smrg // opUnary
2960*627f7eb2Smrg @nogc nothrow pure @safe unittest
2961*627f7eb2Smrg {
2962*627f7eb2Smrg     auto x = Checked!(int, void)(42);
2963*627f7eb2Smrg     assert(x == +x);
2964*627f7eb2Smrg     static assert(is(typeof(-x) == typeof(x)));
2965*627f7eb2Smrg     assert(-x == Checked!(int, void)(-42));
2966*627f7eb2Smrg     static assert(is(typeof(~x) == typeof(x)));
2967*627f7eb2Smrg     assert(~x == Checked!(int, void)(~42));
2968*627f7eb2Smrg     assert(++x == 43);
2969*627f7eb2Smrg     assert(--x == 42);
2970*627f7eb2Smrg 
2971*627f7eb2Smrg     static struct Hook1
2972*627f7eb2Smrg     {
2973*627f7eb2Smrg         uint calls;
2974*627f7eb2Smrg         auto hookOpUnary(string op, T)(T value) if (op == "-")
2975*627f7eb2Smrg         {
2976*627f7eb2Smrg             ++calls;
2977*627f7eb2Smrg             return T(42);
2978*627f7eb2Smrg         }
2979*627f7eb2Smrg         auto hookOpUnary(string op, T)(T value) if (op == "~")
2980*627f7eb2Smrg         {
2981*627f7eb2Smrg             ++calls;
2982*627f7eb2Smrg             return T(43);
2983*627f7eb2Smrg         }
2984*627f7eb2Smrg     }
2985*627f7eb2Smrg     auto x1 = Checked!(int, Hook1)(100);
2986*627f7eb2Smrg     assert(is(typeof(-x1) == typeof(x1)));
2987*627f7eb2Smrg     assert(-x1 == Checked!(int, Hook1)(42));
2988*627f7eb2Smrg     assert(is(typeof(~x1) == typeof(x1)));
2989*627f7eb2Smrg     assert(~x1 == Checked!(int, Hook1)(43));
2990*627f7eb2Smrg     assert(x1.hook.calls == 2);
2991*627f7eb2Smrg 
2992*627f7eb2Smrg     static struct Hook2
2993*627f7eb2Smrg     {
2994*627f7eb2Smrg         uint calls;
2995*627f7eb2Smrg         void hookOpUnary(string op, T)(ref T value) if (op == "++")
2996*627f7eb2Smrg         {
2997*627f7eb2Smrg             ++calls;
2998*627f7eb2Smrg             --value;
2999*627f7eb2Smrg         }
3000*627f7eb2Smrg         void hookOpUnary(string op, T)(ref T value) if (op == "--")
3001*627f7eb2Smrg         {
3002*627f7eb2Smrg             ++calls;
3003*627f7eb2Smrg             ++value;
3004*627f7eb2Smrg         }
3005*627f7eb2Smrg     }
3006*627f7eb2Smrg     auto x2 = Checked!(int, Hook2)(100);
3007*627f7eb2Smrg     assert(++x2 == 99);
3008*627f7eb2Smrg     assert(x2 == 99);
3009*627f7eb2Smrg     assert(--x2 == 100);
3010*627f7eb2Smrg     assert(x2 == 100);
3011*627f7eb2Smrg 
3012*627f7eb2Smrg     auto x3 = Checked!(int, CountOverflows)(int.max - 1);
3013*627f7eb2Smrg     assert(++x3 == int.max);
3014*627f7eb2Smrg     assert(x3.hook.calls == 0);
3015*627f7eb2Smrg     assert(++x3 == int.min);
3016*627f7eb2Smrg     assert(x3.hook.calls == 1);
3017*627f7eb2Smrg     assert(-x3 == int.min);
3018*627f7eb2Smrg     assert(x3.hook.calls == 2);
3019*627f7eb2Smrg 
3020*627f7eb2Smrg     x3 = int.min + 1;
3021*627f7eb2Smrg     assert(--x3 == int.min);
3022*627f7eb2Smrg     assert(x3.hook.calls == 2);
3023*627f7eb2Smrg     assert(--x3 == int.max);
3024*627f7eb2Smrg     assert(x3.hook.calls == 3);
3025*627f7eb2Smrg }
3026*627f7eb2Smrg 
3027*627f7eb2Smrg //
3028*627f7eb2Smrg @nogc nothrow pure @safe unittest
3029*627f7eb2Smrg {
3030*627f7eb2Smrg     Checked!(int, void) x;
3031*627f7eb2Smrg     assert(x == x);
3032*627f7eb2Smrg     assert(x == +x);
3033*627f7eb2Smrg     assert(x == -x);
3034*627f7eb2Smrg     ++x;
3035*627f7eb2Smrg     assert(x == 1);
3036*627f7eb2Smrg     x++;
3037*627f7eb2Smrg     assert(x == 2);
3038*627f7eb2Smrg 
3039*627f7eb2Smrg     x = 42;
3040*627f7eb2Smrg     assert(x == 42);
3041*627f7eb2Smrg     const short _short = 43;
3042*627f7eb2Smrg     x = _short;
3043*627f7eb2Smrg     assert(x == _short);
3044*627f7eb2Smrg     ushort _ushort = 44;
3045*627f7eb2Smrg     x = _ushort;
3046*627f7eb2Smrg     assert(x == _ushort);
3047*627f7eb2Smrg     assert(x == 44.0);
3048*627f7eb2Smrg     assert(x != 44.1);
3049*627f7eb2Smrg     assert(x < 45);
3050*627f7eb2Smrg     assert(x < 44.2);
3051*627f7eb2Smrg     assert(x > -45);
3052*627f7eb2Smrg     assert(x > -44.2);
3053*627f7eb2Smrg 
3054*627f7eb2Smrg     assert(cast(long) x == 44);
3055*627f7eb2Smrg     assert(cast(short) x == 44);
3056*627f7eb2Smrg 
3057*627f7eb2Smrg     const Checked!(uint, void) y;
3058*627f7eb2Smrg     assert(y <= y);
3059*627f7eb2Smrg     assert(y == 0);
3060*627f7eb2Smrg     assert(y < x);
3061*627f7eb2Smrg     x = -1;
3062*627f7eb2Smrg     assert(x > y);
3063*627f7eb2Smrg }
3064