From 322275d5b1db27cfae59d6b1c4744c5e33432753 Mon Sep 17 00:00:00 2001 From: Szymon Stepniak Date: Wed, 17 Oct 2018 00:03:48 +0200 Subject: [PATCH] GROOVY-8848: added mixing named arguments with other arguments use case explanation to the documentation --- src/spec/doc/core-object-orientation.adoc | 36 +++++++++++++++++++ .../test/objectorientation/MethodsTest.groovy | 28 +++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/spec/doc/core-object-orientation.adoc b/src/spec/doc/core-object-orientation.adoc index bfc950ca56a..5faa03811bf 100644 --- a/src/spec/doc/core-object-orientation.adoc +++ b/src/spec/doc/core-object-orientation.adoc @@ -335,6 +335,42 @@ Like constructors, normal methods can also be called with named arguments. They include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=named_arguments ,indent=0] ---- +===== Mixing named arguments with other argument(s) + +Named parameters can be mixed with a regular arguments of any type, however there is one important requirement - `Map` argument +that represents named arguments has to be defined as a first argument in the method arguments list. + +[source,groovy] +---- +include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=named_arguments_with_additional_arguments ,indent=0] +---- +<1> Method call with additional `number` argument of `Integer` type +<2> Method call with changed order of arguments + +WARNING: Groovy allows floating named arguments around and pass them after regular argument(s) when invoking the method. +However, it still expects that `Map` argument that represents named arguments is defined as the first argument of the method. + +If we break the rule mentioned earlier and we swap method arguments, so `Map` gets defined as the second (or later) argument, we will start +seeing `groovy.lang.MissingMethodException`: + +[source,groovy] +---- +include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=failed_named_arguments_with_additional_arguments ,indent=0] +---- +<1> Method call throws `groovy.lang.MissingMethodException: No signature of method: foo() is applicable for argument types: (LinkedHashMap, Integer) values: [[name:Marie, age:1], 23]`, because named arguments represented as `Map` parameter are not defined as the first argument + +Above exception can be avoided if we replace named arguments with an explicit `Map` argument: + +[source,groovy] +---- +include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=explicit_named_arguments_with_additional_arguments ,indent=0] +---- +<1> Explicit `Map` argument in place of named arguments makes invocation valid + + +TIP: Although Groovy allows you mixing named arguments with arguments of any regular type, it may cause unnecessary confusion. +Mix named arguments with additional arguments very carefully and only if it really solves a problem for you. + ==== Default arguments Default arguments make parameters optional. If the argument is not supplied, the method assumes a default value. diff --git a/src/spec/test/objectorientation/MethodsTest.groovy b/src/spec/test/objectorientation/MethodsTest.groovy index df0fda7a554..e89d2639bd8 100644 --- a/src/spec/test/objectorientation/MethodsTest.groovy +++ b/src/spec/test/objectorientation/MethodsTest.groovy @@ -40,6 +40,34 @@ class MethodsTest extends GroovyTestCase { ''' } + void testNamedArgumentsAlongWithOtherArguments() { + assertScript ''' + // tag::named_arguments_with_additional_arguments[] + def foo(Map args, Integer number) { "${args.name}: ${args.age}, and the number is ${number}" } + foo(name: 'Marie', age: 1, 23) //<1> + foo(23, name: 'Marie', age: 1) //<2> + // end::named_arguments_with_additional_arguments[] + ''' + } + + void testFailedNamedArgumentsAlongWithOtherArguments() { + shouldFail ''' + // tag::failed_named_arguments_with_additional_arguments[] + def foo(Integer number, Map args) { "${args.name}: ${args.age}, and the number is ${number}" } + foo(name: 'Marie', age: 1, 23) //<1> + // end::failed_named_arguments_with_additional_arguments[] + ''' + } + + void testExplicitNamedArgumentsAlongWithOtherArguments() { + assertScript ''' + // tag::explicit_named_arguments_with_additional_arguments[] + def foo(Integer number, Map args) { "${args.name}: ${args.age}, and the number is ${number}" } + foo(23, [name: 'Marie', age: 1]) //<1> + // end::explicit_named_arguments_with_additional_arguments[] + ''' + } + void testDefaultArguments() { assertScript ''' // tag::default_arguments[]