GenerateThis

GenerateThis is a mixin string that automatically generates a this() function, customizable with UDA.

enum string GenerateThis;

Examples

GenerateThis creates a constructor with a parameter for every field.

class Class
{
    int field;

    mixin(GenerateThis);
}

auto obj = new Class(5);

obj.field.shouldEqual(5);

When the super class also has a generated constructor, it will be called first.

1 class Class
2 {
3     int field;
4 
5     mixin(GenerateThis);
6 }
7 
8 class Child : Class
9 {
10     int field2;
11 
12     mixin(GenerateThis);
13 }
14 
15 auto obj = new Child(5, 8);
16 
17 obj.field.shouldEqual(5);
18 obj.field2.shouldEqual(8);

Methods are ignored when generating constructors.

class Class
{
    int field;

    void method() { }

    mixin(GenerateThis);
}

auto obj = new Class(5);

obj.field.shouldEqual(5);

When passing arrays to the constructor, these arrays are automatically dup-ed.

class Class
{
    int[] array;

    mixin(GenerateThis);
}

auto array = [2, 3, 4];
auto obj = new Class(array);

array[0] = 1;
obj.array[0].shouldEqual(2);

Arrays passed to the constructor are dup-ed even if they're inside a Nullable.

1 import std.typecons : Nullable, nullable;
2 
3 class Class
4 {
5     Nullable!(int[]) array;
6 
7     mixin(GenerateThis);
8 }
9 
10 auto array = [2, 3, 4];
11 auto obj = new Class(array.nullable);
12 
13 array[0] = 1;
14 obj.array.get[0].shouldEqual(2);
15 
16 obj = new Class(Nullable!(int[]).init);
17 obj.array.isNull.shouldBeTrue;

Associative arrays are also dup-ed.

class Class
{
    int[int] array;

    mixin(GenerateThis);
}

auto array = [2: 3];
auto obj = new Class(array);

array[2] = 4;
obj.array.shouldEqual([2: 3]);

@(This.Default!value) defines a default value for the constructor parameter.

1 class Class
2 {
3     @(This.Default!5)
4     int value = 5;
5 
6     mixin(GenerateThis);
7 }
8 
9 auto obj1 = new Class();
10 
11 obj1.value.shouldEqual(5);
12 
13 auto obj2 = new Class(6);
14 
15 obj2.value.shouldEqual(6);

When using GenerateThis in an empty struct, no constructor is created.

This is because D does not allow empty constructor methods.

struct Struct
{
    mixin(GenerateThis);
}

auto strct = Struct();

@(This.Default!(lambda)) calls the lambda to generate the default value.

This is to handle cases like @(This.Default!(new Class)), where D would allocate the class during startup and reuse the same reference for every constructor call.

import std.conv : to;

class Class
{
    @(This.Default!(() => new Object))
    Object obj;

    mixin(GenerateThis);
}

auto obj1 = new Class();
auto obj2 = new Class();

(cast(void*) obj1.obj).shouldNotEqual(cast(void*) obj2.obj);

When the superclass has a generated constructor, the order of parameters is:

- Super class fields - Class fields - Class fields with default value - Super class fields with default value

1 class Parent
2 {
3     int field1;
4 
5     @(This.Default!2)
6     int field2 = 2;
7 
8     mixin(GenerateThis);
9 }
10 
11 class Child : Parent
12 {
13     int field3;
14 
15     @(This.Default!4)
16     int field4 = 4;
17 
18     mixin(GenerateThis);
19 }
20 
21 auto obj = new Child(1, 2, 3, 4);
22 
23 obj.field1.shouldEqual(1);
24 obj.field3.shouldEqual(2);
25 obj.field4.shouldEqual(3);
26 obj.field2.shouldEqual(4);

No constructor parameter is generated for static fields.

class Class
{
    static int field1;
    int field2;

    mixin(GenerateThis);
}

auto obj = new Class(5);

obj.field1.shouldEqual(0);
obj.field2.shouldEqual(5);

Immutable arrays are supported as constructor parameters.

class Class
{
    immutable(Object)[] array;

    mixin(GenerateThis);
}

