Thread overview
RFC: patch statement
Apr 03, 2017
Dejan Lekic
Apr 03, 2017
Biotronic
Apr 03, 2017
Minty Fresh
April 03, 2017
I know people her do not like to see proposals that change (add stuff to) the language. However, I strongly feel that for the testing purposes D should provide means to patch any object (no matter whether it is final or not!). Therefore I wonder what people think of adding a `patch(obj) {}` or perhaps change the semantics of the `with(obj) {}` so unittest writers can modify the object and set values.

The patch keyword would work ONLY inside unittest {} blocks AND inside functions annotated with @test annotation.

Imagine we have:

int myFun(Person person) { /* some logic here */ }

unittest {
  auto p = new Person() /* does not really matter which constructor we use */
  patch(p) {
    // here we can modify ANY attribute, no matter whether it is private or public
    p.fname = "Nikola"
    p.sname = "Tesla"
  }
  auto res = myFun(p)
  // do some assertions here
}

Similarly:

@test
void test_myFun() {
  // same code as in the unittest above.
}

I do not even know if patch() {} statement is even possible, that is the whole point of writing this, so people can enlighten me... :)

As I said in the introduction paragraph, for this purpose the semantics of the with statement could be changed, but I prefer a different keyword (patch) to be honest.
April 03, 2017
On Monday, 3 April 2017 at 11:16:57 UTC, Dejan Lekic wrote:
> I know people her do not like to see proposals that change (add stuff to) the language. However, I strongly feel that for the testing purposes D should provide means to patch any object (no matter whether it is final or not!). Therefore I wonder what people think of adding a `patch(obj) {}` or perhaps change the semantics of the `with(obj) {}` so unittest writers can modify the object and set values.
>
> The patch keyword would work ONLY inside unittest {} blocks AND inside functions annotated with @test annotation.
>
> Imagine we have:
>
> int myFun(Person person) { /* some logic here */ }
>
> unittest {
>   auto p = new Person() /* does not really matter which constructor we use */
>   patch(p) {
>     // here we can modify ANY attribute, no matter whether it is private or public
>     p.fname = "Nikola"
>     p.sname = "Tesla"
>   }
>   auto res = myFun(p)
>   // do some assertions here
> }
>
> Similarly:
>
> @test
> void test_myFun() {
>   // same code as in the unittest above.
> }
>
> I do not even know if patch() {} statement is even possible, that is the whole point of writing this, so people can enlighten me... :)
>
> As I said in the introduction paragraph, for this purpose the semantics of the with statement could be changed, but I prefer a different keyword (patch) to be honest.

We can already do that. Proof of concept:


// =======================
module bar;

class A {
    private int n;

    public int GetN() {
        return n;
    }
}

// =======================
module foo;
import bar;

void main() {
    import std.stdio : writeln;
    auto a = new A();
    assert(a.GetN() == 0);
    with (patch(a)) { // Look ma, magic!
        n = 3;
    }
    assert(a.GetN() == 3);
}

auto patch(T)(T value) {
    return Patch!T(value);
}

struct Patch(T) {
    T _payload;

    mixin PatchFields!T;
}

mixin template PatchFields(T, int __n = -1) {
    static if (__n == -1) {
        mixin PatchFields!(T, T.tupleof.length-1);
    } else {
        enum name = __traits(identifier, T.tupleof[__n]);

        mixin("auto "~name~"(U)(U value) {
            _payload.tupleof[__n] = value;
        }");

        static if (__n > 0) {
            mixin PatchFields!(T, __n-1);
        }
    }
}
April 03, 2017
On Monday, 3 April 2017 at 11:16:57 UTC, Dejan Lekic wrote:
> I know people her do not like to see proposals that change (add stuff to) the language. However, I strongly feel that for the testing purposes D should provide means to patch any object (no matter whether it is final or not!). Therefore I wonder what people think of adding a `patch(obj) {}` or perhaps change the semantics of the `with(obj) {}` so unittest writers can modify the object and set values.
>
> The patch keyword would work ONLY inside unittest {} blocks AND inside functions annotated with @test annotation.
>
> Imagine we have:
>
> int myFun(Person person) { /* some logic here */ }
>
> unittest {
>   auto p = new Person() /* does not really matter which constructor we use */
>   patch(p) {
>     // here we can modify ANY attribute, no matter whether it is private or public
>     p.fname = "Nikola"
>     p.sname = "Tesla"
>   }
>   auto res = myFun(p)
>   // do some assertions here
> }
>
> Similarly:
>
> @test
> void test_myFun() {
>   // same code as in the unittest above.
> }
>
> I do not even know if patch() {} statement is even possible, that is the whole point of writing this, so people can enlighten me... :)
>
> As I said in the introduction paragraph, for this purpose the semantics of the with statement could be changed, but I prefer a different keyword (patch) to be honest.

It looks like what you're trying to do is set up object mocks for unit testing.
In general, I find that well designed libraries provide such tools for testing, either in the form of factory functions or some other means of constructing mocks for test builds. I try to follow such patterns myself.

Getting back to the immediate subject:
You can already grant write access to whatever attributes with a bit of conditional compilation. Notably, defining accessors that exist only for unittest builds.

You could even go so far as to define a generalized one,
ie.
version(unittest)
{
    void patch(string attr, T)(Person p, T value)
    {
        __traits(getMember, p, attr) = value;
    }
}

And then, elsewhere:

// Given p is some Person.
p.patch!"fname" = "Nikola";
p.patch!"sname" = "Tesla";

So long as this is defined in the same module as the type, it'll be able to access protected and private fields.