rectorphp/rector · error · LogicException
REPLACE_WITH_NULL can not be used if the parent structure is
Error message
REPLACE_WITH_NULL can not be used if the parent structure is an array
What it means
Inside traverseArray(), NodeVisitor::REPLACE_WITH_NULL is meaningless: the node sits at an integer offset of a node array, not on an object property that could be nulled. Rector throws LogicException immediately and requires REMOVE_NODE, which splices the offset out of the array instead.
Source
Thrown at src/PhpParser/NodeTraverser/RectorNodeTraverser.php:228
// stop traversing as node type changed and visitors won't work
continue 2;
}
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
continue 2;
} elseif ($return === NodeVisitor::REMOVE_NODE) {
$doNodes[] = [$i, []];
continue 2;
} elseif ($return === NodeVisitor::DONT_TRAVERSE_CHILDREN) {
$traverseChildren = \false;
} elseif ($return === NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN) {
$traverseChildren = \false;
break;
} elseif ($return === NodeVisitor::STOP_TRAVERSAL) {
$this->stopTraversal = \true;
break 2;
} elseif ($return === NodeVisitor::REPLACE_WITH_NULL) {
throw new LogicException('REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new LogicException('enterNode() returned invalid value of type ' . gettype($return));
}
}
}
if ($traverseChildren) {
$this->traverseNode($node);
if ($this->stopTraversal) {
break;
}
}
}
if ($doNodes !== []) {
while ([$i, $replace] = array_pop($doNodes)) {
array_splice($nodes, $i, 1, $replace);
}
}
return $nodes;View on GitHub (pinned to 408fcb0ff1)
Solutions
- Return NodeVisitor::REMOVE_NODE to delete a node from a statement/argument array
- In Rector rules, prefer having refactor() return NodeVisitor::REMOVE_NODE instead of hand-rolled visitor constants
- Reserve REPLACE_WITH_NULL for nodes stored on object properties (e.g. a single default value)
Example fix
// before: node lives inside $classMethod->stmts[]
if ($shouldDelete) {
return NodeVisitor::REPLACE_WITH_NULL;
}
// after
if ($shouldDelete) {
return NodeVisitor::REMOVE_NODE;
} Defensive patterns
Strategy: validation
Validate before calling
// pick the removal sentinel by parent container, up front
use PhpParser\NodeVisitor;
$removalSentinel = $parentNode instanceof \PhpParser\Node\Stmt\Expression
? NodeVisitor::REMOVE_NODE // node sits in a stmts[] array
: NodeVisitor::REMOVE_NODE; // arrays and properties both use REMOVE_NODE; null only via property assignment
// REPLACE_WITH_NULL is never valid from enterNode on array children: never select it there. Try / catch
try {
$traverser->traverse([$node]);
} catch (\LogicException $e) {
// visitor contract bug: inspect which sentinel the offending visitor returned
$this->fail($e->getMessage());
} Prevention
- Default to NodeVisitor::REMOVE_NODE for deletions; it works in every context
- Let REMOVE_WITH_NULL stay an implementation detail of property-backed replacement in Rector's traverser, not your return value
- In Rector rules, return the constant from refactor() and let AbstractRector translate
When it happens
Trigger: An enterNode()/leaveNode() visitor returns NodeVisitor::REPLACE_WITH_NULL for a node whose parent container is an array — typically a statement inside ClassMethod->stmts or an argument inside FuncCall->args.
Common situations: Porting a php-parser visitor that used null returns on property-backed nodes; writing a rule that deletes statements and picking the wrong sentinel constant by autocomplete.
Related errors
- Invalid node structure: Contains nested arrays
- enterNode() returned invalid value of type
- Trying to replace statement (%s) with expression (%s). Are y
- Trying to replace expression (%s) with statement (%s)
- "%s" rule is deprecated, as turning a docblock type into a r
AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21).
Data as JSON: /api/errors/6b81b5c3bfc5b195.
Report an issue: GitHub.