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