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