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 }