[
https://issues.apache.org/jira/browse/GROOVY-12630?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18135969#comment-18135969
]
ASF GitHub Bot commented on GROOVY-12630:
-----------------------------------------
paulk-asert opened a new pull request, #3160:
URL: https://github.com/apache/groovy/pull/3160
…eal setter
Under static compilation, an increment or decrement of a property whose
getter or setter is an extension method failed with "Access to ... is
forbidden", because code generation reads and writes the operand as a property.
Rewrite it into a getter call, next() or previous(), and a setter call,
evaluating the receiver once; a prefix expression yields the new value and a
postfix expression the old one. A bare name is left to code generation, which
already resolves one in a closure through its delegate.
Compound assignment to such a property now also accepts a real setter with
an extension getter. With a real setter only the getter the type checker
resolved is used, so a class's own getter is not replaced by a same-named
extension method.
> static-compilation gaps (`++`/`--` and real setter with extension getter)
> -------------------------------------------------------------------------
>
> Key: GROOVY-12630
> URL: https://issues.apache.org/jira/browse/GROOVY-12630
> Project: Groovy
> Issue Type: Bug
> Components: Static compilation
> Affects Versions: 6.0.0
> Reporter: Paul King
> Priority: Major
>
> h2. Description
> Under {{@CompileStatic}}, GROOVY-12524 made compound assignment ({{+=}},
> {{-=}}, {{?=}}, ...) work on a property whose accessors are extension
> methods. Two related forms still fail to compile. Both work in dynamic and
> {{@TypeChecked}} code, and both failed the same way in 6.0.0.
> # Prefix and postfix increment and decrement ({{++}}, {{--}}) on an extension
> property.
> # Compound assignment to a property with a real setter and an extension
> getter.
> h2. Example
> An extension module declares:
> {code:java}
> public class Box { public int v; }
> public class Mix {
> public String v = "a";
> public void setRs(String x) { v = "RS:" + x; } // real setter
> }
> public class BoxExtension {
> public static int getCount(Box b) { return b.v; }
> public static void setCount(Box b, int x) { b.v = x; }
> public static String getRs(Mix m) { return m.v + "E"; } // extension
> getter
> }
> {code}
> Then:
> {code:groovy}
> @groovy.transform.CompileStatic
> void test(Box b, Mix m) {
> b.count += 1 // compiles since GROOVY-12524
> b.count++ // Access to Box#count is forbidden
> ++b.count // Access to Box#count is forbidden
> b.count-- // Access to Box#count is forbidden
> m.rs += 'x' // Access to Mix#rs is forbidden
> }
> {code}
> The same failure occurs for a static extension property ({{Box.sx++}}) and
> for extension properties supplied by Groovy itself.
> The long forms compile and are the workaround:
> {code:groovy}
> b.count = b.count + 1
> m.rs = m.rs + 'x'
> {code}
> A real getter with an extension setter ({{m.rg += 'x'}}) already works.
> h2. Cause
> * *Increment and decrement.* Static compilation has no rewrite for
> {{PrefixExpression}} or {{PostfixExpression}}. Codegen loads the operand as a
> property, and that load does not consider extension getters. A plain read
> works only because {{PropertyExpressionTransformer}} first turns it into a
> getter call.
> * *Mixed accessors.* The GROOVY-12524 rewrite in
> {{BinaryExpressionTransformer}} applies only when the setter is an
> {{ExtensionMethodNode}}. When the setter is a real method, the compound
> assignment takes the old path, which fails on the extension getter for the
> read.
> h2. Suggested fix
> * Let the compound-assignment rewrite apply when *either* accessor is an
> extension method. The type checker already records the extension getter for
> the read side.
> * Rewrite {{++p}} / {{--p}} on such a property to {{setP(getP().next())}} /
> {{setP(getP().previous())}}, whose value is the new value. Rewrite {{p++}} /
> {{p--}} the same way, keeping the old value in a temporary as the
> expression's value. Reuse the getter lookup and setter-call construction from
> GROOVY-12524.
> * The receiver can be evaluated twice, once for the read and once for the
> write. Dynamic, {{@TypeChecked}} and {{@CompileStatic}} code all do this
> today, for real and extension properties alike.
> h2. Test clean-up in Groovy12524
> Three {{shouldFail}} tests pass without reaching the guard they are named
> after, and none of them checks a message:
> * {{testAttributeCompoundAssignOfExtensionPropertyIsRejected}}: {{file.@text
> += 'b'}} fails with an internal compiler error
> ({{ArrayIndexOutOfBoundsException}}). Plain {{file.@text = 'b'}} fails the
> same way, which is a separate pre-existing bug.
> * {{testSpreadSafeCompoundAssignIsNotRewrittenAsExtensionProperty}}: fails on
> a generics error ({{Cannot assign
> java.util.List<java.util.List<java.lang.String>> to:
> java.util.List<java.lang.String>}}) before the spread-safe guard is reached.
> * {{testPrimitiveExtensionSetterWithoutGetterIsRejected}}: fails with
> {{Cannot find matching method java.lang.Object#plus(int)}}.
> Each test should assert the error it expects. If no spread-safe form gets
> past type checking, that guard is defensive only and its test can be dropped.
> The Javadoc of the private method
> {{transformExtensionPropertyCompoundAssignment}} also opens with the JIRA
> key, which CONTRIBUTING asks to keep out of doc comments.
> h2. Not in scope
> * {{b.count **= 2}} is rejected under {{@TypeChecked}} and
> {{@CompileStatic}}, with the same error as for a real {{int}} property ({{int
> ** int}} is a {{Number}}).
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)