blob: aae65bb9859be50b0c89c94a7cca44abc64c070e [file] [log] [blame]
Jake Farrellb95b0ff2012-03-22 21:49:10 +00001/*
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
20module thrift.internal.codegen;
21
22import std.traits : InterfacesTuple, isSomeFunction, isSomeString;
23import std.typetuple : staticIndexOf, staticMap, NoDuplicates, TypeTuple;
24import 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 */
33template 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 */
52template isNullable(T) {
53 enum isNullable = __traits(compiles, { T t = null; });
54}
55
56template isStruct(T) {
57 enum isStruct = is(T == struct);
58}
59
60template isException(T) {
61 enum isException = is(T : Exception);
62}
63
64template isEnum(T) {
65 enum isEnum = is(T == enum);
66}
67
68/**
69 * Aliases itself to T.name.
70 */
71template GetMember(T, string name) {
72 mixin("alias T." ~ name ~ " GetMember;");
73}
74
75/**
76 * Aliases itself to typeof(symbol).
77 */
78template TypeOf(alias symbol) {
79 alias typeof(symbol) TypeOf;
80}
81
82/**
83 * Aliases itself to the type of the T member called name.
84 */
85alias Compose!(TypeOf, GetMember) MemberType;
86
87/**
88 * Returns the field metadata array for T if any, or an empty array otherwise.
89 */
90template 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 */
101template 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 */
114template memberReq(T, string name, alias fieldMetaData = cast(TFieldMeta[])null) {
115 enum memberReq = memberReqImpl!(T, name, fieldMetaData).result;
116}
117
118private {
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
139template 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 */
146template 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 */
159template 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 */
178template 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
188template derivedMembers(T) {
189 alias TypeTuple!(__traits(derivedMembers, T)) derivedMembers;
190}
191
192template 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
Roger Meiere3f67102013-01-05 20:46:43 +0100204template FilterMethodNames(T, MemberNames...) {
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000205 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 */
223template 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 */
257template PApply(alias Target, T...) {
258 template PApply(U...) {
259 alias Target!(PApplyMergeArgs!(ConfinedTuple!T, U).Result) PApply;
260 }
261}
262
263/// Ditto.
264template PApplySkip() {}
265
266private 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
292unittest {
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 */
328template 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 */
347template 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 */
360template 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 */
385template 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
401template 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 */
412template 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 */
430template 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}