1 /*******************************************************************************
2
3 Generic utility allowing to recursively visit an arbitrary type and reduce
4 its definition to some compile-time value. It is intended to be used as an
5 implementation cornerstone for complex type traits to avoid having to
6 rewrite the recursive type reflection boilerplate wherever it's needed.
7
8 NB: because this module is often used as purely compile-time dependency it
9 used built-in asserts instead of `ocean.core.Test` to reduce amount of
10 cyclic imports. `ocean.meta` modules in general are not supposed to
11 import anything outside of `ocean.meta`.
12
13 Copyright:
14 Copyright (c) 2017 dunnhumby Germany GmbH.
15 All rights reserved.
16
17 License:
18 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
19 Alternatively, this file may be distributed under the terms of the Tango
20 3-Clause BSD License (see LICENSE_BSD.txt for details).
21
22 *******************************************************************************/
23
24 module ocean.meta.types.ReduceType;
25
26 import ocean.meta.traits.Basic;
27 import ocean.meta.types.Arrays;
28 import ocean.meta.types.Typedef;
29
30 /*******************************************************************************
31
32 Reduces definition of a type to single value using provided Reducer.
33
34 If Reducer does not specify `seed` and `accumulate` explicitly, defaults
35 to `false` seed and `a || b` accumulator. `Result` must alias bool in such
36 case.
37
38 NB: currently `ReduceType` does not work with types which have recursive
39 definition, for example `struct S { S* ptr; }`, crashing compiler at CTFE
40 stage. This will be improved in the future.
41
42 Params:
43 T = arbitrary type to visit/reduce
44 Reducer = struct type conforming `assertValidReducer` requirements used
45 to do actual calculations/checks on the type
46
47 Example:
48
49 Example reducer that counts amount of integer typed entities accessible
50 though the base types (i.e. fields and referenced types):
51
52 ---
53 static struct ExampleReducer
54 {
55 alias int Result;
56 const seed = 0;
57
58 Result accumulate ( Result accum, Result next )
59 {
60 return accum + next;
61 }
62
63 Result visit ( T ) ( )
64 {
65 if (isIntegerType!(T))
66 return 1;
67 }
68 }
69
70 static assert (ReduceType!(int, ExampleReducer) == 1);
71 static assert (ReduceType!(int[int], ExampleReducer) == 2);
72 ---
73
74 Returns:
75 value of type `Reducer.Result` calculated by calling `Reducer.visit` on
76 each nested type of `T` recursively and reducing via
77 `Reducer.accumulate`.
78
79 *******************************************************************************/
80
81 public template ReduceType ( T, Reducer )
82 {
83 static immutable ReduceType = ReduceTypeImpl!(Reducer).init.reduce!(T)();
84 }
85
86 /*******************************************************************************
87
88 Verifies that `Reducer` is an aggregate type that conforms type reducer
89 requirements and issues static assertion otherwise.
90
91 Params:
92 Reducer = aggregate type to check
93
94 *******************************************************************************/
95
96 public template assertValidReducer ( Reducer )
97 {
98 static assert (
99 is(Reducer.Result),
100 Reducer.stringof ~ " must define `Result` type alias"
101 );
102 static assert (
103 is(typeof(Reducer.visit!(int))),
104 Reducer.stringof ~ " must define `visit` templated function "
105 ~ "that takes a type as template argument and returns single "
106 ~ "value of Result type"
107 );
108
109 alias void assertValidReducer;
110 }
111
112 /*******************************************************************************
113
114 Implementation for `ReduceType` handling type recursion
115
116 Params:
117 Reducer = see `ReduceType` param documentation
118
119 *******************************************************************************/
120
121 private struct ReduceTypeImpl ( Reducer )
122 {
123 // Give better error messages for wrong `Reducer` definitions
124 alias assertValidReducer!(Reducer) check;
125
126 // Create instance of reducer struct in case implementation may need
127 // internal state
128 Reducer reducer;
129
130 // Helper to calculate new value and update accumulator in one step
131 private void accumulate ( T ) ( ref T accum, T next )
132 {
133 static if (is(typeof(Reducer.accumulate)))
134 {
135 accum = reducer.accumulate(accum, next);
136 }
137 else
138 {
139 static assert (
140 is(T == bool),
141 "Must specify custom accumulator method for non-bool results"
142 ~ "of ReduceType"
143 );
144 accum = accum || next;
145 }
146 }
147
148 // Main recursive visiting implementation
149 private Reducer.Result reduce ( T ) ( )
150 {
151 static if (is(typeof(reducer.seed)))
152 auto result = reducer.seed;
153 else
154 {
155 static assert (
156 is(Reducer.Result == bool),
157 "Default seed/accumulator are only supported for bool"
158 ~ " ReduceType results"
159 );
160 auto result = false;
161 }
162
163 accumulate(result, reducer.visit!(T)());
164
165 static if (isPrimitiveType!(T))
166 {
167 // do nothing, already processed
168 }
169 else static if (isTypedef!(T))
170 {
171 accumulate(result, reducer.visit!(TypedefBaseType!(T)));
172 }
173 else static if (isAggregateType!(T))
174 {
175 foreach (TElem; typeof(T.init.tupleof))
176 accumulate(result, reduce!(TElem)());
177 }
178 else static if (isArrayType!(T))
179 {
180 static if (
181 isArrayType!(T) == ArrayKind.Static
182 || isArrayType!(T) == ArrayKind.Dynamic)
183 {
184 accumulate(result, reduce!(ElementTypeOf!(T))());
185 }
186 else
187 {
188 // associative
189 accumulate(result, reduce!(ElementTypeOf!(T).Key));
190 accumulate(result, reduce!(ElementTypeOf!(T).Value));
191 }
192 }
193 else static if (isFunctionType!(T))
194 {
195 // ignored for now, visiting argument/return types may be
196 // considered eventually
197 }
198 else static if (isPointerType!(T))
199 {
200 // Dereferencing in typeof(*(void*).init) causes the compilation
201 // error `expression *null is void and has no value`, indicating
202 // that `void*` cannot be implicitly reduced.
203 // So, it must be handled explicitly.
204 // FIXME: https://github.com/sociomantic-tsunami/ocean/issues/704
205 // The above issue prevents using `static if(Unqual!(T) == void*)`
206 // here.
207 static if (is(T == void*) || is(T == const void*)
208 || is(T == immutable void*))
209 {
210 accumulate(result, reduce!(void)());
211 }
212 else
213 {
214 accumulate(result, reduce!(typeof(*T.init))());
215 }
216 }
217 else static if (is(T U == enum))
218 {
219 accumulate(result, reduce!(U)());
220 }
221 else
222 {
223 static assert (false,
224 "Unexpected type kind during recursive iteration: " ~ T.stringof);
225 }
226
227 return result;
228 }
229 }
230
231
232 version (unittest)
233 {
234 import ocean.meta.types.Qualifiers;
235
236 private struct TestAggregate
237 {
238 int x;
239 float[] y;
240 void* z;
241 }
242
243 private struct CheckPrimitiveReducer
244 {
245 alias bool Result;
246
247 Result visit ( T ) ( )
248 {
249 return isPrimitiveType!(T);
250 }
251 }
252 }
253
254 // Sanity test of instantiation of `ReduceType` with primitive types
255 unittest
256 {
257 assert(ReduceType!(int, CheckPrimitiveReducer));
258 assert(ReduceType!(void, CheckPrimitiveReducer));
259 }
260
261 // Sanity test of instantiation of `ReduceType` with aggregate types
262 unittest
263 {
264 assert(!ReduceType!(CheckPrimitiveReducer, CheckPrimitiveReducer));
265 assert(ReduceType!(const TestAggregate, CheckPrimitiveReducer));
266 }
267
268 // Sanity test of instantiation of `ReduceType` with static array types
269 unittest
270 {
271 assert(ReduceType!(float[9], CheckPrimitiveReducer));
272 assert(ReduceType!(void[9], CheckPrimitiveReducer));
273 }
274
275 // Sanity test of instantiation of `ReduceType` with dynamic array types
276 unittest
277 {
278 assert(ReduceType!(char[], CheckPrimitiveReducer));
279 assert(ReduceType!(void[], CheckPrimitiveReducer));
280 }
281
282 // Sanity test of instantiation of `ReduceType` with function type
283 unittest
284 {
285 auto test_dg = delegate ( ) { return 0; };
286 assert(!ReduceType!(typeof(test_dg), CheckPrimitiveReducer));
287
288 auto test_fn = function ( ) { return 0; };
289 assert(!ReduceType!(typeof(test_fn), CheckPrimitiveReducer));
290 }
291
292 // Sanity test of instantiation of `ReduceType` with enums
293 unittest
294 {
295 enum TestEnum : int { ZERO, ONE, TWO }
296 assert(ReduceType!(TestEnum, CheckPrimitiveReducer));
297 }
298
299 // Sanity test of instantiation of `ReduceType` with pointer types
300 unittest
301 {
302 assert(ReduceType!(int*, CheckPrimitiveReducer));
303 assert(ReduceType!(void*, CheckPrimitiveReducer));
304 }