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 }