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 }