1 module boilerplate.constructor; 2 3 version(unittest) 4 { 5 import unit_threaded.should; 6 } 7 8 /++ 9 GenerateThis is a mixin string that automatically generates a this() function, customizable with UDA. 10 +/ 11 public enum string GenerateThis = ` 12 import boilerplate.constructor: GenerateThisTemplate, getUDADefaultOrNothing; 13 import boilerplate.util: udaIndex; 14 import std.meta : AliasSeq; 15 mixin GenerateThisTemplate; 16 mixin(typeof(this).generateThisImpl()); 17 `; 18 19 /// 20 @("creates a constructor") 21 unittest 22 { 23 class Class 24 { 25 int field; 26 27 mixin(GenerateThis); 28 } 29 30 auto obj = new Class(5); 31 32 obj.field.shouldEqual(5); 33 } 34 35 /// 36 @("calls the super constructor if it exists") 37 unittest 38 { 39 class Class 40 { 41 int field; 42 43 mixin(GenerateThis); 44 } 45 46 class Child : Class 47 { 48 int field2; 49 50 mixin(GenerateThis); 51 } 52 53 auto obj = new Child(5, 8); 54 55 obj.field.shouldEqual(5); 56 obj.field2.shouldEqual(8); 57 } 58 59 /// 60 @("separates fields from methods") 61 unittest 62 { 63 class Class 64 { 65 int field; 66 67 void method() { } 68 69 mixin(GenerateThis); 70 } 71 72 auto obj = new Class(5); 73 74 obj.field.shouldEqual(5); 75 } 76 77 /// 78 @("dups arrays") 79 unittest 80 { 81 class Class 82 { 83 int[] array; 84 85 mixin(GenerateThis); 86 } 87 88 auto array = [2, 3, 4]; 89 auto obj = new Class(array); 90 91 array[0] = 1; 92 obj.array[0].shouldEqual(2); 93 } 94 95 /// 96 @("dups arrays hidden behind Nullable") 97 unittest 98 { 99 import std.typecons : Nullable, nullable; 100 101 class Class 102 { 103 Nullable!(int[]) array; 104 105 mixin(GenerateThis); 106 } 107 108 auto array = [2, 3, 4]; 109 auto obj = new Class(array.nullable); 110 111 array[0] = 1; 112 obj.array.get[0].shouldEqual(2); 113 114 obj = new Class(Nullable!(int[]).init); 115 obj.array.isNull.shouldBeTrue; 116 } 117 118 /// 119 @("uses default value for default constructor parameter") 120 unittest 121 { 122 class Class 123 { 124 @(This.Default!5) 125 int value = 5; 126 127 mixin(GenerateThis); 128 } 129 130 auto obj1 = new Class(); 131 132 obj1.value.shouldEqual(5); 133 134 auto obj2 = new Class(6); 135 136 obj2.value.shouldEqual(6); 137 } 138 139 /// 140 @("creates no constructor for an empty struct") 141 unittest 142 { 143 struct Struct 144 { 145 mixin(GenerateThis); 146 } 147 148 auto strct = Struct(); 149 } 150 151 /// 152 @("properly generates new default values on each call") 153 unittest 154 { 155 import std.conv : to; 156 157 class Class 158 { 159 @(This.Default!(() => new Object)) 160 Object obj; 161 162 mixin(GenerateThis); 163 } 164 165 auto obj1 = new Class(); 166 auto obj2 = new Class(); 167 168 (cast(void*) obj1.obj).shouldNotEqual(cast(void*) obj2.obj); 169 } 170 171 /// 172 @("establishes the parent-child parameter order: parent explicit, child explicit, child implicit, parent implicit.") 173 unittest 174 { 175 class Parent 176 { 177 int field1; 178 179 @(This.Default!2) 180 int field2 = 2; 181 182 mixin(GenerateThis); 183 } 184 185 class Child : Parent 186 { 187 int field3; 188 189 @(This.Default!4) 190 int field4 = 4; 191 192 mixin(GenerateThis); 193 } 194 195 auto obj = new Child(1, 2, 3, 4); 196 197 obj.field1.shouldEqual(1); 198 obj.field3.shouldEqual(2); 199 obj.field4.shouldEqual(3); 200 obj.field2.shouldEqual(4); 201 } 202 203 /// 204 @("disregards static fields") 205 unittest 206 { 207 class Class 208 { 209 static int field1; 210 int field2; 211 212 mixin(GenerateThis); 213 } 214 215 auto obj = new Class(5); 216 217 obj.field1.shouldEqual(0); 218 obj.field2.shouldEqual(5); 219 } 220 221 /// 222 @("can initialize with immutable arrays") 223 unittest 224 { 225 class Class 226 { 227 immutable(Object)[] array; 228 229 mixin(GenerateThis); 230 } 231 } 232 233 /// 234 @("can define scope for constructor") 235 unittest 236 { 237 @(This.Private) 238 class PrivateClass 239 { 240 mixin(GenerateThis); 241 } 242 243 @(This.Protected) 244 class ProtectedClass 245 { 246 mixin(GenerateThis); 247 } 248 249 @(This.Package) 250 class PackageClass 251 { 252 mixin(GenerateThis); 253 } 254 255 class PublicClass 256 { 257 mixin(GenerateThis); 258 } 259 260 static assert(__traits(getProtection, PrivateClass.__ctor) == "private"); 261 static assert(__traits(getProtection, ProtectedClass.__ctor) == "protected"); 262 static assert(__traits(getProtection, PackageClass.__ctor) == "package"); 263 static assert(__traits(getProtection, PublicClass.__ctor) == "public"); 264 } 265 266 /// 267 @("can use default tag with new") 268 unittest 269 { 270 class Class 271 { 272 @(This.Default!(() => new Object)) 273 Object foo; 274 275 mixin(GenerateThis); 276 } 277 278 ((new Class).foo !is null).shouldBeTrue; 279 } 280 281 /// 282 @("can exclude fields from constructor") 283 unittest 284 { 285 class Class 286 { 287 @(This.Exclude) 288 int i = 5; 289 290 mixin(GenerateThis); 291 } 292 293 (new Class).i.shouldEqual(5); 294 } 295 296 /// 297 @("marks duppy parameters as const when this does not prevent dupping") 298 unittest 299 { 300 301 struct Struct 302 { 303 } 304 305 class Class 306 { 307 Struct[] values_; 308 309 mixin(GenerateThis); 310 } 311 312 const Struct[] constValues; 313 auto obj = new Class(constValues); 314 } 315 316 /// 317 @("does not include property functions in constructor list") 318 unittest 319 { 320 class Class 321 { 322 int a; 323 324 @property int foo() const 325 { 326 return 0; 327 } 328 329 mixin(GenerateThis); 330 } 331 332 static assert(__traits(compiles, new Class(0))); 333 static assert(!__traits(compiles, new Class(0, 0))); 334 } 335 336 @("declares @nogc on non-dupping constructors") 337 @nogc unittest 338 { 339 struct Struct 340 { 341 int a; 342 343 mixin(GenerateThis); 344 } 345 346 auto str = Struct(5); 347 } 348 349 /// 350 @("can initialize fields using init value") 351 unittest 352 { 353 class Class 354 { 355 @(This.Init!5) 356 int field1; 357 358 @(This.Init!(() => 8)) 359 int field2; 360 361 mixin(GenerateThis); 362 } 363 364 auto obj = new Class; 365 366 obj.field1.shouldEqual(5); 367 obj.field2.shouldEqual(8); 368 } 369 370 /// 371 @("can initialize fields using init value, with lambda that accesses previous value") 372 unittest 373 { 374 class Class 375 { 376 int field1; 377 378 @(This.Init!(self => self.field1 + 5)) 379 int field2; 380 381 mixin(GenerateThis); 382 } 383 384 auto obj = new Class(5); 385 386 obj.field1.shouldEqual(5); 387 obj.field2.shouldEqual(10); 388 } 389 390 /// 391 @("still supports deprecated default attribute") 392 unittest 393 { 394 class Class 395 { 396 @Default!5 397 int value = 5; 398 399 mixin(GenerateThis); 400 } 401 402 auto obj1 = new Class(); 403 404 obj1.value.shouldEqual(5); 405 406 auto obj2 = new Class(6); 407 408 obj2.value.shouldEqual(6); 409 } 410 import std.string : format; 411 412 enum GetSuperTypeAsString_(size_t Index) = format!`typeof(super).GeneratedConstructorTypes_[%s]`(Index); 413 414 enum GetMemberTypeAsString_(string Member) = format!`typeof(this.%s)`(Member); 415 416 enum SuperDefault_(size_t Index) = format!`typeof(super).GeneratedConstructorDefaults_[%s]`(Index); 417 418 enum MemberDefault_(string Member) = 419 format!`getUDADefaultOrNothing!(__traits(getAttributes, this.%s))`(Member); 420 421 mixin template GenerateThisTemplate() 422 { 423 private static generateThisImpl() 424 { 425 if (!__ctfe) 426 { 427 return null; 428 } 429 430 import boilerplate.constructor : GetMemberTypeAsString_, GetSuperTypeAsString_, 431 MemberDefault_, SuperDefault_, This; 432 import boilerplate.util : GenNormalMemberTuple, bucketSort, needToDup, udaIndex; 433 import std.meta : aliasSeqOf, staticMap; 434 import std.range : array, drop, iota; 435 import std.string : endsWith, format, join; 436 import std.typecons : Nullable; 437 438 mixin GenNormalMemberTuple; 439 440 string result = null; 441 442 string visibility = "public"; 443 444 foreach (uda; __traits(getAttributes, typeof(this))) 445 { 446 static if (is(typeof(uda) == ThisEnum)) 447 { 448 static if (uda == This.Protected) 449 { 450 visibility = "protected"; 451 } 452 static if (uda == This.Package) 453 { 454 visibility = "package"; 455 } 456 static if (uda == This.Private) 457 { 458 visibility = "private"; 459 } 460 } 461 } 462 463 enum MemberUseDefault(string Member) 464 = mixin(format!(`udaIndex!(This.Default, __traits(getAttributes, this.%s)) != -1`~ 465 `|| udaIndex!(Default, __traits(getAttributes, this.%s)) != -1`)(Member, Member)); 466 467 static if (is(typeof(typeof(super).GeneratedConstructorFields_))) 468 { 469 enum argsPassedToSuper = typeof(super).GeneratedConstructorFields_.length; 470 enum members = typeof(super).GeneratedConstructorFields_ ~ [NormalMemberTuple]; 471 enum useDefaults = typeof(super).GeneratedConstructorUseDefaults_ 472 ~ [staticMap!(MemberUseDefault, NormalMemberTuple)]; 473 enum CombinedArray(alias SuperPred, alias MemberPred) = [ 474 staticMap!(SuperPred, aliasSeqOf!(typeof(super).GeneratedConstructorTypes_.length.iota)), 475 staticMap!(MemberPred, NormalMemberTuple) 476 ]; 477 } 478 else 479 { 480 enum argsPassedToSuper = 0; 481 static if (NormalMemberTuple.length > 0) 482 { 483 enum members = [NormalMemberTuple]; 484 enum useDefaults = [staticMap!(MemberUseDefault, NormalMemberTuple)]; 485 enum CombinedArray(alias SuperPred, alias MemberPred) = [staticMap!(MemberPred, NormalMemberTuple)]; 486 } 487 else 488 { 489 enum string[] members = null; 490 enum bool[] useDefaults = null; 491 enum string[] CombinedArray(alias SuperPred, alias MemberPred) = null; 492 } 493 } 494 495 enum memberTypes = CombinedArray!(GetSuperTypeAsString_, GetMemberTypeAsString_); 496 enum defaults = CombinedArray!(SuperDefault_, MemberDefault_); 497 498 bool[] passAsConst; 499 string[] fields; 500 string[] quotedFields; 501 string[] args; 502 string[] argexprs; 503 string[] defaultAssignments; 504 string[] useDefaultsStr; 505 string[] types; 506 string[] directInitFields; 507 int[] directInitIndex; 508 bool[] directInitUseSelf; 509 510 bool anyDups = false; 511 512 foreach (i; aliasSeqOf!(members.length.iota)) 513 { 514 enum member = members[i]; 515 516 mixin(`alias Type = ` ~ memberTypes[i] ~ `;`); 517 518 bool includeMember = false; 519 520 enum isNullable = mixin(format!`is(%s: Template!Args, alias Template = Nullable, Args...)`(memberTypes[i])); 521 522 static if (!isNullable) 523 { 524 bool dupExpr = needToDup!Type; 525 bool passExprAsConst = dupExpr && __traits(compiles, const(Type).init.dup); 526 } 527 else 528 { 529 // unpack nullable for dup 530 bool dupExpr = needToDup!(typeof(Type.init.get)); 531 bool passExprAsConst = dupExpr && __traits(compiles, Type(const(Type).init.get.dup)); 532 } 533 534 bool forSuper = false; 535 536 static if (i < argsPassedToSuper) 537 { 538 includeMember = true; 539 forSuper = true; 540 } 541 else 542 { 543 mixin("alias symbol = this." ~ member ~ ";"); 544 545 static assert (is(typeof(symbol)) && !__traits(isTemplate, symbol)); /* must have a resolvable type */ 546 547 import boilerplate.util: isStatic; 548 549 includeMember = true; 550 551 if (mixin(isStatic(member))) 552 { 553 includeMember = false; 554 } 555 556 static if (udaIndex!(This.Init, __traits(getAttributes, symbol)) != -1) 557 { 558 enum udaFieldIndex = udaIndex!(This.Init, __traits(getAttributes, symbol)); 559 560 directInitFields ~= member; 561 directInitIndex ~= udaFieldIndex; 562 directInitUseSelf ~= __traits(compiles, 563 __traits(getAttributes, symbol)[udaFieldIndex].value(typeof(this).init)); 564 includeMember = false; 565 } 566 567 static if (udaIndex!(This.Exclude, __traits(getAttributes, symbol)) != -1) 568 { 569 includeMember = false; 570 } 571 } 572 573 if (!includeMember) continue; 574 575 string paramName; 576 577 if (member.endsWith("_")) 578 { 579 paramName = member[0 .. $ - 1]; 580 } 581 else 582 { 583 paramName = member; 584 } 585 586 string argexpr = paramName; 587 588 if (dupExpr) 589 { 590 anyDups = true; 591 592 static if (isNullable) 593 { 594 argexpr = format!`%s.isNull ? %s.init : %s(%s.get.dup)` 595 (argexpr, memberTypes[i], memberTypes[i], argexpr); 596 } 597 else 598 { 599 argexpr = format!`%s.dup`(argexpr); 600 } 601 } 602 603 passAsConst ~= passExprAsConst; 604 fields ~= member; 605 quotedFields ~= `"` ~ member ~ `"`; 606 args ~= paramName; 607 argexprs ~= argexpr; 608 defaultAssignments ~= useDefaults[i] ? (` = ` ~ defaults[i]) : ``; 609 useDefaultsStr ~= useDefaults[i] ? `true` : `false`; 610 types ~= passExprAsConst ? (`const ` ~ memberTypes[i]) : memberTypes[i]; 611 } 612 613 result ~= visibility ~ ` this(`; 614 615 int establishParameterRank(size_t i) 616 { 617 // parent explicit, our explicit, our implicit, parent implicit 618 const fieldOfParent = i < argsPassedToSuper; 619 return useDefaults[i] * 2 + (useDefaults[i] == fieldOfParent); 620 } 621 622 foreach (k, i; fields.length.iota.array.bucketSort(&establishParameterRank)) 623 { 624 auto type = types[i]; 625 626 if (k > 0) 627 { 628 result ~= `, `; 629 } 630 631 result ~= type ~ ` ` ~ args[i] ~ defaultAssignments[i]; 632 } 633 634 result ~= format!`) pure %snothrow @safe {`(anyDups ? `` : `@nogc `); 635 636 static if (is(typeof(typeof(super).GeneratedConstructorFields_))) 637 { 638 result ~= `super(` ~ args[0 .. argsPassedToSuper].join(", ") ~ `);`; 639 } 640 641 foreach (i; fields.length.iota.drop(argsPassedToSuper)) 642 { 643 auto field = fields[i]; 644 auto argexpr = argexprs[i]; 645 646 result ~= `this.` ~ field ~ ` = ` ~ argexpr ~ `;`; 647 } 648 649 foreach (i, field; directInitFields) 650 { 651 if (directInitUseSelf[i]) 652 { 653 result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value(this);` 654 (field, field, directInitIndex[i]); 655 } 656 else 657 { 658 result ~= format!`this.%s = __traits(getAttributes, this.%s)[%s].value;` 659 (field, field, directInitIndex[i]); 660 } 661 } 662 663 result ~= `}`; 664 665 static if (!is(typeof(this) == struct)) 666 { 667 // don't emit inheritance info for structs 668 result ~= `protected static enum string[] GeneratedConstructorFields_ = [` 669 ~ quotedFields.join(`, `) 670 ~ `];`; 671 672 result ~= `protected static alias GeneratedConstructorTypes_ = AliasSeq!(` 673 ~ types.join(`, `) 674 ~ `);`; 675 676 result ~= `protected static enum bool[] GeneratedConstructorUseDefaults_ = [` 677 ~ useDefaultsStr.join(`, `) 678 ~ `];`; 679 680 result ~= `protected static alias GeneratedConstructorDefaults_ = AliasSeq!(` 681 ~ defaults.join(`, `) 682 ~ `);`; 683 } 684 else 685 { 686 if (fields.length == 0) 687 { 688 // don't generate empty constructor for structs 689 return ""; 690 } 691 } 692 693 return result; 694 } 695 } 696 697 deprecated("Please use This.Default") 698 struct Default(alias Alias) 699 { 700 static if (__traits(compiles, Alias())) 701 { 702 @property static auto value() { return Alias(); } 703 } 704 else 705 { 706 alias value = Alias; 707 } 708 } 709 710 enum ThisEnum 711 { 712 Private, 713 Protected, 714 Package, 715 Exclude 716 } 717 718 struct This 719 { 720 enum Private = ThisEnum.Private; 721 enum Protected = ThisEnum.Protected; 722 enum Package = ThisEnum.Package; 723 enum Exclude = ThisEnum.Exclude; 724 725 // construct with value 726 static struct Init(alias Alias) 727 { 728 static if (__traits(compiles, Alias())) 729 { 730 @property static auto value() { return Alias(); } 731 } 732 else 733 { 734 alias value = Alias; 735 } 736 } 737 738 static struct Default(alias Alias) 739 { 740 static if (__traits(compiles, Alias())) 741 { 742 @property static auto value() { return Alias(); } 743 } 744 else 745 { 746 alias value = Alias; 747 } 748 } 749 } 750 751 public template getUDADefaultOrNothing(attributes...) 752 { 753 import boilerplate.util : udaIndex; 754 755 template EnumTest() 756 { 757 enum EnumTest = attributes[udaIndex!(This.Default, attributes)].value; 758 } 759 760 template EnumTestDeprecated() 761 { 762 enum EnumTest = attributes[udaIndex!(Default, attributes)].value; 763 } 764 765 static if (udaIndex!(This.Default, attributes) == -1 && udaIndex!(Default, attributes) == -1) 766 { 767 enum getUDADefaultOrNothing = 0; 768 } 769 else static if (__traits(compiles, EnumTest!())) 770 { 771 enum getUDADefaultOrNothing = attributes[udaIndex!(This.Default, attributes)].value; 772 } 773 else static if (__traits(compiles, EnumTestDeprecated())) 774 { 775 enum getUDADefaultOrNothing = attributes[udaIndex!(Default, attributes)].value; 776 } 777 else 778 { 779 @property static auto getUDADefaultOrNothing() 780 { 781 static if (udaIndex!(This.Default, attributes) != -1) 782 { 783 return attributes[udaIndex!(This.Default, attributes)].value; 784 } 785 else 786 { 787 return attributes[udaIndex!(Default, attributes)].value; 788 } 789 } 790 } 791 }