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