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 }