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

  1. Return NodeVisitor::REMOVE_NODE to delete a node from a statement/argument array
  2. In Rector rules, prefer having refactor() return NodeVisitor::REMOVE_NODE instead of hand-rolled visitor constants
  3. 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

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


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