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