twigphp/Twig · error · SyntaxError
The variadic argument
Error message
The variadic argument "%s" in macro "%s" cannot have a default value.
What it means
When parsing a macro definition, Twig rejects a variadic argument (declared with the '...' prefix) that is given a default value like '...args = []'. Variadic arguments collect all leftover call arguments, so a default would be meaningless and is therefore a syntax error.
Solutions
- Remove the '= default' from the variadic argument and rely on it capturing zero or more remaining arguments
- Move the default onto a preceding normal argument, or handle the empty case inside the macro body with a conditional
- Check the macro signature: only regular arguments may have defaults, and the variadic must come last
Example fix
// before
{% macro render(items, ...attrs = []) %}
// after
{% macro render(items, ...attrs) %}{% if attrs is empty %}...{% endif %} Defensive patterns
Strategy: validation
Validate before calling
// Template-source check before rendering/compiling
if (preg_match('/\.\.\.\s*\w+\s*=/', $templateSource)) {
throw new InvalidArgumentException('Variadic macro arguments must not have default values.');
} Try / catch
try {
$twig->parse($env->tokenize(new Source($template, 'tpl')));
} catch (SyntaxError $e) {
// report $e->getMessage() with template line to the author
} Prevention
- Never put '= default' after a '...name' macro argument
- Give defaults only to regular (non-variadic) arguments before the variadic one
- Lint macro-heavy templates in CI with lint:twig
When it happens
Trigger: Writing a macro parameter such as '{% macro foo(a, ...rest = ["x"]) %}'; the parser sees '=' after the variadic name in MacroTokenParser::parseDefinition and throws immediately.
Common situations: Copy-pasting ordinary argument syntax onto a variadic argument; misunderstanding Twig variadics after coming from PHP/JS where rest params sometimes appear with defaults in tutorials.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- The variadic argument
- Unexpected character
- Expected endmacro for macro
- A default value for an argument must be a constant (a…
- When using set, you must have the same number of variables…
AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13).
Data as JSON: /api/errors/2b9aa75007707e19.
Report an issue: GitHub.
Appendix: source
Thrown at src/TokenParser/MacroTokenParser.php:101
$stream->expect(Token::OPERATOR_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {
if (\count($arguments)) {
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
// if the comma above was a trailing comma, early exit the argument parse loop
if ($stream->test(Token::PUNCTUATION_TYPE, ')')) {
break;
}
}
if ($stream->nextIf(Token::OPERATOR_TYPE, '...')) {
$token = $stream->expect(Token::NAME_TYPE, null, 'A variadic argument must be a name');
$variadicName = (new LocalVariable($token->getValue(), $token->getLine()))->getAttribute('name');
if (str_starts_with($variadicName, TempNameExpression::RESERVED_NAME_PREFIX)) {
$variadicName = substr($variadicName, \strlen(TempNameExpression::RESERVED_NAME_PREFIX));
}
if ($stream->test(Token::OPERATOR_TYPE, '=')) {
throw new SyntaxError(\sprintf('The variadic argument "%s" in macro "%s" cannot have a default value.', $variadicName, $macroName), $token->getLine(), $stream->getSourceContext());
}
if ($stream->nextIf(Token::PUNCTUATION_TYPE, ',') && !$stream->test(Token::PUNCTUATION_TYPE, ')')) {
throw new SyntaxError(\sprintf('The variadic argument "%s" in macro "%s" must be the last one.', $variadicName, $macroName), $token->getLine(), $stream->getSourceContext());
}
break;
}
$token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name');
$name = new LocalVariable($token->getValue(), $token->getLine());
NodeDocumentation::add($name, $token);
if ($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) {
$default = $this->parser->parseExpression();
} else {
$default = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine());
$default->setAttribute('is_implicit', true);
}
View on GitHub (pinned to a414c3a491)