1 module boilerplate.constructor;
2 
3 import std.algorithm : canFind, map;
4 import std.meta : ApplyLeft, allSatisfy;
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: GenerateThisTemplate;
19     import std.string : replace;
20     mixin GenerateThisTemplate;
21     mixin(typeof(this).generateThisImpl());
22 `;
23 
24 ///
25 @("creates a constructor")
26 unittest
27 {
28     class Class
29     {
30         int field;
31 
32         mixin(GenerateThis);
33     }
34 
35     auto obj = new Class(5);
36 
37     obj.field.shouldEqual(5);
38 }
39 
40 ///
41 @("calls the super constructor if it exists")
42 unittest
43 {
44     class Class
45     {
46         int field;
47 
48         mixin(GenerateThis);
49     }
50 
51     class Child : Class
52     {
53         int field2;
54 
55         mixin(GenerateThis);
56     }
57 
58     auto obj = new Child(5, 8);
59 
60     obj.field.shouldEqual(5);
61     obj.field2.shouldEqual(8);
62 }
63 
64 ///
65 @("separates fields from methods")
66 unittest
67 {
68     class Class
69     {
70         int field;
71 
72         void method() { }
73 
74         mixin(GenerateThis);
75     }
76 
77     auto obj = new Class(5);
78 
79     obj.field.shouldEqual(5);
80 }
81 
82 ///
83 @("dups arrays")
84 unittest
85 {
86     class Class
87     {
88         int[] array;
89 
90         mixin(GenerateThis);
91     }
92 
93     auto array = [2, 3, 4];
94     auto obj = new Class(array);
95 
96     array[0] = 1;
97     obj.array[0].shouldEqual(2);
98 }
99 
100 ///
101 @("dups arrays hidden behind Nullable")
102 unittest
103 {
104     import std.typecons : Nullable, nullable;
105 
106     class Class
107     {
108         Nullable!(int[]) array;
109 
110         mixin(GenerateThis);
111     }
112 
113     auto array = [2, 3, 4];
114     auto obj = new Class(array.nullable);
115 
116     array[0] = 1;
117     obj.array.get[0].shouldEqual(2);
118 
119     obj = new Class(Nullable!(int[]).init);
120     obj.array.isNull.shouldBeTrue;
121 }
122 
123 ///
124 @("uses default value for default constructor parameter")
125 unittest
126 {
127     class Class
128     {
129         @(This.Default!5)
130         int value = 5;
131 
132         mixin(GenerateThis);
133     }
134 
135     auto obj1 = new Class();
136 
137     obj1.value.shouldEqual(5);
138 
139     auto obj2 = new Class(6);
140 
141     obj2.value.shouldEqual(6);
142 }
143 
144 ///
145 @("creates no constructor for an empty struct")
146 unittest
147 {
148     struct Struct
149     {
150         mixin(GenerateThis);
151     }
152 
153     auto strct = Struct();
154 }
155 
156 ///
157 @("properly generates new default values on each call")
158 unittest
159 {
160     import std.conv : to;
161 
162     class Class
163     {
164         @(This.Default!(() => new Object))
165         Object obj;
166 
167         mixin(GenerateThis);
168     }
169 
170     auto obj1 = new Class();
171     auto obj2 = new Class();
172 
173     (cast(void*) obj1.obj).shouldNotEqual(cast(void*) obj2.obj);
174 }
175 
176 ///
177 @("establishes the parent-child parameter order: parent explicit, child explicit, child implicit, parent implicit.")
178 unittest
179 {
180     class Parent
181     {
182         int field1;
183 
184         @(This.Default!2)
185         int field2 = 2;
186 
187         mixin(GenerateThis);
188     }
189 
190     class Child : Parent
191     {
192         int field3;
193 
194         @(This.Default!4)
195         int field4 = 4;
196 
197         mixin(GenerateThis);
198     }
199 
200     auto obj = new Child(1, 2, 3, 4);
201 
202     obj.field1.shouldEqual(1);
203     obj.field3.shouldEqual(2);
204     obj.field4.shouldEqual(3);
205     obj.field2.shouldEqual(4);
206 }
207 
208 ///
209 @("disregards static fields")
210 unittest
211 {
212     class Class
213     {
214         static int field1;
215         int field2;
216 
217         mixin(GenerateThis);
218     }
219 
220     auto obj = new Class(5);
221 
222     obj.field1.shouldEqual(0);
223     obj.field2.shouldEqual(5);
224 }
225 
226 ///
227 @("can initialize with immutable arrays")
228 unittest
229 {
230     class Class
231     {
232         immutable(Object)[] array;
233 
234         mixin(GenerateThis);
235     }
236 }
237 
238 ///
239 @("can define scope for constructor")
240 unittest
241 {
242     @(This.Private)
243     class PrivateClass
244     {
245         mixin(GenerateThis);
246     }
247 
248     @(This.Protected)
249     class ProtectedClass
250     {
251         mixin(GenerateThis);
252     }
253 
254     @(This.Package)
255     class PackageClass
256     {
257         mixin(GenerateThis);
258     }
259 
260     @(This.Package("boilerplate"))
261     class SubPackageClass
262     {
263         mixin(GenerateThis);
264     }
265 
266     class PublicClass
267     {
268         mixin(GenerateThis);
269     }
270 
271     static assert(__traits(getProtection, PrivateClass.__ctor) == "private");
272     static assert(__traits(getProtection, ProtectedClass.__ctor) == "protected");
273     static assert(__traits(getProtection, PackageClass.__ctor) == "package");
274     // getProtection does not return the package name of a package() attribute
275     // static assert(__traits(getProtection, SubPackageClass.__ctor) == `package(boilerplate)`);
276     static assert(__traits(getProtection, PublicClass.__ctor) == "public");
277 }
278 
279 ///
280 @("will assign the same scope to Builder")
281 unittest
282 {
283     @(This.Private)
284     class PrivateClass
285     {
286         mixin(GenerateThis);
287     }
288 
289     @(This.Protected)
290     class ProtectedClass
291     {
292         mixin(GenerateThis);
293     }
294 
295     @(This.Package)
296     class PackageClass
297     {
298         mixin(GenerateThis);
299     }
300 
301     @(This.Package("boilerplate"))
302     class SubPackageClass
303     {
304         mixin(GenerateThis);
305     }
306 
307     class PublicClass
308     {
309         mixin(GenerateThis);
310     }
311 
312     static assert(__traits(getProtection, PrivateClass.Builder) == "private");
313     static assert(__traits(getProtection, ProtectedClass.Builder) == "protected");
314     static assert(__traits(getProtection, PackageClass.Builder) == "package");
315     static assert(__traits(getProtection, PublicClass.Builder) == "public");
316 }
317 
318 ///
319 @("can use default tag with new")
320 unittest
321 {
322     class Class
323     {
324         @(This.Default!(() => new Object))
325         Object foo;
326 
327         mixin(GenerateThis);
328     }
329 
330     ((new Class).foo !is null).shouldBeTrue;
331 }
332 
333 ///
334 @("empty default tag means T()")
335 unittest
336 {
337     class Class
338     {
339         @(This.Default)
340         string s;
341 
342         @(This.Default)
343         int i;
344 
345         mixin(GenerateThis);
346     }
347 
348     (new Class()).i.shouldEqual(0);
349     (new Class()).s.shouldEqual(null);
350 }
351 
352 ///
353 @("can exclude fields from constructor")
354 unittest
355 {
356     class Class
357     {
358         @(This.Exclude)
359         int i = 5;
360 
361         mixin(GenerateThis);
362     }
363 
364     (new Class).i.shouldEqual(5);
365 }
366 
367 ///
368 @("marks duppy parameters as const when this does not prevent dupping")
369 unittest
370 {
371 
372     struct Struct
373     {
374     }
375 
376     class Class
377     {
378         Struct[] values_;
379 
380         mixin(GenerateThis);
381     }
382 
383     const Struct[] constValues;
384     auto obj = new Class(constValues);
385 }
386 
387 ///
388 @("does not include property functions in constructor list")
389 unittest
390 {
391     class Class
392     {
393         int a;
394 
395         @property int foo() const
396         {
397             return 0;
398         }
399 
400         mixin(GenerateThis);
401     }
402 
403     static assert(__traits(compiles, new Class(0)));
404     static assert(!__traits(compiles, new Class(0, 0)));
405 }
406 
407 @("declares @nogc on non-dupping constructors")
408 @nogc unittest
409 {
410     struct Struct
411     {
412         int a;
413 
414         mixin(GenerateThis);
415     }
416 
417     auto str = Struct(5);
418 }
419 
420 ///
421 @("can initialize fields using init value")
422 unittest
423 {
424     class Class
425     {
426         @(This.Init!5)
427         int field1;
428 
429         @(This.Init!(() => 8))
430         int field2;
431 
432         mixin(GenerateThis);
433     }
434 
435     auto obj = new Class;
436 
437     obj.field1.shouldEqual(5);
438     obj.field2.shouldEqual(8);
439 }
440 
441 ///
442 @("can initialize fields using init value, with lambda that accesses previous value")
443 unittest
444 {
445     class Class
446     {
447         int field1;
448 
449         @(This.Init!(self => self.field1 + 5))
450         int field2;
451 
452         mixin(GenerateThis);
453     }
454 
455     auto obj = new Class(5);
456 
457     obj.field1.shouldEqual(5);
458     obj.field2.shouldEqual(10);
459 }
460 
461 ///
462 @("can initialize fields with allocated types")
463 unittest
464 {
465     class Class1
466     {
467         @(This.Init!(self => new Object))
468         Object object;
469 
470         mixin(GenerateThis);
471     }
472 
473     class Class2
474     {
475         @(This.Init!(() => new Object))
476         Object object;
477 
478         mixin(GenerateThis);
479     }
480 
481     class Class3 : Class2
482     {
483         mixin(GenerateThis);
484     }
485 }
486 
487 ///
488 @("generates Builder class that gathers constructor parameters, then calls constructor with them")
489 unittest
490 {
491     static class Class
492     {
493         int field1;
494         int field2;
495         int field3;
496 
497         mixin(GenerateThis);
498     }
499 
500     auto obj = {
501         with (Class.Builder())
502         {
503             field1 = 1;
504             field2 = 2;
505             field3 = 3;
506             return value;
507         }
508     }();
509 
510     with (obj)
511     {
512         field1.shouldEqual(1);
513         field2.shouldEqual(2);
514         field3.shouldEqual(3);
515     }
516 }
517 
518 ///
519 @("builder field order doesn't matter")
520 unittest
521 {
522     static class Class
523     {
524         int field1;
525         int field2;
526         int field3;
527 
528         mixin(GenerateThis);
529     }
530 
531     auto obj = {
532         with (Class.Builder())
533         {
534             field3 = 1;
535             field1 = 2;
536             field2 = 3;
537             return value;
538         }
539     }();
540 
541     with (obj)
542     {
543         field1.shouldEqual(2);
544         field2.shouldEqual(3);
545         field3.shouldEqual(1);
546     }
547 }
548 
549 ///
550 @("default fields can be left out when assigning builder")
551 unittest
552 {
553     static class Class
554     {
555         int field1;
556         @(This.Default!5)
557         int field2;
558         int field3;
559 
560         mixin(GenerateThis);
561     }
562 
563     // constructor is this(field1, field3, field2 = 5)
564     auto obj = {
565         with (Class.Builder())
566         {
567             field1 = 1;
568             field3 = 3;
569             return value;
570         }
571     }();
572 
573     with (obj)
574     {
575         field1.shouldEqual(1);
576         field2.shouldEqual(5);
577         field3.shouldEqual(3);
578     }
579 }
580 
581 ///
582 @("supports Builder in structs")
583 unittest
584 {
585     struct Struct
586     {
587         int field1;
588         int field2;
589         int field3;
590 
591         mixin(GenerateThis);
592     }
593 
594     auto value = {
595         with (Struct.Builder())
596         {
597             field1 = 1;
598             field3 = 3;
599             field2 = 5;
600             return value;
601         }
602     }();
603 
604     static assert(is(typeof(value) == Struct));
605 
606     with (value)
607     {
608         field1.shouldEqual(1);
609         field2.shouldEqual(5);
610         field3.shouldEqual(3);
611     }
612 }
613 
614 ///
615 @("builder strips trailing underlines")
616 unittest
617 {
618     struct Struct
619     {
620         private int a_;
621 
622         mixin(GenerateThis);
623     }
624 
625     auto builder = Struct.Builder();
626 
627     builder.a = 1;
628 
629     auto value = builder.value;
630 
631     value.shouldEqual(Struct(1));
632 }
633 
634 ///
635 @("builder supports nested initialization")
636 unittest
637 {
638     struct Struct1
639     {
640         int a;
641         int b;
642 
643         mixin(GenerateThis);
644     }
645 
646     struct Struct2
647     {
648         int c;
649         Struct1 struct1;
650         int d;
651 
652         mixin(GenerateThis);
653     }
654 
655     auto builder = Struct2.Builder();
656 
657     builder.struct1.a = 1;
658     builder.struct1.b = 2;
659     builder.c = 3;
660     builder.d = 4;
661 
662     auto value = builder.value;
663 
664     static assert(is(typeof(value) == Struct2));
665 
666     with (value)
667     {
668         struct1.a.shouldEqual(1);
669         struct1.b.shouldEqual(2);
670         c.shouldEqual(3);
671         d.shouldEqual(4);
672     }
673 }
674 
675 ///
676 @("builder supports defaults for nested values")
677 unittest
678 {
679     struct Struct1
680     {
681         int a;
682         int b;
683 
684         mixin(GenerateThis);
685     }
686 
687     struct Struct2
688     {
689         int c;
690         @(This.Default!(Struct1(3, 4)))
691         Struct1 struct1;
692         int d;
693 
694         mixin(GenerateThis);
695     }
696 
697     auto builder = Struct2.Builder();
698 
699     builder.c = 1;
700     builder.d = 2;
701 
702     builder.value.shouldEqual(Struct2(1, 2, Struct1(3, 4)));
703 }
704 
705 ///
706 @("builder supports direct value assignment for nested values")
707 unittest
708 {
709     struct Struct1
710     {
711         int a;
712         int b;
713 
714         mixin(GenerateThis);
715     }
716 
717     struct Struct2
718     {
719         int c;
720         Struct1 struct1;
721         int d;
722 
723         mixin(GenerateThis);
724     }
725 
726     auto builder = Struct2.Builder();
727 
728     builder.struct1 = Struct1(2, 3);
729     builder.c = 1;
730     builder.d = 4;
731 
732     builder.value.shouldEqual(Struct2(1, Struct1(2, 3), 4));
733 }
734 
735 ///
736 @("builder supports const args")
737 unittest
738 {
739     struct Struct
740     {
741         const int a;
742 
743         mixin(GenerateThis);
744     }
745 
746     with (Struct.Builder())
747     {
748         a = 5;
749 
750         value.shouldEqual(Struct(5));
751     }
752 }
753 
754 ///
755 @("builder supports fields with destructor")
756 unittest
757 {
758     static struct Struct1
759     {
760         ~this() pure @safe @nogc nothrow { }
761     }
762 
763     struct Struct2
764     {
765         Struct1 struct1;
766 
767         mixin(GenerateThis);
768     }
769 
770     with (Struct2.Builder())
771     {
772         struct1 = Struct1();
773 
774         value.shouldEqual(Struct2(Struct1()));
775     }
776 }
777 
778 ///
779 @("builder supports direct assignment to Nullables")
780 unittest
781 {
782     import std.typecons : Nullable, nullable;
783 
784     struct Struct
785     {
786         const Nullable!int a;
787 
788         mixin(GenerateThis);
789     }
790 
791     with (Struct.Builder())
792     {
793         a = 5;
794 
795         value.shouldEqual(Struct(5.nullable));
796     }
797 }
798 
799 ///
800 @("builder supports reconstruction from value")
801 unittest
802 {
803     import std.typecons : Nullable, nullable;
804 
805     struct Struct
806     {
807         private int a_;
808 
809         int[] b;
810 
811         mixin(GenerateThis);
812     }
813 
814     const originalValue = Struct(2, [3]);
815 
816     with (originalValue.BuilderFrom())
817     {
818         a = 5;
819 
820         value.shouldEqual(Struct(5, [3]));
821     }
822 }
823 
824 ///
825 @("builder supports struct that already contains a value field")
826 unittest
827 {
828     import std.typecons : Nullable, nullable;
829 
830     struct Struct
831     {
832         private int value_;
833 
834         mixin(GenerateThis);
835     }
836 
837     with (Struct.Builder())
838     {
839         value = 5;
840 
841         builderValue.shouldEqual(Struct(5));
842     }
843 }
844 
845 import std.string : format;
846 
847 enum GetSuperTypeAsString_(string member) = format!`typeof(super).ConstructorInfo.FieldInfo.%s.Type`(member);
848 
849 enum GetMemberTypeAsString_(string member) = format!`typeof(this.%s)`(member);
850 
851 enum SuperDefault_(string member) = format!`typeof(super).ConstructorInfo.FieldInfo.%s.fieldDefault`(member);
852 
853 enum MemberDefault_(string member) =
854     format!`getUDADefaultOrNothing!(typeof(this.%s), __traits(getAttributes, this.%s))`(member, member);
855 
856 enum SuperUseDefault_(string member)
857     = format!(`typeof(super).ConstructorInfo.FieldInfo.%s.useDefault`)(member);
858 
859 enum MemberUseDefault_(string member)
860     = format!(`udaIndex!(This.Default, __traits(getAttributes, this.%s)) != -1`)(member);
861 
862 enum SuperAttributes_(string member)
863     = format!(`typeof(super).ConstructorInfo.FieldInfo.%s.attributes`)(member);
864 
865 enum MemberAttributes_(string member)
866     = format!(`__traits(getAttributes, this.%s)`)(member);
867 
868 mixin template GenerateThisTemplate()
869 {
870     private static generateThisImpl()
871     {
872         if (!__ctfe)
873         {
874             return null;
875         }
876 
877         import boilerplate.constructor :
878             GetMemberTypeAsString_, GetSuperTypeAsString_,
879             MemberDefault_, SuperDefault_,
880             MemberUseDefault_, SuperUseDefault_,
881             MemberAttributes_, SuperAttributes_,
882             This;
883         import boilerplate.util : GenNormalMemberTuple, bucketSort, needToDup,
884             reorder, udaIndex, removeTrailingUnderline;
885         import std.algorithm : all, canFind, filter, map;
886         import std.meta : Alias, aliasSeqOf, staticMap;
887         import std.range : array, drop, iota, zip;
888         import std.string : endsWith, format, join;
889         import std.typecons : Nullable;
890 
891         mixin GenNormalMemberTuple;
892 
893         string result = null;
894 
895         string visibility = "public";
896 
897         foreach (uda; __traits(getAttributes, typeof(this)))
898         {
899             static if (is(typeof(uda) == ThisEnum))
900             {
901                 static if (uda == This.Protected)
902                 {
903                     visibility = "protected";
904                 }
905                 static if (uda == This.Private)
906                 {
907                     visibility = "private";
908                 }
909             }
910             else static if (is(uda == This.Package))
911             {
912                 visibility = "package";
913             }
914             else static if (is(typeof(uda) == This.Package))
915             {
916                 visibility = "package(" ~ uda.packageMask ~ ")";
917             }
918         }
919 
920         string[] constructorAttributes = ["pure", "nothrow", "@safe", "@nogc"];
921 
922         static if (is(typeof(typeof(super).ConstructorInfo)))
923         {
924             enum argsPassedToSuper = typeof(super).ConstructorInfo.fields.length;
925             enum members = typeof(super).ConstructorInfo.fields ~ [NormalMemberTuple];
926             enum string[] CombinedArray(alias SuperPred, alias MemberPred) = ([
927                 staticMap!(SuperPred, aliasSeqOf!(typeof(super).ConstructorInfo.fields)),
928                 staticMap!(MemberPred, NormalMemberTuple)
929             ]);
930             constructorAttributes = typeof(super).GeneratedConstructorAttributes_;
931         }
932         else
933         {
934             enum argsPassedToSuper = 0;
935             static if (NormalMemberTuple.length > 0)
936             {
937                 enum members = [NormalMemberTuple];
938                 enum string[] CombinedArray(alias SuperPred, alias MemberPred) = ([
939                     staticMap!(MemberPred, NormalMemberTuple)
940                 ]);
941             }
942             else
943             {
944                 enum string[] members = null;
945                 enum string[] CombinedArray(alias SuperPred, alias MemberPred) = null;
946             }
947         }
948 
949         enum string[] useDefaults = CombinedArray!(SuperUseDefault_, MemberUseDefault_);
950         enum string[] memberTypes = CombinedArray!(GetSuperTypeAsString_, GetMemberTypeAsString_);
951         enum string[] defaults = CombinedArray!(SuperDefault_, MemberDefault_);
952         enum string[] attributes = CombinedArray!(SuperAttributes_, MemberAttributes_);
953 
954         string[] fields;
955         string[] args;
956         string[] argexprs;
957         string[] defaultAssignments;
958         bool[] fieldUseDefault;
959         string[] fieldDefault;
960         string[] fieldAttributes;
961         string[] types;
962         string[] directInitFields;
963         int[] directInitIndex;
964         bool[] directInitUseSelf;
965 
966         foreach (i; aliasSeqOf!(members.length.iota))
967         {
968             enum member = members[i];
969 
970             mixin(`alias Type = ` ~ memberTypes[i] ~ `;`);
971             mixin(`enum bool useDefault = ` ~ useDefaults[i] ~ `;`);
972 
973             bool includeMember = false;
974 
975             enum isNullable = is(Type: Nullable!Arg, Arg);
976 
977             static if (!isNullable)
978             {
979                 bool dupExpr = needToDup!Type;
980                 bool passExprAsConst = dupExpr && __traits(compiles, const(Type).init.dup);
981             }
982             else
983             {
984                 // unpack nullable for dup
985                 bool dupExpr = needToDup!(typeof(Type.init.get));
986                 bool passExprAsConst = dupExpr && __traits(compiles, Type(const(Type).init.get.dup));
987             }
988 
989             bool forSuper = false;
990 
991             static if (i < argsPassedToSuper)
992             {
993                 includeMember = true;
994                 forSuper = true;
995             }
996             else
997             {
998                 mixin("alias symbol = typeof(this)." ~ member ~ ";");
999 
1000                 static assert (is(typeof(symbol)) && !__traits(isTemplate, symbol)); /* must have a resolvable type */
1001 
1002                 import boilerplate.util: isStatic;
1003 
1004                 includeMember = !mixin(isStatic(member));
1005 
1006                 static if (udaIndex!(This.Init, __traits(getAttributes, symbol)) != -1)
1007                 {
1008                     enum udaFieldIndex = udaIndex!(This.Init, __traits(getAttributes, symbol));
1009                     alias initArg = Alias!(__traits(getAttributes, symbol)[udaFieldIndex].value);
1010                     enum lambdaWithSelf = __traits(compiles, initArg(typeof(this).init));
1011                     enum nakedLambda = __traits(compiles, initArg());
1012 
1013                     directInitFields ~= member;
1014                     directInitIndex ~= udaFieldIndex;
1015                     directInitUseSelf ~= __traits(compiles,
1016                         __traits(getAttributes, symbol)[udaFieldIndex].value(typeof(this).init));
1017                     includeMember = false;
1018 
1019                     static if (lambdaWithSelf)
1020                     {
1021                         static if (__traits(compiles, initArg!(typeof(this))))
1022                         {
1023                             enum lambdaAttributes = [__traits(getFunctionAttributes, initArg!(typeof(this)))];
1024                         }
1025                         else
1026                         {
1027                             enum lambdaAttributes = [__traits(getFunctionAttributes, initArg)];
1028                         }
1029 
1030                         constructorAttributes = constructorAttributes.filter!(a => lambdaAttributes.canFind(a)).array;
1031                     }
1032                     else static if (nakedLambda)
1033                     {
1034                         enum lambdaAttributes = [__traits(getFunctionAttributes, initArg)];
1035 
1036                         constructorAttributes = constructorAttributes.filter!(a => lambdaAttributes.canFind(a)).array;
1037                     }
1038                 }
1039 
1040                 static if (udaIndex!(This.Exclude, __traits(getAttributes, symbol)) != -1)
1041                 {
1042                     includeMember = false;
1043                 }
1044             }
1045 
1046             if (!includeMember) continue;
1047 
1048             enum paramName = member.removeTrailingUnderline;
1049 
1050             string argexpr = paramName;
1051 
1052             if (dupExpr)
1053             {
1054                 constructorAttributes = constructorAttributes.filter!(a => a != "@nogc").array;
1055 
1056                 static if (isNullable)
1057                 {
1058                     argexpr = format!`%s.isNull ? %s.init : %s(%s.get.dup)`
1059                         (argexpr, memberTypes[i], memberTypes[i], argexpr);
1060                 }
1061                 else
1062                 {
1063                     argexpr = format!`%s.dup`(argexpr);
1064                 }
1065             }
1066 
1067             fields ~= member;
1068             args ~= paramName;
1069             argexprs ~= argexpr;
1070             fieldUseDefault ~= useDefault;
1071             fieldDefault ~= defaults[i];
1072             fieldAttributes ~= attributes[i];
1073             defaultAssignments ~= useDefault ? (` = ` ~ defaults[i]) : ``;
1074             types ~= passExprAsConst ? (`const ` ~ memberTypes[i]) : memberTypes[i];
1075         }
1076 
1077         size_t establishParameterRank(size_t i)
1078         {
1079             // parent explicit, our explicit, our implicit, parent implicit
1080             const fieldOfParent = i < argsPassedToSuper;
1081             return fieldUseDefault[i] * 2 + (fieldUseDefault[i] == fieldOfParent);
1082         }
1083 
1084         auto constructorFieldOrder = fields.length.iota.array.bucketSort(&establishParameterRank);
1085 
1086         assert(fields.length == types.length);
1087         assert(fields.length == fieldUseDefault.length);
1088         assert(fields.length == fieldDefault.length);
1089 
1090         result ~= format!`
1091             public static alias ConstructorInfo =
1092                 saveConstructorInfo!(%s, %-(%s, %));`
1093         (
1094             fields.reorder(constructorFieldOrder),
1095             zip(
1096                 types.reorder(constructorFieldOrder),
1097                 fieldUseDefault.reorder(constructorFieldOrder),
1098                 fieldDefault.reorder(constructorFieldOrder),
1099                 fieldAttributes.reorder(constructorFieldOrder),
1100             )
1101             .map!(args => format!`ConstructorField!(%s, %s, %s, %s)`(args[0], args[1], args[2], args[3]))
1102             .array
1103         );
1104 
1105         if (!(is(typeof(this) == struct) && fieldUseDefault.all)) // don't emit this() for structs
1106         {
1107             result ~= visibility ~ ` this(`
1108                 ~ constructorFieldOrder
1109                     .map!(i => format!`%s %s%s`(types[i], args[i], defaultAssignments[i]))
1110                     .join(`, `)
1111                 ~ format!`) %-(%s %)`(constructorAttributes);
1112 
1113             result ~= `{`;
1114 
1115             static if (is(typeof(typeof(super).ConstructorInfo)))
1116             {
1117                 result ~= `super(` ~ args[0 .. argsPassedToSuper].join(", ") ~ `);`;
1118             }
1119 
1120             result ~= fields.length.iota.drop(argsPassedToSuper)
1121                 .map!(i => format!`this.%s = %s;`(fields[i], argexprs[i]))
1122                 .join;
1123 
1124             foreach (i, field; directInitFields)
1125             {
1126                 if (directInitUseSelf[i])
1127                 {
1128                     result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value(this);`
1129                         (field, field, directInitIndex[i]);
1130                 }
1131                 else
1132                 {
1133                     result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value;`
1134                         (field, field, directInitIndex[i]);
1135                 }
1136             }
1137 
1138             result ~= `}`;
1139 
1140             result ~= `protected static enum string[] GeneratedConstructorAttributes_ = [`
1141                 ~ constructorAttributes.map!(a => `"` ~ a ~ `"`).join(`, `)
1142                 ~ `];`;
1143         }
1144 
1145         result ~= visibility ~ ` static struct BuilderType(alias T = typeof(this))
1146         {
1147             import boilerplate.builder : BuilderImpl;
1148 
1149             mixin BuilderImpl!T;
1150         }`;
1151 
1152         result ~= visibility ~ ` static auto Builder()()
1153         {
1154             return BuilderType!()();
1155         }`;
1156 
1157         result ~= visibility ~ ` auto BuilderFrom()() const
1158         {
1159             import boilerplate.util : removeTrailingUnderline;
1160 
1161             auto builder = BuilderType!()();
1162 
1163             static foreach (field; ConstructorInfo.fields)
1164             {
1165                 mixin("builder." ~ field.removeTrailingUnderline ~ " = this." ~ field ~ ";");
1166             }
1167             return builder;
1168         }`;
1169 
1170         return result;
1171     }
1172 }
1173 
1174 public template ConstructorField(Type_, bool useDefault_, alias fieldDefault_, attributes_...)
1175 {
1176     public alias Type = Type_;
1177     public enum useDefault = useDefault_;
1178     public alias fieldDefault = fieldDefault_;
1179     public alias attributes = attributes_;
1180 }
1181 
1182 public template saveConstructorInfo(string[] fields_, Fields...)
1183 if (fields_.length == Fields.length
1184     && allSatisfy!(ApplyLeft!(isInstanceOf, ConstructorField), Fields))
1185 {
1186     import std.format : format;
1187 
1188     public enum fields = fields_;
1189 
1190     private template FieldInfo_() {
1191         static foreach (i, field; fields)
1192         {
1193             mixin(format!q{public alias %s = Fields[%s];}(field, i));
1194         }
1195     }
1196 
1197     public alias FieldInfo = FieldInfo_!();
1198 }
1199 
1200 enum ThisEnum
1201 {
1202     Private,
1203     Protected,
1204     Exclude
1205 }
1206 
1207 struct This
1208 {
1209     enum Private = ThisEnum.Private;
1210     enum Protected = ThisEnum.Protected;
1211     struct Package
1212     {
1213         string packageMask = null;
1214     }
1215     enum Exclude = ThisEnum.Exclude;
1216 
1217     // construct with value
1218     static struct Init(alias Alias)
1219     {
1220         static if (__traits(compiles, Alias()))
1221         {
1222             @property static auto value() { return Alias(); }
1223         }
1224         else
1225         {
1226             alias value = Alias;
1227         }
1228     }
1229 
1230     static struct Default(alias Alias)
1231     {
1232         static if (__traits(compiles, Alias()))
1233         {
1234             @property static auto value() { return Alias(); }
1235         }
1236         else
1237         {
1238             alias value = Alias;
1239         }
1240     }
1241 }
1242 
1243 public template getUDADefaultOrNothing(T, attributes...)
1244 {
1245     import boilerplate.util : udaIndex;
1246 
1247     template EnumTest()
1248     {
1249         enum EnumTest = attributes[udaIndex!(This.Default, attributes)].value;
1250     }
1251 
1252     static if (udaIndex!(This.Default, attributes) == -1)
1253     {
1254         enum getUDADefaultOrNothing = 0;
1255     }
1256     // @(This.Default)
1257     else static if (__traits(isSame, attributes[udaIndex!(This.Default, attributes)], This.Default))
1258     {
1259         enum getUDADefaultOrNothing = T.init;
1260     }
1261     else static if (__traits(compiles, EnumTest!()))
1262     {
1263         enum getUDADefaultOrNothing = attributes[udaIndex!(This.Default, attributes)].value;
1264     }
1265     else
1266     {
1267         @property static auto getUDADefaultOrNothing()
1268         {
1269             return attributes[udaIndex!(This.Default, attributes)].value;
1270         }
1271     }
1272 }