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