rectorphp/rector · error · LogicException
Trying to replace expression (%s) with statement (%s)
Error message
Trying to replace expression (%s) with statement (%s)
What it means
The mirror case of the statement/expression guard in ensureReplacementReasonable(): when the old node is an Expr (lives inside a statement, argument, or property) and the replacement is a Stmt, no valid parent slot exists, so Rector throws LogicException printing both concrete types.
Source
Thrown at src/PhpParser/NodeTraverser/RectorNodeTraverser.php:257
}
}
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
$this->visitors = $this->phpVersionedFilter->filter($this->rectors);
// filter out by composer package constraint
$this->visitors = $this->composerPackageConstraintFilter->filter($this->visitors);
// filter by configuration
$this->visitors = $this->configurationRuleFilter->filter($this->visitors);View on GitHub (pinned to 408fcb0ff1)
Solutions
- Return an Expr when the matched node is an Expr; restructure the rule to match the wrapping statement (e.g. Expression parent) and return the statement from there
- Use Rector's helpers like NodeReplacer/exprToStatement patterns or PhpParserBuilder to emit the statement at the parent level
- Check the message's pair of types to confirm which level you accidentally crossed
Example fix
// before: rule matches Expr (MethodCall) but returns a statement
public function refactor(Node $node): ?Node
{
return new Echo_([$node]); // Stmt inside Expr slot -> throws
}
// after: match the wrapping statement instead
public function refactor(Node $node): ?Node
{
if (! $node instanceof Expression) {
return null;
}
return new Echo_([$node->expr]);
} Defensive patterns
Strategy: type-guard
Type guard
/** True when a replacement Node can legally occupy the old node's slot. */
function isSameStmtExprLevel(\PhpParser\Node $old, \PhpParser\Node $new): bool
{
$oldIsStmt = $old instanceof \PhpParser\Node\Stmt;
$newIsStmt = $new instanceof \PhpParser\Node\Stmt;
return $oldIsStmt === $newIsStmt;
} Prevention
- Decide upfront whether the rule operates on Expr or Stmt and match only that level
- To hoist an expression into a statement, match its Expression parent instead of the Expr
- Keep the pair of types printed by the exception handy when reviewing rule diffs
When it happens
Trigger: refactor() on an Expr node returns a statement object such as new Expression(...), new Echo_(...), or a ClassMethod — e.g. a rule that tries to hoist an expression into its own statement from inside the expression match.
Common situations: Rules that wrap an expression in a statement (extract-to-statement refactorings) implemented at the wrong match level; reusing code between an Expression-level and Expr-level rule.
Related errors
- Trying to replace statement (%s) with expression (%s). Are y
- 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/ecbdbd4aa6545e79.
Report an issue: GitHub.