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