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 : RCIAllocator, RCISharedAllocator,
13            allocatorObject, sharedAllocatorObject;
14 
15     private alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed);
16 }
17 
18 ///
19 struct SList(T)
20 {
21     import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
22            theAllocator, processAllocator, make, dispose, stateSize;
23     import std.experimental.allocator.building_blocks.affix_allocator;
24     import std.traits : isImplicitlyConvertible;
25     import std.range.primitives : isInputRange, ElementType;
26     import std.variant : Algebraic;
27     import std.conv : emplace;
28     import core.atomic : atomicOp;
29     import std.algorithm.mutation : move;
30 
31 private:
32     // TODO: should this be static struct?
33     struct Node
34     {
35         T _payload;
36         Node *_next;
37 
38         this(T v, Node *n)
39         {
40             debug(CollectionSList) writefln("SList.Node.ctor: Constructing node" ~
41                     " with payload: %s", v);
42             _payload = v;
43             _next = n;
44         }
45 
46         ~this()
47         {
48             debug(CollectionSList) writefln("SList.Node.dtor: Destroying node" ~
49                     " with payload: %s", _payload);
50         }
51     }
52 
53     // State {
54     Node *_head;
55     mixin(allocatorHandler);
56     // }
57 
58     @nogc nothrow pure @trusted
59     void addRef(QualNode, this Q)(QualNode node)
60     {
61         assert(node !is null);
62         cast(void) _allocator.opPrefix!("+=")(cast(void[Node.sizeof])(*node), 1);
63     }
64 
65     void delRef(ref Node *node)
66     {
67         // Will be optimized away, but the type system infers T's safety
68         if (0) { T t = T.init; }
69 
70         assert(node !is null);
71         //if (_allocator.opPrefix!("-=")(cast(void[Node.sizeof])(*node), 1) == 0)
72         //{
73             //debug(CollectionSList) writefln("SList.delRef: Deleting node %s", node._payload);
74             //dispose(_allocator, node);
75             //node = null;
76         //}
77         () @trusted {
78             if (opCmpPrefix!"=="(node, 0))
79             {
80                 dispose(_allocator, node);
81                 node = null;
82             }
83             else
84             {
85                 cast(void) _allocator.opPrefix!("-=")(cast(void[Node.sizeof])(*node), 1);
86             }
87         }();
88     }
89 
90     pragma(inline, true)
91     @nogc nothrow pure @trusted
92     size_t opCmpPrefix(string op)(const Node *node, size_t val) const
93     if ((op == "==") || (op == "<=") || (op == "<") || (op == ">=") || (op == ">"))
94     {
95         return _allocator.opCmpPrefix!op(cast(void[Node.sizeof])(*node), val);
96     }
97 
98     static string immutableInsert(string stuff)
99     {
100         return q{
101             _allocator = immutable AllocatorHandler(allocator);
102             Node *tmpNode;
103             Node *tmpHead;
104             foreach (item;  } ~ stuff ~ q{ )
105             {
106                 Node *newNode;
107                 () @trusted { newNode =
108                     _allocator.make!(Node)(item, null);
109                 }();
110                 (tmpHead ? tmpNode._next : tmpHead) = newNode;
111                 tmpNode = newNode;
112             }
113             _head = (() @trusted => cast(immutable Node*)(tmpHead))();
114         };
115     }
116 
117 public:
118     /**
119      * Constructs a qualified singly linked list that will use the provided
120      * allocator object. For `immutable` objects, a `RCISharedAllocator` must
121      * be supplied.
122      *
123      * Params:
124      *      allocator = a $(REF RCIAllocator, std,experimental,allocator) or
125      *                  $(REF RCISharedAllocator, std,experimental,allocator)
126      *                  allocator object
127      *
128      * Complexity: $(BIGOH 1)
129      */
130     this(A, this Q)(A allocator)
131     if (!is(Q == shared)
132         && (is(A == RCISharedAllocator) || !is(Q == immutable))
133         && (is(A == RCIAllocator) || is(A == RCISharedAllocator)))
134     {
135         debug(CollectionSList)
136         {
137             writefln("SList.ctor: begin");
138             scope(exit) writefln("SList.ctor: end");
139         }
140         static if (is(Q == immutable) || is(Q == const))
141         {
142             T[] empty;
143             this(allocator, empty);
144         }
145         else
146         {
147             setAllocator(allocator);
148         }
149     }
150 
151     ///
152     static if (is(T == int))
153     @safe unittest
154     {
155         auto sl = SList!int(theAllocator);
156         auto csl = const SList!int(processAllocator);
157         auto isl = immutable SList!int(processAllocator);
158     }
159 
160     /**
161      * Constructs a qualified singly linked list out of a number of items.
162      * Because no allocator was provided, the list will use the
163      * $(REF, GCAllocator, std,experimental,allocator).
164      *
165      * Params:
166      *      values = a variable number of items, either in the form of a
167      *               list or as a built-in array
168      *
169      * Complexity: $(BIGOH m), where `m` is the number of items.
170      */
171     this(U, this Q)(U[] values...)
172     if (isImplicitlyConvertible!(U, T))
173     {
174         this(defaultAllocator!(typeof(this)), values);
175     }
176 
177     ///
178     static if (is(T == int))
179     @safe unittest
180     {
181         import std.algorithm.comparison : equal;
182 
183         // Create a list from a list of ints
184         {
185             auto sl = SList!int(1, 2, 3);
186             assert(equal(sl, [1, 2, 3]));
187         }
188         // Create a list from an array of ints
189         {
190             auto sl = SList!int([1, 2, 3]);
191             assert(equal(sl, [1, 2, 3]));
192         }
193         // Create a list from a list from an input range
194         {
195             auto sl = SList!int(1, 2, 3);
196             auto sl2 = SList!int(sl);
197             assert(equal(sl2, [1, 2, 3]));
198         }
199     }
200 
201     /**
202      * Constructs a qualified singly linked list out of a number of items
203      * that will use the provided allocator object.
204      * For `immutable` objects, a `RCISharedAllocator` must be supplied.
205      *
206      * Params:
207      *      allocator = a $(REF RCIAllocator, std,experimental,allocator) or
208      *                  $(REF RCISharedAllocator, std,experimental,allocator)
209      *                  allocator object
210      *      values = a variable number of items, either in the form of a
211      *               list or as a built-in array
212      *
213      * Complexity: $(BIGOH m), where `m` is the number of items.
214      */
215     this(A, U, this Q)(A allocator, U[] values...)
216     if (!is(Q == shared)
217         && (is(A == RCISharedAllocator) || !is(Q == immutable))
218         && (is(A == RCIAllocator) || is(A == RCISharedAllocator))
219         && isImplicitlyConvertible!(U, T))
220     {
221         debug(CollectionSList)
222         {
223             writefln("SList.ctor: begin");
224             scope(exit) writefln("SList.ctor: end");
225         }
226         static if (is(Q == immutable) || is(Q == const))
227         {
228             mixin(immutableInsert("values"));
229         }
230         else
231         {
232             setAllocator(allocator);
233             insert(0, values);
234         }
235     }
236 
237     /**
238      * Constructs a qualified singly linked list out of an
239      * $(REF_ALTTEXT input range, isInputRange, std,range,primitives).
240      * Because no allocator was provided, the list will use the
241      * $(REF, GCAllocator, std,experimental,allocator).
242      *
243      * Params:
244      *      stuff = an input range of elements that are implitictly convertible
245      *              to `T`
246      *
247      * Complexity: $(BIGOH m), where `m` is the number of elements in the range.
248      */
249     this(Stuff, this Q)(Stuff stuff)
250     if (isInputRange!Stuff
251         && isImplicitlyConvertible!(ElementType!Stuff, T)
252         && !is(Stuff == T[]))
253     {
254         this(defaultAllocator!(typeof(this)), stuff);
255     }
256 
257     /**
258      * Constructs a qualified singly linked list out of an
259      * $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
260      * that will use the provided allocator object.
261      * For `immutable` objects, a `RCISharedAllocator` must be supplied.
262      *
263      * Params:
264      *      allocator = a $(REF RCIAllocator, std,experimental,allocator) or
265      *                  $(REF RCISharedAllocator, std,experimental,allocator)
266      *                  allocator object
267      *      stuff = an input range of elements that are implitictly convertible
268      *              to `T`
269      *
270      * Complexity: $(BIGOH m), where `m` is the number of elements in the range.
271      */
272     this(A, Stuff, this Q)(A allocator, Stuff stuff)
273     if (!is(Q == shared)
274         && (is(A == RCISharedAllocator) || !is(Q == immutable))
275         && (is(A == RCIAllocator) || is(A == RCISharedAllocator))
276         && isInputRange!Stuff
277         && isImplicitlyConvertible!(ElementType!Stuff, T)
278         && !is(Stuff == T[]))
279     {
280         debug(CollectionSList)
281         {
282             writefln("SList.ctor: begin");
283             scope(exit) writefln("SList.ctor: end");
284         }
285         static if (is(Q == immutable) || is(Q == const))
286         {
287             mixin(immutableInsert("stuff"));
288         }
289         else
290         {
291             setAllocator(allocator);
292             insert(0, stuff);
293         }
294     }
295 
296     this(this)
297     {
298         debug(CollectionSList)
299         {
300             writefln("SList.postblit: begin");
301             scope(exit) writefln("SList.postblit: end");
302         }
303         _allocator.bootstrap();
304         if (_head !is null)
305         {
306             addRef(_head);
307             debug(CollectionSList) writefln("SList.postblit: Node %s has refcount: %s",
308                     _head._payload, *prefCount(_head));
309         }
310     }
311 
312     // Immutable ctors
313     // Very important to pass the allocator by ref! (Related to postblit bug)
314     private this(NodeQual, AllocQual, this Q)(NodeQual _newHead, ref AllocQual _newAllocator)
315         if (is(typeof(_head) : typeof(_newHead))
316             && (is(Q == immutable) || is(Q == const)))
317     {
318         _head = _newHead;
319         // Needs a bootstrap
320         // bootstrap is the equivalent of incRef
321         _newAllocator.bootstrap();
322         _allocator = _newAllocator;
323         if (_head !is null)
324         {
325             addRef(_head);
326             debug(CollectionSList) writefln("SList.ctor immutable: Node %s has "
327                     ~ "refcount: %s", _head._payload, *sharedPrefCount(_head));
328         }
329     }
330 
331     ~this()
332     {
333         debug(CollectionSList)
334         {
335             writefln("SList.dtor: Begin for instance %s of type %s",
336                 cast(size_t)(&this), typeof(this).stringof);
337             scope(exit) writefln("SList.dtor: End for instance %s of type %s",
338                     cast(size_t)(&this), typeof(this).stringof);
339         }
340         destroyUnused();
341     }
342 
343     static if (is(T == int))
344     nothrow pure @safe unittest
345     {
346         auto s = SList!int(1, 2, 3);
347 
348         // Infer safety
349         static assert(!__traits(compiles, () @safe { SList!Unsafe(Unsafe(1)); }));
350         static assert(!__traits(compiles, () @safe { auto s = const SList!Unsafe(Unsafe(1)); }));
351         static assert(!__traits(compiles, () @safe { auto s = immutable SList!Unsafe(Unsafe(1)); }));
352 
353         static assert(!__traits(compiles, () @safe { SList!UnsafeDtor(UnsafeDtor(1)); }));
354         static assert(!__traits(compiles, () @safe { auto s = const SList!UnsafeDtor(UnsafeDtor(1)); }));
355         static assert(!__traits(compiles, () @safe { auto s = immutable SList!UnsafeDtor(UnsafeDtor(1)); }));
356 
357         // Infer purity
358         static assert(!__traits(compiles, () pure { SList!Impure(Impure(1)); }));
359         static assert(!__traits(compiles, () pure { auto s = const SList!Impure(Impure(1)); }));
360         static assert(!__traits(compiles, () pure { auto s = immutable SList!Impure(Impure(1)); }));
361 
362         static assert(!__traits(compiles, () pure { SList!ImpureDtor(ImpureDtor(1)); }));
363         static assert(!__traits(compiles, () pure { auto s = const SList!ImpureDtor(ImpureDtor(1)); }));
364         static assert(!__traits(compiles, () pure { auto s = immutable SList!ImpureDtor(ImpureDtor(1)); }));
365 
366         // Infer throwability
367         static assert(!__traits(compiles, () nothrow { SList!Throws(Throws(1)); }));
368         static assert(!__traits(compiles, () nothrow { auto s = const SList!Throws(Throws(1)); }));
369         static assert(!__traits(compiles, () nothrow { auto s = immutable SList!Throws(Throws(1)); }));
370 
371         static assert(!__traits(compiles, () nothrow { SList!ThrowsDtor(ThrowsDtor(1)); }));
372         static assert(!__traits(compiles, () nothrow { auto s = const SList!ThrowsDtor(ThrowsDtor(1)); }));
373         static assert(!__traits(compiles, () nothrow { auto s = immutable SList!ThrowsDtor(ThrowsDtor(1)); }));
374     }
375 
376     private void destroyUnused()
377     {
378         debug(CollectionSList)
379         {
380             writefln("SList.destoryUnused: begin");
381             scope(exit) writefln("SList.destoryUnused: end");
382         }
383         while (_head !is null && opCmpPrefix!"=="(_head, 0))
384         {
385             debug(CollectionSList) writefln("SList.destoryUnused: One ref with head at %s",
386                     _head._payload);
387             Node *tmpNode = _head;
388             _head = _head._next;
389             delRef(tmpNode);
390         }
391 
392         if (_head !is null && opCmpPrefix!">"(_head, 0))
393         {
394             // We reached a copy, so just remove the head ref, thus deleting
395             // the copy in constant time (we are undoing the postblit)
396             debug(CollectionSList) writefln("SList.destoryUnused: Multiple refs with head at %s",
397                     _head._payload);
398             delRef(_head);
399         }
400     }
401 
402     /**
403      * Check whether there are no more references to this list instance.
404      *
405      * Returns:
406      *      `true` if this is the only reference to this list instance;
407      *      `false` otherwise.
408      *
409      * Complexity: $(BIGOH n).
410      */
411     bool isUnique() const
412     {
413         debug(CollectionSList)
414         {
415             writefln("SList.isUnique: begin");
416             scope(exit) writefln("SList.isUnique: end");
417         }
418 
419         // TODO: change this to tail-impl for const/imm types
420         Node *tmpNode = (() @trusted => cast(Node*)_head)();
421         while (tmpNode !is null)
422         {
423             if (opCmpPrefix!">"(tmpNode, 0))
424             {
425                 return false;
426             }
427             tmpNode = tmpNode._next;
428         }
429         return true;
430     }
431 
432     ///
433     static if (is(T == int))
434     @safe unittest
435     {
436         auto sl = SList!int(24, 42);
437         assert(sl.isUnique);
438         {
439             auto sl2 = sl;
440             assert(!sl.isUnique);
441             sl2.front = 0;
442             assert(sl.front == 0);
443         } // sl2 goes out of scope
444         assert(sl.isUnique);
445     }
446 
447     /**
448      * Check if the list is empty.
449      *
450      * Returns:
451      *      `true` if there are no nodes in the list; `false` otherwise.
452      *
453      * Complexity: $(BIGOH 1).
454      */
455     @nogc nothrow pure @safe
456     bool empty() const
457     {
458         return _head is null;
459     }
460 
461     ///
462     static if (is(T == int))
463     @safe unittest
464     {
465         SList!int sl;
466         assert(sl.empty);
467         size_t pos = 0;
468         sl.insert(pos, 1);
469         assert(!sl.empty);
470     }
471 
472     /**
473      * Provide access to the first element in the list. The user must check
474      * that the list isn't `empty`, prior to calling this function.
475      *
476      * Returns:
477      *      a reference to the first element.
478      *
479      * Complexity: $(BIGOH 1).
480      */
481     ref auto front(this _)()
482     {
483         assert(!empty, "SList.front: List is empty");
484         return _head._payload;
485     }
486 
487     ///
488     static if (is(T == int))
489     @safe unittest
490     {
491         auto sl = SList!int(1, 2, 3);
492         assert(sl.front == 1);
493         sl.front = 0;
494         assert(sl.front == 0);
495     }
496 
497     /**
498      * Advance to the next element in the list. The user must check
499      * that the list isn't `empty`, prior to calling this function.
500      *
501      * If there are no more references to the current element (which is being
502      * consumed), then the current element will be destroyed; this will call
503      * `T`'s dtor, if one is defined, and will collect it's resources.
504      *
505      * Complexity: $(BIGOH 1).
506      */
507     void popFront()
508     {
509         debug(CollectionSList)
510         {
511             writefln("SList.popFront: begin");
512             scope(exit) writefln("SList.popFront: end");
513         }
514         assert(!empty, "SList.popFront: List is empty");
515 
516         Node *tmpNode = _head;
517         _head = _head._next;
518         if (opCmpPrefix!">"(tmpNode, 0) && _head !is null)
519         {
520             // If we have another copy of the list then the refcount
521             // must increase, otherwise it will remain the same
522             // This condition is needed because the recounting is zero based
523             addRef(_head);
524         }
525         delRef(tmpNode);
526     }
527 
528     ///
529     static if (is(T == int))
530     @safe unittest
531     {
532         auto a = [1, 2, 3];
533         auto sl = SList!int(a);
534         size_t i = 0;
535         while (!sl.empty)
536         {
537             assert(sl.front == a[i++]);
538             sl.popFront;
539         }
540         assert(sl.empty);
541     }
542 
543     /**
544      * Advance to the next element in the list. The user must check
545      * that the list isn't `empty`, prior to calling this function.
546      *
547      * This must be used in order to iterate through a `const` or `immutable`
548      * list. For a mutable list this is equivalent to calling `popFront`.
549      *
550      * Returns:
551      *      a list that starts with the next element in the original list
552      *
553      * Complexity: $(BIGOH 1).
554      */
555     Qualified tail(this Qualified)()
556     {
557         debug(CollectionSList)
558         {
559             writefln("SList.tail: begin");
560             scope(exit) writefln("SList.tail: end");
561         }
562         assert(!empty, "SList.tail: List is empty");
563 
564         static if (is(Qualified == immutable) || is(Qualified == const))
565         {
566             return Qualified(_head._next, _allocator);
567         }
568         else
569         {
570             return .tail(this);
571         }
572     }
573 
574     ///
575     static if (is(T == int))
576     @safe unittest
577     {
578         auto isl = immutable SList!int([1, 2, 3]);
579         assert(isl.tail.front == 2);
580     }
581 
582     /**
583      * Eagerly iterate over each element in the list and call `fun` over each
584      * element. This should be used to iterate through `const` and `immutable`
585      * lists.
586      *
587      * Normally, the entire list is iterated. If partial iteration (early stopping)
588      * is desired, `fun` needs to return a value of type
589      * $(REF Flag, std,typecons)`!"each"` (`Yes.each` to continue iteration, or
590      * `No.each` to stop).
591      *
592      * Params:
593      *      fun = unary function to apply on each element of the list.
594      *
595      * Returns:
596      *      `Yes.each` if it has iterated through all the elements in the list,
597      *      or `No.each` otherwise.
598      *
599      * Complexity: $(BIGOH n).
600      */
601     template each(alias fun)
602     {
603         import std.typecons : Flag, Yes, No;
604         import std.functional : unaryFun;
605 
606         Flag!"each" each(this Q)()
607         if (is (typeof(unaryFun!fun(T.init))))
608         {
609             alias fn = unaryFun!fun;
610 
611             auto sl = SList!(const SList!T)(this);
612             while (!sl.empty && !sl.front.empty)
613             {
614                 static if (!is(typeof(fn(T.init)) == Flag!"each"))
615                 {
616                     cast(void) fn(sl.front.front);
617                 }
618                 else
619                 {
620                     if (fn(sl.front.front) == No.each)
621                         return No.each;
622                 }
623                 sl ~= sl.front.tail;
624                 sl.popFront;
625             }
626             return Yes.each;
627         }
628     }
629 
630     ///
631     static if (is(T == int))
632     @safe unittest
633     {
634         import std.typecons : Flag, Yes, No;
635 
636         auto isl = immutable SList!int([1, 2, 3]);
637 
638         static bool foo(int x) { return x > 0; }
639 
640         assert(isl.each!foo == Yes.each);
641     }
642 
643     /**
644      * Perform a shallow copy of the list.
645      *
646      * Returns:
647      *      a new reference to the current list.
648      *
649      * Complexity: $(BIGOH 1).
650      */
651     ref Qualified save(this Qualified)()
652     {
653         debug(CollectionSList)
654         {
655             writefln("SList.save: begin");
656             scope(exit) writefln("SList.save: end");
657         }
658         return this;
659     }
660 
661     ///
662     static if (is(T == int))
663     @safe unittest
664     {
665         auto a = [1, 2, 3];
666         auto sl = SList!int(a);
667         size_t i = 0;
668 
669         auto tmp = sl.save;
670         while (!tmp.empty)
671         {
672             assert(tmp.front == a[i++]);
673             tmp.popFront;
674         }
675         assert(tmp.empty);
676         assert(!sl.empty);
677     }
678 
679     /**
680      * Perform a copy of the list. This will create a new list that will copy
681      * the elements of the current list. This will `NOT` call `dup` on the
682      * elements of the list, regardless if `T` defines it or not.
683      *
684      * Returns:
685      *      a new list.
686      *
687      * Complexity: $(BIGOH n).
688      */
689     SList!T dup(this Q)()
690     {
691         debug(CollectionSList)
692         {
693             writefln("SList.dup: begin");
694             scope(exit) writefln("SList.dup: end");
695         }
696 
697         SList!T result;
698         result._allocator = _allocator;
699 
700         static if (is(Q == immutable) || is(Q == const))
701         {
702             static void appendEach(ref SList!T sl, const SList!T isl)
703             {
704                 if (isl.empty) return;
705                 sl ~= isl.front;
706                 appendEach(sl, isl.tail);
707             }
708 
709             appendEach(result, this);
710         }
711         else
712         {
713             result.insert(0, this);
714         }
715         return result;
716     }
717 
718     ///
719     static if (is(T == int))
720     @safe unittest
721     {
722         import std.algorithm.comparison : equal;
723 
724         auto stuff = [1, 2, 3];
725         auto sl = immutable SList!int(stuff);
726         auto slDup = sl.dup;
727         assert(equal(slDup, stuff));
728         slDup.front = 0;
729         assert(slDup.front == 0);
730         assert(sl.front == 1);
731     }
732 
733     /**
734      * Inserts the elements of an
735      * $(REF_ALTTEXT input range, isInputRange, std,range,primitives), or a
736      * variable number of items, at the given `pos`.
737      *
738      * If no allocator was provided when the list was created, the
739      * $(REF, GCAllocator, std,experimental,allocator) will be used.
740      *
741      * Params:
742      *      pos = a positive integer
743      *      stuff = an input range of elements that are implitictly convertible
744      *              to `T`; a variable number of items either in the form of a
745      *              list or as a built-in array
746      *
747      * Returns:
748      *      the number of elements inserted
749      *
750      * Complexity: $(BIGOH pos + m), where `m` is the number of elements in the range.
751      */
752     size_t insert(Stuff)(size_t pos, Stuff stuff)
753     if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
754     {
755         debug(CollectionSList)
756         {
757             writefln("SList.insert: begin");
758             scope(exit) writefln("SList.insert: end");
759         }
760 
761         // Will be optimized away, but the type system infers T's safety
762         if (0) { T t = T.init; }
763 
764         // Ensure we have an allocator. If it was already set, this will do nothing
765         auto a = threadAllocatorObject();
766         setAllocator(a);
767 
768         size_t result;
769         Node *tmpNode;
770         Node *tmpHead;
771         foreach (item; stuff)
772         {
773             Node *newNode;
774             () @trusted { newNode = _allocator.make!(Node)(item, null); }();
775             (tmpHead ? tmpNode._next : tmpHead) = newNode;
776             tmpNode = newNode;
777             ++result;
778         }
779 
780         if (!tmpNode)
781         {
782             return 0;
783         }
784 
785         Node *needle = _head;
786         Node *needlePrev = null;
787         while (pos)
788         {
789             needlePrev = needle;
790             needle = needle._next;
791             --pos;
792         }
793 
794         tmpNode._next = needle;
795         if (needlePrev is null)
796         {
797             _head = tmpHead;
798         }
799         else
800         {
801             needlePrev._next = tmpHead;
802         }
803         return result;
804     }
805 
806     /// ditto
807     size_t insert(Stuff)(size_t pos, Stuff[] stuff...)
808     if (isImplicitlyConvertible!(Stuff, T))
809     {
810         return insert(pos, stuff);
811     }
812 
813     ///
814     static if (is(T == int))
815     @safe unittest
816     {
817         import std.algorithm.comparison : equal;
818 
819         auto s = SList!int(4, 5);
820         SList!int sl;
821         assert(sl.empty);
822 
823         size_t pos = 0;
824         pos += sl.insert(pos, 1);
825         pos += sl.insert(pos, [2, 3]);
826         assert(equal(sl, [1, 2, 3]));
827 
828         // insert from an input range
829         pos += sl.insert(pos, s);
830         assert(equal(sl, [1, 2, 3, 4, 5]));
831         s.front = 0;
832         assert(equal(sl, [1, 2, 3, 4, 5]));
833     }
834 
835     /**
836      * Inserts the elements of an
837      * $(REF_ALTTEXT input range, isInputRange, std,range,primitives), or a
838      * variable number of items, at the end of the list.
839      *
840      * If no allocator was provided when the list was created, the
841      * $(REF, GCAllocator, std,experimental,allocator) will be used.
842      *
843      * Params:
844      *      stuff = an input range of elements that are implitictly convertible
845      *              to `T`; a variable number of items either in the form of a
846      *              list or as a built-in array
847      *
848      * Returns:
849      *      the number of elements inserted
850      *
851      * Complexity: $(BIGOH pos + m), where `m` is the number of elements in the range.
852      */
853     size_t insertBack(Stuff)(Stuff stuff)
854     if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
855     {
856         debug(CollectionSList)
857         {
858             writefln("SList.insertBack: begin");
859             scope(exit) writefln("SList.insertBack: end");
860         }
861 
862         // Will be optimized away, but the type system infers T's safety
863         if (0) { T t = T.init; }
864 
865         // Ensure we have an allocator. If it was already set, this will do nothing
866         auto a = threadAllocatorObject();
867         setAllocator(a);
868 
869         size_t result;
870         Node *tmpNode;
871         Node *tmpHead;
872         foreach (item; stuff)
873         {
874             Node *newNode;
875             () @trusted { newNode = _allocator.make!(Node)(item, null); }();
876             (tmpHead ? tmpNode._next : tmpHead) = newNode;
877             tmpNode = newNode;
878             ++result;
879         }
880 
881         if (!tmpNode)
882         {
883             return 0;
884         }
885 
886         if (_head is null)
887         {
888             _head = tmpHead;
889         }
890         else
891         {
892             Node *endNode;
893             for (endNode = _head; endNode._next !is null; endNode = endNode._next) { }
894             endNode._next = tmpHead;
895         }
896 
897         return result;
898     }
899 
900     /// ditto
901     size_t insertBack(Stuff)(Stuff[] stuff...)
902     if (isImplicitlyConvertible!(Stuff, T))
903     {
904         return insertBack(stuff);
905     }
906 
907     ///
908     static if (is(T == int))
909     @safe unittest
910     {
911         import std.algorithm.comparison : equal;
912 
913         auto s = SList!int(4, 5);
914         SList!int sl;
915         assert(sl.empty);
916 
917         sl.insertBack(1);
918         sl.insertBack([2, 3]);
919         assert(equal(sl, [1, 2, 3]));
920 
921         // insert from an input range
922         sl.insertBack(s);
923         assert(equal(sl, [1, 2, 3, 4, 5]));
924         s.front = 0;
925         assert(equal(sl, [1, 2, 3, 4, 5]));
926     }
927 
928     /**
929      * Create a new list that results from the concatenation of this list
930      * with `rhs`.
931      *
932      * Params:
933      *      rhs = can be an element that is implicitly convertible to `T`, an
934      *            input range of such elements, or another singly linked list
935      *
936      * Returns:
937      *      the newly created list
938      *
939      * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`.
940      */
941     auto ref opBinary(string op, U)(auto ref U rhs)
942         if (op == "~" &&
943             //(is (U : const typeof(this))
944             (is (U == typeof(this))
945              || is (U : T)
946              || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
947             ))
948     {
949         debug(CollectionSList)
950         {
951             writefln("SList.opBinary!~: begin");
952             scope(exit) writefln("SList.opBinary!~: end");
953         }
954 
955         auto newList = this.dup();
956         newList.insertBack(rhs);
957         return newList;
958     }
959 
960     ///
961     static if (is(T == int))
962     @safe unittest
963     {
964         import std.algorithm.comparison : equal;
965 
966         auto sl = SList!int(1);
967         auto sl2 = sl ~ 2;
968 
969         assert(equal(sl2, [1, 2]));
970         sl.front = 0;
971         assert(equal(sl2, [1, 2]));
972     }
973 
974     /**
975      * Assign `rhs` to this list. The current list will now become another
976      * reference to `rhs`, unless `rhs` is `null`, in which case the current
977      * list will become empty. If `rhs` refers to the current list nothing will
978      * happen.
979      *
980      * All the previous list elements that have no more references to them
981      * will be destroyed; this leads to a $(BIGOH n) complexity.
982      *
983      * Params:
984      *      rhs = a reference to a singly linked list
985      *
986      * Returns:
987      *      a reference to this list
988      *
989      * Complexity: $(BIGOH n).
990      */
991     auto ref opAssign()(auto ref typeof(this) rhs)
992     {
993         debug(CollectionSList)
994         {
995             writefln("SList.opAssign: begin");
996             scope(exit) writefln("SList.opAssign: end");
997         }
998 
999         if (rhs._head !is null && _head is rhs._head)
1000         {
1001             return this;
1002         }
1003 
1004         if (rhs._head !is null)
1005         {
1006             rhs.addRef(rhs._head);
1007             debug(CollectionSList) writefln("SList.opAssign: Node %s has refcount: %s",
1008                     rhs._head._payload, *prefCount(rhs._head));
1009         }
1010         destroyUnused();
1011         _head = rhs._head;
1012         _allocator = rhs._allocator;
1013         return this;
1014     }
1015 
1016     ///
1017     static if (is(T == int))
1018     @safe unittest
1019     {
1020         import std.algorithm.comparison : equal;
1021 
1022         auto sl = SList!int(1);
1023         auto sl2 = SList!int(1, 2);
1024 
1025         sl = sl2; // this will free the old sl
1026         assert(equal(sl, [1, 2]));
1027         sl.front = 0;
1028         assert(equal(sl2, [0, 2]));
1029     }
1030 
1031     /**
1032      * Append the elements of `rhs` at the end of the list.
1033      *
1034      * If no allocator was provided when the list was created, the
1035      * $(REF, GCAllocator, std,experimental,allocator) will be used.
1036      *
1037      * Params:
1038      *      rhs = can be an element that is implicitly convertible to `T`, an
1039      *            input range of such elements, or another singly linked list
1040      *
1041      * Returns:
1042      *      a reference to this list
1043      *
1044      * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`.
1045      */
1046     auto ref opOpAssign(string op, U)(auto ref U rhs)
1047         if (op == "~" &&
1048             (is (U == typeof(this))
1049              || is (U : T)
1050              || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
1051             ))
1052     {
1053         debug(CollectionSList)
1054         {
1055             writefln("SList.opOpAssign!~: %s begin", typeof(this).stringof);
1056             scope(exit) writefln("SList.opOpAssign!~: %s end", typeof(this).stringof);
1057         }
1058 
1059         insertBack(rhs);
1060         return this;
1061     }
1062 
1063     ///
1064     static if (is(T == int))
1065     @safe unittest
1066     {
1067         import std.algorithm.comparison : equal;
1068 
1069         auto s = SList!int(4, 5);
1070         SList!int sl;
1071         assert(sl.empty);
1072 
1073         sl ~= 1;
1074         sl ~= [2, 3];
1075         assert(equal(sl, [1, 2, 3]));
1076 
1077         // append an input range
1078         sl ~= s;
1079         assert(equal(sl, [1, 2, 3, 4, 5]));
1080         s.front = 0;
1081         assert(equal(sl, [1, 2, 3, 4, 5]));
1082     }
1083 
1084     /**
1085      * Remove the element at the given `idx` from the list. If there are no
1086      * more references to the given element, then it will be destroyed.
1087      *
1088      * Params:
1089      *      idx = a positive integer
1090      */
1091     void remove(size_t idx = 0)
1092     {
1093         assert(!empty, "SList.remove: List is empty");
1094         if (idx == 0)
1095         {
1096             popFront();
1097             return;
1098         }
1099 
1100         Node *tmpNode = _head;
1101         while(--idx != 0)
1102         {
1103             tmpNode = tmpNode._next;
1104         }
1105         Node *toDel = tmpNode._next;
1106         assert(toDel !is null, "SList.remove: Index out of bounds");
1107         tmpNode._next = tmpNode._next._next;
1108         delRef(toDel);
1109     }
1110 
1111     ///
1112     static if (is(T == int))
1113     @safe unittest
1114     {
1115         import std.algorithm.comparison : equal;
1116 
1117         auto sl = SList!int(1, 2, 3);
1118         auto sl2 = sl;
1119         auto pos = 1;
1120 
1121         assert(equal(sl, [1, 2, 3]));
1122         sl.remove(pos);
1123         assert(equal(sl, [1, 3]));
1124         assert(equal(sl2, [1, 3]));
1125     }
1126 
1127     debug(CollectionSList)
1128     void printRefCount() const
1129     {
1130         writefln("SList.printRefCount: begin");
1131         scope(exit) writefln("SList.printRefCount: end");
1132 
1133         Node *tmpNode = (() @trusted => cast(Node*)_head)();
1134         while (tmpNode !is null)
1135         {
1136             writefln("SList.printRefCount: Node %s has ref count %s",
1137                     tmpNode._payload, *localPrefCount(tmpNode));
1138             tmpNode = tmpNode._next;
1139         }
1140     }
1141 }
1142 
1143 version(unittest) private nothrow pure @safe
1144 void testImmutability(RCISharedAllocator allocator)
1145 {
1146     auto s = immutable SList!(int)(allocator, 1, 2, 3);
1147     auto s2 = s;
1148     auto s3 = s2.save();
1149 
1150     assert(s2.front == 1);
1151     static assert(!__traits(compiles, s2.front = 4));
1152     static assert(!__traits(compiles, s2.popFront()));
1153 
1154     auto s4 = s2.tail;
1155     assert(s4.front == 2);
1156     static assert(!__traits(compiles, s4 = s4.tail));
1157 }
1158 
1159 version(unittest) private nothrow pure @safe
1160 void testConstness(RCISharedAllocator allocator)
1161 {
1162     auto s = const SList!(int)(allocator, 1, 2, 3);
1163     auto s2 = s;
1164     auto s3 = s2.save();
1165 
1166     assert(s2.front == 1);
1167     static assert(!__traits(compiles, s2.front = 4));
1168     static assert(!__traits(compiles, s2.popFront()));
1169 
1170     auto s4 = s2.tail;
1171     assert(s4.front == 2);
1172     static assert(!__traits(compiles, s4 = s4.tail));
1173 }
1174 
1175 @safe unittest
1176 {
1177     import std.conv;
1178     import std.experimental.allocator : processAllocator;
1179     SCAlloc statsCollectorAlloc;
1180     {
1181         // TODO: StatsCollector need to be made shareable
1182         //auto _allocator = sharedAllocatorObject(&statsCollectorAlloc);
1183         () nothrow pure @safe {
1184             testImmutability(processAllocatorObject());
1185             testConstness(processAllocatorObject());
1186         }();
1187     }
1188     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1189     assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1190             ~ to!string(bytesUsed) ~ " bytes");
1191 }
1192 
1193 version(unittest) private nothrow pure @safe
1194 void testConcatAndAppend(RCIAllocator allocator)
1195 {
1196     import std.algorithm.comparison : equal;
1197 
1198     auto sl = SList!(int)(allocator, 1, 2, 3);
1199     SList!(int) sl2 = SList!int(allocator);
1200 
1201     auto sl3 = sl ~ sl2;
1202     assert(equal(sl3, [1, 2, 3]));
1203 
1204     auto sl4 = sl3;
1205     sl3 = sl3 ~ 4;
1206     assert(equal(sl3, [1, 2, 3, 4]));
1207     sl3 = sl3 ~ [5];
1208     assert(equal(sl3, [1, 2, 3, 4, 5]));
1209     assert(equal(sl4, [1, 2, 3]));
1210 
1211     sl4 = sl3;
1212     sl3 ~= 6;
1213     assert(equal(sl3, [1, 2, 3, 4, 5, 6]));
1214     sl3 ~= [7];
1215     assert(equal(sl3, [1, 2, 3, 4, 5, 6, 7]));
1216     assert(equal(sl4, [1, 2, 3, 4, 5, 6, 7]));
1217 
1218     sl3 ~= sl3;
1219     assert(equal(sl3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]));
1220     assert(equal(sl4, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]));
1221 
1222     SList!int sl5 = SList!int(allocator);
1223     sl5 ~= [1, 2, 3];
1224     assert(equal(sl5, [1, 2, 3]));
1225 }
1226 
1227 @safe unittest
1228 {
1229     import std.conv;
1230     SCAlloc statsCollectorAlloc;
1231     {
1232         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1233         () nothrow pure @safe {
1234             testConcatAndAppend(_allocator);
1235         }();
1236     }
1237     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1238     assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1239             ~ to!string(bytesUsed) ~ " bytes");
1240 }
1241 
1242 version(unittest) private nothrow pure @safe
1243 void testSimple(RCIAllocator allocator)
1244 {
1245     import std.algorithm.comparison : equal;
1246     import std.algorithm.searching : canFind;
1247     import std.range.primitives : walkLength;
1248 
1249     auto sl = SList!int(allocator);
1250     assert(sl.empty);
1251     assert(sl.isUnique);
1252 
1253     size_t pos = 0;
1254     sl.insert(pos, 1, 2, 3);
1255     assert(sl.front == 1);
1256     assert(equal(sl, sl));
1257     assert(equal(sl, [1, 2, 3]));
1258 
1259     sl.popFront();
1260     assert(sl.front == 2);
1261     assert(equal(sl, [2, 3]));
1262 
1263     sl.insert(pos, [4, 5, 6]);
1264     sl.insert(pos, 7);
1265     sl.insert(pos, [8]);
1266     assert(equal(sl, [8, 7, 4, 5, 6, 2, 3]));
1267 
1268     sl.insertBack(0, 1);
1269     sl.insertBack([-1, -2]);
1270     assert(equal(sl, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
1271 
1272     sl.front = 9;
1273     assert(equal(sl, [9, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
1274 
1275     auto slTail = sl.tail;
1276     assert(slTail.front == 7);
1277     slTail.front = 8;
1278     assert(slTail.front == 8);
1279     assert(sl.tail.front == 8);
1280     assert(!sl.isUnique);
1281 
1282     assert(canFind(sl, 2));
1283     assert(!canFind(sl, -10));
1284 
1285     sl.remove();
1286     assert(equal(sl, [8, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
1287     sl.remove(2);
1288     assert(equal(sl, [8, 4, 6, 2, 3, 0, 1, -1, -2]));
1289     sl.remove(walkLength(sl) - 1);
1290     assert(equal(sl, [8, 4, 6, 2, 3, 0, 1, -1]));
1291     pos = 1;
1292     sl.insert(pos, 10);
1293     assert(equal(sl, [8, 10, 4, 6, 2, 3, 0, 1, -1]));
1294 }
1295 
1296 @safe unittest
1297 {
1298     import std.conv;
1299     SCAlloc statsCollectorAlloc;
1300     {
1301         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1302         () nothrow pure @safe {
1303             testSimple(_allocator);
1304         }();
1305     }
1306     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1307     assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1308             ~ to!string(bytesUsed) ~ " bytes");
1309 }
1310 
1311 version(unittest) private nothrow pure @safe
1312 void testSimpleImmutable(RCIAllocator allocator)
1313 {
1314     import std.algorithm.comparison : equal;
1315     import std.algorithm.searching : canFind;
1316     import std.range.primitives : walkLength;
1317 
1318     auto sl = SList!(immutable int)(allocator);
1319     assert(sl.empty);
1320 
1321     size_t pos = 0;
1322     sl.insert(pos, 1, 2, 3);
1323     assert(sl.front == 1);
1324     assert(equal(sl, sl));
1325     assert(equal(sl, [1, 2, 3]));
1326 
1327     sl.popFront();
1328     assert(sl.front == 2);
1329     assert(equal(sl, [2, 3]));
1330     assert(sl.tail.front == 3);
1331 
1332     sl.insert(pos, [4, 5, 6]);
1333     sl.insert(pos, 7);
1334     sl.insert(pos, [8]);
1335     assert(equal(sl, [8, 7, 4, 5, 6, 2, 3]));
1336 
1337     sl.insertBack(0, 1);
1338     sl.insertBack([-1, -2]);
1339     assert(equal(sl, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
1340 
1341     // Cannot modify immutable values
1342     static assert(!__traits(compiles, sl.front = 9));
1343 
1344     assert(canFind(sl, 2));
1345     assert(!canFind(sl, -10));
1346 
1347     sl.remove();
1348     assert(equal(sl, [7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
1349     sl.remove(2);
1350     assert(equal(sl, [7, 4, 6, 2, 3, 0, 1, -1, -2]));
1351     sl.remove(walkLength(sl) - 1);
1352     assert(equal(sl, [7, 4, 6, 2, 3, 0, 1, -1]));
1353 }
1354 
1355 @safe unittest
1356 {
1357     import std.conv;
1358     SCAlloc statsCollectorAlloc;
1359     {
1360         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1361         () nothrow pure @safe {
1362             testSimpleImmutable(_allocator);
1363         }();
1364     }
1365     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1366     assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1367             ~ to!string(bytesUsed) ~ " bytes");
1368 }
1369 
1370 version(unittest) private nothrow pure @safe
1371 void testCopyAndRef(RCIAllocator allocator)
1372 {
1373     import std.algorithm.comparison : equal;
1374 
1375     auto slFromList = SList!int(allocator, 1, 2, 3);
1376     auto slFromRange = SList!int(allocator, slFromList);
1377     assert(equal(slFromList, slFromRange));
1378 
1379     slFromList.popFront();
1380     assert(equal(slFromList, [2, 3]));
1381     assert(equal(slFromRange, [1, 2, 3]));
1382 
1383     SList!int slInsFromRange = SList!int(allocator);
1384     size_t pos = 0;
1385     slInsFromRange.insert(pos, slFromList);
1386     slFromList.popFront();
1387     assert(equal(slFromList, [3]));
1388     assert(equal(slInsFromRange, [2, 3]));
1389 
1390     SList!int slInsBackFromRange = SList!int(allocator);
1391     slInsBackFromRange.insert(pos, slFromList);
1392     slFromList.popFront();
1393     assert(slFromList.empty);
1394     assert(equal(slInsBackFromRange, [3]));
1395 
1396     auto slFromRef = slInsFromRange;
1397     auto slFromDup = slInsFromRange.dup;
1398     assert(slInsFromRange.front == 2);
1399     slFromRef.front = 5;
1400     assert(slInsFromRange.front == 5);
1401     assert(slFromDup.front == 2);
1402 }
1403 
1404 @safe unittest
1405 {
1406     import std.conv;
1407     SCAlloc statsCollectorAlloc;
1408     {
1409         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1410         () nothrow pure @safe {
1411             testCopyAndRef(_allocator);
1412         }();
1413     }
1414     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1415     assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1416             ~ to!string(bytesUsed) ~ " bytes");
1417 }
1418 
1419 version(unittest) private nothrow pure @safe
1420 void testWithStruct(RCIAllocator allocator, RCISharedAllocator sharedAlloc)
1421 {
1422     import std.algorithm.comparison : equal;
1423 
1424     auto list = SList!int(allocator, 1, 2, 3);
1425     {
1426         auto listOfLists = SList!(SList!int)(allocator, list);
1427         assert(equal(listOfLists.front, [1, 2, 3]));
1428         listOfLists.front.front = 2;
1429         assert(equal(listOfLists.front, [2, 2, 3]));
1430         size_t pos = 0;
1431         static assert(!__traits(compiles, listOfLists.insert(pos, 1)));
1432 
1433         auto immListOfLists = immutable SList!(SList!int)(sharedAlloc, list);
1434         assert(immListOfLists.front.front == 2);
1435         static assert(!__traits(compiles, immListOfLists.front.front = 2));
1436     }
1437     assert(equal(list, [2, 2, 3]));
1438 }
1439 
1440 @safe unittest
1441 {
1442     import std.conv;
1443     import std.experimental.allocator : processAllocator;
1444     SCAlloc statsCollectorAlloc;
1445     {
1446         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1447         () nothrow pure @safe {
1448             testWithStruct(_allocator, processAllocatorObject());
1449         }();
1450     }
1451     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1452     assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1453             ~ to!string(bytesUsed) ~ " bytes");
1454 }
1455 
1456 version(unittest) private nothrow pure @safe
1457 void testWithClass(RCIAllocator allocator)
1458 {
1459     class MyClass
1460     {
1461         int x;
1462         this(int x) { this.x = x; }
1463     }
1464 
1465     MyClass c = new MyClass(10);
1466     {
1467         auto sl = SList!MyClass(allocator, c);
1468         assert(sl.front.x == 10);
1469         assert(sl.front is c);
1470         sl.front.x = 20;
1471     }
1472     assert(c.x == 20);
1473 }
1474 
1475 @safe unittest
1476 {
1477     import std.conv;
1478     SCAlloc statsCollectorAlloc;
1479     {
1480         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1481         () nothrow pure @safe {
1482             testWithClass(_allocator);
1483         }();
1484     }
1485     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1486     assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1487             ~ to!string(bytesUsed) ~ " bytes");
1488 }