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