Skip to content

Commit 35727fd

Browse files
authored
Disallow discarding an assignment to a struct rvalue (#22369)
Error applies when struct has no pointer fields. (This could be tightened to apply when a pointer field is not tail mutable). My plan is to enable this for the 2024 edition, as it can break a user-defined op overload which has a (non-pointer) field but mutates a global. That should be a rare case and the compiler suggests workarounds. Note that requiring weak purity (for e.g. `opAssign`) would mean the error wouldn't detect bug-prone cases that aren't marked/inferred pure. Require 2024 edition for error
1 parent 18df84a commit 35727fd

5 files changed

Lines changed: 98 additions & 25 deletions

File tree

compiler/src/dmd/expression.d

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2363,6 +2363,7 @@ extern (C++) final class CallExp : UnaExp
23632363
bool inDebugStatement; /// true if this was in a debug statement
23642364
bool ignoreAttributes; /// don't enforce attributes (e.g. call @gc function in @nogc code)
23652365
bool isUfcsRewrite; /// the first argument was pushed in here by a UFCS rewrite
2366+
bool fromOpAssignment; // set when operator overload method call from assignment (2024 edition)
23662367
VarDeclaration vthis2; // container for multi-context
23672368
Expression loweredFrom; // set if this is the result of a lowering
23682369

compiler/src/dmd/expressionsem.d

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5301,6 +5301,17 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
53015301
setError();
53025302
}
53035303