@(This.Private/Protected/Public) can be used to define the visibility scope of the constructor.

1 @(This.Private)
2 class PrivateClass
3 {
4     mixin(GenerateThis);
5 }
6 
7 @(This.Protected)
8 class ProtectedClass
9 {
10     mixin(GenerateThis);
11 }
12 
13 @(This.Package)
14 class PackageClass
15 {
16     mixin(GenerateThis);
17 }
18 
19 @(This.Package("boilerplate"))
20 class SubPackageClass
21 {
22     mixin(GenerateThis);
23 }
24 
25 class PublicClass
26 {
27     mixin(GenerateThis);
28 }
29 
30 static assert(__traits(getProtection, PrivateClass.__ctor) == "private");
31 static assert(__traits(getProtection, ProtectedClass.__ctor) == "protected");
32 static assert(__traits(getProtection, PackageClass.__ctor) == "package");
33 // getProtection does not return the package name of a package() attribute
34 // static assert(__traits(getProtection, SubPackageClass.__ctor) == `package(boilerplate)`);
35 static assert(__traits(getProtection, PublicClass.__ctor) == "public");

@(This.Private/Protected/Public) also assigns a visibility scope to the generated builder.

1 @(This.Private)
2 class PrivateClass
3 {
4     mixin(GenerateThis);
5 }
6 
7 @(This.Protected)
8 class ProtectedClass
9 {
10     mixin(GenerateThis);
11 }
12 
13 @(This.Package)
14 class PackageClass
15 {
16     mixin(GenerateThis);
17 }
18 
19 @(This.Package("boilerplate"))
20 class SubPackageClass
21 {
22     mixin(GenerateThis);
23 }
24 
25 class PublicClass
26 {
27     mixin(GenerateThis);
28 }
29 
30 static assert(__traits(getProtection, PrivateClass.Builder) == "private");
31 static assert(__traits(getProtection, ProtectedClass.Builder) == "protected");
32 static assert(__traits(getProtection, PackageClass.Builder) == "package");
33 static assert(__traits(getProtection, PublicClass.Builder) == "public");

@(This.Default) without a parameter uses the default value of the type.

class Class
{
    @(This.Default)
    string s;

    @(This.Default)
    int i;

    mixin(GenerateThis);
}

(new Class()).i.shouldEqual(0);
(new Class()).s.shouldEqual(string.init);

@(This.Exclude) excludes a field from being set by the generated constructor.

class Class
{
    @(This.Exclude)
    int i = 5;

    mixin(GenerateThis);
}

(new Class).i.shouldEqual(5);

Even if the class holds a field that is not const, a const array can be passed to it as long as the dup of this field does not leak mutable references.

struct Struct
{
}

class Class
{
    Struct[] values_;

    mixin(GenerateThis);
}

const Struct[] constValues;
auto obj = new Class(constValues);

Property functions are disregarded by the generated constructor

class Class
{
    int a;

    @property int foo() const
    {
        return 0;
    }

    mixin(GenerateThis);
}

static assert(__traits(compiles, new Class(0)));
static assert(!__traits(compiles, new Class(0, 0)));

When no parameters need to be dupped, the generated constructor is @nogc.

struct Struct
{
    int a;

    mixin(GenerateThis);
}

auto str = Struct(5);

@(This.Init!value) defines a value for the field that will be assigned in the constructor, and excludes the field from being a constructor parameter.

1 class Class
2 {
3     @(This.Init!5)
4     int field1;
5 
6     @(This.Init!(() => 8))
7     int field2;
8 
9     mixin(GenerateThis);
10 }
11 
12 auto obj = new Class;
13 
14 obj.field1.shouldEqual(5);
15 obj.field2.shouldEqual(8);

@(This.Init!lambda), like @(This.Default!lambda), will be called automatically with this to determine the initializer value.

class Class
{
    int field1;

    @(This.Init!(self => self.field1 + 5))
    int field2;

    mixin(GenerateThis);
}

auto obj = new Class(5);

obj.field1.shouldEqual(5);
obj.field2.shouldEqual(10);

@(This.Init!lambda) can allocate runtime values.

