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