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 with passthrough assignment to Nullable structs") 898 unittest 899 { 900 import std.typecons : Nullable, nullable; 901 902 struct Struct1 903 { 904 int a; 905 906 mixin(GenerateThis); 907 } 908 909 struct Struct2 910 { 911 @(This.Default) 912 Nullable!Struct1 b; 913 914 mixin(GenerateThis); 915 } 916 917 with (Struct2.Builder()) 918 { 919 value.shouldEqual(Struct2(Nullable!Struct1())); 920 921 b.a = 5; 922 923 value.shouldEqual(Struct2(Nullable!Struct1(Struct1(5)))); 924 } 925 } 926 927 /// 928 @("builder with value assignment to Nullable struct field") 929 unittest 930 { 931 import std.typecons : Nullable, nullable; 932 933 struct Struct1 934 { 935 mixin(GenerateThis); 936 } 937 938 struct Struct2 939 { 940 @(This.Default) 941 Nullable!Struct1 value; 942 943 mixin(GenerateThis); 944 } 945 946 with (Struct2.Builder()) 947 { 948 builderValue.shouldEqual(Struct2()); 949 950 value = Struct1(); 951 952 builderValue.shouldEqual(Struct2(Struct1().nullable)); 953 } 954 955 with (Struct2.Builder()) 956 { 957 value = Nullable!Struct1(); 958 959 builderValue.shouldEqual(Struct2()); 960 } 961 } 962 963 /// 964 @("builder supports reconstruction from value") 965 unittest 966 { 967 import std.typecons : Nullable, nullable; 968 969 struct Struct 970 { 971 private int a_; 972 973 int[] b; 974 975 mixin(GenerateThis); 976 } 977 978 const originalValue = Struct(2, [3]); 979 980 with (originalValue.BuilderFrom()) 981 { 982 a = 5; 983 984 value.shouldEqual(Struct(5, [3])); 985 } 986 } 987 988 /// 989 @("builder supports struct that already contains a value field") 990 unittest 991 { 992 import std.typecons : Nullable, nullable; 993 994 struct Struct 995 { 996 private int value_; 997 998 mixin(GenerateThis); 999 } 1000 1001 with (Struct.Builder()) 1002 { 1003 value = 5; 1004 1005 builderValue.shouldEqual(Struct(5)); 1006 } 1007 } 1008 1009 /// 1010 @("builder supports struct that contains struct that has @disable(this)") 1011 unittest 1012 { 1013 import std.typecons : Nullable, nullable; 1014 1015 static struct Inner 1016 { 1017 private int i_; 1018 1019 @disable this(); 1020 1021 mixin(GenerateThis); 1022 } 1023 1024 static struct Struct 1025 { 1026 private Inner inner_; 1027 1028 mixin(GenerateThis); 1029 } 1030 1031 with (Struct.Builder()) 1032 { 1033 inner.i = 3; 1034 1035 value.shouldEqual(Struct(Inner(3))); 1036 } 1037 } 1038 1039 import std.string : format; 1040 1041 enum GetSuperTypeAsString_(string member) = format!`typeof(super).ConstructorInfo.FieldInfo.%s.Type`(member); 1042 1043 enum GetMemberTypeAsString_(string member) = format!`typeof(this.%s)`(member); 1044 1045 enum SuperDefault_(string member) = format!`typeof(super).ConstructorInfo.FieldInfo.%s.fieldDefault`(member); 1046 1047 enum MemberDefault_(string member) = 1048 format!`getUDADefaultOrNothing!(typeof(this.%s), __traits(getAttributes, this.%s))`(member, member); 1049 1050 enum SuperUseDefault_(string member) 1051 = format!(`typeof(super).ConstructorInfo.FieldInfo.%s.useDefault`)(member); 1052 1053 enum MemberUseDefault_(string member) 1054 = format!(`udaIndex!(This.Default, __traits(getAttributes, this.%s)) != -1`)(member); 1055 1056 enum SuperAttributes_(string member) 1057 = format!(`typeof(super).ConstructorInfo.FieldInfo.%s.attributes`)(member); 1058 1059 enum MemberAttributes_(string member) 1060 = format!(`__traits(getAttributes, this.%s)`)(member); 1061 1062 mixin template GenerateThisTemplate() 1063 { 1064 private static generateThisImpl() 1065 { 1066 if (!__ctfe) 1067 { 1068 return null; 1069 } 1070 1071 import boilerplate.constructor : 1072 GetMemberTypeAsString_, GetSuperTypeAsString_, 1073 MemberAttributes_, MemberDefault_, MemberUseDefault_, 1074 SuperAttributes_, SuperDefault_, SuperUseDefault_, 1075 This; 1076 import boilerplate.util : 1077 bucketSort, GenNormalMemberTuple, needToDup, 1078 removeTrailingUnderline, reorder, udaIndex; 1079 import std.algorithm : all, canFind, filter, map; 1080 import std.meta : Alias, aliasSeqOf, staticMap; 1081 import std.range : array, drop, empty, iota, zip; 1082 import std.string : endsWith, format, join; 1083 import std.typecons : Nullable; 1084 1085 mixin GenNormalMemberTuple; 1086 1087 string result = null; 1088 1089 string visibility = "public"; 1090 1091 foreach (uda; __traits(getAttributes, typeof(this))) 1092 { 1093 static if (is(typeof(uda) == ThisEnum)) 1094 { 1095 static if (uda == This.Protected) 1096 { 1097 visibility = "protected"; 1098 } 1099 static if (uda == This.Private) 1100 { 1101 visibility = "private"; 1102 } 1103 } 1104 else static if (is(uda == This.Package)) 1105 { 1106 visibility = "package"; 1107 } 1108 else static if (is(typeof(uda) == This.Package)) 1109 { 1110 visibility = "package(" ~ uda.packageMask ~ ")"; 1111 } 1112 } 1113 1114 string[] constructorAttributes = ["pure", "nothrow", "@safe", "@nogc"]; 1115 1116 static if (is(typeof(typeof(super).ConstructorInfo))) 1117 { 1118 enum argsPassedToSuper = typeof(super).ConstructorInfo.fields.length; 1119 enum members = typeof(super).ConstructorInfo.fields ~ [NormalMemberTuple]; 1120 enum string[] CombinedArray(alias SuperPred, alias MemberPred) = ([ 1121 staticMap!(SuperPred, aliasSeqOf!(typeof(super).ConstructorInfo.fields)), 1122 staticMap!(MemberPred, NormalMemberTuple) 1123 ]); 1124 constructorAttributes = typeof(super).GeneratedConstructorAttributes_; 1125 } 1126 else 1127 { 1128 enum argsPassedToSuper = 0; 1129 static if (NormalMemberTuple.length > 0) 1130 { 1131 enum members = [NormalMemberTuple]; 1132 enum string[] CombinedArray(alias SuperPred, alias MemberPred) = ([ 1133 staticMap!(MemberPred, NormalMemberTuple) 1134 ]); 1135 } 1136 else 1137 { 1138 enum string[] members = null; 1139 enum string[] CombinedArray(alias SuperPred, alias MemberPred) = null; 1140 } 1141 } 1142 1143 enum string[] useDefaults = CombinedArray!(SuperUseDefault_, MemberUseDefault_); 1144 enum string[] memberTypes = CombinedArray!(GetSuperTypeAsString_, GetMemberTypeAsString_); 1145 enum string[] defaults = CombinedArray!(SuperDefault_, MemberDefault_); 1146 enum string[] attributes = CombinedArray!(SuperAttributes_, MemberAttributes_); 1147 1148 string[] fields; 1149 string[] args; 1150 string[] argexprs; 1151 string[] defaultAssignments; 1152 bool[] fieldUseDefault; 1153 string[] fieldDefault; 1154 string[] fieldAttributes; 1155 string[] types; 1156 string[] directInitFields; 1157 int[] directInitIndex; 1158 bool[] directInitUseSelf; 1159 1160 foreach (i; aliasSeqOf!(members.length.iota)) 1161 { 1162 enum member = members[i]; 1163 1164 mixin(`alias Type = ` ~ memberTypes[i] ~ `;`); 1165 mixin(`enum bool useDefault = ` ~ useDefaults[i] ~ `;`); 1166 1167 bool includeMember = false; 1168 1169 enum isNullable = is(Type: Nullable!Arg, Arg); 1170 1171 static if (!isNullable) 1172 { 1173 bool dupExpr = needToDup!Type; 1174 bool passExprAsConst = dupExpr && __traits(compiles, const(Type).init.dup); 1175 } 1176 else 1177 { 1178 // unpack nullable for dup 1179 bool dupExpr = needToDup!(typeof(Type.init.get)); 1180 bool passExprAsConst = dupExpr && __traits(compiles, Type(const(Type).init.get.dup)); 1181 } 1182 1183 bool forSuper = false; 1184 1185 static if (i < argsPassedToSuper) 1186 { 1187 includeMember = true; 1188 forSuper = true; 1189 } 1190 else 1191 { 1192 mixin("alias symbol = typeof(this)." ~ member ~ ";"); 1193 1194 static assert (is(typeof(symbol)) && !__traits(isTemplate, symbol)); /* must have a resolvable type */ 1195 1196 import boilerplate.util: isStatic; 1197 1198 includeMember = !mixin(isStatic(member)); 1199 1200 static if (udaIndex!(This.Init, __traits(getAttributes, symbol)) != -1) 1201 { 1202 enum udaFieldIndex = udaIndex!(This.Init, __traits(getAttributes, symbol)); 1203 alias initArg = Alias!(__traits(getAttributes, symbol)[udaFieldIndex].value); 1204 enum lambdaWithSelf = __traits(compiles, initArg(typeof(this).init)); 1205 enum nakedLambda = __traits(compiles, initArg()); 1206 1207 directInitFields ~= member; 1208 directInitIndex ~= udaFieldIndex; 1209 directInitUseSelf ~= __traits(compiles, 1210 __traits(getAttributes, symbol)[udaFieldIndex].value(typeof(this).init)); 1211 includeMember = false; 1212 1213 static if (lambdaWithSelf) 1214 { 1215 static if (__traits(compiles, initArg!(typeof(this)))) 1216 { 1217 enum lambdaAttributes = [__traits(getFunctionAttributes, initArg!(typeof(this)))]; 1218 } 1219 else 1220 { 1221 enum lambdaAttributes = [__traits(getFunctionAttributes, initArg)]; 1222 } 1223 1224 constructorAttributes = constructorAttributes.filter!(a => lambdaAttributes.canFind(a)).array; 1225 } 1226 else static if (nakedLambda) 1227 { 1228 enum lambdaAttributes = [__traits(getFunctionAttributes, initArg)]; 1229 1230 constructorAttributes = constructorAttributes.filter!(a => lambdaAttributes.canFind(a)).array; 1231 } 1232 } 1233 1234 static if (udaIndex!(This.Exclude, __traits(getAttributes, symbol)) != -1) 1235 { 1236 includeMember = false; 1237 } 1238 } 1239 1240 if (!includeMember) continue; 1241 1242 enum paramName = member.removeTrailingUnderline; 1243 1244 string argexpr = paramName; 1245 1246 if (dupExpr) 1247 { 1248 constructorAttributes = constructorAttributes.filter!(a => a != "@nogc").array; 1249 1250 static if (isNullable) 1251 { 1252 argexpr = format!`%s.isNull ? %s.init : %s(%s.get.dup)` 1253 (argexpr, memberTypes[i], memberTypes[i], argexpr); 1254 } 1255 else 1256 { 1257 argexpr = format!`%s.dup`(argexpr); 1258 } 1259 } 1260 1261 fields ~= member; 1262 args ~= paramName; 1263 argexprs ~= argexpr; 1264 fieldUseDefault ~= useDefault; 1265 fieldDefault ~= defaults[i]; 1266 fieldAttributes ~= attributes[i]; 1267 defaultAssignments ~= useDefault ? (` = ` ~ defaults[i]) : ``; 1268 types ~= passExprAsConst ? (`const ` ~ memberTypes[i]) : memberTypes[i]; 1269 } 1270 1271 size_t establishParameterRank(size_t i) 1272 { 1273 // parent explicit, our explicit, our implicit, parent implicit 1274 const fieldOfParent = i < argsPassedToSuper; 1275 return fieldUseDefault[i] * 2 + (fieldUseDefault[i] == fieldOfParent); 1276 } 1277 1278 auto constructorFieldOrder = fields.length.iota.array.bucketSort(&establishParameterRank); 1279 1280 assert(fields.length == types.length); 1281 assert(fields.length == fieldUseDefault.length); 1282 assert(fields.length == fieldDefault.length); 1283 1284 result ~= format!` 1285 public static alias ConstructorInfo = 1286 saveConstructorInfo!(%s, %-(%s, %));` 1287 ( 1288 fields.reorder(constructorFieldOrder), 1289 zip( 1290 types.reorder(constructorFieldOrder), 1291 fieldUseDefault.reorder(constructorFieldOrder), 1292 fieldDefault.reorder(constructorFieldOrder), 1293 fieldAttributes.reorder(constructorFieldOrder), 1294 ) 1295 .map!(args => format!`ConstructorField!(%s, %s, %s, %s)`(args[0], args[1], args[2], args[3])) 1296 .array 1297 ); 1298 1299 // don't emit this(a = b, c = d) for structs - 1300 // the compiler complains that it collides with this(), which is reserved. 1301 if (is(typeof(this) == struct) && fieldUseDefault.all) 1302 { 1303 // If there are fields, their direct-construction types may diverge from ours 1304 // specifically, see the "struct with only default fields" test below 1305 if (!fields.empty) 1306 { 1307 result ~= `static assert( 1308 is(typeof(this.tupleof) == ConstructorInfo.Types), 1309 "Structs with fields, that are all default, cannot use GenerateThis when their " ~ 1310 "constructor types would diverge from their native types: " ~ 1311 typeof(this).stringof ~ ".this" ~ typeof(this.tupleof).stringof ~ ", " ~ 1312 "but generated constructor would have been " ~ typeof(this).stringof ~ ".this" 1313 ~ ConstructorInfo.Types.stringof 1314 );`; 1315 } 1316 } 1317 else 1318 { 1319 result ~= visibility ~ ` this(` 1320 ~ constructorFieldOrder 1321 .map!(i => format!`%s %s%s`(types[i], args[i], defaultAssignments[i])) 1322 .join(`, `) 1323 ~ format!`) %-(%s %)`(constructorAttributes); 1324 1325 result ~= `{`; 1326 1327 static if (is(typeof(typeof(super).ConstructorInfo))) 1328 { 1329 result ~= `super(` ~ args[0 .. argsPassedToSuper].join(", ") ~ `);`; 1330 } 1331 1332 result ~= fields.length.iota.drop(argsPassedToSuper) 1333 .map!(i => format!`this.%s = %s;`(fields[i], argexprs[i])) 1334 .join; 1335 1336 foreach (i, field; directInitFields) 1337 { 1338 if (directInitUseSelf[i]) 1339 { 1340 result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value(this);` 1341 (field, field, directInitIndex[i]); 1342 } 1343 else 1344 { 1345 result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value;` 1346 (field, field, directInitIndex[i]); 1347 } 1348 } 1349 1350 result ~= `}`; 1351 1352 result ~= `protected static enum string[] GeneratedConstructorAttributes_ = [` 1353 ~ constructorAttributes.map!(a => `"` ~ a ~ `"`).join(`, `) 1354 ~ `];`; 1355 } 1356 1357 result ~= visibility ~ ` static struct BuilderType(alias T = typeof(this)) 1358 { 1359 import boilerplate.builder : BuilderImpl; 1360 1361 mixin BuilderImpl!T; 1362 }`; 1363 1364 result ~= visibility ~ ` static auto Builder()() 1365 { 1366 return BuilderType!()(); 1367 }`; 1368 1369 result ~= visibility ~ ` auto BuilderFrom()() const 1370 { 1371 import boilerplate.util : removeTrailingUnderline; 1372 1373 auto builder = BuilderType!()(); 1374 1375 static foreach (field; ConstructorInfo.fields) 1376 { 1377 mixin("builder." ~ field.removeTrailingUnderline ~ " = this." ~ field ~ ";"); 1378 } 1379 return builder; 1380 }`; 1381 1382 return result; 1383 } 1384 } 1385 1386 public template ConstructorField(Type_, bool useDefault_, alias fieldDefault_, attributes_...) 1387 { 1388 public alias Type = Type_; 1389 public enum useDefault = useDefault_; 1390 public alias fieldDefault = fieldDefault_; 1391 public alias attributes = attributes_; 1392 } 1393 1394 public template saveConstructorInfo(string[] fields_, Fields...) 1395 if (fields_.length == Fields.length 1396 && allSatisfy!(ApplyLeft!(isInstanceOf, ConstructorField), Fields)) 1397 { 1398 import std.format : format; 1399 1400 public enum fields = fields_; 1401 1402 private template FieldInfo_() { 1403 static foreach (i, field; fields) 1404 { 1405 mixin(format!q{public alias %s = Fields[%s];}(field, i)); 1406 } 1407 } 1408 1409 public alias FieldInfo = FieldInfo_!(); 1410 1411 mixin( 1412 format!q{public alias Types = AliasSeq!(%-(%s, %)); } 1413 (fields.map!(field => format!"FieldInfo.%s.Type"(field)).array)); 1414 } 1415 1416 enum ThisEnum 1417 { 1418 Private, 1419 Protected, 1420 Exclude 1421 } 1422 1423 struct This 1424 { 1425 enum Private = ThisEnum.Private; 1426 enum Protected = ThisEnum.Protected; 1427 struct Package 1428 { 1429 string packageMask = null; 1430 } 1431 enum Exclude = ThisEnum.Exclude; 1432 1433 // construct with value 1434 static struct Init(alias Alias) 1435 { 1436 static if (__traits(compiles, Alias())) 1437 { 1438 @property static auto value() { return Alias(); } 1439 } 1440 else 1441 { 1442 alias value = Alias; 1443 } 1444 } 1445 1446 static struct Default(alias Alias) 1447 { 1448 static if (__traits(compiles, Alias())) 1449 { 1450 @property static auto value() { return Alias(); } 1451 } 1452 else 1453 { 1454 alias value = Alias; 1455 } 1456 } 1457 } 1458 1459 public template getUDADefaultOrNothing(T, attributes...) 1460 { 1461 import boilerplate.util : udaIndex; 1462 1463 template EnumTest() 1464 { 1465 enum EnumTest = attributes[udaIndex!(This.Default, attributes)].value; 1466 } 1467 1468 static if (udaIndex!(This.Default, attributes) == -1) 1469 { 1470 enum getUDADefaultOrNothing = 0; 1471 } 1472 // @(This.Default) 1473 else static if (__traits(isSame, attributes[udaIndex!(This.Default, attributes)], This.Default)) 1474 { 1475 enum getUDADefaultOrNothing = T.init; 1476 } 1477 else static if (__traits(compiles, EnumTest!())) 1478 { 1479 enum getUDADefaultOrNothing = attributes[udaIndex!(This.Default, attributes)].value; 1480 } 1481 else 1482 { 1483 @property static auto getUDADefaultOrNothing() 1484 { 1485 return attributes[udaIndex!(This.Default, attributes)].value; 1486 } 1487 } 1488 } 1489 1490 @("struct with only default fields cannot use GenerateThis unless the default this() type matches the generated one") 1491 unittest 1492 { 1493 static assert(!__traits(compiles, { 1494 struct Foo 1495 { 1496 @(This.Default) 1497 int[] array; 1498 1499 mixin(GenerateThis); 1500 } 1501 1502 // because you would be able to do 1503 // const array = [2]; 1504 // auto foo = Foo(array); 1505 // which would be an error, but work with a generated constructor 1506 // however, no constructor could be generated, as it would collide with this() 1507 })); 1508 1509 // This works though. 1510 struct Bar 1511 { 1512 @(This.Default) 1513 const int[] array; 1514 1515 mixin(GenerateThis); 1516 } 1517 1518 const array = [2]; 1519 auto bar = Bar(array); 1520 } 1521 1522 @("very large types can be used") 1523 unittest 1524 { 1525 import std.format : format; 1526 import std.range : iota; 1527 1528 struct VeryLargeType 1529 { 1530 static foreach (i; 500.iota) 1531 { 1532 mixin(format!"int v%s;"(i)); 1533 } 1534 1535 mixin(GenerateThis); 1536 } 1537 1538 struct Wrapper 1539 { 1540 VeryLargeType field; 1541 1542 mixin(GenerateThis); 1543 } 1544 1545 auto builder = Wrapper.Builder(); 1546 }