1 module nxt.cyclic;
2 
3 import core.internal.traits : hasElaborateDestructor;
4 import std.algorithm : max;
5 import std.container.array : Array;
6 import std.range;
7 import std.traits : isMutable;
8 import nxt.container_traits : isAddress;
9 
10 private mixin template CyclicRangePrimitives(T, string makeCopy = "typeof(cast() this) copy;")
11 {
12     size_t capacity() const @property @nogc @safe
13     {
14         return array.length;
15     }
16 
17     size_t length() const @property @nogc @safe
18     {
19         return size;
20     }
21 
22     void put()(auto ref T val)
23     {
24         if (size >= array.length)
25             throw staticError!CyclicRangeError("array capacity overflow", __FILE__, __LINE__);
26         array[(start + size) % array.length] = val;
27         size++;
28     }
29 
30     void put()(const auto ref T val)
31     {
32         if (size >= array.length)
33             throw staticError!CyclicRangeError("array capacity overflow", __FILE__, __LINE__);
34         array[(start + size) % array.length] = val;
35         size++;
36     }
37 
38     void insertBack(T rhs)
39     {
40         put(rhs);
41     }
42 
43     void insertBack(size_t n)(T[n] rhs)
44     {
45         foreach (c; rhs)
46         {
47             put(c);
48         }
49     }
50 
51     void insertBack(Range)(Range rhs)
52         if (__traits(compiles, ElementType!Range) &&
53             is(ElementType!Range : T))
54     {
55         foreach (c; rhs)
56         {
57             put(c);
58         }
59     }
60 
61     alias stableInsertBack = insertBack;
62     alias insert = insertBack;
63     alias stableInsert = insertBack;
64     alias linearInsert = insertBack;
65     alias stableLinearInsert = insertBack;
66 
67     void insertFront()(auto ref T val)
68     {
69         if (size >= array.length)
70             throw staticError!CyclicRangeError("array capacity overflow", __FILE__, __LINE__);
71         start = (start + array.length - 1) % array.length;
72         array[start] = val;
73         size++;
74     }
75 
76     void popFront()
77     {
78         if (size == 0)
79             throw staticError!CyclicRangeError("trying to pop an empty array", __FILE__, __LINE__);
80         static if (hasElaborateDestructor!T)
81         {
82             destroy(array[start]);
83         }
84         else static if (isAddress!T)
85         {
86             array[start] = null; // please the GC
87         }
88         start = (start + 1) % array.length;
89         size--;
90     }
91 
92     auto save()
93     {
94         return this;
95     }
96 
97     bool empty() @nogc @safe @property const
98     {
99         return size == 0;
100     }
101 
102     ref auto front() @nogc @safe @property inout
103     {
104         if (size == 0)
105             throw staticError!CyclicRangeError("trying to call front on empty array",
106                                                __FILE__, __LINE__);
107         return array[start];
108     }
109 
110     void popBack()
111     {
112         if (size == 0)
113             throw staticError!CyclicRangeError("trying to pop an empty array", __FILE__, __LINE__);
114         size--;
115         static if (hasElaborateDestructor!T)
116         {
117             destroy(array[(start + size) % array.length]);
118         }
119         else static if (isAddress!T)
120         {
121             array[(start + size) % array.length] = null; // please the GC
122         }
123     }
124 
125     ref auto back() @property
126     {
127         if (size == 0)
128             throw staticError!CyclicRangeError("trying to call back on empty array",
129                                                __FILE__, __LINE__);
130         return array[(start + size - 1) % array.length];
131     }
132 
133     auto back() @property const
134     {
135         if (size == 0)
136             throw staticError!CyclicRangeError("trying to call back on empty array",
137                                                __FILE__, __LINE__);
138         return array[(start + size - 1) % array.length];
139     }
140 
141     size_t opDollar() @nogc @safe const
142     {
143         return length;
144     }
145 
146     ref inout(T) opIndex(size_t v) inout
147     {
148         if (v >= size)
149             throw staticError!CyclicRangeError("out of range", __FILE__, __LINE__);
150         else
151             return array[(v + start) % array.length];
152     }
153 
154     auto opIndex() const
155     {
156         return this.dup();
157     }
158 
159     private void validateRange(size_t[2] range)
160     {
161         immutable size_t from = range[0];
162         immutable size_t to = range[1];
163         if (to > size)
164             throw staticError!CyclicRangeError("trying to slice over array size",
165                                                __FILE__, __LINE__);
166         if (from > to)
167             throw staticError!CyclicRangeError("trying to negative slice", __FILE__, __LINE__);
168         if (from != to && from >= size || to - from > size)
169             throw staticError!CyclicRangeError("trying to slice over array bounds",
170                                                __FILE__, __LINE__);
171     }
172 
173     auto opIndex(size_t[2] range)
174     {
175         immutable size_t from = range[0];
176         immutable size_t to = range[1];
177         validateRange(range);
178 
179         mixin(makeCopy);
180         copy.start = 0;
181         copy.size = to - from;
182 
183         foreach (const i; 0 .. copy.size)
184         {
185             copy.array[i] = array[(i + start + from) % array.length];
186         }
187         return copy;
188     }
189 
190     static if (isMutable!T)
191     {
192         void opIndexUnary(string op)()
193         {
194             foreach (const i; 0 .. size)
195             {
196                 mixin(op ~ "array[(i + start) % array.length];");
197             }
198         }
199 
200         auto opIndexUnary(string op)(size_t i)
201         {
202             return mixin(op ~ "array[(i + start) % array.length]");
203         }
204 
205         void opIndexUnary(string op)(size_t[2] range)
206         {
207             immutable size_t from = range[0];
208             immutable size_t to = range[1];
209             validateRange(range);
210 
211             foreach (const i; 0 .. to - from)
212             {
213                 mixin(op ~ "array[(i + start + from) % array.length];");
214             }
215         }
216 
217         void opIndexAssign(U)(U val)
218         {
219             foreach (const i; 0 .. size)
220             {
221                 array[(i + start) % array.length] = val;
222             }
223         }
224 
225         void opIndexAssign(U)(U val, size_t i)
226         {
227             opIndex(i) = val;
228         }
229 
230         void opIndexAssign(U)(U val, size_t[2] range)
231         {
232             immutable size_t from = range[0];
233             immutable size_t to = range[1];
234             validateRange(range);
235 
236             foreach (const i; 0 .. to - from)
237             {
238                 array[(i + start + from) % array.length] = val;
239             }
240         }
241 
242         void opIndexOpAssign(string op, U)(U val)
243         {
244             foreach (const i; 0 .. size)
245             {
246                 mixin("array[(i + start) % array.length] " ~ op ~ "= val;");
247             }
248         }
249 
250         void opIndexOpAssign(string op, U)(U val, size_t i)
251         {
252             mixin("array[(i + start) % array.length] " ~ op ~ "= val;");
253         }
254 
255         void opIndexOpAssign(string op, U)(U val, size_t[2] range)
256         {
257             immutable size_t from = range[0];
258             immutable size_t to = range[1];
259             validateRange(range);
260 
261             foreach (const i; 0 .. to - from)
262             {
263                 mixin("array[(i + start + from) % array.length] " ~ op ~ "= val;");
264             }
265         }
266     }
267 
268     static if (isMutable!T)
269     {
270         import std.algorithm.mutation : move;
271 
272         T moveFront()
273         {
274             if (size == 0)
275                 throw staticError!CyclicRangeError("trying to move in empty array",
276                                                    __FILE__, __LINE__);
277             return move(array[0]);
278         }
279 
280         T moveBack()
281         {
282             if (size == 0)
283                 throw staticError!CyclicRangeError("trying to move in empty array",
284                                                    __FILE__, __LINE__);
285             return move(array[(start + size - 1) % array.length]);
286         }
287 
288         T moveAt(size_t i)
289         {
290             if (i >= size)
291                 throw staticError!CyclicRangeError("trying to move outside range",
292                                                    __FILE__, __LINE__);
293             return move(array[(start + i) % array.length]);
294         }
295     }
296 
297     size_t[2] opSlice(size_t i : 0)() const
298     {
299         return [0, size];
300     }
301 
302     size_t[2] opSlice(size_t i : 0)(size_t from, size_t to) const
303     {
304         return [from, to];
305     }
306 
307     /**
308      * Removes the last element from the array and returns it.
309      * Both stable and non-stable versions behave the same and guarantee
310      * that ranges iterating over the array are never invalidated.
311      *
312      * Precondition: `empty == false`
313      *
314      * Returns: The element removed.
315      *
316      * Complexity: $(BIGOH 1).
317      *
318      * Throws: `Exception` if the array is empty.
319      */
320     T removeAny()
321     {
322         auto result = back;
323         removeBack();
324         return result;
325     }
326 
327     alias stableRemoveAny = removeAny;
328 
329     /**
330      * Removes the value from the back of the array. Both stable and non-stable
331      * versions behave the same and guarantee that ranges iterating over the
332      * array are never invalidated.
333      *
334      * Precondition: `empty == false`
335      *
336      * Complexity: $(BIGOH 1).
337      *
338      * Throws: `Exception` if the array is empty.
339      */
340     void removeBack()
341     {
342         popBack();
343     }
344 
345     /// ditto
346     alias stableRemoveBack = removeBack;
347 
348     void removeBack(int howMany)
349     {
350         foreach (const i; 0 .. howMany)
351         {
352             popBack();
353         }
354     }
355 }
356 
357 struct CyclicRange(T)
358 {
359     T[] array;
360     size_t start, size;
361 
362     mixin CyclicRangePrimitives!T;
363 
364     CyclicRange!T dup() const
365     {
366         return cast(CyclicRange!T) this;
367     }
368 }
369 
370 /// @nogc array without memory management using a cyclic array internally
371 /// Should be treated like a static array when no len is set (copies on assignment)
372 /// The maximum capacity is static and by default so many elements that it fills at most 4KiB, but at least 8 elements (even if that is more than 4KiB).
373 /// Set length to 0 to make it a std.container.Array based array
374 /// Bugs: foreach with ref value requires @nogc body to make this @nogc compatible
375 struct CyclicArray(T, size_t len = max(8, 4096 / T.sizeof))
376 {
377     static if (len == 0)
378     {
379         private void reserve(size_t length) @nogc @system nothrow
380         {
381             assert(length > 0);
382             array.length = length;
383         }
384     }
385 
386 private:
387     static if (len == 0)
388         Array!T array;
389     else
390         T[len] array;
391     size_t start;
392     size_t size;
393 
394 public:
395     static if (len == 0)
396     {
397         invariant
398         {
399             assert(array.length > 0);
400         }
401 
402         // @disable this(); // TODO this triggers bug in dmd: https://issues.dlang.org/show_bug.cgi?id=20934
403 
404         this(Array!T array) @nogc
405         {
406             assert(array.length > 0);
407             this.array = array;
408         }
409 
410         this(size_t length) @nogc
411         {
412             assert(length > 0);
413             array = Array!T();
414             reserve(length);
415         }
416 
417         this(size_t n)(T[n] val)
418         {
419             array = Array!T();
420             reserve(max(8, 4096 / T.sizeof));
421             static if (len != 0)
422                 static assert(n <= len);
423             foreach (ref v; val)
424             {
425                 put(v);
426             }
427         }
428 
429         this(Range)(Range val)
430         if (__traits(compiles, ElementType!Range) && is(ElementType!Range : T))
431         {
432             array = Array!T();
433             reserve(max(8, 4096 / T.sizeof));
434             foreach (ref v; val)
435             {
436                 put(v);
437             }
438         }
439 
440         this(Args...)(Args val)
441         {
442             array = Array!T();
443             reserve(max(8, 4096 / T.sizeof));
444             foreach (ref v; val)
445             {
446                 put(v);
447             }
448         }
449     }
450     else
451     {
452         this(size_t n)(T[n] val)
453         {
454             static if (len != 0)
455             {
456                 static assert(n <= len);
457             }
458             foreach (ref v; val)
459             {
460                 put(v);
461             }
462         }
463 
464         this(Range)(Range val)
465             if (__traits(compiles, ElementType!Range) && is(ElementType!Range : T))
466         {
467             foreach (ref v; val)
468             {
469                 put(v);
470             }
471         }
472 
473         this(Args...)(Args val)
474         {
475             foreach (ref v; val)
476             {
477                 put(v);
478             }
479         }
480     }
481 
482     mixin CyclicRangePrimitives!(T, q{
483             static if (len == 0)
484                 auto copy = typeof(cast() this)(array.length);
485             else
486                 typeof(cast() this) copy;
487 	});
488 
489     static if (len != 0)
490         CyclicRange!T byRef() @property @nogc @safe
491         {
492             return CyclicRange!T(array[], start, size);
493         }
494 
495     size_t length() const @property @nogc @safe
496     {
497         return size;
498     }
499 
500     size_t length(size_t val) @property
501     {
502         if (size > array.length)
503             assert(false);
504         if (val == 0)
505         {
506             clear();
507             return size;
508         }
509         if (val > size)
510         {
511             foreach (const i; size .. val)
512             {
513                 array[(start + i) % array.length] = T.init;
514             }
515         }
516         else if (val < size)
517         {
518             static if (hasElaborateDestructor!T)
519             {
520                 foreach (const i; val .. size)
521                 {
522                     destroy(array[(start + i) % array.length]);
523                 }
524             }
525             else static if (isAddress!T)
526             {
527                 foreach (const i; val .. size)
528                 {
529                     array[(start + i) % array.length] = null; // please the GC
530                 }
531             }
532         }
533         return size = val;
534     }
535 
536     void clear()
537     {
538         static if (hasElaborateDestructor!T)
539         {
540             foreach (const i; 0 .. size)
541             {
542                 destroy(array[(start + i) % array.length]);
543             }
544         }
545         start = 0; // optimize clears
546         size = 0;
547     }
548 
549     auto opBinary(string op : "~")(T rhs) const
550     {
551         auto copy = this.dup;
552         copy.put(rhs);
553         return copy;
554     }
555 
556     auto opBinary(string op : "~", size_t n)(T[n] rhs) const
557     {
558         auto copy = this.dup;
559         copy ~= rhs;
560         return copy;
561     }
562 
563     auto opBinary(string op : "~", Range)(Range rhs) const
564         if (isInputRange!Range && is(ElementType!Range : T))
565         {
566             auto copy = this.dup;
567             copy ~= rhs;
568             return copy;
569         }
570 
571     void opOpAssign(string op : "~")(T rhs)
572     {
573         put(rhs);
574     }
575 
576     void opOpAssign(string op : "~", size_t n)(T[n] rhs)
577     {
578         foreach (c; rhs)
579         {
580             put(c);
581         }
582     }
583 
584     void opOpAssign(string op : "~", size_t n)(CyclicArray!(T, n) rhs)
585     {
586         for (int i = 0; i < rhs.size; i++)
587             put(rhs.array[(rhs.start + i) % rhs.array.length]);
588     }
589 
590     void opOpAssign(string op : "~", Range)(Range rhs)
591         if (__traits(compiles, ElementType!Range) && is(ElementType!Range : T))
592     {
593         foreach (c; rhs)
594         {
595             put(c);
596         }
597     }
598 
599     static if (len == 0)
600     {
601         CyclicArray!(T, len) dup() const
602         {
603             auto ret = CyclicArray!(T, len)(array);
604             ret.start = start;
605             ret.size = size;
606             return ret;
607         }
608     }
609     else
610     {
611         CyclicArray!(T, len) dup() const
612         {
613             CyclicArray!(T, len) ret;
614             ret.array = cast(typeof(ret.array)) array;
615             ret.start = start;
616             ret.size = size;
617             return ret;
618         }
619     }
620 
621     static if (len != 0)
622     {
623         int opApply(int delegate(ref T) @nogc dg) @nogc
624         {
625             int result = 0;
626 
627             for (size_t i = 0; i < size; i++)
628             {
629                 result = dg(array[(i + start) % array.length]);
630                 if (result)
631                     break;
632             }
633 
634             return result;
635         }
636 
637         int opApply(int delegate(size_t, ref T) @nogc dg) @nogc
638         {
639             int result = 0;
640 
641             for (size_t i = 0; i < size; i++)
642             {
643                 result = dg(i, array[(i + start) % array.length]);
644                 if (result)
645                     break;
646             }
647 
648             return result;
649         }
650     }
651 
652     bool opEquals(in CyclicArray!(T, len) b)
653     {
654         if (size != b.size)
655             return false;
656         for (int i = 0; i < size; i++)
657             if (array[(i + start) % array.length] != b.array[(i + b.start) % b.array.length])
658                 return false;
659         return true;
660     }
661 
662     bool opEquals(size_t n)(T[n] b)
663     {
664         if (size != n)
665             return false;
666         for (int i = 0; i < size; i++)
667             if (array[(i + start) % array.length] != b[i])
668                 return false;
669         return true;
670     }
671 
672     bool opEquals(Range)(Range b) if (hasLength!Range && is(ElementType!Range : T))
673     {
674         if (size != b.length)
675             return false;
676         auto r = b.save;
677         for (int i = 0; i < size; i++)
678         {
679             if (array[(i + start) % array.length] != r.front)
680                 return false;
681             r.popFront;
682         }
683         return true;
684     }
685 }
686 
687 ///
688 @nogc @safe unittest
689 {
690     CyclicArray!int array;
691     assert(array.length == 0);
692     array ~= 5;
693     assert(!array.empty);
694     assert(array.front == 5);
695     assert(array[0] == 5);
696     array ~= [4, 3];
697 
698     assert(array == [5, 4, 3]);
699 
700     // same efficiency as insertBack/put/concat
701     array.insertFront(5);
702 }
703 
704 @nogc @system unittest
705 {
706     // heap array using std.container.Array with 16 elements
707     auto heapArray = CyclicArray!(int, 0)(16);
708 
709     // custom memory using Array
710     auto myArray = Array!int(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
711     auto customArray = CyclicArray!(int, 0)(myArray[0 .. 6]);
712 }
713 
714 class CyclicRangeError : Error
715 {
716     @nogc @safe pure nothrow this(string msg, string file = __FILE__,
717                                   size_t line = __LINE__, Throwable next = null)
718     {
719         super(msg, file, line, next);
720     }
721 }
722 
723 // TLS storage shared for all errors, chaining might create circular reference
724 private void[128] _store;
725 
726 // only Errors for now as those are rarely chained
727 private T staticError(T, Args...)(auto ref Args args) @trusted if (is(T : Error))
728 {
729     // pure hack, what we actually need is @noreturn and allow to call that in pure functions
730     static T get()
731     {
732         static assert(__traits(classInstanceSize, T) <= _store.length,
733                       T.stringof ~ " is too large for staticError()");
734 
735         _store[0 .. __traits(classInstanceSize, T)] = typeid(T).initializer[];
736         return cast(T) _store.ptr;
737     }
738 
739     auto res = (cast(T function() @trusted pure nothrow @nogc)&get)();
740     res.__ctor(args);
741     return res;
742 }
743 
744 // lots of unittests taken from std.container.array
745 
746 // Give the Range object some testing.
747 @system unittest
748 {
749     import std.algorithm.comparison : equal;
750     import std.range : retro;
751 
752     auto a = CyclicArray!int(0, 1, 2, 3, 4, 5, 6)[];
753     assert(equal(a, [0, 1, 2, 3, 4, 5, 6]));
754     assert(equal(a[], [0, 1, 2, 3, 4, 5, 6]));
755     assert(equal(a[0 .. $], [0, 1, 2, 3, 4, 5, 6]));
756     auto b = CyclicArray!int(6, 5, 4, 3, 2, 1, 0)[];
757     alias A = typeof(a);
758     alias ARef = typeof(a.byRef);
759 
760     static assert(isRandomAccessRange!A);
761     static assert(hasSlicing!A);
762     static assert(hasAssignableElements!A);
763     static assert(hasMobileElements!A);
764 
765     static assert(isRandomAccessRange!ARef);
766     static assert(hasSlicing!ARef);
767     static assert(hasAssignableElements!ARef);
768     static assert(hasMobileElements!ARef);
769 
770     assert(equal(retro(b), a));
771     assert(a.length == 7);
772     assert(equal(a[1 .. 4], [1, 2, 3]));
773 }
774 
775 @system unittest
776 {
777     alias S = structBug5920;
778     uint dMask;
779 
780     auto arr = CyclicArray!S(cast(S[])[]);
781     foreach (const i; 0 .. 8)
782     {
783         arr.insertBack(S(i, &dMask));
784     }
785     // don't check dMask now as S may be copied multiple times (it's ok?)
786     {
787         assert(arr.length == 8);
788         dMask = 0;
789         arr.length = 6;
790         assert(arr.length == 6); // make sure shrinking calls the d'tor
791         assert(dMask == 0b1100_0000);
792         arr.removeBack();
793         assert(arr.length == 5); // make sure removeBack() calls the d'tor
794         assert(dMask == 0b1110_0000);
795         arr.removeBack(3);
796         assert(arr.length == 2); // ditto
797         assert(dMask == 0b1111_1100);
798         arr.clear();
799         assert(arr.length == 0); // make sure clear() calls the d'tor
800         assert(dMask == 0b1111_1111);
801     }
802     assert(dMask == 0b1111_1111); // make sure the d'tor is called once only.
803 }
804 
805 @system unittest
806 {
807     auto a = CyclicArray!(int[])([[1, 2], [3, 4]]);
808     assert(a.length == 2);
809     assert(a[0] == [1, 2]);
810     assert(a[1] == [3, 4]);
811 }
812 
813 @system unittest
814 {
815     import std.algorithm.comparison : equal;
816 
817     //Test "array-wide" operations
818     auto a = CyclicArray!int([0, 1, 2]); //CyclicArray
819     a[] += 5;
820     assert(a[].equal([5, 6, 7]));
821     ++a[];
822     assert(a[].equal([6, 7, 8]));
823     import std.stdio;
824 
825     a[1 .. 3] *= 5;
826     assert(a[].equal([6, 35, 40]));
827     a[0 .. 2] = 0;
828     assert(a[].equal([0, 0, 40]));
829 
830     //Test empty array
831     auto a2 = CyclicArray!int.init;
832     ++a2[];
833     ++a2[0 .. 0];
834     a2[] = 0;
835     a2[0 .. 0] = 0;
836     a2[] += 0;
837     a2[0 .. 0] += 0;
838 
839     //Test "range-wide" operations
840     auto r = CyclicArray!int([0, 1, 2])[]; //CyclicArray.Range
841     r[] += 5;
842     assert(r.equal([5, 6, 7]));
843     ++r[];
844     assert(r.equal([6, 7, 8]));
845     r[1 .. 3] *= 5;
846     assert(r.equal([6, 35, 40]));
847     r[0 .. 2] = 0;
848     assert(r.equal([0, 0, 40]));
849 
850     //Test empty Range
851     auto r2 = CyclicArray!int.init[];
852     ++r2[];
853     ++r2[0 .. 0];
854     r2[] = 0;
855     r2[0 .. 0] = 0;
856     r2[] += 0;
857     r2[0 .. 0] += 0;
858 }
859 
860 // Test issue
861 @system unittest
862 {
863     static struct S
864     {
865         int i = 1337;
866         void* p;
867         this(this)
868         {
869             assert(i == 1337);
870         }
871 
872         ~this() @nogc
873         {
874             assert(i == 1337);
875         }
876     }
877 
878     CyclicArray!S arr;
879     S s;
880     arr ~= s;
881     arr ~= s;
882 }
883 
884 @system unittest
885 {
886     import std.algorithm.iteration : filter;
887 
888     auto a = CyclicArray!int([1, 2, 2].filter!"true"());
889 }
890 
891 @safe unittest
892 {
893     auto arr = new CyclicArray!int;
894 }
895 
896 @system unittest  //6998
897 {
898     static int i = 0;
899     class C
900     {
901         int dummy = 1;
902         this()
903         {
904             ++i;
905         }
906 
907         ~this() @nogc
908         {
909             --i;
910         }
911     }
912 
913     assert(i == 0);
914     auto c = new C();
915     assert(i == 1);
916 
917     //scope
918     {
919         auto arr = CyclicArray!C(c);
920         assert(i == 1);
921     }
922     //CyclicArray should not have destroyed the class instance
923     assert(i == 1);
924 
925     //Just to make sure the GC doesn't collect before the above test.
926     assert(c.dummy == 1);
927 }
928 
929 @system unittest  //6998-2
930 {
931     static class C
932     {
933         int i;
934     }
935 
936     auto c = new C;
937     c.i = 42;
938     CyclicArray!C a;
939     a ~= c;
940     a.clear;
941     assert(c.i == 42); //fails
942 }
943 
944 @nogc:
945 unittest
946 {
947     alias IntArray = CyclicArray!int;
948     alias IntRange = CyclicRange!int;
949 
950     static assert(isInputRange!IntArray);
951     static assert(isOutputRange!(IntArray, int));
952     static assert(isForwardRange!IntArray);
953     static assert(isBidirectionalRange!IntArray);
954     static assert(isRandomAccessRange!IntArray);
955     static assert(hasMobileElements!IntArray);
956     static assert(is(ElementType!IntArray == int));
957     static assert(hasSwappableElements!IntArray);
958     static assert(hasAssignableElements!IntArray);
959     static assert(hasLvalueElements!IntArray);
960     static assert(hasLength!IntArray);
961     static assert(hasSlicing!IntArray);
962 
963     static assert(isInputRange!IntRange);
964     static assert(isOutputRange!(IntRange, int));
965     static assert(isForwardRange!IntRange);
966     static assert(isBidirectionalRange!IntRange);
967     static assert(isRandomAccessRange!IntRange);
968     static assert(hasMobileElements!IntRange);
969     static assert(is(ElementType!IntRange == int));
970     static assert(hasSwappableElements!IntRange);
971     static assert(hasAssignableElements!IntRange);
972     static assert(hasLvalueElements!IntRange);
973     static assert(hasLength!IntRange);
974     static assert(hasSlicing!IntRange);
975 
976     IntArray array;
977     assert(array.length == 0);
978     assert(array.empty);
979     array ~= 5;
980     assert(!array.empty);
981     assert(array.length == 1);
982     assert(array.front == 5);
983     assert(array[0] == 5);
984     assert(array[0 .. 1].front == 5);
985     assert(array[0 .. 0].empty);
986     array ~= cast(int[2])[4, 3];
987     assert(array.length == 3);
988     assert(array[1] == 4);
989     assert(array[2] == 3);
990 
991     for (int i = 0; i < 50000; i++)
992     {
993         array ~= i;
994         array.popFront();
995     }
996 
997     assert(array.length == 3);
998 
999     array ~= array;
1000     assert(array.length == 6);
1001     assert((array ~ array).length == 12);
1002     assert(array.length == 6);
1003 
1004     array[5] = 1;
1005     assert(array[5] == 1);
1006     auto copy = array;
1007     copy[5] = 2;
1008     assert(array[5] == 1);
1009     array[][5] = 1;
1010     assert(array[5] == 1);
1011 
1012     foreach (ref v; array.byRef)
1013     {
1014         v = 42;
1015     }
1016 
1017     assert(array[5] == 42);
1018 }
1019 
1020 unittest
1021 {
1022     alias IntArray = CyclicArray!(int, 0);
1023 
1024     static assert(isInputRange!IntArray);
1025     static assert(isOutputRange!(IntArray, int));
1026     static assert(isForwardRange!IntArray);
1027     static assert(isBidirectionalRange!IntArray);
1028     static assert(isRandomAccessRange!IntArray);
1029     static assert(hasMobileElements!IntArray);
1030     static assert(is(ElementType!IntArray == int));
1031     static assert(hasSwappableElements!IntArray);
1032     static assert(hasAssignableElements!IntArray);
1033     static assert(hasLvalueElements!IntArray);
1034     static assert(hasLength!IntArray);
1035     static assert(hasSlicing!IntArray);
1036 
1037     auto array = IntArray(1024);
1038     assert(array.length == 0);
1039     assert(array.empty);
1040     array ~= 5;
1041     assert(!array.empty);
1042     assert(array.length == 1);
1043     assert(array.front == 5);
1044     assert(array[0] == 5);
1045     assert(array[0 .. 1].front == 5);
1046     assert(array[0 .. 0].empty);
1047     array ~= cast(int[2])[4, 3];
1048     assert(array.length == 3);
1049     assert(array[1] == 4);
1050     assert(array[2] == 3);
1051 
1052     for (int i = 0; i < 50000; i++)
1053     {
1054         array ~= i;
1055         array.popFront();
1056     }
1057 
1058     assert(array.length == 3);
1059 
1060     array ~= array;
1061     assert(array.length == 6);
1062     assert((array ~ array).length == 12);
1063     assert(array.length == 6);
1064 
1065     array[5] = 1;
1066     assert(array[5] == 1);
1067     auto copy = array[];
1068     copy[5] = 2;
1069     assert(array[5] == 1);
1070     array[][5] = 1;
1071     assert(array[5] == 1);
1072 }
1073 
1074 @system unittest
1075 {
1076     CyclicArray!int a;
1077     assert(a.empty);
1078 }
1079 
1080 @system unittest
1081 {
1082     CyclicArray!int a;
1083     a.length = 10;
1084     assert(a.length == 10);
1085     assert(a.capacity >= a.length);
1086 }
1087 
1088 @system unittest
1089 {
1090     struct Dumb
1091     {
1092         int x = 5;
1093     }
1094 
1095     CyclicArray!Dumb a;
1096     a.length = 10;
1097     assert(a.length == 10);
1098     assert(a.capacity >= a.length);
1099     immutable cap = a.capacity;
1100     foreach (ref e; a)
1101     {
1102         e.x = 10;
1103     }
1104     a.length = 5;
1105     assert(a.length == 5);
1106     foreach (ref e; a)
1107     {
1108         assert(e.x == 10);
1109     }
1110 
1111     a.length = 8;
1112     assert(a.length == 8);
1113     assert(Dumb.init.x == 5);
1114     foreach (const i; 0 .. 5)
1115     {
1116         assert(a[i].x == 10);
1117     }
1118     foreach (const i; 5 .. a.length)
1119     {
1120         assert(a[i].x == Dumb.init.x);
1121     }
1122 
1123     a[] = Dumb(1);
1124     a.length = 20;
1125     foreach (const i; 0 .. 8)
1126     {
1127         assert(a[i].x == 1);
1128     }
1129     foreach (const i; 8 .. a.length)
1130     {
1131         assert(a[i].x == Dumb.init.x);
1132     }
1133 
1134     // check if overlapping elements properly initialized
1135     a.length = 1;
1136     a.length = 20;
1137     assert(a[0].x == 1);
1138     foreach (e; a[1 .. $])
1139     {
1140         assert(e.x == Dumb.init.x);
1141     }
1142 }
1143 
1144 @system unittest
1145 {
1146     CyclicArray!int a = CyclicArray!int(1, 2, 3);
1147     //a._data._refCountedDebug = true;
1148     auto b = a.dup;
1149     assert(b == CyclicArray!int(1, 2, 3));
1150     b.front = 42;
1151     assert(b == CyclicArray!int(42, 2, 3));
1152     assert(a == CyclicArray!int(1, 2, 3));
1153 }
1154 
1155 @system unittest
1156 {
1157     auto a = CyclicArray!int(1, 2, 3);
1158     assert(a.length == 3);
1159 }
1160 
1161 @system unittest
1162 {
1163     const CyclicArray!int a = [1, 2];
1164 
1165     assert(a[0] == 1);
1166     assert(a.front == 1);
1167     assert(a.back == 2);
1168 
1169     static assert(!__traits(compiles, { a[0] = 1; }));
1170     static assert(!__traits(compiles, { a.front = 1; }));
1171     static assert(!__traits(compiles, { a.back = 1; }));
1172 
1173     auto r = a[];
1174     size_t i;
1175     foreach (e; r)
1176     {
1177         assert(e == i + 1);
1178         i++;
1179     }
1180 }
1181 
1182 @system unittest
1183 {
1184     auto a = CyclicArray!int(1, 2, 3);
1185     a[1] *= 42;
1186     assert(a[1] == 84);
1187 }
1188 
1189 @system unittest
1190 {
1191     auto a = CyclicArray!int(1, 2, 3);
1192     auto b = CyclicArray!int(11, 12, 13);
1193     auto c = a ~ b;
1194     assert(c == CyclicArray!int(1, 2, 3, 11, 12, 13));
1195     assert(a ~ b[] == CyclicArray!int(1, 2, 3, 11, 12, 13));
1196     assert(a ~ [4, 5] == CyclicArray!int(1, 2, 3, 4, 5));
1197 }
1198 
1199 @system unittest
1200 {
1201     auto a = CyclicArray!int(1, 2, 3);
1202     auto b = CyclicArray!int(11, 12, 13);
1203     a ~= b;
1204     assert(a == CyclicArray!int(1, 2, 3, 11, 12, 13));
1205 }
1206 
1207 @system unittest
1208 {
1209     auto a = CyclicArray!int(1, 2, 3, 4);
1210     assert(a.removeAny() == 4);
1211     assert(a == CyclicArray!int(1, 2, 3));
1212 }
1213 
1214 private struct structBug5920
1215 {
1216     int order;
1217     uint* pDestructionMask;
1218     ~this() @nogc
1219     {
1220         if (pDestructionMask)
1221             *pDestructionMask += 1 << order;
1222     }
1223 }
1224 
1225 @system unittest
1226 {
1227     auto a = CyclicArray!int([1, 1]);
1228     a[1] = 0; //Check CyclicArray.opIndexAssign
1229     assert(a[1] == 0);
1230     a[1] += 1; //Check CyclicArray.opIndexOpAssign
1231     assert(a[1] == 1);
1232 
1233     //Check CyclicArray.opIndexUnary
1234     ++a[0];
1235     //a[0]++ //op++ doesn't return, so this shouldn't work, even with 5044 fixed
1236     assert(a[0] == 2);
1237     assert(+a[0] == +2);
1238     assert(-a[0] == -2);
1239     assert(~a[0] == ~2);
1240 
1241     auto r = a[];
1242     r[1] = 0; //Check CyclicArray.Range.opIndexAssign
1243     assert(r[1] == 0);
1244     r[1] += 1; //Check CyclicArray.Range.opIndexOpAssign
1245     assert(r[1] == 1);
1246 
1247     //Check CyclicArray.Range.opIndexUnary
1248     ++r[0];
1249     //r[0]++ //op++ doesn't return, so this shouldn't work, even with 5044 fixed
1250     assert(r[0] == 3);
1251     assert(+r[0] == +3);
1252     assert(-r[0] == -3);
1253     assert(~r[0] == ~3);
1254 }
1255 
1256 @safe unittest
1257 {
1258     static struct S
1259     {
1260         bool b;
1261         alias b this;
1262     }
1263 
1264     alias A = CyclicArray!S;
1265     alias B = CyclicArray!(shared bool);
1266 }
1267 
1268 @system unittest
1269 {
1270     CyclicArray!int ai;
1271     ai ~= 1;
1272     assert(ai.front == 1);
1273 
1274     static const arr = [1, 2, 3];
1275     ai.insertBack(arr);
1276 }