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