Reputation: 3277
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
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 aMap
we can float around the map's key-values in the argument list. (...) Although the kind of flexibility in theRobot
example is powerful, it can get confusing, so use it sparingly. (...) We can avoid confusion like this by explicitly naming the first parameter asMap
:"def access(Map location, weight, fragile) { /* .. */ }
Btw, IDE like IntelliJ IDEA helps understanding the order of parameters:
Now, if I only set Map fragile
it will alert that there is something wrong with our method invocation:
Also, using @groovy.transform.TypeChecked
and @groovy.transform.CompileStatic
annotations help catching such problems at the compile time. Hope it helps.
Upvotes: 4