1 /** 2 This module implements utility code to throw exceptions in @nogc code. 3 */ 4 module nogc.exception; 5 6 7 import std.experimental.allocator.mallocator: Mallocator; 8 9 10 T enforce(E = NoGcException, T, Args...) 11 (T value, auto ref Args args, in string file = __FILE__, in size_t line = __LINE__) 12 if (is(typeof({ if (!value) {} }))) 13 { 14 import std.functional: forward; 15 if(!value) NoGcException.throw_(File(file), Line(line), forward!args); 16 return value; 17 } 18 19 alias NoGcException = NoGcExceptionImpl!Mallocator; 20 21 class NoGcExceptionImpl(A): Exception { 22 23 import automem.traits: isGlobal; 24 import automem.vector: StringA; 25 import std.meta: anySatisfy; 26 27 alias Allocator = A; 28 29 // just to let enforce pass the right arguments to the constructor 30 protected static struct Dummy {} 31 protected enum isDummy(T) = is(T == Dummy); 32 33 private StringA!Allocator _msg; 34 static if(!isGlobal!Allocator) private Allocator _allocator; 35 36 this(Args...) 37 (auto ref Args args, string file = __FILE__, size_t line = __LINE__) 38 if(isGlobal!Allocator && !anySatisfy!(isDummy, Args)) 39 { 40 import std.functional: forward; 41 this(Dummy(), file, line, forward!args); 42 } 43 44 /// 45 @("exception can be constructed in @nogc code") 46 @safe @nogc pure unittest { 47 static const exception = new NoGcException(); 48 } 49 50 this(Args...) 51 (Allocator allocator, auto ref Args args, string file = __FILE__, size_t line = __LINE__) 52 if(!anySatisfy!(isDummy, Args)) 53 { 54 import std.functional: forward; 55 this(Dummy(), file, line, forward!args); 56 this._allocator = allocator; 57 } 58 59 // exists to be used from throw_ 60 protected this(Args...) 61 (in Dummy _, in string file, in size_t line, scope auto ref Args args) 62 if(isGlobal!Allocator) 63 { 64 import nogc.conv: text, BUFFER_SIZE; 65 import std.functional: forward; 66 67 _msg = text!(BUFFER_SIZE, A)(forward!args); 68 // Setting `Exception.msg` to the allocated memory in this class would 69 // mean it could escape DIP1000 checks. The only sane alternative is 70 // setting it to null 71 super(null, file, line); 72 } 73 74 /** 75 Throws a new NoGcException allowing to adjust the file name and line number 76 */ 77 static void throw_(T = typeof(this), Args...)(in File file, in Line line, scope auto ref Args args) { 78 import std.functional: forward; 79 throw new T(Dummy(), file.value, line.value, forward!args); 80 } 81 82 /// Manually free the msg 83 final void free() @safe @nogc scope { 84 _msg.free; 85 } 86 87 /** 88 We can't let client code access `Exception.msg` since it's not scoped 89 with DIP1000. 90 */ 91 auto msg() @safe @nogc return scope { 92 return _msg.range; 93 } 94 } 95 96 struct File { string value; } 97 struct Line { size_t value; } 98 99 100 mixin template NoGcExceptionCtors() { 101 102 import nogc.exception: File, Line; 103 import automem.traits: isGlobal; 104 import std.meta: anySatisfy; 105 106 this(Args...) 107 (auto ref Args args, string file = __FILE__, size_t line = __LINE__) 108 if(isGlobal!Allocator && !anySatisfy!(isDummy, Args)) 109 { 110 import std.functional: forward; 111 super(forward!args, file, line); 112 } 113 114 this(Args...) 115 (Allocator allocator, auto ref Args args, string file = __FILE__, size_t line = __LINE__) 116 if(!anySatisfy!(isDummy, Args)) 117 { 118 import std.functional: forward; 119 super(allocator, forward!args, file, line); 120 } 121 122 123 // exists to be used from throw_ 124 protected this(Args...) 125 (in Dummy _, in string file, in size_t line, scope auto ref Args args) 126 if(isGlobal!Allocator) 127 { 128 import std.functional: forward; 129 super(_, file, line, forward!args); 130 } 131 132 /** 133 Throws a new NoGcException allowing to adjust the file name and line number 134 */ 135 static void throw_(T = typeof(this), Args...)(in File file, in Line line, scope auto ref Args args) { 136 import std.functional: forward; 137 typeof(super).throw_!T(file, line, forward!args); 138 } 139 }