{"record":{"id":"e120a3ad10691d0c","repo":"twigphp/Twig","slug":"emptynode-cannot-have-children","errorCode":null,"errorMessage":"EmptyNode cannot have children.","messagePattern":"EmptyNode cannot have children\\.","errorType":"exception","errorClass":"LogicException","httpStatus":null,"severity":"error","filePath":"src/Node/EmptyNode.php","lineNumber":31,"sourceCode":"\nuse Twig\\Attribute\\YieldReady;\n\n/**\n * Represents an empty node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nfinal class EmptyNode extends Node\n{\n    public function __construct(int $lineno = 0)\n    {\n        parent::__construct([], [], $lineno);\n    }\n\n    public function setNode(string $name, Node $node): void\n    {\n        throw new \\LogicException('EmptyNode cannot have children.');\n    }\n}\n","sourceCodeStart":13,"sourceCodeEnd":34,"githubUrl":"https://github.com/twigphp/Twig/blob/a414c3a491defb5a60f2fc88ef79ff37c90010cd/src/Node/EmptyNode.php#L13-L34","documentation":"EmptyNode is Twig's placeholder node representing 'nothing' (e.g. an omitted block of an if/for). As an intentional leaf, its setNode() is overridden to always throw: the compiler must never attach children to an EmptyNode. If you reach this, some code is trying to fill in a node slot that is supposed to stay empty.","triggerScenarios":"Calling ->setNode('...', $node) directly on an EmptyNode instance, or a NodeVisitor/compiler pass written by you or an extension that unconditionally assigns children to the node returned from a parse slot without checking it's an EmptyNode placeholder.","commonSituations":"Custom NodeVisitor mutating nodes without instanceof checks; custom expression parsers reusing EmptyNode as a generic container; a visitor that rewrites nodes in leaveNode() but hits the empty placeholder branch of a construct.","solutions":["Before calling setNode(), check the target is a real node: if (!$node instanceof EmptyNode) { $node->setNode(...); } and skip or replace the placeholder otherwise.","Replace the EmptyNode with the concrete node type first (e.g. construct the intended Node and swap it via setNode on the PARENT, not on the EmptyNode).","In a NodeVisitor, implement the swap in leaveNode() by returning a new node instead of mutating the EmptyNode.","If this comes from a third-party extension, update the extension; EmptyNode's immutable no-children contract is by design."],"exampleFix":"// before\n$node->setNode('body', $newBody); // crashes when $node is EmptyNode\n// after\nif (!$node instanceof \\Twig\\Node\\EmptyNode) {\n    $node->setNode('body', $newBody);\n}","handlingStrategy":"type-guard","validationCode":"if ($node instanceof \\Twig\\Node\\EmptyNode) {\n    throw new \\LogicException('Cannot attach children to an EmptyNode placeholder; replace the placeholder in its parent instead.');\n}","typeGuard":"function isAttachableNode(\\Twig\\Node\\Node $n): bool { return !$n instanceof \\Twig\\Node\\EmptyNode; }","tryCatchPattern":"try {\n    $node->setNode($name, $child);\n} catch (\\LogicException $e) {\n    // swap the EmptyNode out at its parent instead of mutating it\n}","preventionTips":["Treat EmptyNode as immutable placeholder — never a container.","In NodeVisitors, replace nodes by returning a new node from leaveNode() rather than mutating in place.","Instanceof-check before every setNode call on nodes obtained from parsing."],"tags":["php","twig","ast","node-visitor","internal"],"backgroundTag":"internal-invariant-violation","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"}