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