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 dstruct.support;
8 
9 import std.traits;
10 import std.typecons;
11 
12 private struct SomeTypeMarker {}
13 
14 private enum isSomeType(T) = is(typeof(T.marker) == SomeTypeMarker);
15 
16 /**
17  * This type represents a value which cannot be null by its contracts.
18  */
19 struct Some(T) if (is(T == class) || isPointer!T) {
20 private:
21     enum marker = SomeTypeMarker.init;
22     T _value;
23 public:
24     /// Disable default construction for Some!T types.
25     @disable this();
26 
27     /**
28      * Construct this object by wrapping a given value.
29      *
30      * Params:
31      *     value = The value to create the object with.
32      */
33     @nogc @safe pure nothrow
34     this(U)(inout(U) value) inout if(is(U : T))
35     in {
36         assert(value !is null, "A null value was given to Some.");
37     } body {
38         static assert(
39             !is(U == typeof(null)),
40             "Some!(" ~ T.stringof ~ ") cannot be constructed with null."
41         );
42 
43         _value = value;
44     }
45 
46     /**
47      * Get the value from this object.
48      *
49      * Returns: The value wrapped by this object.
50      */
51     @nogc @safe pure nothrow
52     @property inout(T) get() inout
53     out(value) {
54         assert(value !is null, "Some returned null!");
55     } body {
56         return _value;
57     }
58 
59     /**
60      * Assign another value to this object.
61      *
62      * Params:
63      *     value = The value to set.
64      */
65     @nogc @safe pure nothrow
66     void opAssign(U)(U value) if(is(U : T))
67     in {
68         assert(value !is null, "A null value was given to Some.");
69     } body {
70         static assert(
71             !is(U == typeof(null)),
72             "Some!(" ~ T.stringof ~ ") cannot be assigned to with null."
73         );
74 
75         _value = value;
76     }
77 
78     /// Implicitly convert Some!T objects to T.
79     alias get this;
80 }
81 
82 /**
83  * A helper function for constructing Some!T values.
84  *
85  * Params:
86  *     value = A value to wrap.
87  *
88  * Returns: The value wrapped in a non-nullable type.
89  */
90 @nogc @safe pure nothrow
91 inout(Some!T) some(T)(inout(T) value) if (is(T == class) || isPointer!T) {
92     return inout(Some!T)(value);
93 }
94 
95 // Test basic usage.
96 unittest {
97     class Klass {}
98     struct Struct {}
99 
100     Some!Klass k = new Klass();
101     k = new Klass();
102 
103     Klass k2 = k;
104 
105     Some!(Struct*) s = new Struct();
106 
107     Struct* s1 = s;
108 }
109 
110 // Test immutable
111 unittest {
112     class Klass {}
113 
114     immutable(Some!Klass) k = new immutable Klass();
115 }
116 
117 // Test class hierarchies.
118 unittest {
119     class Animal {}
120     class Dog : Animal {}
121 
122     Some!Animal a = new Animal();
123     a = new Dog();
124 
125     auto d = new Dog();
126 
127     d = cast(Dog) a;
128 
129     assert(d !is null);
130 
131     Some!Dog d2 = new Dog();
132 
133     Animal a2 = d2;
134 }
135 
136 // Test conversion between wrapper types when wrapped types are compatible.
137 unittest {
138     class Animal {}
139     class Dog : Animal {}
140 
141     Some!Animal a = new Animal();
142     Some!Dog d = new Dog();
143 
144     a = d;
145 }
146 
147 // Test the wrapper function.
148 unittest {
149     class Klass {}
150 
151     auto m = some(new Klass());
152     auto c = some(new const Klass());
153     auto i = some(new immutable Klass());
154 
155     assert(is(typeof(m) == Some!Klass));
156     assert(is(typeof(c) == const(Some!Klass)));
157     assert(is(typeof(i) == immutable(Some!Klass)));
158 }
159 
160 /**
161  * This type represents an optional value for T.
162  *
163  * This is a means of explicitly dealing with null values in every case.
164  */
165 struct Option(T) if(is(T == class) || isPointer!T) {
166 private:
167     T _value;
168 public:
169     /**
170      * Construct this object by wrapping a given value.
171      *
172      * Params:
173      *     value = The value to create the object with.
174      */
175     @nogc @safe pure nothrow
176     this(U)(inout(U) value) inout if(is(U : T)) {
177         _value = value;
178     }
179 
180     /**
181      * Get the value from this object.
182      *
183      * Contracts ensure this value isn't null.
184      *
185      * Returns: Some value from this object.
186      */
187     @nogc @safe pure nothrow
188     @property Some!T get()
189     in {
190         assert(_value !is null, "get called for a null Option type!");
191     } body {
192         return Some!T(_value);
193     }
194 
195     /// ditto
196     @nogc @trusted pure nothrow
197     @property const(Some!T) get() const
198     in {
199         assert(_value !is null, "get called for a null Option type!");
200     } body {
201         return Some!T(cast(T) _value);
202     }
203 
204     /// ditto
205     @nogc @trusted pure nothrow
206     @property immutable(Some!T) get() immutable
207     in {
208         assert(_value !is null, "get called for a null Option type!");
209     } body {
210         return Some!T(cast(T) _value);
211     }
212 
213     static if(is(T == class)) {
214         /**
215          * Given some type U, perform a dynamic cast on the class reference
216          * held within this optional value, and return a new optional value
217          * which may be null if the cast fails.
218          *
219          * Returns: A casted optional value.
220          */
221         @nogc pure nothrow
222         inout(Option!U) dynamicCast(U)() inout {
223             return Option!U(cast(U) _value);
224         }
225     }
226 
227     /**
228      * Return some value from this reference, or a default value
229      * by calling a callable argument. (function pointer, delegate, etc.)
230      *
231      * The delegate can return a nullable type, but if the type is null
232      * an assertion error will be triggered, supposing the program is
233      * running in debug mode. The delegate may also return a Some type.
234      *
235      * Params:
236      *     dg = Some delegate returning a value.
237      *
238      * Returns: This value, if the delegate's value if it is null.
239      */
240     Some!T or(DG)(DG dg)
241     if (
242         isCallable!DG
243         && (ParameterTypeTuple!DG).length == 0
244         && is(ReturnType!DG : T)
245     ) {
246         if (_value is null) {
247             static if(isSomeType!(ReturnType!DG)) {
248                 return cast(Some!T) dg();
249             } else {
250                 return Some!T(dg());
251             }
252         }
253 
254         return some(_value);
255     }
256 
257     /// ditto
258     const(Some!T) or(DG)(DG dg) const
259     if (
260         isCallable!DG
261         && (ParameterTypeTuple!DG).length == 0
262         && is(Unqual!(ReturnType!DG) : Unqual!T)
263     ) {
264         if (_value is null) {
265             static if(isSomeType!(ReturnType!DG)) {
266                 return cast(const(Some!T)) dg();
267             } else {
268                 return const(Some!T)(dg());
269             }
270         }
271 
272         return some(_value);
273     }
274 
275     /// ditto
276     immutable(Some!T) or(DG)(DG dg) immutable
277     if (
278         isCallable!DG
279         && (ParameterTypeTuple!DG).length == 0
280         && is(Unqual!(ReturnType!DG) : Unqual!T)
281     ) {
282         if (_value is null) {
283             static if(isSomeType!(ReturnType!DG)) {
284                 return cast(immutable(Some!T)) dg();
285             } else {
286                 return immutable(Some!T)(dg());
287             }
288         }
289 
290         return some(_value);
291     }
292 
293     /**
294      * Returns: True if the value this option type does not hold a value.
295      */
296     @nogc @safe pure nothrow
297     @property bool isNull() const {
298         return _value is null;
299     }
300 
301     /**
302      * Assign another value to this object.
303      *
304      * Params:
305      *     value = The value to set.
306      */
307     @nogc @safe pure nothrow
308     void opAssign(U)(U value) if(is(U : T)) {
309         _value = value;
310     }
311 }
312 
313 /**
314  * A helper function for constructing Option!T values.
315  *
316  * Params:
317  *     value = A value to wrap.
318  *
319  * Returns: The value wrapped in an option type.
320  */
321 @nogc @safe pure nothrow
322 inout(Option!T) option(T)(inout(T) value) if (is(T == class) || isPointer!T) {
323     return inout(Option!T)(value);
324 }
325 
326 /// ditto
327 @nogc @safe pure nothrow
328 inout(Option!T) option(T)(inout(Some!T) value) {
329     return option(value._value);
330 }
331 
332 // Test basic usage for Option
333 unittest {
334     class Klass {}
335     struct Struct {}
336 
337     Option!Klass k = new Klass();
338     k = new Klass();
339 
340     Klass k2 = k.get;
341 
342     Option!(Struct*) s = new Struct();
343 
344     Struct* s1 = s.get;
345 }
346 
347 // Test class hierarchies for Option
348 unittest {
349     class Animal {}
350     class Dog : Animal {}
351 
352     Option!Animal a = new Animal();
353     a = new Dog();
354 
355     auto d = new Dog();
356 
357     d = cast(Dog) a.get;
358 
359     assert(d !is null);
360 
361     Option!Dog d2 = new Dog();
362 
363     Animal a2 = d2.get;
364 }
365 
366 // Test get across constness.
367 unittest {
368     class Klass {}
369 
370     Option!Klass m = new Klass();
371     const Option!Klass c = new const Klass();
372     immutable Option!Klass i = new immutable Klass();
373 
374     auto someM = m.get();
375     auto someC = c.get();
376     auto someI = i.get();
377 
378     assert(is(typeof(someM) == Some!Klass));
379     assert(is(typeof(someC) == const(Some!Klass)));
380     assert(is(typeof(someI) == immutable(Some!Klass)));
381 }
382 
383 // Test dynamicCast across constness.
384 unittest {
385     class Klass {}
386     class SubKlass {}
387 
388     Option!Klass m = new Klass();
389     const Option!Klass c = new const Klass();
390     immutable Option!Klass i = new immutable Klass();
391 
392     auto subM = m.dynamicCast!SubKlass;
393     auto subC = c.dynamicCast!SubKlass;
394     auto subI = i.dynamicCast!SubKlass;
395 
396     assert(is(typeof(subM) == Option!SubKlass));
397     assert(is(typeof(subC) == const(Option!SubKlass)));
398     assert(is(typeof(subI) == immutable(Option!SubKlass)));
399 }
400 
401 // Test .or, with the nice type qualifiers.
402 unittest {
403     class Klass {}
404 
405     @safe pure nothrow
406     void runTest() {
407         Option!Klass m;
408         const Option!Klass c;
409         immutable Option!Klass i;
410 
411         auto someM = m.or(()=> some(new Klass()));
412         auto someC = c.or(()=> some(new const(Klass)()));
413         auto someI = i.or(()=> some(new immutable(Klass)()));
414 
415         assert(is(typeof(someM) == Some!Klass));
416         assert(is(typeof(someC) == const(Some!Klass)));
417         assert(is(typeof(someI) == immutable(Some!Klass)));
418 
419         auto someOtherM = m.or(()=> new Klass());
420         auto someOtherC = c.or(()=> new const(Klass)());
421         auto someOtherI = i.or(()=> new immutable(Klass)());
422 
423         assert(is(typeof(someOtherM) == Some!Klass));
424         assert(is(typeof(someOtherC) == const(Some!Klass)));
425         assert(is(typeof(someOtherI) == immutable(Some!Klass)));
426     }
427 
428     runTest();
429 }
430 
431 // Test .or with subclasses
432 unittest {
433     class Klass {}
434     class SubKlass : Klass {}
435 
436     @safe pure nothrow
437     void runTest() {
438         Option!Klass m;
439         const Option!Klass c;
440         immutable Option!Klass i;
441 
442         auto someM = m.or(()=> some(new SubKlass()));
443         auto someC = c.or(()=> some(new const(SubKlass)()));
444         auto someI = i.or(()=> some(new immutable(SubKlass)()));
445 
446         assert(is(typeof(someM) == Some!Klass));
447         assert(is(typeof(someC) == const(Some!Klass)));
448         assert(is(typeof(someI) == immutable(Some!Klass)));
449 
450         auto someOtherM = m.or(()=> new SubKlass());
451         auto someOtherC = c.or(()=> new const(SubKlass)());
452         auto someOtherI = i.or(()=> new immutable(SubKlass)());
453 
454         assert(is(typeof(someOtherM) == Some!Klass));
455         assert(is(typeof(someOtherC) == const(Some!Klass)));
456         assert(is(typeof(someOtherI) == immutable(Some!Klass)));
457     }
458 
459     runTest();
460 }
461 
462 // Test .or with bad functions
463 unittest {
464     class Klass {}
465 
466     @system
467     Klass mutFunc() {
468         if (1 == 2) {
469             throw new Exception("");
470         }
471 
472         return new Klass();
473     }
474 
475     @system
476     const(Klass) constFunc() {
477         if (1 == 2) {
478             throw new Exception("");
479         }
480 
481         return new const(Klass)();
482     }
483 
484     @system
485     immutable(Klass) immutableFunc() {
486         if (1 == 2) {
487             throw new Exception("");
488         }
489 
490         return new immutable(Klass)();
491     }
492 
493     Option!Klass m;
494     const Option!Klass c;
495     immutable Option!Klass i;
496 
497     auto someM = m.or(&mutFunc);
498     auto someC = c.or(&constFunc);
499     auto someI = i.or(&immutableFunc);
500 
501     assert(is(typeof(someM) == Some!Klass));
502     assert(is(typeof(someC) == const(Some!Klass)));
503     assert(is(typeof(someI) == immutable(Some!Klass)));
504 }
505 
506 // Test setting Option from Some
507 unittest {
508     class Klass {}
509 
510     Option!Klass m = some(new Klass());
511     const Option!Klass c = some(new const Klass());
512     immutable Option!Klass i = some(new immutable Klass());
513 
514     Option!Klass m2 = option(some(new Klass()));
515     const Option!Klass c2 = option(some(new const Klass()));
516     immutable Option!Klass i2 = option(some(new immutable Klass()));
517 
518     Option!Klass m3;
519 
520     m3 = some(new Klass());
521 }
522 
523 // Test isNull
524 unittest {
525     class Klass {}
526 
527     Option!Klass m;
528 
529     assert(m.isNull);
530 
531     m = new Klass();
532 
533     assert(!m.isNull);
534 }
535 
536 /**
537  * This type represents a range over an optional type.
538  *
539  * This is a RandomAccessRange.
540  */
541 struct OptionRange(T) if(is(T == class) || isPointer!T) {
542 private:
543     T _value;
544 public:
545     /**
546      * Construct this range by wrapping a given value.
547      *
548      * Params:
549      *     value = The value to create the range with.
550      */
551     @nogc @safe pure nothrow
552     this(U)(U value) if(is(U : T)) {
553         _value = value;
554     }
555 
556     ///
557     @nogc @trusted pure nothrow
558     void popFront()
559     in {
560         assert(_value !is null, "Attempted to pop an empty range!");
561     } body {
562         static if(is(T == const) || is(T == immutable)) {
563             // Force the pointer held here into being null.
564             *(cast(void**) &_value) = null;
565         } else {
566             _value = null;
567         }
568     }
569 
570     ///
571     alias popBack = popFront;
572 
573     ///
574     @nogc @safe pure nothrow
575     @property inout(T) front() inout {
576         return _value;
577     }
578 
579     ///
580     alias back = front;
581 
582     ///
583     @nogc @safe pure nothrow
584     @property bool empty() const {
585         return _value is null;
586     }
587 
588     ///
589     @nogc @safe pure nothrow
590     @property typeof(this) save() {
591         return this;
592     }
593 
594     ///
595     @nogc @safe pure nothrow
596     @property size_t length() const {
597         return _value !is null ? 1 : 0;
598     }
599 
600     ///
601     @nogc @safe pure nothrow
602     inout(T) opIndex(size_t index) inout
603     in {
604         assert(index <= length, "Index out of bounds!");
605     } body {
606         return _value;
607     }
608 }
609 
610 /**
611  * Create an OptionRange from an Option type.
612  *
613  * The range shall be empty when the option has no value,
614  * and it shall have one item when the option has a value.
615  *
616  * Params:
617  *     optionalValue = An optional value.
618  *
619  * Returns: A range of 0 or 1 values.
620  */
621 @nogc @safe pure nothrow
622 OptionRange!T range(T)(Option!T optionalValue) {
623     if (optionalValue.isNull) {
624         return typeof(return).init;
625     }
626 
627     return OptionRange!T(optionalValue.get);
628 }
629 
630 /// ditto
631 @nogc @trusted pure nothrow
632 OptionRange!(const(T)) range(T)(const(Option!T) optionalValue) {
633     return cast(typeof(return)) range(cast(Option!T)(optionalValue));
634 }
635 
636 /// ditto
637 @nogc @trusted pure nothrow
638 OptionRange!(immutable(T)) range(T)(immutable(Option!T) optionalValue) {
639     return cast(typeof(return)) range(cast(Option!T)(optionalValue));
640 }
641 
642 // Test creating ranges from option types.
643 unittest {
644     class Klass {}
645 
646     Option!Klass m = new Klass();
647     const(Option!Klass) c = new const Klass();
648     immutable(Option!Klass) i = new immutable Klass();
649 
650     auto mRange = m.range;
651     auto cRange = c.range;
652     auto iRange = i.range;
653 
654     assert(!mRange.empty);
655     assert(!cRange.empty);
656     assert(!iRange.empty);
657 
658     assert(mRange.length == 1);
659     assert(cRange.length == 1);
660     assert(iRange.length == 1);
661 
662     assert(mRange[0] is m.get);
663     assert(cRange[0] is c.get);
664     assert(iRange[0] is i.get);
665 
666     assert(mRange.front is mRange.back);
667     assert(cRange.front is cRange.back);
668     assert(iRange.front is iRange.back);
669 
670     auto mRangeSave = mRange.save;
671     auto cRangeSave = cRange.save;
672     auto iRangeSave = iRange.save;
673 
674     mRange.popFront();
675     cRange.popFront();
676     iRange.popFront();
677 
678     assert(mRange.empty);
679     assert(cRange.empty);
680     assert(iRange.empty);
681 
682     assert(mRange.length == 0);
683     assert(cRange.length == 0);
684     assert(iRange.length == 0);
685 
686     assert(!mRangeSave.empty);
687     assert(!cRangeSave.empty);
688     assert(!iRangeSave.empty);
689 }
690 
691 unittest {
692     import std.range;
693 
694     // Test that all of the essential properties hold for this type.
695     static assert(isInputRange!(OptionRange!(void*)));
696     static assert(isForwardRange!(OptionRange!(void*)));
697     static assert(isBidirectionalRange!(OptionRange!(void*)));
698     static assert(isRandomAccessRange!(OptionRange!(void*)));
699     static assert(!isInfinite!(OptionRange!(void*)));
700 }
701 
702 
703 // Test std.algorithm integration
704 unittest {
705     class Klass {
706         int x = 3;
707     }
708 
709     import std.algorithm;
710 
711     Option!Klass foo = new Klass();
712 
713     auto squareSum(R)(R range) {
714         return reduce!((x, y) => x + y)(0, range.map!(val => val.x * val.x));
715     }
716 
717     auto fooSum = squareSum(foo.range);
718 
719     import std.stdio;
720 
721     assert(fooSum == 9);
722 
723     Option!Klass bar;
724 
725     auto barSum = squareSum(bar.range);
726 
727     assert(barSum == 0);
728 }
729 
730 unittest {
731     class Klass {}
732     class SubKlass : Klass {}
733     class SubSubKlass : SubKlass {}
734 
735     Some!Klass nonNullValue = some(new Klass());
736 
737     Option!Klass optionalValue;
738 
739     // You can check if the value is null.
740     assert(optionalValue.isNull);
741 
742     // You can assign Some!T values to it.
743     optionalValue = nonNullValue;
744 
745     assert(!optionalValue.isNull);
746 
747     // You can assign regular values, including derived types.
748     // Dervied Some!T values will work too.
749     optionalValue = new SubKlass();
750 
751     assert(!optionalValue.isNull);
752 
753     // You can get the value out as a type Some!T from it and cast it
754     // to the class type. The dynamic cast will be used.
755     assert(cast(SubKlass) optionalValue.get() !is null);
756 
757     // Using the right dynamic cast means that the value from that can be null,
758     // when the dynamic cast fails.
759     assert(cast(SubSubKlass) optionalValue.get() is null);
760 
761     // Or create a new optional value with a cast, which will also work
762     // when the optional value is null.
763     //
764     // This method will not exist for optional pointers.
765     Option!SubSubKlass subValue = optionalValue.dynamicCast!SubSubKlass;
766 
767     assert(subValue.isNull);
768 
769     // We can assign back to a regular class reference.
770     Klass regularReference;
771 
772     // When the optional value is null, the range will by empty.
773     optionalValue = null;
774 
775     foreach(value; optionalValue.range) {
776         regularReference = value;
777     }
778 
779     assert(regularReference is null);
780 
781     // We there's a value, the range will have length 1.
782     optionalValue = new Klass();
783 
784     foreach(value; optionalValue.range) {
785         regularReference = value;
786     }
787 
788     assert(regularReference !is null);
789 
790     optionalValue = null;
791 
792     // Finally, we can use a method to use a default value.
793     // If the default is null, an assertion error will be thrown
794     // in debug mode. Any callable will work with .or, and the callable
795     // can also return a Some!T type.
796     Some!Klass someOtherValue = optionalValue.or(() => new SubKlass());
797 }