twigphp/Twig · error · LogicException

A " " can only contain " " nodes; replacing the macro " "…

Error message

A "%s" can only contain "%s" nodes; replacing the macro "%s" with a "%s" node is not supported.

What it means

MacrosNode::setNode() enforces the same invariant as its constructor: only MacroNode instances may occupy a slot in a MacrosNode. Replacing a macro with any other Node type throws a LogicException, keeping the node tree internally consistent for the compiler.

Solutions

  1. Only pass \Twig\Node\MacroNode instances to setNode() on a MacrosNode.
  2. If the replacement is genuinely not a macro, restructure so it lives in a different node container rather than inside MacrosNode.
  3. Guard with `if ($node instanceof MacroNode)` before calling setNode().

Example fix

// before
$macros->setNode('greet', new \Twig\Node\TextNode('hi'));
// after
if ($node instanceof \Twig\Node\MacroNode) { $macros->setNode('greet', $node); }
Defensive patterns

Strategy: type-guard

Validate before calling

if (!$node instanceof \Twig\Node\MacroNode) { throw new LogicException('setNode on MacrosNode requires MacroNode'); }

Type guard

function canReplaceMacro(\Twig\Node\Node $node): bool { return $node instanceof \Twig\Node\MacroNode; }

Try / catch

try {
    $macrosNode->setNode($name, $node);
} catch (\LogicException $e) {
    // replacement was not a MacroNode; skip or handle
}

Prevention

When it happens

Trigger: Calling `$macrosNode->setNode('name', $someOtherNode)` where the node is not a MacroNode — usually from a custom NodeVisitor or AST-modification test (as in testItRejectsReplacingAMacroWithANonMacroNode).

Common situations: Custom Twig node visitors rewriting macros; tests exercising node immutability rules; automated template transformations that swap nodes without type checks.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at src/Node/MacrosNode.php:44

{
    /**
     * @param array<string, MacroNode> $macros
     */
    public function __construct(array $macros = [])
    {
        foreach ($macros as $name => $macro) {
            if (!$macro instanceof MacroNode) {
                throw new \InvalidArgumentException(\sprintf('Using "%s" for the macro "%s" of "%s" is not supported. You must pass a "%s" instance.', get_debug_type($macro), $name, static::class, MacroNode::class));
            }
        }

        parent::__construct($macros);
    }

    public function setNode(string $name, Node $node): void
    {
        if (!$node instanceof MacroNode) {
            throw new \LogicException(\sprintf('A "%s" can only contain "%s" nodes; replacing the macro "%s" with a "%s" node is not supported.', static::class, MacroNode::class, $name, get_debug_type($node)));
        }

        parent::setNode($name, $node);
    }

    public function compile(Compiler $compiler): void
    {
        if (!\count($this)) {
            return;
        }

        $compiler
            ->write("protected function loadDeclaredMacros(): array\n", "{\n")
            ->indent()
            ->write("return [\n")
            ->indent()
        ;

View on GitHub (pinned to a414c3a491)