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