1 ///
2 module stdx.collections.slist;
3 
4 import stdx.collections.common;
5 
6 debug(CollectionSList) 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 ///
18 struct SList(T)
19 {
20     import std.experimental.allocator : IAllocator, theAllocator, make, dispose;
21     import std.experimental.allocator.building_blocks.affix_allocator;
22     import std.traits : isImplicitlyConvertible;
23     import std.range.primitives : isInputRange, ElementType;
24     import std.conv : emplace;
25     import core.atomic : atomicOp;
26 
27 //private:
28     struct Node
29     {
30         T _payload;
31         Node *_next;
32 
33         this(T v, Node *n)
34         {
35             debug(CollectionSList) writefln("SList.Node.ctor: Constructing node" ~
36                     " with payload: %s", v);
37             _payload = v;
38             _next = n;
39         }
40 
41         ~this()
42         {
43             debug(CollectionSList) writefln("SList.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     @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     @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(CollectionSList)
82         {
83             writefln("SList.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(CollectionSList) writefln("SList.delRef: Node %s has refcount: %s; will be: %s",
101                 node._payload, *pref, *pref - 1);
102         if (*pref == 0)
103         {
104             debug(CollectionSList) writefln("SList.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);"
139                 ~"}();"
140                 ~"(tmpHead ? tmpNode._next : tmpHead) = newNode;"
141                 ~"tmpNode = newNode;"
142             ~"}"
143             ~"_head = (() @trusted => cast(immutable Node*)(tmpHead))();";
144     }
145 
146 public:
147     this(this _)(IAllocator allocator)
148     {
149         debug(CollectionSList)
150         {
151             writefln("SList.ctor: begin");
152             scope(exit) writefln("SList.ctor: end");
153         }
154         setAllocator(allocator);
155     }
156 
157     ///
158     this(U, this Qualified)(U[] values...)
159     if (isImplicitlyConvertible!(U, T))
160     {
161         this(theAllocator, values);
162     }
163 
164     /// ditto
165     this(U, this Qualified)(IAllocator allocator, U[] values...)
166     if (isImplicitlyConvertible!(U, T))
167     {
168         debug(CollectionSList)
169         {
170             writefln("SList.ctor: begin");
171             scope(exit) writefln("SList.ctor: end");
172         }
173         static if (is(Qualified == immutable) || is(Qualified == const))
174         {
175             mixin(immutableInsert("values"));
176         }
177         else
178         {
179             setAllocator(allocator);
180             insert(0, values);
181         }
182     }
183 
184     /// ditto
185     this(Stuff, this Qualified)(Stuff stuff)
186     if (isInputRange!Stuff
187         && isImplicitlyConvertible!(ElementType!Stuff, T)
188         && !is(Stuff == T[]))
189     {
190         this(theAllocator, stuff);
191     }
192 
193     /// ditto
194     this(Stuff, this Qualified)(IAllocator allocator, Stuff stuff)
195     if (isInputRange!Stuff
196         && isImplicitlyConvertible!(ElementType!Stuff, T)
197         && !is(Stuff == T[]))
198     {
199         debug(CollectionSList)
200         {
201             writefln("SList.ctor: begin");
202             scope(exit) writefln("SList.ctor: end");
203         }
204         static if (is(Qualified == immutable) || is(Qualified == const))
205         {
206             mixin(immutableInsert("stuff"));
207         }
208         else
209         {
210             setAllocator(allocator);
211             insert(0, stuff);
212         }
213     }
214 
215     this(this)
216     {
217         debug(CollectionSList)
218         {
219             writefln("SList.postblit: begin");
220             scope(exit) writefln("SList.postblit: end");
221         }
222         if (_head !is null)
223         {
224             addRef(_head);
225             debug(CollectionSList) writefln("SList.postblit: Node %s has refcount: %s",
226                     _head._payload, *prefCount(_head));
227         }
228     }
229 
230     // Immutable ctors
231     private this(NodeQual, OuroQual, this Qualified)(NodeQual _newHead,
232             OuroQual ouroborosAllocator)
233         if (is(typeof(_head) : typeof(_newHead))
234             && (is(Qualified == immutable) || is(Qualified == const)))
235     {
236         _head = _newHead;
237         _ouroborosAllocator = ouroborosAllocator;
238         if (_head !is null)
239         {
240             addRef(_head);
241             debug(CollectionSList) writefln("SList.ctor immutable: Node %s has "
242                     ~ "refcount: %s", _head._payload, *prefCount(_head));
243         }
244     }
245 
246     @trusted ~this()
247     {
248         debug(CollectionSList)
249         {
250             writefln("SList.dtor: Begin for instance %s of type %s",
251                 cast(size_t)(&this), typeof(this).stringof);
252             scope(exit) writefln("SList.dtor: End for instance %s of type %s",
253                     cast(size_t)(&this), typeof(this).stringof);
254         }
255         destroyUnused();
256     }
257 
258     ///
259     void destroyUnused()
260     {
261         debug(CollectionSList)
262         {
263             writefln("SList.destoryUnused: begin");
264             scope(exit) writefln("SList.destoryUnused: end");
265         }
266         while (_head !is null && *prefCount(_head) == 0)
267         {
268             debug(CollectionSList) writefln("SList.destoryUnused: One ref with head at %s",
269                     _head._payload);
270             Node *tmpNode = _head;
271             _head = _head._next;
272             delRef(tmpNode);
273         }
274 
275         if (_head !is null && *prefCount(_head) > 0)
276         {
277             // We reached a copy, so just remove the head ref, thus deleting
278             // the copy in constant time (we are undoing the postblit)
279             debug(CollectionSList) writefln("SList.destoryUnused: Multiple refs with head at %s",
280                     _head._payload);
281             delRef(_head);
282         }
283     }
284 
285     ///
286     bool isUnique(this _)()
287     {
288         debug(CollectionSList)
289         {
290             writefln("SList.isUnique: begin");
291             scope(exit) writefln("SList.isUnique: end");
292         }
293 
294         Node *tmpNode = (() @trusted => cast(Node*)_head)();
295         while (tmpNode !is null)
296         {
297             if (*prefCount(tmpNode) > 0)
298             {
299                 return false;
300             }
301             tmpNode = tmpNode._next;
302         }
303         return true;
304     }
305 
306     bool empty(this _)()
307     {
308         return _head is null;
309     }
310 
311     ref auto front(this _)()
312     {
313         assert(!empty, "SList.front: List is empty");
314         return _head._payload;
315     }
316 
317     void popFront()
318     {
319         debug(CollectionSList)
320         {
321             writefln("SList.popFront: begin");
322             scope(exit) writefln("SList.popFront: end");
323         }
324         assert(!empty, "SList.popFront: List is empty");
325 
326         Node *tmpNode = _head;
327         _head = _head._next;
328         if (*prefCount(tmpNode) > 0 &&  _head !is null)
329         {
330             // If we have another copy of the list then the refcount
331             // must increase, otherwise it will remain the same
332             // This condition is needed because the recounting is zero based
333             addRef(_head);
334         }
335         delRef(tmpNode);
336     }
337 
338     Qualified tail(this Qualified)()
339     {
340         debug(CollectionSList)
341         {
342             writefln("SList.tail: begin");
343             scope(exit) writefln("SList.tail: end");
344         }
345         assert(!empty, "SList.tail: List is empty");
346 
347         static if (is(Qualified == immutable) || is(Qualified == const))
348         {
349             return typeof(this)(_head._next, _ouroborosAllocator);
350         }
351         else
352         {
353             return .tail(this);
354         }
355     }
356 
357     ref Qualified save(this Qualified)()
358     {
359         debug(CollectionSList)
360         {
361             writefln("SList.save: begin");
362             scope(exit) writefln("SList.save: end");
363         }
364         return this;
365     }
366 
367     typeof(this) dup()
368     {
369         debug(CollectionSList)
370         {
371             writefln("SList.dup: begin");
372             scope(exit) writefln("SList.dup: end");
373         }
374         IAllocator alloc = getAllocator();
375         if (alloc is null)
376         {
377             alloc = theAllocator;
378         }
379         return typeof(this)(alloc, this);
380     }
381 
382     size_t insert(Stuff)(size_t pos, Stuff stuff)
383     if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
384     {
385         debug(CollectionSList)
386         {
387             writefln("SList.insert: begin");
388             scope(exit) writefln("SList.insert: end");
389         }
390         // Ensure we have an allocator. If it was already set, this will do nothing
391         setAllocator(theAllocator);
392 
393         size_t result;
394         Node *tmpNode;
395         Node *tmpHead;
396         foreach (item; stuff)
397         {
398             Node *newNode;
399             () @trusted { newNode = allocator.make!(Node)(item, null); }();
400             (tmpHead ? tmpNode._next : tmpHead) = newNode;
401             tmpNode = newNode;
402             ++result;
403         }
404 
405         if (!tmpNode)
406         {
407             return 0;
408         }
409 
410         Node *needle = _head;
411         Node *needlePrev = null;
412         while (pos)
413         {
414             needlePrev = needle;
415             needle = needle._next;
416             --pos;
417         }
418 
419         tmpNode._next = needle;
420         if (needlePrev is null)
421         {
422             _head = tmpHead;
423         }
424         else
425         {
426             needlePrev._next = tmpHead;
427         }
428         return result;
429     }
430 
431     size_t insert(Stuff)(size_t pos, Stuff[] stuff...)
432     if (isImplicitlyConvertible!(Stuff, T))
433     {
434         return insert(pos, stuff);
435     }
436 
437     size_t insertBack(Stuff)(Stuff stuff)
438     if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
439     {
440         debug(CollectionSList)
441         {
442             writefln("SList.insertBack: begin");
443             scope(exit) writefln("SList.insertBack: end");
444         }
445         // Ensure we have an allocator. If it was already set, this will do nothing
446         setAllocator(theAllocator);
447 
448         size_t result;
449         Node *tmpNode;
450         Node *tmpHead;
451         foreach (item; stuff)
452         {
453             Node *newNode;
454             () @trusted { newNode = allocator.make!(Node)(item, null); }();
455             (tmpHead ? tmpNode._next : tmpHead) = newNode;
456             tmpNode = newNode;
457             ++result;
458         }
459 
460         if (!tmpNode)
461         {
462             return 0;
463         }
464 
465         if (_head is null)
466         {
467             _head = tmpHead;
468         }
469         else
470         {
471             Node *endNode;
472             for (endNode = _head; endNode._next !is null; endNode = endNode._next) { }
473             endNode._next = tmpHead;
474         }
475 
476         return result;
477     }
478 
479     size_t insertBack(Stuff)(Stuff[] stuff...)
480     if (isImplicitlyConvertible!(Stuff, T))
481     {
482         return insertBack(stuff);
483     }
484 
485     auto ref opBinary(string op, U)(auto ref U rhs)
486         if (op == "~" &&
487             (is (U == typeof(this))
488              || is (U : T)
489              || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
490             ))
491     {
492         debug(CollectionSList)
493         {
494             writefln("SList.opBinary!~: begin");
495             scope(exit) writefln("SList.opBinary!~: end");
496         }
497 
498         IAllocator alloc = getAllocator();
499         if (alloc is null)
500         {
501             static if (is(U == typeof(this)))
502             {
503                 alloc = rhs.getAllocator();
504             }
505             else
506             {
507                 alloc = null;
508             }
509             if (alloc is null)
510             {
511                 alloc = theAllocator;
512             }
513         }
514         typeof(this) newList = typeof(this)(alloc, rhs);
515         newList.insert(0, this);
516         return newList;
517     }
518 
519     auto ref opAssign()(auto ref typeof(this) rhs)
520     {
521         debug(CollectionSList)
522         {
523             writefln("SList.opAssign: begin");
524             scope(exit) writefln("SList.opAssign: end");
525         }
526 
527         if (rhs._head !is null && _head is rhs._head)
528         {
529             return this;
530         }
531 
532         if (rhs._head !is null)
533         {
534             rhs.addRef(rhs._head);
535             debug(CollectionSList) writefln("SList.opAssign: Node %s has refcount: %s",
536                     rhs._head._payload, *prefCount(rhs._head));
537         }
538         destroyUnused();
539         _head = rhs._head;
540         _ouroborosAllocator = rhs._ouroborosAllocator;
541 
542         return this;
543     }
544 
545     auto ref opOpAssign(string op, U)(auto ref U rhs)
546         if (op == "~" &&
547             (is (U == typeof(this))
548              || is (U : T)
549              || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
550             ))
551     {
552         debug(CollectionSList)
553         {
554             writefln("SList.opOpAssign!~: %s begin", typeof(this).stringof);
555             scope(exit) writefln("SList.opOpAssign!~: %s end", typeof(this).stringof);
556         }
557 
558         insertBack(rhs);
559         return this;
560     }
561 
562     void remove(size_t idx = 0)
563     {
564         assert(!empty, "SList.remove: List is empty");
565         if (idx == 0)
566         {
567             popFront();
568             return;
569         }
570 
571         Node *tmpNode = _head;
572         while(--idx != 0)
573         {
574             tmpNode = tmpNode._next;
575         }
576         Node *toDel = tmpNode._next;
577         assert(toDel !is null, "SList.remove: Index out of bounds");
578         tmpNode._next = tmpNode._next._next;
579         delRef(toDel);
580     }
581 
582     debug(CollectionSList) void printRefCount(this _)()
583     {
584         writefln("SList.printRefCount: begin");
585         scope(exit) writefln("SList.printRefCount: end");
586 
587         Node *tmpNode = (() @trusted => cast(Node*)_head)();
588         while (tmpNode !is null)
589         {
590             writefln("SList.printRefCount: Node %s has ref count %s",
591                     tmpNode._payload, *prefCount(tmpNode));
592             tmpNode = tmpNode._next;
593         }
594     }
595 }
596 
597 version(unittest) private @safe void testImmutability(IAllocator allocator)
598 {
599     auto s = immutable SList!(int)(allocator, 1, 2, 3);
600     auto s2 = s;
601     auto s3 = s2.save();
602 
603     assert(s2.front == 1);
604     static assert(!__traits(compiles, s2.front = 4));
605     static assert(!__traits(compiles, s2.popFront()));
606 
607     auto s4 = s2.tail;
608     assert(s4.front == 2);
609     static assert(!__traits(compiles, s4 = s4.tail));
610 }
611 
612 version(unittest) private @safe void testConstness(IAllocator allocator)
613 {
614     auto s = const SList!(int)(allocator, 1, 2, 3);
615     auto s2 = s;
616     auto s3 = s2.save();
617 
618     assert(s2.front == 1);
619     static assert(!__traits(compiles, s2.front = 4));
620     static assert(!__traits(compiles, s2.popFront()));
621 
622     auto s4 = s2.tail;
623     assert(s4.front == 2);
624     static assert(!__traits(compiles, s4 = s4.tail));
625 }
626 
627 @trusted unittest
628 {
629     import std.conv;
630     SCAlloc statsCollectorAlloc;
631     auto _allocator = allocatorObject(statsCollectorAlloc);
632 
633     () @safe {
634         testImmutability(_allocator);
635         testConstness(_allocator);
636         auto bytesUsed = _allocator.impl.bytesUsed;
637         assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
638                 ~ to!string(bytesUsed) ~ " bytes");
639     }();
640 
641     //() @nogc {
642         //_allocator.allocate(1);
643     //}();
644 
645     //() @nogc {
646         //import std.experimental.allocator.building_blocks.affix_allocator;
647         //import std.stdio;
648         //auto a = AffixAllocator!(Mallocator, size_t).instance;
649         //auto ia = allocatorObject(a);
650         //pragma(msg, typeof(a));
651         //auto b = ia.allocate(1);
652         //pragma(msg, typeof(ia.impl.prefix(b)));
653     //}();
654 }
655 
656 version(unittest) private @safe void testConcatAndAppend(IAllocator allocator)
657 {
658     import std.algorithm.comparison : equal;
659 
660     auto sl = SList!(int)(allocator, 1, 2, 3);
661     SList!(int) sl2 = SList!int(allocator);
662 
663     auto sl3 = sl ~ sl2;
664     assert(equal(sl3, [1, 2, 3]));
665 
666     auto sl4 = sl3;
667     sl3 = sl3 ~ 4;
668     assert(equal(sl3, [1, 2, 3, 4]));
669     sl3 = sl3 ~ [5];
670     assert(equal(sl3, [1, 2, 3, 4, 5]));
671     assert(equal(sl4, [1, 2, 3]));
672 
673     sl4 = sl3;
674     sl3 ~= 6;
675     assert(equal(sl3, [1, 2, 3, 4, 5, 6]));
676     sl3 ~= [7];
677     assert(equal(sl3, [1, 2, 3, 4, 5, 6, 7]));
678     assert(equal(sl4, [1, 2, 3, 4, 5, 6, 7]));
679 
680     sl3 ~= sl3;
681     assert(equal(sl3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]));
682     assert(equal(sl4, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]));
683 
684     SList!int sl5 = SList!int(allocator);
685     sl5 ~= [1, 2, 3];
686     assert(equal(sl5, [1, 2, 3]));
687 }
688 
689 @trusted unittest
690 {
691     import std.conv;
692     SCAlloc statsCollectorAlloc;
693     auto _allocator = allocatorObject(statsCollectorAlloc);
694 
695     () @safe {
696         testConcatAndAppend(_allocator);
697         auto bytesUsed = _allocator.impl.bytesUsed;
698         assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
699                 ~ to!string(bytesUsed) ~ " bytes");
700     }();
701 }
702 
703 version(unittest) private @safe void testSimple(IAllocator allocator)
704 {
705     import std.algorithm.comparison : equal;
706     import std.algorithm.searching : canFind;
707     import std.range.primitives : walkLength;
708 
709     auto sl = SList!int(allocator);
710     assert(sl.empty);
711     assert(sl.isUnique);
712 
713     size_t pos = 0;
714     sl.insert(pos, 1, 2, 3);
715     assert(sl.front == 1);
716     assert(equal(sl, sl));
717     assert(equal(sl, [1, 2, 3]));
718 
719     sl.popFront();
720     assert(sl.front == 2);
721     assert(equal(sl, [2, 3]));
722 
723     sl.insert(pos, [4, 5, 6]);
724     sl.insert(pos, 7);
725     sl.insert(pos, [8]);
726     assert(equal(sl, [8, 7, 4, 5, 6, 2, 3]));
727 
728     sl.insertBack(0, 1);
729     sl.insertBack([-1, -2]);
730     assert(equal(sl, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
731 
732     sl.front = 9;
733     assert(equal(sl, [9, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
734 
735     auto slTail = sl.tail;
736     assert(slTail.front == 7);
737     slTail.front = 8;
738     assert(slTail.front == 8);
739     assert(sl.tail.front == 8);
740     assert(!sl.isUnique);
741 
742     assert(canFind(sl, 2));
743     assert(!canFind(sl, -10));
744 
745     sl.remove();
746     assert(equal(sl, [8, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
747     sl.remove(2);
748     assert(equal(sl, [8, 4, 6, 2, 3, 0, 1, -1, -2]));
749     sl.remove(walkLength(sl) - 1);
750     assert(equal(sl, [8, 4, 6, 2, 3, 0, 1, -1]));
751     pos = 1;
752     sl.insert(pos, 10);
753     assert(equal(sl, [8, 10, 4, 6, 2, 3, 0, 1, -1]));
754 }
755 
756 @trusted unittest
757 {
758     import std.conv;
759     SCAlloc statsCollectorAlloc;
760     auto _allocator = allocatorObject(statsCollectorAlloc);
761 
762     () @safe {
763         testSimple(_allocator);
764         auto bytesUsed = _allocator.impl.bytesUsed;
765         assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
766                 ~ to!string(bytesUsed) ~ " bytes");
767     }();
768 }
769 
770 version(unittest) private @safe void testSimpleImmutable(IAllocator allocator)
771 {
772     import std.algorithm.comparison : equal;
773     import std.algorithm.searching : canFind;
774     import std.range.primitives : walkLength;
775 
776     auto sl = SList!(immutable int)(allocator);
777     assert(sl.empty);
778 
779     size_t pos = 0;
780     sl.insert(pos, 1, 2, 3);
781     assert(sl.front == 1);
782     assert(equal(sl, sl));
783     assert(equal(sl, [1, 2, 3]));
784 
785     sl.popFront();
786     assert(sl.front == 2);
787     assert(equal(sl, [2, 3]));
788     assert(sl.tail.front == 3);
789 
790     sl.insert(pos, [4, 5, 6]);
791     sl.insert(pos, 7);
792     sl.insert(pos, [8]);
793     assert(equal(sl, [8, 7, 4, 5, 6, 2, 3]));
794 
795     sl.insertBack(0, 1);
796     sl.insertBack([-1, -2]);
797     assert(equal(sl, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
798 
799     // Cannot modify immutable values
800     static assert(!__traits(compiles, sl.front = 9));
801 
802     assert(canFind(sl, 2));
803     assert(!canFind(sl, -10));
804 
805     sl.remove();
806     assert(equal(sl, [7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
807     sl.remove(2);
808     assert(equal(sl, [7, 4, 6, 2, 3, 0, 1, -1, -2]));
809     sl.remove(walkLength(sl) - 1);
810     assert(equal(sl, [7, 4, 6, 2, 3, 0, 1, -1]));
811 }
812 
813 @trusted unittest
814 {
815     import std.conv;
816     SCAlloc statsCollectorAlloc;
817     auto _allocator = allocatorObject(statsCollectorAlloc);
818 
819     () @safe {
820         testSimpleImmutable(_allocator);
821         auto bytesUsed = _allocator.impl.bytesUsed;
822         assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
823                 ~ to!string(bytesUsed) ~ " bytes");
824     }();
825 }
826 
827 version(unittest) private @safe void testCopyAndRef(IAllocator allocator)
828 {
829     import std.algorithm.comparison : equal;
830 
831     auto slFromList = SList!int(allocator, 1, 2, 3);
832     auto slFromRange = SList!int(allocator, slFromList);
833     assert(equal(slFromList, slFromRange));
834 
835     slFromList.popFront();
836     assert(equal(slFromList, [2, 3]));
837     assert(equal(slFromRange, [1, 2, 3]));
838 
839     SList!int slInsFromRange = SList!int(allocator);
840     size_t pos = 0;
841     slInsFromRange.insert(pos, slFromList);
842     slFromList.popFront();
843     assert(equal(slFromList, [3]));
844     assert(equal(slInsFromRange, [2, 3]));
845 
846     SList!int slInsBackFromRange = SList!int(allocator);
847     slInsBackFromRange.insert(pos, slFromList);
848     slFromList.popFront();
849     assert(slFromList.empty);
850     assert(equal(slInsBackFromRange, [3]));
851 
852     auto slFromRef = slInsFromRange;
853     auto slFromDup = slInsFromRange.dup;
854     assert(slInsFromRange.front == 2);
855     slFromRef.front = 5;
856     assert(slInsFromRange.front == 5);
857     assert(slFromDup.front == 2);
858 }
859 
860 @trusted unittest
861 {
862     import std.conv;
863     SCAlloc statsCollectorAlloc;
864     auto _allocator = allocatorObject(statsCollectorAlloc);
865 
866     () @safe {
867         testCopyAndRef(_allocator);
868         auto bytesUsed = _allocator.impl.bytesUsed;
869         assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
870                 ~ to!string(bytesUsed) ~ " bytes");
871     }();
872 }
873 
874 version(unittest) private @safe void testWithStruct(IAllocator allocator)
875 {
876     import std.algorithm.comparison : equal;
877 
878     auto list = SList!int(allocator, 1, 2, 3);
879     {
880         auto listOfLists = SList!(SList!int)(allocator, list);
881         assert(equal(listOfLists.front, [1, 2, 3]));
882         listOfLists.front.front = 2;
883         assert(equal(listOfLists.front, [2, 2, 3]));
884         size_t pos = 0;
885         static assert(!__traits(compiles, listOfLists.insert(pos, 1)));
886 
887         auto immListOfLists = immutable SList!(SList!int)(allocator, list);
888         assert(immListOfLists.front.front == 2);
889         static assert(!__traits(compiles, immListOfLists.front.front = 2));
890     }
891     assert(equal(list, [2, 2, 3]));
892 }
893 
894 @trusted unittest
895 {
896     import std.conv;
897     SCAlloc statsCollectorAlloc;
898     auto _allocator = allocatorObject(statsCollectorAlloc);
899 
900     () @safe {
901         testWithStruct(_allocator);
902         auto bytesUsed = _allocator.impl.bytesUsed;
903         assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
904                 ~ to!string(bytesUsed) ~ " bytes");
905     }();
906 }
907 
908 version(unittest) private @safe void testWithClass(IAllocator allocator)
909 {
910     class MyClass
911     {
912         int x;
913         this(int x) { this.x = x; }
914     }
915 
916     MyClass c = new MyClass(10);
917     {
918         auto sl = SList!MyClass(allocator, c);
919         assert(sl.front.x == 10);
920         assert(sl.front is c);
921         sl.front.x = 20;
922     }
923     assert(c.x == 20);
924 }
925 
926 @trusted unittest
927 {
928     import std.conv;
929     SCAlloc statsCollectorAlloc;
930     auto _allocator = allocatorObject(statsCollectorAlloc);
931 
932     () @safe {
933         testWithClass(_allocator);
934         auto bytesUsed = _allocator.impl.bytesUsed;
935         assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
936                 ~ to!string(bytesUsed) ~ " bytes");
937     }();
938 }