1 /**
2    Information about types and symbols at compile-time,
3    similar to std.traits.
4  */
5 module mirror.meta.traits;
6 
7 
8 import mirror.trait_enums: Protection;
9 static import std.traits;
10 
11 
12 /// Usable as a predicate to std.meta.Filter
13 enum isEnum(T) = is(T == enum);
14 
15 /// Usable as a predicate to std.meta.Filter
16 enum isStruct(T) = is(T == struct);
17 
18 /// Usable as a predicate to std.meta.Filter
19 enum isInterface(T) = is(T == interface);
20 
21 /// Usable as a predicate to std.meta.Filter
22 enum isClass(T) = is(T == class);
23 
24 /// Usable as a predicate to std.meta.Filter
25 enum isUnion(T) = is(T == union);
26 
27 /**
28    If a type is a class or an interface.
29    Usable as a predicate to std.meta.Filter
30 */
31 enum isOOP(alias T) = is(T == class) || is(T == interface);
32 int add() { return 0; }
33 
34 template moduleOf(alias T) {
35     import std.traits: moduleName;
36     mixin(`import `, moduleName!T, `;`);
37     mixin(`alias moduleOf = `, moduleName!T, `;`);
38 }
39 
40 
41 template isPrivate(alias symbol) {
42     // If a module contains an alias to a basic type, e.g. `alias L = long;`,
43     // then __traits(getProtection, member) fails to compile
44     static if(__traits(compiles, __traits(getProtection, symbol)))
45         enum isPrivate = __traits(getProtection, symbol) == "private";
46     else
47         enum isPrivate = true;  // if it doesn't compile, treat it as private
48 }
49 
50 
51 /**
52    Retrieves the "fundamental type" of a type T.  For most types, this
53    will be exactly the same as T itself.  For arrays or pointers, it
54    removes as many "layers" of array or pointer indirections to get to
55    the most basic atomic type possible.  Examples of inputs and
56    outputs:
57 
58    * T -> T
59    * T[] -> T
60    * T[][] -> T
61    * T* -> T
62 
63  */
64 template FundamentalType(T) {
65 
66     import std.traits: isArray, isPointer, PointerTarget;
67     import std.range: ElementEncodingType;
68 
69     static if(isArray!T)
70         alias removeOneIndirection = ElementEncodingType!T;
71     else static if(isPointer!T)
72         alias removeOneIndirection = PointerTarget!T;
73 
74     private enum isArrayOrPointer(U) = isArray!U || isPointer!U;
75 
76     static if(isArrayOrPointer!T) {
77         static if(isArrayOrPointer!removeOneIndirection)
78             alias FundamentalType = FundamentalType!removeOneIndirection;
79         else
80             alias FundamentalType = removeOneIndirection;
81     } else
82         alias FundamentalType = T;
83 }
84 
85 
86 /**
87    Returns an AliasSeq of all field types of `T`, depth-first
88    recursively.
89  */
90 alias RecursiveFieldTypes(T) = RecursiveFieldTypesImpl!T;
91 
92 
93 private template RecursiveFieldTypesImpl(T, alreadySeen...) {
94 
95     import mirror.meta.traits: isStruct, isClass;
96     import std.meta: staticMap, AliasSeq, NoDuplicates, Filter,
97         templateNot, staticIndexOf;
98 
99     enum isStructOrClass(U) = isStruct!(FundamentalType!U) || isClass!(FundamentalType!U);
100 
101     static if(isStructOrClass!T) {
102 
103         // This check is to deal with forward references such as std.variant.This.
104         // For some reason, checking for __traits(compiles, T.tupleof) always returns
105         // true, but checking the length actually does what we want.
106         // See modules.issues.Issue9.
107         static if(T.tupleof.length)
108             private alias fields = AliasSeq!(T.tupleof);
109         else
110             private alias fields = AliasSeq!();
111 
112         private alias publicFields = Filter!(templateNot!isPrivate, fields);
113         private alias type(alias symbol) = typeof(symbol);
114         private alias types = staticMap!(type, fields);
115 
116         private template recurse(U) {
117 
118             static if(isStructOrClass!U) {
119 
120                 // only recurse if the type hasn't been seen yet to
121                 // prevent infinite recursion
122                 enum shouldRecurse = staticIndexOf!(U, alreadySeen) == -1;
123 
124                 static if(shouldRecurse)
125                     alias recurse = AliasSeq!(U, RecursiveFieldTypesImpl!(FundamentalType!U, NoDuplicates!(T, types, alreadySeen)));
126                 else
127                     alias recurse = AliasSeq!();
128             } else
129                 alias recurse = U;
130         }
131 
132         alias RecursiveFieldTypesImpl = NoDuplicates!(staticMap!(recurse, types));
133     } else
134         alias RecursiveFieldTypesImpl = T;
135 }
136 
137 
138 /**
139    An std.meta.AliasSeq of `T` and all its recursive
140    subtypes.
141  */
142 template RecursiveTypeTree(T...) {
143     import std.meta: staticMap, NoDuplicates;
144     alias RecursiveTypeTree = NoDuplicates!(T, staticMap!(RecursiveFieldTypes, T));
145 }
146 
147 
148 /**
149    Whether or not `F` is a property function
150  */
151 template isProperty(alias F) {
152     import std.traits: functionAttributes, FunctionAttribute;
153     enum isProperty = functionAttributes!F & FunctionAttribute.property;
154 }
155 
156 
157 /**
158    All member function symbols in T with overloads represented
159    separately.
160  */
161 template MemberFunctionsByOverload(T) if(isStruct!T || isClass!T || isInterface!T || isUnion!T)
162 {
163     import mirror.meta.reflection: functionsByOverload;
164     import mirror.trait_enums: Protection;
165     import std.meta: Filter, staticMap;
166 
167     private enum isPublic(alias F) = F.protection != Protection.private_;
168     private alias symbolOf(alias S) = S.symbol;
169 
170     alias members = PublicMembers!T;
171     alias overloads = functionsByOverload!(T, members);
172 
173     alias MemberFunctionsByOverload =
174         Filter!(isMemberFunction,
175                 staticMap!(symbolOf,
176                            Filter!(isPublic,
177                                    functionsByOverload!(T, PublicMembers!T))));
178 }
179 
180 
181 // must be a global template
182 private template isMemberFunction(alias F) {
183     import std.algorithm: startsWith;
184 
185     static if(__traits(compiles, __traits(identifier, F))) {
186         enum name = __traits(identifier, F);
187         alias parent = __traits(parent, F);
188 
189         static if(isOOP!parent) {
190             private static bool isWantedFunction(string name) {
191                 import std.algorithm: among;
192                 return
193                     !name.among("toString", "toHash", "opCmp", "opEquals", "factory")
194                     && !name.startsWith("__")
195                     ;
196             }
197         } else {
198             bool isWantedFunction(string name) { return true; }
199         }
200         private bool isOperator(string name) {
201             return name.startsWith("op") && name.length > 2 && name[2] >= 'A';
202         }
203 
204         enum isOp = isOperator(name);
205         enum isMemberFunction = isWantedFunction(name) && !isOperator(name);
206 
207     } else
208         enum isMemberFunction = false;
209 }
210 
211 
212 template PublicMembers(alias A) {
213     import mirror.meta.traits: isPrivate;
214     import std.meta: Filter, staticMap, Alias, AliasSeq;
215 
216     private alias member(string name) = MemberFromName!(A, name);
217     private alias members = staticMap!(member, __traits(allMembers, A));
218 
219     // In the `member` template above, if it's not possible to get a member from `A`,
220     // then the symbol is an empty AliasSeq. An example of such a situation can be
221     // found in `modules.problems` from the tests directory, where this causes things
222     // to not compile: `version = OopsVersion;`.
223     // So we filter out such members.
224     private enum hasSymbol(alias member) = !is(member == void);
225     private alias goodMembers = Filter!(hasSymbol, members);
226 
227     private enum notPrivate(alias member) = !isPrivate!(member.symbol);
228 
229     alias PublicMembers = Filter!(notPrivate, goodMembers);
230 }
231 
232 
233 template MemberFromName(alias parent, string name) {
234     import std.meta: Alias;
235 
236     enum identifier = name;
237 
238     static if(__traits(compiles, Alias!(__traits(getMember, parent, name)))) {
239 
240         alias symbol = Alias!(__traits(getMember, parent, name));
241 
242         static if(is(symbol))
243             alias Type = symbol;
244         else static if(is(typeof(symbol)))
245             alias Type = typeof(symbol);
246         else
247             alias Type = void;
248 
249     } else
250         alias symbol = void;
251 }
252 
253 
254 package template memberIsSomeFunction(alias member) {
255     import std.traits: isSomeFunction;
256     enum memberIsSomeFunction = isSomeFunction!(member.symbol);
257 }
258 
259 
260 package template memberIsRegularFunction(alias member) {
261     static if(memberIsSomeFunction!member) {
262         import std.algorithm: startsWith;
263         enum memberIsRegularFunction =
264             !member.identifier.startsWith("_sharedStaticCtor")
265             && !member.identifier.startsWith("_staticCtor")
266             ;
267     } else
268         enum memberIsRegularFunction = false;
269 }
270 
271 
272 /**
273    If a function is static member function
274  */
275 template isStaticMemberFunction(alias F) {
276     import std.traits: hasStaticMember;
277 
278     static if(__traits(compiles, hasStaticMember!(__traits(parent, F), __traits(identifier, F))))
279         enum isStaticMemberFunction = hasStaticMember!(__traits(parent, F), __traits(identifier, F));
280     else
281         enum isStaticMemberFunction = false;
282 }
283 
284 
285 /**
286    An AliasSeq of BinaryOperator structs for type T, one for each binary operator.
287  */
288 template BinaryOperators(T) {
289     import std.meta: staticMap, Filter, AliasSeq;
290     import std.traits: hasMember;
291 
292     // See https://dlang.org/spec/operatoroverloading.html#binary
293     private alias overloadable = AliasSeq!(
294         "+", "-",  "*",  "/",  "%", "^^",  "&",
295         "|", "^", "<<", ">>", ">>>", "~", "in",
296     );
297 
298     static if(hasMember!(T, "opBinary") || hasMember!(T, "opBinaryRight")) {
299 
300         private enum hasOperatorDir(BinOpDir dir, string op) = is(typeof(probeOperator!(T, functionName(dir), op)));
301         private enum hasOperator(string op) =
302             hasOperatorDir!(BinOpDir.left, op)
303          || hasOperatorDir!(BinOpDir.right, op);
304 
305         alias ops = Filter!(hasOperator, overloadable);
306 
307         template toBinOp(string op) {
308             enum hasLeft  = hasOperatorDir!(BinOpDir.left, op);
309             enum hasRight = hasOperatorDir!(BinOpDir.right, op);
310 
311             static if(hasLeft && hasRight)
312                 enum toBinOp = BinaryOperator(op, BinOpDir.left | BinOpDir.right);
313             else static if(hasLeft)
314                 enum toBinOp = BinaryOperator(op, BinOpDir.left);
315             else static if(hasRight)
316                 enum toBinOp = BinaryOperator(op, BinOpDir.right);
317             else
318                 static assert(false);
319         }
320 
321         alias BinaryOperators = staticMap!(toBinOp, ops);
322     } else
323         alias BinaryOperators = AliasSeq!();
324 }
325 
326 
327 /**
328    Tests if T has a template function named `funcName`
329    with a string template parameter `op`.
330  */
331 private auto probeOperator(T, string funcName, string op)() {
332     import std.traits: Parameters;
333 
334     mixin(`alias func = T.` ~ funcName ~ `;`);
335     alias P = Parameters!(func!op);
336 
337     mixin(`return T.init.` ~ funcName ~ `!op(P.init);`);
338 }
339 
340 
341 struct BinaryOperator {
342     string op;
343     BinOpDir dirs;  /// left, right, or both
344 }
345 
346 
347 enum BinOpDir {
348     left = 1,
349     right = 2,
350 }
351 
352 
353 string functionName(BinOpDir dir) {
354     final switch(dir) with(BinOpDir) {
355         case left: return "opBinary";
356         case right: return "opBinaryRight";
357     }
358     assert(0);
359 }
360 
361 
362 template UnaryOperators(T) {
363     import std.meta: AliasSeq, Filter;
364 
365     alias overloadable = AliasSeq!("-", "+", "~", "*", "++", "--");
366     enum hasOperator(string op) = is(typeof(probeOperator!(T, "opUnary", op)));
367     alias UnaryOperators = Filter!(hasOperator, overloadable);
368 }
369 
370 
371 template AssignOperators(T) {
372     import std.meta: AliasSeq, Filter;
373 
374     // See https://dlang.org/spec/operatoroverloading.html#op-assign
375     private alias overloadable = AliasSeq!(
376         "+", "-",  "*",  "/",  "%", "^^",  "&",
377         "|", "^", "<<", ">>", ">>>", "~",
378     );
379 
380     private enum hasOperator(string op) = is(typeof(probeOperator!(T, "opOpAssign", op)));
381     alias AssignOperators = Filter!(hasOperator, overloadable);
382 }
383 
384 
385 template NumDefaultParameters(A...) if(A.length == 1) {
386     import std.traits: isCallable, ParameterDefaults;
387     import std.meta: Filter;
388 
389     alias F = A[0];
390     static assert(isCallable!F);
391 
392     private template notVoid(T...) if(T.length == 1) {
393         enum notVoid = !is(T[0] == void);
394     }
395 
396     enum NumDefaultParameters = Filter!(notVoid, ParameterDefaults!F).length;
397 }
398 
399 
400 template NumRequiredParameters(A...) if(A.length == 1) {
401     import std.traits: isCallable, Parameters;
402     alias F = A[0];
403     static assert(isCallable!F);
404     enum NumRequiredParameters = Parameters!F.length - NumDefaultParameters!F;
405 }
406 
407 
408 /**
409    AliasSeq of `Parameter` templates with all information on function `F`'s
410    parameters.
411  */
412 template Parameters(alias F) {
413     import mirror.meta.traits: Parameter;
414     import std.traits: StdParameters = Parameters,
415         ParameterIdentifierTuple, ParameterDefaults, ParameterStorageClassTuple;
416     import std.meta: staticMap, aliasSeqOf;
417     import std.range: iota;
418 
419     alias parameter(size_t i) = Parameter!(
420         StdParameters!F[i],
421         ParameterDefaults!F[i],
422         ParameterIdentifierTuple!F[i],
423         ParameterStorageClassTuple!F[i],
424     );
425 
426     // When a default value is a function pointer, things get... weird
427     alias parameterFallback(size_t i) =
428         Parameter!(StdParameters!F[i], void, ParameterIdentifierTuple!F[i]);
429 
430     static if(__traits(compiles, staticMap!(parameter, aliasSeqOf!(StdParameters!F.length.iota))))
431         alias Parameters = staticMap!(parameter, aliasSeqOf!(StdParameters!F.length.iota));
432     else {
433         import std.traits: fullyQualifiedName;
434         pragma(msg, "WARNING: Cannot get parameter defaults for `", fullyQualifiedName!F, "`");
435         alias Parameters = staticMap!(parameterFallback, aliasSeqOf!(StdParameters!F.length.iota));
436     }
437 }
438 
439 /**
440    Information on a function's parameter
441  */
442 template Parameter(
443     T,
444     alias D,
445     string I,
446     std.traits.ParameterStorageClass sc = std.traits.ParameterStorageClass.none)
447 {
448     alias Type = T;
449     alias Default = D;
450     enum identifier = I;
451     enum storageClass = sc;
452 }
453 
454 
455 /**
456    If the passed in template `T` is `Parameter`
457  */
458 template isParameter(alias T) {
459     import std.traits: TemplateOf;
460     enum isParameter = __traits(isSame, TemplateOf!T, Parameter);
461 }
462 
463 
464 template PublicFieldNames(T) {
465     import std.meta: Filter, AliasSeq;
466     import std.traits: FieldNameTuple;
467 
468     enum isPublic(string fieldName) = __traits(getProtection, __traits(getMember, T, fieldName)) == "public";
469     alias PublicFieldNames = Filter!(isPublic, FieldNameTuple!T);
470 }
471 
472 
473 template isMutableSymbol(alias symbol) {
474     import std.traits: isMutable;
475 
476     static if(isMutable!(typeof(symbol))) {
477         enum isMutableSymbol = __traits(compiles, symbol = symbol.init);
478     } else
479         enum isMutableSymbol = false;
480 }
481 
482 
483 template isVariable(alias member) {
484 
485     enum isVariable =
486         is(typeof(member.symbol))
487         && !is(typeof(member.symbol) == function)
488         && !is(typeof(member.symbol) == void)  // can happen with templates
489         && is(typeof(member.symbol.init))
490         ;
491 }
492 
493 
494 /**
495    The fields of a struct, union, or class
496  */
497 template Fields(T) {
498     import mirror.trait_enums: toProtection;
499     import std.meta: staticMap, aliasSeqOf, Filter;
500     import std.traits: FieldTypeTuple, FieldNameTuple;
501     import std.range: iota;
502 
503     private static struct NoType{}
504 
505     private alias member(string name) = __traits(getMember, T, name);
506 
507     template TypeOf(alias A) {
508         static if(is(typeof(A)))
509             alias TypeOf = typeof(A);
510         else
511             alias TypeOf = NoType;
512     }
513 
514     enum isFunction(string name) = is(TypeOf!(member!name) == function);
515     enum hasType(string name) = !is(TypeOf!(member!name) == NoType);
516     enum isField(string name) = !isFunction!name && hasType!name;
517     alias fieldNames = Filter!(isField, __traits(allMembers, T));
518     alias toField(string name) = Field!(
519         TypeOf!(member!name),
520         name,
521         __traits(getProtection, member!name).toProtection
522         );
523 
524     alias Fields = staticMap!(toField, fieldNames);
525 }
526 
527 
528 /**
529    A field of a struct, union, or class
530  */
531 template Field(F, string id, Protection prot = Protection.public_) {
532     alias Type = F;
533     enum identifier = id;
534     enum protection = prot;
535 }