rectorphp/rector · error · LogicException
Invalid node structure: Contains nested arrays
Error message
Invalid node structure: Contains nested arrays
What it means
RectorNodeTraverser::traverseArray() iterates a node array (statement list, argument list) where every element must be a Node or a non-Node placeholder it can skip. An element that is itself a PHP array has no visitor semantics — the traverser cannot descend into it — so it throws LogicException('Invalid node structure: Contains nested arrays').
Source
Thrown at src/PhpParser/NodeTraverser/RectorNodeTraverser.php:196
if ($traverseChildren) {
$this->traverseNode($subNode);
if ($this->stopTraversal) {
break;
}
}
}
}
/**
* @param Node[] $nodes
* @return Node[]
*/
private function traverseArray(array $nodes): array
{
$doNodes = [];
foreach ($nodes as $i => $node) {
if (!$node instanceof Node) {
if (\is_array($node)) {
throw new LogicException('Invalid node structure: Contains nested arrays');
}
continue;
}
$traverseChildren = \true;
$currentNodeVisitors = $this->getVisitorsForNode($node);
foreach ($currentNodeVisitors as $currentNodeVisitor) {
$return = $currentNodeVisitor->enterNode($node);
if ($return !== null) {
if ($return instanceof Node) {
$originalNodeNodeClass = get_class($node);
$this->ensureReplacementReasonable($node, $return);
$nodes[$i] = $node = $return;
if ($originalNodeNodeClass !== get_class($return)) {
// stop traversing as node type changed and visitors won't work
continue 2;
}
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];View on GitHub (pinned to 408fcb0ff1)
Solutions
- Flatten the return value: return [$stmtA, $stmtB]; (one array of Node, not arrays of arrays)
- If aggregating, use array_merge(...$groups) before returning
- Add a unit test asserting every element of refactor()'s array return is PhpParser\Node
Example fix
// before return [[$newAssign, $newReturn]]; // after return [$newAssign, $newReturn];
Defensive patterns
Strategy: validation
Validate before calling
// guard any node-array you hand back from a visitor
function assertFlatNodeArray(array $nodes): array
{
foreach ($nodes as $n) {
if (is_array($n)) {
throw new LogicException('Nested node array detected; flatten before returning.');
}
}
return $nodes;
} Type guard
function isFlatNodeArray(array $nodes): bool
{
foreach ($nodes as $n) {
if (is_array($n)) {
return false;
}
}
return true;
} Try / catch
try {
$result = $visitor->enterNode($node);
} catch (\LogicException $e) {
// rule-internal bug: fail the test run loudly with the visitor class name
$this->fail(get_class($visitor) . ': ' . $e->getMessage());
} Prevention
- Unit-test custom rules through AbstractRectorTestCase so nested returns fail before production
- When aggregating node groups, always array_merge(...$groups) at the boundary
- Annotate return types (@return Node[]) so static analysis flags array-of-arrays
When it happens
Trigger: A custom NodeVisitor or rule returns a nested array where a flat node list is expected: return [[$stmtA, $stmtB]]; from leaveNode() replacement, or refactor() returning array-of-arrays on one code path.
Common situations: Refactoring a rule that groups generated statements per match and forgets array_merge/array_shift; upgrading php-parser versions where replacement arrays became stricter; a rule builder helper that wraps results in an extra layer.
Related errors
- REPLACE_WITH_NULL can not be used if the parent structure is
- 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/1fa0bdd546e21e03.
Report an issue: GitHub.