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