1 module boilerplate.accessors;
2 
3 import boilerplate.conditions : generateChecksForAttributes, IsConditionAttribute;
4 import boilerplate.util: DeepConst, isStatic;
5 import std.meta : StdMetaFilter = Filter;
6 import std.traits;
7 import std.typecons: Nullable;
8 
9 struct Read
10 {
11     string visibility = "public";
12 }
13 
14 // Deprecated! See below.
15 // RefRead can not check invariants on change, so there's no point.
16 // ref property functions where the value being returned is a field of the class
17 // are entirely equivalent to public fields.
18 struct RefRead
19 {
20     string visibility = "public";
21 }
22 
23 struct ConstRead
24 {
25     string visibility = "public";
26 }
27 
28 struct Write
29 {
30     string visibility = "public";
31 }
32 
33 immutable string GenerateFieldAccessors = `
34     import boilerplate.accessors : GenerateFieldAccessorMethods;
35     mixin GenerateFieldAccessorMethods;
36     mixin(GenerateFieldAccessorMethodsImpl);
37     `;
38 
39 public static string GenerateFieldDecls_(FieldType, Attributes...)
40     (string name, bool synchronize, bool fieldIsStatic, bool fieldIsUnsafe)
41 {
42     if (!__ctfe)
43     {
44         return null;
45     }
46 
47     string result;
48 
49     import boilerplate.accessors :
50         ConstRead,
51         GenerateConstReader, GenerateReader, GenerateRefReader, GenerateWriter,
52         Read, RefRead, Write;
53     import boilerplate.util : udaIndex;
54 
55     static if (udaIndex!(Read, Attributes) != -1)
56     {
57         string readerDecl = GenerateReader!(FieldType, Attributes)(
58             name, fieldIsStatic, fieldIsUnsafe, synchronize);
59 
60         debug (accessors) pragma(msg, readerDecl);
61         result ~= readerDecl;
62     }
63 
64     static if (udaIndex!(RefRead, Attributes) != -1)
65     {
66         result ~= `pragma(msg, "Deprecation! RefRead on " ~ typeof(this).stringof ~ ".` ~ name
67             ~ ` makes a private field effectively public, defeating the point.");`;
68 
69         string refReaderDecl = GenerateRefReader!(FieldType)(name, fieldIsStatic);
70 
71         debug (accessors) pragma(msg, refReaderDecl);
72         result ~= refReaderDecl;
73     }
74 
75     static if (udaIndex!(ConstRead, Attributes) != -1)
76     {
77         string constReaderDecl = GenerateConstReader!(const(FieldType), Attributes)
78             (name, fieldIsStatic, fieldIsUnsafe, synchronize);
79 
80         debug (accessors) pragma(msg, constReaderDecl);
81         result ~= constReaderDecl;
82     }
83 
84     static if (udaIndex!(Write, Attributes) != -1)
85     {
86         string writerDecl = GenerateWriter!(FieldType, Attributes)
87             (name, `this.` ~ name, fieldIsStatic, fieldIsUnsafe, synchronize);
88 
89         debug (accessors) pragma(msg, writerDecl);
90         result ~= writerDecl;
91     }
92 
93     return result;
94 }
95 
96 mixin template GenerateFieldAccessorMethods()
97 {
98     private static string GenerateFieldAccessorMethodsImpl()
99     {
100         if (!__ctfe)
101         {
102             return null;
103         }
104 
105         import boilerplate.accessors : GenerateFieldDecls_;
106         import boilerplate.util : GenNormalMemberTuple, isStatic, isUnsafe;
107 
108         string result = "";
109 
110         mixin GenNormalMemberTuple;
111 
112         foreach (name; NormalMemberTuple)
113         {
114             // synchronized without lock contention is basically free, so always do it
115             // TODO enable when https://issues.dlang.org/show_bug.cgi?id=18504 is fixed
116             // enum synchronize = is(typeof(__traits(getMember, typeof(this), name)) == class);
117             enum fieldIsStatic = mixin(name.isStatic);
118             enum fieldIsUnsafe = mixin(name.isUnsafe);
119 
120             result ~= GenerateFieldDecls_!(
121                 typeof(__traits(getMember, typeof(this), name)),
122                 __traits(getAttributes, __traits(getMember, typeof(this), name))
123             ) (name, /*synchronize*/false, fieldIsStatic, fieldIsUnsafe);
124         }
125 
126         return result;
127     }
128 }
129 
130 string getModifiers(bool isStatic)
131 {
132     return isStatic ? " static" : "";
133 }
134 
135 uint filterAttributes(T)(bool isStatic, bool isUnsafe, FilterMode mode)
136 {
137     import boilerplate.util : needToDup;
138 
139     uint attributes = uint.max;
140 
141     if (needToDup!T)
142     {
143         attributes &= ~FunctionAttribute.nogc;
144         static if (isAssociativeArray!T)
145         {
146             // int[int].dup can throw apparently
147             attributes &= ~FunctionAttribute.nothrow_;
148         }
149     }
150     // Nullable.opAssign is not nogc
151     if (mode == FilterMode.Writer && isInstanceOf!(Nullable, T))
152     {
153         attributes &= ~FunctionAttribute.nogc;
154     }
155     // TODO remove once synchronized (this) is nothrow
156     // see https://github.com/dlang/druntime/pull/2105 , https://github.com/dlang/dmd/pull/7942
157     if (is(T == class))
158     {
159         attributes &= ~FunctionAttribute.nothrow_;
160     }
161     if (isStatic)
162     {
163         attributes &= ~FunctionAttribute.pure_;
164     }
165     if (isUnsafe)
166     {
167         attributes &= ~FunctionAttribute.safe;
168     }
169     return attributes;
170 }
171 
172 enum FilterMode
173 {
174     Reader,
175     Writer,
176 }
177 
178 string GenerateReader(T, Attributes...)(string name, bool fieldIsStatic, bool fieldIsUnsafe, bool synchronize)
179 {
180     import boilerplate.util : needToDup;
181     import std..string : format;
182 
183     auto example = T.init;
184     auto accessorName = accessor(name);
185     enum visibility = getVisibility!(Read, __traits(getAttributes, example));
186     enum needToDupField = needToDup!T;
187 
188     static if (isAssociativeArray!T)
189     {
190         fieldIsUnsafe = true;
191     }
192 
193     uint attributes = inferAttributes!(T, "__postblit") &
194         filterAttributes!T(fieldIsStatic, fieldIsUnsafe, FilterMode.Reader);
195 
196     string attributesString = generateAttributeString(attributes);
197     string accessorBody;
198     string type;
199 
200     if (fieldIsStatic)
201     {
202         type = format!`typeof(this.%s)`(name);
203     }
204     else
205     {
206         type = format!`inout(typeof(this.%s))`(name);
207     }
208 
209     // for types like string where the contents are already const or value,
210     // so we can safely reassign to a non-const type
211     static if (needToDupField)
212     {
213         static if (isArray!T)
214         {
215             accessorBody = format!`return typeof(this.%s).init ~ this.%s;`(name, name);
216         }
217         else static if (isAssociativeArray!T)
218         {
219             accessorBody = format!`return cast(typeof(this.%s)) this.%s.dup;`(name, name);
220         }
221         else
222         {
223             static assert(false, "logic error: need to dup but don't know how");
224         }
225     }
226     else static if (DeepConst!(Unqual!T) && !is(Unqual!T == T))
227     {
228         // necessitated by DMD bug https://issues.dlang.org/show_bug.cgi?id=18545
229         accessorBody = format!`typeof(cast() this.%s) var = this.%s; return var;`(name, name);
230         type = format!`typeof(cast() this.%s)`(name);
231     }
232     else
233     {
234         accessorBody = format!`return this.%s;`(name);
235     }
236 
237     if (synchronize)
238     {
239         accessorBody = format!`synchronized (this) { %s} `(accessorBody);
240     }
241 
242     auto modifiers = getModifiers(fieldIsStatic);
243 
244     if (!fieldIsStatic)
245     {
246         attributesString ~= " inout";
247     }
248 
249     string outCondition = "";
250 
251     if (fieldIsStatic)
252     {
253         if (auto checks = generateChecksForAttributes!(T, StdMetaFilter!(IsConditionAttribute, Attributes))
254             ("result", " in postcondition of @Read"))
255         {
256             outCondition = format!` out(result) { %s } do`(checks);
257         }
258     }
259 
260     return format!("%s%s final @property %s %s()%s%s { %s }")
261                 (visibility, modifiers, type, accessorName, attributesString, outCondition, accessorBody);
262 }
263 
264 @("generates readers as expected")
265 @nogc nothrow pure @safe unittest
266 {
267     int integerValue;
268     string stringValue;
269     int[] intArrayValue;
270     const string constStringValue;
271 
272     static assert(GenerateReader!int("foo", true, false, false) ==
273         "public static final @property typeof(this.foo) foo() " ~
274         "@nogc nothrow @safe { return this.foo; }");
275     static assert(GenerateReader!string("foo", true, false, false) ==
276         "public static final @property typeof(this.foo) foo() " ~
277         "@nogc nothrow @safe { return this.foo; }");
278     static assert(GenerateReader!(int[])("foo", true, false, false) ==
279         "public static final @property typeof(this.foo) foo() nothrow @safe "
280       ~ "{ return typeof(this.foo).init ~ this.foo; }");
281     static assert(GenerateReader!(const string)("foo", true, false, false) ==
282         "public static final @property typeof(cast() this.foo) foo() @nogc nothrow @safe "
283       ~ "{ typeof(cast() this.foo) var = this.foo; return var; }");
284 }
285 
286 string GenerateRefReader(T)(string name, bool isStatic)
287 {
288     import std..string : format;
289 
290     auto example = T.init;
291     auto accessorName = accessor(name);
292     enum visibility = getVisibility!(RefRead, __traits(getAttributes, example));
293 
294     string attributesString;
295     if (isStatic)
296     {
297         attributesString = "@nogc nothrow @safe ";
298     }
299     else
300     {
301         attributesString = "@nogc nothrow pure @safe ";
302     }
303 
304     auto modifiers = getModifiers(isStatic);
305 
306     // no need to synchronize a reference read
307     return format("%s%s final @property ref typeof(this.%s) %s() " ~
308         "%s{ return this.%s; }",
309         visibility, modifiers, name, accessorName, attributesString, name);
310 }
311 
312 @("generates ref readers as expected")
313 @nogc nothrow pure @safe unittest
314 {
315     static assert(GenerateRefReader!int("foo", true) ==
316         "public static final @property ref typeof(this.foo) foo() " ~
317         "@nogc nothrow @safe { return this.foo; }");
318     static assert(GenerateRefReader!string("foo", true) ==
319         "public static final @property ref typeof(this.foo) foo() " ~
320         "@nogc nothrow @safe { return this.foo; }");
321     static assert(GenerateRefReader!(int[])("foo", true) ==
322         "public static final @property ref typeof(this.foo) foo() " ~
323         "@nogc nothrow @safe { return this.foo; }");
324 }
325 
326 string GenerateConstReader(T, Attributes...)(string name, bool isStatic, bool isUnsafe, bool synchronize)
327 {
328     import std..string : format;
329 
330     auto example = T.init;
331     auto accessorName = accessor(name);
332     enum visibility = getVisibility!(ConstRead, __traits(getAttributes, example));
333 
334     uint attributes = inferAttributes!(T, "__postblit") &
335         filterAttributes!T(isStatic, isUnsafe, FilterMode.Reader);
336 
337     string attributesString = generateAttributeString(attributes);
338 
339     static if (DeepConst!(Unqual!T) && !is(Unqual!T == T))
340     {
341         // necessitated by DMD bug https://issues.dlang.org/show_bug.cgi?id=18545
342         string accessorBody = format!`typeof(cast() this.%s) var = this.%s; return var;`(name, name);
343         string type = format!`typeof(cast() const(typeof(this)).init.%s)`(name);
344     }
345     else
346     {
347         string accessorBody = format!`return this.%s; `(name);
348         string type = format!`const(typeof(this.%s))`(name);
349     }
350 
351     if (synchronize)
352     {
353         accessorBody = format!`synchronized (this) { %s} `(accessorBody);
354     }
355 
356     auto modifiers = getModifiers(isStatic);
357 
358     if (isStatic)
359     {
360         string outCondition = "";
361 
362         if (auto checks = generateChecksForAttributes!(T, StdMetaFilter!(IsConditionAttribute, Attributes))
363             ("result", " in postcondition of @ConstRead"))
364         {
365             outCondition = format!` out(result) { %s } do`(checks);
366         }
367 
368         return format("%s%s final @property %s %s()%s%s { %s}",
369             visibility, modifiers, type, accessorName, attributesString, outCondition, accessorBody);
370     }
371 
372     return format("%s%s final @property %s %s() const%s { %s}",
373         visibility, modifiers, type, accessorName, attributesString, accessorBody);
374 }
375 
376 string GenerateWriter(T, Attributes...)(string name, string fieldCode, bool isStatic, bool isUnsafe, bool synchronize)
377 {
378     import boilerplate.util : needToDup;
379     import std.algorithm : canFind;
380     import std..string : format;
381 
382     auto example = T.init;
383     auto accessorName = accessor(name);
384     auto inputName = accessorName;
385     enum needToDupField = needToDup!T;
386     enum visibility = getVisibility!(Write, __traits(getAttributes, example));
387 
388     uint attributes = defaultFunctionAttributes &
389         filterAttributes!T(isStatic, isUnsafe, FilterMode.Writer) &
390         inferAssignAttributes!T &
391         inferAttributes!(T, "__postblit") &
392         inferAttributes!(T, "__dtor");
393 
394     string precondition = ``;
395 
396     if (auto checks = generateChecksForAttributes!(T, StdMetaFilter!(IsConditionAttribute, Attributes))
397         (inputName, " in precondition of @Write"))
398     {
399         precondition = format!` in { import std.format : format; import std.array : empty; %s } do`(checks);
400         attributes &= ~FunctionAttribute.nogc;
401         attributes &= ~FunctionAttribute.nothrow_;
402         // format() is neither pure nor safe
403         if (checks.canFind("format"))
404         {
405             attributes &= ~FunctionAttribute.pure_;
406             attributes &= ~FunctionAttribute.safe;
407         }
408     }
409 
410     auto attributesString = generateAttributeString(attributes);
411     auto modifiers = getModifiers(isStatic);
412 
413     string accessorBody = format!`this.%s = %s%s; `(name, inputName, needToDupField ? ".dup" : "");
414 
415     if (synchronize)
416     {
417         accessorBody = format!`synchronized (this) { %s} `(accessorBody);
418     }
419 
420     string result = format("%s%s final @property void %s(typeof(%s) %s)%s%s { %s}",
421         visibility, modifiers, accessorName, fieldCode, inputName,
422         attributesString, precondition, accessorBody);
423 
424     static if (is(T : Nullable!Arg, Arg))
425     {
426         result ~= format("%s%s final @property void %s(typeof(%s.get) %s)%s%s { %s}",
427         visibility, modifiers, accessorName, fieldCode, inputName,
428         attributesString, precondition, accessorBody);
429     }
430     return result;
431 }
432 
433 @("generates writers as expected")
434 @nogc nothrow pure @safe unittest
435 {
436     static assert(GenerateWriter!int("foo", "integerValue", true, false, false) ==
437         "public static final @property void foo(typeof(integerValue) foo) " ~
438         "@nogc nothrow @safe { this.foo = foo; }");
439     static assert(GenerateWriter!string("foo", "stringValue", true, false, false) ==
440         "public static final @property void foo(typeof(stringValue) foo) " ~
441         "@nogc nothrow @safe { this.foo = foo; }");
442     static assert(GenerateWriter!(int[])("foo", "intArrayValue", true, false, false) ==
443         "public static final @property void foo(typeof(intArrayValue) foo) " ~
444         "nothrow @safe { this.foo = foo.dup; }");
445 }
446 
447 @("generates same-type writer for Nullable")
448 pure @safe unittest
449 {
450     import std.typecons : Nullable, nullable;
451     import unit_threaded.should : shouldEqual;
452 
453     struct Struct
454     {
455         @Write
456         Nullable!int optional_;
457 
458         mixin(GenerateFieldAccessors);
459     }
460 
461     Struct value;
462 
463     value.optional = 5;
464 
465     value.optional_.shouldEqual(5.nullable);
466 }
467 
468 private enum uint defaultFunctionAttributes =
469             FunctionAttribute.nogc |
470             FunctionAttribute.safe |
471             FunctionAttribute.nothrow_ |
472             FunctionAttribute.pure_;
473 
474 private template inferAttributes(T, string M)
475 {
476     uint inferAttributes()
477     {
478         uint attributes = defaultFunctionAttributes;
479 
480         static if (is(T == struct))
481         {
482             static if (hasMember!(T, M))
483             {
484                 attributes &= functionAttributes!(__traits(getMember, T, M));
485             }
486             else
487             {
488                 foreach (field; Fields!T)
489                 {
490                     attributes &= inferAttributes!(field, M);
491                 }
492             }
493         }
494         return attributes;
495     }
496 }
497 
498 private template inferAssignAttributes(T)
499 {
500     uint inferAssignAttributes()
501     {
502         uint attributes = defaultFunctionAttributes;
503 
504         static if (is(T == struct))
505         {
506             static if (hasMember!(T, "opAssign"))
507             {
508                 foreach (o; __traits(getOverloads, T, "opAssign"))
509                 {
510                     alias params = Parameters!o;
511                     static if (params.length == 1 && is(params[0] == T))
512                     {
513                         attributes &= functionAttributes!o;
514                     }
515                 }
516             }
517             else
518             {
519                 foreach (field; Fields!T)
520                 {
521                     attributes &= inferAssignAttributes!field;
522                 }
523             }
524         }
525         return attributes;
526     }
527 }
528 
529 private string generateAttributeString(uint attributes)
530 {
531     string attributesString;
532 
533     if (attributes & FunctionAttribute.nogc)
534     {
535         attributesString ~= " @nogc";
536     }
537     if (attributes & FunctionAttribute.nothrow_)
538     {
539         attributesString ~= " nothrow";
540     }
541     if (attributes & FunctionAttribute.pure_)
542     {
543         attributesString ~= " pure";
544     }
545     if (attributes & FunctionAttribute.safe)
546     {
547         attributesString ~= " @safe";
548     }
549 
550     return attributesString;
551 }
552 
553 private string accessor(string name) @nogc nothrow pure @safe
554 {
555     import std..string : chomp, chompPrefix;
556 
557     return name.chomp("_").chompPrefix("_");
558 }
559 
560 @("removes underlines from names")
561 @nogc nothrow pure @safe unittest
562 {
563     assert(accessor("foo_") == "foo");
564     assert(accessor("_foo") == "foo");
565 }
566 
567 /**
568  * Returns a string with the value of the field "visibility" if the attributes
569  * include an UDA of type A. The default visibility is "public".
570  */
571 template getVisibility(A, attributes...)
572 {
573     import std..string : format;
574 
575     enum getVisibility = helper;
576 
577     private static helper()
578     {
579         static if (!attributes.length)
580         {
581             return A.init.visibility;
582         }
583         else
584         {
585             foreach (i, uda; attributes)
586             {
587                 static if (is(typeof(uda) == A))
588                 {
589                     return uda.visibility;
590                 }
591                 else static if (is(uda == A))
592                 {
593                     return A.init.visibility;
594                 }
595                 else static if (i + 1 == attributes.length)
596                 {
597                     return A.init.visibility;
598                 }
599             }
600         }
601     }
602 }
603 
604 @("applies visibility from the uda parameter")
605 @nogc nothrow pure @safe unittest
606 {
607     @Read("public") int publicInt;
608     @Read("package") int packageInt;
609     @Read("protected") int protectedInt;
610     @Read("private") int privateInt;
611     @Read int defaultVisibleInt;
612     @Read @Write("protected") int publicReadableProtectedWritableInt;
613 
614     static assert(getVisibility!(Read, __traits(getAttributes, publicInt)) == "public");
615     static assert(getVisibility!(Read, __traits(getAttributes, packageInt)) == "package");
616     static assert(getVisibility!(Read, __traits(getAttributes, protectedInt)) == "protected");
617     static assert(getVisibility!(Read, __traits(getAttributes, privateInt)) == "private");
618     static assert(getVisibility!(Read, __traits(getAttributes, defaultVisibleInt)) == "public");
619     static assert(getVisibility!(Read, __traits(getAttributes, publicReadableProtectedWritableInt)) == "public");
620     static assert(getVisibility!(Write, __traits(getAttributes, publicReadableProtectedWritableInt)) == "protected");
621 }
622 
623 @("creates accessors for flags")
624 nothrow pure @safe unittest
625 {
626     import std.typecons : Flag, No, Yes;
627 
628     class Test
629     {
630         @Read
631         @Write
632         public Flag!"someFlag" test_ = Yes.someFlag;
633 
634         mixin(GenerateFieldAccessors);
635     }
636 
637     with (new Test)
638     {
639         assert(test == Yes.someFlag);
640 
641         test = No.someFlag;
642 
643         assert(test == No.someFlag);
644 
645         static assert(is(typeof(test) == Flag!"someFlag"));
646     }
647 }
648 
649 @("creates accessors for nullables")
650 nothrow pure @safe unittest
651 {
652     import std.typecons : Nullable;
653 
654     class Test
655     {
656         @Read @Write
657         public Nullable!string test_ = Nullable!string("X");
658 
659         mixin(GenerateFieldAccessors);
660     }
661 
662     with (new Test)
663     {
664         assert(!test.isNull);
665         assert(test.get == "X");
666 
667         static assert(is(typeof(test) == Nullable!string));
668     }
669 }
670 
671 @("does not break with const Nullable accessor")
672 nothrow pure @safe unittest
673 {
674     import std.typecons : Nullable;
675 
676     class Test
677     {
678         @Read
679         private const Nullable!string test_;
680 
681         mixin(GenerateFieldAccessors);
682     }
683 
684     with (new Test)
685     {
686         assert(test.isNull);
687     }
688 }
689 
690 @("creates non-const reader")
691 nothrow pure @safe unittest
692 {
693     class Test
694     {
695         @Read
696         int i_;
697 
698         mixin(GenerateFieldAccessors);
699     }
700 
701     auto mutableObject = new Test;
702     const constObject = mutableObject;
703 
704     mutableObject.i_ = 42;
705 
706     assert(mutableObject.i == 42);
707 
708     static assert(is(typeof(mutableObject.i) == int));
709     static assert(is(typeof(constObject.i) == const(int)));
710 }
711 
712 @("creates ref reader")
713 nothrow pure @safe unittest
714 {
715     class Test
716     {
717         @RefRead
718         int i_;
719 
720         mixin(GenerateFieldAccessors);
721     }
722 
723     auto mutableTestObject = new Test;
724 
725     mutableTestObject.i = 42;
726 
727     assert(mutableTestObject.i == 42);
728     static assert(is(typeof(mutableTestObject.i) == int));
729 }
730 
731 @("creates writer")
732 nothrow pure @safe unittest
733 {
734     class Test
735     {
736         @Read @Write
737         private int i_;
738 
739         mixin(GenerateFieldAccessors);
740     }
741 
742     auto mutableTestObject = new Test;
743     mutableTestObject.i = 42;
744 
745     assert(mutableTestObject.i == 42);
746     static assert(!__traits(compiles, mutableTestObject.i += 1));
747     static assert(is(typeof(mutableTestObject.i) == int));
748 }
749 
750 @("checks whether hasUDA can be used for each member")
751 nothrow pure @safe unittest
752 {
753     class Test
754     {
755         alias Z = int;
756 
757         @Read @Write
758         private int i_;
759 
760         mixin(GenerateFieldAccessors);
761     }
762 
763     auto mutableTestObject = new Test;
764     mutableTestObject.i = 42;
765 
766     assert(mutableTestObject.i == 42);
767     static assert(!__traits(compiles, mutableTestObject.i += 1));
768 }
769 
770 @("returns non const for PODs and structs.")
771 nothrow pure @safe unittest
772 {
773     import std.algorithm : map, sort;
774     import std.array : array;
775 
776     class C
777     {
778         @Read
779         string s_;
780 
781         mixin(GenerateFieldAccessors);
782     }
783 
784     C[] a = null;
785 
786     static assert(__traits(compiles, a.map!(c => c.s).array.sort()));
787 }
788 
789 @("functions with strings")
790 nothrow pure @safe unittest
791 {
792     class C
793     {
794         @Read @Write
795         string s_;
796 
797         mixin(GenerateFieldAccessors);
798     }
799 
800     with (new C)
801     {
802         s = "foo";
803         assert(s == "foo");
804         static assert(is(typeof(s) == string));
805     }
806 }
807 
808 @("supports user-defined accessors")
809 nothrow pure @safe unittest
810 {
811     class C
812     {
813         this()
814         {
815             str_ = "foo";
816         }
817 
818         @RefRead
819         private string str_;
820 
821         public @property const(string) str() const
822         {
823             return this.str_.dup;
824         }
825 
826         mixin(GenerateFieldAccessors);
827     }
828 
829     with (new C)
830     {
831         str = "bar";
832     }
833 }
834 
835 @("creates accessor for locally defined types")
836 @system unittest
837 {
838     class X
839     {
840     }
841 
842     class Test
843     {
844         @Read
845         public X x_;
846 
847         mixin(GenerateFieldAccessors);
848     }
849 
850     with (new Test)
851     {
852         x_ = new X;
853 
854         assert(x == x_);
855         static assert(is(typeof(x) == X));
856     }
857 }
858 
859 @("creates const reader for simple structs")
860 nothrow pure @safe unittest
861 {
862     class Test
863     {
864         struct S
865         {
866             int i;
867         }
868 
869         @Read
870         S s_;
871 
872         mixin(GenerateFieldAccessors);
873     }
874 
875     auto mutableObject = new Test;
876     const constObject = mutableObject;
877 
878     mutableObject.s_.i = 42;
879 
880     assert(constObject.s.i == 42);
881 
882     static assert(is(typeof(mutableObject.s) == Test.S));
883     static assert(is(typeof(constObject.s) == const(Test.S)));
884 }
885 
886 @("returns copies when reading structs")
887 nothrow pure @safe unittest
888 {
889     class Test
890     {
891         struct S
892         {
893             int i;
894         }
895 
896         @Read
897         S s_;
898 
899         mixin(GenerateFieldAccessors);
900     }
901 
902     auto mutableObject = new Test;
903 
904     mutableObject.s.i = 42;
905 
906     assert(mutableObject.s.i == int.init);
907 }
908 
909 @("works with const arrays")
910 nothrow pure @safe unittest
911 {
912     class X
913     {
914     }
915 
916     class C
917     {
918         @Read
919         private const(X)[] foo_;
920 
921         mixin(GenerateFieldAccessors);
922     }
923 
924     auto x = new X;
925 
926     with (new C)
927     {
928         foo_ = [x];
929 
930         auto y = foo;
931 
932         static assert(is(typeof(y) == const(X)[]));
933         static assert(is(typeof(foo) == const(X)[]));
934     }
935 }
936 
937 @("has correct type of int")
938 nothrow pure @safe unittest
939 {
940     class C
941     {
942         @Read
943         private int foo_;
944 
945         mixin(GenerateFieldAccessors);
946     }
947 
948     with (new C)
949     {
950         static assert(is(typeof(foo) == int));
951     }
952 }
953 
954 @("works under inheritance (https://github.com/funkwerk/accessors/issues/5)")
955 @nogc nothrow pure @safe unittest
956 {
957     class A
958     {
959         @Read
960         string foo_;
961 
962         mixin(GenerateFieldAccessors);
963     }
964 
965     class B : A
966     {
967         @Read
968         string bar_;
969 
970         mixin(GenerateFieldAccessors);
971     }
972 }
973 
974 @("transfers struct attributes")
975 @nogc nothrow pure @safe unittest
976 {
977     struct S
978     {
979         this(this)
980         {
981         }
982 
983         void opAssign(S s)
984         {
985         }
986     }
987 
988     class A
989     {
990         @Read
991         S[] foo_;
992 
993         @ConstRead
994         S bar_;
995 
996         @Write
997         S baz_;
998 
999         mixin(GenerateFieldAccessors);
1000     }
1001 }
1002 
1003 @("returns array with mutable elements when reading")
1004 nothrow pure @safe unittest
1005 {
1006     struct Field
1007     {
1008     }
1009 
1010     struct S
1011     {
1012         @Read
1013         Field[] foo_;
1014 
1015         mixin(GenerateFieldAccessors);
1016     }
1017 
1018     with (S())
1019     {
1020         Field[] arr = foo;
1021     }
1022 }
1023 
1024 @("ConstRead with tailconst type")
1025 nothrow pure @safe unittest
1026 {
1027     struct S
1028     {
1029         @ConstRead
1030         string foo_;
1031 
1032         mixin(GenerateFieldAccessors);
1033     }
1034 
1035     auto var = S().foo;
1036 
1037     static assert(is(typeof(var) == string));
1038 }
1039 
1040 @("generates safe static properties for static members")
1041 @safe unittest
1042 {
1043     class MyStaticTest
1044     {
1045         @Read
1046         static int stuff_ = 8;
1047 
1048         mixin(GenerateFieldAccessors);
1049     }
1050 
1051     assert(MyStaticTest.stuff == 8);
1052 }
1053 
1054 @safe unittest
1055 {
1056     struct S
1057     {
1058         @Read @Write
1059         static int foo_ = 8;
1060 
1061         @RefRead
1062         static int bar_ = 6;
1063 
1064         mixin(GenerateFieldAccessors);
1065     }
1066 
1067     assert(S.foo == 8);
1068     static assert(is(typeof({ S.foo = 8; })));
1069     assert(S.bar == 6);
1070 }
1071 
1072 @("does not set @safe on accessors for static __gshared members")
1073 unittest
1074 {
1075     class Test
1076     {
1077         @Read
1078         __gshared int stuff_ = 8;
1079 
1080         mixin(GenerateFieldAccessors);
1081     }
1082 
1083     assert(Test.stuff == 8);
1084 }
1085 
1086 @("does not set inout on accessors for static fields")
1087 unittest
1088 {
1089     class Test
1090     {
1091         @Read
1092         __gshared Object[] stuff_;
1093 
1094         mixin(GenerateFieldAccessors);
1095     }
1096 }
1097 
1098 unittest
1099 {
1100     struct Thing
1101     {
1102         @Read
1103         private int[] content_;
1104 
1105         mixin(GenerateFieldAccessors);
1106     }
1107 
1108     class User
1109     {
1110         void helper(const int[])
1111         {
1112         }
1113 
1114         void doer(const Thing thing)
1115         {
1116             helper(thing.content);
1117         }
1118     }
1119 }
1120 
1121 @("dups nullable arrays")
1122 unittest
1123 {
1124     class Class
1125     {
1126     }
1127 
1128     struct Thing
1129     {
1130         @Read
1131         private Class[] classes_;
1132 
1133         mixin(GenerateFieldAccessors);
1134     }
1135 
1136     const Thing thing;
1137 
1138     assert(thing.classes.length == 0);
1139 }
1140 
1141 @("dups associative arrays")
1142 unittest
1143 {
1144     struct Thing
1145     {
1146         @Read
1147         @Write
1148         private int[int] value_;
1149 
1150         mixin(GenerateFieldAccessors);
1151     }
1152 
1153     auto thing = Thing([2: 3]);
1154     auto value = [2: 3];
1155 
1156     thing.value = value;
1157 
1158     // overwrite return value of @Read
1159     thing.value[2] = 4;
1160     // overwrite source value of @Write
1161     value[2] = 4;
1162 
1163     // member value is still unchanged.
1164     assert(thing.value == [2: 3]);
1165 }
1166 
1167 @("generates invariant checks via precondition for writers")
1168 unittest
1169 {
1170     import boilerplate.conditions : AllNonNull;
1171     import core.exception : AssertError;
1172     import std.algorithm : canFind;
1173     import std.conv : to;
1174     import unit_threaded.should : shouldThrow;
1175 
1176     class Thing
1177     {
1178         @Write @AllNonNull
1179         Object[] objects_;
1180 
1181         this(Object[] objects)
1182         {
1183             this.objects_ = objects.dup;
1184         }
1185 
1186         mixin(GenerateFieldAccessors);
1187     }
1188 
1189     auto thing = new Thing([new Object]);
1190 
1191     auto error = ({ thing.objects = [null]; })().shouldThrow!AssertError;
1192 
1193     assert(error.to!string.canFind("in precondition"));
1194 }
1195 
1196 @("generates out conditions for invariant tags on static accessors")
1197 unittest
1198 {
1199     import boilerplate.conditions : NonInit;
1200     import core.exception : AssertError;
1201     import unit_threaded.should : shouldThrow;
1202 
1203     struct Struct
1204     {
1205         @Read
1206         @NonInit
1207         static int test_;
1208 
1209         @ConstRead
1210         @NonInit
1211         static int test2_;
1212 
1213         mixin(GenerateFieldAccessors);
1214     }
1215 
1216     Struct.test.shouldThrow!AssertError;
1217     Struct.test2.shouldThrow!AssertError;
1218 }