twigphp/Twig · error · RuntimeError
Positional arguments cannot be used after named arguments…
Error message
Positional arguments cannot be used after named arguments for macro "%s".
What it means
Twig throws this RuntimeError when a macro call passes positional arguments after named ones, e.g. {{ m(a=1, 2) }}. PHP itself forbids this ordering, so callLegacy() in TwigMacro detects the mixed, misordered call upfront and reports it with the template call-site line and source instead of a cryptic PHP error. It is one of the lenient-to-strict deprecation checks that become hard errors in Twig 4.0.
Solutions
- Reorder the macro call so all positional arguments come before any named arguments.
- Convert the positional arguments to named arguments so the whole call is uniformly named.
- If arguments are built dynamically in PHP, append positional values before named ones or key everything by name before invoking the macro.
Example fix
{# before #}
{{ macro.box(padding=2, 'title') }}
{# after #}
{{ macro.box('title', padding=2) }} Defensive patterns
Strategy: validation
Validate before calling
// PHP, before invoking a macro programmatically
$posSeen = false; $namedSeen = false;
foreach (array_keys($args) as $k) {
if (is_int($k)) { $posSeen = $posSeen || $namedSeen; }
else { $namedSeen = true; }
}
if ($posSeen) { throw new InvalidArgumentException('Reorder: positional args must precede named args'); } Type guard
function argsAreWellOrdered(array $args): bool {
$namedSeen = false;
foreach (array_keys($args) as $k) {
if (is_int($k)) { if ($namedSeen) return false; }
else { $namedSeen = true; }
}
return true;
} Try / catch
try {
$out = $macro->callLegacy($args, $source, $lineno);
} catch (Twig\Error\RuntimeError $e) {
if (str_contains($e->getMessage(), 'Positional arguments cannot be used after named arguments')) {
ksort($args, SORT_STRING); // or reorder so integers come first
$out = $macro->callLegacy($args, $source, $lineno);
} else { throw $e; }
} Prevention
- Always place positional arguments before named arguments in macro calls.
- Prefer fully named macro calls in new templates.
- Run your Twig template test suite under strict Twig 4 settings to catch ordering problems early.
When it happens
Trigger: Calling TwigMacro::callLegacy() with an arguments array that is not a list and contains an integer key occurring after a string (named) key — i.e. a template calling a macro with positional arguments following named arguments.
Common situations: Template authors start adding a new named argument to a macro call and leave existing positional arguments after it; dynamically built call arrays where names are added before leftover positional values; templates upgraded from Twig 2/3 that previously relied on lenient handling.
Related errors
- The variadic argument
- The variadic argument
- Argument " " is defined twice for macro " ".
- Named arguments are not supported for
- The "html_classes" function argument
AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13).
Data as JSON: /api/errors/84745eb49bd75f9e.
Report an issue: GitHub.
Appendix: source
Thrown at src/TwigMacro.php:129
$misordered = $misordered || $sawNamed;
++$positionalCount;
} else {
$sawNamed = true;
if (null === $i = $this->argumentIndexes[$key] ?? null) {
$hasUnknownNamed = true;
} else {
if (null === $duplicate && $i < $positionalCount) {
$duplicate = $key;
}
if (isset($this->requiredNames[$key])) {
++$namedRequired;
}
}
}
}
if ($misordered) {
throw new RuntimeError(\sprintf('Positional arguments cannot be used after named arguments for macro "%s".', $this->name), $lineno, $source);
}
if (null !== $duplicate) {
throw new RuntimeError(\sprintf('Argument "%s" is defined twice for macro "%s".', $duplicate, $this->name), $lineno, $source);
}
// For a fully named call, the coverage of the required arguments is exact; a
// mixed call falls back to the precise (and slower) per-argument check.
$mayMissRequired = 0 === $positionalCount
? $namedRequired < \count($this->requiredNames)
: $positionalCount < $this->requiredCount;
if ($mayMissRequired || (!$this->variadic && ($hasUnknownNamed || $positionalCount > \count($this->arguments)))) {
$this->triggerLegacyDeprecations($arguments, $positionalCount, $source, $lineno);
}
foreach ($this->renamedArguments as $name => $parameterName) {
if (\array_key_exists($name, $arguments)) {
$arguments[$parameterName] = $arguments[$name];View on GitHub (pinned to a414c3a491)