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