1 /++ 2 Base reflection utilities. 3 4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 5 Authors: Ilya Yaroshenko 6 Macros: 7 +/ 8 module mir.reflection; 9 10 import std.meta; 11 import std.traits: hasUDA, getUDAs, Parameters, isSomeFunction, FunctionAttribute, functionAttributes, EnumMembers, isAggregateType; 12 13 deprecated 14 package alias isSomeStruct = isAggregateType; 15 16 /++ 17 Match types like `std.typeconst: Nullable`. 18 +/ 19 template isStdNullable(T) 20 { 21 import std.traits : hasMember; 22 23 T* aggregate; 24 25 enum bool isStdNullable = 26 hasMember!(T, "isNull") && 27 hasMember!(T, "get") && 28 hasMember!(T, "nullify") && 29 is(typeof(__traits(getMember, aggregate, "isNull")()) == bool) && 30 !is(typeof(__traits(getMember, aggregate, "get")()) == void) && 31 is(typeof(__traits(getMember, aggregate, "nullify")()) == void); 32 } 33 34 /// 35 version(mir_core_test) unittest 36 { 37 import std.typecons; 38 static assert(isStdNullable!(Nullable!double)); 39 } 40 41 /// 42 version(mir_core_test) unittest 43 { 44 import mir.algebraic; 45 static assert(isStdNullable!(Nullable!double)); 46 } 47 48 /// Attribute for deprecated API 49 struct reflectDeprecated(string target) 50 { 51 string info; 52 } 53 54 /// Attribute to rename methods, types and functions 55 template ReflectName(string target) 56 { 57 /// 58 struct ReflectName(Args...) 59 { 60 /// 61 string name; 62 } 63 } 64 65 /// ditto 66 template reflectName(string target = null, Args...) 67 { 68 /// 69 auto reflectName(string name) 70 { 71 alias TargetName = ReflectName!target; 72 return TargetName!Args(name); 73 } 74 } 75 76 /// 77 version(mir_core_test) unittest 78 { 79 enum E { A, B, C } 80 81 struct S 82 { 83 @reflectName("A") 84 int a; 85 86 @reflectName!"c++"("B") 87 int b; 88 89 @reflectName!("C", double)("cd") 90 @reflectName!("C", float)("cf") 91 F c(F)() 92 { 93 return b; 94 } 95 } 96 97 import std.traits: hasUDA; 98 99 alias UniName = ReflectName!null; 100 alias CppName = ReflectName!"c++"; 101 alias CName = ReflectName!"C"; 102 103 static assert(hasUDA!(S.a, UniName!()("A"))); 104 static assert(hasUDA!(S.b, CppName!()("B"))); 105 106 // static assert(hasUDA!(S.c, ReflectName)); // doesn't work for now 107 static assert(hasUDA!(S.c, CName)); 108 static assert(hasUDA!(S.c, CName!double)); 109 static assert(hasUDA!(S.c, CName!float)); 110 static assert(hasUDA!(S.c, CName!double("cd"))); 111 static assert(hasUDA!(S.c, CName!float("cf"))); 112 } 113 114 /// Attribute to rename methods, types and functions 115 template ReflectMeta(string target, string[] fields) 116 { 117 /// 118 struct ReflectMeta(Args...) 119 { 120 /// 121 Args args; 122 static foreach(i, field; fields) 123 mixin(`alias ` ~ field ~` = args[` ~ i.stringof ~`];`); 124 } 125 } 126 127 /// ditto 128 template reflectMeta(string target, string[] fields) 129 { 130 /// 131 auto reflectMeta(Args...)(Args args) 132 if (args.length <= fields.length) 133 { 134 alias TargetMeta = ReflectMeta!(target, fields); 135 return TargetMeta!Args(args); 136 } 137 } 138 139 /// 140 version(mir_core_test) unittest 141 { 142 enum E { A, B, C } 143 144 struct S 145 { 146 int a; 147 @reflectMeta!("c++", ["type"])(E.C) 148 int b; 149 } 150 151 import std.traits: hasUDA; 152 153 alias CppMeta = ReflectMeta!("c++", ["type"]); 154 155 static assert(CppMeta!E(E.C).type == E.C); 156 static assert(!hasUDA!(S.a, CppMeta!E(E.A))); 157 static assert(hasUDA!(S.b, CppMeta!E(E.C))); 158 } 159 160 /++ 161 Attribute to ignore a reflection target 162 +/ 163 template reflectIgnore(string target) 164 { 165 enum reflectIgnore; 166 } 167 168 /// 169 version(mir_core_test) unittest 170 { 171 struct S 172 { 173 @reflectIgnore!"c++" 174 int a; 175 } 176 177 import std.traits: hasUDA; 178 static assert(hasUDA!(S.a, reflectIgnore!"c++")); 179 } 180 181 /// Attribute for documentation and unittests 182 struct ReflectDoc(string target) 183 { 184 /// 185 string text; 186 /// 187 reflectUnittest!target test; 188 189 /// 190 @safe pure nothrow @nogc 191 this(string text) 192 { 193 this.text = text; 194 } 195 196 /// 197 @safe pure nothrow @nogc 198 this(string text, reflectUnittest!target test) 199 { 200 this.text = text; 201 this.test = test; 202 } 203 204 /// 205 void toString(W)(scope ref W w) scope const 206 { 207 w.put(cast()this.text); 208 209 if (this.test.text.length) 210 { 211 w.put("\nExample usage:\n"); 212 w.put(cast()this.test.text); 213 } 214 } 215 216 /// 217 @safe pure nothrow 218 string toString()() scope const 219 { 220 return this.text ~ "\nExample usage:\n" ~ this.test.text; 221 } 222 } 223 224 /++ 225 Attribute for documentation. 226 +/ 227 template reflectDoc(string target = null) 228 { 229 /// 230 ReflectDoc!target reflectDoc(string text) 231 { 232 return ReflectDoc!target(text); 233 } 234 235 /// 236 ReflectDoc!target reflectDoc(string text, reflectUnittest!target test) 237 { 238 return ReflectDoc!target(text, test); 239 } 240 } 241 242 /++ 243 +/ 244 template reflectGetDocs(string target, alias symbol) 245 { 246 static if (hasUDA!(symbol, ReflectDoc!target)) 247 static immutable(ReflectDoc!target[]) reflectGetDocs = [getUDAs!(symbol, ReflectDoc!target)]; 248 else 249 static immutable(ReflectDoc!target[]) reflectGetDocs = null; 250 } 251 252 /// ditto 253 template reflectGetDocs(string target) 254 { 255 /// 256 alias reflectGetDocs(alias symbol) = .reflectGetDocs!(target, symbol); 257 258 /// ditto 259 immutable(ReflectDoc!target)[] reflectGetDocs(T)(T value) 260 @safe pure nothrow @nogc 261 if (is(T == enum)) 262 { 263 foreach (i, member; EnumMembers!T) 264 {{ 265 alias all = __traits(getAttributes, EnumMembers!T[i]); 266 }} 267 static immutable ReflectDoc!target[][EnumMembers!T.length] docs = [staticMap!(reflectGetDocs, EnumMembers!T)]; 268 import mir.enums: getEnumIndex; 269 uint index = void; 270 if (getEnumIndex(value, index)) 271 return docs[index]; 272 assert(0); 273 } 274 } 275 276 /// 277 version(mir_core_test) unittest 278 { 279 enum E 280 { 281 @reflectDoc("alpha") 282 a, 283 @reflectDoc!"C#"("Beta", reflectUnittest!"C#"("some c# code")) 284 @reflectDoc("beta") 285 b, 286 c, 287 } 288 289 alias Doc = ReflectDoc!null; 290 alias CSDoc = ReflectDoc!"C#"; 291 292 static assert(reflectGetDocs!null(E.a) == [Doc("alpha")]); 293 static assert(reflectGetDocs!"C#"(E.b) == [CSDoc("Beta", reflectUnittest!"C#"("some c# code"))]); 294 static assert(reflectGetDocs!null(E.b) == [Doc("beta")]); 295 static assert(reflectGetDocs!null(E.c) is null); 296 297 struct S 298 { 299 @reflectDoc("alpha") 300 @reflectDoc!"C#"("Alpha") 301 int a; 302 } 303 304 static assert(reflectGetDocs!(null, S.a) == [Doc("alpha")]); 305 static assert(reflectGetDocs!("C#", S.a) == [CSDoc("Alpha")]); 306 307 import std.conv: to; 308 static assert(CSDoc("Beta", reflectUnittest!"C#"("some c# code")).to!string == "Beta\nExample usage:\nsome c# code"); 309 } 310 311 /++ 312 Attribute for extern unit-test. 313 +/ 314 struct reflectUnittest(string target) 315 { 316 /// 317 string text; 318 319 @safe pure nothrow @nogc: 320 321 this(string text) 322 { 323 this.text = text; 324 } 325 326 this(const typeof(this) other) 327 { 328 this.text = other.text; 329 } 330 } 331 332 /++ 333 +/ 334 template reflectGetUnittest(string target, alias symbol) 335 { 336 static if (hasUDA!(symbol, reflectUnittest!target)) 337 enum string reflectGetUnittest = getUDA!(symbol, reflectUnittest).text; 338 else 339 enum string reflectGetUnittest = null; 340 } 341 342 /// ditto 343 template reflectGetUnittest(string target) 344 { 345 /// 346 alias reflectGetUnittest(alias symbol) = .reflectGetUnittest!(target, symbol); 347 348 /// 349 string reflectGetUnittest(T)(T value) 350 if (is(T == enum)) 351 { 352 foreach (i, member; EnumMembers!T) 353 {{ 354 alias all = __traits(getAttributes, EnumMembers!T[i]); 355 }} 356 static immutable string[EnumMembers!T.length] tests = [staticMap!(reflectGetUnittest, EnumMembers!T)]; 357 import mir.enums: getEnumIndex; 358 uint index = void; 359 if (getEnumIndex(value, index)) 360 return tests[index]; 361 assert(0); 362 } 363 } 364 365 /// 366 version(mir_core_test) unittest 367 { 368 enum E 369 { 370 @reflectUnittest!"c++"("assert(E::a == 0);") 371 a, 372 @reflectUnittest!"c++"("assert(E::b == 1);") 373 b, 374 c, 375 } 376 377 static assert(reflectGetUnittest!"c++"(E.a) == "assert(E::a == 0);"); 378 static assert(reflectGetUnittest!"c++"(E.b) == "assert(E::b == 1);"); 379 static assert(reflectGetUnittest!"c++"(E.c) is null); 380 381 struct S 382 { 383 @reflectUnittest!"c++"("alpha") 384 int a; 385 } 386 387 static assert(reflectGetUnittest!("c++", S.a) == "alpha"); 388 } 389 390 /++ 391 Returns: single UDA. 392 +/ 393 template getUDA(alias symbol, alias attribute) 394 { 395 private alias all = getUDAs!(symbol, attribute); 396 static if (all.length != 1) 397 static assert(0, "Exactly one " ~ attribute.stringof ~ " attribute is required, " ~ "got " ~ all.length.stringof); 398 else 399 { 400 static if (is(typeof(all[0]))) 401 enum getUDA = all[0]; 402 else 403 alias getUDA = all[0]; 404 } 405 } 406 407 /++ 408 Checks if T has a field member. 409 +/ 410 enum bool isOriginalMember(T, string member) = __traits(identifier, __traits(getMember, T, member)) == member; 411 412 /// 413 version(mir_core_test) unittest 414 { 415 struct D 416 { 417 int a; 418 alias b = a; 419 } 420 421 static assert(isOriginalMember!(D, "a")); 422 static assert(!isOriginalMember!(D, "b")); 423 } 424 425 /++ 426 Checks if T has a field member. 427 +/ 428 enum bool hasField(T, string member) = __traits(compiles, (ref T aggregate) { return __traits(getMember, aggregate, member).offsetof; }); 429 430 deprecated("use 'hasField' instead") alias isField = hasField; 431 432 /// 433 version(mir_core_test) unittest 434 { 435 struct D 436 { 437 int gi; 438 } 439 440 struct I 441 { 442 int f; 443 444 D base; 445 alias base this; 446 447 void gi(double ) @property {} 448 void gi(uint ) @property {} 449 } 450 451 struct S 452 { 453 int d; 454 455 I i; 456 alias i this; 457 458 int gm() @property {return 0;} 459 int gc() const @property {return 0;} 460 void gs(int) @property {} 461 } 462 463 static assert(!hasField!(S, "gi")); 464 static assert(!hasField!(S, "gs")); 465 static assert(!hasField!(S, "gc")); 466 static assert(!hasField!(S, "gm")); 467 static assert(!hasField!(S, "gi")); 468 static assert(hasField!(S, "d")); 469 static assert(hasField!(S, "f")); 470 static assert(hasField!(S, "i")); 471 } 472 473 /// with classes 474 version(mir_core_test) unittest 475 { 476 class I 477 { 478 int f; 479 480 void gi(double ) @property {} 481 void gi(uint ) @property {} 482 } 483 484 class S 485 { 486 int d; 487 488 I i; 489 alias i this; 490 491 int gm() @property {return 0;} 492 int gc() const @property {return 0;} 493 void gs(int) @property {} 494 } 495 496 static assert(!hasField!(S, "gi")); 497 static assert(!hasField!(S, "gs")); 498 static assert(!hasField!(S, "gc")); 499 static assert(!hasField!(S, "gm")); 500 static assert(hasField!(S, "d")); 501 static assert(hasField!(S, "f")); 502 static assert(hasField!(S, "i")); 503 } 504 505 /++ 506 Checks if member is property. 507 +/ 508 template isProperty(T, string member) 509 { 510 T* aggregate; 511 512 static if (__traits(compiles, isSomeFunction!(__traits(getMember, *aggregate, member)))) 513 { 514 static if (isSomeFunction!(__traits(getMember, *aggregate, member))) 515 { 516 enum bool isProperty = isPropertyImpl!(__traits(getMember, *aggregate, member)); 517 } 518 else 519 { 520 enum bool isProperty = false; 521 } 522 } 523 else 524 enum bool isProperty = false; 525 } 526 527 /// 528 version(mir_core_test) unittest 529 { 530 struct D 531 { 532 int y; 533 534 void gf(double ) @property {} 535 void gf(uint ) @property {} 536 } 537 538 struct I 539 { 540 int f; 541 542 D base; 543 alias base this; 544 545 void gi(double ) @property {} 546 void gi(uint ) @property {} 547 } 548 549 struct S 550 { 551 int d; 552 553 I i; 554 alias i this; 555 556 int gm() @property {return 0;} 557 int gc() const @property {return 0;} 558 void gs(int) @property {} 559 } 560 561 static assert(isProperty!(S, "gf")); 562 static assert(isProperty!(S, "gi")); 563 static assert(isProperty!(S, "gs")); 564 static assert(isProperty!(S, "gc")); 565 static assert(isProperty!(S, "gm")); 566 static assert(!isProperty!(S, "d")); 567 static assert(!isProperty!(S, "f")); 568 static assert(!isProperty!(S, "y")); 569 } 570 571 /++ 572 Returns: list of the setter properties. 573 574 Note: The implementation ignores templates. 575 +/ 576 template getSetters(T, string member) 577 { 578 static if (__traits(hasMember, T, member)) 579 alias getSetters = Filter!(hasSingleArgument, Filter!(isPropertyImpl, __traits(getOverloads, T, member))); 580 else 581 alias getSetters = AliasSeq!(); 582 } 583 584 /// 585 version(mir_core_test) unittest 586 { 587 struct I 588 { 589 int f; 590 591 void gi(double ) @property {} 592 void gi(uint ) @property {} 593 } 594 595 struct S 596 { 597 int d; 598 599 I i; 600 alias i this; 601 602 int gm() @property {return 0;} 603 int gc() const @property {return 0;} 604 void gs(int) @property {} 605 } 606 607 static assert(getSetters!(S, "gi").length == 2); 608 static assert(getSetters!(S, "gs").length == 1); 609 static assert(getSetters!(S, "gc").length == 0); 610 static assert(getSetters!(S, "gm").length == 0); 611 static assert(getSetters!(S, "d").length == 0); 612 static assert(getSetters!(S, "f").length == 0); 613 } 614 615 /++ 616 Returns: list of the serializable (public getters) members. 617 +/ 618 enum string[] SerializableMembers(T) = [Filter!(ApplyLeft!(Serializable, T), FieldsAndProperties!T)]; 619 620 /// 621 version(mir_core_test) unittest 622 { 623 struct D 624 { 625 int y; 626 627 int gf() @property {return 0;} 628 } 629 630 struct I 631 { 632 int f; 633 634 D base; 635 alias base this; 636 637 int gi() @property {return 0;} 638 } 639 640 struct S 641 { 642 int d; 643 644 package int p; 645 646 int gm() @property {return 0;} 647 648 private int q; 649 650 I i; 651 alias i this; 652 653 int gc() const @property {return 0;} 654 void gs(int) @property {} 655 } 656 657 static assert(SerializableMembers!S == ["y", "gf", "f", "gi", "d", "gm", "gc"]); 658 static assert(SerializableMembers!(const S) == ["y", "f", "d", "gc"]); 659 } 660 661 /++ 662 Returns: list of the deserializable (public setters) members. 663 +/ 664 enum string[] DeserializableMembers(T) = [Filter!(ApplyLeft!(Deserializable, T), FieldsAndProperties!T)]; 665 666 /// 667 version(mir_core_test) unittest 668 { 669 struct I 670 { 671 int f; 672 void ga(int) @property {} 673 } 674 675 struct S 676 { 677 int d; 678 package int p; 679 680 int gm() @property {return 0;} 681 void gm(int) @property {} 682 683 private int q; 684 685 I i; 686 alias i this; 687 688 689 void gc(int, int) @property {} 690 void gc(int) @property {} 691 } 692 693 S s; 694 // s.gc(0); 695 696 static assert (DeserializableMembers!S == ["f", "ga", "d", "gm", "gc"]); 697 static assert (DeserializableMembers!(const S) == []); 698 } 699 700 // This trait defines what members should be serialized - 701 // public members that are either readable and writable or getter properties 702 private template Serializable(T, string member) 703 { 704 static if (!isPublic!(T, member)) 705 enum Serializable = false; 706 else 707 enum Serializable = isReadable!(T, member); // any readable is good 708 } 709 710 private enum bool hasSingleArgument(alias fun) = Parameters!fun.length == 1; 711 private enum bool hasZeroArguments(alias fun) = Parameters!fun.length == 0; 712 713 // This trait defines what members should be serialized - 714 // public members that are either readable and writable or setter properties 715 private template Deserializable(T, string member) 716 { 717 static if (!isPublic!(T, member)) 718 enum Deserializable = false; 719 else 720 static if (isReadableAndWritable!(T, member)) 721 enum Deserializable = true; 722 else 723 static if (getSetters!(T, member).length == 1) 724 enum Deserializable = is(typeof((ref T val){ __traits(getMember, val, member) = Parameters!(getSetters!(T, member)[0])[0].init; })); 725 else 726 enum Deserializable = false; 727 } 728 729 private enum FieldsAndProperties(T) = Reverse!(NoDuplicates!(Reverse!(FieldsAndPropertiesImpl!T))); 730 731 private template allMembers(T) 732 { 733 static if (isAggregateType!T) 734 alias allMembers = __traits(allMembers, T); 735 else 736 alias allMembers = AliasSeq!(); 737 } 738 739 private template FieldsAndPropertiesImpl(T) 740 { 741 alias isProperty = ApplyLeft!(.isProperty, T); 742 alias hasField = ApplyLeft!(.hasField, T); 743 alias isOriginalMember = ApplyLeft!(.isOriginalMember, T); 744 alias isMember = templateAnd!(templateOr!(hasField, isProperty), isOriginalMember); 745 static if (__traits(getAliasThis, T).length) 746 { 747 T* aggregate; 748 alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); 749 static if (isAggregateType!T) 750 alias baseMembers = FieldsAndPropertiesImpl!A; 751 else 752 alias baseMembers = AliasSeq!(); 753 alias members = Erase!(__traits(getAliasThis, T)[0], __traits(allMembers, T)); 754 alias FieldsAndPropertiesImpl = AliasSeq!(baseMembers, Filter!(isMember, members)); 755 } 756 else 757 { 758 import mir.algebraic; 759 static if (isVariant!T) 760 alias members = staticMap!(allMembers, T.AllowedTypes); 761 else 762 alias members = allMembers!T; 763 alias FieldsAndPropertiesImpl = AliasSeq!(Filter!(isMember, members)); 764 } 765 } 766 767 // check if the member is readable 768 private template isReadable(T, string member) 769 { 770 T* aggregate; 771 enum bool isReadable = __traits(compiles, { static fun(T)(auto ref T t) {} fun(__traits(getMember, *aggregate, member)); }); 772 } 773 774 // check if the member is readable/writeble? 775 private template isReadableAndWritable(T, string member) 776 { 777 T* aggregate; 778 enum bool isReadableAndWritable = __traits(compiles, __traits(getMember, *aggregate, member) = __traits(getMember, *aggregate, member)); 779 } 780 781 package template isPublic(T, string member) 782 { 783 T* aggregate; 784 enum bool isPublic = !__traits(getProtection, __traits(getMember, *aggregate, member)).privateOrPackage; 785 } 786 787 // check if the member is property 788 private template isSetter(T, string member) 789 { 790 T* aggregate; 791 static if (__traits(compiles, isSomeFunction!(__traits(getMember, *aggregate, member)))) 792 { 793 static if (isSomeFunction!(__traits(getMember, *aggregate, member))) 794 { 795 enum bool isSetter = getSetters!(T, member).length > 0;; 796 } 797 else 798 { 799 enum bool isSetter = false; 800 } 801 } 802 else 803 enum bool isSetter = false; 804 } 805 806 private template isGetter(T, string member) 807 { 808 T* aggregate; 809 static if (__traits(compiles, isSomeFunction!(__traits(getMember, *aggregate, member)))) 810 { 811 static if (isSomeFunction!(__traits(getMember, *aggregate, member))) 812 { 813 enum bool isGetter = Filter!(hasZeroArguments, Filter!(isPropertyImpl, __traits(getOverloads, T, member))).length == 1; 814 } 815 else 816 { 817 enum bool isGetter = false; 818 } 819 } 820 else 821 enum bool isGetter = false; 822 } 823 824 private enum bool isPropertyImpl(alias member) = (functionAttributes!member & FunctionAttribute.property) != 0; 825 826 private bool privateOrPackage()(string protection) 827 { 828 return protection == "private" || protection == "package"; 829 }