1 /++
2 	A draft of a better way to do exceptions
3 
4 	History:
5 		Originally written in May 2015 as a demo, but I never used it inside arsd.
6 
7 		Deprecated in March 2023 (dub v11.0), with the successful parts moved to [arsd.core]. It is unlikely to get any future updates.
8 +/
9 deprecated("This was just a proof of concept demo, the actual concepts are now implemented inside arsd.core")
10 module arsd.exception;
11 /*
12 	Exceptions 2.0
13 */
14 
15 interface ThrowableBase {
16 	void fly(string file = __FILE__, size_t line = __LINE__); // should be built into the compiler's throw statement
17 
18 	// override these as needed
19 	void printMembers(scope void delegate(in char[]) sink) const; // Tip: use mixin PrintMembers; instead of doing it yourself
20 	void getHumanReadableMessage(scope void delegate(in char[]) sink) const; // the exception name should give this generally but it is nice if you have an error code that needs translation or something else that isn't obvious from the name
21 	void printName(scope void delegate(in char[]) sink) const; // only need to override this if you aren't happy with RTTI's name field
22 
23 	// just call this when you are ready
24 	void toString(scope void delegate(in char[]) sink) const;
25 }
26 
27 mixin template ThrowableBaseImplementation() {
28 	// This sets file and line at the throw point instead of in the ctor
29 	// thereby separating allocation from error information - call this and
30 	// file+line will be set then allowing you to reuse exception objects easier
31 	void fly(string file = __FILE__, size_t line = __LINE__) {
32 		this.file = file;
33 		this.line = line;
34 		throw this;
35 	}
36 
37 	// You don't really need this - the class name and members should give all the
38 	// necessary info, but it can be nice in cases like a Windows or errno exception
39 	// where the code isn't necessarily as at-a-glance easy as the string from GetLastError.
40 	/* virtual */ void getHumanReadableMessage(scope void delegate(in char[]) sink) const {
41 		sink(msg); // for backward compatibility
42 	}
43 
44 	// This prints the really useful info to the user, the members' values.
45 	// You don't have to write this typically, instead use the mixin below.
46 	/* virtual */ void printMembers(scope void delegate(in char[]) sink) const {
47 		// this is done with the mixin from derived classes
48 	}
49 
50 	/* virtual */ void printName(scope void delegate(in char[]) sink) const {
51 		sink(typeid(this).name); // FIXME: would be nice if eponymous templates didn't spew the name twice
52 	}
53 
54 	override void toString(scope void delegate(in char[]) sink) const {
55 		char[32] tmpBuff = void;
56 		printName(sink);
57 		sink("@"); sink(file);
58 		sink("("); sink(line.sizeToTempString(tmpBuff[])); sink(")");
59 		sink(": "); getHumanReadableMessage(sink);
60 		sink("\n");
61 		printMembers(sink);
62 		if (info) {
63 			try {
64 				sink("----------------");
65 				foreach (t; info) {
66 					sink("\n"); sink(t);
67 				}
68 			}
69 			catch (Throwable) {
70 				// ignore more errors
71 			}
72 		}
73 	}
74 
75 }
76 
77 class ExceptionBase : Exception, ThrowableBase {
78 	// Hugely simplified ctor - nothing is even needed
79 	this() {
80 		super("");
81 	}
82 
83 	mixin ThrowableBaseImplementation;
84 }
85 
86 class ErrorBase : Error, ThrowableBase {
87 	this() { super(""); }
88 	mixin ThrowableBaseImplementation;
89 }
90 
91 // Mix this into your derived class to print all its members automatically for easier debugging!
92 mixin template PrintMembers() {
93 	override void printMembers(scope void delegate(in char[]) sink) const {
94 		foreach(memberName; __traits(derivedMembers, typeof(this))) {
95 			static if(is(typeof(__traits(getMember, this, memberName))) && !is(typeof(__traits(getMember, typeof(this), memberName)) == function)) {
96 				sink("\t");
97 				sink(memberName);
98 				sink(" = ");
99 				static if(is(typeof(__traits(getMember, this, memberName)) : const(char)[]))
100 					sink(__traits(getMember, this, memberName));
101 				else static if(is(typeof(__traits(getMember, this, memberName)) : long)) {
102 					char[32] tmpBuff = void;
103 					sink(sizeToTempString(__traits(getMember, this, memberName), tmpBuff));
104 				} // else pragma(msg, typeof(__traits(getMember, this, memberName)));
105 				sink("\n");
106 			}
107 		}
108 
109 		super.printMembers(sink);
110 	}
111 }
112 
113 // The class name SHOULD obviate this but you can also add another message if you like.
114 // You can also just override the getHumanReadableMessage yourself in cases like calling strerror
115 mixin template StaticHumanReadableMessage(string s) {
116 	override void getHumanReadableMessage(scope void delegate(in char[]) sink) const {
117 		sink(s);
118 	}
119 }
120 
121 
122 
123 
124 /*
125 	Enforce 2.0
126 */
127 
128 interface DynamicException {
129 	/*
130 	TypeInfo getArgumentType(size_t idx);
131 	void*    getArgumentData(size_t idx);
132 	string   getArgumentAsString(size_t idx);
133 	*/
134 }
135 
136 template enforceBase(ExceptionBaseClass, string failureCondition = "ret is null") {
137 	auto enforceBase(alias func, string file = __FILE__, size_t line = __LINE__, T...)(T args) {
138 		auto ret = func(args);
139 		if(mixin(failureCondition)) {
140 			class C : ExceptionBaseClass, DynamicException {
141 				T args;
142 				this(T args) {
143 					this.args = args;
144 				}
145 
146 				override void printMembers(scope void delegate(in char[]) sink) const {
147 					import std.traits;
148 					import std.conv;
149 					foreach(idx, arg; args) {
150 						sink("\t");
151 						sink(ParameterIdentifierTuple!func[idx]);
152 						sink(" = ");
153 						sink(to!string(arg));
154 						sink("\n");
155 					}
156 					sink("\treturn value = ");
157 					sink(to!string(ret));
158 					sink("\n");
159 				}
160 
161 				override void printName(scope void delegate(in char[]) sink) const {
162 					sink(__traits(identifier, ExceptionBaseClass));
163 				}
164 
165 				override void getHumanReadableMessage(scope void delegate(in char[]) sink) const {
166 					sink(__traits(identifier, func));
167 					sink(" call failed");
168 				}
169 			}
170 
171 			auto exception = new C(args);
172 			exception.file = file;
173 			exception.line = line;
174 			throw exception;
175 		}
176 
177 		return ret;
178 	}
179 }
180 
181 /// Raises an exception given a set of local variables to print out
182 void raise(ExceptionBaseClass, T...)(string file = __FILE__, size_t line = __LINE__) {
183 	class C : ExceptionBaseClass, DynamicException {
184 		override void printMembers(scope void delegate(in char[]) sink) const {
185 			import std.conv;
186 			foreach(idx, arg; T) {
187 				sink("\t");
188 				sink(__traits(identifier, T[idx]));
189 				sink(" = ");
190 				sink(to!string(arg));
191 				sink("\n");
192 			}
193 		}
194 
195 		override void printName(scope void delegate(in char[]) sink) const {
196 			sink(__traits(identifier, ExceptionBaseClass));
197 		}
198 	}
199 
200 	auto exception = new C();
201 	exception.file = file;
202 	exception.line = line;
203 	throw exception;
204 }
205 
206 const(char)[] sizeToTempString(long size, char[] buffer) {
207 	size_t pos = buffer.length - 1;
208 	bool negative = size < 0;
209 	if(size < 0)
210 		size = -size;
211 	while(size) {
212 		buffer[pos] = size % 10 + '0';
213 		size /= 10;
214 		pos--;
215 	}
216 	if(negative) {
217 		buffer[pos] = '-';
218 		pos--;
219 	}
220 	return buffer[pos + 1 .. $];
221 }
222 
223 /////////////////////////////
224 /* USAGE EXAMPLE FOLLOWS */
225 /////////////////////////////
226 
227 
228 // Make sure there's sane base classes for things that take
229 // various types. For example, RangeError might be thrown for
230 // any type of index, but we might just catch any kind of range error.
231 //
232 // The base class gives us an easy catch point for the category.
233 class MyRangeError : ErrorBase {
234 	// unnecessary but kinda nice to have static error message
235 	mixin StaticHumanReadableMessage!"Index out of bounds";
236 }
237 
238 // Now, we do a new class for each error condition that can happen
239 // inheriting from a convenient catch-all base class for our error type
240 // (which might be ExceptionBase itself btw)
241 class TypedRangeError(T) : MyRangeError {
242 	// Error details are stored as DATA MEMBERS
243 	// do NOT convert them to a string yourself
244 	this(T index) {
245 		this.index = index;
246 	}
247 
248 	mixin StaticHumanReadableMessage!(T.stringof ~ " index out of bounds");
249 
250 	// The data members can be easily inspected to learn more
251 	// about the error, perhaps even to retry it programmatically
252 	// and this also avoids the need to do something like call to!string
253 	// and string concatenation functions at the construction point.
254 	//
255 	// Yea, this gives more info AND is allocation-free. What's not to love?
256 	//
257 	// Templated ones can be a pain just because of the need to specify it to
258 	// catch or cast, but it will always at least be available in the printed string.
259 	T index;
260 
261 	// Then, mixin PrintMembers uses D's reflection to do all the messy toString
262 	// data sink nonsense for you. Do this in each subclass where you add more
263 	// data members (which out to be generally all of them, more info is good.
264 	mixin PrintMembers;
265 }
266 
267 version(exception_2_example) {
268 
269 	// We can pass pre-constructed exceptions to functions and get good file/line and stacktrace info!
270 	void stackExample(ThrowableBase exception) {
271 		// throw it now (custom function cuz I change the behavior a wee bit)
272 		exception.fly(); // ideally, I'd change the throw statement to call this function for you to set up line and file
273 	}
274 
275 	void main() {
276 		int a = 230;
277 		string file = "lol";
278 		static class BadValues : ExceptionBase {}
279 		//raise!(BadValues, a, file);
280 
281 		alias enforce = enforceBase!ExceptionBase;
282 
283 		import core.stdc.stdio;
284 		auto fp = enforce!fopen("nofile.txt".ptr, "rb".ptr);
285 
286 
287 		// construct, passing it error details as data, not strings.
288 		auto exception = new TypedRangeError!int(4); // exception construction is separated from file/line setting
289 		stackExample(exception); // so you can allocate/construct in one place, then set and throw somewhere else
290 	}
291 }