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