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