1 module accessors; 2 3 import std.traits; 4 5 struct Read 6 { 7 string visibility = "public"; 8 } 9 10 // Deprecated! See below. 11 // RefRead can not check invariants on change, so there's no point. 12 // ref property functions where the value being returned is a field of the class 13 // are entirely equivalent to public fields. 14 struct RefRead 15 { 16 string visibility = "public"; 17 } 18 19 struct ConstRead 20 { 21 string visibility = "public"; 22 } 23 24 struct Write 25 { 26 string visibility = "public"; 27 } 28 29 immutable string GenerateFieldAccessors = ` 30 mixin GenerateFieldAccessorMethods; 31 mixin(GenerateFieldAccessorMethodsImpl); 32 `; 33 34 mixin template GenerateFieldAccessorMethods() 35 { 36 import std.meta : Alias, Filter; 37 38 private enum bool isNotThis(string T) = T != "this"; 39 40 static enum GenerateFieldAccessorMethodsImpl() 41 { 42 import std.traits : hasUDA; 43 44 string result = ""; 45 46 foreach (name; Filter!(isNotThis, __traits(derivedMembers, typeof(this)))) 47 { 48 enum string fieldCode = `Alias!(__traits(getMember, typeof(this), "` ~ name ~ `"))`; 49 mixin("alias field = " ~ fieldCode ~ ";"); 50 51 static if (__traits(compiles, hasUDA!(field, Read))) 52 { 53 static if (hasUDA!(field, Read)) 54 { 55 enum string readerDecl = GenerateReader!(name, field); 56 debug (accessors) pragma(msg, readerDecl); 57 result ~= readerDecl; 58 } 59 60 static if (hasUDA!(field, RefRead)) 61 { 62 result ~= `pragma(msg, "Deprecation! RefRead on ` ~ typeof(this).stringof ~ `.` ~ name 63 ~ ` makes a private field effectively public, defeating the point.");`; 64 65 enum string refReaderDecl = GenerateRefReader!(name, field); 66 debug (accessors) pragma(msg, refReaderDecl); 67 result ~= refReaderDecl; 68 } 69 70 static if (hasUDA!(field, ConstRead)) 71 { 72 enum string constReaderDecl = GenerateConstReader!(name, field); 73 debug (accessors) pragma(msg, constReaderDecl); 74 result ~= constReaderDecl; 75 } 76 77 static if (hasUDA!(field, Write)) 78 { 79 enum string writerDecl = GenerateWriter!(name, field, fieldCode); 80 debug (accessors) pragma(msg, writerDecl); 81 result ~= writerDecl; 82 } 83 } 84 } 85 86 return result; 87 } 88 } 89 90 template isStatic(alias field) 91 { 92 enum isStatic = helper; 93 94 static enum helper() 95 { 96 return is(typeof({ auto f = field; })); 97 } 98 } 99 100 template getModifiers(alias field) 101 { 102 enum getModifiers = helper; 103 104 static enum helper() 105 { 106 static if (isStatic!field) 107 { 108 return " static"; 109 } 110 else 111 { 112 return ""; 113 } 114 } 115 } 116 117 template filterAttributes(alias field) 118 { 119 enum filterAttributes = helper; 120 121 static enum helper() 122 { 123 uint attributes = uint.max; 124 static if (needToDup!field) 125 { 126 attributes &= ~FunctionAttribute.nogc; 127 } 128 static if (isStatic!field) 129 { 130 attributes &= ~FunctionAttribute.pure_; 131 } 132 return attributes; 133 } 134 } 135 136 template GenerateReader(string name, alias field) 137 { 138 enum GenerateReader = helper; 139 140 static enum helper() 141 { 142 import std..string : format; 143 144 enum visibility = getVisibility!(field, Read); 145 enum accessorName = accessor(name); 146 enum needToDup = needToDup!field; 147 148 enum uint attributes = inferAttributes!(typeof(field), "__postblit") & 149 filterAttributes!field; 150 151 string attributesString = generateAttributeString!attributes; 152 153 static if (needToDup) 154 { 155 return format("%s%s final @property auto %s() inout %s" 156 ~ "{ return typeof(this.%s).init ~ this.%s; }", 157 visibility, getModifiers!field, accessorName, attributesString, name, name); 158 } 159 else static if (isStatic!field) 160 { 161 return format("%s static final @property auto %s() %s{ return this.%s; }", 162 visibility, accessorName, attributesString, name); 163 } 164 else 165 { 166 return format("%s final @property auto %s() inout %s{ return this.%s; }", 167 visibility, accessorName, attributesString, name); 168 } 169 } 170 } 171 172 /// 173 @nogc nothrow pure @safe unittest 174 { 175 int integerValue; 176 string stringValue; 177 int[] intArrayValue; 178 179 static assert(GenerateReader!("foo", integerValue) == 180 "public static final @property auto foo() " ~ 181 "@nogc nothrow @safe { return this.foo; }"); 182 static assert(GenerateReader!("foo", stringValue) == 183 "public static final @property auto foo() " ~ 184 "@nogc nothrow @safe { return this.foo; }"); 185 static assert(GenerateReader!("foo", intArrayValue) == 186 "public static final @property auto foo() inout nothrow @safe " 187 ~ "{ return typeof(this.foo).init ~ this.foo; }"); 188 } 189 190 template GenerateRefReader(string name, alias field) 191 { 192 enum GenerateRefReader = helper; 193 194 static enum helper() 195 { 196 import std..string : format; 197 198 enum visibility = getVisibility!(field, RefRead); 199 enum accessorName = accessor(name); 200 201 static if (isStatic!field) 202 { 203 enum string attributesString = "@nogc nothrow @safe "; 204 } 205 else 206 { 207 enum string attributesString = "@nogc nothrow pure @safe "; 208 } 209 210 return format("%s%s final @property ref auto %s() " ~ 211 "%s{ return this.%s; }", 212 visibility, getModifiers!field, accessorName, attributesString, name); 213 } 214 } 215 216 /// 217 @nogc nothrow pure @safe unittest 218 { 219 int integerValue; 220 string stringValue; 221 int[] intArrayValue; 222 223 static assert(GenerateRefReader!("foo", integerValue) == 224 "public static final @property ref auto foo() " ~ 225 "@nogc nothrow @safe { return this.foo; }"); 226 static assert(GenerateRefReader!("foo", stringValue) == 227 "public static final @property ref auto foo() " ~ 228 "@nogc nothrow @safe { return this.foo; }"); 229 static assert(GenerateRefReader!("foo", intArrayValue) == 230 "public static final @property ref auto foo() " ~ 231 "@nogc nothrow @safe { return this.foo; }"); 232 } 233 234 template GenerateConstReader(string name, alias field) 235 { 236 enum GenerateConstReader = helper; 237 238 static enum helper() 239 { 240 import std..string : format; 241 242 enum visibility = getVisibility!(field, RefRead); 243 enum accessorName = accessor(name); 244 245 alias attributes = inferAttributes!(typeof(field), "__postblit"); 246 string attributesString = generateAttributeString!attributes; 247 248 return format("%s final @property auto %s() const %s { return this.%s; }", 249 visibility, accessorName, attributesString, name); 250 } 251 } 252 253 template GenerateWriter(string name, alias field, string fieldCode) 254 { 255 enum GenerateWriter = helper; 256 257 static enum helper() 258 { 259 import std..string : format; 260 261 enum visibility = getVisibility!(field, Write); 262 enum accessorName = accessor(name); 263 enum inputName = accessorName; 264 enum needToDup = needToDup!field; 265 266 enum uint attributes = defaultFunctionAttributes & 267 filterAttributes!field & 268 inferAssignAttributes!(typeof(field)) & 269 inferAttributes!(typeof(field), "__postblit") & 270 inferAttributes!(typeof(field), "__dtor"); 271 272 enum attributesString = generateAttributeString!attributes; 273 274 return format("%s%s final @property void %s(typeof(%s) %s) %s{ this.%s = %s%s; }", 275 visibility, getModifiers!field, accessorName, fieldCode, inputName, 276 attributesString, name, inputName, needToDup ? ".dup" : ""); 277 } 278 } 279 280 /// 281 @nogc nothrow pure @safe unittest 282 { 283 int integerValue; 284 string stringValue; 285 int[] intArrayValue; 286 287 static assert(GenerateWriter!("foo", integerValue, "integerValue") == 288 "public static final @property void foo(typeof(integerValue) foo) " ~ 289 "@nogc nothrow @safe { this.foo = foo; }"); 290 static assert(GenerateWriter!("foo", stringValue, "stringValue") == 291 "public static final @property void foo(typeof(stringValue) foo) " ~ 292 "@nogc nothrow @safe { this.foo = foo; }"); 293 static assert(GenerateWriter!("foo", intArrayValue, "intArrayValue") == 294 "public static final @property void foo(typeof(intArrayValue) foo) " ~ 295 "nothrow @safe { this.foo = foo.dup; }"); 296 } 297 298 private enum uint defaultFunctionAttributes = 299 FunctionAttribute.nogc | 300 FunctionAttribute.safe | 301 FunctionAttribute.nothrow_ | 302 FunctionAttribute.pure_; 303 304 private template inferAttributes(T, string M) 305 { 306 enum uint inferAttributes() 307 { 308 uint attributes = defaultFunctionAttributes; 309 310 static if (is(T == struct)) 311 { 312 static if (hasMember!(T, M)) 313 { 314 attributes &= functionAttributes!(__traits(getMember, T, M)); 315 } 316 else 317 { 318 foreach (field; Fields!T) 319 { 320 attributes &= inferAttributes!(field, M); 321 } 322 } 323 } 324 return attributes; 325 } 326 } 327 328 private enum uint inferAssignAttributes(T)() 329 { 330 static if (hasElaborateAssign!T) 331 { 332 void testAttributes()() 333 { 334 auto testObject = T.init; 335 testObject = T.init; 336 } 337 return functionAttributes!(testAttributes!()); 338 } 339 else 340 { 341 return defaultFunctionAttributes; 342 } 343 } 344 345 // Issue 22: https://github.com/funkwerk/accessors/issues/22 346 @nogc nothrow pure @safe unittest 347 { 348 static struct SysTime 349 { 350 void opAssign(SysTime) @safe pure nothrow 351 { 352 new int; 353 } 354 } 355 static struct Nullable 356 { 357 SysTime t; 358 void opAssign()(SysTime) 359 { 360 } 361 } 362 static assert((inferAssignAttributes!Nullable & FunctionAttribute.nogc) == 0); 363 } 364 365 private template generateAttributeString(uint attributes) 366 { 367 enum string generateAttributeString() 368 { 369 string attributesString; 370 371 static if (attributes & FunctionAttribute.nogc) 372 { 373 attributesString ~= "@nogc "; 374 } 375 static if (attributes & FunctionAttribute.nothrow_) 376 { 377 attributesString ~= "nothrow "; 378 } 379 static if (attributes & FunctionAttribute.pure_) 380 { 381 attributesString ~= "pure "; 382 } 383 static if (attributes & FunctionAttribute.safe) 384 { 385 attributesString ~= "@safe "; 386 } 387 388 return attributesString; 389 } 390 } 391 392 private template needToDup(alias field) 393 { 394 enum needToDup = helper; 395 396 static enum helper() 397 { 398 static if (isSomeString!(typeof(field))) 399 { 400 return false; 401 } 402 else 403 { 404 return isArray!(typeof(field)); 405 } 406 } 407 } 408 409 @nogc nothrow pure @safe unittest 410 { 411 int integerField; 412 int[] integerArrayField; 413 string stringField; 414 415 static assert(!needToDup!integerField); 416 static assert(needToDup!integerArrayField); 417 static assert(!needToDup!stringField); 418 } 419 420 static string accessor(string name) @nogc nothrow pure @safe 421 { 422 import std..string : chomp, chompPrefix; 423 424 return name.chomp("_").chompPrefix("_"); 425 } 426 427 /// 428 @nogc nothrow pure @safe unittest 429 { 430 assert(accessor("foo_") == "foo"); 431 assert(accessor("_foo") == "foo"); 432 } 433 434 /** 435 * Returns a string with the value of the field "visibility" if the field 436 * is annotated with an UDA of type A. The default visibility is "public". 437 */ 438 template getVisibility(alias field, A) 439 { 440 import std..string : format; 441 442 enum getVisibility = helper; 443 444 static enum helper() 445 { 446 alias attributes = getUDAs!(field, A); 447 448 static if (attributes.length == 0) 449 { 450 return A.init.visibility; 451 } 452 else 453 { 454 static assert(attributes.length == 1, 455 format("%s should not have more than one attribute @%s", field.stringof, A.stringof)); 456 457 static if (is(typeof(attributes[0]))) 458 return attributes[0].visibility; 459 else 460 return A.init.visibility; 461 } 462 } 463 } 464 465 /// 466 @nogc nothrow pure @safe unittest 467 { 468 @Read("public") int publicInt; 469 @Read("package") int packageInt; 470 @Read("protected") int protectedInt; 471 @Read("private") int privateInt; 472 @Read int defaultVisibleInt; 473 @Read @Write("protected") int publicReadableProtectedWritableInt; 474 475 static assert(getVisibility!(publicInt, Read) == "public"); 476 static assert(getVisibility!(packageInt, Read) == "package"); 477 static assert(getVisibility!(protectedInt, Read) == "protected"); 478 static assert(getVisibility!(privateInt, Read) == "private"); 479 static assert(getVisibility!(defaultVisibleInt, Read) == "public"); 480 static assert(getVisibility!(publicReadableProtectedWritableInt, Read) == "public"); 481 static assert(getVisibility!(publicReadableProtectedWritableInt, Write) == "protected"); 482 } 483 484 /// Creates accessors for flags. 485 nothrow pure @safe unittest 486 { 487 import std.typecons : Flag, No, Yes; 488 489 class Test 490 { 491 @Read 492 @Write 493 public Flag!"someFlag" test_ = Yes.someFlag; 494 495 mixin(GenerateFieldAccessors); 496 } 497 498 with (new Test) 499 { 500 assert(test == Yes.someFlag); 501 502 test = No.someFlag; 503 504 assert(test == No.someFlag); 505 506 static assert(is(typeof(test) == Flag!"someFlag")); 507 } 508 } 509 510 /// Creates accessors for Nullables. 511 nothrow pure @safe unittest 512 { 513 import std.typecons : Nullable; 514 515 class Test 516 { 517 @Read @Write 518 public Nullable!string test_ = Nullable!string("X"); 519 520 mixin(GenerateFieldAccessors); 521 } 522 523 with (new Test) 524 { 525 assert(!test.isNull); 526 assert(test.get == "X"); 527 528 static assert(is(typeof(test) == Nullable!string)); 529 } 530 } 531 532 /// Creates non-const reader. 533 nothrow pure @safe unittest 534 { 535 class Test 536 { 537 @Read 538 int i_; 539 540 mixin(GenerateFieldAccessors); 541 } 542 543 auto mutableObject = new Test; 544 const constObject = mutableObject; 545 546 mutableObject.i_ = 42; 547 548 assert(mutableObject.i == 42); 549 550 static assert(is(typeof(mutableObject.i) == int)); 551 static assert(is(typeof(constObject.i) == const(int))); 552 } 553 554 /// Creates ref reader. 555 nothrow pure @safe unittest 556 { 557 class Test 558 { 559 @RefRead 560 int i_; 561 562 mixin(GenerateFieldAccessors); 563 } 564 565 auto mutableTestObject = new Test; 566 567 mutableTestObject.i = 42; 568 569 assert(mutableTestObject.i == 42); 570 static assert(is(typeof(mutableTestObject.i) == int)); 571 } 572 573 /// Creates writer. 574 nothrow pure @safe unittest 575 { 576 class Test 577 { 578 @Read @Write 579 private int i_; 580 581 mixin(GenerateFieldAccessors); 582 } 583 584 auto mutableTestObject = new Test; 585 mutableTestObject.i = 42; 586 587 assert(mutableTestObject.i == 42); 588 static assert(!__traits(compiles, mutableTestObject.i += 1)); 589 static assert(is(typeof(mutableTestObject.i) == int)); 590 } 591 592 /// Checks whether hasUDA can be used for each member. 593 nothrow pure @safe unittest 594 { 595 class Test 596 { 597 alias Z = int; 598 599 @Read @Write 600 private int i_; 601 602 mixin(GenerateFieldAccessors); 603 } 604 605 auto mutableTestObject = new Test; 606 mutableTestObject.i = 42; 607 608 assert(mutableTestObject.i == 42); 609 static assert(!__traits(compiles, mutableTestObject.i += 1)); 610 } 611 612 /// Returns non const for PODs and structs. 613 nothrow pure @safe unittest 614 { 615 import std.algorithm : map, sort; 616 import std.array : array; 617 618 class C 619 { 620 @Read 621 string s_; 622 623 mixin(GenerateFieldAccessors); 624 } 625 626 C[] a = null; 627 628 static assert(__traits(compiles, a.map!(c => c.s).array.sort())); 629 } 630 631 /// Regression. 632 nothrow pure @safe unittest 633 { 634 class C 635 { 636 @Read @Write 637 string s_; 638 639 mixin(GenerateFieldAccessors); 640 } 641 642 with (new C) 643 { 644 s = "foo"; 645 assert(s == "foo"); 646 static assert(is(typeof(s) == string)); 647 } 648 } 649 650 /// Supports user-defined accessors. 651 nothrow pure @safe unittest 652 { 653 class C 654 { 655 this() 656 { 657 str_ = "foo"; 658 } 659 660 @RefRead 661 private string str_; 662 663 public @property const(string) str() const 664 { 665 return this.str_.dup; 666 } 667 668 mixin(GenerateFieldAccessors); 669 } 670 671 with (new C) 672 { 673 str = "bar"; 674 } 675 } 676 677 /// Creates accessor for locally defined types. 678 @system unittest 679 { 680 class X 681 { 682 } 683 684 class Test 685 { 686 @Read 687 public X x_; 688 689 mixin(GenerateFieldAccessors); 690 } 691 692 with (new Test) 693 { 694 x_ = new X; 695 696 assert(x == x_); 697 static assert(is(typeof(x) == X)); 698 } 699 } 700 701 /// Creates const reader for simple structs. 702 nothrow pure @safe unittest 703 { 704 class Test 705 { 706 struct S 707 { 708 int i; 709 } 710 711 @Read 712 S s_; 713 714 mixin(GenerateFieldAccessors); 715 } 716 717 auto mutableObject = new Test; 718 const constObject = mutableObject; 719 720 mutableObject.s_.i = 42; 721 722 assert(constObject.s.i == 42); 723 724 static assert(is(typeof(mutableObject.s) == Test.S)); 725 static assert(is(typeof(constObject.s) == const(Test.S))); 726 } 727 728 /// Reader for structs return copies. 729 nothrow pure @safe unittest 730 { 731 class Test 732 { 733 struct S 734 { 735 int i; 736 } 737 738 @Read 739 S s_; 740 741 mixin(GenerateFieldAccessors); 742 } 743 744 auto mutableObject = new Test; 745 746 mutableObject.s.i = 42; 747 748 assert(mutableObject.s.i == int.init); 749 } 750 751 /// Creates reader for const arrays. 752 nothrow pure @safe unittest 753 { 754 class X 755 { 756 } 757 758 class C 759 { 760 @Read 761 private const(X)[] foo_; 762 763 mixin(GenerateFieldAccessors); 764 } 765 766 auto x = new X; 767 768 with (new C) 769 { 770 foo_ = [x]; 771 772 auto y = foo; 773 774 static assert(is(typeof(y) == const(X)[])); 775 static assert(is(typeof(foo) == const(X)[])); 776 } 777 } 778 779 /// Property has correct type. 780 nothrow pure @safe unittest 781 { 782 class C 783 { 784 @Read 785 private int foo_; 786 787 mixin(GenerateFieldAccessors); 788 } 789 790 with (new C) 791 { 792 static assert(is(typeof(foo) == int)); 793 } 794 } 795 796 /// Inheritance (https://github.com/funkwerk/accessors/issues/5). 797 @nogc nothrow pure @safe unittest 798 { 799 class A 800 { 801 @Read 802 string foo_; 803 804 mixin(GenerateFieldAccessors); 805 } 806 807 class B : A 808 { 809 @Read 810 string bar_; 811 812 mixin(GenerateFieldAccessors); 813 } 814 } 815 816 /// Transfers struct attributes. 817 @nogc nothrow pure @safe unittest 818 { 819 struct S 820 { 821 this(this) 822 { 823 } 824 825 void opAssign(S s) 826 { 827 } 828 } 829 830 class A 831 { 832 @Read 833 S[] foo_; 834 835 @ConstRead 836 S bar_; 837 838 @Write 839 S baz_; 840 841 mixin(GenerateFieldAccessors); 842 } 843 } 844 845 /// @Read property returns array with mutable elements. 846 nothrow pure @safe unittest 847 { 848 struct Field 849 { 850 } 851 852 struct S 853 { 854 @Read 855 Field[] foo_; 856 857 mixin(GenerateFieldAccessors); 858 } 859 860 with (S()) 861 { 862 Field[] arr = foo; 863 } 864 } 865 866 /// Static properties are generated for static members. 867 unittest 868 { 869 class MyStaticTest 870 { 871 @Read 872 static int stuff_ = 8; 873 874 mixin(GenerateFieldAccessors); 875 } 876 877 assert(MyStaticTest.stuff == 8); 878 } 879 880 unittest 881 { 882 struct S 883 { 884 @Read @Write 885 static int foo_ = 8; 886 887 @RefRead 888 static int bar_ = 6; 889 890 mixin(GenerateFieldAccessors); 891 } 892 893 assert(S.foo == 8); 894 static assert(is(typeof({ S.foo = 8; }))); 895 assert(S.bar == 6); 896 } 897 898 @nogc nothrow pure @safe unittest 899 { 900 struct Thing 901 { 902 @Read 903 private int[] content_; 904 905 mixin(GenerateFieldAccessors); 906 } 907 908 class User 909 { 910 void helper(const int[] content) 911 { 912 } 913 914 void doer(const Thing thing) 915 { 916 helper(thing.content); 917 } 918 } 919 } 920 921 nothrow pure @safe unittest 922 { 923 class Class 924 { 925 } 926 927 struct Thing 928 { 929 @Read 930 private Class[] classes_; 931 932 mixin(GenerateFieldAccessors); 933 } 934 935 Thing thing; 936 assert(thing.classes.length == 0); 937 }