| // Written in the D programming language. |
| |
| /++ |
| This module defines functions related to exceptions and general error |
| handling. It also defines functions intended to aid in unit testing. |
| |
| $(SCRIPT inhibitQuickIndex = 1;) |
| $(BOOKTABLE, |
| $(TR $(TH Category) $(TH Functions)) |
| $(TR $(TD Assumptions) $(TD |
| $(LREF assertNotThrown) |
| $(LREF assertThrown) |
| $(LREF assumeUnique) |
| $(LREF assumeWontThrow) |
| $(LREF mayPointTo) |
| )) |
| $(TR $(TD Enforce) $(TD |
| $(LREF doesPointTo) |
| $(LREF enforce) |
| $(LREF enforceEx) |
| $(LREF errnoEnforce) |
| )) |
| $(TR $(TD Handlers) $(TD |
| $(LREF collectException) |
| $(LREF collectExceptionMsg) |
| $(LREF ifThrown) |
| $(LREF handle) |
| )) |
| $(TR $(TD Other) $(TD |
| $(LREF basicExceptionCtors) |
| $(LREF emptyExceptionMsg) |
| $(LREF ErrnoException) |
| $(LREF RangePrimitive) |
| )) |
| ) |
| |
| Synopsis of some of std.exception's functions: |
| -------------------- |
| string synopsis() |
| { |
| FILE* f = enforce(fopen("some/file")); |
| // f is not null from here on |
| FILE* g = enforce!WriteException(fopen("some/other/file", "w")); |
| // g is not null from here on |
| |
| Exception e = collectException(write(g, readln(f))); |
| if (e) |
| { |
| ... an exception occurred... |
| ... We have the exception to play around with... |
| } |
| |
| string msg = collectExceptionMsg(write(g, readln(f))); |
| if (msg) |
| { |
| ... an exception occurred... |
| ... We have the message from the exception but not the exception... |
| } |
| |
| char[] line; |
| enforce(readln(f, line)); |
| return assumeUnique(line); |
| } |
| -------------------- |
| |
| Copyright: Copyright Andrei Alexandrescu 2008-, Jonathan M Davis 2011-. |
| License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0) |
| Authors: $(HTTP erdani.org, Andrei Alexandrescu) and Jonathan M Davis |
| Source: $(PHOBOSSRC std/_exception.d) |
| |
| +/ |
| module std.exception; |
| |
| import std.range.primitives; |
| import std.traits; |
| |
| /++ |
| Asserts that the given expression does $(I not) throw the given type |
| of $(D Throwable). If a $(D Throwable) of the given type is thrown, |
| it is caught and does not escape assertNotThrown. Rather, an |
| $(D AssertError) is thrown. However, any other $(D Throwable)s will escape. |
| |
| Params: |
| T = The $(D Throwable) to test for. |
| expression = The expression to test. |
| msg = Optional message to output on test failure. |
| If msg is empty, and the thrown exception has a |
| non-empty msg field, the exception's msg field |
| will be output on test failure. |
| file = The file where the error occurred. |
| Defaults to $(D __FILE__). |
| line = The line where the error occurred. |
| Defaults to $(D __LINE__). |
| |
| Throws: |
| $(D AssertError) if the given $(D Throwable) is thrown. |
| |
| Returns: |
| the result of `expression`. |
| +/ |
| auto assertNotThrown(T : Throwable = Exception, E) |
| (lazy E expression, |
| string msg = null, |
| string file = __FILE__, |
| size_t line = __LINE__) |
| { |
| import core.exception : AssertError; |
| try |
| { |
| return expression(); |
| } |
| catch (T t) |
| { |
| immutable message = msg.length == 0 ? t.msg : msg; |
| immutable tail = message.length == 0 ? "." : ": " ~ message; |
| throw new AssertError("assertNotThrown failed: " ~ T.stringof ~ " was thrown" ~ tail, file, line, t); |
| } |
| } |
| /// |
| @system unittest |
| { |
| import core.exception : AssertError; |
| |
| import std.string; |
| assertNotThrown!StringException(enforce!StringException(true, "Error!")); |
| |
| //Exception is the default. |
| assertNotThrown(enforce!StringException(true, "Error!")); |
| |
| assert(collectExceptionMsg!AssertError(assertNotThrown!StringException( |
| enforce!StringException(false, "Error!"))) == |
| `assertNotThrown failed: StringException was thrown: Error!`); |
| } |
| @system unittest |
| { |
| import core.exception : AssertError; |
| import std.string; |
| assert(collectExceptionMsg!AssertError(assertNotThrown!StringException( |
| enforce!StringException(false, ""), "Error!")) == |
| `assertNotThrown failed: StringException was thrown: Error!`); |
| |
| assert(collectExceptionMsg!AssertError(assertNotThrown!StringException( |
| enforce!StringException(false, ""))) == |
| `assertNotThrown failed: StringException was thrown.`); |
| |
| assert(collectExceptionMsg!AssertError(assertNotThrown!StringException( |
| enforce!StringException(false, ""), "")) == |
| `assertNotThrown failed: StringException was thrown.`); |
| } |
| |
| @system unittest |
| { |
| import core.exception : AssertError; |
| |
| void throwEx(Throwable t) { throw t; } |
| bool nothrowEx() { return true; } |
| |
| try |
| { |
| assert(assertNotThrown!Exception(nothrowEx())); |
| } |
| catch (AssertError) assert(0); |
| |
| try |
| { |
| assert(assertNotThrown!Exception(nothrowEx(), "It's a message")); |
| } |
| catch (AssertError) assert(0); |
| |
| try |
| { |
| assert(assertNotThrown!AssertError(nothrowEx())); |
| } |
| catch (AssertError) assert(0); |
| |
| try |
| { |
| assert(assertNotThrown!AssertError(nothrowEx(), "It's a message")); |
| } |
| catch (AssertError) assert(0); |
| |
| { |
| bool thrown = false; |
| try |
| { |
| assertNotThrown!Exception( |
| throwEx(new Exception("It's an Exception"))); |
| } |
| catch (AssertError) thrown = true; |
| assert(thrown); |
| } |
| |
| { |
| bool thrown = false; |
| try |
| { |
| assertNotThrown!Exception( |
| throwEx(new Exception("It's an Exception")), "It's a message"); |
| } |
| catch (AssertError) thrown = true; |
| assert(thrown); |
| } |
| |
| { |
| bool thrown = false; |
| try |
| { |
| assertNotThrown!AssertError( |
| throwEx(new AssertError("It's an AssertError", __FILE__, __LINE__))); |
| } |
| catch (AssertError) thrown = true; |
| assert(thrown); |
| } |
| |
| { |
| bool thrown = false; |
| try |
| { |
| assertNotThrown!AssertError( |
| throwEx(new AssertError("It's an AssertError", __FILE__, __LINE__)), |
| "It's a message"); |
| } |
| catch (AssertError) thrown = true; |
| assert(thrown); |
| } |
| } |
| |
| /++ |
| Asserts that the given expression throws the given type of $(D Throwable). |
| The $(D Throwable) is caught and does not escape assertThrown. However, |
| any other $(D Throwable)s $(I will) escape, and if no $(D Throwable) |
| of the given type is thrown, then an $(D AssertError) is thrown. |
| |
| Params: |
| T = The $(D Throwable) to test for. |
| expression = The expression to test. |
| msg = Optional message to output on test failure. |
| file = The file where the error occurred. |
| Defaults to $(D __FILE__). |
| line = The line where the error occurred. |
| Defaults to $(D __LINE__). |
| |
| Throws: |
| $(D AssertError) if the given $(D Throwable) is not thrown. |
| +/ |
| void assertThrown(T : Throwable = Exception, E) |
| (lazy E expression, |
| string msg = null, |
| string file = __FILE__, |
| size_t line = __LINE__) |
| { |
| import core.exception : AssertError; |
| |
| try |
| expression(); |
| catch (T) |
| return; |
| throw new AssertError("assertThrown failed: No " ~ T.stringof ~ " was thrown" |
| ~ (msg.length == 0 ? "." : ": ") ~ msg, |
| file, line); |
| } |
| /// |
| @system unittest |
| { |
| import core.exception : AssertError; |
| import std.string; |
| |
| assertThrown!StringException(enforce!StringException(false, "Error!")); |
| |
| //Exception is the default. |
| assertThrown(enforce!StringException(false, "Error!")); |
| |
| assert(collectExceptionMsg!AssertError(assertThrown!StringException( |
| enforce!StringException(true, "Error!"))) == |
| `assertThrown failed: No StringException was thrown.`); |
| } |
| |
| @system unittest |
| { |
| import core.exception : AssertError; |
| |
| void throwEx(Throwable t) { throw t; } |
| void nothrowEx() { } |
| |
| try |
| { |
| assertThrown!Exception(throwEx(new Exception("It's an Exception"))); |
| } |
| catch (AssertError) assert(0); |
| |
| try |
| { |
| assertThrown!Exception(throwEx(new Exception("It's an Exception")), |
| "It's a message"); |
| } |
| catch (AssertError) assert(0); |
| |
| try |
| { |
| assertThrown!AssertError(throwEx(new AssertError("It's an AssertError", |
| __FILE__, __LINE__))); |
| } |
| catch (AssertError) assert(0); |
| |
| try |
| { |
| assertThrown!AssertError(throwEx(new AssertError("It's an AssertError", |
| __FILE__, __LINE__)), |
| "It's a message"); |
| } |
| catch (AssertError) assert(0); |
| |
| |
| { |
| bool thrown = false; |
| try |
| assertThrown!Exception(nothrowEx()); |
| catch (AssertError) |
| thrown = true; |
| |
| assert(thrown); |
| } |
| |
| { |
| bool thrown = false; |
| try |
| assertThrown!Exception(nothrowEx(), "It's a message"); |
| catch (AssertError) |
| thrown = true; |
| |
| assert(thrown); |
| } |
| |
| { |
| bool thrown = false; |
| try |
| assertThrown!AssertError(nothrowEx()); |
| catch (AssertError) |
| thrown = true; |
| |
| assert(thrown); |
| } |
| |
| { |
| bool thrown = false; |
| try |
| assertThrown!AssertError(nothrowEx(), "It's a message"); |
| catch (AssertError) |
| thrown = true; |
| |
| assert(thrown); |
| } |
| } |
| |
| |
| /++ |
| Enforces that the given value is true. |
| |
| Params: |
| value = The value to test. |
| E = Exception type to throw if the value evalues to false. |
| msg = The error message to put in the exception if it is thrown. |
| file = The source file of the caller. |
| line = The line number of the caller. |
| |
| Returns: $(D value), if `cast(bool) value` is true. Otherwise, |
| $(D new Exception(msg)) is thrown. |
| |
| Note: |
| $(D enforce) is used to throw exceptions and is therefore intended to |
| aid in error handling. It is $(I not) intended for verifying the logic |
| of your program. That is what $(D assert) is for. Also, do not use |
| $(D enforce) inside of contracts (i.e. inside of $(D in) and $(D out) |
| blocks and $(D invariant)s), because they will be compiled out when |
| compiling with $(I -release). Use $(D assert) in contracts. |
| |
| Example: |
| -------------------- |
| auto f = enforce(fopen("data.txt")); |
| auto line = readln(f); |
| enforce(line.length, "Expected a non-empty line."); |
| -------------------- |
| +/ |
| T enforce(E : Throwable = Exception, T)(T value, lazy const(char)[] msg = null, |
| string file = __FILE__, size_t line = __LINE__) |
| if (is(typeof({ if (!value) {} }))) |
| { |
| if (!value) bailOut!E(file, line, msg); |
| return value; |
| } |
| |
| /++ |
| Enforces that the given value is true. |
| |
| Params: |
| value = The value to test. |
| dg = The delegate to be called if the value evaluates to false. |
| file = The source file of the caller. |
| line = The line number of the caller. |
| |
| Returns: $(D value), if `cast(bool) value` is true. Otherwise, the given |
| delegate is called. |
| |
| The safety and purity of this function are inferred from $(D Dg)'s safety |
| and purity. |
| +/ |
| T enforce(T, Dg, string file = __FILE__, size_t line = __LINE__) |
| (T value, scope Dg dg) |
| if (isSomeFunction!Dg && is(typeof( dg() )) && |
| is(typeof({ if (!value) {} }))) |
| { |
| if (!value) dg(); |
| return value; |
| } |
| |
| private void bailOut(E : Throwable = Exception)(string file, size_t line, in char[] msg) |
| { |
| static if (is(typeof(new E(string.init, string.init, size_t.init)))) |
| { |
| throw new E(msg ? msg.idup : "Enforcement failed", file, line); |
| } |
| else static if (is(typeof(new E(string.init, size_t.init)))) |
| { |
| throw new E(file, line); |
| } |
| else |
| { |
| static assert(0, "Expected this(string, string, size_t) or this(string, size_t)" ~ |
| " constructor for " ~ __traits(identifier, E)); |
| } |
| } |
| |
| @safe unittest |
| { |
| assert(enforce(123) == 123); |
| |
| try |
| { |
| enforce(false, "error"); |
| assert(false); |
| } |
| catch (Exception e) |
| { |
| assert(e.msg == "error"); |
| assert(e.file == __FILE__); |
| assert(e.line == __LINE__-7); |
| } |
| } |
| |
| @safe unittest |
| { |
| // Issue 10510 |
| extern(C) void cFoo() { } |
| enforce(false, &cFoo); |
| } |
| |
| // purity and safety inference test |
| @system unittest |
| { |
| import std.meta : AliasSeq; |
| |
| foreach (EncloseSafe; AliasSeq!(false, true)) |
| foreach (EnclosePure; AliasSeq!(false, true)) |
| { |
| foreach (BodySafe; AliasSeq!(false, true)) |
| foreach (BodyPure; AliasSeq!(false, true)) |
| { |
| enum code = |
| "delegate void() " ~ |
| (EncloseSafe ? "@safe " : "") ~ |
| (EnclosePure ? "pure " : "") ~ |
| "{ enforce(true, { " ~ |
| "int n; " ~ |
| (BodySafe ? "" : "auto p = &n + 10; " ) ~ // unsafe code |
| (BodyPure ? "" : "static int g; g = 10; ") ~ // impure code |
| "}); " ~ |
| "}"; |
| enum expect = |
| (BodySafe || !EncloseSafe) && (!EnclosePure || BodyPure); |
| |
| version (none) |
| pragma(msg, "safe = ", EncloseSafe?1:0, "/", BodySafe?1:0, ", ", |
| "pure = ", EnclosePure?1:0, "/", BodyPure?1:0, ", ", |
| "expect = ", expect?"OK":"NG", ", ", |
| "code = ", code); |
| |
| static assert(__traits(compiles, mixin(code)()) == expect); |
| } |
| } |
| } |
| |
| // Test for bugzilla 8637 |
| @system unittest |
| { |
| struct S |
| { |
| static int g; |
| ~this() {} // impure & unsafe destructor |
| bool opCast(T:bool)() { |
| int* p = cast(int*) 0; // unsafe operation |
| int n = g; // impure operation |
| return true; |
| } |
| } |
| S s; |
| |
| enforce(s); |
| enforce(s, {}); |
| enforce(s, new Exception("")); |
| |
| errnoEnforce(s); |
| |
| alias E1 = Exception; |
| static class E2 : Exception |
| { |
| this(string fn, size_t ln) { super("", fn, ln); } |
| } |
| static class E3 : Exception |
| { |
| this(string msg) { super(msg, __FILE__, __LINE__); } |
| } |
| enforce!E1(s); |
| enforce!E2(s); |
| } |
| |
| @safe unittest |
| { |
| // Issue 14685 |
| |
| class E : Exception |
| { |
| this() { super("Not found"); } |
| } |
| static assert(!__traits(compiles, { enforce!E(false); })); |
| } |
| |
| /++ |
| Enforces that the given value is true. |
| |
| Params: |
| value = The value to test. |
| ex = The exception to throw if the value evaluates to false. |
| |
| Returns: $(D value), if `cast(bool) value` is true. Otherwise, $(D ex) is |
| thrown. |
| |
| Example: |
| -------------------- |
| auto f = enforce(fopen("data.txt")); |
| auto line = readln(f); |
| enforce(line.length, new IOException); // expect a non-empty line |
| -------------------- |
| +/ |
| T enforce(T)(T value, lazy Throwable ex) |
| { |
| if (!value) throw ex(); |
| return value; |
| } |
| |
| @safe unittest |
| { |
| assertNotThrown(enforce(true, new Exception("this should not be thrown"))); |
| assertThrown(enforce(false, new Exception("this should be thrown"))); |
| } |
| |
| /++ |
| Enforces that the given value is true, throwing an `ErrnoException` if it |
| is not. |
| |
| Params: |
| value = The value to test. |
| msg = The message to include in the `ErrnoException` if it is thrown. |
| |
| Returns: $(D value), if `cast(bool) value` is true. Otherwise, |
| $(D new ErrnoException(msg)) is thrown. It is assumed that the last |
| operation set $(D errno) to an error code corresponding with the failed |
| condition. |
| |
| Example: |
| -------------------- |
| auto f = errnoEnforce(fopen("data.txt")); |
| auto line = readln(f); |
| enforce(line.length); // expect a non-empty line |
| -------------------- |
| +/ |
| T errnoEnforce(T, string file = __FILE__, size_t line = __LINE__) |
| (T value, lazy string msg = null) |
| { |
| if (!value) throw new ErrnoException(msg, file, line); |
| return value; |
| } |
| |
| |
| /++ |
| If $(D !value) is $(D false), $(D value) is returned. Otherwise, |
| $(D new E(msg, file, line)) is thrown. Or if $(D E) doesn't take a message |
| and can be constructed with $(D new E(file, line)), then |
| $(D new E(file, line)) will be thrown. |
| |
| This is legacy name, it is recommended to use $(D enforce!E) instead. |
| |
| Example: |
| -------------------- |
| auto f = enforceEx!FileMissingException(fopen("data.txt")); |
| auto line = readln(f); |
| enforceEx!DataCorruptionException(line.length); |
| -------------------- |
| +/ |
| template enforceEx(E : Throwable) |
| if (is(typeof(new E("", __FILE__, __LINE__)))) |
| { |
| /++ Ditto +/ |
| T enforceEx(T)(T value, lazy string msg = "", string file = __FILE__, size_t line = __LINE__) |
| { |
| if (!value) throw new E(msg, file, line); |
| return value; |
| } |
| } |
| |
| /++ Ditto +/ |
| template enforceEx(E : Throwable) |
| if (is(typeof(new E(__FILE__, __LINE__))) && !is(typeof(new E("", __FILE__, __LINE__)))) |
| { |
| /++ Ditto +/ |
| T enforceEx(T)(T value, string file = __FILE__, size_t line = __LINE__) |
| { |
| if (!value) throw new E(file, line); |
| return value; |
| } |
| } |
| |
| @system unittest |
| { |
| import core.exception : OutOfMemoryError; |
| import std.array : empty; |
| assertNotThrown(enforceEx!Exception(true)); |
| assertNotThrown(enforceEx!Exception(true, "blah")); |
| assertNotThrown(enforceEx!OutOfMemoryError(true)); |
| |
| { |
| auto e = collectException(enforceEx!Exception(false)); |
| assert(e !is null); |
| assert(e.msg.empty); |
| assert(e.file == __FILE__); |
| assert(e.line == __LINE__ - 4); |
| } |
| |
| { |
| auto e = collectException(enforceEx!Exception(false, "hello", "file", 42)); |
| assert(e !is null); |
| assert(e.msg == "hello"); |
| assert(e.file == "file"); |
| assert(e.line == 42); |
| } |
| |
| { |
| auto e = collectException!Error(enforceEx!OutOfMemoryError(false)); |
| assert(e !is null); |
| assert(e.msg == "Memory allocation failed"); |
| assert(e.file == __FILE__); |
| assert(e.line == __LINE__ - 4); |
| } |
| |
| { |
| auto e = collectException!Error(enforceEx!OutOfMemoryError(false, "file", 42)); |
| assert(e !is null); |
| assert(e.msg == "Memory allocation failed"); |
| assert(e.file == "file"); |
| assert(e.line == 42); |
| } |
| |
| static assert(!is(typeof(enforceEx!int(true)))); |
| } |
| |
| @safe unittest |
| { |
| alias enf = enforceEx!Exception; |
| assertNotThrown(enf(true)); |
| assertThrown(enf(false, "blah")); |
| } |
| |
| |
| /++ |
| Catches and returns the exception thrown from the given expression. |
| If no exception is thrown, then null is returned and $(D result) is |
| set to the result of the expression. |
| |
| Note that while $(D collectException) $(I can) be used to collect any |
| $(D Throwable) and not just $(D Exception)s, it is generally ill-advised to |
| catch anything that is neither an $(D Exception) nor a type derived from |
| $(D Exception). So, do not use $(D collectException) to collect |
| non-$(D Exception)s unless you're sure that that's what you really want to |
| do. |
| |
| Params: |
| T = The type of exception to catch. |
| expression = The expression which may throw an exception. |
| result = The result of the expression if no exception is thrown. |
| +/ |
| T collectException(T = Exception, E)(lazy E expression, ref E result) |
| { |
| try |
| { |
| result = expression(); |
| } |
| catch (T e) |
| { |
| return e; |
| } |
| return null; |
| } |
| /// |
| @system unittest |
| { |
| int b; |
| int foo() { throw new Exception("blah"); } |
| assert(collectException(foo(), b)); |
| |
| int[] a = new int[3]; |
| import core.exception : RangeError; |
| assert(collectException!RangeError(a[4], b)); |
| } |
| |
| /++ |
| Catches and returns the exception thrown from the given expression. |
| If no exception is thrown, then null is returned. $(D E) can be |
| $(D void). |
| |
| Note that while $(D collectException) $(I can) be used to collect any |
| $(D Throwable) and not just $(D Exception)s, it is generally ill-advised to |
| catch anything that is neither an $(D Exception) nor a type derived from |
| $(D Exception). So, do not use $(D collectException) to collect |
| non-$(D Exception)s unless you're sure that that's what you really want to |
| do. |
| |
| Params: |
| T = The type of exception to catch. |
| expression = The expression which may throw an exception. |
| +/ |
| T collectException(T : Throwable = Exception, E)(lazy E expression) |
| { |
| try |
| { |
| expression(); |
| } |
| catch (T t) |
| { |
| return t; |
| } |
| return null; |
| } |
| |
| @safe unittest |
| { |
| int foo() { throw new Exception("blah"); } |
| assert(collectException(foo())); |
| } |
| |
| /++ |
| Catches the exception thrown from the given expression and returns the |
| msg property of that exception. If no exception is thrown, then null is |
| returned. $(D E) can be $(D void). |
| |
| If an exception is thrown but it has an empty message, then |
| $(D emptyExceptionMsg) is returned. |
| |
| Note that while $(D collectExceptionMsg) $(I can) be used to collect any |
| $(D Throwable) and not just $(D Exception)s, it is generally ill-advised to |
| catch anything that is neither an $(D Exception) nor a type derived from |
| $(D Exception). So, do not use $(D collectExceptionMsg) to collect |
| non-$(D Exception)s unless you're sure that that's what you really want to |
| do. |
| |
| Params: |
| T = The type of exception to catch. |
| expression = The expression which may throw an exception. |
| +/ |
| string collectExceptionMsg(T = Exception, E)(lazy E expression) |
| { |
| import std.array : empty; |
| try |
| { |
| expression(); |
| |
| return cast(string) null; |
| } |
| catch (T e) |
| return e.msg.empty ? emptyExceptionMsg : e.msg; |
| } |
| /// |
| @safe unittest |
| { |
| void throwFunc() { throw new Exception("My Message."); } |
| assert(collectExceptionMsg(throwFunc()) == "My Message."); |
| |
| void nothrowFunc() {} |
| assert(collectExceptionMsg(nothrowFunc()) is null); |
| |
| void throwEmptyFunc() { throw new Exception(""); } |
| assert(collectExceptionMsg(throwEmptyFunc()) == emptyExceptionMsg); |
| } |
| |
| /++ |
| Value that collectExceptionMsg returns when it catches an exception |
| with an empty exception message. |
| +/ |
| enum emptyExceptionMsg = "<Empty Exception Message>"; |
| |
| /** |
| * Casts a mutable array to an immutable array in an idiomatic |
| * manner. Technically, $(D assumeUnique) just inserts a cast, |
| * but its name documents assumptions on the part of the |
| * caller. $(D assumeUnique(arr)) should only be called when |
| * there are no more active mutable aliases to elements of $(D |
| * arr). To strengthen this assumption, $(D assumeUnique(arr)) |
| * also clears $(D arr) before returning. Essentially $(D |
| * assumeUnique(arr)) indicates commitment from the caller that there |
| * is no more mutable access to any of $(D arr)'s elements |
| * (transitively), and that all future accesses will be done through |
| * the immutable array returned by $(D assumeUnique). |
| * |
| * Typically, $(D assumeUnique) is used to return arrays from |
| * functions that have allocated and built them. |
| * |
| * Params: |
| * array = The array to cast to immutable. |
| * |
| * Returns: The immutable array. |
| * |
| * Example: |
| * |
| * ---- |
| * string letters() |
| * { |
| * char[] result = new char['z' - 'a' + 1]; |
| * foreach (i, ref e; result) |
| * { |
| * e = cast(char)('a' + i); |
| * } |
| * return assumeUnique(result); |
| * } |
| * ---- |
| * |
| * The use in the example above is correct because $(D result) |
| * was private to $(D letters) and is inaccessible in writing |
| * after the function returns. The following example shows an |
| * incorrect use of $(D assumeUnique). |
| * |
| * Bad: |
| * |
| * ---- |
| * private char[] buffer; |
| * string letters(char first, char last) |
| * { |
| * if (first >= last) return null; // fine |
| * auto sneaky = buffer; |
| * sneaky.length = last - first + 1; |
| * foreach (i, ref e; sneaky) |
| * { |
| * e = cast(char)('a' + i); |
| * } |
| * return assumeUnique(sneaky); // BAD |
| * } |
| * ---- |
| * |
| * The example above wreaks havoc on client code because it is |
| * modifying arrays that callers considered immutable. To obtain an |
| * immutable array from the writable array $(D buffer), replace |
| * the last line with: |
| * ---- |
| * return to!(string)(sneaky); // not that sneaky anymore |
| * ---- |
| * |
| * The call will duplicate the array appropriately. |
| * |
| * Note that checking for uniqueness during compilation is |
| * possible in certain cases, especially when a function is |
| * marked as a pure function. The following example does not |
| * need to call assumeUnique because the compiler can infer the |
| * uniqueness of the array in the pure function: |
| * ---- |
| * string letters() pure |
| * { |
| * char[] result = new char['z' - 'a' + 1]; |
| * foreach (i, ref e; result) |
| * { |
| * e = cast(char)('a' + i); |
| * } |
| * return result; |
| * } |
| * ---- |
| * |
| * For more on infering uniqueness see the $(B unique) and |
| * $(B lent) keywords in the |
| * $(HTTP archjava.fluid.cs.cmu.edu/papers/oopsla02.pdf, ArchJava) |
| * language. |
| * |
| * The downside of using $(D assumeUnique)'s |
| * convention-based usage is that at this time there is no |
| * formal checking of the correctness of the assumption; |
| * on the upside, the idiomatic use of $(D assumeUnique) is |
| * simple and rare enough to be tolerable. |
| * |
| */ |
| immutable(T)[] assumeUnique(T)(T[] array) pure nothrow |
| { |
| return .assumeUnique(array); // call ref version |
| } |
| /// ditto |
| immutable(T)[] assumeUnique(T)(ref T[] array) pure nothrow |
| { |
| auto result = cast(immutable(T)[]) array; |
| array = null; |
| return result; |
| } |
| /// ditto |
| immutable(T[U]) assumeUnique(T, U)(ref T[U] array) pure nothrow |
| { |
| auto result = cast(immutable(T[U])) array; |
| array = null; |
| return result; |
| } |
| |
| @system unittest |
| { |
| // @system due to assumeUnique |
| int[] arr = new int[1]; |
| auto arr1 = assumeUnique(arr); |
| assert(is(typeof(arr1) == immutable(int)[]) && arr == null); |
| } |
| |
| // @@@BUG@@@ |
| version (none) @system unittest |
| { |
| int[string] arr = ["a":1]; |
| auto arr1 = assumeUnique(arr); |
| assert(is(typeof(arr1) == immutable(int[string])) && arr == null); |
| } |
| |
| /** |
| * Wraps a possibly-throwing expression in a $(D nothrow) wrapper so that it |
| * can be called by a $(D nothrow) function. |
| * |
| * This wrapper function documents commitment on the part of the caller that |
| * the appropriate steps have been taken to avoid whatever conditions may |
| * trigger an exception during the evaluation of $(D expr). If it turns out |
| * that the expression $(I does) throw at runtime, the wrapper will throw an |
| * $(D AssertError). |
| * |
| * (Note that $(D Throwable) objects such as $(D AssertError) that do not |
| * subclass $(D Exception) may be thrown even from $(D nothrow) functions, |
| * since they are considered to be serious runtime problems that cannot be |
| * recovered from.) |
| * |
| * Params: |
| * expr = The expression asserted not to throw. |
| * msg = The message to include in the `AssertError` if the assumption turns |
| * out to be false. |
| * file = The source file name of the caller. |
| * line = The line number of the caller. |
| * |
| * Returns: |
| * The value of `expr`, if any. |
| */ |
| T assumeWontThrow(T)(lazy T expr, |
| string msg = null, |
| string file = __FILE__, |
| size_t line = __LINE__) nothrow |
| { |
| import core.exception : AssertError; |
| try |
| { |
| return expr; |
| } |
| catch (Exception e) |
| { |
| import std.range.primitives : empty; |
| immutable tail = msg.empty ? "." : ": " ~ msg; |
| throw new AssertError("assumeWontThrow failed: Expression did throw" ~ |
| tail, file, line); |
| } |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.math : sqrt; |
| |
| // This function may throw. |
| int squareRoot(int x) |
| { |
| if (x < 0) |
| throw new Exception("Tried to take root of negative number"); |
| return cast(int) sqrt(cast(double) x); |
| } |
| |
| // This function never throws. |
| int computeLength(int x, int y) nothrow |
| { |
| // Since x*x + y*y is always positive, we can safely assume squareRoot |
| // won't throw, and use it to implement this nothrow function. If it |
| // does throw (e.g., if x*x + y*y overflows a 32-bit value), then the |
| // program will terminate. |
| return assumeWontThrow(squareRoot(x*x + y*y)); |
| } |
| |
| assert(computeLength(3, 4) == 5); |
| } |
| |
| @system unittest |
| { |
| import core.exception : AssertError; |
| |
| void alwaysThrows() |
| { |
| throw new Exception("I threw up"); |
| } |
| void bad() nothrow |
| { |
| assumeWontThrow(alwaysThrows()); |
| } |
| assertThrown!AssertError(bad()); |
| } |
| |
| /** |
| Checks whether a given source object contains pointers or references to a given |
| target object. |
| |
| Params: |
| source = The source object |
| target = The target object |
| |
| Returns: $(D true) if $(D source)'s representation embeds a pointer |
| that points to $(D target)'s representation or somewhere inside |
| it. |
| |
| If $(D source) is or contains a dynamic array, then, then these functions will check |
| if there is overlap between the dynamic array and $(D target)'s representation. |
| |
| If $(D source) is a class, then it will be handled as a pointer. |
| |
| If $(D target) is a pointer, a dynamic array or a class, then these functions will only |
| check if $(D source) points to $(D target), $(I not) what $(D target) references. |
| |
| If $(D source) is or contains a union, then there may be either false positives or |
| false negatives: |
| |
| $(D doesPointTo) will return $(D true) if it is absolutely certain |
| $(D source) points to $(D target). It may produce false negatives, but never |
| false positives. This function should be prefered when trying to validate |
| input data. |
| |
| $(D mayPointTo) will return $(D false) if it is absolutely certain |
| $(D source) does not point to $(D target). It may produce false positives, but never |
| false negatives. This function should be prefered for defensively choosing a |
| code path. |
| |
| Note: Evaluating $(D doesPointTo(x, x)) checks whether $(D x) has |
| internal pointers. This should only be done as an assertive test, |
| as the language is free to assume objects don't have internal pointers |
| (TDPL 7.1.3.5). |
| */ |
| bool doesPointTo(S, T, Tdummy=void)(auto ref const S source, ref const T target) @trusted pure nothrow |
| if (__traits(isRef, source) || isDynamicArray!S || |
| isPointer!S || is(S == class)) |
| { |
| static if (isPointer!S || is(S == class) || is(S == interface)) |
| { |
| const m = *cast(void**) &source; |
| const b = cast(void*) ⌖ |
| const e = b + target.sizeof; |
| return b <= m && m < e; |
| } |
| else static if (is(S == struct) || is(S == union)) |
| { |
| foreach (i, Subobj; typeof(source.tupleof)) |
| static if (!isUnionAliased!(S, i)) |
| if (doesPointTo(source.tupleof[i], target)) return true; |
| return false; |
| } |
| else static if (isStaticArray!S) |
| { |
| foreach (size_t i; 0 .. S.length) |
| if (doesPointTo(source[i], target)) return true; |
| return false; |
| } |
| else static if (isDynamicArray!S) |
| { |
| import std.array : overlap; |
| return overlap(cast(void[]) source, cast(void[])(&target)[0 .. 1]).length != 0; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| // for shared objects |
| /// ditto |
| bool doesPointTo(S, T)(auto ref const shared S source, ref const shared T target) @trusted pure nothrow |
| { |
| return doesPointTo!(shared S, shared T, void)(source, target); |
| } |
| |
| /// ditto |
| bool mayPointTo(S, T, Tdummy=void)(auto ref const S source, ref const T target) @trusted pure nothrow |
| if (__traits(isRef, source) || isDynamicArray!S || |
| isPointer!S || is(S == class)) |
| { |
| static if (isPointer!S || is(S == class) || is(S == interface)) |
| { |
| const m = *cast(void**) &source; |
| const b = cast(void*) ⌖ |
| const e = b + target.sizeof; |
| return b <= m && m < e; |
| } |
| else static if (is(S == struct) || is(S == union)) |
| { |
| foreach (i, Subobj; typeof(source.tupleof)) |
| if (mayPointTo(source.tupleof[i], target)) return true; |
| return false; |
| } |
| else static if (isStaticArray!S) |
| { |
| foreach (size_t i; 0 .. S.length) |
| if (mayPointTo(source[i], target)) return true; |
| return false; |
| } |
| else static if (isDynamicArray!S) |
| { |
| import std.array : overlap; |
| return overlap(cast(void[]) source, cast(void[])(&target)[0 .. 1]).length != 0; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| // for shared objects |
| /// ditto |
| bool mayPointTo(S, T)(auto ref const shared S source, ref const shared T target) @trusted pure nothrow |
| { |
| return mayPointTo!(shared S, shared T, void)(source, target); |
| } |
| |
| /// Pointers |
| @system unittest |
| { |
| int i = 0; |
| int* p = null; |
| assert(!p.doesPointTo(i)); |
| p = &i; |
| assert( p.doesPointTo(i)); |
| } |
| |
| /// Structs and Unions |
| @system unittest |
| { |
| struct S |
| { |
| int v; |
| int* p; |
| } |
| int i; |
| auto s = S(0, &i); |
| |
| // structs and unions "own" their members |
| // pointsTo will answer true if one of the members pointsTo. |
| assert(!s.doesPointTo(s.v)); //s.v is just v member of s, so not pointed. |
| assert( s.p.doesPointTo(i)); //i is pointed by s.p. |
| assert( s .doesPointTo(i)); //which means i is pointed by s itself. |
| |
| // Unions will behave exactly the same. Points to will check each "member" |
| // individually, even if they share the same memory |
| } |
| |
| /// Arrays (dynamic and static) |
| @system unittest |
| { |
| int i; |
| int[] slice = [0, 1, 2, 3, 4]; |
| int[5] arr = [0, 1, 2, 3, 4]; |
| int*[] slicep = [&i]; |
| int*[1] arrp = [&i]; |
| |
| // A slice points to all of its members: |
| assert( slice.doesPointTo(slice[3])); |
| assert(!slice[0 .. 2].doesPointTo(slice[3])); // Object 3 is outside of the |
| // slice [0 .. 2] |
| |
| // Note that a slice will not take into account what its members point to. |
| assert( slicep[0].doesPointTo(i)); |
| assert(!slicep .doesPointTo(i)); |
| |
| // static arrays are objects that own their members, just like structs: |
| assert(!arr.doesPointTo(arr[0])); // arr[0] is just a member of arr, so not |
| // pointed. |
| assert( arrp[0].doesPointTo(i)); // i is pointed by arrp[0]. |
| assert( arrp .doesPointTo(i)); // which means i is pointed by arrp |
| // itself. |
| |
| // Notice the difference between static and dynamic arrays: |
| assert(!arr .doesPointTo(arr[0])); |
| assert( arr[].doesPointTo(arr[0])); |
| assert( arrp .doesPointTo(i)); |
| assert(!arrp[].doesPointTo(i)); |
| } |
| |
| /// Classes |
| @system unittest |
| { |
| class C |
| { |
| this(int* p){this.p = p;} |
| int* p; |
| } |
| int i; |
| C a = new C(&i); |
| C b = a; |
| |
| // Classes are a bit particular, as they are treated like simple pointers |
| // to a class payload. |
| assert( a.p.doesPointTo(i)); // a.p points to i. |
| assert(!a .doesPointTo(i)); // Yet a itself does not point i. |
| |
| //To check the class payload itself, iterate on its members: |
| () |
| { |
| import std.traits : Fields; |
| |
| foreach (index, _; Fields!C) |
| if (doesPointTo(a.tupleof[index], i)) |
| return; |
| assert(0); |
| }(); |
| |
| // To check if a class points a specific payload, a direct memmory check |
| // can be done: |
| auto aLoc = cast(ubyte[__traits(classInstanceSize, C)]*) a; |
| assert(b.doesPointTo(*aLoc)); // b points to where a is pointing |
| } |
| |
| @system unittest |
| { |
| struct S1 { int a; S1 * b; } |
| S1 a1; |
| S1 * p = &a1; |
| assert(doesPointTo(p, a1)); |
| |
| S1 a2; |
| a2.b = &a1; |
| assert(doesPointTo(a2, a1)); |
| |
| struct S3 { int[10] a; } |
| S3 a3; |
| auto a4 = a3.a[2 .. 3]; |
| assert(doesPointTo(a4, a3)); |
| |
| auto a5 = new double[4]; |
| auto a6 = a5[1 .. 2]; |
| assert(!doesPointTo(a5, a6)); |
| |
| auto a7 = new double[3]; |
| auto a8 = new double[][1]; |
| a8[0] = a7; |
| assert(!doesPointTo(a8[0], a8[0])); |
| |
| // don't invoke postblit on subobjects |
| { |
| static struct NoCopy { this(this) { assert(0); } } |
| static struct Holder { NoCopy a, b, c; } |
| Holder h; |
| cast(void) doesPointTo(h, h); |
| } |
| |
| shared S3 sh3; |
| shared sh3sub = sh3.a[]; |
| assert(doesPointTo(sh3sub, sh3)); |
| |
| int[] darr = [1, 2, 3, 4]; |
| |
| //dynamic arrays don't point to each other, or slices of themselves |
| assert(!doesPointTo(darr, darr)); |
| assert(!doesPointTo(darr[0 .. 1], darr)); |
| |
| //But they do point their elements |
| foreach (i; 0 .. 4) |
| assert(doesPointTo(darr, darr[i])); |
| assert(doesPointTo(darr[0 .. 3], darr[2])); |
| assert(!doesPointTo(darr[0 .. 3], darr[3])); |
| } |
| |
| @system unittest |
| { |
| //tests with static arrays |
| //Static arrays themselves are just objects, and don't really *point* to anything. |
| //They aggregate their contents, much the same way a structure aggregates its attributes. |
| //*However* The elements inside the static array may themselves point to stuff. |
| |
| //Standard array |
| int[2] k; |
| assert(!doesPointTo(k, k)); //an array doesn't point to itself |
| //Technically, k doesn't point its elements, although it does alias them |
| assert(!doesPointTo(k, k[0])); |
| assert(!doesPointTo(k, k[1])); |
| //But an extracted slice will point to the same array. |
| assert(doesPointTo(k[], k)); |
| assert(doesPointTo(k[], k[1])); |
| |
| //An array of pointers |
| int*[2] pp; |
| int a; |
| int b; |
| pp[0] = &a; |
| assert( doesPointTo(pp, a)); //The array contains a pointer to a |
| assert(!doesPointTo(pp, b)); //The array does NOT contain a pointer to b |
| assert(!doesPointTo(pp, pp)); //The array does not point itslef |
| |
| //A struct containing a static array of pointers |
| static struct S |
| { |
| int*[2] p; |
| } |
| S s; |
| s.p[0] = &a; |
| assert( doesPointTo(s, a)); //The struct contains an array that points a |
| assert(!doesPointTo(s, b)); //But doesn't point b |
| assert(!doesPointTo(s, s)); //The struct doesn't actually point itslef. |
| |
| //An array containing structs that have pointers |
| static struct SS |
| { |
| int* p; |
| } |
| SS[2] ss = [SS(&a), SS(null)]; |
| assert( doesPointTo(ss, a)); //The array contains a struct that points to a |
| assert(!doesPointTo(ss, b)); //The array doesn't contains a struct that points to b |
| assert(!doesPointTo(ss, ss)); //The array doesn't point itself. |
| } |
| |
| |
| @system unittest //Unions |
| { |
| int i; |
| union U //Named union |
| { |
| size_t asInt = 0; |
| int* asPointer; |
| } |
| struct S |
| { |
| union //Anonymous union |
| { |
| size_t asInt = 0; |
| int* asPointer; |
| } |
| } |
| |
| U u; |
| S s; |
| assert(!doesPointTo(u, i)); |
| assert(!doesPointTo(s, i)); |
| assert(!mayPointTo(u, i)); |
| assert(!mayPointTo(s, i)); |
| |
| u.asPointer = &i; |
| s.asPointer = &i; |
| assert(!doesPointTo(u, i)); |
| assert(!doesPointTo(s, i)); |
| assert( mayPointTo(u, i)); |
| assert( mayPointTo(s, i)); |
| |
| u.asInt = cast(size_t)&i; |
| s.asInt = cast(size_t)&i; |
| assert(!doesPointTo(u, i)); |
| assert(!doesPointTo(s, i)); |
| assert( mayPointTo(u, i)); |
| assert( mayPointTo(s, i)); |
| } |
| |
| @system unittest //Classes |
| { |
| int i; |
| static class A |
| { |
| int* p; |
| } |
| A a = new A, b = a; |
| assert(!doesPointTo(a, b)); //a does not point to b |
| a.p = &i; |
| assert(!doesPointTo(a, i)); //a does not point to i |
| } |
| @safe unittest //alias this test |
| { |
| static int i; |
| static int j; |
| struct S |
| { |
| int* p; |
| @property int* foo(){return &i;} |
| alias foo this; |
| } |
| assert(is(S : int*)); |
| S s = S(&j); |
| assert(!doesPointTo(s, i)); |
| assert( doesPointTo(s, j)); |
| assert( doesPointTo(cast(int*) s, i)); |
| assert(!doesPointTo(cast(int*) s, j)); |
| } |
| @safe unittest //more alias this opCast |
| { |
| void* p; |
| class A |
| { |
| void* opCast(T)() if (is(T == void*)) |
| { |
| return p; |
| } |
| alias foo = opCast!(void*); |
| alias foo this; |
| } |
| assert(!doesPointTo(A.init, p)); |
| assert(!mayPointTo(A.init, p)); |
| } |
| |
| /+ |
| Returns true if the field at index $(D i) in ($D T) shares its address with another field. |
| |
| Note: This does not merelly check if the field is a member of an union, but also that |
| it is not a single child. |
| +/ |
| package enum isUnionAliased(T, size_t i) = isUnionAliasedImpl!T(T.tupleof[i].offsetof); |
| private bool isUnionAliasedImpl(T)(size_t offset) |
| { |
| int count = 0; |
| foreach (i, U; typeof(T.tupleof)) |
| if (T.tupleof[i].offsetof == offset) |
| ++count; |
| return count >= 2; |
| } |
| // |
| @safe unittest |
| { |
| static struct S |
| { |
| int a0; //Not aliased |
| union |
| { |
| int a1; //Not aliased |
| } |
| union |
| { |
| int a2; //Aliased |
| int a3; //Aliased |
| } |
| union A4 |
| { |
| int b0; //Not aliased |
| } |
| A4 a4; |
| union A5 |
| { |
| int b0; //Aliased |
| int b1; //Aliased |
| } |
| A5 a5; |
| } |
| |
| static assert(!isUnionAliased!(S, 0)); //a0; |
| static assert(!isUnionAliased!(S, 1)); //a1; |
| static assert( isUnionAliased!(S, 2)); //a2; |
| static assert( isUnionAliased!(S, 3)); //a3; |
| static assert(!isUnionAliased!(S, 4)); //a4; |
| static assert(!isUnionAliased!(S.A4, 0)); //a4.b0; |
| static assert(!isUnionAliased!(S, 5)); //a5; |
| static assert( isUnionAliased!(S.A5, 0)); //a5.b0; |
| static assert( isUnionAliased!(S.A5, 1)); //a5.b1; |
| } |
| |
| package string errnoString(int errno) nothrow @trusted |
| { |
| import core.stdc.string : strlen; |
| version (CRuntime_Glibc) |
| { |
| import core.stdc.string : strerror_r; |
| char[1024] buf = void; |
| auto s = strerror_r(errno, buf.ptr, buf.length); |
| } |
| else version (Posix) |
| { |
| // XSI-compliant |
| import core.stdc.string : strerror_r; |
| char[1024] buf = void; |
| const(char)* s; |
| if (strerror_r(errno, buf.ptr, buf.length) == 0) |
| s = buf.ptr; |
| else |
| return "Unknown error"; |
| } |
| else |
| { |
| import core.stdc.string : strerror; |
| auto s = strerror(errno); |
| } |
| return s[0 .. s.strlen].idup; |
| } |
| |
| /********************* |
| * Thrown if errors that set $(D errno) occur. |
| */ |
| class ErrnoException : Exception |
| { |
| final @property uint errno() { return _errno; } /// Operating system error code. |
| private uint _errno; |
| /// Constructor which takes an error message. The current global $(REF errno, core,stdc,errno) value is used as error code. |
| this(string msg, string file = null, size_t line = 0) @trusted |
| { |
| import core.stdc.errno : errno; |
| this(msg, errno, file, line); |
| } |
| /// Constructor which takes an error message and error code. |
| this(string msg, int errno, string file = null, size_t line = 0) @trusted |
| { |
| _errno = errno; |
| super(msg ~ " (" ~ errnoString(errno) ~ ")", file, line); |
| } |
| |
| @system unittest |
| { |
| import core.stdc.errno : errno, EAGAIN; |
| |
| auto old = errno; |
| scope(exit) errno = old; |
| |
| errno = EAGAIN; |
| auto ex = new ErrnoException("oh no"); |
| assert(ex.errno == EAGAIN); |
| } |
| |
| @system unittest |
| { |
| import core.stdc.errno : EAGAIN; |
| auto ex = new ErrnoException("oh no", EAGAIN); |
| assert(ex.errno == EAGAIN); |
| } |
| } |
| |
| /++ |
| ML-style functional exception handling. Runs the supplied expression and |
| returns its result. If the expression throws a $(D Throwable), runs the |
| supplied error handler instead and return its result. The error handler's |
| type must be the same as the expression's type. |
| |
| Params: |
| E = The type of $(D Throwable)s to catch. Defaults to $(D Exception) |
| T1 = The type of the expression. |
| T2 = The return type of the error handler. |
| expression = The expression to run and return its result. |
| errorHandler = The handler to run if the expression throwed. |
| |
| Returns: |
| expression, if it does not throw. Otherwise, returns the result of |
| errorHandler. |
| |
| Example: |
| -------------------- |
| //Revert to a default value upon an error: |
| assert("x".to!int().ifThrown(0) == 0); |
| -------------------- |
| |
| You can also chain multiple calls to ifThrown, each capturing errors from the |
| entire preceding expression. |
| |
| Example: |
| -------------------- |
| //Chaining multiple calls to ifThrown to attempt multiple things in a row: |
| string s="true"; |
| assert(s.to!int(). |
| ifThrown(cast(int) s.to!double()). |
| ifThrown(cast(int) s.to!bool()) |
| == 1); |
| |
| //Respond differently to different types of errors |
| assert(enforce("x".to!int() < 1).to!string() |
| .ifThrown!ConvException("not a number") |
| .ifThrown!Exception("number too small") |
| == "not a number"); |
| -------------------- |
| |
| The expression and the errorHandler must have a common type they can both |
| be implicitly casted to, and that type will be the type of the compound |
| expression. |
| |
| Example: |
| -------------------- |
| //null and new Object have a common type(Object). |
| static assert(is(typeof(null.ifThrown(new Object())) == Object)); |
| static assert(is(typeof((new Object()).ifThrown(null)) == Object)); |
| |
| //1 and new Object do not have a common type. |
| static assert(!__traits(compiles, 1.ifThrown(new Object()))); |
| static assert(!__traits(compiles, (new Object()).ifThrown(1))); |
| -------------------- |
| |
| If you need to use the actual thrown exception, you can use a delegate. |
| Example: |
| -------------------- |
| //Use a lambda to get the thrown object. |
| assert("%s".format().ifThrown!Exception(e => e.classinfo.name) == "std.format.FormatException"); |
| -------------------- |
| +/ |
| //lazy version |
| CommonType!(T1, T2) ifThrown(E : Throwable = Exception, T1, T2)(lazy scope T1 expression, lazy scope T2 errorHandler) |
| { |
| static assert(!is(typeof(return) == void), |
| "The error handler's return value(" |
| ~ T2.stringof ~ |
| ") does not have a common type with the expression(" |
| ~ T1.stringof ~ |
| ")." |
| ); |
| try |
| { |
| return expression(); |
| } |
| catch (E) |
| { |
| return errorHandler(); |
| } |
| } |
| |
| ///ditto |
| //delegate version |
| CommonType!(T1, T2) ifThrown(E : Throwable, T1, T2)(lazy scope T1 expression, scope T2 delegate(E) errorHandler) |
| { |
| static assert(!is(typeof(return) == void), |
| "The error handler's return value(" |
| ~ T2.stringof ~ |
| ") does not have a common type with the expression(" |
| ~ T1.stringof ~ |
| ")." |
| ); |
| try |
| { |
| return expression(); |
| } |
| catch (E e) |
| { |
| return errorHandler(e); |
| } |
| } |
| |
| ///ditto |
| //delegate version, general overload to catch any Exception |
| CommonType!(T1, T2) ifThrown(T1, T2)(lazy scope T1 expression, scope T2 delegate(Exception) errorHandler) |
| { |
| static assert(!is(typeof(return) == void), |
| "The error handler's return value(" |
| ~ T2.stringof ~ |
| ") does not have a common type with the expression(" |
| ~ T1.stringof ~ |
| ")." |
| ); |
| try |
| { |
| return expression(); |
| } |
| catch (Exception e) |
| { |
| return errorHandler(e); |
| } |
| } |
| |
| //Verify Examples |
| @system unittest |
| { |
| import std.conv; |
| import std.string; |
| //Revert to a default value upon an error: |
| assert("x".to!int().ifThrown(0) == 0); |
| |
| //Chaining multiple calls to ifThrown to attempt multiple things in a row: |
| string s="true"; |
| assert(s.to!int(). |
| ifThrown(cast(int) s.to!double()). |
| ifThrown(cast(int) s.to!bool()) |
| == 1); |
| |
| //Respond differently to different types of errors |
| assert(enforce("x".to!int() < 1).to!string() |
| .ifThrown!ConvException("not a number") |
| .ifThrown!Exception("number too small") |
| == "not a number"); |
| |
| //null and new Object have a common type(Object). |
| static assert(is(typeof(null.ifThrown(new Object())) == Object)); |
| static assert(is(typeof((new Object()).ifThrown(null)) == Object)); |
| |
| //1 and new Object do not have a common type. |
| static assert(!__traits(compiles, 1.ifThrown(new Object()))); |
| static assert(!__traits(compiles, (new Object()).ifThrown(1))); |
| |
| //Use a lambda to get the thrown object. |
| assert("%s".format().ifThrown(e => e.classinfo.name) == "std.format.FormatException"); |
| } |
| |
| @system unittest |
| { |
| import core.exception; |
| import std.conv; |
| import std.string; |
| //Basic behaviour - all versions. |
| assert("1".to!int().ifThrown(0) == 1); |
| assert("x".to!int().ifThrown(0) == 0); |
| assert("1".to!int().ifThrown!ConvException(0) == 1); |
| assert("x".to!int().ifThrown!ConvException(0) == 0); |
| assert("1".to!int().ifThrown(e=>0) == 1); |
| assert("x".to!int().ifThrown(e=>0) == 0); |
| static if (__traits(compiles, 0.ifThrown!Exception(e => 0))) //This will only work with a fix that was not yet pulled |
| { |
| assert("1".to!int().ifThrown!ConvException(e=>0) == 1); |
| assert("x".to!int().ifThrown!ConvException(e=>0) == 0); |
| } |
| |
| //Exceptions other than stated not caught. |
| assert("x".to!int().ifThrown!StringException(0).collectException!ConvException() !is null); |
| static if (__traits(compiles, 0.ifThrown!Exception(e => 0))) //This will only work with a fix that was not yet pulled |
| { |
| assert("x".to!int().ifThrown!StringException(e=>0).collectException!ConvException() !is null); |
| } |
| |
| //Default does not include errors. |
| int throwRangeError() { throw new RangeError; } |
| assert(throwRangeError().ifThrown(0).collectException!RangeError() !is null); |
| assert(throwRangeError().ifThrown(e=>0).collectException!RangeError() !is null); |
| |
| //Incompatible types are not accepted. |
| static assert(!__traits(compiles, 1.ifThrown(new Object()))); |
| static assert(!__traits(compiles, (new Object()).ifThrown(1))); |
| static assert(!__traits(compiles, 1.ifThrown(e=>new Object()))); |
| static assert(!__traits(compiles, (new Object()).ifThrown(e=>1))); |
| } |
| |
| version (unittest) package |
| @property void assertCTFEable(alias dg)() |
| { |
| static assert({ cast(void) dg(); return true; }()); |
| cast(void) dg(); |
| } |
| |
| /** This $(D enum) is used to select the primitives of the range to handle by the |
| $(LREF handle) range wrapper. The values of the $(D enum) can be $(D OR)'d to |
| select multiple primitives to be handled. |
| |
| $(D RangePrimitive.access) is a shortcut for the access primitives; $(D front), |
| $(D back) and $(D opIndex). |
| |
| $(D RangePrimitive.pop) is a shortcut for the mutating primitives; |
| $(D popFront) and $(D popBack). |
| */ |
| enum RangePrimitive |
| { |
| front = 0b00_0000_0001, /// |
| back = 0b00_0000_0010, /// Ditto |
| popFront = 0b00_0000_0100, /// Ditto |
| popBack = 0b00_0000_1000, /// Ditto |
| empty = 0b00_0001_0000, /// Ditto |
| save = 0b00_0010_0000, /// Ditto |
| length = 0b00_0100_0000, /// Ditto |
| opDollar = 0b00_1000_0000, /// Ditto |
| opIndex = 0b01_0000_0000, /// Ditto |
| opSlice = 0b10_0000_0000, /// Ditto |
| access = front | back | opIndex, /// Ditto |
| pop = popFront | popBack, /// Ditto |
| } |
| |
| /** Handle exceptions thrown from range primitives. |
| |
| Use the $(LREF RangePrimitive) enum to specify which primitives to _handle. |
| Multiple range primitives can be handled at once by using the $(D OR) operator |
| or the pseudo-primitives $(D RangePrimitive.access) and $(D RangePrimitive.pop). |
| All handled primitives must have return types or values compatible with the |
| user-supplied handler. |
| |
| Params: |
| E = The type of $(D Throwable) to _handle. |
| primitivesToHandle = Set of range primitives to _handle. |
| handler = The callable that is called when a handled primitive throws a |
| $(D Throwable) of type $(D E). The handler must accept arguments of |
| the form $(D E, ref IRange) and its return value is used as the primitive's |
| return value whenever $(D E) is thrown. For $(D opIndex), the handler can |
| optionally recieve a third argument; the index that caused the exception. |
| input = The range to _handle. |
| |
| Returns: A wrapper $(D struct) that preserves the range interface of $(D input). |
| |
| Note: |
| Infinite ranges with slicing support must return an instance of |
| $(REF Take, std,range) when sliced with a specific lower and upper |
| bound (see $(REF hasSlicing, std,range,primitives)); $(D handle) deals with |
| this by $(D take)ing 0 from the return value of the handler function and |
| returning that when an exception is caught. |
| */ |
| auto handle(E : Throwable, RangePrimitive primitivesToHandle, alias handler, Range)(Range input) |
| if (isInputRange!Range) |
| { |
| static struct Handler |
| { |
| private Range range; |
| |
| static if (isForwardRange!Range) |
| { |
| @property typeof(this) save() |
| { |
| static if (primitivesToHandle & RangePrimitive.save) |
| { |
| try |
| { |
| return typeof(this)(range.save); |
| } |
| catch (E exception) |
| { |
| return typeof(this)(handler(exception, this.range)); |
| } |
| } |
| else |
| return typeof(this)(range.save); |
| } |
| } |
| |
| static if (isInfinite!Range) |
| { |
| enum bool empty = false; |
| } |
| else |
| { |
| @property bool empty() |
| { |
| static if (primitivesToHandle & RangePrimitive.empty) |
| { |
| try |
| { |
| return this.range.empty; |
| } |
| catch (E exception) |
| { |
| return handler(exception, this.range); |
| } |
| } |
| else |
| return this.range.empty; |
| } |
| } |
| |
| @property auto ref front() |
| { |
| static if (primitivesToHandle & RangePrimitive.front) |
| { |
| try |
| { |
| return this.range.front; |
| } |
| catch (E exception) |
| { |
| return handler(exception, this.range); |
| } |
| } |
| else |
| return this.range.front; |
| } |
| |
| void popFront() |
| { |
| static if (primitivesToHandle & RangePrimitive.popFront) |
| { |
| try |
| { |
| this.range.popFront(); |
| } |
| catch (E exception) |
| { |
| handler(exception, this.range); |
| } |
| } |
| else |
| this.range.popFront(); |
| } |
| |
| static if (isBidirectionalRange!Range) |
| { |
| @property auto ref back() |
| { |
| static if (primitivesToHandle & RangePrimitive.back) |
| { |
| try |
| { |
| return this.range.back; |
| } |
| catch (E exception) |
| { |
| return handler(exception, this.range); |
| } |
| } |
| else |
| return this.range.back; |
| } |
| |
| void popBack() |
| { |
| static if (primitivesToHandle & RangePrimitive.popBack) |
| { |
| try |
| { |
| this.range.popBack(); |
| } |
| catch (E exception) |
| { |
| handler(exception, this.range); |
| } |
| } |
| else |
| this.range.popBack(); |
| } |
| } |
| |
| static if (isRandomAccessRange!Range) |
| { |
| auto ref opIndex(size_t index) |
| { |
| static if (primitivesToHandle & RangePrimitive.opIndex) |
| { |
| try |
| { |
| return this.range[index]; |
| } |
| catch (E exception) |
| { |
| static if (__traits(compiles, handler(exception, this.range, index))) |
| return handler(exception, this.range, index); |
| else |
| return handler(exception, this.range); |
| } |
| } |
| else |
| return this.range[index]; |
| } |
| } |
| |
| static if (hasLength!Range) |
| { |
| @property auto length() |
| { |
| static if (primitivesToHandle & RangePrimitive.length) |
| { |
| try |
| { |
| return this.range.length; |
| } |
| catch (E exception) |
| { |
| return handler(exception, this.range); |
| } |
| } |
| else |
| return this.range.length; |
| } |
| } |
| |
| static if (hasSlicing!Range) |
| { |
| static if (hasLength!Range) |
| { |
| typeof(this) opSlice(size_t lower, size_t upper) |
| { |
| static if (primitivesToHandle & RangePrimitive.opSlice) |
| { |
| try |
| { |
| return typeof(this)(this.range[lower .. upper]); |
| } |
| catch (E exception) |
| { |
| return typeof(this)(handler(exception, this.range)); |
| } |
| } |
| else |
| return typeof(this)(this.range[lower .. upper]); |
| } |
| } |
| else static if (is(typeof(Range.init[size_t.init .. $]))) |
| { |
| import std.range : Take, takeExactly; |
| static struct DollarToken {} |
| enum opDollar = DollarToken.init; |
| |
| typeof(this) opSlice(size_t lower, DollarToken) |
| { |
| static if (primitivesToHandle & RangePrimitive.opSlice) |
| { |
| try |
| { |
| return typeof(this)(this.range[lower .. $]); |
| } |
| catch (E exception) |
| { |
| return typeof(this)(handler(exception, this.range)); |
| } |
| } |
| else |
| return typeof(this)(this.range[lower .. $]); |
| } |
| |
| Take!Handler opSlice(size_t lower, size_t upper) |
| { |
| static if (primitivesToHandle & RangePrimitive.opSlice) |
| { |
| try |
| { |
| return takeExactly(typeof(this)(this.range[lower .. $]), upper - 1); |
| } |
| catch (E exception) |
| { |
| return takeExactly(typeof(this)(handler(exception, this.range)), 0); |
| } |
| } |
| else |
| return takeExactly(typeof(this)(this.range[lower .. $]), upper - 1); |
| } |
| } |
| } |
| } |
| |
| return Handler(input); |
| } |
| |
| /// |
| pure @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : map, splitter; |
| import std.conv : to, ConvException; |
| |
| auto s = "12,1337z32,54,2,7,9,1z,6,8"; |
| |
| // The next line composition will throw when iterated |
| // as some elements of the input do not convert to integer |
| auto r = s.splitter(',').map!(a => to!int(a)); |
| |
| // Substitute 0 for cases of ConvException |
| auto h = r.handle!(ConvException, RangePrimitive.front, (e, r) => 0); |
| assert(h.equal([12, 0, 54, 2, 7, 9, 0, 6, 8])); |
| } |
| |
| /// |
| pure @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.range : retro; |
| import std.utf : UTFException; |
| |
| auto str = "hello\xFFworld"; // 0xFF is an invalid UTF-8 code unit |
| |
| auto handled = str.handle!(UTFException, RangePrimitive.access, |
| (e, r) => ' '); // Replace invalid code points with spaces |
| |
| assert(handled.equal("hello world")); // `front` is handled, |
| assert(handled.retro.equal("dlrow olleh")); // as well as `back` |
| } |
| |
| pure nothrow @safe unittest |
| { |
| static struct ThrowingRange |
| { |
| pure @safe: |
| @property bool empty() |
| { |
| throw new Exception("empty has thrown"); |
| } |
| |
| @property int front() |
| { |
| throw new Exception("front has thrown"); |
| } |
| |
| @property int back() |
| { |
| throw new Exception("back has thrown"); |
| } |
| |
| void popFront() |
| { |
| throw new Exception("popFront has thrown"); |
| } |
| |
| void popBack() |
| { |
| throw new Exception("popBack has thrown"); |
| } |
| |
| int opIndex(size_t) |
| { |
| throw new Exception("opIndex has thrown"); |
| } |
| |
| ThrowingRange opSlice(size_t, size_t) |
| { |
| throw new Exception("opSlice has thrown"); |
| } |
| |
| @property size_t length() |
| { |
| throw new Exception("length has thrown"); |
| } |
| |
| alias opDollar = length; |
| |
| @property ThrowingRange save() |
| { |
| throw new Exception("save has thrown"); |
| } |
| } |
| |
| static assert(isInputRange!ThrowingRange); |
| static assert(isForwardRange!ThrowingRange); |
| static assert(isBidirectionalRange!ThrowingRange); |
| static assert(hasSlicing!ThrowingRange); |
| static assert(hasLength!ThrowingRange); |
| |
| auto f = ThrowingRange(); |
| auto fb = f.handle!(Exception, RangePrimitive.front | RangePrimitive.back, |
| (e, r) => -1)(); |
| assert(fb.front == -1); |
| assert(fb.back == -1); |
| assertThrown(fb.popFront()); |
| assertThrown(fb.popBack()); |
| assertThrown(fb.empty); |
| assertThrown(fb.save); |
| assertThrown(fb[0]); |
| |
| auto accessRange = f.handle!(Exception, RangePrimitive.access, |
| (e, r) => -1); |
| assert(accessRange.front == -1); |
| assert(accessRange.back == -1); |
| assert(accessRange[0] == -1); |
| assertThrown(accessRange.popFront()); |
| assertThrown(accessRange.popBack()); |
| |
| auto pfb = f.handle!(Exception, RangePrimitive.pop, (e, r) => -1)(); |
| |
| pfb.popFront(); // this would throw otherwise |
| pfb.popBack(); // this would throw otherwise |
| |
| auto em = f.handle!(Exception, |
| RangePrimitive.empty, (e, r) => false)(); |
| |
| assert(!em.empty); |
| |
| auto arr = f.handle!(Exception, |
| RangePrimitive.opIndex, (e, r) => 1337)(); |
| |
| assert(arr[0] == 1337); |
| |
| auto arr2 = f.handle!(Exception, |
| RangePrimitive.opIndex, (e, r, i) => i)(); |
| |
| assert(arr2[0] == 0); |
| assert(arr2[1337] == 1337); |
| |
| auto save = f.handle!(Exception, |
| RangePrimitive.save, |
| function(Exception e, ref ThrowingRange r) { |
| return ThrowingRange(); |
| })(); |
| |
| save.save; |
| |
| auto slice = f.handle!(Exception, |
| RangePrimitive.opSlice, (e, r) => ThrowingRange())(); |
| |
| auto sliced = slice[0 .. 1337]; // this would throw otherwise |
| |
| static struct Infinite |
| { |
| import std.range : Take; |
| pure @safe: |
| enum bool empty = false; |
| int front() { assert(false); } |
| void popFront() { assert(false); } |
| Infinite save() @property { assert(false); } |
| static struct DollarToken {} |
| enum opDollar = DollarToken.init; |
| Take!Infinite opSlice(size_t, size_t) { assert(false); } |
| Infinite opSlice(size_t, DollarToken) |
| { |
| throw new Exception("opSlice has thrown"); |
| } |
| } |
| |
| static assert(isInputRange!Infinite); |
| static assert(isInfinite!Infinite); |
| static assert(hasSlicing!Infinite); |
| |
| assertThrown(Infinite()[0 .. $]); |
| |
| auto infinite = Infinite.init.handle!(Exception, |
| RangePrimitive.opSlice, (e, r) => Infinite())(); |
| |
| auto infSlice = infinite[0 .. $]; // this would throw otherwise |
| } |
| |
| |
| /++ |
| Convenience mixin for trivially sub-classing exceptions |
| |
| Even trivially sub-classing an exception involves writing boilerplate code |
| for the constructor to: 1$(RPAREN) correctly pass in the source file and line number |
| the exception was thrown from; 2$(RPAREN) be usable with $(LREF enforce) which |
| expects exception constructors to take arguments in a fixed order. This |
| mixin provides that boilerplate code. |
| |
| Note however that you need to mark the $(B mixin) line with at least a |
| minimal (i.e. just $(B ///)) DDoc comment if you want the mixed-in |
| constructors to be documented in the newly created Exception subclass. |
| |
| $(RED Current limitation): Due to |
| $(LINK2 https://issues.dlang.org/show_bug.cgi?id=11500, bug #11500), |
| currently the constructors specified in this mixin cannot be overloaded with |
| any other custom constructors. Thus this mixin can currently only be used |
| when no such custom constructors need to be explicitly specified. |
| +/ |
| mixin template basicExceptionCtors() |
| { |
| /++ |
| Params: |
| msg = The message for the exception. |
| file = The file where the exception occurred. |
| line = The line number where the exception occurred. |
| next = The previous exception in the chain of exceptions, if any. |
| +/ |
| this(string msg, string file = __FILE__, size_t line = __LINE__, |
| Throwable next = null) @nogc @safe pure nothrow |
| { |
| super(msg, file, line, next); |
| } |
| |
| /++ |
| Params: |
| msg = The message for the exception. |
| next = The previous exception in the chain of exceptions. |
| file = The file where the exception occurred. |
| line = The line number where the exception occurred. |
| +/ |
| this(string msg, Throwable next, string file = __FILE__, |
| size_t line = __LINE__) @nogc @safe pure nothrow |
| { |
| super(msg, file, line, next); |
| } |
| } |
| |
| /// |
| @safe unittest |
| { |
| class MeaCulpa: Exception |
| { |
| /// |
| mixin basicExceptionCtors; |
| } |
| |
| try |
| throw new MeaCulpa("test"); |
| catch (MeaCulpa e) |
| { |
| assert(e.msg == "test"); |
| assert(e.file == __FILE__); |
| assert(e.line == __LINE__ - 5); |
| } |
| } |
| |
| @safe pure nothrow unittest |
| { |
| class TestException : Exception { mixin basicExceptionCtors; } |
| auto e = new Exception("msg"); |
| auto te1 = new TestException("foo"); |
| auto te2 = new TestException("foo", e); |
| } |
| |
| @safe unittest |
| { |
| class TestException : Exception { mixin basicExceptionCtors; } |
| auto e = new Exception("!!!"); |
| |
| auto te1 = new TestException("message", "file", 42, e); |
| assert(te1.msg == "message"); |
| assert(te1.file == "file"); |
| assert(te1.line == 42); |
| assert(te1.next is e); |
| |
| auto te2 = new TestException("message", e, "file", 42); |
| assert(te2.msg == "message"); |
| assert(te2.file == "file"); |
| assert(te2.line == 42); |
| assert(te2.next is e); |
| |
| auto te3 = new TestException("foo"); |
| assert(te3.msg == "foo"); |
| assert(te3.file == __FILE__); |
| assert(te3.line == __LINE__ - 3); |
| assert(te3.next is null); |
| |
| auto te4 = new TestException("foo", e); |
| assert(te4.msg == "foo"); |
| assert(te4.file == __FILE__); |
| assert(te4.line == __LINE__ - 3); |
| assert(te4.next is e); |
| } |