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 }