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)
69 			return;
70         while (!_input.empty &&
71 			   !pred(_input.front))
72             _input.popFront();
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() => this;
88 
89     import std.range.primitives : isInfinite;
90     static if (isInfinite!Range)
91         enum bool empty = false;
92     else
93     {
94         @property bool empty()
95         {
96             prime();
97             import std.range.primitives : empty;
98 			return _input.empty;
99         }
100     }
101 
102     void popFront()
103     {
104         import std.range.primitives : front, popFront, empty;
105         do
106         {
107             _input.popFront();
108         } while (!_input.empty && !pred(_input.front));
109         _primed = true;
110     }
111 
112     @property Subclass front()
113     {
114         import std.range.primitives : front;
115         prime();
116         assert(!empty, "Attempting to fetch the front of an empty filter.");
117         return cast(typeof(return))_input.front;
118     }
119 
120     import std.range.primitives : isForwardRange;
121     static if (isForwardRange!R)
122     {
123         @property auto save()
124         {
125             import std.range.primitives : save;
126             return typeof(this)(_input.save, _primed);
127         }
128     }
129 }
130 
131 /** Variant of std.algorithm.iteration : that filters out all elements of
132  * `range` that are instances of `Subclass`.
133  */
134 template castFilter(Subclass)
135 {
136     import std.range.primitives : isInputRange, ElementType;
137     auto castFilter(Range)(Range range)
138     if (isInputRange!(Range) &&
139         is(ElementType!Range == class) &&
140         is(Subclass : ElementType!Range))
141 		=> DowncastingFilterResult!(Subclass, Range)(range);
142 }
143 
144 ///
145 @safe pure unittest
146 {
147     class X
148     {
149         this(int x) { this.x = x; }
150         int x;
151     }
152     class Y : X
153     {
154         this(int x) { super(x); }
155     }
156     auto y = castFilter!Y([new X(42), new Y(43)]);
157     auto yf = y.front;
158     static assert(is(typeof(yf) == Y));
159     y.popFront();
160     assert(y.empty);
161 }