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