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