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