We have a number of open issues around improving named-argument support. I am currently doing a spike around one AST transformation and one metadata annotation for the type checker. These relate to various issues and pull requests that already contain some history:
[1] https://issues.apache.org/jira/browse/GROOVY-7956 [2] https://issues.apache.org/jira/browse/GROOVY-7632 [3] https://github.com/bsideup/GroovyMapArguments My current spike borrows code from above with some slight renaming. It currently has a @NamedVariant AST transform targetted for 2.5 if I get it finished soon. This can be placed on any method or constructor and produces a variant with a Map as the first parameter that complies with Groovy's existing named-argument approach. The method looks for any parameter annotated with @NamedParam or @NamedDelegate. These are conceptually "pulled out" of the generated method's signature and passed along via the first argument map. Any parameter annotated with @NamedParam is assumed to correspond to a single key for the map. For any parameter annotated with @NamedDelegate, the property names of the type of that argument are assumed to be the keys. Examples include: @NamedVariant def foo(a, @NamedParam String b, c, @NamedParam(required=true) d) { println "$a $b $c $d" } which produces a method like this: def foo(Map _it, a, c) { assert _it.containsKey('d') this(a, _it.b, c, _it.d) } If we have this class: class Animal { String type String name } Then this definition: @NamedVariant def describe(@NamedDelegate Animal animal) { "${animal.type.toUpperCase()}:$animal.name" } produces an additional method like this: def describe(Map __namedArgs) { this.describe((( __namedArgs ) as Animal)) } which could be called like this: assert describe(type: 'Dog', name: 'Rover') == 'DOG:Rover' Currently if no @NamedXXX annotations are found, @NamedDelegate is assumed for the first argument (so we could have left it out in the example above). Currently any number of @NamedParam and @NamedDelegate annotations can be used but keys can't be duplicated. A more elaborate example is as follows: // another domain class class Color { Integer r, g, b } @NamedVariant def describe(@NamedDelegate Animal animal, @NamedDelegate Color color, @NamedParam('dob') Date born) { ... } produces (approximately since I am ignoring missing keys): def describe(Map __namedArgs) { Map __colorArgs = [:] __colorArgs.r = __namedArgs.r __colorArgs.g = __namedArgs.g __colorArgs.b = __namedArgs.b Map __animalArgs = [:] __animalArgs.type = __namedArgs.type __animalArgs.name = __namedArgs.name this.describe(__animalArgs as Animal, __colorArgs as Color, __namedArgs.dob) } This has little impact on type checking since normal type checking will occur on the non-Map variant which is still retained. We can do a tiny bit more if we keep the annotation around and check the required flag but that is a minor discussion point. We could also generate some additional metadata annotation(s) as per subsequent discussion if needed but I'll defer that discussion for now. Over and above the @NamedVariant annotation, I am also suggesting a way to improve type checking on Map-based methods where the @NamedVariant might not be possible, e.g. Java classes or existing classes which can't easily be changed. It would look like the following (again heavily based on above references): @NamedParams(typeHint = SqlTypeHelper) Sql newInstance(Map<String, Object> args) { ... } or @NamedParams([ @NamedParam("password"), @NamedParam(value = "user", type = "String", required = true)]) Sql newInstance(Map<String, Object> args) { ... } In the first case the property names and types from the typeHint class are used for improved type checking. In the second case, the information is embedded in the nested annotations. Any thoughts or early comments while I continue on the spike(s)? More detailed comments can wait for the spike(s) so long as this doesn't seem way off what people would like to see. It's currently one spike but I'll probably split into two before making the PRs. Cheers, Paul.