1 /**
2 	Templates and CTFE-functions useful for type introspection during  code generation.
3 
4 	Some of those are very similar to `traits` utilities but instead of general type
5 	information focus on properties that are most important during such code generation.
6 
7 	Copyright: © 2013 RejectedSoftware e.K.
8 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
9 	Authors: Sönke Ludwig, Михаил Страшун
10 */
11 
12 module vson.meta.codegen;
13 
14 import std.traits : FunctionTypeOf, isSomeFunction;
15 
16 /*
17 	As user types defined inside unittest blocks don't have proper parent
18 	module, those need to be defined outside for tests that require module
19 	inspection for some reasons. All such tests use single declaration
20 	compiled in this module in unittest version.
21 */
22 version(unittest)
23 {
24 	private:
25 		interface TestInterface
26 		{
27 			static struct Inner
28 			{
29 			}
30 
31 			const(Inner[]) func1(ref string name);
32 			ref int func1();
33 			shared(Inner[4]) func2(...) const;
34 			immutable(int[string]) func3(in Inner anotherName) @safe;
35 		}
36 }
37 
38 /**
39 	For a given type T finds all user-defined symbols it embeds.
40 
41 	Important property of such symbols is that they are likely to
42 	need an explicit import if used in some other scope / module.
43 
44 	Implementation is incomplete and tuned for REST interface generation needs.
45 
46 	Params:
47 		T = type to introspect for qualified symbols
48 
49 	Returns:
50 		tuple of "interesting" symbols, no duplicates
51 */
52 template getSymbols(T)
53 {
54 	import std.typetuple : TypeTuple, NoDuplicates;
55 	import std.traits;
56 
57 	private template Implementation(T)
58 	{
59 		static if (isAggregateType!T || is(T == enum)) {
60 			alias Implementation = TypeTuple!T;
61 		}
62 		else static if (isStaticArray!T || isArray!T) {
63 			alias Implementation = Implementation!(typeof(T.init[0]));
64 		}
65 		else static if (isAssociativeArray!T) {
66 			alias Implementation = TypeTuple!(
67 				Implementation!(ValueType!T),
68 				Implementation!(KeyType!T)
69 			);
70 		}
71 		else static if (isPointer!T) {
72 			alias Implementation = Implementation!(PointerTarget!T);
73 		}
74 		else
75 			alias Implementation = TypeTuple!();
76 	}
77 
78 	alias getSymbols = NoDuplicates!(Implementation!T);
79 }
80 
81 ///
82 unittest
83 {
84 	import std.typetuple : TypeTuple;
85 
86 	struct A {}
87 	interface B {}
88 	alias Type = A[const(B[A*])];
89 
90 	// can't directly compare tuples thus comparing their string representation
91 	static assert (getSymbols!Type.stringof == TypeTuple!(A, B).stringof);
92 	static assert (getSymbols!int.stringof == TypeTuple!().stringof);
93 }
94 
95 /**
96 	For a given interface I finds all modules that types in its methods
97 	come from.
98 
99 	These modules need to be imported in the scope code generated from I
100 	is used to avoid errors with unresolved symbols for user types.
101 
102 	Params:
103 		I = interface to inspect
104 
105 	Returns:
106 		list of module name strings, no duplicates
107 */
108 string[] getRequiredImports(I)()
109 	if (is(I == interface))
110 {
111 	import std.traits : MemberFunctionsTuple, moduleName,
112 		ParameterTypeTuple, ReturnType;
113 
114 	if( !__ctfe )
115 		assert(false);
116 
117 	bool[string] visited;
118 	string[] ret;
119 
120 	void addModule(string name)
121 	{
122 		if (name !in visited) {
123 			ret ~= name;
124 			visited[name] = true;
125 		}
126 	}
127 
128 	foreach (method; __traits(allMembers, I)) {
129 		foreach (overload; MemberFunctionsTuple!(I, method)) {
130 			alias FuncType = FunctionTypeOf!overload;
131 
132 			foreach (symbol; getSymbols!(ReturnType!FuncType)) {
133 				static if (__traits(compiles, moduleName!symbol)) {
134 					addModule(moduleName!symbol);
135 				}
136 			}
137 
138 			foreach (P; ParameterTypeTuple!FuncType) {
139 				foreach (symbol; getSymbols!P) {
140 					static if (__traits(compiles, moduleName!symbol)) {
141 						addModule(moduleName!symbol);
142 					}
143 				}
144 			}
145 		}
146 	}
147 
148 	return ret;
149 }
150 
151 ///
152 unittest
153 {
154 	// `Test` is an interface using single user type
155 	enum imports = getRequiredImports!TestInterface;
156 	static assert (imports.length == 1);
157 	static assert (imports[0] == "vson.meta.codegen");
158 }
159 
160 /**
161  * Returns a Tuple of the parameters.
162  * It can be used to declare function.
163  */
164 template ParameterTuple(alias Func)
165 {
166 	static if (is(FunctionTypeOf!Func Params == __parameters)) {
167 		alias ParameterTuple = Params;
168 	} else static assert(0, "Argument to ParameterTuple must be a function");
169 }
170 
171 ///
172 unittest
173 {
174 	void foo(string val = "Test", int = 10);
175 	void bar(ParameterTuple!foo) { assert(val == "Test"); }
176 	// Variadic functions require special handling:
177 	import core.vararg;
178 	void foo2(string val, ...);
179 	void bar2(ParameterTuple!foo2, ...) { assert(val == "42"); }
180 
181 	bar();
182 	bar2("42");
183 
184 	// Note: outside of a parameter list, it's value is the type of the param.
185 	import std.traits : ParameterDefaultValueTuple;
186 	ParameterTuple!(foo)[0] test = ParameterDefaultValueTuple!(foo)[0];
187 	assert(test == "Test");
188 }
189 
190 /// Returns a Tuple containing a 1-element parameter list, with an optional default value.
191 /// Can be used to concatenate a parameter to a parameter list, or to create one.
192 template ParameterTuple(T, string identifier, TUnused = void)
193 {
194 	import std..string : format;
195 	mixin(q{private void __func(T %s);}.format(identifier));
196 	alias ParameterTuple = ParameterTuple!__func;
197 }
198 
199 
200 /// Ditto
201 template ParameterTuple(T, string identifier, T DefVal)
202 {
203 	import std..string : format;
204 	mixin(q{private void __func(T %s = DefVal);}.format(identifier));
205 	alias ParameterTuple = ParameterTuple!__func;
206 }
207 
208 ///
209 unittest
210 {
211 	void foo(ParameterTuple!(int, "arg2")) { assert(arg2 == 42); }
212 	foo(42);
213 
214 	void bar(string arg);
215 	void bar2(ParameterTuple!bar, ParameterTuple!(string, "val")) { assert(val == arg); }
216 	bar2("isokay", "isokay");
217 
218 	// For convenience, you can directly pass the result of std.traits.ParameterDefaultValueTuple
219 	// without checking for void.
220 	import std.traits : PDVT = ParameterDefaultValueTuple;
221 	import std.traits : arity;
222 	void baz(string test, int = 10);
223 
224 	static assert(is(PDVT!(baz)[0] == void));
225 	// void baz2(string test2, string test);
226 	void baz2(ParameterTuple!(string, "test2", PDVT!(baz)[0]), ParameterTuple!(baz)[0..$-1]) { assert(test == test2); }
227 	static assert(arity!baz2 == 2);
228 	baz2("Try", "Try");
229 
230 	// void baz3(string test, int = 10, int ident = 10);
231 	void baz3(ParameterTuple!baz, ParameterTuple!(int, "ident", PDVT!(baz)[1])) { assert(ident == 10); }
232 	baz3("string");
233 }
234 
235 /// Returns a string of the functions attributes, suitable to be mixed
236 /// on the LHS of the function declaration.
237 ///
238 /// Unfortunately there is no "nice" syntax for declaring a function,
239 /// so we have to resort on string for functions attributes.
240 template FuncAttributes(alias Func)
241 {
242 	static if (__VERSION__ <= 2065)
243 	{
244 		import std.traits;
245 		enum FuncAttributes = {
246 			alias FA = FunctionAttribute;
247 			string res;
248 			enum attr = functionAttributes!Func;
249 			if (attr & FA.nothrow_) res ~= "nothrow ";
250 			if (attr & FA.property) res ~= "@property ";
251 			if (attr & FA.pure_) res ~= "pure ";
252 			if (attr & FA.ref_) res ~= "ref ";
253 			if (attr & FA.safe) res ~= "@safe ";
254 			if (attr & FA.trusted) res ~= "@trusted ";
255 			static if (is(FunctionTypeOf!Func == const)) res ~= "const ";
256 			static if (is(FunctionTypeOf!Func == immutable)) res ~= "immutable ";
257 			static if (is(FunctionTypeOf!Func == inout)) res ~= "inout ";
258 			static if (is(FunctionTypeOf!Func == shared)) res ~= "shared ";
259 			return res.length ? res[0 .. $-1] : res;
260 		}();
261 	}
262 	else
263 	{
264 		import std.array : join;
265 		enum FuncAttributes = [__traits(getFunctionAttributes, Func)].join(" ");
266 	}
267 }
268 
269 
270 
271 /// A template mixin which allow you to clone a function, and specify the implementation.
272 mixin template CloneFunction(alias Func, string body_, string identifier = __traits(identifier, Func))
273 {
274 	// Template mixin: everything has to be self-contained.
275 	import std..string : format;
276 	import std.traits : ReturnType;
277 	import vson.meta.codegen : ParameterTuple, FuncAttributes;
278 	// Sadly this is not possible:
279 	// class Test {
280 	//   int foo(string par) pure @safe nothrow { /* ... */ }
281 	//   typeof(foo) bar {
282 	//      return foo(par);
283 	//   }
284 	// }
285 	mixin(q{
286 		ReturnType!Func %s(ParameterTuple!Func) %s {
287 			%s
288 		}
289 	}.format(identifier, FuncAttributes!Func, body_));
290 }
291 
292 ///
293 unittest
294 {
295 	interface ITest
296 	{
297 	  int foo(string par, int, string p = "foo", int = 10) pure @safe nothrow const;
298 	  @property int foo2() pure @safe nothrow const;
299 	}
300 
301 	class Test : ITest
302 	{
303 		mixin CloneFunction!(ITest.foo, q{
304 			return 84;
305 		}, "customname");
306 	override:
307 		mixin CloneFunction!(ITest.foo, q{
308 			return 42;
309 		});
310 		mixin CloneFunction!(ITest.foo2, q{
311 			return 42;
312 		});
313 	}
314 
315 	assert(new Test().foo("", 21) == 42);
316 	assert(new Test().foo2 == 42);
317 	assert(new Test().customname("", 21) == 84);
318 }