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 }