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