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