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 }