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 }