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