From 370b84f1d518156cd125e10daf00d432727f578b Mon Sep 17 00:00:00 2001 From: IdanArye Date: Tue, 22 Jan 2013 04:18:13 +0200 Subject: [PATCH] Added ML style functional exception handling. Using the new "ifThrown" function in the std.exception module. --- std/exception.d | 171 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/std/exception.d b/std/exception.d index 1ff80048c04..6011fca8af9 100644 --- a/std/exception.d +++ b/std/exception.d @@ -1199,3 +1199,174 @@ unittest //process(a.sd); // works //process(structuralCast!StorableDocument(d)); // works } + +/++ + 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} + T = The return type of the expression and the error handler. + expression = The expression to run and return its result. + errorHandler = The handler to run if the expression throwed. + + Examples: +-------------------- + //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. + + Examples: +-------------------- + //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 expection, 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 +unittest +{ + //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"); +} + +unittest +{ + //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[] a=[]; + assert(a[0].ifThrown(0).collectException!RangeError() !is null); + assert(a[0].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))); +}