Paul King created GROOVY-12700:
----------------------------------
Summary: SC: multiple-assignment declaration resolves a name on
the right-hand side to the variable being declared instead of the field or
property
Key: GROOVY-12700
URL: https://issues.apache.org/jira/browse/GROOVY-12700
Project: Groovy
Issue Type: Bug
Components: Static compilation
Affects Versions: 6.0.0, 5.1.3, 4.0.33
Reporter: Paul King
h3. Description
Under {{@CompileStatic}}, a multiple-assignment declaration whose right-hand
side is a list literal is compiled as a sequence of single declarations. When
an element on the right-hand side uses a name that the left-hand side declares,
the compiled code reads the newly declared local variable rather than the field
or property that the name refers to at that point. Dynamic Groovy and
{{@TypeChecked}} read the field or property.
{code:groovy}
class Foo {
String a = 'field a'
static d = 'static d'
//@groovy.transform.CompileStatic
def method() {
def (a, b) = [1, a]
def (c, d, e) = [1, 2, d]
println([a, b, c, d, e])
}
}
def f = new Foo()
f.method()
println f.a
println f.d
{code}
||Mode||Output of {{method()}}||
|dynamic|{{[1, field a, 1, 2, static d]}}|
|{{@TypeChecked}}|{{[1, field a, 1, 2, static d]}}|
|{{@CompileStatic}}|{{[1, 1, 1, 2, 2]}}|
The fields themselves are unchanged in every mode ({{field a}} and {{static d}}
are printed afterwards), so only the values read on the right-hand side differ.
The same happens with typed declarations. With {{String a = 'field a'}}, {{def
(int a, String b) = [1, a]}} returns {{[1, field a]}} dynamically and {{[1,
1]}} under {{@CompileStatic}}: the type checker checks {{String b}} against the
field, but the generated code reads the local {{int a}}. In every case the code
compiles without warnings and produces a different value at run time.
Reproduced with 4.0.32, 5.1.3, 6.0.0 and master.
h3. Cause
{{BinaryExpressionTransformer#transformMultipleAssignment}} (static
compilation, instruction selection phase) rewrites {{def (x, y) = [e1, e2]}}
into
{code:groovy}
def x = e1
def y = e2
{code}
For a declaration it emits the single declarations in order, so by the time
{{e2}} is evaluated, {{x}} is already a local variable in scope, and a
{{VariableExpression}} {{x}} in {{e2}} is generated as a read of that local. In
the source, and in the dynamic and type-checked code, every element of the list
is evaluated before any of the variables is declared.
The non-declaration branch of the same method ({{(x, y) = [y, x]}}) already
avoids the corresponding problem for plain assignments by first storing every
right-hand value in a temporary and then assigning the temporaries.
h3. Suggested fix
For declarations, evaluate the right-hand elements into temporaries before
declaring the variables, as the assignment branch does. This could be done only
when an element refers to one of the names being declared, to keep the current
bytecode for the common case.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)