diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java index 71c848820b51..20ee8e20b597 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java @@ -229,8 +229,9 @@ else if (spelParamCount != declaredParamCount) { ReflectionHelper.convertAllMethodHandleArguments(converter, functionArgs, methodHandle, varArgPosition); if (isSuspectedVarargs) { - if (declaredParamCount == 1) { - // We only repackage the varargs if it is the ONLY argument -- for example, + if (declaredParamCount == 1 && !methodHandle.isVarargsCollector()) { + // We only repackage the arguments if the MethodHandle accepts a single + // argument AND the MethodHandle is not a "varargs collector" -- for example, // when we are dealing with a bound MethodHandle. functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation( methodHandle.type().parameterArray(), functionArgs); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java b/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java index 895952f62a45..d9280f5be4bf 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java @@ -108,11 +108,16 @@ private static void populateMethodHandles(StandardEvaluationContext testContext) "formatObjectVarargs", MethodType.methodType(String.class, String.class, Object[].class)); testContext.registerFunction("formatObjectVarargs", formatObjectVarargs); - // #formatObjectVarargs(format, args...) + // #formatPrimitiveVarargs(format, args...) MethodHandle formatPrimitiveVarargs = MethodHandles.lookup().findStatic(TestScenarioCreator.class, "formatPrimitiveVarargs", MethodType.methodType(String.class, String.class, int[].class)); testContext.registerFunction("formatPrimitiveVarargs", formatPrimitiveVarargs); + // #varargsFunctionHandle(args...) + MethodHandle varargsFunctionHandle = MethodHandles.lookup().findStatic(TestScenarioCreator.class, + "varargsFunction", MethodType.methodType(String.class, String[].class)); + testContext.registerFunction("varargsFunctionHandle", varargsFunctionHandle); + // #add(int, int) MethodHandle add = MethodHandles.lookup().findStatic(TestScenarioCreator.class, "add", MethodType.methodType(int.class, int.class, int.class)); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java index 38d7d047f210..88f8b0b8b39d 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java @@ -79,6 +79,8 @@ void functionInvocationWithStringArgument() { @Test void functionWithVarargs() { + // static String varargsFunction(String... strings) -> Arrays.toString(strings) + evaluate("#varargsFunction()", "[]", String.class); evaluate("#varargsFunction(new String[0])", "[]", String.class); evaluate("#varargsFunction('a')", "[a]", String.class); @@ -241,6 +243,27 @@ void functionFromMethodHandleWithListConvertedToVarargsArray() { evaluate("#formatObjectVarargs('x -> %s %s %s', {'a', 'b', 'c'})", expected, String.class); } + @Test // gh-34109 + void functionViaMethodHandleForStaticMethodThatAcceptsOnlyVarargs() { + // #varargsFunctionHandle: static String varargsFunction(String... strings) -> Arrays.toString(strings) + + evaluate("#varargsFunctionHandle()", "[]", String.class); + evaluate("#varargsFunctionHandle(new String[0])", "[]", String.class); + evaluate("#varargsFunctionHandle('a')", "[a]", String.class); + evaluate("#varargsFunctionHandle('a','b','c')", "[a, b, c]", String.class); + evaluate("#varargsFunctionHandle(new String[]{'a','b','c'})", "[a, b, c]", String.class); + // Conversion from int to String + evaluate("#varargsFunctionHandle(25)", "[25]", String.class); + evaluate("#varargsFunctionHandle('b',25)", "[b, 25]", String.class); + evaluate("#varargsFunctionHandle(new int[]{1, 2, 3})", "[1, 2, 3]", String.class); + // Strings that contain a comma + evaluate("#varargsFunctionHandle('a,b')", "[a,b]", String.class); + evaluate("#varargsFunctionHandle('a', 'x,y', 'd')", "[a, x,y, d]", String.class); + // null values + evaluate("#varargsFunctionHandle(null)", "[null]", String.class); + evaluate("#varargsFunctionHandle('a',null,'b')", "[a, null, b]", String.class); + } + @Test void functionMethodMustBeStatic() throws Exception { SpelExpressionParser parser = new SpelExpressionParser();