1 module notnull;
2 
3 /** Thrown if $(D nnL) gets passed a pointer that is null.
4 */
5 class NullPointerException : Exception {
6     @safe pure nothrow this(string file = __FILE__, 
7 			size_t line = __LINE__, Throwable next = null) 
8 	{
9         super("Tried to deference a null pointer", file, line, next );
10     }
11 
12     @safe pure nothrow this(NullPointerException old) {
13 		super(old.msg, old.file, old.line, old.next);
14 	}
15 }
16 
17 /** Returns a reference to the object the passed pointer points to.
18 If $(D ptr) is null a $(D NullPointerException) is thrown.
19 */
20 ref T nnL(T)(return scope T* ptr, string file = __FILE__,
21 	   	size_t line = __LINE__) 
22 {
23 	if(ptr is null) {
24 		throw new NullPointerException(file, line);
25 	}
26 
27 	return *ptr;
28 }
29 
30 unittest {
31 	int a;
32 	nnL(&a);
33 
34 	(&a).nnL();
35 }
36 
37 unittest {
38 	import std.exception : assertThrown;
39 	int* a = null;
40 	assertThrown!NullPointerException(nnL(a));
41 }
42 
43 unittest {
44 	import std.exception : assertThrown, assertNotThrown;
45 	struct A {
46 	}
47 
48 	struct B {
49 		A* a;
50 	}
51 
52 	B* b;
53 	assertThrown!NullPointerException(nnL(b));
54 	
55 	b = new B;
56 	assertNotThrown(nnL(b));
57 
58 	assertThrown!NullPointerException(nnL(b).a.nnL());
59 	assertThrown!NullPointerException(b.nnL().a.nnL());
60 
61 	b.a = new A;
62 	assertNotThrown(nnL(b).a.nnL());
63 }
64 
65 private string buildErrorStringRecur(E...)() if(E.length > 0) {
66 	return E[0].stringof ~ "," ~ buildErrorStringRecur!(E[1 .. $]);
67 }
68 
69 private string buildErrorStringRecur(E...)() if(E.length == 0) {
70 	return "}";
71 }
72 
73 private string buildErrorString(Exp...)() {
74 	return "enum ErrorType { hasData, hasNoData, " ~ 
75 		buildErrorStringRecur!Exp();
76 }
77 
78 private string buildThrowSwitch(Exp...)() {
79 	return `switch(this.error) {
80 		case ErrorType.hasData:
81 			goto default;
82 		default:
83 			break;
84 		case ErrorType.hasNoData:
85 			assert(false, "No Data nor an Exception Present");
86 	`~ buildThrowSwitchRecu!(Exp)();
87 }
88 
89 private string buildThrowSwitchRecu(E...)() if(E.length > 0) {
90 	static if(is(E[0] == Exception)) {
91 		return "case ErrorType.Exception:" ~
92 			"throw new Exception(" ~
93 				"(cast(Exception)(this.payload.ptr)).msg," ~
94 				"(cast(Exception)(this.payload.ptr)).file," ~
95 				"(cast(Exception)(this.payload.ptr)).line," ~
96 				"(cast(Exception)(this.payload.ptr)).next);"
97 			~ buildThrowSwitchRecu!(E[1 .. $]);
98 	} else static if(is(E[0] == Error)) {
99 		return "case ErrorType.Error:" ~
100 			"throw new Error(" ~
101 				"(cast(Error)(this.payload.ptr)).msg," ~
102 				"(cast(Error)(this.payload.ptr)).file," ~
103 				"(cast(Error)(this.payload.ptr)).line," ~
104 				"(cast(Error)(this.payload.ptr)).next);"
105 			~ buildThrowSwitchRecu!(E[1 .. $]);
106 	} else static if(is(E[0] == Throwable)) {
107 		return "case ErrorType.Throwable:" ~
108 			"throw new Throwable(" ~
109 				"(cast(Throwable)(this.payload.ptr)).msg," ~
110 				"(cast(Throwable)(this.payload.ptr)).file," ~
111 				"(cast(Throwable)(this.payload.ptr)).line," ~
112 				"(cast(Throwable)(this.payload.ptr)).next);"
113 			~ buildThrowSwitchRecu!(E[1 .. $]);
114 	} else {
115 		return "case ErrorType." ~ E[0].stringof ~ `:
116 			throw new ` ~ E[0].stringof ~ "((cast(" ~ E[0].stringof 
117 			~ ")(this.payload.ptr)));"
118 			~ buildThrowSwitchRecu!(E[1 .. $]);
119 	}
120 }
121 
122 private string buildThrowSwitchRecu(E...)() if(E.length == 0) {
123 	return "}";
124 }
125 
126 private template classSize(C) if(is(C : Exception)) {
127 	enum size_t classSize = __traits(classInstanceSize, C);
128 }
129 
130 struct Null(T,Exp...) {
131 	import std.algorithm.comparison : max;
132 	import std.meta : staticMap;
133 	import std.traits : isImplicitlyConvertible;
134 
135 	alias Type = T;
136 
137 	static if(Exp.length > 0) {
138 		align(8)
139 		void[max(staticMap!(classSize, Exp), T.sizeof)] payload;
140 	} else {
141 		align(8)
142 		void[T.sizeof] payload;
143 	}
144 	mixin(buildErrorString!Exp());
145 	ErrorType error = ErrorType.hasNoData;
146 
147 	@property bool isNull() pure nothrow @nogc const {
148 		return this.error >= ErrorType.hasNoData;
149 	}
150 
151 	@property bool isNotNull() pure nothrow @nogc const {
152 		return this.error == ErrorType.hasData;
153 	}
154 	
155 	void opAssign(S)(auto ref S s) if(isImplicitlyConvertible!(S,T)) {
156 		*(cast(T*)(this.payload.ptr)) = s;
157 		this.error = ErrorType.hasData;
158 	}
159 
160 	void set(E, string file = __FILE__, size_t line = __LINE__, Args...)(auto ref Args args) {
161 		import std.conv : emplace;
162 		emplace!E(this.payload[0 .. $], args, file, line);
163 		mixin("this.error = ErrorType." ~ E.stringof ~ ";");
164 	}
165 
166 	ErrorType getError() const pure @safe nothrow @nogc {
167 		return this.error;
168 	}
169 
170 	ref T get() @trusted {
171 		if(this.error == ErrorType.hasData) {
172 			return *(cast(T*)(this.payload.ptr));
173 		}
174 		mixin(buildThrowSwitch!(Exp)());
175 		assert(false);
176 	}
177 
178 	void rethrow() {
179 		mixin(buildThrowSwitch!(Exp)());
180 	}
181 }
182 
183 T makeNull(T,S)(auto ref S s) {
184 	T ret;
185 	ret = s;
186 	return ret;
187 }
188 
189 T makeNull(T,E, string file = __FILE__, size_t line = __LINE__, Args...)
190 		(auto ref Args args) 
191 {
192 	T ret;
193 	ret.set!(E,file,line)(args);
194 	return ret;
195 }
196 
197 pure unittest {
198 	alias NullInt = Null!(int, NullPointerException, Exception);
199 	NullInt ni;
200 	pragma(msg, NullInt.sizeof);
201 	assert(ni.isNull);
202 	assert(!ni.isNotNull);
203 
204 	ni = 10;
205 	assert(ni.get() == 10);
206 }
207 
208 pure unittest {
209 	alias NullInt = Null!(int, NullPointerException, Exception);
210 	NullInt ni;
211 
212 	string f = __FILE__;
213 	int l = __LINE__ + 1;
214 	ni.set!NullPointerException();
215 	assert(ni.getError() == NullInt.ErrorType.NullPointerException);
216 
217 	NullPointerException npe;
218 	try {
219 		ni.rethrow();
220 	} catch(NullPointerException e) {
221 		npe = e;
222 	}
223 	assert(npe !is null);
224 	assert(npe.file == f, npe.file);
225 	assert(npe.line == l);
226 }
227 
228 pure unittest {
229 	alias NullInt = Null!(int, NullPointerException, Exception);
230 	auto ni = makeNull!NullInt(10);
231 	assert(ni.isNotNull);
232 	assert(ni.get() == 10);
233 }
234 
235 pure @safe unittest {
236 	alias NullT = Null!(float);
237 	NullT nf;
238 }
239 
240 pure unittest {
241 	alias NullInt = Null!(int, NullPointerException, Exception);
242 	auto ni = makeNull!(NullInt,NullPointerException)();
243 	assert(ni.isNull);
244 }
245 
246 pure unittest {
247 	alias NullInt = Null!(int, NullPointerException, Exception);
248 	string msg = "some message";
249 	auto ni = makeNull!(NullInt,Exception)(msg);
250 	assert(ni.isNull);
251 
252 	try {
253 		ni.rethrow();
254 	} catch(Exception e) {
255 		assert(msg == e.msg);
256 		return;
257 	}
258 	assert(false, "Wrong Exception rethrown");
259 }
260 
261 pure unittest {
262 	alias NullInt = Null!(int, NullPointerException, Exception);
263 	NullInt ni;
264 	assert(ni.isNull);
265 	assert(!ni.isNotNull);
266 
267 	ni = 10;
268 	assert(!ni.isNull);
269 	assert(ni.isNotNull);
270 	assert(ni.get() == 10);
271 
272 	ni.set!NullPointerException();
273 	assert(ni.isNull);
274 }