Issue 21175 - opAssign should be allowed to return void and let the compiler take care of chained assignments
Summary: opAssign should be allowed to return void and let the compiler take care of c...
Status: NEW
Alias: None
Product: D
Classification: Unclassified
Component: dmd (show other issues)
Version: D2
Hardware: All All
: P4 enhancement
Assignee: No Owner
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2020-08-18 21:39 UTC by Andrei Alexandrescu
Modified: 2023-02-22 14:27 UTC (History)
4 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Andrei Alexandrescu 2020-08-18 21:39:28 UTC
The return value of opAssign should always be `this` of type `ref T`. Literally anything else is a bug, and that makes for a poor convention. The compiler should take care of all that. Means user code is free of the convention and can return void.

Chained assignments should be lowered as follows. Currently e1 = e2 = e3 is lowered as:

e1.opAssign(e2.opAssign(e3))

It should be lowered as:

(ref T x) { x.opAssign(e3); e1.opAssign(x); }(e2)

meaning e2 is evaluated first, then e3 is evaluated and assigned to (the result of evaluating) e2, then then x is assigned to (the result of evaluating) e1.
Comment 1 Paul Backus 2020-08-19 00:45:15 UTC
I think a lowering that works for all cases is

    e1 = e2

to

    auto ref typeof(e1) (auto ref typeof(e1) v1) {
        v1.opAssign(e2);
        return v1;
    }(e1)

This can be expanded recursively to handle any number of chained assignments (e.g., replace e2 with `e2a = e2b`).

The only catch is that D doesn't currently allow function literals that return `auto ref`.
Comment 2 Dennis 2021-11-18 15:23:35 UTC
N.b. this is relevant to dip1000, since this works:

```
@safe:
struct S {
	int* x;
	void opAssign(return scope S other) scope {
		this.x = other.x;
	}
}

void main() {
	scope S b;
	scope S a;
	a = b;
}
```

But this doesn't:
```
@safe:
struct S {
	int* x;
	ref S opAssign(return scope S other) return scope {
		this.x = other.x;
		return this;
	}
}

void main() {
	scope S b;
	scope S a;
	a = b;
}
```

source/apps/retscope.d(7): Error: scope variable `other` assigned to `this` with longer lifetime

The return destination of `return scope S other` was `this` when `opAssign` returned `void`, but now the return destination is the return value. Example in Phobos:

https://github.com/dlang/phobos/blob/62780daf85c4c57bbc805e1e6499a33a852a2802/std/datetime/systime.d#L696

The only reason this compiles currently is because of issue 20149
Comment 3 Bolpat 2023-02-22 14:27:34 UTC
(In reply to Andrei Alexandrescu from comment #0)
> The return value of opAssign should always be `this` of type `ref T`.
> Literally anything else is a bug, and that makes for a poor convention. The
> compiler should take care of all that. Means user code is free of the
> convention and can return void.
> 
> Chained assignments should be lowered as follows. Currently e1 = e2 = e3 is
> lowered as:
> 
> e1.opAssign(e2.opAssign(e3))
> 
> It should be lowered as:
> 
> (ref T x) { x.opAssign(e3); e1.opAssign(x); }(e2)
> 
> meaning e2 is evaluated first, then e3 is evaluated and assigned to (the
> result of evaluating) e2, then then x is assigned to (the result of
> evaluating) e1.

The idea is right in principle, but what comes to my mind is C++ valarray. A lot of operator= in there return void. I guess that’s because an assignment operator should not return something else than a reference to the assigned object. If that’s not possible, return `void`. D has better indexing operators than C++:
`obj[index] = rhs` can lower to one function call of `opIndexAssign`, meaning that the example is invalid in D; but that is due to index+assignment being a special case in D. There are similar scenarios imaginable in which the issue is relevant.