1 /******************************************************************************
2 
3     Templates to generate a a hash or a string that describes the binary layout
4     of a value type, fully recursing into aggregates.
5 
6     The data layout identifier hash is the 64-bit Fnv1a hash value of a string
7     that is generated from a struct or union by concatenating the offsets
8     and types of each field in order of appearance, recursing into structs,
9     unions and function/delegate parameter lists and using the base type of
10     enums and typedefs.
11 
12     The type identifier of a non-aggregate type is the `.stringof` of that type
13     (or its base if it is a `typedef` or `enum`).
14 
15     Copyright:
16         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
17         All rights reserved.
18 
19     License:
20         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
21         Alternatively, this file may be distributed under the terms of the Tango
22         3-Clause BSD License (see LICENSE_BSD.txt for details).
23 
24  ******************************************************************************/
25 
26 module ocean.io.serialize.TypeId;
27 
28 import ocean.io.digest.Fnv1: StaticFnv1a64, Fnv164Const;
29 import ocean.meta.types.Typedef;
30 import ocean.meta.traits.Basic /* : isTypedef */;
31 
32 /// Usage example
33 unittest
34 {
35     static struct S
36     {
37         mixin(Typedef!(int, `Spam`));
38 
39         uint spam;
40 
41         static struct T
42         {
43             enum Eggs : ushort
44             {
45                 Ham = 7
46             }
47 
48             Eggs eggs;                              // at offset 0
49             char[] str;                             // at offset 8
50         }
51 
52         Spam x;                                     // at offset 0
53         T[][5] y;                                   // at offset 8
54         Spam delegate ( T ) dg;                     // at offset 88
55         T*[float function(Spam, T.Eggs)] a;         // at offset 104
56     }
57 
58     static immutable id = TypeId!(S);
59     static assert(id ==
60                   `struct{` ~
61                     `0LU` ~ `uint` ~
62                     `4LU` ~ `int` ~
63                     `8LU` ~ `struct{` ~
64                       `0LU` ~ `ushort` ~
65                       `8LU` ~ `char[]` ~
66                     `}[][5LU]` ~
67                     `88LU` ~ `intdelegate(` ~
68                       `struct{` ~
69                         `0LU` ~ `ushort` ~
70                         `8LU` ~ `char[]` ~
71                       `}` ~
72                     `)` ~
73                     `104LU` ~ `struct{` ~
74                       `0LU` ~ `ushort` ~
75                       `8LU` ~ `char[]` ~
76                     `}*[floatfunction(intushort)]` ~
77                   `}`);
78 
79     static immutable hash = TypeHash!(S);
80     static assert(hash == 0x3ff282c0d315761b);
81 }
82 
83 
84 /******************************************************************************
85 
86     Evaluates to the type identifier of T, fully recursing into structs, unions
87     and function/delegate parameter lists. T may be or contain any type except
88     a class or interface.
89 
90  ******************************************************************************/
91 
92 template TypeId ( T )
93 {
94     static if (is (T == struct) && !isTypedef!(T))
95     {
96         static immutable TypeId = "struct{" ~ AggregateId!(CheckedBaseType!(T)) ~ "}";
97     }
98     else static if (is (T == union))
99     {
100         static immutable TypeId = "union{" ~ AggregateId!(CheckedBaseType!(T)) ~ "}";
101     }
102     else static if (is (T Base : Base[]))
103     {
104         static if (is (T == Base[]))
105         {
106             static immutable TypeId = TypeId!(Base) ~ "[]";
107         }
108         else
109         {
110             static immutable TypeId = TypeId!(Base) ~ "[" ~ T.length.stringof ~ "]";
111         }
112     }
113     else static if (is (T Base == Base*))
114     {
115         static if (is (Base Args == function) && is (Base R == return))
116         {
117             static immutable TypeId = TypeId!(R) ~ "function(" ~ TupleId!(Args) ~ ")";
118         }
119         else
120         {
121             static immutable TypeId = TypeId!(Base) ~ "*";
122         }
123     }
124     else static if (is (T    Func == delegate) &&
125                     is (Func Args == function) && is (Func R == return))
126     {
127         static immutable TypeId = TypeId!(R) ~ "delegate(" ~ TupleId!(Args) ~ ")";
128     }
129     else static if (is (typeof (T.init.values[0]) V) &&
130                     is (typeof (T.init.keys[0])   K) &&
131                     is (V[K] == T))
132     {
133         static immutable TypeId = TypeId!(V) ~ "[" ~ TypeId!(K) ~ "]";
134     }
135     else
136     {
137         static immutable TypeId = CheckedBaseType!(T).stringof;
138     }
139 }
140 
141 unittest
142 {
143     static struct Sample
144     {
145         int[4] arr;
146         int a;
147         double b;
148         char* c;
149     }
150 
151 
152     static immutable x = TypeId!(Sample);
153     static immutable ExpectedSampleStr = `struct{0LUint[4LU]16LUint24LUdouble32LUchar*}`;
154     static assert(x == ExpectedSampleStr);
155 
156     // This looks like a bug
157     mixin(Typedef!(Sample, `NestedTypedef`));
158     static assert (TypeId!(NestedTypedef) == `Sample`);
159 
160     static struct Bar { NestedTypedef f; }
161     static assert(TypeId!(Bar) == `struct{0LUSample}`);
162 
163     union Foo { char* ptr; ulong val; }
164     static assert(TypeId!(Foo) == `union{0LUchar*0LUulong}`);
165 
166     interface IFoo {}
167     mixin(Typedef!(IFoo, `DasInterface`));
168     static assert (!is(typeof(TypeId!(IFoo))));
169     static assert (!is(typeof(TypeId!(DasInterface))));
170 
171     mixin(Typedef!(Object, `Klass`));
172     static assert (!is(typeof(TypeId!(Object))));
173     static assert (!is(typeof(TypeId!(Klass))));
174 }
175 
176 /******************************************************************************
177 
178     Evaluates to the type hash of T, which is the 64-bit Fnv1a hash of the
179     string that would be generated by TypeId!(T).
180 
181  ******************************************************************************/
182 
183 template TypeHash ( T )
184 {
185     static immutable TypeHash = TypeHash!(Fnv164Const.INIT, T);
186 }
187 
188 unittest
189 {
190     static struct Sample
191     {
192         int[4] arr;
193         int a;
194         double b;
195         char* c;
196     }
197 
198     static immutable hash = TypeHash!(Sample);
199     static immutable ExpectedSampleHash = 0x25E3D303374B7838;
200     static assert(hash == ExpectedSampleHash);
201 
202     // This looks like a bug
203     mixin(Typedef!(Sample, `NestedTypedef`));
204     static assert (TypeHash!(NestedTypedef) == StaticFnv1a64!(`Sample`));
205 
206     static struct Bar { NestedTypedef f; }
207     static assert(TypeHash!(Bar) == 0xB3F1A91424ABC725);
208 
209     union Foo { char* ptr; ulong val; }
210     static assert(TypeHash!(Foo) == 0xC4BD15CE20899C30);
211 
212     interface IFoo {}
213     mixin(Typedef!(IFoo, `DasInterface`));
214     static assert (!is(typeof(TypeHash!(IFoo))));
215     static assert (!is(typeof(TypeHash!(DasInterface))));
216 
217     mixin(Typedef!(Object, `Klass`));
218     static assert (!is(typeof(TypeHash!(Object))));
219     static assert (!is(typeof(TypeHash!(Klass))));
220 }
221 
222 /******************************************************************************
223 
224     Evaluates to the type hash of T, which is the 64-bit Fnv1a hash of the
225     string that would be generated by TypeId!(T), using hash as initial hash
226     value so that TypeHash!(TypeHash!(A), B) evaluates to the 64-bit Fvn1a hash
227     value of TypeId!(A) ~ TypeId!(B).
228 
229  ******************************************************************************/
230 
231 template TypeHash ( ulong hash, T )
232 {
233     static if (is (T == struct) && !isTypedef!(T))
234     {
235         static immutable TypeHash = StaticFnv1a64!(AggregateHash!(StaticFnv1a64!(hash, "struct{"), CheckedBaseType!(T)), "}");
236     }
237     else static if (is (T == union))
238     {
239         static immutable TypeHash = StaticFnv1a64!(AggregateHash!(StaticFnv1a64!(hash, "union{"), CheckedBaseType!(T)), "}");
240     }
241     else static if (is (T Base : Base[]))
242     {
243         static if (is (T == Base[]))
244         {
245             static immutable TypeHash = StaticFnv1a64!(TypeHash!(hash, Base), "[]");
246         }
247         else
248         {
249             static immutable TypeHash = StaticFnv1a64!(TypeHash!(hash, Base), "[" ~ T.length.stringof ~ "]");
250         }
251     }
252     else static if (is (T Base == Base*))
253     {
254         static if (is (Base Args == function) && is (Base R == return))
255         {
256             static immutable TypeHash = StaticFnv1a64!(TupleHash!(StaticFnv1a64!(TypeHash!(hash, R), "function("), Args), ")");
257         }
258         else
259         {
260             static immutable TypeHash = StaticFnv1a64!(TypeHash!(Base), "*");
261         }
262     }
263     else static if (is (T    Func == delegate) &&
264                     is (Func Args == function) && is (Func R == return))
265     {
266         static immutable TypeHash = StaticFnv1a64!(TupleHash!(StaticFnv1a64!(TypeHash!(hash, R), "delegate("), Args), ")");
267     }
268     else static if (is (typeof (T.init.values[0]) V) &&
269                     is (typeof (T.init.keys[0])   K) &&
270                     is (V[K] == T))
271     {
272         static immutable TypeHash = StaticFnv1a64!(TypeHash!(StaticFnv1a64!(TypeHash!(hash, V), "["), K), "]");
273     }
274     else
275     {
276         static immutable TypeHash = StaticFnv1a64!(hash, CheckedBaseType!(T).stringof);
277     }
278 }
279 
280 /******************************************************************************
281 
282     Evaluates to the concatenated type identifiers of the fields of T, starting
283     with the n-th field. T must be a struct or union.
284 
285  ******************************************************************************/
286 
287 template AggregateId ( T, size_t n = 0 )
288 {
289     static if (n < T.tupleof.length)
290     {
291         static immutable AggregateId = T.tupleof[n].offsetof.stringof ~ TypeId!(typeof (T.tupleof[n])) ~ AggregateId!(T, n + 1);
292     }
293     else
294     {
295         static immutable AggregateId = "";
296     }
297 }
298 
299 /******************************************************************************
300 
301     Evaluates to the concatenated type identifiers of the elements of T.
302 
303  ******************************************************************************/
304 
305 template TupleId ( T ... )
306 {
307     static if (T.length)
308     {
309         static immutable TupleId = TypeId!(T[0]) ~ TupleId!(T[1 .. $]);
310     }
311     else
312     {
313         static immutable TupleId = "";
314     }
315 }
316 
317 /******************************************************************************
318 
319     Evaluates to the hash value of the type identifiers of the fields of T,
320     starting with the n-th field, using hash as initial hash value. T must be a
321     struct or union.
322 
323  ******************************************************************************/
324 
325 template AggregateHash ( ulong hash, T, size_t n = 0 )
326 {
327     static if (n < T.tupleof.length)
328     {
329         static immutable AggregateHash = AggregateHash!(TypeHash!(StaticFnv1a64!(hash, T.tupleof[n].offsetof.stringof), typeof (T.tupleof[n])), T, n + 1);
330     }
331     else
332     {
333         static immutable AggregateHash = hash;
334     }
335 }
336 
337 /******************************************************************************
338 
339     Evaluates to the hash value of the concatenated type identifiers of the
340     elements of T, using hash as initial hash value.
341 
342  ******************************************************************************/
343 
344 template TupleHash ( ulong hash, T ... )
345 {
346     static if (T.length)
347     {
348         static immutable TupleHash = TupleHash!(TypeHash!(hash, T[0]), T[1 .. $]);
349     }
350     else
351     {
352         static immutable TupleHash = hash;
353     }
354 }
355 
356 /******************************************************************************
357 
358     Aliases the base type of T, if T is a typedef or enum, or T otherwise.
359     Recurses into further typedefs/enums if required.
360     Veryfies that the aliased type is not a class, pointer, function, delegate
361     or associative array (a reference type other than a dynamic array).
362 
363  ******************************************************************************/
364 
365 template CheckedBaseType ( T )
366 {
367     alias BaseType!(T) CheckedBaseType;
368 
369     static assert (!(is (CheckedBaseType == class) ||
370                      is (CheckedBaseType == interface)), TypeErrorMsg!(T, CheckedBaseType));
371 }
372 
373 /******************************************************************************
374 
375     Aliases the base type of T, if T is a typedef or enum, or T otherwise.
376     Recurses into further typedefs/enums if required.
377 
378  ******************************************************************************/
379 
380 template BaseType ( T )
381 {
382     static if (isTypedef!(T))
383         alias TypedefBaseType!(T) BaseType;
384     else static if (is (T Base == enum))
385     {
386         alias BaseType!(Base) BaseType;
387     }
388     else
389     {
390         alias T BaseType;
391     }
392 }
393 
394 /******************************************************************************
395 
396     Evaluates to an error messsage used by CheckedBaseType.
397 
398  ******************************************************************************/
399 
400 template TypeErrorMsg ( T, Base )
401 {
402     static if (is (T == Base))
403     {
404         static immutable TypeErrorMsg = Base.stringof ~ " is not supported because it is a class or interface";
405     }
406     else
407     {
408         static immutable TypeErrorMsg = T.stringof ~ " is a typedef of " ~ Base.stringof ~ " which is not supported because it is a class or interface";
409     }
410 }