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.

class Class
{
    int field;

    mixin(GenerateThis);
}

class Child : Class
{
    int field2;

    mixin(GenerateThis);
}

auto obj = new Child(5, 8);

obj.field.shouldEqual(5);
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.

import std.typecons : Nullable, nullable;

class Class
{
    Nullable!(int[]) array;

    mixin(GenerateThis);
}

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

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

obj = new Class(Nullable!(int[]).init);
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.

class Class
{
    @(This.Default!5)
    int value = 5;

    mixin(GenerateThis);
}

auto obj1 = new Class();

obj1.value.shouldEqual(5);

auto obj2 = new Class(6);

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

class Parent
{
    int field1;

    @(This.Default!2)
    int field2 = 2;

    mixin(GenerateThis);
}

class Child : Parent
{
    int field3;

    @(This.Default!4)
    int field4 = 4;

    mixin(GenerateThis);
}

auto obj = new Child(1, 2, 3, 4);

obj.field1.shouldEqual(1);
obj.field3.shouldEqual(2);
obj.field4.shouldEqual(3);
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.

@(This.Private)
class PrivateClass
{
    mixin(GenerateThis);
}

@(This.Protected)
class ProtectedClass
{
    mixin(GenerateThis);
}

@(This.Package)
class PackageClass
{
    mixin(GenerateThis);
}

@(This.Package("boilerplate"))
class SubPackageClass
{
    mixin(GenerateThis);
}

class PublicClass
{
    mixin(GenerateThis);
}

static assert(__traits(getProtection, PrivateClass.__ctor) == "private");
static assert(__traits(getProtection, ProtectedClass.__ctor) == "protected");
static assert(__traits(getProtection, PackageClass.__ctor) == "package");
// getProtection does not return the package name of a package() attribute
// static assert(__traits(getProtection, SubPackageClass.__ctor) == `package(boilerplate)`);
static assert(__traits(getProtection, PublicClass.__ctor) == "public");

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

@(This.Private)
class PrivateClass
{
    mixin(GenerateThis);
}

@(This.Protected)
class ProtectedClass
{
    mixin(GenerateThis);
}

@(This.Package)
class PackageClass
{
    mixin(GenerateThis);
}

@(This.Package("boilerplate"))
class SubPackageClass
{
    mixin(GenerateThis);
}

class PublicClass
{
    mixin(GenerateThis);
}

static assert(__traits(getProtection, PrivateClass.Builder) == "private");
static assert(__traits(getProtection, ProtectedClass.Builder) == "protected");
static assert(__traits(getProtection, PackageClass.Builder) == "package");
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.

class Class
{
    @(This.Init!5)
    int field1;

    @(This.Init!(() => 8))
    int field2;

    mixin(GenerateThis);
}

auto obj = new Class;

obj.field1.shouldEqual(5);
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.

class Class1
{
    @(This.Init!(self => new Object))
    Object object;

    mixin(GenerateThis);
}

class Class2
{
    @(This.Init!(() => new Object))
    Object object;

    mixin(GenerateThis);
}

class Class3 : Class2
{
    mixin(GenerateThis);
}

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.

static class Class
{
    int field1;
    int field2;
    int field3;

    mixin(GenerateThis);
}

auto obj = {
    with (Class.Builder())
    {
        field1 = 1;
        field2 = 2;
        field3 = 3;
        return value;
    }
}();

with (obj)
{
    field1.shouldEqual(1);
    field2.shouldEqual(2);
    field3.shouldEqual(3);
}

The order in which Builder fields are set is irrelevant.

static class Class
{
    int field1;
    int field2;
    int field3;

    mixin(GenerateThis);
}

auto obj = {
    with (Class.Builder())
    {
        field3 = 1;
        field1 = 2;
        field2 = 3;
        return value;
    }
}();

with (obj)
{
    field1.shouldEqual(2);
    field2.shouldEqual(3);
    field3.shouldEqual(1);
}

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

static class Class
{
    int field1;
    @(This.Default!5)
    int field2;
    int field3;

    mixin(GenerateThis);
}

// constructor is this(field1, field3, field2 = 5)
auto obj = {
    with (Class.Builder())
    {
        field1 = 1;
        field3 = 3;
        return value;
    }
}();

with (obj)
{
    field1.shouldEqual(1);
    field2.shouldEqual(5);
    field3.shouldEqual(3);
}

Builder can be used with structs as well as classes.

struct Struct
{
    int field1;
    int field2;
    int field3;

    mixin(GenerateThis);
}

auto value = {
    with (Struct.Builder())
    {
        field1 = 1;
        field3 = 3;
        field2 = 5;
        return value;
    }
}();

static assert(is(typeof(value) == Struct));

with (value)
{
    field1.shouldEqual(1);
    field2.shouldEqual(5);
    field3.shouldEqual(3);
}

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.

struct Struct1
{
    int a;
    int b;

    mixin(GenerateThis);
}

struct Struct2
{
    int c;
    Struct1 struct1;
    int d;

    mixin(GenerateThis);
}

auto builder = Struct2.Builder();

