When a macro uses .args, and has catch-all parameter, allow positional macro arguments when the actual catch-all length is 0. Also, added some tests for 0 argument calls.
diff --git a/src/main/java/freemarker/core/Macro.java b/src/main/java/freemarker/core/Macro.java
index b64e9e4..17c63f6 100644
--- a/src/main/java/freemarker/core/Macro.java
+++ b/src/main/java/freemarker/core/Macro.java
@@ -37,6 +37,7 @@
import freemarker.template.TemplateModelIterator;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
+import freemarker.template.utility.Constants;
/**
* An element representing a macro or function declaration.
@@ -323,7 +324,8 @@
if (argsSpecVarDraft != null) {
final String catchAllParamName = getMacro().catchAllParamName;
- final TemplateModel catchAllArgValue = catchAllParamName != null ? localVars.get(catchAllParamName) : null;
+ final TemplateModel catchAllArgValue = catchAllParamName != null
+ ? localVars.get(catchAllParamName) : null;
if (getMacro().isFunction()) {
int lengthWithCatchAlls = argsSpecVarDraft.length;
@@ -350,10 +352,15 @@
TemplateHashModelEx2 catchAllHash;
if (catchAllParamName != null) {
if (catchAllArgValue instanceof TemplateSequenceModel) {
- throw new _MiscTemplateException("The macro can only by called with named arguments, " +
- "because it uses both .", BuiltinVariable.ARGS, " and catch-all parameter.");
+ if (((TemplateSequenceModel) catchAllArgValue).size() != 0) {
+ throw new _MiscTemplateException("The macro can only by called with named arguments, " +
+ "because it uses both .", BuiltinVariable.ARGS, " and a non-empty catch-all " +
+ "parameter.");
+ }
+ catchAllHash = Constants.EMPTY_HASH_EX2;
+ } else {
+ catchAllHash = (TemplateHashModelEx2) catchAllArgValue;
}
- catchAllHash = (TemplateHashModelEx2) catchAllArgValue;
lengthWithCatchAlls += catchAllHash.size();
} else {
catchAllHash = null;
diff --git a/src/main/java/freemarker/template/utility/Constants.java b/src/main/java/freemarker/template/utility/Constants.java
index 2162ef6..8392c01 100644
--- a/src/main/java/freemarker/template/utility/Constants.java
+++ b/src/main/java/freemarker/template/utility/Constants.java
@@ -95,9 +95,14 @@
}
}
-
- public static final TemplateHashModelEx EMPTY_HASH = new EmptyHashModel();
-
+
+ /**
+ * @since 2.3.30
+ */
+ public static final TemplateHashModelEx2 EMPTY_HASH_EX2 = new EmptyHashModel();
+
+ public static final TemplateHashModelEx EMPTY_HASH = EMPTY_HASH_EX2;
+
/**
* An empty hash. Since 2.3.27, it implements {@link TemplateHashModelEx2}, before that it was only
* {@link TemplateHashModelEx}.
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 3b1432c..6c03186 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -24617,7 +24617,8 @@
</listitem>
<listitem>
- <para>If a macro has a catch-all argument, and the macro uses
+ <para>If a macro has a catch-all parameter, and the actual
+ catch-all argument is not empty, and the macro uses
<literal>.args</literal> somewhere, it can only be called with
named arguments (like <literal><@m a=1 b=2 /></literal>),
and not with positional arguments (like <literal><@m 1 2
diff --git a/src/test/java/freemarker/core/ArgsSpecialVariableTest.java b/src/test/java/freemarker/core/ArgsSpecialVariableTest.java
index 1064744..bc94cef 100644
--- a/src/test/java/freemarker/core/ArgsSpecialVariableTest.java
+++ b/src/test/java/freemarker/core/ArgsSpecialVariableTest.java
@@ -41,6 +41,12 @@
}
@Test
+ public void macroZeroArgsTest() throws IOException, TemplateException {
+ assertOutput("<#macro m>${.args?size}</#macro><@m />", "0");
+ assertOutput("<#macro m others...>${.args?size}</#macro><@m />", "0");
+ }
+
+ @Test
public void macroWithDefaultsTest() throws IOException, TemplateException {
String macroDef = "<#macro m a b c=3><#list .args as k, v>${k}=${v}<#sep>, </#list></#macro>";
String expectedOutput = "" +
@@ -80,8 +86,8 @@
@Test
public void macroWithCatchAllTest() throws IOException, TemplateException {
- assertOutput("" +
- "<#macro m a b=2 others...><#list .args as k, v>${k}=${v}<#sep>, </#list></#macro>" +
+ String macroDef = "<#macro m a b=2 others...><#list .args as k, v>${k}=${v}<#sep>, </#list></#macro>";
+ assertOutput(macroDef +
"<@m a=11 b=22 c=33 d=44 />; " +
"<@m a=11 b=22 />; " +
"<@m a=11 />; " +
@@ -91,9 +97,9 @@
"a=11, b=2; " +
"a=11, b=2, c=33");
- assertErrorContains("" +
- "<#macro m a b=2 others...><#list .args as k, v>${k}=${v}<#sep>, </#list></#macro>" +
- "<@m 1, 2 />",
+ assertOutput(macroDef + "<@m 1, 2 />",
+ "a=1, b=2");
+ assertErrorContains(macroDef + "<@m 1, 2, 3 />",
".args", "catch-all");
}
@@ -106,6 +112,13 @@
expectedOutput);
}
+
+ @Test
+ public void functionZeroArgsTest() throws IOException, TemplateException {
+ assertOutput("<#function f><#return .args?size></#function>${f()}", "0");
+ assertOutput("<#function f others...><#return .args?size></#function>${f()}", "0");
+ }
+
@Test
public void functionWithDefaultsTest() throws IOException, TemplateException {
String functionDef = "<#function f a b c=3><#return .args?join(', ')></#function>";
@@ -150,7 +163,7 @@
"11, 2; " +
"11, 2, 33");
}
-
+
@Test
public void usedInWrongContextTest() throws IOException, TemplateException {
assertErrorContains("${.args}", "args", "macro", "function");