[ 
https://issues.apache.org/jira/browse/GROOVY-12515?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Paul King updated GROOVY-12515:
-------------------------------
    Description: 
Description

A compound Elvis-assignment whose left-hand side is a property with a 
non-trivial receiver evaluates that receiver twice: once for the read in the 
Elvis test and once more for the store. The property getter and setter each run 
once. The same happens dynamically, under @TypeChecked and under @CompileStatic.

{code:groovy}
class Bean {
    def prop
}

class Holder {
    Bean bean = new Bean()
    int receiverEvals = 0
    Bean getBean() { receiverEvals++; bean }
}

def h = new Holder()
h.bean.prop ?= 'x'
assert h.receiverEvals == 1   // fails: 2
{code}

Expected: the receiver (h.bean) is evaluated once, as the left-hand side of an 
assignment is in Java and as a[i] ?= b has evaluated its receiver and index 
since GROOVY-12099. A receiver that is not referentially transparent (a factory 
call, a cursor, a side-effecting getter) otherwise reads from one object and 
writes to another.

Actual: BinaryExpressionHelper.evaluateElvisEqual rewrites lhs ?= rhs to lhs = 
(lhs ?: rhs) and hands it to evaluateEqual, which evaluates the property's 
object expression for the store after the Elvis expression has already 
evaluated it for the read. GROOVY-12099 added a temporary-slot path for 
non-safe subscripts only; property targets still take the generic path.

??= (GROOVY-12442) is built on the same rewrite, so it inherits the behaviour. 
The fix should cover both operators.

Fix sketch

Extend the GROOVY-12099 treatment in evaluateElvisEqual to a PropertyExpression 
left-hand side: store the object expression in a temporary (storeInTemporary), 
build a PropertyExpression over the resulting VariableSlotLoader, and use it 
for both the Elvis read and the store. Safe (?.) and spread-safe property 
targets keep their existing behaviour, as safe subscripts do. An implicit-this 
receiver needs no temporary.

Tests

A bugs/Groovy12515 test in the shape of Groovy12099: receiver evaluated once 
when the value is kept and when the fallback is used, non-idempotent receiver, 
left-to-right order, for ?= and ??=, dynamically and under @CompileStatic. 
NullCoalescingOperatorTest.testAssignToPropertyCallsAccessorsOnce already 
asserts getter-once and setter-once for ??=.

  was:
Description

A compound Elvis-assignment whose left-hand side is a property with a 
non-trivial receiver evaluates that receiver twice: once for the read in the 
Elvis test and once more for the store. The property getter and setter each run 
once. The same happens dynamically, under @TypeChecked and under @CompileStatic.

class Bean {
    def prop
}
class Holder {
    Bean bean = new Bean()
    int receiverEvals = 0
    Bean getBean() { receiverEvals++; bean }
}

def h = new Holder()
h.bean.prop ?= 'x'
assert h.receiverEvals == 1   // fails: 2

Expected: the receiver (h.bean) is evaluated once, as the left-hand side of an 
assignment is in Java and as a[i] ?= b has evaluated its receiver and index 
since GROOVY-12099. A receiver that is not referentially transparent (a factory 
call, a cursor, a side-effecting getter) otherwise reads from one object and 
writes to another.

Actual: BinaryExpressionHelper.evaluateElvisEqual rewrites lhs ?= rhs to lhs = 
(lhs ?: rhs) and hands it to evaluateEqual, which evaluates the property's 
object expression for the store after the Elvis expression has already 
evaluated it for the read. GROOVY-12099 added a temporary-slot path for 
non-safe subscripts only; property targets still take the generic path.

??= (GROOVY-12442) is built on the same rewrite, so it inherits the behaviour. 
The fix should cover both operators.

Fix sketch

Extend the GROOVY-12099 treatment in evaluateElvisEqual to a PropertyExpression 
left-hand side: store the object expression in a temporary (storeInTemporary), 
build a PropertyExpression over the resulting VariableSlotLoader, and use it 
for both the Elvis read and the store. Safe (?.) and spread-safe property 
targets keep their existing behaviour, as safe subscripts do. An implicit-this 
receiver needs no temporary.

Tests

A bugs/Groovy<NNNNN> test in the shape of Groovy12099: receiver evaluated once 
when the value is kept and when the fallback is used, non-idempotent receiver, 
left-to-right order, for ?= and ??=, dynamically and under @CompileStatic. 
NullCoalescingOperatorTest.testAssignToPropertyCallsAccessorsOnce already 
asserts getter-once and setter-once for ??=.


> Elvis-assignment on a property evaluates the receiver twice (a.b.c ?= d, 
> a.b.c ??= d)
> -------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12515
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12515
>             Project: Groovy
>          Issue Type: Bug
>          Components: bytecode
>    Affects Versions: 6.0.0
>            Reporter: Paul King
>            Priority: Major
>
> Description
> A compound Elvis-assignment whose left-hand side is a property with a 
> non-trivial receiver evaluates that receiver twice: once for the read in the 
> Elvis test and once more for the store. The property getter and setter each 
> run once. The same happens dynamically, under @TypeChecked and under 
> @CompileStatic.
> {code:groovy}
> class Bean {
>     def prop
> }
> class Holder {
>     Bean bean = new Bean()
>     int receiverEvals = 0
>     Bean getBean() { receiverEvals++; bean }
> }
> def h = new Holder()
> h.bean.prop ?= 'x'
> assert h.receiverEvals == 1   // fails: 2
> {code}
> Expected: the receiver (h.bean) is evaluated once, as the left-hand side of 
> an assignment is in Java and as a[i] ?= b has evaluated its receiver and 
> index since GROOVY-12099. A receiver that is not referentially transparent (a 
> factory call, a cursor, a side-effecting getter) otherwise reads from one 
> object and writes to another.
> Actual: BinaryExpressionHelper.evaluateElvisEqual rewrites lhs ?= rhs to lhs 
> = (lhs ?: rhs) and hands it to evaluateEqual, which evaluates the property's 
> object expression for the store after the Elvis expression has already 
> evaluated it for the read. GROOVY-12099 added a temporary-slot path for 
> non-safe subscripts only; property targets still take the generic path.
> ??= (GROOVY-12442) is built on the same rewrite, so it inherits the 
> behaviour. The fix should cover both operators.
> Fix sketch
> Extend the GROOVY-12099 treatment in evaluateElvisEqual to a 
> PropertyExpression left-hand side: store the object expression in a temporary 
> (storeInTemporary), build a PropertyExpression over the resulting 
> VariableSlotLoader, and use it for both the Elvis read and the store. Safe 
> (?.) and spread-safe property targets keep their existing behaviour, as safe 
> subscripts do. An implicit-this receiver needs no temporary.
> Tests
> A bugs/Groovy12515 test in the shape of Groovy12099: receiver evaluated once 
> when the value is kept and when the fallback is used, non-idempotent 
> receiver, left-to-right order, for ?= and ??=, dynamically and under 
> @CompileStatic. 
> NullCoalescingOperatorTest.testAssignToPropertyCallsAccessorsOnce already 
> asserts getter-once and setter-once for ??=.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to