May 24
https://issues.dlang.org/show_bug.cgi?id=24562

          Issue ID: 24562
           Summary: inout on the copy constructor makes it so that a
                    normal opAssign does not work with multiple layers of
                    objects
           Product: D
           Version: D2
          Hardware: All
                OS: All
            Status: NEW
          Severity: blocker
          Priority: P1
         Component: dmd
          Assignee: nobody@puremagic.com
          Reporter: issues.dlang@jmdavisProg.com

This code fails to compile

---
void main()
{
    static struct V
    {
        int _data;

        this(ref return scope inout(V) rhs) scope @trusted inout pure nothrow
        {
            this._data = rhs._data;
        }

        ref opAssign(T)(auto ref T rhs)
            if(is(immutable T == immutable V))
        {
            this._data = rhs._data;
            return this;
        }
    }

    static struct S1
    {
        V v;
    }

    S1 a;
    auto b = a;
    a = b;

    static struct S2
    {
        S1 s;
    }

    S2 s;
    auto t = s;
    s = t;
}
---

and gives the error:

---
q.d(36): Error: generated function `q.main.S2.opAssign` cannot be used because it is annotated with `@disable`
---

No reason is given as to why it's been disabled, so I really don't know what the problem is, but removing the copy constructor fixes the problem (which obviously is unacceptable for code that needs a copy constructor), and adding inout to opAssign fixes the problem. However, putting inout on an opAssign makes _no_ sense, because outside of really weird edge cases when the type isn't actually holding the data, you can't assign to a const or immutable variable. And opAssign with inout on it shouldn't even compile if it assigns to any members (like this opAssign does), since the object could be const or immutable underneath the hood. If opAssign isn't templated, then putting inout on it gives the proper error (I've reported the issue with inout and opAssign here: https://issues.dlang.org/show_bug.cgi?id=24561).

Regardless, something really weird is happening with opAssign to make it so that you have to declare it with inout and templatize it to make example code work.

And note that it's only S2 which has issues. Assigning to an S1 is fine. So, it seems likely that something funny is going on with the opAssign generated for S1 which then makes it so that the opAssign for S2 cannot be generated.

Maybe there are actually is a good reason for this situation, and someone can explain it, but certainly from what I can see, this should be considered a compiler bug. It should _never_ be required to annotate opAssign with mutability qualifiers, and in the vast majority of cases, it would make no sense for the compiler to generate an opAssign with any mutability qualifiers, since its implementation wouldn't work anyway.

I'm marking this a blocker, because it's blocking stuff that I'm trying to do at Symmetry. Copy constructors and opAssign need to play together nicely, and they're clearly not. Part of the problem is that the language basically forces you to put inout on a copy constructor right now, but even if that problem is fixed, it should still work to have a copy constructor with inout while simultaneously having an opAssign with no mutability qualifiers - and it _does_ work for the type itself and the type containing it, but once you go one level deeper, it doesn't. Instead, it just gives a cryptic error message about opAssign having been disabled.

--