1 module nxt.class_range;
2 
3 import std.traits : isArray;
4 import std.range.primitives : isInputRange, ElementType;
5 
6 /** Upcast all elements in `x` of type `T` to the type `U`, where `U` is a
7  * superclass of `T`.
8  */
9 inout(U)[] upcastElementsTo(U, T)(return scope inout(T)[] x) @trusted
10 if (is(T == class) &&
11     is(U == class)
12     // TODO: also check that `T` is a subclass of `U`
13     )
14 {
15     static if (__VERSION__ >= 2095) {
16         static assert(0, "This function is not needed anymore");
17     }
18     return cast(typeof(return))x;
19 }
20 
21 /// ditto
22 auto upcastElementsTo(U, R)(inout(R) x) @trusted
23 if (!isArray!R &&
24     is(U == class) &&
25     isInputRange!R && is(ElementType!R == class)
26     // TODO: also check that `ElementType!R` is a subclass of `U`
27     )
28 {
29     import std.algorithm.iteration : map;
30     return x.map!(_ => cast(U)_);
31 }
32 
33 ///
34 @safe pure unittest
35 {
36     class X
37     {
38         this(int x) { this.x = x; }
39         int x;
40     }
41     class Y : X
42     {
43         this(int x) { super(x); }
44         int x;
45     }
46 
47     void f2(X[2] xs) {}
48 
49     Y[2] xy = [new Y(42), new Y(43)];
50     static assert(is(typeof(xy) == Y[2]));
51 
52     // TODO: f2(xy);
53 }
54 
55 private struct DowncastingFilterResult(Subclass, Range)
56 {
57     import core.internal.traits : Unqual;
58 
59     alias R = Unqual!Range;
60     R _input;
61     private bool _primed;
62 
63     alias pred = _ => cast(Subclass)_ !is null;
64 
65     private void prime()
66     {
67         import std.range.primitives : empty, front, popFront;
68         if (_primed) return;
69         while (!_input.empty && !pred(_input.front))
70         {
71             _input.popFront();
72         }
73         _primed = true;
74     }
75 
76     this(R r)
77     {
78         _input = r;
79     }
80 
81     private this(R r, bool primed)
82     {
83         _input = r;
84         _primed = primed;
85     }
86 
87     auto opSlice() { return this; }
88 
89     import std.range.primitives : isInfinite;
90     static if (isInfinite!Range)
91     {
92         enum bool empty = false;
93     }
94     else
95     {
96         @property bool empty()
97         {
98             import std.range.primitives : empty;
99             prime(); return _input.empty;
100         }
101     }
102 
103     void popFront()
104     {
105         import std.range.primitives : front, popFront, empty;
106         do
107         {
108             _input.popFront();
109         } while (!_input.empty && !pred(_input.front));
110         _primed = true;
111     }
112 
113     @property Subclass front()
114     {
115         import std.range.primitives : front;
116         prime();
117         assert(!empty, "Attempting to fetch the front of an empty filter.");
118         return cast(typeof(return))_input.front;
119     }
120 
121     import std.range.primitives : isForwardRange;
122     static if (isForwardRange!R)
123     {
124         @property auto save()
125         {
126             import std.range.primitives : save;
127             return typeof(this)(_input.save, _primed);
128         }
129     }
130 }
131 
132 /** Variant of std.algorithm.iteration : that filters out all elements of
133  * `range` that are instances of `Subclass`.
134  */
135 template castFilter(Subclass)
136 {
137     import std.range.primitives : isInputRange, ElementType;
138     auto castFilter(Range)(Range range)
139     if (isInputRange!(Range) &&
140         is(ElementType!Range == class) &&
141         is(Subclass : ElementType!Range))
142     {
143         return DowncastingFilterResult!(Subclass, Range)(range);
144     }
145 }
146 
147 ///
148 @safe pure unittest
149 {
150     class X
151     {
152         this(int x) { this.x = x; }
153         int x;
154     }
155     class Y : X
156     {
157         this(int x) { super(x); }
158     }
159     auto y = castFilter!Y([new X(42), new Y(43)]);
160     auto yf = y.front;
161     static assert(is(typeof(yf) == Y));
162     y.popFront();
163     assert(y.empty);
164 }