rectorphp/rector · error · LogicException
Trying to replace statement (%s) with expression (%s). Are y
Error message
Trying to replace statement (%s) with expression (%s). Are you missing a Stmt_Expression wrapper?
What it means
ensureReplacementReasonable() runs before any replacement is committed: if the old node is a Stmt and the new one is a bare Expr, PHP-Parser's AST could not represent it (statements must sit at statement positions), so Rector throws LogicException and hints at the missing Stmt wrapper — PhpParser\Node\Stmt\Expression.
Source
Thrown at src/PhpParser/NodeTraverser/RectorNodeTraverser.php:252
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;
}
private function ensureReplacementReasonable(Node $old, Node $new): void
{
if ($old instanceof Stmt) {
if ($new instanceof Expr) {
throw new LogicException(sprintf('Trying to replace statement (%s) ', $old->getType()) . sprintf('with expression (%s). Are you missing a ', $new->getType()) . 'Stmt_Expression wrapper?');
}
return;
}
if ($new instanceof Stmt) {
throw new LogicException(sprintf('Trying to replace expression (%s) ', $old->getType()) . sprintf('with statement (%s)', $new->getType()));
}
}
/**
* This must happen after $this->configuration is set after ProcessCommand::execute() is run, otherwise we get default false positives.
*
* This should be removed after https://github.com/rectorphp/rector/issues/5584 is resolved
*/
private function prepareNodeVisitors(): void
{
if ($this->areNodeVisitorsPrepared) {
return;
}
// filter out by PHP versionView on GitHub (pinned to 408fcb0ff1)
Solutions
- Wrap the expression: return new \PhpParser\Node\Stmt\Expression($expr);
- Or return an array of statements if you produce several
- Double-check you matched the right node level: match Expression and return a new Expression, not the inner Expr
Example fix
// before: $node is Stmt_Expression return new MethodCall($node->expr, 'configure'); // after use PhpParser\Node\Stmt\Expression; return new Expression(new MethodCall($node->expr, 'configure'));
Defensive patterns
Strategy: type-guard
Type guard
use PhpParser\Node;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Expression;
/** Coerce a replacement to the level of the node it replaces. */
function toSameNodeLevel(Node $old, Node $new): Node
{
if ($old instanceof Stmt && ! $new instanceof Stmt && $new instanceof \PhpParser\Node\Expr) {
return new Expression($new);
}
return $new;
} Prevention
- Match and return at the same AST level (Expression in, Expression out)
- Wrap generated expressions with new Expression(...) the moment you create them for statement slots
- Add fixture tests covering the exact statement type your rule rewrites
When it happens
Trigger: A rule matched on a statement (e.g. Expression, Echo_, If_) has its refactor() return an Expr such as new MethodCall(...) or new Assign(...) without wrapping; the message prints the concrete types, e.g. 'Trying to replace statement (Stmt_Expression) with expression (Expr_MethodCall)'.
Common situations: Downgrading a statement into a call (replacing an if with a function call); rules migrated from older Rector where the check was looser; returning the inner expression of a wrapped node by mistake.
Related errors
- Trying to replace expression (%s) with statement (%s)
- Invalid node structure: Contains nested arrays
- REPLACE_WITH_NULL can not be used if the parent structure is
- enterNode() returned invalid value of type
- "%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/fa52ae93696f8d85.
Report an issue: GitHub.