1 module boilerplate.accessors; 2 3 import boilerplate.conditions : generateChecksForAttributes, IsConditionAttribute; 4 import boilerplate.util: DeepConst, isStatic; 5 import std.meta : StdMetaFilter = Filter; 6 import std.traits; 7 import std.typecons: Nullable; 8 9 struct Read 10 { 11 string visibility = "public"; 12 } 13 14 // Deprecated! See below. 15 // RefRead can not check invariants on change, so there's no point. 16 // ref property functions where the value being returned is a field of the class 17 // are entirely equivalent to public fields. 18 struct RefRead 19 { 20 string visibility = "public"; 21 } 22 23 struct ConstRead 24 { 25 string visibility = "public"; 26 } 27 28 struct Write 29 { 30 string visibility = "public"; 31 } 32 33 immutable string GenerateFieldAccessors = ` 34 import boilerplate.accessors : GenerateFieldAccessorMethods; 35 mixin GenerateFieldAccessorMethods; 36 mixin(GenerateFieldAccessorMethodsImpl); 37 `; 38 39 public static string GenerateFieldDecls_(FieldType, Attributes...) 40 (string name, bool synchronize, bool fieldIsStatic, bool fieldIsUnsafe) 41 { 42 if (!__ctfe) 43 { 44 return null; 45 } 46 47 string result; 48 49 import boilerplate.accessors : 50 ConstRead, 51 GenerateConstReader, GenerateReader, GenerateRefReader, GenerateWriter, 52 Read, RefRead, Write; 53 import boilerplate.util : udaIndex; 54 55 static if (udaIndex!(Read, Attributes) != -1) 56 { 57 string readerDecl = GenerateReader!(FieldType, Attributes)( 58 name, fieldIsStatic, fieldIsUnsafe, synchronize); 59 60 debug (accessors) pragma(msg, readerDecl); 61 result ~= readerDecl; 62 } 63 64 static if (udaIndex!(RefRead, Attributes) != -1) 65 { 66 result ~= `pragma(msg, "Deprecation! RefRead on " ~ typeof(this).stringof ~ ".` ~ name 67 ~ ` makes a private field effectively public, defeating the point.");`; 68 69 string refReaderDecl = GenerateRefReader!(FieldType)(name, fieldIsStatic); 70 71 debug (accessors) pragma(msg, refReaderDecl); 72 result ~= refReaderDecl; 73 } 74 75 static if (udaIndex!(ConstRead, Attributes) != -1) 76 { 77 string constReaderDecl = GenerateConstReader!(FieldType, Attributes) 78 (name, fieldIsStatic, fieldIsUnsafe, synchronize); 79 80 debug (accessors) pragma(msg, constReaderDecl); 81 result ~= constReaderDecl; 82 } 83 84 static if (udaIndex!(Write, Attributes) != -1) 85 { 86 string writerDecl = GenerateWriter!(FieldType, Attributes) 87 (name, `this.` ~ name, fieldIsStatic, fieldIsUnsafe, synchronize); 88 89 debug (accessors) pragma(msg, writerDecl); 90 result ~= writerDecl; 91 } 92 93 return result; 94 } 95 96 mixin template GenerateFieldAccessorMethods() 97 { 98 private static string GenerateFieldAccessorMethodsImpl() 99 { 100 if (!__ctfe) 101 { 102 return null; 103 } 104 105 import boilerplate.accessors : GenerateFieldDecls_; 106 import boilerplate.util : GenNormalMemberTuple, isStatic, isUnsafe; 107 108 string result = ""; 109 110 mixin GenNormalMemberTuple; 111 112 foreach (name; NormalMemberTuple) 113 { 114 // synchronized without lock contention is basically free, so always do it 115 // TODO enable when https://issues.dlang.org/show_bug.cgi?id=18504 is fixed 116 // enum synchronize = is(typeof(__traits(getMember, typeof(this), name)) == class); 117 enum fieldIsStatic = mixin(name.isStatic); 118 enum fieldIsUnsafe = mixin(name.isUnsafe); 119 120 result ~= GenerateFieldDecls_!( 121 typeof(__traits(getMember, typeof(this), name)), 122 __traits(getAttributes, __traits(getMember, typeof(this), name)) 123 ) (name, /*synchronize*/false, fieldIsStatic, fieldIsUnsafe); 124 } 125 126 return result; 127 } 128 } 129 130 string getModifiers(bool isStatic) 131 { 132 return isStatic ? " static" : ""; 133 } 134 135 uint filterAttributes(T)(bool isStatic, bool isUnsafe, FilterMode mode) 136 { 137 import boilerplate.util : needToDup; 138 139 uint attributes = uint.max; 140 141 if (needToDup!T) 142 { 143 attributes &= ~FunctionAttribute.nogc; 144 } 145 // Nullable.opAssign is not nogc 146 if (mode == FilterMode.Writer && isInstanceOf!(Nullable, T)) 147 { 148 attributes &= ~FunctionAttribute.nogc; 149 } 150 // TODO remove once synchronized (this) is nothrow 151 // see https://github.com/dlang/druntime/pull/2105 , https://github.com/dlang/dmd/pull/7942 152 if (is(T == class)) 153 { 154 attributes &= ~FunctionAttribute.nothrow_; 155 } 156 if (isStatic) 157 { 158 attributes &= ~FunctionAttribute.pure_; 159 } 160 if (isUnsafe) 161 { 162 attributes &= ~FunctionAttribute.safe; 163 } 164 return attributes; 165 } 166 167 enum FilterMode 168 { 169 Reader, 170 Writer, 171 } 172 173 string GenerateReader(T, Attributes...)(string name, bool fieldIsStatic, bool fieldIsUnsafe, bool synchronize) 174 { 175 import boilerplate.util : needToDup; 176 import std.string : format; 177 import std.traits : Unqual; 178 179 auto example = T.init; 180 auto accessorName = accessor(name); 181 enum visibility = getVisibility!(Read, __traits(getAttributes, example)); 182 enum needToDupField = needToDup!T; 183 184 uint attributes = inferAttributes!(T, "__postblit") & 185 filterAttributes!T(fieldIsStatic, fieldIsUnsafe, FilterMode.Reader); 186 187 string attributesString = generateAttributeString(attributes); 188 string accessorBody; 189 string type; 190 191 if (fieldIsStatic) 192 { 193 type = format!`typeof(this.%s)`(name); 194 } 195 else 196 { 197 type = format!`inout(typeof(this.%s))`(name); 198 } 199 200 // for types like string where the contents are already const or value, 201 // so we can safely reassign to a non-const type 202 static if (needToDupField) 203 { 204 accessorBody = format!`return typeof(this.%s).init ~ this.%s;`(name, name); 205 } 206 else static if (DeepConst!(Unqual!T) && !is(Unqual!T == T)) 207 { 208 // necessitated by DMD bug https://issues.dlang.org/show_bug.cgi?id=18545 209 accessorBody = format!`typeof(cast() this.%s) var = this.%s; return var;`(name, name); 210 type = format!`typeof(cast() this.%s)`(name); 211 } 212 else 213 { 214 accessorBody = format!`return this.%s;`(name); 215 } 216 217 if (synchronize) 218 { 219 accessorBody = format!`synchronized (this) { %s} `(accessorBody); 220 } 221 222 auto modifiers = getModifiers(fieldIsStatic); 223 224 if (!fieldIsStatic) 225 { 226 attributesString ~= " inout"; 227 } 228 229 string outCondition = ""; 230 231 if (fieldIsStatic) 232 { 233 if (auto checks = generateChecksForAttributes!(T, StdMetaFilter!(IsConditionAttribute, Attributes)) 234 ("result", " in postcondition of @Read")) 235 { 236 outCondition = format!` out(result) { %s } body`(checks); 237 } 238 } 239 240 return format!("%s%s final @property %s %s()%s%s { %s }") 241 (visibility, modifiers, type, accessorName, attributesString, outCondition, accessorBody); 242 } 243 244 @("generates readers as expected") 245 @nogc nothrow pure @safe unittest 246 { 247 int integerValue; 248 string stringValue; 249 int[] intArrayValue; 250 const string constStringValue; 251 252 static assert(GenerateReader!int("foo", true, false, false) == 253 "public static final @property typeof(this.foo) foo() " ~ 254 "@nogc nothrow @safe { return this.foo; }"); 255 static assert(GenerateReader!string("foo", true, false, false) == 256 "public static final @property typeof(this.foo) foo() " ~ 257 "@nogc nothrow @safe { return this.foo; }"); 258 static assert(GenerateReader!(int[])("foo", true, false, false) == 259 "public static final @property typeof(this.foo) foo() nothrow @safe " 260 ~ "{ return typeof(this.foo).init ~ this.foo; }"); 261 static assert(GenerateReader!(const string)("foo", true, false, false) == 262 "public static final @property typeof(cast() this.foo) foo() @nogc nothrow @safe " 263 ~ "{ typeof(cast() this.foo) var = this.foo; return var; }"); 264 } 265 266 string GenerateRefReader(T)(string name, bool isStatic) 267 { 268 import std.string : format; 269 270 auto example = T.init; 271 auto accessorName = accessor(name); 272 enum visibility = getVisibility!(RefRead, __traits(getAttributes, example)); 273 274 string attributesString; 275 if (isStatic) 276 { 277 attributesString = "@nogc nothrow @safe "; 278 } 279 else 280 { 281 attributesString = "@nogc nothrow pure @safe "; 282 } 283 284 auto modifiers = getModifiers(isStatic); 285 286 // no need to synchronize a reference read 287 return format("%s%s final @property ref typeof(this.%s) %s() " ~ 288 "%s{ return this.%s; }", 289 visibility, modifiers, name, accessorName, attributesString, name); 290 } 291 292 @("generates ref readers as expected") 293 @nogc nothrow pure @safe unittest 294 { 295 static assert(GenerateRefReader!int("foo", true) == 296 "public static final @property ref typeof(this.foo) foo() " ~ 297 "@nogc nothrow @safe { return this.foo; }"); 298 static assert(GenerateRefReader!string("foo", true) == 299 "public static final @property ref typeof(this.foo) foo() " ~ 300 "@nogc nothrow @safe { return this.foo; }"); 301 static assert(GenerateRefReader!(int[])("foo", true) == 302 "public static final @property ref typeof(this.foo) foo() " ~ 303 "@nogc nothrow @safe { return this.foo; }"); 304 } 305 306 string GenerateConstReader(T, Attributes...)(string name, bool isStatic, bool isUnsafe, bool synchronize) 307 { 308 import std.string : format; 309 310 auto example = T.init; 311 auto accessorName = accessor(name); 312 enum visibility = getVisibility!(ConstRead, __traits(getAttributes, example)); 313 314 uint attributes = inferAttributes!(T, "__postblit") & 315 filterAttributes!T(isStatic, isUnsafe, FilterMode.Reader); 316 317 string attributesString = generateAttributeString(attributes); 318 319 string accessorBody = format!`return this.%s; `(name); 320 321 if (synchronize) 322 { 323 accessorBody = format!`synchronized (this) { %s} `(accessorBody); 324 } 325 326 auto modifiers = getModifiers(isStatic); 327 328 if (isStatic) 329 { 330 string outCondition = ""; 331 332 if (auto checks = generateChecksForAttributes!(T, StdMetaFilter!(IsConditionAttribute, Attributes)) 333 ("result", " in postcondition of @ConstRead")) 334 { 335 outCondition = format!` out(result) { %s } body`(checks); 336 } 337 338 return format("%s%s final @property const(typeof(this.%s)) %s()%s%s { %s}", 339 visibility, modifiers, name, accessorName, attributesString, outCondition, accessorBody); 340 } 341 342 return format("%s%s final @property const(typeof(this.%s)) %s() const%s { %s}", 343 visibility, modifiers, name, accessorName, attributesString, accessorBody); 344 } 345 346 string GenerateWriter(T, Attributes...)(string name, string fieldCode, bool isStatic, bool isUnsafe, bool synchronize) 347 { 348 import boilerplate.util : needToDup; 349 import std.algorithm : canFind; 350 import std.string : format; 351 352 auto example = T.init; 353 auto accessorName = accessor(name); 354 auto inputName = accessorName; 355 enum needToDupField = needToDup!T; 356 enum visibility = getVisibility!(Write, __traits(getAttributes, example)); 357 358 uint attributes = defaultFunctionAttributes & 359 filterAttributes!T(isStatic, isUnsafe, FilterMode.Writer) & 360 inferAssignAttributes!T & 361 inferAttributes!(T, "__postblit") & 362 inferAttributes!(T, "__dtor"); 363 364 string precondition = ``; 365 366 if (auto checks = generateChecksForAttributes!(T, StdMetaFilter!(IsConditionAttribute, Attributes)) 367 (inputName, " in precondition of @Write")) 368 { 369 precondition = format!` in { import std.format : format; import std.array : empty; %s } body`(checks); 370 attributes &= ~FunctionAttribute.nogc; 371 attributes &= ~FunctionAttribute.nothrow_; 372 // format() is neither pure nor safe 373 if (checks.canFind("format")) 374 { 375 attributes &= ~FunctionAttribute.pure_; 376 attributes &= ~FunctionAttribute.safe; 377 } 378 } 379 380 auto attributesString = generateAttributeString(attributes); 381 auto modifiers = getModifiers(isStatic); 382 383 string accessorBody = format!`this.%s = %s%s; `(name, inputName, needToDupField ? ".dup" : ""); 384 385 if (synchronize) 386 { 387 accessorBody = format!`synchronized (this) { %s} `(accessorBody); 388 } 389 390 string result = format("%s%s final @property void %s(typeof(%s) %s)%s%s { %s}", 391 visibility, modifiers, accessorName, fieldCode, inputName, 392 attributesString, precondition, accessorBody); 393 394 static if (is(T : Nullable!Arg, Arg)) 395 { 396 result ~= format("%s%s final @property void %s(typeof(%s.get) %s)%s%s { %s}", 397 visibility, modifiers, accessorName, fieldCode, inputName, 398 attributesString, precondition, accessorBody); 399 } 400 return result; 401 } 402 403 @("generates writers as expected") 404 @nogc nothrow pure @safe unittest 405 { 406 static assert(GenerateWriter!int("foo", "integerValue", true, false, false) == 407 "public static final @property void foo(typeof(integerValue) foo) " ~ 408 "@nogc nothrow @safe { this.foo = foo; }"); 409 static assert(GenerateWriter!string("foo", "stringValue", true, false, false) == 410 "public static final @property void foo(typeof(stringValue) foo) " ~ 411 "@nogc nothrow @safe { this.foo = foo; }"); 412 static assert(GenerateWriter!(int[])("foo", "intArrayValue", true, false, false) == 413 "public static final @property void foo(typeof(intArrayValue) foo) " ~ 414 "nothrow @safe { this.foo = foo.dup; }"); 415 } 416 417 @("generates same-type writer for Nullable") 418 pure @safe unittest 419 { 420 import std.typecons : Nullable, nullable; 421 import unit_threaded.should : shouldEqual; 422 423 struct Struct 424 { 425 @Write 426 Nullable!int optional_; 427 428 mixin(GenerateFieldAccessors); 429 } 430 431 Struct value; 432 433 value.optional = 5; 434 435 value.optional_.shouldEqual(5.nullable); 436 } 437 438 private enum uint defaultFunctionAttributes = 439 FunctionAttribute.nogc | 440 FunctionAttribute.safe | 441 FunctionAttribute.nothrow_ | 442 FunctionAttribute.pure_; 443 444 private template inferAttributes(T, string M) 445 { 446 uint inferAttributes() 447 { 448 uint attributes = defaultFunctionAttributes; 449 450 static if (is(T == struct)) 451 { 452 static if (hasMember!(T, M)) 453 { 454 attributes &= functionAttributes!(__traits(getMember, T, M)); 455 } 456 else 457 { 458 foreach (field; Fields!T) 459 { 460 attributes &= inferAttributes!(field, M); 461 } 462 } 463 } 464 return attributes; 465 } 466 } 467 468 private template inferAssignAttributes(T) 469 { 470 uint inferAssignAttributes() 471 { 472 uint attributes = defaultFunctionAttributes; 473 474 static if (is(T == struct)) 475 { 476 static if (hasMember!(T, "opAssign")) 477 { 478 foreach (o; __traits(getOverloads, T, "opAssign")) 479 { 480 alias params = Parameters!o; 481 static if (params.length == 1 && is(params[0] == T)) 482 { 483 attributes &= functionAttributes!o; 484 } 485 } 486 } 487 else 488 { 489 foreach (field; Fields!T) 490 { 491 attributes &= inferAssignAttributes!field; 492 } 493 } 494 } 495 return attributes; 496 } 497 } 498 499 private string generateAttributeString(uint attributes) 500 { 501 string attributesString; 502 503 if (attributes & FunctionAttribute.nogc) 504 { 505 attributesString ~= " @nogc"; 506 } 507 if (attributes & FunctionAttribute.nothrow_) 508 { 509 attributesString ~= " nothrow"; 510 } 511 if (attributes & FunctionAttribute.pure_) 512 { 513 attributesString ~= " pure"; 514 } 515 if (attributes & FunctionAttribute.safe) 516 { 517 attributesString ~= " @safe"; 518 } 519 520 return attributesString; 521 } 522 523 private string accessor(string name) @nogc nothrow pure @safe 524 { 525 import std.string : chomp, chompPrefix; 526 527 return name.chomp("_").chompPrefix("_"); 528 } 529 530 @("removes underlines from names") 531 @nogc nothrow pure @safe unittest 532 { 533 assert(accessor("foo_") == "foo"); 534 assert(accessor("_foo") == "foo"); 535 } 536 537 /** 538 * Returns a string with the value of the field "visibility" if the attributes 539 * include an UDA of type A. The default visibility is "public". 540 */ 541 template getVisibility(A, attributes...) 542 { 543 import std.string : format; 544 545 enum getVisibility = helper; 546 547 private static helper() 548 { 549 static if (!attributes.length) 550 { 551 return A.init.visibility; 552 } 553 else 554 { 555 foreach (i, uda; attributes) 556 { 557 static if (is(typeof(uda) == A)) 558 { 559 return uda.visibility; 560 } 561 else static if (is(uda == A)) 562 { 563 return A.init.visibility; 564 } 565 else static if (i + 1 == attributes.length) 566 { 567 return A.init.visibility; 568 } 569 } 570 } 571 } 572 } 573 574 @("applies visibility from the uda parameter") 575 @nogc nothrow pure @safe unittest 576 { 577 @Read("public") int publicInt; 578 @Read("package") int packageInt; 579 @Read("protected") int protectedInt; 580 @Read("private") int privateInt; 581 @Read int defaultVisibleInt; 582 @Read @Write("protected") int publicReadableProtectedWritableInt; 583 584 static assert(getVisibility!(Read, __traits(getAttributes, publicInt)) == "public"); 585 static assert(getVisibility!(Read, __traits(getAttributes, packageInt)) == "package"); 586 static assert(getVisibility!(Read, __traits(getAttributes, protectedInt)) == "protected"); 587 static assert(getVisibility!(Read, __traits(getAttributes, privateInt)) == "private"); 588 static assert(getVisibility!(Read, __traits(getAttributes, defaultVisibleInt)) == "public"); 589 static assert(getVisibility!(Read, __traits(getAttributes, publicReadableProtectedWritableInt)) == "public"); 590 static assert(getVisibility!(Write, __traits(getAttributes, publicReadableProtectedWritableInt)) == "protected"); 591 } 592 593 @("creates accessors for flags") 594 nothrow pure @safe unittest 595 { 596 import std.typecons : Flag, No, Yes; 597 598 class Test 599 { 600 @Read 601 @Write 602 public Flag!"someFlag" test_ = Yes.someFlag; 603 604 mixin(GenerateFieldAccessors); 605 } 606 607 with (new Test) 608 { 609 assert(test == Yes.someFlag); 610 611 test = No.someFlag; 612 613 assert(test == No.someFlag); 614 615 static assert(is(typeof(test) == Flag!"someFlag")); 616 } 617 } 618 619 @("creates accessors for nullables") 620 nothrow pure @safe unittest 621 { 622 import std.typecons : Nullable; 623 624 class Test 625 { 626 @Read @Write 627 public Nullable!string test_ = Nullable!string("X"); 628 629 mixin(GenerateFieldAccessors); 630 } 631 632 with (new Test) 633 { 634 assert(!test.isNull); 635 assert(test.get == "X"); 636 637 static assert(is(typeof(test) == Nullable!string)); 638 } 639 } 640 641 @("does not break with const Nullable accessor") 642 nothrow pure @safe unittest 643 { 644 import std.typecons : Nullable; 645 646 class Test 647 { 648 @Read 649 private const Nullable!string test_; 650 651 mixin(GenerateFieldAccessors); 652 } 653 654 with (new Test) 655 { 656 assert(test.isNull); 657 } 658 } 659 660 @("creates non-const reader") 661 nothrow pure @safe unittest 662 { 663 class Test 664 { 665 @Read 666 int i_; 667 668 mixin(GenerateFieldAccessors); 669 } 670 671 auto mutableObject = new Test; 672 const constObject = mutableObject; 673 674 mutableObject.i_ = 42; 675 676 assert(mutableObject.i == 42); 677 678 static assert(is(typeof(mutableObject.i) == int)); 679 static assert(is(typeof(constObject.i) == const(int))); 680 } 681 682 @("creates ref reader") 683 nothrow pure @safe unittest 684 { 685 class Test 686 { 687 @RefRead 688 int i_; 689 690 mixin(GenerateFieldAccessors); 691 } 692 693 auto mutableTestObject = new Test; 694 695 mutableTestObject.i = 42; 696 697 assert(mutableTestObject.i == 42); 698 static assert(is(typeof(mutableTestObject.i) == int)); 699 } 700 701 @("creates writer") 702 nothrow pure @safe unittest 703 { 704 class Test 705 { 706 @Read @Write 707 private int i_; 708 709 mixin(GenerateFieldAccessors); 710 } 711 712 auto mutableTestObject = new Test; 713 mutableTestObject.i = 42; 714 715 assert(mutableTestObject.i == 42); 716 static assert(!__traits(compiles, mutableTestObject.i += 1)); 717 static assert(is(typeof(mutableTestObject.i) == int)); 718 } 719 720 @("checks whether hasUDA can be used for each member") 721 nothrow pure @safe unittest 722 { 723 class Test 724 { 725 alias Z = int; 726 727 @Read @Write 728 private int i_; 729 730 mixin(GenerateFieldAccessors); 731 } 732 733 auto mutableTestObject = new Test; 734 mutableTestObject.i = 42; 735 736 assert(mutableTestObject.i == 42); 737 static assert(!__traits(compiles, mutableTestObject.i += 1)); 738 } 739 740 @("returns non const for PODs and structs.") 741 nothrow pure @safe unittest 742 { 743 import std.algorithm : map, sort; 744 import std.array : array; 745 746 class C 747 { 748 @Read 749 string s_; 750 751 mixin(GenerateFieldAccessors); 752 } 753 754 C[] a = null; 755 756 static assert(__traits(compiles, a.map!(c => c.s).array.sort())); 757 } 758 759 @("functions with strings") 760 nothrow pure @safe unittest 761 { 762 class C 763 { 764 @Read @Write 765 string s_; 766 767 mixin(GenerateFieldAccessors); 768 } 769 770 with (new C) 771 { 772 s = "foo"; 773 assert(s == "foo"); 774 static assert(is(typeof(s) == string)); 775 } 776 } 777 778 @("supports user-defined accessors") 779 nothrow pure @safe unittest 780 { 781 class C 782 { 783 this() 784 { 785 str_ = "foo"; 786 } 787 788 @RefRead 789 private string str_; 790 791 public @property const(string) str() const 792 { 793 return this.str_.dup; 794 } 795 796 mixin(GenerateFieldAccessors); 797 } 798 799 with (new C) 800 { 801 str = "bar"; 802 } 803 } 804 805 @("creates accessor for locally defined types") 806 @system unittest 807 { 808 class X 809 { 810 } 811 812 class Test 813 { 814 @Read 815 public X x_; 816 817 mixin(GenerateFieldAccessors); 818 } 819 820 with (new Test) 821 { 822 x_ = new X; 823 824 assert(x == x_); 825 static assert(is(typeof(x) == X)); 826 } 827 } 828 829 @("creates const reader for simple structs") 830 nothrow pure @safe unittest 831 { 832 class Test 833 { 834 struct S 835 { 836 int i; 837 } 838 839 @Read 840 S s_; 841 842 mixin(GenerateFieldAccessors); 843 } 844 845 auto mutableObject = new Test; 846 const constObject = mutableObject; 847 848 mutableObject.s_.i = 42; 849 850 assert(constObject.s.i == 42); 851 852 static assert(is(typeof(mutableObject.s) == Test.S)); 853 static assert(is(typeof(constObject.s) == const(Test.S))); 854 } 855 856 @("returns copies when reading structs") 857 nothrow pure @safe unittest 858 { 859 class Test 860 { 861 struct S 862 { 863 int i; 864 } 865 866 @Read 867 S s_; 868 869 mixin(GenerateFieldAccessors); 870 } 871 872 auto mutableObject = new Test; 873 874 mutableObject.s.i = 42; 875 876 assert(mutableObject.s.i == int.init); 877 } 878 879 @("works with const arrays") 880 nothrow pure @safe unittest 881 { 882 class X 883 { 884 } 885 886 class C 887 { 888 @Read 889 private const(X)[] foo_; 890 891 mixin(GenerateFieldAccessors); 892 } 893 894 auto x = new X; 895 896 with (new C) 897 { 898 foo_ = [x]; 899 900 auto y = foo; 901 902 static assert(is(typeof(y) == const(X)[])); 903 static assert(is(typeof(foo) == const(X)[])); 904 } 905 } 906 907 @("has correct type of int") 908 nothrow pure @safe unittest 909 { 910 class C 911 { 912 @Read 913 private int foo_; 914 915 mixin(GenerateFieldAccessors); 916 } 917 918 with (new C) 919 { 920 static assert(is(typeof(foo) == int)); 921 } 922 } 923 924 @("works under inheritance (https://github.com/funkwerk/accessors/issues/5)") 925 @nogc nothrow pure @safe unittest 926 { 927 class A 928 { 929 @Read 930 string foo_; 931 932 mixin(GenerateFieldAccessors); 933 } 934 935 class B : A 936 { 937 @Read 938 string bar_; 939 940 mixin(GenerateFieldAccessors); 941 } 942 } 943 944 @("transfers struct attributes") 945 @nogc nothrow pure @safe unittest 946 { 947 struct S 948 { 949 this(this) 950 { 951 } 952 953 void opAssign(S s) 954 { 955 } 956 } 957 958 class A 959 { 960 @Read 961 S[] foo_; 962 963 @ConstRead 964 S bar_; 965 966 @Write 967 S baz_; 968 969 mixin(GenerateFieldAccessors); 970 } 971 } 972 973 @("returns array with mutable elements when reading") 974 nothrow pure @safe unittest 975 { 976 struct Field 977 { 978 } 979 980 struct S 981 { 982 @Read 983 Field[] foo_; 984 985 mixin(GenerateFieldAccessors); 986 } 987 988 with (S()) 989 { 990 Field[] arr = foo; 991 } 992 } 993 994 @("generates safe static properties for static members") 995 @safe unittest 996 { 997 class MyStaticTest 998 { 999 @Read 1000 static int stuff_ = 8; 1001 1002 mixin(GenerateFieldAccessors); 1003 } 1004 1005 assert(MyStaticTest.stuff == 8); 1006 } 1007 1008 @safe unittest 1009 { 1010 struct S 1011 { 1012 @Read @Write 1013 static int foo_ = 8; 1014 1015 @RefRead 1016 static int bar_ = 6; 1017 1018 mixin(GenerateFieldAccessors); 1019 } 1020 1021 assert(S.foo == 8); 1022 static assert(is(typeof({ S.foo = 8; }))); 1023 assert(S.bar == 6); 1024 } 1025 1026 @("does not set @safe on accessors for static __gshared members") 1027 unittest 1028 { 1029 class Test 1030 { 1031 @Read 1032 __gshared int stuff_ = 8; 1033 1034 mixin(GenerateFieldAccessors); 1035 } 1036 1037 assert(Test.stuff == 8); 1038 } 1039 1040 @("does not set inout on accessors for static fields") 1041 unittest 1042 { 1043 class Test 1044 { 1045 @Read 1046 __gshared Object[] stuff_; 1047 1048 mixin(GenerateFieldAccessors); 1049 } 1050 } 1051 1052 unittest 1053 { 1054 struct Thing 1055 { 1056 @Read 1057 private int[] content_; 1058 1059 mixin(GenerateFieldAccessors); 1060 } 1061 1062 class User 1063 { 1064 void helper(const int[]) 1065 { 1066 } 1067 1068 void doer(const Thing thing) 1069 { 1070 helper(thing.content); 1071 } 1072 } 1073 } 1074 1075 @("correctly handles nullable array dupping") 1076 unittest 1077 { 1078 class Class 1079 { 1080 } 1081 1082 struct Thing 1083 { 1084 @Read 1085 private Class[] classes_; 1086 1087 mixin(GenerateFieldAccessors); 1088 } 1089 1090 const Thing thing; 1091 1092 assert(thing.classes.length == 0); 1093 } 1094 1095 @("generates invariant checks via precondition for writers") 1096 unittest 1097 { 1098 import boilerplate.conditions : AllNonNull; 1099 import core.exception : AssertError; 1100 import std.algorithm : canFind; 1101 import std.conv : to; 1102 import unit_threaded.should : shouldThrow; 1103 1104 class Thing 1105 { 1106 @Write @AllNonNull 1107 Object[] objects_; 1108 1109 this(Object[] objects) 1110 { 1111 this.objects_ = objects.dup; 1112 } 1113 1114 mixin(GenerateFieldAccessors); 1115 } 1116 1117 auto thing = new Thing([new Object]); 1118 1119 auto error = ({ thing.objects = [null]; })().shouldThrow!AssertError; 1120 1121 assert(error.to!string.canFind("in precondition")); 1122 } 1123 1124 @("generates out conditions for invariant tags on static accessors") 1125 unittest 1126 { 1127 import boilerplate.conditions : NonInit; 1128 import core.exception : AssertError; 1129 import unit_threaded.should : shouldThrow; 1130 1131 struct Struct 1132 { 1133 @Read 1134 @NonInit 1135 static int test_; 1136 1137 @ConstRead 1138 @NonInit 1139 static int test2_; 1140 1141 mixin(GenerateFieldAccessors); 1142 } 1143 1144 Struct.test.shouldThrow!AssertError; 1145 Struct.test2.shouldThrow!AssertError; 1146 }