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, Default, 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 @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 @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 @Default!2 180 int field2 = 2; 181 182 mixin(GenerateThis); 183 } 184 185 class Child : Parent 186 { 187 int field3; 188 189 @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 @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 import std.string : format; 350 351 enum GetSuperTypeAsString_(size_t Index) = format!`typeof(super).GeneratedConstructorTypes_[%s]`(Index); 352 353 enum GetMemberTypeAsString_(string Member) = format!`typeof(this.%s)`(Member); 354 355 enum SuperDefault_(size_t Index) = format!`typeof(super).GeneratedConstructorDefaults_[%s]`(Index); 356 357 enum MemberDefault_(string Member) = 358 format!`getUDADefaultOrNothing!(__traits(getAttributes, this.%s))`(Member); 359 360 mixin template GenerateThisTemplate() 361 { 362 private static generateThisImpl() 363 { 364 if (!__ctfe) 365 { 366 return null; 367 } 368 369 import boilerplate.constructor : GetMemberTypeAsString_, GetSuperTypeAsString_, 370 MemberDefault_, SuperDefault_, This; 371 import boilerplate.util : GenNormalMemberTuple, bucketSort, needToDup, udaIndex; 372 import std.meta : aliasSeqOf, staticMap; 373 import std.range : array, drop, iota; 374 import std.string : endsWith, format, join; 375 import std.typecons : Nullable; 376 377 mixin GenNormalMemberTuple; 378 379 string result = null; 380 381 string visibility = "public"; 382 383 foreach (uda; __traits(getAttributes, typeof(this))) 384 { 385 static if (is(typeof(uda) == This)) 386 { 387 static if (uda == This.Protected) 388 { 389 visibility = "protected"; 390 } 391 static if (uda == This.Package) 392 { 393 visibility = "package"; 394 } 395 static if (uda == This.Private) 396 { 397 visibility = "private"; 398 } 399 } 400 } 401 402 enum MemberUseDefault(string Member) 403 = mixin(format!`udaIndex!(Default, __traits(getAttributes, this.%s)) != -1`(Member)); 404 405 static if (is(typeof(typeof(super).GeneratedConstructorFields_))) 406 { 407 enum argsPassedToSuper = typeof(super).GeneratedConstructorFields_.length; 408 enum members = typeof(super).GeneratedConstructorFields_ ~ [NormalMemberTuple]; 409 enum useDefaults = typeof(super).GeneratedConstructorUseDefaults_ 410 ~ [staticMap!(MemberUseDefault, NormalMemberTuple)]; 411 enum CombinedArray(alias SuperPred, alias MemberPred) = [ 412 staticMap!(SuperPred, aliasSeqOf!(typeof(super).GeneratedConstructorTypes_.length.iota)), 413 staticMap!(MemberPred, NormalMemberTuple) 414 ]; 415 } 416 else 417 { 418 enum argsPassedToSuper = 0; 419 static if (NormalMemberTuple.length > 0) 420 { 421 enum members = [NormalMemberTuple]; 422 enum useDefaults = [staticMap!(MemberUseDefault, NormalMemberTuple)]; 423 enum CombinedArray(alias SuperPred, alias MemberPred) = [staticMap!(MemberPred, NormalMemberTuple)]; 424 } 425 else 426 { 427 enum string[] members = null; 428 enum bool[] useDefaults = null; 429 enum string[] CombinedArray(alias SuperPred, alias MemberPred) = null; 430 } 431 } 432 433 enum memberTypes = CombinedArray!(GetSuperTypeAsString_, GetMemberTypeAsString_); 434 enum defaults = CombinedArray!(SuperDefault_, MemberDefault_); 435 436 bool[] passAsConst; 437 string[] fields; 438 string[] quotedFields; 439 string[] args; 440 string[] argexprs; 441 string[] defaultAssignments; 442 string[] useDefaultsStr; 443 string[] types; 444 445 bool anyDups = false; 446 447 foreach (i; aliasSeqOf!(members.length.iota)) 448 { 449 enum member = members[i]; 450 451 mixin(`alias Type = ` ~ memberTypes[i] ~ `;`); 452 453 bool includeMember = false; 454 455 enum isNullable = mixin(format!`is(%s: Template!Args, alias Template = Nullable, Args...)`(memberTypes[i])); 456 457 static if (!isNullable) 458 { 459 bool dupExpr = needToDup!Type; 460 bool passExprAsConst = dupExpr && __traits(compiles, const(Type).init.dup); 461 } 462 else 463 { 464 // unpack nullable for dup 465 bool dupExpr = needToDup!(typeof(Type.init.get)); 466 bool passExprAsConst = dupExpr && __traits(compiles, Type(const(Type).init.get.dup)); 467 } 468 469 bool forSuper = false; 470 471 static if (i < argsPassedToSuper) 472 { 473 includeMember = true; 474 forSuper = true; 475 } 476 else 477 { 478 mixin("alias symbol = this." ~ member ~ ";"); 479 480 static assert (is(typeof(symbol)) && !__traits(isTemplate, symbol)); /* must have a resolvable type */ 481 482 import boilerplate.util: isStatic; 483 484 includeMember = true; 485 486 if (mixin(isStatic(member))) 487 { 488 includeMember = false; 489 } 490 491 static if (udaIndex!(This.Exclude, __traits(getAttributes, symbol)) != -1) 492 { 493 includeMember = false; 494 } 495 } 496 497 if (!includeMember) continue; 498 499 string paramName; 500 501 if (member.endsWith("_")) 502 { 503 paramName = member[0 .. $ - 1]; 504 } 505 else 506 { 507 paramName = member; 508 } 509 510 string argexpr = paramName; 511 512 if (dupExpr) 513 { 514 anyDups = true; 515 516 static if (isNullable) 517 { 518 argexpr = format!`%s.isNull ? %s.init : %s(%s.get.dup)` 519 (argexpr, memberTypes[i], memberTypes[i], argexpr); 520 } 521 else 522 { 523 argexpr = format!`%s.dup`(argexpr); 524 } 525 } 526 527 passAsConst ~= passExprAsConst; 528 fields ~= member; 529 quotedFields ~= `"` ~ member ~ `"`; 530 args ~= paramName; 531 argexprs ~= argexpr; 532 defaultAssignments ~= useDefaults[i] ? (` = ` ~ defaults[i]) : ``; 533 useDefaultsStr ~= useDefaults[i] ? `true` : `false`; 534 types ~= passExprAsConst ? (`const ` ~ memberTypes[i]) : memberTypes[i]; 535 } 536 537 result ~= visibility ~ ` this(`; 538 539 int establishParameterRank(size_t i) 540 { 541 // parent explicit, our explicit, our implicit, parent implicit 542 const fieldOfParent = i < argsPassedToSuper; 543 return useDefaults[i] * 2 + (useDefaults[i] == fieldOfParent); 544 } 545 546 foreach (k, i; fields.length.iota.array.bucketSort(&establishParameterRank)) 547 { 548 auto type = types[i]; 549 550 if (k > 0) 551 { 552 result ~= `, `; 553 } 554 555 result ~= type ~ ` ` ~ args[i] ~ defaultAssignments[i]; 556 } 557 558 result ~= format!`) pure %snothrow @safe {`(anyDups ? `` : `@nogc `); 559 560 static if (is(typeof(typeof(super).GeneratedConstructorFields_))) 561 { 562 result ~= `super(` ~ args[0 .. argsPassedToSuper].join(", ") ~ `);`; 563 } 564 565 foreach (i; fields.length.iota.drop(argsPassedToSuper)) 566 { 567 auto field = fields[i]; 568 auto argexpr = argexprs[i]; 569 570 result ~= `this.` ~ field ~ ` = ` ~ argexpr ~ `;`; 571 } 572 573 result ~= `}`; 574 575 static if (!is(typeof(this) == struct)) 576 { 577 // don't emit inheritance info for structs 578 result ~= `protected static enum string[] GeneratedConstructorFields_ = [` 579 ~ quotedFields.join(`, `) 580 ~ `];`; 581 582 result ~= `protected static alias GeneratedConstructorTypes_ = AliasSeq!(` 583 ~ types.join(`, `) 584 ~ `);`; 585 586 result ~= `protected static enum bool[] GeneratedConstructorUseDefaults_ = [` 587 ~ useDefaultsStr.join(`, `) 588 ~ `];`; 589 590 result ~= `protected static alias GeneratedConstructorDefaults_ = AliasSeq!(` 591 ~ defaults.join(`, `) 592 ~ `);`; 593 } 594 else 595 { 596 if (fields.length == 0) 597 { 598 // don't generate empty constructor for structs 599 return ""; 600 } 601 } 602 603 return result; 604 } 605 } 606 607 struct Default(alias Alias) 608 { 609 static if (__traits(compiles, Alias())) 610 { 611 @property static auto value() { return Alias(); } 612 } 613 else 614 { 615 alias value = Alias; 616 } 617 } 618 619 enum This 620 { 621 Private, 622 Protected, 623 Package, 624 Exclude 625 } 626 627 private size_t udaDefaultIndex(alias Symbol)() 628 { 629 enum numTraits = __traits(getAttributes, Symbol).length; 630 631 static if (numTraits == 0) 632 { 633 return -1; 634 } 635 else 636 { 637 foreach (i, uda; __traits(getAttributes, Symbol)) 638 { 639 static if (is(uda: Template!Args, alias Template = Default, Args...)) 640 { 641 return i; 642 } 643 else static if (i == numTraits - 1) 644 { 645 return -1; 646 } 647 } 648 } 649 } 650 651 public template getUDADefaultOrNothing(attributes...) 652 { 653 import boilerplate.util : udaIndex; 654 655 template EnumTest() 656 { 657 enum EnumTest = attributes[udaIndex!(Default, attributes)].value; 658 } 659 660 static if (udaIndex!(Default, attributes) == -1) 661 { 662 enum getUDADefaultOrNothing = 0; 663 } 664 else static if (__traits(compiles, EnumTest!())) 665 { 666 enum getUDADefaultOrNothing = attributes[udaIndex!(Default, attributes)].value; 667 } 668 else 669 { 670 @property static auto getUDADefaultOrNothing() 671 { 672 return attributes[udaIndex!(Default, attributes)].value; 673 } 674 } 675 }