1 module autowrap.reflection;
2 
3 
4 public import autowrap.types: isModule, Modules, Module;
5 import std.meta: allSatisfy;
6 import std.traits: isArray, isCallable;
7 import std.typecons: Flag, No;
8 
9 
10 private alias I(alias T) = T;
11 private enum isString(alias T) = is(typeof(T) == string);
12 
13 
14 template AllFunctions(Modules modules) {
15     import std.algorithm: map;
16     import std.array: join;
17     import std.typecons: Yes, No;  // needed for Module.toString in the mixin
18 
19     enum modulesList = modules.value.map!(a => a.toString).join(", ");
20     mixin(`alias AllFunctions = AllFunctions!(`, modulesList, `);`);
21 }
22 
23 
24 template AllFunctions(Modules...) if(allSatisfy!(isString, Modules)) {
25     import std.meta: staticMap;
26     enum module_(string name) = Module(name);
27     alias AllFunctions = staticMap!(Functions, staticMap!(module_, Modules));
28 }
29 
30 template AllFunctions(Modules...) if(allSatisfy!(isModule, Modules)) {
31     import std.meta: staticMap;
32     alias AllFunctions = staticMap!(Functions, Modules);
33 }
34 
35 
36 template Functions(Module module_) {
37     mixin(`import dmodule = ` ~ module_.name ~ `;`);
38     alias Functions = Functions!(dmodule, module_.alwaysExport);
39 }
40 
41 
42 template Functions(alias module_, Flag!"alwaysExport" alwaysExport = No.alwaysExport)
43     if(!is(typeof(module_) == string))
44 {
45     import std.meta: staticMap, Filter;
46 
47     private template isExport(string memberName) {
48         static if(__traits(compiles, I!(__traits(getMember, module_, memberName))))
49             enum isExport = isExportFunction!(__traits(getMember, module_, memberName), alwaysExport);
50         else
51             enum isExport = false;
52     }
53 
54     alias exportFunctions = Filter!(isExport, __traits(allMembers, module_));
55     private alias toFunctionSymbol(string memberName) =
56         FunctionSymbol!(memberName, module_, __traits(getMember, module_, memberName));
57     alias Functions = staticMap!(toFunctionSymbol, exportFunctions);
58 }
59 
60 template FunctionSymbol(string N, alias M, alias S) {
61 
62     import std.traits: moduleName_ = moduleName;
63 
64     alias name = N;
65     alias module_ = M;
66     enum moduleName = moduleName_!module_;
67     alias symbol = S;
68 }
69 
70 template AllAggregates(Modules modules) {
71     import std.algorithm: map;
72     import std.array: join;
73     import std.typecons: Yes, No;  // needed for Module.toString in the mixin
74 
75     enum modulesList = modules.value.map!(a => a.toString).join(", ");
76     mixin(`alias AllAggregates = AllAggregates!(`, modulesList, `);`);
77 }
78 
79 template AllAggregates(ModuleNames...) if(allSatisfy!(isString, ModuleNames)) {
80     import std.meta: staticMap;
81 
82     enum module_(string name) = Module(name);
83     enum Modules = staticMap!(module_, ModuleNames);
84 
85     alias AllAggregates = AllAggregates!(staticMap!(module_, ModuleNames));
86 }
87 
88 template AllAggregates(Modules...) if(allSatisfy!(isModule, Modules)) {
89 
90     import std.meta: NoDuplicates, Filter;
91     import std.traits: isCopyable, Unqual;
92     import std.datetime: Date, DateTime;
93 
94     // definitions
95     alias aggregates = AggregateDefinitionsInModules!Modules;
96 
97     // return and parameter types
98     alias functionTypes = FunctionTypesInModules!Modules;
99 
100     alias copyables = Filter!(isCopyable, NoDuplicates!(aggregates, functionTypes));
101 
102     template notAlreadyWrapped(T) {
103         alias Type = Unqual!T;
104         enum notAlreadyWrapped = !is(Type == Date) && !is(Type == DateTime);
105     }
106 
107     alias notWrapped = Filter!(notAlreadyWrapped, copyables);
108     alias public_ = Filter!(isPublicSymbol, notWrapped);
109 
110     alias AllAggregates = public_;
111 }
112 
113 private template AggregateDefinitionsInModules(Modules...) if(allSatisfy!(isModule, Modules)) {
114     import std.meta: staticMap;
115     alias AggregateDefinitionsInModules = staticMap!(AggregateDefinitionsInModule, Modules);
116 }
117 
118 private template AggregateDefinitionsInModule(Module module_) {
119 
120     mixin(`import dmodule  = ` ~ module_.name ~ `;`);
121     import std.meta: Filter, staticMap, NoDuplicates, AliasSeq;
122 
123     alias Member(string memberName) = Symbol!(dmodule, memberName);
124     alias members = staticMap!(Member, __traits(allMembers, dmodule));
125     alias aggregates = Filter!(isUserAggregate, members);
126     alias recursives = staticMap!(RecursiveAggregates, aggregates);
127     alias all = AliasSeq!(aggregates, recursives);
128     alias AggregateDefinitionsInModule = NoDuplicates!all;
129 }
130 
131 
132 // All return and parameter types of the functions in the given modules
133 private template FunctionTypesInModules(Modules...) if(allSatisfy!(isModule, Modules)) {
134     import std.meta: staticMap;
135     alias FunctionTypesInModules = staticMap!(FunctionTypesInModule, Modules);
136 }
137 
138 
139 // All return and parameter types of the functions in the given module
140 private template FunctionTypesInModule(Module module_) {
141 
142     mixin(`import dmodule  = ` ~ module_.name ~ `;`);
143     import std.traits: ReturnType, Parameters;
144     import std.meta: Filter, staticMap, AliasSeq, NoDuplicates;
145 
146     alias Member(string memberName) = Symbol!(dmodule, memberName);
147     alias members = staticMap!(Member, __traits(allMembers, dmodule));
148     template isWantedExportFunction(T...) if(T.length == 1) {
149         import std.traits: isSomeFunction;
150         alias F = T[0];
151         static if(isSomeFunction!F)
152             enum isWantedExportFunction = isExportFunction!(F, module_.alwaysExport);
153         else
154             enum isWantedExportFunction = false;
155     }
156     alias functions = Filter!(isWantedExportFunction, members);
157 
158     // all return types of all functions
159     alias returns = NoDuplicates!(Filter!(isUserAggregate, staticMap!(PrimordialType, staticMap!(ReturnType, functions))));
160     // recurse on the types in `returns` to also wrap the aggregate types of the members
161     alias recursiveReturns = NoDuplicates!(staticMap!(RecursiveAggregates, returns));
162     // all of the parameters types of all of the functions
163     alias params = NoDuplicates!(Filter!(isUserAggregate, staticMap!(PrimordialType, staticMap!(Parameters, functions))));
164     // recurse on the types in `params` to also wrap the aggregate types of the members
165     alias recursiveParams = NoDuplicates!(staticMap!(RecursiveAggregates, returns));
166     // chain all types
167     alias functionTypes = AliasSeq!(returns, recursiveReturns, params, recursiveParams);
168 
169     alias FunctionTypesInModule = NoDuplicates!(Filter!(isUserAggregate, functionTypes));
170 }
171 
172 
173 private template RecursiveAggregates(T) {
174     mixin RecursiveAggregateImpl!(T, RecursiveAggregateHelper);
175     alias RecursiveAggregates = RecursiveAggregateImpl;
176 }
177 
178 // Only exists because if RecursiveAggregate recurses using itself dmd complains.
179 // So instead, we ping-pong between identical templates.
180 private template RecursiveAggregateHelper(T) {
181     mixin RecursiveAggregateImpl!(T, RecursiveAggregates);
182     alias RecursiveAggregateHelper = RecursiveAggregateImpl;
183 }
184 
185 /**
186    Only exists because if RecursiveAggregate recurses using itself dmd complains.
187    Instead there's a canonical implementation and we ping-pong between two
188    templates that mix this in.
189  */
190 private mixin template RecursiveAggregateImpl(T, alias Other) {
191     import std.meta: staticMap, Filter, AliasSeq, NoDuplicates;
192     import std.traits: isInstanceOf, Unqual;
193     import std.typecons: Typedef, TypedefType;
194     import std.datetime: Date;
195 
196     static if(isInstanceOf!(Typedef, T)) {
197         alias RecursiveAggregateImpl = TypedefType!T;
198     } else static if (is(T == Date)) {
199         alias RecursiveAggregateImpl = Date;
200     } else static if(isUserAggregate!T) {
201         alias AggMember(string memberName) = Symbol!(T, memberName);
202         alias members = staticMap!(AggMember, __traits(allMembers, T));
203         enum isNotMe(U) = !is(Unqual!T == Unqual!U);
204 
205         alias types = staticMap!(Type, members);
206         alias primordials = staticMap!(PrimordialType, types);
207         alias userAggregates = Filter!(isUserAggregate, primordials);
208         alias aggregates = NoDuplicates!(Filter!(isNotMe, userAggregates));
209 
210         static if(aggregates.length == 0)
211             alias RecursiveAggregateImpl = T;
212         else
213             alias RecursiveAggregateImpl = AliasSeq!(aggregates, staticMap!(Other, aggregates));
214     } else
215         alias RecursiveAggregateImpl = T;
216 }
217 
218 
219 // must be a global template for staticMap
220 private template Type(T...) if(T.length == 1) {
221     import std.traits: isSomeFunction;
222     import std.meta: AliasSeq;
223 
224     static if(isSomeFunction!(T[0]))
225         alias Type = AliasSeq!();
226     else static if(is(T[0]))
227         alias Type = T[0];
228     else
229         alias Type = typeof(T[0]);
230 }
231 
232 // if a type is a struct or a class
233 template isUserAggregate(A...) if(A.length == 1) {
234     import std.datetime;
235     import std.traits: Unqual, isInstanceOf;
236     import std.typecons: Tuple;
237     alias T = A[0];
238 
239     enum isUserAggregate =
240         !is(Unqual!T == DateTime) &&
241         !isInstanceOf!(Tuple, T) &&
242         (is(T == struct) || is(T == class));
243 }
244 
245 
246 // Given a parent (module, struct, ...) and a memberName, alias the actual member,
247 // or void if not possible
248 package template Symbol(alias parent, string memberName) {
249     static if(__traits(compiles, I!(__traits(getMember, parent, memberName))))
250         alias Symbol = I!(__traits(getMember, parent, memberName));
251     else
252         alias Symbol = void;
253 }
254 
255 
256 // T -> T, T[] -> T, T[][] -> T, T* -> T
257 template PrimordialType(T) if(isArray!T) {
258 
259     import std.range.primitives: ElementEncodingType;
260     import std.traits: Unqual;
261 
262     static if(isArray!(ElementEncodingType!T))
263         alias PrimordialType = PrimordialType!(ElementEncodingType!T);
264     else
265         alias PrimordialType = Unqual!(ElementEncodingType!T);
266 }
267 
268 
269 // T -> T, T[] -> T, T[][] -> T, T* -> T
270 template PrimordialType(T) if(!isArray!T) {
271 
272     import std.traits: isPointer, PointerTarget, Unqual;
273 
274     static if(isPointer!T) {
275         static if(isPointer!(PointerTarget!T))
276             alias PrimordialType = PrimordialType!(PointerTarget!T);
277         else
278             alias PrimordialType = Unqual!(PointerTarget!T);
279     } else
280         alias PrimordialType = Unqual!T;
281 }
282 
283 
284 package template isExportFunction(alias F, Flag!"alwaysExport" alwaysExport = No.alwaysExport) {
285     import std.traits: isFunction;
286 
287     static if(!isFunction!F)
288         enum isExportFunction = false;
289     else {
290         version(AutowrapAlwaysExport) {
291             enum linkage = __traits(getLinkage, F);
292             enum isExportFunction = linkage != "C" && linkage != "C++";
293         } else version(AutowrapAlwaysExportC) {
294             enum linkage = __traits(getLinkage, F);
295             enum isExportFunction = linkage == "C" || linkage == "C++";
296         } else
297             enum isExportFunction = isExportSymbol!(F, alwaysExport);
298     }
299 }
300 
301 
302 private template isExportSymbol(alias S, Flag!"alwaysExport" alwaysExport = No.alwaysExport) {
303     static if(__traits(compiles, __traits(getProtection, S)))
304         enum isExportSymbol = isPublicSymbol!S && (alwaysExport || __traits(getProtection, S) == "export");
305     else
306         enum isExportSymbol = false;
307 }
308 
309 private template isPublicSymbol(alias S) {
310     enum isPublicSymbol = __traits(getProtection, S) == "export" || __traits(getProtection, S) == "public";
311 }
312 
313 
314 template PublicFieldNames(T) {
315     import std.meta: Filter, AliasSeq;
316     import std.traits: FieldNameTuple;
317 
318     enum isPublic(string fieldName) = __traits(getProtection, __traits(getMember, T, fieldName)) == "public";
319     alias publicFields = Filter!(isPublic, FieldNameTuple!T);
320 
321     // FIXME - See #54
322     static if(is(T == class))
323         alias PublicFieldNames = AliasSeq!();
324     else
325         alias PublicFieldNames = publicFields;
326 }
327 
328 
329 template PublicFieldTypes(T) {
330     import std.meta: staticMap;
331 
332     alias fieldType(string name) = typeof(__traits(getMember, T, name));
333 
334     alias PublicFieldTypes = staticMap!(fieldType, PublicFieldNames!T);
335 }
336 
337 
338 template Properties(functions...) {
339     import std.meta: Filter;
340     alias Properties = Filter!(isProperty, functions);
341 }
342 
343 
344 template isProperty(alias F) {
345     import std.traits: functionAttributes, FunctionAttribute;
346     enum isProperty = functionAttributes!F & FunctionAttribute.property;
347 }
348 
349 
350 template isStatic(alias F) {
351     import std.traits: hasStaticMember;
352     enum isStatic = hasStaticMember!(__traits(parent, F), __traits(identifier, F));
353 }
354 
355 
356 // From a function symbol to an AliasSeq of `Parameter`
357 template FunctionParameters(A...) if(A.length == 1 && isCallable!(A[0])) {
358     import std.traits: Parameters, ParameterIdentifierTuple, ParameterDefaults;
359     import std.meta: staticMap, aliasSeqOf;
360     import std.range: iota;
361 
362     alias F = A[0];
363 
364     alias parameter(size_t i) = Parameter!(
365         Parameters!F[i],
366         ParameterIdentifierTuple!F[i],
367         ParameterDefaults!F[i]
368     );
369 
370     alias FunctionParameters = staticMap!(parameter, aliasSeqOf!(Parameters!F.length.iota));
371 }
372 
373 
374 template Parameter(T, string id, D...) if(D.length == 1) {
375     alias Type = T;
376     enum identifier = id;
377 
378     static if(is(D[0] == void))
379         alias Default = void;
380     else
381         enum Default = D[0];
382 }
383 
384 template isParameter(alias T) {
385     import std.traits: TemplateOf;
386     enum isParameter = __traits(isSame, TemplateOf!T, Parameter);
387 }
388 
389 
390 template NumDefaultParameters(A...) if(A.length == 1 && isCallable!(A[0])) {
391     import std.meta: Filter;
392     import std.traits: ParameterDefaults;
393 
394     alias F = A[0];
395 
396     template notVoid(T...) if(T.length == 1) {
397         enum notVoid = !is(T[0] == void);
398     }
399 
400     enum NumDefaultParameters = Filter!(notVoid, ParameterDefaults!F).length;
401 }
402 
403 
404 template NumRequiredParameters(A...) if(A.length == 1 && isCallable!(A[0])) {
405     import std.traits: Parameters;
406     alias F = A[0];
407     enum NumRequiredParameters = Parameters!F.length - NumDefaultParameters!F;
408 }
409 
410 
411 template BinaryOperators(T) {
412     import std.meta: staticMap, Filter, AliasSeq;
413     import std.traits: hasMember;
414 
415     // See https://dlang.org/spec/operatoroverloading.html#binary
416     private alias overloadable = AliasSeq!(
417         "+", "-",  "*",  "/",  "%", "^^",  "&",
418         "|", "^", "<<", ">>", ">>>", "~", "in",
419     );
420 
421     static if(hasMember!(T, "opBinary") || hasMember!(T, "opBinaryRight")) {
422 
423         private enum hasOperatorDir(BinOpDir dir, string op) = is(typeof(probeOperator!(T, functionName(dir), op)));
424         private enum hasOperator(string op) =
425             hasOperatorDir!(BinOpDir.left, op)
426             || hasOperatorDir!(BinOpDir.right, op);
427 
428         alias ops = Filter!(hasOperator, overloadable);
429 
430         template toBinOp(string op) {
431             enum hasLeft  = hasOperatorDir!(BinOpDir.left, op);
432             enum hasRight = hasOperatorDir!(BinOpDir.right, op);
433 
434             static if(hasLeft && hasRight)
435                 enum toBinOp = BinaryOperator(op, BinOpDir.left | BinOpDir.right);
436             else static if(hasLeft)
437                 enum toBinOp = BinaryOperator(op, BinOpDir.left);
438             else static if(hasRight)
439                 enum toBinOp = BinaryOperator(op, BinOpDir.right);
440             else
441                 static assert(false);
442         }
443 
444         alias BinaryOperators = staticMap!(toBinOp, ops);
445     } else
446         alias BinaryOperators = AliasSeq!();
447 }
448 
449 
450 /**
451    Tests if T has a template function named `funcName`
452    with a string template parameter `op`.
453  */
454 private auto probeOperator(T, string funcName, string op)() {
455     import std.traits: Parameters;
456 
457     mixin(`alias func = T.` ~ funcName ~ `;`);
458     alias P = Parameters!(func!op);
459 
460     mixin(`return T.init.` ~ funcName ~ `!op(P.init);`);
461 }
462 
463 
464 struct BinaryOperator {
465     string op;
466     BinOpDir dirs;  /// left, right, or both
467 }
468 
469 
470 enum BinOpDir {
471     left = 1,
472     right = 2,
473 }
474 
475 
476 string functionName(BinOpDir dir) {
477     final switch(dir) with(BinOpDir) {
478         case left: return "opBinary";
479         case right: return "opBinaryRight";
480     }
481     assert(0);
482 }
483 
484 
485 
486 template UnaryOperators(T) {
487     import std.meta: AliasSeq, Filter;
488 
489     alias overloadable = AliasSeq!("-", "+", "~", "*", "++", "--");
490     enum hasOperator(string op) = is(typeof(probeOperator!(T, "opUnary", op)));
491     alias UnaryOperators = Filter!(hasOperator, overloadable);
492 }
493 
494 
495 template AssignOperators(T) {
496     import std.meta: AliasSeq, Filter;
497 
498     // See https://dlang.org/spec/operatoroverloading.html#op-assign
499     private alias overloadable = AliasSeq!(
500         "+", "-",  "*",  "/",  "%", "^^",  "&",
501         "|", "^", "<<", ">>", ">>>", "~",
502     );
503 
504     private enum hasOperator(string op) = is(typeof(probeOperator!(T, "opOpAssign", op)));
505     alias AssignOperators = Filter!(hasOperator, overloadable);
506 }