├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dub.json ├── src └── accessors.d └── test ├── PersonId.d └── main.d /.gitignore: -------------------------------------------------------------------------------- 1 | *.[oa] 2 | .dub 3 | __test__*__ 4 | /accessors-test-library 5 | /accessors 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | os: 4 | - linux 5 | 6 | language: d 7 | 8 | d: 9 | - dmd-2.078.0 10 | - dmd-2.077.1 11 | - dmd-2.076.1 12 | - dmd-2.075.1 13 | 14 | env: 15 | matrix: 16 | - ARCH=x86_64 17 | 18 | script: 19 | - dub test -b unittest-cov --arch=$ARCH 20 | 21 | after_success: 22 | - bash <(curl -s https://codecov.io/bash) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # accessors 2 | 3 | [![Build Status](https://travis-ci.org/funkwerk/accessors.svg?branch=master)](https://travis-ci.org/funkwerk/accessors) 4 | [![License](https://img.shields.io/badge/license-BSL_1.0-blue.svg)](https://raw.githubusercontent.com/funkwerk/accessors/master/LICENSE) 5 | [![Dub Version](https://img.shields.io/dub/v/accessors.svg)](https://code.dlang.org/packages/accessors) 6 | [![codecov](https://codecov.io/gh/funkwerk/accessors/branch/master/graph/badge.svg)](https://codecov.io/gh/funkwerk/accessors) 7 | 8 | `accessors` module allows to generate getters and setters automatically. 9 | 10 | **Deprecation warning:** `accessors` has been succeeded by [boilerplate](https://github.com/funkwerk/boilerplate). 11 | 12 | ## Usage 13 | 14 | ```d 15 | import accessors; 16 | 17 | class WithAccessors 18 | { 19 | @Read @Write 20 | private int num_; 21 | 22 | mixin(GenerateFieldAccessors); 23 | } 24 | ``` 25 | 26 | `@Read` and `@Write` generate the following two methods: 27 | 28 | ```d 29 | public final @property auto num() inout @nogc nothrow pure @safe 30 | { 31 | return this.num_; 32 | } 33 | 34 | public final @property void num(int num) @nogc nothrow pure @safe 35 | { 36 | this.num_ = num; 37 | } 38 | ``` 39 | 40 | The accessors names are derived from the appropriate member variables by 41 | removing an underscore from the beginning or the end of the variable name. 42 | 43 | ### Available user defined attributes 44 | 45 | * `@Read` 46 | * `@ConstRead` 47 | * `@Write` 48 | 49 | As you can see there are multiple attributes that can be used to generate 50 | getters and only one for the setters. The getters differ by the return 51 | type. `@Read` returns an `inout` value, `@ConstRead` - a `const` value. 52 | 53 | ### Accessor visibility 54 | 55 | Visibility of the generated accessors is by default `public`, but it can be 56 | changed. In order to achieve this, you have to pass `public`, `protected`, 57 | `private` or `package` as argument to the attribute: 58 | 59 | ```d 60 | import accessors; 61 | 62 | class WithAccessors 63 | { 64 | @Read("public") @Write("protected") 65 | private int num_; 66 | 67 | mixin(GenerateFieldAccessors); 68 | } 69 | ``` 70 | 71 | ## Example 72 | 73 | ```d 74 | import accessors; 75 | import std.stdio; 76 | 77 | class Person 78 | { 79 | @Read @Write 80 | private uint age_; 81 | 82 | @ConstRead 83 | private string name_; 84 | 85 | this(in string name, in uint age = 0) 86 | { 87 | this.name_ = name; 88 | this.age_ = age; 89 | } 90 | 91 | mixin(GenerateFieldAccessors); 92 | } 93 | 94 | void main() 95 | { 96 | auto person = new Person("Saul Kripke"); 97 | 98 | person.age = 57; 99 | 100 | writeln(person.name, ": ", person.age); 101 | } 102 | ``` 103 | 104 | ## Bugs 105 | 106 | If you experience compile-time problems, open an 107 | [issue](https://github.com/funkwerk/accessors/issues) with the 108 | information about the type of the member variable fails. 109 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "accessors", 3 | "description": "D getter/setter generator", 4 | "license": "BSL-1.0", 5 | "authors": [ 6 | "Eugene Wissner" 7 | ], 8 | 9 | "targetType": "library", 10 | 11 | "configurations": [ 12 | { 13 | "name": "library" 14 | }, 15 | { 16 | "name": "unittest", 17 | "targetType": "executable", 18 | "sourcePaths": ["test"], 19 | "mainSourceFile": "test/main.d" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/accessors.d: -------------------------------------------------------------------------------- 1 | module accessors; 2 | 3 | import std.traits; 4 | 5 | struct Read 6 | { 7 | string visibility = "public"; 8 | } 9 | 10 | // Deprecated! See below. 11 | // RefRead can not check invariants on change, so there's no point. 12 | // ref property functions where the value being returned is a field of the class 13 | // are entirely equivalent to public fields. 14 | struct RefRead 15 | { 16 | string visibility = "public"; 17 | } 18 | 19 | struct ConstRead 20 | { 21 | string visibility = "public"; 22 | } 23 | 24 | struct Write 25 | { 26 | string visibility = "public"; 27 | } 28 | 29 | immutable string GenerateFieldAccessors = ` 30 | mixin GenerateFieldAccessorMethods; 31 | mixin(GenerateFieldAccessorMethodsImpl); 32 | `; 33 | 34 | mixin template GenerateFieldAccessorMethods() 35 | { 36 | import std.meta : Alias, Filter; 37 | 38 | private enum bool isNotThis(string T) = T != "this"; 39 | 40 | static enum GenerateFieldAccessorMethodsImpl() 41 | { 42 | import std.traits : hasUDA; 43 | 44 | string result = ""; 45 | 46 | foreach (name; Filter!(isNotThis, __traits(derivedMembers, typeof(this)))) 47 | { 48 | enum string fieldCode = `Alias!(__traits(getMember, typeof(this), "` ~ name ~ `"))`; 49 | mixin("alias field = " ~ fieldCode ~ ";"); 50 | 51 | static if (__traits(compiles, hasUDA!(field, Read))) 52 | { 53 | static if (hasUDA!(field, Read)) 54 | { 55 | enum string readerDecl = GenerateReader!(name, field); 56 | debug (accessors) pragma(msg, readerDecl); 57 | result ~= readerDecl; 58 | } 59 | 60 | static if (hasUDA!(field, RefRead)) 61 | { 62 | result ~= `pragma(msg, "Deprecation! RefRead on ` ~ typeof(this).stringof ~ `.` ~ name 63 | ~ ` makes a private field effectively public, defeating the point.");`; 64 | 65 | enum string refReaderDecl = GenerateRefReader!(name, field); 66 | debug (accessors) pragma(msg, refReaderDecl); 67 | result ~= refReaderDecl; 68 | } 69 | 70 | static if (hasUDA!(field, ConstRead)) 71 | { 72 | enum string constReaderDecl = GenerateConstReader!(name, field); 73 | debug (accessors) pragma(msg, constReaderDecl); 74 | result ~= constReaderDecl; 75 | } 76 | 77 | static if (hasUDA!(field, Write)) 78 | { 79 | enum string writerDecl = GenerateWriter!(name, field, fieldCode); 80 | debug (accessors) pragma(msg, writerDecl); 81 | result ~= writerDecl; 82 | } 83 | } 84 | } 85 | 86 | return result; 87 | } 88 | } 89 | 90 | template isStatic(alias field) 91 | { 92 | enum isStatic = helper; 93 | 94 | static enum helper() 95 | { 96 | return is(typeof({ auto f = field; })); 97 | } 98 | } 99 | 100 | template getModifiers(alias field) 101 | { 102 | enum getModifiers = helper; 103 | 104 | static enum helper() 105 | { 106 | static if (isStatic!field) 107 | { 108 | return " static"; 109 | } 110 | else 111 | { 112 | return ""; 113 | } 114 | } 115 | } 116 | 117 | template filterAttributes(alias field) 118 | { 119 | enum filterAttributes = helper; 120 | 121 | static enum helper() 122 | { 123 | uint attributes = uint.max; 124 | static if (needToDup!(typeof(field))) 125 | { 126 | attributes &= ~FunctionAttribute.nogc; 127 | } 128 | static if (isStatic!field) 129 | { 130 | attributes &= ~FunctionAttribute.pure_; 131 | } 132 | return attributes; 133 | } 134 | } 135 | 136 | template GenerateReader(string name, alias field) 137 | { 138 | enum GenerateReader = helper; 139 | 140 | static enum helper() 141 | { 142 | import std.string : format; 143 | 144 | enum visibility = getVisibility!(field, Read); 145 | enum accessorName = accessor(name); 146 | enum needToDup = needToDup!(typeof(field)); 147 | 148 | enum uint attributes = inferAttributes!(typeof(field), "__postblit") & 149 | filterAttributes!field; 150 | 151 | string attributesString = generateAttributeString!attributes; 152 | 153 | static if (needToDup) 154 | { 155 | return format("%s%s final @property auto %s() inout %s" 156 | ~ "{ return typeof(this.%s).init ~ this.%s; }", 157 | visibility, getModifiers!field, accessorName, attributesString, name, name); 158 | } 159 | else static if (isStatic!field) 160 | { 161 | return format("%s static final @property auto %s() %s{ return this.%s; }", 162 | visibility, accessorName, attributesString, name); 163 | } 164 | else 165 | { 166 | return format("%s final @property auto %s() inout %s{ return this.%s; }", 167 | visibility, accessorName, attributesString, name); 168 | } 169 | } 170 | } 171 | 172 | /// 173 | @nogc nothrow pure @safe unittest 174 | { 175 | int integerValue; 176 | string stringValue; 177 | int[] intArrayValue; 178 | 179 | static assert(GenerateReader!("foo", integerValue) == 180 | "public static final @property auto foo() " ~ 181 | "@nogc nothrow @safe { return this.foo; }"); 182 | static assert(GenerateReader!("foo", stringValue) == 183 | "public static final @property auto foo() " ~ 184 | "@nogc nothrow @safe { return this.foo; }"); 185 | static assert(GenerateReader!("foo", intArrayValue) == 186 | "public static final @property auto foo() inout nothrow @safe " 187 | ~ "{ return typeof(this.foo).init ~ this.foo; }"); 188 | } 189 | 190 | template GenerateRefReader(string name, alias field) 191 | { 192 | enum GenerateRefReader = helper; 193 | 194 | static enum helper() 195 | { 196 | import std.string : format; 197 | 198 | enum visibility = getVisibility!(field, RefRead); 199 | enum accessorName = accessor(name); 200 | 201 | static if (isStatic!field) 202 | { 203 | enum string attributesString = "@nogc nothrow @safe "; 204 | } 205 | else 206 | { 207 | enum string attributesString = "@nogc nothrow pure @safe "; 208 | } 209 | 210 | return format("%s%s final @property ref auto %s() " ~ 211 | "%s{ return this.%s; }", 212 | visibility, getModifiers!field, accessorName, attributesString, name); 213 | } 214 | } 215 | 216 | /// 217 | @nogc nothrow pure @safe unittest 218 | { 219 | int integerValue; 220 | string stringValue; 221 | int[] intArrayValue; 222 | 223 | static assert(GenerateRefReader!("foo", integerValue) == 224 | "public static final @property ref auto foo() " ~ 225 | "@nogc nothrow @safe { return this.foo; }"); 226 | static assert(GenerateRefReader!("foo", stringValue) == 227 | "public static final @property ref auto foo() " ~ 228 | "@nogc nothrow @safe { return this.foo; }"); 229 | static assert(GenerateRefReader!("foo", intArrayValue) == 230 | "public static final @property ref auto foo() " ~ 231 | "@nogc nothrow @safe { return this.foo; }"); 232 | } 233 | 234 | template GenerateConstReader(string name, alias field) 235 | { 236 | enum GenerateConstReader = helper; 237 | 238 | static enum helper() 239 | { 240 | import std.string : format; 241 | 242 | enum visibility = getVisibility!(field, RefRead); 243 | enum accessorName = accessor(name); 244 | 245 | alias attributes = inferAttributes!(typeof(field), "__postblit"); 246 | string attributesString = generateAttributeString!attributes; 247 | 248 | return format("%s final @property auto %s() const %s { return this.%s; }", 249 | visibility, accessorName, attributesString, name); 250 | } 251 | } 252 | 253 | template GenerateWriter(string name, alias field, string fieldCode) 254 | { 255 | enum GenerateWriter = helper; 256 | 257 | static enum helper() 258 | { 259 | import std.string : format; 260 | 261 | enum visibility = getVisibility!(field, Write); 262 | enum accessorName = accessor(name); 263 | enum inputName = accessorName; 264 | enum needToDup = needToDup!(typeof(field)); 265 | 266 | enum uint attributes = defaultFunctionAttributes & 267 | filterAttributes!field & 268 | inferAssignAttributes!(typeof(field)) & 269 | inferAttributes!(typeof(field), "__postblit") & 270 | inferAttributes!(typeof(field), "__dtor"); 271 | 272 | enum attributesString = generateAttributeString!attributes; 273 | 274 | return format("%s%s final @property void %s(typeof(%s) %s) %s{ this.%s = %s%s; }", 275 | visibility, getModifiers!field, accessorName, fieldCode, inputName, 276 | attributesString, name, inputName, needToDup ? ".dup" : ""); 277 | } 278 | } 279 | 280 | /// 281 | @nogc nothrow pure @safe unittest 282 | { 283 | int integerValue; 284 | string stringValue; 285 | int[] intArrayValue; 286 | 287 | static assert(GenerateWriter!("foo", integerValue, "integerValue") == 288 | "public static final @property void foo(typeof(integerValue) foo) " ~ 289 | "@nogc nothrow @safe { this.foo = foo; }"); 290 | static assert(GenerateWriter!("foo", stringValue, "stringValue") == 291 | "public static final @property void foo(typeof(stringValue) foo) " ~ 292 | "@nogc nothrow @safe { this.foo = foo; }"); 293 | static assert(GenerateWriter!("foo", intArrayValue, "intArrayValue") == 294 | "public static final @property void foo(typeof(intArrayValue) foo) " ~ 295 | "nothrow @safe { this.foo = foo.dup; }"); 296 | } 297 | 298 | private enum uint defaultFunctionAttributes = 299 | FunctionAttribute.nogc | 300 | FunctionAttribute.safe | 301 | FunctionAttribute.nothrow_ | 302 | FunctionAttribute.pure_; 303 | 304 | private template inferAttributes(T, string M) 305 | { 306 | enum uint inferAttributes() 307 | { 308 | uint attributes = defaultFunctionAttributes; 309 | 310 | static if (is(T == struct)) 311 | { 312 | static if (hasMember!(T, M)) 313 | { 314 | attributes &= functionAttributes!(__traits(getMember, T, M)); 315 | } 316 | else 317 | { 318 | foreach (field; Fields!T) 319 | { 320 | attributes &= inferAttributes!(field, M); 321 | } 322 | } 323 | } 324 | return attributes; 325 | } 326 | } 327 | 328 | private enum uint inferAssignAttributes(T)() 329 | { 330 | static if (hasElaborateAssign!T) 331 | { 332 | void testAttributes()() 333 | { 334 | auto testObject = T.init; 335 | testObject = T.init; 336 | } 337 | return functionAttributes!(testAttributes!()); 338 | } 339 | else 340 | { 341 | return defaultFunctionAttributes; 342 | } 343 | } 344 | 345 | // Issue 22: https://github.com/funkwerk/accessors/issues/22 346 | @nogc nothrow pure @safe unittest 347 | { 348 | static struct SysTime 349 | { 350 | void opAssign(SysTime) @safe pure nothrow 351 | { 352 | new int; 353 | } 354 | } 355 | static struct Nullable 356 | { 357 | SysTime t; 358 | void opAssign()(SysTime) 359 | { 360 | } 361 | } 362 | static assert((inferAssignAttributes!Nullable & FunctionAttribute.nogc) == 0); 363 | } 364 | 365 | private template generateAttributeString(uint attributes) 366 | { 367 | enum string generateAttributeString() 368 | { 369 | string attributesString; 370 | 371 | static if (attributes & FunctionAttribute.nogc) 372 | { 373 | attributesString ~= "@nogc "; 374 | } 375 | static if (attributes & FunctionAttribute.nothrow_) 376 | { 377 | attributesString ~= "nothrow "; 378 | } 379 | static if (attributes & FunctionAttribute.pure_) 380 | { 381 | attributesString ~= "pure "; 382 | } 383 | static if (attributes & FunctionAttribute.safe) 384 | { 385 | attributesString ~= "@safe "; 386 | } 387 | 388 | return attributesString; 389 | } 390 | } 391 | 392 | private enum needToDup(T) = isArray!T && !DeepConst!T; 393 | 394 | private enum DeepConst(T) = __traits(compiles, (const T x) { T y = x; }); 395 | 396 | @nogc nothrow pure @safe unittest 397 | { 398 | int integerField; 399 | int[] integerArrayField; 400 | string stringField; 401 | const int[] constIntegerArrayField; 402 | 403 | static assert(!needToDup!(typeof(integerField))); 404 | static assert(needToDup!(typeof(integerArrayField))); 405 | static assert(!needToDup!(typeof(stringField))); 406 | static assert(!needToDup!(typeof(constIntegerArrayField))); 407 | } 408 | 409 | static string accessor(string name) @nogc nothrow pure @safe 410 | { 411 | import std.string : chomp, chompPrefix; 412 | 413 | return name.chomp("_").chompPrefix("_"); 414 | } 415 | 416 | /// 417 | @nogc nothrow pure @safe unittest 418 | { 419 | assert(accessor("foo_") == "foo"); 420 | assert(accessor("_foo") == "foo"); 421 | } 422 | 423 | /** 424 | * Returns a string with the value of the field "visibility" if the field 425 | * is annotated with an UDA of type A. The default visibility is "public". 426 | */ 427 | template getVisibility(alias field, A) 428 | { 429 | import std.string : format; 430 | 431 | enum getVisibility = helper; 432 | 433 | static enum helper() 434 | { 435 | alias attributes = getUDAs!(field, A); 436 | 437 | static if (attributes.length == 0) 438 | { 439 | return A.init.visibility; 440 | } 441 | else 442 | { 443 | static assert(attributes.length == 1, 444 | format("%s should not have more than one attribute @%s", field.stringof, A.stringof)); 445 | 446 | static if (is(typeof(attributes[0]))) 447 | return attributes[0].visibility; 448 | else 449 | return A.init.visibility; 450 | } 451 | } 452 | } 453 | 454 | /// 455 | @nogc nothrow pure @safe unittest 456 | { 457 | @Read("public") int publicInt; 458 | @Read("package") int packageInt; 459 | @Read("protected") int protectedInt; 460 | @Read("private") int privateInt; 461 | @Read int defaultVisibleInt; 462 | @Read @Write("protected") int publicReadableProtectedWritableInt; 463 | 464 | static assert(getVisibility!(publicInt, Read) == "public"); 465 | static assert(getVisibility!(packageInt, Read) == "package"); 466 | static assert(getVisibility!(protectedInt, Read) == "protected"); 467 | static assert(getVisibility!(privateInt, Read) == "private"); 468 | static assert(getVisibility!(defaultVisibleInt, Read) == "public"); 469 | static assert(getVisibility!(publicReadableProtectedWritableInt, Read) == "public"); 470 | static assert(getVisibility!(publicReadableProtectedWritableInt, Write) == "protected"); 471 | } 472 | 473 | /// Creates accessors for flags. 474 | nothrow pure @safe unittest 475 | { 476 | import std.typecons : Flag, No, Yes; 477 | 478 | class Test 479 | { 480 | @Read 481 | @Write 482 | public Flag!"someFlag" test_ = Yes.someFlag; 483 | 484 | mixin(GenerateFieldAccessors); 485 | } 486 | 487 | with (new Test) 488 | { 489 | assert(test == Yes.someFlag); 490 | 491 | test = No.someFlag; 492 | 493 | assert(test == No.someFlag); 494 | 495 | static assert(is(typeof(test) == Flag!"someFlag")); 496 | } 497 | } 498 | 499 | /// Creates accessors for Nullables. 500 | nothrow pure @safe unittest 501 | { 502 | import std.typecons : Nullable; 503 | 504 | class Test 505 | { 506 | @Read @Write 507 | public Nullable!string test_ = Nullable!string("X"); 508 | 509 | mixin(GenerateFieldAccessors); 510 | } 511 | 512 | with (new Test) 513 | { 514 | assert(!test.isNull); 515 | assert(test.get == "X"); 516 | 517 | static assert(is(typeof(test) == Nullable!string)); 518 | } 519 | } 520 | 521 | /// Creates non-const reader. 522 | nothrow pure @safe unittest 523 | { 524 | class Test 525 | { 526 | @Read 527 | int i_; 528 | 529 | mixin(GenerateFieldAccessors); 530 | } 531 | 532 | auto mutableObject = new Test; 533 | const constObject = mutableObject; 534 | 535 | mutableObject.i_ = 42; 536 | 537 | assert(mutableObject.i == 42); 538 | 539 | static assert(is(typeof(mutableObject.i) == int)); 540 | static assert(is(typeof(constObject.i) == const(int))); 541 | } 542 | 543 | /// Creates ref reader. 544 | nothrow pure @safe unittest 545 | { 546 | class Test 547 | { 548 | @RefRead 549 | int i_; 550 | 551 | mixin(GenerateFieldAccessors); 552 | } 553 | 554 | auto mutableTestObject = new Test; 555 | 556 | mutableTestObject.i = 42; 557 | 558 | assert(mutableTestObject.i == 42); 559 | static assert(is(typeof(mutableTestObject.i) == int)); 560 | } 561 | 562 | /// Creates writer. 563 | nothrow pure @safe unittest 564 | { 565 | class Test 566 | { 567 | @Read @Write 568 | private int i_; 569 | 570 | mixin(GenerateFieldAccessors); 571 | } 572 | 573 | auto mutableTestObject = new Test; 574 | mutableTestObject.i = 42; 575 | 576 | assert(mutableTestObject.i == 42); 577 | static assert(!__traits(compiles, mutableTestObject.i += 1)); 578 | static assert(is(typeof(mutableTestObject.i) == int)); 579 | } 580 | 581 | /// Checks whether hasUDA can be used for each member. 582 | nothrow pure @safe unittest 583 | { 584 | class Test 585 | { 586 | alias Z = int; 587 | 588 | @Read @Write 589 | private int i_; 590 | 591 | mixin(GenerateFieldAccessors); 592 | } 593 | 594 | auto mutableTestObject = new Test; 595 | mutableTestObject.i = 42; 596 | 597 | assert(mutableTestObject.i == 42); 598 | static assert(!__traits(compiles, mutableTestObject.i += 1)); 599 | } 600 | 601 | /// Returns non const for PODs and structs. 602 | nothrow pure @safe unittest 603 | { 604 | import std.algorithm : map, sort; 605 | import std.array : array; 606 | 607 | class C 608 | { 609 | @Read 610 | string s_; 611 | 612 | mixin(GenerateFieldAccessors); 613 | } 614 | 615 | C[] a = null; 616 | 617 | static assert(__traits(compiles, a.map!(c => c.s).array.sort())); 618 | } 619 | 620 | /// Regression. 621 | nothrow pure @safe unittest 622 | { 623 | class C 624 | { 625 | @Read @Write 626 | string s_; 627 | 628 | mixin(GenerateFieldAccessors); 629 | } 630 | 631 | with (new C) 632 | { 633 | s = "foo"; 634 | assert(s == "foo"); 635 | static assert(is(typeof(s) == string)); 636 | } 637 | } 638 | 639 | /// Supports user-defined accessors. 640 | nothrow pure @safe unittest 641 | { 642 | class C 643 | { 644 | this() 645 | { 646 | str_ = "foo"; 647 | } 648 | 649 | @RefRead 650 | private string str_; 651 | 652 | public @property const(string) str() const 653 | { 654 | return this.str_.dup; 655 | } 656 | 657 | mixin(GenerateFieldAccessors); 658 | } 659 | 660 | with (new C) 661 | { 662 | str = "bar"; 663 | } 664 | } 665 | 666 | /// Creates accessor for locally defined types. 667 | @system unittest 668 | { 669 | class X 670 | { 671 | } 672 | 673 | class Test 674 | { 675 | @Read 676 | public X x_; 677 | 678 | mixin(GenerateFieldAccessors); 679 | } 680 | 681 | with (new Test) 682 | { 683 | x_ = new X; 684 | 685 | assert(x == x_); 686 | static assert(is(typeof(x) == X)); 687 | } 688 | } 689 | 690 | /// Creates const reader for simple structs. 691 | nothrow pure @safe unittest 692 | { 693 | class Test 694 | { 695 | struct S 696 | { 697 | int i; 698 | } 699 | 700 | @Read 701 | S s_; 702 | 703 | mixin(GenerateFieldAccessors); 704 | } 705 | 706 | auto mutableObject = new Test; 707 | const constObject = mutableObject; 708 | 709 | mutableObject.s_.i = 42; 710 | 711 | assert(constObject.s.i == 42); 712 | 713 | static assert(is(typeof(mutableObject.s) == Test.S)); 714 | static assert(is(typeof(constObject.s) == const(Test.S))); 715 | } 716 | 717 | /// Reader for structs return copies. 718 | nothrow pure @safe unittest 719 | { 720 | class Test 721 | { 722 | struct S 723 | { 724 | int i; 725 | } 726 | 727 | @Read 728 | S s_; 729 | 730 | mixin(GenerateFieldAccessors); 731 | } 732 | 733 | auto mutableObject = new Test; 734 | 735 | mutableObject.s.i = 42; 736 | 737 | assert(mutableObject.s.i == int.init); 738 | } 739 | 740 | /// Creates reader for const arrays. 741 | nothrow pure @safe unittest 742 | { 743 | class X 744 | { 745 | } 746 | 747 | class C 748 | { 749 | @Read 750 | private const(X)[] foo_; 751 | 752 | mixin(GenerateFieldAccessors); 753 | } 754 | 755 | auto x = new X; 756 | 757 | with (new C) 758 | { 759 | foo_ = [x]; 760 | 761 | auto y = foo; 762 | 763 | static assert(is(typeof(y) == const(X)[])); 764 | static assert(is(typeof(foo) == const(X)[])); 765 | } 766 | } 767 | 768 | /// Property has correct type. 769 | nothrow pure @safe unittest 770 | { 771 | class C 772 | { 773 | @Read 774 | private int foo_; 775 | 776 | mixin(GenerateFieldAccessors); 777 | } 778 | 779 | with (new C) 780 | { 781 | static assert(is(typeof(foo) == int)); 782 | } 783 | } 784 | 785 | /// Inheritance (https://github.com/funkwerk/accessors/issues/5). 786 | @nogc nothrow pure @safe unittest 787 | { 788 | class A 789 | { 790 | @Read 791 | string foo_; 792 | 793 | mixin(GenerateFieldAccessors); 794 | } 795 | 796 | class B : A 797 | { 798 | @Read 799 | string bar_; 800 | 801 | mixin(GenerateFieldAccessors); 802 | } 803 | } 804 | 805 | /// Transfers struct attributes. 806 | @nogc nothrow pure @safe unittest 807 | { 808 | struct S 809 | { 810 | this(this) 811 | { 812 | } 813 | 814 | void opAssign(S s) 815 | { 816 | } 817 | } 818 | 819 | class A 820 | { 821 | @Read 822 | S[] foo_; 823 | 824 | @ConstRead 825 | S bar_; 826 | 827 | @Write 828 | S baz_; 829 | 830 | mixin(GenerateFieldAccessors); 831 | } 832 | } 833 | 834 | /// @Read property returns array with mutable elements. 835 | nothrow pure @safe unittest 836 | { 837 | struct Field 838 | { 839 | } 840 | 841 | struct S 842 | { 843 | @Read 844 | Field[] foo_; 845 | 846 | mixin(GenerateFieldAccessors); 847 | } 848 | 849 | with (S()) 850 | { 851 | Field[] arr = foo; 852 | } 853 | } 854 | 855 | /// Static properties are generated for static members. 856 | unittest 857 | { 858 | class MyStaticTest 859 | { 860 | @Read 861 | static int stuff_ = 8; 862 | 863 | mixin(GenerateFieldAccessors); 864 | } 865 | 866 | assert(MyStaticTest.stuff == 8); 867 | } 868 | 869 | unittest 870 | { 871 | struct S 872 | { 873 | @Read @Write 874 | static int foo_ = 8; 875 | 876 | @RefRead 877 | static int bar_ = 6; 878 | 879 | mixin(GenerateFieldAccessors); 880 | } 881 | 882 | assert(S.foo == 8); 883 | static assert(is(typeof({ S.foo = 8; }))); 884 | assert(S.bar == 6); 885 | } 886 | 887 | @nogc nothrow pure @safe unittest 888 | { 889 | struct Thing 890 | { 891 | @Read 892 | private int[] content_; 893 | 894 | mixin(GenerateFieldAccessors); 895 | } 896 | 897 | class User 898 | { 899 | void helper(const int[] content) 900 | { 901 | } 902 | 903 | void doer(const Thing thing) 904 | { 905 | helper(thing.content); 906 | } 907 | } 908 | } 909 | 910 | nothrow pure @safe unittest 911 | { 912 | class Class 913 | { 914 | } 915 | 916 | struct Thing 917 | { 918 | @Read 919 | private Class[] classes_; 920 | 921 | mixin(GenerateFieldAccessors); 922 | } 923 | 924 | Thing thing; 925 | assert(thing.classes.length == 0); 926 | } 927 | -------------------------------------------------------------------------------- /test/PersonId.d: -------------------------------------------------------------------------------- 1 | module PersonId; 2 | 3 | class PersonId 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /test/main.d: -------------------------------------------------------------------------------- 1 | import accessors; 2 | 3 | void main() 4 | { 5 | } 6 | 7 | // Issue #11: https://github.com/funkwerk/accessors/issues/11 8 | @nogc nothrow pure @safe unittest 9 | { 10 | import PersonId : AnotherPersonId = PersonId; 11 | 12 | class PersonId 13 | { 14 | } 15 | 16 | class Foo 17 | { 18 | @ConstRead 19 | private AnotherPersonId anotherPersonId_; 20 | 21 | @Read 22 | private AnotherPersonId[] personIdArray_; 23 | 24 | mixin(GenerateFieldAccessors); 25 | } 26 | } 27 | --------------------------------------------------------------------------------