Jakub Bochenski
Jakub Bochenski

Reputation: 3277

Omiting brackets for named arguments inverts order

There is nothing in http://docs.groovy-lang.org/latest/html/documentation/#_named_arguments that would explain this behaviour.

def foo(String a,Map b) { println "a: ${a}; b: ${b}" }
foo('a',b : 'c')

Results in an error: No signature of method: Script1.foo() is applicable for argument types: (java.util.LinkedHashMap, java.lang.String) values: [[b:c], a]

def foo(String a,Map b) { println "a: ${a}; b: ${b}" }
foo('a',[b : 'c'])

Prints out: a: a; b: [b:c]

Swapping the order of arguments in definition also makes it compile:

def foo(Map b,String a) { println "a: ${a}; b: ${b}" }
foo('a',b : 'c')

Prints out a: a; b: [b:c]

Is this a bug in groovy or is it some unexpected "groovy goodness"?

Upvotes: 4

Views: 95

Answers (1)

Szymon Stepniak
Szymon Stepniak

Reputation: 42234

This is actually undocumented Groovy behavior. When using named parameters with additional parameters, Groovy expects that Map parameter that stands for named parameters is the first parameter of the method if you skip square brackets in map definition. If we analyze the bytecode generated by compiler we will see that following line:

foo('a',b : 'c')

is expressed by following Java code:

CallSite[] var1 = $getCallSiteArray();
return var1[1].callCurrent(this, ScriptBytecodeAdapter.createMap(new Object[]{"b", "c"}), "a");

As you can see the order of parameters passed to callCurrent() method is reversed comparing to the one you have defined when calling foo() method. It's a bit confusing, especially that adding square brackets explicitly changes generated bytecode:

foo('a', [b: 'c'])

is expressed by following Java code:

CallSite[] var1 = $getCallSiteArray();
return var1[1].callCurrent(this, "a", ScriptBytecodeAdapter.createMap(new Object[]{"b", "c"}));

It got explained briefly in "Programming Groovy 2" book by Venkat Subramanian :

class Robot {
   def type, height, width

   def access(location, weight, fragile) {
       println "Received fragile? $fragile, weight: $weight, loc: $location"
   }
}

robot = new Robot(type: 'arm', width: 10, height: 10)
robot.access(x: 30, y: 20, z: 10, 50, true)
robot.access(50, true, x: 30, y: 20, z: 10)

"This access() method receives three parameters, but if the first parameter is a Map we can float around the map's key-values in the argument list. (...) Although the kind of flexibility in the Robot example is powerful, it can get confusing, so use it sparingly. (...) We can avoid confusion like this by explicitly naming the first parameter as Map:"

def access(Map location, weight, fragile) { /* .. */ }

Btw, IDE like IntelliJ IDEA helps understanding the order of parameters:

enter image description here

Now, if I only set Map fragile it will alert that there is something wrong with our method invocation:

enter image description here

Also, using @groovy.transform.TypeChecked and @groovy.transform.CompileStatic annotations help catching such problems at the compile time. Hope it helps.

Upvotes: 4

Related Questions