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