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 }