1 module accessors;
2 
3 import std.traits;
4 
5 struct Read
6 {
7     string visibility = "public";
8 }
9 
10 // Deprecated! See below.
11 // RefRead can not check invariants on change, so there's no point.
12 // ref property functions where the value being returned is a field of the class
13 // are entirely equivalent to public fields.
14 struct RefRead
15 {
16     string visibility = "public";
17 }
18 
19 struct ConstRead
20 {
21     string visibility = "public";
22 }
23 
24 struct Write
25 {
26     string visibility = "public";
27 }
28 
29 immutable string GenerateFieldAccessors = `
30     mixin GenerateFieldAccessorMethods;
31     mixin(GenerateFieldAccessorMethodsImpl);
32     `;
33 
34 mixin template GenerateFieldAccessorMethods()
35 {
36     import std.meta : Alias, Filter;
37 
38     private enum bool isNotThis(string T) = T != "this";
39 
40     static enum GenerateFieldAccessorMethodsImpl()
41     {
42         import std.traits : hasUDA;
43 
44         string result = "";
45 
46         foreach (name; Filter!(isNotThis, __traits(derivedMembers, typeof(this))))
47         {
48             enum string fieldCode = `Alias!(__traits(getMember, typeof(this), "` ~ name ~ `"))`;
49             mixin("alias field = " ~ fieldCode ~ ";");
50 
51             static if (__traits(compiles, hasUDA!(field, Read)))
52             {
53                 static if (hasUDA!(field, Read))
54                 {
55                     enum string readerDecl = GenerateReader!(name, field);
56                     debug (accessors) pragma(msg, readerDecl);
57                     result ~= readerDecl;
58                 }
59 
60                 static if (hasUDA!(field, RefRead))
61                 {
62                     result ~= `pragma(msg, "Deprecation! RefRead on ` ~ typeof(this).stringof ~ `.` ~ name
63                         ~ ` makes a private field effectively public, defeating the point.");`;
64 
65                     enum string refReaderDecl = GenerateRefReader!(name, field);
66                     debug (accessors) pragma(msg, refReaderDecl);
67                     result ~= refReaderDecl;
68                 }
69 
70                 static if (hasUDA!(field, ConstRead))
71                 {
72                     enum string constReaderDecl = GenerateConstReader!(name, field);
73                     debug (accessors) pragma(msg, constReaderDecl);
74                     result ~= constReaderDecl;
75                 }
76 
77                 static if (hasUDA!(field, Write))
78                 {
79                     enum string writerDecl = GenerateWriter!(name, field, fieldCode);
80                     debug (accessors) pragma(msg, writerDecl);
81                     result ~= writerDecl;
82                 }
83             }
84         }
85 
86         return result;
87     }
88 }
89 
90 template isStatic(alias field)
91 {
92     enum isStatic = helper;
93 
94     static enum helper()
95     {
96         return is(typeof({ auto f = field; }));
97     }
98 }
99 
100 template getModifiers(alias field)
101 {
102     enum getModifiers = helper;
103 
104     static enum helper()
105     {
106         static if (isStatic!field)
107         {
108             return " static";
109         }
110         else
111         {
112             return "";
113         }
114     }
115 }
116 
117 template filterAttributes(alias field)
118 {
119     enum filterAttributes = helper;
120 
121     static enum helper()
122     {
123         uint attributes = uint.max;
124         static if (needToDup!(typeof(field)))
125         {
126             attributes &= ~FunctionAttribute.nogc;
127         }
128         static if (isStatic!field)
129         {
130             attributes &= ~FunctionAttribute.pure_;
131         }
132         return attributes;
133     }
134 }
135 
136 template GenerateReader(string name, alias field)
137 {
138     enum GenerateReader = helper;
139 
140     static enum helper()
141     {
142         import std..string : format;
143 
144         enum visibility = getVisibility!(field, Read);
145         enum accessorName = accessor(name);
146         enum needToDup = needToDup!(typeof(field));
147 
148         enum uint attributes = inferAttributes!(typeof(field), "__postblit") &
149             filterAttributes!field;
150 
151         string attributesString = generateAttributeString!attributes;
152 
153         static if (needToDup)
154         {
155             return format("%s%s final @property auto %s() inout %s"
156                         ~ "{ return typeof(this.%s).init ~ this.%s; }",
157                           visibility, getModifiers!field, accessorName, attributesString, name, name);
158         }
159         else static if (isStatic!field)
160         {
161             return format("%s static final @property auto %s() %s{ return this.%s; }",
162                 visibility, accessorName, attributesString, name);
163         }
164         else
165         {
166             return format("%s final @property auto %s() inout %s{ return this.%s; }",
167                 visibility, accessorName, attributesString, name);
168         }
169     }
170 }
171 
172 ///
173 @nogc nothrow pure @safe unittest
174 {
175     int integerValue;
176     string stringValue;
177     int[] intArrayValue;
178 
179     static assert(GenerateReader!("foo", integerValue) ==
180         "public static final @property auto foo() " ~
181         "@nogc nothrow @safe { return this.foo; }");
182     static assert(GenerateReader!("foo", stringValue) ==
183         "public static final @property auto foo() " ~
184         "@nogc nothrow @safe { return this.foo; }");
185     static assert(GenerateReader!("foo", intArrayValue) ==
186         "public static final @property auto foo() inout nothrow @safe "
187       ~ "{ return typeof(this.foo).init ~ this.foo; }");
188 }
189 
190 template GenerateRefReader(string name, alias field)
191 {
192     enum GenerateRefReader = helper;
193 
194     static enum helper()
195     {
196         import std..string : format;
197 
198         enum visibility = getVisibility!(field, RefRead);
199         enum accessorName = accessor(name);
200 
201         static if (isStatic!field)
202         {
203             enum string attributesString = "@nogc nothrow @safe ";
204         }
205         else
206         {
207             enum string attributesString = "@nogc nothrow pure @safe ";
208         }
209 
210         return format("%s%s final @property ref auto %s() " ~
211             "%s{ return this.%s; }",
212             visibility, getModifiers!field, accessorName, attributesString, name);
213     }
214 }
215 
216 ///
217 @nogc nothrow pure @safe unittest
218 {
219     int integerValue;
220     string stringValue;
221     int[] intArrayValue;
222 
223     static assert(GenerateRefReader!("foo", integerValue) ==
224         "public static final @property ref auto foo() " ~
225         "@nogc nothrow @safe { return this.foo; }");
226     static assert(GenerateRefReader!("foo", stringValue) ==
227         "public static final @property ref auto foo() " ~
228         "@nogc nothrow @safe { return this.foo; }");
229     static assert(GenerateRefReader!("foo", intArrayValue) ==
230         "public static final @property ref auto foo() " ~
231         "@nogc nothrow @safe { return this.foo; }");
232 }
233 
234 template GenerateConstReader(string name, alias field)
235 {
236     enum GenerateConstReader = helper;
237 
238     static enum helper()
239     {
240         import std..string : format;
241 
242         enum visibility = getVisibility!(field, RefRead);
243         enum accessorName = accessor(name);
244 
245         alias attributes = inferAttributes!(typeof(field), "__postblit");
246         string attributesString = generateAttributeString!attributes;
247 
248         return format("%s final @property auto %s() const %s { return this.%s; }",
249             visibility, accessorName, attributesString, name);
250     }
251 }
252 
253 template GenerateWriter(string name, alias field, string fieldCode)
254 {
255     enum GenerateWriter = helper;
256 
257     static enum helper()
258     {
259         import std..string : format;
260 
261         enum visibility = getVisibility!(field, Write);
262         enum accessorName = accessor(name);
263         enum inputName = accessorName;
264         enum needToDup = needToDup!(typeof(field));
265 
266         enum uint attributes = defaultFunctionAttributes &
267             filterAttributes!field &
268             inferAssignAttributes!(typeof(field)) &
269             inferAttributes!(typeof(field), "__postblit") &
270             inferAttributes!(typeof(field), "__dtor");
271 
272         enum attributesString = generateAttributeString!attributes;
273 
274         return format("%s%s final @property void %s(typeof(%s) %s) %s{ this.%s = %s%s; }",
275             visibility, getModifiers!field, accessorName, fieldCode, inputName,
276             attributesString, name, inputName, needToDup ? ".dup" : "");
277     }
278 }
279 
280 ///
281 @nogc nothrow pure @safe unittest
282 {
283     int integerValue;
284     string stringValue;
285     int[] intArrayValue;
286 
287     static assert(GenerateWriter!("foo", integerValue, "integerValue") ==
288         "public static final @property void foo(typeof(integerValue) foo) " ~
289         "@nogc nothrow @safe { this.foo = foo; }");
290     static assert(GenerateWriter!("foo", stringValue, "stringValue") ==
291         "public static final @property void foo(typeof(stringValue) foo) " ~
292         "@nogc nothrow @safe { this.foo = foo; }");
293     static assert(GenerateWriter!("foo", intArrayValue, "intArrayValue") ==
294         "public static final @property void foo(typeof(intArrayValue) foo) " ~
295         "nothrow @safe { this.foo = foo.dup; }");
296 }
297 
298 private enum uint defaultFunctionAttributes =
299             FunctionAttribute.nogc |
300             FunctionAttribute.safe |
301             FunctionAttribute.nothrow_ |
302             FunctionAttribute.pure_;
303 
304 private template inferAttributes(T, string M)
305 {
306     enum uint inferAttributes()
307     {
308         uint attributes = defaultFunctionAttributes;
309 
310         static if (is(T == struct))
311         {
312             static if (hasMember!(T, M))
313             {
314                 attributes &= functionAttributes!(__traits(getMember, T, M));
315             }
316             else
317             {
318                 foreach (field; Fields!T)
319                 {
320                     attributes &= inferAttributes!(field, M);
321                 }
322             }
323         }
324         return attributes;
325     }
326 }
327 
328 private enum uint inferAssignAttributes(T)()
329 {
330     static if (hasElaborateAssign!T)
331     {
332         void testAttributes()()
333         {
334             auto testObject = T.init;
335             testObject = T.init;
336         }
337         return functionAttributes!(testAttributes!());
338     }
339     else
340     {
341         return defaultFunctionAttributes;
342     }
343 }
344 
345 // Issue 22: https://github.com/funkwerk/accessors/issues/22
346 @nogc nothrow pure @safe unittest
347 {
348     static struct SysTime
349     {
350         void opAssign(SysTime) @safe pure nothrow
351         {
352             new int;
353         }
354     }
355     static struct Nullable
356     {
357         SysTime t;
358         void opAssign()(SysTime)
359         {
360         }
361     }
362     static assert((inferAssignAttributes!Nullable & FunctionAttribute.nogc) == 0);
363 }
364 
365 private template generateAttributeString(uint attributes)
366 {
367     enum string generateAttributeString()
368     {
369         string attributesString;
370 
371         static if (attributes & FunctionAttribute.nogc)
372         {
373             attributesString ~= "@nogc ";
374         }
375         static if (attributes & FunctionAttribute.nothrow_)
376         {
377             attributesString ~= "nothrow ";
378         }
379         static if (attributes & FunctionAttribute.pure_)
380         {
381             attributesString ~= "pure ";
382         }
383         static if (attributes & FunctionAttribute.safe)
384         {
385             attributesString ~= "@safe ";
386         }
387 
388         return attributesString;
389     }
390 }
391 
392 private enum needToDup(T) = isArray!T && !DeepConst!T;
393 
394 private enum DeepConst(T) = __traits(compiles, (const T x) { T y = x; });
395 
396 @nogc nothrow pure @safe unittest
397 {
398     int integerField;
399     int[] integerArrayField;
400     string stringField;
401     const int[] constIntegerArrayField;
402 
403     static assert(!needToDup!(typeof(integerField)));
404     static assert(needToDup!(typeof(integerArrayField)));
405     static assert(!needToDup!(typeof(stringField)));
406     static assert(!needToDup!(typeof(constIntegerArrayField)));
407 }
408 
409 static string accessor(string name) @nogc nothrow pure @safe
410 {
411     import std..string : chomp, chompPrefix;
412 
413     return name.chomp("_").chompPrefix("_");
414 }
415 
416 ///
417 @nogc nothrow pure @safe unittest
418 {
419     assert(accessor("foo_") == "foo");
420     assert(accessor("_foo") == "foo");
421 }
422 
423 /**
424  * Returns a string with the value of the field "visibility" if the field
425  * is annotated with an UDA of type A. The default visibility is "public".
426  */
427 template getVisibility(alias field, A)
428 {
429     import std..string : format;
430 
431     enum getVisibility = helper;
432 
433     static enum helper()
434     {
435         alias attributes = getUDAs!(field, A);
436 
437         static if (attributes.length == 0)
438         {
439             return A.init.visibility;
440         }
441         else
442         {
443             static assert(attributes.length == 1,
444                 format("%s should not have more than one attribute @%s", field.stringof, A.stringof));
445 
446             static if (is(typeof(attributes[0])))
447                 return attributes[0].visibility;
448             else
449                 return A.init.visibility;
450         }
451     }
452 }
453 
454 ///
455 @nogc nothrow pure @safe unittest
456 {
457     @Read("public") int publicInt;
458     @Read("package") int packageInt;
459     @Read("protected") int protectedInt;
460     @Read("private") int privateInt;
461     @Read int defaultVisibleInt;
462     @Read @Write("protected") int publicReadableProtectedWritableInt;
463 
464     static assert(getVisibility!(publicInt, Read) == "public");
465     static assert(getVisibility!(packageInt, Read) == "package");
466     static assert(getVisibility!(protectedInt, Read) == "protected");
467     static assert(getVisibility!(privateInt, Read) == "private");
468     static assert(getVisibility!(defaultVisibleInt, Read) == "public");
469     static assert(getVisibility!(publicReadableProtectedWritableInt, Read) == "public");
470     static assert(getVisibility!(publicReadableProtectedWritableInt, Write) == "protected");
471 }
472 
473 /// Creates accessors for flags.
474 nothrow pure @safe unittest
475 {
476     import std.typecons : Flag, No, Yes;
477 
478     class Test
479     {
480         @Read
481         @Write
482         public Flag!"someFlag" test_ = Yes.someFlag;
483 
484         mixin(GenerateFieldAccessors);
485     }
486 
487     with (new Test)
488     {
489         assert(test == Yes.someFlag);
490 
491         test = No.someFlag;
492 
493         assert(test == No.someFlag);
494 
495         static assert(is(typeof(test) == Flag!"someFlag"));
496     }
497 }
498 
499 /// Creates accessors for Nullables.
500 nothrow pure @safe unittest
501 {
502     import std.typecons : Nullable;
503 
504     class Test
505     {
506         @Read @Write
507         public Nullable!string test_ = Nullable!string("X");
508 
509         mixin(GenerateFieldAccessors);
510     }
511 
512     with (new Test)
513     {
514         assert(!test.isNull);
515         assert(test.get == "X");
516 
517         static assert(is(typeof(test) == Nullable!string));
518     }
519 }
520 
521 /// Creates non-const reader.
522 nothrow pure @safe unittest
523 {
524     class Test
525     {
526         @Read
527         int i_;
528 
529         mixin(GenerateFieldAccessors);
530     }
531 
532     auto mutableObject = new Test;
533     const constObject = mutableObject;
534 
535     mutableObject.i_ = 42;
536 
537     assert(mutableObject.i == 42);
538 
539     static assert(is(typeof(mutableObject.i) == int));
540     static assert(is(typeof(constObject.i) == const(int)));
541 }
542 
543 /// Creates ref reader.
544 nothrow pure @safe unittest
545 {
546     class Test
547     {
548         @RefRead
549         int i_;
550 
551         mixin(GenerateFieldAccessors);
552     }
553 
554     auto mutableTestObject = new Test;
555 
556     mutableTestObject.i = 42;
557 
558     assert(mutableTestObject.i == 42);
559     static assert(is(typeof(mutableTestObject.i) == int));
560 }
561 
562 /// Creates writer.
563 nothrow pure @safe unittest
564 {
565     class Test
566     {
567         @Read @Write
568         private int i_;
569 
570         mixin(GenerateFieldAccessors);
571     }
572 
573     auto mutableTestObject = new Test;
574     mutableTestObject.i = 42;
575 
576     assert(mutableTestObject.i == 42);
577     static assert(!__traits(compiles, mutableTestObject.i += 1));
578     static assert(is(typeof(mutableTestObject.i) == int));
579 }
580 
581 /// Checks whether hasUDA can be used for each member.
582 nothrow pure @safe unittest
583 {
584     class Test
585     {
586         alias Z = int;
587 
588         @Read @Write
589         private int i_;
590 
591         mixin(GenerateFieldAccessors);
592     }
593 
594     auto mutableTestObject = new Test;
595     mutableTestObject.i = 42;
596 
597     assert(mutableTestObject.i == 42);
598     static assert(!__traits(compiles, mutableTestObject.i += 1));
599 }
600 
601 /// Returns non const for PODs and structs.
602 nothrow pure @safe unittest
603 {
604     import std.algorithm : map, sort;
605     import std.array : array;
606 
607     class C
608     {
609         @Read
610         string s_;
611 
612         mixin(GenerateFieldAccessors);
613     }
614 
615     C[] a = null;
616 
617     static assert(__traits(compiles, a.map!(c => c.s).array.sort()));
618 }
619 
620 /// Regression.
621 nothrow pure @safe unittest
622 {
623     class C
624     {
625         @Read @Write
626         string s_;
627 
628         mixin(GenerateFieldAccessors);
629     }
630 
631     with (new C)
632     {
633         s = "foo";
634         assert(s == "foo");
635         static assert(is(typeof(s) == string));
636     }
637 }
638 
639 /// Supports user-defined accessors.
640 nothrow pure @safe unittest
641 {
642     class C
643     {
644         this()
645         {
646             str_ = "foo";
647         }
648 
649         @RefRead
650         private string str_;
651 
652         public @property const(string) str() const
653         {
654             return this.str_.dup;
655         }
656 
657         mixin(GenerateFieldAccessors);
658     }
659 
660     with (new C)
661     {
662         str = "bar";
663     }
664 }
665 
666 /// Creates accessor for locally defined types.
667 @system unittest
668 {
669     class X
670     {
671     }
672 
673     class Test
674     {
675         @Read
676         public X x_;
677 
678         mixin(GenerateFieldAccessors);
679     }
680 
681     with (new Test)
682     {
683         x_ = new X;
684 
685         assert(x == x_);
686         static assert(is(typeof(x) == X));
687     }
688 }
689 
690 /// Creates const reader for simple structs.
691 nothrow pure @safe unittest
692 {
693     class Test
694     {
695         struct S
696         {
697             int i;
698         }
699 
700         @Read
701         S s_;
702 
703         mixin(GenerateFieldAccessors);
704     }
705 
706     auto mutableObject = new Test;
707     const constObject = mutableObject;
708 
709     mutableObject.s_.i = 42;
710 
711     assert(constObject.s.i == 42);
712 
713     static assert(is(typeof(mutableObject.s) == Test.S));
714     static assert(is(typeof(constObject.s) == const(Test.S)));
715 }
716 
717 /// Reader for structs return copies.
718 nothrow pure @safe unittest
719 {
720     class Test
721     {
722         struct S
723         {
724             int i;
725         }
726 
727         @Read
728         S s_;
729 
730         mixin(GenerateFieldAccessors);
731     }
732 
733     auto mutableObject = new Test;
734 
735     mutableObject.s.i = 42;
736 
737     assert(mutableObject.s.i == int.init);
738 }
739 
740 /// Creates reader for const arrays.
741 nothrow pure @safe unittest
742 {
743     class X
744     {
745     }
746 
747     class C
748     {
749         @Read
750         private const(X)[] foo_;
751 
752         mixin(GenerateFieldAccessors);
753     }
754 
755     auto x = new X;
756 
757     with (new C)
758     {
759         foo_ = [x];
760 
761         auto y = foo;
762 
763         static assert(is(typeof(y) == const(X)[]));
764         static assert(is(typeof(foo) == const(X)[]));
765     }
766 }
767 
768 /// Property has correct type.
769 nothrow pure @safe unittest
770 {
771     class C
772     {
773         @Read
774         private int foo_;
775 
776         mixin(GenerateFieldAccessors);
777     }
778 
779     with (new C)
780     {
781         static assert(is(typeof(foo) == int));
782     }
783 }
784 
785 /// Inheritance (https://github.com/funkwerk/accessors/issues/5).
786 @nogc nothrow pure @safe unittest
787 {
788     class A
789     {
790         @Read
791         string foo_;
792 
793         mixin(GenerateFieldAccessors);
794     }
795 
796     class B : A
797     {
798         @Read
799         string bar_;
800 
801         mixin(GenerateFieldAccessors);
802     }
803 }
804 
805 /// Transfers struct attributes.
806 @nogc nothrow pure @safe unittest
807 {
808     struct S
809     {
810         this(this)
811         {
812         }
813 
814         void opAssign(S s)
815         {
816         }
817     }
818 
819     class A
820     {
821         @Read
822         S[] foo_;
823 
824         @ConstRead
825         S bar_;
826 
827         @Write
828         S baz_;
829 
830         mixin(GenerateFieldAccessors);
831     }
832 }
833 
834 /// @Read property returns array with mutable elements.
835 nothrow pure @safe unittest
836 {
837     struct Field
838     {
839     }
840 
841     struct S
842     {
843         @Read
844         Field[] foo_;
845 
846         mixin(GenerateFieldAccessors);
847     }
848 
849     with (S())
850     {
851         Field[] arr = foo;
852     }
853 }
854 
855 /// Static properties are generated for static members.
856 unittest
857 {
858     class MyStaticTest
859     {
860         @Read
861         static int stuff_ = 8;
862 
863         mixin(GenerateFieldAccessors);
864     }
865 
866     assert(MyStaticTest.stuff == 8);
867 }
868 
869 unittest
870 {
871     struct S
872     {
873         @Read @Write
874         static int foo_ = 8;
875 
876         @RefRead
877         static int bar_ = 6;
878 
879         mixin(GenerateFieldAccessors);
880     }
881 
882     assert(S.foo == 8);
883     static assert(is(typeof({ S.foo = 8; })));
884     assert(S.bar == 6);
885 }
886 
887 @nogc nothrow pure @safe unittest
888 {
889     struct Thing
890     {
891         @Read
892         private int[] content_;
893 
894         mixin(GenerateFieldAccessors);
895     }
896 
897     class User
898     {
899         void helper(const int[] content)
900         {
901         }
902 
903         void doer(const Thing thing)
904         {
905             helper(thing.content);
906         }
907     }
908 }
909 
910 nothrow pure @safe unittest
911 {
912     class Class
913     {
914     }
915 
916     struct Thing
917     {
918         @Read
919         private Class[] classes_;
920 
921         mixin(GenerateFieldAccessors);
922     }
923 
924     Thing thing;
925     assert(thing.classes.length == 0);
926 }