1 module boilerplate.constructor; 2 3 import std.algorithm : canFind, map; 4 import std.meta : ApplyLeft, allSatisfy; 5 import std.range : array; 6 import std.traits : hasElaborateDestructor, isInstanceOf, isNested; 7 import std.typecons : Tuple; 8 9 version(unittest) 10 { 11 import unit_threaded.should; 12 } 13 14 /++ 15 GenerateThis is a mixin string that automatically generates a this() function, customizable with UDA. 16 +/ 17 public enum string GenerateThis = ` 18 import boilerplate.constructor: GenerateThisTemplate; 19 import std.string : replace; 20 mixin GenerateThisTemplate; 21 mixin(typeof(this).generateThisImpl()); 22 `; 23 24 /// 25 @("creates a constructor") 26 unittest 27 { 28 class Class 29 { 30 int field; 31 32 mixin(GenerateThis); 33 } 34 35 auto obj = new Class(5); 36 37 obj.field.shouldEqual(5); 38 } 39 40 /// 41 @("calls the super constructor if it exists") 42 unittest 43 { 44 class Class 45 { 46 int field; 47 48 mixin(GenerateThis); 49 } 50 51 class Child : Class 52 { 53 int field2; 54 55 mixin(GenerateThis); 56 } 57 58 auto obj = new Child(5, 8); 59 60 obj.field.shouldEqual(5); 61 obj.field2.shouldEqual(8); 62 } 63 64 /// 65 @("separates fields from methods") 66 unittest 67 { 68 class Class 69 { 70 int field; 71 72 void method() { } 73 74 mixin(GenerateThis); 75 } 76 77 auto obj = new Class(5); 78 79 obj.field.shouldEqual(5); 80 } 81 82 /// 83 @("dups arrays") 84 unittest 85 { 86 class Class 87 { 88 int[] array; 89 90 mixin(GenerateThis); 91 } 92 93 auto array = [2, 3, 4]; 94 auto obj = new Class(array); 95 96 array[0] = 1; 97 obj.array[0].shouldEqual(2); 98 } 99 100 /// 101 @("dups arrays hidden behind Nullable") 102 unittest 103 { 104 import std.typecons : Nullable, nullable; 105 106 class Class 107 { 108 Nullable!(int[]) array; 109 110 mixin(GenerateThis); 111 } 112 113 auto array = [2, 3, 4]; 114 auto obj = new Class(array.nullable); 115 116 array[0] = 1; 117 obj.array.get[0].shouldEqual(2); 118 119 obj = new Class(Nullable!(int[]).init); 120 obj.array.isNull.shouldBeTrue; 121 } 122 123 /// 124 @("uses default value for default constructor parameter") 125 unittest 126 { 127 class Class 128 { 129 @(This.Default!5) 130 int value = 5; 131 132 mixin(GenerateThis); 133 } 134 135 auto obj1 = new Class(); 136 137 obj1.value.shouldEqual(5); 138 139 auto obj2 = new Class(6); 140 141 obj2.value.shouldEqual(6); 142 } 143 144 /// 145 @("creates no constructor for an empty struct") 146 unittest 147 { 148 struct Struct 149 { 150 mixin(GenerateThis); 151 } 152 153 auto strct = Struct(); 154 } 155 156 /// 157 @("properly generates new default values on each call") 158 unittest 159 { 160 import std.conv : to; 161 162 class Class 163 { 164 @(This.Default!(() => new Object)) 165 Object obj; 166 167 mixin(GenerateThis); 168 } 169 170 auto obj1 = new Class(); 171 auto obj2 = new Class(); 172 173 (cast(void*) obj1.obj).shouldNotEqual(cast(void*) obj2.obj); 174 } 175 176 /// 177 @("establishes the parent-child parameter order: parent explicit, child explicit, child implicit, parent implicit.") 178 unittest 179 { 180 class Parent 181 { 182 int field1; 183 184 @(This.Default!2) 185 int field2 = 2; 186 187 mixin(GenerateThis); 188 } 189 190 class Child : Parent 191 { 192 int field3; 193 194 @(This.Default!4) 195 int field4 = 4; 196 197 mixin(GenerateThis); 198 } 199 200 auto obj = new Child(1, 2, 3, 4); 201 202 obj.field1.shouldEqual(1); 203 obj.field3.shouldEqual(2); 204 obj.field4.shouldEqual(3); 205 obj.field2.shouldEqual(4); 206 } 207 208 /// 209 @("disregards static fields") 210 unittest 211 { 212 class Class 213 { 214 static int field1; 215 int field2; 216 217 mixin(GenerateThis); 218 } 219 220 auto obj = new Class(5); 221 222 obj.field1.shouldEqual(0); 223 obj.field2.shouldEqual(5); 224 } 225 226 /// 227 @("can initialize with immutable arrays") 228 unittest 229 { 230 class Class 231 { 232 immutable(Object)[] array; 233 234 mixin(GenerateThis); 235 } 236 } 237 238 /// 239 @("can define scope for constructor") 240 unittest 241 { 242 @(This.Private) 243 class PrivateClass 244 { 245 mixin(GenerateThis); 246 } 247 248 @(This.Protected) 249 class ProtectedClass 250 { 251 mixin(GenerateThis); 252 } 253 254 @(This.Package) 255 class PackageClass 256 { 257 mixin(GenerateThis); 258 } 259 260 @(This.Package("boilerplate")) 261 class SubPackageClass 262 { 263 mixin(GenerateThis); 264 } 265 266 class PublicClass 267 { 268 mixin(GenerateThis); 269 } 270 271 static assert(__traits(getProtection, PrivateClass.__ctor) == "private"); 272 static assert(__traits(getProtection, ProtectedClass.__ctor) == "protected"); 273 static assert(__traits(getProtection, PackageClass.__ctor) == "package"); 274 // getProtection does not return the package name of a package() attribute 275 // static assert(__traits(getProtection, SubPackageClass.__ctor) == `package(boilerplate)`); 276 static assert(__traits(getProtection, PublicClass.__ctor) == "public"); 277 } 278 279 /// 280 @("will assign the same scope to Builder") 281 unittest 282 { 283 @(This.Private) 284 class PrivateClass 285 { 286 mixin(GenerateThis); 287 } 288 289 @(This.Protected) 290 class ProtectedClass 291 { 292 mixin(GenerateThis); 293 } 294 295 @(This.Package) 296 class PackageClass 297 { 298 mixin(GenerateThis); 299 } 300 301 @(This.Package("boilerplate")) 302 class SubPackageClass 303 { 304 mixin(GenerateThis); 305 } 306 307 class PublicClass 308 { 309 mixin(GenerateThis); 310 } 311 312 static assert(__traits(getProtection, PrivateClass.Builder) == "private"); 313 static assert(__traits(getProtection, ProtectedClass.Builder) == "protected"); 314 static assert(__traits(getProtection, PackageClass.Builder) == "package"); 315 static assert(__traits(getProtection, PublicClass.Builder) == "public"); 316 } 317 318 /// 319 @("can use default tag with new") 320 unittest 321 { 322 class Class 323 { 324 @(This.Default!(() => new Object)) 325 Object foo; 326 327 mixin(GenerateThis); 328 } 329 330 ((new Class).foo !is null).shouldBeTrue; 331 } 332 333 /// 334 @("empty default tag means T()") 335 unittest 336 { 337 class Class 338 { 339 @(This.Default) 340 string s; 341 342 @(This.Default) 343 int i; 344 345 mixin(GenerateThis); 346 } 347 348 (new Class()).i.shouldEqual(0); 349 (new Class()).s.shouldEqual(null); 350 } 351 352 /// 353 @("can exclude fields from constructor") 354 unittest 355 { 356 class Class 357 { 358 @(This.Exclude) 359 int i = 5; 360 361 mixin(GenerateThis); 362 } 363 364 (new Class).i.shouldEqual(5); 365 } 366 367 /// 368 @("marks duppy parameters as const when this does not prevent dupping") 369 unittest 370 { 371 372 struct Struct 373 { 374 } 375 376 class Class 377 { 378 Struct[] values_; 379 380 mixin(GenerateThis); 381 } 382 383 const Struct[] constValues; 384 auto obj = new Class(constValues); 385 } 386 387 /// 388 @("does not include property functions in constructor list") 389 unittest 390 { 391 class Class 392 { 393 int a; 394 395 @property int foo() const 396 { 397 return 0; 398 } 399 400 mixin(GenerateThis); 401 } 402 403 static assert(__traits(compiles, new Class(0))); 404 static assert(!__traits(compiles, new Class(0, 0))); 405 } 406 407 @("declares @nogc on non-dupping constructors") 408 @nogc unittest 409 { 410 struct Struct 411 { 412 int a; 413 414 mixin(GenerateThis); 415 } 416 417 auto str = Struct(5); 418 } 419 420 /// 421 @("can initialize fields using init value") 422 unittest 423 { 424 class Class 425 { 426 @(This.Init!5) 427 int field1; 428 429 @(This.Init!(() => 8)) 430 int field2; 431 432 mixin(GenerateThis); 433 } 434 435 auto obj = new Class; 436 437 obj.field1.shouldEqual(5); 438 obj.field2.shouldEqual(8); 439 } 440 441 /// 442 @("can initialize fields using init value, with lambda that accesses previous value") 443 unittest 444 { 445 class Class 446 { 447 int field1; 448 449 @(This.Init!(self => self.field1 + 5)) 450 int field2; 451 452 mixin(GenerateThis); 453 } 454 455 auto obj = new Class(5); 456 457 obj.field1.shouldEqual(5); 458 obj.field2.shouldEqual(10); 459 } 460 461 /// 462 @("can initialize fields with allocated types") 463 unittest 464 { 465 class Class1 466 { 467 @(This.Init!(self => new Object)) 468 Object object; 469 470 mixin(GenerateThis); 471 } 472 473 class Class2 474 { 475 @(This.Init!(() => new Object)) 476 Object object; 477 478 mixin(GenerateThis); 479 } 480 481 class Class3 : Class2 482 { 483 mixin(GenerateThis); 484 } 485 } 486 487 /// 488 @("generates Builder class that gathers constructor parameters, then calls constructor with them") 489 unittest 490 { 491 static class Class 492 { 493 int field1; 494 int field2; 495 int field3; 496 497 mixin(GenerateThis); 498 } 499 500 auto obj = { 501 with (Class.Builder()) 502 { 503 field1 = 1; 504 field2 = 2; 505 field3 = 3; 506 return value; 507 } 508 }(); 509 510 with (obj) 511 { 512 field1.shouldEqual(1); 513 field2.shouldEqual(2); 514 field3.shouldEqual(3); 515 } 516 } 517 518 /// 519 @("builder field order doesn't matter") 520 unittest 521 { 522 static class Class 523 { 524 int field1; 525 int field2; 526 int field3; 527 528 mixin(GenerateThis); 529 } 530 531 auto obj = { 532 with (Class.Builder()) 533 { 534 field3 = 1; 535 field1 = 2; 536 field2 = 3; 537 return value; 538 } 539 }(); 540 541 with (obj) 542 { 543 field1.shouldEqual(2); 544 field2.shouldEqual(3); 545 field3.shouldEqual(1); 546 } 547 } 548 549 /// 550 @("default fields can be left out when assigning builder") 551 unittest 552 { 553 static class Class 554 { 555 int field1; 556 @(This.Default!5) 557 int field2; 558 int field3; 559 560 mixin(GenerateThis); 561 } 562 563 // constructor is this(field1, field3, field2 = 5) 564 auto obj = { 565 with (Class.Builder()) 566 { 567 field1 = 1; 568 field3 = 3; 569 return value; 570 } 571 }(); 572 573 with (obj) 574 { 575 field1.shouldEqual(1); 576 field2.shouldEqual(5); 577 field3.shouldEqual(3); 578 } 579 } 580 581 /// 582 @("supports Builder in structs") 583 unittest 584 { 585 struct Struct 586 { 587 int field1; 588 int field2; 589 int field3; 590 591 mixin(GenerateThis); 592 } 593 594 auto value = { 595 with (Struct.Builder()) 596 { 597 field1 = 1; 598 field3 = 3; 599 field2 = 5; 600 return value; 601 } 602 }(); 603 604 static assert(is(typeof(value) == Struct)); 605 606 with (value) 607 { 608 field1.shouldEqual(1); 609 field2.shouldEqual(5); 610 field3.shouldEqual(3); 611 } 612 } 613 614 /// 615 @("builder strips trailing underlines") 616 unittest 617 { 618 struct Struct 619 { 620 private int a_; 621 622 mixin(GenerateThis); 623 } 624 625 auto builder = Struct.Builder(); 626 627 builder.a = 1; 628 629 auto value = builder.value; 630 631 value.shouldEqual(Struct(1)); 632 } 633 634 /// 635 @("builder supports nested initialization") 636 unittest 637 { 638 struct Struct1 639 { 640 int a; 641 int b; 642 643 mixin(GenerateThis); 644 } 645 646 struct Struct2 647 { 648 int c; 649 Struct1 struct1; 650 int d; 651 652 mixin(GenerateThis); 653 } 654 655 auto builder = Struct2.Builder(); 656 657 builder.struct1.a = 1; 658 builder.struct1.b = 2; 659 builder.c = 3; 660 builder.d = 4; 661 662 auto value = builder.value; 663 664 static assert(is(typeof(value) == Struct2)); 665 666 with (value) 667 { 668 struct1.a.shouldEqual(1); 669 struct1.b.shouldEqual(2); 670 c.shouldEqual(3); 671 d.shouldEqual(4); 672 } 673 } 674 675 /// 676 @("builder supports defaults for nested values") 677 unittest 678 { 679 struct Struct1 680 { 681 int a; 682 int b; 683 684 mixin(GenerateThis); 685 } 686 687 struct Struct2 688 { 689 int c; 690 @(This.Default!(Struct1(3, 4))) 691 Struct1 struct1; 692 int d; 693 694 mixin(GenerateThis); 695 } 696 697 auto builder = Struct2.Builder(); 698 699 builder.c = 1; 700 builder.d = 2; 701 702 builder.value.shouldEqual(Struct2(1, 2, Struct1(3, 4))); 703 } 704 705 /// 706 @("builder supports direct value assignment for nested values") 707 unittest 708 { 709 struct Struct1 710 { 711 int a; 712 int b; 713 714 mixin(GenerateThis); 715 } 716 717 struct Struct2 718 { 719 int c; 720 Struct1 struct1; 721 int d; 722 723 mixin(GenerateThis); 724 } 725 726 auto builder = Struct2.Builder(); 727 728 builder.struct1 = Struct1(2, 3); 729 builder.c = 1; 730 builder.d = 4; 731 732 builder.value.shouldEqual(Struct2(1, Struct1(2, 3), 4)); 733 } 734 735 /// 736 @("builder supports const args") 737 unittest 738 { 739 struct Struct 740 { 741 const int a; 742 743 mixin(GenerateThis); 744 } 745 746 with (Struct.Builder()) 747 { 748 a = 5; 749 750 value.shouldEqual(Struct(5)); 751 } 752 } 753 754 /// 755 @("builder supports fields with destructor") 756 unittest 757 { 758 static struct Struct1 759 { 760 ~this() pure @safe @nogc nothrow { } 761 } 762 763 struct Struct2 764 { 765 Struct1 struct1; 766 767 mixin(GenerateThis); 768 } 769 770 with (Struct2.Builder()) 771 { 772 struct1 = Struct1(); 773 774 value.shouldEqual(Struct2(Struct1())); 775 } 776 } 777 778 /// 779 @("builder supports direct assignment to Nullables") 780 unittest 781 { 782 import std.typecons : Nullable, nullable; 783 784 struct Struct 785 { 786 const Nullable!int a; 787 788 mixin(GenerateThis); 789 } 790 791 with (Struct.Builder()) 792 { 793 a = 5; 794 795 value.shouldEqual(Struct(5.nullable)); 796 } 797 } 798 799 /// 800 @("builder supports reconstruction from value") 801 unittest 802 { 803 import std.typecons : Nullable, nullable; 804 805 struct Struct 806 { 807 private int a_; 808 809 int[] b; 810 811 mixin(GenerateThis); 812 } 813 814 const originalValue = Struct(2, [3]); 815 816 with (originalValue.BuilderFrom()) 817 { 818 a = 5; 819 820 value.shouldEqual(Struct(5, [3])); 821 } 822 } 823 824 /// 825 @("builder supports struct that already contains a value field") 826 unittest 827 { 828 import std.typecons : Nullable, nullable; 829 830 struct Struct 831 { 832 private int value_; 833 834 mixin(GenerateThis); 835 } 836 837 with (Struct.Builder()) 838 { 839 value = 5; 840 841 builderValue.shouldEqual(Struct(5)); 842 } 843 } 844 845 import std.string : format; 846 847 enum GetSuperTypeAsString_(string member) = format!`typeof(super).ConstructorInfo.FieldInfo.%s.Type`(member); 848 849 enum GetMemberTypeAsString_(string member) = format!`typeof(this.%s)`(member); 850 851 enum SuperDefault_(string member) = format!`typeof(super).ConstructorInfo.FieldInfo.%s.fieldDefault`(member); 852 853 enum MemberDefault_(string member) = 854 format!`getUDADefaultOrNothing!(typeof(this.%s), __traits(getAttributes, this.%s))`(member, member); 855 856 enum SuperUseDefault_(string member) 857 = format!(`typeof(super).ConstructorInfo.FieldInfo.%s.useDefault`)(member); 858 859 enum MemberUseDefault_(string member) 860 = format!(`udaIndex!(This.Default, __traits(getAttributes, this.%s)) != -1`)(member); 861 862 enum SuperAttributes_(string member) 863 = format!(`typeof(super).ConstructorInfo.FieldInfo.%s.attributes`)(member); 864 865 enum MemberAttributes_(string member) 866 = format!(`__traits(getAttributes, this.%s)`)(member); 867 868 mixin template GenerateThisTemplate() 869 { 870 private static generateThisImpl() 871 { 872 if (!__ctfe) 873 { 874 return null; 875 } 876 877 import boilerplate.constructor : 878 GetMemberTypeAsString_, GetSuperTypeAsString_, 879 MemberDefault_, SuperDefault_, 880 MemberUseDefault_, SuperUseDefault_, 881 MemberAttributes_, SuperAttributes_, 882 This; 883 import boilerplate.util : GenNormalMemberTuple, bucketSort, needToDup, 884 reorder, udaIndex, removeTrailingUnderline; 885 import std.algorithm : all, canFind, filter, map; 886 import std.meta : Alias, aliasSeqOf, staticMap; 887 import std.range : array, drop, iota, zip; 888 import std.string : endsWith, format, join; 889 import std.typecons : Nullable; 890 891 mixin GenNormalMemberTuple; 892 893 string result = null; 894 895 string visibility = "public"; 896 897 foreach (uda; __traits(getAttributes, typeof(this))) 898 { 899 static if (is(typeof(uda) == ThisEnum)) 900 { 901 static if (uda == This.Protected) 902 { 903 visibility = "protected"; 904 } 905 static if (uda == This.Private) 906 { 907 visibility = "private"; 908 } 909 } 910 else static if (is(uda == This.Package)) 911 { 912 visibility = "package"; 913 } 914 else static if (is(typeof(uda) == This.Package)) 915 { 916 visibility = "package(" ~ uda.packageMask ~ ")"; 917 } 918 } 919 920 string[] constructorAttributes = ["pure", "nothrow", "@safe", "@nogc"]; 921 922 static if (is(typeof(typeof(super).ConstructorInfo))) 923 { 924 enum argsPassedToSuper = typeof(super).ConstructorInfo.fields.length; 925 enum members = typeof(super).ConstructorInfo.fields ~ [NormalMemberTuple]; 926 enum string[] CombinedArray(alias SuperPred, alias MemberPred) = ([ 927 staticMap!(SuperPred, aliasSeqOf!(typeof(super).ConstructorInfo.fields)), 928 staticMap!(MemberPred, NormalMemberTuple) 929 ]); 930 constructorAttributes = typeof(super).GeneratedConstructorAttributes_; 931 } 932 else 933 { 934 enum argsPassedToSuper = 0; 935 static if (NormalMemberTuple.length > 0) 936 { 937 enum members = [NormalMemberTuple]; 938 enum string[] CombinedArray(alias SuperPred, alias MemberPred) = ([ 939 staticMap!(MemberPred, NormalMemberTuple) 940 ]); 941 } 942 else 943 { 944 enum string[] members = null; 945 enum string[] CombinedArray(alias SuperPred, alias MemberPred) = null; 946 } 947 } 948 949 enum string[] useDefaults = CombinedArray!(SuperUseDefault_, MemberUseDefault_); 950 enum string[] memberTypes = CombinedArray!(GetSuperTypeAsString_, GetMemberTypeAsString_); 951 enum string[] defaults = CombinedArray!(SuperDefault_, MemberDefault_); 952 enum string[] attributes = CombinedArray!(SuperAttributes_, MemberAttributes_); 953 954 string[] fields; 955 string[] args; 956 string[] argexprs; 957 string[] defaultAssignments; 958 bool[] fieldUseDefault; 959 string[] fieldDefault; 960 string[] fieldAttributes; 961 string[] types; 962 string[] directInitFields; 963 int[] directInitIndex; 964 bool[] directInitUseSelf; 965 966 foreach (i; aliasSeqOf!(members.length.iota)) 967 { 968 enum member = members[i]; 969 970 mixin(`alias Type = ` ~ memberTypes[i] ~ `;`); 971 mixin(`enum bool useDefault = ` ~ useDefaults[i] ~ `;`); 972 973 bool includeMember = false; 974 975 enum isNullable = is(Type: Nullable!Arg, Arg); 976 977 static if (!isNullable) 978 { 979 bool dupExpr = needToDup!Type; 980 bool passExprAsConst = dupExpr && __traits(compiles, const(Type).init.dup); 981 } 982 else 983 { 984 // unpack nullable for dup 985 bool dupExpr = needToDup!(typeof(Type.init.get)); 986 bool passExprAsConst = dupExpr && __traits(compiles, Type(const(Type).init.get.dup)); 987 } 988 989 bool forSuper = false; 990 991 static if (i < argsPassedToSuper) 992 { 993 includeMember = true; 994 forSuper = true; 995 } 996 else 997 { 998 mixin("alias symbol = typeof(this)." ~ member ~ ";"); 999 1000 static assert (is(typeof(symbol)) && !__traits(isTemplate, symbol)); /* must have a resolvable type */ 1001 1002 import boilerplate.util: isStatic; 1003 1004 includeMember = !mixin(isStatic(member)); 1005 1006 static if (udaIndex!(This.Init, __traits(getAttributes, symbol)) != -1) 1007 { 1008 enum udaFieldIndex = udaIndex!(This.Init, __traits(getAttributes, symbol)); 1009 alias initArg = Alias!(__traits(getAttributes, symbol)[udaFieldIndex].value); 1010 enum lambdaWithSelf = __traits(compiles, initArg(typeof(this).init)); 1011 enum nakedLambda = __traits(compiles, initArg()); 1012 1013 directInitFields ~= member; 1014 directInitIndex ~= udaFieldIndex; 1015 directInitUseSelf ~= __traits(compiles, 1016 __traits(getAttributes, symbol)[udaFieldIndex].value(typeof(this).init)); 1017 includeMember = false; 1018 1019 static if (lambdaWithSelf) 1020 { 1021 static if (__traits(compiles, initArg!(typeof(this)))) 1022 { 1023 enum lambdaAttributes = [__traits(getFunctionAttributes, initArg!(typeof(this)))]; 1024 } 1025 else 1026 { 1027 enum lambdaAttributes = [__traits(getFunctionAttributes, initArg)]; 1028 } 1029 1030 constructorAttributes = constructorAttributes.filter!(a => lambdaAttributes.canFind(a)).array; 1031 } 1032 else static if (nakedLambda) 1033 { 1034 enum lambdaAttributes = [__traits(getFunctionAttributes, initArg)]; 1035 1036 constructorAttributes = constructorAttributes.filter!(a => lambdaAttributes.canFind(a)).array; 1037 } 1038 } 1039 1040 static if (udaIndex!(This.Exclude, __traits(getAttributes, symbol)) != -1) 1041 { 1042 includeMember = false; 1043 } 1044 } 1045 1046 if (!includeMember) continue; 1047 1048 enum paramName = member.removeTrailingUnderline; 1049 1050 string argexpr = paramName; 1051 1052 if (dupExpr) 1053 { 1054 constructorAttributes = constructorAttributes.filter!(a => a != "@nogc").array; 1055 1056 static if (isNullable) 1057 { 1058 argexpr = format!`%s.isNull ? %s.init : %s(%s.get.dup)` 1059 (argexpr, memberTypes[i], memberTypes[i], argexpr); 1060 } 1061 else 1062 { 1063 argexpr = format!`%s.dup`(argexpr); 1064 } 1065 } 1066 1067 fields ~= member; 1068 args ~= paramName; 1069 argexprs ~= argexpr; 1070 fieldUseDefault ~= useDefault; 1071 fieldDefault ~= defaults[i]; 1072 fieldAttributes ~= attributes[i]; 1073 defaultAssignments ~= useDefault ? (` = ` ~ defaults[i]) : ``; 1074 types ~= passExprAsConst ? (`const ` ~ memberTypes[i]) : memberTypes[i]; 1075 } 1076 1077 size_t establishParameterRank(size_t i) 1078 { 1079 // parent explicit, our explicit, our implicit, parent implicit 1080 const fieldOfParent = i < argsPassedToSuper; 1081 return fieldUseDefault[i] * 2 + (fieldUseDefault[i] == fieldOfParent); 1082 } 1083 1084 auto constructorFieldOrder = fields.length.iota.array.bucketSort(&establishParameterRank); 1085 1086 assert(fields.length == types.length); 1087 assert(fields.length == fieldUseDefault.length); 1088 assert(fields.length == fieldDefault.length); 1089 1090 result ~= format!` 1091 public static alias ConstructorInfo = 1092 saveConstructorInfo!(%s, %-(%s, %));` 1093 ( 1094 fields.reorder(constructorFieldOrder), 1095 zip( 1096 types.reorder(constructorFieldOrder), 1097 fieldUseDefault.reorder(constructorFieldOrder), 1098 fieldDefault.reorder(constructorFieldOrder), 1099 fieldAttributes.reorder(constructorFieldOrder), 1100 ) 1101 .map!(args => format!`ConstructorField!(%s, %s, %s, %s)`(args[0], args[1], args[2], args[3])) 1102 .array 1103 ); 1104 1105 if (!(is(typeof(this) == struct) && fieldUseDefault.all)) // don't emit this() for structs 1106 { 1107 result ~= visibility ~ ` this(` 1108 ~ constructorFieldOrder 1109 .map!(i => format!`%s %s%s`(types[i], args[i], defaultAssignments[i])) 1110 .join(`, `) 1111 ~ format!`) %-(%s %)`(constructorAttributes); 1112 1113 result ~= `{`; 1114 1115 static if (is(typeof(typeof(super).ConstructorInfo))) 1116 { 1117 result ~= `super(` ~ args[0 .. argsPassedToSuper].join(", ") ~ `);`; 1118 } 1119 1120 result ~= fields.length.iota.drop(argsPassedToSuper) 1121 .map!(i => format!`this.%s = %s;`(fields[i], argexprs[i])) 1122 .join; 1123 1124 foreach (i, field; directInitFields) 1125 { 1126 if (directInitUseSelf[i]) 1127 { 1128 result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value(this);` 1129 (field, field, directInitIndex[i]); 1130 } 1131 else 1132 { 1133 result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value;` 1134 (field, field, directInitIndex[i]); 1135 } 1136 } 1137 1138 result ~= `}`; 1139 1140 result ~= `protected static enum string[] GeneratedConstructorAttributes_ = [` 1141 ~ constructorAttributes.map!(a => `"` ~ a ~ `"`).join(`, `) 1142 ~ `];`; 1143 } 1144 1145 result ~= visibility ~ ` static struct BuilderType(alias T = typeof(this)) 1146 { 1147 import boilerplate.builder : BuilderImpl; 1148 1149 mixin BuilderImpl!T; 1150 }`; 1151 1152 result ~= visibility ~ ` static auto Builder()() 1153 { 1154 return BuilderType!()(); 1155 }`; 1156 1157 result ~= visibility ~ ` auto BuilderFrom()() const 1158 { 1159 import boilerplate.util : removeTrailingUnderline; 1160 1161 auto builder = BuilderType!()(); 1162 1163 static foreach (field; ConstructorInfo.fields) 1164 { 1165 mixin("builder." ~ field.removeTrailingUnderline ~ " = this." ~ field ~ ";"); 1166 } 1167 return builder; 1168 }`; 1169 1170 return result; 1171 } 1172 } 1173 1174 public template ConstructorField(Type_, bool useDefault_, alias fieldDefault_, attributes_...) 1175 { 1176 public alias Type = Type_; 1177 public enum useDefault = useDefault_; 1178 public alias fieldDefault = fieldDefault_; 1179 public alias attributes = attributes_; 1180 } 1181 1182 public template saveConstructorInfo(string[] fields_, Fields...) 1183 if (fields_.length == Fields.length 1184 && allSatisfy!(ApplyLeft!(isInstanceOf, ConstructorField), Fields)) 1185 { 1186 import std.format : format; 1187 1188 public enum fields = fields_; 1189 1190 private template FieldInfo_() { 1191 static foreach (i, field; fields) 1192 { 1193 mixin(format!q{public alias %s = Fields[%s];}(field, i)); 1194 } 1195 } 1196 1197 public alias FieldInfo = FieldInfo_!(); 1198 } 1199 1200 enum ThisEnum 1201 { 1202 Private, 1203 Protected, 1204 Exclude 1205 } 1206 1207 struct This 1208 { 1209 enum Private = ThisEnum.Private; 1210 enum Protected = ThisEnum.Protected; 1211 struct Package 1212 { 1213 string packageMask = null; 1214 } 1215 enum Exclude = ThisEnum.Exclude; 1216 1217 // construct with value 1218 static struct Init(alias Alias) 1219 { 1220 static if (__traits(compiles, Alias())) 1221 { 1222 @property static auto value() { return Alias(); } 1223 } 1224 else 1225 { 1226 alias value = Alias; 1227 } 1228 } 1229 1230 static struct Default(alias Alias) 1231 { 1232 static if (__traits(compiles, Alias())) 1233 { 1234 @property static auto value() { return Alias(); } 1235 } 1236 else 1237 { 1238 alias value = Alias; 1239 } 1240 } 1241 } 1242 1243 public template getUDADefaultOrNothing(T, attributes...) 1244 { 1245 import boilerplate.util : udaIndex; 1246 1247 template EnumTest() 1248 { 1249 enum EnumTest = attributes[udaIndex!(This.Default, attributes)].value; 1250 } 1251 1252 static if (udaIndex!(This.Default, attributes) == -1) 1253 { 1254 enum getUDADefaultOrNothing = 0; 1255 } 1256 // @(This.Default) 1257 else static if (__traits(isSame, attributes[udaIndex!(This.Default, attributes)], This.Default)) 1258 { 1259 enum getUDADefaultOrNothing = T.init; 1260 } 1261 else static if (__traits(compiles, EnumTest!())) 1262 { 1263 enum getUDADefaultOrNothing = attributes[udaIndex!(This.Default, attributes)].value; 1264 } 1265 else 1266 { 1267 @property static auto getUDADefaultOrNothing() 1268 { 1269 return attributes[udaIndex!(This.Default, attributes)].value; 1270 } 1271 } 1272 }