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 template inferAssignAttributes(T) 329 { 330 enum uint inferAssignAttributes() 331 { 332 uint attributes = defaultFunctionAttributes; 333 334 static if (is(T == struct)) 335 { 336 static if (hasMember!(T, "opAssign")) 337 { 338 foreach (o; __traits(getOverloads, T, "opAssign")) 339 { 340 alias params = Parameters!o; 341 static if (params.length == 1 && is(params[0] == T)) 342 { 343 attributes &= functionAttributes!o; 344 } 345 } 346 } 347 else 348 { 349 foreach (field; Fields!T) 350 { 351 attributes &= inferAssignAttributes!field; 352 } 353 } 354 } 355 return attributes; 356 } 357 } 358 359 private template generateAttributeString(uint attributes) 360 { 361 enum string generateAttributeString() 362 { 363 string attributesString; 364 365 static if (attributes & FunctionAttribute.nogc) 366 { 367 attributesString ~= "@nogc "; 368 } 369 static if (attributes & FunctionAttribute.nothrow_) 370 { 371 attributesString ~= "nothrow "; 372 } 373 static if (attributes & FunctionAttribute.pure_) 374 { 375 attributesString ~= "pure "; 376 } 377 static if (attributes & FunctionAttribute.safe) 378 { 379 attributesString ~= "@safe "; 380 } 381 382 return attributesString; 383 } 384 } 385 386 private template needToDup(alias field) 387 { 388 enum needToDup = helper; 389 390 static enum helper() 391 { 392 static if (isSomeString!(typeof(field))) 393 { 394 return false; 395 } 396 else 397 { 398 return isArray!(typeof(field)); 399 } 400 } 401 } 402 403 @nogc nothrow pure @safe unittest 404 { 405 int integerField; 406 int[] integerArrayField; 407 string stringField; 408 409 static assert(!needToDup!integerField); 410 static assert(needToDup!integerArrayField); 411 static assert(!needToDup!stringField); 412 } 413 414 static string accessor(string name) @nogc nothrow pure @safe 415 { 416 import std.string : chomp, chompPrefix; 417 418 return name.chomp("_").chompPrefix("_"); 419 } 420 421 /// 422 @nogc nothrow pure @safe unittest 423 { 424 assert(accessor("foo_") == "foo"); 425 assert(accessor("_foo") == "foo"); 426 } 427 428 /** 429 * Returns a string with the value of the field "visibility" if the field 430 * is annotated with an UDA of type A. The default visibility is "public". 431 */ 432 template getVisibility(alias field, A) 433 { 434 import std.string : format; 435 436 enum getVisibility = helper; 437 438 static enum helper() 439 { 440 alias attributes = getUDAs!(field, A); 441 442 static if (attributes.length == 0) 443 { 444 return A.init.visibility; 445 } 446 else 447 { 448 static assert(attributes.length == 1, 449 format("%s should not have more than one attribute @%s", field.stringof, A.stringof)); 450 451 static if (is(typeof(attributes[0]))) 452 return attributes[0].visibility; 453 else 454 return A.init.visibility; 455 } 456 } 457 } 458 459 /// 460 @nogc nothrow pure @safe unittest 461 { 462 @Read("public") int publicInt; 463 @Read("package") int packageInt; 464 @Read("protected") int protectedInt; 465 @Read("private") int privateInt; 466 @Read int defaultVisibleInt; 467 @Read @Write("protected") int publicReadableProtectedWritableInt; 468 469 static assert(getVisibility!(publicInt, Read) == "public"); 470 static assert(getVisibility!(packageInt, Read) == "package"); 471 static assert(getVisibility!(protectedInt, Read) == "protected"); 472 static assert(getVisibility!(privateInt, Read) == "private"); 473 static assert(getVisibility!(defaultVisibleInt, Read) == "public"); 474 static assert(getVisibility!(publicReadableProtectedWritableInt, Read) == "public"); 475 static assert(getVisibility!(publicReadableProtectedWritableInt, Write) == "protected"); 476 } 477 478 /// Creates accessors for flags. 479 nothrow pure @safe unittest 480 { 481 import std.typecons : Flag, No, Yes; 482 483 class Test 484 { 485 @Read 486 @Write 487 public Flag!"someFlag" test_ = Yes.someFlag; 488 489 mixin(GenerateFieldAccessors); 490 } 491 492 with (new Test) 493 { 494 assert(test == Yes.someFlag); 495 496 test = No.someFlag; 497 498 assert(test == No.someFlag); 499 500 static assert(is(typeof(test) == Flag!"someFlag")); 501 } 502 } 503 504 /// Creates accessors for Nullables. 505 nothrow pure @safe unittest 506 { 507 import std.typecons : Nullable; 508 509 class Test 510 { 511 @Read @Write 512 public Nullable!string test_ = Nullable!string("X"); 513 514 mixin(GenerateFieldAccessors); 515 } 516 517 with (new Test) 518 { 519 assert(!test.isNull); 520 assert(test.get == "X"); 521 522 static assert(is(typeof(test) == Nullable!string)); 523 } 524 } 525 526 /// Creates non-const reader. 527 nothrow pure @safe unittest 528 { 529 class Test 530 { 531 @Read 532 int i_; 533 534 mixin(GenerateFieldAccessors); 535 } 536 537 auto mutableObject = new Test; 538 const constObject = mutableObject; 539 540 mutableObject.i_ = 42; 541 542 assert(mutableObject.i == 42); 543 544 static assert(is(typeof(mutableObject.i) == int)); 545 static assert(is(typeof(constObject.i) == const(int))); 546 } 547 548 /// Creates ref reader. 549 nothrow pure @safe unittest 550 { 551 class Test 552 { 553 @RefRead 554 int i_; 555 556 mixin(GenerateFieldAccessors); 557 } 558 559 auto mutableTestObject = new Test; 560 561 mutableTestObject.i = 42; 562 563 assert(mutableTestObject.i == 42); 564 static assert(is(typeof(mutableTestObject.i) == int)); 565 } 566 567 /// Creates writer. 568 nothrow pure @safe unittest 569 { 570 class Test 571 { 572 @Read @Write 573 private int i_; 574 575 mixin(GenerateFieldAccessors); 576 } 577 578 auto mutableTestObject = new Test; 579 mutableTestObject.i = 42; 580 581 assert(mutableTestObject.i == 42); 582 static assert(!__traits(compiles, mutableTestObject.i += 1)); 583 static assert(is(typeof(mutableTestObject.i) == int)); 584 } 585 586 /// Checks whether hasUDA can be used for each member. 587 nothrow pure @safe unittest 588 { 589 class Test 590 { 591 alias Z = int; 592 593 @Read @Write 594 private int i_; 595 596 mixin(GenerateFieldAccessors); 597 } 598 599 auto mutableTestObject = new Test; 600 mutableTestObject.i = 42; 601 602 assert(mutableTestObject.i == 42); 603 static assert(!__traits(compiles, mutableTestObject.i += 1)); 604 } 605 606 /// Returns non const for PODs and structs. 607 nothrow pure @safe unittest 608 { 609 import std.algorithm : map, sort; 610 import std.array : array; 611 612 class C 613 { 614 @Read 615 string s_; 616 617 mixin(GenerateFieldAccessors); 618 } 619 620 C[] a = null; 621 622 static assert(__traits(compiles, a.map!(c => c.s).array.sort())); 623 } 624 625 /// Regression. 626 nothrow pure @safe unittest 627 { 628 class C 629 { 630 @Read @Write 631 string s_; 632 633 mixin(GenerateFieldAccessors); 634 } 635 636 with (new C) 637 { 638 s = "foo"; 639 assert(s == "foo"); 640 static assert(is(typeof(s) == string)); 641 } 642 } 643 644 /// Supports user-defined accessors. 645 nothrow pure @safe unittest 646 { 647 class C 648 { 649 this() 650 { 651 str_ = "foo"; 652 } 653 654 @RefRead 655 private string str_; 656 657 public @property const(string) str() const 658 { 659 return this.str_.dup; 660 } 661 662 mixin(GenerateFieldAccessors); 663 } 664 665 with (new C) 666 { 667 str = "bar"; 668 } 669 } 670 671 /// Creates accessor for locally defined types. 672 @system unittest 673 { 674 class X 675 { 676 } 677 678 class Test 679 { 680 @Read 681 public X x_; 682 683 mixin(GenerateFieldAccessors); 684 } 685 686 with (new Test) 687 { 688 x_ = new X; 689 690 assert(x == x_); 691 static assert(is(typeof(x) == X)); 692 } 693 } 694 695 /// Creates const reader for simple structs. 696 nothrow pure @safe unittest 697 { 698 class Test 699 { 700 struct S 701 { 702 int i; 703 } 704 705 @Read 706 S s_; 707 708 mixin(GenerateFieldAccessors); 709 } 710 711 auto mutableObject = new Test; 712 const constObject = mutableObject; 713 714 mutableObject.s_.i = 42; 715 716 assert(constObject.s.i == 42); 717 718 static assert(is(typeof(mutableObject.s) == Test.S)); 719 static assert(is(typeof(constObject.s) == const(Test.S))); 720 } 721 722 /// Reader for structs return copies. 723 nothrow pure @safe unittest 724 { 725 class Test 726 { 727 struct S 728 { 729 int i; 730 } 731 732 @Read 733 S s_; 734 735 mixin(GenerateFieldAccessors); 736 } 737 738 auto mutableObject = new Test; 739 740 mutableObject.s.i = 42; 741 742 assert(mutableObject.s.i == int.init); 743 } 744 745 /// Creates reader for const arrays. 746 nothrow pure @safe unittest 747 { 748 class X 749 { 750 } 751 752 class C 753 { 754 @Read 755 private const(X)[] foo_; 756 757 mixin(GenerateFieldAccessors); 758 } 759 760 auto x = new X; 761 762 with (new C) 763 { 764 foo_ = [x]; 765 766 auto y = foo; 767 768 static assert(is(typeof(y) == const(X)[])); 769 static assert(is(typeof(foo) == const(X)[])); 770 } 771 } 772 773 /// Property has correct type. 774 nothrow pure @safe unittest 775 { 776 class C 777 { 778 @Read 779 private int foo_; 780 781 mixin(GenerateFieldAccessors); 782 } 783 784 with (new C) 785 { 786 static assert(is(typeof(foo) == int)); 787 } 788 } 789 790 /// Inheritance (https://github.com/funkwerk/accessors/issues/5). 791 @nogc nothrow pure @safe unittest 792 { 793 class A 794 { 795 @Read 796 string foo_; 797 798 mixin(GenerateFieldAccessors); 799 } 800 801 class B : A 802 { 803 @Read 804 string bar_; 805 806 mixin(GenerateFieldAccessors); 807 } 808 } 809 810 /// Transfers struct attributes. 811 @nogc nothrow pure @safe unittest 812 { 813 struct S 814 { 815 this(this) 816 { 817 } 818 819 void opAssign(S s) 820 { 821 } 822 } 823 824 class A 825 { 826 @Read 827 S[] foo_; 828 829 @ConstRead 830 S bar_; 831 832 @Write 833 S baz_; 834 835 mixin(GenerateFieldAccessors); 836 } 837 } 838 839 /// @Read property returns array with mutable elements. 840 nothrow pure @safe unittest 841 { 842 struct Field 843 { 844 } 845 846 struct S 847 { 848 @Read 849 Field[] foo_; 850 851 mixin(GenerateFieldAccessors); 852 } 853 854 with (S()) 855 { 856 Field[] arr = foo; 857 } 858 } 859 860 /// Static properties are generated for static members. 861 unittest 862 { 863 class MyStaticTest 864 { 865 @Read 866 static int stuff_ = 8; 867 868 mixin(GenerateFieldAccessors); 869 } 870 871 assert(MyStaticTest.stuff == 8); 872 } 873 874 unittest 875 { 876 struct S 877 { 878 @Read @Write 879 static int foo_ = 8; 880 881 @RefRead 882 static int bar_ = 6; 883 884 mixin(GenerateFieldAccessors); 885 } 886 887 assert(S.foo == 8); 888 static assert(is(typeof({ S.foo = 8; }))); 889 assert(S.bar == 6); 890 } 891 892 @nogc nothrow pure @safe unittest 893 { 894 struct Thing 895 { 896 @Read 897 private int[] content_; 898 899 mixin(GenerateFieldAccessors); 900 } 901 902 class User 903 { 904 void helper(const int[] content) 905 { 906 } 907 908 void doer(const Thing thing) 909 { 910 helper(thing.content); 911 } 912 } 913 } 914 915 nothrow pure @safe unittest 916 { 917 class Class 918 { 919 } 920 921 struct Thing 922 { 923 @Read 924 private Class[] classes_; 925 926 mixin(GenerateFieldAccessors); 927 } 928 929 Thing thing; 930 assert(thing.classes.length == 0); 931 }