1 /**
2  * This module defines an Option!T type and a related Some!T type for
3  * dealing nullable data in a safe manner.
4  */
5 module dstruct.option;
6 
7 import std.traits: isPointer;
8 
9 /**
10  * This type represents a value which cannot be null by its contracts.
11  */
12 struct Some(T) if (is(T == class) || isPointer!T) {
13 private:
14     T _value;
15 public:
16     /// Disable default construction for Some!T types.
17     @disable this();
18 
19     /**
20      * Construct this object by wrapping a given value.
21      *
22      * Params:
23      *     value = The value to create the object with.
24      */
25     @safe pure nothrow
26     this(U)(inout(U) value) inout if(is(U : T))
27     in {
28         assert(value !is null, "A null value was given to Some.");
29     } body {
30         _value = value;
31     }
32 
33     /**
34      * Get the value from this object.
35      *
36      * Returns: The value wrapped by this object.
37      */
38     @safe pure nothrow
39     @property inout(T) get() inout
40     out(value) {
41         assert(value !is null, "Some returned null!");
42     } body {
43         return _value;
44     }
45 
46     /**
47      * Assign another value to this object.
48      *
49      * Params:
50      *     value = The value to set.
51      */
52     @safe pure nothrow
53     void opAssign(U)(U value) if(is(U : T))
54     in {
55         assert(value !is null, "A null value was given to Some.");
56     } body {
57         _value = value;
58     }
59 
60     /// Implicitly convert Some!T objects to T.
61     alias get this;
62 }
63 
64 /**
65  * A helper function for constructing Some!T values.
66  *
67  * Params:
68  *     value = A value to wrap.
69  *
70  * Returns: The value wrapped in a non-nullable type.
71  */
72 @safe pure nothrow
73 inout(Some!T) some(T)(inout(T) value) {
74     return inout(Some!T)(value);
75 }
76 
77 // Test basic usage.
78 unittest {
79     class Klass {}
80     struct Struct {}
81 
82     Some!Klass k = new Klass();
83     k = new Klass();
84 
85     Klass k2 = k;
86 
87     Some!(Struct*) s = new Struct();
88 
89     Struct* s1 = s;
90 }
91 
92 // Test immutable
93 unittest {
94     class Klass {}
95 
96     immutable(Some!Klass) k = new immutable Klass();
97 }
98 
99 // Test class hierarchies.
100 unittest {
101     class Animal {}
102     class Dog : Animal {}
103 
104     Some!Animal a = new Animal();
105     a = new Dog();
106 
107     auto d = new Dog();
108 
109     d = cast(Dog) a;
110 
111     assert(d !is null);
112 
113     Some!Dog d2 = new Dog();
114 
115     Animal a2 = d2;
116 }
117 
118 // Test conversion between wrapper types when wrapped types are compatible.
119 unittest {
120     class Animal {}
121     class Dog : Animal {}
122 
123     Some!Animal a = new Animal();
124     Some!Dog d = new Dog();
125 
126     a = d;
127 }
128 
129 // Test the wrapper function.
130 unittest {
131     class Klass {}
132 
133     auto m = some(new Klass());
134     auto c = some(new const Klass());
135     auto i = some(new immutable Klass());
136 
137     assert(is(typeof(m) == Some!Klass));
138     assert(is(typeof(c) == const(Some!Klass)));
139     assert(is(typeof(i) == immutable(Some!Klass)));
140 }
141 
142 /**
143  * This type represents an optional value for T.
144  *
145  * This is a means of explicitly dealing with null values in every case.
146  */
147 struct Option(T) if(is(T == class) || isPointer!T) {
148 private:
149     T _value;
150 public:
151     /**
152      * Construct this object by wrapping a given value.
153      *
154      * Params:
155      *     value = The value to create the object with.
156      */
157     @safe pure nothrow
158     this(U)(inout(U) value) inout if(is(U : T)) {
159         _value = value;
160     }
161 
162     /**
163      * Get the value from this object.
164      *
165      * Contracts ensure this value isn't null.
166      *
167      * Returns: Some value from this object.
168      */
169     @safe pure nothrow
170     @property Some!T get()
171     in {
172         assert(_value !is null, "get called for a null Option type!");
173     } body {
174         return Some!T(_value);
175     }
176 
177     /// ditto
178     @trusted pure nothrow
179     @property const(Some!T) get() const
180     in {
181         assert(_value !is null, "get called for a null Option type!");
182     } body {
183         return Some!T(cast(T) _value);
184     }
185 
186     /// ditto
187     @trusted pure nothrow
188     @property immutable(Some!T) get() immutable
189     in {
190         assert(_value !is null, "get called for a null Option type!");
191     } body {
192         return Some!T(cast(T) _value);
193     }
194 
195     /**
196      * Returns: True if the value this option type does not hold a value.
197      */
198     @trusted pure nothrow
199     @property bool isNull() const {
200         return _value is null;
201     }
202 
203     /**
204      * Assign another value to this object.
205      *
206      * Params:
207      *     value = The value to set.
208      */
209     @safe pure nothrow
210     void opAssign(U)(U value) if(is(U : T)) {
211         _value = value;
212     }
213 
214     /**
215      * Permit foreach over an option type.
216      *
217      * Example:
218      * ---
219      *     Option!T value = null;
220      *
221      *     foreach(someValue; value) {
222      *         // We never enter this loop body.
223      *     }
224      *
225      *     value = new T();
226      *
227      *     foreach(someValue; value) {
228      *         // We enter this loop body exactly once.
229      *     }
230      * ---
231      */
232     int opApply(int delegate(Some!T) dg) {
233         if (_value !is null) {
234             return dg(Some!T(_value));
235         }
236 
237         return 0;
238     }
239 
240     /// ditto
241     int opApply(int delegate(const(Some!T)) dg) const {
242         if (_value !is null) {
243             return dg(cast(const) Some!T(cast(T) _value));
244         }
245 
246         return 0;
247     }
248 
249     /// ditto
250     int opApply(int delegate(immutable(Some!T)) dg) immutable {
251         if (_value !is null) {
252             return dg(cast(immutable) Some!T(cast(T) _value));
253         }
254 
255         return 0;
256     }
257 
258     /// Reverse iteration is exactly the same as forward iteration.
259     alias opApplyReverse = opApply;
260 }
261 
262 /**
263  * A helper function for constructing Option!T values.
264  *
265  * Params:
266  *     value = A value to wrap.
267  *
268  * Returns: The value wrapped in an option type.
269  */
270 @safe pure nothrow
271 inout(Option!T) option(T)(inout(T) value) {
272     return inout(Option!T)(value);
273 }
274 
275 /// ditto
276 @safe pure nothrow
277 inout(Option!T) option(T)(inout(Some!T) value) {
278     return option(value._value);
279 }
280 
281 // Test basic usage for Option
282 unittest {
283     class Klass {}
284     struct Struct {}
285 
286     Option!Klass k = new Klass();
287     k = new Klass();
288 
289     Klass k2 = k.get;
290 
291     Option!(Struct*) s = new Struct();
292 
293     Struct* s1 = s.get;
294 }
295 
296 // Test class hierarchies for Option
297 unittest {
298     class Animal {}
299     class Dog : Animal {}
300 
301     Option!Animal a = new Animal();
302     a = new Dog();
303 
304     auto d = new Dog();
305 
306     d = cast(Dog) a.get;
307 
308     assert(d !is null);
309 
310     Option!Dog d2 = new Dog();
311 
312     Animal a2 = d2.get;
313 }
314 
315 // Test get across constness.
316 unittest {
317     class Klass {}
318 
319     Option!Klass m = new Klass();
320     const Option!Klass c = new const Klass();
321     immutable Option!Klass i = new immutable Klass();
322 
323     auto someM = m.get();
324     auto someC = c.get();
325     auto someI = i.get();
326 
327     assert(is(typeof(someM) == Some!Klass));
328     assert(is(typeof(someC) == const(Some!Klass)));
329     assert(is(typeof(someI) == immutable(Some!Klass)));
330 }
331 
332 // Test foreach on option across constness.
333 unittest {
334     class Klass {}
335 
336     Option!Klass m = new Klass();
337     const Option!Klass c = new const Klass();
338     immutable Option!Klass i = new immutable Klass();
339 
340     size_t mCount = 0;
341     size_t cCount = 0;
342     size_t iCount = 0;
343 
344     foreach(val; m) {
345         ++mCount;
346     }
347 
348     foreach(val; c) {
349         ++cCount;
350     }
351 
352     foreach(val; i) {
353         ++iCount;
354     }
355 
356     assert(mCount == 1);
357     assert(cCount == 1);
358     assert(iCount == 1);
359 }
360 
361 // Test empty foreach
362 unittest {
363     class Klass {}
364 
365     Option!Klass m = null;
366     const Option!Klass c = null;
367     immutable Option!Klass i = null;
368 
369     size_t mCount = 0;
370     size_t cCount = 0;
371     size_t iCount = 0;
372 
373     foreach(val; m) {
374         ++mCount;
375     }
376 
377     foreach(val; c) {
378         ++cCount;
379     }
380 
381     foreach(val; i) {
382         ++iCount;
383     }
384 
385     assert(mCount == 0);
386     assert(cCount == 0);
387     assert(iCount == 0);
388 }
389 
390 // Test foreach_reverse on option across constness.
391 unittest {
392     class Klass {}
393 
394     Option!Klass m = new Klass();
395     const Option!Klass c = new const Klass();
396     immutable Option!Klass i = new immutable Klass();
397 
398     size_t mCount = 0;
399     size_t cCount = 0;
400     size_t iCount = 0;
401 
402     foreach_reverse(val; m) {
403         ++mCount;
404     }
405 
406     foreach_reverse(val; c) {
407         ++cCount;
408     }
409 
410     foreach_reverse(val; i) {
411         ++iCount;
412     }
413 
414     assert(mCount == 1);
415     assert(cCount == 1);
416     assert(iCount == 1);
417 }
418 
419 // Test empty foreach_reverse
420 unittest {
421     class Klass {}
422 
423     Option!Klass m = null;
424     const Option!Klass c = null;
425     immutable Option!Klass i = null;
426 
427     size_t mCount = 0;
428     size_t cCount = 0;
429     size_t iCount = 0;
430 
431     foreach_reverse(val; m) {
432         ++mCount;
433     }
434 
435     foreach_reverse(val; c) {
436         ++cCount;
437     }
438 
439     foreach_reverse(val; i) {
440         ++iCount;
441     }
442 
443     assert(mCount == 0);
444     assert(cCount == 0);
445     assert(iCount == 0);
446 }
447 
448 // Test setting Option from Some
449 unittest {
450     class Klass {}
451 
452     Option!Klass m = some(new Klass());
453     const Option!Klass c = some(new const Klass());
454     immutable Option!Klass i = some(new immutable Klass());
455 
456     Option!Klass m2 = option(some(new Klass()));
457     const Option!Klass c2 = option(some(new const Klass()));
458     immutable Option!Klass i2 = option(some(new immutable Klass()));
459 
460 
461     Option!Klass m3;
462 
463     m3 = some(new Klass());
464 }
465 
466 // Test isNull
467 unittest {
468     class Klass {}
469 
470     Option!Klass m;
471 
472     assert(m.isNull);
473 
474     m = new Klass();
475 
476     assert(!m.isNull);
477 }
478 
479 /**
480  * This type represents a range over an optional type.
481  *
482  * This is a RandomAccessRange.
483  */
484 struct OptionRange(T) if(is(T == class) || isPointer!T) {
485 private:
486     T _value;
487 public:
488     /**
489      * Construct this range by wrapping a given value.
490      *
491      * Params:
492      *     value = The value to create the range with.
493      */
494     @safe pure nothrow
495     this(U)(U value) if(is(U : T)) {
496         _value = value;
497     }
498 
499     ///
500     @trusted pure nothrow
501     void popFront()
502     in {
503         assert(_value !is null, "Attempted to pop an empty range!");
504     } body {
505         static if(is(T == const) || is(T == immutable)) {
506             // Force the pointer held here into being null.
507             *(cast(void**) &_value) = null;
508         } else {
509             _value = null;
510         }
511     }
512 
513     ///
514     alias popBack = popFront;
515 
516     ///
517     @safe pure nothrow
518     @property inout(T) front() inout {
519         return _value;
520     }
521 
522     ///
523     alias back = front;
524 
525     ///
526     @safe pure nothrow
527     @property bool empty() const {
528         return _value is null;
529     }
530 
531     ///
532     @safe pure nothrow
533     @property typeof(this) save() {
534         return this;
535     }
536 
537     ///
538     @safe pure nothrow
539     @property size_t length() const {
540         return _value !is null ? 1 : 0;
541     }
542 
543     ///
544     @safe pure nothrow
545     inout(T) opIndex(size_t index) inout
546     in {
547         assert(index <= length, "Index out of bounds!");
548     } body {
549         return _value;
550     }
551 }
552 
553 /**
554  * Create an OptionRange from an Option type.
555  *
556  * The range shall be empty when the option has no value,
557  * and it shall have one item when the option has a value.
558  *
559  * Params:
560  *     optionalValue = An optional value.
561  *
562  * Returns: A range of 0 or 1 values.
563  */
564 @safe pure nothrow
565 OptionRange!T range(T)(Option!T optionalValue) {
566     if (optionalValue.isNull) {
567         return typeof(return).init;
568     }
569 
570     return OptionRange!T(optionalValue.get);
571 }
572 
573 /// ditto
574 @trusted pure nothrow
575 OptionRange!(const(T)) range(T)(const(Option!T) optionalValue) {
576     return cast(typeof(return)) range(cast(Option!T)(optionalValue));
577 }
578 
579 /// ditto
580 @trusted pure nothrow
581 OptionRange!(immutable(T)) range(T)(immutable(Option!T) optionalValue) {
582     return cast(typeof(return)) range(cast(Option!T)(optionalValue));
583 }
584 
585 // Test creating ranges from option types.
586 unittest {
587     class Klass {}
588 
589     Option!Klass m = new Klass();
590     const(Option!Klass) c = new const Klass();
591     immutable(Option!Klass) i = new immutable Klass();
592 
593     auto mRange = m.range;
594     auto cRange = c.range;
595     auto iRange = i.range;
596 
597     assert(!mRange.empty);
598     assert(!cRange.empty);
599     assert(!iRange.empty);
600 
601     assert(mRange.length == 1);
602     assert(cRange.length == 1);
603     assert(iRange.length == 1);
604 
605     assert(mRange[0] is m.get);
606     assert(cRange[0] is c.get);
607     assert(iRange[0] is i.get);
608 
609     assert(mRange.front is mRange.back);
610     assert(cRange.front is cRange.back);
611     assert(iRange.front is iRange.back);
612 
613     auto mRangeSave = mRange.save;
614     auto cRangeSave = cRange.save;
615     auto iRangeSave = iRange.save;
616 
617     mRange.popFront();
618     cRange.popFront();
619     iRange.popFront();
620 
621     assert(mRange.empty);
622     assert(cRange.empty);
623     assert(iRange.empty);
624 
625     assert(mRange.length == 0);
626     assert(cRange.length == 0);
627     assert(iRange.length == 0);
628 
629     assert(!mRangeSave.empty);
630     assert(!cRangeSave.empty);
631     assert(!iRangeSave.empty);
632 }
633 
634 unittest {
635     import std.range;
636 
637     // Test that all of the essential properties hold for this type.
638     static assert(isInputRange!(OptionRange!(void*)));
639     static assert(isForwardRange!(OptionRange!(void*)));
640     static assert(isBidirectionalRange!(OptionRange!(void*)));
641     static assert(isRandomAccessRange!(OptionRange!(void*)));
642     static assert(!isInfinite!(OptionRange!(void*)));
643 }
644 
645 
646 // Test std.algorithm integration
647 unittest {
648     class Klass {
649         int x = 3;
650     }
651 
652     import std.algorithm;
653 
654     Option!Klass foo = new Klass();
655 
656     auto squareSum(R)(R range) {
657         return reduce!((x, y) => x + y)(0, range.map!(val => val.x * val.x));
658     }
659 
660     auto fooSum = squareSum(foo.range);
661 
662     import std.stdio;
663 
664     assert(fooSum == 9);
665 
666     Option!Klass bar;
667 
668     auto barSum = squareSum(bar.range);
669 
670     assert(barSum == 0);
671 }