1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 module thrift.internal.codegen;
21 
22 import std.traits : InterfacesTuple, isSomeFunction, isSomeString;
23 import std.typetuple : staticIndexOf, staticMap, NoDuplicates, TypeTuple;
24 import thrift.codegen.base;
25 
26 /**
27  * Removes all type qualifiers from T.
28  *
29  * In contrast to std.traits.Unqual, FullyUnqual also removes qualifiers from
30  * array elements (e.g. immutable(byte[]) -> byte[], not immutable(byte)[]),
31  * excluding strings (string isn't reduced to char[]).
32  */
33 template FullyUnqual(T) {
34   static if (is(T _ == const(U), U)) {
35     alias FullyUnqual!U FullyUnqual;
36   } else static if (is(T _ == immutable(U), U)) {
37     alias FullyUnqual!U FullyUnqual;
38   } else static if (is(T _ == shared(U), U)) {
39     alias FullyUnqual!U FullyUnqual;
40   } else static if (is(T _ == U[], U) && !isSomeString!T) {
41     alias FullyUnqual!(U)[] FullyUnqual;
42   } else static if (is(T _ == V[K], K, V)) {
43     alias FullyUnqual!(V)[FullyUnqual!K] FullyUnqual;
44   } else {
45     alias T FullyUnqual;
46   }
47 }
48 
49 /**
50  * true if null can be assigned to the passed type, false if not.
51  */
52 template isNullable(T) {
53   enum isNullable = __traits(compiles, { T t = null; });
54 }
55 
56 template isStruct(T) {
57   enum isStruct = is(T == struct);
58 }
59 
60 template isException(T) {
61  enum isException = is(T : Exception);
62 }
63 
64 template isEnum(T) {
65   enum isEnum = is(T == enum);
66 }
67 
68 /**
69  * Aliases itself to T.name.
70  */
71 template GetMember(T, string name) {
72   mixin("alias T." ~ name ~ " GetMember;");
73 }
74 
75 /**
76  * Aliases itself to typeof(symbol).
77  */
78 template TypeOf(alias symbol) {
79   alias typeof(symbol) TypeOf;
80 }
81 
82 /**
83  * Aliases itself to the type of the T member called name.
84  */
85 alias Compose!(TypeOf, GetMember) MemberType;
86 
87 /**
88  * Returns the field metadata array for T if any, or an empty array otherwise.
89  */
90 template getFieldMeta(T) if (isStruct!T || isException!T) {
91   static if (is(typeof(T.fieldMeta) == TFieldMeta[])) {
92     enum getFieldMeta = T.fieldMeta;
93   } else {
94     enum TFieldMeta[] getFieldMeta = [];
95   }
96 }
97 
98 /**
99  * Merges the field metadata array for D with the passed array.
100  */
101 template mergeFieldMeta(T, alias fieldMetaData = cast(TFieldMeta[])null) {
102   // Note: We don't use getFieldMeta here to avoid bug if it is instantiated
103   // from TIsSetFlags, see comment there.
104   static if (is(typeof(T.fieldMeta) == TFieldMeta[])) {
105     enum mergeFieldMeta = T.fieldMeta ~ fieldMetaData;
106   } else {
107     enum TFieldMeta[] mergeFieldMeta = fieldMetaData;
108   }
109 }
110 
111 /**
112  * Returns the field requirement level for T.name.
113  */
114 template memberReq(T, string name, alias fieldMetaData = cast(TFieldMeta[])null) {
115   enum memberReq = memberReqImpl!(T, name, fieldMetaData).result;
116 }
117 
118 private {
119   import std.algorithm : find;
120   // DMD @@BUG@@: Missing import leads to failing build without error
121   // message in unittest/debug/thrift/codegen/async_client.
122   import std.array : empty, front;
123 
124   template memberReqImpl(T, string name, alias fieldMetaData) {
125     enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name);
126     static if (meta.empty || meta.front.req == TReq.AUTO) {
127       static if (isNullable!(MemberType!(T, name))) {
128         enum result = TReq.OPTIONAL;
129       } else {
130         enum result = TReq.REQUIRED;
131       }
132     } else {
133       enum result = meta.front.req;
134     }
135   }
136 }
137 
138 
139 template notIgnored(T, string name, alias fieldMetaData = cast(TFieldMeta[])null) {
140   enum notIgnored = memberReq!(T, name, fieldMetaData) != TReq.IGNORE;
141 }
142 
143 /**
144  * Returns the method metadata array for T if any, or an empty array otherwise.
145  */
146 template getMethodMeta(T) if (isService!T) {
147   static if (is(typeof(T.methodMeta) == TMethodMeta[])) {
148     enum getMethodMeta = T.methodMeta;
149   } else {
150     enum TMethodMeta[] getMethodMeta = [];
151   }
152 }
153 
154 
155 /**
156  * true if T.name is a member variable. Exceptions include methods, static
157  * members, artifacts like package aliases, …
158  */
159 template isValueMember(T, string name) {
160   static if (!is(MemberType!(T, name))) {
161     enum isValueMember = false;
162   } else static if (
163     is(MemberType!(T, name) == void) ||
164     isSomeFunction!(MemberType!(T, name)) ||
165     __traits(compiles, { return mixin("T." ~ name); }())
166   ) {
167     enum isValueMember = false;
168   } else {
169     enum isValueMember = true;
170   }
171 }
172 
173 /**
174  * Returns a tuple containing the names of the fields of T, not including
175  * inherited fields. If a member is marked as TReq.IGNORE, it is not included
176  * as well.
177  */
178 template FieldNames(T, alias fieldMetaData = cast(TFieldMeta[])null) {
179   alias StaticFilter!(
180     All!(
181       PApply!(isValueMember, T),
182       PApply!(notIgnored, T, PApplySkip, fieldMetaData)
183     ),
184     __traits(derivedMembers, T)
185   ) FieldNames;
186 }
187 
188 template derivedMembers(T) {
189   alias TypeTuple!(__traits(derivedMembers, T)) derivedMembers;
190 }
191 
192 template AllMemberMethodNames(T) if (isService!T) {
193   alias NoDuplicates!(
194     FilterMethodNames!(
195       T,
196       staticMap!(
197         derivedMembers,
198         TypeTuple!(T, InterfacesTuple!T)
199       )
200     )
201   ) AllMemberMethodNames;
202 }
203 
204 template FilterMethodNames(T, MemberNames...) {
205   alias StaticFilter!(
206     CompilesAndTrue!(
207       Compose!(isSomeFunction, TypeOf, PApply!(GetMember, T))
208     ),
209     MemberNames
210   ) FilterMethodNames;
211 }
212 
213 /**
214  * Returns a type tuple containing only the elements of T for which the
215  * eponymous template predicate pred is true.
216  *
217  * Example:
218  * ---
219  * alias StaticFilter!(isIntegral, int, string, long, float[]) Filtered;
220  * static assert(is(Filtered == TypeTuple!(int, long)));
221  * ---
222  */
223 template StaticFilter(alias pred, T...) {
224   static if (T.length == 0) {
225     alias TypeTuple!() StaticFilter;
226   } else static if (pred!(T[0])) {
227     alias TypeTuple!(T[0], StaticFilter!(pred, T[1 .. $])) StaticFilter;
228   } else {
229     alias StaticFilter!(pred, T[1 .. $]) StaticFilter;
230   }
231 }
232 
233 /**
234  * Binds the first n arguments of a template to a particular value (where n is
235  * the number of arguments passed to PApply).
236  *
237  * The passed arguments are always applied starting from the left. However,
238  * the special PApplySkip marker template can be used to indicate that an
239  * argument should be skipped, so that e.g. the first and third argument
240  * to a template can be fixed, but the second and remaining arguments would
241  * still be left undefined.
242  *
243  * Skipping a number of parameters, but not providing enough arguments to
244  * assign all of them during instantiation of the resulting template is an
245  * error.
246  *
247  * Example:
248  * ---
249  * struct Foo(T, U, V) {}
250  * alias PApply!(Foo, int, long) PartialFoo;
251  * static assert(is(PartialFoo!float == Foo!(int, long, float)));
252  *
253  * alias PApply!(Test, int, PApplySkip, float) SkippedTest;
254  * static assert(is(SkippedTest!long == Test!(int, long, float)));
255  * ---
256  */
257 template PApply(alias Target, T...) {
258   template PApply(U...) {
259     alias Target!(PApplyMergeArgs!(ConfinedTuple!T, U).Result) PApply;
260   }
261 }
262 
263 /// Ditto.
264 template PApplySkip() {}
265 
266 private template PApplyMergeArgs(alias Preset, Args...) {
267   static if (Preset.length == 0) {
268     alias Args Result;
269   } else {
270     enum nextSkip = staticIndexOf!(PApplySkip, Preset.Tuple);
271     static if (nextSkip == -1) {
272       alias TypeTuple!(Preset.Tuple, Args) Result;
273     } else static if (Args.length == 0) {
274       // Have to use a static if clause instead of putting the condition
275       // directly into the assert to avoid DMD trying to access Args[0]
276       // nevertheless below.
277       static assert(false,
278         "PArgsSkip encountered, but no argument left to bind.");
279     } else {
280       alias TypeTuple!(
281         Preset.Tuple[0 .. nextSkip],
282         Args[0],
283         PApplyMergeArgs!(
284           ConfinedTuple!(Preset.Tuple[nextSkip + 1 .. $]),
285           Args[1 .. $]
286         ).Result
287       ) Result;
288     }
289   }
290 }
291 
292 unittest {
293   struct Test(T, U, V) {}
294   alias PApply!(Test, int, long) PartialTest;
295   static assert(is(PartialTest!float == Test!(int, long, float)));
296 
297   alias PApply!(Test, int, PApplySkip, float) SkippedTest;
298   static assert(is(SkippedTest!long == Test!(int, long, float)));
299 
300   alias PApply!(Test, int, PApplySkip, PApplySkip) TwoSkipped;
301   static assert(!__traits(compiles, TwoSkipped!long));
302 }
303 
304 
305 /**
306  * Composes a number of templates. The result is a template equivalent to
307  * all the passed templates evaluated from right to left, akin to the
308  * mathematical function composition notation: Instantiating Compose!(A, B, C)
309  * is the same as instantiating A!(B!(C!(…))).
310  *
311  * This is especially useful for creating a template to use with staticMap/
312  * StaticFilter, as demonstrated below.
313  *
314  * Example:
315  * ---
316  * template AllMethodNames(T) {
317  *   alias StaticFilter!(
318  *     CompilesAndTrue!(
319  *       Compose!(isSomeFunction, TypeOf, PApply!(GetMember, T))
320  *     ),
321  *     __traits(allMembers, T)
322  *   ) AllMethodNames;
323  * }
324  *
325  * pragma(msg, AllMethodNames!Object);
326  * ---
327  */
328 template Compose(T...) {
329   static if (T.length == 0) {
330     template Compose(U...) {
331       alias U Compose;
332     }
333   } else {
334     template Compose(U...) {
335       alias Instantiate!(T[0], Instantiate!(.Compose!(T[1 .. $]), U)) Compose;
336     }
337   }
338 }
339 
340 /**
341  * Instantiates the given template with the given list of parameters.
342  *
343  * Used to work around syntactic limiations of D with regard to instantiating
344  * a template from a type tuple (e.g. T[0]!(...) is not valid) or a template
345  * returning another template (e.g. Foo!(Bar)!(Baz) is not allowed).
346  */
347 template Instantiate(alias Template, Params...) {
348   alias Template!Params Instantiate;
349 }
350 
351 /**
352  * Combines several template predicates using logical AND, i.e. instantiating
353  * All!(a, b, c) with parameters P for some templates a, b, c is equivalent to
354  * a!P && b!P && c!P.
355  *
356  * The templates are evaluated from left to right, aborting evaluation in a
357  * shurt-cut manner if a false result is encountered, in which case the latter
358  * instantiations do not need to compile.
359  */
360 template All(T...) {
361   static if (T.length == 0) {
362     template All(U...) {
363       enum All = true;
364     }
365   } else {
366     template All(U...) {
367       static if (Instantiate!(T[0], U)) {
368         alias Instantiate!(.All!(T[1 .. $]), U) All;
369       } else {
370         enum All = false;
371       }
372     }
373   }
374 }
375 
376 /**
377  * Combines several template predicates using logical OR, i.e. instantiating
378  * Any!(a, b, c) with parameters P for some templates a, b, c is equivalent to
379  * a!P || b!P || c!P.
380  *
381  * The templates are evaluated from left to right, aborting evaluation in a
382  * shurt-cut manner if a true result is encountered, in which case the latter
383  * instantiations do not need to compile.
384  */
385 template Any(T...) {
386   static if (T.length == 0) {
387     template Any(U...) {
388       enum Any = false;
389     }
390   } else {
391     template Any(U...) {
392       static if (Instantiate!(T[0], U)) {
393         enum Any = true;
394       } else {
395         alias Instantiate!(.Any!(T[1 .. $]), U) Any;
396       }
397     }
398   }
399 }
400 
401 template ConfinedTuple(T...) {
402   alias T Tuple;
403   enum length = T.length;
404 }
405 
406 /*
407  * foreach (Item; Items) {
408  *   List = Operator!(Item, List);
409  * }
410  * where Items is a ConfinedTuple and List is a type tuple.
411  */
412 template ForAllWithList(alias Items, alias Operator, List...) if (
413   is(typeof(Items.length) : size_t)
414 ){
415   static if (Items.length == 0) {
416     alias List ForAllWithList;
417   } else {
418     alias .ForAllWithList!(
419       ConfinedTuple!(Items.Tuple[1 .. $]),
420       Operator,
421       Operator!(Items.Tuple[0], List)
422     ) ForAllWithList;
423   }
424 }
425 
426 /**
427  * Wraps the passed template predicate so it returns true if it compiles and
428  * evaluates to true, false it it doesn't compile or evaluates to false.
429  */
430 template CompilesAndTrue(alias T) {
431   template CompilesAndTrue(U...) {
432     static if (is(typeof(T!U) : bool)) {
433       enum bool CompilesAndTrue = T!U;
434     } else {
435       enum bool CompilesAndTrue = false;
436     }
437   }
438 }