Paul King created GROOVY-12705:
----------------------------------

             Summary: SC: multiple assignment with a list literal that has more 
values than variables does not evaluate the extra values
                 Key: GROOVY-12705
                 URL: https://issues.apache.org/jira/browse/GROOVY-12705
             Project: Groovy
          Issue Type: Bug
          Components: Static compilation
    Affects Versions: 6.0.0, 5.1.3, 4.0.33
            Reporter: Paul King


h3. Description

When a multiple assignment has more values on the right-hand side than 
variables on the left, the extra values are ignored. In dynamic Groovy and 
under {{@TypeChecked}} they are still evaluated, from left to right, and only 
their results are discarded. Under {{@CompileStatic}}, a right-hand side that 
is a list literal is compiled as one assignment per variable, and the extra 
values are never evaluated, so their side effects are lost.

{code:groovy}
import groovy.transform.CompileStatic

class Dyn {
    List<String> log = []
    String v(String s) { log << s; s }
    def declare() { def (a, b) = [v('a'), v('b'), v('c')]; [a, b, log] }
    def assign()  { def a, b; (a, b) = [v('a'), v('b'), v('c')]; [a, b, log] }
}
@CompileStatic
class Stat {
    List<String> log = []
    String v(String s) { log << s; s }
    def declare() { def (a, b) = [v('a'), v('b'), v('c')]; [a, b, log] }
    def assign()  { def a, b; (a, b) = [v('a'), v('b'), v('c')]; [a, b, log] }
}
println "dynamic declare: ${new Dyn().declare()}  assign: ${new Dyn().assign()}"
println "static  declare: ${new Stat().declare()}  assign: ${new 
Stat().assign()}"
{code}

{noformat}
dynamic declare: [a, b, [a, b, c]]  assign: [a, b, [a, b, c]]
static  declare: [a, b, [a, b]]  assign: [a, b, [a, b]]
{noformat}

The output is the same with 2.4.12, 2.5.23, 3.0.25, 4.0.33, 5.1.3, 6.0.0 and 
current master (JDK 17). With {{@TypeChecked}} instead of {{@CompileStatic}}, 
the log is {{[a, b, c]}}, as in dynamic Groovy.

h3. Expected

{{v('c')}} is called under {{@CompileStatic}} too, after {{v('a')}} and 
{{v('b')}}, and its result is discarded. The variables get the same values as 
now.

A right-hand side that is not a list literal, for example a method returning a 
list, is not affected: the list is built in full in every mode.

h3. Cause

{{BinaryExpressionTransformer.transformMultipleAssignment}} turns {{(a, b) = 
[x, y, z]}} into a sequence of single assignments (through temporary variables 
for an assignment, directly for a declaration). Both forms stop at the shorter 
of the two sides, so a value without a variable is dropped instead of being 
evaluated.

h3. Suggested fix

Keep each extra value as an expression statement (or the declaration of an 
unused temporary), after the values that are assigned and before any variable 
is written, so that the values are evaluated in the same order as in dynamic 
Groovy.

h3. Backwards compatibility proposal

For anyone needing the existing behavior, a transform could be used. It then 
becomes opt-in and importantly, would be the same in every mode.

{code:groovy}
@Retention(SOURCE) @Target(LOCAL_VARIABLE)
@GroovyASTTransformationClass('probe.TrimExtraValuesTransform')
@interface TrimExtraValues { }

@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
class TrimExtraValuesTransform implements ASTTransformation {
    void visit(ASTNode[] nodes, SourceUnit source) {
        def decl = nodes[1]
        if (decl instanceof DeclarationExpression && 
decl.isMultipleAssignmentDeclaration()
                && decl.rightExpression instanceof ListExpression) {
            def values = decl.rightExpression.expressions
            int n = decl.tupleExpression.expressions.size()
            while (values.size() > n) values.remove(values.size() - 1)
        }
    }
}
{code}



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

Reply via email to