1 class Class1
2 {
3     @(This.Init!(self => new Object))
4     Object object;
5 
6     mixin(GenerateThis);
7 }
8 
9 class Class2
10 {
11     @(This.Init!(() => new Object))
12     Object object;
13 
14     mixin(GenerateThis);
15 }
16 
17 class Class3 : Class2
18 {
19     mixin(GenerateThis);
20 }

GenerateThis creates a .Builder() property that can be used to incrementally construct the value.

builder.value will call the generated constructor with the assigned values.

1 static class Class
2 {
3     int field1;
4     int field2;
5     int field3;
6 
7     mixin(GenerateThis);
8 }
9 
10 auto obj = {
11     with (Class.Builder())
12     {
13         field1 = 1;
14         field2 = 2;
15         field3 = 3;
16         return value;
17     }
18 }();
19 
20 with (obj)
21 {
22     field1.shouldEqual(1);
23     field2.shouldEqual(2);
24     field3.shouldEqual(3);
25 }

The order in which Builder fields are set is irrelevant.

1 static class Class
2 {
3     int field1;
4     int field2;
5     int field3;
6 
7     mixin(GenerateThis);
8 }
9 
10 auto obj = {
11     with (Class.Builder())
12     {
13         field3 = 1;
14         field1 = 2;
15         field2 = 3;
16         return value;
17     }
18 }();
19 
20 with (obj)
21 {
22     field1.shouldEqual(2);
23     field2.shouldEqual(3);
24     field3.shouldEqual(1);
25 }

Builder fields with a @(This.Default) value can be omitted.

1 static class Class
2 {
3     int field1;
4     @(This.Default!5)
5     int field2;
6     int field3;
7 
8     mixin(GenerateThis);
9 }
10 
11 // constructor is this(field1, field3, field2 = 5)
12 auto obj = {
13     with (Class.Builder())
14     {
15         field1 = 1;
16         field3 = 3;
17         return value;
18     }
19 }();
20 
21 with (obj)
22 {
23     field1.shouldEqual(1);
24     field2.shouldEqual(5);
25     field3.shouldEqual(3);
26 }

Builder can be used with structs as well as classes.

1 struct Struct
2 {
3     int field1;
4     int field2;
5     int field3;
6 
7     mixin(GenerateThis);
8 }
9 
10 auto value = {
11     with (Struct.Builder())
12     {
13         field1 = 1;
14         field3 = 3;
15         field2 = 5;
16         return value;
17     }
18 }();
19 
20 static assert(is(typeof(value) == Struct));
21 
22 with (value)
23 {
24     field1.shouldEqual(1);
25     field2.shouldEqual(5);
26     field3.shouldEqual(3);
27 }

Builder fields don't contain the trailing newline of a private field.

struct Struct
{
    private int a_;

    mixin(GenerateThis);
}

auto builder = Struct.Builder();

builder.a = 1;

auto value = builder.value;

value.shouldEqual(Struct(1));

When a field in the type has a generated constructor itself, its fields can be set directly on a Builder of the outer type.

1 struct Struct1
2 {
3     int a;
4     int b;
5 
6     mixin(GenerateThis);
7 }
8 
9 struct Struct2
10 {
11     int c;
12     Struct1 struct1;
13     int d;
14 
15     mixin(GenerateThis);
16 }
17 
18 auto builder = Struct2.Builder();
19 
20 builder.struct1.a = 1;
21 builder.struct1.b = 2;
22 builder.c = 3;
23 builder.d = 4;
24 
25 auto value = builder.value;
26 
27 static assert(is(typeof(value) == Struct2));
28 
29 with (value)
30 {
31     struct1.a.shouldEqual(1);
32     struct1.b.shouldEqual(2);
33     c.shouldEqual(3);
34     d.shouldEqual(4);
35 }

Builder can use @(This.Default) values for nested fields.

1 struct Struct1
2 {
3     int a;
4     int b;
5 
6     mixin(GenerateThis);
7 }
8 
9 struct Struct2
10 {
11     int c;
12     @(This.Default!(Struct1(3, 4)))
13     Struct1 struct1;
14     int d;
15 
16     mixin(GenerateThis);
17 }
18 
19 auto builder = Struct2.Builder();
20 
21 builder.c = 1;
22 builder.d = 2;
23 
24 builder.value.shouldEqual(Struct2(1, 2, Struct1(3, 4)));

