mirror of
https://github.com/dlang/phobos.git
synced 2025-04-26 21:22:20 +03:00
2482 lines
72 KiB
D
2482 lines
72 KiB
D
// 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;)
|
|
$(DIVC quickindex,
|
|
$(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 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)
|
|
))
|
|
))
|
|
|
|
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
|
|
$(HTTP jmdavisprog.com, Jonathan M Davis)
|
|
Source: $(PHOBOSSRC std/exception.d)
|
|
|
|
+/
|
|
module std.exception;
|
|
|
|
/// Synopis
|
|
@system unittest
|
|
{
|
|
import core.stdc.stdlib : malloc, free;
|
|
import std.algorithm.comparison : equal;
|
|
import std.algorithm.iteration : map, splitter;
|
|
import std.algorithm.searching : endsWith;
|
|
import std.conv : ConvException, to;
|
|
import std.range : front, retro;
|
|
|
|
// use enforce like assert
|
|
int a = 3;
|
|
enforce(a > 2, "a needs to be higher than 2.");
|
|
|
|
// enforce can throw a custom exception
|
|
enforce!ConvException(a > 2, "a needs to be higher than 2.");
|
|
|
|
// enforce will return it's input
|
|
enum size = 42;
|
|
auto memory = enforce(malloc(size), "malloc failed")[0 .. size];
|
|
scope(exit) free(memory.ptr);
|
|
|
|
// collectException can be used to test for exceptions
|
|
Exception e = collectException("abc".to!int);
|
|
assert(e.file.endsWith("conv.d"));
|
|
|
|
// and just for the exception message
|
|
string msg = collectExceptionMsg("abc".to!int);
|
|
assert(msg == "Unexpected 'a' when converting from type string to type int");
|
|
|
|
// assertThrown can be used to assert that an exception is thrown
|
|
assertThrown!ConvException("abc".to!int);
|
|
|
|
// ifThrown can be used to provide a default value if an exception is thrown
|
|
assert("x".to!int().ifThrown(0) == 0);
|
|
|
|
// handle is a more advanced version of ifThrown for ranges
|
|
auto r = "12,1337z32,54".splitter(',').map!(a => to!int(a));
|
|
auto h = r.handle!(ConvException, RangePrimitive.front, (e, r) => 0);
|
|
assert(h.equal([12, 0, 54]));
|
|
assertThrown!ConvException(h.retro.equal([54, 0, 12]));
|
|
|
|
// basicExceptionCtors avoids the boilerplate when creating custom exceptions
|
|
static class MeaCulpa : Exception
|
|
{
|
|
mixin basicExceptionCtors;
|
|
}
|
|
e = collectException((){throw new MeaCulpa("diagnostic message");}());
|
|
assert(e.msg == "diagnostic message");
|
|
assert(e.file == __FILE__);
|
|
assert(e.line == __LINE__ - 3);
|
|
|
|
// assumeWontThrow can be used to cast throwing code into `nothrow`
|
|
void exceptionFreeCode() nothrow
|
|
{
|
|
// auto-decoding only throws if an invalid UTF char is given
|
|
assumeWontThrow("abc".front);
|
|
}
|
|
|
|
// assumeUnique can be used to cast mutable instance to an `immutable` one
|
|
// use with care
|
|
char[] str = " mutable".dup;
|
|
str[0 .. 2] = "im";
|
|
immutable res = assumeUnique(str);
|
|
assert(res == "immutable");
|
|
}
|
|
|
|
import std.range.primitives;
|
|
import std.traits;
|
|
|
|
/++
|
|
Asserts that the given expression does $(I not) throw the given type
|
|
of `Throwable`. If a `Throwable` of the given type is thrown,
|
|
it is caught and does not escape assertNotThrown. Rather, an
|
|
`AssertError` is thrown. However, any other `Throwable`s will escape.
|
|
|
|
Params:
|
|
T = The `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 `__FILE__`.
|
|
line = The line where the error occurred.
|
|
Defaults to `__LINE__`.
|
|
|
|
Throws:
|
|
`AssertError` if the given `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;
|
|
|
|
static noreturn 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 `Throwable`.
|
|
The `Throwable` is caught and does not escape assertThrown. However,
|
|
any other `Throwable`s $(I will) escape, and if no `Throwable`
|
|
of the given type is thrown, then an `AssertError` is thrown.
|
|
|
|
Params:
|
|
T = The `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 `__FILE__`.
|
|
line = The line where the error occurred.
|
|
Defaults to `__LINE__`.
|
|
|
|
Throws:
|
|
`AssertError` if the given `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;
|
|
|
|
static if (!is(immutable E == immutable noreturn))
|
|
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;
|
|
|
|
static noreturn 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.
|
|
If the given value is false, an exception is thrown.
|
|
The
|
|
$(UL
|
|
$(LI `msg` - error message as a `string`)
|
|
$(LI `dg` - custom delegate that return a string and is only called if an exception occurred)
|
|
$(LI `ex` - custom exception to be thrown. It is `lazy` and is only created if an exception occurred)
|
|
)
|
|
|
|
Params:
|
|
value = The value to test.
|
|
E = Exception type to throw if the value evaluates to false.
|
|
msg = The error message to put in the exception if it is thrown.
|
|
dg = The delegate to be called if the value evaluates to false.
|
|
ex = The exception to throw if the value evaluates to false.
|
|
file = The source file of the caller.
|
|
line = The line number of the caller.
|
|
|
|
Returns: `value`, if `cast(bool) value` is true. Otherwise,
|
|
depending on the chosen overload, `new Exception(msg)`, `dg()` or `ex` is thrown.
|
|
|
|
$(PANEL
|
|
$(NOTE `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 `assert` is for.)
|
|
|
|
Do not use
|
|
`enforce` inside of contracts (i.e. inside of `in` and `out`
|
|
blocks and `invariant`s), because contracts are compiled out when
|
|
compiling with $(I -release).
|
|
)
|
|
|
|
If a delegate is passed, the safety and purity of this function are inferred
|
|
from `Dg`'s safety and purity.
|
|
+/
|
|
template enforce(E : Throwable = Exception)
|
|
if (is(typeof(new E("", string.init, size_t.init)) : Throwable) ||
|
|
is(typeof(new E(string.init, size_t.init)) : Throwable))
|
|
{
|
|
///
|
|
T enforce(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;
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
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;
|
|
}
|
|
|
|
/// ditto
|
|
T enforce(T)(T value, lazy Throwable ex)
|
|
{
|
|
if (!value) throw ex();
|
|
return value;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import core.stdc.stdlib : malloc, free;
|
|
import std.conv : ConvException, to;
|
|
|
|
// use enforce like assert
|
|
int a = 3;
|
|
enforce(a > 2, "a needs to be higher than 2.");
|
|
|
|
// enforce can throw a custom exception
|
|
enforce!ConvException(a > 2, "a needs to be higher than 2.");
|
|
|
|
// enforce will return it's input
|
|
enum size = 42;
|
|
auto memory = enforce(malloc(size), "malloc failed")[0 .. size];
|
|
scope(exit) free(memory.ptr);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
assertNotThrown(enforce(true, new Exception("this should not be thrown")));
|
|
assertThrown(enforce(false, new Exception("this should be thrown")));
|
|
}
|
|
|
|
///
|
|
@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);
|
|
}
|
|
}
|
|
|
|
/// Alias your own enforce function
|
|
@safe unittest
|
|
{
|
|
import std.conv : ConvException;
|
|
alias convEnforce = enforce!ConvException;
|
|
assertNotThrown(convEnforce(true));
|
|
assertThrown!ConvException(convEnforce(false, "blah"));
|
|
}
|
|
|
|
private noreturn bailOut(E : Throwable = Exception)(string file, size_t line, scope const(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));
|
|
}
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=10510
|
|
@safe unittest
|
|
{
|
|
extern(C) void cFoo() { }
|
|
enforce(false, &cFoo);
|
|
}
|
|
|
|
// purity and safety inference test
|
|
@system unittest
|
|
{
|
|
static foreach (EncloseSafe; [false, true])
|
|
static foreach (EnclosePure; [false, true])
|
|
{
|
|
static foreach (BodySafe; [false, true])
|
|
static foreach (BodyPure; [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 https://issues.dlang.org/show_bug.cgi?id=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);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=14685
|
|
@safe unittest
|
|
{
|
|
class E : Exception
|
|
{
|
|
this() { super("Not found"); }
|
|
}
|
|
static assert(!__traits(compiles, { enforce!E(false); }));
|
|
}
|
|
|
|
/++
|
|
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: `value`, if `cast(bool) value` is true. Otherwise,
|
|
$(D new ErrnoException(msg)) is thrown. It is assumed that the last
|
|
operation set `errno` to an error code corresponding with the failed
|
|
condition.
|
|
+/
|
|
alias errnoEnforce = enforce!ErrnoException;
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import core.stdc.stdio : fclose, fgets, fopen;
|
|
import std.file : thisExePath;
|
|
import std.string : toStringz;
|
|
|
|
auto f = fopen(thisExePath.toStringz, "r").errnoEnforce;
|
|
scope(exit) fclose(f);
|
|
char[100] buf;
|
|
auto line = fgets(buf.ptr, buf.length, f);
|
|
enforce(line !is null); // expect a non-empty line
|
|
}
|
|
|
|
/++
|
|
Catches and returns the exception thrown from the given expression.
|
|
If no exception is thrown, then null is returned and `result` is
|
|
set to the result of the expression.
|
|
|
|
Note that while `collectException` $(I can) be used to collect any
|
|
`Throwable` and not just `Exception`s, it is generally ill-advised to
|
|
catch anything that is neither an `Exception` nor a type derived from
|
|
`Exception`. So, do not use `collectException` to collect
|
|
non-`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;
|
|
}
|
|
// Avoid "statement not reachable" warning
|
|
static if (!is(immutable E == immutable noreturn))
|
|
return null;
|
|
}
|
|
///
|
|
@system unittest
|
|
{
|
|
int b;
|
|
int foo() { throw new Exception("blah"); }
|
|
assert(collectException(foo(), b));
|
|
|
|
version (D_NoBoundsChecks) {}
|
|
else
|
|
{
|
|
// check for out of bounds error
|
|
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. `E` can be
|
|
`void`.
|
|
|
|
Note that while `collectException` $(I can) be used to collect any
|
|
`Throwable` and not just `Exception`s, it is generally ill-advised to
|
|
catch anything that is neither an `Exception` nor a type derived from
|
|
`Exception`. So, do not use `collectException` to collect
|
|
non-`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;
|
|
}
|
|
// Avoid "statement not reachable" warning
|
|
static if (!is(immutable E == immutable noreturn))
|
|
return null;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int foo() { throw new Exception("blah"); }
|
|
assert(collectException(foo()).msg == "blah");
|
|
}
|
|
|
|
/++
|
|
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. `E` can be `void`.
|
|
|
|
If an exception is thrown but it has an empty message, then
|
|
`emptyExceptionMsg` is returned.
|
|
|
|
Note that while `collectExceptionMsg` $(I can) be used to collect any
|
|
`Throwable` and not just `Exception`s, it is generally ill-advised to
|
|
catch anything that is neither an `Exception` nor a type derived from
|
|
`Exception`. So, do not use `collectExceptionMsg` to collect
|
|
non-`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();
|
|
|
|
// Avoid "statement not reachable" warning
|
|
static if (!is(immutable E == immutable noreturn))
|
|
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>";
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=22364
|
|
@system unittest
|
|
{
|
|
static noreturn foo() { throw new Exception(""); }
|
|
|
|
const ex = collectException!(Exception, noreturn)(foo());
|
|
assert(ex);
|
|
|
|
const msg = collectExceptionMsg!(Exception, noreturn)(foo());
|
|
assert(msg);
|
|
|
|
noreturn n;
|
|
|
|
// Triggers a backend assertion failure
|
|
// collectException!(Exception, noreturn)(foo(), n);
|
|
|
|
static assert(__traits(compiles, collectException!(Exception, noreturn)(foo(), n)));
|
|
}
|
|
|
|
/**
|
|
Casts a mutable array to an immutable array in an idiomatic
|
|
manner. Technically, `assumeUnique` just inserts a cast,
|
|
but its name documents assumptions on the part of the
|
|
caller. `assumeUnique(arr)` should only be called when
|
|
there are no more active mutable aliases to elements of $(D
|
|
arr). To strengthen this assumption, `assumeUnique(arr)`
|
|
also clears `arr` before returning. Essentially $(D
|
|
assumeUnique(arr)) indicates commitment from the caller that there
|
|
is no more mutable access to any of `arr`'s elements
|
|
(transitively), and that all future accesses will be done through
|
|
the immutable array returned by `assumeUnique`.
|
|
|
|
Typically, `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:
|
|
|
|
$(RUNNABLE_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 `result`
|
|
was private to `letters` and the memory it referenced can no longer be written to
|
|
after the function returns. The following example shows an
|
|
incorrect use of `assumeUnique`.
|
|
|
|
Bad:
|
|
|
|
$(RUNNABLE_EXAMPLE
|
|
----
|
|
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 modifies the
|
|
returned array that the previous caller considered immutable. To obtain an
|
|
immutable array from the writable array `buffer`, replace
|
|
the last line with:
|
|
|
|
----
|
|
return to!(string)(sneaky); // not that sneaky anymore
|
|
----
|
|
|
|
The `to` call will duplicate the array appropriately.
|
|
|
|
$(PANEL
|
|
$(NOTE Checking for uniqueness during compilation is
|
|
possible in certain cases, especially when a function is
|
|
marked (or inferred) as `pure`. The following example does not
|
|
need to call `assumeUnique` because the compiler can infer the
|
|
uniqueness of the array in the pure function:)
|
|
|
|
$(RUNNABLE_EXAMPLE
|
|
----
|
|
static 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 www.cs.cmu.edu/~aldrich/papers/aldrich-dissertation.pdf, ArchJava)
|
|
language.
|
|
)
|
|
|
|
The downside of using `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 `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
|
|
{
|
|
int[] arr = new int[1];
|
|
auto arr1 = arr.assumeUnique;
|
|
static assert(is(typeof(arr1) == immutable(int)[]));
|
|
assert(arr == null);
|
|
assert(arr1 == [0]);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
int[string] arr = ["a":1];
|
|
auto arr1 = arr.assumeUnique;
|
|
static assert(is(typeof(arr1) == immutable(int[string])));
|
|
assert(arr == null);
|
|
assert(arr1.keys == ["a"]);
|
|
}
|
|
|
|
/**
|
|
* Wraps a possibly-throwing expression in a `nothrow` wrapper so that it
|
|
* can be called by a `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 `expr`. If it turns out
|
|
* that the expression $(I does) throw at runtime, the wrapper will throw an
|
|
* `AssertError`.
|
|
*
|
|
* (Note that `Throwable` objects such as `AssertError` that do not
|
|
* subclass `Exception` may be thrown even from `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.algebraic : 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
|
|
|
|
Bugs:
|
|
The function is explicitly annotated `@nogc` because inference could fail,
|
|
see $(LINK2 https://issues.dlang.org/show_bug.cgi?id=17084, Bugzilla issue 17084).
|
|
|
|
Returns: `true` if `source`'s representation embeds a pointer
|
|
that points to `target`'s representation or somewhere inside
|
|
it.
|
|
|
|
If `source` is or contains a dynamic array, then, then these functions will check
|
|
if there is overlap between the dynamic array and `target`'s representation.
|
|
|
|
If `source` is a class, then it will be handled as a pointer.
|
|
|
|
If `target` is a pointer, a dynamic array or a class, then these functions will only
|
|
check if `source` points to `target`, $(I not) what `target` references.
|
|
|
|
If `source` is or contains a union or `void[n]`, then there may be either false positives or
|
|
false negatives:
|
|
|
|
`doesPointTo` will return `true` if it is absolutely certain
|
|
`source` points to `target`. It may produce false negatives, but never
|
|
false positives. This function should be prefered when trying to validate
|
|
input data.
|
|
|
|
`mayPointTo` will return `false` if it is absolutely certain
|
|
`source` does not point to `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 `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) @nogc @trusted pure nothrow
|
|
if (__traits(isRef, source) || isDynamicArray!S ||
|
|
is(S == U*, U) || is(S == class))
|
|
{
|
|
static if (is(S == U*, U) || 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)
|
|
{
|
|
static if (!is(S == void[n], size_t n))
|
|
{
|
|
foreach (ref s; source)
|
|
if (doesPointTo(s, 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 ||
|
|
is(S == U*, U) || is(S == class))
|
|
{
|
|
static if (is(S == U*, U) || 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)
|
|
{
|
|
static if (is(S == void[n], size_t n))
|
|
{
|
|
static if (n >= (void[]).sizeof)
|
|
{
|
|
// could contain a slice, which could point at anything.
|
|
// But a void[N] that is all 0 cannot point anywhere
|
|
import std.algorithm.searching : any;
|
|
if (__ctfe || any(cast(ubyte[]) source[]))
|
|
return true;
|
|
}
|
|
else static if (n >= (void*).sizeof)
|
|
{
|
|
// Reinterpreting cast is impossible during ctfe
|
|
if (__ctfe)
|
|
return true;
|
|
|
|
// Only check for properly aligned pointers
|
|
enum al = (void*).alignof - 1;
|
|
const base = cast(size_t) &source;
|
|
const alBase = (base + al) & ~al;
|
|
|
|
if ((n - (alBase - base)) >= (void*).sizeof &&
|
|
mayPointTo(*(cast(void**) alBase), target))
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
// trick the compiler when initializing slice
|
|
// https://issues.dlang.org/show_bug.cgi?id=18637
|
|
int* p = &i;
|
|
int[] slice = [0, 1, 2, 3, 4];
|
|
int[5] arr = [0, 1, 2, 3, 4];
|
|
int*[] slicep = [p];
|
|
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
|
|
}
|
|
|
|
|
|
version (StdUnittest)
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=17084
|
|
// the bug doesn't happen if these declarations are in the unittest block
|
|
// (static or not).
|
|
private struct Page17084
|
|
{
|
|
URL17084 url;
|
|
int opCmp(P)(P) { return 0; }
|
|
int opCmp(P)(shared(P)) shared { return 0; }
|
|
}
|
|
|
|
private struct URL17084
|
|
{
|
|
int[] queryParams;
|
|
string toString()() const { return ""; }
|
|
alias toString this;
|
|
}
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=17084
|
|
@system unittest
|
|
{
|
|
import std.algorithm.sorting : sort;
|
|
Page17084[] s;
|
|
sort(s);
|
|
shared(Page17084)[] p;
|
|
sort(p);
|
|
}
|
|
|
|
@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.
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=20426
|
|
align((void*).alignof) void[32] voidArr = void;
|
|
(cast(void*[]) voidArr[])[] = null; // Ensure no false pointers
|
|
|
|
// zeroed void ranges can't point at anything
|
|
assert(!mayPointTo(voidArr, a));
|
|
assert(!mayPointTo(voidArr, b));
|
|
|
|
*cast(void**) &voidArr[16] = &a; // Pointers should be found
|
|
|
|
alias SA = void[size_t.sizeof + 3];
|
|
SA *smallArr1 = cast(SA*)&voidArr;
|
|
SA *smallArr2 = cast(SA*)&(voidArr[16]);
|
|
|
|
// But it should only consider properly aligned pointers
|
|
// Write single bytes to avoid issues due to misaligned writes
|
|
void*[1] tmp = [&b];
|
|
(cast(ubyte[]) voidArr[3 .. 3 + (void*).sizeof])[] = cast(ubyte[]) tmp[];
|
|
|
|
|
|
assert( mayPointTo(*smallArr2, a));
|
|
assert(!mayPointTo(*smallArr1, b));
|
|
|
|
assert(!doesPointTo(voidArr, a)); // Value might be a false pointer
|
|
assert(!doesPointTo(voidArr, b));
|
|
|
|
SA *smallArr3 = cast(SA *) &voidArr[13]; // Works for weird sizes/alignments
|
|
assert( mayPointTo(*smallArr3, a));
|
|
assert(!mayPointTo(*smallArr3, b));
|
|
|
|
assert(!doesPointTo(*smallArr3, a));
|
|
assert(!doesPointTo(*smallArr3, b));
|
|
|
|
auto v3 = cast(void[3]*) &voidArr[16]; // Arrays smaller than pointers are ignored
|
|
assert(!mayPointTo(*v3, a));
|
|
assert(!mayPointTo(*v3, b));
|
|
|
|
assert(!doesPointTo(*v3, a));
|
|
assert(!doesPointTo(*v3, b));
|
|
|
|
assert(mayPointTo(voidArr, a)); // slice-contiaining void[N] might point at anything
|
|
assert(mayPointTo(voidArr, b));
|
|
|
|
static assert(() {
|
|
void[16] arr1 = void;
|
|
void[size_t.sizeof] arr2 = void;
|
|
int var;
|
|
return mayPointTo(arr1, var) && !doesPointTo(arr1, var) &&
|
|
mayPointTo(arr2, var) && !doesPointTo(arr2, var);
|
|
}());
|
|
}
|
|
|
|
|
|
@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));
|
|
}
|
|
|
|
/+
|
|
Returns true if the field at index `i` in $(D T) shares its address with another field.
|
|
|
|
Note: This does not merely 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;
|
|
}
|
|
|
|
version (CRuntime_Glibc) version = GNU_STRERROR;
|
|
version (CRuntime_UClibc) version = GNU_STRERROR;
|
|
|
|
package string errnoString(int errno) nothrow @trusted
|
|
{
|
|
import core.stdc.string : strlen;
|
|
version (GNU_STRERROR)
|
|
{
|
|
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 `errno` occur.
|
|
*/
|
|
class ErrnoException : Exception
|
|
{
|
|
/// Operating system error code.
|
|
final @property uint errno() nothrow pure scope @nogc @safe { return _errno; }
|
|
private uint _errno;
|
|
/// Localized error message generated through $(REF strerror_r, core,stdc,string) or $(REF strerror, core,stdc,string).
|
|
final @property string errnoMsg() nothrow pure scope @nogc @safe { return _errnoMsg; }
|
|
private string _errnoMsg;
|
|
/// 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) @safe
|
|
{
|
|
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) @safe
|
|
{
|
|
_errno = errno;
|
|
_errnoMsg = errnoString(errno);
|
|
super(msg ~ " (" ~ errnoMsg ~ ")", file, line);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import core.stdc.errno : EAGAIN;
|
|
auto ex = new ErrnoException("oh no", EAGAIN);
|
|
assert(ex.errno == EAGAIN);
|
|
}
|
|
|
|
/// errno is used by default if no explicit error code is provided
|
|
@safe unittest
|
|
{
|
|
import core.stdc.errno : errno, EAGAIN;
|
|
|
|
auto old = errno;
|
|
scope(exit) errno = old;
|
|
|
|
// fake that errno got set by the callee
|
|
errno = EAGAIN;
|
|
auto ex = new ErrnoException("oh no");
|
|
assert(ex.errno == EAGAIN);
|
|
}
|
|
|
|
/++
|
|
ML-style functional exception handling. Runs the supplied expression and
|
|
returns its result. If the expression throws a `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 `Throwable`s to catch. Defaults to `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.
|
|
+/
|
|
//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);
|
|
}
|
|
}
|
|
|
|
/// Revert to a default value upon an error:
|
|
@safe unittest
|
|
{
|
|
import std.conv : to;
|
|
assert("x".to!int.ifThrown(0) == 0);
|
|
}
|
|
|
|
/**
|
|
Chain multiple calls to ifThrown, each capturing errors from the
|
|
entire preceding expression.
|
|
*/
|
|
@safe unittest
|
|
{
|
|
import std.conv : ConvException, to;
|
|
string s = "true";
|
|
assert(s.to!int.ifThrown(cast(int) s.to!double)
|
|
.ifThrown(cast(int) s.to!bool) == 1);
|
|
|
|
s = "2.0";
|
|
assert(s.to!int.ifThrown(cast(int) s.to!double)
|
|
.ifThrown(cast(int) s.to!bool) == 2);
|
|
|
|
// Respond differently to different types of errors
|
|
alias orFallback = (lazy a) => a.ifThrown!ConvException("not a number")
|
|
.ifThrown!Exception("number too small");
|
|
|
|
assert(orFallback(enforce("x".to!int < 1).to!string) == "not a number");
|
|
assert(orFallback(enforce("2".to!int < 1).to!string) == "number too small");
|
|
}
|
|
|
|
/**
|
|
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.
|
|
*/
|
|
@safe unittest
|
|
{
|
|
// 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.
|
|
@system unittest
|
|
{
|
|
import std.format : format;
|
|
assert("%s".format.ifThrown!Exception(e => typeid(e).name) == "std.format.FormatException");
|
|
}
|
|
|
|
//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 => typeid(e).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 (StdUnittest) package
|
|
void assertCTFEable(alias dg)()
|
|
{
|
|
static assert({ cast(void) dg(); return true; }());
|
|
cast(void) dg();
|
|
}
|
|
|
|
/** This `enum` is used to select the primitives of the range to handle by the
|
|
$(LREF handle) range wrapper. The values of the `enum` can be `OR`'d to
|
|
select multiple primitives to be handled.
|
|
|
|
`RangePrimitive.access` is a shortcut for the access primitives; `front`,
|
|
`back` and `opIndex`.
|
|
|
|
`RangePrimitive.pop` is a shortcut for the mutating primitives;
|
|
`popFront` and `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
|
|
}
|
|
|
|
///
|
|
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`
|
|
}
|
|
|
|
/** 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 `OR` operator
|
|
or the pseudo-primitives `RangePrimitive.access` and `RangePrimitive.pop`.
|
|
All handled primitives must have return types or values compatible with the
|
|
user-supplied handler.
|
|
|
|
Params:
|
|
E = The type of `Throwable` to _handle.
|
|
primitivesToHandle = Set of range primitives to _handle.
|
|
handler = The callable that is called when a handled primitive throws a
|
|
`Throwable` of type `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 `E` is thrown. For `opIndex`, the handler can
|
|
optionally recieve a third argument; the index that caused the exception.
|
|
input = The range to _handle.
|
|
|
|
Returns: A wrapper `struct` that preserves the range interface of `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)); `handle` deals with
|
|
this by `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);
|
|
}
|