1 /++ 2 Various traits that are too kameloso-specific to be in [lu]. 3 4 They generally deal with lengths of aggregate member names, used to format 5 output and align columns for [kameloso.printing.printObject]. 6 7 More of our homebrewn traits were deemed too generic to be in kameloso and 8 were moved to [lu.traits] instead. 9 10 See_Also: 11 https://github.com/zorael/lu/blob/master/source/lu/traits.d 12 13 Copyright: [JR](https://github.com/zorael) 14 License: [Boost Software License 1.0](https://www.boost.org/users/license.html) 15 16 Authors: 17 [JR](https://github.com/zorael) 18 +/ 19 module kameloso.traits; 20 21 private: 22 23 import std.traits : isAggregateType; 24 import std.typecons : Flag, No, Yes; 25 26 public: 27 28 29 // memberstringIsThisCtorOrDtor 30 /++ 31 Returns whether or not the member name of an aggregate has the special name 32 `this`, `__ctor` or `__dtor`. 33 34 CTFEable. 35 36 Params: 37 memberstring = Aggregate member string to compare. 38 39 Returns: 40 `true` if the member string matches `this`, `__ctor` or `__dtor`; 41 `false` if not. 42 +/ 43 auto memberstringIsThisCtorOrDtor(const string memberstring) pure @safe nothrow @nogc 44 { 45 return 46 (memberstring == "this") || 47 (memberstring == "__ctor") || 48 (memberstring == "__dtor"); 49 } 50 51 52 // memberIsVisibleAndNotDeprecated 53 /++ 54 Eponymous template; aliases itself to `true` if the passed member of the 55 passed aggregate `Thing` is not `private` and not `deprecated`. 56 57 Compilers previous to 2.096 need to flip the order of the checks (visibility 58 first, deprecations second), whereas it doesn't matter for compilers 2.096 59 onwards. If the order isn't flipped though we get deprecation warnings. 60 Having it this way means we get the visibility/deprecation check we want on 61 all (supported) compiler versions, but regrettably deprecation messages 62 on older compilers. Unsure where the breakpoint is. 63 64 Params: 65 Thing = Some aggregate. 66 memberstring = String name of the member of `Thing` that we want to check 67 the visibility and deprecationness of. 68 +/ 69 template memberIsVisibleAndNotDeprecated(Thing, string memberstring) 70 if (isAggregateType!Thing && memberstring.length) 71 { 72 static if (__VERSION__ >= 2096L) 73 { 74 /+ 75 __traits(getVisibility) over deprecated __traits(getProtection). 76 __traits(isDeprecated) before __traits(getVisibility) to gag 77 deprecation warnings. 78 +/ 79 static if ( 80 !__traits(isDeprecated, __traits(getMember, Thing, memberstring)) && 81 (__traits(getVisibility, __traits(getMember, Thing, memberstring)) != "private") && 82 (__traits(getVisibility, __traits(getMember, Thing, memberstring)) != "package")) 83 { 84 enum memberIsVisibleAndNotDeprecated = true; 85 } 86 else 87 { 88 enum memberIsVisibleAndNotDeprecated = false; 89 } 90 } 91 else static if (__VERSION__ >= 2089L) 92 { 93 /+ 94 __traits(isDeprecated) before __traits(getProtection) to gag 95 deprecation warnings. 96 +/ 97 static if ( 98 !__traits(isDeprecated, __traits(getMember, Thing, memberstring)) && 99 (__traits(getProtection, __traits(getMember, Thing, memberstring)) != "private") && 100 (__traits(getProtection, __traits(getMember, Thing, memberstring)) != "package")) 101 { 102 enum memberIsVisibleAndNotDeprecated = true; 103 } 104 else 105 { 106 enum memberIsVisibleAndNotDeprecated = false; 107 } 108 } 109 else 110 { 111 /+ 112 __traits(getProtection) before __traits(isDeprecated) to actually 113 compile if member not visible. 114 115 This order is not necessary for all versions, but the oldest require 116 it. Additionally we can't avoid the deprecation messages no matter 117 what we do, so just lump the rest here. 118 +/ 119 static if ( 120 (__traits(getProtection, __traits(getMember, Thing, memberstring)) != "private") && 121 (__traits(getProtection, __traits(getMember, Thing, memberstring)) != "package") && 122 !__traits(isDeprecated, __traits(getMember, Thing, memberstring))) 123 { 124 enum memberIsVisibleAndNotDeprecated = true; 125 } 126 else 127 { 128 enum memberIsVisibleAndNotDeprecated = false; 129 } 130 } 131 } 132 133 /// 134 unittest 135 { 136 struct Foo 137 { 138 public int i; 139 private bool b; 140 package string s; 141 deprecated public int di; 142 } 143 144 class Bar 145 { 146 public int i; 147 private bool b; 148 package string s; 149 deprecated public int di; 150 } 151 152 static assert( memberIsVisibleAndNotDeprecated!(Foo, "i")); 153 static assert(!memberIsVisibleAndNotDeprecated!(Foo, "b")); 154 static assert(!memberIsVisibleAndNotDeprecated!(Foo, "s")); 155 static assert(!memberIsVisibleAndNotDeprecated!(Foo, "di")); 156 157 static assert( memberIsVisibleAndNotDeprecated!(Bar, "i")); 158 static assert(!memberIsVisibleAndNotDeprecated!(Bar, "b")); 159 static assert(!memberIsVisibleAndNotDeprecated!(Bar, "s")); 160 static assert(!memberIsVisibleAndNotDeprecated!(Bar, "di")); 161 } 162 163 164 // memberIsMutable 165 /++ 166 As the name suggests, aliases itself to `true` if the passed member of the 167 passed aggregate `Thing` is mutable, which includes that it's not an enum. 168 169 Params: 170 Thing = Some aggregate. 171 memberstring = String name of the member of `Thing` that we want to 172 determine is a non-enum mutable. 173 +/ 174 template memberIsMutable(Thing, string memberstring) 175 if (isAggregateType!Thing && memberstring.length) 176 { 177 import std.traits : isMutable; 178 179 enum memberIsMutable = 180 isMutable!(typeof(__traits(getMember, Thing, memberstring))) && 181 __traits(compiles, __traits(getMember, Thing, memberstring).offsetof); 182 } 183 184 /// 185 unittest 186 { 187 struct Foo 188 { 189 int i; 190 const bool b; 191 immutable string s; 192 enum float f = 3.14; 193 } 194 195 class Bar 196 { 197 int i; 198 const bool b; 199 immutable string s; 200 enum float f = 3.14; 201 } 202 203 static assert( memberIsMutable!(Foo, "i")); 204 static assert(!memberIsMutable!(Foo, "b")); 205 static assert(!memberIsMutable!(Foo, "s")); 206 static assert(!memberIsMutable!(Foo, "f")); 207 208 static assert( memberIsMutable!(Bar, "i")); 209 static assert(!memberIsMutable!(Bar, "b")); 210 static assert(!memberIsMutable!(Bar, "s")); 211 static assert(!memberIsMutable!(Bar, "f")); 212 } 213 214 215 // memberIsValue 216 /++ 217 Aliases itself to `true` if the passed member of the passed aggregate is a 218 value and not a type, a function, a template or an enum. 219 220 Params: 221 Thing = Some aggregate. 222 memberstring = String name of the member of `Thing` that we want to 223 determine is a non-type non-function non-template non-enum value. 224 +/ 225 template memberIsValue(Thing, string memberstring) 226 if (isAggregateType!Thing && memberstring.length) 227 { 228 import std.traits : isSomeFunction, isType; 229 230 enum memberIsValue = 231 !isType!(__traits(getMember, Thing, memberstring)) && 232 !isSomeFunction!(__traits(getMember, Thing, memberstring)) && 233 !__traits(isTemplate, __traits(getMember, Thing, memberstring)) && 234 !is(__traits(getMember, Thing, memberstring) == enum); 235 } 236 237 /// 238 unittest 239 { 240 struct Foo 241 { 242 int i; 243 void f() {} 244 template t(T) {} 245 enum E { abc, } 246 } 247 248 class Bar 249 { 250 int i; 251 void f() {} 252 template t(T) {} 253 enum E { abc, } 254 } 255 256 static assert( memberIsValue!(Foo, "i")); 257 static assert(!memberIsValue!(Foo, "f")); 258 static assert(!memberIsValue!(Foo, "t")); 259 static assert(!memberIsValue!(Foo, "E")); 260 261 static assert( memberIsValue!(Bar, "i")); 262 static assert(!memberIsValue!(Bar, "f")); 263 static assert(!memberIsValue!(Bar, "t")); 264 static assert(!memberIsValue!(Bar, "E")); 265 } 266 267 268 // UnderscoreOpDispatcher 269 /++ 270 Mixin template mixing in an `opDispatch` redirecting calls to members whose 271 names match the passed variable string but with an underscore prepended. 272 273 Example: 274 --- 275 struct Foo 276 { 277 int _i; 278 string _s; 279 bool _b; 280 281 mixin UnderscoreOpDispatcher; 282 } 283 284 Foo f; 285 f.i = 42; // f.opDispatch!"i"(42); 286 f.s = "hello"; // f.opDispatch!"s"("hello"); 287 f.b = true; // f.opDispatch!"b"(true); 288 289 assert(f.i == 42); 290 assert(f.s == "hello"); 291 assert(f.b); 292 --- 293 +/ 294 mixin template UnderscoreOpDispatcher() 295 { 296 ref auto opDispatch(string var, T)(T value) 297 { 298 import std.traits : isArray, isSomeString; 299 300 enum realVar = '_' ~ var; 301 alias V = typeof(mixin(realVar)); 302 303 static if (isArray!V && !isSomeString!V) 304 { 305 mixin(realVar) ~= value; 306 } 307 else 308 { 309 mixin(realVar) = value; 310 } 311 312 return this; 313 } 314 315 auto opDispatch(string var)() const 316 { 317 enum realVar = '_' ~ var; 318 return mixin(realVar); 319 } 320 } 321 322 /// 323 unittest 324 { 325 import dialect.defs; 326 327 struct Foo 328 { 329 IRCEvent.Type[] _acceptedEventTypes; 330 alias _onEvent = _acceptedEventTypes; 331 bool _verbose; 332 bool _chainable; 333 334 mixin UnderscoreOpDispatcher; 335 } 336 337 auto f = Foo() 338 .onEvent(IRCEvent.Type.CHAN) 339 .onEvent(IRCEvent.Type.EMOTE) 340 .onEvent(IRCEvent.Type.QUERY) 341 .chainable(true) 342 .verbose(false); 343 344 assert(f.acceptedEventTypes == [ IRCEvent.Type.CHAN, IRCEvent.Type.EMOTE, IRCEvent.Type.QUERY ]); 345 assert(f.chainable); 346 assert(!f.verbose); 347 } 348 349 350 // longestMemberNames 351 /++ 352 Introspects one or more aggregate types and determines the name of the 353 longest member found between them, as well as the name of the longest type. 354 Ignores [lu.uda.Unserialisable|Unserialisable] members. 355 356 This is used for formatting terminal output of configuration files, so that 357 columns line up. 358 359 Params: 360 Things = Types to introspect. 361 +/ 362 alias longestMemberNames(Things...) = longestMemberNamesImpl!(No.unserialisable, Things); 363 364 /// 365 unittest 366 { 367 import lu.uda : Hidden, Unserialisable; 368 369 struct Foo 370 { 371 string veryLongName; 372 char[][string] css; 373 @Unserialisable string[][string] veryVeryVeryLongNameThatIsInvalid; 374 @Hidden float likewiseWayLongerButInvalid; 375 deprecated bool alsoVeryLongButDeprecated; 376 void aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(); 377 void bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb(T)(); 378 string foo(string,string,string,string,string,string,string,string); 379 } 380 381 struct Bar 382 { 383 string evenLongerName; 384 float f; 385 386 @Unserialisable short shoooooooooooooooort; 387 388 @Unserialisable 389 @Hidden 390 long looooooooooooooooooooooong; 391 } 392 393 alias fooNames = longestMemberNames!Foo; 394 static assert((fooNames.member == "veryLongName"), fooNames.member); 395 static assert((fooNames.type == "char[][string]"), fooNames.type); 396 397 alias barNames = longestMemberNames!Bar; 398 static assert((barNames.member == "evenLongerName"), barNames.member); 399 static assert((barNames.type == "string"), barNames.type); 400 401 alias bothNames = longestMemberNames!(Foo, Bar); 402 static assert((bothNames.member == "evenLongerName"), bothNames.member); 403 static assert((bothNames.type == "char[][string]"), bothNames.type); 404 } 405 406 407 // longestUnserialisableMemberNames 408 /++ 409 Introspects one or more aggregate types and determines the name of the 410 longest member found between them, as well as the name of the longest type. 411 Includes [lu.uda.Unserialisable|Unserialisable] members. 412 413 This is used for formatting terminal output of configuration files, so that 414 columns line up. 415 416 Params: 417 Things = Types to introspect. 418 +/ 419 alias longestUnserialisableMemberNames(Things...) = 420 longestMemberNamesImpl!(Yes.unserialisable, Things); 421 422 /// 423 unittest 424 { 425 import lu.uda : Hidden, Unserialisable; 426 427 struct Foo 428 { 429 string veryLongName; 430 char[][string] css; 431 @Unserialisable string[][string] veryVeryVeryLongNameThatIsInvalid; 432 @Hidden float likewiseWayLongerButInvalid; 433 deprecated bool alsoVeryLongButDeprecated; 434 void aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(); 435 void bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb(T)(); 436 string foo(string,string,string,string,string,string,string,string); 437 } 438 439 struct Bar 440 { 441 string evenLongerName; 442 float f; 443 444 @Unserialisable short shoooooooooooooooort; 445 446 @Unserialisable 447 @Hidden 448 long looooooooooooooooooooooong; 449 } 450 451 alias fooNames = longestUnserialisableMemberNames!Foo; 452 static assert((fooNames.member == "veryVeryVeryLongNameThatIsInvalid"), fooNames.member); 453 static assert((fooNames.type == "string[][string]"), fooNames.type); 454 455 alias barNames = longestUnserialisableMemberNames!Bar; 456 static assert((barNames.member == "shoooooooooooooooort"), barNames.member); 457 static assert((barNames.type == "string"), barNames.type); 458 459 alias bothNames = longestUnserialisableMemberNames!(Foo, Bar); 460 static assert((bothNames.member == "veryVeryVeryLongNameThatIsInvalid"), bothNames.member); 461 static assert((bothNames.type == "string[][string]"), bothNames.type); 462 } 463 464 465 // longestMemberNamesImpl 466 /++ 467 Introspects one or more aggregate types and determines the name of the 468 longest member found between them, as well as the name of the longest type. 469 Only includes [lu.uda.Unserialisable|Unserialisable] members if `unserialisable` 470 is set. 471 472 This is used for formatting terminal output of configuration files, so that 473 columns line up. 474 475 Params: 476 unserialisable = Whether to consider all members or only those not 477 [lu.uda.Unserialisable|Unserialisable]. 478 Things = Types to introspect. 479 +/ 480 private template longestMemberNamesImpl(Flag!"unserialisable" unserialisable, Things...) 481 if (Things.length > 0) 482 { 483 enum longestMemberNamesImpl = () 484 { 485 import lu.traits : isSerialisable; 486 import lu.uda : Hidden, Unserialisable; 487 import std.traits : hasUDA, isAggregateType; 488 489 static struct Results 490 { 491 string member; 492 string type; 493 } 494 495 Results results; 496 if (!__ctfe) return results; 497 498 foreach (Thing; Things) 499 { 500 static if (isAggregateType!Thing) 501 { 502 foreach (immutable memberstring; __traits(derivedMembers, Thing)) 503 { 504 static if ( 505 !memberstringIsThisCtorOrDtor(memberstring) && 506 memberIsVisibleAndNotDeprecated!(Thing, memberstring) && 507 memberIsValue!(Thing, memberstring) && 508 isSerialisable!(__traits(getMember, Thing, memberstring)) && 509 !hasUDA!(__traits(getMember, Thing, memberstring), Hidden) && 510 (unserialisable || !hasUDA!(__traits(getMember, Thing, memberstring), Unserialisable))) 511 { 512 import lu.traits : isTrulyString; 513 import std.traits : isArray, isAssociativeArray; 514 515 alias T = typeof(__traits(getMember, Thing, memberstring)); 516 517 static if (!isTrulyString!T && (isArray!T || isAssociativeArray!T)) 518 { 519 import lu.traits : UnqualArray; 520 enum typestring = UnqualArray!T.stringof; 521 } 522 else 523 { 524 import std.traits : Unqual; 525 enum typestring = Unqual!T.stringof; 526 } 527 528 if (typestring.length > results.type.length) 529 { 530 results.type = typestring; 531 } 532 533 if (memberstring.length > results.member.length) 534 { 535 results.member = memberstring; 536 } 537 } 538 } 539 } 540 else 541 { 542 import std.format : format; 543 544 enum pattern = "Non-aggregate type `%s` passed to `longestNamesImpl`"; 545 enum message = pattern.format(Thing.stringof); 546 static assert(0, message); 547 } 548 } 549 550 return results; 551 }(); 552 } 553 554 555 // udaIndexOf 556 /++ 557 Returns the index of a given UDA, as annotated on a symbol. 558 559 Params: 560 symbol = Symbol to introspect. 561 T = UDA to get the index of. 562 563 Returns: 564 The index of the UDA if found, or `-1` if it was not present. 565 +/ 566 enum udaIndexOf(alias symbol, T) = () 567 { 568 ptrdiff_t index = -1; 569 570 foreach (immutable i, uda; __traits(getAttributes, symbol)) 571 { 572 static if (is(typeof(uda))) 573 { 574 alias U = typeof(uda); 575 } 576 else 577 { 578 alias U = uda; 579 } 580 581 static if (is(U == T)) 582 { 583 index = i; 584 break; 585 } 586 } 587 588 return index; 589 }();