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 import std.string : format;
825 
826 enum GetSuperTypeAsString_(string member) = format!`typeof(super).ConstructorInfo.FieldInfo.%s.Type`(member);
827 
828 enum GetMemberTypeAsString_(string member) = format!`typeof(this.%s)`(member);
829 
830 enum SuperDefault_(string member) = format!`typeof(super).ConstructorInfo.FieldInfo.%s.fieldDefault`(member);
831 
832 enum MemberDefault_(string member) =
833     format!`getUDADefaultOrNothing!(typeof(this.%s), __traits(getAttributes, this.%s))`(member, member);
834 
835 enum SuperUseDefault_(string member)
836     = format!(`typeof(super).ConstructorInfo.FieldInfo.%s.useDefault`)(member);
837 
838 enum MemberUseDefault_(string member)
839     = format!(`udaIndex!(This.Default, __traits(getAttributes, this.%s)) != -1`)(member);
840 
841 enum SuperAttributes_(string member)
842     = format!(`typeof(super).ConstructorInfo.FieldInfo.%s.attributes`)(member);
843 
844 enum MemberAttributes_(string member)
845     = format!(`__traits(getAttributes, this.%s)`)(member);
846 
847 mixin template GenerateThisTemplate()
848 {
849     private static generateThisImpl()
850     {
851         if (!__ctfe)
852         {
853             return null;
854         }
855 
856         import boilerplate.constructor :
857             GetMemberTypeAsString_, GetSuperTypeAsString_,
858             MemberDefault_, SuperDefault_,
859             MemberUseDefault_, SuperUseDefault_,
860             MemberAttributes_, SuperAttributes_,
861             This;
862         import boilerplate.util : GenNormalMemberTuple, bucketSort, needToDup,
863             reorder, udaIndex, removeTrailingUnderline;
864         import std.algorithm : all, canFind, filter, map;
865         import std.meta : Alias, aliasSeqOf, staticMap;
866         import std.range : array, drop, iota, zip;
867         import std.string : endsWith, format, join;
868         import std.typecons : Nullable;
869 
870         mixin GenNormalMemberTuple;
871 
872         string result = null;
873 
874         string visibility = "public";
875 
876         foreach (uda; __traits(getAttributes, typeof(this)))
877         {
878             static if (is(typeof(uda) == ThisEnum))
879             {
880                 static if (uda == This.Protected)
881                 {
882                     visibility = "protected";
883                 }
884                 static if (uda == This.Private)
885                 {
886                     visibility = "private";
887                 }
888             }
889             else static if (is(uda == This.Package))
890             {
891                 visibility = "package";
892             }
893             else static if (is(typeof(uda) == This.Package))
894             {
895                 visibility = "package(" ~ uda.packageMask ~ ")";
896             }
897         }
898 
899         string[] constructorAttributes = ["pure", "nothrow", "@safe", "@nogc"];
900 
901         static if (is(typeof(typeof(super).ConstructorInfo)))
902         {
903             enum argsPassedToSuper = typeof(super).ConstructorInfo.fields.length;
904             enum members = typeof(super).ConstructorInfo.fields ~ [NormalMemberTuple];
905             enum string[] CombinedArray(alias SuperPred, alias MemberPred) = ([
906                 staticMap!(SuperPred, aliasSeqOf!(typeof(super).ConstructorInfo.fields)),
907                 staticMap!(MemberPred, NormalMemberTuple)
908             ]);
909             constructorAttributes = typeof(super).GeneratedConstructorAttributes_;
910         }
911         else
912         {
913             enum argsPassedToSuper = 0;
914             static if (NormalMemberTuple.length > 0)
915             {
916                 enum members = [NormalMemberTuple];
917                 enum string[] CombinedArray(alias SuperPred, alias MemberPred) = ([
918                     staticMap!(MemberPred, NormalMemberTuple)
919                 ]);
920             }
921             else
922             {
923                 enum string[] members = null;
924                 enum string[] CombinedArray(alias SuperPred, alias MemberPred) = null;
925             }
926         }
927 
928         enum string[] useDefaults = CombinedArray!(SuperUseDefault_, MemberUseDefault_);
929         enum string[] memberTypes = CombinedArray!(GetSuperTypeAsString_, GetMemberTypeAsString_);
930         enum string[] defaults = CombinedArray!(SuperDefault_, MemberDefault_);
931         enum string[] attributes = CombinedArray!(SuperAttributes_, MemberAttributes_);
932 
933         string[] fields;
934         string[] args;
935         string[] argexprs;
936         string[] defaultAssignments;
937         bool[] fieldUseDefault;
938         string[] fieldDefault;
939         string[] fieldAttributes;
940         string[] types;
941         string[] directInitFields;
942         int[] directInitIndex;
943         bool[] directInitUseSelf;
944 
945         foreach (i; aliasSeqOf!(members.length.iota))
946         {
947             enum member = members[i];
948 
949             mixin(`alias Type = ` ~ memberTypes[i] ~ `;`);
950             mixin(`enum bool useDefault = ` ~ useDefaults[i] ~ `;`);
951 
952             bool includeMember = false;
953 
954             enum isNullable = is(Type: Nullable!Arg, Arg);
955 
956             static if (!isNullable)
957             {
958                 bool dupExpr = needToDup!Type;
959                 bool passExprAsConst = dupExpr && __traits(compiles, const(Type).init.dup);
960             }
961             else
962             {
963                 // unpack nullable for dup
964                 bool dupExpr = needToDup!(typeof(Type.init.get));
965                 bool passExprAsConst = dupExpr && __traits(compiles, Type(const(Type).init.get.dup));
966             }
967 
968             bool forSuper = false;
969 
970             static if (i < argsPassedToSuper)
971             {
972                 includeMember = true;
973                 forSuper = true;
974             }
975             else
976             {
977                 mixin("alias symbol = typeof(this)." ~ member ~ ";");
978 
979                 static assert (is(typeof(symbol)) && !__traits(isTemplate, symbol)); /* must have a resolvable type */
980 
981                 import boilerplate.util: isStatic;
982 
983                 includeMember = !mixin(isStatic(member));
984 
985                 static if (udaIndex!(This.Init, __traits(getAttributes, symbol)) != -1)
986                 {
987                     enum udaFieldIndex = udaIndex!(This.Init, __traits(getAttributes, symbol));
988                     alias initArg = Alias!(__traits(getAttributes, symbol)[udaFieldIndex].value);
989                     enum lambdaWithSelf = __traits(compiles, initArg(typeof(this).init));
990                     enum nakedLambda = __traits(compiles, initArg());
991 
992                     directInitFields ~= member;
993                     directInitIndex ~= udaFieldIndex;
994                     directInitUseSelf ~= __traits(compiles,
995                         __traits(getAttributes, symbol)[udaFieldIndex].value(typeof(this).init));
996                     includeMember = false;
997 
998                     static if (lambdaWithSelf)
999                     {
1000                         static if (__traits(compiles, initArg!(typeof(this))))
1001                         {
1002                             enum lambdaAttributes = [__traits(getFunctionAttributes, initArg!(typeof(this)))];
1003                         }
1004                         else
1005                         {
1006                             enum lambdaAttributes = [__traits(getFunctionAttributes, initArg)];
1007                         }
1008 
1009                         constructorAttributes = constructorAttributes.filter!(a => lambdaAttributes.canFind(a)).array;
1010                     }
1011                     else static if (nakedLambda)
1012                     {
1013                         enum lambdaAttributes = [__traits(getFunctionAttributes, initArg)];
1014 
1015                         constructorAttributes = constructorAttributes.filter!(a => lambdaAttributes.canFind(a)).array;
1016                     }
1017                 }
1018 
1019                 static if (udaIndex!(This.Exclude, __traits(getAttributes, symbol)) != -1)
1020                 {
1021                     includeMember = false;
1022                 }
1023             }
1024 
1025             if (!includeMember) continue;
1026 
1027             enum paramName = member.removeTrailingUnderline;
1028 
1029             string argexpr = paramName;
1030 
1031             if (dupExpr)
1032             {
1033                 constructorAttributes = constructorAttributes.filter!(a => a != "@nogc").array;
1034 
1035                 static if (isNullable)
1036                 {
1037                     argexpr = format!`%s.isNull ? %s.init : %s(%s.get.dup)`
1038                         (argexpr, memberTypes[i], memberTypes[i], argexpr);
1039                 }
1040                 else
1041                 {
1042                     argexpr = format!`%s.dup`(argexpr);
1043                 }
1044             }
1045 
1046             fields ~= member;
1047             args ~= paramName;
1048             argexprs ~= argexpr;
1049             fieldUseDefault ~= useDefault;
1050             fieldDefault ~= defaults[i];
1051             fieldAttributes ~= attributes[i];
1052             defaultAssignments ~= useDefault ? (` = ` ~ defaults[i]) : ``;
1053             types ~= passExprAsConst ? (`const ` ~ memberTypes[i]) : memberTypes[i];
1054         }
1055 
1056         size_t establishParameterRank(size_t i)
1057         {
1058             // parent explicit, our explicit, our implicit, parent implicit
1059             const fieldOfParent = i < argsPassedToSuper;
1060             return fieldUseDefault[i] * 2 + (fieldUseDefault[i] == fieldOfParent);
1061         }
1062 
1063         auto constructorFieldOrder = fields.length.iota.array.bucketSort(&establishParameterRank);
1064 
1065         assert(fields.length == types.length);
1066         assert(fields.length == fieldUseDefault.length);
1067         assert(fields.length == fieldDefault.length);
1068 
1069         result ~= format!`
1070             public static alias ConstructorInfo =
1071                 saveConstructorInfo!(%s, %-(%s, %));`
1072         (
1073             fields.reorder(constructorFieldOrder),
1074             zip(
1075                 types.reorder(constructorFieldOrder),
1076                 fieldUseDefault.reorder(constructorFieldOrder),
1077                 fieldDefault.reorder(constructorFieldOrder),
1078                 fieldAttributes.reorder(constructorFieldOrder),
1079             )
1080             .map!(args => format!`ConstructorField!(%s, %s, %s, %s)`(args[0], args[1], args[2], args[3]))
1081             .array
1082         );
1083 
1084         if (!(is(typeof(this) == struct) && fieldUseDefault.all)) // don't emit this() for structs
1085         {
1086             result ~= visibility ~ ` this(`
1087                 ~ constructorFieldOrder
1088                     .map!(i => format!`%s %s%s`(types[i], args[i], defaultAssignments[i]))
1089                     .join(`, `)
1090                 ~ format!`) %-(%s %)`(constructorAttributes);
1091 
1092             result ~= `{`;
1093 
1094             static if (is(typeof(typeof(super).ConstructorInfo)))
1095             {
1096                 result ~= `super(` ~ args[0 .. argsPassedToSuper].join(", ") ~ `);`;
1097             }
1098 
1099             result ~= fields.length.iota.drop(argsPassedToSuper)
1100                 .map!(i => format!`this.%s = %s;`(fields[i], argexprs[i]))
1101                 .join;
1102 
1103             foreach (i, field; directInitFields)
1104             {
1105                 if (directInitUseSelf[i])
1106                 {
1107                     result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value(this);`
1108                         (field, field, directInitIndex[i]);
1109                 }
1110                 else
1111                 {
1112                     result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value;`
1113                         (field, field, directInitIndex[i]);
1114                 }
1115             }
1116 
1117             result ~= `}`;
1118 
1119             result ~= `protected static enum string[] GeneratedConstructorAttributes_ = [`
1120                 ~ constructorAttributes.map!(a => `"` ~ a ~ `"`).join(`, `)
1121                 ~ `];`;
1122         }
1123 
1124         result ~= visibility ~ ` static struct BuilderType(alias T = typeof(this))
1125         {
1126             import boilerplate.builder : BuilderImpl;
1127 
1128             mixin BuilderImpl!T;
1129         }`;
1130 
1131         result ~= visibility ~ ` static auto Builder()()
1132         {
1133             return BuilderType!()();
1134         }`;
1135 
1136         result ~= visibility ~ ` auto BuilderFrom()() const
1137         {
1138             import boilerplate.util : removeTrailingUnderline;
1139 
1140             auto builder = BuilderType!()();
1141 
1142             static foreach (field; ConstructorInfo.fields)
1143             {
1144                 mixin("builder." ~ field.removeTrailingUnderline ~ " = this." ~ field ~ ";");
1145             }
1146             return builder;
1147         }`;
1148 
1149         return result;
1150     }
1151 }
1152 
1153 public template ConstructorField(Type_, bool useDefault_, alias fieldDefault_, attributes_...)
1154 {
1155     public alias Type = Type_;
1156     public enum useDefault = useDefault_;
1157     public alias fieldDefault = fieldDefault_;
1158     public alias attributes = attributes_;
1159 }
1160 
1161 public template saveConstructorInfo(string[] fields_, Fields...)
1162 if (fields_.length == Fields.length
1163     && allSatisfy!(ApplyLeft!(isInstanceOf, ConstructorField), Fields))
1164 {
1165     import std.format : format;
1166 
1167     public enum fields = fields_;
1168 
1169     private template FieldInfo_() {
1170         static foreach (i, field; fields)
1171         {
1172             mixin(format!q{public alias %s = Fields[%s];}(field, i));
1173         }
1174     }
1175 
1176     public alias FieldInfo = FieldInfo_!();
1177 }
1178 
1179 enum ThisEnum
1180 {
1181     Private,
1182     Protected,
1183     Exclude
1184 }
1185 
1186 struct This
1187 {
1188     enum Private = ThisEnum.Private;
1189     enum Protected = ThisEnum.Protected;
1190     struct Package
1191     {
1192         string packageMask = null;
1193     }
1194     enum Exclude = ThisEnum.Exclude;
1195 
1196     // construct with value
1197     static struct Init(alias Alias)
1198     {
1199         static if (__traits(compiles, Alias()))
1200         {
1201             @property static auto value() { return Alias(); }
1202         }
1203         else
1204         {
1205             alias value = Alias;
1206         }
1207     }
1208 
1209     static struct Default(alias Alias)
1210     {
1211         static if (__traits(compiles, Alias()))
1212         {
1213             @property static auto value() { return Alias(); }
1214         }
1215         else
1216         {
1217             alias value = Alias;
1218         }
1219     }
1220 }
1221 
1222 public template getUDADefaultOrNothing(T, attributes...)
1223 {
1224     import boilerplate.util : udaIndex;
1225 
1226     template EnumTest()
1227     {
1228         enum EnumTest = attributes[udaIndex!(This.Default, attributes)].value;
1229     }
1230 
1231     static if (udaIndex!(This.Default, attributes) == -1)
1232     {
1233         enum getUDADefaultOrNothing = 0;
1234     }
1235     // @(This.Default)
1236     else static if (__traits(isSame, attributes[udaIndex!(This.Default, attributes)], This.Default))
1237     {
1238         enum getUDADefaultOrNothing = T.init;
1239     }
1240     else static if (__traits(compiles, EnumTest!()))
1241     {
1242         enum getUDADefaultOrNothing = attributes[udaIndex!(This.Default, attributes)].value;
1243     }
1244     else
1245     {
1246         @property static auto getUDADefaultOrNothing()
1247         {
1248             return attributes[udaIndex!(This.Default, attributes)].value;
1249         }
1250     }
1251 }