1 /**
2 	Utility templates that help working with User Defined Attributes
3 
4 	Copyright: © 2013 RejectedSoftware e.K.
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig, Михаил Страшун
7 */
8 
9 module vson.meta.uda;
10 
11 //import vson.meta.traits;
12 
13 
14 /**
15 	Small convenience wrapper to find and extract certain UDA from given type.
16 	Will stop on first element which is of required type.
17 
18 	Params:
19 		UDA = type or template to search for in UDA list
20 		Symbol = symbol to query for UDA's
21 		allow_types = if set to `false` considers attached `UDA` types an error
22 			(only accepts instances/values)
23 
24 	Returns: aggregated search result struct with 3 field. `value` aliases found UDA.
25 		`found` is boolean flag for having a valid find. `index` is integer index in
26 		attribute list this UDA was found at.
27 */
28 template findFirstUDA(alias UDA, alias Symbol, bool allow_types = false) if (!is(UDA))
29 {
30 	enum findFirstUDA = findNextUDA!(UDA, Symbol, 0, allow_types);
31 }
32 
33 /// Ditto
34 template findFirstUDA(UDA, alias Symbol, bool allow_types = false)
35 {
36 	enum findFirstUDA = findNextUDA!(UDA, Symbol, 0, allow_types);
37 }
38 
39 private struct UdaSearchResult(alias UDA)
40 {
41 	alias value = UDA;
42 	bool found = false;
43 	long index = -1;
44 }
45 
46 /**
47 	Small convenience wrapper to find and extract certain UDA from given type.
48 	Will start at the given index and stop on the next element which is of required type.
49 
50 	Params:
51 		UDA = type or template to search for in UDA list
52 		Symbol = symbol to query for UDA's
53 		idx = 0-based index to start at. Should be positive, and under the total number of attributes.
54 		allow_types = if set to `false` considers attached `UDA` types an error
55 			(only accepts instances/values)
56 
57 	Returns: aggregated search result struct with 3 field. `value` aliases found UDA.
58 		`found` is boolean flag for having a valid find. `index` is integer index in
59 		attribute list this UDA was found at.
60  */
61 template findNextUDA(alias UDA, alias Symbol, long idx, bool allow_types = false) if (!is(UDA))
62 {
63 	import std.traits : isInstanceOf;
64 	import std.typetuple : TypeTuple;
65 
66 	private alias udaTuple = TypeTuple!(__traits(getAttributes, Symbol));
67 
68 	static assert(idx >= 0, "Index given to findNextUDA can't be negative");
69 	static assert(idx <= udaTuple.length, "Index given to findNextUDA is above the number of attribute");
70 
71 	public template extract(size_t index, list...)
72 	{
73 		static if (!list.length) enum extract = UdaSearchResult!(null)(false, -1);
74 		else {
75 			static if (is(list[0])) {
76 				static if (is(UDA) && is(list[0] == UDA) || !is(UDA) && isInstanceOf!(UDA, list[0])) {
77 					static assert (allow_types, "findNextUDA is designed to look up values, not types");
78 					enum extract = UdaSearchResult!(list[0])(true, index);
79 				} else enum extract = extract!(index + 1, list[1..$]);
80 			} else {
81 				static if (is(UDA) && is(typeof(list[0]) == UDA) || !is(UDA) && isInstanceOf!(UDA, typeof(list[0]))) {
82 					import vson.meta.traits : isPropertyGetter;
83 					static if (isPropertyGetter!(list[0])) {
84 						enum value = list[0];
85 						enum extract = UdaSearchResult!(value)(true, index);
86 					} else enum extract = UdaSearchResult!(list[0])(true, index);
87 				} else enum extract = extract!(index + 1, list[1..$]);
88 			}
89 		}
90 	}
91 
92 	enum findNextUDA = extract!(idx, udaTuple[idx .. $]);
93 }
94 /// ditto
95 template findNextUDA(UDA, alias Symbol, long idx, bool allow_types = false)
96 {
97 	import std.traits : isInstanceOf;
98 	import std.typetuple : TypeTuple;
99 
100 	private alias udaTuple = TypeTuple!(__traits(getAttributes, Symbol));
101 
102 	static assert(idx >= 0, "Index given to findNextUDA can't be negative");
103 	static assert(idx <= udaTuple.length, "Index given to findNextUDA is above the number of attribute");
104 
105 	public template extract(size_t index, list...)
106 	{
107 		static if (!list.length) enum extract = UdaSearchResult!(null)(false, -1);
108 		else {
109 			static if (is(list[0])) {
110 				static if (is(list[0] == UDA)) {
111 					static assert (allow_types, "findNextUDA is designed to look up values, not types");
112 					enum extract = UdaSearchResult!(list[0])(true, index);
113 				} else enum extract = extract!(index + 1, list[1..$]);
114 			} else {
115 				static if (is(typeof(list[0]) == UDA)) {
116 					import vson.meta.traits : isPropertyGetter;
117 					static if (isPropertyGetter!(list[0])) {
118 						enum value = list[0];
119 						enum extract = UdaSearchResult!(value)(true, index);
120 					} else enum extract = UdaSearchResult!(list[0])(true, index);
121 				} else enum extract = extract!(index + 1, list[1..$]);
122 			}
123 		}
124     }
125 
126 	enum findNextUDA = extract!(idx, udaTuple[idx .. $]);
127 }
128 
129 
130 ///
131 unittest
132 {
133 	struct Attribute { int x; }
134 
135 	@("something", Attribute(42), Attribute(41))
136 	void symbol();
137 
138 	enum result0 = findNextUDA!(string, symbol, 0);
139 	static assert (result0.found);
140 	static assert (result0.index == 0);
141 	static assert (result0.value == "something");
142 
143 	enum result1 = findNextUDA!(Attribute, symbol, 0);
144 	static assert (result1.found);
145 	static assert (result1.index == 1);
146 	static assert (result1.value == Attribute(42));
147 
148 	enum result2 = findNextUDA!(int, symbol, 0);
149 	static assert (!result2.found);
150 
151 	enum result3 = findNextUDA!(Attribute, symbol, result1.index + 1);
152 	static assert (result3.found);
153 	static assert (result3.index == 2);
154 	static assert (result3.value == Attribute(41));
155 }
156 
157 unittest
158 {
159 	struct Attribute { int x; }
160 
161 	@(Attribute) void symbol();
162 
163 	static assert (!is(findNextUDA!(Attribute, symbol, 0)));
164 
165 	enum result0 = findNextUDA!(Attribute, symbol, 0, true);
166 	static assert (result0.found);
167 	static assert (result0.index == 0);
168 	static assert (is(result0.value == Attribute));
169 }
170 
171 unittest
172 {
173 	struct Attribute { int x; }
174 	enum Dummy;
175 
176 	@property static Attribute getter()
177 	{
178 		return Attribute(42);
179 	}
180 
181 	@Dummy @getter void symbol();
182 
183 	enum result0 = findNextUDA!(Attribute, symbol, 0);
184 	static assert (result0.found);
185 	static assert (result0.index == 1);
186 	static assert (result0.value == Attribute(42));
187 }
188 
189 /// Eager version of findNextUDA that represent all instances of UDA in a Tuple.
190 /// If one of the attribute is a type instead of an instance, compilation will fail.
191 template UDATuple(alias UDA, alias Sym) {
192 	import std.typetuple : TypeTuple;
193 
194 	private template extract(size_t maxSize, Founds...)
195 	{
196 		private alias LastFound = Founds[$ - 1];
197 		// No more to find
198 		static if (!LastFound.found)
199 			enum extract = Founds[0 .. $ - 1];
200 		else {
201 			// For ease of use, this is a Tuple of UDA, not a tuple of UdaSearchResult!(...)
202 			private alias Result = TypeTuple!(Founds[0 .. $ - 1], LastFound.value);
203 			// We're at the last parameter
204 			static if (LastFound.index == maxSize)
205 				enum extract = Result;
206 			else
207 				enum extract = extract!(maxSize, Result, findNextUDA!(UDA, Sym, LastFound.index + 1));
208 		}
209 	}
210 
211 	private enum maxIndex = TypeTuple!(__traits(getAttributes, Sym)).length;
212 	enum UDATuple = extract!(maxIndex, findNextUDA!(UDA, Sym, 0));
213 }
214 
215 unittest
216 {
217 	import std.typetuple : TypeTuple;
218 
219 	struct Attribute { int x; }
220 	enum Dummy;
221 
222 	@(Dummy, Attribute(21), Dummy, Attribute(42), Attribute(84)) void symbol() {}
223 	@(Dummy, Attribute(21), Dummy, Attribute(42), Attribute) void wrong() {}
224 
225 	alias Cmp = TypeTuple!(Attribute(21), Attribute(42), Attribute(84));
226 	static assert(Cmp == UDATuple!(Attribute, symbol));
227 	static assert(!is(UDATuple!(Attribute, wrong)));
228 }
229 
230 /// Avoid repeating the same error message again and again.
231 /// ----
232 /// if (!__ctfe)
233 ///	assert(0, onlyAsUda!func);
234 /// ----
235 template onlyAsUda(string from /*= __FUNCTION__*/)
236 {
237 	// With default param, DMD think expression is void, even when writing 'enum string onlyAsUda = ...'
238 	enum onlyAsUda = from~" must only be used as an attribute - not called as a runtime function.";
239 }