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