Consider:
shared int x;
(cast() x) = 5;
assert(x == 5);
So the cast yields an lvalue. However:
shared int x;
auto p = &(cast() x);
This does not compile claiming that the result of cast is an rvalue. Should pass and use an lvalue.
Same for this:
int x;
(cast(shared) x) = 5;
assert(x == 5);
...
auto p = &(cast(shared) x);
Comment #1 by ag0aep6g — 2019-03-19T16:13:44Z
Dupliate of issue 17729?
Comment #2 by dlang-bot — 2019-03-21T12:04:15Z
@RazvanN7 created dlang/dmd pull request #9474 "Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue" fixing this issue:
- Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue
https://github.com/dlang/dmd/pull/9474
Comment #3 by pro.mathias.lang — 2019-03-23T09:30:54Z
> So the cast yields an lvalue.
Only for shared, and probably because its semantic are not defined.
If you replace this shared with `const` or `immutable`, of course this `cast` is rejected, even if the error message is somewhat confusing ("Error: cannot modify constant cast(int)x", instead of mentioning lvalue).
> This does not compile claiming that the result of cast is an rvalue. Should pass and use an lvalue.
I don't see any reason it should pass. If anything, the first example should be disallowed. What you aim to do can be equally done by taking a pointer to the qualified variable, and casting that pointer.
```
shared int x;
auto p = (cast(int*) &x);
```
Comment #4 by andrei — 2019-03-23T15:47:55Z
Mathias, the reason for which we want cast() and cast(shared) to compile has to do with a DIP in the works on __mutable. That would require inserting a manual cast (either cast() or cast(shared)) when the compiler is unable to assess whether the data originated as unqualified or immutable.
Yes, we could take the address and cast it but it's more difficult to specify and verify that two operations must be done in immediate sequence, as opposed to one operation. I think there may be a few cases in which two successive operations are semantically checked together but wouldn't want to add to those.
Comment #5 by uplink.coder — 2019-03-24T08:53:55Z
> the reason for which we want cast() and cast(shared) to compile has to do with a DIP in the works on __mutable.
That should have been in the issue description, as it at least gives a "tangible benefit" for which one may break the language.
> it but it's more difficult to specify and verify that two operations must be done in immediate sequence, as opposed to one operation
That's very true, however in this case it'd be bought by making the semantics of cast much more complicated. I.E. the complexity of __mutable would be pushed into cast, considering that people relay on cast to work properly and intuitively whereas __mutable is not relied upon by anyone; Such changes should be considered carefully.
> I think there may be a few cases in which two successive operations are semantically checked together but wouldn't want to add to those.
Also very true!
However taking the address of a value is not allowed to modify it in any way. Therefore taking the address is at least in first approximation semantically invariant with regards to the value itself. The address that & yielded is also required not to change, those facts can be used in the semantic definition of mutable and will support the desired semantics.
Comment #6 by andrei — 2019-03-24T12:24:19Z
>That's very true, however in this case it'd be bought by making the semantics of cast much more complicated. I.E. the complexity of __mutable would be pushed into cast, considering that people relay on cast to work properly and intuitively whereas __mutable is not relied upon by anyone; Such changes should be considered carefully.
This is nonsense, sigh. Can't even start to debate... There's no complexity tossed around? What's proper and intuitive and what isn't?
Applied to primitive types, cast() and cast(qualifier) are currently meaningless.
immutable int a;
auto b = cast() a;
auto c = int(a);
assert(is(typeof(b) == typeof(c)));
That cast didn't really cast anything because the same code can be written without. Same goes the other way:
int a;
auto b = cast(immutable) a;
immutable c = a;
assert(is(typeof(b) == typeof(c)));
Again, a nominal cast that really doesn't cast.
Comment #7 by pro.mathias.lang — 2019-03-24T13:33:30Z
> Mathias, the reason for which we want cast() and cast(shared) to compile has to do with a DIP in the works on __mutable. That would require inserting a manual cast (either cast() or cast(shared)) when the compiler is unable to assess whether the data originated as unqualified or immutable.
Its relationship with `__mutable` wasn't mentioned anywhere in the original post, although it wasn't too hard to guess to be fair.
OP describes cast as supposed to return lvalues, which is the opposite of how the language work, except for an unfinished type constructor. Even if it's a convenient property for your current focus, it is the oddity here, not the norm.
> Yes, we could take the address and cast it but it's more difficult to specify and verify that two operations must be done in immediate sequence, as opposed to one operation. I think there may be a few cases in which two successive operations are semantically checked together but wouldn't want to add to those.
The point is that there is no bug here. `cast` returning an rvalue is very clearly intended. Changing this should go through the proper process, so that the benefit and impact of the change is considered from the PoV of the whole language, not just `__mutable`.
Comment #8 by andrei — 2019-03-26T16:07:37Z
It seems the main anomaly is that cast() yields (sometimes...) an lvalue for shared data. Razvan, can you investigate if code would break if we forced cast() on shared data to always return an rvalue?
Comment #9 by dlang-bot — 2019-03-28T13:35:57Z
@RazvanN7 created dlang/dmd pull request #9505 "Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue" fixing this issue:
- Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue
https://github.com/dlang/dmd/pull/9505
Comment #10 by razvan.nitu1305 — 2019-03-28T13:56:17Z
(In reply to Andrei Alexandrescu from comment #8)
> It seems the main anomaly is that cast() yields (sometimes...) an lvalue for
> shared data. Razvan, can you investigate if code would break if we forced
> cast() on shared data to always return an rvalue?
The intention in the compiler code is that casts should always return rvalues, however the optimizer can simply bypass the check sometimes. For example:
const(int) x;
cast()x = 5;
Here the optimizer sees that x is a constant and rewrites the cast to `cast(int)0`; later on when the lhs expression is analyzed to see if it is modifiable you get the confusing error that Mathias pointed out. In the case of `shared x` it cannot constfold x since the variable could be modified from another thread so it simply drops `shared` from the lhs type. I would argue that an optimizing pass should be done only after semantic is done.
It seems that the simplest way to deal with these cases is to simply allow cast expressions to return lvalue in some situations; these situations are already described in the spec [1], under the "Lvalue" section.
[1] https://dlang.org/spec/expression.html#definitions-and-terms
Comment #11 by dlang-bot — 2019-05-24T03:57:19Z
dlang/dmd pull request #9505 "Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue" was merged into master:
- 91e7151838a703e9c31cf308f3711ddee47c86e6 by RazvanN7:
Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue
https://github.com/dlang/dmd/pull/9505
Comment #12 by ag0aep6g — 2019-05-25T13:08:28Z
*** Issue 17729 has been marked as a duplicate of this issue. ***
Comment #13 by kinke — 2019-06-30T16:16:36Z
It does yield an lvalue now with v2.087, but a wrong one (temporary, I guess):
void main()
{
const x = 0;
const p = &x;
(cast() x) = 5; // wrongly const-folded to 0 = 5
assert(*p == 5); // fails, still 0
}
Comment #14 by ag0aep6g — 2019-06-30T17:59:24Z
(In reply to kinke from comment #13)
> It does yield an lvalue now with v2.087, but a wrong one (temporary, I
> guess):
>
> void main()
> {
> const x = 0;
> const p = &x;
> (cast() x) = 5; // wrongly const-folded to 0 = 5
> assert(*p == 5); // fails, still 0
> }
That code is invalid. Mutating x has undefined behavior.
Comment #15 by kinke — 2019-06-30T22:30:07Z
More to the point:
void main()
{
const x = 0;
assert(&x == &(cast() x)); // fails; lowered to `assert(& x is &0)`
}
Comment #16 by razvan.nitu1305 — 2019-07-02T09:18:08Z
The root problem here is that dmd does optimizations and semantic analysis in the same pass. Will try to fix this.
Comment #17 by dlang-bot — 2019-07-02T11:44:21Z
@RazvanN7 created dlang/dmd pull request #10121 "Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue" fixing this issue:
- Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue
https://github.com/dlang/dmd/pull/10121
Comment #18 by dlang-bot — 2019-07-02T21:04:40Z
@kinke created dlang/dmd pull request #10124 "[WIP] [stable] Don't optimize() lhs lvalues of (bin)assign expressions to constants " mentioning this issue:
- Respect `keepLvalue` when optimizing/const-folding a CastExp
One aspect of issue 19754.
https://github.com/dlang/dmd/pull/10124
Comment #19 by dlang-bot — 2019-07-26T18:13:07Z
@kinke created dlang/dmd pull request #10220 "[stable] Revert "Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue"" fixing this issue:
- Revert "Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue"
This reverts commit 91e7151838a703e9c31cf308f3711ddee47c86e6.
Conflicts:
src/dmd/expressionsem.d
src/dmd/typesem.d
test/fail_compilation/fail17491.d
https://github.com/dlang/dmd/pull/10220
Comment #20 by dlang-bot — 2019-07-29T10:15:46Z
dlang/dmd pull request #10220 "[stable] Revert "Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue"" was merged into stable:
- 0b4a3a31519159369fad8a727520de9ae5b78949 by Martin Kinkelin:
Revert "Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue"
This reverts commit 91e7151838a703e9c31cf308f3711ddee47c86e6.
Conflicts:
src/dmd/expressionsem.d
src/dmd/typesem.d
test/fail_compilation/fail17491.d
https://github.com/dlang/dmd/pull/10220
Comment #21 by dlang-bot — 2019-07-30T15:38:13Z
@wilzbach updated dlang/dmd pull request #10232 "Merge remote-tracking branch 'upstream/stable' into merge_stable" fixing this issue:
- Revert "Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue"
This reverts commit 91e7151838a703e9c31cf308f3711ddee47c86e6.
Conflicts:
src/dmd/expressionsem.d
src/dmd/typesem.d
test/fail_compilation/fail17491.d
https://github.com/dlang/dmd/pull/10232
Comment #22 by dlang-bot — 2020-09-25T09:46:15Z
dlang/dmd pull request #10124 "Fix Issue 19754 - Re-apply #9505 and fix it, making isLvalue() logic more restrictive wrt. literals" was merged into master:
- 5b02408b35891643a2541e4ad52234b118bc9472 by Martin Kinkelin:
Revert "Revert "Fix Issue 19754 - cast() sometimes yields lvalue, sometimes yields rvalue""
This reverts commit 0b4a3a31519159369fad8a727520de9ae5b78949.
- a47de9b85230da3fa9e3ac7b604c075e387532d1 by Martin Kinkelin:
Respect `keepLvalue` when optimizing/const-folding a CastExp
One aspect of issue 19754.
https://github.com/dlang/dmd/pull/10124