November 11
https://issues.dlang.org/show_bug.cgi?id=24854

          Issue ID: 24854
           Summary: An @disabled opAssign is generated when it doesn't
                    need to be @disabled
           Product: D
           Version: D2
          Hardware: All
                OS: All
            Status: NEW
          Severity: enhancement
          Priority: P1
         Component: dmd
          Assignee: nobody@puremagic.com
          Reporter: issues.dlang@jmdavisProg.com

This is kind of an ugly corner case, so it could be argued that it should be left as-is, but we could be generating an opAssign that works when we aren't. So, I don't know if this is really a bug or just an enhancement request. It could also be argued that once @disable gets involved, you really need to implement stuff manually, since it does get kind of weird otherwise. But I'm creating this issue so that the current situation is at least documented.

In any case, this code

```
void main()
{
    import std.traits;

    static struct Member
    {
        @disable void opAssign(Member) {}
        void opAssign(ref Member) {}
    }

    static struct S { Member member; }

    Member m;
    Member m2;
    m = m2;
    m = Member.init;

    S s;
    S s2;
    s = s2;
    s = S.init;
}
```

results in

```
q.d(16): Error: function `q.main.Member.opAssign` cannot be used because it is
annotated with `@disable`
q.d(20): Error: generated function `q.main.S.opAssign` cannot be used because
it is annotated with `@disable`
q.d(21): Error: generated function `q.main.S.opAssign` cannot be used because
it is annotated with `@disable`
```

This code

```
void main()
{
    import std.traits;

    static struct Member
    {
        @disable void opAssign(Member) {}
        void opAssign(ref Member) {}
    }

    mixin listOpAssign!Member;
    pragma(msg, "");

    static struct S { Member member; }
    mixin listOpAssign!S;
}

template listOpAssign(T)
{
    static if(__traits(hasMember, T, "opAssign"))
    {
        pragma(msg, "Overloads of opAssign for " ~ T.stringof);
        pragma(msg, "---");

        static foreach(sym; __traits(getOverloads, T, "opAssign"))
        {
            pragma(msg, typeof(sym).stringof ~ ": " ~
                        (__traits(isDisabled, sym) ? "disabled" : "NOT
disabled"));
        }
    }
    else
        pragma(msg, T.stringof ~ " has no opAssign");
}
```

prints

```
Overloads of opAssign for Member
---
void(Member __param_0): disabled
void(ref Member __param_0): NOT disabled

Overloads of opAssign for S
---
ref S(S p): disabled
```

So, Member is as expected. The rvalue overload is @disabled (and thus results in a compilation error when used), and the lvalue overload works just fine.

On the other hand, with S, only a single overload is created, and it's @disabled in spite of the fact that it doesn't need to be. The obvious implementation would be

```
ref opAssign(S p)
{
    this.member = p.member;
}

ref opAssign(ref S p)
{
    this.member = p.member;
}
```

And because Member has a working opAssign that takes an lvalue, this could work.

Now, the reverse situation can't work, I don't think (at least not without doing some gymnastics with extra copies of S's member field) - that is when Member has an @disabled lvalue overload and a working rvalue overload. That currently results in exactly the same @disabled signature for opAssign on Member. However, it becomes problematic to implement opAssign in S even though there is a working one in Member, because while you might pass an rvalue to S's opAssign, S's opAssign will then naturally need to use an lvalue to assign to its member field, and that overload of Member's opAssign is @disabled. It _could_ be worked around by creating a copy that's an rvalue, but that seems like it's going too far for a compiler-generated function, and anyone who really wants that could do it themselves.

So, I don't know if we want to fix this situation or not, but it did surprise me, since I expected that since Member had a working opAssign, S would get one as well, and it doesn't.

--