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