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