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!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!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!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 template needToDup(alias field)
393 {
394     enum needToDup = helper;
395 
396     static enum helper()
397     {
398         static if (isSomeString!(typeof(field)))
399         {
400             return false;
401         }
402         else
403         {
404             return isArray!(typeof(field));
405         }
406     }
407 }
408 
409 @nogc nothrow pure @safe unittest
410 {
411     int integerField;
412     int[] integerArrayField;
413     string stringField;
414 
415     static assert(!needToDup!integerField);
416     static assert(needToDup!integerArrayField);
417     static assert(!needToDup!stringField);
418 }
419 
420 static string accessor(string name) @nogc nothrow pure @safe
421 {
422     import std..string : chomp, chompPrefix;
423 
424     return name.chomp("_").chompPrefix("_");
425 }
426 
427 ///
428 @nogc nothrow pure @safe unittest
429 {
430     assert(accessor("foo_") == "foo");
431     assert(accessor("_foo") == "foo");
432 }
433 
434 /**
435  * Returns a string with the value of the field "visibility" if the field
436  * is annotated with an UDA of type A. The default visibility is "public".
437  */
438 template getVisibility(alias field, A)
439 {
440     import std..string : format;
441 
442     enum getVisibility = helper;
443 
444     static enum helper()
445     {
446         alias attributes = getUDAs!(field, A);
447 
448         static if (attributes.length == 0)
449         {
450             return A.init.visibility;
451         }
452         else
453         {
454             static assert(attributes.length == 1,
455                 format("%s should not have more than one attribute @%s", field.stringof, A.stringof));
456 
457             static if (is(typeof(attributes[0])))
458                 return attributes[0].visibility;
459             else
460                 return A.init.visibility;
461         }
462     }
463 }
464 
465 ///
466 @nogc nothrow pure @safe unittest
467 {
468     @Read("public") int publicInt;
469     @Read("package") int packageInt;
470     @Read("protected") int protectedInt;
471     @Read("private") int privateInt;
472     @Read int defaultVisibleInt;
473     @Read @Write("protected") int publicReadableProtectedWritableInt;
474 
475     static assert(getVisibility!(publicInt, Read) == "public");
476     static assert(getVisibility!(packageInt, Read) == "package");
477     static assert(getVisibility!(protectedInt, Read) == "protected");
478     static assert(getVisibility!(privateInt, Read) == "private");
479     static assert(getVisibility!(defaultVisibleInt, Read) == "public");
480     static assert(getVisibility!(publicReadableProtectedWritableInt, Read) == "public");
481     static assert(getVisibility!(publicReadableProtectedWritableInt, Write) == "protected");
482 }
483 
484 /// Creates accessors for flags.
485 nothrow pure @safe unittest
486 {
487     import std.typecons : Flag, No, Yes;
488 
489     class Test
490     {
491         @Read
492         @Write
493         public Flag!"someFlag" test_ = Yes.someFlag;
494 
495         mixin(GenerateFieldAccessors);
496     }
497 
498     with (new Test)
499     {
500         assert(test == Yes.someFlag);
501 
502         test = No.someFlag;
503 
504         assert(test == No.someFlag);
505 
506         static assert(is(typeof(test) == Flag!"someFlag"));
507     }
508 }
509 
510 /// Creates accessors for Nullables.
511 nothrow pure @safe unittest
512 {
513     import std.typecons : Nullable;
514 
515     class Test
516     {
517         @Read @Write
518         public Nullable!string test_ = Nullable!string("X");
519 
520         mixin(GenerateFieldAccessors);
521     }
522 
523     with (new Test)
524     {
525         assert(!test.isNull);
526         assert(test.get == "X");
527 
528         static assert(is(typeof(test) == Nullable!string));
529     }
530 }
531 
532 /// Creates non-const reader.
533 nothrow pure @safe unittest
534 {
535     class Test
536     {
537         @Read
538         int i_;
539 
540         mixin(GenerateFieldAccessors);
541     }
542 
543     auto mutableObject = new Test;
544     const constObject = mutableObject;
545 
546     mutableObject.i_ = 42;
547 
548     assert(mutableObject.i == 42);
549 
550     static assert(is(typeof(mutableObject.i) == int));
551     static assert(is(typeof(constObject.i) == const(int)));
552 }
553 
554 /// Creates ref reader.
555 nothrow pure @safe unittest
556 {
557     class Test
558     {
559         @RefRead
560         int i_;
561 
562         mixin(GenerateFieldAccessors);
563     }
564 
565     auto mutableTestObject = new Test;
566 
567     mutableTestObject.i = 42;
568 
569     assert(mutableTestObject.i == 42);
570     static assert(is(typeof(mutableTestObject.i) == int));
571 }
572 
573 /// Creates writer.
574 nothrow pure @safe unittest
575 {
576     class Test
577     {
578         @Read @Write
579         private int i_;
580 
581         mixin(GenerateFieldAccessors);
582     }
583 
584     auto mutableTestObject = new Test;
585     mutableTestObject.i = 42;
586 
587     assert(mutableTestObject.i == 42);
588     static assert(!__traits(compiles, mutableTestObject.i += 1));
589     static assert(is(typeof(mutableTestObject.i) == int));
590 }
591 
592 /// Checks whether hasUDA can be used for each member.
593 nothrow pure @safe unittest
594 {
595     class Test
596     {
597         alias Z = int;
598 
599         @Read @Write
600         private int i_;
601 
602         mixin(GenerateFieldAccessors);
603     }
604 
605     auto mutableTestObject = new Test;
606     mutableTestObject.i = 42;
607 
608     assert(mutableTestObject.i == 42);
609     static assert(!__traits(compiles, mutableTestObject.i += 1));
610 }
611 
612 /// Returns non const for PODs and structs.
613 nothrow pure @safe unittest
614 {
615     import std.algorithm : map, sort;
616     import std.array : array;
617 
618     class C
619     {
620         @Read
621         string s_;
622 
623         mixin(GenerateFieldAccessors);
624     }
625 
626     C[] a = null;
627 
628     static assert(__traits(compiles, a.map!(c => c.s).array.sort()));
629 }
630 
631 /// Regression.
632 nothrow pure @safe unittest
633 {
634     class C
635     {
636         @Read @Write
637         string s_;
638 
639         mixin(GenerateFieldAccessors);
640     }
641 
642     with (new C)
643     {
644         s = "foo";
645         assert(s == "foo");
646         static assert(is(typeof(s) == string));
647     }
648 }
649 
650 /// Supports user-defined accessors.
651 nothrow pure @safe unittest
652 {
653     class C
654     {
655         this()
656         {
657             str_ = "foo";
658         }
659 
660         @RefRead
661         private string str_;
662 
663         public @property const(string) str() const
664         {
665             return this.str_.dup;
666         }
667 
668         mixin(GenerateFieldAccessors);
669     }
670 
671     with (new C)
672     {
673         str = "bar";
674     }
675 }
676 
677 /// Creates accessor for locally defined types.
678 @system unittest
679 {
680     class X
681     {
682     }
683 
684     class Test
685     {
686         @Read
687         public X x_;
688 
689         mixin(GenerateFieldAccessors);
690     }
691 
692     with (new Test)
693     {
694         x_ = new X;
695 
696         assert(x == x_);
697         static assert(is(typeof(x) == X));
698     }
699 }
700 
701 /// Creates const reader for simple structs.
702 nothrow pure @safe unittest
703 {
704     class Test
705     {
706         struct S
707         {
708             int i;
709         }
710 
711         @Read
712         S s_;
713 
714         mixin(GenerateFieldAccessors);
715     }
716 
717     auto mutableObject = new Test;
718     const constObject = mutableObject;
719 
720     mutableObject.s_.i = 42;
721 
722     assert(constObject.s.i == 42);
723 
724     static assert(is(typeof(mutableObject.s) == Test.S));
725     static assert(is(typeof(constObject.s) == const(Test.S)));
726 }
727 
728 /// Reader for structs return copies.
729 nothrow pure @safe unittest
730 {
731     class Test
732     {
733         struct S
734         {
735             int i;
736         }
737 
738         @Read
739         S s_;
740 
741         mixin(GenerateFieldAccessors);
742     }
743 
744     auto mutableObject = new Test;
745 
746     mutableObject.s.i = 42;
747 
748     assert(mutableObject.s.i == int.init);
749 }
750 
751 /// Creates reader for const arrays.
752 nothrow pure @safe unittest
753 {
754     class X
755     {
756     }
757 
758     class C
759     {
760         @Read
761         private const(X)[] foo_;
762 
763         mixin(GenerateFieldAccessors);
764     }
765 
766     auto x = new X;
767 
768     with (new C)
769     {
770         foo_ = [x];
771 
772         auto y = foo;
773 
774         static assert(is(typeof(y) == const(X)[]));
775         static assert(is(typeof(foo) == const(X)[]));
776     }
777 }
778 
779 /// Property has correct type.
780 nothrow pure @safe unittest
781 {
782     class C
783     {
784         @Read
785         private int foo_;
786 
787         mixin(GenerateFieldAccessors);
788     }
789 
790     with (new C)
791     {
792         static assert(is(typeof(foo) == int));
793     }
794 }
795 
796 /// Inheritance (https://github.com/funkwerk/accessors/issues/5).
797 @nogc nothrow pure @safe unittest
798 {
799     class A
800     {
801         @Read
802         string foo_;
803 
804         mixin(GenerateFieldAccessors);
805     }
806 
807     class B : A
808     {
809         @Read
810         string bar_;
811 
812         mixin(GenerateFieldAccessors);
813     }
814 }
815 
816 /// Transfers struct attributes.
817 @nogc nothrow pure @safe unittest
818 {
819     struct S
820     {
821         this(this)
822         {
823         }
824 
825         void opAssign(S s)
826         {
827         }
828     }
829 
830     class A
831     {
832         @Read
833         S[] foo_;
834 
835         @ConstRead
836         S bar_;
837 
838         @Write
839         S baz_;
840 
841         mixin(GenerateFieldAccessors);
842     }
843 }
844 
845 /// @Read property returns array with mutable elements.
846 nothrow pure @safe unittest
847 {
848     struct Field
849     {
850     }
851 
852     struct S
853     {
854         @Read
855         Field[] foo_;
856 
857         mixin(GenerateFieldAccessors);
858     }
859 
860     with (S())
861     {
862         Field[] arr = foo;
863     }
864 }
865 
866 /// Static properties are generated for static members.
867 unittest
868 {
869     class MyStaticTest
870     {
871         @Read
872         static int stuff_ = 8;
873 
874         mixin(GenerateFieldAccessors);
875     }
876 
877     assert(MyStaticTest.stuff == 8);
878 }
879 
880 unittest
881 {
882     struct S
883     {
884         @Read @Write
885         static int foo_ = 8;
886 
887         @RefRead
888         static int bar_ = 6;
889 
890         mixin(GenerateFieldAccessors);
891     }
892 
893     assert(S.foo == 8);
894     static assert(is(typeof({ S.foo = 8; })));
895     assert(S.bar == 6);
896 }
897 
898 @nogc nothrow pure @safe unittest
899 {
900     struct Thing
901     {
902         @Read
903         private int[] content_;
904 
905         mixin(GenerateFieldAccessors);
906     }
907 
908     class User
909     {
910         void helper(const int[] content)
911         {
912         }
913 
914         void doer(const Thing thing)
915         {
916             helper(thing.content);
917         }
918     }
919 }
920 
921 nothrow pure @safe unittest
922 {
923     class Class
924     {
925     }
926 
927     struct Thing
928     {
929         @Read
930         private Class[] classes_;
931 
932         mixin(GenerateFieldAccessors);
933     }
934 
935     Thing thing;
936     assert(thing.classes.length == 0);
937 }