twigphp/Twig · error · SyntaxError

The variadic argument

Error message

The variadic argument "%s" in macro "%s" cannot have the same name as another argument.

What it means

When a macro declares a variadic argument, its name must be unique among the macro's arguments. Twig compiles macro arguments into a fixed signature, so a variadic parameter colliding with a named parameter would be ambiguous and is rejected in MacroNode's constructor.

Solutions

  1. Rename the variadic parameter so it differs from all other macro arguments
  2. Remove the now-duplicated plain argument

Example fix

// before
{% macro render(item, extra, ...extra) %}...{% endmacro %}
// after
{% macro render(item, ...extra) %}...{% endmacro %}
Defensive patterns

Strategy: validation

Validate before calling

$names = ['item', 'extra', 'extra']; // parsed macro args incl. variadic
if (count($names) !== count(array_unique($names))) {
    throw new InvalidArgumentException('Macro argument names must be unique');
}

Try / catch

try {
    $twig->load('macros.html');
} catch (\Twig\Error\SyntaxError $e) {
    // duplicate variadic name detected
}

Prevention

When it happens

Trigger: {% macro foo(a, ...args) %} combined with another argument also named 'args' (after Twig strips the internal reserved prefix) — variadicName equals a stripped argument name.

Common situations: Copy-pasting macro signatures where the variadic '...' name duplicates an existing parameter; refactoring a positional arg into the variadic without deleting the original.

Related errors


AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13). Data as JSON: /api/errors/183e273f00e00119. Report an issue: GitHub.

Appendix: source

Thrown at src/Node/MacroNode.php:68

        if (!$arguments instanceof ArrayExpression) {
            trigger_deprecation('twig/twig', '3.15', \sprintf('Not passing a "%s" instance as the "arguments" argument of the "%s" constructor is deprecated ("%s" given).', ArrayExpression::class, static::class, $arguments::class));

            $args = new ArrayExpression([], $arguments->getTemplateLine());
            foreach ($arguments as $n => $default) {
                $args->addElement($default, new LocalVariable($n, $default->getTemplateLine()));
            }
            $arguments = $args;
        }

        $seen = [];
        foreach ($arguments->getKeyValuePairs() as $pair) {
            $argName = $pair['key']->getAttribute('name');
            if (TempNameExpression::RESERVED_NAME_PREFIX.self::VARARGS_NAME === $argName) {
                throw new SyntaxError(\sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $pair['value']->getTemplateLine(), $pair['value']->getSourceContext());
            }
            if (null !== $variadicName && $variadicName === $this->stripReservedPrefix($argName)) {
                throw new SyntaxError(\sprintf('The variadic argument "%s" in macro "%s" cannot have the same name as another argument.', $variadicName, $name), $pair['value']->getTemplateLine(), $pair['value']->getSourceContext());
            }
            if (isset($seen[$argName])) {
                throw new SyntaxError(\sprintf('Argument "%s" is defined twice for macro "%s".', $this->stripReservedPrefix($argName), $name), $pair['value']->getTemplateLine(), $pair['value']->getSourceContext());
            }
            $seen[$argName] = true;
        }

        parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name, 'variadic_name' => $variadicName], $lineno);
    }

    public function compile(Compiler $compiler): void
    {
        // 4.0 cleanup: only an explicitly declared variadic ("...name") gets a trailing
        // "...$bucket" parameter and a context entry; a non-variadic macro must NOT emit
        // "...$varargs" anymore (so extra arguments raise an error), and the implicit
        // "varargs" context entry and VARARGS_NAME handling below go away.
        $variadicName = $this->getAttribute('variadic_name');
        if (null === $variadicName) {

View on GitHub (pinned to a414c3a491)