1 module boilerplate.constructor;
2 
3 version(unittest)
4 {
5     import unit_threaded.should;
6 }
7 
8 /++
9 GenerateThis is a mixin string that automatically generates a this() function, customizable with UDA.
10 +/
11 public enum string GenerateThis = `
12     import boilerplate.constructor: GenerateThisTemplate, getUDADefaultOrNothing;
13     import boilerplate.util: udaIndex;
14     import std.meta : AliasSeq;
15     mixin GenerateThisTemplate;
16     mixin(typeof(this).generateThisImpl());
17 `;
18 
19 ///
20 @("creates a constructor")
21 unittest
22 {
23     class Class
24     {
25         int field;
26 
27         mixin(GenerateThis);
28     }
29 
30     auto obj = new Class(5);
31 
32     obj.field.shouldEqual(5);
33 }
34 
35 ///
36 @("calls the super constructor if it exists")
37 unittest
38 {
39     class Class
40     {
41         int field;
42 
43         mixin(GenerateThis);
44     }
45 
46     class Child : Class
47     {
48         int field2;
49 
50         mixin(GenerateThis);
51     }
52 
53     auto obj = new Child(5, 8);
54 
55     obj.field.shouldEqual(5);
56     obj.field2.shouldEqual(8);
57 }
58 
59 ///
60 @("separates fields from methods")
61 unittest
62 {
63     class Class
64     {
65         int field;
66 
67         void method() { }
68 
69         mixin(GenerateThis);
70     }
71 
72     auto obj = new Class(5);
73 
74     obj.field.shouldEqual(5);
75 }
76 
77 ///
78 @("dups arrays")
79 unittest
80 {
81     class Class
82     {
83         int[] array;
84 
85         mixin(GenerateThis);
86     }
87 
88     auto array = [2, 3, 4];
89     auto obj = new Class(array);
90 
91     array[0] = 1;
92     obj.array[0].shouldEqual(2);
93 }
94 
95 ///
96 @("dups arrays hidden behind Nullable")
97 unittest
98 {
99     import std.typecons : Nullable, nullable;
100 
101     class Class
102     {
103         Nullable!(int[]) array;
104 
105         mixin(GenerateThis);
106     }
107 
108     auto array = [2, 3, 4];
109     auto obj = new Class(array.nullable);
110 
111     array[0] = 1;
112     obj.array.get[0].shouldEqual(2);
113 
114     obj = new Class(Nullable!(int[]).init);
115     obj.array.isNull.shouldBeTrue;
116 }
117 
118 ///
119 @("uses default value for default constructor parameter")
120 unittest
121 {
122     class Class
123     {
124         @(This.Default!5)
125         int value = 5;
126 
127         mixin(GenerateThis);
128     }
129 
130     auto obj1 = new Class();
131 
132     obj1.value.shouldEqual(5);
133 
134     auto obj2 = new Class(6);
135 
136     obj2.value.shouldEqual(6);
137 }
138 
139 ///
140 @("creates no constructor for an empty struct")
141 unittest
142 {
143     struct Struct
144     {
145         mixin(GenerateThis);
146     }
147 
148     auto strct = Struct();
149 }
150 
151 ///
152 @("properly generates new default values on each call")
153 unittest
154 {
155     import std.conv : to;
156 
157     class Class
158     {
159         @(This.Default!(() => new Object))
160         Object obj;
161 
162         mixin(GenerateThis);
163     }
164 
165     auto obj1 = new Class();
166     auto obj2 = new Class();
167 
168     (cast(void*) obj1.obj).shouldNotEqual(cast(void*) obj2.obj);
169 }
170 
171 ///
172 @("establishes the parent-child parameter order: parent explicit, child explicit, child implicit, parent implicit.")
173 unittest
174 {
175     class Parent
176     {
177         int field1;
178 
179         @(This.Default!2)
180         int field2 = 2;
181 
182         mixin(GenerateThis);
183     }
184 
185     class Child : Parent
186     {
187         int field3;
188 
189         @(This.Default!4)
190         int field4 = 4;
191 
192         mixin(GenerateThis);
193     }
194 
195     auto obj = new Child(1, 2, 3, 4);
196 
197     obj.field1.shouldEqual(1);
198     obj.field3.shouldEqual(2);
199     obj.field4.shouldEqual(3);
200     obj.field2.shouldEqual(4);
201 }
202 
203 ///
204 @("disregards static fields")
205 unittest
206 {
207     class Class
208     {
209         static int field1;
210         int field2;
211 
212         mixin(GenerateThis);
213     }
214 
215     auto obj = new Class(5);
216 
217     obj.field1.shouldEqual(0);
218     obj.field2.shouldEqual(5);
219 }
220 
221 ///
222 @("can initialize with immutable arrays")
223 unittest
224 {
225     class Class
226     {
227         immutable(Object)[] array;
228 
229         mixin(GenerateThis);
230     }
231 }
232 
233 ///
234 @("can define scope for constructor")
235 unittest
236 {
237     @(This.Private)
238     class PrivateClass
239     {
240         mixin(GenerateThis);
241     }
242 
243     @(This.Protected)
244     class ProtectedClass
245     {
246         mixin(GenerateThis);
247     }
248 
249     @(This.Package)
250     class PackageClass
251     {
252         mixin(GenerateThis);
253     }
254 
255     class PublicClass
256     {
257         mixin(GenerateThis);
258     }
259 
260     static assert(__traits(getProtection, PrivateClass.__ctor) == "private");
261     static assert(__traits(getProtection, ProtectedClass.__ctor) == "protected");
262     static assert(__traits(getProtection, PackageClass.__ctor) == "package");
263     static assert(__traits(getProtection, PublicClass.__ctor) == "public");
264 }
265 
266 ///
267 @("can use default tag with new")
268 unittest
269 {
270     class Class
271     {
272         @(This.Default!(() => new Object))
273         Object foo;
274 
275         mixin(GenerateThis);
276     }
277 
278     ((new Class).foo !is null).shouldBeTrue;
279 }
280 
281 ///
282 @("can exclude fields from constructor")
283 unittest
284 {
285     class Class
286     {
287         @(This.Exclude)
288         int i = 5;
289 
290         mixin(GenerateThis);
291     }
292 
293     (new Class).i.shouldEqual(5);
294 }
295 
296 ///
297 @("marks duppy parameters as const when this does not prevent dupping")
298 unittest
299 {
300 
301     struct Struct
302     {
303     }
304 
305     class Class
306     {
307         Struct[] values_;
308 
309         mixin(GenerateThis);
310     }
311 
312     const Struct[] constValues;
313     auto obj = new Class(constValues);
314 }
315 
316 ///
317 @("does not include property functions in constructor list")
318 unittest
319 {
320     class Class
321     {
322         int a;
323 
324         @property int foo() const
325         {
326             return 0;
327         }
328 
329         mixin(GenerateThis);
330     }
331 
332     static assert(__traits(compiles, new Class(0)));
333     static assert(!__traits(compiles, new Class(0, 0)));
334 }
335 
336 @("declares @nogc on non-dupping constructors")
337 @nogc unittest
338 {
339     struct Struct
340     {
341         int a;
342 
343         mixin(GenerateThis);
344     }
345 
346     auto str = Struct(5);
347 }
348 
349 ///
350 @("can initialize fields using init value")
351 unittest
352 {
353     class Class
354     {
355         @(This.Init!5)
356         int field1;
357 
358         @(This.Init!(() => 8))
359         int field2;
360 
361         mixin(GenerateThis);
362     }
363 
364     auto obj = new Class;
365 
366     obj.field1.shouldEqual(5);
367     obj.field2.shouldEqual(8);
368 }
369 
370 ///
371 @("can initialize fields using init value, with lambda that accesses previous value")
372 unittest
373 {
374     class Class
375     {
376         int field1;
377 
378         @(This.Init!(self => self.field1 + 5))
379         int field2;
380 
381         mixin(GenerateThis);
382     }
383 
384     auto obj = new Class(5);
385 
386     obj.field1.shouldEqual(5);
387     obj.field2.shouldEqual(10);
388 }
389 
390 ///
391 @("still supports deprecated default attribute")
392 unittest
393 {
394     class Class
395     {
396         @Default!5
397         int value = 5;
398 
399         mixin(GenerateThis);
400     }
401 
402     auto obj1 = new Class();
403 
404     obj1.value.shouldEqual(5);
405 
406     auto obj2 = new Class(6);
407 
408     obj2.value.shouldEqual(6);
409 }
410 import std.string : format;
411 
412 enum GetSuperTypeAsString_(size_t Index) = format!`typeof(super).GeneratedConstructorTypes_[%s]`(Index);
413 
414 enum GetMemberTypeAsString_(string Member) = format!`typeof(this.%s)`(Member);
415 
416 enum SuperDefault_(size_t Index) = format!`typeof(super).GeneratedConstructorDefaults_[%s]`(Index);
417 
418 enum MemberDefault_(string Member) =
419     format!`getUDADefaultOrNothing!(__traits(getAttributes, this.%s))`(Member);
420 
421 mixin template GenerateThisTemplate()
422 {
423     private static generateThisImpl()
424     {
425         if (!__ctfe)
426         {
427             return null;
428         }
429 
430         import boilerplate.constructor : GetMemberTypeAsString_, GetSuperTypeAsString_,
431             MemberDefault_, SuperDefault_, This;
432         import boilerplate.util : GenNormalMemberTuple, bucketSort, needToDup, udaIndex;
433         import std.meta : aliasSeqOf, staticMap;
434         import std.range : array, drop, iota;
435         import std.string : endsWith, format, join;
436         import std.typecons : Nullable;
437 
438         mixin GenNormalMemberTuple;
439 
440         string result = null;
441 
442         string visibility = "public";
443 
444         foreach (uda; __traits(getAttributes, typeof(this)))
445         {
446             static if (is(typeof(uda) == ThisEnum))
447             {
448                 static if (uda == This.Protected)
449                 {
450                     visibility = "protected";
451                 }
452                 static if (uda == This.Package)
453                 {
454                     visibility = "package";
455                 }
456                 static if (uda == This.Private)
457                 {
458                     visibility = "private";
459                 }
460             }
461         }
462 
463         enum MemberUseDefault(string Member)
464             = mixin(format!(`udaIndex!(This.Default, __traits(getAttributes, this.%s)) != -1`~
465                 `|| udaIndex!(Default, __traits(getAttributes, this.%s)) != -1`)(Member, Member));
466 
467         static if (is(typeof(typeof(super).GeneratedConstructorFields_)))
468         {
469             enum argsPassedToSuper = typeof(super).GeneratedConstructorFields_.length;
470             enum members = typeof(super).GeneratedConstructorFields_ ~ [NormalMemberTuple];
471             enum useDefaults = typeof(super).GeneratedConstructorUseDefaults_
472                 ~ [staticMap!(MemberUseDefault, NormalMemberTuple)];
473             enum CombinedArray(alias SuperPred, alias MemberPred) = [
474                 staticMap!(SuperPred, aliasSeqOf!(typeof(super).GeneratedConstructorTypes_.length.iota)),
475                 staticMap!(MemberPred, NormalMemberTuple)
476             ];
477         }
478         else
479         {
480             enum argsPassedToSuper = 0;
481             static if (NormalMemberTuple.length > 0)
482             {
483                 enum members = [NormalMemberTuple];
484                 enum useDefaults = [staticMap!(MemberUseDefault, NormalMemberTuple)];
485                 enum CombinedArray(alias SuperPred, alias MemberPred) = [staticMap!(MemberPred, NormalMemberTuple)];
486             }
487             else
488             {
489                 enum string[] members = null;
490                 enum bool[] useDefaults = null;
491                 enum string[] CombinedArray(alias SuperPred, alias MemberPred) = null;
492             }
493         }
494 
495         enum memberTypes = CombinedArray!(GetSuperTypeAsString_, GetMemberTypeAsString_);
496         enum defaults = CombinedArray!(SuperDefault_, MemberDefault_);
497 
498         bool[] passAsConst;
499         string[] fields;
500         string[] quotedFields;
501         string[] args;
502         string[] argexprs;
503         string[] defaultAssignments;
504         string[] useDefaultsStr;
505         string[] types;
506         string[] directInitFields;
507         int[] directInitIndex;
508         bool[] directInitUseSelf;
509 
510         bool anyDups = false;
511 
512         foreach (i; aliasSeqOf!(members.length.iota))
513         {
514             enum member = members[i];
515 
516             mixin(`alias Type = ` ~ memberTypes[i] ~ `;`);
517 
518             bool includeMember = false;
519 
520             enum isNullable = mixin(format!`is(%s: Template!Args, alias Template = Nullable, Args...)`(memberTypes[i]));
521 
522             static if (!isNullable)
523             {
524                 bool dupExpr = needToDup!Type;
525                 bool passExprAsConst = dupExpr && __traits(compiles, const(Type).init.dup);
526             }
527             else
528             {
529                 // unpack nullable for dup
530                 bool dupExpr = needToDup!(typeof(Type.init.get));
531                 bool passExprAsConst = dupExpr && __traits(compiles, Type(const(Type).init.get.dup));
532             }
533 
534             bool forSuper = false;
535 
536             static if (i < argsPassedToSuper)
537             {
538                 includeMember = true;
539                 forSuper = true;
540             }
541             else
542             {
543                 mixin("alias symbol = this." ~ member ~ ";");
544 
545                 static assert (is(typeof(symbol)) && !__traits(isTemplate, symbol)); /* must have a resolvable type */
546 
547                 import boilerplate.util: isStatic;
548 
549                 includeMember = true;
550 
551                 if (mixin(isStatic(member)))
552                 {
553                     includeMember = false;
554                 }
555 
556                 static if (udaIndex!(This.Init, __traits(getAttributes, symbol)) != -1)
557                 {
558                     enum udaFieldIndex = udaIndex!(This.Init, __traits(getAttributes, symbol));
559 
560                     directInitFields ~= member;
561                     directInitIndex ~= udaFieldIndex;
562                     directInitUseSelf ~= __traits(compiles,
563                         __traits(getAttributes, symbol)[udaFieldIndex].value(typeof(this).init));
564                     includeMember = false;
565                 }
566 
567                 static if (udaIndex!(This.Exclude, __traits(getAttributes, symbol)) != -1)
568                 {
569                     includeMember = false;
570                 }
571             }
572 
573             if (!includeMember) continue;
574 
575             string paramName;
576 
577             if (member.endsWith("_"))
578             {
579                 paramName = member[0 .. $ - 1];
580             }
581             else
582             {
583                 paramName = member;
584             }
585 
586             string argexpr = paramName;
587 
588             if (dupExpr)
589             {
590                 anyDups = true;
591 
592                 static if (isNullable)
593                 {
594                     argexpr = format!`%s.isNull ? %s.init : %s(%s.get.dup)`
595                         (argexpr, memberTypes[i], memberTypes[i], argexpr);
596                 }
597                 else
598                 {
599                     argexpr = format!`%s.dup`(argexpr);
600                 }
601             }
602 
603             passAsConst ~= passExprAsConst;
604             fields ~= member;
605             quotedFields ~= `"` ~ member ~ `"`;
606             args ~= paramName;
607             argexprs ~= argexpr;
608             defaultAssignments ~= useDefaults[i] ? (` = ` ~ defaults[i]) : ``;
609             useDefaultsStr ~= useDefaults[i] ? `true` : `false`;
610             types ~= passExprAsConst ? (`const ` ~ memberTypes[i]) : memberTypes[i];
611         }
612 
613         result ~= visibility ~ ` this(`;
614 
615         int establishParameterRank(size_t i)
616         {
617             // parent explicit, our explicit, our implicit, parent implicit
618             const fieldOfParent = i < argsPassedToSuper;
619             return useDefaults[i] * 2 + (useDefaults[i] == fieldOfParent);
620         }
621 
622         foreach (k, i; fields.length.iota.array.bucketSort(&establishParameterRank))
623         {
624             auto type = types[i];
625 
626             if (k > 0)
627             {
628                 result ~= `, `;
629             }
630 
631             result ~= type ~ ` ` ~ args[i] ~ defaultAssignments[i];
632         }
633 
634         result ~= format!`) pure %snothrow @safe {`(anyDups ? `` : `@nogc `);
635 
636         static if (is(typeof(typeof(super).GeneratedConstructorFields_)))
637         {
638             result ~= `super(` ~ args[0 .. argsPassedToSuper].join(", ") ~ `);`;
639         }
640 
641         foreach (i; fields.length.iota.drop(argsPassedToSuper))
642         {
643             auto field = fields[i];
644             auto argexpr = argexprs[i];
645 
646             result ~= `this.` ~ field ~ ` = ` ~ argexpr ~ `;`;
647         }
648 
649         foreach (i, field; directInitFields)
650         {
651             if (directInitUseSelf[i])
652             {
653                 result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value(this);`
654                     (field, field, directInitIndex[i]);
655             }
656             else
657             {
658                 result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value;`
659                     (field, field, directInitIndex[i]);
660             }
661         }
662 
663         result ~= `}`;
664 
665         static if (!is(typeof(this) == struct))
666         {
667             // don't emit inheritance info for structs
668             result ~= `protected static enum string[] GeneratedConstructorFields_ = [`
669                 ~ quotedFields.join(`, `)
670                 ~ `];`;
671 
672             result ~= `protected static alias GeneratedConstructorTypes_ = AliasSeq!(`
673                 ~ types.join(`, `)
674                 ~ `);`;
675 
676             result ~= `protected static enum bool[] GeneratedConstructorUseDefaults_ = [`
677                 ~ useDefaultsStr.join(`, `)
678                 ~ `];`;
679 
680             result ~= `protected static alias GeneratedConstructorDefaults_ = AliasSeq!(`
681                 ~ defaults.join(`, `)
682                 ~ `);`;
683         }
684         else
685         {
686             if (fields.length == 0)
687             {
688                 // don't generate empty constructor for structs
689                 return "";
690             }
691         }
692 
693         return result;
694     }
695 }
696 
697 deprecated("Please use This.Default")
698 struct Default(alias Alias)
699 {
700     static if (__traits(compiles, Alias()))
701     {
702         @property static auto value() { return Alias(); }
703     }
704     else
705     {
706         alias value = Alias;
707     }
708 }
709 
710 enum ThisEnum
711 {
712     Private,
713     Protected,
714     Package,
715     Exclude
716 }
717 
718 struct This
719 {
720     enum Private = ThisEnum.Private;
721     enum Protected = ThisEnum.Protected;
722     enum Package = ThisEnum.Package;
723     enum Exclude = ThisEnum.Exclude;
724 
725     // construct with value
726     static struct Init(alias Alias)
727     {
728         static if (__traits(compiles, Alias()))
729         {
730             @property static auto value() { return Alias(); }
731         }
732         else
733         {
734             alias value = Alias;
735         }
736     }
737 
738     static struct Default(alias Alias)
739     {
740         static if (__traits(compiles, Alias()))
741         {
742             @property static auto value() { return Alias(); }
743         }
744         else
745         {
746             alias value = Alias;
747         }
748     }
749 }
750 
751 public template getUDADefaultOrNothing(attributes...)
752 {
753     import boilerplate.util : udaIndex;
754 
755     template EnumTest()
756     {
757         enum EnumTest = attributes[udaIndex!(This.Default, attributes)].value;
758     }
759 
760     template EnumTestDeprecated()
761     {
762         enum EnumTest = attributes[udaIndex!(Default, attributes)].value;
763     }
764 
765     static if (udaIndex!(This.Default, attributes) == -1 && udaIndex!(Default, attributes) == -1)
766     {
767         enum getUDADefaultOrNothing = 0;
768     }
769     else static if (__traits(compiles, EnumTest!()))
770     {
771         enum getUDADefaultOrNothing = attributes[udaIndex!(This.Default, attributes)].value;
772     }
773     else static if (__traits(compiles, EnumTestDeprecated()))
774     {
775         enum getUDADefaultOrNothing = attributes[udaIndex!(Default, attributes)].value;
776     }
777     else
778     {
779         @property static auto getUDADefaultOrNothing()
780         {
781             static if (udaIndex!(This.Default, attributes) != -1)
782             {
783                 return attributes[udaIndex!(This.Default, attributes)].value;
784             }
785             else
786             {
787                 return attributes[udaIndex!(Default, attributes)].value;
788             }
789         }
790     }
791 }