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 }