1 module stdx.collection.array;
2 
3 import stdx.collection.common;
4 
5 debug(CollectionArray) 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 Array(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, Unqual, isArray;
21     import std.range.primitives : isInputRange, isInfinite,
22            ElementType, hasLength;
23     import std.conv : emplace;
24     import core.atomic : atomicOp;
25 
26 //private:
27     T[] _payload;
28     Unqual!T[] _support;
29 
30     static enum double capacityFactor = 3.0 / 2;
31     static enum initCapacity = 3;
32 
33     alias MutableAlloc = AffixAllocator!(IAllocator, size_t);
34     Mutable!MutableAlloc _ouroborosAllocator;
35 
36     /// Returns the actual allocator from ouroboros
37     @trusted ref auto allocator(this _)()
38     {
39         assert(!_ouroborosAllocator.isNull);
40         return _ouroborosAllocator.get();
41     }
42 
43     /// Constructs the ouroboros allocator from allocator if the ouroboros
44     //allocator wasn't previously set
45     @trusted bool setAllocator(IAllocator allocator)
46     {
47         if (_ouroborosAllocator.isNull)
48         {
49             _ouroborosAllocator = Mutable!(MutableAlloc)(allocator,
50                     MutableAlloc(allocator));
51             return true;
52         }
53         return false;
54     }
55 
56     @trusted IAllocator getAllocator(this _)()
57     {
58         return _ouroborosAllocator.isNull ? null : allocator().parent;
59     }
60 
61     @trusted void addRef(SupportQual, this Qualified)(SupportQual support)
62     {
63         assert(support !is null);
64         debug(CollectionArray)
65         {
66             writefln("Array.addRef: Array %s has refcount: %s; will be: %s",
67                     support, *prefCount(support), *prefCount(support) + 1);
68         }
69         static if (is(Qualified == immutable) || is(Qualified == const))
70         {
71             atomicOp!"+="(*prefCount(support), 1);
72         }
73         else
74         {
75             ++*prefCount(support);
76         }
77     }
78 
79     @trusted void delRef(Unqual!T[] support)
80     {
81         assert(support !is null);
82         size_t *pref = prefCount(support);
83         debug(CollectionArray) writefln("Array.delRef: Array %s has refcount: %s; will be: %s",
84                 support, *pref, *pref - 1);
85         if (*pref == 0)
86         {
87             debug(CollectionArray) writefln("Array.delRef: Deleting array %s", support);
88             allocator.dispose(support);
89         }
90         else
91         {
92             --*pref;
93         }
94     }
95 
96     @trusted auto prefCount(SupportQual, this Qualified)(SupportQual support)
97     {
98         assert(support !is null);
99         static if (is(Qualified == immutable) || is(Qualified == const))
100         {
101             return cast(shared size_t*)(&allocator.prefix(support));
102         }
103         else
104         {
105             return cast(size_t*)(&allocator.prefix(support));
106         }
107     }
108 
109     static string immutableInsert(StuffType)(string stuff)
110     {
111         static if (hasLength!StuffType)
112         {
113             auto stuffLengthStr = ""
114                 ~"size_t stuffLength = " ~ stuff ~ ".length;";
115         }
116         else
117         {
118             auto stuffLengthStr = ""
119                 ~"import std.range.primitives : walkLength;"
120                 ~"size_t stuffLength = walkLength(" ~ stuff ~ ");";
121         }
122 
123         return ""
124         ~ stuffLengthStr
125         ~"auto tmpAlloc = Mutable!(MutableAlloc)(allocator, MutableAlloc(allocator));"
126         ~"_ouroborosAllocator = (() @trusted => cast(immutable)(tmpAlloc))();"
127         ~"auto tmpSupport = cast(Unqual!T[])(tmpAlloc.get().allocate(stuffLength * T.sizeof));"
128         ~"assert(stuffLength == 0 || (stuffLength > 0 && tmpSupport !is
129         null));"
130         ~"for (size_t i = 0; i < tmpSupport.length; ++i)"
131         ~"{"
132                 ~"emplace(&tmpSupport[i]);"
133         ~"}"
134         ~"size_t i = 0;"
135         ~"foreach (item; " ~ stuff ~ ")"
136         ~"{"
137             ~"tmpSupport[i++] = item;"
138         ~"}"
139         ~"_support = cast(typeof(_support))(tmpSupport);"
140         ~"_payload = cast(T[])(_support[0 .. stuffLength]);";
141     }
142 
143     void destroyUnused()
144     {
145         debug(CollectionArray)
146         {
147             writefln("Array.destoryUnused: begin");
148             scope(exit) writefln("Array.destoryUnused: end");
149         }
150         if (_support !is null)
151         {
152             delRef(_support);
153         }
154     }
155 
156 public:
157     this(this _)(IAllocator allocator)
158     {
159         debug(CollectionArray)
160         {
161             writefln("Array.ctor: begin");
162             scope(exit) writefln("Array.ctor: end");
163         }
164         setAllocator(allocator);
165     }
166 
167     this(U, this Qualified)(U[] values...)
168     if (isImplicitlyConvertible!(U, T))
169     {
170         this(theAllocator, values);
171     }
172 
173     this(U, this Qualified)(IAllocator allocator, U[] values...)
174     if (isImplicitlyConvertible!(U, T))
175     {
176         debug(CollectionArray)
177         {
178             writefln("Array.ctor: begin");
179             scope(exit) writefln("Array.ctor: end");
180         }
181         static if (is(Qualified == immutable) || is(Qualified == const))
182         {
183             mixin(immutableInsert!(typeof(values))("values"));
184             assert(!_ouroborosAllocator.isNull);
185         }
186         else
187         {
188             setAllocator(allocator);
189             insert(0, values);
190         }
191     }
192 
193     this(Stuff, this Qualified)(Stuff stuff)
194     if (isInputRange!Stuff && !isInfinite!Stuff
195         && isImplicitlyConvertible!(ElementType!Stuff, T)
196         && !is(Stuff == T[]))
197     {
198         this(theAllocator, stuff);
199     }
200 
201     this(Stuff, this Qualified)(IAllocator allocator, Stuff stuff)
202     if (isInputRange!Stuff && !isInfinite!Stuff
203         && isImplicitlyConvertible!(ElementType!Stuff, T)
204         && !is(Stuff == T[]))
205     {
206         debug(CollectionArray)
207         {
208             writefln("Array.ctor: begin");
209             scope(exit) writefln("Array.ctor: end");
210         }
211         static if (is(Qualified == immutable) || is(Qualified == const))
212         {
213             mixin(immutableInsert!(typeof(stuff))("stuff"));
214             assert(!_ouroborosAllocator.isNull);
215         }
216         else
217         {
218             setAllocator(allocator);
219             insert(0, stuff);
220         }
221     }
222 
223     this(this)
224     {
225         debug(CollectionArray)
226         {
227             writefln("Array.postblit: begin");
228             scope(exit) writefln("Array.postblit: end");
229         }
230         if (_support !is null)
231         {
232             addRef(_support);
233             debug(CollectionArray) writefln("Array.postblit: Array %s has refcount: %s",
234                     _support, *prefCount(_support));
235         }
236     }
237 
238     // Immutable ctors
239     private this(SuppQual, PaylQual, OuroQual, this Qualified)(SuppQual support,
240             PaylQual payload, OuroQual ouroborosAllocator)
241         //if (is(typeof(_support) : typeof(support))
242             //&& (is(Qualified == immutable) || is(Qualified == const)))
243     {
244         _support = support;
245         _payload = payload;
246         _ouroborosAllocator = ouroborosAllocator;
247         if (_support !is null)
248         {
249             addRef(_support);
250             debug(CollectionArray) writefln("Array.ctor immutable: Array %s has "
251                     ~ "refcount: %s", _support, *prefCount(_support));
252         }
253     }
254 
255     @trusted ~this()
256     {
257         debug(CollectionArray)
258         {
259             writefln("Array.dtor: Begin for instance %s of type %s",
260                 cast(size_t)(&this), typeof(this).stringof);
261             scope(exit) writefln("Array.dtor: End for instance %s of type %s",
262                     cast(size_t)(&this), typeof(this).stringof);
263         }
264         destroyUnused();
265     }
266 
267     private @trusted size_t slackFront() const
268     {
269         return _payload.ptr - _support.ptr;
270     }
271 
272     private @trusted size_t slackBack() const
273     {
274         return _support.ptr + _support.length - _payload.ptr - _payload.length;
275     }
276 
277     size_t length() const
278     {
279         return _payload.length;
280     }
281 
282     void forceLength(size_t len)
283     {
284         assert(len <= capacity);
285         _payload = cast(T[])(_support[slackFront .. len]);
286     }
287 
288     alias opDollar = length;
289 
290     @trusted size_t capacity() const
291     {
292         return length + slackBack;
293     }
294 
295     void reserve(size_t n)
296     {
297         debug(CollectionArray)
298         {
299             writefln("Array.reserve: begin");
300             scope(exit) writefln("Array.reserve: end");
301         }
302         setAllocator(theAllocator);
303 
304         if (n <= capacity) { return; }
305         if (_support && *prefCount(_support) == 0)
306         {
307             void[] buf = _support;
308             if (allocator.expand(buf, (n - capacity) * T.sizeof))
309             {
310                 _support = cast(Unqual!T[])(buf);
311                 // TODO: emplace extended buf
312                 return;
313             }
314             else
315             {
316                 //assert(0, "Array.reserve: Failed to expand array.");
317             }
318         }
319 
320         auto tmpSupport = cast(Unqual!T[])(allocator.allocate(n * T.sizeof));
321         assert(tmpSupport !is null);
322         for (size_t i = 0; i < tmpSupport.length; ++i)
323         {
324                 emplace(&tmpSupport[i]);
325         }
326         tmpSupport[0 .. _payload.length] = _payload[];
327         __dtor();
328         _support = tmpSupport;
329         _payload = cast(T[])(_support[0 .. _payload.length]);
330         assert(capacity >= n);
331     }
332 
333     size_t insert(Stuff)(size_t pos, Stuff stuff)
334     if (!isArray!(typeof(stuff)) && isInputRange!Stuff && !isInfinite!Stuff
335         && isImplicitlyConvertible!(ElementType!Stuff, T))
336     {
337         debug(CollectionArray)
338         {
339             writefln("Array.insert: begin");
340             scope(exit) writefln("Array.insert: end");
341         }
342         setAllocator(theAllocator);
343 
344         static if (hasLength!Stuff)
345         {
346             size_t stuffLength = stuff.length;
347         }
348         else
349         {
350             import std.range.primitives : walkLength;
351             size_t stuffLength = walkLength(stuff);
352         }
353         if (stuffLength == 0) return 0;
354 
355         auto tmpSupport = cast(Unqual!T[])(allocator.allocate(stuffLength * T.sizeof));
356         assert(stuffLength == 0 || (stuffLength > 0 && tmpSupport !is null));
357         for (size_t i = 0; i < tmpSupport.length; ++i)
358         {
359                 emplace(&tmpSupport[i]);
360         }
361 
362         size_t i = 0;
363         foreach (item; stuff)
364         {
365             tmpSupport[i++] = item;
366         }
367         size_t result = insert(pos, tmpSupport);
368         allocator.dispose(tmpSupport);
369         return result;
370     }
371 
372     size_t insert(Stuff)(size_t pos, Stuff[] stuff...)
373     if (isImplicitlyConvertible!(Stuff, T))
374     {
375         debug(CollectionArray)
376         {
377             writefln("Array.insert: begin");
378             scope(exit) writefln("Array.insert: end");
379         }
380         assert(pos <= _payload.length);
381         setAllocator(theAllocator);
382 
383         if (stuff.length == 0) return 0;
384         if (stuff.length > slackBack)
385         {
386             double newCapacity = capacity ? capacity * capacityFactor : stuff.length;
387             while (newCapacity < capacity + stuff.length)
388             {
389                 newCapacity = newCapacity * capacityFactor;
390             }
391             reserve(cast(size_t)(newCapacity));
392         }
393         //_support[pos + stuff.length .. _payload.length + stuff.length] =
394             //_support[pos .. _payload.length];
395         for (size_t i = _payload.length + stuff.length - 1; i >= pos +
396                 stuff.length; --i)
397         {
398             // Avoids underflow if payload is empty
399             _support[i] = _support[i - stuff.length];
400         }
401         _support[pos .. pos + stuff.length] = stuff[];
402         _payload = cast(T[])(_support[0 .. _payload.length + stuff.length]);
403         return stuff.length;
404     }
405 
406     bool isUnique(this _)()
407     {
408         debug(CollectionArray)
409         {
410             writefln("Array.isUnique: begin");
411             scope(exit) writefln("Array.isUnique: end");
412         }
413 
414         if (_support !is null)
415         {
416             return *prefCount(_support) == 0;
417         }
418         return true;
419     }
420 
421     bool empty(this _)()
422     {
423         return length == 0;
424     }
425 
426     ref auto front(this _)()
427     {
428         assert(!empty, "Array.front: Array is empty");
429         return _payload[0];
430     }
431 
432     void popFront()
433     {
434         debug(CollectionArray)
435         {
436             writefln("Array.popFront: begin");
437             scope(exit) writefln("Array.popFront: end");
438         }
439         assert(!empty, "Array.popFront: Array is empty");
440         _payload = _payload[1 .. $];
441     }
442 
443     Qualified tail(this Qualified)()
444     {
445         debug(CollectionArray)
446         {
447             writefln("Array.tail: begin");
448             scope(exit) writefln("Array.tail: end");
449         }
450         assert(!empty, "Array.tail: Array is empty");
451 
452         static if (is(Qualified == immutable) || is(Qualified == const))
453         {
454             return this[1 .. $];
455         }
456         else
457         {
458             return .tail(this);
459         }
460     }
461 
462     ref auto save(this _)()
463     {
464         debug(CollectionArray)
465         {
466             writefln("Array.save: begin");
467             scope(exit) writefln("Array.save: end");
468         }
469         return this;
470     }
471 
472     typeof(this) dup()
473     {
474         debug(CollectionArray)
475         {
476             writefln("Array.dup: begin");
477             scope(exit) writefln("Array.dup: end");
478         }
479         IAllocator alloc = getAllocator();
480         if (alloc is null)
481         {
482             alloc = theAllocator;
483         }
484         return typeof(this)(alloc, this);
485     }
486 
487     Qualified opSlice(this Qualified)()
488     {
489         debug(CollectionArray)
490         {
491             writefln("Array.opSlice(): begin");
492             scope(exit) writefln("Array.opSlice(): end");
493         }
494         return this.save;
495     }
496 
497     Qualified opSlice(this Qualified)(size_t start, size_t end)
498     in
499     {
500         assert(start <= end && end <= length,
501                "Array.opSlice(s, e): Invalid bounds: Ensure start <= end <= length");
502     }
503     body
504     {
505         debug(CollectionArray)
506         {
507             writefln("Array.opSlice(s, e): begin");
508             scope(exit) writefln("Array.opSlice(s, e): end");
509         }
510         return typeof(this)(_support, _payload[start .. end], _ouroborosAllocator);
511     }
512 
513     ref auto opIndex(this _)(size_t idx)
514     in
515     {
516         assert(idx <= length, "Array.opIndex: Index out of bounds");
517     }
518     body
519     {
520         return _payload[idx];
521     }
522 
523     ref auto opIndexUnary(string op)(size_t idx)
524     in
525     {
526         assert(idx <= length, "Array.opIndexUnary!" ~ op ~ ": Index out of bounds");
527     }
528     body
529     {
530         mixin("return " ~ op ~ "_payload[idx];");
531     }
532 
533     ref auto opIndexAssign(U)(U elem, size_t idx)
534     if (isImplicitlyConvertible!(U, T))
535     in
536     {
537         assert(idx <= length, "Array.opIndexAssign: Index out of bounds");
538     }
539     body
540     {
541         return _payload[idx] = elem;
542     }
543 
544     ref auto opIndexOpAssign(string op, U)(U elem, size_t idx)
545     if (isImplicitlyConvertible!(U, T))
546     in
547     {
548         assert(idx <= length, "Array.opIndexOpAssign!" ~ op ~ ": Index out of bounds");
549     }
550     body
551     {
552         mixin("return _payload[idx]" ~ op ~ "= elem;");
553     }
554 
555     auto ref opBinary(string op, U)(auto ref U rhs)
556         if (op == "~" &&
557             (is (U == typeof(this))
558              || is (U : T)
559              || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
560             ))
561     {
562         debug(CollectionArray)
563         {
564             writefln("Array.opBinary!~: begin");
565             scope(exit) writefln("Array.opBinary!~: end");
566         }
567 
568         //TODO: should work for immutable, const as well
569 
570         typeof(this) newArray = this.dup();
571         newArray.insert(length, rhs);
572         return newArray;
573     }
574 
575     auto ref opAssign()(auto ref typeof(this) rhs)
576     {
577         debug(CollectionArray)
578         {
579             writefln("Array.opAssign: begin");
580             scope(exit) writefln("Array.opAssign: end");
581         }
582 
583         if (rhs._support !is null && _support is rhs._support)
584         {
585             return this;
586         }
587 
588         if (rhs._support !is null)
589         {
590             rhs.addRef(rhs._support);
591             debug(CollectionArray) writefln("Array.opAssign: Array %s has refcount: %s",
592                     rhs._payload, *prefCount(rhs._support));
593         }
594         //__dtor();
595         destroyUnused();
596         _support = rhs._support;
597         _payload = rhs._payload;
598         _ouroborosAllocator = rhs._ouroborosAllocator;
599         return this;
600     }
601 
602     auto ref opOpAssign(string op, U)(auto ref U rhs)
603         if (op == "~" &&
604             (is (U == typeof(this))
605              || is (U : T)
606              || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
607             ))
608     {
609         debug(CollectionArray)
610         {
611             writefln("Array.opOpAssign!~: %s begin", typeof(this).stringof);
612             scope(exit) writefln("Array.opOpAssign!~: %s end", typeof(this).stringof);
613         }
614         insert(length, rhs);
615         return this;
616     }
617 }
618 
619 version(unittest) private @trusted void testConcatAndAppend(IAllocator allocator)
620 {
621     import std.algorithm.comparison : equal;
622 
623     auto a = Array!(int)(allocator, 1, 2, 3);
624     Array!(int) a2 = Array!(int)(allocator);
625 
626     auto a3 = a ~ a2;
627     assert(equal(a3, [1, 2, 3]));
628 
629     auto a4 = a3;
630     a3 = a3 ~ 4;
631     assert(equal(a3, [1, 2, 3, 4]));
632     a3 = a3 ~ [5];
633     assert(equal(a3, [1, 2, 3, 4, 5]));
634     assert(equal(a4, [1, 2, 3]));
635 
636     a4 = a3;
637     a3 ~= 6;
638     assert(equal(a3, [1, 2, 3, 4, 5, 6]));
639     a3 ~= [7];
640 
641     a3 ~= a3;
642     assert(equal(a3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]));
643 
644     Array!int a5 = Array!(int)(allocator);
645     a5 ~= [1, 2, 3];
646     assert(equal(a5, [1, 2, 3]));
647     auto a6 = a5;
648     a5 = a5;
649     a5[0] = 10;
650     assert(equal(a5, a6));
651 }
652 
653 @trusted unittest
654 {
655     import std.conv;
656     SCAlloc statsCollectorAlloc;
657     auto _allocator = allocatorObject(statsCollectorAlloc);
658 
659     () @safe {
660         testConcatAndAppend(_allocator);
661         auto bytesUsed = _allocator.impl.bytesUsed;
662         assert(bytesUsed == 0, "Array ref count leaks memory; leaked "
663                 ~ to!string(bytesUsed) ~ " bytes");
664     }();
665 }
666 
667 version(unittest) private @trusted void testSimple(IAllocator allocator)
668 {
669     import std.algorithm.comparison : equal;
670     import std.algorithm.searching : canFind;
671 
672     auto a = Array!int(allocator);
673     assert(a.empty);
674     assert(a.isUnique);
675 
676     size_t pos = 0;
677     a.insert(pos, 1, 2, 3);
678     assert(a.front == 1);
679     assert(equal(a, a));
680     assert(equal(a, [1, 2, 3]));
681 
682     a.popFront();
683     assert(a.front == 2);
684     assert(equal(a, [2, 3]));
685 
686     a.insert(pos, [4, 5, 6]);
687     a.insert(pos, 7);
688     a.insert(pos, [8]);
689     assert(equal(a, [8, 7, 4, 5, 6, 2, 3]));
690 
691     a.insert(a.length, 0, 1);
692     a.insert(a.length, [-1, -2]);
693     assert(equal(a, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
694 
695     a.front = 9;
696     assert(equal(a, [9, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
697 
698     auto aTail = a.tail;
699     assert(aTail.front == 7);
700     aTail.front = 8;
701     assert(aTail.front == 8);
702     assert(a.tail.front == 8);
703     assert(!a.isUnique);
704 
705     assert(canFind(a, 2));
706     assert(!canFind(a, -10));
707 }
708 
709 @trusted unittest
710 {
711     import std.conv;
712     SCAlloc statsCollectorAlloc;
713     auto _allocator = allocatorObject(statsCollectorAlloc);
714 
715     () @safe {
716         testSimple(_allocator);
717         auto bytesUsed = _allocator.impl.bytesUsed;
718         assert(bytesUsed == 0, "Array ref count leaks memory; leaked "
719                 ~ to!string(bytesUsed) ~ " bytes");
720     }();
721 }
722 
723 version(unittest) private @trusted void testSimpleImmutable(IAllocator allocator)
724 {
725     import std.algorithm.comparison : equal;
726     import std.algorithm.searching : canFind;
727 
728     auto a = Array!(immutable int)(allocator);
729     assert(a.empty);
730 
731     size_t pos = 0;
732     a.insert(pos, 1, 2, 3);
733     assert(a.front == 1);
734     assert(equal(a, a));
735     assert(equal(a, [1, 2, 3]));
736 
737     a.popFront();
738     assert(a.front == 2);
739     assert(equal(a, [2, 3]));
740     assert(a.tail.front == 3);
741 
742     a.insert(pos, [4, 5, 6]);
743     a.insert(pos, 7);
744     a.insert(pos, [8]);
745     assert(equal(a, [8, 7, 4, 5, 6, 2, 3]));
746 
747     a.insert(a.length, 0, 1);
748     a.insert(a.length, [-1, -2]);
749     assert(equal(a, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
750 
751     // Cannot modify immutable values
752     static assert(!__traits(compiles, a.front = 9));
753 
754     assert(canFind(a, 2));
755     assert(!canFind(a, -10));
756 }
757 
758 @trusted unittest
759 {
760     import std.conv;
761     SCAlloc statsCollectorAlloc;
762     auto _allocator = allocatorObject(statsCollectorAlloc);
763 
764     () @safe {
765         testSimpleImmutable(_allocator);
766         auto bytesUsed = _allocator.impl.bytesUsed;
767         assert(bytesUsed == 0, "Array ref count leaks memory; leaked "
768                 ~ to!string(bytesUsed) ~ " bytes");
769     }();
770 }
771 
772 version(unittest) private @trusted void testCopyAndRef(IAllocator allocator)
773 {
774     import std.algorithm.comparison : equal;
775 
776     auto aFromList = Array!int(allocator, 1, 2, 3);
777     auto aFromRange = Array!int(allocator, aFromList);
778     assert(equal(aFromList, aFromRange));
779 
780     aFromList.popFront();
781     assert(equal(aFromList, [2, 3]));
782     assert(equal(aFromRange, [1, 2, 3]));
783 
784     size_t pos = 0;
785     Array!int aInsFromRange = Array!int(allocator);
786     aInsFromRange.insert(pos, aFromList);
787     aFromList.popFront();
788     assert(equal(aFromList, [3]));
789     assert(equal(aInsFromRange, [2, 3]));
790 
791     Array!int aInsBackFromRange = Array!int(allocator);
792     aInsBackFromRange.insert(pos, aFromList);
793     aFromList.popFront();
794     assert(aFromList.empty);
795     assert(equal(aInsBackFromRange, [3]));
796 
797     auto aFromRef = aInsFromRange;
798     auto aFromDup = aInsFromRange.dup;
799     assert(aInsFromRange.front == 2);
800     aFromRef.front = 5;
801     assert(aInsFromRange.front == 5);
802     assert(aFromDup.front == 2);
803 }
804 
805 @trusted unittest
806 {
807     import std.conv;
808     SCAlloc statsCollectorAlloc;
809     auto _allocator = allocatorObject(statsCollectorAlloc);
810 
811     () @safe {
812         testCopyAndRef(_allocator);
813         auto bytesUsed = _allocator.impl.bytesUsed;
814         assert(bytesUsed == 0, "Array ref count leaks memory; leaked "
815                 ~ to!string(bytesUsed) ~ " bytes");
816     }();
817 }
818 
819 version(unittest) private @trusted void testImmutability(IAllocator allocator)
820 {
821     auto a = immutable Array!(int)(allocator, 1, 2, 3);
822     auto a2 = a;
823     auto a3 = a2.save();
824 
825     assert(a2.front == 1);
826     assert(a2[0] == a2.front);
827     static assert(!__traits(compiles, a2.front = 4));
828     static assert(!__traits(compiles, a2.popFront()));
829 
830     auto a4 = a2.tail;
831     assert(a4.front == 2);
832     static assert(!__traits(compiles, a4 = a4.tail));
833 }
834 
835 version(unittest) private @trusted void testConstness(IAllocator allocator)
836 {
837     auto a = const Array!(int)(allocator, 1, 2, 3);
838     auto a2 = a;
839     auto a3 = a2.save();
840 
841     assert(a2.front == 1);
842     assert(a2[0] == a2.front);
843     static assert(!__traits(compiles, a2.front = 4));
844     static assert(!__traits(compiles, a2.popFront()));
845 
846     auto a4 = a2.tail;
847     assert(a4.front == 2);
848     static assert(!__traits(compiles, a4 = a4.tail));
849 }
850 
851 @trusted unittest
852 {
853     import std.conv;
854     SCAlloc statsCollectorAlloc;
855     auto _allocator = allocatorObject(statsCollectorAlloc);
856 
857     () @safe {
858         testImmutability(_allocator);
859         testConstness(_allocator);
860         auto bytesUsed = _allocator.impl.bytesUsed;
861         assert(bytesUsed == 0, "Array ref count leaks memory; leaked "
862                 ~ to!string(bytesUsed) ~ " bytes");
863     }();
864 }
865 
866 version(unittest) private @trusted void testWithStruct(IAllocator allocator)
867 {
868     import std.algorithm.comparison : equal;
869     import std.stdio;
870 
871     auto array = Array!int(allocator, 1, 2, 3);
872     {
873         auto arrayOfArrays = Array!(Array!int)(allocator, array);
874         assert(equal(arrayOfArrays.front, [1, 2, 3]));
875         arrayOfArrays.front.front = 2;
876         assert(equal(arrayOfArrays.front, [2, 2, 3]));
877         static assert(!__traits(compiles, arrayOfArrays.insert(1)));
878 
879         auto immArrayOfArrays = immutable Array!(Array!int)(allocator, array);
880         assert(immArrayOfArrays.front.front == 2);
881         static assert(!__traits(compiles, immArrayOfArrays.front.front = 2));
882     }
883     assert(equal(array, [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, "Array ref count leaks memory; leaked "
896                 ~ to!string(bytesUsed) ~ " bytes");
897     }();
898 }
899 
900 version(unittest) private @trusted 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 a = Array!MyClass(allocator, c);
911         assert(a.front.x == 10);
912         assert(a.front is c);
913         a.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, "Array ref count leaks memory; leaked "
928                 ~ to!string(bytesUsed) ~ " bytes");
929     }();
930 }
931 
932 version(unittest) private @trusted void testOpOverloads(IAllocator allocator)
933 {
934     auto a = Array!int(allocator, 1, 2, 3, 4);
935     assert(a[0] == 1); // opIndex
936 
937     // opIndexUnary
938     ++a[0];
939     assert(a[0] == 2);
940     --a[0];
941     assert(a[0] == 1);
942     a[0]++;
943     assert(a[0] == 2);
944     a[0]--;
945     assert(a[0] == 1);
946 
947     // opIndexAssign
948     a[0] = 2;
949     assert(a[0] == 2);
950 
951     // opIndexOpAssign
952     a[0] /= 2;
953     assert(a[0] == 1);
954     a[0] *= 2;
955     assert(a[0] == 2);
956     a[0] -= 1;
957     assert(a[0] == 1);
958     a[0] += 1;
959     assert(a[0] == 2);
960 }
961 
962 @trusted unittest
963 {
964     import std.conv;
965     SCAlloc statsCollectorAlloc;
966     auto _allocator = allocatorObject(statsCollectorAlloc);
967 
968     () @safe {
969         testOpOverloads(_allocator);
970         auto bytesUsed = _allocator.impl.bytesUsed;
971         assert(bytesUsed == 0, "Array ref count leaks memory; leaked "
972                 ~ to!string(bytesUsed) ~ " bytes");
973     }();
974 }
975 
976 version(unittest) private @trusted void testSlice(IAllocator allocator)
977 {
978     import std.algorithm.comparison : equal;
979 
980     auto a = Array!int(allocator, 1, 2, 3, 4);
981     auto b = a[];
982     assert(equal(a, b));
983     b[1] = 5;
984     assert(a[1] == 5);
985 
986     size_t startPos = 2;
987     auto c = b[startPos .. $];
988     assert(equal(c, [3, 4]));
989     c[0] = 5;
990     assert(equal(a, b));
991     assert(equal(a, [1, 5, 5, 4]));
992     assert(a.capacity == b.capacity && b.capacity == c.capacity + startPos);
993 
994     c ~= 5;
995     assert(equal(c, [5, 4, 5]));
996     assert(equal(a, b));
997     assert(equal(a, [1, 5, 5, 4]));
998 }
999 
1000 @trusted unittest
1001 {
1002     import std.conv;
1003     SCAlloc statsCollectorAlloc;
1004     auto _allocator = allocatorObject(statsCollectorAlloc);
1005 
1006     () @safe {
1007         testSlice(_allocator);
1008         auto bytesUsed = _allocator.impl.bytesUsed;
1009         assert(bytesUsed == 0, "Array ref count leaks memory; leaked "
1010                 ~ to!string(bytesUsed) ~ " bytes");
1011     }();
1012 }
1013 
1014 //@trusted unittest {
1015     //import stdx.collection.slist;
1016     //import std.stdio;
1017 
1018     //{
1019         //auto a = Array!(SList!int)(SList!int(1));
1020         //writefln("Array: %s", *a.prefCount(a._support));
1021         //writefln("SList: %s", *a.front.prefCount(a.front._head));
1022         //{
1023             //auto b = a;
1024             //writefln("Array: %s", *a.prefCount(a._support));
1025             //writefln("SList: %s", *a.front.prefCount(a.front._head));
1026             //size_t i = 0;
1027             //auto sl = a.front;
1028             ////while(!a.front.empty)
1029             ////{
1030                 ////writefln("[%s] %s", i, a.front.front);
1031                 ////a.front.popFront;
1032             ////}
1033             //while(!sl.empty)
1034             //{
1035                 //writefln("[%s] %s", i, sl.front);
1036                 //sl.popFront;
1037             //}
1038             //writefln("At end of scope");
1039         //}
1040         //writefln("After end of scope");
1041         //writefln("Array: %s", *a.prefCount(a._support));
1042         //writefln("SList: %s", *a.front.prefCount(a.front._head));
1043     //}
1044 
1045     //import std.conv;
1046     //writefln("HERE");
1047     //auto bytesUsed = _allocator.bytesUsed;
1048     //assert(bytesUsed == 0, "Array ref count leaks memory; leaked "
1049                            //~ to!string(bytesUsed) ~ " bytes");
1050 /*}*/