Builder also allows assigning a single value directly for a nested Builder field.

1 struct Struct1
2 {
3     int a;
4     int b;
5 
6     mixin(GenerateThis);
7 }
8 
9 struct Struct2
10 {
11     int c;
12     Struct1 struct1;
13     int d;
14 
15     mixin(GenerateThis);
16 }
17 
18 auto builder = Struct2.Builder();
19 
20 builder.struct1 = Struct1(2, 3);
21 builder.c = 1;
22 builder.d = 4;
23 
24 builder.value.shouldEqual(Struct2(1, Struct1(2, 3), 4));

When a Builder field is an array, it can be initialized by specifying the value for each desired index.

1 struct Struct1
2 {
3     int value;
4 
5     mixin(GenerateThis);
6 }
7 
8 struct Struct2
9 {
10     Struct1[] array;
11 
12     mixin(GenerateThis);
13 }
14 
15 auto builder = Struct2.Builder();
16 
17 builder.array[0].value = 1;
18 builder.array[1].value = 2;
19 
20 builder.value.shouldEqual(Struct2([Struct1(1), Struct1(2)]));

Builder handles fields that are aliased to this.

1 struct Struct2
2 {
3     string text;
4 
5     alias text this;
6 
7     mixin(GenerateThis);
8 }
9 
10 struct Struct1
11 {
12     Struct2 nested;
13 
14     mixin(GenerateThis);
15 }
16 
17 auto builder = Struct1.Builder();
18 
19 builder.nested.text = "foo";
20 
21 builder.value.shouldEqual(Struct1(Struct2("foo")));

When a Builder field is an array, it can also be initialized by appending values.

1 struct Struct1
2 {
3     int value;
4 
5     mixin(GenerateThis);
6 }
7 
8 struct Struct2
9 {
10     Struct1[] array;
11 
12     mixin(GenerateThis);
13 }
14 
15 auto builder = Struct2.Builder();
16 
17 builder.array ~= Struct1(1);
18 builder.array ~= Struct1(2);
19 
20 builder.value.shouldEqual(Struct2([Struct1(1), Struct1(2)]));

When a value has been assigned to a recursive Builder field, its fields can still be individually overridden. This uses the BuilderFrom reverse-Builder property.

1 struct Struct1
2 {
3     int a;
4     int b;
5 
6     mixin(GenerateThis);
7 }
8 
9 struct Struct2
10 {
11     Struct1 struct1;
12 
13     mixin(GenerateThis);
14 }
15 
16 auto builder = Struct2.Builder();
17 
18 builder.struct1 = Struct1(2, 3);
19 builder.struct1.b = 4;
20 
21 builder.value.shouldEqual(Struct2(Struct1(2, 4)));

Builder does not attempt to use BuilderFrom when this would leak a non-constant reference.

1 import core.exception : AssertError;
2 
3 struct Struct1
4 {
5     int a;
6 
7     private Object[] b_;
8 
9     mixin(GenerateThis);
10 }
11 
12 struct Struct2
13 {
14     Struct1 struct1;
15 
16     mixin(GenerateThis);
17 }
18 
19 // this should at least compile, despite the BuilderFrom hack not working with Struct1
20 auto builder = Struct2.Builder();
21 
22 builder.struct1 = Struct1(2, null);
23 
24 void set()
25 {
26     builder.struct1.b = null;
27 }
28 
29 set().shouldThrow!AssertError(
30     "Builder: cannot set sub-field directly since field is already " ~
31     "being initialized by value (and BuilderFrom is unavailable in Struct1)");

When a recursive Builder field has already been directly assigned, it cannot be later overwritten with a whole-value assignment.

1 import core.exception : AssertError;
2 
3 struct Struct1
4 {
5     int a;
6     int b;
7 
8     mixin(GenerateThis);
9 }
10 
11 struct Struct2
12 {
13     Struct1 struct1;
14 
15     mixin(GenerateThis);
16 }
17 
18 auto builder = Struct2.Builder();
19 
20 builder.struct1.b = 4;
21 
22 void set()
23 {
24     builder.struct1 = Struct1(2, 3);
25 }
26 set().shouldThrow!AssertError("Builder: cannot set field by value since a subfield has already been set.");

