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 }