1 module nxt.optional;
2 
3 import nxt.nullable_traits : isNullable;
4 
5 private struct None {}
6 
7 struct Optional(T)
8 {
9     import core.internal.traits : Unqual;
10     private Unqual!T value;
11 
12     static if (!isNullable!T)
13         private bool present;
14 
15     this(T value)
16     {
17         opAssign(value);
18     }
19 
20     this(None) {}
21 
22     void opAssign(T value)
23     {
24         this.value = value;
25         static if (!isNullable!T)
26             present = true;
27     }
28 
29     void opAssign(None)
30     {
31         static if (isNullable!T)
32             value = null;
33         else
34             present = false;
35     }
36 
37 	static if (isNullable!T)
38 		bool isPresent() const => value !is null;
39 	else
40 		bool isPresent() const => present;
41 
42     T get() in(isPresent) => value;
43 
44     T or(lazy T alternativeValue) => isPresent ? value : alternativeValue;
45 
46     bool empty() const @property => !isPresent;
47 
48     T front() => get;
49 
50     void popFront()
51     {
52         static if (isNullable!T)
53             value = null;
54         else
55             present = false;
56     }
57 
58     size_t length() const => isPresent ? 1 : 0;
59 
60     // auto ref opDispatch(string name, Args...)(auto ref Args args)
61     // {
62     //     import std.traits : PointerTarget, isPointer;
63     //     import dlp.core.traits : hasField, TypeOfMember, getMember;
64 
65     //     static if (isPointer!T)
66     //         alias StoredType = PointerTarget!T;
67     //     else
68     //         alias StoredType = T;
69 
70     //     static if (is(StoredType == class) || is(StoredType == struct))
71     //     {
72     //         static if (hasField!(StoredType, name))
73     //         {
74     //             alias FieldType = TypeOfMember!(StoredType, name);
75 
76     //             if (isPresent)
77     //                 return optional(value.getMember!name);
78     //             else
79     //                 return none!FieldType;
80     //         }
81     //         else
82     //         {
83     //             alias ReturnType = typeof(__traits(getMember, value, name)(args));
84 
85     //             if (isPresent)
86     //                 return optional(__traits(getMember, value, name)(args));
87     //             else
88     //                 return none!ReturnType;
89     //         }
90     //     }
91     //     else
92     //     {
93     //         return optional(value.getMember!name);
94     //     }
95 
96     //     assert(0);
97     // }
98 
99     // @safe pure nothrow @nogc unittest
100     // {
101     //     assert(Optional!Foo(Foo(3)).a.get == 3);
102     //     assert(Optional!Foo.init.a.empty);
103 
104     //     assert(Optional!Foo(Foo()).opDispatch!"c"(4).get == 4);
105     //     assert(Optional!Foo.init.c(4).empty);
106 
107     //     assert(Optional!Foo(Foo(1, new Bar(5))).b.a.get == 5);
108     //     assert(Optional!Foo(Foo(1)).b.a.empty);
109     // }
110 }
111 
112 @safe pure nothrow @nogc unittest
113 {
114     enum newVale = 4;
115     Optional!int a = 3;
116     a = newVale;
117     assert(a.get == newVale);
118 }
119 
120 @safe pure nothrow @nogc unittest
121 {
122     Optional!int a = 3;
123     a = none;
124     assert(!a.isPresent);
125 }
126 
127 @safe pure nothrow @nogc unittest
128 {
129     Optional!int a = 3;
130     assert(a.isPresent);
131 
132     Optional!(int*) b = null;
133     assert(!b.isPresent);
134 }
135 
136 @safe pure nothrow @nogc unittest
137 {
138     Optional!int a = 3;
139     assert(a.get == 3);
140 }
141 
142 @safe pure /*nothrow*/ unittest
143 {
144     Optional!int a = 3;
145     assert(a.or(4) == 3);
146 
147     Optional!int b = none;
148     assert(b.or(4) == 4);
149 }
150 
151 @safe pure nothrow @nogc unittest
152 {
153     Optional!int a = 3;
154     assert(!a.empty);
155 
156     Optional!int b = none;
157     assert(b.empty);
158 }
159 
160 @safe pure nothrow @nogc unittest
161 {
162     Optional!int a = 3;
163     assert(a.get == 3);
164 }
165 
166 /** Instantiate an `Optional` `value`. */
167 Optional!T optional(T)(T value) => Optional!T(value);
168 
169 ///
170 @safe pure nothrow @nogc unittest
171 {
172     Optional!int a = 3;
173     a.popFront();
174     assert(!a.isPresent);
175 }
176 
177 ///
178 @safe pure nothrow @nogc unittest
179 {
180     Optional!int a = 3;
181     assert(a.length == 1);
182 
183     Optional!int b = none;
184     assert(b.length == 0);
185 }
186 
187 ///
188 @safe pure nothrow @nogc unittest
189 {
190     assert(optional(3).isPresent);
191 }
192 
193 ///
194 @trusted pure nothrow @nogc unittest
195 {
196     int i;
197     assert(optional(&i).isPresent);
198     assert(!optional!(int*)(null).isPresent);
199 }
200 
201 ///
202 @safe pure nothrow @nogc unittest
203 {
204     import std.algorithm : map;
205     enum value = 3;
206     assert(optional(value).map!(e => e).front == value);
207 }
208 
209 Optional!T some(T)(T value)
210 in
211 {
212     static if (isNullable!T)
213         assert(value !is null);
214 }
215 do
216 {
217     Optional!T o;
218     o.value = value;
219     o.present = true;
220 
221     return o;
222 }
223 
224 ///
225 @safe pure nothrow @nogc unittest
226 {
227     assert(some(3).isPresent);
228 }
229 
230 @safe pure nothrow @nogc None none()
231 {
232     return None();
233 }
234 
235 Optional!T none(T)() => Optional!T.init;
236 
237 ///
238 @safe pure nothrow @nogc unittest
239 {
240     assert(!none!int.isPresent);
241 }
242 
243 version (unittest)
244 {
245     // Cannot put this inside the @safe pure nothrow @nogc unittest block due to
246     // https://issues.dlang.org/show_bug.cgi?id=19157
247     private struct Foo
248     {
249         int a;
250         Bar* b;
251         int c(int a) => a;
252         Bar d(int a) => Bar(a);
253     }
254 
255     private struct Bar
256     {
257         int a;
258         int foo(int a) => a;
259     }
260 }