1 module arsd.ranged;
2 
3 import std.conv;
4 import std.traits;
5 
6 class BoundedOverflowException : Exception {
7 	this(string msg, string file = __FILE__, size_t line = __LINE__) {
8 		super(msg, file, line);
9 	}
10 }
11 
12 template WrapAround(T, T min = T.min, T max = T.max) {
13 	// FIXME - make this work in more places
14 	static assert(isIntegral!T);
15 	static assert(min == T.min);
16 	static assert(max == T.max);
17 	alias T WrapAround;
18 }
19 
20 // fIXME ME
21 template Saturated() {}
22 
23 template Bounded(T, T min = T.min, T max = T.max) {
24 	void onOverflow(string file, int line, T _payload, T rhs) {
25 		throw new BoundedOverflowException("Overflow (payload: " ~ to!string(_payload) ~ ")", file, line);
26 	}
27 
28 	alias BoundedBase!(T, onOverflow, min, max) Bounded;
29 }
30 
31 struct BoundedBase(T, alias onOverflow, T min, T max) {
32 	T _payload;
33 
34 	static string runCheckCode() { return q{
35 		static if(isIntegral!T) {
36 			static if(isSigned!T)
37 				asm { jo overflow; }
38 			else
39 				asm { jc overflow; }
40 		}
41 
42 		if(_payload < min) goto overflow;
43 		if(_payload > max) goto overflow;
44 
45 		goto ok;
46 
47 		overflow:
48 			onOverflow(file, line, _payload, rhs);
49 		ok:
50 			;
51 		};
52 	}
53 
54 	T opUnary(string op, string file = __FILE__, int line = __LINE__)() {
55 		static assert(0, "I don't think these ops work with the overflow check.");
56 		mixin("_payload " ~ op ~ ";");
57 
58 		T rhs = 1;
59 		mixin(runCheckCode());
60 	}
61 
62 	T opBinary(string op, string file = __FILE__, int line = __LINE__)(T rhs) {
63 		T tmp = void;
64 		mixin("tmp = _payload " ~ op ~ "rhs;");
65 		_payload = tmp;
66 
67 		mixin(runCheckCode());
68 		return tmp;
69 	}
70 
71 	T opOpAssign(string op, string file = __FILE__, int line = __LINE__)(T rhs) {
72 		T tmp = void;
73 		mixin("tmp = _payload " ~ op ~ "rhs;");
74 		_payload = tmp;
75 
76 		mixin(runCheckCode());
77 		return _payload;
78 	}
79 
80 	T opAssign(T rhs, string file = __FILE__, int line = __LINE__) {
81 		_payload = rhs;
82 
83 		mixin(runCheckCode());
84 		return _payload;
85 	}
86 
87 	string toString() {
88 		return to!string(_payload);
89 	}
90 
91 	alias _payload this;
92 }
93 
94 
95 /**
96 	NotNull ensures a null value can never be stored.
97 
98 	* You must initialize it when declared
99 
100 	* You must never assign the null literal to it (this is a compile time error)
101 
102 	* If you assign a null value at runtime to it, it will immediately throw an Error
103 	  at the point of assignment.
104 
105         NotNull!T can be substituted for T at any time, but T cannot become
106 	NotNull without some attention: either declaring NotNull!T, or using
107 	the convenience function, notNull.
108 
109 	Examples:
110 	---
111 		int myInt;
112 		NotNull!(int *) not_null = &myInt;
113 		// you can now use variable not_null anywhere you would
114 		// have used a regular int*, but with the assurance that
115 		// it never stored null.
116 	---
117 */
118 struct NotNull(T) if(__traits(compiles, { T t; assert(t is null); }))
119 {
120     private T _notNullData;
121     @property inout(T) _notNullDataHelper() inout
122     {
123         assert(_notNullData !is null); // sanity check of invariant
124         return _notNullData;
125     }
126     // Apparently a compiler bug - the invariant being uncommented breaks all kinds of stuff.
127     // invariant() { assert(_notNullData !is null); }
128 
129     alias _notNullDataHelper this; /// this is substitutable for the regular (nullable) type
130     @disable this();
131 
132     bool opCast(T:bool)() {
133     writeln("here");
134 	return _notNullData !is null;
135     }
136 
137     // this could arguably break the static type check because
138     // you can assign it from a variable that is null.. but I
139     // think it is important that NotNull!Object = new Object();
140     // works, without having to say assumeNotNull(new Object())
141     // for convenience of using with local variables.
142 
143     /// constructs with a runtime not null check (via assert())
144     this(T value)
145     {
146         assert(value !is null);
147         _notNullData = value;
148     }
149 
150     @disable this(typeof(null)); /// the null literal can be caught at compile time
151     @disable typeof(this) opAssign(typeof(null)); /// ditto
152 
153     /// .
154     NotNull!T opAssign(NotNull!T rhs)
155     {
156         this._notNullData = rhs._notNullData;
157         return this;
158     }
159 }
160 
161 /// A convenience function to construct a NotNull value from something you know isn't null.
162 NotNull!T assumeNotNull(T)(T t)
163 {
164     return NotNull!T(t); // note the constructor asserts it is not null
165 }
166 
167 /// A convenience function to check for null. If you pass null, it will throw an exception. Otherwise, return NotNull!T.
168 NotNull!T enforceNotNull(T)(T t)
169 {
170     enforce(t !is null);
171     return NotNull!T(t);
172 }
173 
174 unittest
175 {
176     import core.exception;
177     import std.exception;
178 
179     void NotNullCompiliationTest1()() // I'm making these templates to defer compiling them
180     {
181         NotNull!(int*) defaultInitiliation; // should fail because this would be null otherwise
182     }
183     assert(!__traits(compiles, NotNullCompiliationTest1!()()));
184 
185     void NotNullCompiliationTest2()()
186     {
187         NotNull!(int*) defaultInitiliation = null; // should fail here too at compile time
188     }
189     assert(!__traits(compiles, NotNullCompiliationTest2!()()));
190 
191     int dummy;
192     NotNull!(int*) foo = &dummy;
193 
194     assert(!__traits(compiles, foo = null)); // again, literal null is caught at compile time
195 
196     int* test;
197 
198     test = &dummy;
199 
200     foo = assumeNotNull(test); // should be fine
201 
202     void bar(int* a) {}
203 
204     // these should both compile, since NotNull!T is a subtype of T
205     bar(test);
206     bar(foo);
207 
208     void takesNotNull(NotNull!(int*) a) { }
209 
210     assert(!__traits(compiles, takesNotNull(test))); // should not work; plain int might be null
211     takesNotNull(foo); // should be fine
212 
213     takesNotNull(assumeNotNull(test)); // this should work too
214     assert(!__traits(compiles, takesNotNull(assumeNotNull(null)))); // notNull(null) shouldn't compile
215     test = null; // reset our pointer
216 
217     assertThrown!AssertError(takesNotNull(assumeNotNull(test))); // test is null now, so this should throw an assert failure
218 
219     void takesConstNotNull(in NotNull!(int *) a) {}
220 
221     test = &dummy; // make it valid again
222     takesConstNotNull(assumeNotNull(test)); // should Just Work
223 
224     NotNull!(int*) foo2 = foo; // we should be able to assign NotNull to other NotNulls too
225     foo2 = foo; // including init and assignment
226 }
227 
228 
229 
230 NotNull!T checkNull(T)(T a) {
231 	NotNull!T t = cast(T) 1; // just so it isn't null...
232 	if(a is null)
233 		t._notNullData = null; // this is normally unacceptable but we use it here for the magic of if(auto f = ...)
234 	else
235 		t = assumeNotNull(a);
236 	return t;
237 }
238 
239 
240 import std.stdio;
241 
242 void main() {
243 	int* a = null;
244 	if(auto b = checkNull(a)) {
245 		static assert(is(typeof(b) == NotNull!(int*)));
246 		assert(b !is null);
247 	} else {
248 		writefln("was null");
249 	}
250 
251 /*
252 	Bounded!(uint) a;
253 
254 	a = 0; // uint.max - 2;
255 
256 	//a--;
257 	a -= 10;
258 
259 	a += 5;
260 	writefln("%s", a);
261 
262 	a += 5;
263 	writefln("%s", a);
264 
265 	a += 5;
266 */
267 }
Suggestion Box / Bug Report