diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 6767054b40ec..85037980f271 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -11642,6 +11642,10 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor // printf("PreExp::semantic('%s')\n", toChars()); if (Expression e = exp.opOverloadUnary(sc)) { + if (checkRvalueAssign(sc, exp.e1, Id.opUnary)) + { + e = ErrorExp.get(); + } result = e; return; } diff --git a/compiler/src/dmd/opover.d b/compiler/src/dmd/opover.d index 0fd85726a384..a85675d1036f 100644 --- a/compiler/src/dmd/opover.d +++ b/compiler/src/dmd/opover.d @@ -537,11 +537,35 @@ Expression opOverloadAssign(AssignExp e, Scope* sc, Type[2] aliasThisStop) bool choseReverse; if (auto result = pickBestBinaryOverload(sc, null, s, null, e, choseReverse)) + { + if (checkRvalueAssign(sc, e.e1, Id.opAssign)) + { + return ErrorExp.get(); + } return result; - + } return binAliasThis(e, sc, aliasThisStop); } +bool checkRvalueAssign(Scope *sc, Expression e, Identifier op) +{ + if (!sc.intypeof && sc.hasEdition(Edition.v2024) && + e.type && e.type.ty == Tstruct && !e.isLvalue()) + { + TypeStruct ts = cast(TypeStruct)e.type; + // nested struct may assign data outside of the struct, e.g. ae.utils.array.list(args) + if (!ts.sym.isNested()) + { + const char* action = op == Id.opAssign ? "assign to" : "modify"; + error(e.loc, "cannot %s struct rvalue `%s`", action, e.toChars()); + errorSupplemental(e.loc, "if the assignment is used for side-effects, call `%s` directly", + op.toChars()); + return true; + } + } + return false; +} + Expression opOverloadBinary(BinExp e, Scope* sc, Type[2] aliasThisStop) { if (Expression err = binSemanticProp(e, sc)) @@ -1004,6 +1028,10 @@ Expression opOverloadBinaryAssign(BinAssignExp e, Scope* sc, Type[2] aliasThisSt if (e.e1.type.isTypeError() || e.e2.type.isTypeError()) return ErrorExp.get(); + if (checkRvalueAssign(sc, e.e1, Id.opOpAssign)) + { + return ErrorExp.get(); + } AggregateDeclaration ad1 = isAggregate(e.e1.type); Dsymbol s = search_function(ad1, Id.opOpAssign); if (s && !(s.isTemplateDeclaration() || s.isOverloadSet())) diff --git a/compiler/test/fail_compilation/fail9936.d b/compiler/test/fail_compilation/fail9936.d index 0d7d44ae439b..345461efb593 100644 --- a/compiler/test/fail_compilation/fail9936.d +++ b/compiler/test/fail_compilation/fail9936.d @@ -3,7 +3,7 @@ TEST_OUTPUT: --- fail_compilation/fail9936.d(25): Error: `S().opBinary` isn't a template fail_compilation/fail9936.d(26): Error: `S().opBinaryRight` isn't a template -fail_compilation/fail9936.d(27): Error: `S().opOpAssign` isn't a template +fail_compilation/fail9936.d(27): Error: `s.opOpAssign` isn't a template fail_compilation/fail9936.d(29): Error: `S().opIndexUnary` isn't a template fail_compilation/fail9936.d(30): Error: `S().opUnary` isn't a template --- @@ -17,14 +17,14 @@ struct S auto opIndexUnary(S s) { return 1; } auto opUnary(S s) { return 1; } } -void main() +void f(S s) { static assert(!is(typeof( S() + S() ))); static assert(!is(typeof( 100 + S() ))); - static assert(!is(typeof( S() += S() ))); + static assert(!is(typeof( s += S() ))); S() + S(); 100 + S(); - S() += S(); + s += S(); +S()[0]; +S(); diff --git a/compiler/test/fail_compilation/struct_rvalue_assign.d b/compiler/test/fail_compilation/struct_rvalue_assign.d new file mode 100644 index 000000000000..81cc6a2d94d9 --- /dev/null +++ b/compiler/test/fail_compilation/struct_rvalue_assign.d @@ -0,0 +1,31 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/struct_rvalue_assign.d(16): Error: cannot assign to struct rvalue `foo()` +fail_compilation/struct_rvalue_assign.d(16): if the assignment is used for side-effects, call `opAssign` directly +fail_compilation/struct_rvalue_assign.d(17): Error: cannot modify struct rvalue `foo()` +fail_compilation/struct_rvalue_assign.d(17): if the assignment is used for side-effects, call `opOpAssign` directly +fail_compilation/struct_rvalue_assign.d(18): Error: cannot modify struct rvalue `foo()` +fail_compilation/struct_rvalue_assign.d(18): if the assignment is used for side-effects, call `opUnary` directly +--- +*/ +module sra 2024; + +void main() +{ + foo() = S.init; + foo() += 5; + ++foo(); + cast(void) ~foo(); // other unary ops are OK +} + +S foo() => S.init; + +struct S +{ + int i; + + void opAssign(S s) {} + void opOpAssign(string op : "+")(int) {} + void opUnary(string op)() {} +}