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

  1. Flatten the return value: return [$stmtA, $stmtB]; (one array of Node, not arrays of arrays)
  2. If aggregating, use array_merge(...$groups) before returning
  3. 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

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


AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21). Data as JSON: /api/errors/1fa0bdd546e21e03. Report an issue: GitHub.