From 8a2bd81b39a5c3c8f12adab1fb207930fc1d784c Mon Sep 17 00:00:00 2001 From: Atila Neves Date: Tue, 25 Jan 2022 01:17:24 +0100 Subject: [PATCH] Move checkedint out of experimental (#8100) Move checkedint out of experimental Signed-off-by: Razvan Nitu Merged-on-behalf-of: Razvan Nitu --- .dscanner.ini | 7 +- changelog/checkedint.dd | 5 + dub.sdl | 4 +- index.dd | 2 +- posix.mak | 2 +- std/checkedint.d | 3591 +++++++++++++++++++++++++++++++++ std/experimental/checkedint.d | 3477 +------------------------------ std/file.d | 4 +- win32.mak | 2 +- win64.mak | 2 +- 10 files changed, 3620 insertions(+), 3476 deletions(-) create mode 100644 changelog/checkedint.dd create mode 100644 std/checkedint.d diff --git a/.dscanner.ini b/.dscanner.ini index 123794ad387..4863d3220ba 100644 --- a/.dscanner.ini +++ b/.dscanner.ini @@ -167,7 +167,7 @@ assert_without_msg="-etc.c.SQL_,\ -std.experimental.allocator.mallocator,\ -std.experimental.allocator.mmap_allocator,\ -std.experimental.allocator.typed,\ --std.experimental.checkedint,\ +-std.checkedint,\ -std.file,\ -std.functional,\ -std.getopt,\ @@ -213,7 +213,7 @@ assert_without_msg="-etc.c.SQL_,\ ; Checks for assignment to auto-ref function parameters auto_ref_assignment_check="-std.algorithm.mutation,-std.typecons" ; Checks for variables that could be declared immutable -could_be_immutable_check="-std.algorithm.comparison,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.base64,-std.bigint,-std.bitmanip,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.stopwatch,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.logger.multilogger,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.cstring,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.scopebuffer,-std.internal.test.dummyrange,-std.json,-std.math,-std.mathspecial,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.interfaces,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.regex.internal.thompson,-std.signals,-std.socket,-std.stdio,-std.string,-std.sumtype,-std.traits,-std.typecons,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.windows.registry,-std.xml,-std.zip,-std.zlib" +could_be_immutable_check="-std.algorithm.comparison,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.base64,-std.bigint,-std.bitmanip,-std.checkedint,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.stopwatch,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.logger.multilogger,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.cstring,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.scopebuffer,-std.internal.test.dummyrange,-std.json,-std.math,-std.mathspecial,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.interfaces,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.regex.internal.thompson,-std.signals,-std.socket,-std.stdio,-std.string,-std.sumtype,-std.traits,-std.typecons,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.windows.registry,-std.xml,-std.zip,-std.zlib" ; Check for poor exception handling practices exception_check="-std.concurrency,-std.net.curl,-std.parallelism,-std.range,-std.socket,-std.typecons" ; Checks for poor placement of function attributes @@ -293,7 +293,7 @@ number_style_check="+disabled" ;number_style_check="-std.algorithm.iteration,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.container.array,-std.conv,-std.datetime.date,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.md,-std.digest.ripemd,-std.digest.sha,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.checkedint,-std.file,-std.format,-std.functional,-std.internal.math.biguintcore,-std.internal.math.gammafunction,-std.json,-std.math,-std.outbuffer,-std.parallelism,-std.random,-std.range,-std.regex.internal.generator,-std.utf,-std.zip,-std.zlib" ; Checks that opEquals, opCmp, toHash, and toString are either const, immutable ; , or inout. -object_const_check="-std.algorithm.searching,-std.array,-std.bitmanip,-std.concurrency,-std.container.rbtree,-std.conv,-std.datetime.interval,-std.encoding,-std.exception,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.typecons,-std.format,-std.functional,-std.meta,-std.numeric,-std.range,-std.regex,-std.stdio,-std.sumtype,-std.typecons,-std.variant,-std.xml" +object_const_check="-std.algorithm.searching,-std.array,-std.bitmanip,-std.checkedint,-std.concurrency,-std.container.rbtree,-std.conv,-std.datetime.interval,-std.encoding,-std.exception,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.typecons,-std.format,-std.functional,-std.meta,-std.numeric,-std.range,-std.regex,-std.stdio,-std.sumtype,-std.typecons,-std.variant,-std.xml" ; Checks that opEquals and toHash are both defined or neither are defined opequals_tohash_check="-std.complex,-std.container.array,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.datetime,-std.datetime.date,-std.experimental.checkedint,-std.internal.test.dummyrange,-std.json,-std.numeric,-std.random,-std.socket,-std.sumtype,-std.typecons,-std.uni" ; Check for properly documented public functions (Returns, Params) @@ -310,6 +310,7 @@ properly_documented_public_functions="-etc.c.odbc.sql,\ -std.base64,\ -std.bigint,\ -std.bitmanip,\ +-std.checkedint,\ -std.complex,\ -std.container,\ -std.container.array,\ diff --git a/changelog/checkedint.dd b/changelog/checkedint.dd new file mode 100644 index 00000000000..5bc722daf8a --- /dev/null +++ b/changelog/checkedint.dd @@ -0,0 +1,5 @@ +Move checkedint out of experimental. + +std.experimental.checkedint is now std.checkedint. The old name +is still available and publicly imports the new one, and is also +deprecated. \ No newline at end of file diff --git a/dub.sdl b/dub.sdl index a9a81e4047e..bcaf2ef06ca 100644 --- a/dub.sdl +++ b/dub.sdl @@ -15,9 +15,9 @@ subPackage { auto pkgDir = environment.get("DUB_PACKAGE_DIR"); auto destDir = pkgDir.buildPath("source", "stdx"); mkdirRecurse(destDir); - pkgDir.buildPath("std", "experimental", "checkedint.d") + pkgDir.buildPath("std", "checkedint.d") .readText - .replace("std.experimental.checkedint", "stdx.checkedint") + .replace("std.checkedint", "stdx.checkedint") .toFile(destDir.buildPath("checkedint.d"));'` sourceFiles "source/stdx/checkedint.d" importPaths "source" diff --git a/index.dd b/index.dd index 8613a3c2b66..cbc173d891d 100644 --- a/index.dd +++ b/index.dd @@ -84,7 +84,7 @@ $(BOOKTABLE , ) $(LEADINGROW Data integrity) $(TR - $(TDNW $(MREF std,experimental,checkedint)) + $(TDNW $(MREF std,checkedint)) $(TD Checked integral types.) ) $(TR diff --git a/posix.mak b/posix.mak index 40ae19aa3ca..530b824e9b6 100644 --- a/posix.mak +++ b/posix.mak @@ -208,7 +208,7 @@ STD_PACKAGES = std $(addprefix std/,\ # Modules broken down per package -PACKAGE_std = array ascii base64 bigint bitmanip compiler complex concurrency \ +PACKAGE_std = array ascii base64 bigint bitmanip checkedint compiler complex concurrency \ conv csv demangle encoding exception file \ functional getopt json mathspecial meta mmfile numeric \ outbuffer package parallelism path process random signals socket stdint \ diff --git a/std/checkedint.d b/std/checkedint.d new file mode 100644 index 00000000000..635c4207fcd --- /dev/null +++ b/std/checkedint.d @@ -0,0 +1,3591 @@ +// Written in the D programming language. +/** +$(SCRIPT inhibitQuickIndex = 1;) + +This module defines facilities for efficient checking of integral operations +against overflow, casting with loss of precision, unexpected change of sign, +etc. The checking (and possibly correction) can be done at operation level, for +example $(LREF opChecked)$(D !"+"(x, y, overflow)) adds two integrals `x` and +`y` and sets `overflow` to `true` if an overflow occurred. The flag `overflow` +(a `bool` passed by reference) is not touched if the operation succeeded, so the +same flag can be reused for a sequence of operations and tested at the end. + +Issuing individual checked operations is flexible and efficient but often +tedious. The $(LREF Checked) facility offers encapsulated integral wrappers that +do all checking internally and have configurable behavior upon erroneous +results. For example, `Checked!int` is a type that behaves like `int` but aborts +execution immediately whenever involved in an operation that produces the +arithmetically wrong result. The accompanying convenience function $(LREF +checked) uses type deduction to convert a value `x` of integral type `T` to +`Checked!T` by means of `checked(x)`. For example: + +--- +void main() +{ + import std.checkedint, std.stdio; + writeln((checked(5) + 7).get); // 12 + writeln((checked(10) * 1000 * 1000 * 1000).get); // Overflow +} +--- + +Similarly, $(D checked(-1) > uint(0)) aborts execution (even though the built-in +comparison $(D int(-1) > uint(0)) is surprisingly true due to language's +conversion rules modeled after C). Thus, `Checked!int` is a virtually drop-in +replacement for `int` useable in debug builds, to be replaced by `int` in +release mode if efficiency demands it. + +`Checked` has customizable behavior with the help of a second type parameter, +`Hook`. Depending on what methods `Hook` defines, core operations on the +underlying integral may be verified for overflow or completely redefined. If +`Hook` defines no method at all and carries no state, there is no change in +behavior, i.e. $(D Checked!(int, void)) is a wrapper around `int` that adds no +customization at all. + +This module provides a few predefined hooks (below) that add useful behavior to +`Checked`: + +$(BOOKTABLE , + $(TR $(TD $(LREF Abort)) $(TD + fails every incorrect operation with a message to $(REF + stderr, std, stdio) followed by a call to `assert(0)`. It is the default + second parameter, i.e. `Checked!short` is the same as + $(D Checked!(short, Abort)). + )) + $(TR $(TD $(LREF Throw)) $(TD + fails every incorrect operation by throwing an exception. + )) + $(TR $(TD $(LREF Warn)) $(TD + prints incorrect operations to $(REF stderr, std, stdio) + but otherwise preserves the built-in behavior. + )) + $(TR $(TD $(LREF ProperCompare)) $(TD + fixes the comparison operators `==`, `!=`, `<`, `<=`, `>`, and `>=` + to return correct results in all circumstances, + at a slight cost in efficiency. For example, + $(D Checked!(uint, ProperCompare)(1) > -1) is `true`, + which is not the case for the built-in comparison. Also, comparing + numbers for equality with floating-point numbers only passes if the + integral can be converted to the floating-point number precisely, + so as to preserve transitivity of equality. + )) + $(TR $(TD $(LREF WithNaN)) $(TD + reserves a special "Not a Number" (NaN) value akin to the homonym value + reserved for floating-point values. Once a $(D Checked!(X, WithNaN)) + gets this special value, it preserves and propagates it until + reassigned. $(LREF isNaN) can be used to query whether the object + is not a number. + )) + $(TR $(TD $(LREF Saturate)) $(TD + implements saturating arithmetic, i.e. $(D Checked!(int, Saturate)) + "stops" at `int.max` for all operations that would cause an `int` to + overflow toward infinity, and at `int.min` for all operations that would + correspondingly overflow toward negative infinity. + )) +) + + +These policies may be used alone, e.g. $(D Checked!(uint, WithNaN)) defines a +`uint`-like type that reaches a stable NaN state for all erroneous operations. +They may also be "stacked" on top of each other, owing to the property that a +checked integral emulates an actual integral, which means another checked +integral can be built on top of it. Some combinations of interest include: + +$(BOOKTABLE , + $(TR $(TD $(D Checked!(Checked!int, ProperCompare)))) + $(TR $(TD +defines an `int` with fixed +comparison operators that will fail with `assert(0)` upon overflow. (Recall that +`Abort` is the default policy.) The order in which policies are combined is +important because the outermost policy (`ProperCompare` in this case) has the +first crack at intercepting an operator. The converse combination $(D +Checked!(Checked!(int, ProperCompare))) is meaningless because `Abort` will +intercept comparison and will fail without giving `ProperCompare` a chance to +intervene. + )) + $(TR $(TD)) + $(TR $(TDNW $(D Checked!(Checked!(int, ProperCompare), WithNaN)))) + $(TR $(TD +defines an `int`-like +type that supports a NaN value. For values that are not NaN, comparison works +properly. Again the composition order is important; $(D Checked!(Checked!(int, +WithNaN), ProperCompare)) does not have good semantics because `ProperCompare` +intercepts comparisons before the numbers involved are tested for NaN. + )) +) + +The hook's members are looked up statically in a Design by Introspection manner +and are all optional. The table below illustrates the members that a hook type +may define and their influence over the behavior of the `Checked` type using it. +In the table, `hook` is an alias for `Hook` if the type `Hook` does not +introduce any state, or an object of type `Hook` otherwise. + +$(TABLE , +$(TR $(TH `Hook` member) $(TH Semantics in $(D Checked!(T, Hook))) +) +$(TR $(TD `defaultValue`) $(TD If defined, `Hook.defaultValue!T` is used as the +default initializer of the payload.) +) +$(TR $(TD `min`) $(TD If defined, `Hook.min!T` is used as the minimum value of +the payload.) +) +$(TR $(TD `max`) $(TD If defined, `Hook.max!T` is used as the maximum value of +the payload.) +) +$(TR $(TD `hookOpCast`) $(TD If defined, `hook.hookOpCast!U(get)` is forwarded +to unconditionally when the payload is to be cast to type `U`.) +) +$(TR $(TD `onBadCast`) $(TD If defined and `hookOpCast` is $(I not) defined, +`onBadCast!U(get)` is forwarded to when the payload is to be cast to type `U` +and the cast would lose information or force a change of sign.) +) +$(TR $(TD `hookOpEquals`) $(TD If defined, $(D hook.hookOpEquals(get, rhs)) is +forwarded to unconditionally when the payload is compared for equality against +value `rhs` of integral, floating point, or Boolean type.) +) +$(TR $(TD `hookOpCmp`) $(TD If defined, $(D hook.hookOpCmp(get, rhs)) is +forwarded to unconditionally when the payload is compared for ordering against +value `rhs` of integral, floating point, or Boolean type.) +) +$(TR $(TD `hookOpUnary`) $(TD If defined, `hook.hookOpUnary!op(get)` (where `op` +is the operator symbol) is forwarded to for unary operators `-` and `~`. In +addition, for unary operators `++` and `--`, `hook.hookOpUnary!op(payload)` is +called, where `payload` is a reference to the value wrapped by `Checked` so the +hook can change it.) +) +$(TR $(TD `hookOpBinary`) $(TD If defined, $(D hook.hookOpBinary!op(get, rhs)) +(where `op` is the operator symbol and `rhs` is the right-hand side operand) is +forwarded to unconditionally for binary operators `+`, `-`, `*`, `/`, `%`, +`^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.) +) +$(TR $(TD `hookOpBinaryRight`) $(TD If defined, $(D +hook.hookOpBinaryRight!op(lhs, get)) (where `op` is the operator symbol and +`lhs` is the left-hand side operand) is forwarded to unconditionally for binary +operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.) +) +$(TR $(TD `onOverflow`) $(TD If defined, `hook.onOverflow!op(get)` is forwarded +to for unary operators that overflow but only if `hookOpUnary` is not defined. +Unary `~` does not overflow; unary `-` overflows only when the most negative +value of a signed type is negated, and the result of the hook call is returned. +When the increment or decrement operators overflow, the payload is assigned the +result of `hook.onOverflow!op(get)`. When a binary operator overflows, the +result of $(D hook.onOverflow!op(get, rhs)) is returned, but only if `Hook` does +not define `hookOpBinary`.) +) +$(TR $(TD `hookOpOpAssign`) $(TD If defined, $(D hook.hookOpOpAssign!op(payload, +rhs)) (where `op` is the operator symbol and `rhs` is the right-hand side +operand) is forwarded to unconditionally for binary operators `+=`, `-=`, `*=`, `/=`, `%=`, +`^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=`.) +) +$(TR $(TD `onLowerBound`) $(TD If defined, $(D hook.onLowerBound(value, bound)) +(where `value` is the value being assigned) is forwarded to when the result of +binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, +and `>>>=` is smaller than the smallest value representable by `T`.) +) +$(TR $(TD `onUpperBound`) $(TD If defined, $(D hook.onUpperBound(value, bound)) +(where `value` is the value being assigned) is forwarded to when the result of +binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, +and `>>>=` is larger than the largest value representable by `T`.) +) +$(TR $(TD `hookToHash`) $(TD If defined, $(D hook.hookToHash(payload)) +(where `payload` is a reference to the value wrapped by Checked) is forwarded +to when `toHash` is called on a Checked type. Custom hashing can be implemented +in a `Hook`, otherwise the built-in hashing is used.) +) +) + +Source: $(PHOBOSSRC std/checkedint.d) +*/ +module std.checkedint; +import std.traits : isFloatingPoint, isIntegral, isNumeric, isUnsigned, Unqual; + +/// +@safe unittest +{ + int[] concatAndAdd(int[] a, int[] b, int offset) + { + // Aborts on overflow on size computation + auto r = new int[(checked(a.length) + b.length).get]; + // Aborts on overflow on element computation + foreach (i; 0 .. a.length) + r[i] = (a[i] + checked(offset)).get; + foreach (i; 0 .. b.length) + r[i + a.length] = (b[i] + checked(offset)).get; + return r; + } + assert(concatAndAdd([1, 2, 3], [4, 5], -1) == [0, 1, 2, 3, 4]); +} + + +/// `Saturate` stops at an overflow +@safe unittest +{ + auto x = (cast(byte) 127).checked!Saturate; + assert(x == 127); + x++; + assert(x == 127); +} + +/// `WithNaN` has a special "Not a Number" (NaN) value akin to the homonym value reserved for floating-point values +@safe unittest +{ + auto x = 100.checked!WithNaN; + assert(x == 100); + x /= 0; + assert(x.isNaN); +} + +/// `ProperCompare` fixes the comparison operators ==, !=, <, <=, >, and >= to return correct results +@safe unittest +{ + uint x = 1; + auto y = x.checked!ProperCompare; + assert(x < -1); // built-in comparison + assert(y > -1); // ProperCompare +} + +/// `Throw` fails every incorrect operation by throwing an exception +@safe unittest +{ + import std.exception : assertThrown; + auto x = -1.checked!Throw; + assertThrown(x / 0); + assertThrown(x + int.min); + assertThrown(x == uint.max); +} + +/** +Checked integral type wraps an integral `T` and customizes its behavior with the +help of a `Hook` type. The type wrapped must be one of the predefined integrals +(unqualified), or another instance of `Checked`. + +Params: + T = type that is wrapped in the `Checked` type + Hook = hook type that customizes the behavior of the `Checked` type +*/ +struct Checked(T, Hook = Abort) +if (isIntegral!T || is(T == Checked!(U, H), U, H)) +{ + import std.algorithm.comparison : among; + import std.experimental.allocator.common : stateSize; + import std.format.spec : FormatSpec; + import std.range.primitives : isInputRange, ElementType; + import std.traits : hasMember, isSomeChar; + + /** + The type of the integral subject to checking. + */ + alias Representation = T; + + // state { + static if (hasMember!(Hook, "defaultValue")) + private T payload = Hook.defaultValue!T; + else + private T payload; + /** + `hook` is a member variable if it has state, or an alias for `Hook` + otherwise. + */ + static if (stateSize!Hook > 0) Hook hook; + else alias hook = Hook; + // } state + + // get + /** + Returns: + A copy of the underlying value. + */ + auto get() inout { return payload; } + /// + @safe unittest + { + auto x = checked(ubyte(42)); + static assert(is(typeof(x.get()) == ubyte)); + assert(x.get == 42); + const y = checked(ubyte(42)); + static assert(is(typeof(y.get()) == const ubyte)); + assert(y.get == 42); + } + + /** + Defines the minimum and maximum. These values are hookable by defining + `Hook.min` and/or `Hook.max`. + */ + static if (hasMember!(Hook, "min")) + { + enum Checked!(T, Hook) min = Checked!(T, Hook)(Hook.min!T); + /// + @safe unittest + { + assert(Checked!short.min == -32768); + assert(Checked!(short, WithNaN).min == -32767); + assert(Checked!(uint, WithNaN).max == uint.max - 1); + } + } + else + { + /// ditto + enum Checked!(T, Hook) min = Checked(T.min); + } + static if (hasMember!(Hook, "max")) + { + /// ditto + enum Checked!(T, Hook) max = Checked(Hook.max!T); + } + else + { + /// ditto + enum Checked!(T, Hook) max = Checked(T.max); + } + + /** + Constructor taking a value properly convertible to the underlying type. `U` + may be either an integral that can be converted to `T` without a loss, or + another `Checked` instance whose representation may be in turn converted to + `T` without a loss. + */ + this(U)(U rhs) + if (valueConvertible!(U, T) || + !isIntegral!T && is(typeof(T(rhs))) || + is(U == Checked!(V, W), V, W) && + is(typeof(Checked!(T, Hook)(rhs.get)))) + { + static if (isIntegral!U) + payload = rhs; + else + payload = rhs.payload; + } + /// + @safe unittest + { + auto a = checked(42L); + assert(a == 42); + auto b = Checked!long(4242); // convert 4242 to long + assert(b == 4242); + } + + /** + Assignment operator. Has the same constraints as the constructor. + + Params: + rhs = The value to assign + + Returns: + A reference to `this` + */ + ref Checked opAssign(U)(U rhs) return + if (is(typeof(Checked!(T, Hook)(rhs)))) + { + static if (isIntegral!U) + payload = rhs; + else + payload = rhs.payload; + return this; + } + /// + @safe unittest + { + Checked!long a; + a = 42L; + assert(a == 42); + a = 4242; + assert(a == 4242); + } + + /// + @safe unittest + { + Checked!long a, b; + a = b = 3; + assert(a == 3 && b == 3); + } + + /** + Construct from a decimal string. The conversion follows the same rules as + $(REF to, std, conv) converting a string to the wrapped `T` type. + + Params: + str = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + of characters + */ + this(Range)(Range str) + if (isInputRange!Range && isSomeChar!(ElementType!Range)) + { + import std.conv : to; + + this(to!T(str)); + } + + /** + $(REF to, std, conv) can convert a string to a `Checked!T`: + */ + @system unittest + { + import std.conv : to; + + const a = to!long("1234"); + const b = to!(Checked!long)("1234"); + assert(a == b); + } + + // opCast + /** + Casting operator to integral, `bool`, or floating point type. + + If a cast to a floating-point type is requested and `Hook` defines + `onBadCast`, the cast is verified by ensuring $(D get == cast(T) + U(get)). If that is not `true`, `hook.onBadCast!U(get)` is returned. + + If a cast to an integral type is requested and `Hook` defines `onBadCast`, + the cast is verified by ensuring `get` and $(D cast(U) + get) are the same arithmetic number. (Note that `int(-1)` and + `uint(1)` are different values arithmetically although they have the same + bitwise representation and compare equal by language rules.) If the numbers + are not arithmetically equal, `hook.onBadCast!U(get)` is + returned. + + Params: + U = The type to cast to + + Returns: + If `Hook` defines `hookOpCast`, the call immediately returns + `hook.hookOpCast!U(get)`. Otherwise, casting to `bool` yields $(D + get != 0) and casting to another integral that can represent all + values of `T` returns `get` promoted to `U`. + */ + U opCast(U, this _)() + if (isIntegral!U || isFloatingPoint!U || is(U == bool)) + { + static if (hasMember!(Hook, "hookOpCast")) + { + return hook.hookOpCast!U(payload); + } + else static if (is(U == bool)) + { + return payload != 0; + } + else static if (valueConvertible!(T, U)) + { + return payload; + } + // may lose bits or precision + else static if (!hasMember!(Hook, "onBadCast")) + { + return cast(U) payload; + } + else + { + if (isUnsigned!T || !isUnsigned!U || + T.sizeof > U.sizeof || payload >= 0) + { + auto result = cast(U) payload; + // If signedness is different, we need additional checks + if (result == payload && + (!isUnsigned!T || isUnsigned!U || result >= 0)) + return result; + } + return hook.onBadCast!U(payload); + } + } + /// + @safe unittest + { + assert(cast(uint) checked(42) == 42); + assert(cast(uint) checked!WithNaN(-42) == uint.max); + } + + // opEquals + /** + Compares `this` against `rhs` for equality. + + If `U` is also an instance of `Checked`, both hooks (left- and right-hand + side) are introspected for the method `hookOpEquals`. If both define it, + priority is given to the left-hand side. + + Params: + rhs = Right-hand side to compare for equality + + Returns: + If `Hook` defines `hookOpEquals`, the function forwards to $(D + hook.hookOpEquals(get, rhs)). Otherwise, the result of the + built-in operation $(D get == rhs) is returned. + + */ + bool opEquals(U, this _)(U rhs) + if (isIntegral!U || isFloatingPoint!U || is(U == bool) || + is(U == Checked!(V, W), V, W) && is(typeof(this == rhs.payload))) + { + static if (is(U == Checked!(V, W), V, W)) + { + alias R = typeof(payload + rhs.payload); + static if (is(Hook == W)) + { + // Use the lhs hook if there + return this == rhs.payload; + } + else static if (valueConvertible!(T, R) && valueConvertible!(V, R)) + { + return payload == rhs.payload; + } + else static if (hasMember!(Hook, "hookOpEquals")) + { + return hook.hookOpEquals(payload, rhs.payload); + } + else static if (hasMember!(W, "hookOpEquals")) + { + return rhs.hook.hookOpEquals(rhs.payload, payload); + } + else + { + return payload == rhs.payload; + } + } + else static if (hasMember!(Hook, "hookOpEquals")) + return hook.hookOpEquals(payload, rhs); + else static if (isIntegral!U || isFloatingPoint!U || is(U == bool)) + return payload == rhs; + } + + /// + static if (is(T == int) && is(Hook == void)) @safe unittest + { + import std.traits : isUnsigned; + + static struct MyHook + { + static bool thereWereErrors; + static bool hookOpEquals(L, R)(L lhs, R rhs) + { + if (lhs != rhs) return false; + static if (isUnsigned!L && !isUnsigned!R) + { + if (lhs > 0 && rhs < 0) thereWereErrors = true; + } + else static if (isUnsigned!R && !isUnsigned!L) + if (lhs < 0 && rhs > 0) thereWereErrors = true; + // Preserve built-in behavior. + return true; + } + } + auto a = checked!MyHook(-42); + assert(a == uint(-42)); + assert(MyHook.thereWereErrors); + MyHook.thereWereErrors = false; + assert(checked!MyHook(uint(-42)) == -42); + assert(MyHook.thereWereErrors); + static struct MyHook2 + { + static bool hookOpEquals(L, R)(L lhs, R rhs) + { + return lhs == rhs; + } + } + MyHook.thereWereErrors = false; + assert(checked!MyHook2(uint(-42)) == a); + // Hook on left hand side takes precedence, so no errors + assert(!MyHook.thereWereErrors); + } + + // toHash + /** + Generates a hash for `this`. If `Hook` defines `hookToHash`, the call + immediately returns `hook.hookToHash(payload)`. If `Hook` does not + implement `hookToHash`, but it has state, a hash will be generated for + the `Hook` using the built-in function and it will be xored with the + hash of the `payload`. + + Returns: + The hash of `this` instance. + + */ + size_t toHash() const nothrow @safe + { + static if (hasMember!(Hook, "hookToHash")) + { + return hook.hookToHash(payload); + } + else static if (stateSize!Hook > 0) + { + static if (hasMember!(typeof(payload), "toHash")) + { + return payload.toHash() ^ hashOf(hook); + } + else + { + return hashOf(payload) ^ hashOf(hook); + } + } + else static if (hasMember!(typeof(payload), "toHash")) + { + return payload.toHash(); + } + else + { + return .hashOf(payload); + } + } + + /// ditto + size_t toHash(this _)() shared const nothrow @safe + { + import core.atomic : atomicLoad, MemoryOrder; + static if (is(typeof(this.payload.atomicLoad!(MemoryOrder.acq)) P)) + { + auto payload = __ctfe ? cast(P) this.payload + : this.payload.atomicLoad!(MemoryOrder.acq); + } + else + { + alias payload = this.payload; + } + + static if (hasMember!(Hook, "hookToHash")) + { + return hook.hookToHash(payload); + } + else static if (stateSize!Hook > 0) + { + static if (hasMember!(typeof(payload), "toHash")) + { + return payload.toHash() ^ hashOf(hook); + } + else + { + return hashOf(payload) ^ hashOf(hook); + } + } + else static if (hasMember!(typeof(payload), "toHash")) + { + return payload.toHash(); + } + else + { + return .hashOf(payload); + } + } + + /** + Writes a string representation of this to a `sink`. + + Params: + sink = A `Char` accepting + $(REF_ALTTEXT output range, isOutputRange, std,range,primitives). + fmt = A $(REF FormatSpec, std, format) which controls how this + is formatted. + */ + void toString(Writer, Char)(scope ref Writer sink, scope const ref FormatSpec!Char fmt) const + { + import std.format.write : formatValue; + if (fmt.spec == 's') + return formatValue(sink, this, fmt); + else + return formatValue(sink, payload, fmt); + } + + /** + `toString` is rarely directly invoked; the usual way of using it is via + $(REF format, std, format): + */ + @system unittest + { + import std.format; + + assert(format("%04d", checked(15)) == "0015"); + assert(format("0x%02x", checked(15)) == "0x0f"); + } + + // opCmp + /** + + Compares `this` against `rhs` for ordering. If `Hook` defines `hookOpCmp`, + the function forwards to $(D hook.hookOpCmp(get, rhs)). Otherwise, the + result of the built-in comparison operation is returned. + + If `U` is also an instance of `Checked`, both hooks (left- and right-hand + side) are introspected for the method `hookOpCmp`. If both define it, + priority is given to the left-hand side. + + Params: + rhs = The right-hand side operand + U = either the type of `rhs` or the underlying type + if `rhs` is a `Checked` instance + Hook1 = If `rhs` is a `Checked` instance, `Hook1` represents + the instance's behavior hook + + Returns: + The result of `hookOpCmp` if `hook` defines `hookOpCmp`. If + `U` is an instance of `Checked` and `hook` does not define + `hookOpCmp`, result of `rhs.hook.hookOpCmp` is returned. + If none of the instances specify the behavior via `hookOpCmp`, + `-1` is returned if `lhs` is lesser than `rhs`, `1` if `lhs` + is greater than `rhs` and `0` on equality. + */ + auto opCmp(U, this _)(const U rhs) //const pure @safe nothrow @nogc + if (isIntegral!U || isFloatingPoint!U || is(U == bool)) + { + static if (hasMember!(Hook, "hookOpCmp")) + { + return hook.hookOpCmp(payload, rhs); + } + else static if (valueConvertible!(T, U) || valueConvertible!(U, T)) + { + return payload < rhs ? -1 : payload > rhs; + } + else static if (isFloatingPoint!U) + { + U lhs = payload; + return lhs < rhs ? U(-1.0) + : lhs > rhs ? U(1.0) + : lhs == rhs ? U(0.0) : U.init; + } + else + { + return payload < rhs ? -1 : payload > rhs; + } + } + + /// ditto + auto opCmp(U, Hook1, this _)(Checked!(U, Hook1) rhs) + { + alias R = typeof(payload + rhs.payload); + static if (valueConvertible!(T, R) && valueConvertible!(U, R)) + { + return payload < rhs.payload ? -1 : payload > rhs.payload; + } + else static if (is(Hook == Hook1)) + { + // Use the lhs hook + return this.opCmp(rhs.payload); + } + else static if (hasMember!(Hook, "hookOpCmp")) + { + return hook.hookOpCmp(get, rhs.get); + } + else static if (hasMember!(Hook1, "hookOpCmp")) + { + return -rhs.hook.hookOpCmp(rhs.payload, get); + } + else + { + return payload < rhs.payload ? -1 : payload > rhs.payload; + } + } + + /// + static if (is(T == int) && is(Hook == void)) @safe unittest + { + import std.traits : isUnsigned; + + static struct MyHook + { + static bool thereWereErrors; + static int hookOpCmp(L, R)(L lhs, R rhs) + { + static if (isUnsigned!L && !isUnsigned!R) + { + if (rhs < 0 && rhs >= lhs) + thereWereErrors = true; + } + else static if (isUnsigned!R && !isUnsigned!L) + { + if (lhs < 0 && lhs >= rhs) + thereWereErrors = true; + } + // Preserve built-in behavior. + return lhs < rhs ? -1 : lhs > rhs; + } + } + auto a = checked!MyHook(-42); + assert(a > uint(42)); + assert(MyHook.thereWereErrors); + static struct MyHook2 + { + static int hookOpCmp(L, R)(L lhs, R rhs) + { + // Default behavior + return lhs < rhs ? -1 : lhs > rhs; + } + } + MyHook.thereWereErrors = false; + assert(Checked!(uint, MyHook2)(uint(-42)) <= a); + //assert(Checked!(uint, MyHook2)(uint(-42)) >= a); + // Hook on left hand side takes precedence, so no errors + assert(!MyHook.thereWereErrors); + assert(a <= Checked!(uint, MyHook2)(uint(-42))); + assert(MyHook.thereWereErrors); + } + + // For coverage + static if (is(T == int) && is(Hook == void)) @safe unittest + { + assert(checked(42) <= checked!void(42)); + assert(checked!void(42) <= checked(42u)); + assert(checked!void(42) <= checked!(void*)(42u)); + } + + // opUnary + /** + + Defines unary operators `+`, `-`, `~`, `++`, and `--`. Unary `+` is not + overridable and always has built-in behavior (returns `this`). For the + others, if `Hook` defines `hookOpUnary`, `opUnary` forwards to $(D + Checked!(typeof(hook.hookOpUnary!op(get)), + Hook)(hook.hookOpUnary!op(get))). + + If `Hook` does not define `hookOpUnary` but defines `onOverflow`, `opUnary` + forwards to `hook.onOverflow!op(get)` in case an overflow occurs. + For `++` and `--`, the payload is assigned from the result of the call to + `onOverflow`. + + Note that unary `-` is considered to overflow if `T` is a signed integral of + 32 or 64 bits and is equal to the most negative value. This is because that + value has no positive negation. + + Params: + op = The unary operator + + Returns: + A `Checked` instance representing the result of the unary + operation + */ + auto opUnary(string op, this _)() + if (op == "+" || op == "-" || op == "~") + { + static if (op == "+") + return Checked(this); // "+" is not hookable + else static if (hasMember!(Hook, "hookOpUnary")) + { + auto r = hook.hookOpUnary!op(payload); + return Checked!(typeof(r), Hook)(r); + } + else static if (op == "-" && isIntegral!T && T.sizeof >= 4 && + !isUnsigned!T && hasMember!(Hook, "onOverflow")) + { + static assert(is(typeof(-payload) == typeof(payload))); + bool overflow; + import core.checkedint : negs; + auto r = negs(payload, overflow); + if (overflow) r = hook.onOverflow!op(payload); + return Checked(r); + } + else + return Checked(mixin(op ~ "payload")); + } + + /// ditto + ref Checked opUnary(string op)() return + if (op == "++" || op == "--") + { + static if (hasMember!(Hook, "hookOpUnary")) + hook.hookOpUnary!op(payload); + else static if (hasMember!(Hook, "onOverflow")) + { + static if (op == "++") + { + if (payload == max.payload) + payload = hook.onOverflow!"++"(payload); + else + ++payload; + } + else + { + if (payload == min.payload) + payload = hook.onOverflow!"--"(payload); + else + --payload; + } + } + else + mixin(op ~ "payload;"); + return this; + } + + /// + static if (is(T == int) && is(Hook == void)) @safe unittest + { + static struct MyHook + { + static bool thereWereErrors; + static L hookOpUnary(string x, L)(L lhs) + { + if (x == "-" && lhs == -lhs) thereWereErrors = true; + return -lhs; + } + } + auto a = checked!MyHook(long.min); + assert(a == -a); + assert(MyHook.thereWereErrors); + auto b = checked!void(42); + assert(++b == 43); + } + + // opBinary + /** + + Defines binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, + and `>>>`. If `Hook` defines `hookOpBinary`, `opBinary` forwards to $(D + Checked!(typeof(hook.hookOpBinary!op(get, rhs)), + Hook)(hook.hookOpBinary!op(get, rhs))). + + If `Hook` does not define `hookOpBinary` but defines `onOverflow`, + `opBinary` forwards to `hook.onOverflow!op(get, rhs)` in case an + overflow occurs. + + If two `Checked` instances are involved in a binary operation and both + define `hookOpBinary`, the left-hand side hook has priority. If both define + `onOverflow`, a compile-time error occurs. + + Params: + op = The binary operator + rhs = The right hand side operand + U = If `rhs` is a `Checked` instance, `U` represents + the underlying instance type + Hook1 = If `rhs` is a `Checked` instance, `Hook1` represents + the instance's behavior hook + + Returns: + A `Checked` instance representing the result of the binary + operation + */ + auto opBinary(string op, Rhs)(const Rhs rhs) + if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) + { + return opBinaryImpl!(op, Rhs, typeof(this))(rhs); + } + + /// ditto + auto opBinary(string op, Rhs)(const Rhs rhs) const + if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) + { + return opBinaryImpl!(op, Rhs, typeof(this))(rhs); + } + + private auto opBinaryImpl(string op, Rhs, this _)(const Rhs rhs) + { + alias R = typeof(mixin("payload" ~ op ~ "rhs")); + static assert(is(typeof(mixin("payload" ~ op ~ "rhs")) == R)); + static if (isIntegral!R) alias Result = Checked!(R, Hook); + else alias Result = R; + + static if (hasMember!(Hook, "hookOpBinary")) + { + auto r = hook.hookOpBinary!op(payload, rhs); + return Checked!(typeof(r), Hook)(r); + } + else static if (is(Rhs == bool)) + { + return mixin("this" ~ op ~ "ubyte(rhs)"); + } + else static if (isFloatingPoint!Rhs) + { + return mixin("payload" ~ op ~ "rhs"); + } + else static if (hasMember!(Hook, "onOverflow")) + { + bool overflow; + auto r = opChecked!op(payload, rhs, overflow); + if (overflow) r = hook.onOverflow!op(payload, rhs); + return Result(r); + } + else + { + // Default is built-in behavior + return Result(mixin("payload" ~ op ~ "rhs")); + } + } + + /// ditto + auto opBinary(string op, U, Hook1)(Checked!(U, Hook1) rhs) + { + return opBinaryImpl2!(op, U, Hook1, typeof(this))(rhs); + } + + /// ditto + auto opBinary(string op, U, Hook1)(Checked!(U, Hook1) rhs) const + { + return opBinaryImpl2!(op, U, Hook1, typeof(this))(rhs); + } + + private + auto opBinaryImpl2(string op, U, Hook1, this _)(Checked!(U, Hook1) rhs) + { + alias R = typeof(get + rhs.payload); + static if (valueConvertible!(T, R) && valueConvertible!(U, R) || + is(Hook == Hook1)) + { + // Delegate to lhs + return mixin("this" ~ op ~ "rhs.payload"); + } + else static if (hasMember!(Hook, "hookOpBinary")) + { + return hook.hookOpBinary!op(payload, rhs); + } + else static if (hasMember!(Hook1, "hookOpBinary")) + { + // Delegate to rhs + return mixin("this.payload" ~ op ~ "rhs"); + } + else static if (hasMember!(Hook, "onOverflow") && + !hasMember!(Hook1, "onOverflow")) + { + // Delegate to lhs + return mixin("this" ~ op ~ "rhs.payload"); + } + else static if (hasMember!(Hook1, "onOverflow") && + !hasMember!(Hook, "onOverflow")) + { + // Delegate to rhs + return mixin("this.payload" ~ op ~ "rhs"); + } + else + { + static assert(0, "Conflict between lhs and rhs hooks," ~ + " use .get on one side to disambiguate."); + } + } + + static if (is(T == int) && is(Hook == void)) @safe unittest + { + const a = checked(42); + assert(a + 1 == 43); + assert(a + checked(uint(42)) == 84); + assert(checked(42) + checked!void(42u) == 84); + assert(checked!void(42) + checked(42u) == 84); + + static struct MyHook + { + static uint tally; + static auto hookOpBinary(string x, L, R)(L lhs, R rhs) + { + ++tally; + return mixin("lhs" ~ x ~ "rhs"); + } + } + assert(checked!MyHook(42) + checked(42u) == 84); + assert(checked!void(42) + checked!MyHook(42u) == 84); + assert(MyHook.tally == 2); + } + + // opBinaryRight + /** + + Defines binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, + `>>`, and `>>>` for the case when a built-in numeric or Boolean type is on + the left-hand side, and a `Checked` instance is on the right-hand side. + + Params: + op = The binary operator + lhs = The left hand side operand + + Returns: + A `Checked` instance representing the result of the binary + operation + + */ + auto opBinaryRight(string op, Lhs)(const Lhs lhs) + if (isIntegral!Lhs || isFloatingPoint!Lhs || is(Lhs == bool)) + { + return opBinaryRightImpl!(op, Lhs, typeof(this))(lhs); + } + + /// ditto + auto opBinaryRight(string op, Lhs)(const Lhs lhs) const + if (isIntegral!Lhs || isFloatingPoint!Lhs || is(Lhs == bool)) + { + return opBinaryRightImpl!(op, Lhs, typeof(this))(lhs); + } + + private auto opBinaryRightImpl(string op, Lhs, this _)(const Lhs lhs) + { + static if (hasMember!(Hook, "hookOpBinaryRight")) + { + auto r = hook.hookOpBinaryRight!op(lhs, payload); + return Checked!(typeof(r), Hook)(r); + } + else static if (hasMember!(Hook, "hookOpBinary")) + { + auto r = hook.hookOpBinary!op(lhs, payload); + return Checked!(typeof(r), Hook)(r); + } + else static if (is(Lhs == bool)) + { + return mixin("ubyte(lhs)" ~ op ~ "this"); + } + else static if (isFloatingPoint!Lhs) + { + return mixin("lhs" ~ op ~ "payload"); + } + else static if (hasMember!(Hook, "onOverflow")) + { + bool overflow; + auto r = opChecked!op(lhs, T(payload), overflow); + if (overflow) r = hook.onOverflow!op(lhs, payload); + return Checked!(typeof(r), Hook)(r); + } + else + { + // Default is built-in behavior + auto r = mixin("lhs" ~ op ~ "T(payload)"); + return Checked!(typeof(r), Hook)(r); + } + } + + static if (is(T == int) && is(Hook == void)) @safe unittest + { + assert(1 + checked(1) == 2); + static uint tally; + static struct MyHook + { + static auto hookOpBinaryRight(string x, L, R)(L lhs, R rhs) + { + ++tally; + return mixin("lhs" ~ x ~ "rhs"); + } + } + assert(1 + checked!MyHook(1) == 2); + assert(tally == 1); + + immutable x1 = checked(1); + assert(1 + x1 == 2); + immutable x2 = checked!MyHook(1); + assert(1 + x2 == 2); + assert(tally == 2); + } + + // opOpAssign + /** + + Defines operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, + `<<=`, `>>=`, and `>>>=`. + + If `Hook` defines `hookOpOpAssign`, `opOpAssign` forwards to + `hook.hookOpOpAssign!op(payload, rhs)`, where `payload` is a reference to + the internally held data so the hook can change it. + + Otherwise, the operator first evaluates $(D auto result = + opBinary!op(payload, rhs).payload), which is subject to the hooks in + `opBinary`. Then, if `result` is less than $(D Checked!(T, Hook).min) and if + `Hook` defines `onLowerBound`, the payload is assigned from $(D + hook.onLowerBound(result, min)). If `result` is greater than $(D Checked!(T, + Hook).max) and if `Hook` defines `onUpperBound`, the payload is assigned + from $(D hook.onUpperBound(result, min)). + + If the right-hand side is also a Checked but with a different hook or + underlying type, the hook and underlying type of this Checked takes + precedence. + + In all other cases, the built-in behavior is carried out. + + Params: + op = The operator involved (without the `"="`, e.g. `"+"` for `"+="` etc) + rhs = The right-hand side of the operator (left-hand side is `this`) + + Returns: A reference to `this`. + */ + ref Checked opOpAssign(string op, Rhs)(const Rhs rhs) return + if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) + { + static assert(is(typeof(mixin("payload" ~ op ~ "=rhs")) == T)); + + static if (hasMember!(Hook, "hookOpOpAssign")) + { + hook.hookOpOpAssign!op(payload, rhs); + } + else + { + alias R = typeof(get + rhs); + auto r = opBinary!op(rhs).get; + import std.conv : unsigned; + + static if (ProperCompare.hookOpCmp(R.min, min.get) < 0 && + hasMember!(Hook, "onLowerBound")) + { + if (ProperCompare.hookOpCmp(r, min.get) < 0) + { + // Example: Checked!uint(1) += int(-3) + payload = hook.onLowerBound(r, min.get); + return this; + } + } + static if (ProperCompare.hookOpCmp(max.get, R.max) < 0 && + hasMember!(Hook, "onUpperBound")) + { + if (ProperCompare.hookOpCmp(r, max.get) > 0) + { + // Example: Checked!uint(1) += long(uint.max) + payload = hook.onUpperBound(r, max.get); + return this; + } + } + payload = cast(T) r; + } + return this; + } + + /// ditto + ref Checked opOpAssign(string op, Rhs)(const Rhs rhs) return + if (is(Rhs == Checked!(RhsT, RhsHook), RhsT, RhsHook)) + { + return opOpAssign!(op, typeof(rhs.payload))(rhs.payload); + } + + /// + static if (is(T == int) && is(Hook == void)) @safe unittest + { + static struct MyHook + { + static bool thereWereErrors; + static T onLowerBound(Rhs, T)(Rhs rhs, T bound) + { + thereWereErrors = true; + return bound; + } + static T onUpperBound(Rhs, T)(Rhs rhs, T bound) + { + thereWereErrors = true; + return bound; + } + } + auto x = checked!MyHook(byte.min); + x -= 1; + assert(MyHook.thereWereErrors); + MyHook.thereWereErrors = false; + x = byte.max; + x += 1; + assert(MyHook.thereWereErrors); + } +} + +/// +@safe @nogc pure nothrow unittest +{ + // Hook that ignores all problems. + static struct Ignore + { + @nogc nothrow pure @safe static: + Dst onBadCast(Dst, Src)(Src src) { return cast(Dst) src; } + Lhs onLowerBound(Rhs, T)(Rhs rhs, T bound) { return cast(T) rhs; } + T onUpperBound(Rhs, T)(Rhs rhs, T bound) { return cast(T) rhs; } + bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) { return lhs == rhs; } + int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) { return (lhs > rhs) - (lhs < rhs); } + typeof(~Lhs()) onOverflow(string x, Lhs)(ref Lhs lhs) { return mixin(x ~ "lhs"); } + typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + static if (x == "/") + return typeof(lhs / rhs).min; + else + return mixin("lhs" ~ x ~ "rhs"); + } + } + + auto x = Checked!(int, Ignore)(5) + 7; +} + + +/** + +Convenience function that turns an integral into the corresponding `Checked` +instance by using template argument deduction. The hook type may be specified +(by default `Abort`). + +Params: + Hook = type that customizes the behavior, by default `Abort` + T = type represetinfg the underlying represantion of the `Checked` instance + value = the actual value of the representation + +Returns: + A `Checked` instance customized by the provided `Hook` and `value` +*/ +Checked!(T, Hook) checked(Hook = Abort, T)(const T value) +if (is(typeof(Checked!(T, Hook)(value)))) +{ + return Checked!(T, Hook)(value); +} + +/// +@safe unittest +{ + static assert(is(typeof(checked(42)) == Checked!int)); + assert(checked(42) == Checked!int(42)); + static assert(is(typeof(checked!WithNaN(42)) == Checked!(int, WithNaN))); + assert(checked!WithNaN(42) == Checked!(int, WithNaN)(42)); +} + +// get +@safe unittest +{ + void test(T)() + { + assert(Checked!(T, void)(ubyte(22)).get == 22); + } + test!ubyte; + test!(const ubyte); + test!(immutable ubyte); +} + +@system unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=21758 + assert(4 * checked(5L) == 20); + assert(20 / checked(5L) == 4); + assert(2 ^^ checked(3L) == 8); + assert(12 % checked(5L) == 2); + assert((0xff & checked(3L)) == 3); + assert((0xf0 | checked(3L)) == 0xf3); + assert((0xff ^ checked(3L)) == 0xfc); +} + +// Abort +/** + +Force all integral errors to fail by printing an error message to `stderr` and +then abort the program. `Abort` is the default second argument for `Checked`. + +*/ +struct Abort +{ +static: + /** + + Called automatically upon a bad cast (one that loses precision or attempts + to convert a negative value to an unsigned type). The source type is `Src` + and the destination type is `Dst`. + + Params: + src = Souce operand + + Returns: + Nominally the result is the desired value of the cast operation, + which will be forwarded as the result of the cast. For `Abort`, the + function never returns because it aborts the program. + */ + Dst onBadCast(Dst, Src)(Src src) + { + Warn.onBadCast!Dst(src); + assert(0); + } + + /** + + Called automatically upon a bounds error. + + Params: + rhs = The right-hand side value in the assignment, after the operator has + been evaluated + bound = The value of the bound being violated + + Returns: Nominally the result is the desired value of the operator, which + will be forwarded as result. For `Abort`, the function never returns because + it aborts the program. + + */ + T onLowerBound(Rhs, T)(Rhs rhs, T bound) + { + Warn.onLowerBound(rhs, bound); + assert(0); + } + /// ditto + T onUpperBound(Rhs, T)(Rhs rhs, T bound) + { + Warn.onUpperBound(rhs, bound); + assert(0); + } + + /** + + Called automatically upon a comparison for equality. In case of a erroneous + comparison (one that would make a signed negative value appear equal to an + unsigned positive value), this hook issues `assert(0)` which terminates the + application. + + Params: + lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + rhs = The right-hand side type involved in the operator + + Returns: Upon a correct comparison, returns the result of the comparison. + Otherwise, the function terminates the application so it never returns. + + */ + static bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + bool error; + auto result = opChecked!"=="(lhs, rhs, error); + if (error) + { + Warn.hookOpEquals(lhs, rhs); + assert(0); + } + return result; + } + + /** + + Called automatically upon a comparison for ordering using one of the + operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e. + it would make a signed negative value appear greater than or equal to an + unsigned positive value), then application is terminated with `assert(0)`. + Otherwise, the three-state result is returned (positive if $(D lhs > rhs), + negative if $(D lhs < rhs), `0` otherwise). + + Params: + lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + rhs = The right-hand side type involved in the operator + + Returns: For correct comparisons, returns a positive integer if $(D lhs > + rhs), a negative integer if $(D lhs < rhs), `0` if the two are equal. Upon + a mistaken comparison such as $(D int(-1) < uint(0)), the function never + returns because it aborts the program. + + */ + int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + bool error; + auto result = opChecked!"cmp"(lhs, rhs, error); + if (error) + { + Warn.hookOpCmp(lhs, rhs); + assert(0); + } + return result; + } + + /** + + Called automatically upon an overflow during a unary or binary operation. + + Params: + x = The operator, e.g. `-` + lhs = The left-hand side (or sole) argument + rhs = The right-hand side type involved in the operator + + Returns: Nominally the result is the desired value of the operator, which + will be forwarded as result. For `Abort`, the function never returns because + it aborts the program. + + */ + typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs) + { + Warn.onOverflow!x(lhs); + assert(0); + } + /// ditto + typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + Warn.onOverflow!x(lhs, rhs); + assert(0); + } +} + +/// +@safe unittest +{ + void test(T)() + { + Checked!(int, Abort) x; + x = 42; + auto x1 = cast(T) x; + assert(x1 == 42); + //x1 += long(int.max); + } + test!short; + test!(const short); + test!(immutable short); +} + + +// Throw +/** + +Force all integral errors to fail by throwing an exception of type +`Throw.CheckFailure`. The message coming with the error is similar to the one +printed by `Warn`. + +*/ +struct Throw +{ + /** + Exception type thrown upon any failure. + */ + static class CheckFailure : Exception + { + /** + Params: + f = format specifier + vals = actual values for the format specifier + */ + this(T...)(string f, T vals) + { + import std.format : format; + super(format(f, vals)); + } + } + + /** + + Called automatically upon a bad cast (one that loses precision or attempts + to convert a negative value to an unsigned type). The source type is `Src` + and the destination type is `Dst`. + + Params: + src = source operand + + Returns: + Nominally the result is the desired value of the cast operation, + which will be forwarded as the result of the cast. For `Throw`, the + function never returns because it throws an exception. + + Throws: + `CheckFailure` on bad cast + */ + static Dst onBadCast(Dst, Src)(Src src) + { + throw new CheckFailure("Erroneous cast: cast(%s) %s(%s)", + Dst.stringof, Src.stringof, src); + } + + /** + + Called automatically upon a bounds error. + + Params: + rhs = The right-hand side value in the assignment, after the operator has + been evaluated + bound = The value of the bound being violated + + Returns: + Nominally the result is the desired value of the operator, which + will be forwarded as result. For `Throw`, the function never returns because + it throws. + + Throws: + `CheckFailure` on overflow + + */ + static T onLowerBound(Rhs, T)(Rhs rhs, T bound) + { + throw new CheckFailure("Lower bound error: %s(%s) < %s(%s)", + Rhs.stringof, rhs, T.stringof, bound); + } + /// ditto + static T onUpperBound(Rhs, T)(Rhs rhs, T bound) + { + throw new CheckFailure("Upper bound error: %s(%s) > %s(%s)", + Rhs.stringof, rhs, T.stringof, bound); + } + + /** + + Called automatically upon a comparison for equality. Throws upon an + erroneous comparison (one that would make a signed negative value appear + equal to an unsigned positive value). + + Params: + lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + rhs = The right-hand side type involved in the operator + + Returns: The result of the comparison. + + Throws: `CheckFailure` if the comparison is mathematically erroneous. + + */ + static bool hookOpEquals(L, R)(L lhs, R rhs) + { + bool error; + auto result = opChecked!"=="(lhs, rhs, error); + if (error) + { + throw new CheckFailure("Erroneous comparison: %s(%s) == %s(%s)", + L.stringof, lhs, R.stringof, rhs); + } + return result; + } + + /** + + Called automatically upon a comparison for ordering using one of the + operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e. + it would make a signed negative value appear greater than or equal to an + unsigned positive value), throws a `Throw.CheckFailure` exception. + Otherwise, the three-state result is returned (positive if $(D lhs > rhs), + negative if $(D lhs < rhs), `0` otherwise). + + Params: + lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + rhs = The right-hand side type involved in the operator + + Returns: For correct comparisons, returns a positive integer if $(D lhs > + rhs), a negative integer if $(D lhs < rhs), `0` if the two are equal. + + Throws: Upon a mistaken comparison such as $(D int(-1) < uint(0)), the + function never returns because it throws a `Throw.CheckedFailure` exception. + + */ + static int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + bool error; + auto result = opChecked!"cmp"(lhs, rhs, error); + if (error) + { + throw new CheckFailure("Erroneous ordering comparison: %s(%s) and %s(%s)", + Lhs.stringof, lhs, Rhs.stringof, rhs); + } + return result; + } + + /** + + Called automatically upon an overflow during a unary or binary operation. + + Params: + x = The operator, e.g. `-` + lhs = The left-hand side (or sole) argument + rhs = The right-hand side type involved in the operator + + Returns: + Nominally the result is the desired value of the operator, which + will be forwarded as result. For `Throw`, the function never returns because + it throws an exception. + + Throws: + `CheckFailure` on overflow + + */ + static typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs) + { + throw new CheckFailure("Overflow on unary operator: %s%s(%s)", + x, Lhs.stringof, lhs); + } + /// ditto + static typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + throw new CheckFailure("Overflow on binary operator: %s(%s) %s %s(%s)", + Lhs.stringof, lhs, x, Rhs.stringof, rhs); + } +} + +/// +@safe unittest +{ + void test(T)() + { + Checked!(int, Throw) x; + x = 42; + auto x1 = cast(T) x; + assert(x1 == 42); + x = T.max + 1; + import std.exception : assertThrown, assertNotThrown; + assertThrown(cast(T) x); + x = x.max; + assertThrown(x += 42); + assertThrown(x += 42L); + x = x.min; + assertThrown(-x); + assertThrown(x -= 42); + assertThrown(x -= 42L); + x = -1; + assertNotThrown(x == -1); + assertThrown(x == uint(-1)); + assertNotThrown(x <= -1); + assertThrown(x <= uint(-1)); + } + test!short; + test!(const short); + test!(immutable short); +} + +// Warn +/** +Hook that prints to `stderr` a trace of all integral errors, without affecting +default behavior. +*/ +struct Warn +{ + import std.stdio : writefln; +static: + /** + + Called automatically upon a bad cast from `src` to type `Dst` (one that + loses precision or attempts to convert a negative value to an unsigned + type). + + Params: + src = The source of the cast + Dst = The target type of the cast + + Returns: `cast(Dst) src` + + */ + Dst onBadCast(Dst, Src)(Src src) + { + trustedStderr.writefln("Erroneous cast: cast(%s) %s(%s)", + Dst.stringof, Src.stringof, src); + return cast(Dst) src; + } + + /** + + Called automatically upon a bad `opOpAssign` call (one that loses precision + or attempts to convert a negative value to an unsigned type). + + Params: + rhs = The right-hand side value in the assignment, after the operator has + been evaluated + bound = The bound being violated + + Returns: `cast(T) rhs` + */ + T onLowerBound(Rhs, T)(Rhs rhs, T bound) + { + trustedStderr.writefln("Lower bound error: %s(%s) < %s(%s)", + Rhs.stringof, rhs, T.stringof, bound); + return cast(T) rhs; + } + /// ditto + T onUpperBound(Rhs, T)(Rhs rhs, T bound) + { + trustedStderr.writefln("Upper bound error: %s(%s) > %s(%s)", + Rhs.stringof, rhs, T.stringof, bound); + return cast(T) rhs; + } + + /** + + Called automatically upon a comparison for equality. In case of an Erroneous + comparison (one that would make a signed negative value appear equal to an + unsigned positive value), writes a warning message to `stderr` as a side + effect. + + Params: + lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + rhs = The right-hand side type involved in the operator + + Returns: In all cases the function returns the built-in result of $(D lhs == + rhs). + + */ + bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + bool error; + auto result = opChecked!"=="(lhs, rhs, error); + if (error) + { + trustedStderr.writefln("Erroneous comparison: %s(%s) == %s(%s)", + Lhs.stringof, lhs, Rhs.stringof, rhs); + return lhs == rhs; + } + return result; + } + + /// + @safe unittest + { + auto x = checked!Warn(-42); + // Passes + assert(x == -42); + // Passes but prints a warning + // assert(x == uint(-42)); + } + + /** + + Called automatically upon a comparison for ordering using one of the + operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e. + it would make a signed negative value appear greater than or equal to an + unsigned positive value), then a warning message is printed to `stderr`. + + Params: + lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + rhs = The right-hand side type involved in the operator + + Returns: In all cases, returns $(D lhs < rhs ? -1 : lhs > rhs). The result + is not autocorrected in case of an erroneous comparison. + + */ + int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + bool error; + auto result = opChecked!"cmp"(lhs, rhs, error); + if (error) + { + trustedStderr.writefln("Erroneous ordering comparison: %s(%s) and %s(%s)", + Lhs.stringof, lhs, Rhs.stringof, rhs); + return lhs < rhs ? -1 : lhs > rhs; + } + return result; + } + + /// + @safe unittest + { + auto x = checked!Warn(-42); + // Passes + assert(x <= -42); + // Passes but prints a warning + // assert(x <= uint(-42)); + } + + /** + + Called automatically upon an overflow during a unary or binary operation. + + Params: + x = The operator involved + Lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + Rhs = The right-hand side type involved in the operator + + Returns: + $(D mixin(x ~ "lhs")) for unary, $(D mixin("lhs" ~ x ~ "rhs")) for + binary + + */ + typeof(~Lhs()) onOverflow(string x, Lhs)(ref Lhs lhs) + { + trustedStderr.writefln("Overflow on unary operator: %s%s(%s)", + x, Lhs.stringof, lhs); + return mixin(x ~ "lhs"); + } + /// ditto + typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + trustedStderr.writefln("Overflow on binary operator: %s(%s) %s %s(%s)", + Lhs.stringof, lhs, x, Rhs.stringof, rhs); + static if (x == "/") // Issue 20743: mixin below would cause SIGFPE on POSIX + return typeof(lhs / rhs).min; // or EXCEPTION_INT_OVERFLOW on Windows + else + return mixin("lhs" ~ x ~ "rhs"); + } + + // This is safe because we do not assign to the reference returned by + // `stderr`. The ability for the caller to do that is why `stderr` is not + // safe in the general case. + private @property auto ref trustedStderr() @trusted + { + import std.stdio : stderr; + + return stderr; + } +} + +/// +@safe unittest +{ + auto x = checked!Warn(42); + short x1 = cast(short) x; + //x += long(int.max); + auto y = checked!Warn(cast(const int) 42); + short y1 = cast(const byte) y; +} + +@system unittest +{ + auto a = checked!Warn(int.min); + auto b = checked!Warn(-1); + auto x = checked!Abort(int.min); + auto y = checked!Abort(-1); + + // Temporarily redirect output to stderr to make sure we get the right output. + import std.file : exists, remove; + import std.process : uniqueTempPath; + import std.stdio : stderr; + auto tmpname = uniqueTempPath; + scope(exit) if (exists(tmpname)) remove(tmpname); + auto t = stderr; + stderr.open(tmpname, "w"); + // Open a new scope to minimize code ran with stderr redirected. + { + scope(exit) stderr = t; + assert(a / b == a * b); + import std.exception : assertThrown; + import core.exception : AssertError; + assertThrown!AssertError(x / y); + } + import std.file : readText; + import std.ascii : newline; + auto witness = readText(tmpname); + auto expected = +"Overflow on binary operator: int(-2147483648) / const(int)(-1)" ~ newline ~ +"Overflow on binary operator: int(-2147483648) * const(int)(-1)" ~ newline ~ +"Overflow on binary operator: int(-2147483648) / const(int)(-1)" ~ newline; + assert(witness == expected, "'" ~ witness ~ "'"); +} + +// https://issues.dlang.org/show_bug.cgi?id=22249 +@safe unittest +{ + alias _ = Warn.onLowerBound!(int, int); +} + +// ProperCompare +/** + +Hook that provides arithmetically correct comparisons for equality and ordering. +Comparing an object of type $(D Checked!(X, ProperCompare)) against another +integral (for equality or ordering) ensures that no surprising conversions from +signed to unsigned integral occur before the comparison. Using $(D Checked!(X, +ProperCompare)) on either side of a comparison for equality against a +floating-point number makes sure the integral can be properly converted to the +floating point type, thus making sure equality is transitive. + +*/ +struct ProperCompare +{ + /** + Hook for `==` and `!=` that ensures comparison against integral values has + the behavior expected by the usual arithmetic rules. The built-in semantics + yield surprising behavior when comparing signed values against unsigned + values for equality, for example $(D uint.max == -1) or $(D -1_294_967_296 == + 3_000_000_000u). The call $(D hookOpEquals(x, y)) returns `true` if and only + if `x` and `y` represent the same arithmetic number. + + If one of the numbers is an integral and the other is a floating-point + number, $(D hookOpEquals(x, y)) returns `true` if and only if the integral + can be converted exactly (without approximation) to the floating-point + number. This is in order to preserve transitivity of equality: if $(D + hookOpEquals(x, y)) and $(D hookOpEquals(y, z)) then $(D hookOpEquals(y, + z)), in case `x`, `y`, and `z` are a mix of integral and floating-point + numbers. + + Params: + lhs = The left-hand side of the comparison for equality + rhs = The right-hand side of the comparison for equality + + Returns: + The result of the comparison, `true` if the values are equal + */ + static bool hookOpEquals(L, R)(L lhs, R rhs) + { + alias C = typeof(lhs + rhs); + static if (isFloatingPoint!C) + { + static if (!isFloatingPoint!L) + { + return hookOpEquals(rhs, lhs); + } + else static if (!isFloatingPoint!R) + { + static assert(isFloatingPoint!L && !isFloatingPoint!R); + auto rhs1 = C(rhs); + return lhs == rhs1 && cast(R) rhs1 == rhs; + } + else + return lhs == rhs; + } + else + { + bool error; + auto result = opChecked!"=="(lhs, rhs, error); + if (error) + { + // Only possible error is a wrong "true" + return false; + } + return result; + } + } + + /** + Hook for `<`, `<=`, `>`, and `>=` that ensures comparison against integral + values has the behavior expected by the usual arithmetic rules. The built-in + semantics yield surprising behavior when comparing signed values against + unsigned values, for example $(D 0u < -1). The call $(D hookOpCmp(x, y)) + returns `-1` if and only if `x` is smaller than `y` in abstract arithmetic + sense. + + If one of the numbers is an integral and the other is a floating-point + number, $(D hookOpEquals(x, y)) returns a floating-point number that is `-1` + if `x < y`, `0` if `x == y`, `1` if `x > y`, and `NaN` if the floating-point + number is `NaN`. + + Params: + lhs = The left-hand side of the comparison for ordering + rhs = The right-hand side of the comparison for ordering + + Returns: + The result of the comparison (negative if $(D lhs < rhs), positive if $(D + lhs > rhs), `0` if the values are equal) + */ + static auto hookOpCmp(L, R)(L lhs, R rhs) + { + alias C = typeof(lhs + rhs); + static if (isFloatingPoint!C) + { + return lhs < rhs + ? C(-1) + : lhs > rhs ? C(1) : lhs == rhs ? C(0) : C.init; + } + else + { + static if (!valueConvertible!(L, C) || !valueConvertible!(R, C)) + { + static assert(isUnsigned!C); + static assert(isUnsigned!L != isUnsigned!R); + if (!isUnsigned!L && lhs < 0) + return -1; + if (!isUnsigned!R && rhs < 0) + return 1; + } + return lhs < rhs ? -1 : lhs > rhs; + } + } +} + +/// +@safe unittest +{ + alias opEqualsProper = ProperCompare.hookOpEquals; + assert(opEqualsProper(42, 42)); + assert(opEqualsProper(42.0, 42.0)); + assert(opEqualsProper(42u, 42)); + assert(opEqualsProper(42, 42u)); + assert(-1 == 4294967295u); + assert(!opEqualsProper(-1, 4294967295u)); + assert(!opEqualsProper(const uint(-1), -1)); + assert(!opEqualsProper(uint(-1), -1.0)); + assert(3_000_000_000U == -1_294_967_296); + assert(!opEqualsProper(3_000_000_000U, -1_294_967_296)); +} + +@safe unittest +{ + alias opCmpProper = ProperCompare.hookOpCmp; + assert(opCmpProper(42, 42) == 0); + assert(opCmpProper(42, 42.0) == 0); + assert(opCmpProper(41, 42.0) < 0); + assert(opCmpProper(42, 41.0) > 0); + import std.math.traits : isNaN; + assert(isNaN(opCmpProper(41, double.init))); + assert(opCmpProper(42u, 42) == 0); + assert(opCmpProper(42, 42u) == 0); + assert(opCmpProper(-1, uint(-1)) < 0); + assert(opCmpProper(uint(-1), -1) > 0); + assert(opCmpProper(-1.0, -1) == 0); +} + +@safe unittest +{ + auto x1 = Checked!(uint, ProperCompare)(42u); + assert(x1.get < -1); + assert(x1 > -1); +} + +// WithNaN +/** + +Hook that reserves a special value as a "Not a Number" representative. For +signed integrals, the reserved value is `T.min`. For signed integrals, the +reserved value is `T.max`. + +The default value of a $(D Checked!(X, WithNaN)) is its NaN value, so care must +be taken that all variables are explicitly initialized. Any arithmetic and logic +operation involving at least on NaN becomes NaN itself. All of $(D a == b), $(D +a < b), $(D a > b), $(D a <= b), $(D a >= b) yield `false` if at least one of +`a` and `b` is NaN. + +*/ +struct WithNaN +{ +static: + /** + The default value used for values not explicitly initialized. It is the NaN + value, i.e. `T.min` for signed integrals and `T.max` for unsigned integrals. + */ + enum T defaultValue(T) = T.min == 0 ? T.max : T.min; + /** + The maximum value representable is `T.max` for signed integrals, $(D + T.max - 1) for unsigned integrals. The minimum value representable is $(D + T.min + 1) for signed integrals, `0` for unsigned integrals. + */ + enum T max(T) = cast(T) (T.min == 0 ? T.max - 1 : T.max); + /// ditto + enum T min(T) = cast(T) (T.min == 0 ? T(0) : T.min + 1); + + /** + If `rhs` is `WithNaN.defaultValue!Rhs`, returns + `WithNaN.defaultValue!Lhs`. Otherwise, returns $(D cast(Lhs) rhs). + + Params: + rhs = the value being cast (`Rhs` is the first argument to `Checked`) + Lhs = the target type of the cast + + Returns: The result of the cast operation. + */ + Lhs hookOpCast(Lhs, Rhs)(Rhs rhs) + { + static if (is(Lhs == bool)) + { + return rhs != defaultValue!Rhs && rhs != 0; + } + else static if (valueConvertible!(Rhs, Lhs)) + { + return rhs != defaultValue!Rhs ? Lhs(rhs) : defaultValue!Lhs; + } + else + { + // Not value convertible, only viable option is rhs fits within the + // bounds of Lhs + static if (ProperCompare.hookOpCmp(Rhs.min, Lhs.min) < 0) + { + // Example: hookOpCast!short(int(42)), hookOpCast!uint(int(42)) + if (ProperCompare.hookOpCmp(rhs, Lhs.min) < 0) + return defaultValue!Lhs; + } + static if (ProperCompare.hookOpCmp(Rhs.max, Lhs.max) > 0) + { + // Example: hookOpCast!int(uint(42)) + if (ProperCompare.hookOpCmp(rhs, Lhs.max) > 0) + return defaultValue!Lhs; + } + return cast(Lhs) rhs; + } + } + + /// + @safe unittest + { + auto x = checked!WithNaN(422); + assert((cast(ubyte) x) == 255); + x = checked!WithNaN(-422); + assert((cast(byte) x) == -128); + assert(cast(short) x == -422); + assert(cast(bool) x); + x = x.init; // set back to NaN + assert(x != true); + assert(x != false); + } + + /** + + Returns `false` if $(D lhs == WithNaN.defaultValue!Lhs), $(D lhs == rhs) + otherwise. + + Params: + lhs = The left-hand side of the comparison (`Lhs` is the first argument to + `Checked`) + rhs = The right-hand side of the comparison + + Returns: `lhs != WithNaN.defaultValue!Lhs && lhs == rhs` + */ + bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + return lhs != defaultValue!Lhs && lhs == rhs; + } + + /** + + If $(D lhs == WithNaN.defaultValue!Lhs), returns `double.init`. Otherwise, + has the same semantics as the default comparison. + + Params: + lhs = The left-hand side of the comparison (`Lhs` is the first argument to + `Checked`) + rhs = The right-hand side of the comparison + + Returns: `double.init` if `lhs == WitnNaN.defaultValue!Lhs`, `-1.0` if $(D + lhs < rhs), `0.0` if $(D lhs == rhs), `1.0` if $(D lhs > rhs). + + */ + double hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + if (lhs == defaultValue!Lhs) return double.init; + return lhs < rhs + ? -1.0 + : lhs > rhs ? 1.0 : lhs == rhs ? 0.0 : double.init; + } + + /// + @safe unittest + { + Checked!(int, WithNaN) x; + assert(!(x < 0) && !(x > 0) && !(x == 0)); + x = 1; + assert(x > 0 && !(x < 0) && !(x == 0)); + } + + /** + Defines hooks for unary operators `-`, `~`, `++`, and `--`. + + For `-` and `~`, if $(D v == WithNaN.defaultValue!T), returns + `WithNaN.defaultValue!T`. Otherwise, the semantics is the same as for the + built-in operator. + + For `++` and `--`, if $(D v == WithNaN.defaultValue!Lhs) or the operation + would result in an overflow, sets `v` to `WithNaN.defaultValue!T`. + Otherwise, the semantics is the same as for the built-in operator. + + Params: + x = The operator symbol + v = The left-hand side of the comparison (`T` is the first argument to + `Checked`) + + Returns: $(UL $(LI For $(D x == "-" || x == "~"): If $(D v == + WithNaN.defaultValue!T), the function returns `WithNaN.defaultValue!T`. + Otherwise it returns the normal result of the operator.) $(LI For $(D x == + "++" || x == "--"): The function returns `void`.)) + + */ + auto hookOpUnary(string x, T)(ref T v) + { + static if (x == "-" || x == "~") + { + return v != defaultValue!T ? mixin(x ~ "v") : v; + } + else static if (x == "++") + { + static if (defaultValue!T == T.min) + { + if (v != defaultValue!T) + { + if (v == T.max) v = defaultValue!T; + else ++v; + } + } + else + { + static assert(defaultValue!T == T.max); + if (v != defaultValue!T) ++v; + } + } + else static if (x == "--") + { + if (v != defaultValue!T) --v; + } + } + + /// + @safe unittest + { + Checked!(int, WithNaN) x; + ++x; + assert(x.isNaN); + x = 1; + assert(!x.isNaN); + x = -x; + ++x; + assert(!x.isNaN); + } + + @safe unittest // for coverage + { + Checked!(uint, WithNaN) y; + ++y; + assert(y.isNaN); + } + + /** + Defines hooks for binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, + `^`, `<<`, `>>`, and `>>>` for cases where a `Checked` object is the + left-hand side operand. If $(D lhs == WithNaN.defaultValue!Lhs), returns + $(D WithNaN.defaultValue!(typeof(lhs + rhs))) without evaluating the + operand. Otherwise, evaluates the operand. If evaluation does not overflow, + returns the result. Otherwise, returns $(D WithNaN.defaultValue!(typeof(lhs + + rhs))). + + Params: + x = The operator symbol + lhs = The left-hand side operand (`Lhs` is the first argument to `Checked`) + rhs = The right-hand side operand + + Returns: If $(D lhs != WithNaN.defaultValue!Lhs) and the operator does not + overflow, the function returns the same result as the built-in operator. In + all other cases, returns $(D WithNaN.defaultValue!(typeof(lhs + rhs))). + */ + auto hookOpBinary(string x, L, R)(L lhs, R rhs) + { + alias Result = typeof(lhs + rhs); + if (lhs != defaultValue!L) + { + bool error; + auto result = opChecked!x(lhs, rhs, error); + if (!error) return result; + } + return defaultValue!Result; + } + + /// + @safe unittest + { + Checked!(int, WithNaN) x; + assert((x + 1).isNaN); + x = 100; + assert(!(x + 1).isNaN); + } + + /** + Defines hooks for binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, + `^`, `<<`, `>>`, and `>>>` for cases where a `Checked` object is the + right-hand side operand. If $(D rhs == WithNaN.defaultValue!Rhs), returns + $(D WithNaN.defaultValue!(typeof(lhs + rhs))) without evaluating the + operand. Otherwise, evaluates the operand. If evaluation does not overflow, + returns the result. Otherwise, returns $(D WithNaN.defaultValue!(typeof(lhs + + rhs))). + + Params: + x = The operator symbol + lhs = The left-hand side operand + rhs = The right-hand side operand (`Rhs` is the first argument to `Checked`) + + Returns: If $(D rhs != WithNaN.defaultValue!Rhs) and the operator does not + overflow, the function returns the same result as the built-in operator. In + all other cases, returns $(D WithNaN.defaultValue!(typeof(lhs + rhs))). + */ + auto hookOpBinaryRight(string x, L, R)(L lhs, R rhs) + { + alias Result = typeof(lhs + rhs); + if (rhs != defaultValue!R) + { + bool error; + auto result = opChecked!x(lhs, rhs, error); + if (!error) return result; + } + return defaultValue!Result; + } + /// + @safe unittest + { + Checked!(int, WithNaN) x; + assert((1 + x).isNaN); + x = 100; + assert(!(1 + x).isNaN); + } + + /** + + Defines hooks for binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, + `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=` for cases where a `Checked` + object is the left-hand side operand. If $(D lhs == + WithNaN.defaultValue!Lhs), no action is carried. Otherwise, evaluates the + operand. If evaluation does not overflow and fits in `Lhs` without loss of + information or change of sign, sets `lhs` to the result. Otherwise, sets + `lhs` to `WithNaN.defaultValue!Lhs`. + + Params: + x = The operator symbol (without the `=`) + lhs = The left-hand side operand (`Lhs` is the first argument to `Checked`) + rhs = The right-hand side operand + + Returns: `void` + */ + void hookOpOpAssign(string x, L, R)(ref L lhs, R rhs) + { + if (lhs == defaultValue!L) + return; + bool error; + auto temp = opChecked!x(lhs, rhs, error); + lhs = error + ? defaultValue!L + : hookOpCast!L(temp); + } + + /// + @safe unittest + { + Checked!(int, WithNaN) x; + x += 4; + assert(x.isNaN); + x = 0; + x += 4; + assert(!x.isNaN); + x += int.max; + assert(x.isNaN); + } +} + +/// +@safe unittest +{ + auto x1 = Checked!(int, WithNaN)(); + assert(x1.isNaN); + assert(x1.get == int.min); + assert(x1 != x1); + assert(!(x1 < x1)); + assert(!(x1 > x1)); + assert(!(x1 == x1)); + ++x1; + assert(x1.isNaN); + assert(x1.get == int.min); + --x1; + assert(x1.isNaN); + assert(x1.get == int.min); + x1 = 42; + assert(!x1.isNaN); + assert(x1 == x1); + assert(x1 <= x1); + assert(x1 >= x1); + static assert(x1.min == int.min + 1); + x1 += long(int.max); +} + +/** +Queries whether a $(D Checked!(T, WithNaN)) object is not a number (NaN). + +Params: + x = the `Checked` instance queried + +Returns: + `true` if `x` is a NaN, `false` otherwise +*/ +bool isNaN(T)(const Checked!(T, WithNaN) x) +{ + return x.get == x.init.get; +} + +/// +@safe unittest +{ + auto x1 = Checked!(int, WithNaN)(); + assert(x1.isNaN); + x1 = 1; + assert(!x1.isNaN); + x1 = x1.init; + assert(x1.isNaN); +} + +@safe unittest +{ + void test1(T)() + { + auto x1 = Checked!(T, WithNaN)(); + assert(x1.isNaN); + assert(x1.get == int.min); + assert(x1 != x1); + assert(!(x1 < x1)); + assert(!(x1 > x1)); + assert(!(x1 == x1)); + assert(x1.get == int.min); + auto x2 = Checked!(T, WithNaN)(42); + assert(!x2.isNaN); + assert(x2 == x2); + assert(x2 <= x2); + assert(x2 >= x2); + static assert(x2.min == T.min + 1); + } + test1!int; + test1!(const int); + test1!(immutable int); + + void test2(T)() + { + auto x1 = Checked!(T, WithNaN)(); + assert(x1.get == T.min); + assert(x1 != x1); + assert(!(x1 < x1)); + assert(!(x1 > x1)); + assert(!(x1 == x1)); + ++x1; + assert(x1.get == T.min); + --x1; + assert(x1.get == T.min); + x1 = 42; + assert(x1 == x1); + assert(x1 <= x1); + assert(x1 >= x1); + static assert(x1.min == T.min + 1); + x1 += long(T.max); + } + test2!int; +} + +@safe unittest +{ + alias Smart(T) = Checked!(Checked!(T, ProperCompare), WithNaN); + Smart!int x1; + assert(x1 != x1); + x1 = -1; + assert(x1 < 1u); + auto x2 = Smart!(const int)(42); +} + +// Saturate +/** + +Hook that implements $(I saturation), i.e. any arithmetic operation that would +overflow leaves the result at its extreme value (`min` or `max` depending on the +direction of the overflow). + +Saturation is not sticky; if a value reaches its saturation value, another +operation may take it back to normal range. + +*/ +struct Saturate +{ +static: + /** + + Implements saturation for operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, + and `>>>=`. This hook is called if the result of the binary operation does + not fit in `Lhs` without loss of information or a change in sign. + + Params: + Rhs = The right-hand side type in the assignment, after the operation has + been computed + bound = The bound being violated + + Returns: `Lhs.max` if $(D rhs >= 0), `Lhs.min` otherwise. + + */ + T onLowerBound(Rhs, T)(Rhs, T bound) + { + return bound; + } + /// ditto + T onUpperBound(Rhs, T)(Rhs, T bound) + { + return bound; + } + /// + @safe unittest + { + auto x = checked!Saturate(short(100)); + x += 33000; + assert(x == short.max); + x -= 70000; + assert(x == short.min); + } + + /** + + Implements saturation for operators `+`, `-` (unary and binary), `*`, `/`, + `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`. + + For unary `-`, `onOverflow` is called if $(D lhs == Lhs.min) and `Lhs` is a + signed type. The function returns `Lhs.max`. + + For binary operators, the result is as follows: $(UL $(LI `Lhs.max` if the + result overflows in the positive direction, on division by `0`, or on + shifting right by a negative value) $(LI `Lhs.min` if the result overflows + in the negative direction) $(LI `0` if `lhs` is being shifted left by a + negative value, or shifted right by a large positive value)) + + Params: + x = The operator involved in the `opAssign` operation + Lhs = The left-hand side type of the operator (`Lhs` is the first argument to + `Checked`) + Rhs = The right-hand side type in the operator + + Returns: The saturated result of the operator. + + */ + auto onOverflow(string x, Lhs)(Lhs) + { + static assert(x == "-" || x == "++" || x == "--"); + return x == "--" ? Lhs.min : Lhs.max; + } + /// ditto + typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + static if (x == "+") + return rhs >= 0 ? Lhs.max : Lhs.min; + else static if (x == "*") + return (lhs >= 0) == (rhs >= 0) ? Lhs.max : Lhs.min; + else static if (x == "^^") + return lhs > 0 || !(rhs & 1) ? Lhs.max : Lhs.min; + else static if (x == "-") + return rhs >= 0 ? Lhs.min : Lhs.max; + else static if (x == "/" || x == "%") + return Lhs.max; + else static if (x == "<<") + return rhs >= 0 ? Lhs.max : 0; + else static if (x == ">>" || x == ">>>") + return rhs >= 0 ? 0 : Lhs.max; + else + static assert(false); + } + /// + @safe unittest + { + assert(checked!Saturate(int.max) + 1 == int.max); + assert(checked!Saturate(100) ^^ 10 == int.max); + assert(checked!Saturate(-100) ^^ 10 == int.max); + assert(checked!Saturate(100) / 0 == int.max); + assert(checked!Saturate(100) << -1 == 0); + assert(checked!Saturate(100) << 33 == int.max); + assert(checked!Saturate(100) >> -1 == int.max); + assert(checked!Saturate(100) >> 33 == 0); + } +} + +/// +@safe unittest +{ + auto x = checked!Saturate(int.max); + ++x; + assert(x == int.max); + --x; + assert(x == int.max - 1); + x = int.min; + assert(-x == int.max); + x -= 42; + assert(x == int.min); + assert(x * -2 == int.max); +} + +/* +Yields `true` if `T1` is "value convertible" (by C's "value preserving" rule, +see $(HTTP c-faq.com/expr/preservingrules.html)) to `T2`, where the two are +integral types. That is, all of values in `T1` are also in `T2`. For example +`int` is value convertible to `long` but not to `uint` or `ulong`. +*/ +private enum valueConvertible(T1, T2) = isIntegral!T1 && isIntegral!T2 && + is(T1 : T2) && ( + isUnsigned!T1 == isUnsigned!T2 || // same signedness + !isUnsigned!T2 && T2.sizeof > T1.sizeof // safely convertible + ); + +/** + +Defines binary operations with overflow checking for any two integral types. +The result type obeys the language rules (even when they may be +counterintuitive), and `overflow` is set if an overflow occurs (including +inadvertent change of signedness, e.g. `-1` is converted to `uint`). +Conceptually the behavior is: + +$(OL $(LI Perform the operation in infinite precision) +$(LI If the infinite-precision result fits in the result type, return it and +do not touch `overflow`) +$(LI Otherwise, set `overflow` to `true` and return an unspecified value) +) + +The implementation exploits properties of types and operations to minimize +additional work. + +Params: +x = The binary operator involved, e.g. `/` +lhs = The left-hand side of the operator +rhs = The right-hand side of the operator +overflow = The overflow indicator (assigned `true` in case there's an error) + +Returns: +The result of the operation, which is the same as the built-in operator +*/ +typeof(mixin(x == "cmp" ? "0" : ("L() " ~ x ~ " R()"))) +opChecked(string x, L, R)(const L lhs, const R rhs, ref bool overflow) +if (isIntegral!L && isIntegral!R) +{ + static if (x == "cmp") + alias Result = int; + else + alias Result = typeof(mixin("L() " ~ x ~ " R()")); + + import core.checkedint : addu, adds, subs, muls, subu, mulu; + import std.algorithm.comparison : among; + static if (x == "==") + { + alias C = typeof(lhs + rhs); + static if (valueConvertible!(L, C) && valueConvertible!(R, C)) + { + // Values are converted to R before comparison, cool. + return lhs == rhs; + } + else + { + static assert(isUnsigned!C); + static assert(isUnsigned!L != isUnsigned!R); + if (lhs != rhs) return false; + // R(lhs) and R(rhs) have the same bit pattern, yet may be + // different due to signedness change. + static if (!isUnsigned!R) + { + if (rhs >= 0) + return true; + } + else + { + if (lhs >= 0) + return true; + } + overflow = true; + return true; + } + } + else static if (x == "cmp") + { + alias C = typeof(lhs + rhs); + static if (!valueConvertible!(L, C) || !valueConvertible!(R, C)) + { + static assert(isUnsigned!C); + static assert(isUnsigned!L != isUnsigned!R); + if (!isUnsigned!L && lhs < 0) + { + overflow = true; + return -1; + } + if (!isUnsigned!R && rhs < 0) + { + overflow = true; + return 1; + } + } + return lhs < rhs ? -1 : lhs > rhs; + } + else static if (x.among("<<", ">>", ">>>")) + { + // Handle shift separately from all others. The test below covers + // negative rhs as well. + import std.conv : unsigned; + if (unsigned(rhs) > 8 * Result.sizeof) goto fail; + return mixin("lhs" ~ x ~ "rhs"); + } + else static if (x.among("&", "|", "^")) + { + // Nothing to check + return mixin("lhs" ~ x ~ "rhs"); + } + else static if (x == "^^") + { + // Exponentiation is weird, handle separately + return pow(lhs, rhs, overflow); + } + else static if (valueConvertible!(L, Result) && + valueConvertible!(R, Result)) + { + static if (L.sizeof < Result.sizeof && R.sizeof < Result.sizeof && + x.among("+", "-", "*")) + { + // No checks - both are value converted and result is in range + return mixin("lhs" ~ x ~ "rhs"); + } + else static if (x == "+") + { + static if (isUnsigned!Result) alias impl = addu; + else alias impl = adds; + return impl(Result(lhs), Result(rhs), overflow); + } + else static if (x == "-") + { + static if (isUnsigned!Result) alias impl = subu; + else alias impl = subs; + return impl(Result(lhs), Result(rhs), overflow); + } + else static if (x == "*") + { + static if (!isUnsigned!L && !isUnsigned!R && + is(L == Result)) + { + if (lhs == Result.min && rhs == -1) goto fail; + } + static if (isUnsigned!Result) alias impl = mulu; + else alias impl = muls; + return impl(Result(lhs), Result(rhs), overflow); + } + else static if (x == "/" || x == "%") + { + static if (!isUnsigned!L && !isUnsigned!R && + is(L == Result) && x == "/") + { + if (lhs == Result.min && rhs == -1) goto fail; + } + if (rhs == 0) goto fail; + return mixin("lhs" ~ x ~ "rhs"); + } + else static assert(0, x); + } + else // Mixed signs + { + static assert(isUnsigned!Result); + static assert(isUnsigned!L != isUnsigned!R); + static if (x == "+") + { + static if (!isUnsigned!L) + { + if (lhs < 0) + return subu(Result(rhs), Result(-lhs), overflow); + } + else static if (!isUnsigned!R) + { + if (rhs < 0) + return subu(Result(lhs), Result(-rhs), overflow); + } + return addu(Result(lhs), Result(rhs), overflow); + } + else static if (x == "-") + { + static if (!isUnsigned!L) + { + if (lhs < 0) goto fail; + } + else static if (!isUnsigned!R) + { + if (rhs < 0) + return addu(Result(lhs), Result(-rhs), overflow); + } + return subu(Result(lhs), Result(rhs), overflow); + } + else static if (x == "*") + { + static if (!isUnsigned!L) + { + if (lhs < 0) goto fail; + } + else static if (!isUnsigned!R) + { + if (rhs < 0) goto fail; + } + return mulu(Result(lhs), Result(rhs), overflow); + } + else static if (x == "/" || x == "%") + { + static if (!isUnsigned!L) + { + if (lhs < 0 || rhs == 0) goto fail; + } + else static if (!isUnsigned!R) + { + if (rhs <= 0) goto fail; + } + return mixin("Result(lhs)" ~ x ~ "Result(rhs)"); + } + else static assert(0, x); + } + debug assert(false); +fail: + overflow = true; + return Result(0); +} + +/// +@safe unittest +{ + bool overflow; + assert(opChecked!"+"(const short(1), short(1), overflow) == 2 && !overflow); + assert(opChecked!"+"(1, 1, overflow) == 2 && !overflow); + assert(opChecked!"+"(1, 1u, overflow) == 2 && !overflow); + assert(opChecked!"+"(-1, 1u, overflow) == 0 && !overflow); + assert(opChecked!"+"(1u, -1, overflow) == 0 && !overflow); +} + +/// +@safe unittest +{ + bool overflow; + assert(opChecked!"-"(1, 1, overflow) == 0 && !overflow); + assert(opChecked!"-"(1, 1u, overflow) == 0 && !overflow); + assert(opChecked!"-"(1u, -1, overflow) == 2 && !overflow); + assert(opChecked!"-"(-1, 1u, overflow) == 0 && overflow); +} + +@safe unittest +{ + bool overflow; + assert(opChecked!"*"(2, 3, overflow) == 6 && !overflow); + assert(opChecked!"*"(2, 3u, overflow) == 6 && !overflow); + assert(opChecked!"*"(1u, -1, overflow) == 0 && overflow); + //assert(mul(-1, 1u, overflow) == uint.max - 1 && overflow); +} + +@safe unittest +{ + bool overflow; + assert(opChecked!"/"(6, 3, overflow) == 2 && !overflow); + assert(opChecked!"/"(6, 3, overflow) == 2 && !overflow); + assert(opChecked!"/"(6u, 3, overflow) == 2 && !overflow); + assert(opChecked!"/"(6, 3u, overflow) == 2 && !overflow); + assert(opChecked!"/"(11, 0, overflow) == 0 && overflow); + overflow = false; + assert(opChecked!"/"(6u, 0, overflow) == 0 && overflow); + overflow = false; + assert(opChecked!"/"(-6, 2u, overflow) == 0 && overflow); + overflow = false; + assert(opChecked!"/"(-6, 0u, overflow) == 0 && overflow); + overflow = false; + assert(opChecked!"cmp"(0u, -6, overflow) == 1 && overflow); + overflow = false; + assert(opChecked!"|"(1, 2, overflow) == 3 && !overflow); +} + +/* +Exponentiation function used by the implementation of operator `^^`. +*/ +private pure @safe nothrow @nogc +auto pow(L, R)(const L lhs, const R rhs, ref bool overflow) +if (isIntegral!L && isIntegral!R) +{ + if (rhs <= 1) + { + if (rhs == 0) return 1; + static if (!isUnsigned!R) + return rhs == 1 + ? lhs + : (rhs == -1 && (lhs == 1 || lhs == -1)) ? lhs : 0; + else + return lhs; + } + + typeof(lhs ^^ rhs) b = void; + static if (!isUnsigned!L && isUnsigned!(typeof(b))) + { + // Need to worry about mixed-sign stuff + if (lhs < 0) + { + if (rhs & 1) + { + if (lhs < 0) overflow = true; + return 0; + } + b = -lhs; + } + else + { + b = lhs; + } + } + else + { + b = lhs; + } + if (b == 1) return 1; + if (b == -1) return (rhs & 1) ? -1 : 1; + if (rhs > 63) + { + overflow = true; + return 0; + } + + assert((b > 1 || b < -1) && rhs > 1); + return powImpl(b, cast(uint) rhs, overflow); +} + +// Inspiration: http://www.stepanovpapers.com/PAM.pdf +pure @safe nothrow @nogc +private T powImpl(T)(T b, uint e, ref bool overflow) +if (isIntegral!T && T.sizeof >= 4) +{ + assert(e > 1); + + import core.checkedint : muls, mulu; + static if (isUnsigned!T) alias mul = mulu; + else alias mul = muls; + + T r = b; + --e; + // Loop invariant: r * (b ^^ e) is the actual result + for (;; e /= 2) + { + if (e % 2) + { + r = mul(r, b, overflow); + if (e == 1) break; + } + b = mul(b, b, overflow); + } + return r; +} + +@safe unittest +{ + static void testPow(T)(T x, uint e) + { + bool overflow; + assert(opChecked!"^^"(T(0), 0, overflow) == 1); + assert(opChecked!"^^"(-2, T(0), overflow) == 1); + assert(opChecked!"^^"(-2, T(1), overflow) == -2); + assert(opChecked!"^^"(-1, -1, overflow) == -1); + assert(opChecked!"^^"(-2, 1, overflow) == -2); + assert(opChecked!"^^"(-2, -1, overflow) == 0); + assert(opChecked!"^^"(-2, 4u, overflow) == 16); + assert(!overflow); + assert(opChecked!"^^"(-2, 3u, overflow) == 0); + assert(overflow); + overflow = false; + assert(opChecked!"^^"(3, 64u, overflow) == 0); + assert(overflow); + overflow = false; + foreach (uint i; 0 .. e) + { + assert(opChecked!"^^"(x, i, overflow) == x ^^ i); + assert(!overflow); + } + assert(opChecked!"^^"(x, e, overflow) == x ^^ e); + assert(overflow); + } + + testPow!int(3, 21); + testPow!uint(3, 21); + testPow!long(3, 40); + testPow!ulong(3, 41); +} + +version (StdUnittest) private struct CountOverflows +{ + uint calls; + auto onOverflow(string op, Lhs)(Lhs lhs) + { + ++calls; + return mixin(op ~ "lhs"); + } + auto onOverflow(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + ++calls; + return mixin("lhs" ~ op ~ "rhs"); + } + T onLowerBound(Rhs, T)(Rhs rhs, T) + { + ++calls; + return cast(T) rhs; + } + T onUpperBound(Rhs, T)(Rhs rhs, T) + { + ++calls; + return cast(T) rhs; + } +} + +// opBinary +@nogc nothrow pure @safe unittest +{ + static struct CountOpBinary + { + uint calls; + auto hookOpBinary(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + ++calls; + return mixin("lhs" ~ op ~ "rhs"); + } + } + auto x = Checked!(const int, void)(42), y = Checked!(immutable int, void)(142); + assert(x + y == 184); + assert(x + 100 == 142); + assert(y - x == 100); + assert(200 - x == 158); + assert(y * x == 142 * 42); + assert(x / 1 == 42); + assert(x % 20 == 2); + + auto x1 = Checked!(int, CountOverflows)(42); + assert(x1 + 0 == 42); + assert(x1 + false == 42); + assert(is(typeof(x1 + 0.5) == double)); + assert(x1 + 0.5 == 42.5); + assert(x1.hook.calls == 0); + assert(x1 + int.max == int.max + 42); + assert(x1.hook.calls == 1); + assert(x1 * 2 == 84); + assert(x1.hook.calls == 1); + assert(x1 / 2 == 21); + assert(x1.hook.calls == 1); + assert(x1 % 20 == 2); + assert(x1.hook.calls == 1); + assert(x1 << 2 == 42 << 2); + assert(x1.hook.calls == 1); + assert(x1 << 42 == x1.get << x1.get); + assert(x1.hook.calls == 2); + x1 = int.min; + assert(x1 - 1 == int.max); + assert(x1.hook.calls == 3); + + auto x2 = Checked!(int, CountOpBinary)(42); + assert(x2 + 1 == 43); + assert(x2.hook.calls == 1); + + auto x3 = Checked!(uint, CountOverflows)(42u); + assert(x3 + 1 == 43); + assert(x3.hook.calls == 0); + assert(x3 - 1 == 41); + assert(x3.hook.calls == 0); + assert(x3 + (-42) == 0); + assert(x3.hook.calls == 0); + assert(x3 - (-42) == 84); + assert(x3.hook.calls == 0); + assert(x3 * 2 == 84); + assert(x3.hook.calls == 0); + assert(x3 * -2 == -84); + assert(x3.hook.calls == 1); + assert(x3 / 2 == 21); + assert(x3.hook.calls == 1); + assert(x3 / -2 == 0); + assert(x3.hook.calls == 2); + assert(x3 ^^ 2 == 42 * 42); + assert(x3.hook.calls == 2); + + auto x4 = Checked!(int, CountOverflows)(42); + assert(x4 + 1 == 43); + assert(x4.hook.calls == 0); + assert(x4 + 1u == 43); + assert(x4.hook.calls == 0); + assert(x4 - 1 == 41); + assert(x4.hook.calls == 0); + assert(x4 * 2 == 84); + assert(x4.hook.calls == 0); + x4 = -2; + assert(x4 + 2u == 0); + assert(x4.hook.calls == 0); + assert(x4 * 2u == -4); + assert(x4.hook.calls == 1); + + auto x5 = Checked!(int, CountOverflows)(3); + assert(x5 ^^ 0 == 1); + assert(x5 ^^ 1 == 3); + assert(x5 ^^ 2 == 9); + assert(x5 ^^ 3 == 27); + assert(x5 ^^ 4 == 81); + assert(x5 ^^ 5 == 81 * 3); + assert(x5 ^^ 6 == 81 * 9); +} + +// opBinaryRight +@nogc nothrow pure @safe unittest +{ + auto x1 = Checked!(int, CountOverflows)(42); + assert(1 + x1 == 43); + assert(true + x1 == 43); + assert(0.5 + x1 == 42.5); + auto x2 = Checked!(int, void)(42); + assert(x1 + x2 == 84); + assert(x2 + x1 == 84); +} + +// opOpAssign +@safe unittest +{ + auto x1 = Checked!(int, CountOverflows)(3); + assert((x1 += 2) == 5); + x1 *= 2_000_000_000L; + assert(x1.hook.calls == 1); + x1 *= -2_000_000_000L; + assert(x1.hook.calls == 2); + + auto x2 = Checked!(ushort, CountOverflows)(ushort(3)); + assert((x2 += 2) == 5); + assert(x2.hook.calls == 0); + assert((x2 += ushort.max) == cast(ushort) (ushort(5) + ushort.max)); + assert(x2.hook.calls == 1); + + auto x3 = Checked!(uint, CountOverflows)(3u); + x3 *= ulong(2_000_000_000); + assert(x3.hook.calls == 1); +} + +// opAssign +@safe unittest +{ + Checked!(int, void) x; + x = 42; + assert(x.get == 42); + x = x; + assert(x.get == 42); + x = short(43); + assert(x.get == 43); + x = ushort(44); + assert(x.get == 44); +} + +@safe unittest +{ + static assert(!is(typeof(Checked!(short, void)(ushort(42))))); + static assert(!is(typeof(Checked!(int, void)(long(42))))); + static assert(!is(typeof(Checked!(int, void)(ulong(42))))); + assert(Checked!(short, void)(short(42)).get == 42); + assert(Checked!(int, void)(ushort(42)).get == 42); +} + +// opCast +@nogc nothrow pure @safe unittest +{ + static assert(is(typeof(cast(float) Checked!(int, void)(42)) == float)); + assert(cast(float) Checked!(int, void)(42) == 42); + + assert(is(typeof(cast(long) Checked!(int, void)(42)) == long)); + assert(cast(long) Checked!(int, void)(42) == 42); + static assert(is(typeof(cast(long) Checked!(uint, void)(42u)) == long)); + assert(cast(long) Checked!(uint, void)(42u) == 42); + + auto x = Checked!(int, void)(42); + if (x) {} else assert(0); + x = 0; + if (x) assert(0); + + static struct Hook1 + { + uint calls; + Dst hookOpCast(Dst, Src)(Src value) + { + ++calls; + return 42; + } + } + auto y = Checked!(long, Hook1)(long.max); + assert(cast(int) y == 42); + assert(cast(uint) y == 42); + assert(y.hook.calls == 2); + + static struct Hook2 + { + uint calls; + Dst onBadCast(Dst, Src)(Src value) + { + ++calls; + return 42; + } + } + auto x1 = Checked!(uint, Hook2)(100u); + assert(cast(ushort) x1 == 100); + assert(cast(short) x1 == 100); + assert(cast(float) x1 == 100); + assert(cast(double) x1 == 100); + assert(cast(real) x1 == 100); + assert(x1.hook.calls == 0); + assert(cast(int) x1 == 100); + assert(x1.hook.calls == 0); + x1 = uint.max; + assert(cast(int) x1 == 42); + assert(x1.hook.calls == 1); + + auto x2 = Checked!(int, Hook2)(-100); + assert(cast(short) x2 == -100); + assert(cast(ushort) x2 == 42); + assert(cast(uint) x2 == 42); + assert(cast(ulong) x2 == 42); + assert(x2.hook.calls == 3); +} + +// opEquals +@nogc nothrow pure @safe unittest +{ + assert(Checked!(int, void)(42) == 42L); + assert(42UL == Checked!(int, void)(42)); + + static struct Hook1 + { + uint calls; + bool hookOpEquals(Lhs, Rhs)(const Lhs lhs, const Rhs rhs) + { + ++calls; + return lhs != rhs; + } + } + auto x1 = Checked!(int, Hook1)(100); + assert(x1 != Checked!(long, Hook1)(100)); + assert(x1.hook.calls == 1); + assert(x1 != 100u); + assert(x1.hook.calls == 2); + + static struct Hook2 + { + uint calls; + bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + ++calls; + return false; + } + } + auto x2 = Checked!(int, Hook2)(-100); + assert(x2 != x1); + // For coverage: lhs has no hookOpEquals, rhs does + assert(Checked!(uint, void)(100u) != x2); + // For coverage: different types, neither has a hookOpEquals + assert(Checked!(uint, void)(100u) == Checked!(int, void*)(100)); + assert(x2.hook.calls == 0); + assert(x2 != -100); + assert(x2.hook.calls == 1); + assert(x2 != cast(uint) -100); + assert(x2.hook.calls == 2); + x2 = 100; + assert(x2 != cast(uint) 100); + assert(x2.hook.calls == 3); + x2 = -100; + + auto x3 = Checked!(uint, Hook2)(100u); + assert(x3 != 100); + x3 = uint.max; + assert(x3 != -1); + + assert(x2 != x3); +} + +// opCmp +@nogc nothrow pure @safe unittest +{ + Checked!(int, void) x; + assert(x <= x); + assert(x < 45); + assert(x < 45u); + assert(x > -45); + assert(x < 44.2); + assert(x > -44.2); + assert(!(x < double.init)); + assert(!(x > double.init)); + assert(!(x <= double.init)); + assert(!(x >= double.init)); + + static struct Hook1 + { + uint calls; + int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + ++calls; + return 0; + } + } + auto x1 = Checked!(int, Hook1)(42); + assert(!(x1 < 43u)); + assert(!(43u < x1)); + assert(x1.hook.calls == 2); + + static struct Hook2 + { + uint calls; + int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + ++calls; + return ProperCompare.hookOpCmp(lhs, rhs); + } + } + auto x2 = Checked!(int, Hook2)(-42); + assert(x2 < 43u); + assert(43u > x2); + assert(x2.hook.calls == 2); + x2 = 42; + assert(x2 > 41u); + + auto x3 = Checked!(uint, Hook2)(42u); + assert(x3 > 41); + assert(x3 > -41); +} + +// opUnary +@nogc nothrow pure @safe unittest +{ + auto x = Checked!(int, void)(42); + assert(x == +x); + static assert(is(typeof(-x) == typeof(x))); + assert(-x == Checked!(int, void)(-42)); + static assert(is(typeof(~x) == typeof(x))); + assert(~x == Checked!(int, void)(~42)); + assert(++x == 43); + assert(--x == 42); + + static struct Hook1 + { + uint calls; + auto hookOpUnary(string op, T)(T value) if (op == "-") + { + ++calls; + return T(42); + } + auto hookOpUnary(string op, T)(T value) if (op == "~") + { + ++calls; + return T(43); + } + } + auto x1 = Checked!(int, Hook1)(100); + assert(is(typeof(-x1) == typeof(x1))); + assert(-x1 == Checked!(int, Hook1)(42)); + assert(is(typeof(~x1) == typeof(x1))); + assert(~x1 == Checked!(int, Hook1)(43)); + assert(x1.hook.calls == 2); + + static struct Hook2 + { + uint calls; + void hookOpUnary(string op, T)(ref T value) if (op == "++") + { + ++calls; + --value; + } + void hookOpUnary(string op, T)(ref T value) if (op == "--") + { + ++calls; + ++value; + } + } + auto x2 = Checked!(int, Hook2)(100); + assert(++x2 == 99); + assert(x2 == 99); + assert(--x2 == 100); + assert(x2 == 100); + + auto x3 = Checked!(int, CountOverflows)(int.max - 1); + assert(++x3 == int.max); + assert(x3.hook.calls == 0); + assert(++x3 == int.min); + assert(x3.hook.calls == 1); + assert(-x3 == int.min); + assert(x3.hook.calls == 2); + + x3 = int.min + 1; + assert(--x3 == int.min); + assert(x3.hook.calls == 2); + assert(--x3 == int.max); + assert(x3.hook.calls == 3); +} + +// +@nogc nothrow pure @safe unittest +{ + Checked!(int, void) x; + assert(x == x); + assert(x == +x); + assert(x == -x); + ++x; + assert(x == 1); + x++; + assert(x == 2); + + x = 42; + assert(x == 42); + const short _short = 43; + x = _short; + assert(x == _short); + ushort _ushort = 44; + x = _ushort; + assert(x == _ushort); + assert(x == 44.0); + assert(x != 44.1); + assert(x < 45); + assert(x < 44.2); + assert(x > -45); + assert(x > -44.2); + + assert(cast(long) x == 44); + assert(cast(short) x == 44); + + const Checked!(uint, void) y; + assert(y <= y); + assert(y == 0); + assert(y < x); + x = -1; + assert(x > y); +} + +@nogc nothrow pure @safe unittest +{ + alias cint = Checked!(int, void); + cint a = 1, b = 2; + a += b; + assert(a == cint(3)); + + alias ccint = Checked!(cint, Saturate); + ccint c = 14; + a += c; + assert(a == cint(17)); +} + +// toHash +@safe unittest +{ + assert(checked(42).toHash() == checked(42).toHash()); + assert(checked(12).toHash() != checked(19).toHash()); + + static struct Hook1 + { + static size_t hookToHash(T)(T payload) nothrow @trusted + { + static if (size_t.sizeof == 4) + { + return typeid(payload).getHash(&payload) ^ 0xFFFF_FFFF; + } + else + { + return typeid(payload).getHash(&payload) ^ 0xFFFF_FFFF_FFFF_FFFF; + } + + } + } + + auto a = checked!Hook1(78); + auto b = checked!Hook1(78); + assert(a.toHash() == b.toHash()); + + assert(checked!Hook1(12).toHash() != checked!Hook1(13).toHash()); + + static struct Hook2 + { + static if (size_t.sizeof == 4) + { + static size_t hashMask = 0xFFFF_0000; + } + else + { + static size_t hashMask = 0xFFFF_0000_FFFF_0000; + } + + static size_t hookToHash(T)(T payload) nothrow @trusted + { + return typeid(payload).getHash(&payload) ^ hashMask; + } + } + + auto x = checked!Hook2(1901); + auto y = checked!Hook2(1989); + + assert((() nothrow @safe => x.toHash() == x.toHash())()); + + assert(x.toHash() == x.toHash()); + assert(x.toHash() != y.toHash()); + assert(checked!Hook1(1901).toHash() != x.toHash()); + + immutable z = checked!Hook1(1901); + immutable t = checked!Hook1(1901); + immutable w = checked!Hook2(1901); + + assert(z.toHash() == t.toHash()); + assert(z.toHash() != x.toHash()); + assert(z.toHash() != w.toHash()); + + const long c = 0xF0F0F0F0; + const long d = 0xF0F0F0F0; + + assert(checked!Hook1(c).toHash() != checked!Hook2(c)); + assert(checked!Hook1(c).toHash() != checked!Hook1(d)); + + // Hook with state, does not implement hookToHash + static struct Hook3 + { + ulong var1 = ulong.max; + uint var2 = uint.max; + } + + assert(checked!Hook3(12).toHash() != checked!Hook3(13).toHash()); + assert(checked!Hook3(13).toHash() == checked!Hook3(13).toHash()); + + // Hook with no state and no hookToHash, payload has its own hashing function + auto x1 = Checked!(Checked!int, ProperCompare)(123); + auto x2 = Checked!(Checked!int, ProperCompare)(123); + auto x3 = Checked!(Checked!int, ProperCompare)(144); + + assert(x1.toHash() == x2.toHash()); + assert(x1.toHash() != x3.toHash()); + assert(x2.toHash() != x3.toHash()); + + // Check shared. + { + shared shared0 = checked(12345678); + shared shared1 = checked!Hook1(123456789); + shared shared2 = checked!Hook2(234567891); + shared shared3 = checked!Hook3(345678912); + assert(shared0.toHash() == hashOf(shared0)); + assert(shared1.toHash() == hashOf(shared1)); + assert(shared2.toHash() == hashOf(shared2)); + assert(shared3.toHash() == hashOf(shared3)); + } +} + +/// +@safe unittest +{ + struct MyHook + { + static size_t hookToHash(T)(const T payload) nothrow @trusted + { + return .hashOf(payload); + } + } + + int[Checked!(int, MyHook)] aa; + Checked!(int, MyHook) var = 42; + aa[var] = 100; + + assert(aa[var] == 100); + + int[Checked!(int, Abort)] bb; + Checked!(int, Abort) var2 = 42; + bb[var2] = 100; + + assert(bb[var2] == 100); +} diff --git a/std/experimental/checkedint.d b/std/experimental/checkedint.d index 354851bfc84..9237341d418 100644 --- a/std/experimental/checkedint.d +++ b/std/experimental/checkedint.d @@ -1,3467 +1,14 @@ -// Written in the D programming language. -/** -$(SCRIPT inhibitQuickIndex = 1;) - -This module defines facilities for efficient checking of integral operations -against overflow, casting with loss of precision, unexpected change of sign, -etc. The checking (and possibly correction) can be done at operation level, for -example $(LREF opChecked)$(D !"+"(x, y, overflow)) adds two integrals `x` and -`y` and sets `overflow` to `true` if an overflow occurred. The flag `overflow` -(a `bool` passed by reference) is not touched if the operation succeeded, so the -same flag can be reused for a sequence of operations and tested at the end. - -Issuing individual checked operations is flexible and efficient but often -tedious. The $(LREF Checked) facility offers encapsulated integral wrappers that -do all checking internally and have configurable behavior upon erroneous -results. For example, `Checked!int` is a type that behaves like `int` but aborts -execution immediately whenever involved in an operation that produces the -arithmetically wrong result. The accompanying convenience function $(LREF -checked) uses type deduction to convert a value `x` of integral type `T` to -`Checked!T` by means of `checked(x)`. For example: - ---- -void main() -{ - import std.experimental.checkedint, std.stdio; - writeln((checked(5) + 7).get); // 12 - writeln((checked(10) * 1000 * 1000 * 1000).get); // Overflow -} ---- - -Similarly, $(D checked(-1) > uint(0)) aborts execution (even though the built-in -comparison $(D int(-1) > uint(0)) is surprisingly true due to language's -conversion rules modeled after C). Thus, `Checked!int` is a virtually drop-in -replacement for `int` useable in debug builds, to be replaced by `int` in -release mode if efficiency demands it. - -`Checked` has customizable behavior with the help of a second type parameter, -`Hook`. Depending on what methods `Hook` defines, core operations on the -underlying integral may be verified for overflow or completely redefined. If -`Hook` defines no method at all and carries no state, there is no change in -behavior, i.e. $(D Checked!(int, void)) is a wrapper around `int` that adds no -customization at all. - -This module provides a few predefined hooks (below) that add useful behavior to -`Checked`: - -$(BOOKTABLE , - $(TR $(TD $(LREF Abort)) $(TD - fails every incorrect operation with a message to $(REF - stderr, std, stdio) followed by a call to `assert(0)`. It is the default - second parameter, i.e. `Checked!short` is the same as - $(D Checked!(short, Abort)). - )) - $(TR $(TD $(LREF Throw)) $(TD - fails every incorrect operation by throwing an exception. - )) - $(TR $(TD $(LREF Warn)) $(TD - prints incorrect operations to $(REF stderr, std, stdio) - but otherwise preserves the built-in behavior. - )) - $(TR $(TD $(LREF ProperCompare)) $(TD - fixes the comparison operators `==`, `!=`, `<`, `<=`, `>`, and `>=` - to return correct results in all circumstances, - at a slight cost in efficiency. For example, - $(D Checked!(uint, ProperCompare)(1) > -1) is `true`, - which is not the case for the built-in comparison. Also, comparing - numbers for equality with floating-point numbers only passes if the - integral can be converted to the floating-point number precisely, - so as to preserve transitivity of equality. - )) - $(TR $(TD $(LREF WithNaN)) $(TD - reserves a special "Not a Number" (NaN) value akin to the homonym value - reserved for floating-point values. Once a $(D Checked!(X, WithNaN)) - gets this special value, it preserves and propagates it until - reassigned. $(LREF isNaN) can be used to query whether the object - is not a number. - )) - $(TR $(TD $(LREF Saturate)) $(TD - implements saturating arithmetic, i.e. $(D Checked!(int, Saturate)) - "stops" at `int.max` for all operations that would cause an `int` to - overflow toward infinity, and at `int.min` for all operations that would - correspondingly overflow toward negative infinity. - )) -) - - -These policies may be used alone, e.g. $(D Checked!(uint, WithNaN)) defines a -`uint`-like type that reaches a stable NaN state for all erroneous operations. -They may also be "stacked" on top of each other, owing to the property that a -checked integral emulates an actual integral, which means another checked -integral can be built on top of it. Some combinations of interest include: - -$(BOOKTABLE , - $(TR $(TD $(D Checked!(Checked!int, ProperCompare)))) - $(TR $(TD -defines an `int` with fixed -comparison operators that will fail with `assert(0)` upon overflow. (Recall that -`Abort` is the default policy.) The order in which policies are combined is -important because the outermost policy (`ProperCompare` in this case) has the -first crack at intercepting an operator. The converse combination $(D -Checked!(Checked!(int, ProperCompare))) is meaningless because `Abort` will -intercept comparison and will fail without giving `ProperCompare` a chance to -intervene. - )) - $(TR $(TD)) - $(TR $(TDNW $(D Checked!(Checked!(int, ProperCompare), WithNaN)))) - $(TR $(TD -defines an `int`-like -type that supports a NaN value. For values that are not NaN, comparison works -properly. Again the composition order is important; $(D Checked!(Checked!(int, -WithNaN), ProperCompare)) does not have good semantics because `ProperCompare` -intercepts comparisons before the numbers involved are tested for NaN. - )) -) - -The hook's members are looked up statically in a Design by Introspection manner -and are all optional. The table below illustrates the members that a hook type -may define and their influence over the behavior of the `Checked` type using it. -In the table, `hook` is an alias for `Hook` if the type `Hook` does not -introduce any state, or an object of type `Hook` otherwise. - -$(TABLE , -$(TR $(TH `Hook` member) $(TH Semantics in $(D Checked!(T, Hook))) -) -$(TR $(TD `defaultValue`) $(TD If defined, `Hook.defaultValue!T` is used as the -default initializer of the payload.) -) -$(TR $(TD `min`) $(TD If defined, `Hook.min!T` is used as the minimum value of -the payload.) -) -$(TR $(TD `max`) $(TD If defined, `Hook.max!T` is used as the maximum value of -the payload.) -) -$(TR $(TD `hookOpCast`) $(TD If defined, `hook.hookOpCast!U(get)` is forwarded -to unconditionally when the payload is to be cast to type `U`.) -) -$(TR $(TD `onBadCast`) $(TD If defined and `hookOpCast` is $(I not) defined, -`onBadCast!U(get)` is forwarded to when the payload is to be cast to type `U` -and the cast would lose information or force a change of sign.) -) -$(TR $(TD `hookOpEquals`) $(TD If defined, $(D hook.hookOpEquals(get, rhs)) is -forwarded to unconditionally when the payload is compared for equality against -value `rhs` of integral, floating point, or Boolean type.) -) -$(TR $(TD `hookOpCmp`) $(TD If defined, $(D hook.hookOpCmp(get, rhs)) is -forwarded to unconditionally when the payload is compared for ordering against -value `rhs` of integral, floating point, or Boolean type.) -) -$(TR $(TD `hookOpUnary`) $(TD If defined, `hook.hookOpUnary!op(get)` (where `op` -is the operator symbol) is forwarded to for unary operators `-` and `~`. In -addition, for unary operators `++` and `--`, `hook.hookOpUnary!op(payload)` is -called, where `payload` is a reference to the value wrapped by `Checked` so the -hook can change it.) -) -$(TR $(TD `hookOpBinary`) $(TD If defined, $(D hook.hookOpBinary!op(get, rhs)) -(where `op` is the operator symbol and `rhs` is the right-hand side operand) is -forwarded to unconditionally for binary operators `+`, `-`, `*`, `/`, `%`, -`^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.) -) -$(TR $(TD `hookOpBinaryRight`) $(TD If defined, $(D -hook.hookOpBinaryRight!op(lhs, get)) (where `op` is the operator symbol and -`lhs` is the left-hand side operand) is forwarded to unconditionally for binary -operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.) -) -$(TR $(TD `onOverflow`) $(TD If defined, `hook.onOverflow!op(get)` is forwarded -to for unary operators that overflow but only if `hookOpUnary` is not defined. -Unary `~` does not overflow; unary `-` overflows only when the most negative -value of a signed type is negated, and the result of the hook call is returned. -When the increment or decrement operators overflow, the payload is assigned the -result of `hook.onOverflow!op(get)`. When a binary operator overflows, the -result of $(D hook.onOverflow!op(get, rhs)) is returned, but only if `Hook` does -not define `hookOpBinary`.) -) -$(TR $(TD `hookOpOpAssign`) $(TD If defined, $(D hook.hookOpOpAssign!op(payload, -rhs)) (where `op` is the operator symbol and `rhs` is the right-hand side -operand) is forwarded to unconditionally for binary operators `+=`, `-=`, `*=`, `/=`, `%=`, -`^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=`.) -) -$(TR $(TD `onLowerBound`) $(TD If defined, $(D hook.onLowerBound(value, bound)) -(where `value` is the value being assigned) is forwarded to when the result of -binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, -and `>>>=` is smaller than the smallest value representable by `T`.) -) -$(TR $(TD `onUpperBound`) $(TD If defined, $(D hook.onUpperBound(value, bound)) -(where `value` is the value being assigned) is forwarded to when the result of -binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, -and `>>>=` is larger than the largest value representable by `T`.) -) -$(TR $(TD `hookToHash`) $(TD If defined, $(D hook.hookToHash(payload)) -(where `payload` is a reference to the value wrapped by Checked) is forwarded -to when `toHash` is called on a Checked type. Custom hashing can be implemented -in a `Hook`, otherwise the built-in hashing is used.) -) -) - -Source: $(PHOBOSSRC std/experimental/checkedint.d) -*/ -module std.experimental.checkedint; -import std.traits : isFloatingPoint, isIntegral, isNumeric, isUnsigned, Unqual; - -/// -@safe unittest -{ - int[] concatAndAdd(int[] a, int[] b, int offset) - { - // Aborts on overflow on size computation - auto r = new int[(checked(a.length) + b.length).get]; - // Aborts on overflow on element computation - foreach (i; 0 .. a.length) - r[i] = (a[i] + checked(offset)).get; - foreach (i; 0 .. b.length) - r[i + a.length] = (b[i] + checked(offset)).get; - return r; - } - assert(concatAndAdd([1, 2, 3], [4, 5], -1) == [0, 1, 2, 3, 4]); -} - - -/// `Saturate` stops at an overflow -@safe unittest -{ - auto x = (cast(byte) 127).checked!Saturate; - assert(x == 127); - x++; - assert(x == 127); -} - -/// `WithNaN` has a special "Not a Number" (NaN) value akin to the homonym value reserved for floating-point values -@safe unittest -{ - auto x = 100.checked!WithNaN; - assert(x == 100); - x /= 0; - assert(x.isNaN); -} - -/// `ProperCompare` fixes the comparison operators ==, !=, <, <=, >, and >= to return correct results -@safe unittest -{ - uint x = 1; - auto y = x.checked!ProperCompare; - assert(x < -1); // built-in comparison - assert(y > -1); // ProperCompare -} - -/// `Throw` fails every incorrect operation by throwing an exception -@safe unittest -{ - import std.exception : assertThrown; - auto x = -1.checked!Throw; - assertThrown(x / 0); - assertThrown(x + int.min); - assertThrown(x == uint.max); -} - -/** -Checked integral type wraps an integral `T` and customizes its behavior with the -help of a `Hook` type. The type wrapped must be one of the predefined integrals -(unqualified), or another instance of `Checked`. -*/ -struct Checked(T, Hook = Abort) -if (isIntegral!T || is(T == Checked!(U, H), U, H)) -{ - import std.algorithm.comparison : among; - import std.experimental.allocator.common : stateSize; - import std.format.spec : FormatSpec; - import std.range.primitives : isInputRange, ElementType; - import std.traits : hasMember, isSomeChar; - - /** - The type of the integral subject to checking. - */ - alias Representation = T; - - // state { - static if (hasMember!(Hook, "defaultValue")) - private T payload = Hook.defaultValue!T; - else - private T payload; - /** - `hook` is a member variable if it has state, or an alias for `Hook` - otherwise. - */ - static if (stateSize!Hook > 0) Hook hook; - else alias hook = Hook; - // } state - - // get - /** - Returns a copy of the underlying value. - */ - auto get() inout { return payload; } - /// - @safe unittest - { - auto x = checked(ubyte(42)); - static assert(is(typeof(x.get()) == ubyte)); - assert(x.get == 42); - const y = checked(ubyte(42)); - static assert(is(typeof(y.get()) == const ubyte)); - assert(y.get == 42); - } - - /** - Defines the minimum and maximum. These values are hookable by defining - `Hook.min` and/or `Hook.max`. - */ - static if (hasMember!(Hook, "min")) - { - enum Checked!(T, Hook) min = Checked!(T, Hook)(Hook.min!T); - /// - @safe unittest - { - assert(Checked!short.min == -32768); - assert(Checked!(short, WithNaN).min == -32767); - assert(Checked!(uint, WithNaN).max == uint.max - 1); - } - } - else - enum Checked!(T, Hook) min = Checked(T.min); - /// ditto - static if (hasMember!(Hook, "max")) - enum Checked!(T, Hook) max = Checked(Hook.max!T); - else - enum Checked!(T, Hook) max = Checked(T.max); - - /** - Constructor taking a value properly convertible to the underlying type. `U` - may be either an integral that can be converted to `T` without a loss, or - another `Checked` instance whose representation may be in turn converted to - `T` without a loss. - */ - this(U)(U rhs) - if (valueConvertible!(U, T) || - !isIntegral!T && is(typeof(T(rhs))) || - is(U == Checked!(V, W), V, W) && - is(typeof(Checked!(T, Hook)(rhs.get)))) - { - static if (isIntegral!U) - payload = rhs; - else - payload = rhs.payload; - } - /// - @safe unittest - { - auto a = checked(42L); - assert(a == 42); - auto b = Checked!long(4242); // convert 4242 to long - assert(b == 4242); - } - - /** - Assignment operator. Has the same constraints as the constructor. - */ - ref Checked opAssign(U)(U rhs) return - if (is(typeof(Checked!(T, Hook)(rhs)))) - { - static if (isIntegral!U) - payload = rhs; - else - payload = rhs.payload; - return this; - } - /// - @safe unittest - { - Checked!long a; - a = 42L; - assert(a == 42); - a = 4242; - assert(a == 4242); - } - - /// - @safe unittest - { - Checked!long a, b; - a = b = 3; - assert(a == 3 && b == 3); - } - - /** - Construct from a decimal string. The conversion follows the same rules as - $(REF to, std, conv) converting a string to the wrapped `T` type. - - Params: - str = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) - of characters - */ - this(Range)(Range str) - if (isInputRange!Range && isSomeChar!(ElementType!Range)) - { - import std.conv : to; - - this(to!T(str)); - } - - /** - $(REF to, std, conv) can convert a string to a `Checked!T`: - */ - @system unittest - { - import std.conv : to; - - const a = to!long("1234"); - const b = to!(Checked!long)("1234"); - assert(a == b); - } - - // opCast - /** - Casting operator to integral, `bool`, or floating point type. If `Hook` - defines `hookOpCast`, the call immediately returns - `hook.hookOpCast!U(get)`. Otherwise, casting to `bool` yields $(D - get != 0) and casting to another integral that can represent all - values of `T` returns `get` promoted to `U`. - - If a cast to a floating-point type is requested and `Hook` defines - `onBadCast`, the cast is verified by ensuring $(D get == cast(T) - U(get)). If that is not `true`, `hook.onBadCast!U(get)` is returned. - - If a cast to an integral type is requested and `Hook` defines `onBadCast`, - the cast is verified by ensuring `get` and $(D cast(U) - get) are the same arithmetic number. (Note that `int(-1)` and - `uint(1)` are different values arithmetically although they have the same - bitwise representation and compare equal by language rules.) If the numbers - are not arithmetically equal, `hook.onBadCast!U(get)` is - returned. - - */ - U opCast(U, this _)() - if (isIntegral!U || isFloatingPoint!U || is(U == bool)) - { - static if (hasMember!(Hook, "hookOpCast")) - { - return hook.hookOpCast!U(payload); - } - else static if (is(U == bool)) - { - return payload != 0; - } - else static if (valueConvertible!(T, U)) - { - return payload; - } - // may lose bits or precision - else static if (!hasMember!(Hook, "onBadCast")) - { - return cast(U) payload; - } - else - { - if (isUnsigned!T || !isUnsigned!U || - T.sizeof > U.sizeof || payload >= 0) - { - auto result = cast(U) payload; - // If signedness is different, we need additional checks - if (result == payload && - (!isUnsigned!T || isUnsigned!U || result >= 0)) - return result; - } - return hook.onBadCast!U(payload); - } - } - /// - @safe unittest - { - assert(cast(uint) checked(42) == 42); - assert(cast(uint) checked!WithNaN(-42) == uint.max); - } - - // opEquals - /** - Compares `this` against `rhs` for equality. If `Hook` defines - `hookOpEquals`, the function forwards to $(D - hook.hookOpEquals(get, rhs)). Otherwise, the result of the - built-in operation $(D get == rhs) is returned. - - If `U` is also an instance of `Checked`, both hooks (left- and right-hand - side) are introspected for the method `hookOpEquals`. If both define it, - priority is given to the left-hand side. - - */ - bool opEquals(U, this _)(U rhs) - if (isIntegral!U || isFloatingPoint!U || is(U == bool) || - is(U == Checked!(V, W), V, W) && is(typeof(this == rhs.payload))) - { - static if (is(U == Checked!(V, W), V, W)) - { - alias R = typeof(payload + rhs.payload); - static if (is(Hook == W)) - { - // Use the lhs hook if there - return this == rhs.payload; - } - else static if (valueConvertible!(T, R) && valueConvertible!(V, R)) - { - return payload == rhs.payload; - } - else static if (hasMember!(Hook, "hookOpEquals")) - { - return hook.hookOpEquals(payload, rhs.payload); - } - else static if (hasMember!(W, "hookOpEquals")) - { - return rhs.hook.hookOpEquals(rhs.payload, payload); - } - else - { - return payload == rhs.payload; - } - } - else static if (hasMember!(Hook, "hookOpEquals")) - return hook.hookOpEquals(payload, rhs); - else static if (isIntegral!U || isFloatingPoint!U || is(U == bool)) - return payload == rhs; - } - - /// - static if (is(T == int) && is(Hook == void)) @safe unittest - { - import std.traits : isUnsigned; - - static struct MyHook - { - static bool thereWereErrors; - static bool hookOpEquals(L, R)(L lhs, R rhs) - { - if (lhs != rhs) return false; - static if (isUnsigned!L && !isUnsigned!R) - { - if (lhs > 0 && rhs < 0) thereWereErrors = true; - } - else static if (isUnsigned!R && !isUnsigned!L) - if (lhs < 0 && rhs > 0) thereWereErrors = true; - // Preserve built-in behavior. - return true; - } - } - auto a = checked!MyHook(-42); - assert(a == uint(-42)); - assert(MyHook.thereWereErrors); - MyHook.thereWereErrors = false; - assert(checked!MyHook(uint(-42)) == -42); - assert(MyHook.thereWereErrors); - static struct MyHook2 - { - static bool hookOpEquals(L, R)(L lhs, R rhs) - { - return lhs == rhs; - } - } - MyHook.thereWereErrors = false; - assert(checked!MyHook2(uint(-42)) == a); - // Hook on left hand side takes precedence, so no errors - assert(!MyHook.thereWereErrors); - } - - // toHash - /** - Generates a hash for `this`. If `Hook` defines `hookToHash`, the call - immediately returns `hook.hookToHash(payload)`. If `Hook` does not - implement `hookToHash`, but it has state, a hash will be generated for - the `Hook` using the built-in function and it will be xored with the - hash of the `payload`. - */ - size_t toHash() const nothrow @safe - { - static if (hasMember!(Hook, "hookToHash")) - { - return hook.hookToHash(payload); - } - else static if (stateSize!Hook > 0) - { - static if (hasMember!(typeof(payload), "toHash")) - { - return payload.toHash() ^ hashOf(hook); - } - else - { - return hashOf(payload) ^ hashOf(hook); - } - } - else static if (hasMember!(typeof(payload), "toHash")) - { - return payload.toHash(); - } - else - { - return .hashOf(payload); - } - } - - /// ditto - size_t toHash(this _)() shared const nothrow @safe - { - import core.atomic : atomicLoad, MemoryOrder; - static if (is(typeof(this.payload.atomicLoad!(MemoryOrder.acq)) P)) - { - auto payload = __ctfe ? cast(P) this.payload - : this.payload.atomicLoad!(MemoryOrder.acq); - } - else - { - alias payload = this.payload; - } - - static if (hasMember!(Hook, "hookToHash")) - { - return hook.hookToHash(payload); - } - else static if (stateSize!Hook > 0) - { - static if (hasMember!(typeof(payload), "toHash")) - { - return payload.toHash() ^ hashOf(hook); - } - else - { - return hashOf(payload) ^ hashOf(hook); - } - } - else static if (hasMember!(typeof(payload), "toHash")) - { - return payload.toHash(); - } - else - { - return .hashOf(payload); - } - } - - /** - Writes a string representation of this to a `sink`. - - Params: - sink = A `Char` accepting - $(REF_ALTTEXT output range, isOutputRange, std,range,primitives). - fmt = A $(REF FormatSpec, std, format) which controls how this - is formatted. - */ - void toString(Writer, Char)(scope ref Writer sink, scope const ref FormatSpec!Char fmt) const - { - import std.format.write : formatValue; - if (fmt.spec == 's') - return formatValue(sink, this, fmt); - else - return formatValue(sink, payload, fmt); - } - - /** - `toString` is rarely directly invoked; the usual way of using it is via - $(REF format, std, format): - */ - @system unittest - { - import std.format; - - assert(format("%04d", checked(15)) == "0015"); - assert(format("0x%02x", checked(15)) == "0x0f"); - } - - // opCmp - /** - - Compares `this` against `rhs` for ordering. If `Hook` defines `hookOpCmp`, - the function forwards to $(D hook.hookOpCmp(get, rhs)). Otherwise, the - result of the built-in comparison operation is returned. - - If `U` is also an instance of `Checked`, both hooks (left- and right-hand - side) are introspected for the method `hookOpCmp`. If both define it, - priority is given to the left-hand side. - - */ - auto opCmp(U, this _)(const U rhs) //const pure @safe nothrow @nogc - if (isIntegral!U || isFloatingPoint!U || is(U == bool)) - { - static if (hasMember!(Hook, "hookOpCmp")) - { - return hook.hookOpCmp(payload, rhs); - } - else static if (valueConvertible!(T, U) || valueConvertible!(U, T)) - { - return payload < rhs ? -1 : payload > rhs; - } - else static if (isFloatingPoint!U) - { - U lhs = payload; - return lhs < rhs ? U(-1.0) - : lhs > rhs ? U(1.0) - : lhs == rhs ? U(0.0) : U.init; - } - else - { - return payload < rhs ? -1 : payload > rhs; - } - } - - /// ditto - auto opCmp(U, Hook1, this _)(Checked!(U, Hook1) rhs) - { - alias R = typeof(payload + rhs.payload); - static if (valueConvertible!(T, R) && valueConvertible!(U, R)) - { - return payload < rhs.payload ? -1 : payload > rhs.payload; - } - else static if (is(Hook == Hook1)) - { - // Use the lhs hook - return this.opCmp(rhs.payload); - } - else static if (hasMember!(Hook, "hookOpCmp")) - { - return hook.hookOpCmp(get, rhs.get); - } - else static if (hasMember!(Hook1, "hookOpCmp")) - { - return -rhs.hook.hookOpCmp(rhs.payload, get); - } - else - { - return payload < rhs.payload ? -1 : payload > rhs.payload; - } - } - - /// - static if (is(T == int) && is(Hook == void)) @safe unittest - { - import std.traits : isUnsigned; - - static struct MyHook - { - static bool thereWereErrors; - static int hookOpCmp(L, R)(L lhs, R rhs) - { - static if (isUnsigned!L && !isUnsigned!R) - { - if (rhs < 0 && rhs >= lhs) - thereWereErrors = true; - } - else static if (isUnsigned!R && !isUnsigned!L) - { - if (lhs < 0 && lhs >= rhs) - thereWereErrors = true; - } - // Preserve built-in behavior. - return lhs < rhs ? -1 : lhs > rhs; - } - } - auto a = checked!MyHook(-42); - assert(a > uint(42)); - assert(MyHook.thereWereErrors); - static struct MyHook2 - { - static int hookOpCmp(L, R)(L lhs, R rhs) - { - // Default behavior - return lhs < rhs ? -1 : lhs > rhs; - } - } - MyHook.thereWereErrors = false; - assert(Checked!(uint, MyHook2)(uint(-42)) <= a); - //assert(Checked!(uint, MyHook2)(uint(-42)) >= a); - // Hook on left hand side takes precedence, so no errors - assert(!MyHook.thereWereErrors); - assert(a <= Checked!(uint, MyHook2)(uint(-42))); - assert(MyHook.thereWereErrors); - } - - // For coverage - static if (is(T == int) && is(Hook == void)) @safe unittest - { - assert(checked(42) <= checked!void(42)); - assert(checked!void(42) <= checked(42u)); - assert(checked!void(42) <= checked!(void*)(42u)); - } - - // opUnary - /** - - Defines unary operators `+`, `-`, `~`, `++`, and `--`. Unary `+` is not - overridable and always has built-in behavior (returns `this`). For the - others, if `Hook` defines `hookOpUnary`, `opUnary` forwards to $(D - Checked!(typeof(hook.hookOpUnary!op(get)), - Hook)(hook.hookOpUnary!op(get))). - - If `Hook` does not define `hookOpUnary` but defines `onOverflow`, `opUnary` - forwards to `hook.onOverflow!op(get)` in case an overflow occurs. - For `++` and `--`, the payload is assigned from the result of the call to - `onOverflow`. - - Note that unary `-` is considered to overflow if `T` is a signed integral of - 32 or 64 bits and is equal to the most negative value. This is because that - value has no positive negation. - - */ - auto opUnary(string op, this _)() - if (op == "+" || op == "-" || op == "~") - { - static if (op == "+") - return Checked(this); // "+" is not hookable - else static if (hasMember!(Hook, "hookOpUnary")) - { - auto r = hook.hookOpUnary!op(payload); - return Checked!(typeof(r), Hook)(r); - } - else static if (op == "-" && isIntegral!T && T.sizeof >= 4 && - !isUnsigned!T && hasMember!(Hook, "onOverflow")) - { - static assert(is(typeof(-payload) == typeof(payload))); - bool overflow; - import core.checkedint : negs; - auto r = negs(payload, overflow); - if (overflow) r = hook.onOverflow!op(payload); - return Checked(r); - } - else - return Checked(mixin(op ~ "payload")); - } - - /// ditto - ref Checked opUnary(string op)() return - if (op == "++" || op == "--") - { - static if (hasMember!(Hook, "hookOpUnary")) - hook.hookOpUnary!op(payload); - else static if (hasMember!(Hook, "onOverflow")) - { - static if (op == "++") - { - if (payload == max.payload) - payload = hook.onOverflow!"++"(payload); - else - ++payload; - } - else - { - if (payload == min.payload) - payload = hook.onOverflow!"--"(payload); - else - --payload; - } - } - else - mixin(op ~ "payload;"); - return this; - } - - /// - static if (is(T == int) && is(Hook == void)) @safe unittest - { - static struct MyHook - { - static bool thereWereErrors; - static L hookOpUnary(string x, L)(L lhs) - { - if (x == "-" && lhs == -lhs) thereWereErrors = true; - return -lhs; - } - } - auto a = checked!MyHook(long.min); - assert(a == -a); - assert(MyHook.thereWereErrors); - auto b = checked!void(42); - assert(++b == 43); - } - - // opBinary - /** - - Defines binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, - and `>>>`. If `Hook` defines `hookOpBinary`, `opBinary` forwards to $(D - Checked!(typeof(hook.hookOpBinary!op(get, rhs)), - Hook)(hook.hookOpBinary!op(get, rhs))). - - If `Hook` does not define `hookOpBinary` but defines `onOverflow`, - `opBinary` forwards to `hook.onOverflow!op(get, rhs)` in case an - overflow occurs. - - If two `Checked` instances are involved in a binary operation and both - define `hookOpBinary`, the left-hand side hook has priority. If both define - `onOverflow`, a compile-time error occurs. - - */ - auto opBinary(string op, Rhs)(const Rhs rhs) - if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) - { - return opBinaryImpl!(op, Rhs, typeof(this))(rhs); - } - - /// ditto - auto opBinary(string op, Rhs)(const Rhs rhs) const - if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) - { - return opBinaryImpl!(op, Rhs, typeof(this))(rhs); - } - - private auto opBinaryImpl(string op, Rhs, this _)(const Rhs rhs) - { - alias R = typeof(mixin("payload" ~ op ~ "rhs")); - static assert(is(typeof(mixin("payload" ~ op ~ "rhs")) == R)); - static if (isIntegral!R) alias Result = Checked!(R, Hook); - else alias Result = R; - - static if (hasMember!(Hook, "hookOpBinary")) - { - auto r = hook.hookOpBinary!op(payload, rhs); - return Checked!(typeof(r), Hook)(r); - } - else static if (is(Rhs == bool)) - { - return mixin("this" ~ op ~ "ubyte(rhs)"); - } - else static if (isFloatingPoint!Rhs) - { - return mixin("payload" ~ op ~ "rhs"); - } - else static if (hasMember!(Hook, "onOverflow")) - { - bool overflow; - auto r = opChecked!op(payload, rhs, overflow); - if (overflow) r = hook.onOverflow!op(payload, rhs); - return Result(r); - } - else - { - // Default is built-in behavior - return Result(mixin("payload" ~ op ~ "rhs")); - } - } - - /// ditto - auto opBinary(string op, U, Hook1)(Checked!(U, Hook1) rhs) - { - return opBinaryImpl2!(op, U, Hook1, typeof(this))(rhs); - } - - /// ditto - auto opBinary(string op, U, Hook1)(Checked!(U, Hook1) rhs) const - { - return opBinaryImpl2!(op, U, Hook1, typeof(this))(rhs); - } - - private - auto opBinaryImpl2(string op, U, Hook1, this _)(Checked!(U, Hook1) rhs) - { - alias R = typeof(get + rhs.payload); - static if (valueConvertible!(T, R) && valueConvertible!(U, R) || - is(Hook == Hook1)) - { - // Delegate to lhs - return mixin("this" ~ op ~ "rhs.payload"); - } - else static if (hasMember!(Hook, "hookOpBinary")) - { - return hook.hookOpBinary!op(payload, rhs); - } - else static if (hasMember!(Hook1, "hookOpBinary")) - { - // Delegate to rhs - return mixin("this.payload" ~ op ~ "rhs"); - } - else static if (hasMember!(Hook, "onOverflow") && - !hasMember!(Hook1, "onOverflow")) - { - // Delegate to lhs - return mixin("this" ~ op ~ "rhs.payload"); - } - else static if (hasMember!(Hook1, "onOverflow") && - !hasMember!(Hook, "onOverflow")) - { - // Delegate to rhs - return mixin("this.payload" ~ op ~ "rhs"); - } - else - { - static assert(0, "Conflict between lhs and rhs hooks," ~ - " use .get on one side to disambiguate."); - } - } - - static if (is(T == int) && is(Hook == void)) @safe unittest - { - const a = checked(42); - assert(a + 1 == 43); - assert(a + checked(uint(42)) == 84); - assert(checked(42) + checked!void(42u) == 84); - assert(checked!void(42) + checked(42u) == 84); - - static struct MyHook - { - static uint tally; - static auto hookOpBinary(string x, L, R)(L lhs, R rhs) - { - ++tally; - return mixin("lhs" ~ x ~ "rhs"); - } - } - assert(checked!MyHook(42) + checked(42u) == 84); - assert(checked!void(42) + checked!MyHook(42u) == 84); - assert(MyHook.tally == 2); - } - - // opBinaryRight - /** - - Defines binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, - `>>`, and `>>>` for the case when a built-in numeric or Boolean type is on - the left-hand side, and a `Checked` instance is on the right-hand side. - - */ - auto opBinaryRight(string op, Lhs)(const Lhs lhs) - if (isIntegral!Lhs || isFloatingPoint!Lhs || is(Lhs == bool)) - { - return opBinaryRightImpl!(op, Lhs, typeof(this))(lhs); - } - - /// ditto - auto opBinaryRight(string op, Lhs)(const Lhs lhs) const - if (isIntegral!Lhs || isFloatingPoint!Lhs || is(Lhs == bool)) - { - return opBinaryRightImpl!(op, Lhs, typeof(this))(lhs); - } - - private auto opBinaryRightImpl(string op, Lhs, this _)(const Lhs lhs) - { - static if (hasMember!(Hook, "hookOpBinaryRight")) - { - auto r = hook.hookOpBinaryRight!op(lhs, payload); - return Checked!(typeof(r), Hook)(r); - } - else static if (hasMember!(Hook, "hookOpBinary")) - { - auto r = hook.hookOpBinary!op(lhs, payload); - return Checked!(typeof(r), Hook)(r); - } - else static if (is(Lhs == bool)) - { - return mixin("ubyte(lhs)" ~ op ~ "this"); - } - else static if (isFloatingPoint!Lhs) - { - return mixin("lhs" ~ op ~ "payload"); - } - else static if (hasMember!(Hook, "onOverflow")) - { - bool overflow; - auto r = opChecked!op(lhs, T(payload), overflow); - if (overflow) r = hook.onOverflow!op(lhs, payload); - return Checked!(typeof(r), Hook)(r); - } - else - { - // Default is built-in behavior - auto r = mixin("lhs" ~ op ~ "T(payload)"); - return Checked!(typeof(r), Hook)(r); - } - } - - static if (is(T == int) && is(Hook == void)) @safe unittest - { - assert(1 + checked(1) == 2); - static uint tally; - static struct MyHook - { - static auto hookOpBinaryRight(string x, L, R)(L lhs, R rhs) - { - ++tally; - return mixin("lhs" ~ x ~ "rhs"); - } - } - assert(1 + checked!MyHook(1) == 2); - assert(tally == 1); - - immutable x1 = checked(1); - assert(1 + x1 == 2); - immutable x2 = checked!MyHook(1); - assert(1 + x2 == 2); - assert(tally == 2); - } - - // opOpAssign - /** - - Defines operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, - `<<=`, `>>=`, and `>>>=`. - - If `Hook` defines `hookOpOpAssign`, `opOpAssign` forwards to - `hook.hookOpOpAssign!op(payload, rhs)`, where `payload` is a reference to - the internally held data so the hook can change it. - - Otherwise, the operator first evaluates $(D auto result = - opBinary!op(payload, rhs).payload), which is subject to the hooks in - `opBinary`. Then, if `result` is less than $(D Checked!(T, Hook).min) and if - `Hook` defines `onLowerBound`, the payload is assigned from $(D - hook.onLowerBound(result, min)). If `result` is greater than $(D Checked!(T, - Hook).max) and if `Hook` defines `onUpperBound`, the payload is assigned - from $(D hook.onUpperBound(result, min)). - - If the right-hand side is also a Checked but with a different hook or - underlying type, the hook and underlying type of this Checked takes - precedence. - - In all other cases, the built-in behavior is carried out. - - Params: - op = The operator involved (without the `"="`, e.g. `"+"` for `"+="` etc) - rhs = The right-hand side of the operator (left-hand side is `this`) - - Returns: A reference to `this`. - */ - ref Checked opOpAssign(string op, Rhs)(const Rhs rhs) return - if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) - { - static assert(is(typeof(mixin("payload" ~ op ~ "=rhs")) == T)); - - static if (hasMember!(Hook, "hookOpOpAssign")) - { - hook.hookOpOpAssign!op(payload, rhs); - } - else - { - alias R = typeof(get + rhs); - auto r = opBinary!op(rhs).get; - import std.conv : unsigned; - - static if (ProperCompare.hookOpCmp(R.min, min.get) < 0 && - hasMember!(Hook, "onLowerBound")) - { - if (ProperCompare.hookOpCmp(r, min.get) < 0) - { - // Example: Checked!uint(1) += int(-3) - payload = hook.onLowerBound(r, min.get); - return this; - } - } - static if (ProperCompare.hookOpCmp(max.get, R.max) < 0 && - hasMember!(Hook, "onUpperBound")) - { - if (ProperCompare.hookOpCmp(r, max.get) > 0) - { - // Example: Checked!uint(1) += long(uint.max) - payload = hook.onUpperBound(r, max.get); - return this; - } - } - payload = cast(T) r; - } - return this; - } - - /// ditto - ref Checked opOpAssign(string op, Rhs)(const Rhs rhs) return - if (is(Rhs == Checked!(RhsT, RhsHook), RhsT, RhsHook)) - { - return opOpAssign!(op, typeof(rhs.payload))(rhs.payload); - } - - /// - static if (is(T == int) && is(Hook == void)) @safe unittest - { - static struct MyHook - { - static bool thereWereErrors; - static T onLowerBound(Rhs, T)(Rhs rhs, T bound) - { - thereWereErrors = true; - return bound; - } - static T onUpperBound(Rhs, T)(Rhs rhs, T bound) - { - thereWereErrors = true; - return bound; - } - } - auto x = checked!MyHook(byte.min); - x -= 1; - assert(MyHook.thereWereErrors); - MyHook.thereWereErrors = false; - x = byte.max; - x += 1; - assert(MyHook.thereWereErrors); - } -} - -/** - -Convenience function that turns an integral into the corresponding `Checked` -instance by using template argument deduction. The hook type may be specified -(by default `Abort`). - -*/ -Checked!(T, Hook) checked(Hook = Abort, T)(const T value) -if (is(typeof(Checked!(T, Hook)(value)))) -{ - return Checked!(T, Hook)(value); -} - -/// -@safe unittest -{ - static assert(is(typeof(checked(42)) == Checked!int)); - assert(checked(42) == Checked!int(42)); - static assert(is(typeof(checked!WithNaN(42)) == Checked!(int, WithNaN))); - assert(checked!WithNaN(42) == Checked!(int, WithNaN)(42)); -} - -// get -@safe unittest -{ - void test(T)() - { - assert(Checked!(T, void)(ubyte(22)).get == 22); - } - test!ubyte; - test!(const ubyte); - test!(immutable ubyte); -} - -@system unittest -{ - // https://issues.dlang.org/show_bug.cgi?id=21758 - assert(4 * checked(5L) == 20); - assert(20 / checked(5L) == 4); - assert(2 ^^ checked(3L) == 8); - assert(12 % checked(5L) == 2); - assert((0xff & checked(3L)) == 3); - assert((0xf0 | checked(3L)) == 0xf3); - assert((0xff ^ checked(3L)) == 0xfc); -} - -// Abort -/** - -Force all integral errors to fail by printing an error message to `stderr` and -then abort the program. `Abort` is the default second argument for `Checked`. - -*/ -struct Abort -{ -static: - /** - - Called automatically upon a bad cast (one that loses precision or attempts - to convert a negative value to an unsigned type). The source type is `Src` - and the destination type is `Dst`. - - Params: - src = The source of the cast - - Returns: Nominally the result is the desired value of the cast operation, - which will be forwarded as the result of the cast. For `Abort`, the - function never returns because it aborts the program. - - */ - Dst onBadCast(Dst, Src)(Src src) - { - Warn.onBadCast!Dst(src); - assert(0); - } - - /** - - Called automatically upon a bounds error. - - Params: - rhs = The right-hand side value in the assignment, after the operator has - been evaluated - bound = The value of the bound being violated - - Returns: Nominally the result is the desired value of the operator, which - will be forwarded as result. For `Abort`, the function never returns because - it aborts the program. - - */ - T onLowerBound(Rhs, T)(Rhs rhs, T bound) - { - Warn.onLowerBound(rhs, bound); - assert(0); - } - /// ditto - T onUpperBound(Rhs, T)(Rhs rhs, T bound) - { - Warn.onUpperBound(rhs, bound); - assert(0); - } - - /** - - Called automatically upon a comparison for equality. In case of a erroneous - comparison (one that would make a signed negative value appear equal to an - unsigned positive value), this hook issues `assert(0)` which terminates the - application. - - Params: - lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of - the operator is `Checked!int` - rhs = The right-hand side type involved in the operator - - Returns: Upon a correct comparison, returns the result of the comparison. - Otherwise, the function terminates the application so it never returns. - - */ - static bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - bool error; - auto result = opChecked!"=="(lhs, rhs, error); - if (error) - { - Warn.hookOpEquals(lhs, rhs); - assert(0); - } - return result; - } - - /** - - Called automatically upon a comparison for ordering using one of the - operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e. - it would make a signed negative value appear greater than or equal to an - unsigned positive value), then application is terminated with `assert(0)`. - Otherwise, the three-state result is returned (positive if $(D lhs > rhs), - negative if $(D lhs < rhs), `0` otherwise). - - Params: - lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of - the operator is `Checked!int` - rhs = The right-hand side type involved in the operator - - Returns: For correct comparisons, returns a positive integer if $(D lhs > - rhs), a negative integer if $(D lhs < rhs), `0` if the two are equal. Upon - a mistaken comparison such as $(D int(-1) < uint(0)), the function never - returns because it aborts the program. - - */ - int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - bool error; - auto result = opChecked!"cmp"(lhs, rhs, error); - if (error) - { - Warn.hookOpCmp(lhs, rhs); - assert(0); - } - return result; - } - - /** - - Called automatically upon an overflow during a unary or binary operation. - - Params: - x = The operator, e.g. `-` - lhs = The left-hand side (or sole) argument - rhs = The right-hand side type involved in the operator - - Returns: Nominally the result is the desired value of the operator, which - will be forwarded as result. For `Abort`, the function never returns because - it aborts the program. - - */ - typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs) - { - Warn.onOverflow!x(lhs); - assert(0); - } - /// ditto - typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - Warn.onOverflow!x(lhs, rhs); - assert(0); - } -} - -@safe unittest -{ - void test(T)() - { - Checked!(int, Abort) x; - x = 42; - auto x1 = cast(T) x; - assert(x1 == 42); - //x1 += long(int.max); - } - test!short; - test!(const short); - test!(immutable short); -} - - -// Throw -/** - -Force all integral errors to fail by throwing an exception of type -`Throw.CheckFailure`. The message coming with the error is similar to the one -printed by `Warn`. - -*/ -struct Throw -{ - /** - Exception type thrown upon any failure. - */ - static class CheckFailure : Exception - { - this(T...)(string f, T vals) - { - import std.format : format; - super(format(f, vals)); - } - } - - /** - - Called automatically upon a bad cast (one that loses precision or attempts - to convert a negative value to an unsigned type). The source type is `Src` - and the destination type is `Dst`. - - Params: - src = The source of the cast - - Returns: Nominally the result is the desired value of the cast operation, - which will be forwarded as the result of the cast. For `Throw`, the - function never returns because it throws an exception. - - */ - static Dst onBadCast(Dst, Src)(Src src) - { - throw new CheckFailure("Erroneous cast: cast(%s) %s(%s)", - Dst.stringof, Src.stringof, src); - } - - /** - - Called automatically upon a bounds error. - - Params: - rhs = The right-hand side value in the assignment, after the operator has - been evaluated - bound = The value of the bound being violated - - Returns: Nominally the result is the desired value of the operator, which - will be forwarded as result. For `Throw`, the function never returns because - it throws. - - */ - static T onLowerBound(Rhs, T)(Rhs rhs, T bound) - { - throw new CheckFailure("Lower bound error: %s(%s) < %s(%s)", - Rhs.stringof, rhs, T.stringof, bound); - } - /// ditto - static T onUpperBound(Rhs, T)(Rhs rhs, T bound) - { - throw new CheckFailure("Upper bound error: %s(%s) > %s(%s)", - Rhs.stringof, rhs, T.stringof, bound); - } - - /** - - Called automatically upon a comparison for equality. Throws upon an - erroneous comparison (one that would make a signed negative value appear - equal to an unsigned positive value). - - Params: - lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of - the operator is `Checked!int` - rhs = The right-hand side type involved in the operator - - Returns: The result of the comparison. - - Throws: `CheckFailure` if the comparison is mathematically erroneous. - - */ - static bool hookOpEquals(L, R)(L lhs, R rhs) - { - bool error; - auto result = opChecked!"=="(lhs, rhs, error); - if (error) - { - throw new CheckFailure("Erroneous comparison: %s(%s) == %s(%s)", - L.stringof, lhs, R.stringof, rhs); - } - return result; - } - - /** - - Called automatically upon a comparison for ordering using one of the - operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e. - it would make a signed negative value appear greater than or equal to an - unsigned positive value), throws a `Throw.CheckFailure` exception. - Otherwise, the three-state result is returned (positive if $(D lhs > rhs), - negative if $(D lhs < rhs), `0` otherwise). - - Params: - lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of - the operator is `Checked!int` - rhs = The right-hand side type involved in the operator - - Returns: For correct comparisons, returns a positive integer if $(D lhs > - rhs), a negative integer if $(D lhs < rhs), `0` if the two are equal. - - Throws: Upon a mistaken comparison such as $(D int(-1) < uint(0)), the - function never returns because it throws a `Throw.CheckedFailure` exception. - - */ - static int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - bool error; - auto result = opChecked!"cmp"(lhs, rhs, error); - if (error) - { - throw new CheckFailure("Erroneous ordering comparison: %s(%s) and %s(%s)", - Lhs.stringof, lhs, Rhs.stringof, rhs); - } - return result; - } - - /** - - Called automatically upon an overflow during a unary or binary operation. - - Params: - x = The operator, e.g. `-` - lhs = The left-hand side (or sole) argument - rhs = The right-hand side type involved in the operator - - Returns: Nominally the result is the desired value of the operator, which - will be forwarded as result. For `Throw`, the function never returns because - it throws an exception. - - */ - static typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs) - { - throw new CheckFailure("Overflow on unary operator: %s%s(%s)", - x, Lhs.stringof, lhs); - } - /// ditto - static typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - throw new CheckFailure("Overflow on binary operator: %s(%s) %s %s(%s)", - Lhs.stringof, lhs, x, Rhs.stringof, rhs); - } -} - -/// -@safe unittest -{ - void test(T)() - { - Checked!(int, Throw) x; - x = 42; - auto x1 = cast(T) x; - assert(x1 == 42); - x = T.max + 1; - import std.exception : assertThrown, assertNotThrown; - assertThrown(cast(T) x); - x = x.max; - assertThrown(x += 42); - assertThrown(x += 42L); - x = x.min; - assertThrown(-x); - assertThrown(x -= 42); - assertThrown(x -= 42L); - x = -1; - assertNotThrown(x == -1); - assertThrown(x == uint(-1)); - assertNotThrown(x <= -1); - assertThrown(x <= uint(-1)); - } - test!short; - test!(const short); - test!(immutable short); -} - -// Warn -/** -Hook that prints to `stderr` a trace of all integral errors, without affecting -default behavior. -*/ -struct Warn -{ - import std.stdio : writefln; -static: - /** - - Called automatically upon a bad cast from `src` to type `Dst` (one that - loses precision or attempts to convert a negative value to an unsigned - type). - - Params: - src = The source of the cast - Dst = The target type of the cast - - Returns: `cast(Dst) src` - - */ - Dst onBadCast(Dst, Src)(Src src) - { - trustedStderr.writefln("Erroneous cast: cast(%s) %s(%s)", - Dst.stringof, Src.stringof, src); - return cast(Dst) src; - } - - /** - - Called automatically upon a bad `opOpAssign` call (one that loses precision - or attempts to convert a negative value to an unsigned type). - - Params: - rhs = The right-hand side value in the assignment, after the operator has - been evaluated - bound = The bound being violated - - Returns: `cast(T) rhs` - */ - T onLowerBound(Rhs, T)(Rhs rhs, T bound) - { - trustedStderr.writefln("Lower bound error: %s(%s) < %s(%s)", - Rhs.stringof, rhs, T.stringof, bound); - return cast(T) rhs; - } - /// ditto - T onUpperBound(Rhs, T)(Rhs rhs, T bound) - { - trustedStderr.writefln("Upper bound error: %s(%s) > %s(%s)", - Rhs.stringof, rhs, T.stringof, bound); - return cast(T) rhs; - } - - /** - - Called automatically upon a comparison for equality. In case of an Erroneous - comparison (one that would make a signed negative value appear equal to an - unsigned positive value), writes a warning message to `stderr` as a side - effect. - - Params: - lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of - the operator is `Checked!int` - rhs = The right-hand side type involved in the operator - - Returns: In all cases the function returns the built-in result of $(D lhs == - rhs). - - */ - bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - bool error; - auto result = opChecked!"=="(lhs, rhs, error); - if (error) - { - trustedStderr.writefln("Erroneous comparison: %s(%s) == %s(%s)", - Lhs.stringof, lhs, Rhs.stringof, rhs); - return lhs == rhs; - } - return result; - } - - /// - @safe unittest - { - auto x = checked!Warn(-42); - // Passes - assert(x == -42); - // Passes but prints a warning - // assert(x == uint(-42)); - } - - /** - - Called automatically upon a comparison for ordering using one of the - operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e. - it would make a signed negative value appear greater than or equal to an - unsigned positive value), then a warning message is printed to `stderr`. - - Params: - lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of - the operator is `Checked!int` - rhs = The right-hand side type involved in the operator - - Returns: In all cases, returns $(D lhs < rhs ? -1 : lhs > rhs). The result - is not autocorrected in case of an erroneous comparison. - - */ - int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - bool error; - auto result = opChecked!"cmp"(lhs, rhs, error); - if (error) - { - trustedStderr.writefln("Erroneous ordering comparison: %s(%s) and %s(%s)", - Lhs.stringof, lhs, Rhs.stringof, rhs); - return lhs < rhs ? -1 : lhs > rhs; - } - return result; - } - - /// - @safe unittest - { - auto x = checked!Warn(-42); - // Passes - assert(x <= -42); - // Passes but prints a warning - // assert(x <= uint(-42)); - } - - /** - - Called automatically upon an overflow during a unary or binary operation. - - Params: - x = The operator involved - Lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of - the operator is `Checked!int` - Rhs = The right-hand side type involved in the operator - - Returns: $(D mixin(x ~ "lhs")) for unary, $(D mixin("lhs" ~ x ~ "rhs")) for - binary - - */ - typeof(~Lhs()) onOverflow(string x, Lhs)(ref Lhs lhs) - { - trustedStderr.writefln("Overflow on unary operator: %s%s(%s)", - x, Lhs.stringof, lhs); - return mixin(x ~ "lhs"); - } - /// ditto - typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - trustedStderr.writefln("Overflow on binary operator: %s(%s) %s %s(%s)", - Lhs.stringof, lhs, x, Rhs.stringof, rhs); - static if (x == "/") // Issue 20743: mixin below would cause SIGFPE on POSIX - return typeof(lhs / rhs).min; // or EXCEPTION_INT_OVERFLOW on Windows - else - return mixin("lhs" ~ x ~ "rhs"); - } - - // This is safe because we do not assign to the reference returned by - // `stderr`. The ability for the caller to do that is why `stderr` is not - // safe in the general case. - private @property auto ref trustedStderr() @trusted - { - import std.stdio : stderr; - - return stderr; - } -} - -/// -@safe unittest -{ - auto x = checked!Warn(42); - short x1 = cast(short) x; - //x += long(int.max); - auto y = checked!Warn(cast(const int) 42); - short y1 = cast(const byte) y; -} - -@system unittest -{ - auto a = checked!Warn(int.min); - auto b = checked!Warn(-1); - auto x = checked!Abort(int.min); - auto y = checked!Abort(-1); - - // Temporarily redirect output to stderr to make sure we get the right output. - import std.file : exists, remove; - import std.process : uniqueTempPath; - import std.stdio : stderr; - auto tmpname = uniqueTempPath; - scope(exit) if (exists(tmpname)) remove(tmpname); - auto t = stderr; - stderr.open(tmpname, "w"); - // Open a new scope to minimize code ran with stderr redirected. - { - scope(exit) stderr = t; - assert(a / b == a * b); - import std.exception : assertThrown; - import core.exception : AssertError; - assertThrown!AssertError(x / y); - } - import std.file : readText; - import std.ascii : newline; - auto witness = readText(tmpname); - auto expected = -"Overflow on binary operator: int(-2147483648) / const(int)(-1)" ~ newline ~ -"Overflow on binary operator: int(-2147483648) * const(int)(-1)" ~ newline ~ -"Overflow on binary operator: int(-2147483648) / const(int)(-1)" ~ newline; - assert(witness == expected, "'" ~ witness ~ "'"); -} - -// https://issues.dlang.org/show_bug.cgi?id=22249 -@safe unittest -{ - alias _ = Warn.onLowerBound!(int, int); -} - -// ProperCompare -/** - -Hook that provides arithmetically correct comparisons for equality and ordering. -Comparing an object of type $(D Checked!(X, ProperCompare)) against another -integral (for equality or ordering) ensures that no surprising conversions from -signed to unsigned integral occur before the comparison. Using $(D Checked!(X, -ProperCompare)) on either side of a comparison for equality against a -floating-point number makes sure the integral can be properly converted to the -floating point type, thus making sure equality is transitive. - -*/ -struct ProperCompare -{ - /** - Hook for `==` and `!=` that ensures comparison against integral values has - the behavior expected by the usual arithmetic rules. The built-in semantics - yield surprising behavior when comparing signed values against unsigned - values for equality, for example $(D uint.max == -1) or $(D -1_294_967_296 == - 3_000_000_000u). The call $(D hookOpEquals(x, y)) returns `true` if and only - if `x` and `y` represent the same arithmetic number. - - If one of the numbers is an integral and the other is a floating-point - number, $(D hookOpEquals(x, y)) returns `true` if and only if the integral - can be converted exactly (without approximation) to the floating-point - number. This is in order to preserve transitivity of equality: if $(D - hookOpEquals(x, y)) and $(D hookOpEquals(y, z)) then $(D hookOpEquals(y, - z)), in case `x`, `y`, and `z` are a mix of integral and floating-point - numbers. - - Params: - lhs = The left-hand side of the comparison for equality - rhs = The right-hand side of the comparison for equality - - Returns: - The result of the comparison, `true` if the values are equal - */ - static bool hookOpEquals(L, R)(L lhs, R rhs) - { - alias C = typeof(lhs + rhs); - static if (isFloatingPoint!C) - { - static if (!isFloatingPoint!L) - { - return hookOpEquals(rhs, lhs); - } - else static if (!isFloatingPoint!R) - { - static assert(isFloatingPoint!L && !isFloatingPoint!R); - auto rhs1 = C(rhs); - return lhs == rhs1 && cast(R) rhs1 == rhs; - } - else - return lhs == rhs; - } - else - { - bool error; - auto result = opChecked!"=="(lhs, rhs, error); - if (error) - { - // Only possible error is a wrong "true" - return false; - } - return result; - } - } - - /** - Hook for `<`, `<=`, `>`, and `>=` that ensures comparison against integral - values has the behavior expected by the usual arithmetic rules. The built-in - semantics yield surprising behavior when comparing signed values against - unsigned values, for example $(D 0u < -1). The call $(D hookOpCmp(x, y)) - returns `-1` if and only if `x` is smaller than `y` in abstract arithmetic - sense. - - If one of the numbers is an integral and the other is a floating-point - number, $(D hookOpEquals(x, y)) returns a floating-point number that is `-1` - if `x < y`, `0` if `x == y`, `1` if `x > y`, and `NaN` if the floating-point - number is `NaN`. - - Params: - lhs = The left-hand side of the comparison for ordering - rhs = The right-hand side of the comparison for ordering - - Returns: - The result of the comparison (negative if $(D lhs < rhs), positive if $(D - lhs > rhs), `0` if the values are equal) - */ - static auto hookOpCmp(L, R)(L lhs, R rhs) - { - alias C = typeof(lhs + rhs); - static if (isFloatingPoint!C) - { - return lhs < rhs - ? C(-1) - : lhs > rhs ? C(1) : lhs == rhs ? C(0) : C.init; - } - else - { - static if (!valueConvertible!(L, C) || !valueConvertible!(R, C)) - { - static assert(isUnsigned!C); - static assert(isUnsigned!L != isUnsigned!R); - if (!isUnsigned!L && lhs < 0) - return -1; - if (!isUnsigned!R && rhs < 0) - return 1; - } - return lhs < rhs ? -1 : lhs > rhs; - } - } -} - -/// -@safe unittest -{ - alias opEqualsProper = ProperCompare.hookOpEquals; - assert(opEqualsProper(42, 42)); - assert(opEqualsProper(42.0, 42.0)); - assert(opEqualsProper(42u, 42)); - assert(opEqualsProper(42, 42u)); - assert(-1 == 4294967295u); - assert(!opEqualsProper(-1, 4294967295u)); - assert(!opEqualsProper(const uint(-1), -1)); - assert(!opEqualsProper(uint(-1), -1.0)); - assert(3_000_000_000U == -1_294_967_296); - assert(!opEqualsProper(3_000_000_000U, -1_294_967_296)); -} - -@safe unittest -{ - alias opCmpProper = ProperCompare.hookOpCmp; - assert(opCmpProper(42, 42) == 0); - assert(opCmpProper(42, 42.0) == 0); - assert(opCmpProper(41, 42.0) < 0); - assert(opCmpProper(42, 41.0) > 0); - import std.math.traits : isNaN; - assert(isNaN(opCmpProper(41, double.init))); - assert(opCmpProper(42u, 42) == 0); - assert(opCmpProper(42, 42u) == 0); - assert(opCmpProper(-1, uint(-1)) < 0); - assert(opCmpProper(uint(-1), -1) > 0); - assert(opCmpProper(-1.0, -1) == 0); -} - -@safe unittest -{ - auto x1 = Checked!(uint, ProperCompare)(42u); - assert(x1.get < -1); - assert(x1 > -1); -} - -// WithNaN -/** - -Hook that reserves a special value as a "Not a Number" representative. For -signed integrals, the reserved value is `T.min`. For signed integrals, the -reserved value is `T.max`. - -The default value of a $(D Checked!(X, WithNaN)) is its NaN value, so care must -be taken that all variables are explicitly initialized. Any arithmetic and logic -operation involving at least on NaN becomes NaN itself. All of $(D a == b), $(D -a < b), $(D a > b), $(D a <= b), $(D a >= b) yield `false` if at least one of -`a` and `b` is NaN. - -*/ -struct WithNaN -{ -static: - /** - The default value used for values not explicitly initialized. It is the NaN - value, i.e. `T.min` for signed integrals and `T.max` for unsigned integrals. - */ - enum T defaultValue(T) = T.min == 0 ? T.max : T.min; - /** - The maximum value representable is `T.max` for signed integrals, $(D - T.max - 1) for unsigned integrals. The minimum value representable is $(D - T.min + 1) for signed integrals, `0` for unsigned integrals. - */ - enum T max(T) = cast(T) (T.min == 0 ? T.max - 1 : T.max); - /// ditto - enum T min(T) = cast(T) (T.min == 0 ? T(0) : T.min + 1); - - /** - If `rhs` is `WithNaN.defaultValue!Rhs`, returns - `WithNaN.defaultValue!Lhs`. Otherwise, returns $(D cast(Lhs) rhs). - - Params: - rhs = the value being cast (`Rhs` is the first argument to `Checked`) - Lhs = the target type of the cast - - Returns: The result of the cast operation. - */ - Lhs hookOpCast(Lhs, Rhs)(Rhs rhs) - { - static if (is(Lhs == bool)) - { - return rhs != defaultValue!Rhs && rhs != 0; - } - else static if (valueConvertible!(Rhs, Lhs)) - { - return rhs != defaultValue!Rhs ? Lhs(rhs) : defaultValue!Lhs; - } - else - { - // Not value convertible, only viable option is rhs fits within the - // bounds of Lhs - static if (ProperCompare.hookOpCmp(Rhs.min, Lhs.min) < 0) - { - // Example: hookOpCast!short(int(42)), hookOpCast!uint(int(42)) - if (ProperCompare.hookOpCmp(rhs, Lhs.min) < 0) - return defaultValue!Lhs; - } - static if (ProperCompare.hookOpCmp(Rhs.max, Lhs.max) > 0) - { - // Example: hookOpCast!int(uint(42)) - if (ProperCompare.hookOpCmp(rhs, Lhs.max) > 0) - return defaultValue!Lhs; - } - return cast(Lhs) rhs; - } - } - - /// - @safe unittest - { - auto x = checked!WithNaN(422); - assert((cast(ubyte) x) == 255); - x = checked!WithNaN(-422); - assert((cast(byte) x) == -128); - assert(cast(short) x == -422); - assert(cast(bool) x); - x = x.init; // set back to NaN - assert(x != true); - assert(x != false); - } - - /** - - Returns `false` if $(D lhs == WithNaN.defaultValue!Lhs), $(D lhs == rhs) - otherwise. - - Params: - lhs = The left-hand side of the comparison (`Lhs` is the first argument to - `Checked`) - rhs = The right-hand side of the comparison - - Returns: `lhs != WithNaN.defaultValue!Lhs && lhs == rhs` - */ - bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - return lhs != defaultValue!Lhs && lhs == rhs; - } - - /** - - If $(D lhs == WithNaN.defaultValue!Lhs), returns `double.init`. Otherwise, - has the same semantics as the default comparison. - - Params: - lhs = The left-hand side of the comparison (`Lhs` is the first argument to - `Checked`) - rhs = The right-hand side of the comparison - - Returns: `double.init` if `lhs == WitnNaN.defaultValue!Lhs`, `-1.0` if $(D - lhs < rhs), `0.0` if $(D lhs == rhs), `1.0` if $(D lhs > rhs). - - */ - double hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - if (lhs == defaultValue!Lhs) return double.init; - return lhs < rhs - ? -1.0 - : lhs > rhs ? 1.0 : lhs == rhs ? 0.0 : double.init; - } - - /// - @safe unittest - { - Checked!(int, WithNaN) x; - assert(!(x < 0) && !(x > 0) && !(x == 0)); - x = 1; - assert(x > 0 && !(x < 0) && !(x == 0)); - } - - /** - Defines hooks for unary operators `-`, `~`, `++`, and `--`. - - For `-` and `~`, if $(D v == WithNaN.defaultValue!T), returns - `WithNaN.defaultValue!T`. Otherwise, the semantics is the same as for the - built-in operator. - - For `++` and `--`, if $(D v == WithNaN.defaultValue!Lhs) or the operation - would result in an overflow, sets `v` to `WithNaN.defaultValue!T`. - Otherwise, the semantics is the same as for the built-in operator. - - Params: - x = The operator symbol - v = The left-hand side of the comparison (`T` is the first argument to - `Checked`) - - Returns: $(UL $(LI For $(D x == "-" || x == "~"): If $(D v == - WithNaN.defaultValue!T), the function returns `WithNaN.defaultValue!T`. - Otherwise it returns the normal result of the operator.) $(LI For $(D x == - "++" || x == "--"): The function returns `void`.)) - - */ - auto hookOpUnary(string x, T)(ref T v) - { - static if (x == "-" || x == "~") - { - return v != defaultValue!T ? mixin(x ~ "v") : v; - } - else static if (x == "++") - { - static if (defaultValue!T == T.min) - { - if (v != defaultValue!T) - { - if (v == T.max) v = defaultValue!T; - else ++v; - } - } - else - { - static assert(defaultValue!T == T.max); - if (v != defaultValue!T) ++v; - } - } - else static if (x == "--") - { - if (v != defaultValue!T) --v; - } - } - - /// - @safe unittest - { - Checked!(int, WithNaN) x; - ++x; - assert(x.isNaN); - x = 1; - assert(!x.isNaN); - x = -x; - ++x; - assert(!x.isNaN); - } - - @safe unittest // for coverage - { - Checked!(uint, WithNaN) y; - ++y; - assert(y.isNaN); - } - - /** - Defines hooks for binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, - `^`, `<<`, `>>`, and `>>>` for cases where a `Checked` object is the - left-hand side operand. If $(D lhs == WithNaN.defaultValue!Lhs), returns - $(D WithNaN.defaultValue!(typeof(lhs + rhs))) without evaluating the - operand. Otherwise, evaluates the operand. If evaluation does not overflow, - returns the result. Otherwise, returns $(D WithNaN.defaultValue!(typeof(lhs + - rhs))). - - Params: - x = The operator symbol - lhs = The left-hand side operand (`Lhs` is the first argument to `Checked`) - rhs = The right-hand side operand - - Returns: If $(D lhs != WithNaN.defaultValue!Lhs) and the operator does not - overflow, the function returns the same result as the built-in operator. In - all other cases, returns $(D WithNaN.defaultValue!(typeof(lhs + rhs))). - */ - auto hookOpBinary(string x, L, R)(L lhs, R rhs) - { - alias Result = typeof(lhs + rhs); - if (lhs != defaultValue!L) - { - bool error; - auto result = opChecked!x(lhs, rhs, error); - if (!error) return result; - } - return defaultValue!Result; - } - - /// - @safe unittest - { - Checked!(int, WithNaN) x; - assert((x + 1).isNaN); - x = 100; - assert(!(x + 1).isNaN); - } - - /** - Defines hooks for binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, - `^`, `<<`, `>>`, and `>>>` for cases where a `Checked` object is the - right-hand side operand. If $(D rhs == WithNaN.defaultValue!Rhs), returns - $(D WithNaN.defaultValue!(typeof(lhs + rhs))) without evaluating the - operand. Otherwise, evaluates the operand. If evaluation does not overflow, - returns the result. Otherwise, returns $(D WithNaN.defaultValue!(typeof(lhs + - rhs))). - - Params: - x = The operator symbol - lhs = The left-hand side operand - rhs = The right-hand side operand (`Rhs` is the first argument to `Checked`) - - Returns: If $(D rhs != WithNaN.defaultValue!Rhs) and the operator does not - overflow, the function returns the same result as the built-in operator. In - all other cases, returns $(D WithNaN.defaultValue!(typeof(lhs + rhs))). - */ - auto hookOpBinaryRight(string x, L, R)(L lhs, R rhs) - { - alias Result = typeof(lhs + rhs); - if (rhs != defaultValue!R) - { - bool error; - auto result = opChecked!x(lhs, rhs, error); - if (!error) return result; - } - return defaultValue!Result; - } - /// - @safe unittest - { - Checked!(int, WithNaN) x; - assert((1 + x).isNaN); - x = 100; - assert(!(1 + x).isNaN); - } - - /** - - Defines hooks for binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, - `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=` for cases where a `Checked` - object is the left-hand side operand. If $(D lhs == - WithNaN.defaultValue!Lhs), no action is carried. Otherwise, evaluates the - operand. If evaluation does not overflow and fits in `Lhs` without loss of - information or change of sign, sets `lhs` to the result. Otherwise, sets - `lhs` to `WithNaN.defaultValue!Lhs`. - - Params: - x = The operator symbol (without the `=`) - lhs = The left-hand side operand (`Lhs` is the first argument to `Checked`) - rhs = The right-hand side operand - - Returns: `void` - */ - void hookOpOpAssign(string x, L, R)(ref L lhs, R rhs) - { - if (lhs == defaultValue!L) - return; - bool error; - auto temp = opChecked!x(lhs, rhs, error); - lhs = error - ? defaultValue!L - : hookOpCast!L(temp); - } - - /// - @safe unittest - { - Checked!(int, WithNaN) x; - x += 4; - assert(x.isNaN); - x = 0; - x += 4; - assert(!x.isNaN); - x += int.max; - assert(x.isNaN); - } -} - -/// -@safe unittest -{ - auto x1 = Checked!(int, WithNaN)(); - assert(x1.isNaN); - assert(x1.get == int.min); - assert(x1 != x1); - assert(!(x1 < x1)); - assert(!(x1 > x1)); - assert(!(x1 == x1)); - ++x1; - assert(x1.isNaN); - assert(x1.get == int.min); - --x1; - assert(x1.isNaN); - assert(x1.get == int.min); - x1 = 42; - assert(!x1.isNaN); - assert(x1 == x1); - assert(x1 <= x1); - assert(x1 >= x1); - static assert(x1.min == int.min + 1); - x1 += long(int.max); -} - -/** -Queries whether a $(D Checked!(T, WithNaN)) object is not a number (NaN). - -Params: x = the `Checked` instance queried - -Returns: `true` if `x` is a NaN, `false` otherwise -*/ -bool isNaN(T)(const Checked!(T, WithNaN) x) -{ - return x.get == x.init.get; -} - -/// -@safe unittest -{ - auto x1 = Checked!(int, WithNaN)(); - assert(x1.isNaN); - x1 = 1; - assert(!x1.isNaN); - x1 = x1.init; - assert(x1.isNaN); -} - -@safe unittest -{ - void test1(T)() - { - auto x1 = Checked!(T, WithNaN)(); - assert(x1.isNaN); - assert(x1.get == int.min); - assert(x1 != x1); - assert(!(x1 < x1)); - assert(!(x1 > x1)); - assert(!(x1 == x1)); - assert(x1.get == int.min); - auto x2 = Checked!(T, WithNaN)(42); - assert(!x2.isNaN); - assert(x2 == x2); - assert(x2 <= x2); - assert(x2 >= x2); - static assert(x2.min == T.min + 1); - } - test1!int; - test1!(const int); - test1!(immutable int); - - void test2(T)() - { - auto x1 = Checked!(T, WithNaN)(); - assert(x1.get == T.min); - assert(x1 != x1); - assert(!(x1 < x1)); - assert(!(x1 > x1)); - assert(!(x1 == x1)); - ++x1; - assert(x1.get == T.min); - --x1; - assert(x1.get == T.min); - x1 = 42; - assert(x1 == x1); - assert(x1 <= x1); - assert(x1 >= x1); - static assert(x1.min == T.min + 1); - x1 += long(T.max); - } - test2!int; -} - -@safe unittest -{ - alias Smart(T) = Checked!(Checked!(T, ProperCompare), WithNaN); - Smart!int x1; - assert(x1 != x1); - x1 = -1; - assert(x1 < 1u); - auto x2 = Smart!(const int)(42); -} - -// Saturate -/** - -Hook that implements $(I saturation), i.e. any arithmetic operation that would -overflow leaves the result at its extreme value (`min` or `max` depending on the -direction of the overflow). - -Saturation is not sticky; if a value reaches its saturation value, another -operation may take it back to normal range. - -*/ -struct Saturate -{ -static: - /** - - Implements saturation for operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, - and `>>>=`. This hook is called if the result of the binary operation does - not fit in `Lhs` without loss of information or a change in sign. - - Params: - Rhs = The right-hand side type in the assignment, after the operation has - been computed - bound = The bound being violated - - Returns: `Lhs.max` if $(D rhs >= 0), `Lhs.min` otherwise. - - */ - T onLowerBound(Rhs, T)(Rhs rhs, T bound) - { - return bound; - } - /// ditto - T onUpperBound(Rhs, T)(Rhs rhs, T bound) - { - return bound; - } - /// - @safe unittest - { - auto x = checked!Saturate(short(100)); - x += 33000; - assert(x == short.max); - x -= 70000; - assert(x == short.min); - } - - /** - - Implements saturation for operators `+`, `-` (unary and binary), `*`, `/`, - `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`. - - For unary `-`, `onOverflow` is called if $(D lhs == Lhs.min) and `Lhs` is a - signed type. The function returns `Lhs.max`. - - For binary operators, the result is as follows: $(UL $(LI `Lhs.max` if the - result overflows in the positive direction, on division by `0`, or on - shifting right by a negative value) $(LI `Lhs.min` if the result overflows - in the negative direction) $(LI `0` if `lhs` is being shifted left by a - negative value, or shifted right by a large positive value)) - - Params: - x = The operator involved in the `opAssign` operation - Lhs = The left-hand side of the operator (`Lhs` is the first argument to - `Checked`) - Rhs = The right-hand side type in the operator - - Returns: The saturated result of the operator. - - */ - auto onOverflow(string x, Lhs)(Lhs lhs) - { - static assert(x == "-" || x == "++" || x == "--"); - return x == "--" ? Lhs.min : Lhs.max; - } - /// ditto - typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - static if (x == "+") - return rhs >= 0 ? Lhs.max : Lhs.min; - else static if (x == "*") - return (lhs >= 0) == (rhs >= 0) ? Lhs.max : Lhs.min; - else static if (x == "^^") - return lhs > 0 || !(rhs & 1) ? Lhs.max : Lhs.min; - else static if (x == "-") - return rhs >= 0 ? Lhs.min : Lhs.max; - else static if (x == "/" || x == "%") - return Lhs.max; - else static if (x == "<<") - return rhs >= 0 ? Lhs.max : 0; - else static if (x == ">>" || x == ">>>") - return rhs >= 0 ? 0 : Lhs.max; - else - static assert(false); - } - /// - @safe unittest - { - assert(checked!Saturate(int.max) + 1 == int.max); - assert(checked!Saturate(100) ^^ 10 == int.max); - assert(checked!Saturate(-100) ^^ 10 == int.max); - assert(checked!Saturate(100) / 0 == int.max); - assert(checked!Saturate(100) << -1 == 0); - assert(checked!Saturate(100) << 33 == int.max); - assert(checked!Saturate(100) >> -1 == int.max); - assert(checked!Saturate(100) >> 33 == 0); - } -} - -/// -@safe unittest -{ - auto x = checked!Saturate(int.max); - ++x; - assert(x == int.max); - --x; - assert(x == int.max - 1); - x = int.min; - assert(-x == int.max); - x -= 42; - assert(x == int.min); - assert(x * -2 == int.max); -} - -/* -Yields `true` if `T1` is "value convertible" (by C's "value preserving" rule, -see $(HTTP c-faq.com/expr/preservingrules.html)) to `T2`, where the two are -integral types. That is, all of values in `T1` are also in `T2`. For example -`int` is value convertible to `long` but not to `uint` or `ulong`. -*/ -private enum valueConvertible(T1, T2) = isIntegral!T1 && isIntegral!T2 && - is(T1 : T2) && ( - isUnsigned!T1 == isUnsigned!T2 || // same signedness - !isUnsigned!T2 && T2.sizeof > T1.sizeof // safely convertible - ); /** - -Defines binary operations with overflow checking for any two integral types. -The result type obeys the language rules (even when they may be -counterintuitive), and `overflow` is set if an overflow occurs (including -inadvertent change of signedness, e.g. `-1` is converted to `uint`). -Conceptually the behavior is: - -$(OL $(LI Perform the operation in infinite precision) -$(LI If the infinite-precision result fits in the result type, return it and -do not touch `overflow`) -$(LI Otherwise, set `overflow` to `true` and return an unspecified value) -) - -The implementation exploits properties of types and operations to minimize -additional work. - -Params: -x = The binary operator involved, e.g. `/` -lhs = The left-hand side of the operator -rhs = The right-hand side of the operator -overflow = The overflow indicator (assigned `true` in case there's an error) - -Returns: -The result of the operation, which is the same as the built-in operator -*/ -typeof(mixin(x == "cmp" ? "0" : ("L() " ~ x ~ " R()"))) -opChecked(string x, L, R)(const L lhs, const R rhs, ref bool overflow) -if (isIntegral!L && isIntegral!R) -{ - static if (x == "cmp") - alias Result = int; - else - alias Result = typeof(mixin("L() " ~ x ~ " R()")); - - import core.checkedint : addu, adds, subs, muls, subu, mulu; - import std.algorithm.comparison : among; - static if (x == "==") - { - alias C = typeof(lhs + rhs); - static if (valueConvertible!(L, C) && valueConvertible!(R, C)) - { - // Values are converted to R before comparison, cool. - return lhs == rhs; - } - else - { - static assert(isUnsigned!C); - static assert(isUnsigned!L != isUnsigned!R); - if (lhs != rhs) return false; - // R(lhs) and R(rhs) have the same bit pattern, yet may be - // different due to signedness change. - static if (!isUnsigned!R) - { - if (rhs >= 0) - return true; - } - else - { - if (lhs >= 0) - return true; - } - overflow = true; - return true; - } - } - else static if (x == "cmp") - { - alias C = typeof(lhs + rhs); - static if (!valueConvertible!(L, C) || !valueConvertible!(R, C)) - { - static assert(isUnsigned!C); - static assert(isUnsigned!L != isUnsigned!R); - if (!isUnsigned!L && lhs < 0) - { - overflow = true; - return -1; - } - if (!isUnsigned!R && rhs < 0) - { - overflow = true; - return 1; - } - } - return lhs < rhs ? -1 : lhs > rhs; - } - else static if (x.among("<<", ">>", ">>>")) - { - // Handle shift separately from all others. The test below covers - // negative rhs as well. - import std.conv : unsigned; - if (unsigned(rhs) > 8 * Result.sizeof) goto fail; - return mixin("lhs" ~ x ~ "rhs"); - } - else static if (x.among("&", "|", "^")) - { - // Nothing to check - return mixin("lhs" ~ x ~ "rhs"); - } - else static if (x == "^^") - { - // Exponentiation is weird, handle separately - return pow(lhs, rhs, overflow); - } - else static if (valueConvertible!(L, Result) && - valueConvertible!(R, Result)) - { - static if (L.sizeof < Result.sizeof && R.sizeof < Result.sizeof && - x.among("+", "-", "*")) - { - // No checks - both are value converted and result is in range - return mixin("lhs" ~ x ~ "rhs"); - } - else static if (x == "+") - { - static if (isUnsigned!Result) alias impl = addu; - else alias impl = adds; - return impl(Result(lhs), Result(rhs), overflow); - } - else static if (x == "-") - { - static if (isUnsigned!Result) alias impl = subu; - else alias impl = subs; - return impl(Result(lhs), Result(rhs), overflow); - } - else static if (x == "*") - { - static if (!isUnsigned!L && !isUnsigned!R && - is(L == Result)) - { - if (lhs == Result.min && rhs == -1) goto fail; - } - static if (isUnsigned!Result) alias impl = mulu; - else alias impl = muls; - return impl(Result(lhs), Result(rhs), overflow); - } - else static if (x == "/" || x == "%") - { - static if (!isUnsigned!L && !isUnsigned!R && - is(L == Result) && x == "/") - { - if (lhs == Result.min && rhs == -1) goto fail; - } - if (rhs == 0) goto fail; - return mixin("lhs" ~ x ~ "rhs"); - } - else static assert(0, x); - } - else // Mixed signs - { - static assert(isUnsigned!Result); - static assert(isUnsigned!L != isUnsigned!R); - static if (x == "+") - { - static if (!isUnsigned!L) - { - if (lhs < 0) - return subu(Result(rhs), Result(-lhs), overflow); - } - else static if (!isUnsigned!R) - { - if (rhs < 0) - return subu(Result(lhs), Result(-rhs), overflow); - } - return addu(Result(lhs), Result(rhs), overflow); - } - else static if (x == "-") - { - static if (!isUnsigned!L) - { - if (lhs < 0) goto fail; - } - else static if (!isUnsigned!R) - { - if (rhs < 0) - return addu(Result(lhs), Result(-rhs), overflow); - } - return subu(Result(lhs), Result(rhs), overflow); - } - else static if (x == "*") - { - static if (!isUnsigned!L) - { - if (lhs < 0) goto fail; - } - else static if (!isUnsigned!R) - { - if (rhs < 0) goto fail; - } - return mulu(Result(lhs), Result(rhs), overflow); - } - else static if (x == "/" || x == "%") - { - static if (!isUnsigned!L) - { - if (lhs < 0 || rhs == 0) goto fail; - } - else static if (!isUnsigned!R) - { - if (rhs <= 0) goto fail; - } - return mixin("Result(lhs)" ~ x ~ "Result(rhs)"); - } - else static assert(0, x); - } - debug assert(false); -fail: - overflow = true; - return Result(0); -} - -/// -@safe unittest -{ - bool overflow; - assert(opChecked!"+"(const short(1), short(1), overflow) == 2 && !overflow); - assert(opChecked!"+"(1, 1, overflow) == 2 && !overflow); - assert(opChecked!"+"(1, 1u, overflow) == 2 && !overflow); - assert(opChecked!"+"(-1, 1u, overflow) == 0 && !overflow); - assert(opChecked!"+"(1u, -1, overflow) == 0 && !overflow); -} - -/// -@safe unittest -{ - bool overflow; - assert(opChecked!"-"(1, 1, overflow) == 0 && !overflow); - assert(opChecked!"-"(1, 1u, overflow) == 0 && !overflow); - assert(opChecked!"-"(1u, -1, overflow) == 2 && !overflow); - assert(opChecked!"-"(-1, 1u, overflow) == 0 && overflow); -} - -@safe unittest -{ - bool overflow; - assert(opChecked!"*"(2, 3, overflow) == 6 && !overflow); - assert(opChecked!"*"(2, 3u, overflow) == 6 && !overflow); - assert(opChecked!"*"(1u, -1, overflow) == 0 && overflow); - //assert(mul(-1, 1u, overflow) == uint.max - 1 && overflow); -} - -@safe unittest -{ - bool overflow; - assert(opChecked!"/"(6, 3, overflow) == 2 && !overflow); - assert(opChecked!"/"(6, 3, overflow) == 2 && !overflow); - assert(opChecked!"/"(6u, 3, overflow) == 2 && !overflow); - assert(opChecked!"/"(6, 3u, overflow) == 2 && !overflow); - assert(opChecked!"/"(11, 0, overflow) == 0 && overflow); - overflow = false; - assert(opChecked!"/"(6u, 0, overflow) == 0 && overflow); - overflow = false; - assert(opChecked!"/"(-6, 2u, overflow) == 0 && overflow); - overflow = false; - assert(opChecked!"/"(-6, 0u, overflow) == 0 && overflow); - overflow = false; - assert(opChecked!"cmp"(0u, -6, overflow) == 1 && overflow); - overflow = false; - assert(opChecked!"|"(1, 2, overflow) == 3 && !overflow); -} - -/* -Exponentiation function used by the implementation of operator `^^`. -*/ -private pure @safe nothrow @nogc -auto pow(L, R)(const L lhs, const R rhs, ref bool overflow) -if (isIntegral!L && isIntegral!R) -{ - if (rhs <= 1) - { - if (rhs == 0) return 1; - static if (!isUnsigned!R) - return rhs == 1 - ? lhs - : (rhs == -1 && (lhs == 1 || lhs == -1)) ? lhs : 0; - else - return lhs; - } - - typeof(lhs ^^ rhs) b = void; - static if (!isUnsigned!L && isUnsigned!(typeof(b))) - { - // Need to worry about mixed-sign stuff - if (lhs < 0) - { - if (rhs & 1) - { - if (lhs < 0) overflow = true; - return 0; - } - b = -lhs; - } - else - { - b = lhs; - } - } - else - { - b = lhs; - } - if (b == 1) return 1; - if (b == -1) return (rhs & 1) ? -1 : 1; - if (rhs > 63) - { - overflow = true; - return 0; - } - - assert((b > 1 || b < -1) && rhs > 1); - return powImpl(b, cast(uint) rhs, overflow); -} - -// Inspiration: http://www.stepanovpapers.com/PAM.pdf -pure @safe nothrow @nogc -private T powImpl(T)(T b, uint e, ref bool overflow) -if (isIntegral!T && T.sizeof >= 4) -{ - assert(e > 1); - - import core.checkedint : muls, mulu; - static if (isUnsigned!T) alias mul = mulu; - else alias mul = muls; - - T r = b; - --e; - // Loop invariant: r * (b ^^ e) is the actual result - for (;; e /= 2) - { - if (e % 2) - { - r = mul(r, b, overflow); - if (e == 1) break; - } - b = mul(b, b, overflow); - } - return r; -} - -@safe unittest -{ - static void testPow(T)(T x, uint e) - { - bool overflow; - assert(opChecked!"^^"(T(0), 0, overflow) == 1); - assert(opChecked!"^^"(-2, T(0), overflow) == 1); - assert(opChecked!"^^"(-2, T(1), overflow) == -2); - assert(opChecked!"^^"(-1, -1, overflow) == -1); - assert(opChecked!"^^"(-2, 1, overflow) == -2); - assert(opChecked!"^^"(-2, -1, overflow) == 0); - assert(opChecked!"^^"(-2, 4u, overflow) == 16); - assert(!overflow); - assert(opChecked!"^^"(-2, 3u, overflow) == 0); - assert(overflow); - overflow = false; - assert(opChecked!"^^"(3, 64u, overflow) == 0); - assert(overflow); - overflow = false; - foreach (uint i; 0 .. e) - { - assert(opChecked!"^^"(x, i, overflow) == x ^^ i); - assert(!overflow); - } - assert(opChecked!"^^"(x, e, overflow) == x ^^ e); - assert(overflow); - } - - testPow!int(3, 21); - testPow!uint(3, 21); - testPow!long(3, 40); - testPow!ulong(3, 41); -} - -version (StdUnittest) private struct CountOverflows -{ - uint calls; - auto onOverflow(string op, Lhs)(Lhs lhs) - { - ++calls; - return mixin(op ~ "lhs"); - } - auto onOverflow(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - ++calls; - return mixin("lhs" ~ op ~ "rhs"); - } - T onLowerBound(Rhs, T)(Rhs rhs, T bound) - { - ++calls; - return cast(T) rhs; - } - T onUpperBound(Rhs, T)(Rhs rhs, T bound) - { - ++calls; - return cast(T) rhs; - } -} - -// opBinary -@nogc nothrow pure @safe unittest -{ - static struct CountOpBinary - { - uint calls; - auto hookOpBinary(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - ++calls; - return mixin("lhs" ~ op ~ "rhs"); - } - } - auto x = Checked!(const int, void)(42), y = Checked!(immutable int, void)(142); - assert(x + y == 184); - assert(x + 100 == 142); - assert(y - x == 100); - assert(200 - x == 158); - assert(y * x == 142 * 42); - assert(x / 1 == 42); - assert(x % 20 == 2); - - auto x1 = Checked!(int, CountOverflows)(42); - assert(x1 + 0 == 42); - assert(x1 + false == 42); - assert(is(typeof(x1 + 0.5) == double)); - assert(x1 + 0.5 == 42.5); - assert(x1.hook.calls == 0); - assert(x1 + int.max == int.max + 42); - assert(x1.hook.calls == 1); - assert(x1 * 2 == 84); - assert(x1.hook.calls == 1); - assert(x1 / 2 == 21); - assert(x1.hook.calls == 1); - assert(x1 % 20 == 2); - assert(x1.hook.calls == 1); - assert(x1 << 2 == 42 << 2); - assert(x1.hook.calls == 1); - assert(x1 << 42 == x1.get << x1.get); - assert(x1.hook.calls == 2); - x1 = int.min; - assert(x1 - 1 == int.max); - assert(x1.hook.calls == 3); - - auto x2 = Checked!(int, CountOpBinary)(42); - assert(x2 + 1 == 43); - assert(x2.hook.calls == 1); - - auto x3 = Checked!(uint, CountOverflows)(42u); - assert(x3 + 1 == 43); - assert(x3.hook.calls == 0); - assert(x3 - 1 == 41); - assert(x3.hook.calls == 0); - assert(x3 + (-42) == 0); - assert(x3.hook.calls == 0); - assert(x3 - (-42) == 84); - assert(x3.hook.calls == 0); - assert(x3 * 2 == 84); - assert(x3.hook.calls == 0); - assert(x3 * -2 == -84); - assert(x3.hook.calls == 1); - assert(x3 / 2 == 21); - assert(x3.hook.calls == 1); - assert(x3 / -2 == 0); - assert(x3.hook.calls == 2); - assert(x3 ^^ 2 == 42 * 42); - assert(x3.hook.calls == 2); - - auto x4 = Checked!(int, CountOverflows)(42); - assert(x4 + 1 == 43); - assert(x4.hook.calls == 0); - assert(x4 + 1u == 43); - assert(x4.hook.calls == 0); - assert(x4 - 1 == 41); - assert(x4.hook.calls == 0); - assert(x4 * 2 == 84); - assert(x4.hook.calls == 0); - x4 = -2; - assert(x4 + 2u == 0); - assert(x4.hook.calls == 0); - assert(x4 * 2u == -4); - assert(x4.hook.calls == 1); - - auto x5 = Checked!(int, CountOverflows)(3); - assert(x5 ^^ 0 == 1); - assert(x5 ^^ 1 == 3); - assert(x5 ^^ 2 == 9); - assert(x5 ^^ 3 == 27); - assert(x5 ^^ 4 == 81); - assert(x5 ^^ 5 == 81 * 3); - assert(x5 ^^ 6 == 81 * 9); -} - -// opBinaryRight -@nogc nothrow pure @safe unittest -{ - auto x1 = Checked!(int, CountOverflows)(42); - assert(1 + x1 == 43); - assert(true + x1 == 43); - assert(0.5 + x1 == 42.5); - auto x2 = Checked!(int, void)(42); - assert(x1 + x2 == 84); - assert(x2 + x1 == 84); -} - -// opOpAssign -@safe unittest -{ - auto x1 = Checked!(int, CountOverflows)(3); - assert((x1 += 2) == 5); - x1 *= 2_000_000_000L; - assert(x1.hook.calls == 1); - x1 *= -2_000_000_000L; - assert(x1.hook.calls == 2); - - auto x2 = Checked!(ushort, CountOverflows)(ushort(3)); - assert((x2 += 2) == 5); - assert(x2.hook.calls == 0); - assert((x2 += ushort.max) == cast(ushort) (ushort(5) + ushort.max)); - assert(x2.hook.calls == 1); - - auto x3 = Checked!(uint, CountOverflows)(3u); - x3 *= ulong(2_000_000_000); - assert(x3.hook.calls == 1); -} - -// opAssign -@safe unittest -{ - Checked!(int, void) x; - x = 42; - assert(x.get == 42); - x = x; - assert(x.get == 42); - x = short(43); - assert(x.get == 43); - x = ushort(44); - assert(x.get == 44); -} - -@safe unittest -{ - static assert(!is(typeof(Checked!(short, void)(ushort(42))))); - static assert(!is(typeof(Checked!(int, void)(long(42))))); - static assert(!is(typeof(Checked!(int, void)(ulong(42))))); - assert(Checked!(short, void)(short(42)).get == 42); - assert(Checked!(int, void)(ushort(42)).get == 42); -} - -// opCast -@nogc nothrow pure @safe unittest -{ - static assert(is(typeof(cast(float) Checked!(int, void)(42)) == float)); - assert(cast(float) Checked!(int, void)(42) == 42); - - assert(is(typeof(cast(long) Checked!(int, void)(42)) == long)); - assert(cast(long) Checked!(int, void)(42) == 42); - static assert(is(typeof(cast(long) Checked!(uint, void)(42u)) == long)); - assert(cast(long) Checked!(uint, void)(42u) == 42); - - auto x = Checked!(int, void)(42); - if (x) {} else assert(0); - x = 0; - if (x) assert(0); - - static struct Hook1 - { - uint calls; - Dst hookOpCast(Dst, Src)(Src value) - { - ++calls; - return 42; - } - } - auto y = Checked!(long, Hook1)(long.max); - assert(cast(int) y == 42); - assert(cast(uint) y == 42); - assert(y.hook.calls == 2); - - static struct Hook2 - { - uint calls; - Dst onBadCast(Dst, Src)(Src value) - { - ++calls; - return 42; - } - } - auto x1 = Checked!(uint, Hook2)(100u); - assert(cast(ushort) x1 == 100); - assert(cast(short) x1 == 100); - assert(cast(float) x1 == 100); - assert(cast(double) x1 == 100); - assert(cast(real) x1 == 100); - assert(x1.hook.calls == 0); - assert(cast(int) x1 == 100); - assert(x1.hook.calls == 0); - x1 = uint.max; - assert(cast(int) x1 == 42); - assert(x1.hook.calls == 1); - - auto x2 = Checked!(int, Hook2)(-100); - assert(cast(short) x2 == -100); - assert(cast(ushort) x2 == 42); - assert(cast(uint) x2 == 42); - assert(cast(ulong) x2 == 42); - assert(x2.hook.calls == 3); -} - -// opEquals -@nogc nothrow pure @safe unittest -{ - assert(Checked!(int, void)(42) == 42L); - assert(42UL == Checked!(int, void)(42)); - - static struct Hook1 - { - uint calls; - bool hookOpEquals(Lhs, Rhs)(const Lhs lhs, const Rhs rhs) - { - ++calls; - return lhs != rhs; - } - } - auto x1 = Checked!(int, Hook1)(100); - assert(x1 != Checked!(long, Hook1)(100)); - assert(x1.hook.calls == 1); - assert(x1 != 100u); - assert(x1.hook.calls == 2); - - static struct Hook2 - { - uint calls; - bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - ++calls; - return false; - } - } - auto x2 = Checked!(int, Hook2)(-100); - assert(x2 != x1); - // For coverage: lhs has no hookOpEquals, rhs does - assert(Checked!(uint, void)(100u) != x2); - // For coverage: different types, neither has a hookOpEquals - assert(Checked!(uint, void)(100u) == Checked!(int, void*)(100)); - assert(x2.hook.calls == 0); - assert(x2 != -100); - assert(x2.hook.calls == 1); - assert(x2 != cast(uint) -100); - assert(x2.hook.calls == 2); - x2 = 100; - assert(x2 != cast(uint) 100); - assert(x2.hook.calls == 3); - x2 = -100; - - auto x3 = Checked!(uint, Hook2)(100u); - assert(x3 != 100); - x3 = uint.max; - assert(x3 != -1); - - assert(x2 != x3); -} - -// opCmp -@nogc nothrow pure @safe unittest -{ - Checked!(int, void) x; - assert(x <= x); - assert(x < 45); - assert(x < 45u); - assert(x > -45); - assert(x < 44.2); - assert(x > -44.2); - assert(!(x < double.init)); - assert(!(x > double.init)); - assert(!(x <= double.init)); - assert(!(x >= double.init)); - - static struct Hook1 - { - uint calls; - int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - ++calls; - return 0; - } - } - auto x1 = Checked!(int, Hook1)(42); - assert(!(x1 < 43u)); - assert(!(43u < x1)); - assert(x1.hook.calls == 2); - - static struct Hook2 - { - uint calls; - int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) - { - ++calls; - return ProperCompare.hookOpCmp(lhs, rhs); - } - } - auto x2 = Checked!(int, Hook2)(-42); - assert(x2 < 43u); - assert(43u > x2); - assert(x2.hook.calls == 2); - x2 = 42; - assert(x2 > 41u); - - auto x3 = Checked!(uint, Hook2)(42u); - assert(x3 > 41); - assert(x3 > -41); -} - -// opUnary -@nogc nothrow pure @safe unittest -{ - auto x = Checked!(int, void)(42); - assert(x == +x); - static assert(is(typeof(-x) == typeof(x))); - assert(-x == Checked!(int, void)(-42)); - static assert(is(typeof(~x) == typeof(x))); - assert(~x == Checked!(int, void)(~42)); - assert(++x == 43); - assert(--x == 42); - - static struct Hook1 - { - uint calls; - auto hookOpUnary(string op, T)(T value) if (op == "-") - { - ++calls; - return T(42); - } - auto hookOpUnary(string op, T)(T value) if (op == "~") - { - ++calls; - return T(43); - } - } - auto x1 = Checked!(int, Hook1)(100); - assert(is(typeof(-x1) == typeof(x1))); - assert(-x1 == Checked!(int, Hook1)(42)); - assert(is(typeof(~x1) == typeof(x1))); - assert(~x1 == Checked!(int, Hook1)(43)); - assert(x1.hook.calls == 2); - - static struct Hook2 - { - uint calls; - void hookOpUnary(string op, T)(ref T value) if (op == "++") - { - ++calls; - --value; - } - void hookOpUnary(string op, T)(ref T value) if (op == "--") - { - ++calls; - ++value; - } - } - auto x2 = Checked!(int, Hook2)(100); - assert(++x2 == 99); - assert(x2 == 99); - assert(--x2 == 100); - assert(x2 == 100); - - auto x3 = Checked!(int, CountOverflows)(int.max - 1); - assert(++x3 == int.max); - assert(x3.hook.calls == 0); - assert(++x3 == int.min); - assert(x3.hook.calls == 1); - assert(-x3 == int.min); - assert(x3.hook.calls == 2); - - x3 = int.min + 1; - assert(--x3 == int.min); - assert(x3.hook.calls == 2); - assert(--x3 == int.max); - assert(x3.hook.calls == 3); -} - -// -@nogc nothrow pure @safe unittest -{ - Checked!(int, void) x; - assert(x == x); - assert(x == +x); - assert(x == -x); - ++x; - assert(x == 1); - x++; - assert(x == 2); - - x = 42; - assert(x == 42); - const short _short = 43; - x = _short; - assert(x == _short); - ushort _ushort = 44; - x = _ushort; - assert(x == _ushort); - assert(x == 44.0); - assert(x != 44.1); - assert(x < 45); - assert(x < 44.2); - assert(x > -45); - assert(x > -44.2); - - assert(cast(long) x == 44); - assert(cast(short) x == 44); - - const Checked!(uint, void) y; - assert(y <= y); - assert(y == 0); - assert(y < x); - x = -1; - assert(x > y); -} - -@nogc nothrow pure @safe unittest -{ - alias cint = Checked!(int, void); - cint a = 1, b = 2; - a += b; - assert(a == cint(3)); - - alias ccint = Checked!(cint, Saturate); - ccint c = 14; - a += c; - assert(a == cint(17)); -} - -// toHash -@safe unittest -{ - assert(checked(42).toHash() == checked(42).toHash()); - assert(checked(12).toHash() != checked(19).toHash()); - - static struct Hook1 - { - static size_t hookToHash(T)(T payload) nothrow @trusted - { - static if (size_t.sizeof == 4) - { - return typeid(payload).getHash(&payload) ^ 0xFFFF_FFFF; - } - else - { - return typeid(payload).getHash(&payload) ^ 0xFFFF_FFFF_FFFF_FFFF; - } - - } - } - - auto a = checked!Hook1(78); - auto b = checked!Hook1(78); - assert(a.toHash() == b.toHash()); - - assert(checked!Hook1(12).toHash() != checked!Hook1(13).toHash()); - - static struct Hook2 - { - static if (size_t.sizeof == 4) - { - static size_t hashMask = 0xFFFF_0000; - } - else - { - static size_t hashMask = 0xFFFF_0000_FFFF_0000; - } - - static size_t hookToHash(T)(T payload) nothrow @trusted - { - return typeid(payload).getHash(&payload) ^ hashMask; - } - } - - auto x = checked!Hook2(1901); - auto y = checked!Hook2(1989); - - assert((() nothrow @safe => x.toHash() == x.toHash())()); - - assert(x.toHash() == x.toHash()); - assert(x.toHash() != y.toHash()); - assert(checked!Hook1(1901).toHash() != x.toHash()); - - immutable z = checked!Hook1(1901); - immutable t = checked!Hook1(1901); - immutable w = checked!Hook2(1901); - - assert(z.toHash() == t.toHash()); - assert(z.toHash() != x.toHash()); - assert(z.toHash() != w.toHash()); - - const long c = 0xF0F0F0F0; - const long d = 0xF0F0F0F0; - - assert(checked!Hook1(c).toHash() != checked!Hook2(c)); - assert(checked!Hook1(c).toHash() != checked!Hook1(d)); - - // Hook with state, does not implement hookToHash - static struct Hook3 - { - ulong var1 = ulong.max; - uint var2 = uint.max; - } - - assert(checked!Hook3(12).toHash() != checked!Hook3(13).toHash()); - assert(checked!Hook3(13).toHash() == checked!Hook3(13).toHash()); - - // Hook with no state and no hookToHash, payload has its own hashing function - auto x1 = Checked!(Checked!int, ProperCompare)(123); - auto x2 = Checked!(Checked!int, ProperCompare)(123); - auto x3 = Checked!(Checked!int, ProperCompare)(144); - - assert(x1.toHash() == x2.toHash()); - assert(x1.toHash() != x3.toHash()); - assert(x2.toHash() != x3.toHash()); - - // Check shared. - { - shared shared0 = checked(12345678); - shared shared1 = checked!Hook1(123456789); - shared shared2 = checked!Hook2(234567891); - shared shared3 = checked!Hook3(345678912); - assert(shared0.toHash() == hashOf(shared0)); - assert(shared1.toHash() == hashOf(shared1)); - assert(shared2.toHash() == hashOf(shared2)); - assert(shared3.toHash() == hashOf(shared3)); - } -} - -/// -@safe unittest -{ - struct MyHook - { - static size_t hookToHash(T)(const T payload) nothrow @trusted - { - return .hashOf(payload); - } - } - - int[Checked!(int, MyHook)] aa; - Checked!(int, MyHook) var = 42; - aa[var] = 100; - - assert(aa[var] == 100); - - int[Checked!(int, Abort)] bb; - Checked!(int, Abort) var2 = 42; - bb[var2] = 100; - - assert(bb[var2] == 100); -} + * This module is now deprecated, use $(MREF std, experimental) + * instead. + * + * Copyright: Copyright The D Language Foundation 2005 - 2015. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: + * Source: $(PHOBOSSRC std/experimental/checkedint.d) + * + * $(SCRIPT inhibitQuickIndex = 1;) + */ +deprecated module std.experimental.checkedint; +public import std.checkedint; diff --git a/std/file.d b/std/file.d index 927f06eb54b..c974ada2ae8 100644 --- a/std/file.d +++ b/std/file.d @@ -355,7 +355,7 @@ version (Posix) private void[] readImpl(scope const(char)[] name, scope const(FS import core.memory : GC; import std.algorithm.comparison : min; import std.conv : to; - import std.experimental.checkedint : checked; + import std.checkedint : checked; // A few internal configuration parameters { enum size_t @@ -3366,7 +3366,7 @@ version (Posix) @system unittest // input range of dchars version (Windows) string getcwd() @trusted { import std.conv : to; - import std.experimental.checkedint : checked; + import std.checkedint : checked; /* GetCurrentDirectory's return value: 1. function succeeds: the number of characters that are written to the buffer, not including the terminating null character. diff --git a/win32.mak b/win32.mak index 3e2569cd50f..d89f08be3a3 100644 --- a/win32.mak +++ b/win32.mak @@ -275,7 +275,7 @@ SRC_STD_INTERNAL_WINDOWS= \ std\internal\windows\advapi32.d SRC_STD_EXP= \ - std\experimental\checkedint.d std\experimental\typecons.d + std\checkedint.d std\experimental\checkedint.d std\experimental\typecons.d SRC_STD_UNI = std\uni\package.d \ diff --git a/win64.mak b/win64.mak index ae4504d5d7c..041fb19c21a 100644 --- a/win64.mak +++ b/win64.mak @@ -298,7 +298,7 @@ SRC_STD_INTERNAL_WINDOWS= \ std\internal\windows\advapi32.d SRC_STD_EXP= \ - std\experimental\checkedint.d std\experimental\typecons.d + std\checkedint.d std\experimental\checkedint.d std\experimental\typecons.d SRC_STD_UNI = std\uni\package.d