1 module gfx.core.typecons; 2 3 import std.range.primitives : isInputRange; 4 5 /// template that resolves to true if an object of type T can be assigned to null 6 template isNullAssignable(T) { 7 enum isNullAssignable = 8 is(typeof((inout int = 0) { 9 T t = T.init; 10 t = null; 11 })); 12 } 13 14 version(unittest) { 15 private interface ITest {} 16 private class CTest {} 17 private struct STest {} 18 static assert( isNullAssignable!ITest); 19 static assert( isNullAssignable!CTest); 20 static assert(!isNullAssignable!STest); 21 static assert( isNullAssignable!(STest*)); 22 } 23 24 /// constructs an option from a value 25 auto some(T)(T val) { 26 static if (isNullAssignable!T) { 27 return Option!T(val); 28 } 29 else { 30 import std.traits : Unqual; 31 return Option!(Unqual!T)(val); 32 } 33 } 34 35 /// symbolic value that constructs an Option in none state 36 enum none(T) = Option!(T).init; 37 38 /// Check that init value yields a none 39 unittest { 40 41 auto vopt = none!int; 42 assert(vopt.isNone); 43 vopt = 12; 44 assert(vopt.isSome); 45 assert(vopt.front == 12); 46 47 // auto ropt = none!CTest; 48 // assert(ropt.isNone); 49 // assert(ropt._val is null); 50 // ropt = new CTest; 51 // assert(vopt.isSome); 52 } 53 54 auto option(R)(R input) if (isInputRange!R) 55 { 56 import std.range.primitives : ElementType; 57 alias T = ElementType!R; 58 Option!T res; 59 60 if (!input.empty) { 61 res = input.front; 62 input.popFront(); 63 assert(input.empty, "attempt to build Option with more than one element)"); 64 } 65 66 return res; 67 } 68 69 70 struct Option(T) 71 { 72 import std.traits : Unqual; 73 alias ValT = Unqual!T; 74 private ValT _val = ValT.init; 75 76 static if (isNullAssignable!ValT) { 77 this(inout ValT val) inout { 78 _val = val; 79 } 80 81 @property bool isSome() const { 82 return _val !is null; 83 } 84 85 @property bool isNone() const { 86 return _val is null; 87 } 88 89 void setNone() { 90 _val = null; 91 } 92 93 void opAssign()(T val) { 94 _val = val; 95 } 96 } 97 else { 98 private bool _isSome = false; 99 100 this(inout ValT val) inout { 101 _val = val; 102 _isSome = true; 103 } 104 105 @property bool isSome() const { 106 return _isSome; 107 } 108 109 @property bool isNone() const { 110 return !_isSome; 111 } 112 113 void setNone() { 114 .destroy(_val); 115 _isSome = false; 116 } 117 118 void opAssign()(ValT val) { 119 _val = val; 120 _isSome = true; 121 } 122 } 123 124 // casting to type that have implicit cast available (e.g Option!int to Option!uint) 125 auto opCast(V : Option!U, U)() if (is(T : U)) { 126 return Option!U(val_); 127 } 128 129 @property ref inout(ValT) get() inout @safe pure nothrow 130 { 131 enum message = "Called `get' on none Option!" ~ T.stringof ~ "."; 132 assert(isSome, message); 133 return _val; 134 } 135 136 template toString() 137 { 138 import std.format : FormatSpec, formatValue; 139 // Needs to be a template because of DMD @@BUG@@ 13737. 140 void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) 141 { 142 if (isNull) 143 { 144 sink.formatValue("Option.none", fmt); 145 } 146 else 147 { 148 sink.formatValue(_value, fmt); 149 } 150 } 151 152 // Issue 14940 153 void toString()(scope void delegate(const(char)[]) @safe sink, FormatSpec!char fmt) 154 { 155 if (isNull) 156 { 157 sink.formatValue("Option.none", fmt); 158 } 159 else 160 { 161 sink.formatValue(_value, fmt); 162 } 163 } 164 } 165 166 // range interface 167 168 @property bool empty() const { 169 return isNone; 170 } 171 172 @property size_t length() const { 173 return isSome ? 1 : 0; 174 } 175 176 @property void popFront() { 177 setNone(); 178 } 179 180 static if (isNullAssignable!ValT) { 181 @property inout(ValT) front() inout { 182 return get; 183 } 184 @property Option!(inout(ValT)) save() inout { 185 return Option!(inout(ValT))(_val); 186 } 187 } 188 else { 189 @property ValT front() const { 190 return get; 191 } 192 193 @property Option!ValT save() const { 194 return isSome ? Option!ValT(_val) : none!T; 195 } 196 } 197 198 } 199 200 201 template isOption(T) 202 { 203 import std.traits : TemplateOf; 204 205 static if (__traits(compiles, TemplateOf!T)) { 206 enum isOption = __traits(isSame, TemplateOf!T, Option); 207 } 208 else { 209 enum isOption = false; 210 } 211 } 212 213 static assert (isOption!(Option!int)); 214 // static assert (isOption!(Option!Object)); 215 static assert (!isOption!(Object)); 216 217 218 /// execute fun with option value as parameter if option isSome 219 auto ifSome(alias fun, OptT)(OptT opt) { 220 static assert(isOption!OptT, "ifSome must be called with Option"); 221 if (opt.isSome) { 222 fun(opt.get); 223 } 224 return opt; 225 } 226 /// execute fun without parameter if option isNone 227 auto ifNone(alias fun, OptT)(OptT opt) { 228 static assert(isOption!OptT, "ifNone must be called with Option"); 229 if (opt.isNone) { 230 fun(); 231 } 232 return opt; 233 }