{"record":{"id":"ed53aaf70855b91b","repo":"twigphp/Twig","slug":"a-s-can-only-contain-s-nodes-replacing-the-macro-s-with-a-s","errorCode":null,"errorMessage":"A \"%s\" can only contain \"%s\" nodes; replacing the macro \"%s\" with a \"%s\" node is not supported.","messagePattern":"A \"(.+?)\" can only contain \"(.+?)\" nodes; replacing the macro \"(.+?)\" with a \"(.+?)\" node is not supported\\.","errorType":"exception","errorClass":"LogicException","httpStatus":null,"severity":"error","filePath":"src/Node/MacrosNode.php","lineNumber":44,"sourceCode":"{\n    /**\n     * @param array<string, MacroNode> $macros\n     */\n    public function __construct(array $macros = [])\n    {\n        foreach ($macros as $name => $macro) {\n            if (!$macro instanceof MacroNode) {\n                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));\n            }\n        }\n\n        parent::__construct($macros);\n    }\n\n    public function setNode(string $name, Node $node): void\n    {\n        if (!$node instanceof MacroNode) {\n            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)));\n        }\n\n        parent::setNode($name, $node);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        if (!\\count($this)) {\n            return;\n        }\n\n        $compiler\n            ->write(\"protected function loadDeclaredMacros(): array\\n\", \"{\\n\")\n            ->indent()\n            ->write(\"return [\\n\")\n            ->indent()\n        ;\n","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/twigphp/Twig/blob/a414c3a491defb5a60f2fc88ef79ff37c90010cd/src/Node/MacrosNode.php#L26-L62","documentation":"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.","triggerScenarios":"Calling `$macrosNode->setNode('name', $someOtherNode)` where the node is not a MacroNode — usually from a custom NodeVisitor or AST-modification test (as in testItRejectsReplacingAMacroWithANonMacroNode).","commonSituations":"Custom Twig node visitors rewriting macros; tests exercising node immutability rules; automated template transformations that swap nodes without type checks.","solutions":["Only pass \\Twig\\Node\\MacroNode instances to setNode() on a MacrosNode.","If the replacement is genuinely not a macro, restructure so it lives in a different node container rather than inside MacrosNode.","Guard with `if ($node instanceof MacroNode)` before calling setNode()."],"exampleFix":"// before\n$macros->setNode('greet', new \\Twig\\Node\\TextNode('hi'));\n// after\nif ($node instanceof \\Twig\\Node\\MacroNode) { $macros->setNode('greet', $node); }","handlingStrategy":"type-guard","validationCode":"if (!$node instanceof \\Twig\\Node\\MacroNode) { throw new LogicException('setNode on MacrosNode requires MacroNode'); }","typeGuard":"function canReplaceMacro(\\Twig\\Node\\Node $node): bool { return $node instanceof \\Twig\\Node\\MacroNode; }","tryCatchPattern":"try {\n    $macrosNode->setNode($name, $node);\n} catch (\\LogicException $e) {\n    // replacement was not a MacroNode; skip or handle\n}","preventionTips":["Check instanceof MacroNode before any setNode call on MacrosNode","Keep node visitors limited to transformations that preserve node types","Write AST-transform tests similar to testItRejectsReplacingAMacroWithANonMacroNode"],"tags":["twig","ast","macro"],"backgroundTag":"type-mismatch","analyzedSha":"a414c3a491defb5a60f2fc88ef79ff37c90010cd","analyzedAt":"2026-09-13T15:10:46.849Z","contentChangedAt":"2026-09-13T15:10:46.849Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}