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