Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Map does not work for GString in Groovy?

With the following snippet I cannot retrieve gString from a map:

def contents = "contents"
def gString = "$contents"

def map = [(gString): true]

assert map.size() == 1 // Passes
assert gString.hashCode() == map.keySet().first().hashCode() // Passes, same hash code
assert map[gString] // Fails

How on earth is that possible?

Assertion message clearly shows that there's something seriously wrong with Groovy:

assert map[gString] // Fails
       |  ||
       |  |contents
       |  null
       [contents:true]

It's not the same question as Why groovy does not see some values in dictionary? First answer there suggests:

You're adding GString instances as keys in your map, then searching for them using String instances.

In this question I clearly add GString and try to retrieve GString.

Also neither Why are there different behaviors for the ways of addressing GString keys in maps? nor Groovy different results on using equals() and == on a GStringImpl have an answer for me. I do not mutate anything and I do not mix String with GString.

like image 394
Michal Kordas Avatar asked Aug 25 '16 09:08

Michal Kordas


People also ask

How do I add a map to Groovy?

Add Item to a Map The first way is using the square brackets for the key. This way useful if you have a dynamic key name for example the key name join with index. The second way is using a key separate with map name by a dot ".". Or this example also working in Groovy.

What is a GString in Groovy?

GString: A GString is just like a normal String, except that it evaluates expression that are embedded with in string in the form ${..}. [java] String demo="Brother" String doubleQuotes= "Hello ${demo}" println doubleQuotes //Output:Hello Brother.

How do you check if a map contains a key in Groovy?

We can use the find(), findAll(), and grep() methods to filter and search for map entries based on keys and values.

How do I compare two maps in Groovy?

With Groovy 1.8 the equals() method is added to Map . This means we can check if maps are equals. They are equals if both maps have the same size, and keys and values are the same.


1 Answers

tl;dr: You seem to have discovered a bug in Groovy's runtime argument overloading evaluation.

Answer:

map[gString] is evaluated as map.getAt(gString) at runtime straightforwardly via Groovy's operator overloading mechanism. So far, so good, but now is where everything starts to go awry. The Java LinkedHashMap class does not have a getAt method anywhere in it's type hierarchy, so Groovy must use dynamically associated mixin methods instead (Actually that statement is sort of reversed. Groovy uses mixin methods before using the declared methods in the class hierarchy.)

So, to make a long story short, Groovy resolves map.getAt(gString) to use the category method DefaultGroovyMethods.getAt(). Easy-peasy, right? Except that this method has a large number of different argument overloads, several of which might apply, especially when you take Groovy's default argument coercion into account.

Unfortunately, instead of choosing DefaultGroovyMethods.getAt(Map<K,V>,K), which would seem to be a perfect match, Groovy chooses DefaultGroovyMethods.getAt(Object,String), which coerces the GString key argument into a String. Since the actual key is in fact a GString, the method ultimately fails to find the value.

To me the real killer is that if the argument overload resolution is performed directly from code (instead of after the operator resolution and the category method selection), then Groovy makes the right overload choice! That is to say, if you replace this expression:

map[gString]

with this expression:

DefaultGroovyMethods.getAt(map,gString)

then the argument overloading is resolved correctly, and the correct value is found and returned.

like image 128
BalRog Avatar answered Sep 29 '22 18:09

BalRog