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