twigphp/Twig · error · LogicException
A documentation target can only be set while parsing a tag.
Error message
A documentation target can only be set while parsing a tag.
What it means
Parser::setDocumentationTarget() records which Node a documentation comment (like {@documentation}) applies to, but only while a tag is being parsed. Parser internals push a placeholder onto the documentationTargets stack when a tag starts and pop it when the tag ends; calling setDocumentationTarget with an empty stack means no tag is currently open, so the call is invalid.
Solutions
- Move the setDocumentationTarget() call so it happens while the tag's parse is still in progress (inside the TokenParser's parse() before returning).
- Remove the call entirely if the custom tag does not support the documentation feature.
- Guard the call with a state check if it comes from optional code paths that may run outside tag parsing.
Example fix
// before (TokenParser, too late)
public function parse(Token $token): Node
{
$node = $this->parser->parseExpression();
$this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
$this->parser->setDocumentationTarget($node); // stack already popped
return $node;
}
// after
public function parse(Token $token): Node
{
$node = $this->parser->parseExpression();
$this->parser->setDocumentationTarget($node); // still inside tag parsing
$this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
return $node;
} Defensive patterns
Strategy: type-guard
Type guard
// Only call inside a TokenParser::parse() that has not yet consumed BLOCK_END_TYPE if (!($this->parser instanceof \Twig\Parser)) return; // and only within parse scope
Try / catch
try { $parser->setDocumentationTarget($node); } catch (\LogicException $e) { if (str_contains($e->getMessage(), 'while parsing a tag')) { /* called outside tag parsing */ } throw $e; } Prevention
- Only invoke parser state-mutating methods from within TokenParser::parse()
- Never call setDocumentationTarget from NodeVisitors
- Re-check custom parsers after upgrading Twig versions
When it happens
Trigger: Calling $parser->setDocumentationTarget($node) from a TokenParser after the tag's subparse has finished (the placeholder was already popped), or from code outside tag parsing entirely (e.g. a NodeVisitor invoking it during traversal).
Common situations: Custom TokenParser implementations calling setDocumentationTarget too late/early in parseTag lifecycle; debugging or monkey-patching the parser; extension code ported across Twig versions where documentation-target handling changed.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- The documentation target for a tag can only be set once.
- Left side must be ArrayExpression for object/mapping…
- The "name" attribute must be a string.
- The "format_list" filter requires the "IntlListFormatter"…
- SpanishInflector is not available.
AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13).
Data as JSON: /api/errors/0ce0b43d2f1525cf.
Report an issue: GitHub.
Appendix: source
Thrown at src/Parser.php:325
{
if (isset($this->blocks[$name])) {
throw new SyntaxError(\sprintf("The block '%s' has already been defined line %d.", $name, $this->blocks[$name]->getTemplateLine()), $this->getCurrentToken()->getLine(), $this->blocks[$name]->getSourceContext());
}
$this->blocks[$name] = new BodyNode([$value], [], $value->getTemplateLine());
}
public function hasMacro(string $name): bool
{
trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__);
return isset($this->macros[$name]);
}
public function setDocumentationTarget(Node $node): void
{
if (null === $index = array_key_last($this->documentationTargets)) {
throw new \LogicException('A documentation target can only be set while parsing a tag.');
}
if (null !== $this->documentationTargets[$index]) {
throw new \LogicException('The documentation target for a tag can only be set once.');
}
$this->documentationTargets[$index] = $node;
}
public function setMacro(string $name, MacroNode $node): void
{
if (isset($this->macros[$name])) {
trigger_deprecation('twig/twig', '3.29', 'Defining the macro "%s" more than once in "%s" is deprecated and will throw a SyntaxError in Twig 4.0 (previous definition at line %d, new definition at line %d). The last definition is used in Twig 3.', $name, $this->stream->getSourceContext()->getName(), $this->macros[$name]->getTemplateLine(), $node->getTemplateLine());
}
$this->macros[$name] = $node;
}
public function addTrait($trait): voidView on GitHub (pinned to a414c3a491)