This is a proposal for a breaking change for how Groovy interacts with
objects implementing the Map interface.

== Background ==

Groovy supports looking up the values of a map by converting
property-style access `map.foo` to `map.get('foo')`. My understanding
is that the purpose of this feature is to allow drop-in substitution
of a map for a POJO.

Currently, property-style access is always(?) routed to the Map's
`get` method, regardless of any actual properties available on the
object. This is the case even in static compilation where the
variable's declared type implements a property matching the attempted
access. This behavior leads to unexpected results and even heap
pollution.

As two examples, the `isEmpty()` method is defined for all collections
and returns a primitive boolean. `list.empty` and `set.empty`
correctly return true/false depending on the contents of the
collection, but `map.empty` is interpreted as a request to
`map.get('empty')`. This is entirely surprising, especially when it
results in an NPE (when for some reason the null isn't as-booleaned).

Additionally, some classes implementing Map also provide other
ergonomics, such as Spring's HttpHeaders, which eventually implements
`Map<String, List<String>>`. It has explicit computed properties for
most of the common headers, with such property types as URI (Location)
and long (Last-Modified). Groovy, however, interprets
`headers.location` as a call to `headers.get('location')` and returns
a `List<String>` instead of `headers.getLocation()`.

This feature provides novel and evening-occupying results
(particularly as Eclipse's hover and autocomplete suggest the expected
behavior!).

== Proposal ==

Starting in Groovy 4, prioritize explicitly defined properties for
resolution ahead of `Map#get`.

If some object implementing Map actually defines properties, then it
seems to me a reasonable guess that that support is based on the
intended/expected usage patterns, and that users employing that object
would expect to have it available. In the case where clients
specifically want to use a lookup, get and `[]` are easily available.

-- 
Christopher Smith

Reply via email to