twigphp/Twig · error · LogicException
Operators with more than 2 operands are not supported yet…
Error message
Operators with more than 2 operands are not supported yet, got %d.
What it means
During safe-analysis, when a node implements OperatorEscapeInterface the visitor intersects the safeness of its operands; the current implementation only supports one or two operands. An operator declaring more than 2 operands to escape hits this LogicException, signaling an unsupported operator definition in the optimizer pipeline.
Solutions
- Change getOperandNamesToEscape() to return at most two operand names; escape the extra operands by different means (e.g. mark them safe manually via setSafe or handle them in your own visitor).
- Split the multi-operand operation into nested binary nodes so each has at most 2 escapable operands.
- Implement a custom NodeVisitor that performs safe analysis for your operator and registers it, bypassing the core limitation.
- Keep the operands <= 2 when designing any node implementing OperatorEscapeInterface.
Example fix
// before
public function getOperandNamesToEscape(): array
{
return ['left', 'middle', 'right'];
}
// after
public function getOperandNamesToEscape(): array
{
return ['left', 'right']; // handle 'middle' elsewhere or nest nodes
} Defensive patterns
Strategy: validation
Validate before calling
$operands = $node->getOperandNamesToEscape();
if (count($operands) > 2) {
throw new \LogicException('Operator ' . get_class($node) . ' declares ' . count($operands) . ' escapable operands; max supported is 2.');
} Try / catch
try {
$env->compile($source);
} catch (\LogicException $e) {
if (str_contains($e->getMessage(), 'more than 2 operands')) {
// disable optimizer or fix the custom operator definition
} else {
throw $e;
}
} Prevention
- Design OperatorEscapeInterface nodes with at most 2 escapable operands.
- Split multi-operand operations into nested binary nodes.
- Test custom operators with the optimizer enabled (safe analysis) before release.
When it happens
Trigger: A custom Twig operator/expression node implementing OperatorEscapeInterface::getOperandNamesToEscape() returning 3+ child names; adding an escape-analysis-aware operator extension with multiple operands processed by SafeAnalysisNodeVisitor with the optimizer enabled.
Common situations: Writing a custom ternary-style or variadic operator expression class in a Twig extension; a third-party extension that defines an OperatorEscapeInterface node designed for newer/older Twig internals; forked core modifications adding operands to an existing operator.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- The "deprecation_info" option must be an instance of
- A block chain cannot contain templates from different Twig…
- The tag of a node can only be set once.
- Attribute " " does not exist for Node " ".
- Node " " does not exist for Node " ".
AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13).
Data as JSON: /api/errors/a8ba8ae810a2406e.
Report an issue: GitHub.
Appendix: source
Thrown at src/NodeVisitor/SafeAnalysisNodeVisitor.php:108
return $node;
}
public function leaveNode(Node $node, Environment $env): ?Node
{
if ($node instanceof ConstantExpression) {
// constants are marked safe for all
$this->setSafe($node, ['all']);
} elseif ($node instanceof BlockReferenceExpression) {
// blocks are safe by definition
$this->setSafe($node, ['all']);
} elseif ($node instanceof ParentExpression) {
// parent block is safe by definition
$this->setSafe($node, ['all']);
} elseif ($node instanceof OperatorEscapeInterface) {
// intersect safeness of operands
$operands = $node->getOperandNamesToEscape();
if (2 < \count($operands)) {
throw new \LogicException(\sprintf('Operators with more than 2 operands are not supported yet, got %d.', \count($operands)));
} elseif (2 === \count($operands)) {
$safe = $this->intersectSafe($this->getSafe($node->getNode($operands[0])), $this->getSafe($node->getNode($operands[1])));
$this->setSafe($node, $safe);
}
} elseif ($node instanceof FilterExpression) {
// filter expression is safe when the filter is safe
if ($node->hasAttribute('twig_callable')) {
$filter = $node->getAttribute('twig_callable');
} else {
// legacy
$filter = $env->getFilter($node->getAttribute('name'));
}
if ($filter) {
$safe = $filter->getSafe($node->getNode('arguments'));
if (null === $safe) {
trigger_deprecation('twig/twig', '3.16', 'The "%s::getSafe()" method should not return "null" anymore, return "[]" instead.', $filter::class);
$safe = [];View on GitHub (pinned to a414c3a491)