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