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