1 /** 2 Provides safe dispatching utilities 3 */ 4 module optional.oc; 5 6 import optional.optional: Optional; 7 import std.typecons: Nullable; 8 import bolts.from; 9 10 private string autoReturn(string expression)() { 11 return ` 12 auto ref expr() { 13 return ` ~ expression ~ `; 14 } 15 ` ~ q{ 16 import optional.traits: isOptional; 17 auto ref val() { 18 // If the dispatched result is an Optional itself, we flatten it out so that client code 19 // does not have to do a.oc.member.oc.otherMember 20 static if (isOptional!(typeof(expr()))) { 21 return expr().front; 22 } else { 23 return expr(); 24 } 25 } 26 alias R = typeof(val()); 27 static if (is(R == void)) { 28 if (!value.empty) { 29 val(); 30 } 31 } else { 32 if (value.empty) { 33 return OptionalChain!R(no!R()); 34 } 35 static if (isOptional!(typeof(expr()))) { 36 // If the dispatched result is an optional, check if the expression is empty before 37 // calling val() because val() calls .front which would assert if empty. 38 if (expr().empty) { 39 return OptionalChain!R(no!R()); 40 } 41 } 42 return OptionalChain!R(some(val())); 43 } 44 }; 45 } 46 47 package struct OptionalChain(T) { 48 import std.traits: hasMember; 49 50 public Optional!T value; 51 alias value this; 52 53 this(Optional!T value) { 54 this.value = value; 55 } 56 57 this(T value) { 58 this.value = value; 59 } 60 61 static if (!hasMember!(T, "toString")) { 62 public string toString()() { 63 return value.toString; 64 } 65 } 66 67 static if (hasMember!(T, "empty")) { 68 public auto empty() { 69 if (value.empty) { 70 return no!(typeof(T.empty)); 71 } else { 72 return some(value.front.empty); 73 } 74 } 75 } else { 76 public auto empty() { 77 return value.empty; 78 } 79 } 80 81 static if (hasMember!(T, "front")) { 82 public auto front() { 83 if (value.empty) { 84 return no!(typeof(T.front)); 85 } else { 86 return some(value.front.front); 87 } 88 } 89 } else { 90 public auto front() { 91 return value.front; 92 } 93 } 94 95 static if (hasMember!(T, "popFront")) { 96 public auto popFront() { 97 if (value.empty) { 98 return no!(typeof(T.popFront)); 99 } else { 100 return some(value.front.popFront); 101 } 102 } 103 } else { 104 public auto popFront() { 105 return value.popFront; 106 } 107 } 108 109 public template opDispatch(string name) if (hasMember!(T, name)) { 110 import optional: no, some; 111 static if (is(typeof(__traits(getMember, T, name)) == function)) { 112 auto opDispatch(Args...)(auto ref Args args) { 113 mixin(autoReturn!("value.front." ~ name ~ "(args)")); 114 } 115 } else static if (__traits(isTemplate, mixin("T." ~ name))) { 116 // member template 117 template opDispatch(Ts...) { 118 enum targs = Ts.length ? "!Ts" : ""; 119 auto opDispatch(Args...)(auto ref Args args) { 120 mixin(autoReturn!("value.front." ~ name ~ targs ~ "(args)")); 121 } 122 } 123 } else { 124 // non-function field 125 auto opDispatch(Args...)(auto ref Args args) { 126 static if (Args.length == 0) { 127 mixin(autoReturn!("value.front." ~ name)); 128 } else static if (Args.length == 1) { 129 mixin(autoReturn!("value.front." ~ name ~ " = args[0]")); 130 } else { 131 static assert( 132 0, 133 "Dispatched " ~ T.stringof ~ "." ~ name ~ " was resolved to non-function field that has more than one argument", 134 ); 135 } 136 } 137 } 138 } 139 } 140 141 /** 142 Allows you to call dot operator on a nullable type or an optional. 143 144 If there is no value inside, or it is null, dispatching will still work but will 145 produce a series of no-ops. 146 147 Works with `std.typecons.Nullable` 148 149 If you try and call a manifest constant or static data on T then whether the manifest 150 or static immutable data is called depends on if the instance is valid. 151 152 Returns: 153 A type aliased to an Optional of whatever T.blah would've returned. 154 --- 155 struct A { 156 struct Inner { 157 int g() { return 7; } 158 } 159 Inner inner() { return Inner(); } 160 int f() { return 4; } 161 } 162 auto a = some(A()); 163 auto b = no!A; 164 auto c = no!(A*); 165 oc(a).inner.g; // calls inner and calls g 166 oc(b).inner.g; // no op. 167 oc(c).inner.g; // no op. 168 --- 169 */ 170 auto oc(T)(auto ref T value) if (from.bolts.traits.isNullTestable!T) { 171 return OptionalChain!T(value); 172 } 173 /// Ditto 174 auto oc(T)(auto ref Optional!T value) { 175 return OptionalChain!T(value); 176 } 177 /// Ditto 178 auto oc(T)(auto ref Nullable!T value) { 179 import optional: no; 180 if (value.isNull) { 181 return OptionalChain!T(no!T); 182 } 183 return OptionalChain!T(value.get); 184 }