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 version

View on GitHub (pinned to 408fcb0ff1)

Solutions

  1. Wrap the expression: return new \PhpParser\Node\Stmt\Expression($expr);
  2. Or return an array of statements if you produce several
  3. 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

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


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