1 /**
2     Some tools to help to auto-generate interface implementation.
3 
4     Copyright: © 2018 Eliott Dumeix
5     License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 */
7 module autointf;
8 
9 import std.traits : hasUDA;
10 import vibe.internal.meta.uda : onlyAsUda;
11 
12 public import std.traits : ReturnType;
13 
14 /// Methods marked with this attribute will not be auto implemented.
15 package struct NoAutoImplementMethod
16 {
17 }
18 
19 NoAutoImplementMethod noAutoImplement() @safe
20 {
21     if (!__ctfe)
22         assert(false, onlyAsUda!__FUNCTION__);
23     return NoAutoImplementMethod();
24 }
25 
26 /// attributes utils
27 private enum isEnabledMethod(alias M) = !hasUDA!(M, NoAutoImplementMethod);
28 
29 /// Base class for creating a context conserved during the invocation process.
30 class UserContext
31 {
32 }
33 
34 struct SubInterface
35 {
36 
37 }
38 
39 /// Provides all necessary informations to implement an automated interface or class.
40 /// inspired by /web/vibe/web/internal/rest/common.d
41 struct InterfaceInfo(T) if (is(T == class) || is(T == interface))
42 {
43 @safe:
44 
45     import std.meta : Filter;
46     import std.traits : FunctionTypeOf, InterfacesTuple, MemberFunctionsTuple,
47         ParameterIdentifierTuple, ParameterStorageClass,
48         ParameterStorageClassTuple, ParameterTypeTuple, ReturnType;
49     import std.typetuple : TypeTuple;
50     import vibe.internal.meta.funcattr : IsAttributedParameter;
51     import vibe.internal.meta.traits : derivedMethod;
52     import vibe.internal.meta.uda;
53 
54     // determine the implementation interface I and check for validation errors
55     alias BaseInterfaces = InterfacesTuple!T;
56 
57     // some static checks
58     static assert(BaseInterfaces.length > 0 || is(T == interface),
59         "Cannot get interface infos for type '" ~ T.stringof ~ "' because it doesn't implement an interface");
60     static if (BaseInterfaces.length > 1)
61         pragma(msg, "Type '" ~ T.stringof
62             ~ "' implements more than one interface: make sure the one describing the auto interface is the first one");
63 
64     // alias the base interface
65     static if (is(T == interface))
66         alias I = T;
67     else
68         alias I = BaseInterfaces[0];
69 
70     /// The name of each interface member (Runtime).
71     enum memberNames = [__traits(allMembers, I)];
72 
73     /// Aliases to all interface methods (Compile-time).
74     alias Members = GetMembers!();
75 
76     /** Aliases for each method (Compile-time).
77     This tuple has the same number of entries as `methods`. */
78     alias Methods = GetMethods!();
79 
80     /// Number of methods (Runtime).
81     enum methodCount = Methods.length;
82 
83     /** Information about each route (Runtime).
84     This array has the same number of fields as `RouteFunctions`. */
85     MethodInfo[methodCount] methods;
86 
87     /// Static information about each route (Compile-time).
88     static if (methodCount)
89         static const StaticMethodInfo[methodCount] staticMethods = computeStaticRoutes();
90     else
91         static const StaticMethodInfo[0] staticMethods;
92 
93     /// Fills the struct with information.
94     this(int dummy)
95     {
96         computeMethods();
97     }
98 
99     // copying this struct is costly, so we forbid it
100     @disable this(this);
101 
102     private void computeMethods()
103     {
104         import std.algorithm.searching : any;
105 
106         foreach (si, RF; Methods)
107         {
108             enum smethod = staticMethods[si];
109 
110             MethodInfo route;
111             route.name = smethod.name;
112 
113             route.parameters.length = smethod.parameters.length;
114 
115             bool prefix_id = false;
116 
117             alias PT = ParameterTypeTuple!RF;
118             foreach (i, _; PT)
119             {
120                 enum sparam = smethod.parameters[i];
121                 ParameterInfo pi;
122                 pi.name = sparam.name;
123 
124                 route.parameters[i] = pi;
125             }
126 
127             methods[si] = route;
128         }
129     }
130 
131     // ////////////////////////////////////////////////////////////////////////
132     // compile time methods
133     // ////////////////////////////////////////////////////////////////////////
134     private template SubInterfaceType(alias F)
135     {
136         import std.traits : ReturnType, isInstanceOf;
137 
138         alias RT = ReturnType!F;
139         static if (is(RT == interface))
140             alias SubInterfaceType = RT;
141         else
142             alias SubInterfaceType = void;
143     }
144 
145     private template GetMembers()
146     {
147         template Impl(size_t idx)
148         {
149             static if (idx < memberNames.length)
150             {
151                 enum name = memberNames[idx];
152                 static if (name.length != 0)
153                     alias Impl = TypeTuple!(MemberFunctionsTuple!(I, name), Impl!(idx + 1));
154                 else
155                     alias Impl = Impl!(idx + 1);
156             }
157             else
158                 alias Impl = TypeTuple!();
159         }
160 
161         alias GetMembers = Impl!0;
162     }
163 
164     private template GetMethods()
165     {
166         template Impl(size_t idx)
167         {
168             static if (idx < Members.length)
169             {
170                 alias F = Members[idx];
171                 alias SI = SubInterfaceType!F;
172                 static if (is(SI == void) && isEnabledMethod!F)
173                     alias Impl = TypeTuple!(F, Impl!(idx + 1));
174                 else
175                     alias Impl = Impl!(idx + 1);
176             }
177             else
178                 alias Impl = TypeTuple!();
179         }
180 
181         alias GetMethods = Impl!0;
182     }
183 
184     private static StaticMethodInfo[methodCount] computeStaticRoutes()
185     {
186         static import std.traits;
187         import std.algorithm.searching : any, count;
188         import std.algorithm : countUntil;
189         import std.meta : AliasSeq;
190 
191         assert(__ctfe);
192 
193         StaticMethodInfo[methodCount] ret;
194 
195         foreach (fi, func; Methods)
196         {
197             StaticMethodInfo sroute;
198             sroute.name = __traits(identifier, func);
199 
200             static if (!is(T == I))
201                 alias cfunc = derivedMethod!(T, func);
202             else
203                 alias cfunc = func;
204 
205             alias FuncType = FunctionTypeOf!func;
206             alias ParameterTypes = ParameterTypeTuple!FuncType;
207             alias ReturnType = std.traits.ReturnType!FuncType;
208             enum parameterNames = [ParameterIdentifierTuple!func];
209 
210             // get some meta
211             enum name = __traits(identifier, func);
212 
213             sroute.name = name;
214 
215             foreach (i, PT; ParameterTypes)
216             {
217                 enum pname = parameterNames[i];
218 
219                 // Comparison template for anySatisfy
220                 // template Cmp(WebParamAttribute attr) { enum Cmp = (attr.identifier == ParamNames[i]); }
221                 // alias CompareParamName = GenCmp!("Loop"~func.mangleof, i, parameterNames[i]);
222                 // mixin(CompareParamName.Decl);
223                 StaticParameterInfo pi;
224                 pi.name = parameterNames[i];
225 
226                 // determine in/out storage class
227                 enum SC = ParameterStorageClassTuple!func[i];
228                 static assert(!(SC & ParameterStorageClass.out_));
229 
230                 sroute.parameters ~= pi;
231             }
232 
233             ret[fi] = sroute;
234         }
235 
236         return ret;
237     }
238 }
239 
240 ///
241 unittest
242 {
243     import std.typecons : tuple;
244     import std.traits;
245 
246     @("api")
247     interface IAPI
248     {
249         @("value")
250         string hello(int number);
251 
252         @noAutoImplement()
253         void disabledMethod();
254     }
255 
256     alias Info = InterfaceInfo!IAPI; // Compile-time infos
257     auto info = new Info(0); // Runtime infos
258 
259     // Runtime infos
260     assert(info.memberNames.length == 2);
261     assert(info.memberNames[0] == "hello");
262     assert(info.memberNames[1] == "disabledMethod");
263     assert(info.methodCount == 1 );
264     assert(info.methods.length == 1);
265     assert(info.methods[0].name == "hello");
266     assert(info.methods[0].parameters.length == 1);
267     assert(info.methods[0].parameters[0].name == "number");
268 
269     // Compile time
270     static assert(Info.Members.length == 2);
271     static assert(Info.Methods.length == 1);
272 
273     static assert(Info.staticMethods.length == 1);
274     static assert(Info.staticMethods[0].name == "hello");
275     static assert(Info.staticMethods[0].parameters.length == 1);
276     static assert(Info.staticMethods[0].parameters[0].name == "number");
277 }
278 
279 
280 /// Static informations about a method.
281 struct StaticMethodInfo
282 {
283     string name; // D name of the function
284     StaticParameterInfo[] parameters;
285 }
286 
287 /// Static informations about a method parameter.
288 struct StaticParameterInfo
289 {
290     string name;
291 }
292 
293 /// Informations about a method.
294 struct MethodInfo
295 {
296     string name; // D name of the function
297     ParameterInfo[] parameters;
298 }
299 
300 /// Informations about a method parameter.
301 struct ParameterInfo
302 {
303     string name;
304 }
305 
306 string autoImplementMethods(I, alias ExecuteMethod)()
307 {
308     import std.array : join;
309     import std..string : format;
310     import std.traits : ParameterIdentifierTuple;
311 
312     alias Info = InterfaceInfo!I;
313 
314     // add required import
315     string ret = q{
316         import vibe.internal.meta.codegen : CloneFunction;
317     };
318 
319     // generate method implementation
320     foreach (i, F; Info.Methods)
321     {
322         alias ParamNames = ParameterIdentifierTuple!F;
323 
324         static if (ParamNames.length == 0)
325             enum pnames = "";
326         else
327             enum pnames = [ParamNames].join(", ");
328 
329         ret ~= q{
330             mixin CloneFunction!(%3$s, q{
331                 return %2$s!(%3$s)(%1$s);
332             }, true);
333         }.format(pnames, __traits(identifier, ExecuteMethod), __traits(identifier, F));
334     }
335 
336     return ret;
337 }
338 
339 unittest
340 {
341     class AutoFunctionName(I) : I
342     {
343         private ReturnType!Func executeMethod(alias Func, ARGS...)(ARGS arg)
344         {
345             import std.traits;
346             import std.conv : to;
347 
348             // retrieve some compile time informations
349             alias RT = ReturnType!Func;
350             alias PTT = ParameterTypeTuple!Func;
351 
352             string ret;
353 
354             foreach (i, PT; PTT)
355             {
356                 ret ~= to!string(arg[i]);
357             }
358 
359             return __traits(identifier, Func) ~ ret;
360         }
361 
362         mixin(autoImplementMethods!(I, executeMethod));
363     }
364 
365     interface ISubAPi
366     {
367         string getNumber(int n);
368 
369         @noAutoImplement()
370         final string foo()
371         {
372             return "foo";
373         }
374     }
375 
376     interface IAPI : ISubAPi
377     {
378         @("foo") string hello(int number, string str);
379 
380         string helloWorld();
381     }
382 
383     auto api = new AutoFunctionName!IAPI();
384 
385     assert(api.hello(42, "foo") == "hello42foo");
386     assert(api.helloWorld() == "helloWorld");
387     assert(api.getNumber(12) == "getNumber12");
388 }