twigphp/Twig · error · SyntaxError

Argument " " is defined twice for macro " ".

Error message

Argument "%s" is defined twice for macro "%s".

What it means

MacroNode's constructor builds the argument node for a macro definition and is a generic duplicate-detection guard: while iterating the macro's declared argument names it tracks already-seen names, and this SyntaxError fires at template compile time when the same argument name is declared more than once in a macro signature (e.g. {% macro foo(a, a) %}), which would make the generated argument array ambiguous. The input at fault is the macro's argument list in the template source.

Solutions

  1. Remove or rename the duplicated macro argument
  2. Deduplicate argument lists in template-generation code before emitting the macro

Example fix

// before
{% macro greet(name, name) %}...{% endmacro %}
// after
{% macro greet(firstName, lastName) %}...{% endmacro %}
Defensive patterns

Strategy: validation

Validate before calling

$args = ['name', 'name'];
if (count($args) !== count(array_unique(array_map('strtolower', $args)))) {
    throw new InvalidArgumentException('Duplicate macro arguments');
}

Try / catch

try {
    $twig->load('macros.html');
} catch (\Twig\Error\SyntaxError $e) {
    if (str_contains($e->getMessage(), 'defined twice')) { /* fix macro */ }
}

Prevention

When it happens

Trigger: {% macro greet(name, name) %} or the same argument emitted twice by generated templates.

Common situations: Merging macro signatures during refactoring; template generators concatenating argument lists without de-duplication.

Related errors


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

Appendix: source

Thrown at src/Node/MacroNode.php:71

            $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) {
            // Legacy implicit "varargs" bucket: to be removed in 4.0.
            $bucketName = self::VARARGS_NAME;
            $bucketVar = self::VARARGS_NAME;

View on GitHub (pinned to a414c3a491)