builder.struct1.a = 1;
builder.struct1.b = 2;
builder.c = 3;
builder.d = 4;

auto value = builder.value;

static assert(is(typeof(value) == Struct2));

with (value)
{
    struct1.a.shouldEqual(1);
    struct1.b.shouldEqual(2);
    c.shouldEqual(3);
    d.shouldEqual(4);
}

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

struct Struct1
{
    int a;
    int b;

    mixin(GenerateThis);
}

struct Struct2
{
    int c;
    @(This.Default!(Struct1(3, 4)))
    Struct1 struct1;
    int d;

    mixin(GenerateThis);
}

auto builder = Struct2.Builder();

builder.c = 1;
builder.d = 2;

builder.value.shouldEqual(Struct2(1, 2, Struct1(3, 4)));

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

struct Struct1
{
    int a;
    int b;

    mixin(GenerateThis);
}

struct Struct2
{
    int c;
    Struct1 struct1;
    int d;

    mixin(GenerateThis);
}

auto builder = Struct2.Builder();

builder.struct1 = Struct1(2, 3);
builder.c = 1;
builder.d = 4;

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.

struct Struct1
{
    int value;

    mixin(GenerateThis);
}

struct Struct2
{
    Struct1[] array;

    mixin(GenerateThis);
}

auto builder = Struct2.Builder();

builder.array[0].value = 1;
builder.array[1].value = 2;

builder.value.shouldEqual(Struct2([Struct1(1), Struct1(2)]));

Builder handles fields that are aliased to this.

struct Struct2
{
    string text;

    alias text this;

    mixin(GenerateThis);
}

struct Struct1
{
    Struct2 nested;

    mixin(GenerateThis);
}

auto builder = Struct1.Builder();

builder.nested.text = "foo";

builder.value.shouldEqual(Struct1(Struct2("foo")));

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

struct Struct1
{
    int value;

    mixin(GenerateThis);
}

struct Struct2
{
    Struct1[] array;

    mixin(GenerateThis);
}

auto builder = Struct2.Builder();

builder.array ~= Struct1(1);
builder.array ~= Struct1(2);

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.

struct Struct1
{
    int a;
    int b;

    mixin(GenerateThis);
}

struct Struct2
{
    Struct1 struct1;

    mixin(GenerateThis);
}

auto builder = Struct2.Builder();

builder.struct1 = Struct1(2, 3);
builder.struct1.b = 4;

builder.value.shouldEqual(Struct2(Struct1(2, 4)));

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

import core.exception : AssertError;

struct Struct1
{
    int a;
    int b;

    mixin(GenerateThis);
}

struct Struct2
{
    Struct1 struct1;

    mixin(GenerateThis);
}

auto builder = Struct2.Builder();

builder.struct1.b = 4;

void set()
{
    builder.struct1 = Struct1(2, 3);
}
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.

static struct Struct1
{
    ~this() pure @safe @nogc nothrow { }
}

struct Struct2
{
    Struct1 struct1;

    mixin(GenerateThis);
}

with (Struct2.Builder())
{
    struct1 = Struct1();

    value.shouldEqual(Struct2(Struct1()));
}

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

import std.typecons : Nullable, nullable;

struct Struct
{
    const Nullable!int a;

    mixin(GenerateThis);
}

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

    value.shouldEqual(Struct(5.nullable));
}

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

import std.typecons : Nullable, nullable;

struct Struct1
{
    int a;

    mixin(GenerateThis);
}

struct Struct2
{
    @(This.Default)
    Nullable!Struct1 b;

    mixin(GenerateThis);
}

with (Struct2.Builder())
{
    value.shouldEqual(Struct2(Nullable!Struct1()));

    b.a = 5;

    value.shouldEqual(Struct2(Nullable!Struct1(Struct1(5))));
}

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

import std.typecons : Nullable, nullable;

struct Struct1
{
    mixin(GenerateThis);
}

struct Struct2
{
    @(This.Default)
    Nullable!Struct1 value;

    mixin(GenerateThis);
}

with (Struct2.Builder())
{
    builderValue.shouldEqual(Struct2());

    value = Struct1();

    builderValue.shouldEqual(Struct2(Struct1().nullable));
}

with (Struct2.Builder())
{
    value = Nullable!Struct1();

    builderValue.shouldEqual(Struct2());
}

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

import std.typecons : Nullable, nullable;

struct Struct
{
    private int a_;

    int[] b;

    mixin(GenerateThis);
}

const originalValue = Struct(2, [3]);

with (originalValue.BuilderFrom())
{
    a = 5;

    value.shouldEqual(Struct(5, [3]));
}

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

import std.typecons : Nullable, nullable;

struct Struct
{
    private int value_;

    mixin(GenerateThis);
}

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

    builderValue.shouldEqual(Struct(5));
}

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

import std.typecons : Nullable, nullable;

static struct Inner
{
    private int i_;

    @disable this();

    mixin(GenerateThis);
}

static struct Struct
{
    private Inner inner_;

    mixin(GenerateThis);
}

with (Struct.Builder())
{
    inner.i = 3;

    value.shouldEqual(Struct(Inner(3)));
}

Meta