Paul King created GROOVY-12730:
----------------------------------

             Summary: @IndexedProperty on a record component (or a @RecordType 
/ @RecordBase class property) generates an indexed setter setX(int, V), even 
though records expose no setters. @Immutable classes correctly receive only the 
indexed getter
                 Key: GROOVY-12730
                 URL: https://issues.apache.org/jira/browse/GROOVY-12730
             Project: Groovy
          Issue Type: Bug
    Affects Versions: 6.0.0
            Reporter: Paul King


h2. Reproducer

{code:groovy}
import groovy.transform.*

@RecordType
class RecordBox {
    @IndexedProperty List<String> items
}

@Immutable
class ImmutableBox {
    @IndexedProperty List<String> items
}

def setters = { Class c -> c.declaredMethods.findAll { it.name == 'setItems' 
}*.parameterTypes*.simpleName }
assert setters(ImmutableBox) == []
assert setters(RecordBox) == []          // fails: [[int, String]]

def r = new RecordBox(['a', 'b'])
r.setItems(0, 'z')                        // compiles and runs
assert r.items == ['a', 'b']              // fails: [z, b]
{code}

The behaviour is the same with and without joint compilation.

h2. Cause

{{IndexedPropertyASTTransformation}} omits the indexed setter when the field 
carries {{ImmutableASTTransformation.IMMUTABLE_BREADCRUMB}}. That marker is set 
by {{ImmutableASTTransformation.adjustPropertyForImmutability}}, but 
{{RecordTypeASTTransformation.adjustPropertyForShallowImmutability}} makes 
record fields final without setting it, so the indexed setter is still 
generated.

h2. Notes

* Records are only shallowly immutable: the list returned by the component 
accessor is the original (mutable) list, so the contents can already be changed 
via {{r.items()[0] = 'z'}}. The problem is therefore one of API consistency 
rather than a broken guarantee: a record has no setters, so it should not gain 
an indexed one.
* Plain classes with a {{final}} {{@IndexedProperty}} property keep their 
indexed setter. A final reference to a mutable list is a legitimate pattern and 
is out of scope here.
* The joint-compilation stubber ({{IndexedPropertyASTStubber}}) omits the 
setter for {{@RecordType}} / {{@RecordBase}} classes as of PR #3262 
(GROOVY-12726). This change makes the compiled class agree with the stub.

h2. Proposed fix

Mark record properties as read-only for {{@IndexedProperty}}, either by setting 
{{IMMUTABLE_BREADCRUMB}} in 
{{RecordTypeASTTransformation.adjustPropertyForShallowImmutability}} or by 
having {{IndexedPropertyASTTransformation}} recognise record classes directly. 
Only the indexed getter is then generated, matching {{@Immutable}}.

This removes a public method from affected classes, so it should be noted in 
COMPATIBILITY.md and is not proposed for backporting.



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

Reply via email to