1 /**
2     Provides meta utilities that can modify types
3 */
4 module bolts.meta;
5 
6 import bolts.internal;
7 
8 /**
9     Flattens a list of types to their `ElementType` or in the case of an AliasPack it's `.UnpackDeep`
10 
11     If a type is a range then its `ElementType` is used
12 */
13 template Flatten(Values...) {
14     import std.meta: AliasSeq;
15     static if (Values.length) {
16         import std.range: isInputRange;
17         import std.traits: isInstanceOf;
18 
19         alias Head = Values[0];
20         alias Tail = Values[1..$];
21 
22         static if (isInstanceOf!(AliasPack, Head)) {
23             alias Flatten = Flatten!(Head.UnpackDeep, Flatten!Tail);
24         } else static if (isInputRange!Head) {
25             import std.range: ElementType;
26             alias Flatten = Flatten!(ElementType!Head, Flatten!Tail);
27         } else {
28             alias Flatten = AliasSeq!(Head, Flatten!Tail);
29         }
30     } else {
31         alias Flatten = AliasSeq!();
32     }
33 }
34 
35 ///
36 unittest {
37     import std.algorithm: filter;
38     import std.meta: AliasSeq;
39 
40     alias R1 = typeof([1, 2, 3].filter!"true");
41     alias R2 = typeof([1.0, 2.0, 3.0]);
42 
43     static assert(is(Flatten!(int, double) == AliasSeq!(int, double)));
44     static assert(is(Flatten!(int, R1, R2) == AliasSeq!(int, int, double)));
45     static assert(is(Flatten!(R1, int) == AliasSeq!(int, int)));
46 
47     import std.traits: CommonType;
48     static assert(is(CommonType!(Flatten!(int, R1, R2, float)) == double));
49 
50     static assert(is(Flatten!(R1, int, AliasPack!(AliasPack!(float, int)), int) == AliasSeq!(int, int, float, int, int)));
51 }
52 
53 /**
54     Same as an AliasSeq that does not auto expand.
55 
56     You can get to the provided compile time sequence (AliasSeq) by accessing the `.Unpack` member.
57     And if you want a recursive expansion there's `UnpackDeep` for that. Also a convenience
58     `.equals!(otherAliasPack)` is provided.
59 
60     See_Also:
61         - https://forum.dlang.org/post/mnobngrzdmqbxomulpts@forum.dlang.org
62 */
63 template AliasPack(T...) {
64     alias Unpack = T;
65     enum length = Unpack.length;
66 
67     private template UnpackDeepImpl(U...) {
68         import std.meta: AliasSeq;
69         static if (U.length) {
70             import std.traits: isInstanceOf;
71             static if (isInstanceOf!(AliasPack, U[0])) {
72                 alias Head = UnpackDeepImpl!(U[0].Unpack);
73             } else {
74                 import std.meta: Alias;
75                 alias Head = Alias!(U[0]);
76             }
77             alias UnpackDeepImpl = AliasSeq!(Head, UnpackDeepImpl!(U[1 .. $]));
78         } else {
79             alias UnpackDeepImpl = AliasSeq!();
80         }
81     }
82 
83     alias UnpackDeep = UnpackDeepImpl!T;
84 
85     template equals(U...) {
86         static if (T.length == U.length) {
87             static if (T.length == 0) {
88                 enum equals = true;
89             } else {
90                 import bolts.traits: isSame;
91                 enum equals = isSame!(T[0], U[0]) && AliasPack!(T[1 .. $]).equals!(U[1 .. $]);
92             }
93         } else {
94             enum equals = false;
95         }
96     }
97 
98     static if (length > 0) {
99         import std.meta: Alias;
100         alias Head = Alias!(T[0]);
101     } else {
102         alias Head = void;
103     }
104 
105     static if (length > 1) {
106         alias Tail = AliasPack!(T[1 .. $]);
107     } else {
108         alias Tail = AliasPack!();
109     }
110 }
111 
112 ///
113 unittest {
114     alias P = AliasPack!(1, int, "abc");
115     static assert( P.equals!(1, int, "abc"));
116     static assert(!P.equals!(1, int, "cba"));
117 
118     static assert(P.Head == 1);
119     static assert(P.Tail.equals!(int, "abc"));
120     static assert(is(P.Tail.Head == int));
121     static assert(P.Tail.Tail.Head == "abc");
122 
123     alias PackOfPacks = AliasPack!(AliasPack!(1, int), AliasPack!(2, float), 17);
124     static assert(AliasPack!(PackOfPacks.UnpackDeep).equals!(1, int, 2, float, 17));
125 }
126 
127 deprecated("use Zip instead")
128 alias staticZip = Zip;
129 
130 /**
131     Zips sequences of `AliasPack`s together into an AliasPack of AliasPacks.
132 
133     See_Also:
134         - https://forum.dlang.org/post/mnobngrzdmqbxomulpts@forum.dlang.org
135 */
136 template Zip(Seqs...) {
137     import std.traits: isInstanceOf;
138     import std.meta: allSatisfy, staticMap;
139 
140     private enum isPack(alias T) = isInstanceOf!(AliasPack, T);
141 
142     static assert(
143         Seqs.length >= 2 && allSatisfy!(isPack, Seqs),
144         "Must have 2 or more arguments of type AliasPack"
145     );
146 
147     enum len = Seqs[0].length;
148     static foreach (Seq; Seqs[1 .. $]) {
149         static assert(
150             Seq.length == len,
151             "All arguments to Zip must have the same length"
152         );
153     }
154 
155     alias Head(alias P) = P.Head;
156     alias Tail(alias P) = P.Tail;
157 
158     static if (len == 0) {
159         alias Zip = AliasPack!();
160     } else {
161         alias Zip = AliasPack!(
162             AliasPack!(staticMap!(Head, Seqs)),
163             Zip!(staticMap!(Tail, Seqs)).Unpack
164         );
165     }
166 }
167 
168 ///
169 unittest {
170     alias a = AliasPack!(1, 2, 3);
171     alias b = AliasPack!(4, 5, 6);
172     alias c = AliasPack!(7, 8, 9);
173     alias d = Zip!(a, b, c);
174 
175     static assert(d.length == 3);
176 
177     static assert(d.Unpack[0].equals!(1, 4, 7));
178     static assert(d.Unpack[1].equals!(2, 5, 8));
179     static assert(d.Unpack[2].equals!(3, 6, 9));
180 }
181 
182 /**
183     Extract the elements of an `AliasPack` at given positions.
184 
185     The function takes two parameters - the first is the `AliasPack` to extract
186     from, the second is an `AliasPack` of positions.
187 */
188 template Pluck(alias Pack, size_t[] Positions) if (from.std.traits.isInstanceOf!(AliasPack, Pack)) {
189     static if (Positions.length == 0) {
190         alias Pluck = AliasPack!();
191     } else {
192         static assert(
193             Positions[0] >= 0 && Positions[0] < Pack.length,
194             "Position is out of range"
195         );
196         alias Pluck = AliasPack!(
197             Pack.Unpack[Positions[0]],
198             Pluck!(Pack, Positions[1..$]).Unpack
199         );
200     }
201 }
202 
203 ///
204 unittest {
205     static assert(Pluck!(AliasPack!(), []).equals!());
206     static assert(Pluck!(AliasPack!(int, char, float), []).equals!());
207     static assert(Pluck!(AliasPack!(int, char, float), [0, 2]).equals!(int, float));
208 }
209 
210 /**
211     Filters all the members of a type based on a provided predicate
212 
213     The predicate takes two parameters - the first is the type, the second is
214     the string name of the member being iterated on.
215 */
216 template FilterMembersOf(T, alias Fn) {
217     import std.meta: Filter, ApplyLeft;
218     alias ResolvedType = ResolvePointer!T;
219     alias FilterMembersOf = Filter!(ApplyLeft!(Fn, ResolvedType), __traits(allMembers, ResolvedType));
220 }
221 
222 ///
223 unittest {
224     import std.meta: AliasSeq;
225 
226     struct S {
227         int i;
228         float f;
229         int i2;
230         short s;
231     }
232 
233     enum hasInt(T, string name) = is(typeof(__traits(getMember, T, name)) == int);
234     static assert(FilterMembersOf!(S, hasInt) == AliasSeq!("i", "i2"));
235 }
236 
237 /**
238     Removes all the attributes from compile-time entity that is given as the only argument
239 */
240 template RemoveAttributes(T...) {
241     import std.traits: functionLinkage, SetFunctionAttributes, FunctionAttribute;
242     alias U = from.bolts.traits.TypesOf!T[0];
243 	alias RemoveAttributes = SetFunctionAttributes!(U, functionLinkage!U, FunctionAttribute.none);
244 }
245 
246 ///
247 unittest {
248     void f0() @safe {}
249     void f1() @system {}
250 
251     static assert(is(RemoveAttributes!f1 == RemoveAttributes!f0));
252 }