twigphp/Twig · error · SyntaxError
The variadic argument
Error message
The variadic argument "%s" in macro "%s" must be the last one.
What it means
In a macro signature, the variadic argument must be the final parameter. Twig throws this when a comma follows the variadic argument and another argument comes after it, e.g. '{% macro foo(...rest, extra) %}'.
Solutions
- Move the variadic argument to the end of the parameter list
- Rename and reorder parameters so all fixed arguments precede '...name'
- Split the macro into two if you genuinely need parameters after the catch-all
Example fix
// before
{% macro render(...attrs, class) %}
// after
{% macro render(class, ...attrs) %} Defensive patterns
Strategy: validation
Validate before calling
// Ensure '...' argument is last in every macro signature
foreach ($macroSignatures as $sig) {
$variadicPos = null;
foreach ($sig as $i => $p) {
if (str_starts_with($p, '...')) { $variadicPos = $i; }
}
if ($variadicPos !== null && $variadicPos !== count($sig) - 1) {
throw new InvalidArgumentException('Variadic macro argument must be last.');
}
} Try / catch
try {
$twig->render($template, $context);
} catch (SyntaxError $e) {
error_log($e->getMessage() . ' at line ' . $e->getTemplateLine());
} Prevention
- Always order macro parameters: required, optional (with defaults), then the single variadic
- When adding parameters to a macro with '...args', insert them before the variadic
- Code-review macro signature changes
When it happens
Trigger: Declaring '{% macro m(...args, name) %}'; in parseDefinition, after consuming the ',' the stream does not see ')' immediately, so the variadic is not last and a SyntaxError is raised.
Common situations: Adding a new parameter after an existing variadic when refactoring macros; porting signatures from languages that allow rest params anywhere.
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/69c02702a9234865.
Report an issue: GitHub.
Appendix: source
Thrown at src/TokenParser/MacroTokenParser.php:104
$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);
}
if (!$this->checkConstantExpression($default)) {
throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext());
}View on GitHub (pinned to a414c3a491)