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