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