1 /******************************************************************************
2 
3     Struct Converter functions
4 
5     Functions to make converting an instance to a similar but not equal type
6     easier.
7 
8     Copyright:
9         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
10         All rights reserved.
11 
12     License:
13         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
14         Alternatively, this file may be distributed under the terms of the Tango
15         3-Clause BSD License (see LICENSE_BSD.txt for details).
16 
17  ******************************************************************************/
18 
19 module ocean.core.StructConverter;
20 
21 import ocean.meta.types.Qualifiers;
22 import ocean.meta.codegen.Identifier;
23 import ocean.meta.traits.Basic;
24 import ocean.meta.types.Arrays : StripAllArrays;
25 import ocean.core.TypeConvert : toDg;
26 
27 version (unittest) import ocean.core.Test;
28 
29 /***************************************************************************
30 
31     Copies members of the same name from <From> to <To>.
32 
33     Given a variable in <To> called 'example_var', if a static convert function
34     in <To> exists with the name 'convert_example_var', then this function will
35     be called and no automatic conversion will happen for that variable. The
36     function must have one of the following signatures:
37     ---
38     void function ( ref <From>, ref <To>, void[] delegate ( size_t ) )
39     void function ( ref <From>, ref <To> )
40     ---
41     The delegate passed to the first version can be used to allocate temporary
42     buffers that the convert function might need in order to do its converting.
43 
44     If no convert function exists and the types differ, various things happen:
45 
46     * For structs it calls this function again
47     * For dynamic arrays, a temporary array of the same length is created and
48       this function is called for every element of the array
49     * For static arrays the same happens, just without a temporary allocation
50 
51     If the types are the same a simple assignment will be done. The types have
52     to match exactly, implicit conversions are not supported.
53 
54     It is an error if a variable in <To> doesn't exist in <From> and no convert
55     function for it exists,
56 
57     Note: Dynamic arrays of the same type will actually reference the same
58           memory where as arrays of similar types that were converted use memory
59           provided by the requestBuffer delegate.
60 
61     Params:
62         From          = type we're copying from
63         To            = type we're copying to
64         from          = instance we're copying from
65         to            = instance we're copying to
66         requestBuffer = delegate to request temporary buffers used during
67                         conversion.
68 
69 ***************************************************************************/
70 
71 public void structConvert ( From, To ) ( ref From from, out To to,
72     void[] delegate ( size_t ) requestBuffer
73         = ( size_t n ) { return new void[n]; } )
74 {
75     static assert (is(From == struct) && is (To == struct),
76             "structConvert works only on structs, not on " ~
77             From.stringof ~ " / " ~ To.stringof);
78 
79     static if (is(From == To))
80     {
81         to = from;
82     }
83     else
84     {
85         foreach ( to_index, ref to_member; to.tupleof )
86         {
87             enum fieldName = identifier!(to.tupleof[to_index]);
88             enum convFuncName = "convert_" ~ fieldName;
89 
90             static if (hasConvertFunction!(From, convFuncName, To)())
91             {
92                 callBestOverload!(From, To, convFuncName)(from, to, requestBuffer);
93             }
94             else static if (structHasMember!(fieldName, From)())
95             {
96                 auto from_field = getField!(fieldName)(from);
97                 auto to_field = &to.tupleof[to_index];
98 
99                 copyField(from_field, to_field, requestBuffer);
100             }
101             else
102             {
103                 static assert ( false, "Unhandled field: " ~
104                             fieldName ~ " of type " ~
105                             typeof(to_member).stringof);
106             }
107         }
108     }
109 }
110 
111 /*******************************************************************************
112 
113     Helper function for structConvert().
114 
115     Copies a field to another field, doing a conversion if required and
116     possible.
117 
118     Params:
119         From = type of field we copy/convert from
120         To   = type of field we copy/convert to
121         from_field = pointer to the field we want to copy/convert from
122         to_field   = pointer to the field we want to copy/convert to
123         requestBuffer = delegate to request temporary memory for doing
124                         conversions
125 
126 *******************************************************************************/
127 
128 private void copyField ( From, To ) ( From* from_field, To* to_field,
129                                       void[] delegate ( size_t ) requestBuffer )
130 {
131     static if ( is ( typeof(*to_field) : typeof(*from_field) ) )
132     {
133         static if ( isArrayType!(typeof((*to_field))) == ArrayKind.Static )
134         {
135             (*to_field)[] = (*from_field)[];
136         }
137         else
138         {
139             *to_field = *from_field;
140         }
141     }
142     else static if ( is ( typeof((*to_field)) == struct ) &&
143                      is ( typeof(*from_field) == struct ) )
144     {
145         alias structConvert!(typeof(*from_field), typeof((*to_field))) copyMember;
146 
147         copyMember(*from_field, *to_field,
148                    requestBuffer);
149     }
150     else static if (isArrayType!(typeof((*to_field))) == ArrayKind.Static &&
151                     isArrayType!(typeof(*from_field)) == ArrayKind.Static)
152     {
153         alias StripAllArrays!(typeof(*to_field))   ToBaseType;
154         alias StripAllArrays!(typeof(*from_field)) FromBaseType;
155 
156         static if ( is(ToBaseType == struct) &&
157                     is(FromBaseType == struct) )
158         {
159             foreach ( i, ref el; *to_field )
160             {
161                 structConvert!(FromBaseType, ToBaseType)((*from_field)[i],
162                                                        el, requestBuffer);
163             }
164         }
165         else
166         {
167             static assert (1==0, "Unsupported auto-struct-conversion " ~
168                 FromBaseType.stringof ~ " -> " ~ ToBaseType.stringof ~
169                 ". Please provide the convert function " ~ To.stringof ~
170                  "." ~ convertToFunctionName(FieldName!(to_index, To)));
171         }
172     }
173     else static if (isArrayType!(typeof((*to_field))) == ArrayKind.Dynamic &&
174                     isArrayType!(typeof(*from_field)) == ArrayKind.Dynamic)
175     {
176         alias StripAllArrays!(typeof(*to_field))   ToBaseType;
177         alias StripAllArrays!(typeof(*from_field)) FromBaseType;
178 
179         static if ( is(ToBaseType == struct) &&
180                     is(FromBaseType == struct) )
181         {
182             if ( from_field.length > 0 )
183             {
184                 auto buf = requestBuffer(from_field.length * ToBaseType.sizeof);
185 
186                 *to_field = (cast(ToBaseType*)buf)[0 .. from_field.length];
187 
188                 foreach ( i, ref el; *to_field )
189                 {
190                     structConvert!(FromBaseType, ToBaseType)((*from_field)[i],
191                                                           el, requestBuffer);
192                 }
193             }
194             else
195             {
196                 *to_field = null;
197             }
198         }
199         else
200         {
201             static assert (false, "Unsupported auto-struct-conversion " ~
202                 FromBaseType.stringof ~ " -> " ~ ToBaseType.stringof ~
203                 ". Please provide the convert function " ~ To.stringof ~
204                  "." ~ convertToFunctionName(FieldName!(to_index, To)));
205         }
206     }
207     else
208     {
209         // Workaround for error-swallowing DMD1 bug
210         /+
211             module main;
212 
213             size_t foo ( bool bar ) ( int )
214             {
215             }
216 
217             unittest
218             {
219                     foo!(true) ();
220             }
221 
222             Outputs:
223                 main.d(10): Error: template main.foo(bool bar) does not match any function template declaration
224                 main.d(10): Error: template main.foo(bool bar) cannot deduce template function from argument types !(true)()
225 
226             Fixing the error in foo (by adding `return 0;`) changes the output to
227 
228             main.d(10): Error: function main.foo!(true).foo (int _param_0) does not match parameter types ()
229             main.d(10): Error: expected 1 function arguments, not 0
230         +/
231 
232         pragma(msg, "Unhandled field: " ~
233                     FieldName!(to_index, To) ~ " of types " ~ To.stringof ~ "." ~
234                         typeof((*to_field)).stringof ~ " " ~ From.stringof ~ "." ~
235                         typeof(*from_field).stringof);
236 
237         static assert ( false, "Unhandled field: " ~
238                         FieldName!(to_index, To) ~ " of types " ~
239                         typeof((*to_field)).stringof ~ " " ~
240                         typeof(*from_field).stringof);
241     }
242 }
243 
244 /*******************************************************************************
245 
246     Checks whether struct S has a member (variable or method) of the given name
247 
248     Params:
249         name = name to check for
250         S    = struct to check
251 
252     Returns:
253         true if S has queried member, else false
254 
255 *******************************************************************************/
256 
257 private bool structHasMember ( string name, S ) ( )
258 {
259     mixin(`
260         static if (is(typeof(S.` ~ name ~`)))
261         {
262             return true;
263         }
264         else
265         {
266             return false;
267         }`);
268 }
269 
270 /*******************************************************************************
271 
272     Calls the function given in function_name in struct To.
273     The function must have one of the following signatures:
274     ---
275     void delegate ( ref <From>, void[] delegate ( size_t ) )
276     void delegate ( ref <From> )
277     void delegate ( );
278     ---
279 
280     Params:
281         From = type of the struct that will be passed to the function
282         To   = type of the struct that has to have that function
283         function_name = name of the function that To must have
284         from = struct instance that will be passed to the function
285         to   = struct instance that should have said function declared
286         requestBuffer = memory request method that the function can use (it
287                         should not allocate memory itself)
288 
289 *******************************************************************************/
290 
291 private void callBestOverloadOld ( From, To, string function_name )
292            ( ref From from, ref To to, void[] delegate ( size_t ) requestBuffer )
293 {
294      mixin (`
295         void delegate ( ref From, void[] delegate ( size_t ) ) longest_convert;
296         void delegate ( ref From ) long_convert;
297         void delegate ( ) convert;
298 
299         static if ( is ( typeof(longest_convert = &to.`~function_name~`)) )
300         {
301             longest_convert = &to.`~function_name~`;
302 
303             longest_convert(from, requestBuffer);
304         }
305         else static if ( is ( typeof(long_convert = &to.`~function_name~`)) )
306         {
307             long_convert = &to.`~function_name~`;
308 
309             long_convert(from);
310         }
311         else static if ( is ( typeof(convert = &to.`~function_name~`)) )
312         {
313             convert = &to.`~function_name~`;
314 
315             convert();
316         }
317         else
318         {
319             const convFuncTypeString = typeof(&to.`~function_name~`).stringof;
320             static assert ( false,
321               "Function ` ~
322              To.stringof ~ `.` ~ function_name ~
323              ` (" ~ convFuncTypeString ~ ") doesn't `
324            ~ `have any of the accepted types `
325            ~ `'void delegate ( ref "~From.stringof~", void[] delegate ( size_t ) )' or `
326            ~ `'void delegate ( ref "~From.stringof~" )' or `
327            ~ `'void delegate ( )'" );
328         }`);
329 
330 }
331 
332 /*******************************************************************************
333 
334     Checks if a given type T is of the old convert function style
335 
336     Params:
337         From = struct that we convert from
338         T    = function/delegate type that is used
339         t    = pointer to the function/delegate that is used
340 
341     Returns:
342         true if the given function/delegate is of the old convert style
343 
344 *******************************************************************************/
345 
346 private bool isOldOverload ( From, string func_name, To ) ( )
347 {
348     void delegate ( ref From, void[] delegate ( size_t ) ) longest_convert;
349     void delegate ( ref From ) long_convert;
350     void delegate ( ) convert;
351 
352     To to;
353 
354     mixin(`
355     static if ( is ( typeof(longest_convert = &to.` ~ func_name ~ `) ))
356     {
357         return true;
358     }
359     else static if ( is ( typeof(long_convert = &to.` ~ func_name ~ `) ))
360     {
361         return true;
362     }
363     else static if ( is ( typeof(convert = &to.` ~ func_name ~ ` )  ))
364     {
365         return true;
366     }
367     else
368     {
369         return false;
370     }`);
371 }
372 
373 version (unittest)
374 {
375     struct Test ( ubyte Ver )
376     {
377         void old ( ) {}
378 
379         static void should_fail1 ( ) {}
380         void should_fail2 ( ref Test!(1) from, ref Test!(2) to ) {}
381     }
382 
383     unittest
384     {
385         Test!(2) t;
386 
387         static assert(isOldOverload!(Test!(1), "old", Test!(2))());
388         static assert(!isOldOverload!(Test!(1), "should_fail1", Test!(2))());
389         static assert(!isOldOverload!(Test!(1), "should_fail2", Test!(2))());
390     }
391 }
392 
393 /*******************************************************************************
394 
395     Checks if a convert function of the given name exists for the given structs
396 
397     Params:
398         From = struct that we convert from
399         func_name = name of the function we're looking for
400         To = struct we're converting to
401 
402     Returns:
403         true if such a function is found
404 
405 *******************************************************************************/
406 
407 private bool hasConvertFunction ( From, string func_name, To ) ( )
408 {
409     static if (is (typeof(isOldOverload!(From, func_name, To)())))
410         if (isOldOverload!(From, func_name, To)())
411             return true;
412 
413     void function ( ref From, ref To, void[] delegate ( size_t ) ) longest_convert;
414     void function ( ref From, ref To ) long_convert;
415 
416     mixin(`
417     static if ( is ( typeof(longest_convert = &To.` ~ func_name ~ `) ))
418     {
419         return true;
420     }
421     else static if ( is ( typeof(long_convert = &To.` ~ func_name ~ `) ))
422     {
423         return true;
424     }
425     else
426     {
427         return false;
428     }`);
429 }
430 
431 /*******************************************************************************
432 
433     Calls the function given in function_name in struct To.
434     The function must have one of the following signatures:
435     ---
436     void function ( ref <From>, ref <To>, void[] delegate ( size_t ) )
437     void function ( ref <From>, ref <To> )
438     ---
439 
440     Params:
441         From = type of the struct that will be passed to the function
442         To   = type of the struct that has to have that function
443         function_name = name of the function that To must have
444         from = struct instance that will be passed to the function
445         to   = struct instance that should have said function declared
446         requestBuffer = memory request method that the function can use (it
447                         should not allocate memory itself)
448 
449 *******************************************************************************/
450 
451 private void callBestOverload ( From, To, string function_name )
452            ( ref From from, ref To to, void[] delegate ( size_t ) requestBuffer )
453 {
454      mixin (`
455         void function ( ref From, ref To, void[] delegate ( size_t ) ) longest_convert;
456         void function ( ref From, ref To ) long_convert;
457 
458         static if (is (typeof(isOldOverload!(From, function_name, To)())))
459             const is_old = isOldOverload!(From, function_name, To)();
460         else
461             const is_old = false;
462 
463         static if (is_old)
464         {
465             // None matched, try legacy signatures
466             callBestOverloadOld!(From, To, function_name)(from, to, requestBuffer);
467         }
468         else static if ( is ( typeof(longest_convert = &To.`~function_name~`)) )
469         {
470             To.`~function_name~`(from, to, requestBuffer);
471         }
472         else static if ( is ( typeof(long_convert = &To.`~function_name~`)) )
473         {
474             To.`~function_name~`(from, to);
475         }
476         else
477         {
478             const convFuncTypeString = typeof(&to.`~function_name~`).stringof;
479             static assert ( false,
480               "Function ` ~
481              To.stringof ~ `.` ~ function_name ~
482              ` (" ~ convFuncTypeString ~ ") doesn't ` ~
483              `have any of the accepted types ` ~
484              `'void function ( ref "~From.stringof~", ref "~To.stringof~", void[] delegate ( size_t ) )' or ` ~
485              `'void function ( ref "~From.stringof~", ref "~To.stringof~" )'");
486         }`);
487 }
488 
489 /*******************************************************************************
490 
491     aliases to the type of the member <name> in the struct <Struct>
492 
493     Params:
494         name = name of the member you want the type of
495         Struct = struct that <name> is member of
496 
497 *******************************************************************************/
498 
499 private template TypeOf ( string name, Struct )
500 {
501     mixin(`alias typeof(Struct.`~name~`) TypeOf;`);
502 }
503 
504 /*******************************************************************************
505 
506     Returns a pointer to the field <field_name> defined in the struct <Struct>
507 
508     Params:
509         field_name = name of the field in the struct <Struct>
510         Struct     = struct that is expected to have a member called
511                      <field_name>
512 
513     Returns:
514         pointer to the field <field_name> defined in the struct <Struct>
515 
516 *******************************************************************************/
517 
518 private TypeOf!(field_name, Struct)* getField ( string field_name, Struct )
519                                               ( ref Struct s )
520 {
521     mixin(`
522         static if ( is ( typeof(Struct.`~field_name~`) ) )
523         {
524             return &(s.`~field_name~`);
525         }
526         else
527         {
528             return null;
529         }`);
530 }
531 
532 version (unittest)
533 {
534     void[] testAlloc( size_t s )
535     {
536         return new void[s];
537     }
538 }
539 
540 // same struct
541 unittest
542 {
543     struct A
544     {
545         int x;
546     }
547 
548     A a1, a2;
549     a1.x = 42;
550 
551     structConvert(a1, a2, toDg(&testAlloc));
552     test ( a1.x == a2.x, "failure to copy same type instances" );
553 }
554 
555 // same fields, different order
556 unittest
557 {
558     struct A
559     {
560         int a;
561         int b;
562         short c;
563     }
564 
565     struct B
566     {
567         short c;
568         int a;
569         int b;
570     }
571 
572     auto a = A(1,2,3);
573     B b;
574 
575     structConvert(a, b, toDg(&testAlloc));
576 
577     test ( a.a == b.a, "a != a" );
578     test ( a.b == b.b, "b != b" );
579     test ( a.c == b.c, "c != c" );
580 }
581 
582 // no conversion method -> failure
583 unittest
584 {
585     struct A
586     {
587         int x;
588     }
589 
590     struct B
591     {
592         int x;
593         int y;
594     }
595 
596     A a; B b;
597 
598     static assert (!is(typeof(structConvert(a, b, toDg(&testAlloc)))));
599 }
600 
601 // multiple conversions at once
602 unittest
603 {
604     static struct A
605     {
606         int a;
607         int b;
608         int[][] i;
609         int c;
610         char[] the;
611 
612         struct C
613         {
614             int b;
615         }
616 
617         C srt;
618     }
619 
620     static struct B
621     {
622         int c;
623         short b;
624         int a;
625         short d;
626         char[] the;
627         int[][] i;
628 
629         struct C
630         {
631             int b;
632             int c;
633             int ff;
634 
635             // verify different conversion signatures...
636             static void convert_c ( ref A.C, ref C ) {}
637             static void convert_ff ( ref A.C, ref C, void[] delegate ( size_t ) ) {}
638         }
639 
640         C srt;
641 
642         static void convert_b ( ref A structa, ref B dst )
643         {
644             dst.b = cast(short) structa.b;
645         }
646 
647         static void convert_d ( ref A structa, ref B dst )
648         {
649             dst.d = cast(short) structa.a;
650         }
651     }
652 
653     auto a = A(1,2, [[1,2], [45,234], [53],[3]],3, "THE TEH THE RTANEIARTEN".dup);
654     B b_loaded;
655 
656     structConvert!(A, B)(a, b_loaded, toDg(&testAlloc));
657 
658     test ( b_loaded.a == a.a, "Conversion failure" );
659     test ( b_loaded.b == a.b, "Conversion failure" );
660     test ( b_loaded.c == a.c, "Conversion failure" );
661     test ( b_loaded.d == a.a, "Conversion failure" );
662     test ( b_loaded.the[] == a.the[], "Conversion failure" );
663     test ( b_loaded.the.ptr == a.the.ptr, "Conversion failure" );
664     test ( b_loaded.i.ptr == a.i.ptr, "Conversion failure" );
665     test ( b_loaded.i[0][] == a.i[0][], "Nested array mismatch" );
666     test ( b_loaded.i[1][] == a.i[1][], "Nested array mismatch" );
667     test ( b_loaded.i[2][] == a.i[2][], "Nested array mismatch" );
668     test ( b_loaded.i[3][] == a.i[3][], "Nested array mismatch" );
669 }
670 
671 // multiple conversion overloads
672 
673 version (unittest)
674 {
675     // can't place those structs inside unit test block because of
676     // forward reference issue
677 
678     struct A
679     {
680         int x;
681 
682         static void convert_x ( ref B src, ref A dst )
683         {
684             dst.x = cast(int) src.x;
685         }
686     }
687 
688     struct B
689     {
690         uint x;
691 
692         static void convert_x ( ref A src, ref B dst )
693         {
694             dst.x = cast(uint) src.x;
695         }
696 
697         static void convert_x ( ref C src, ref B dst )
698         {
699             dst.x = cast(uint) src.x;
700         }
701     }
702 
703     struct C
704     {
705         double x;
706 
707         static void convert_x ( ref B src, ref C dst )
708         {
709             dst.x = src.x;
710         }
711     }
712 }
713 
714 unittest
715 {
716     A a; B b; C c;
717 
718     a.x = 42;
719     structConvert(a, b, toDg(&testAlloc));
720     test(b.x == 42);
721 
722     b.x = 43;
723     structConvert(b, a, toDg(&testAlloc));
724     test(a.x == 43);
725 
726     structConvert(b, c, toDg(&testAlloc));
727     test(c.x == 43);
728 
729     c.x = 44;
730     structConvert(c, b, toDg(&testAlloc));
731     test(b.x == 44);
732 }