1 module jumped.introspection;
2 
3 import std.traits;
4 import std.algorithm.searching;
5 import std.meta;
6 
7 /// Checks if a symbol has a specified attribute.
8 /// If the attribute cannot be found, it checks for attributes
9 /// on the original attribute type itself. It will keep doing this
10 /// until it cannot found anymore attributes.
11 template hasAnnotation(alias uda, alias symbol)
12 {
13 	static if (!__traits(compiles, __traits(getAttributes, symbol)))
14 	{
15 		alias hasAnnotation = Alias!false;
16 	}
17 	else static if (hasUDA!(symbol, uda))
18 	{
19 		alias hasAnnotation = Alias!true;
20 	}
21 	else
22 	{
23 		alias hasAnnotation = hasAnnotation!(uda, __traits(getAttributes, symbol));
24 	}
25 }
26 
27 private template hasAnnotation(alias uda, alias symbol, Symbols...)
28 {
29 	static if (hasAnnotation!(uda, symbol))
30 		alias hasAnnotation = Alias!true;
31 	else
32 		alias hasAnnotation = hasAnnotation!(uda, Symbols);
33 }
34 
35 private template hasAnnotation(alias UDA)
36 {
37 	alias hasAnnotation = Alias!false;
38 }
39 
40 @("hasAnnotation finds all base annotation")
41 unittest
42 {
43 	struct annotationA; // @suppress(dscanner.style.phobos_naming_convention)
44 
45 	@annotationA
46 	struct annotationB; // @suppress(dscanner.style.phobos_naming_convention)
47 
48 	@annotationB
49 	struct Hello;
50 
51 	static assert(hasAnnotation!(annotationB, Hello) == true);
52 }
53 
54 @("hasAnnotation finds all indirect annotation")
55 unittest
56 {
57 	struct annotationA; // @suppress(dscanner.style.phobos_naming_convention)
58 
59 	@annotationA
60 	struct annotationB; // @suppress(dscanner.style.phobos_naming_convention)
61 
62 	@annotationB
63 	struct Hello;
64 
65 	static assert(hasAnnotation!(annotationA, Hello) == true);
66 }
67 
68 
69 @("hasAnnotation is false when the annotation cannot be found")
70 unittest
71 {
72 	struct annotationA; // @suppress(dscanner.style.phobos_naming_convention)
73 
74 	struct annotationB; // @suppress(dscanner.style.phobos_naming_convention)
75 
76 	@annotationB
77 	struct Hello;
78 
79 	static assert(hasAnnotation!(annotationA, Hello) == false);
80 }
81 
82 /// Finds all members of a symbol that have a specific annotation, directly or
83 /// indirectly. It returns a tuple of the name of each each member that has them
84 /// annotation.
85 template getMembersByAnnotation(alias symbol, alias uda)
86 {
87 	alias getMembersByAnnotation = filterSymbolsByAnnotation!(uda, symbol, [__traits(allMembers, symbol)]);
88 }
89 
90 private template filterSymbolsByAnnotation(alias uda, alias symbol, alias members)
91 if (members.length > 1)
92 {
93 	alias filterSymbolsByAnnotation = AliasSeq!(
94 		filterSymbolsByAnnotation!(uda, symbol, [members[0]]),
95 		filterSymbolsByAnnotation!(uda, symbol, members[1..$])
96 	);
97 }
98 
99 private template filterSymbolsByAnnotation(alias uda, alias symbol, alias members)
100 if (members.length == 1)
101 {
102 	alias member = __traits(getMember, symbol, members[0]);
103 	static if (hasAnnotation!(uda, member))
104 	{
105 		alias filterSymbolsByAnnotation = Alias!(member);
106 	}
107 	else
108 	{
109 		alias filterSymbolsByAnnotation = AliasSeq!();
110 	}
111 }
112 
113 @("getSymbolsByAnnotation can find symbols with direct annotation")
114 unittest
115 {
116 	struct annotation; // @suppress(dscanner.style.phobos_naming_convention)
117 
118 	static class MyClass
119 	{
120 		@annotation public void someMethod() {}
121 
122 		public void otherMethod() {}
123 	}
124 
125 	alias annotations = getMembersByAnnotation!(MyClass, annotation);
126 	static assert(annotations.length == 1);
127 	static assert(__traits(identifier, annotations[0]) == "someMethod");
128 }
129 
130 @("getSymbolsByAnnotation can find symbols with indirect annotation")
131 unittest
132 {
133 	struct annotationA; // @suppress(dscanner.style.phobos_naming_convention)
134 
135 	@annotationA
136 	struct annotationB; // @suppress(dscanner.style.phobos_naming_convention)
137 
138 	static class MyClass
139 	{
140 		@annotationB public void someMethod() {}
141 
142 		public void otherMethod() {}
143 	}
144 
145 	alias annotations = getMembersByAnnotation!(MyClass, annotationA);
146 	static assert(annotations.length == 1);
147 	static assert(__traits(identifier, annotations[0]) == "someMethod");
148 }
149 
150 @("getSymbolsByAnnotation returns empty tuple if none were found")
151 unittest
152 {
153 	struct annotationA; // @suppress(dscanner.style.phobos_naming_convention)
154 	static class MyClass
155 	{
156 		public void otherMethod() {}
157 	}
158 
159 	alias annotations = getMembersByAnnotation!(MyClass, annotationA);
160 	static assert(annotations.length == 0);
161 }
162 
163 @("structures can contain third-party symbols with arguments")
164 unittest
165 {
166 	static struct otherAnnotation
167 	{
168 		string value;
169 	}
170 
171 	static struct myAnnotation;
172 
173 	static class MyClass
174 	{
175 		@otherAnnotation("foo")
176 		public void methodA() {}
177 	}
178 
179 	alias annotations = getMembersByAnnotation!(MyClass, myAnnotation);
180 	static assert(annotations.length == 0);
181 }
182 
183 @("methods can have multiple annotations")
184 unittest
185 {
186 	struct annotationA;
187 
188 	struct annotationB;
189 
190 	static class MyClass
191 	{
192 		@annotationA
193 		@annotationB
194 		private int getValue()
195 		{
196 			return 5;
197 		}
198 	}
199 
200 	alias annotations = getMembersByAnnotation!(MyClass, annotationA);
201 	static assert(annotations.length == 1);
202 }