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