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