[ 
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)

Reply via email to