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