Details
-
Improvement
-
Status: Closed
-
Major
-
Resolution: Fixed
-
None
-
None
Description
The rules for property read and write for map-based types still have several inconsistencies:
class M extends HashMap<String,Object> { public pub protected pro @PackageScope pack private priv def prop def getAny() {} void setAny(value) {} }
- Read and write do not share a consistent resolution order – noted in 8555. Should access method selection take visibility into account? I think sender information is not always available to MetaClassImpl so the "inside class" behavior is required.
void test1(M map) { map.pub // field (fixed in 5001) map.pro // entry map.pack // entry map.priv // entry map.prop // property (fixed in 5001) map.any // getter (all visibilities -- fixed in 5001) or entry for subclass of M (private or package-private other-package subclass -- fixed in 11357) map.empty // entry ("class" and "metaClass" as well) map.pub = null // field map.pro = null // field map.pack = null // entry map.priv = null // entry map.prop = null // property map.any = null // setter (all visibilities) or entry for subclass of M (private or package-private other-package subclass -- fixed in 11357) map.empty = null // entry map.class = null // entry map.metaClass = null // setter via ScriptBytecodeAdapter#setGroovyObjectProperty }
- "this" and "super" have different behavior.
// add this to the body of M void test2(M that) { this.pub // field this.pro // field this.pack // field this.priv // field this.prop // property this.any // getter (all visibilities -- fixed in 5001) this.class // entry (dynamic) or isser (static) this.empty // entry or getter (static) this.metaClass // field (dynamic) or getter (static) this.pub = null // field (same for "pro", "pack", "priv" and "prop") this.any = null // setter (all visibilities) or entry for subclass of M (private or package-private other-package subclass -- fixed in 11357) this.empty = null // entry (dynamic) or error (static) this.class = null // entry (dynamic) or error (static) this.metaClass = null // field (dynamic) or setter (static) def that = this that.* // see test1 (except "empty", "class" and "metaClass") that.empty // entry (dynamic) or isser (static) that.class // entry (dynamic) or getter (static) that.metaClass // entry (dynamic) or getter (static) }
- Dynamic and static compilation have different behavior.
@groovy.transform.CompileStatic void test3(M map) { map.pub // field (changed in 5001/5491) map.pro // field (changed in 5001/5491: in package or subclass) or entry map.pack // field (changed in 5001/5491: in package) or entry map.priv // entry map.prop // property (changed in 5001/5491) map.any // getter (accessible -- changed in 5001/5491) or entry (inaccessible) map.empty // isser (changed in 5001/5491) map.class // getter (changed in 5001/5491) map.metaClass // getter (changed in 5001/5491) map.pub = null // entry (changed in 6954) or field (fixed in 11376) map.pro = null // entry (changed in 6954) or field (fixed in 11376) map.pack = null // entry map.priv = null // entry map.prop = null // property map.any = null // setter (accessible) or error (inaccessible) map.empty = null // error "Cannot set read-only property: empty" (and "class") -- GROOVY-11369 map.metaClass = null // setter via DefaultGroovyMethods#setMetaClass }
- Closures intercept some properties.
void test4(M map) { map.with { pub // field pro // entry pack // entry priv // entry prop // property directive // closure property metaClass // closure property (and so on for "owner", "delegate", "thisObject", ...) } }
- The rules change a bit for fields declared by super class.
class MM extends M { void test5() { this.pub // field (fixed in 5001) this.pro // entry this.pack // entry this.priv // entry this.prop // property (fixed in 5001) this.any // getter (public, protected, package-private if same-package) or entry (private, package-private if other-package) this.class // entry this.empty // entry this.metaClass // entry this.pub = null // field this.pro = null // field this.pack = null // entry this.priv = null // entry this.prop = null // property this.any = null // setter (public, protected, package-private if same-package) or entry (private, package-private if other-package -- fixed in 11357) this.empty = null // entry this.class = null // entry this.metaClass = null // setter via ScriptBytecodeAdapter#setGroovyObjectProperty } }
- Calling a name bypasses map lookup.
void test6(M map) { map.pack() // field read and call map.priv() // field read and call }
GROOVY-662, GROOVY-5001, GROOVY-5491, GROOVY-5517, GROOVY-5985, GROOVY-6144, GROOVY-6277, GROOVY-6954, GROOVY-8065, GROOVY-8074, GROOVY-8265, GROOVY-8555, GROOVY-8978, GROOVY-9127, GROOVY-11144, GROOVY-11319, GROOVY-11223, GROOVY-11357, GROOVY-11360, GROOVY-11368, GROOVY-11369, GROOVY-11370, GROOVY-11376 GROOVY-11384, GROOVY-11386, GROOVY-11387, GROOVY-11390, GROOVY-11393, GROOVY-11401, GROOVY-11402
Attachments
Issue Links
- relates to
-
GROOVY-11403 property semantics of map-based types (pt.2)
- Closed