5304+
// `e` calls opAssign, opOpAssign, opUnary!++, opUnary!-- when lowered from an assignment
5305+
private void setOpOverloadAssign(Expression e)
5306+
{
5307+
result = e;
5308+
auto ce = e.isCallExp();
5309+
// rvalue error in discardValue from 2024 edition
5310+
if (!ce || !sc.hasEdition(Edition.v2024))
5311+
return;
5312+
ce.fromOpAssignment = true;
5313+
}
5314+
53045315
/**************************
53055316
* Semantically analyze Expression.
53065317
* Determine types, fold constants, etc.
@@ -9171,10 +9182,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
91719182
{
91729183

91739184
if (Expression e = exp.opOverloadBinaryAssign(sc, aliasThisStop))
9174-
{
9175-
result = e;
9176-
return;
9177-
}
9185+
return setOpOverloadAssign(e);
91789186

91799187
Expression e;
91809188
if (exp.e1.op == EXP.arrayLength)
@@ -11811,10 +11819,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
1181111819

1181211820
// printf("PreExp::semantic('%s')\n", toChars());
1181311821
if (Expression e = exp.opOverloadUnary(sc))
11814-
{
11815-
result = e;
11816-
return;
11817-
}
11822+
return setOpOverloadAssign(e);
1181811823

1181911824
// Rewrite as e1+=1 or e1-=1
1182011825
Expression e;
@@ -12554,10 +12559,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
1255412559
else if (exp.op == EXP.assign)
1255512560
{
1255612561
if (Expression e = exp.isAssignExp().opOverloadAssign(sc, aliasThisStop))
12557-
{
12558-
result = e;
12559-
return;
12560-
}
12562+
return setOpOverloadAssign(e);
1256112563
}
1256212564
else
1256312565
assert(exp.op == EXP.blit);
@@ -12574,10 +12576,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
1257412576
if (exp.op == EXP.assign && !exp.e2.implicitConvTo(exp.e1.type))
1257512577
{
1257612578
if (Expression e = exp.isAssignExp().opOverloadAssign(sc, aliasThisStop))
12577-
{
12578-
result = e;
12579-
return;
12580-
}
12579+
return setOpOverloadAssign(e);
1258112580
}
1258212581
if (exp.e2.checkValue())
1258312582
return setError();
@@ -13242,10 +13241,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
1324213241
override void visit(PowAssignExp exp)
1324313242
{
1324413243
if (Expression e = exp.opOverloadBinaryAssign(sc, aliasThisStop))
13245-
{
13246-
result = e;
13247-
return;
13248-
}
13244+
return setOpOverloadAssign(e);
1324913245

1325013246
if (exp.suggestOpOpAssign(sc, parent) ||
1325113247
exp.e1.checkReadModifyWrite(exp.op, exp.e2))
@@ -13319,13 +13315,9 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
1331913315

1332013316
override void visit(CatAssignExp exp)
1332113317
{
13322-
1332313318
//printf("CatAssignExp::semantic() %s\n", exp.toChars());
1332413319
if (Expression e = exp.opOverloadBinaryAssign(sc, aliasThisStop))
13325-
{
13326-
result = e;
13327-
return;
13328-
}
13320+
return setOpOverloadAssign(e);
1332913321

1333013322
if (exp.suggestOpOpAssign(sc, parent))
1333113323
return setError();

compiler/src/dmd/frontend.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2594,6 +2594,7 @@ class CallExp final : public UnaExp
25942594
bool inDebugStatement;
25952595
bool ignoreAttributes;
25962596
bool isUfcsRewrite;
2597+
bool fromOpAssignment;
25972598
VarDeclaration* vthis2;
25982599
Expression* loweredFrom;
25992600
static CallExp* create(Loc loc, Expression* e, Array<Expression* >* exps);

compiler/src/dmd/sideeffect.d

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,23 @@ bool discardValue(Expression e)
258258
error(e.loc, "discarded assignment to indexed array literal");
259259
}
260260
}
261+
// check assignment to struct rvalue
262+
auto ce = e.isCallExp();
263+
if (ce && ce.fromOpAssignment)
264+
{
265+
if (auto dve = ce.e1.isDotVarExp())
266+
{
267+
auto lhs = dve.e1;
268+
auto ts = lhs.type.isTypeStruct();
269+
if (ts && !lhs.isLvalue() && !ts.sym.hasPointerField) // Don't disallow writing to data through a pointer field
270+
{
271+
error(lhs.loc, "assignment to struct rvalue `%s` is discarded",
272+
lhs.toChars());
273+
errorSupplemental(e.loc, "if the assignment is needed to modify a global, call `%s` directly or use an lvalue",
274+
dve.var.toChars());
275+
}
276+
}
277+
}
261278
return false;
262279
}
263280
switch (e.op)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
TEST_OUTPUT:
3+
---
4+
fail_compilation/struct_rvalue_assign.d(16): Error: assignment to struct rvalue `foo()` is discarded
5+
fail_compilation/struct_rvalue_assign.d(16): if the assignment is needed to modify a global, call `opAssign` directly or use an lvalue
6+
fail_compilation/struct_rvalue_assign.d(17): Error: assignment to struct rvalue `foo()` is discarded
7+
fail_compilation/struct_rvalue_assign.d(17): if the assignment is needed to modify a global, call `opOpAssign` directly or use an lvalue
8+
fail_compilation/struct_rvalue_assign.d(18): Error: assignment to struct rvalue `foo()` is discarded
9+
fail_compilation/struct_rvalue_assign.d(18): if the assignment is needed to modify a global, call `opUnary` directly or use an lvalue
10+
---
11+
*/
12+
module sra 2024;
13+
14+
void main()
15+
{
16+
foo() = S.init;
17+
foo() += 5;
18+
++foo();
19+
*foo(); // other unary ops may be OK
20+
21+
// allowed
22+
foo().opAssign(S.init);
23+
foo().opOpAssign!"+"(5);
24+
foo().opUnary!"++"();
25+
}
26+
27+
S foo() => S.init;
28+
29+
struct S
30+
{
31+
int i;
32+
33+
void opAssign(S s);
34+
void opOpAssign(string op : "+")(int);
35+
void opUnary(string op : "++")();
36+
void opUnary(string op : "*")();
37+
}
38+
39+
void test()
40+
{
41+
int i;
42+
43+
static struct Ptr
44+
{
45+
int* p;
46+
void opAssign(int rhs) { *p = rhs; }
47+
}
48+
Ptr(&i) = 1; // allowed
49+
50+
struct Nested
51+
{
52+
void opAssign(int rhs) { i = rhs; }
53+
}
54+
Nested() = 1; // allowed
55+
56+
static struct StaticOp
57+
{
58+
static si = 0;
59+
static void opAssign(int rhs) { si = rhs; }
60+
}
61+
StaticOp() = 1; // allowed
62+
}

0 commit comments

Comments
 (0)