From 1518a8a852ee4f7ee7597c6f5d59c9356c9ff067 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 1 Feb 2026 08:29:49 +0700 Subject: [PATCH 01/10] [DeadCode] Allow remove @var on expression assign on RemoveUselessVarTagRector --- .../Fixture/on_expression.php.inc | 20 ++++++++++++ .../PhpDoc/DeadVarTagValueNodeAnalyzer.php | 31 ++++++++++++------- .../PhpDoc/TagRemover/VarTagRemover.php | 2 +- .../Property/RemoveUselessVarTagRector.php | 10 ++++-- 4 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 rules-tests/DeadCode/Rector/Property/RemoveUselessVarTagRector/Fixture/on_expression.php.inc diff --git a/rules-tests/DeadCode/Rector/Property/RemoveUselessVarTagRector/Fixture/on_expression.php.inc b/rules-tests/DeadCode/Rector/Property/RemoveUselessVarTagRector/Fixture/on_expression.php.inc new file mode 100644 index 00000000000..ed6084a7e7b --- /dev/null +++ b/rules-tests/DeadCode/Rector/Property/RemoveUselessVarTagRector/Fixture/on_expression.php.inc @@ -0,0 +1,20 @@ + +----- + diff --git a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php index 9d0b078cee3..4b2a68d0380 100644 --- a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php +++ b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php @@ -6,6 +6,7 @@ use PhpParser\Node; use PhpParser\Node\Stmt\ClassConst; +use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Property; use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; @@ -14,6 +15,7 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use Rector\DeadCode\PhpDoc\Guard\TemplateTypeRemovalGuard; +use Rector\NodeTypeResolver\NodeTypeResolver; use Rector\NodeTypeResolver\TypeComparator\TypeComparator; use Rector\StaticTypeMapper\StaticTypeMapper; @@ -22,27 +24,34 @@ public function __construct( private TypeComparator $typeComparator, private StaticTypeMapper $staticTypeMapper, - private TemplateTypeRemovalGuard $templateTypeRemovalGuard + private TemplateTypeRemovalGuard $templateTypeRemovalGuard, + private NodeTypeResolver $nodeTypeResolver ) { } - public function isDead(VarTagValueNode $varTagValueNode, Property|ClassConst $property): bool + public function isDead(VarTagValueNode $varTagValueNode, Property|ClassConst|Expression $node): bool { - if (! $property->type instanceof Node) { - return false; - } + if (! $node instanceof Expression) { + if (! $node->type instanceof Node) { + return false; + } - if ($varTagValueNode->description !== '') { - return false; + if ($varTagValueNode->description !== '') { + return false; + } } + $targetNode = $node instanceof Expression + ? $node->expr + : $node->type; + if ($varTagValueNode->type instanceof GenericTypeNode) { return false; } // is strict type superior to doc type? keep strict type only - $propertyType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($property->type); - $docType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($varTagValueNode->type, $property); + $propertyType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($targetNode); + $docType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($varTagValueNode->type, $node); if (! $this->templateTypeRemovalGuard->isLegal($docType)) { return false; @@ -59,9 +68,9 @@ public function isDead(VarTagValueNode $varTagValueNode, Property|ClassConst $pr } if ($this->typeComparator->arePhpParserAndPhpStanPhpDocTypesEqual( - $property->type, + $targetNode, $varTagValueNode->type, - $property + $node )) { return true; } diff --git a/rules/DeadCode/PhpDoc/TagRemover/VarTagRemover.php b/rules/DeadCode/PhpDoc/TagRemover/VarTagRemover.php index 929b496cd77..a33631c9d15 100644 --- a/rules/DeadCode/PhpDoc/TagRemover/VarTagRemover.php +++ b/rules/DeadCode/PhpDoc/TagRemover/VarTagRemover.php @@ -32,7 +32,7 @@ public function __construct( ) { } - public function removeVarTagIfUseless(PhpDocInfo $phpDocInfo, Property|ClassConst $property): bool + public function removeVarTagIfUseless(PhpDocInfo $phpDocInfo, Property|ClassConst|Expression $property): bool { $varTagValueNode = $phpDocInfo->getVarTagValueNode(); if (! $varTagValueNode instanceof VarTagValueNode) { diff --git a/rules/DeadCode/Rector/Property/RemoveUselessVarTagRector.php b/rules/DeadCode/Rector/Property/RemoveUselessVarTagRector.php index f3d21ac78de..9cd915256b6 100644 --- a/rules/DeadCode/Rector/Property/RemoveUselessVarTagRector.php +++ b/rules/DeadCode/Rector/Property/RemoveUselessVarTagRector.php @@ -5,7 +5,9 @@ namespace Rector\DeadCode\Rector\Property; use PhpParser\Node; +use PhpParser\Node\Expr\Assign; use PhpParser\Node\Stmt\ClassConst; +use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Property; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\DeadCode\PhpDoc\TagRemover\VarTagRemover; @@ -54,14 +56,18 @@ final class SomeClass */ public function getNodeTypes(): array { - return [Property::class, ClassConst::class]; + return [Property::class, ClassConst::class, Expression::class]; } /** - * @param Property|ClassConst $node + * @param Property|ClassConst|Expression $node */ public function refactor(Node $node): ?Node { + if ($node instanceof Expression && ! $node->expr instanceof Assign) { + return null; + } + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); $hasChanged = $this->varTagRemover->removeVarTagIfUseless($phpDocInfo, $node); From 2bd4b027fd9a8830cab797c9a381c4798da685b4 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 1 Feb 2026 08:40:44 +0700 Subject: [PATCH 02/10] skip non-equal type with var type --- .../skip_on_expression_from_docblock.php.inc | 10 ++++++++++ .../Source/SomeReturnDocblock.php | 15 +++++++++++++++ .../PhpDoc/DeadVarTagValueNodeAnalyzer.php | 9 +++++++++ 3 files changed, 34 insertions(+) create mode 100644 rules-tests/DeadCode/Rector/Property/RemoveUselessVarTagRector/Fixture/skip_on_expression_from_docblock.php.inc create mode 100644 rules-tests/DeadCode/Rector/Property/RemoveUselessVarTagRector/Source/SomeReturnDocblock.php diff --git a/rules-tests/DeadCode/Rector/Property/RemoveUselessVarTagRector/Fixture/skip_on_expression_from_docblock.php.inc b/rules-tests/DeadCode/Rector/Property/RemoveUselessVarTagRector/Fixture/skip_on_expression_from_docblock.php.inc new file mode 100644 index 00000000000..ace38d5063b --- /dev/null +++ b/rules-tests/DeadCode/Rector/Property/RemoveUselessVarTagRector/Fixture/skip_on_expression_from_docblock.php.inc @@ -0,0 +1,10 @@ +get(); diff --git a/rules-tests/DeadCode/Rector/Property/RemoveUselessVarTagRector/Source/SomeReturnDocblock.php b/rules-tests/DeadCode/Rector/Property/RemoveUselessVarTagRector/Source/SomeReturnDocblock.php new file mode 100644 index 00000000000..eadb55e6b29 --- /dev/null +++ b/rules-tests/DeadCode/Rector/Property/RemoveUselessVarTagRector/Source/SomeReturnDocblock.php @@ -0,0 +1,15 @@ +expr : $node->type; + if ($node instanceof Expression) { + $varType = $this->nodeTypeResolver->getType($targetNode); + $nativeType = $this->nodeTypeResolver->getNativeType($targetNode); + + if (! $varType->equals($nativeType)) { + return false; + } + } + if ($varTagValueNode->type instanceof GenericTypeNode) { return false; } From 0f321000e6fbe05bef618e25fb0d9de8a5f0a8e8 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 1 Feb 2026 08:43:11 +0700 Subject: [PATCH 03/10] only get type check on Expr --- rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php index 707867af498..b2d051708e7 100644 --- a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php +++ b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php @@ -5,6 +5,7 @@ namespace Rector\DeadCode\PhpDoc; use PhpParser\Node; +use PhpParser\Node\Expr; use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Property; @@ -46,6 +47,10 @@ public function isDead(VarTagValueNode $varTagValueNode, Property|ClassConst|Exp : $node->type; if ($node instanceof Expression) { + if (! $targetNode instanceof Expr) { + return false; + } + $varType = $this->nodeTypeResolver->getType($targetNode); $nativeType = $this->nodeTypeResolver->getNativeType($targetNode); From 0b8cb5425868d464e1e7d35af1ed4a929d467ac4 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 1 Feb 2026 08:45:34 +0700 Subject: [PATCH 04/10] allow with description --- .../DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php index b2d051708e7..7d22730fea4 100644 --- a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php +++ b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php @@ -32,14 +32,12 @@ public function __construct( public function isDead(VarTagValueNode $varTagValueNode, Property|ClassConst|Expression $node): bool { - if (! $node instanceof Expression) { - if (! $node->type instanceof Node) { - return false; - } + if (! $node instanceof Expression && ! $node->type instanceof Node) { + return false; + } - if ($varTagValueNode->description !== '') { - return false; - } + if ($varTagValueNode->description !== '') { + return false; } $targetNode = $node instanceof Expression From 46e986c4124d0b2ad9ca28d749b0f9b56ad6df35 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 1 Feb 2026 08:48:35 +0700 Subject: [PATCH 05/10] setup Expr --- rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php index 7d22730fea4..bd3331025a4 100644 --- a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php +++ b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php @@ -40,15 +40,12 @@ public function isDead(VarTagValueNode $varTagValueNode, Property|ClassConst|Exp return false; } + /** @var Expr $targetNode */ $targetNode = $node instanceof Expression ? $node->expr : $node->type; if ($node instanceof Expression) { - if (! $targetNode instanceof Expr) { - return false; - } - $varType = $this->nodeTypeResolver->getType($targetNode); $nativeType = $this->nodeTypeResolver->getNativeType($targetNode); From a62b354b3de33e31ef0777649ffbc3f583a783e3 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 1 Feb 2026 20:39:20 +0700 Subject: [PATCH 06/10] get scope from Expression --- .../PhpDoc/DeadVarTagValueNodeAnalyzer.php | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php index bd3331025a4..0eafc0afe87 100644 --- a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php +++ b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php @@ -6,6 +6,7 @@ use PhpParser\Node; use PhpParser\Node\Expr; +use PhpParser\Node\Expr\Assign; use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Property; @@ -16,8 +17,8 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use Rector\DeadCode\PhpDoc\Guard\TemplateTypeRemovalGuard; -use Rector\NodeTypeResolver\NodeTypeResolver; use Rector\NodeTypeResolver\TypeComparator\TypeComparator; +use Rector\PHPStan\ScopeFetcher; use Rector\StaticTypeMapper\StaticTypeMapper; final readonly class DeadVarTagValueNodeAnalyzer @@ -26,7 +27,6 @@ public function __construct( private TypeComparator $typeComparator, private StaticTypeMapper $staticTypeMapper, private TemplateTypeRemovalGuard $templateTypeRemovalGuard, - private NodeTypeResolver $nodeTypeResolver ) { } @@ -40,14 +40,29 @@ public function isDead(VarTagValueNode $varTagValueNode, Property|ClassConst|Exp return false; } - /** @var Expr $targetNode */ - $targetNode = $node instanceof Expression - ? $node->expr - : $node->type; + $targetNode = null; + + if ($node instanceof Expression && $node->expr instanceof Assign) { + $targetNode = $node->expr->expr; + } elseif ($node instanceof Property || $node instanceof ClassConst) { + $targetNode = $node->type; + } + + // allow Identifier, ComplexType, and Name on Property and ClassConst + if (! $targetNode instanceof Node) { + return false; + } if ($node instanceof Expression) { - $varType = $this->nodeTypeResolver->getType($targetNode); - $nativeType = $this->nodeTypeResolver->getNativeType($targetNode); + $scope = ScopeFetcher::fetch($node); + + // only allow Expr on assign expr + if (! $targetNode instanceof Expr) { + return false; + } + + $varType = $scope->getType($targetNode); + $nativeType = $scope->getNativeType($targetNode); if (! $varType->equals($nativeType)) { return false; From c6e579e9d27aeb590b2b84a2c759a9ba0f43a4cd Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 1 Feb 2026 20:40:57 +0700 Subject: [PATCH 07/10] clean up --- rules/DeadCode/Rector/Property/RemoveUselessVarTagRector.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/rules/DeadCode/Rector/Property/RemoveUselessVarTagRector.php b/rules/DeadCode/Rector/Property/RemoveUselessVarTagRector.php index 9cd915256b6..1c07d2c5419 100644 --- a/rules/DeadCode/Rector/Property/RemoveUselessVarTagRector.php +++ b/rules/DeadCode/Rector/Property/RemoveUselessVarTagRector.php @@ -5,7 +5,6 @@ namespace Rector\DeadCode\Rector\Property; use PhpParser\Node; -use PhpParser\Node\Expr\Assign; use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Property; @@ -64,10 +63,6 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if ($node instanceof Expression && ! $node->expr instanceof Assign) { - return null; - } - $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); $hasChanged = $this->varTagRemover->removeVarTagIfUseless($phpDocInfo, $node); From d46a785cd07a3186e8e516d94577fc313befcee2 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 1 Feb 2026 20:58:24 +0700 Subject: [PATCH 08/10] use doc type --- .../PhpDoc/DeadVarTagValueNodeAnalyzer.php | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php index 0eafc0afe87..b515facc45b 100644 --- a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php +++ b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php @@ -53,6 +53,15 @@ public function isDead(VarTagValueNode $varTagValueNode, Property|ClassConst|Exp return false; } + if ($varTagValueNode->type instanceof GenericTypeNode) { + return false; + } + + // is strict type superior to doc type? keep strict type only + $propertyType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($targetNode); + $docType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($varTagValueNode->type, $node); + + if ($node instanceof Expression) { $scope = ScopeFetcher::fetch($node); @@ -61,22 +70,12 @@ public function isDead(VarTagValueNode $varTagValueNode, Property|ClassConst|Exp return false; } - $varType = $scope->getType($targetNode); $nativeType = $scope->getNativeType($targetNode); - - if (! $varType->equals($nativeType)) { + if (! $docType->equals($nativeType)) { return false; } } - if ($varTagValueNode->type instanceof GenericTypeNode) { - return false; - } - - // is strict type superior to doc type? keep strict type only - $propertyType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($targetNode); - $docType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($varTagValueNode->type, $node); - if (! $this->templateTypeRemovalGuard->isLegal($docType)) { return false; } From 90745ea029c896b22fd22e95a2f2cb6813e7e788 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 1 Feb 2026 20:58:42 +0700 Subject: [PATCH 09/10] cleanup --- rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php index b515facc45b..6cdfd81d3a8 100644 --- a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php +++ b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php @@ -61,7 +61,6 @@ public function isDead(VarTagValueNode $varTagValueNode, Property|ClassConst|Exp $propertyType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($targetNode); $docType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($varTagValueNode->type, $node); - if ($node instanceof Expression) { $scope = ScopeFetcher::fetch($node); From 4379568abf4ddcaf773845d81e6dabc73065c63f Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 1 Feb 2026 14:00:04 +0000 Subject: [PATCH 10/10] [ci-review] Rector Rectify --- .../Rector/Catch_/CatchExceptionNameMatchingTypeRector.php | 1 - rules/Php55/RegexMatcher.php | 1 - 2 files changed, 2 deletions(-) diff --git a/rules/CodingStyle/Rector/Catch_/CatchExceptionNameMatchingTypeRector.php b/rules/CodingStyle/Rector/Catch_/CatchExceptionNameMatchingTypeRector.php index 6502f7b67a8..7864ddde1ca 100644 --- a/rules/CodingStyle/Rector/Catch_/CatchExceptionNameMatchingTypeRector.php +++ b/rules/CodingStyle/Rector/Catch_/CatchExceptionNameMatchingTypeRector.php @@ -108,7 +108,6 @@ public function refactor(Node $node): ?Node /** @var Variable $catchVar */ $catchVar = $catch->var; - /** @var string $oldVariableName */ $oldVariableName = (string) $this->getName($catchVar); $typeShortName = $this->resolveVariableName($catch->types[0]); diff --git a/rules/Php55/RegexMatcher.php b/rules/Php55/RegexMatcher.php index b53cceda040..b9bf582ca43 100644 --- a/rules/Php55/RegexMatcher.php +++ b/rules/Php55/RegexMatcher.php @@ -41,7 +41,6 @@ public function resolvePatternExpressionWithoutEIfFound(Expr $expr): Concat|Stri default => $delimiter }; - /** @var string $modifiers */ $modifiers = $this->resolveModifiers((string) Strings::after($pattern, $delimiter, -1)); if (! \str_contains($modifiers, 'e')) { return null;