[ 
https://issues.apache.org/jira/browse/GROOVY-12466?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18131627#comment-18131627
 ] 

ASF GitHub Bot commented on GROOVY-12466:
-----------------------------------------

paulk-asert merged PR #3011:
URL: https://github.com/apache/groovy/pull/3011




> GEP-15: compound assignment that resolves to a *Assign method is refused for 
> final parameters and for final fields used inside their own class
> ----------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12466
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12466
>             Project: Groovy
>          Issue Type: Bug
>          Components: Compiler, Static Type Checker
>    Affects Versions: 6.0.0
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>
> h2. Problem
> GEP-15 lets a class define {{plusAssign}}, {{minusAssign}}, 
> {{leftShiftAssign}} and the other {{*Assign}} methods. The 6.0 documentation 
> (core-operators, "compound assignment") says that when the compiler resolves 
> such a method statically, the receiver "is mutated in place, no reassignment 
> occurs (so {{final}} fields and variables can use compound assignment in that 
> case)". That holds for final local variables and for final fields reached 
> from outside their class. It does not hold for final parameters, or for final 
> fields used inside their own class:
> {code:groovy}
> @CompileStatic
> class Acc {
>     int total
>     void plusAssign(int v) { total += v }
> }
> @CompileStatic
> class H {
>     final Acc acc = new Acc()
>     int viaField() { acc += 1; acc.total }          // error: cannot modify 
> final field 'acc' outside of constructor.
>     int viaThis() { this.acc += 1; acc.total }      // error: cannot modify 
> final field 'acc' outside of constructor.
>     static int viaParam(final Acc a) { a += 1; a.total }  // error: Cannot 
> assign a value to final variable 'a'
>     static int viaLocal(Acc x) { final Acc a = x; a += 1; a.total }  // 
> compiles and runs: 1
> }
> @CompileStatic
> int outside() { def h = new H(); h.acc += 1; h.acc.total }  // compiles and 
> runs: 1
> {code}
> Results on 6.0.0:
> ||Target of {{+= 1}}||@CompileStatic||@TypeChecked||dynamic||
> |final local variable|compiles, runs|compiles, runs|refused (expected)|
> |final field from outside ({{h.acc}})|compiles, runs|compiles, runs|n/a|
> |final field inside its class ({{acc}} or 
> {{this.acc}})|*refused*|*refused*|refused (expected)|
> |final parameter|*refused*|*refused*|refused (expected)|
> The dynamic rows are expected. The documentation says the final check cannot 
> be relaxed there, because the compiler doesn't know whether a {{*Assign}} 
> method will respond at runtime. The {{CompoundAssignmentTest}} cases cover 
> the final local and the outside-field forms, but not these two.
> h2. Cause
> Two checks reject the assignment without considering the {{*Assign}} target 
> that static type checking records in 
> {{StaticTypesMarker.COMPOUND_ASSIGN_TARGET}}:
> * *Final parameters:* {{VariableScopeVisitor.checkFinalFieldAccess}} reports 
> "Cannot assign a value to final variable" for any assignment operator whose 
> target is a final {{Parameter}}. It runs during semantic analysis, before 
> type checking, so it cannot know whether a {{*Assign}} method will be found. 
> The later check in {{Verifier}} (the {{FinalVariableAnalyzer}} callback) 
> already skips expressions carrying {{COMPOUND_ASSIGN_TARGET}}, for parameters 
> as well as locals, but is never reached.
> * *Final fields:* {{ClassCompletionVerifier.checkFinalFieldAccess}} reports 
> "cannot modify final field ... outside of constructor" for any assignment 
> operator whose target is a final field named directly or through {{this}}. It 
> runs after type checking, so the marker is available, but it is not 
> consulted. A field reached through another object ({{h.acc}}) passes only 
> because this check ignores properties on anything other than {{this}}.
> h2. Proposed fix
> * In {{VariableScopeVisitor}}, report final parameters only for plain 
> assignment ({{=}}). Leave compound operators on final parameters to the 
> {{FinalVariableAnalyzer}} check in {{Verifier}}, which already reports "The 
> parameter \[a\] is declared final but is reassigned" when no {{*Assign}} 
> target was resolved, in dynamic code or when the type has no such method. The 
> error is still reported in those cases, with that message instead of the 
> current one.
> * In {{ClassCompletionVerifier}}, skip the final-field error when the 
> assignment's {{BinaryExpression}} carries {{COMPOUND_ASSIGN_TARGET}}. The 
> check currently receives only the left-hand expression, so it needs the 
> binary expression passed in as well.
> * Confirm that code generation emits no field store or local store for these 
> cases, in both static code generation and the classic code generation used by 
> {{@TypeChecked}}. A store to a final field outside a constructor would fail 
> bytecode verification. The final-local and outside-field cases show the 
> call-only path already works.
> h2. Tests
> Extend {{CompoundAssignmentTest}}:
> * Final parameter with {{plusAssign}}, under {{@CompileStatic}} and 
> {{@TypeChecked}}: compiles, mutates the argument in place, and does not 
> rebind the parameter.
> * Final instance field and final static field, used by simple name and 
> through {{this}} inside their own class: compiles and mutates in place.
> * {{leftShiftAssign}} and an {{@OperatorRename}}-renamed {{*Assign}} method, 
> for the same targets.
> * Still refused: the same forms in dynamic code; a type with only {{plus}} 
> and no {{plusAssign}}; plain {{=}} to a final parameter or field outside a 
> constructor.
> * Still allowed: assignment to a final field inside a constructor.
> h2. Related
> Found while checking PurityChecker against GEP-15 operators. That is a 
> separate ticket: PurityChecker does not treat {{a += v}} that resolves to 
> {{plusAssign}} as a mutation of {{a}}.



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

Reply via email to