1 ///
2 module stdx.collections.dlist;
3 
4 import stdx.collections.common;
5 
6 debug(CollectionDList) import std.stdio;
7 
8 version(unittest)
9 {
10     import std.experimental.allocator.mallocator;
11     import std.experimental.allocator.building_blocks.stats_collector;
12     import std.experimental.allocator : IAllocator, allocatorObject;
13 
14     private alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed);
15 }
16 
17 struct DList(T)
18 {
19     import std.experimental.allocator : IAllocator, theAllocator, make, dispose;
20     import std.experimental.allocator.building_blocks.affix_allocator;
21     import std.traits : isImplicitlyConvertible;
22     import std.range.primitives : isInputRange, ElementType;
23     import std.conv : emplace;
24     import core.atomic : atomicOp;
25 
26 private:
27     struct Node
28     {
29         T _payload;
30         Node *_next;
31         Node *_prev;
32 
33         this(T v, Node *n, Node *p)
34         {
35             debug(CollectionDList) writefln("DList.Node.ctor: Constructing node" ~
36                     " with payload: %s", v);
37             _payload = v;
38             _next = n;
39             _prev = p;
40         }
41 
42         ~this()
43         {
44             debug(CollectionDList) writefln("DList.Node.dtor: Destroying node" ~
45                     " with payload: %s", _payload);
46         }
47     }
48 
49     Node *_head;
50 
51     alias MutableAlloc = AffixAllocator!(IAllocator, size_t);
52     Mutable!MutableAlloc _ouroborosAllocator;
53 
54     /// Returns the actual allocator from ouroboros
55     @trusted ref auto allocator(this _)()
56     {
57         assert(!_ouroborosAllocator.isNull);
58         return _ouroborosAllocator.get();
59     }
60 
61     /// Constructs the ouroboros allocator from allocator if the ouroboros
62     //allocator wasn't previously set
63     public @trusted bool setAllocator(IAllocator allocator)
64     {
65         if (_ouroborosAllocator.isNull)
66         {
67             _ouroborosAllocator = Mutable!(MutableAlloc)(allocator,
68                     MutableAlloc(allocator));
69             return true;
70         }
71         return false;
72     }
73 
74     public @trusted IAllocator getAllocator(this _)()
75     {
76         return _ouroborosAllocator.isNull ? null : allocator().parent;
77     }
78 
79     @trusted void addRef(QualNode, this Qualified)(QualNode node)
80     {
81         assert(node !is null);
82         debug(CollectionDList)
83         {
84             writefln("DList.addRef: Node %s has refcount: %s; will be: %s",
85                     node._payload, *prefCount(node), *prefCount(node) + 1);
86         }
87         static if (is(Qualified == immutable) || is(Qualified == const))
88         {
89             atomicOp!"+="(*prefCount(node), 1);
90         }
91         else
92         {
93             ++*prefCount(node);
94         }
95     }
96 
97     @trusted void delRef(ref Node *node)
98     {
99         assert(node !is null);
100         size_t *pref = prefCount(node);
101         debug(CollectionDList) writefln("DList.delRef: Node %s has refcount: %s; will be: %s",
102                 node._payload, *pref, *pref - 1);
103         if (*pref == 0)
104         {
105             debug(CollectionDList) writefln("DList.delRef: Deleting node %s", node._payload);
106             allocator.dispose(node);
107             node = null;
108         }
109         else
110         {
111             --*pref;
112         }
113     }
114 
115     @trusted auto prefCount(QualNode, this Qualified)(QualNode node)
116     {
117         assert(node !is null);
118         static if (is(Qualified == immutable) || is(Qualified == const))
119         {
120             return cast(shared size_t*)(&allocator.prefix(cast(void[Node.sizeof])(*node)));
121         }
122         else
123         {
124             return cast(size_t*)(&allocator.prefix(cast(void[Node.sizeof])(*node)));
125         }
126     }
127 
128     static string immutableInsert(string stuff)
129     {
130         return ""
131             ~"auto tmpAlloc = Mutable!(MutableAlloc)(allocator, MutableAlloc(allocator));"
132             ~"_ouroborosAllocator = (() @trusted => cast(immutable)(tmpAlloc))();"
133             ~"Node *tmpNode;"
134             ~"Node *tmpHead;"
135             ~"foreach (item; " ~ stuff ~ ")"
136             ~"{"
137                 ~"Node *newNode;"
138                 ~"() @trusted { newNode ="
139                     ~"tmpAlloc.get().make!(Node)(item, null, null);"
140                 ~"}();"
141                 ~"if (tmpHead is null)"
142                 ~"{"
143                     ~"tmpHead = tmpNode = newNode;"
144                 ~"}"
145                 ~"else"
146                 ~"{"
147                     ~"tmpNode._next = newNode;"
148                     ~"newNode._prev = tmpNode;"
149                     ~"addRef(newNode._prev);"
150                     ~"tmpNode = newNode;"
151                 ~"}"
152             ~"}"
153             ~"_head = (() @trusted => cast(immutable Node*)(tmpHead))();";
154     }
155 
156 public:
157     this(this _)(IAllocator allocator)
158     {
159         debug(CollectionDList)
160         {
161             writefln("DList.ctor: begin");
162             scope(exit) writefln("DList.ctor: end");
163         }
164         setAllocator(allocator);
165     }
166 
167     this(U, this Qualified)(U[] values...)
168     if (isImplicitlyConvertible!(U, T))
169     {
170         this(theAllocator, values);
171     }
172 
173     this(U, this Qualified)(IAllocator allocator, U[] values...)
174     if (isImplicitlyConvertible!(U, T))
175     {
176         debug(CollectionDList)
177         {
178             writefln("DList.ctor: begin");
179             scope(exit) writefln("DList.ctor: end");
180         }
181         static if (is(Qualified == immutable) || is(Qualified == const))
182         {
183             mixin(immutableInsert("values"));
184         }
185         else
186         {
187             setAllocator(allocator);
188             insert(0, values);
189         }
190     }
191 
192     this(Stuff, this Qualified)(Stuff stuff)
193     if (isInputRange!Stuff
194         && isImplicitlyConvertible!(ElementType!Stuff, T)
195         && !is(Stuff == T[]))
196     {
197         this(theAllocator, stuff);
198     }
199 
200     this(Stuff, this Qualified)(IAllocator allocator, Stuff stuff)
201     if (isInputRange!Stuff
202         && isImplicitlyConvertible!(ElementType!Stuff, T)
203         && !is(Stuff == T[]))
204     {
205         debug(CollectionDList)
206         {
207             writefln("DList.ctor: begin");
208             scope(exit) writefln("DList.ctor: end");
209         }
210         static if (is(Qualified == immutable) || is(Qualified == const))
211         {
212             mixin(immutableInsert("stuff"));
213         }
214         else
215         {
216             setAllocator(allocator);
217             insert(0, stuff);
218         }
219     }
220 
221     this(this)
222     {
223         debug(CollectionDList)
224         {
225             writefln("DList.postblit: begin");
226             scope(exit) writefln("DList.postblit: end");
227         }
228         if (_head !is null)
229         {
230             addRef(_head);
231             debug(CollectionDList) writefln("DList.postblit: Node %s has refcount: %s",
232                     _head._payload, *prefCount(_head));
233         }
234     }
235 
236     // Immutable ctors
237     private this(NodeQual, OuroQual, this Qualified)(NodeQual _newHead,
238             OuroQual ouroborosAllocator)
239         if (is(typeof(_head) : typeof(_newHead))
240             && (is(Qualified == immutable) || is(Qualified == const)))
241     {
242         _head = _newHead;
243         _ouroborosAllocator = ouroborosAllocator;
244         if (_head !is null)
245         {
246             addRef(_head);
247             debug(CollectionDList) writefln("DList.ctor immutable: Node %s has "
248                     ~ "refcount: %s", _head._payload, *prefCount(_head));
249         }
250     }
251 
252     ~this()
253     {
254         debug(CollectionDList)
255         {
256             writefln("DList.dtor: Begin for instance %s of type %s",
257                 cast(size_t)(&this), typeof(this).stringof);
258             scope(exit) writefln("DList.dtor: End for instance %s of type %s",
259                     cast(size_t)(&this), typeof(this).stringof);
260         }
261         if (_head !is null)
262         {
263             delRef(_head);
264             if (_head !is null
265                 && ((_head._prev !is null) || (_head._next !is null)))
266             {
267                 // If it was a single node list, only delRef must be used
268                 // in order to avoid premature/double freeing
269                 destroyUnused(_head);
270             }
271         }
272     }
273 
274     void destroyUnused(Node *startNode)
275     {
276         debug(CollectionDList)
277         {
278             writefln("DList.destoryUnused: begin");
279             scope(exit) writefln("DList.destoryUnused: end");
280         }
281 
282         if (startNode is null) return;
283 
284         Node *tmpNode = startNode;
285         bool isCycle = true;
286         while (tmpNode !is null)
287         {
288             if (((tmpNode._next is null || tmpNode._prev is null)
289                   && *prefCount(tmpNode) == 0)
290                 || (tmpNode._next !is null && tmpNode._prev !is null
291                     && *prefCount(tmpNode) == 1))
292             {
293                 // The last node should always have rc == 0 (only one ref,
294                 // from prev._next)
295                 // The first node should always have rc == 0 (only one ref,
296                 // from next._prev), since we don't take into account
297                 // the head ref (that was deleted either by the dtor or by pop)
298                 // Nodes within the cycle should always have rc == 1
299                 tmpNode = tmpNode._next;
300             }
301             else
302             {
303                 isCycle = false;
304                 break;
305             }
306         }
307 
308         tmpNode = startNode._prev;
309         while (isCycle && tmpNode !is null)
310         {
311             if (((tmpNode._next is null || tmpNode._prev is null)
312                   && *prefCount(tmpNode) == 0)
313                 || (tmpNode._next !is null && tmpNode._prev !is null
314                     && *prefCount(tmpNode) == 1))
315             {
316                 tmpNode = tmpNode._prev;
317             }
318             else
319             {
320                 isCycle = false;
321                 break;
322             }
323         }
324 
325         if (isCycle)
326         {
327             // We can safely deallocate memory
328             tmpNode = startNode._next;
329             while (tmpNode !is null)
330             {
331                 Node *oldNode = tmpNode;
332                 tmpNode = tmpNode._next;
333                 () @trusted { allocator.dispose(oldNode); }();
334             }
335             tmpNode = startNode;
336             while (tmpNode !is null)
337             {
338                 Node *oldNode = tmpNode;
339                 tmpNode = tmpNode._prev;
340                 () @trusted { allocator.dispose(oldNode); }();
341             }
342         }
343     }
344 
345     bool isUnique(this _)()
346     {
347         debug(CollectionDList)
348         {
349             writefln("DList.isUnique: begin");
350             scope(exit) writefln("DList.isUnique: end");
351         }
352 
353         if (empty)
354         {
355             return true;
356         }
357 
358         Node *tmpNode = (() @trusted => cast(Node*)_head)();
359 
360         // Rewind to the beginning of the list
361         while (tmpNode !is null && tmpNode._prev !is null)
362         {
363             tmpNode = tmpNode._prev;
364         }
365 
366         // For a single node list, head should have rc == 0
367         if (tmpNode._next is null && (*prefCount(tmpNode) > 0))
368         {
369             return false;
370         }
371 
372         while (tmpNode !is null)
373         {
374             if (tmpNode._next is null || tmpNode._prev is null)
375             {
376                 // The first and last node should have rc == 0 unless the _head
377                 // is pointing to them, in which case rc must be 1
378                 if (((tmpNode is _head) && (*prefCount(tmpNode) > 1))
379                     || ((tmpNode !is _head) && (*prefCount(tmpNode) > 0)))
380                 {
381                     return false;
382                 }
383             }
384             else if (((tmpNode is _head) && (*prefCount(tmpNode) > 2))
385                     || ((tmpNode !is _head) && (*prefCount(tmpNode) > 1)))
386             {
387                 // Any other node should have rc == 1 unless the _head
388                 // is pointing to it, in which case rc must be 2
389                 return false;
390             }
391             tmpNode = tmpNode._next;
392         }
393 
394         return true;
395     }
396 
397     bool empty(this _)()
398     {
399         return _head is null;
400     }
401 
402     ref auto front(this _)()
403     {
404         assert(!empty, "DList.front: List is empty");
405         return _head._payload;
406     }
407 
408     void popFront()
409     {
410         debug(CollectionDList)
411         {
412             writefln("DList.popFront: begin");
413             scope(exit) writefln("DList.popFront: end");
414         }
415         assert(!empty, "DList.popFront: List is empty");
416         Node *tmpNode = _head;
417         _head = _head._next;
418         if (_head !is null)
419         {
420             addRef(_head);
421             delRef(tmpNode);
422         }
423         else
424         {
425             delRef(tmpNode);
426             if (tmpNode !is null
427                 && ((tmpNode._prev !is null) || (tmpNode._next !is null)))
428             {
429                 // If it was a single node list, only delRef must be used
430                 // in order to avoid premature/double freeing
431                 destroyUnused(tmpNode);
432             }
433         }
434     }
435 
436     void popPrev()
437     {
438         debug(CollectionDList)
439         {
440             writefln("DList.popPrev: begin");
441             scope(exit) writefln("DList.popPrev: end");
442         }
443         assert(!empty, "DList.popPrev: List is empty");
444         Node *tmpNode = _head;
445         _head = _head._prev;
446         if (_head !is null) {
447             addRef(_head);
448             delRef(tmpNode);
449         }
450         else
451         {
452             delRef(tmpNode);
453             if (tmpNode !is null
454                 && ((tmpNode._prev !is null) || (tmpNode._next !is null)))
455             {
456                 // If it was a single node list, only delRef must be used
457                 // in order to avoid premature/double freeing
458                 destroyUnused(tmpNode);
459             }
460         }
461     }
462 
463     Qualified tail(this Qualified)()
464     {
465         debug(CollectionDList)
466         {
467             writefln("DList.popFront: begin");
468             scope(exit) writefln("DList.popFront: end");
469         }
470         assert(!empty, "DList.popFront: List is empty");
471 
472         static if (is(Qualified == immutable) || is(Qualified == const))
473         {
474             return typeof(this)(_head._next, _ouroborosAllocator);
475         }
476         else
477         {
478             return .tail(this);
479         }
480     }
481 
482     ref Qualified save(this Qualified)()
483     {
484         debug(CollectionDList)
485         {
486             writefln("DList.save: begin");
487             scope(exit) writefln("DList.save: end");
488         }
489         return this;
490     }
491 
492     typeof(this) dup()
493     {
494         debug(CollectionDList)
495         {
496             writefln("DList.dup: begin");
497             scope(exit) writefln("DList.dup: end");
498         }
499         IAllocator alloc = getAllocator();
500         if (alloc is null)
501         {
502             alloc = theAllocator;
503         }
504         return typeof(this)(alloc, this);
505     }
506 
507     size_t insert(Stuff)(size_t pos, Stuff stuff)
508     if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
509     {
510         debug(CollectionDList)
511         {
512             writefln("DList.insert: begin");
513             scope(exit) writefln("DList.insert: end");
514         }
515         setAllocator(theAllocator);
516 
517         size_t result;
518         Node *tmpNode;
519         Node *tmpHead;
520         foreach (item; stuff)
521         {
522             Node *newNode;
523             () @trusted { newNode = allocator.make!(Node)(item, null, null); }();
524             if (tmpHead is null)
525             {
526                 tmpHead = tmpNode = newNode;
527             }
528             else
529             {
530                 tmpNode._next = newNode;
531                 newNode._prev = tmpNode;
532                 addRef(newNode._prev);
533                 tmpNode = newNode;
534             }
535             ++result;
536         }
537 
538         if (!tmpNode)
539         {
540             return 0;
541         }
542 
543         if (!_head) assert(pos == 0);
544 
545         size_t initPos = pos;
546         Node *needle = _head;
547         while (pos && needle._next !is null)
548         {
549             needle = needle._next;
550             --pos;
551         }
552 
553         // Check if we need to insert at the back of the list
554         if (initPos != 0 && needle._next is null && pos >= 1)
555         {
556             // We need to insert at the back of the list
557             assert(pos == 1, "Index out of range");
558             needle._next = tmpHead;
559             tmpHead._prev = needle;
560             addRef(needle);
561             return result;
562         }
563         assert(pos == 0, "Index out of range");
564 
565         tmpNode._next = needle;
566         if (needle !is null)
567         {
568             addRef(needle);
569             if (needle._prev !is null)
570             {
571                 tmpHead._prev = needle._prev;
572                 needle._prev._next = tmpHead;
573                 // Inc ref only when tmpHead will be the new head of the list
574                 if (initPos == 0)
575                 {
576                     addRef(tmpHead);
577                 }
578 
579                 // Delete extra ref, since we already added the ref earlier
580                 // through tmpNode._next
581                 delRef(needle);
582             }
583             if (initPos == 0)
584             {
585                 // Pass the ref to the new head
586                 delRef(needle);
587             }
588             assert(needle !is null);
589             needle._prev = tmpNode;
590             if (tmpHead == tmpNode)
591             {
592                 addRef(tmpHead);
593             }
594             else
595             {
596                 addRef(needle._prev);
597             }
598         }
599 
600         if (initPos == 0)
601         {
602             _head = tmpHead;
603         }
604         return result;
605     }
606 
607     size_t insert(Stuff)(size_t pos, Stuff[] stuff...)
608     if (isImplicitlyConvertible!(Stuff, T))
609     {
610         return insert(pos, stuff);
611     }
612 
613     size_t insertBack(Stuff)(Stuff stuff)
614     if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
615     {
616         debug(CollectionDList)
617         {
618             writefln("DList.insertBack: begin");
619             scope(exit) writefln("DList.insertBack: end");
620         }
621         setAllocator(theAllocator);
622 
623         size_t result;
624         Node *tmpNode;
625         Node *tmpHead;
626         foreach (item; stuff)
627         {
628             Node *newNode;
629             () @trusted { newNode = allocator.make!(Node)(item, null, null); }();
630             if (tmpHead is null)
631             {
632                 tmpHead = tmpNode = newNode;
633             }
634             else
635             {
636                 tmpNode._next = newNode;
637                 newNode._prev = tmpNode;
638                 addRef(newNode._prev);
639                 tmpNode = newNode;
640             }
641             ++result;
642         }
643 
644         if (!tmpNode)
645         {
646             return 0;
647         }
648 
649         if (_head is null)
650         {
651             _head = tmpHead;
652         }
653         else
654         {
655             Node *endNode;
656             for (endNode = _head; endNode._next !is null; endNode = endNode._next) { }
657             endNode._next = tmpHead;
658             // don't addRef(tmpHead) since the ref will pass from tmpHead to
659             // endNode._next when tmpHead's scope ends
660             tmpHead._prev = endNode;
661             addRef(endNode);
662         }
663 
664         return result;
665     }
666 
667     size_t insertBack(Stuff)(Stuff[] stuff...)
668     if (isImplicitlyConvertible!(Stuff, T))
669     {
670         return insertBack(stuff);
671     }
672 
673     auto ref opBinary(string op, U)(auto ref U rhs)
674         if (op == "~" &&
675             (is (U == typeof(this))
676              || is (U : T)
677              || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
678             ))
679     {
680         debug(CollectionDList)
681         {
682             writefln("DList.opBinary!~: begin");
683             scope(exit) writefln("DList.opBinary!~: end");
684         }
685 
686         IAllocator alloc = getAllocator();
687         if (alloc is null)
688         {
689             static if (is(U == typeof(this)))
690             {
691                 alloc = rhs.getAllocator();
692             }
693             else
694             {
695                 alloc = null;
696             }
697             if (alloc is null)
698             {
699                 alloc = theAllocator;
700             }
701         }
702 
703         typeof(this) newList = typeof(this)(alloc, rhs);
704         newList.insert(0, this);
705         return newList;
706     }
707 
708     auto ref opAssign()(auto ref typeof(this) rhs)
709     {
710         debug(CollectionDList)
711         {
712             writefln("DList.opAssign: begin");
713             scope(exit) writefln("DList.opAssign: end");
714         }
715 
716         if (rhs._head !is null && _head is rhs._head)
717         {
718             return this;
719         }
720 
721         if (rhs._head !is null)
722         {
723             rhs.addRef(rhs._head);
724             debug(CollectionDList) writefln("DList.opAssign: Node %s has refcount: %s",
725                     rhs._head._payload, *prefCount(rhs._head));
726         }
727 
728         if (_head !is null)
729         {
730             Node *tmpNode = _head;
731             delRef(tmpNode);
732             if (tmpNode !is null
733                 && ((tmpNode._prev !is null) || (tmpNode._next !is null)))
734             {
735                 // If it was a single node list, only delRef must be used
736                 // in order to avoid premature/double freeing
737                 destroyUnused(tmpNode);
738             }
739         }
740         _head = rhs._head;
741         _ouroborosAllocator = rhs._ouroborosAllocator;
742 
743         return this;
744     }
745 
746     auto ref opOpAssign(string op, U)(auto ref U rhs)
747         if (op == "~" &&
748             (is (U == typeof(this))
749              || is (U : T)
750              || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
751             ))
752     {
753         debug(CollectionDList)
754         {
755             writefln("DList.opOpAssign!~: %s begin", typeof(this).stringof);
756             scope(exit) writefln("DList.opOpAssign!~: %s end", typeof(this).stringof);
757         }
758 
759         insertBack(rhs);
760         return this;
761     }
762 
763     void remove()
764     {
765         debug(CollectionDList)
766         {
767             writefln("DList.remove: begin");
768             scope(exit) writefln("DList.remove: end");
769         }
770         assert(!empty, "DList.remove: List is empty");
771 
772         Node *tmpNode = _head;
773         _head = _head._next;
774         if (_head !is null)
775         {
776             //addRef(_head);
777             _head._prev = tmpNode._prev;
778             delRef(tmpNode); // Remove tmpNode._next._prev ref
779             tmpNode._next = null;
780             //delRef(_head);
781             if (tmpNode._prev !is null)
782             {
783                 addRef(_head);
784                 tmpNode._prev._next = _head;
785                 delRef(tmpNode); // Remove tmpNode._prev._next ref
786                 tmpNode._prev = null;
787             }
788         }
789         else if (tmpNode._prev !is null)
790         {
791             _head = tmpNode._prev;
792             //addRef(_head);
793             tmpNode._prev = null;
794             //delRef(_head);
795             _head._next = null;
796             delRef(tmpNode);
797         }
798         delRef(tmpNode); // Remove old head ref
799         if (tmpNode !is null
800                 && ((tmpNode._prev !is null) || (tmpNode._next !is null)))
801         {
802             // If it was a single node list, only delRef must be used
803             // in order to avoid premature/double freeing
804             destroyUnused(tmpNode);
805         }
806     }
807 
808     //debug(CollectionDList) void printRefCount(Node *sn = null)
809     void printRefCount(Node *sn = null)
810     {
811         import std.stdio;
812         writefln("DList.printRefCount: begin");
813         scope(exit) writefln("DList.printRefCount: end");
814 
815         Node *tmpNode;
816         if (sn is null)
817             tmpNode = _head;
818         else
819             tmpNode = sn;
820 
821         while (tmpNode !is null && tmpNode._prev !is null)
822         {
823             // Rewind to the beginning of the list
824             tmpNode = tmpNode._prev;
825         }
826         while (tmpNode !is null)
827         {
828             writefln("DList.printRefCount: Node %s has ref count %s",
829                     tmpNode._payload, *prefCount(tmpNode));
830             tmpNode = tmpNode._next;
831         }
832     }
833 }
834 
835 version (unittest) private @trusted void testInit(IAllocator allocator)
836 {
837     import std.algorithm.comparison : equal;
838 
839     DList!int dl = DList!int(allocator);
840     assert(dl.empty);
841     assert(dl.isUnique);
842     int[] empty;
843     assert(equal(dl, empty));
844 
845     DList!int dl2 = DList!int(allocator, 1);
846     assert(equal(dl2, [1]));
847 
848     DList!int dl3 = DList!int(allocator, 1, 2);
849     assert(equal(dl3, [1, 2]));
850 
851     DList!int dl4 = DList!int(allocator, [1]);
852     assert(equal(dl4, [1]));
853 
854     DList!int dl5 = DList!int(allocator, [1, 2]);
855     assert(equal(dl5, [1, 2]));
856 }
857 
858 @trusted unittest
859 {
860     import std.conv;
861     SCAlloc statsCollectorAlloc;
862     auto _allocator = allocatorObject(statsCollectorAlloc);
863 
864     () @safe {
865         testInit(_allocator);
866         auto bytesUsed = _allocator.impl.bytesUsed;
867         assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
868                 ~ to!string(bytesUsed) ~ " bytes");
869     }();
870 }
871 
872 version (unittest) private @trusted void testInsert(IAllocator allocator)
873 {
874     import std.algorithm.comparison : equal;
875     import std.range.primitives : walkLength;
876 
877     DList!int dl = DList!int(allocator, 1);
878     size_t pos = 0;
879     dl.insert(pos, 2);
880     assert(equal(dl, [2, 1]));
881 
882     DList!int dl2 = DList!int(allocator, 1);
883     dl2.insert(pos, 2, 3);
884     assert(equal(dl2, [2, 3, 1]));
885 
886     DList!int dl3 = DList!int(allocator, 1, 2);
887     dl3.insert(pos, 3);
888     assert(equal(dl3, [3, 1, 2]));
889 
890     DList!int dl4 = DList!int(allocator, 1, 2);
891     dl4.insert(pos, 3, 4);
892     assert(equal(dl4, [3, 4, 1, 2]));
893 
894     DList!int dl5 = DList!int(allocator, 1, 2);
895     dl5.popFront();
896     dl5.insert(pos, 3);
897     assert(equal(dl5, [3, 2]));
898     dl5.popPrev();
899     assert(equal(dl5, [1, 3, 2]));
900 
901     DList!int dl6 = DList!int(allocator, 1, 2);
902     dl6.popFront();
903     dl6.insert(pos, 3, 4);
904     assert(equal(dl6, [3, 4, 2]));
905     dl6.popPrev();
906     assert(equal(dl6, [1, 3, 4, 2]));
907     dl6.insertBack(5);
908     assert(equal(dl6, [1, 3, 4, 2, 5]));
909     dl6.insertBack(6, 7);
910     assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7]));
911     dl6.insertBack([8]);
912     assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8]));
913     dl6.insertBack([9, 10]);
914     assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10]));
915     int[] empty;
916     dl6.insertBack(empty);
917     assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10]));
918     dl6.insert(pos, empty);
919     assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10]));
920 
921     DList!int dl7 = DList!int(allocator, 1);
922     assert(equal(dl7, [1]));
923     dl7.insert(pos, 2);
924     assert(equal(dl7, [2, 1]));
925     pos = walkLength(dl7);
926     dl7.insert(pos, 3);
927     assert(equal(dl7, [2, 1, 3]));
928     dl7.insert(pos, 4);
929     assert(equal(dl7, [2, 1, 4, 3]));
930 }
931 
932 @trusted unittest
933 {
934     import std.conv;
935     SCAlloc statsCollectorAlloc;
936     auto _allocator = allocatorObject(statsCollectorAlloc);
937 
938     () @safe {
939         testInsert(_allocator);
940         auto bytesUsed = _allocator.impl.bytesUsed;
941         assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
942                 ~ to!string(bytesUsed) ~ " bytes");
943     }();
944 }
945 
946 version (unittest) private @trusted void testRemove(IAllocator allocator)
947 {
948     import std.algorithm.comparison : equal;
949 
950     DList!int dl = DList!int(allocator, 1);
951     size_t pos = 0;
952     dl.remove();
953     assert(dl.empty);
954     assert(dl.isUnique);
955     assert(!dl._ouroborosAllocator.isNull);
956 
957     dl.insert(pos, 2);
958     auto dl2 = dl;
959     auto dl3 = dl;
960     assert(!dl.isUnique);
961 
962     dl.popFront();
963     assert(dl.empty);
964     assert(!dl._ouroborosAllocator.isNull);
965 
966     dl2.popPrev();
967     assert(dl2.empty);
968     assert(dl3.isUnique);
969 
970     auto dl4 = dl3;
971     assert(!dl3.isUnique);
972     dl4.remove();
973     assert(dl4.empty);
974     assert(!dl3.empty);
975     assert(dl3.isUnique);
976 }
977 
978 @trusted unittest
979 {
980     import std.conv;
981     SCAlloc statsCollectorAlloc;
982     auto _allocator = allocatorObject(statsCollectorAlloc);
983 
984     () @safe {
985         testRemove(_allocator);
986         auto bytesUsed = _allocator.impl.bytesUsed;
987         assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
988                 ~ to!string(bytesUsed) ~ " bytes");
989     }();
990 }
991 
992 version (unittest) private @trusted void testCopyAndRef(IAllocator allocator)
993 {
994     import std.algorithm.comparison : equal;
995 
996     auto dlFromList = DList!int(allocator, 1, 2, 3);
997     auto dlFromRange = DList!int(allocator, dlFromList);
998     assert(equal(dlFromList, dlFromRange));
999 
1000     dlFromList.popFront();
1001     assert(equal(dlFromList, [2, 3]));
1002     assert(equal(dlFromRange, [1, 2, 3]));
1003 
1004     DList!int dlInsFromRange = DList!int(allocator);
1005     size_t pos = 0;
1006     dlInsFromRange.insert(pos, dlFromList);
1007     dlFromList.popFront();
1008     assert(equal(dlFromList, [3]));
1009     assert(equal(dlInsFromRange, [2, 3]));
1010 
1011     DList!int dlInsBackFromRange = DList!int(allocator);
1012     dlInsBackFromRange.insert(pos, dlFromList);
1013     dlFromList.popFront();
1014     assert(dlFromList.empty);
1015     assert(equal(dlInsBackFromRange, [3]));
1016 
1017     auto dlFromRef = dlInsFromRange;
1018     auto dlFromDup = dlInsFromRange.dup;
1019     assert(dlInsFromRange.front == 2);
1020     dlFromRef.front = 5;
1021     assert(dlInsFromRange.front == 5);
1022     assert(dlFromDup.front == 2);
1023 }
1024 
1025 @trusted unittest
1026 {
1027     import std.conv;
1028     SCAlloc statsCollectorAlloc;
1029     auto _allocator = allocatorObject(statsCollectorAlloc);
1030 
1031     () @safe {
1032         testCopyAndRef(_allocator);
1033         auto bytesUsed = _allocator.impl.bytesUsed;
1034         assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1035                 ~ to!string(bytesUsed) ~ " bytes");
1036     }();
1037 }
1038 
1039 @trusted unittest
1040 {
1041     import std.algorithm.comparison : equal;
1042 
1043     SCAlloc statsCollectorAlloc;
1044     auto _allocator = allocatorObject(statsCollectorAlloc);
1045 
1046     DList!int dl = DList!int(_allocator, 1, 2, 3);
1047     auto before = _allocator.impl.bytesUsed;
1048     {
1049         DList!int dl2 = dl;
1050         dl2.popFront();
1051         assert(equal(dl2, [2, 3]));
1052     }
1053     assert(before == _allocator.impl.bytesUsed);
1054     assert(equal(dl, [1, 2, 3]));
1055     dl.tail();
1056 }
1057 
1058 version(unittest) private @trusted void testImmutability(IAllocator allocator)
1059 {
1060     auto s = immutable DList!(int)(allocator, 1, 2, 3);
1061     auto s2 = s;
1062     auto s3 = s2.save();
1063 
1064     assert(s2.front == 1);
1065     static assert(!__traits(compiles, s2.front = 4));
1066     static assert(!__traits(compiles, s2.popFront()));
1067 
1068     auto s4 = s2.tail;
1069     assert(s4.front == 2);
1070     static assert(!__traits(compiles, s4 = s4.tail));
1071 }
1072 
1073 version(unittest) private @trusted void testConstness(IAllocator allocator)
1074 {
1075     auto s = const DList!(int)(allocator, 1, 2, 3);
1076     auto s2 = s;
1077     auto s3 = s2.save();
1078 
1079     assert(s2.front == 1);
1080     static assert(!__traits(compiles, s2.front = 4));
1081     static assert(!__traits(compiles, s2.popFront()));
1082 
1083     auto s4 = s2.tail;
1084     assert(s4.front == 2);
1085     static assert(!__traits(compiles, s4 = s4.tail));
1086 }
1087 
1088 @trusted unittest
1089 {
1090     import std.conv;
1091     SCAlloc statsCollectorAlloc;
1092     auto _allocator = allocatorObject(statsCollectorAlloc);
1093 
1094     () @safe {
1095         testConstness(_allocator);
1096         testImmutability(_allocator);
1097         auto bytesUsed = _allocator.impl.bytesUsed;
1098         assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1099                 ~ to!string(bytesUsed) ~ " bytes");
1100     }();
1101 }
1102 
1103 version(unittest) private @trusted void testConcatAndAppend(IAllocator allocator)
1104 {
1105     import std.algorithm.comparison : equal;
1106 
1107     auto dl = DList!(int)(allocator, 1, 2, 3);
1108     DList!(int) dl2 = DList!int(allocator);
1109 
1110     auto dl3 = dl ~ dl2;
1111     assert(equal(dl3, [1, 2, 3]));
1112 
1113     auto dl4 = dl3;
1114     dl3 = dl3 ~ 4;
1115     assert(equal(dl3, [1, 2, 3, 4]));
1116     dl3 = dl3 ~ [5];
1117     assert(equal(dl3, [1, 2, 3, 4, 5]));
1118     assert(equal(dl4, [1, 2, 3]));
1119 
1120     dl4 = dl3;
1121     dl3 ~= 6;
1122     assert(equal(dl3, [1, 2, 3, 4, 5, 6]));
1123     dl3 ~= [7];
1124     assert(equal(dl3, [1, 2, 3, 4, 5, 6, 7]));
1125     assert(equal(dl4, [1, 2, 3, 4, 5, 6, 7]));
1126 
1127     dl3 ~= dl3;
1128     assert(equal(dl3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]));
1129     assert(equal(dl4, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]));
1130 
1131     DList!int dl5 = DList!int(allocator);
1132     dl5 ~= [1, 2, 3];
1133     assert(equal(dl5, [1, 2, 3]));
1134 }
1135 
1136 @trusted unittest
1137 {
1138     import std.conv;
1139     SCAlloc statsCollectorAlloc;
1140     auto _allocator = allocatorObject(statsCollectorAlloc);
1141 
1142     () @safe {
1143         testConcatAndAppend(_allocator);
1144         auto bytesUsed = _allocator.impl.bytesUsed;
1145         assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1146                 ~ to!string(bytesUsed) ~ " bytes");
1147     }();
1148 }
1149 
1150 version(unittest) private @trusted void testAssign(IAllocator allocator)
1151 {
1152     import std.algorithm.comparison : equal;
1153 
1154     auto dl = DList!int(allocator, 1, 2, 3);
1155     assert(equal(dl, [1, 2, 3]));
1156     {
1157         auto dl2 = DList!int(allocator, 4, 5, 6);
1158         dl = dl2;
1159         assert(equal(dl, dl2));
1160     }
1161     assert(equal(dl, [4, 5, 6]));
1162     dl.popPrev();
1163     assert(dl.empty);
1164 }
1165 
1166 @trusted unittest
1167 {
1168     import std.conv;
1169     SCAlloc statsCollectorAlloc;
1170     auto _allocator = allocatorObject(statsCollectorAlloc);
1171 
1172     () @safe {
1173         testAssign(_allocator);
1174         auto bytesUsed = _allocator.impl.bytesUsed;
1175         assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1176                 ~ to!string(bytesUsed) ~ " bytes");
1177     }();
1178 }
1179 
1180 version(unittest) private @trusted void testWithStruct(IAllocator allocator)
1181 {
1182     import std.algorithm.comparison : equal;
1183 
1184     auto list = DList!int(allocator, 1, 2, 3);
1185     {
1186         auto listOfLists = DList!(DList!int)(allocator, list);
1187         size_t pos = 0;
1188         assert(equal(listOfLists.front, [1, 2, 3]));
1189         listOfLists.front.front = 2;
1190         assert(equal(listOfLists.front, [2, 2, 3]));
1191         static assert(!__traits(compiles, listOfLists.insert(pos, 1)));
1192 
1193         auto immListOfLists = immutable DList!(DList!int)(allocator, list);
1194         assert(immListOfLists.front.front == 2);
1195         static assert(!__traits(compiles, immListOfLists.front.front = 2));
1196     }
1197     assert(equal(list, [2, 2, 3]));
1198 }
1199 
1200 @trusted unittest
1201 {
1202     import std.conv;
1203     SCAlloc statsCollectorAlloc;
1204     auto _allocator = allocatorObject(statsCollectorAlloc);
1205 
1206     () @safe {
1207         testWithStruct(_allocator);
1208         auto bytesUsed = _allocator.impl.bytesUsed;
1209         assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1210                 ~ to!string(bytesUsed) ~ " bytes");
1211     }();
1212 }
1213 
1214 version(unittest) private @trusted void testWithClass(IAllocator allocator)
1215 {
1216     class MyClass
1217     {
1218         int x;
1219         this(int x) { this.x = x; }
1220     }
1221 
1222     MyClass c = new MyClass(10);
1223     {
1224         auto dl = DList!MyClass(allocator, c);
1225         assert(dl.front.x == 10);
1226         assert(dl.front is c);
1227         dl.front.x = 20;
1228     }
1229     assert(c.x == 20);
1230 }
1231 
1232 @trusted unittest
1233 {
1234     import std.conv;
1235     SCAlloc statsCollectorAlloc;
1236     auto _allocator = allocatorObject(statsCollectorAlloc);
1237 
1238     () @safe {
1239         testWithClass(_allocator);
1240         auto bytesUsed = _allocator.impl.bytesUsed;
1241         assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1242                 ~ to!string(bytesUsed) ~ " bytes");
1243     }();
1244 }