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 * When a recursive `Builder` field has already been directly assigned, it cannot be 935 * later overwritten with a whole-value assignment. 936 */ 937 @("builder refuses overriding field assignment with value assignment") 938 unittest 939 { 940 import core.exception : AssertError; 941 942 struct Struct1 943 { 944 int a; 945 int b; 946 947 mixin(GenerateThis); 948 } 949 950 struct Struct2 951 { 952 Struct1 struct1; 953 954 mixin(GenerateThis); 955 } 956 957 auto builder = Struct2.Builder(); 958 959 builder.struct1.b = 4; 960 961 void set() 962 { 963 builder.struct1 = Struct1(2, 3); 964 } 965 set().shouldThrow!AssertError("Builder: cannot set field by value since a subfield has already been set."); 966 } 967 968 /** 969 * `Builder` supports assigning const fields. 970 */ 971 @("builder supports const args") 972 unittest 973 { 974 struct Struct 975 { 976 const int a; 977 978 mixin(GenerateThis); 979 } 980 981 with (Struct.Builder()) 982 { 983 a = 5; 984 985 value.shouldEqual(Struct(5)); 986 } 987 } 988 989 /** 990 * `Builder` supports assigning recursive values with a destructor. 991 */ 992 @("builder supports fields with destructor") 993 unittest 994 { 995 static struct Struct1 996 { 997 ~this() pure @safe @nogc nothrow { } 998 } 999 1000 struct Struct2 1001 { 1002 Struct1 struct1; 1003 1004 mixin(GenerateThis); 1005 } 1006 1007 with (Struct2.Builder()) 1008 { 1009 struct1 = Struct1(); 1010 1011 value.shouldEqual(Struct2(Struct1())); 1012 } 1013 } 1014 1015 /** 1016 * When a `Builder` field is `Nullable!T`, it can be directly assigned a `T`. 1017 */ 1018 @("builder supports direct assignment to Nullables") 1019 unittest 1020 { 1021 import std.typecons : Nullable, nullable; 1022 1023 struct Struct 1024 { 1025 const Nullable!int a; 1026 1027 mixin(GenerateThis); 1028 } 1029 1030 with (Struct.Builder()) 1031 { 1032 a = 5; 1033 1034 value.shouldEqual(Struct(5.nullable)); 1035 } 1036 } 1037 1038 /** 1039 * When a `Builder` field is `Nullable!T`, and `T` has a `Builder`, `T`'s fields can be directly assigned. 1040 */ 1041 @("builder with passthrough assignment to Nullable structs") 1042 unittest 1043 { 1044 import std.typecons : Nullable, nullable; 1045 1046 struct Struct1 1047 { 1048 int a; 1049 1050 mixin(GenerateThis); 1051 } 1052 1053 struct Struct2 1054 { 1055 @(This.Default) 1056 Nullable!Struct1 b; 1057 1058 mixin(GenerateThis); 1059 } 1060 1061 with (Struct2.Builder()) 1062 { 1063 value.shouldEqual(Struct2(Nullable!Struct1())); 1064 1065 b.a = 5; 1066 1067 value.shouldEqual(Struct2(Nullable!Struct1(Struct1(5)))); 1068 } 1069 } 1070 1071 /** 1072 * When a `Builder` field is `Nullable!T`, and `T` has a `Builder`, `T` can still be assigned either as 1073 * `T` or `Nullable!T`. 1074 */ 1075 @("builder with value assignment to Nullable struct field") 1076 unittest 1077 { 1078 import std.typecons : Nullable, nullable; 1079 1080 struct Struct1 1081 { 1082 mixin(GenerateThis); 1083 } 1084 1085 struct Struct2 1086 { 1087 @(This.Default) 1088 Nullable!Struct1 value; 1089 1090 mixin(GenerateThis); 1091 } 1092 1093 with (Struct2.Builder()) 1094 { 1095 builderValue.shouldEqual(Struct2()); 1096 1097 value = Struct1(); 1098 1099 builderValue.shouldEqual(Struct2(Struct1().nullable)); 1100 } 1101 1102 with (Struct2.Builder()) 1103 { 1104 value = Nullable!Struct1(); 1105 1106 builderValue.shouldEqual(Struct2()); 1107 } 1108 } 1109 1110 /** 1111 * A value with `GenerateThis` can be turned back into a builder using `BuilderFrom()`. 1112 * This can be used to reassign immutable fields. 1113 */ 1114 @("builder supports reconstruction from value") 1115 unittest 1116 { 1117 import std.typecons : Nullable, nullable; 1118 1119 struct Struct 1120 { 1121 private int a_; 1122 1123 int[] b; 1124 1125 mixin(GenerateThis); 1126 } 1127 1128 const originalValue = Struct(2, [3]); 1129 1130 with (originalValue.BuilderFrom()) 1131 { 1132 a = 5; 1133 1134 value.shouldEqual(Struct(5, [3])); 1135 } 1136 } 1137 1138 /** 1139 * When a type already has a `value` field, `builderValue` can be used to get the builder value. 1140 */ 1141 @("builder supports struct that already contains a value field") 1142 unittest 1143 { 1144 import std.typecons : Nullable, nullable; 1145 1146 struct Struct 1147 { 1148 private int value_; 1149 1150 mixin(GenerateThis); 1151 } 1152 1153 with (Struct.Builder()) 1154 { 1155 value = 5; 1156 1157 builderValue.shouldEqual(Struct(5)); 1158 } 1159 } 1160 1161 /** 1162 * `Builder` will handle structs that contain structs with `@disable(this)`. 1163 */ 1164 @("builder supports struct that contains struct that has @disable(this)") 1165 unittest 1166 { 1167 import std.typecons : Nullable, nullable; 1168 1169 static struct Inner 1170 { 1171 private int i_; 1172 1173 @disable this(); 1174 1175 mixin(GenerateThis); 1176 } 1177 1178 static struct Struct 1179 { 1180 private Inner inner_; 1181 1182 mixin(GenerateThis); 1183 } 1184 1185 with (Struct.Builder()) 1186 { 1187 inner.i = 3; 1188 1189 value.shouldEqual(Struct(Inner(3))); 1190 } 1191 } 1192 1193 @("destructors with code that is unsafe, system or throws exceptions") 1194 { 1195 struct S 1196 { 1197 ~this() { throw new Exception("test"); } 1198 } 1199 1200 struct T 1201 { 1202 S s; 1203 1204 mixin(GenerateThis); 1205 } 1206 } 1207 1208 @("builder supports appending to transitive non-const fields") 1209 unittest 1210 { 1211 struct Struct1 1212 { 1213 int[] values; 1214 1215 mixin(GenerateThis); 1216 } 1217 1218 struct Struct2 1219 { 1220 Struct1[] array; 1221 1222 mixin(GenerateThis); 1223 } 1224 1225 auto builder = Struct2.Builder(); 1226 1227 builder.array ~= [Struct1([1]), Struct1([2])]; 1228 1229 builder.value.shouldEqual(Struct2([Struct1([1]), Struct1([2])])); 1230 } 1231 1232 import std.string : format; 1233 1234 mixin template GenerateThisTemplate() 1235 { 1236 private static generateThisImpl() 1237 { 1238 if (!__ctfe) 1239 { 1240 return null; 1241 } 1242 1243 import boilerplate.constructor : filterCanFind, mapFormat, This; 1244 import boilerplate.util : 1245 bucketSort, GenNormalMemberTuple, needToDup, 1246 optionallyRemoveTrailingUnderline, 1247 removeTrailingUnderline, reorder, udaIndex; 1248 import std.algorithm : all, canFind, filter, map; 1249 import std.meta : Alias, aliasSeqOf, staticMap; 1250 import std.range : array, drop, empty, iota, zip; 1251 import std.string : endsWith, format, join; 1252 import std.typecons : Nullable; 1253 1254 mixin GenNormalMemberTuple; 1255 1256 string result = null; 1257 1258 string visibility = "public"; 1259 1260 foreach (uda; __traits(getAttributes, typeof(this))) 1261 { 1262 static if (is(typeof(uda) == ThisEnum)) 1263 { 1264 static if (uda == This.Protected) 1265 { 1266 visibility = "protected"; 1267 } 1268 static if (uda == This.Private) 1269 { 1270 visibility = "private"; 1271 } 1272 } 1273 else static if (is(uda == This.Package)) 1274 { 1275 visibility = "package"; 1276 } 1277 else static if (is(typeof(uda) == This.Package)) 1278 { 1279 visibility = "package(" ~ uda.packageMask ~ ")"; 1280 } 1281 } 1282 1283 string[] constructorAttributes = ["pure", "nothrow", "@safe", "@nogc"]; 1284 1285 static if (is(typeof(typeof(super).ConstructorInfo))) 1286 { 1287 enum argsPassedToSuper = typeof(super).ConstructorInfo.fields.length; 1288 enum members = typeof(super).ConstructorInfo.fields ~ [NormalMemberTuple]; 1289 1290 constructorAttributes = typeof(super).GeneratedConstructorAttributes_; 1291 } 1292 else 1293 { 1294 enum argsPassedToSuper = 0; 1295 static if (NormalMemberTuple.length > 0) 1296 { 1297 enum members = [NormalMemberTuple]; 1298 } 1299 else 1300 { 1301 enum string[] members = null; 1302 } 1303 } 1304 1305 string[] fields; 1306 string[] args; 1307 string[] argexprs; 1308 string[] defaultAssignments; 1309 bool[] fieldUseDefault; 1310 string[] fieldDefault; 1311 string[] fieldAttributes; 1312 string[] types; 1313 string[] directInitFields; 1314 int[] directInitIndex; 1315 bool[] directInitUseSelf; 1316 1317 foreach (i; aliasSeqOf!(members.length.iota)) 1318 { 1319 enum member = members[i]; 1320 1321 static if (i < argsPassedToSuper) 1322 { 1323 enum bool useDefault = __traits(getMember, typeof(super).ConstructorInfo.FieldInfo, member).useDefault; 1324 enum string memberTypeAsString = "typeof(super).ConstructorInfo.FieldInfo." ~ member ~ ".Type"; 1325 enum string default_ = "typeof(super).ConstructorInfo.FieldInfo." ~ member ~ ".fieldDefault"; 1326 enum string attributes = "typeof(super).ConstructorInfo.FieldInfo." ~ member ~ ".attributes"; 1327 } 1328 else 1329 { 1330 enum bool useDefault = udaIndex!(This.Default, __traits(getAttributes, __traits(getMember, typeof(this), member))) != -1; 1331 enum string memberTypeAsString = "typeof(this." ~ member ~ ")"; 1332 enum string default_ = "getUDADefaultOrNothing!(typeof(this." ~ member ~ "), __traits(getAttributes, this." ~ member ~ "))"; 1333 enum string attributes = "__traits(getAttributes, this." ~ member ~ ")"; 1334 } 1335 1336 mixin(`alias Type = ` ~ memberTypeAsString ~ `;`); 1337 1338 bool includeMember = false; 1339 1340 enum isNullable = is(Type: Nullable!Arg, Arg); 1341 1342 static if (!isNullable) 1343 { 1344 enum bool dupExpr = needToDup!Type; 1345 enum bool passExprAsConst = dupExpr && __traits(compiles, { Type value = const(Type).init.dup; }); 1346 } 1347 else 1348 { 1349 // unpack nullable for dup 1350 enum bool dupExpr = needToDup!(typeof(Type.init.get)); 1351 enum bool passExprAsConst = dupExpr && __traits(compiles, { Type value = const(Type).init.get.dup; }); 1352 } 1353 1354 enum scopeAttributes = [__traits(getFunctionAttributes, { 1355 static if (passExprAsConst) { const Type parameter = Type.init; } 1356 else { Type parameter = Type.init; } 1357 1358 static if (isNullable) { auto value = parameter.get; } 1359 else { auto value = parameter; } 1360 1361 static if (dupExpr) 1362 { 1363 Type dupped = value.dup; 1364 } 1365 })]; 1366 constructorAttributes = constructorAttributes.filterCanFind(scopeAttributes); 1367 1368 bool forSuper = false; 1369 1370 static if (i < argsPassedToSuper) 1371 { 1372 includeMember = true; 1373 forSuper = true; 1374 } 1375 else 1376 { 1377 mixin("alias symbol = typeof(this)." ~ member ~ ";"); 1378 1379 static assert (is(typeof(symbol)) && !__traits(isTemplate, symbol)); /* must have a resolvable type */ 1380 1381 import boilerplate.util: isStatic; 1382 1383 includeMember = !mixin(isStatic(member)); 1384 1385 static if (udaIndex!(This.Init, __traits(getAttributes, symbol)) != -1) 1386 { 1387 enum udaFieldIndex = udaIndex!(This.Init, __traits(getAttributes, symbol)); 1388 alias initArg = Alias!(__traits(getAttributes, symbol)[udaFieldIndex].value); 1389 enum lambdaWithSelf = __traits(compiles, initArg(typeof(this).init)); 1390 enum nakedLambda = __traits(compiles, initArg()); 1391 1392 directInitFields ~= member; 1393 directInitIndex ~= udaFieldIndex; 1394 directInitUseSelf ~= __traits(compiles, 1395 __traits(getAttributes, symbol)[udaFieldIndex].value(typeof(this).init)); 1396 includeMember = false; 1397 1398 static if (lambdaWithSelf) 1399 { 1400 static if (__traits(compiles, initArg!(typeof(this)))) 1401 { 1402 enum lambdaAttributes = [__traits(getFunctionAttributes, initArg!(typeof(this)))]; 1403 } 1404 else 1405 { 1406 enum lambdaAttributes = [__traits(getFunctionAttributes, initArg)]; 1407 } 1408 constructorAttributes = constructorAttributes.filterCanFind(lambdaAttributes); 1409 } 1410 else static if (nakedLambda) 1411 { 1412 enum lambdaAttributes = [__traits(getFunctionAttributes, initArg)]; 1413 1414 constructorAttributes = constructorAttributes.filterCanFind(lambdaAttributes); 1415 } 1416 } 1417 1418 static if (udaIndex!(This.Exclude, __traits(getAttributes, symbol)) != -1) 1419 { 1420 includeMember = false; 1421 } 1422 } 1423 1424 if (!includeMember) continue; 1425 1426 enum paramName = optionallyRemoveTrailingUnderline!member; 1427 1428 string argexpr = paramName; 1429 1430 if (dupExpr) 1431 { 1432 constructorAttributes = constructorAttributes.filter!(q{a != "@nogc"}).array; 1433 1434 static if (isNullable) 1435 { 1436 argexpr = format!`%s.isNull ? %s.init : %s(%s.get.dup)` 1437 (argexpr, memberTypeAsString, memberTypeAsString, argexpr); 1438 } 1439 else 1440 { 1441 argexpr = argexpr ~ ".dup"; 1442 } 1443 } 1444 1445 fields ~= member; 1446 args ~= paramName; 1447 argexprs ~= argexpr; 1448 fieldUseDefault ~= useDefault; 1449 fieldDefault ~= default_; 1450 fieldAttributes ~= attributes; 1451 defaultAssignments ~= useDefault ? (` = ` ~ default_) : ``; 1452 types ~= passExprAsConst ? (`const ` ~ memberTypeAsString) : memberTypeAsString; 1453 } 1454 1455 size_t establishParameterRank(size_t i) 1456 { 1457 // parent explicit, our explicit, our implicit, parent implicit 1458 const fieldOfParent = i < argsPassedToSuper; 1459 return fieldUseDefault[i] * 2 + (fieldUseDefault[i] == fieldOfParent); 1460 } 1461 1462 auto constructorFieldOrder = fields.length.iota.array.bucketSort(&establishParameterRank); 1463 1464 assert(fields.length == types.length); 1465 assert(fields.length == fieldUseDefault.length); 1466 assert(fields.length == fieldDefault.length); 1467 1468 result ~= format!` 1469 public static alias ConstructorInfo = 1470 saveConstructorInfo!(%s, %-(%s, %));` 1471 ( 1472 fields.reorder(constructorFieldOrder), 1473 zip( 1474 types.reorder(constructorFieldOrder), 1475 fieldUseDefault.reorder(constructorFieldOrder), 1476 fieldDefault.reorder(constructorFieldOrder), 1477 fieldAttributes.reorder(constructorFieldOrder), 1478 ) 1479 .map!(q{format!`ConstructorField!(%s, %s, %s, %s)`(a[0], a[1], a[2], a[3])}) 1480 .array 1481 ); 1482 1483 // don't emit this(a = b, c = d) for structs - 1484 // the compiler complains that it collides with this(), which is reserved. 1485 if (is(typeof(this) == struct) && fieldUseDefault.all) 1486 { 1487 // If there are fields, their direct-construction types may diverge from ours 1488 // specifically, see the "struct with only default fields" test below 1489 if (!fields.empty) 1490 { 1491 result ~= `static assert( 1492 is(typeof(this.tupleof) == ConstructorInfo.Types), 1493 "Structs with fields, that are all default, cannot use GenerateThis when their " ~ 1494 "constructor types would diverge from their native types: " ~ 1495 typeof(this).stringof ~ ".this" ~ typeof(this.tupleof).stringof ~ ", " ~ 1496 "but generated constructor would have been " ~ typeof(this).stringof ~ ".this" 1497 ~ ConstructorInfo.Types.stringof 1498 );`; 1499 } 1500 } 1501 else 1502 { 1503 result ~= visibility ~ ` this(` 1504 ~ constructorFieldOrder.mapFormat!`%s %s%s`(types, args, defaultAssignments).join(`, `) 1505 ~ format!`) %-(%s %)`(constructorAttributes); 1506 1507 result ~= `{`; 1508 1509 static if (is(typeof(typeof(super).ConstructorInfo))) 1510 { 1511 result ~= `super(` ~ args[0 .. argsPassedToSuper].join(", ") ~ `);`; 1512 } 1513 1514 result ~= fields.length.iota.drop(argsPassedToSuper).mapFormat!`this.%s = %s;`(fields, argexprs).join; 1515 1516 foreach (i, field; directInitFields) 1517 { 1518 if (directInitUseSelf[i]) 1519 { 1520 result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value(this);` 1521 (field, field, directInitIndex[i]); 1522 } 1523 else 1524 { 1525 result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value;` 1526 (field, field, directInitIndex[i]); 1527 } 1528 } 1529 1530 result ~= `}`; 1531 1532 result ~= `protected static enum string[] GeneratedConstructorAttributes_ = [` 1533 ~ constructorAttributes.map!(q{`"` ~ a ~ `"`}).join(`, `) 1534 ~ `];`; 1535 } 1536 1537 result ~= visibility ~ ` static struct BuilderType(alias T = typeof(this)) 1538 { 1539 import boilerplate.builder : BuilderImpl; 1540 1541 mixin BuilderImpl!T; 1542 }`; 1543 1544 result ~= visibility ~ ` static auto Builder()() 1545 { 1546 return BuilderType!()(); 1547 }`; 1548 1549 /** 1550 * We are allowed to read the private field values here. 1551 * We aren't actually leaking private or mutable information because: 1552 * - the constructor will dup it again anyways, if required 1553 * - we cannot read it from the Builder, because Builders are write-only 1554 * - if we can't read it off the current value, the builderValue will 1555 * have the same type - so we can't read it off there either! 1556 */ 1557 result ~= visibility ~ ` auto BuilderFrom(this This)() 1558 { 1559 import boilerplate.util : optionallyRemoveTrailingUnderline; 1560 1561 auto builder = BuilderType!()(); 1562 1563 static foreach (field; ConstructorInfo.fields) 1564 { 1565 mixin("builder." ~ optionallyRemoveTrailingUnderline!field ~ " = this." ~ field ~ ";"); 1566 } 1567 return builder; 1568 }`; 1569 1570 return result; 1571 } 1572 } 1573 1574 public template ConstructorField(Type_, bool useDefault_, alias fieldDefault_, attributes_...) 1575 { 1576 public alias Type = Type_; 1577 public enum useDefault = useDefault_; 1578 public alias fieldDefault = fieldDefault_; 1579 public alias attributes = attributes_; 1580 } 1581 1582 public template saveConstructorInfo(string[] fields_, Fields...) 1583 // if (fields_.length == Fields.length 1584 // && allSatisfy!(ApplyLeft!(isInstanceOf, ConstructorField), Fields)) 1585 { 1586 import std.format : format; 1587 1588 public enum fields = fields_; 1589 1590 private template FieldInfo_() { 1591 static foreach (i, field; fields) 1592 { 1593 mixin(format!q{public alias %s = Fields[%s];}(field, i)); 1594 } 1595 } 1596 1597 public alias FieldInfo = FieldInfo_!(); 1598 1599 mixin( 1600 format!q{public alias Types = AliasSeq!(%-(%s, %)); } 1601 (fields.map!(field => format!"FieldInfo.%s.Type"(field)).array)); 1602 } 1603 1604 enum ThisEnum 1605 { 1606 Private, 1607 Protected, 1608 Exclude 1609 } 1610 1611 struct This 1612 { 1613 enum Private = ThisEnum.Private; 1614 enum Protected = ThisEnum.Protected; 1615 struct Package 1616 { 1617 string packageMask = null; 1618 } 1619 enum Exclude = ThisEnum.Exclude; 1620 1621 // construct with value 1622 static struct Init(alias Alias) 1623 { 1624 static if (__traits(compiles, Alias())) 1625 { 1626 @property static auto value() { return Alias(); } 1627 } 1628 else 1629 { 1630 alias value = Alias; 1631 } 1632 } 1633 1634 static struct Default(alias Alias) 1635 { 1636 static if (__traits(compiles, Alias())) 1637 { 1638 @property static auto value() { return Alias(); } 1639 } 1640 else 1641 { 1642 alias value = Alias; 1643 } 1644 } 1645 } 1646 1647 public template getUDADefaultOrNothing(T, attributes...) 1648 { 1649 import boilerplate.util : udaIndex; 1650 1651 template EnumTest() 1652 { 1653 enum EnumTest = attributes[udaIndex!(This.Default, attributes)].value; 1654 } 1655 1656 static if (udaIndex!(This.Default, attributes) == -1) 1657 { 1658 enum getUDADefaultOrNothing = 0; 1659 } 1660 // @(This.Default) 1661 else static if (__traits(isSame, attributes[udaIndex!(This.Default, attributes)], This.Default)) 1662 { 1663 enum getUDADefaultOrNothing = T.init; 1664 } 1665 else static if (__traits(compiles, EnumTest!())) 1666 { 1667 enum getUDADefaultOrNothing = attributes[udaIndex!(This.Default, attributes)].value; 1668 } 1669 else 1670 { 1671 @property static auto getUDADefaultOrNothing() 1672 { 1673 return attributes[udaIndex!(This.Default, attributes)].value; 1674 } 1675 } 1676 } 1677 1678 @("struct with only default fields cannot use GenerateThis unless the default this() type matches the generated one") 1679 unittest 1680 { 1681 static assert(!__traits(compiles, { 1682 struct Foo 1683 { 1684 @(This.Default) 1685 int[] array; 1686 1687 mixin(GenerateThis); 1688 } 1689 1690 // because you would be able to do 1691 // const array = [2]; 1692 // auto foo = Foo(array); 1693 // which would be an error, but work with a generated constructor 1694 // however, no constructor could be generated, as it would collide with this() 1695 })); 1696 1697 // This works though. 1698 struct Bar 1699 { 1700 @(This.Default) 1701 const int[] array; 1702 1703 mixin(GenerateThis); 1704 } 1705 1706 const array = [2]; 1707 auto bar = Bar(array); 1708 } 1709 1710 @("very large types can be used") 1711 unittest 1712 { 1713 import std.format : format; 1714 import std.range : iota; 1715 1716 struct VeryLargeType 1717 { 1718 static foreach (i; 500.iota) 1719 { 1720 mixin(format!"int v%s;"(i)); 1721 } 1722 1723 mixin(GenerateThis); 1724 } 1725 1726 struct Wrapper 1727 { 1728 VeryLargeType field; 1729 1730 mixin(GenerateThis); 1731 } 1732 1733 auto builder = Wrapper.Builder(); 1734 } 1735 1736 @("const nullable assignment") 1737 unittest 1738 { 1739 import std.typecons : Nullable; 1740 1741 // non-reference type 1742 struct Foo 1743 { 1744 } 1745 1746 struct Bar 1747 { 1748 Nullable!Foo foo; 1749 1750 mixin(GenerateThis); 1751 } 1752 1753 auto builder = Bar.Builder(); 1754 1755 // trigger assignment bug where dmd tries to roundtrip over const(Foo), implicitly triggering .get 1756 // avoided by additional assignment overload in the Nullable case 1757 builder.foo = Nullable!(const Foo)(); 1758 } 1759 1760 // can't strip const, because int[] is a reference type and precludes it 1761 @("const nullable assignment with reference type") 1762 unittest 1763 { 1764 import std.typecons : Nullable, nullable; 1765 1766 struct Foo 1767 { 1768 int[] reference; 1769 } 1770 1771 struct Bar 1772 { 1773 Nullable!Foo foo; 1774 1775 mixin(GenerateThis); 1776 } 1777 1778 auto builder = Bar.Builder(); 1779 1780 int[] array = [2]; 1781 auto foo = Foo(array); 1782 1783 // direct assignment still works 1784 static assert(__traits(compiles, { builder.foo = foo.nullable; })); 1785 // but const assignment is blocked by opAssign(U) 1786 static assert(!__traits(compiles, { builder.foo = (cast(const) foo).nullable; })); 1787 } 1788 1789 @("nullable null assignment to buildable field") 1790 unittest 1791 { 1792 import std.typecons : Nullable; 1793 1794 struct Foo 1795 { 1796 mixin(GenerateThis); 1797 } 1798 1799 struct Bar 1800 { 1801 Nullable!Foo foo; 1802 1803 mixin(GenerateThis); 1804 } 1805 1806 auto builder = Bar.Builder(); 1807 1808 builder.foo = Nullable!Foo(); 1809 1810 builder.value.shouldEqual(Bar(Nullable!Foo())); 1811 } 1812 1813 // helper to avoid lambda, std.algorithm use in heavily-reused mixin GenerateThisTemplate 1814 public string[] filterCanFind(string[] array, string[] other) 1815 { 1816 import std.algorithm : canFind, filter; 1817 1818 return array.filter!(a => other.canFind(a)).array; 1819 } 1820 1821 // ditto 1822 public string[] mapFormat(string fmt, Range, T...)(Range range, T args) 1823 { 1824 import std.algorithm : map; 1825 import std.format : format; 1826 import std.range : iota, join; 1827 1828 enum argElements = T.length.iota.map!(k => format!"args[%s][i]"(k)).join(", "); 1829 1830 return range.map!((i) { 1831 return mixin("format!fmt(" ~ argElements ~ ")"); 1832 }).array; 1833 }