Builder supports assigning const fields.

struct Struct
{
    const int a;

    mixin(GenerateThis);
}

with (Struct.Builder())
{
    a = 5;

    value.shouldEqual(Struct(5));
}

Builder supports assigning recursive values with a destructor.

1 static struct Struct1
2 {
3     ~this() pure @safe @nogc nothrow { }
4 }
5 
6 struct Struct2
7 {
8     Struct1 struct1;
9 
10     mixin(GenerateThis);
11 }
12 
13 with (Struct2.Builder())
14 {
15     struct1 = Struct1();
16 
17     value.shouldEqual(Struct2(Struct1()));
18 }

When a Builder field is Nullable!T, it can be directly assigned a T.

1 import std.typecons : Nullable, nullable;
2 
3 struct Struct
4 {
5     const Nullable!int a;
6 
7     mixin(GenerateThis);
8 }
9 
10 with (Struct.Builder())
11 {
12     a = 5;
13 
14     value.shouldEqual(Struct(5.nullable));
15 }

When a Builder field is Nullable!T, and T has a Builder, T's fields can be directly assigned.

1 import std.typecons : Nullable, nullable;
2 
3 struct Struct1
4 {
5     int a;
6 
7     mixin(GenerateThis);
8 }
9 
10 struct Struct2
11 {
12     @(This.Default)
13     Nullable!Struct1 b;
14 
15     mixin(GenerateThis);
16 }
17 
18 with (Struct2.Builder())
19 {
20     value.shouldEqual(Struct2(Nullable!Struct1()));
21 
22     b.a = 5;
23 
24     value.shouldEqual(Struct2(Nullable!Struct1(Struct1(5))));
25 }

When a Builder field is Nullable!T, and T has a Builder, T can still be assigned either as T or Nullable!T.

1 import std.typecons : Nullable, nullable;
2 
3 struct Struct1
4 {
5     mixin(GenerateThis);
6 }
7 
8 struct Struct2
9 {
10     @(This.Default)
11     Nullable!Struct1 value;
12 
13     mixin(GenerateThis);
14 }
15 
16 with (Struct2.Builder())
17 {
18     builderValue.shouldEqual(Struct2());
19 
20     value = Struct1();
21 
22     builderValue.shouldEqual(Struct2(Struct1().nullable));
23 }
24 
25 with (Struct2.Builder())
26 {
27     value = Nullable!Struct1();
28 
29     builderValue.shouldEqual(Struct2());
30 }

A value with GenerateThis can be turned back into a builder using BuilderFrom(). This can be used to reassign immutable fields.

1 import std.typecons : Nullable, nullable;
2 
3 struct Struct
4 {
5     private int a_;
6 
7     int[] b;
8 
9     mixin(GenerateThis);
10 }
11 
12 const originalValue = Struct(2, [3]);
13 
14 with (originalValue.BuilderFrom())
15 {
16     a = 5;
17 
18     value.shouldEqual(Struct(5, [3]));
19 }

When a type already has a value field, builderValue can be used to get the builder value.

1 import std.typecons : Nullable, nullable;
2 
3 struct Struct
4 {
5     private int value_;
6 
7     mixin(GenerateThis);
8 }
9 
10 with (Struct.Builder())
11 {
12     value = 5;
13 
14     builderValue.shouldEqual(Struct(5));
15 }

Builder will handle structs that contain structs with @disable(this).

1 import std.typecons : Nullable, nullable;
2 
3 static struct Inner
4 {
5     private int i_;
6 
7     @disable this();
8 
9     mixin(GenerateThis);
10 }
11 
12 static struct Struct
13 {
14     private Inner inner_;
15 
16     mixin(GenerateThis);
17 }
18 
19 with (Struct.Builder())
20 {
21     inner.i = 3;
22 
23     value.shouldEqual(Struct(Inner(3)));
24 }

Meta