1 /++
2 Base reflection utilities.
3 
4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
5 Authors: Ilya Yaroshenko 
6 Macros:
7 +/
8 module mir.reflection;
9 
10 import std.meta;
11 import std.traits: hasUDA, getUDAs, Parameters, isSomeFunction, FunctionAttribute, functionAttributes, EnumMembers, isAggregateType;
12 
13 deprecated
14 package alias isSomeStruct = isAggregateType;
15 
16 /++
17 Match types like `std.typeconst: Nullable`.
18 +/
19 template isStdNullable(T)
20 {
21     import std.traits : hasMember;
22 
23     T* aggregate;
24 
25     enum bool isStdNullable =
26         hasMember!(T, "isNull") &&
27         hasMember!(T, "get") &&
28         hasMember!(T, "nullify") &&
29         is(typeof(__traits(getMember, aggregate, "isNull")()) == bool) &&
30         !is(typeof(__traits(getMember, aggregate, "get")()) == void) &&
31         is(typeof(__traits(getMember, aggregate, "nullify")()) == void);
32 }
33 
34 ///
35 version(mir_core_test) unittest
36 {
37     import std.typecons;
38     static assert(isStdNullable!(Nullable!double));
39 }
40 
41 ///
42 version(mir_core_test) unittest
43 {
44     import mir.algebraic;
45     static assert(isStdNullable!(Nullable!double));
46 }
47 
48 ///  Attribute for deprecated API
49 struct reflectDeprecated(string target)
50 {
51     string info;
52 }
53 
54 /// Attribute to rename methods, types and functions
55 template ReflectName(string target)
56 {
57     ///
58     struct ReflectName(Args...)
59     {
60         ///
61         string name;
62     }
63 }
64 
65 /// ditto
66 template reflectName(string target = null, Args...)
67 {
68     ///
69     auto reflectName(string name)
70     {
71         alias TargetName = ReflectName!target;
72         return TargetName!Args(name);
73     }
74 }
75 
76 ///
77 version(mir_core_test) unittest
78 {
79     enum E { A, B, C }
80 
81     struct S
82     {
83         @reflectName("A")
84         int a;
85 
86         @reflectName!"c++"("B")
87         int b;
88 
89         @reflectName!("C",  double)("cd")
90         @reflectName!("C",  float)("cf")
91         F c(F)()
92         {
93             return b;
94         }
95     }
96 
97     import std.traits: hasUDA;
98 
99     alias UniName = ReflectName!null;
100     alias CppName = ReflectName!"c++";
101     alias CName = ReflectName!"C";
102 
103     static assert(hasUDA!(S.a, UniName!()("A")));
104     static assert(hasUDA!(S.b, CppName!()("B")));
105 
106     // static assert(hasUDA!(S.c, ReflectName)); // doesn't work for now
107     static assert(hasUDA!(S.c, CName));
108     static assert(hasUDA!(S.c, CName!double));
109     static assert(hasUDA!(S.c, CName!float));
110     static assert(hasUDA!(S.c, CName!double("cd")));
111     static assert(hasUDA!(S.c, CName!float("cf")));
112 }
113 
114 /// Attribute to rename methods, types and functions
115 template ReflectMeta(string target, string[] fields)
116 {
117     ///
118     struct ReflectMeta(Args...)
119     {
120         ///
121         Args args;
122         static foreach(i, field; fields)
123             mixin(`alias ` ~ field ~` = args[` ~ i.stringof ~`];`);
124     }
125 }
126 
127 /// ditto
128 template reflectMeta(string target, string[] fields)
129 {
130     ///
131     auto reflectMeta(Args...)(Args args)
132         if (args.length <= fields.length)
133     {
134         alias TargetMeta = ReflectMeta!(target, fields);
135         return TargetMeta!Args(args);
136     }
137 }
138 
139 ///
140 version(mir_core_test) unittest
141 {
142     enum E { A, B, C }
143 
144     struct S
145     {
146         int a;
147         @reflectMeta!("c++", ["type"])(E.C)
148         int b;
149     }
150 
151     import std.traits: hasUDA;
152 
153     alias CppMeta = ReflectMeta!("c++", ["type"]);
154 
155     static assert(CppMeta!E(E.C).type == E.C);
156     static assert(!hasUDA!(S.a, CppMeta!E(E.A)));
157     static assert(hasUDA!(S.b, CppMeta!E(E.C)));
158 }
159 
160 /++
161 Attribute to ignore a reflection target
162 +/
163 template reflectIgnore(string target)
164 {
165     enum reflectIgnore;
166 }
167 
168 ///
169 version(mir_core_test) unittest
170 {
171     struct S
172     {
173         @reflectIgnore!"c++"
174         int a;
175     }
176 
177     import std.traits: hasUDA;
178     static assert(hasUDA!(S.a, reflectIgnore!"c++"));
179 }
180 
181 /// Attribute for documentation and unittests
182 struct ReflectDoc(string target)
183 {
184     ///
185     string text;
186     ///
187     reflectUnittest!target test;
188 
189     ///
190     @safe pure nothrow @nogc
191     this(string text)
192     {
193         this.text = text;
194     }
195 
196     ///
197     @safe pure nothrow @nogc
198     this(string text, reflectUnittest!target test)
199     {
200         this.text = text;
201         this.test = test;
202     }
203 
204     ///
205     void toString(W)(scope ref W w) scope const
206     {
207         w.put(cast()this.text);
208 
209         if (this.test.text.length)
210         {
211             w.put("\nExample usage:\n");
212             w.put(cast()this.test.text);
213         }
214     }
215 
216     ///
217     @safe pure nothrow
218     string toString()() scope const
219     {
220         return this.text ~ "\nExample usage:\n" ~ this.test.text;
221     }
222 }
223 
224 /++
225 Attribute for documentation.
226 +/
227 template reflectDoc(string target = null)
228 {
229     ///
230     ReflectDoc!target reflectDoc(string text)
231     {
232         return ReflectDoc!target(text);
233     }
234 
235     ///
236     ReflectDoc!target reflectDoc(string text, reflectUnittest!target test)
237     {
238         return ReflectDoc!target(text, test);
239     }
240 }
241 
242 /++
243 +/
244 template reflectGetDocs(string target, alias symbol)
245 {
246     static if (hasUDA!(symbol, ReflectDoc!target))
247         static immutable(ReflectDoc!target[]) reflectGetDocs = [getUDAs!(symbol, ReflectDoc!target)];
248     else
249         static immutable(ReflectDoc!target[]) reflectGetDocs = null;
250 }
251 
252 /// ditto
253 template reflectGetDocs(string target)
254 {
255     ///
256     alias reflectGetDocs(alias symbol) = .reflectGetDocs!(target, symbol);
257 
258     /// ditto
259     immutable(ReflectDoc!target)[] reflectGetDocs(T)(T value)
260         @safe pure nothrow @nogc
261         if (is(T == enum))
262     {
263         foreach (i, member; EnumMembers!T)
264         {{
265             alias all = __traits(getAttributes, EnumMembers!T[i]);
266         }}
267         static immutable ReflectDoc!target[][EnumMembers!T.length] docs = [staticMap!(reflectGetDocs, EnumMembers!T)];
268         import mir.enums: getEnumIndex;
269         uint index = void;
270         if (getEnumIndex(value, index))
271             return docs[index];
272         assert(0);
273     }
274 }
275 
276 ///
277 version(mir_core_test) unittest
278 {
279     enum E
280     {
281         @reflectDoc("alpha")
282         a,
283         @reflectDoc!"C#"("Beta", reflectUnittest!"C#"("some c# code"))
284         @reflectDoc("beta")
285         b,
286         c,
287     }
288 
289     alias Doc = ReflectDoc!null;
290     alias CSDoc = ReflectDoc!"C#";
291 
292     static assert(reflectGetDocs!null(E.a) == [Doc("alpha")]);
293     static assert(reflectGetDocs!"C#"(E.b) == [CSDoc("Beta", reflectUnittest!"C#"("some c# code"))]);
294     static assert(reflectGetDocs!null(E.b) == [Doc("beta")]);
295     static assert(reflectGetDocs!null(E.c) is null);
296 
297     struct S
298     {
299         @reflectDoc("alpha")
300         @reflectDoc!"C#"("Alpha")
301         int a;
302     }
303 
304     static assert(reflectGetDocs!(null, S.a) == [Doc("alpha")]);
305     static assert(reflectGetDocs!("C#", S.a) == [CSDoc("Alpha")]);
306 
307     import std.conv: to;
308     static assert(CSDoc("Beta", reflectUnittest!"C#"("some c# code")).to!string == "Beta\nExample usage:\nsome c# code");
309 }
310 
311 /++
312 Attribute for extern unit-test.
313 +/
314 struct reflectUnittest(string target)
315 {
316     ///
317     string text;
318 
319 @safe pure nothrow @nogc:
320 
321     this(string text)
322     {
323         this.text = text;
324     }
325 
326     this(const typeof(this) other)
327     {
328         this.text = other.text;
329     }
330 }
331 
332 /++
333 +/
334 template reflectGetUnittest(string target, alias symbol)
335 {
336     static if (hasUDA!(symbol, reflectUnittest!target))
337         enum string reflectGetUnittest = getUDA!(symbol, reflectUnittest).text;
338     else
339         enum string reflectGetUnittest = null;
340 }
341 
342 /// ditto
343 template reflectGetUnittest(string target)
344 {
345     ///
346     alias reflectGetUnittest(alias symbol) = .reflectGetUnittest!(target, symbol);
347 
348     ///
349     string reflectGetUnittest(T)(T value)
350         if (is(T == enum))
351     {
352         foreach (i, member; EnumMembers!T)
353         {{
354             alias all = __traits(getAttributes, EnumMembers!T[i]);
355         }}
356         static immutable string[EnumMembers!T.length] tests = [staticMap!(reflectGetUnittest, EnumMembers!T)];
357         import mir.enums: getEnumIndex;
358         uint index = void;
359         if (getEnumIndex(value, index))
360             return tests[index];
361         assert(0);
362     }
363 }
364 
365 ///
366 version(mir_core_test) unittest
367 {
368     enum E
369     {
370         @reflectUnittest!"c++"("assert(E::a == 0);")
371         a,
372         @reflectUnittest!"c++"("assert(E::b == 1);")
373         b,
374         c,
375     }
376 
377     static assert(reflectGetUnittest!"c++"(E.a) == "assert(E::a == 0);");
378     static assert(reflectGetUnittest!"c++"(E.b) == "assert(E::b == 1);");
379     static assert(reflectGetUnittest!"c++"(E.c) is null);
380 
381     struct S
382     {
383         @reflectUnittest!"c++"("alpha")
384         int a;
385     }
386 
387     static assert(reflectGetUnittest!("c++", S.a) == "alpha");
388 }
389 
390 /++
391 Returns: single UDA.
392 +/
393 template getUDA(alias symbol, alias attribute)
394 {
395     private alias all = getUDAs!(symbol, attribute);
396     static if (all.length != 1)
397         static assert(0, "Exactly one " ~ attribute.stringof ~ " attribute is required, " ~ "got " ~ all.length.stringof);
398     else
399     {
400         static if (is(typeof(all[0])))
401             enum getUDA = all[0];
402         else
403             alias getUDA = all[0];
404     }
405 }
406 
407 /++
408 Checks if T has a field member.
409 +/
410 enum bool isOriginalMember(T, string member) = __traits(identifier, __traits(getMember, T, member)) == member;
411 
412 ///
413 version(mir_core_test) unittest
414 {
415     struct D
416     {
417         int a;
418         alias b = a;
419     }
420 
421     static assert(isOriginalMember!(D, "a"));
422     static assert(!isOriginalMember!(D, "b"));
423 }
424 
425 /++
426 Checks if T has a field member.
427 +/
428 enum bool hasField(T, string member) = __traits(compiles, (ref T aggregate) { return __traits(getMember, aggregate, member).offsetof; });
429 
430 deprecated("use 'hasField' instead") alias isField = hasField;
431 
432 ///
433 version(mir_core_test) unittest
434 {
435     struct D
436     {
437         int gi;
438     }
439 
440     struct I
441     {
442         int f;
443 
444         D base;
445         alias base this;
446 
447         void gi(double ) @property {}
448         void gi(uint ) @property {}
449     }
450 
451     struct S
452     {
453         int d;
454 
455         I i;
456         alias i this;
457 
458         int gm() @property {return 0;}
459         int gc() const @property {return 0;}
460         void gs(int) @property {}
461     }
462 
463     static assert(!hasField!(S, "gi"));
464     static assert(!hasField!(S, "gs"));
465     static assert(!hasField!(S, "gc"));
466     static assert(!hasField!(S, "gm"));
467     static assert(!hasField!(S, "gi"));
468     static assert(hasField!(S, "d"));
469     static assert(hasField!(S, "f"));
470     static assert(hasField!(S, "i"));
471 }
472 
473 ///  with classes
474 version(mir_core_test) unittest
475 {
476     class I
477     {
478         int f;
479 
480         void gi(double ) @property {}
481         void gi(uint ) @property {}
482     }
483 
484     class S
485     {
486         int d;
487 
488         I i;
489         alias i this;
490 
491         int gm() @property {return 0;}
492         int gc() const @property {return 0;}
493         void gs(int) @property {}
494     }
495 
496     static assert(!hasField!(S, "gi"));
497     static assert(!hasField!(S, "gs"));
498     static assert(!hasField!(S, "gc"));
499     static assert(!hasField!(S, "gm"));
500     static assert(hasField!(S, "d"));
501     static assert(hasField!(S, "f"));
502     static assert(hasField!(S, "i"));
503 }
504 
505 /++
506 Checks if member is property.
507 +/
508 template isProperty(T, string member)
509 {
510     T* aggregate;
511 
512     static if (__traits(compiles, isSomeFunction!(__traits(getMember, *aggregate, member))))
513     {
514         static if (isSomeFunction!(__traits(getMember, *aggregate, member)))
515         {
516             enum bool isProperty = isPropertyImpl!(__traits(getMember, *aggregate, member));
517         }
518         else
519         {
520             enum bool isProperty = false;
521         }
522     }
523     else
524         enum bool isProperty = false;
525 }
526 
527 ///
528 version(mir_core_test) unittest
529 {
530     struct D
531     {
532         int y;
533 
534         void gf(double ) @property {}
535         void gf(uint ) @property {}
536     }
537 
538     struct I
539     {
540         int f;
541 
542         D base;
543         alias base this;
544 
545         void gi(double ) @property {}
546         void gi(uint ) @property {}
547     }
548 
549     struct S
550     {
551         int d;
552 
553         I i;
554         alias i this;
555 
556         int gm() @property {return 0;}
557         int gc() const @property {return 0;}
558         void gs(int) @property {}
559     }
560 
561     static assert(isProperty!(S, "gf"));
562     static assert(isProperty!(S, "gi"));
563     static assert(isProperty!(S, "gs"));
564     static assert(isProperty!(S, "gc"));
565     static assert(isProperty!(S, "gm"));
566     static assert(!isProperty!(S, "d"));
567     static assert(!isProperty!(S, "f"));
568     static assert(!isProperty!(S, "y"));
569 }
570 
571 /++
572 Returns: list of the setter properties.
573 
574 Note: The implementation ignores templates.
575 +/
576 template getSetters(T, string member)
577 {
578     static if (__traits(hasMember, T, member))
579         alias getSetters = Filter!(hasSingleArgument, Filter!(isPropertyImpl, __traits(getOverloads, T, member)));
580     else
581         alias getSetters = AliasSeq!();
582 }
583 
584 ///
585 version(mir_core_test) unittest
586 {
587     struct I
588     {
589         int f;
590 
591         void gi(double ) @property {}
592         void gi(uint ) @property {}
593     }
594 
595     struct S
596     {
597         int d;
598 
599         I i;
600         alias i this;
601 
602         int gm() @property {return 0;}
603         int gc() const @property {return 0;}
604         void gs(int) @property {}
605     }
606 
607     static assert(getSetters!(S, "gi").length == 2);
608     static assert(getSetters!(S, "gs").length == 1);
609     static assert(getSetters!(S, "gc").length == 0);
610     static assert(getSetters!(S, "gm").length == 0);
611     static assert(getSetters!(S, "d").length == 0);
612     static assert(getSetters!(S, "f").length == 0);
613 }
614 
615 /++
616 Returns: list of the serializable (public getters) members.
617 +/
618 enum string[] SerializableMembers(T) = [Filter!(ApplyLeft!(Serializable, T), FieldsAndProperties!T)];
619 
620 ///
621 version(mir_core_test) unittest
622 {
623     struct D
624     {
625         int y;
626 
627         int gf() @property {return 0;}
628     }
629 
630     struct I
631     {
632         int f;
633 
634         D base;
635         alias base this;
636 
637         int gi() @property {return 0;}
638     }
639 
640     struct S
641     {
642         int d;
643 
644         package int p;
645 
646         int gm() @property {return 0;}
647 
648         private int q;
649 
650         I i;
651         alias i this;
652 
653         int gc() const @property {return 0;}
654         void gs(int) @property {}
655     }
656 
657     static assert(SerializableMembers!S == ["y", "gf", "f", "gi", "d", "gm", "gc"]);
658     static assert(SerializableMembers!(const S) == ["y", "f", "d", "gc"]);
659 }
660 
661 /++
662 Returns: list of the deserializable (public setters) members.
663 +/
664 enum string[] DeserializableMembers(T) = [Filter!(ApplyLeft!(Deserializable, T), FieldsAndProperties!T)];
665 
666 ///
667 version(mir_core_test) unittest
668 {
669     struct I
670     {
671         int f;
672         void ga(int) @property {}
673     }
674 
675     struct S
676     {
677         int d;
678         package int p;
679 
680         int gm() @property {return 0;}
681         void gm(int) @property {}
682 
683         private int q;
684 
685         I i;
686         alias i this;
687 
688 
689         void gc(int, int) @property {}
690         void gc(int) @property {}
691     }
692 
693     S s;
694     // s.gc(0);
695 
696     static assert (DeserializableMembers!S == ["f", "ga", "d", "gm", "gc"]);
697     static assert (DeserializableMembers!(const S) == []);
698 }
699 
700 // This trait defines what members should be serialized -
701 // public members that are either readable and writable or getter properties
702 private template Serializable(T, string member)
703 {
704     static if (!isPublic!(T, member))
705         enum Serializable = false;
706     else
707         enum Serializable = isReadable!(T, member); // any readable is good
708 }
709 
710 private enum bool hasSingleArgument(alias fun) = Parameters!fun.length == 1;
711 private enum bool hasZeroArguments(alias fun) = Parameters!fun.length == 0;
712 
713 // This trait defines what members should be serialized -
714 // public members that are either readable and writable or setter properties
715 private template Deserializable(T, string member)
716 {
717     static if (!isPublic!(T, member))
718         enum Deserializable = false;
719     else
720     static if (isReadableAndWritable!(T, member))
721         enum Deserializable = true;
722     else
723     static if (getSetters!(T, member).length == 1)
724         enum Deserializable =  is(typeof((ref T val){ __traits(getMember, val, member) = Parameters!(getSetters!(T, member)[0])[0].init; }));
725     else
726         enum Deserializable = false;
727 }
728 
729 private enum FieldsAndProperties(T) = Reverse!(NoDuplicates!(Reverse!(FieldsAndPropertiesImpl!T)));
730 
731 private template allMembers(T)
732 {
733     static if (isAggregateType!T)
734         alias allMembers = __traits(allMembers, T);
735     else
736         alias allMembers = AliasSeq!();
737 }
738 
739 private template FieldsAndPropertiesImpl(T)
740 {
741     alias isProperty = ApplyLeft!(.isProperty, T);
742     alias hasField = ApplyLeft!(.hasField, T);
743     alias isOriginalMember = ApplyLeft!(.isOriginalMember, T);
744     alias isMember = templateAnd!(templateOr!(hasField, isProperty), isOriginalMember);
745     static if (__traits(getAliasThis, T).length)
746     {
747         T* aggregate;
748         alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T)));
749         static if (isAggregateType!T)
750             alias baseMembers = FieldsAndPropertiesImpl!A;
751         else
752             alias baseMembers = AliasSeq!();
753         alias members = Erase!(__traits(getAliasThis, T)[0], __traits(allMembers, T));
754         alias FieldsAndPropertiesImpl = AliasSeq!(baseMembers, Filter!(isMember, members));
755     }
756     else
757     {
758         import mir.algebraic;
759         static if (isVariant!T)
760             alias members = staticMap!(allMembers, T.AllowedTypes);
761         else
762             alias members = allMembers!T;
763         alias FieldsAndPropertiesImpl = AliasSeq!(Filter!(isMember, members));
764     }
765 }
766 
767 // check if the member is readable
768 private template isReadable(T, string member)
769 {
770     T* aggregate;
771     enum bool isReadable = __traits(compiles, { static fun(T)(auto ref T t) {} fun(__traits(getMember, *aggregate, member)); });
772 }
773 
774 // check if the member is readable/writeble?
775 private template isReadableAndWritable(T, string member)
776 {
777     T* aggregate;
778     enum bool isReadableAndWritable = __traits(compiles, __traits(getMember, *aggregate, member) = __traits(getMember, *aggregate, member));
779 }
780 
781 package template isPublic(T, string member)
782 {
783     T* aggregate;
784     enum bool isPublic = !__traits(getProtection, __traits(getMember, *aggregate, member)).privateOrPackage;
785 }
786 
787 // check if the member is property
788 private template isSetter(T, string member)
789 {
790     T* aggregate;
791     static if (__traits(compiles, isSomeFunction!(__traits(getMember, *aggregate, member))))
792     {
793         static if (isSomeFunction!(__traits(getMember, *aggregate, member)))
794         {
795             enum bool isSetter = getSetters!(T, member).length > 0;;
796         }
797         else
798         {
799             enum bool isSetter = false;
800         }
801     }
802     else
803         enum bool isSetter = false;
804 }
805 
806 private template isGetter(T, string member)
807 {
808     T* aggregate;
809     static if (__traits(compiles, isSomeFunction!(__traits(getMember, *aggregate, member))))
810     {
811         static if (isSomeFunction!(__traits(getMember, *aggregate, member)))
812         {
813             enum bool isGetter = Filter!(hasZeroArguments, Filter!(isPropertyImpl, __traits(getOverloads, T, member))).length == 1;
814         }
815         else
816         {
817             enum bool isGetter = false;
818         }
819     }
820     else
821         enum bool isGetter = false;
822 }
823 
824 private enum bool isPropertyImpl(alias member) = (functionAttributes!member & FunctionAttribute.property) != 0;
825 
826 private bool privateOrPackage()(string protection)
827 {
828     return protection == "private" || protection == "package";
829 }