{"record":{"id":"ecbdbd4aa6545e79","repo":"rectorphp/rector","slug":"trying-to-replace-expression-s-with-statement","errorCode":null,"errorMessage":"Trying to replace expression (%s) with statement (%s)","messagePattern":"Trying to replace expression \\((.+?)\\) with statement \\((.+?)\\)","errorType":"exception","errorClass":"LogicException","httpStatus":null,"severity":"error","filePath":"src/PhpParser/NodeTraverser/RectorNodeTraverser.php","lineNumber":257,"sourceCode":"            }\n        }\n        if ($doNodes !== []) {\n            while ([$i, $replace] = array_pop($doNodes)) {\n                array_splice($nodes, $i, 1, $replace);\n            }\n        }\n        return $nodes;\n    }\n    private function ensureReplacementReasonable(Node $old, Node $new): void\n    {\n        if ($old instanceof Stmt) {\n            if ($new instanceof Expr) {\n                throw new LogicException(sprintf('Trying to replace statement (%s) ', $old->getType()) . sprintf('with expression (%s). Are you missing a ', $new->getType()) . 'Stmt_Expression wrapper?');\n            }\n            return;\n        }\n        if ($new instanceof Stmt) {\n            throw new LogicException(sprintf('Trying to replace expression (%s) ', $old->getType()) . sprintf('with statement (%s)', $new->getType()));\n        }\n    }\n    /**\n     * This must happen after $this->configuration is set after ProcessCommand::execute() is run, otherwise we get default false positives.\n     *\n     * This should be removed after https://github.com/rectorphp/rector/issues/5584 is resolved\n     */\n    private function prepareNodeVisitors(): void\n    {\n        if ($this->areNodeVisitorsPrepared) {\n            return;\n        }\n        // filter out by PHP version\n        $this->visitors = $this->phpVersionedFilter->filter($this->rectors);\n        // filter out by composer package constraint\n        $this->visitors = $this->composerPackageConstraintFilter->filter($this->visitors);\n        // filter by configuration\n        $this->visitors = $this->configurationRuleFilter->filter($this->visitors);","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/rectorphp/rector/blob/408fcb0ff1833e3d26cbc9b04c23a77565814a16/src/PhpParser/NodeTraverser/RectorNodeTraverser.php#L239-L275","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: rule matches Expr (MethodCall) but returns a statement\npublic function refactor(Node $node): ?Node\n{\n    return new Echo_([$node]); // Stmt inside Expr slot -> throws\n}\n\n// after: match the wrapping statement instead\npublic function refactor(Node $node): ?Node\n{\n    if (! $node instanceof Expression) {\n        return null;\n    }\n    return new Echo_([$node->expr]);\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"/** True when a replacement Node can legally occupy the old node's slot. */\nfunction isSameStmtExprLevel(\\PhpParser\\Node $old, \\PhpParser\\Node $new): bool\n{\n    $oldIsStmt = $old instanceof \\PhpParser\\Node\\Stmt;\n    $newIsStmt = $new instanceof \\PhpParser\\Node\\Stmt;\n    return $oldIsStmt === $newIsStmt;\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["php","rector","php-parser","ast","node-replacement"],"backgroundTag":"invalid-node-replacement","analyzedSha":"408fcb0ff1833e3d26cbc9b04c23a77565814a16","analyzedAt":"2026-08-21T05:11:02.643Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}