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