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