[
https://issues.apache.org/jira/browse/GROOVY-12619?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18135650#comment-18135650
]
ASF GitHub Bot commented on GROOVY-12619:
-----------------------------------------
codeconsole opened a new pull request, #3145:
URL: https://github.com/apache/groovy/pull/3145
A compound assignment or an increment of a property evaluated the receiver
expression twice: once for the read and once for the write. This happened in
dynamic Groovy, under `@TypeChecked` and under `@CompileStatic`, and in 4.0.33,
5.0.8 and 6.0.0:
```groovy
class Box { int v = 1 }
class C {
int calls = 0
Box box = new Box()
Box obj() { calls++; box }
def m() { obj().v += 2; [calls, box.v] }
}
assert new C().m() == [1, 3] // was [2, 3]
```
The same was true of `obj().v++`, `++obj().v`, `obj().@f += 2`, `obj()?.v +=
2`, `b.inner.v += 1` with a side-effecting `getB()`, a static property reached
through an instance, and an extension property such as `obj().text += 'x'`.
Subscripts already evaluate the receiver once (GROOVY-12098).
`BinaryExpressionHelper` now evaluates the receiver of such a property once
into a temporary. This applies to compound assignment (in `eval`, before any
dispatcher-specific path) and to prefix and postfix `++` and `--`. The read and
the write then both use that temporary. Receivers without side effects stay in
place: variables (including `this` and `super`), classes, constants and a
qualified `Outer.this`. Under static compilation, `BinaryExpressionTransformer`
rewrites some compound assignments before code generation: operators that
resolve to a method, such as `String` or `BigDecimal` `+=`, and extension
properties (GROOVY-12524). Those rewrites now wrap the receiver in a
`TemporaryVariableExpression`, as GROOVY-5746 does for subscripts.
`Groovy12524` recorded the double evaluation of an extension property's
receiver and now expects a single one. The elvis assignment `?=` is left alone
(GROOVY-12515).
## Tests
`bugs.Groovy12619` covers compound assignment with several operators, the
expression value, evaluation order, prefix and postfix increment and decrement,
property chains, safe navigation, attributes, static properties, closures and
generic receivers, extension properties, and a `plusAssign` method. It runs
each case in dynamic, `@TypeChecked` and `@CompileStatic` mode and fails
without the change. So does
`Groovy12524.testCompoundAssignEvaluatesReceiverOnce`. The
`groovy.transform.stc`, `org.codehaus.groovy.classgen` (including `asm.sc`),
`org.codehaus.groovy.transform.traitx`, `groovy.operator`, `bugs` and `gls`
packages, plus the property, postfix, prefix and navigation tests in `groovy`,
pass. The Checkstyle and CodeNarc gates pass.
> Compound assignment and increment on a property evaluate the receiver
> expression twice
> --------------------------------------------------------------------------------------
>
> Key: GROOVY-12619
> URL: https://issues.apache.org/jira/browse/GROOVY-12619
> Project: Groovy
> Issue Type: Bug
> Affects Versions: 4.0.33, 5.0.8, 6.0.0
> Reporter: Scott Murphy Heiberg
> Priority: Major
>
> {code:groovy}
> class Box { int v = 1 }
> class C {
> int calls = 0
> Box box = new Box()
> Box obj() { calls++; box }
> def m() { obj().v += 2; [calls, box.v] }
> def n() { obj().v++; [calls, box.v] }
> }
> assert new C().m() == [1, 3]
> assert new C().n() == [1, 2]
> {code}
> Actual: m() returns [2, 3] and n() returns [2, 2]. The receiver obj() is
> called once for the read of the property and again for the write. The same
> happens in dynamic Groovy, under @TypeChecked and under @CompileStatic. It
> also happens for prefix and postfix increment and decrement, attribute access
> ({{obj().@f += 2}}), safe navigation ({{obj()?.v += 2}}), chains with a
> side-effecting getter, static properties reached through an instance, and
> extension properties such as File text. Under @CompileStatic, {{obj().d++}}
> on a BigDecimal property calls obj() three times.
> Expected: the receiver is evaluated once, before the right-hand side, and
> reused for the read and the write. Subscripts already behave this way
> (GROOVY-12098).
> Cause: the code generator evaluated the property expression once as an rvalue
> and once as the assignment target. The static compiler's rewrites to x.p =
> x.p.op(y) and to the extension setter reused the receiver expression on both
> sides. The elvis assignment form is GROOVY-12515.
> Seen in 4.0.33, 5.0.8 and 6.0.0.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)