From dddda674a2e5ddf37f108602ebaaa912b3bd46fd Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 2 Apr 2026 21:33:46 +0700 Subject: [PATCH 1/6] [DeadCode] Skip with assign to call with target has #[NoDiscard] attribute --- ...ip_assign_no_discard_function_call.php.inc | 19 +++++++++++++++++++ .../RemoveUnusedVariableAssignRector.php | 15 ++++++++++++++- src/PhpParser/AstResolver.php | 2 +- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_function_call.php.inc diff --git a/rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_function_call.php.inc b/rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_function_call.php.inc new file mode 100644 index 00000000000..6890d4ccabd --- /dev/null +++ b/rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_function_call.php.inc @@ -0,0 +1,19 @@ +some_call(); + } + + #[\NoDiscard] + private function some_call() + { + + } +} + +?> \ No newline at end of file diff --git a/rules/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector.php b/rules/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector.php index 1fe9691bd89..badd2c74ff6 100644 --- a/rules/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector.php +++ b/rules/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector.php @@ -8,6 +8,7 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\AssignRef; +use PhpParser\Node\Expr\CallLike; use PhpParser\Node\Expr\Cast; use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\FuncCall; @@ -25,6 +26,8 @@ use Rector\NodeAnalyzer\VariableAnalyzer; use Rector\NodeManipulator\StmtsManipulator; use Rector\Php\ReservedKeywordAnalyzer; +use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer; +use Rector\PhpParser\AstResolver; use Rector\PhpParser\Enum\NodeGroup; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\Rector\AbstractRector; @@ -42,7 +45,9 @@ public function __construct( private readonly SideEffectNodeDetector $sideEffectNodeDetector, private readonly VariableAnalyzer $variableAnalyzer, private readonly BetterNodeFinder $betterNodeFinder, - private readonly StmtsManipulator $stmtsManipulator + private readonly StmtsManipulator $stmtsManipulator, + private readonly AstResolver $astResolver, + private readonly PhpAttributeAnalyzer $phpAttributeAnalyzer ) { } @@ -265,6 +270,14 @@ function (Node $subNode) use (&$refVariableNames) { continue; } + if ($assign->expr instanceof CallLike) { + $targetCall = $this->astResolver->resolveClassMethodOrFunctionFromCall($assign->expr); + if (($targetCall instanceof ClassMethod || $targetCall instanceof Function_) + && $this->phpAttributeAnalyzer->hasPhpAttribute($targetCall, 'NoDiscard')) { + continue; + } + } + $assignedVariableNamesByStmtPosition[$key] = $variableName; } diff --git a/src/PhpParser/AstResolver.php b/src/PhpParser/AstResolver.php index 392b22b2f29..3b1047bde6f 100644 --- a/src/PhpParser/AstResolver.php +++ b/src/PhpParser/AstResolver.php @@ -112,7 +112,7 @@ function (Node $node) use ($classLikeName, $methodName, &$classMethod): bool { } public function resolveClassMethodOrFunctionFromCall( - FuncCall | StaticCall | MethodCall | New_ $call + FuncCall | StaticCall | MethodCall | New_ | NullsafeMethodCall $call ): ClassMethod | Function_ | null { if ($call instanceof FuncCall) { return $this->resolveFunctionFromFuncCall($call); From e4c78b2f1af069b32e37e83a6eff15bbc74065dd Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 2 Apr 2026 21:37:24 +0700 Subject: [PATCH 2/6] [DeadCode] Skip with assign to call with target has #[NoDiscard] attribute --- .../Assign/RemoveUnusedVariableAssignRector.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/rules/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector.php b/rules/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector.php index badd2c74ff6..eadfc71a7bb 100644 --- a/rules/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector.php +++ b/rules/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector.php @@ -8,11 +8,14 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\AssignRef; -use PhpParser\Node\Expr\CallLike; use PhpParser\Node\Expr\Cast; use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Include_; +use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Expr\New_; +use PhpParser\Node\Expr\NullsafeMethodCall; +use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Class_; @@ -270,7 +273,11 @@ function (Node $subNode) use (&$refVariableNames) { continue; } - if ($assign->expr instanceof CallLike) { + if ($assign->expr instanceof FuncCall + || $assign->expr instanceof StaticCall + || $assign->expr instanceof MethodCall + || $assign->expr instanceof New_ + || $assign->expr instanceof NullsafeMethodCall) { $targetCall = $this->astResolver->resolveClassMethodOrFunctionFromCall($assign->expr); if (($targetCall instanceof ClassMethod || $targetCall instanceof Function_) && $this->phpAttributeAnalyzer->hasPhpAttribute($targetCall, 'NoDiscard')) { From 2d7e74d8fb2a2e087ab17e0a9f36966b2b4176b6 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 2 Apr 2026 21:38:17 +0700 Subject: [PATCH 3/6] [DeadCode] Skip with assign to call with target has #[NoDiscard] attribute --- .../Fixture/skip_assign_no_discard_function_call.php.inc | 2 -- 1 file changed, 2 deletions(-) diff --git a/rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_function_call.php.inc b/rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_function_call.php.inc index 6890d4ccabd..d1085923541 100644 --- a/rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_function_call.php.inc +++ b/rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_function_call.php.inc @@ -15,5 +15,3 @@ class SkipAssignNoDiscardFunctionCall } } - -?> \ No newline at end of file From 21a2e4588ce17ff9796dce330f48b484efbe49b3 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 2 Apr 2026 21:41:23 +0700 Subject: [PATCH 4/6] [DeadCode] Skip with assign to call with target has #[NoDiscard] attribute --- rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php b/rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php index b76e243b90b..b87cf7054dd 100644 --- a/rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php +++ b/rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php @@ -12,6 +12,7 @@ use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Property; use Rector\NodeNameResolver\NodeNameResolver; use Rector\PhpAttribute\Enum\DocTagNodeState; @@ -23,7 +24,7 @@ public function __construct( ) { } - public function hasPhpAttribute(Property | ClassLike | ClassMethod | Param $node, string $attributeClass): bool + public function hasPhpAttribute(Property | ClassLike | ClassMethod | Param | Function_ $node, string $attributeClass): bool { foreach ($node->attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attribute) { From 68b490bd7959c58de3e428d80a9f17bd3508dd82 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 2 Apr 2026 21:42:26 +0700 Subject: [PATCH 5/6] [DeadCode] Skip with assign to call with target has #[NoDiscard] attribute --- rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php b/rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php index b87cf7054dd..264b0c246a0 100644 --- a/rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php +++ b/rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php @@ -24,7 +24,7 @@ public function __construct( ) { } - public function hasPhpAttribute(Property | ClassLike | ClassMethod | Param | Function_ $node, string $attributeClass): bool + public function hasPhpAttribute(Property | ClassLike | ClassMethod | Function_ | Param $node, string $attributeClass): bool { foreach ($node->attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attribute) { @@ -42,7 +42,7 @@ public function hasPhpAttribute(Property | ClassLike | ClassMethod | Param | Fun /** * @param string[] $attributeClasses */ - public function hasPhpAttributes(Property | ClassLike | ClassMethod | Param $node, array $attributeClasses): bool + public function hasPhpAttributes(Property | ClassLike | ClassMethod | Function_ | Param $node, array $attributeClasses): bool { foreach ($attributeClasses as $attributeClass) { if ($this->hasPhpAttribute($node, $attributeClass)) { From c1f81d444fea52e4ebb9ada82904847e3c50c6dc Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 2 Apr 2026 21:48:04 +0700 Subject: [PATCH 6/6] rename test --- ..._call.php.inc => skip_assign_no_discard_method_call.php.inc} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/{skip_assign_no_discard_function_call.php.inc => skip_assign_no_discard_method_call.php.inc} (86%) diff --git a/rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_function_call.php.inc b/rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_method_call.php.inc similarity index 86% rename from rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_function_call.php.inc rename to rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_method_call.php.inc index d1085923541..754972eff93 100644 --- a/rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_function_call.php.inc +++ b/rules-tests/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector/Fixture/skip_assign_no_discard_method_call.php.inc @@ -2,7 +2,7 @@ namespace Rector\Tests\DeadCode\Rector\Assign\RemoveUnusedVariableAssignRector\Fixture; -class SkipAssignNoDiscardFunctionCall +class SkipAssignNoDiscardMethodCall { public function run() {