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 @("builder supports implicit nullable conversion") 1233 unittest 1234 { 1235 import std.typecons : Nullable, nullable; 1236 1237 immutable struct Struct 1238 { 1239 Nullable!int a; 1240 1241 mixin(GenerateThis); 1242 } 1243 1244 auto builder = Struct.Builder(); 1245 immutable int i; 1246 1247 builder.a = i.nullable; 1248 } 1249 1250 @("struct constructor with type substitution") 1251 unittest 1252 { 1253 import std.typecons : Nullable, nullable; 1254 1255 immutable struct Struct 1256 { 1257 @(This.As!int) 1258 Nullable!int a; 1259 1260 mixin(GenerateThis); 1261 } 1262 1263 Struct(5).a.shouldEqual(Nullable!int(5)); 1264 } 1265 1266 @("class constructor with type substitution") 1267 unittest 1268 { 1269 import std.typecons : Nullable, nullable; 1270 1271 class Class 1272 { 1273 @(This.As!int) 1274 Nullable!int a; 1275 1276 mixin(GenerateThis); 1277 } 1278 1279 (new Class(5)).a.shouldEqual(Nullable!int(5)); 1280 } 1281 1282 import std.string : format; 1283 1284 mixin template GenerateThisTemplate() 1285 { 1286 private static generateThisImpl() 1287 { 1288 if (!__ctfe) 1289 { 1290 return null; 1291 } 1292 1293 import boilerplate.constructor : filterCanFind, mapFormat, This; 1294 import boilerplate.util : 1295 bucketSort, GenNormalMemberTuple, needToDup, 1296 optionallyRemoveTrailingUnderline, 1297 removeTrailingUnderline, reorder, udaIndex; 1298 import std.algorithm : all, canFind, filter, map; 1299 import std.conv : to; 1300 import std.meta : Alias, aliasSeqOf, staticMap; 1301 import std.range : array, drop, empty, iota, zip; 1302 import std.string : endsWith, format, join; 1303 import std.traits : getUDAs; 1304 import std.typecons : Nullable; 1305 1306 mixin GenNormalMemberTuple; 1307 1308 string result = `import std.traits : getUDAs;`; 1309 1310 string visibility = "public"; 1311 1312 foreach (uda; __traits(getAttributes, typeof(this))) 1313 { 1314 static if (is(typeof(uda) == ThisEnum)) 1315 { 1316 static if (uda == This.Protected) 1317 { 1318 visibility = "protected"; 1319 } 1320 static if (uda == This.Private) 1321 { 1322 visibility = "private"; 1323 } 1324 } 1325 else static if (is(uda == This.Package)) 1326 { 1327 visibility = "package"; 1328 } 1329 else static if (is(typeof(uda) == This.Package)) 1330 { 1331 visibility = "package(" ~ uda.packageMask ~ ")"; 1332 } 1333 } 1334 1335 string[] constructorAttributes = ["pure", "nothrow", "@safe", "@nogc"]; 1336 1337 static if (is(typeof(typeof(super).ConstructorInfo))) 1338 { 1339 enum argsPassedToSuper = typeof(super).ConstructorInfo.fields.length; 1340 enum members = typeof(super).ConstructorInfo.fields ~ [NormalMemberTuple]; 1341 1342 constructorAttributes = typeof(super).GeneratedConstructorAttributes_; 1343 } 1344 else 1345 { 1346 enum argsPassedToSuper = 0; 1347 static if (NormalMemberTuple.length > 0) 1348 { 1349 enum members = [NormalMemberTuple]; 1350 } 1351 else 1352 { 1353 enum string[] members = null; 1354 } 1355 } 1356 1357 string[] fields; 1358 string[] args; 1359 string[] argexprs; 1360 string[] defaultAssignments; 1361 bool[] fieldUseDefault; 1362 string[] fieldDefault; 1363 string[] fieldAttributes; 1364 string[] types; 1365 string[] directInitFields; 1366 int[] directInitIndex; 1367 bool[] directInitUseSelf; 1368 1369 foreach (i; aliasSeqOf!(members.length.iota)) 1370 { 1371 enum member = members[i]; 1372 alias attributes = __traits(getAttributes, __traits(getMember, typeof(this), member)); 1373 1374 static if (i < argsPassedToSuper) 1375 { 1376 enum bool useDefault = __traits(getMember, typeof(super).ConstructorInfo.FieldInfo, member).useDefault; 1377 enum string memberTypeAsString = "typeof(super).ConstructorInfo.FieldInfo." ~ member ~ ".Type"; 1378 enum string default_ = "typeof(super).ConstructorInfo.FieldInfo." ~ member ~ ".fieldDefault"; 1379 enum string attributesStr = "typeof(super).ConstructorInfo.FieldInfo." ~ member ~ ".attributes"; 1380 } 1381 else 1382 { 1383 static if (udaIndex!(This.As, attributes) != -1) 1384 { 1385 enum string memberTypeAsString = "getUDAs!(this." ~ member ~ ", This.As)[0].Type"; 1386 } 1387 else 1388 { 1389 enum string memberTypeAsString = "typeof(this." ~ member ~ ")"; 1390 } 1391 enum bool useDefault = udaIndex!(This.Default, attributes) != -1; 1392 enum string default_ = "getUDADefaultOrNothing!(typeof(this." ~ member ~ ")," 1393 ~ " __traits(getAttributes, this." ~ member ~ "))"; 1394 enum string attributesStr = "__traits(getAttributes, this." ~ member ~ ")"; 1395 } 1396 1397 mixin(`alias Type = ` ~ memberTypeAsString ~ `;`); 1398 1399 bool includeMember = false; 1400 1401 enum isNullable = is(Type: Nullable!Arg, Arg); 1402 1403 static if (!isNullable) 1404 { 1405 enum bool dupExpr = needToDup!Type; 1406 enum bool passExprAsConst = dupExpr && __traits(compiles, { Type value = const(Type).init.dup; }); 1407 } 1408 else 1409 { 1410 // unpack nullable for dup 1411 enum bool dupExpr = needToDup!(typeof(Type.init.get)); 1412 enum bool passExprAsConst = dupExpr && __traits(compiles, { Type value = const(Type).init.get.dup; }); 1413 } 1414 1415 enum scopeAttributes = [__traits(getFunctionAttributes, { 1416 static if (passExprAsConst) { const Type parameter = Type.init; } 1417 else { Type parameter = Type.init; } 1418 1419 static if (isNullable) { auto value = parameter.get; } 1420 else { auto value = parameter; } 1421 1422 static if (dupExpr) 1423 { 1424 Type dupped = value.dup; 1425 } 1426 })]; 1427 constructorAttributes = constructorAttributes.filterCanFind(scopeAttributes); 1428 1429 bool forSuper = false; 1430 1431 static if (i < argsPassedToSuper) 1432 { 1433 includeMember = true; 1434 forSuper = true; 1435 } 1436 else 1437 { 1438 mixin("alias symbol = typeof(this)." ~ member ~ ";"); 1439 1440 static assert (is(typeof(symbol)) && !__traits(isTemplate, symbol)); /* must have a resolvable type */ 1441 1442 import boilerplate.util: isStatic; 1443 1444 includeMember = !mixin(isStatic(member)); 1445 1446 static if (udaIndex!(This.Init, __traits(getAttributes, symbol)) != -1) 1447 { 1448 enum udaFieldIndex = udaIndex!(This.Init, __traits(getAttributes, symbol)); 1449 alias initArg = Alias!(__traits(getAttributes, symbol)[udaFieldIndex].value); 1450 enum lambdaWithSelf = __traits(compiles, initArg(typeof(this).init)); 1451 enum nakedLambda = __traits(compiles, initArg()); 1452 1453 directInitFields ~= member; 1454 directInitIndex ~= udaFieldIndex; 1455 directInitUseSelf ~= __traits(compiles, 1456 __traits(getAttributes, symbol)[udaFieldIndex].value(typeof(this).init)); 1457 includeMember = false; 1458 1459 static if (lambdaWithSelf) 1460 { 1461 static if (__traits(compiles, initArg!(typeof(this)))) 1462 { 1463 enum lambdaAttributes = [__traits(getFunctionAttributes, initArg!(typeof(this)))]; 1464 } 1465 else 1466 { 1467 enum lambdaAttributes = [__traits(getFunctionAttributes, initArg)]; 1468 } 1469 constructorAttributes = constructorAttributes.filterCanFind(lambdaAttributes); 1470 } 1471 else static if (nakedLambda) 1472 { 1473 enum lambdaAttributes = [__traits(getFunctionAttributes, initArg)]; 1474 1475 constructorAttributes = constructorAttributes.filterCanFind(lambdaAttributes); 1476 } 1477 } 1478 1479 static if (udaIndex!(This.Exclude, __traits(getAttributes, symbol)) != -1) 1480 { 1481 includeMember = false; 1482 } 1483 } 1484 1485 if (!includeMember) continue; 1486 1487 enum paramName = optionallyRemoveTrailingUnderline!member; 1488 1489 string argexpr = paramName; 1490 1491 if (dupExpr) 1492 { 1493 constructorAttributes = constructorAttributes.filter!(q{a != "@nogc"}).array; 1494 1495 static if (isNullable) 1496 { 1497 argexpr = format!`%s.isNull ? %s.init : %s(%s.get.dup)` 1498 (argexpr, memberTypeAsString, memberTypeAsString, argexpr); 1499 } 1500 else 1501 { 1502 argexpr = argexpr ~ ".dup"; 1503 } 1504 } 1505 1506 fields ~= member; 1507 args ~= paramName; 1508 argexprs ~= argexpr; 1509 fieldUseDefault ~= useDefault; 1510 fieldDefault ~= default_; 1511 fieldAttributes ~= attributesStr; 1512 defaultAssignments ~= useDefault ? (` = ` ~ default_) : ``; 1513 types ~= passExprAsConst ? (`const ` ~ memberTypeAsString) : memberTypeAsString; 1514 } 1515 1516 size_t establishParameterRank(size_t i) 1517 { 1518 // parent explicit, our explicit, our implicit, parent implicit 1519 const fieldOfParent = i < argsPassedToSuper; 1520 return fieldUseDefault[i] * 2 + (fieldUseDefault[i] == fieldOfParent); 1521 } 1522 1523 auto constructorFieldOrder = fields.length.iota.array.bucketSort(&establishParameterRank); 1524 1525 assert(fields.length == types.length); 1526 assert(fields.length == fieldUseDefault.length); 1527 assert(fields.length == fieldDefault.length); 1528 1529 result ~= format!` 1530 public static alias ConstructorInfo = 1531 saveConstructorInfo!(%s, %-(%s, %));` 1532 ( 1533 fields.reorder(constructorFieldOrder), 1534 zip( 1535 types.reorder(constructorFieldOrder), 1536 fieldUseDefault.reorder(constructorFieldOrder), 1537 fieldDefault.reorder(constructorFieldOrder), 1538 fieldAttributes.reorder(constructorFieldOrder), 1539 ) 1540 .map!(q{format!`ConstructorField!(%s, %s, %s, %s)`(a[0], a[1], a[2], a[3])}) 1541 .array 1542 ); 1543 1544 // don't emit this(a = b, c = d) for structs - 1545 // the compiler complains that it collides with this(), which is reserved. 1546 if (is(typeof(this) == struct) && fieldUseDefault.all) 1547 { 1548 // If there are fields, their direct-construction types may diverge from ours 1549 // specifically, see the "struct with only default fields" test below 1550 if (!fields.empty) 1551 { 1552 result ~= `static assert( 1553 is(typeof(this.tupleof) == ConstructorInfo.Types), 1554 "Structs with fields, that are all default, cannot use GenerateThis when their " ~ 1555 "constructor types would diverge from their native types: " ~ 1556 typeof(this).stringof ~ ".this" ~ typeof(this.tupleof).stringof ~ ", " ~ 1557 "but generated constructor would have been " ~ typeof(this).stringof ~ ".this" 1558 ~ ConstructorInfo.Types.stringof 1559 );`; 1560 } 1561 } 1562 else 1563 { 1564 result ~= visibility ~ ` this(` 1565 ~ constructorFieldOrder.mapFormat!`%s %s%s`(types, args, defaultAssignments).join(`, `) 1566 ~ format!`) %-(%s %)`(constructorAttributes); 1567 1568 result ~= `{`; 1569 1570 static if (is(typeof(typeof(super).ConstructorInfo))) 1571 { 1572 result ~= `super(` ~ args[0 .. argsPassedToSuper].join(", ") ~ `);`; 1573 } 1574 1575 result ~= fields.length.iota.drop(argsPassedToSuper).mapFormat!`this.%s = %s;`(fields, argexprs).join; 1576 1577 foreach (i, field; directInitFields) 1578 { 1579 if (directInitUseSelf[i]) 1580 { 1581 result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value(this);` 1582 (field, field, directInitIndex[i]); 1583 } 1584 else 1585 { 1586 result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value;` 1587 (field, field, directInitIndex[i]); 1588 } 1589 } 1590 1591 result ~= `}`; 1592 1593 result ~= `protected static enum string[] GeneratedConstructorAttributes_ = [` 1594 ~ constructorAttributes.map!(q{`"` ~ a ~ `"`}).join(`, `) 1595 ~ `];`; 1596 } 1597 1598 result ~= visibility ~ ` static struct BuilderType(alias T = typeof(this)) 1599 { 1600 import boilerplate.builder : BuilderImpl; 1601 1602 mixin BuilderImpl!T; 1603 }`; 1604 1605 result ~= visibility ~ ` static auto Builder()() 1606 { 1607 return BuilderType!()(); 1608 }`; 1609 1610 /** 1611 * We are allowed to read the private field values here. 1612 * We aren't actually leaking private or mutable information because: 1613 * - the constructor will dup it again anyways, if required 1614 * - we cannot read it from the Builder, because Builders are write-only 1615 * - if we can't read it off the current value, the builderValue will 1616 * have the same type - so we can't read it off there either! 1617 */ 1618 result ~= visibility ~ ` auto BuilderFrom(this This)() 1619 { 1620 import boilerplate.util : optionallyRemoveTrailingUnderline; 1621 1622 auto builder = BuilderType!()(); 1623 1624 static foreach (field; ConstructorInfo.fields) 1625 { 1626 mixin("builder." ~ optionallyRemoveTrailingUnderline!field ~ " = this." ~ field ~ ";"); 1627 } 1628 return builder; 1629 }`; 1630 1631 return result; 1632 } 1633 } 1634 1635 public template ConstructorField(Type_, bool useDefault_, alias fieldDefault_, attributes_...) 1636 { 1637 public alias Type = Type_; 1638 public enum useDefault = useDefault_; 1639 public alias fieldDefault = fieldDefault_; 1640 public alias attributes = attributes_; 1641 } 1642 1643 public template saveConstructorInfo(string[] fields_, Fields...) 1644 // if (fields_.length == Fields.length 1645 // && allSatisfy!(ApplyLeft!(isInstanceOf, ConstructorField), Fields)) 1646 { 1647 import std.format : format; 1648 1649 public enum fields = fields_; 1650 1651 private template FieldInfo_() { 1652 static foreach (i, field; fields) 1653 { 1654 mixin(format!q{public alias %s = Fields[%s];}(field, i)); 1655 } 1656 } 1657 1658 public alias FieldInfo = FieldInfo_!(); 1659 1660 mixin( 1661 format!q{public alias Types = AliasSeq!(%-(%s, %)); } 1662 (fields.map!(field => format!"FieldInfo.%s.Type"(field)).array)); 1663 } 1664 1665 enum ThisEnum 1666 { 1667 Private, 1668 Protected, 1669 Exclude 1670 } 1671 1672 struct This 1673 { 1674 enum Private = ThisEnum.Private; 1675 enum Protected = ThisEnum.Protected; 1676 struct Package 1677 { 1678 string packageMask = null; 1679 } 1680 enum Exclude = ThisEnum.Exclude; 1681 1682 // construct with value 1683 static struct Init(alias Alias) 1684 { 1685 static if (__traits(compiles, Alias())) 1686 { 1687 @property static auto value() { return Alias(); } 1688 } 1689 else 1690 { 1691 alias value = Alias; 1692 } 1693 } 1694 1695 static struct Default(alias Alias) 1696 { 1697 static if (__traits(compiles, Alias())) 1698 { 1699 @property static auto value() { return Alias(); } 1700 } 1701 else 1702 { 1703 alias value = Alias; 1704 } 1705 } 1706 1707 static struct As(Type_) 1708 { 1709 alias Type = Type_; 1710 } 1711 } 1712 1713 public template getUDADefaultOrNothing(T, attributes...) 1714 { 1715 import boilerplate.util : udaIndex; 1716 1717 template EnumTest() 1718 { 1719 enum EnumTest = attributes[udaIndex!(This.Default, attributes)].value; 1720 } 1721 1722 static if (udaIndex!(This.Default, attributes) == -1) 1723 { 1724 enum getUDADefaultOrNothing = 0; 1725 } 1726 // @(This.Default) 1727 else static if (__traits(isSame, attributes[udaIndex!(This.Default, attributes)], This.Default)) 1728 { 1729 enum getUDADefaultOrNothing = T.init; 1730 } 1731 else static if (__traits(compiles, EnumTest!())) 1732 { 1733 enum getUDADefaultOrNothing = attributes[udaIndex!(This.Default, attributes)].value; 1734 } 1735 else 1736 { 1737 @property static auto getUDADefaultOrNothing() 1738 { 1739 return attributes[udaIndex!(This.Default, attributes)].value; 1740 } 1741 } 1742 } 1743 1744 @("struct with only default fields cannot use GenerateThis unless the default this() type matches the generated one") 1745 unittest 1746 { 1747 static assert(!__traits(compiles, { 1748 struct Foo 1749 { 1750 @(This.Default) 1751 int[] array; 1752 1753 mixin(GenerateThis); 1754 } 1755 1756 // because you would be able to do 1757 // const array = [2]; 1758 // auto foo = Foo(array); 1759 // which would be an error, but work with a generated constructor 1760 // however, no constructor could be generated, as it would collide with this() 1761 })); 1762 1763 // This works though. 1764 struct Bar 1765 { 1766 @(This.Default) 1767 const int[] array; 1768 1769 mixin(GenerateThis); 1770 } 1771 1772 const array = [2]; 1773 auto bar = Bar(array); 1774 } 1775 1776 @("very large types can be used") 1777 unittest 1778 { 1779 import std.format : format; 1780 import std.range : iota; 1781 1782 struct VeryLargeType 1783 { 1784 static foreach (i; 500.iota) 1785 { 1786 mixin(format!"int v%s;"(i)); 1787 } 1788 1789 mixin(GenerateThis); 1790 } 1791 1792 struct Wrapper 1793 { 1794 VeryLargeType field; 1795 1796 mixin(GenerateThis); 1797 } 1798 1799 auto builder = Wrapper.Builder(); 1800 } 1801 1802 @("const nullable assignment") 1803 unittest 1804 { 1805 import std.typecons : Nullable; 1806 1807 // non-reference type 1808 struct Foo 1809 { 1810 } 1811 1812 struct Bar 1813 { 1814 Nullable!Foo foo; 1815 1816 mixin(GenerateThis); 1817 } 1818 1819 auto builder = Bar.Builder(); 1820 1821 // trigger assignment bug where dmd tries to roundtrip over const(Foo), implicitly triggering .get 1822 // avoided by additional assignment overload in the Nullable case 1823 builder.foo = Nullable!(const Foo)(); 1824 } 1825 1826 // can't strip const, because int[] is a reference type and precludes it 1827 @("const nullable assignment with reference type") 1828 unittest 1829 { 1830 import std.typecons : Nullable, nullable; 1831 1832 struct Foo 1833 { 1834 int[] reference; 1835 } 1836 1837 struct Bar 1838 { 1839 Nullable!Foo foo; 1840 1841 mixin(GenerateThis); 1842 } 1843 1844 auto builder = Bar.Builder(); 1845 1846 int[] array = [2]; 1847 auto foo = Foo(array); 1848 1849 // direct assignment still works 1850 static assert(__traits(compiles, { builder.foo = foo.nullable; })); 1851 // but const assignment is blocked by opAssign(U) 1852 static assert(!__traits(compiles, { builder.foo = (cast(const) foo).nullable; })); 1853 } 1854 1855 @("nullable null assignment to buildable field") 1856 unittest 1857 { 1858 import std.typecons : Nullable; 1859 1860 struct Foo 1861 { 1862 mixin(GenerateThis); 1863 } 1864 1865 struct Bar 1866 { 1867 Nullable!Foo foo; 1868 1869 mixin(GenerateThis); 1870 } 1871 1872 auto builder = Bar.Builder(); 1873 1874 builder.foo = Nullable!Foo(); 1875 1876 builder.value.shouldEqual(Bar(Nullable!Foo())); 1877 } 1878 1879 @("@safe generated class constructor") 1880 unittest 1881 { 1882 class Foo 1883 { 1884 pure: 1885 @safe: 1886 1887 int i; 1888 1889 mixin(GenerateThis); 1890 } 1891 } 1892 1893 // helper to avoid lambda, std.algorithm use in heavily-reused mixin GenerateThisTemplate 1894 public string[] filterCanFind(string[] array, string[] other) nothrow pure @safe 1895 { 1896 import std.algorithm : canFind, filter; 1897 1898 return array.filter!(a => other.canFind(a)).array; 1899 } 1900 1901 // ditto 1902 public string[] mapFormat(string fmt, Range, T...)(Range range, T args) 1903 { 1904 import std.algorithm : map; 1905 import std.format : format; 1906 import std.range : iota, join; 1907 1908 enum argElements = T.length.iota.map!(k => format!"args[%s][i]"(k)).join(", "); 1909 1910 return range.map!((i) { 1911 return mixin("format!fmt(" ~ argElements ~ ")"); 1912 }).array; 1913 }