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